javascript - game(2)

김동하·2020년 10월 18일
0

tic-tac-toe

css가 신박하나 따라하기가 너무 힘들어서 일단 킵.. 나중에 다시 css만 보자.

로직

클릭한다 X가 된다. 플레아어가 바뀐다. winning array는 미리 준비하고 거기에 맞는 배열이면 승리한다.

세팅

삼항연사자로 currentTurn을 구한다. placeMark 라는 함수가 있으면 간편하게 class list를 추가할 수 있다. 한 번 클릭을 하면 다음 턴으로 넘어가야 한다. swap 함수로 간단하게 바꿀 수 있다. 일단 클릭할 때 win인지 아닌지 확인해야하니까 win 함수를 기준으로 if문을 쓴다. 이제 win이거나 draw거나 아니거나로 각각 함수를 넣어준다. 먼저 아니거나 그니까 플레이 중일 때는 이제 순서를 바꿔야 한다. circleTurn에 flag 주면 조금 지져분해지니까 전역에 circleTurn을 선언만 하고 circleTurn = !circleTurn으로 해줘도 된다.

이제 자기 턴에 hove하면 미리 그림이 나오는 함수 만들어야 한다. 어떻게 할지 고민하다가 답을 봤는데 생각보다 간단. board에 class는 주는 것. css에 board에 주었으니까 js에 board에 class를 추가하고 hover하면 나타난다. 중요한 거는 함수 처음에 remove로 board에서 class다 제거

답안 방향으로 draw와 win 함수

win 함수가 조금 어려움.. 일단 endGame 함수의 인자는 flase or draw로 준다. 한 플레이어의 cell이 winning array에 있는 배열에 있는 숫자들을 가지고 있는지 보면 되니까 일단 winning array를 다 돈다. win 함수는 true를 리턴한다. 그러므토 win에 currentclass를 준다. 일단 win 함수 내부에서 return 값으로 winning array를 some으로 돈다. click할 때마다 winning array 모든 배열이 리턴된다. 그리고 (2중 배열이므로) 그 배열들 하나씩 다 돌아야 하니까 every로 가자. index를 받아 nodelist에서 해당 div를 찾고 그 div들이 currentClass를 포함하는지 찾으면 된다.

some 하나라도 true면 true, every 모두 true여야 true

막힘 이중배열을 some, every로 찾아서 참값을 리턴하는 건데 처음 some을 하면 모든 배열이 리턴된다. 그리고 그 각 배열에 every를 하면 currenClass가 이 배열에 포함되어있는지 확인하는데 every하고 index하면 각 배열의 0번째만 나온다.

function checkWin(currentClass) {
    return WINNING_ARRAY.some(combination => {
        return combination.every(index => {
            return cellElements[index].classList.contains(currentClass)
        })
    })
}

예를들어 0번째 div을 클릭하면 some을 통해 모든 배열을 돌면서 모든 배열의 0번째가 every의 index로 출력된다. cellElements[index]를 통해 각 div이 나오고 currentTurn이 포함된 것을 찾는다. 0번째 카드에 currentTurn이 포함됐으므로 0번째 칸의 index를 가진 배열만 다음 배열을 출력하는데 (즉, 0번을 칸을 눌렀을 때 다음 경우의 수) 이제 다음 찾을 것이 없으므로 아무것도 출력하지 않는다. 조건을 만족할 때 true를 리턴! true면 endGame은 false. draw면 endGame은 true

isDraw 함수엔 winning array를 두 플레이가 포함하지 못하게 된 것이다. return으로 모든 div nodelist를 가져와 every를 한다. 여기서 spread 연산자를 사용해야 한다. return으로 cell이 x나 o를 가지고 있는지 확인한다.

const arr = ['a', 'b', 'c']
console.log(arr) // ['a', 'b', 'c']
console.log(...arr) // 'a' 'b' 'c'

flogger

로직

