게임 오버 팝업 띄우기

더 이상 블럭을 넣을 수 없을 때 나오는 팝업은 화면 전체를 가려야하기 때문에 root가 아닌 동등한 레벨의 새로운 노드로 넣어줘야 할 필요가 있었다. 이를 구현하기 위해 리액트의 Portals를 사용하기로 하였다.

팝업창 만들기

GameoverModel.js

import { useContext } from 'react'
import ReactDOM from 'react-dom'
import GameModeContext from './context/GameMode'
import GameScoreContext from './context/GameScore'
import './GameoverModal.css'

const GameoverModal = () => {
    const gameMode = useContext(GameModeContext)
    const gameScore = useContext(GameScoreContext)
    const link = document.getElementById('game-over')

    const reStart = () => {
        gameMode.actions.setGamemode(new Number(gameMode.state.gamemode))
        gameScore.actions.setGameScore(0)
        
        link.style.display = 'none'
    }

    return ReactDOM.createPortal(
    <div className="modal">
        <div className="content">
            <h1>Score : {gameScore.state.gameScore}</h1>
            <h3>Game Over</h3>
            <div> 이상 놓을  있는 블럭이 없습니다.</div>
            <button className="btn btn-primary" onClick={reStart}>재시작</button>
        </div>
    </div>
        , link)
}

export default GameoverModal

점수를 보여줌과 동시에 재시작 버튼을 통해 새로운 게임을 시작할 수 있도록 하였다. 점수를 0으로 초기화하고 new Number()를 통해 이전 게임 모드를 다시 설정해줌으로써 Gameboard.js의 블럭 재생성 및 게임판 초기화가 작동하도록 구성하였다. 다만 이 경우, new Number()로 생성된 결과가 Object이기 때문에, 다른 컴포넌트의 비교 부분을 엄격한 비교(===)에서 동등 비교(==)로 바꾸어주었다.
특징은 ReactDOM.createPortal()을 통해 일반적인 컴포넌트 구조에서 벗어나 다른 DOM에 모달창을 삽입한 것. 이를 위해여 index.html에 ‘game-over’를 id로 가지는 div를 하나 구성해두었다. 초기에는 보일 필요 없으므로 css에서 display 속성을 none으로 설정해두었다.

팝업창 띄우기

Gameboard.js

const DisplayBlock = () => {
    ...
    useEffect(() => {
        const allBlockContainer = document.getElementsByClassName('block-container')
        const board = getGameBoard()
        isItOver:
            try {
                for (let i = 0; i < allBlockContainer.length; i++) {
                    const blockContainer = allBlockContainer[i]
                    const block = blockContainer.getAttribute('data-shape').split('/').map((each) => (each.split(',').map((str) => (Number(str)))))
                    const row = block[0].length
                    const col = block.length

                    for (let i = 0; i <= board.length - col; i++) {
                        for (let j = 0; j <= board.length - row; j ++) {
                            if (isCanMerge(board, block, i, j, row, col) === 1) break isItOver
                        }
                    }
                }
                throw new Error('game over')
            } catch(error) {
                document.getElementById('game-over').style.display = 'block'
            }
    }, [blockState])
    ...
}

이전에 작성해둔 게임 오버 판별 로직에서 이제 더 이상 움직일 블럭이 없어 Error를 반환할 경우, catch를 통해 검출하여 만들어둔 모달창의 display속성을 none에서 block으로 변경해준다.

 

모달창이 root가 아닌 제대로 의도한 DOM에 삽입되어 출력되었다.

 

게임 플레이 시에도 정상적으로 출력되며, 재시작 또한 제대로 작동된다.