5 files changed,
142 insertions(+),
141 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-07-03 23:52:08 +0300
Authored at:
2026-07-03 23:50:26 +0300
Change ID:
pxqqmoutptyvpyoyqoqzqtvzpzuxupos
Parent:
3f3048b
jump to
| M | game.typ |
| M | lib.typ |
| D | moves.typ |
| M | rules.typ |
| M | tests/move-gen.typ |
M
game.typ
··· 1 1 #import "board.typ": apply-move, at, copy-grid, file-to-col, find-pieces, is-black, is-white, on-board, piece-type, render-board, square-to-coords, starting-grid 2 2 #import "notation.typ": parse-move 3 -#import "moves.typ": generate-moves-for, pawn-moves 4 -#import "rules.typ": has-legal-move, is-attacked, is-in-check 3 +#import "rules.typ": generate-moves-for, has-legal-move, is-attacked, is-in-check, pawn-moves 5 4 6 5 #let initial-state() = ( 7 6 grid: starting-grid, ··· 180 179 out.push(render-board(state.grid)) 181 180 out.join() 182 181 } 182 + 183 +// Usage: 184 +// #show: chess.with() 185 +// g4 d5 Bg5 Bxg4 c4 c6 cxd5 cxd5 Qb3 186 +// 187 +#let chess(body) = { 188 + set page(height: auto, width: auto, margin: (top: 0.5in, bottom: 0.5in, rest: 0.5in)) 189 + let moves = parse-moves(body) 190 + let states = apply-moves(moves) 191 + render(states.last()) 192 +}
M
lib.typ
··· 1 -#import "game.typ": render, apply-moves, parse-moves 2 - 3 -// Usage: 4 -// #show: chess.with() 5 -// g4 d5 Bg5 Bxg4 c4 c6 cxd5 cxd5 Qb3 6 -// 7 -#let chess(body) = { 8 - set page(height: auto, width: auto, margin: (top: 0.5in, bottom: 0.5in, rest: 0.5in)) 9 - let moves = parse-moves(body) 10 - let states = apply-moves(moves) 11 - render(states.last()) 12 -} 1 +#import "game.typ": chess
D
moves.typ
··· 1 -#import "board.typ": at, is-black, is-white, on-board, piece-type 2 - 3 -#let enemy(turn, target) = if turn == "white" { is-black(target) } else { is-white(target) } 4 - 5 -#let pawn-dir(turn) = if turn == "white" { -1 } else { 1 } 6 -#let pawn-moves(grid, col, row, turn, ep: none) = { // returns list of (dest-col, dest-row, prom?), ep == en passant 7 - let moves = () 8 - let start-row = if turn == "white" { 7 } else { 2 } 9 - let dir = pawn-dir(turn) 10 - 11 - // forward one 12 - let nr = row + dir 13 - if on-board(col, nr) and at(grid, col, nr) == "." { 14 - if nr == 1 or nr == 8 { 15 - for prom in ("Q", "R", "B", "N") { moves.push((col, nr, prom)) } 16 - } else { moves.push((col, nr)) } 17 - 18 - // forward two from start 19 - let nr2 = row + 2 * dir 20 - if row == start-row and at(grid, col, nr2) == "." { moves.push((col, nr2)) } 21 - } 22 - 23 - // captures 24 - for dc in (-1, 1) { 25 - let nc = col + dc 26 - let nr = row + dir 27 - if on-board(nc, nr) { 28 - if enemy(turn, at(grid, nc, nr)) { 29 - if nr == 1 or nr == 8 { 30 - for prom in ("Q", "R", "B", "N") { moves.push((nc, nr, prom)) } 31 - } else { 32 - moves.push((nc, nr)) 33 - } 34 - } 35 - 36 - // en passant capture: pawn ends on ep square, captured pawn is one rank behind 37 - if ep != none and nc == ep.at(0) { 38 - let captured-row = ep.at(1) - dir 39 - if enemy(turn, at(grid, nc, captured-row)) { moves.push((ep.at(0), ep.at(1))) } 40 - } 41 - } 42 - } 43 - 44 - moves 45 -} 46 - 47 -#let knight-offsets = ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)) 48 -#let knight-moves(grid, col, row, turn) = { 49 - let moves = () 50 - for (dc, dr) in knight-offsets { 51 - let nc = col + dc 52 - let nr = row + dr 53 - if on-board(nc, nr) { 54 - let target = at(grid, nc, nr) 55 - if target == "." or enemy(turn, target) { 56 - moves.push((nc, nr)) 57 - } 58 - } 59 - } 60 - moves 61 -} 62 - 63 -#let sliding-moves(grid, col, row, turn, dirs) = { 64 - let moves = () 65 - for (dc, dr) in dirs { 66 - let nc = col + dc 67 - let nr = row + dr 68 - while on-board(nc, nr) { 69 - let target = at(grid, nc, nr) 70 - if target == "." { 71 - moves.push((nc, nr)) 72 - } else if enemy(turn, target) { 73 - moves.push((nc, nr)) 74 - break 75 - } else { break } 76 - nc = nc + dc 77 - nr = nr + dr 78 - } 79 - } 80 - moves 81 -} 82 - 83 -#let bishop-dirs = ((-1, -1), (-1, 1), (1, -1), (1, 1)) 84 -#let bishop-moves(grid, col, row, turn) = sliding-moves(grid, col, row, turn, bishop-dirs) 85 - 86 -#let rook-dirs = ((-1, 0), (1, 0), (0, -1), (0, 1)) 87 -#let rook-moves(grid, col, row, turn) = sliding-moves(grid, col, row, turn, rook-dirs) 88 - 89 -#let queen-moves(grid, col, row, turn) = { 90 - let moves = () 91 - for m in bishop-moves(grid, col, row, turn) { moves.push(m) } 92 - for m in rook-moves(grid, col, row, turn) { moves.push(m) } 93 - moves 94 -} 95 - 96 -#let king-dirs = ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)) 97 -#let king-moves(grid, col, row, turn) = { 98 - let moves = () 99 - for (dc, dr) in king-dirs { 100 - let nc = col + dc 101 - let nr = row + dr 102 - if on-board(nc, nr) { 103 - if at(grid, nc, nr) == "." or enemy(turn, at(grid, nc, nr)) { 104 - moves.push((nc, nr)) 105 - } 106 - } 107 - } 108 - moves 109 -} 110 - 111 -#let generate-moves-for(grid, col, row, turn, ep: none) = { 112 - let p = at(grid, col, row) 113 - if p == "." { return () } 114 - if turn == "white" and not is-white(p) { return () } 115 - if turn == "black" and not is-black(p) { return () } 116 - 117 - let type = piece-type(p) 118 - if type == "P" { return pawn-moves(grid, col, row, turn, ep: ep) } 119 - if type == "N" { return knight-moves(grid, col, row, turn) } 120 - if type == "B" { return bishop-moves(grid, col, row, turn) } 121 - if type == "R" { return rook-moves(grid, col, row, turn) } 122 - if type == "Q" { return queen-moves(grid, col, row, turn) } 123 - if type == "K" { return king-moves(grid, col, row, turn) } 124 - () 125 -}
M
rules.typ
··· 1 1 #import "board.typ": at, copy-grid, find-pieces, is-black, is-white, on-board, piece-type 2 -#import "moves.typ": bishop-dirs, generate-moves-for, king-dirs, knight-offsets, pawn-moves, rook-dirs 2 + 3 +// move generation 4 + 5 +#let enemy(turn, target) = if turn == "white" { is-black(target) } else { is-white(target) } 6 + 7 +#let pawn-dir(turn) = if turn == "white" { -1 } else { 1 } 8 +#let pawn-moves(grid, col, row, turn, ep: none) = { 9 + let moves = () 10 + let start-row = if turn == "white" { 7 } else { 2 } 11 + let dir = pawn-dir(turn) 12 + 13 + // forward one 14 + let nr = row + dir 15 + if on-board(col, nr) and at(grid, col, nr) == "." { 16 + if nr == 1 or nr == 8 { 17 + for prom in ("Q", "R", "B", "N") { moves.push((col, nr, prom)) } 18 + } else { moves.push((col, nr)) } 19 + 20 + // forward two from start 21 + let nr2 = row + 2 * dir 22 + if row == start-row and at(grid, col, nr2) == "." { moves.push((col, nr2)) } 23 + } 24 + 25 + // captures 26 + for dc in (-1, 1) { 27 + let nc = col + dc 28 + let nr = row + dir 29 + if on-board(nc, nr) { 30 + if enemy(turn, at(grid, nc, nr)) { 31 + if nr == 1 or nr == 8 { 32 + for prom in ("Q", "R", "B", "N") { moves.push((nc, nr, prom)) } 33 + } else { 34 + moves.push((nc, nr)) 35 + } 36 + } 37 + 38 + // en passant capture: pawn ends on ep square, captured pawn is one rank behind 39 + if ep != none and nc == ep.at(0) { 40 + let captured-row = ep.at(1) - dir 41 + if enemy(turn, at(grid, nc, captured-row)) { moves.push((ep.at(0), ep.at(1))) } 42 + } 43 + } 44 + } 45 + 46 + moves 47 +} 48 + 49 +#let knight-offsets = ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)) 50 +#let knight-moves(grid, col, row, turn) = { 51 + let moves = () 52 + for (dc, dr) in knight-offsets { 53 + let nc = col + dc 54 + let nr = row + dr 55 + if on-board(nc, nr) { 56 + let target = at(grid, nc, nr) 57 + if target == "." or enemy(turn, target) { 58 + moves.push((nc, nr)) 59 + } 60 + } 61 + } 62 + moves 63 +} 64 + 65 +#let sliding-moves(grid, col, row, turn, dirs) = { 66 + let moves = () 67 + for (dc, dr) in dirs { 68 + let nc = col + dc 69 + let nr = row + dr 70 + while on-board(nc, nr) { 71 + let target = at(grid, nc, nr) 72 + if target == "." { 73 + moves.push((nc, nr)) 74 + } else if enemy(turn, target) { 75 + moves.push((nc, nr)) 76 + break 77 + } else { break } 78 + nc = nc + dc 79 + nr = nr + dr 80 + } 81 + } 82 + moves 83 +} 84 + 85 +#let bishop-dirs = ((-1, -1), (-1, 1), (1, -1), (1, 1)) 86 +#let bishop-moves(grid, col, row, turn) = sliding-moves(grid, col, row, turn, bishop-dirs) 87 + 88 +#let rook-dirs = ((-1, 0), (1, 0), (0, -1), (0, 1)) 89 +#let rook-moves(grid, col, row, turn) = sliding-moves(grid, col, row, turn, rook-dirs) 90 + 91 +#let queen-moves(grid, col, row, turn) = { 92 + let moves = () 93 + for m in bishop-moves(grid, col, row, turn) { moves.push(m) } 94 + for m in rook-moves(grid, col, row, turn) { moves.push(m) } 95 + moves 96 +} 97 + 98 +#let king-dirs = ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)) 99 +#let king-moves(grid, col, row, turn) = { 100 + let moves = () 101 + for (dc, dr) in king-dirs { 102 + let nc = col + dc 103 + let nr = row + dr 104 + if on-board(nc, nr) { 105 + if at(grid, nc, nr) == "." or enemy(turn, at(grid, nc, nr)) { 106 + moves.push((nc, nr)) 107 + } 108 + } 109 + } 110 + moves 111 +} 112 + 113 +#let generate-moves-for(grid, col, row, turn, ep: none) = { 114 + let p = at(grid, col, row) 115 + if p == "." { return () } 116 + if turn == "white" and not is-white(p) { return () } 117 + if turn == "black" and not is-black(p) { return () } 118 + 119 + let type = piece-type(p) 120 + if type == "P" { return pawn-moves(grid, col, row, turn, ep: ep) } 121 + if type == "N" { return knight-moves(grid, col, row, turn) } 122 + if type == "B" { return bishop-moves(grid, col, row, turn) } 123 + if type == "R" { return rook-moves(grid, col, row, turn) } 124 + if type == "Q" { return queen-moves(grid, col, row, turn) } 125 + if type == "K" { return king-moves(grid, col, row, turn) } 126 + () 127 +} 128 + 129 +// chess rules 3 130 4 131 #let is-attacked(grid, col, row, by-color) = { 5 132 let enemy(piece) = if by-color == "white" { is-white(piece) } else { is-black(piece) }