setTimeout으로 자동차, 통나무가 움직이고 지정된 div에 닿으면 사망한다. 목적기에 닿아야 승리. 일단 html과 css로 다 지정해둔다. css class로 지정한 car left, car right, log, left, log right를 가져온다. board의 width도 필요하고 현재 index(개구리 시작점) 그리고 timerId 변수 있어야 한다.

시작

개구리 움직이기

개구리 렌더하고 개구리 움직일 수 있게 해준다. 중요한 건 함수가 실행될 때마다 현재 인덱스에서 개구리 클래스 지워주기. switch문으로 키보드를 정한다 option은 e.keyCode. 동시에 한계를 정해준다.

이제 함수는 총 3가지로 나뉘는데 car,log들의 index를 재정의하는 것, car,log를 자동으로 움직이게 하는 것, 개구리가 car가 아닌 div, 그리고 log에서 움직일 수 있게 하는 것이다. 추가적으로 win, lose도 생각한다.

자동차 오른쪽 왼쪽 이동

차들이 이동한다. div들이 매 초에 따라 이동해야 하니까 일단 car의 클래스를 변화시킨다. moveLeftCar에 하나의 car div이 필요한데 이를 위해서 autoMoveCar를 만든다. 각 div들을 통일적으로 움직여야 하니까 movePiece 함수를 만들고 autoMoveCar를 넣는다. autoMoveCar는 car div들을 forEach한다. 즉, autoMoveCar forEach로 moveCarLeft를 실행하는 것.

auto 함수에서 forEach로 move Car Left를 한다. move Car Left이제 switch를 한다. 각 c1,c2,c3 클래스가 있으니까 맞는 클래스가 각기 다른 방향으로 이동할 것 그러니까 옵션은 true다. 즉, c1,c2,c3케이스 별로 클래스를 지우고 더해주면 된다. 이제 timerId에 setInterval로 시간을 정하면 움직이는 것을 확인할 수 있다. car right, log left, log right도 같은 로직

이제 카, 강물에 부딪히면 죽고 도로와 통나무에서 갈 수 있는 로직을 만들어야 한다. 간단하게 카 파트는 카에 부딪히면 죽고 로그 파트는 로그 이외는 죽게 하면 된다. 일단 win, lose함수를 만들어서 개구리가 움직일 때마다 체크할 수 있게 무브 개구리 함수 안에 넣는다.

복잡하니까 먼저 죽는 것부터. squares[curretIndex]로 차와 물이 있는 div에 가면 게임오버. frog클래스 지우고 다 멈춤.

로그 먼저. 로그 위에 있으면 가만히 있어도 로그를 따라서 내려가야함. index를 어떻게 찾을까.

만약 currentIndex가 통나무 포함 -> if로 통나무 3개 중 하나면 클래스 지우고 currentIndex += 1하고 다시 add로 했는데 그러면 이상함.. 왜 그런지 모르겠는데 뭔가 이상함. 개구리가 올라타면 통나무가 하나 사라짐. 되긴 하는데 어색.

그냥 left log, right log가 있는 row 전체를 if문으로 넣는다. 어차피 물로 가면 죽으니까. 이 부분이 약간 헷갈린다.

space-invaders

로직

invader 는 setTnterval에 따라 +1을 하다가 벽 끝에서 +width를 하고 다시 -1을 하면서 currnet index를 바꾼다. 우주선은 키보드로 움직이고 스페이스바를 누르면 광선이 나오는데 직선으로 가서 인베이더에 닿으면 인베이더는 색이 잠시 바뀌고 사라진다. 먼저 인베이더와 우주선 렌더. 인베이더가 담긴 배열은 하드 코딩으로 하고 forEach로 class 추가.

시작

인베이더들을 움직여야 하니까 invaderId 선언하고 moveInvader 만들고 setInterval에 담아서 invaderId에 할당한다. 그리고 moveInvader 함수 안에 왼쪽, 오른쪽 한계를 정해주고 만약 부딪히면 방향 바꾸는 것을 해준다. 하드코딩으로 만들었던 invader 배열을 이용한다. 왼쪽이나 오른쪽에 부딪히면 direction을 바꿔주면 된다.

