schess 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,183 @@
1
+ import chalk from 'chalk';
2
+ import { ChessGame } from '../game/ChessGame.js';
3
+ import type { GameState, PlayerColor, Square, Position } from '../game/types.js';
4
+ import { renderBoard, renderStatus, renderControls, positionToSquare, squareToPosition } from './Board.js';
5
+ import { createKeyboardHandler, type KeyAction } from './Keyboard.js';
6
+
7
+ export interface NetworkCallbacks {
8
+ onMove: (from: Square, to: Square) => void;
9
+ onResign: () => void;
10
+ }
11
+
12
+ export class GameScreen {
13
+ private game: ChessGame;
14
+ private state: GameState;
15
+ private keyboard: ReturnType<typeof createKeyboardHandler>;
16
+ private networkCallbacks: NetworkCallbacks;
17
+
18
+ constructor(playerColor: PlayerColor, callbacks: NetworkCallbacks) {
19
+ this.game = new ChessGame();
20
+ this.networkCallbacks = callbacks;
21
+
22
+ // Initialize cursor at center
23
+ const startRow = playerColor === 'w' ? 6 : 1; // Start near own pieces
24
+ this.state = {
25
+ cursor: { row: startRow, col: 4 },
26
+ selectedSquare: null,
27
+ validMoves: [],
28
+ lastMove: null,
29
+ isMyTurn: playerColor === 'w', // White goes first
30
+ playerColor,
31
+ gameOver: false,
32
+ gameOverReason: null,
33
+ };
34
+
35
+ this.keyboard = createKeyboardHandler(this.handleKey.bind(this));
36
+ }
37
+
38
+ private handleKey(action: KeyAction): void {
39
+ if (this.state.gameOver) {
40
+ if (action === 'quit' || action === 'enter') {
41
+ this.stop();
42
+ process.exit(0);
43
+ }
44
+ return;
45
+ }
46
+
47
+ switch (action) {
48
+ case 'up':
49
+ this.moveCursor(-1, 0);
50
+ break;
51
+ case 'down':
52
+ this.moveCursor(1, 0);
53
+ break;
54
+ case 'left':
55
+ this.moveCursor(0, -1);
56
+ break;
57
+ case 'right':
58
+ this.moveCursor(0, 1);
59
+ break;
60
+ case 'enter':
61
+ this.handleSelect();
62
+ break;
63
+ case 'escape':
64
+ this.cancelSelection();
65
+ break;
66
+ case 'quit':
67
+ this.networkCallbacks.onResign();
68
+ this.stop();
69
+ console.log(chalk.yellow('\nYou resigned. Game over.'));
70
+ process.exit(0);
71
+ break;
72
+ }
73
+
74
+ this.render();
75
+ }
76
+
77
+ private moveCursor(dRow: number, dCol: number): void {
78
+ // Adjust for board orientation
79
+ const actualDRow = this.state.playerColor === 'w' ? dRow : -dRow;
80
+ const actualDCol = this.state.playerColor === 'w' ? dCol : -dCol;
81
+
82
+ const newRow = Math.max(0, Math.min(7, this.state.cursor.row + actualDRow));
83
+ const newCol = Math.max(0, Math.min(7, this.state.cursor.col + actualDCol));
84
+ this.state.cursor = { row: newRow, col: newCol };
85
+ }
86
+
87
+ private handleSelect(): void {
88
+ if (!this.state.isMyTurn) return;
89
+
90
+ const cursorSquare = positionToSquare(this.state.cursor);
91
+
92
+ if (this.state.selectedSquare) {
93
+ // Try to make a move
94
+ if (this.state.validMoves.includes(cursorSquare)) {
95
+ const move = this.game.makeMove(this.state.selectedSquare, cursorSquare);
96
+ if (move) {
97
+ this.state.lastMove = { from: this.state.selectedSquare, to: cursorSquare };
98
+ this.networkCallbacks.onMove(this.state.selectedSquare, cursorSquare);
99
+ this.state.isMyTurn = false;
100
+
101
+ // Check game over
102
+ if (this.game.isGameOver()) {
103
+ this.state.gameOver = true;
104
+ this.state.gameOverReason = this.game.getGameOverReason();
105
+ }
106
+ }
107
+ this.cancelSelection();
108
+ } else {
109
+ // Clicked on a different square - check if it's own piece
110
+ const piece = this.game.getPiece(cursorSquare);
111
+ if (piece && piece.color === this.state.playerColor) {
112
+ this.selectPiece(cursorSquare);
113
+ } else {
114
+ this.cancelSelection();
115
+ }
116
+ }
117
+ } else {
118
+ // Select a piece
119
+ const piece = this.game.getPiece(cursorSquare);
120
+ if (piece && piece.color === this.state.playerColor) {
121
+ this.selectPiece(cursorSquare);
122
+ }
123
+ }
124
+ }
125
+
126
+ private selectPiece(square: Square): void {
127
+ this.state.selectedSquare = square;
128
+ this.state.validMoves = this.game.getValidMoves(square);
129
+ }
130
+
131
+ private cancelSelection(): void {
132
+ this.state.selectedSquare = null;
133
+ this.state.validMoves = [];
134
+ }
135
+
136
+ // Called when opponent makes a move
137
+ receiveMove(from: Square, to: Square): void {
138
+ this.game.loadMove(from, to);
139
+ this.state.lastMove = { from, to };
140
+ this.state.isMyTurn = true;
141
+
142
+ // Check game over
143
+ if (this.game.isGameOver()) {
144
+ this.state.gameOver = true;
145
+ this.state.gameOverReason = this.game.getGameOverReason();
146
+ }
147
+
148
+ this.render();
149
+ }
150
+
151
+ // Called when opponent resigns
152
+ opponentResigned(): void {
153
+ this.state.gameOver = true;
154
+ this.state.gameOverReason = 'Opponent resigned';
155
+ this.render();
156
+ }
157
+
158
+ render(): void {
159
+ // Clear screen
160
+ console.clear();
161
+
162
+ // Title
163
+ console.log(chalk.bold.cyan('\n ♔ SCHESS - Chess over LAN ♚\n'));
164
+
165
+ // Board
166
+ console.log(renderBoard(this.game, this.state));
167
+ console.log();
168
+
169
+ // Status
170
+ console.log(renderStatus(this.game, this.state));
171
+ console.log(renderControls());
172
+ console.log();
173
+ }
174
+
175
+ start(): void {
176
+ this.keyboard.start();
177
+ this.render();
178
+ }
179
+
180
+ stop(): void {
181
+ this.keyboard.stop();
182
+ }
183
+ }
@@ -0,0 +1,57 @@
1
+ import * as readline from 'readline';
2
+
3
+ export type KeyAction = 'up' | 'down' | 'left' | 'right' | 'enter' | 'escape' | 'quit' | 'unknown';
4
+
5
+ export interface KeyboardHandler {
6
+ onKey: (action: KeyAction) => void;
7
+ start: () => void;
8
+ stop: () => void;
9
+ }
10
+
11
+ export function createKeyboardHandler(onKey: (action: KeyAction) => void): KeyboardHandler {
12
+ let isRunning = false;
13
+
14
+ const handleKeypress = (_str: string, key: readline.Key): void => {
15
+ if (!isRunning) return;
16
+
17
+ let action: KeyAction = 'unknown';
18
+
19
+ if (key.name === 'up') action = 'up';
20
+ else if (key.name === 'down') action = 'down';
21
+ else if (key.name === 'left') action = 'left';
22
+ else if (key.name === 'right') action = 'right';
23
+ else if (key.name === 'return') action = 'enter';
24
+ else if (key.name === 'escape') action = 'escape';
25
+ else if (key.name === 'q' || (key.ctrl && key.name === 'c')) action = 'quit';
26
+
27
+ if (action !== 'unknown') {
28
+ onKey(action);
29
+ }
30
+ };
31
+
32
+ return {
33
+ onKey,
34
+ start: () => {
35
+ if (isRunning) return;
36
+ isRunning = true;
37
+
38
+ // Enable raw mode
39
+ if (process.stdin.isTTY) {
40
+ readline.emitKeypressEvents(process.stdin);
41
+ process.stdin.setRawMode(true);
42
+ process.stdin.resume();
43
+ process.stdin.on('keypress', handleKeypress);
44
+ }
45
+ },
46
+ stop: () => {
47
+ if (!isRunning) return;
48
+ isRunning = false;
49
+
50
+ if (process.stdin.isTTY) {
51
+ process.stdin.setRawMode(false);
52
+ process.stdin.removeListener('keypress', handleKeypress);
53
+ process.stdin.pause();
54
+ }
55
+ },
56
+ };
57
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }