class Monster {
constructor(name, hp, attack) {
this.name = name
this.hp = hp
this.attack = attack
}
........
class 부분은 위와 같다.
그리고 바로 이 시점에서, 제출한 과제에 대한 피드백을 받았다.
피드백의 내용을 요약하면 다음과 같다.
우선 참신한 컨셉에 놀랐고,
필수기능과 도전 기능에 해당하는 부분을 모두 성공적으로 구현해내었음.
도박성 기술인 지름길 스킬에 실패 할 경우, 주사위를 표시하여
직관적으로 확인할 수 있는 부분이 좋았음.
아쉬운 점으로, 반복되는 코드가 많다는 점 !
또, 깃 활용 시 커밋 내용을 확실하고 직관적으로 알 수 있게 작성하여
히스토리로써도 관리되게끔 할 수 있으면 좋겠음
전체적으로 과제 결과물이 괜찮았다는 내용이었다 !
어흐흘
헛되지 않았다......
즉각적으로 피드백을 적용해보자면,
제출했던 스테이지 클리어 보상 함수 내용이다.
확실히 초 하드코딩으로 반복되어 지저분해 보이는 부분이다.
반복문을 통해 간소화를 해볼까?
// 불필요한 반복 간소화
await delay(0.2)
console.log(chalk.cyanBright.bold(`${stage === 6 ? `${monster.name} 돌파 !! 보상을 5회 획득합니다.`:` 랜덤 보상을 3회 획득합니다. `} \n`));
for(let i = 0; i < (stage ===6 ? 5 : 3); i++) {
RandomReward(player, stage);
await delay(0.2)
}
for문을 통해 간소화 하고, 추가로 로그 부분은 삼항 연산자를 통해 출력하도록 개선했다.
문제는 다음인데,
일이 커질것 같은 예감이 든다...
이미 코드를 짤 때에도, 이게 최선일까 고민했던 부분이다.
저렇게 코드를 만들었던 이유는,
전투 로그 출력에 딜레이를 주고싶었던 작은 소망에서 시작된 것인데..
그건 결코 간단한 일은 아니었다.
전투 로그는 화면을 초기화 하고 logs.forEach ~~ 가 실행 될 때에 갱신이 되기 때문에,
전투 도중에 logs에 push 한다고 해서 즉각적으로 보여지는게 아니다.
따라서 내가 선택한 방법은 (현재의 내 머리에서 나올 수 있는 유일한 방법..흑흑)
위 처럼 매번 전투 logs가 push 되는 상황마다
console.clear 와 상태창 불러오고, 전투로그를 불러오는
이른바 짜치는 코드를 짜버렸던 것이다.
당연히 해당 코드를 간소할 방법 또한 생각 해 낼 수 없었다.
그렇기 때문에 저 상태로 제출한 것이니까 !!
결국, 그대로 다시 튜터님에게
내가 생각하지 못하고 있었던 부분을 바로 체크해 주셨다.
어....음...... 이 부분을 말로 설명하려니까 굉장히 어려운데,
결국 그렇게 보이기만 하면 된다??
기존에 작성한 방법은 logs 가 push 될 때마다 초기화 해서 그때 그때 출력하는 방법이고,
튜너님은 결국 그런 (짜치는) 과정 필요 없이 기존에 있던 메서드 logs.forEach ~~에서
새로 추가된 logs 에 해당하는 log만 딜레이로 출력되게 하면 보이는 건 똑같다는 말을 해주셨다.
헉 !!!!
사실, 생각지도 못한 부분이었기 때문에, 처음 설명을 들었을땐 (???????????) 상태였다...
결국
이 녀석이 로그를 뱉어낼 때, 새로 추가된 log를 뱉을 때만 딜레이를 주게 하면 된다
이 말입니다 !!
그렇게 하면 초기 셋팅 처럼, 전투로그를 턴마다 한번 만 쏟아내게 되지만
눈에 보이게 되는건
로그 초기화(순식간이라 안보임)-> 기존 로그 한번에 쫙( 이전 log와 그대로인 것 처럼 보임)->
추가된 log 는 딜레이되어 출력 (새로 등장하는것 처럼 보임)
이렇게 되는것이다.
왜 이 생각을 못했을까?
바로 시작해보자.
// 화면 초기화
function displayReset(stage, player, monster, logs) {
console.clear();
displayStatus(stage, player, monster);
while (logs.length > 14) { logs.shift(); }
logs.forEach((log) => console.log(log));
}
이런 흉물은 더 이상 필요 없게 되었다.
시작은
솔직히, 뭐라고 하는지 잘 모르겠는 위의 친구를 평범한 함수로 바꾸는것 부터 시작이다.
// **NEW** 전투로그 출력 함수
async function addLogs (logs, oldLogs) {
for (let log of oldLogs) {
console.log(log)
}
for (let log of logs) {
await delay(0.2)
console.log(log)
oldLogs.push(log)
}
await delay(0.3);
}
1) 플레이어의 공격력과 최대배율에 따른 난수 데미지 / 몬스터의 공격력 또한 1.4배의 난수를 가짐
2) 축지법 ! 스킬은 일종의 카운터 스킬로, 성공확률이 존재
3) 필살! 암벽등반! 스킬은 연속공격 스킬로, 5~10회 랜덤횟수를 공격
4) 지름길 개척 스킬은 도망 스킬로, 10%의 확률(고정)로 성공함
4) 스테이지 클리어 보상 5가지중 3개를 랜덤 획득하며 (중복 가능)
각각의 보상은 0.8배~2.0배의 난수를 가짐
5) 몬스터의 name은, 따로 설정한 배열에서 랜덤으로 선택함
6) 몬스터의 패턴(체력 소모)는 확률에 따라 상이하게 발생 (빗나감, 약화된 공격, 평범한 공격, 강력한 공격, 아주 강력한 공격)
2. 복잡한 행동 패턴
1) 필살! 암벽등반 ! 기술은 확률로직 외에도, 충전을 3개를 소모해야 사용할 수 있으며,
충전은 stage clear 랜덤 보상으로만 획득할 수 있습니다. (원활한 Test를 위해 3충전을 가지고 시작합니다.)
추가로, 확률 로직과 delay 텍스트 출력 등등 약간의 연출이 추가되어 있습니다.
2) 지름길 개척 기술은, 성공시 연출효과...를 출력합니다.
3) 위의 연출 효과를 응용하여 함수로 만들었고, 시작 시(오프닝), 엔딩, 패배 화면에 적용했습니다.
### 게임 기획 분석**
**컨셉 잡기**
1. 등산을 컨셉으로 제작한 게임입니다.
플레이어 : 등산가
플레이어의 체력 : 체력
플레이어의 공격력 : 등산력(...)
몬스터 : 등산로
몬스터의 체력 : 남은 거리
몬스터의 공격력 : 체력 소모량
2. 컨셉만 약간 특이할 뿐, 구현내용은 크게 다르지 않습니다.
다만 !
체력이 0이 된 몬스터는 공격을 못하겠지만,
남은거리가 0이되는 등반은 체력을 소모합니다.
때문에, 마지막 일격에도 반격을 받는 느낌이 되었고 의도된 구현입니다. (더블 KO 는 패배 처리)
**전투 기획**
1. 플레이어= 등산가 의 행동 선택지
1. 등반하기 : 100% 확률로 등산력에 해당하는 거리를 등반. 이후 체력 소모(상대 턴) 발생합니다.
2. 축지법 ! : 기본35% 확률로 등산력에 해당하는 거리를 등반. 성공시 체력을 소모하지 않습니다. 실패시 체력소모만 발생합니다.
35% 확률로 굉장히 저조한 성능이지만, 이후 랜덤 보상을 통해 확률이 증가할 수 있습니다.
3. 필살! 암벽등반! : 최대 충전3, 초기 충전3, 사용시 충전 3 소모.
등산력에 해당하는 거리를 5~10회 랜덤횟수 등반합니다. 충전은 랜덤 보상에서 획득 가능합니다.
4. 지름길 개척 : 도망에 해당하는 기술로, 낮은 확률(10%) 로 성공하여 스테이지 클리어로 판정하고 보상 또한 정상적으로 획득합니다.
=> 이는 사실상 즉사기에 해당하기 때문에, 성공시 등산로의 남은 거리를 0으로 만들고 체력 소모 턴이 발생하지 않습니다.
실패시 체력 소모만 발생.
2. 플레이어의 능력치
1. 체력 : 0이 되면 게임오버가 되며, 스테이지 클리어시 확정적으로 일정량을 회복합니다.
추가적으로 랜덤 보상에서도 획득할 수 있습니다.
2. 등산력 : 공격력에 해당하며, 등산력 ~ 등산력 x (1+최대배율/100) 의 난수를 가집니다.
확정 증가는 없으며, 랜덤 보상을 통해 상승할 수 있습니다.
3. 최대배율 : 최대 공격력 배율에 해당하며, 계산은 위와 같습니다. 기본 20의 최대배율로 시작합니다. (1.2배)
확정 증가는 없으며, 랜덤 보상을 통해 상승할 수 있습니다.
4. 축지법 확률 : 축지법 ! 스킬의 성공확률이며, 35%로 시작합니다.
확정 증가는 없으며, 랜덤 보상을 통해 상승할 수 있습니다.
최대 75% 의 상한을 가지고 있습니다.
2. 몬스터 = 등산로의 패턴
1. 엄밀히 따지면, 몬스터가 완전한 턴을 받는 형식의 구현은 아니고,
플레이어의 행동에 따라 몬스터의 공격 이벤트가 발생하는 방식입니다.
2. 체력을 소모할 때, 등산로(monster) 의 공격력을 참조하며, 등산로의 공격력은 1 배 ~ 1.4배 난수를 갖습니다.
3. 추가적으로 확률에 따라 체력 소모가 0.6배 , 1배, 1.3배, 1.6배, 2배, 체력소모 0 이 되는 이벤트가 발생합니다.
확률은 각각 상이합니다. (낮은 확률로 컨셉이 파괴되는 텍스트가 출력됩니다. )
4. 5층에서는 중간 BOSS 를, 10층에서는 최종BOSS 를 조우합니다.
BOSS 는 전용 name 을 가지고, 별도의 체력과 공격력을 가집니다.
5. BOSS 이외의 몬스터는 지정된 값(배열) 중 랜덤한 name을 갖습니다.
(name중 일부는 컨셉이 파괴되는것도 있습니다. )
6. 등산로(monster)는 stage 마다 증가한 체력과 공격력을 갖습니다.
7. 등산로(monster)의 남은거리(hp)가 0 이 될 경우, stage가 클리어 되며,
랜덤 보상 3가지를 획득하고 다음 stage 로 넘어갑니다.
8. 중간 BOSS 클리어시엔 랜덤 5가지 보상을 획득하며,
최종 BOSS 클리어시엔 엔딩이 출력됩니다.
3. 랜덤 보상
1. stage 를 클리어하여 랜덤한 보상 3개를 획득합니다. (중간BOSS 클리어 시 5개)
2. 체력 회복 / 등산력 / 최대 배율 / 필살 충전 / 축지법 확률
종류는 총 5가지 이며 각각 동일한 확률을 가집니다.
3. 체력 회복, 등산력, 최대배율은 stage 에 따라 기본 보상량이 증가합니다.
또한 0.8배~ 2.0배의 난수가 적용됩니다.
4. 축지법 확률은 stage 에 상관없이 고정된 보상량을 갖지만,
마찬가지로 0.8배~ 2.0배 난수가 적용됩니다.
5. 모든 보상은 현재 수치 -> 증가된 수치 , 증가량을 표기합니다.
**추가적인 요소**
1. 전투로그가 14줄을 넘어갈 경우 먼저 출력되었던 로그가 제거되어 14줄이 유지됩니다.
2. 지름길 개척 스킬 성공 시, 연출 효과를 추가했습니다.
반복문을 통해, 콘솔 클리어와 문자열 출력을 반복합니다.
3. 위의 연출을 함수화 시켜서 GAME-OVER, GAME-CLAER 에도 적용 했습니다.
4. setTimeout 을 사용하는 delay 함수를 사용하여, 로그 출력간에 delay를 적용했습니다.
readme 에는 기획과 구현기능에 관한 내용을 담아보았다.
과제 마무리에 관한 내용은 과제 제출날짜인 내일 TIL로 작성할 계획이다.
오늘은?
오랜만에 알고리즘 풀이 시간을 가져 보았다.
물론, 과제 진행중에도 알고리즘 풀이시간을 갖긴 했지만,
TIL로 작성하기엔 시간도 양도 부족했었다.
그럼 시작해볼까?
뭐지 언어 영역인가...
시작부터 문제 설명만 10줄이 넘어가는 알고리즘 문제를 만났다.
쉽게 설명하면....
[1,3,4,6] 에서
index 0 = 1 개
index 1 = 3 개
index 2 = 4 개
index 3 = 6 개
의 숫자가 존재하며 이를 풀어서 나열하면 0 111 2222 333333 이 되고,
이 목록을 0을 기준으로 반으로 쪼개서 1223330333221 을 출력해야하는 상황이다.
이 때, 1은 3개 이기 때문에, 반으로 쪼개고 남은 1개는 버려지게 된다.
가장 쉽고 직관적인 방법으로 접근해보자.
1.
index 1~끝까지, 해당 index의 value의 절반만큼(소수점 버림) index를 빈 문자열 answer에 추가
answer = "122333"
2.
index 0 을 value 만큼 추가. ( 문제에서 0은 1개로 고정이라고 했기 때문에 arr += 0 을 해도 같다)
3.
answer = "1223330"
그 후, 1에 사용한 방법을 역순으로 적용
index의 끝에서~1 까지, 해당 index의 value의 절반만큼 어쩌구 저쩌구
answer = "1223330333221"
벌써부터 O(n^2 *2 ) 급의 코드가 만들어질것 같은 예감이 든다.
당장 해보자 !
function solution(food) {
var answer = '';
for(let i = 1; i < food.length; i++) {
for(let j = 0; j < food[i]/2; j++ ){
answer += i
}
}
return answer;
}
console.log(solution([1,3,4,6])) // "1122333" 실패 !
// 홀수value 를 가진 경우 +1회가 더 출력되는 모습
계획중 1에 해당하는 부분 이다.
위 처럼 코드를 짰더니, food[i] /2 에 홀수가 들어갈 경우 절반 +1 회 출력이 되어버린다.
function solution(food) {
var answer = '';
for(let i = 1; i < food.length; i++) {
for(let j = 1; j <= food[i]/2; j++ ){
answer += i
}
}
return answer;
}
console.log(solution([1,3,4,6])) // "122333"
소수점을 고려하지 않도록 위처럼 수정해 주었다.
그 후, 0을 추가하고 다시 뒤에서 부터 반복 !
function solution(food) {
var answer = '';
for(let i = 1; i < food.length; i++) {
for(let j = 1; j <= food[i]/2; j++ ){
answer += i
}
}
answer += 0
for(let i = food.length; i > 0 ; i--) {
for( j = 1; j <= food[i]/2; j++) {
answer +=i
}
}
return answer;
}
계획한 그대로 코드가 되었다...
잘 작동하는 모습...
여기서 끝낸다면 취업의 길은 O=(food.length-1 ^2 *2) 만큼 멀어졌다고 볼 수 있다.
취업과 가까워 지기 위해 검색을 해본 결과, repeat() 함수를 사용하는것이 좋아보였다.
str.repeat(n) 메서드는, str 을 n번 만큼 반복 출력하는 메서드로,
이 알고리즘 문제에 특화된 메서드라고 볼 수 있다.
심지어 소수점을 과감하게 버리는 대담함 까지 가지고있다.
이를 이용해서 2중 반복문을 1중 반복문으로 간소화 할 수 있다.
function solution(food) {
var answer = '';
for(let i = 1; i < food.length; i++) {
answer += i.repeat(food[i]/2);
}
answer += 0
for(let i = food.length; i > 0 ; i--) {
answer += i.repeat(food[i]/2);
}
return answer;
}
에러 발생
i.repeat() is not function
앗... i는 문자열이 아니었다. 어차피 참고용 i이기 때문에 문자열로 전환해주자.
function solution(food) {
var answer = '';
for(let i = 1; i < food.length; i++) {
answer += i.toString().repeat(food[i]/2);
}
answer += 0
for(let i = food.length; i > 0 ; i--) {
answer += i.toString().repeat(food[i]/2);
}
return answer;
}
보다 깔끔하고, 2중 for문을 사용하지 않게되어 효율적이게 되었다.
작동도 잘 된다.
function solution(food) {
var answer = [];
for(let i = 1; i < food.length; i++) {
answer.push(i.toString().repeat(food[i]/2));
}
return answer.join("")+"0"+answer.reverse().join("")
}
억지로 줄인다면 위처럼 배열로 제작한 뒤 조립하여 return 할 수도 있다.
코드가 줄어든 것 같지만 메서드도 많이 쓰고 배열이 문자열 보다 많은 메모리를 쓰기 때문에
효율적인 코드는 아니라고 할 수 있다.
다른 사람이 작성한 코드
헉... 위에 작성한 나의 코드와 굉장히 유사한 형태의 코드가 눈에 띄었다.
다만, 문자열 방식을 그대로 사용하고
리턴 과정에서 [...res].reverse().join('') 이라는 엄청난 기술로
즉각적으로 문자열을 뒤집어서 출력하는 방법을 사용했다.
주목할 점은
[...res] 인데, 아주 간단한 방법으로 문자열 쪼개서 배열로 만드는 최신 기술(?) 이 존재했다.