벽에 부딪히면 direction = width가 된다. 그리고 이제 왼쪽 / 오른쪽 정하는 건 if(leftEdge)로 정하면 된다. boolean이기 때문에.

거대한 인베이더 무리들을 어떻게 움직일까. 만약 인베이더 하나면 remove -> 새로운 index 주고 -> 다시 add 하면 되는데 여러개니까 for 문을 돌린다. squares에서 인베이더 배열에 i를 주면 index가 나오니까 다시 인덱스로 인베이더만 찾는다.

(헷갈림) 인베이더 배열[i]가 이제 currentInvader므로 인베이더 배열[i]에 direction 을 추가한다.

키보드를 누르면 레이저가 나가야 한다. 레이저는 인베이더 setInterval과 독립적이다. 일단 swtich를 만든다. 키보드를 누르면 레이저가 -width만큼 나간다. 그리고 invader에 가면 bomb 클래스로 바뀜. clearInterval로 레이저 멈추고 bomb클래스도 setTimeOut으로 지워줌. 이제 레이저가 width 밖으로 나갈 때 if해줘야 한다. clearinterval로 laserId 를 멈춘다. 그리고 setTimeout으로 laserindex도 remove한다.

총 맞은 에일리언 배열 만들어서 추가해야한다. 이제 총 맞은(currentLaserIndex)를 인베이더 배열에서 어떻게 찾을까. 에일리언이 렌더될 때마다 다시 생성되니까 무브할 때 만약 죽은 에일리언이면 만들지 않는다. 죽은 에일리언 배열에 있는 인덱스를 이용해서 includes(i)면 add 하지 않는다.

(헷갈림) moverLaser 함수는 shoot 밖에 안에?? (왜인지모름) 함수 밖에 선언하면 우주선이 움직일 때마다 레이저도 발사되고 원래 나가던 레이저는 멈춤. 아마 처음에 레이저 인덱스를 우주선 인덱스와 같게 설정해서 그런듯. 그리고 함수 밖에 선언하면 laserIndex가 전역이 되어서 문제가 생기나?

(헷갈림) 레이저가 인베이더 닿았을 때 레이저 인덱스는 곧 인베이더 배열에 저장된 인베이더의 인덱스인데 인덱스로 나오니까 그 친구만 쏙 고르기 힘듦. 이때 indexOf를 사용하면 그 친구만 나옴

// laserIndex = 47
invaders = [0,1,3,47]
 
invaders.indexOf(laserIndex) // 4 

명심할 것 --> indexOf, includes 의 활용

pac-man

로직

나쁜놈들은 랜덤 함수에 의해 나온 숫자만큼으로 움직인다. 당연히 벽은 못 가고 그냥 바보처럼 움직인다. 주안공은 콩알먹으면서 점수 만들고 아이템을 먹으면 나쁜넘들을 죽일 수 있다. Ghost는 class로 만들고 개구리 건너기 처럼 각 고스트들을 호출하면서 각기 다르게 움직이도록한다.

시작

미리 준비한 layout 배열을 이용해 기본 배경을 만든다. 팩맨 움직이는 건 위에랑 똑같고 하나 추가해야 하면 순간이동인데 특정 index에 들어가면 다른 index로 나오는 거다 그거만 수정해주면 된다.

이제 class로 고스트를 만들자. 고스트는 각각 다른 속도와 특성을 가질 것이기 때문에 클래스로 객체를 생성하는 게 편하다. 고스트의 constructor를 생각해보면 다른 speed와 다른 태어나느 장소, 다른 className이다. 이제 ghost배열을 만들어서 Ghost 생성자를 통해 만든 객체를 준다. 그리고 forEach로 랜더한다. 이제 move랜더 해봅시다.

4가지 경우를 배열에 넣고 랜덤넘버로 direction을 설정한다. 그리고 ghost 객체에서 타이머를 사용해서 interval 한다. 스피드는 ghost.speed로 해주면 각기 다른 4개의 함수가 탄생하는 경이로운 순간이다. classList.add/remove의 인자로 여러 개를 줄 수 있다. 문제는 벽에 부딪히명 멈추는데 else로 다시 디렉션을 설정한다.

