
체스를 좋아하는데, chess.com이나 lichess.org를 쓰다 보면 "왜 프리미엄 결제를 해야 무제한으로 분석을 쓸 수 있지?"라는 생각이 들었습니다. 광고도 띄워주고, 인터넷 연결도 필요하고… 그래서 완전히 로컬에서 돌아가는, 무료로 모든 기능을 쓸 수 있는 체스 플랫폼을 만들기로 했습니다.
목표는 명확했어요.
프로젝트의 핵심은 5가지 게임 모드입니다.
로컬 2인 플레이 모드입니다. 드래그 앤 드롭으로 기물을 움직이고, 실시간 타이머가 작동합니다. 친구와 한 컴퓨터에서 대결할 때 유용해요.
Stockfish 엔진을 20단계 난이도로 조절할 수 있습니다. 초보자는 레벨 1~5, 중급자는 6~12, 고급자는 13~20으로 설정하면 적절한 상대를 만날 수 있어요.
Magnus Carlsen, Garry Kasparov, Bobby Fischer, Anatoly Karpov, Mikhail Tal 등 5명의 세계 챔피언 플레이 스타일을 AI가 모방합니다. 각 선수의 오프닝 선호도, 공격성, 포지션 스타일을 반영했어요.
Lichess 오픈 퍼즐 데이터베이스 5,169,862개를 전부 가져왔습니다. 문제는 퍼즐 로딩 속도였어요.
처음엔 단순하게 ORDER BY RANDOM() LIMIT 1 쿼리를 썼더니 388ms가 걸렸습니다. 516만 행을 스캔하니까 당연히 느릴 수밖에 없었죠.
해결책은 인덱싱 + 스마트 랜덤 선택이었습니다.
# 기존 방식 (느림)
cursor.execute("SELECT * FROM puzzles ORDER BY RANDOM() LIMIT 1")
# 개선 방식 (빠름)
min_rating, max_rating = 800, 2800
random_rating = random.randint(min_rating, max_rating)
cursor.execute("""
SELECT * FROM puzzles
WHERE rating >= ?
ORDER BY rating
LIMIT 1
""", (random_rating,))
rating 컬럼에 인덱스를 걸고, 랜덤한 rating 값을 기준으로 WHERE rating >= ?로 필터링한 뒤 LIMIT 1로 첫 번째 퍼즐만 가져오는 방식입니다.
결과는? 388ms → 0.4ms. 970배 빨라졌습니다.
체스 게임을 두면서 실시간으로 Stockfish가 모든 수를 분석합니다.
핵심 기능:
분류 기준은 다음과 같습니다:
eval_diff = current_eval - best_move_eval
if eval_diff <= 0.1: classification = 'best'
elif eval_diff <= 0.5: classification = 'good'
elif eval_diff <= 1.0: classification = 'inaccuracy'
elif eval_diff <= 2.0: classification = 'mistake'
else: classification = 'blunder'
처음에는 게임이 끝난 후 PGN을 입력해서 분석하는 방식을 생각했는데, "두면서 바로 피드백을 받으면 더 좋지 않을까?" 해서 실시간 분석으로 변경했습니다.
수를 둘 때마다 자동으로 스크롤이 내려가고, 블런더를 두면 빨간색으로 강조되어 즉시 알 수 있습니다.
퍼즐 모드를 만들면서 가장 신경 쓴 부분이 사용자 경험이었습니다.
흑이 두는 퍼즐인지, 백이 두는 퍼즐인지 헷갈렸어요. 그래서 체스보드 위에 턴 인디케이터를 추가했습니다.
<div className="turn-indicator">
{gameState.turn === 'w' ? (
<div className="turn-badge white-turn">⚪ 백의 차례</div>
) : (
<div className="turn-badge black-turn">⚫ 흑의 차례</div>
)}
</div>
CSS로 pulse 애니메이션을 추가해서 현재 차례가 명확히 보이도록 했습니다.
오답을 두면 2초 동안 아무것도 못 누르게 막혀 있었어요. 답답했습니다. 딜레이를 2초 → 0.8초로 줄였습니다.
// 오답 처리
setPuzzleStatus('incorrect');
setTimeout(() => {
setPuzzleStatus('solving');
}, 800); // 2000에서 800으로 단축
처음엔 서버 API를 호출해서 힌트를 가져왔는데, API 에러가 자주 발생했어요. 그래서 클라이언트에서 로컬로 힌트를 생성하도록 변경했습니다.
const handleHint = () => {
const nextMove = currentPuzzle.solution[currentSolutionIndex];
if (nextMove) {
const from = nextMove.substring(0, 2);
const to = nextMove.substring(2, 4);
setHint(`${from} → ${to}`); // e2 → e4 형식으로 표시
}
};
서버 의존성을 제거하니 힌트가 즉시 뜨고, 에러도 사라졌습니다.
"정답 보기" 버튼을 누르면 모든 수를 자동으로 빠르게 쭉 두고 지나갔어요. 뭐가 뭔지 모르겠더라고요.
그래서 한 수씩만 보여주는 방식으로 변경했습니다. 버튼을 한 번 누르면 한 수만 두고, 다시 누르면 다음 수를 보여줍니다.
const handleShowSolution = () => {
if (currentSolutionIndex < currentPuzzle.solution.length) {
const move = currentPuzzle.solution[currentSolutionIndex];
makeMove({ from: move.substring(0, 2), to: move.substring(2, 4) });
setCurrentSolutionIndex(currentSolutionIndex + 1);
}
};
이제 사용자가 원하는 속도로 정답을 확인할 수 있습니다.
Framer Motion으로 애니메이션을 추가했습니다.
글래스모피즘 디자인을 적용해서 현대적인 느낌을 살렸습니다.
.puzzle-controls-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
935MB 크기의 puzzles.db를 Git에 올리는 것 자체가 문제였어요. Git LFS(Large File Storage)를 사용해서 해결했습니다.
git lfs install
git lfs track "*.db"
git add .gitattributes puzzles.db
git commit -m "Add puzzles database with Git LFS"
react-chessboard 패키지가 React 18과 peer dependency 충돌이 있었어요. --legacy-peer-deps 플래그로 강제 설치했습니다.
npm install --legacy-peer-deps
Windows, Mac, Linux마다 Stockfish 실행 파일 경로가 달라서 환경별로 분기 처리했습니다.
import platform
if platform.system() == 'Windows':
STOCKFISH_PATH = 'server/stockfish/stockfish-windows-x86-64-avx2.exe'
elif platform.system() == 'Darwin': # Mac
STOCKFISH_PATH = 'server/stockfish/stockfish-mac-m1'
else: # Linux
STOCKFISH_PATH = 'server/stockfish/stockfish-linux-x86-64-avx2'
게임 분석 모드에서 수를 둘 때마다 Stockfish를 호출하는데, 처음엔 너무 느렸어요.
각 수마다 서버 왕복(round-trip)이 발생하니까 체감 딜레이가 있었습니다.
해결책은 백엔드에서 PGN 전체를 받아서 한 번에 분석하는 방식이었습니다.
# 프론트엔드: 수를 둘 때마다 전체 PGN을 보냄
const pgn = game.pgn();
const response = await fetch('http://localhost:5000/ai/analyze', {
method: 'POST',
body: JSON.stringify({ pgn })
});
# 백엔드: PGN을 파싱해서 모든 수를 한 번에 분석
board = chess.Board()
for move in game.mainline_moves():
board.push(move)
info = engine.analyse(board, chess.engine.Limit(time=0.1))
# 평가값 계산...
이렇게 하니 수를 둘 때 1초 이내에 분석 결과가 나왔습니다.

