A browser-based chess practice web application that allows users to play local two-player games and review move sequences (kifu) on an interactive board. Built with TypeScript and React.
The application is available online at: https://ysgrProgramming.github.io/chess-app/
The application is automatically deployed to GitHub Pages whenever code is merged to the main branch. You can check the deployment status in the GitHub Actions workflow runs.
-
Install dependencies (if not already done):
npm install
-
Start the development server:
npm run dev
-
Open the application in your browser (typically
http://localhost:5173). -
The game starts with White's turn. The turn indicator at the top of the board shows the current active player.
- Click/Tap: Click or tap a piece to select it, then click/tap the destination square. Legal destination squares are highlighted automatically.
- Drag and Drop: Drag a piece to its destination square and drop it.
- Mobile: On touch devices, tap to select, then tap the destination. Horizontal drag gestures are also supported.
Only legal moves are accepted. If you attempt an illegal move, the piece will return to its original position or the move will be cancelled.
- Undo: Click the "Undo" button to step back exactly one move. The board and move list update accordingly.
- Previous move / Next move: Use these buttons to navigate through the move history step by step.
- New Game: Click "New Game" to reset the board to the initial position and clear the move list.
- Jump to any move: Click any move in the Move List sidebar to jump to that position in the game.
- Preview mode: When viewing an earlier position, you can preview future moves. If you make a new move from a preview position, all moves after that point are discarded, and play continues from the selected position.
- Navigation: Use "Previous move" and "Next move" buttons to step through the game history.
- Copy moves: Click the "Copy moves" button in the Move List sidebar to copy all moves to your clipboard in text format (e.g., "1. e4 e5 2. Nf3 Nc6").
- Download moves: Click the "Download moves" button to download the move list as a text file (
.txt). The file includes move numbers, SAN notation, and game result if applicable.
Note: The application uses Standard Algebraic Notation (SAN) for move representation, including check (+) and checkmate (#) indicators.
The game ends automatically when:
- Checkmate: The opponent's king is in checkmate.
- Stalemate: The active player has no legal moves and is not in check.
- Draw by threefold repetition: The same board position occurs three times.
- Draw by 50-move rule: 50 moves have been made without a pawn move or capture.
You can also:
- Offer draw: Click "Offer draw" to propose a draw. The opponent can accept or decline.
- Resign: Click "Resign" to forfeit the game.
The application is designed to work on modern browsers that support:
- ES2020+ JavaScript features
- CSS Grid and Flexbox
- Drag and Drop API
- Touch Events API (for mobile support)
Recommended browsers:
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- Mobile browsers (iOS Safari, Chrome Mobile)
Assumptions:
- JavaScript is enabled
- Local storage (sessionStorage) is available for game state persistence within a session
- The viewport is at least 320px wide (mobile-friendly)
The application is built with React and TypeScript, following a component-based architecture with centralized state management.
App
├── ChessBoard (interactive board with drag-and-drop)
│ └── PromotionDialog (pawn promotion selection)
├── MoveList (kifu sidebar with move history)
└── GameStateContext (centralized game state management)
The rules engine handles all chess logic:
- Board state management: Represents the 64-square board with pieces, castling rights, en passant targets, and move counters.
- Move validation: Validates moves according to chess rules (piece movement, check detection, castling, en passant).
- Game result evaluation: Detects checkmate, stalemate, threefold repetition, and 50-move rule draws.
- Legal move generation: Computes all legal moves for a given piece or position.
Key functions:
createInitialBoardState(): Creates the standard starting position.validateMove(): Validates if a move is legal.applyMove(): Applies a move to the board state.getLegalMoves(): Returns all legal destination squares for a piece.evaluateGameResult(): Determines the current game result.
Manages the overall game state:
- Move history: Stores all moves in the game.
- Current position pointer: Tracks which move is currently displayed (for replay/preview).
- Session persistence: Saves game state to
sessionStoragefor persistence within a browser session. - State reducer: Uses React's
useReducerfor predictable state updates.
Key concepts:
- Linear history: Making a move from a preview position discards future moves.
- Preview mode: When viewing an earlier position,
isPreviewingis true, and the game is not considered "over" until returning to the canonical position.
Converts moves to Standard Algebraic Notation (SAN):
- Move to SAN: Converts a
Moveobject to SAN string (e.g., "e4", "Nf3", "O-O"). - Disambiguation: Handles cases where multiple pieces can move to the same square (e.g., "Nbd2" vs "Nfd2").
- Check/Checkmate indicators: Appends
+for check and#for checkmate.
Exports move history in various formats:
- Text format: Human-readable move list (e.g., "1. e4 e5 2. Nf3 Nc6").
- PGN format: Portable Game Notation with headers and moves.
User Interaction (click/drag)
↓
ChessBoard Component
↓
handleMove callback
↓
GameStateContext.handleMove()
↓
gameReducer (updates state)
↓
Board state recomputed from move history
↓
ChessBoard re-renders with new position
↓
MoveList updates with new move
To add new features without breaking existing functionality:
- Follow TDD: Write tests first, then implement.
- Extend types: Add new fields to
GameStateorBoardStateinsrc/lib/types.tsif needed. - Update reducer: Add new action types to
GameActionand handle them ingameReducer. - Maintain backward compatibility: When modifying state structure, ensure old session data can be migrated or ignored gracefully.
- Test edge cases: Consider preview mode, undo/redo, and game end conditions.
Example: Adding a new game end condition
// 1. Add result type to GameResult union in types.ts
type GameResult =
| { type: "ongoing" }
| { type: "checkmate"; winner: Color }
| { type: "stalemate" }
| { type: "draw"; reason: "threefold repetition" | "50-move rule" | "agreement" }
| { type: "resignation"; winner: Color }
| { type: "new-condition"; /* ... */ }; // Add new condition
// 2. Update evaluateGameResult() in chessEngine.ts
export function evaluateGameResult(...): GameResult {
// ... existing checks ...
if (/* new condition detected */) {
return { type: "new-condition", /* ... */ };
}
// ...
}
// 3. Update UI components to handle new result type
// (App.tsx, MoveList.tsx, etc.)To integrate the chess game component into a host page:
import React from "react";
import { GameStateProvider } from "./contexts/GameStateContext";
import { App } from "./App";
function MyHostPage() {
return (
<GameStateProvider>
<App />
</GameStateProvider>
);
}
export default MyHostPage;The GameStateProvider must wrap the App component (or any component that uses useGameState()). The App component includes the board, move list, and all controls.
For a minimal integration with just the board:
import React, { useState } from "react";
import { ChessBoard } from "./components/ChessBoard";
import { createInitialBoardState, applyMove, validateMove } from "./lib/chessEngine";
import type { BoardState, Move } from "./lib/types";
function MinimalChessBoard() {
const [boardState, setBoardState] = useState(createInitialBoardState());
const handleMove = (move: Move) => {
const validation = validateMove(boardState, move);
if (validation.valid) {
const newState = applyMove(boardState, move);
setBoardState(newState);
}
};
return <ChessBoard boardState={boardState} onMove={handleMove} />;
}The application is automatically deployed to GitHub Pages whenever code is pushed to the main branch.
Deployment Process:
- Automatic Trigger: The deployment workflow (
.github/workflows/deploy.yml) runs automatically on every push tomain - Quality Gates: Before deployment, the workflow runs:
- Lint checks (
make lint) - Test suite (
make test) - Build process (
npm run build)
- Lint checks (
- Deployment: If all checks pass, the built application is deployed to GitHub Pages
- Live URL: The application is available at https://ysgrProgramming.github.io/chess-app/
Checking Deployment Status:
- View deployment workflow runs: GitHub Actions - Deploy workflow
- The workflow shows the deployment status, including any failures
- Note: Pull requests do not trigger deployment; only merges to
mainbranch trigger automatic deployment
Repository Settings:
- GitHub Pages source must be set to "GitHub Actions" (not a branch)
- The workflow requires
pages: writeandid-token: writepermissions
本プロジェクトは、AIコードエディタ Cursor と GitHub CLI、そして CI (GitHub Actions) を組み合わせ、論理的整合性と品質を担保しながらソフトウェア開発を行う「半自律型AI開発エコシステム」の設計書です。
このシステムの核心は、単一のAIにすべてを任せるのではなく、「要件定義」「管理・レビュー」「実装」という3つの役割(ロール)を明確に分割し、システム的に手順を強制する点にあります。これにより、LLM特有の「幻覚(ハルシネーション)」や「自己正当化」を防ぎ、人間の関与を最小限かつ高レイヤーな意思決定のみに集中させます。
一人の人間に二重人格を演じさせるのではなく、**完全に独立したチャットセッション(コンテキスト)**で異なる役割をAIに実行させます。
- Architect: 曖昧なアイデアを尋問し、定義書に落とし込む。
- Manager: 実装コードを書かず、仕様管理とレビューに徹する(長期記憶)。
- Engineer: Issueごとに生成され、タスク完了と共に破棄される(短期記憶)。
AIの「動くはずです」という言葉は信用しません。
GitHub Actions 上で make test (単体テスト) と make lint (静的解析) が通過しない限り、プルリクエスト(PR)のレビュー段階に進むことをシステム的に禁止します。
人間は「AIの思考」には介入しません。人間が行うのは以下の2点のみです。
- 承認と意思決定(GO/NO-GOの判断)
- 物理的なスイッチング(AIからの「指示サイン」に従い、適切なエージェントを呼び出す)
システムは .cursor/rules/*.mdc によって定義される以下の3つのロールで構成されます。
| ロール | 担当フェーズ | 振る舞い・権限 |
|---|---|---|
| Architect ( ai-architect) |
企画・要件定義 | 「尋問官」 人間からの入力が曖昧な場合、 REQUIREMENT.md が埋まるまで質問を繰り返す。定義が完了するまで実装フェーズへの移行を許可しない。 |
| Manager ( ai-manager) |
タスク分解 レビュー メタチェック |
「PM兼レビュアー」 要件定義からIssueを作成する。PRに対し、コードそのものではなく「論理と要件の整合性」をレビューする。プロジェクト全体の設計歪みを検知する。 |
| Engineer ( ai-engineer) |
実装 テスト |
「使い捨て実装者」 Issue単位で起動する。TDD(テスト駆動開発)を厳守し、PR作成時にはコードの変更理由を論理的に解説する義務を持つ。 |
- Interface: Cursor (Composer機能)
- Context Control: ロールごとに「New Chat」を行い、コンテキストを汚染させない。
- Communication: GitHub Issue / PR (全ての決定事項と議論はここに集約)
- Tools:
gh(GitHub CLI): AIによるIssue/PRの操作make: テスト・Lintコマンドの統一GitHub Actions: CIによる品質ゲート
- 人間が「こういうアプリを作りたい」と Architect を呼び出す。
- Architect は
REQUIREMENT.mdのテンプレートに基づき、不足情報を人間にヒアリングする。 - 全ての定義が埋まったら、Manager への引き継ぎサインを出す。
- Manager は要件定義書を読み込み、実装可能な粒度の
taskIssue に分解して登録する。 - 最初の Issue に着手するためのプロンプトを人間に提示する。
- 人間は「Issue #X をやって」と Engineer を呼び出す(必ずNew Chat)。
- Engineer は以下の厳格なループを実行する:
- TDD: テスト作成 → 失敗(Red) → 実装 → 成功(Green)
- Lint:
make lintの通過 - Isolation:
.env等の秘匿情報には触れない。実験コードはコミットしない。
- PRを作成する。この際、**「要件ごとの論理的解決策(Self-Walkthrough)」**を記述する。
- CI (GitHub Actions) が自動実行される。失敗したPRはレビュー対象外。
- Manager はPRを読み、以下を確認する:
- CIがGreenであるか。
- PRの「論理的解決策」が理にかなっているか。
- Diffが要件を満たしているか。
- 問題なければマージ(Approve)、問題があれば修正指示(Reject)。
- マージ完了後、Manager は「今回の変更がアーキテクチャ全体に悪影響(歪み)を与えていないか」を診断する。
- リファクタリングが必要と判断した場合、自らは修正せず、新たな
refactorIssue を作成してバックログに積む。
人間が迷わず操作できるよう、AIはアクションが必要なタイミングで必ず以下の**「アクションブロック」**を表示します。
::: action 🛑 人間の操作が必要です
**状況:** [現在のステータス (例: PR作成完了)]
**操作:** [次に人間がすべき物理操作 (例: New Chatを開く)]
**次のプロンプト:**
(ここにあるテキストをコピーして、次のチャットに入力するだけ)
> @ai-manager.mdc PR #15 のレビューをお願いします。
> :::root/
├── .cursor/
│ └── rules/ # 各AIロールの振る舞い定義 (.mdc)
│ ├── ai-architect.mdc
│ ├── ai-manager.mdc
│ ├── ai-engineer.mdc
│ └── tech-stack.mdc # 技術選定・コーディング規約
├── .github/
│ ├── workflows/ # CI定義 (push時に test/lint 実行)
│ ├── ISSUE_TEMPLATE/ # タスク定義用テンプレート
│ └── PULL_REQUEST_TEMPLATE.md # Self-Walkthrough記述用
├── REQUIREMENT.md # アーキテクトが管理する要件定義書
├── ROADMAP.md # マネージャーが管理する進捗表
├── Makefile # コマンド集約 (test, lint)
└── src/ # ソースコード
- TDD & Lint 強制: ローカルで
make testmake lintが通らないコードはPush禁止。さらにCIでダブルチェックを行う。 - シーケンシャル処理: 複数のIssueを同時に進行させない。常に「1つのIssue、1つのEngineerセッション」で完結させ、コンテキストの混濁を防ぐ。
- 秘匿情報の保護: AIには
.envなどのCredentialファイルへのアクセス権限を与えず、モックデータでのテストを原則とする。
- Node.js: バージョン 20.0.0 以上が必要(
package.jsonのenginesフィールドを参照) - npm: Node.jsに付属し、PATHに含まれていること
-
依存関係のインストール
make setup # または npm install -
動作確認
make test # すべてのテストが通過することを確認 make lint # Lintエラーがないことを確認
問題: make testやmake lintがnpm: command not foundエラーを出す
- 原因:
npmがPATHに含まれていない - 解決策:
- Node.jsが正しくインストールされているか確認:
node --version、npm --version - nvmを使用している場合、シェルの設定ファイル(
.bashrc、.zshrcなど)でnvmが自動的にロードされるように設定してください - または、手動で
source ~/.nvm/nvm.shを実行してからmakeコマンドを実行してください
- Node.jsが正しくインストールされているか確認:
問題: CI(GitHub Actions)でテストが失敗する
- 原因: CI環境でNode.jsが正しくセットアップされていない
- 解決策: CIの設定(
.github/workflows/ci.yml)でNode.jsのバージョンを指定してください
- アプリを起動する
npm run dev(またはvite) を実行し、ブラウザでアプリを開きます。初期状態では「White's turn」が表示され、白番から開始します。 - 駒を動かす
- クリック: 動かしたい駒→移動先のマスの順にクリックします。合法手のみ受け付けられ、相手番の駒は選択できません。
- ドラッグ&ドロップ: 駒をドラッグして目的のマスにドロップすることも可能です。
- 選択中の駒にはハイライトが表示され、合法手のマスにはガイドが描画されます。
- ターン管理
画面左上のターンインジケータが常に現在の手番を示します。手番でない駒は移動できず、常に交互に進行します。 - 棋譜(Move List)の活用
Move ListにはSAN表記の棋譜が蓄積されます。任意の手をクリックするとその時点の局面へジャンプし、kifuの先をプレビューできます。- プレビュー中に新しい手を指すと、以降の手は破棄され、選択していた手番のプレイヤーから分岐を再開できます。
- Undo / Reset
Undo: 直前の手を一手だけ巻き戻します(ターンも自動で追従)。New Game: 盤面と棋譜をリセットし、再び白番から開始します。
- ローカル対戦フロー (Flow 1)
- アプリ起動後すぐに白番が指し、続いて黒番が同じデバイスで指します。
- 詰み/やり直しを確認したいときは
UndoやMove Listを使って局面を巻き戻し、必要に応じて新しい変化を入力します。 - いつでも
New Gameで初期局面から再開でき、棋譜は自動クリアされます。
この手順に従うことで、アカウントなし・ネットワークなしで2人が1台のデバイスを共有し、完全な対局を実行できます。