이제 팩맨이 맛있는 걸 먹었을 때를 구현하자. 두 개 함수를 만든다. petdot과 powerPellet을 만들어서 다른 경우를 설정한다. petdot은 간단하고 powerpellet을 먹으면 고스트들이 scared로 변하는데 forEach로 ghost.isScared = true되고 setTimeout으로 unScared가 10초 후 실행되게 한다. 고스트 함수 안에 isScard가 true일 때 설정한다. 그리고 시간이 지나면 다시 원래대로 돌아와야 하니까 unScared 함수 만들어서 forEach로 되돌린다.

그리고 scared일 때 팩맨한테 잡히면 죽는 것도 고스트 무브 안에서 if로 정해준다. 그리고 curretnIndex를 startindex로 초기화.

업그레이드 버전

멍청한 고스트에게 힘을 줘서 최단거리로 팩맨을 잡으러 갈 수 있게 설정하자.

필요한 것

일단 blinky란 녀석만 강해졌다고 가정하고 이 녀석의 좌표와 팩맨의 좌표가 필요하다. 좌표를 구할 함수를 만드는데 리턴값으로 좌표로 x, y를 담은 배열을 리턴한다. 이제 moveBlink 함수를 만들어서 똑같이 랜덤으로 탄생한 direction을 정하고 timerId를 만든다. 그리고 setInterval을 화끈하게 실행한다.

무빙 로직

x는 index % width y는 Math.floor(index / width)

벽이 아니고 다른 고스트가 없다면 어떻게 움직이냐면 최단거리로 간다. 그러면 3개가 필요하다 현재 blinky 좌표, 팩맨 좌표, 새로운 blinky 좌표. getCoordinates 함수에서 나온 값은 배열이니까 변수도 배열을 받아야 하는데 객체가 아니니까(?) 변수에 [x, y] = getCoordinates(index)를 한다. 그러면 변수에 좌표를 배열로 받을 수 있다!

이렇게 두 배열(blinky xy좌표, 팩맨 xy좌표)을 받고 현재 blinky 좌표에 direction을 더한 인덱스 값을 좌표로 하는 새로운 블린키 좌표값도 구한다. 이제부터가 살짝(아주 많이) 헷갈리는데 두 함수가 필요하다. x,y가 팩맨과 가까워졌는지 return으로 boolean 하는 함수다. 먼저 x부터 보자. 새로운 좌표 x에서 팩맨 x를 뺀 값이 전 좌표에서 뺸 값보다 크면 true 아니라면 false다. y도 똑같이 한다. 그리고 if문을 돌려서 둘 중 하나라도 맞으면 direction 을 추가하고 blicky 클래스를 더한다. 아니라면 클래스를 먼저 더하고 direction을 재설정한다.

candy-crusch

로직

가로, 세로 3개 혹 4개가 일치하면 없어지고 위에서 새로운 퍼즐들이 내려온다. mouse로 div을 옳겨서 자리를 바꾸고 정해진 한계를 넘어서 바꾸려고 하면 return이다.

시작

보드 만들고 settaribute로 draggle, true해준다. id도 준다. 랜덤으로 이미지를 넣어준다. 이제부터 어렵다. 일단 color가 dragged되고 있는 거랑 바뀐거랑 그 두 가지에 대한 id 총 4개의 변수를 만든다. 이제 squares는 총 6개의 이벤트 리스너가 필요하다. dragstart, dragend, dragover, dragenter, dragleave, drop이다. 각각 함수를 만들어 준다.

drag start는 드래그를 한 순간 한 번만 실행된다. drag end는 놓은 순간 실행된다. drag over는 드래그 한 상태에서 움직이면 계속 실해된다. drag enter는 드래그 한 상태에서 새로운 div으로 넘어가면 발생한다.(추측) drag leave는 말그대로 떠나면 발생. drop은 over를 막아야 발생한다. board 밖에 떨어뜨리면 돌아가면서 end가 발생하고 다른 div위에 떨어뜨리면 drop이 발생한다. 예상했던 것처럼 enter, leaver, over는 필요없다. e.target 찍어보면 end는 떨어뜨린 순간 잡고 있던 div, drop은 떨어진 div의 정보를 보여준다.

