all repos

tyess @ 3f3048b6eabb39e39333e63cf4d67ba48587a099

chess in typst

tyess/rules.typ (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
what's creamy, 27 days ago
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
3
4
#let is-attacked(grid, col, row, by-color) = {
5
  let enemy(piece) = if by-color == "white" { is-white(piece) } else { is-black(piece) }
6
7
  // knight attacks
8
  for (dc, dr) in knight-offsets {
9
    let p = at(grid, col + dc, row + dr)
10
    if p != "." and piece-type(p) == "N" and enemy(p) { return true }
11
  }
12
13
  // king attacks
14
  for (dc, dr) in king-dirs {
15
    let p = at(grid, col + dc, row + dr)
16
    if p != "." and piece-type(p) == "K" and enemy(p) { return true }
17
  }
18
19
  // pawn attacks
20
  let pawn-checks = if by-color == "white" { ((-1, 1), (1, 1)) } else { ((-1, -1), (1, -1)) }
21
  for (dc, dr) in pawn-checks {
22
    let p = at(grid, col + dc, row + dr)
23
    if p != "." and piece-type(p) == "P" and enemy(p) { return true }
24
  }
25
26
  // sliding pieces
27
  for (dirs, types) in ((bishop-dirs, ("B", "Q")), (rook-dirs, ("R", "Q"))) {
28
    for (dc, dr) in dirs {
29
      let nc = col + dc
30
      let nr = row + dr
31
      while on-board(nc, nr) {
32
        let p = at(grid, nc, nr)
33
        if p != "." {
34
          if enemy(p) and types.contains(piece-type(p)) { return true }
35
          break // blocked by any piece
36
        }
37
        nc += dc
38
        nr += dr
39
      }
40
    }
41
  }
42
43
  false
44
}
45
46
#let is-in-check(grid, turn) = {
47
  let king-char = if turn == "white" { "K" } else { "k" }
48
  let enemy = if turn == "white" { "black" } else { "white" }
49
  for r in range(8) {
50
    for c in range(8) {
51
      if grid.at(r).at(c) == king-char {
52
        return is-attacked(grid, c + 1, r + 1, enemy)
53
      }
54
    }
55
  }
56
  false
57
}
58
59
#let can-castle(grid, row, empty-cols, check-cols, enemy) = {
60
  if is-attacked(grid, 5, row, enemy) { return false }
61
  for c in empty-cols { if at(grid, c, row) != "." { return false } }
62
  for c in check-cols { if is-attacked(grid, c, row, enemy) { return false } }
63
  true
64
}
65
66
#let has-legal-move(grid, turn, ep: none, castles: "") = {
67
  for r in range(8) {
68
    for c in range(8) {
69
      let p = grid.at(r).at(c)
70
      if p == "." { continue }
71
      if turn == "white" and not is-white(p) { continue }
72
      if turn == "black" and not is-black(p) { continue }
73
74
      let type = piece-type(p)
75
76
      let col = c + 1
77
      let row = r + 1
78
      let raw-moves = if type == "P" { pawn-moves(grid, col, row, turn, ep: ep) } else {
79
        generate-moves-for(grid, col, row, turn, ep: ep)
80
      }
81
82
      for m in raw-moves {
83
        let (dc, dr) = (m.at(0), m.at(1))
84
        let new-grid = copy-grid(grid)
85
        new-grid.at(r).at(c) = "."
86
        let mover = p
87
        let is-pawn = type == "P"
88
        if is-pawn and (dr == 1 or dr == 8) {
89
          mover = if turn == "white" { "Q" } else { "q" }
90
        }
91
        new-grid.at(dr - 1).at(dc - 1) = mover
92
        if is-pawn and ep != none and dc == ep.at(0) and dr == ep.at(1) {
93
          let captured-row = if turn == "white" { dr + 1 } else { dr - 1 }
94
          new-grid.at(captured-row - 1).at(dc - 1) = "."
95
        }
96
        if not is-in-check(new-grid, turn) { return true }
97
      }
98
    }
99
  }
100
101
  let enemy = if turn == "white" { "black" } else { "white" }
102
  let options = if turn == "white" {
103
    (("K", 8, (6, 7), (6, 7)), ("Q", 8, (2, 3, 4), (3, 4)))
104
  } else {
105
    (("k", 1, (6, 7), (6, 7)), ("q", 1, (2, 3, 4), (3, 4)))
106
  }
107
108
  for (flag, row, empty, check) in options {
109
    if castles.contains(flag) and can-castle(grid, row, empty, check, enemy) { return true }
110
  }
111
112
  false
113
}