기본 클래스와 메서드, 필수 기능 작성
Game 클래스 객체 안에서 함수 흐름을 정리해보자.
이 때 몬스터를 확실히 물리치기 전에 리스트에서 제거하면 안 되는 건가 싶을 수 있지만, 어차피 몬스터를 물리치지 못하고 죽으면 재도전 기회 없이 게임이 끝나기 때문에 상관 없다.
전투는 Character
와 Monster
클래스 내의 공격, 방어 메서드로 진행한다. 주의할 점은 두 클래스는 서로 Game
클래스 안에서 간접적인 영향만 끼칠 수 있다는 점이다.
예시 ) 캐릭터가 방어를 한다면, 몬스터의 공격을 줄이는 것이 아니라 그만큼 본인의 체력을 올린다.
물론
Game
메서드에서 영향을 주고 받을 수 있지만 그러면switch
문 안에 너무 긴 코드를 써야 하기 때문에 지양하겠다.
// 공격
void attackMonster(Monster monster){
monster.hp-=atk;
}
// 방어
void defend(int monsterAtk){
hp+=Random().nextInt(def-4)+5;
}
방어를 선택하면 일정량의 체력을 얻는다. 항상 같은 값으로 방어하는 것보다 변동 있는 편이 좋을 것 같아서 본인의 방어력을 기준으로 Random()
을 돌려 나온 값만큼 체력을 추가하도록 구현했다.
문제
방어 최저값을 5로 두었는데, 방어력이 5보다 낮은 캐릭터가 존재할 수 있다. 최대 방어력을 기준으로 해서 일정 퍼센트로 최저치를 보장하고 싶다.
해결
void defend(){ hp+=Random().nextInt(def-(def~/3)+1)+(def~/3); }
나눗셈 결과의 정수값만 반환하는
~/
연산자를 이용해서 다시 작성해보았다. 그런데 이러면 방어력이 3의 배수가 아닌 경우 손해가 있다.
이 게임에서는 방어력을 3배수로 맞추는 게 좋을 것 같다.
문제
플레이어가 자신의 턴에 1이나 2를 입력해서 행동을 선택할 수 있는데, 이외 값을 입력하면 그대로 턴이 넘어간다.
해결
while
문으로 올바르지 않은 값(정수가 아님, 1 또는 2가 아님)을 걸러내는 과정을 추가했다.
여기까지 개발하고 나니 문제가 2개 있었다.
while
문에서 조건이 true
로 되어있었다. while
안에 switch
가 있을 때는 break
를 사용해도 반복문을 탈출할 수 없다. 대신 조건에 boolean
변수를 따로 선언해서 두는 편이 좋다. (수정 완료) // 수정 전 getRandomMonster
Monster getRandomMonster(){
// 몬스터 뽑기
int monsterNum=Random().nextInt(monsterList.length);
// 뽑은 몬스터 저장
Monster monsterPicked=monsterList[monsterNum];
// 뽑은 몬스터 삭제
monsterList.remove(monsterNum);
return monsterPicked;
}
remove
는 배열의 특정 값을 제거, removeAt
은 인덱스를 제거한다.monsterNum
은 인덱스 값을 의미하기 때문에 remove
가 아닌 removeAt
을 쓰는 것이 적절하다. (수정 완료)
▲ 파일 내 데이터
파일 종류는 txt
이고 데이터는 csv
양식으로 적혀 있다.
csv
파일이 아니기 때문에 Dart에 내장되어있는 csv 패키지를 활용할 수는 없을 것이다. 그래서 아래 과정을 거쳐야 한다.
String
으로 읽어온다.split('\n')
으로 쪼갠 문단을 List<String>
에 넣는다.split(',')
으로 각 문단을 한 번 더 쪼개서 List<List<String>>
에 넣는다.Monster
객체를 하나씩 형성해서 List<Monster>
에 넣는다.코드 길이가 길어지는 것 같아서, 혹시 라이브러리나 함수로 간략하게 줄이는 방법이 있을지 찾아보면 좋을 것 같다.
-> transform 함수를 사용할 수도 있지만 우선은 split
만으로 구현했다.
먼저 클래스의 생성자에 파일 객체를 만들고 readAsStringSync()
를 통해 읽은 값을 변수에 저장한다. 이 변수들은 어차피 그대로 쓰지 않고 가공해서 쓸 것이기 때문에 클래스 멤버 변수로 선언해줄 필요는 없다.
(잘못해서 멤버 변수에 한 번, 생성자에 한 번씩 선언 두 번 했다가 테스트가 이상하게 나와서 고민했다.)
// File reading
var characterFile = File('assets/txt/characters.txt');
var monsterFile = File('assets/txt/monsters.txt');
StringcharacterContents = characterFile.readAsStringSync();
String monsterContents = monsterFile.readAsStringSync();
그 다음으로 2~5번 과정을 진행한다.
// (2) 개행 문자로 split
List<String> monsterOnceSplited=monsterContents.split('\n');
List<List<String>> monsterTable=[];
// (3) 쉼표로 split
for(int i=1;i<monsterOnceSplited.length;i++){
monsterTable.add(monsterOnceSplited[i].split(','));
// (4) 정수 형변환
var temp=Monster(monsterTable[i-1][0], int.parse(monsterTable[i-1][1]),
int.parse(monsterTable[i-1][2]));
// (5) List<Monster>에 투입
monsterList.add(temp);
}
파일을 문단별로 잘라서 MonsterOnceSplited
에 저장한 뒤 각 인덱스의 값을 또 쉼표로 잘라서 monsterTable
에 넣는다. 이때 2차원 리스트가 필요해서 이름에 'Table'을 넣었다.
이때 유의할 점은 csv식 구성에서 첫 번째 행은 각 항목의 분류명(name, hp 등...)으로 이루어질 때가 있다는 점이다.
따라서 이 부분을 건너뛰고 monsterTable
의 인덱스[1]부터 시작해야 한다.
분류명이 포함되지 않은 파일이라면 상관 없다.
캐릭터 정보는 몬스터와 달리 하나만 존재하므로 비슷한 과정을 거치되 for 문을 사용할 필요는 없다.
// 기본 형태
print('캐릭터의 이름을 입력하세요.');
String name=stdin.readLineSync().toString();
var game=Game(name);
이제 이 코드에 정규표현식을 적용해서 입력을 제한해보도록 하겠다.
입력 가능한 문자는 영문 대소문자이다. (한글 입력 시 오류 발생)
RegExp
클래스인 reg
객체를 선언한다.hasMatch()
로 사용자의 입력값이 reg
에서 허용한 범위 내에 속하는지 알아본다.이 때 reg
는
RegExp reg=RegExp(r'^[a-zA-Z]+$')
처럼 선언해주었다. 프로그램에 적용하면 문제 없이 작동한다. 테스트에는 없지만 영문 대소문자와 불가능한 문자를 섞어 써도 정상적으로 사용 불가 처리가 된다.
게임이 끝나면 파일에 저장할지 여부를 묻는데, 이를 위해 우선 승패를 가려야 한다.
String fightResult='승리';
if(character.hp<=0&&monsterList.isNotEmpty){
fightResult='패배';
}
else if(character.hp<=0&&monsterList.isEmpty){
fightResult='무승부';
}
기본적으로 게임 결과를 '승리'로 설정해두고 이후 캐릭터의 체력과 잔여 몬스터의 수에 따라 패배나 무승부로 바꾼다.
print('결과를 저장하시겠습니까? [y/n]');
String cmd;
try{
cmd=stdin.readLineSync().toString();}
catch(e){
print('입력이 잘못되었습니다.');
continue;
}
저장 여부를 입력받을 때는 예외 처리를 했고, 이후 switch
문으로 결과를 저장할지 말지 판별한다.
(그런데 지금 보니 예외가 발생하지 않는 상황에서 잘못 선언된 것 같다. 하단 메모에 적어두었다.)
var resultFile = File('assets/txt/result.txt');
String contents='이름: ${character.name}, 남은 체력: ${character.hp},
결과: $fightResult/n';
resultFile.writeAsStringSync(contents);
print('결과를 저장하였습니다.');
File
클래스의 writeAsStringSync
를 이용해서 작성할 수 있었다.
처음 파일 불러오기를 할 때는 헤맸는데, 작성은 오히려 읽기와 비슷한 과정으로 이루어져서 전보다 금방 구현할 수 있었다.
File reading
을 구현했지만, 내일은 강의에서 배운 async
라이브러리의 클래스로 바꿔서 비동기로 다시 구현해볼 예정이다.