11@module "board"
22
33@use "string" as string
4- @use "helper" as helper
54@use "piece" as piece
65
6+ pub const Move -> struct {
7+ from_col: int,
8+ from_row: int,
9+ to_col: int,
10+ to_row: int
11+ };
12+
13+ pub const Board -> struct {
14+ starting_fen: *char,
15+ length_fen: int,
16+ squares: *char,
17+
18+ white_can_castle_kingside: int,
19+ white_can_castle_queenside: int,
20+ black_can_castle_kingside: int,
21+ black_can_castle_queenside: int,
22+ };
23+
724#returns_ownership
825pub const init_board -> fn (bd: *Board) *Board {
926 bd.squares = cast<*char>(alloc(8 * sizeof<*char>));
@@ -12,6 +29,11 @@ pub const init_board -> fn (bd: *Board) *Board {
1229 bd.starting_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
1330 bd.length_fen = string::strlen(bd.starting_fen);
1431
32+ bd.white_can_castle_kingside = 1;
33+ bd.white_can_castle_queenside = 1;
34+ bd.black_can_castle_kingside = 1;
35+ bd.black_can_castle_queenside = 1;
36+
1537 loop [i: int = 0, row: int = 0, col: int = 0](i < bd.length_fen) : (++i) {
1638 let c: char = bd.starting_fen[i];
1739 if (c == '/') {
@@ -46,21 +68,124 @@ pub const parse_move -> fn (mv: *Move, move: *char) *Move {
4668 return mv;
4769}
4870
49- const is_check -> fn (bd: *Board, king_color: int, king_row: int, king_col: int) int {
50- // 1. Locate the King (already provided by king_row, king_col)
71+ pub const validate_pawn_path -> fn (bd: *Board, mv: *Move, dr: int, dc: int) int {
72+ if (dc == 0 && (dr == -2 || dr == 2)) {
73+ let mid_row: int = (mv.from_row + mv.to_row) / 2;
74+ let mid: char = bd.squares[mid_row * 8 + mv.from_col];
75+ if (mid != '.') { return 0; }
76+ }
77+ return 1;
78+ }
79+
80+ pub const is_path_clear -> fn (bd: *Board, mv: *Move) int {
81+ let dc: int = mv.to_col - mv.from_col;
82+ let dr: int = mv.to_row - mv.from_row;
83+
84+ let step_c: int;
85+ if (dc == 0) { step_c = 0;
86+ } elif (dc > 0) { step_c = 1;
87+ } else { step_c = -1; }
88+
89+ let step_r: int;
90+ if (dr == 0) { step_r = 0;
91+ } elif (dr > 0) { step_r = 1;
92+ } else { step_r = -1; }
93+
94+ let curr_col: int = mv.from_col + step_c;
95+ let curr_row: int = mv.from_row + step_r;
96+
97+ loop (curr_col != mv.to_col || curr_row != mv.to_row) {
98+ if (bd.squares[curr_row * 8 + curr_col] != '.') {
99+ return 0;
100+ }
51101
52- // 2. Iterate Through Opponent's Pieces
53- // (Loop through all squares, identify opponent's pieces)
102+ curr_col = curr_col + step_c;
103+ curr_row = curr_row + step_r;
104+ }
105+
106+ return 1;
107+ }
54108
55- // 3. Check for Attacks
56- // (Inside the loop, for each opponent's piece,
57- // call a helper function like 'can_piece_attack_square'
58- // to see if it attacks king_row, king_col)
109+ pub const is_move_legal -> fn (rule: *PieceRules, bd: *Board, mv: *Move) int {
110+ let dc: int = mv.to_col - mv.from_col;
111+ let dr: int = mv.to_row - mv.from_row;
112+ if (piece::can_move_direction(rule, dc, dr) == 0) { return 0; }
113+ if (rule.can_slide == 1 && rule.can_jump == 0) {
114+ if (is_path_clear(bd, mv) == 0) { return 0; }
115+ }
116+ return 1;
117+ }
59118
60- // 4. Return Check Status
61- // (If any piece attacks, return true; otherwise, return false after the loop)
119+ pub const is_king_in_check -> fn (bd: *Board, is_white: int) int {
120+ let king_char: char;
121+ if (is_white == 1) { king_char = 'K'; }
122+ else { king_char = 'k'; }
123+
124+ let king_row: int = -1;
125+ let king_col: int = -1;
62126
63- return 0;
127+ // Find the king position
128+ loop [i: int = 0](i < 64) : (++i) {
129+ if (bd.squares[i] == king_char) {
130+ king_row = i / 8;
131+ king_col = i % 8;
132+ break;
133+ }
134+ }
135+
136+ if (king_row == -1) {
137+ // Should never happen unless the king was captured (illegal position)
138+ output("Error: King not found on board!\n");
139+ return 1;
140+ }
141+
142+ // 2️⃣ Scan all enemy pieces to see if they can attack the king
143+ loop [i: int = 0](i < 64) : (++i) {
144+ let ch: char = bd.squares[i];
145+ if (ch == '.') { continue; }
146+
147+ // Skip same color pieces
148+ if (is_white == 1 && piece::is_white_piece(ch) == 1) { continue; }
149+ if (is_white == 0 && piece::is_black_piece(ch) == 1) { continue; }
150+
151+ let rule: *PieceRules = piece::get_rules(ch);
152+ if (rule == cast<*PieceRules>(0)) { continue; }
153+
154+ let from_row: int = i / 8;
155+ let from_col: int = i % 8;
156+ let dr: int = king_row - from_row;
157+ let dc: int = king_col - from_col;
158+
159+ // Special handling for pawn attacks
160+ if (ch == 'P' || ch == 'p') {
161+ // Pawns attack diagonally forward only
162+ if (ch == 'P' && dr == -1 && (dc == -1 || dc == 1)) { return 1; }
163+ if (ch == 'p' && dr == 1 && (dc == -1 || dc == 1)) { return 1; }
164+ continue;
165+ }
166+
167+ // Check if move direction matches the piece’s legal move
168+ let can_attack: int = piece::can_move_direction(rule, dc, dr);
169+ if (can_attack == 0) { continue; }
170+
171+ // Check path clearance for sliding pieces
172+ if (rule.can_slide == 1 && rule.can_jump == 0) {
173+ let mv: Move = {
174+ from_row: from_row,
175+ from_col: from_col,
176+ to_row: king_row,
177+ to_col: king_col
178+ };
179+ let clear: int = is_path_clear(bd, &mv);
180+ if (clear == 0) { continue; }
181+ }
182+
183+ // If we reach this point → piece can attack the king
184+ return 1;
185+ }
186+
187+ // 3️⃣ No attacks found → king is safe
188+ return 0;
64189}
65190
66191pub const update_board -> fn (bd: *Board, mv: *Move, white_to_move: *int, is_valid: *int) void {
@@ -101,7 +226,7 @@ pub const update_board -> fn (bd: *Board, mv: *Move, white_to_move: *int, is_val
101226 }
102227
103228 // Check capture legality
104- if (piece::can_capture(target, is_white_piece, is_black_piece ) == 0) {
229+ if (piece::can_capture(piece_char, target ) == 0) {
105230 output("Error: Cannot capture your own piece.\n");
106231 *is_valid = 1;
107232 return;
@@ -121,23 +246,38 @@ pub const update_board -> fn (bd: *Board, mv: *Move, white_to_move: *int, is_val
121246 // Special handling for pawns
122247 if (piece_char == 'P' || piece_char == 'p') {
123248 if (piece::validate_pawn_move(rule, dc, dr, mv.from_row, target, is_white_piece) == 0 ||
124- piece:: validate_pawn_path(bd, mv, dr, dc) == 0) {
249+ validate_pawn_path(bd, mv, dr, dc) == 0) {
125250 output("Error: Illegal pawn move.\n");
126251 *is_valid = 1;
127252 return;
128253 }
129254 } else {
130- if (piece:: is_move_legal(rule, bd, mv) == 0) {
255+ if (is_move_legal(rule, bd, mv) == 0) {
131256 output("Error: Illegal move for this piece.\n");
132257 *is_valid = 1;
133258 return;
134259 }
135260 }
261+
262+ // Save old state
263+ let backup_from: char = bd.squares[from_index];
264+ let backup_to: char = bd.squares[to_index];
136265
137- // Move the piece
266+ // Simulate move
138267 bd.squares[to_index] = piece_char;
139268 bd.squares[from_index] = '.';
140269
270+ // Check if own king is now in check
271+ let in_check: int = is_king_in_check(bd, -cast<int>(is_white_piece == 1));
272+ if (in_check == 1) {
273+ // Undo move
274+ bd.squares[from_index] = backup_from;
275+ bd.squares[to_index] = backup_to;
276+ output("Error: Move leaves king in check.\n");
277+ *is_valid = 1;
278+ return;
279+ }
280+
141281 // Flip turn
142282 if (*white_to_move == 1) {
143283 *white_to_move = 0;
0 commit comments