[로데시] 개발
[대학전쟁3] 양면빙고 웹 개발 07 - timer, highlight, restart 본문
반응형

0️⃣ 지난 번까지..
지난 주에 양면빙고를 끝냈어야 했는데... 너무 오랜만에 글을 써본다... ☰ 양면빙고 (double-sided-bingo) 개발 과정
1️⃣ timer
🌟 App.js 코드const TURN_LIMIT = 60;
const [timeLeft, setTimeLeft] = useState(TURN_LIMIT);
const isDanger = timeLeft <= 5;
ㆍ TURN_LIMIT - 한 turn당 제한 시간을 60으로(초 단위) 설정한다. (현재는 test 단계기 때문에 10으로 설정했다)
const endTurnNoCount = () => {
if (!winner) {
setCurrentTurn(prev => prev === "me" ? "opponent" : "me");
}
};
현재는 다른 player의 turn으로 넘어가면 setTurnCount이 1 증가하게 되어있다. 하지만 시간 초과로 turn이 넘어갈 경우에는 setTurnCount이 증가하면 안 된다. (착수하지 않았기 때문이다.) 따라서 turn이 넘어가지만 setTurnCount은 증가하지 않는 함수, endTurnNoCount를 추가로 작성한다.
useEffect(() => {
setTimeLeft(TURN_LIMIT);
}, [currentTurn]);
턴 바뀔 때마다 타이머 TURN_LIMIT가 reset 되도록 코드를 작성한다.
useEffect(() => {
if (winner) return;
const timer = setInterval(() => {
setTimeLeft(prev => {
if (prev <= 1) {
endTurnNoCount();
return TURN_LIMIT;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, [currentTurn, winner]);
currentTurn이 변경될 때마다 타이머를 실행하기 위해 useEffect가 실행 되도록 한다. (단, winner가 결정되면 타이머를 실행하지 않도록 바로 return 한다.) setInterval을 통해 1초마다 setTimeLeft를 호출하여 남은 시간을 1씩 감소시킨다. 남은 시간이 1 이하가 되면 endTurnNoCount 함수를 호출해 setTurnCount이 증가 없이 턴을 종료하고 다음 player의 턴을 위해 시간을 TURN_LIMIT으로 초기화한다.
<div className={`timer ${isDanger ? "danger" : ""}`}>
<span className="timer-icon">?</span>
<span className="timer-text">Time Left</span>
<span className="timer-value">{timeLeft}s</span>
</div>
남은 시간을 화면 상단에 표시한다. danger 클래스를 추가해 isDanger의 값에 따라 타이머의 시각적 효과를 달리 한다.
.timer {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
border-radius: 10px;
background: #1f2937;
color: #ffffff;
font-weight: 600;
margin-left: 300px;
}
.timer-icon {
font-size: 18px;
}
.timer-value {
font-size: 18px;
font-weight: 700;
color: #22c55e;
}
.timer.danger {
background: #dc2626;
animation: pulse 1s infinite;
}
.timer.danger .timer-value {
color: #fca5a5;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.timer-container.danger {
animation: pulse 1s infinite;
}
남은 시간이 5 초과일 경우에는 흰색, 초록색 text와 어두운 계열 배경으로 깔끔한 timer 버튼 UI를 구성한다. 남은 시간이 5 이하일 경우 글자색과 배경색이 각각 #fca5a5 , #dc2626 으로 변한다. 또한 pulse 애니메이션을 적용해 타이머의 크기가 반복적으로 커졌다 작아지도록 설정하여 시간이 얼마 남지 않았음을 시각적으로 강조한다.
2️⃣ 빙고 완료 시, 빙고 highlight
🌟 App.js 코드const [winningLine, setWinningLine] = useState([]);
완성된 빙고 line을 저장할 const를 작성한다.
// 빙고 기준 set
const lines = [
// 행 빙고
[0,1,2,3], [4,5,6,7], [8,9,10,11], [12,13,14,15],
// 열 빙고
[0,4,8,12], [1,5,9,13], [2,6,10,14], [3,7,11,15],
// 대각선 빙고
[0,5,10,15], [3,6,9,12],
];
const checkBingo = (boardTiles, targetColor) => {
const winningLine = lines.find((line) =>
line.every((idx) => {
const tile = boardTiles[idx];
if (!tile) return false;
return getVisibleColor(tile) === targetColor;
})
);
return winningLine || null;
};
빙고가 완성이 될 경우 해당 빙고를 시각적으로 표시하기 위해 checkBingo 함수 코드를 재작성한다. 빙고의 완성 여부를 판별함과 동시에 완성된 빙고 line 배열을 반환한다. 미리 정의한 'lines' 배열을 순회하며 빙고 조건에 만족하는 첫 번째 line을 반환한다.
useEffect(() => {
// 1. 내 빙고 체크
const myBingoLine = checkBingo(boardTiles, players.me.mainColor);
if (myBingoLine) {
setWinner("me");
setWinningLine(myBingoLine);
return;
}
// 2. 상대 빙고 체크
const oppBingoLine = checkBingo(boardTiles, players.opponent.mainColor);
if (oppBingoLine) {
setWinner("opponent");
setWinningLine(oppBingoLine);
}
}, [boardTiles]);
빙고판(bingoboard)이 바뀔 때마다 useEffect를 통해 checkBingo 호출한다. checkBingo를 통해 빙고가 완성됨을 확인하면 완성된 빙고 line을 setWinningLine에 저장한다.
<BingoBoard winningLine={winningLine} />
완성된 빙고 line을 BingoBoard 에 전달해준다.
const BingoCell = ({ winningLine= [] })
const isBingo = winningLine.includes(cellId);
isBingo={isBingo}
ㆍ winningLine= [] - winningLine을 props로 받는다.
const BingoCell = ({ isBingo })
${isBingo ? "bingo-highlight" : ""}
ㆍ isBingo - props로 받는다.
.bingo-highlight {
border: 3px solid #11ff00 !important;
box-shadow: 0 0 15px #11ff00;
transform: scale(1.05);
z-index: 10;
transition: all 0.3s ease;
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
빙고가 완성된 경우 border와 box-shadow를 사용해 완성된 line을 시각적으로 강조하고 pulse 애니메이션으로 highlight의 크기가 반복적으로 변하도록 스타일을 적용한다.
3️⃣ restart 버튼
🌟 App.js 코드{winner && (
<div className="end">
<h1 className="winner">
Winner: {winner === "me" ? "ME" : "OPPONENT"}
</h1>
<button
className="restart"
onClick={() => window.location.reload()}
>
게임 다시 시작
</button>
</div>
)}
빙고가 완성되어 winner가 set되면 조건부 렌더링을 통해 winner를 표시하고 그 아래에는 게임을 다시 시작할 수 있는 restart 버튼을 렌더링한다. restart 버튼은 window.location.reload()를 호출해 웹 전체 상태를 초기화하기 때문에 새 게임을 이어서 할 수 있다.
/* 빙고 완성 시 */
.end {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
color: white;
display: flex;
flex-direction: column; /* ? 세로 정렬 */
align-items: center;
justify-content: center;
gap: 24px; /* h1과 버튼 사이 간격 */
z-index: 999;
}
/* 승자 */
.winner {
font-size: 48px;
margin: 0;
}
/* 다시 시작 버튼 */
.restart {
font-size: 18px;
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
background-color: #ffffff;
color: #000000;
}
.restart:hover {
background-color: #e0e0e0;
}
기본 상태에서는 흰색 배경과 검은색 text로 깔끔한 restart 버튼 UI를 구성하고 마우스를 올렸을 때는 :hover를 통해 배경색이 회색으로 변경되도록 설정해 버튼이 클릭 가능한 인터랙션 요소임을 직관적으로 알 수 있도록 한다.
4️⃣ 앞으로...
타이머, 빙고 highlight, restart 버튼을 만들었다. 이제 진짜 진짜 거의 끝이다! 다음에는 컴퓨터 한 대에서 턴을 번갈아 진행하던 방식이 아니라 서로 다른 두 기기에서 play할 수 있도록 구현하고자 한다. 또한 현재처럼 하나의 기기에서도 2명이 play할 수 있게 main color 등이 표시되는 방법을 바꿀 것이다. 그리고 웹 배포까지 하면 끝이다..

반응형
'개인 개발 > 대학전쟁' 카테고리의 다른 글
| [대학전쟁3] 양면빙고 웹 개발 09 - UI/UX 개선 및 기능 보완 (0) | 2026.02.22 |
|---|---|
| [대학전쟁3] 양면빙고 웹 개발 08 - 2인 play (0) | 2026.02.08 |
| [대학전쟁3] 양면빙고 웹 개발 06 - 직전 타일 이동, 뒤집기 제한 (0) | 2026.01.23 |
| [대학전쟁] 양면빙고 웹 개발 05 - 빙고 판단, player 2명 (0) | 2026.01.20 |
| [대학전쟁] 양면빙고 웹 개발 04 - 타일 뒤집기 (flip), 이동 (move) (1) | 2026.01.18 |