CheckmateAI 홈 인터페이스 — 기능의 중심을 한 화면에 압축한 구조
5가지 게임 모드를 시각적으로 균형 있게 배치해, 사용자가 무엇을 할지 즉시 결정할 수 있게 설계했다.

Stockfish기반 AI 대국 화면
실시간 평가값과 엔진 추천 수를 노출해, 초보부터 고급자까지 자기 플레이를 확실히 교정할 수 있는 구조로 만들었다.

퍼즐 모드 UI
턴 인디케이터, 오답 딜레이 최적화, 단계별 정답 재생 등 쓰는 순간 체감되는 개선에 집중했다.

실시간 게임 분석 화면
수를 둘 때마다 Stockfish가 즉시 평가하고, 블런더/실수를 자동 감지해 최선수를 추천한다. 오른쪽 패널에서 실시간 통계와 정확도를 확인할 수 있다.
체스를 좋아해서 시작한 프로젝트였는데, 생각보다 많은 것을 배웠습니다. 특히 퍼즐 로딩 최적화 과정에서 인덱싱의 중요성을 체감했고, 사용자 경험 개선에 얼마나 많은 디테일이 필요한지 알게 되었어요.
무엇보다 로컬에서 돌아가는 완전한 체스 플랫폼을 만들었다는 게 뿌듯합니다. 인터넷 없이도, 결제 없이도 마음껏 체스를 연습할 수 있으니까요.
코드는 GitHub에 올려뒀습니다. 체스 좋아하시는 분들은 한번 써보세요!