신박 -> this를 이렇게 사용하는군...

this 혹은 e.target 으로 바꿀 수가 있는데 일단 e.target으로 간다. drop 하는 순간 이미지가 바뀐다. 하지만 복사가 되니까 drop 하는 순간 전에 가져왔던 div의 이미도 현재로 바꿔야 한다. drop 함수가 실행되는 순간에 바뀌어야 하니까 일단 떨어트린 div의 이미지를 replced 변수에 할당. 함수 안에서 전에 잡아왔던 div를 찾아야 하는데 그러기 위해서는 id가 필요하다. 다시 start로 가서 beingDraggedId를 currentTarget.id로 해주고 drop에서 그 squares[id] 로 해당 div에게 replaced를 준다. 전역 변수 2개와 id 그리고 전체 배열을 이용해 바꾸는 것이다.

이제 drag end에서 떨어지고 나서 동서남북으로 한 칸만 움직인 것인지 체크한다. 아니라면 원상태로 만들어야 한다. beingDraggedId를 이용해서 방향을 결정한다. valid moves배열을 만든다. 그리고 새로운 move 즉, replaced Id가 valid에 포함 includes 됐는지 검사하는 변수를 만든다.

이제 옮기고 나서 replacedId 를 초기화 시켜준다. 그리고 아닐 때를 정해줘야 한다. valid move가 아니면 beingDragged랑 beingReplaced 모두 원래 상태가 되어야 한다. 간단하게 id로 원래 being은 being 이미지를 주면 된다.

짝 맞으면 터트리기

for문으로 돈다. 검사할 배열을 만든다. row로 3개면 i, i+1, i+2 그리고 target img, 즉 하나씩 검사하는 개별 이미지를 정한다. every로 모두 같은 이미지인지 검사한다. every를 통해 나온 인덱스로 전체 스퀘어에서 고르고 그것이 target img와 같은지 확인한다.

헷갈림 -> every 좀 헷갈리는데 추상화 하자면 조건 배열.every(index => squares[index] === 개별 타겟) 이다. for 안에서 돌렸기 때문에 전체 배열을 돈다.

[i, i+1, i+2].every(index => squares[index].style.backgroundImage === squares[i].style.backgroundImage)

이제 짝이 맞으면 조건 배열에 있는 i들을 div으로 가지는 애들 image를 없애야 한다. 그러면 every는 true반환이니까 if 조건으로 해주고 그 안에 조건 배열에 forEach해서 해당 div backgroundImage 를 "" 한다.

빈 칸 채우기

캔디가 터지면 새로운 캔디가 쌓여야 한다. 일단 for문으로 전체를 돌아서 블랭크를 고른다. i+width가 비었는지 확인하면 된다. 그리고 i+width의 이미지에 i의 이미지를 준다. i는 빈 칸이 된다. 쭉 떨어지면 이제 윗줄들이 빈다. 첫줄만 확인해서 첫줄이 비웠으면 채우고 그럼 다음부터는 알아서 아래도 채운다. 첫 줄을 배열에 담고 i들 중 firstRow에 해당하는 i가 있으면 boolean을 뱉는 변수를 만든다. 즉, i+width가 빈칸인 것들 중 i+width를 다 채운 상태에서 위쪽 칸이 비었는지 확인하는 것! 어렵다.. 그리고 i가 비었고 firstrow면 다시 이미지를 만들어서 채운다.

const firstRow = [0, 1, 2, 3, 4, 5, 6, 7]
const isFirstRow = firstRow.includes(i)
if (isFirstRow && squares[i].style.backgroundImage === "") {
}

window에 setInterval을 걸어서 check함수들과 movedown 함수를 실행한다.

profile
프론트엔드 개발

0개의 댓글