ychess is a chess implementation written in nim.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

818 lines
27 KiB

  1. import tables
  2. from strutils import parseInt
  3. import algorithm
  4. type
  5. Color* = enum
  6. Black = -1, White = 1
  7. ## Board that saves the board
  8. Board* = array[0..119, int]
  9. ## Board that checks if pieces moved
  10. Moved* = array[0..119, bool]
  11. ## Castle rights for each player
  12. CastleRights = tuple
  13. wk: bool
  14. wq: bool
  15. bk: bool
  16. bq: bool
  17. ## Game as object of different values
  18. Game* = object
  19. board*: Board
  20. moved: Moved
  21. toMove*: Color
  22. previousBoard*: seq[Board]
  23. previousCastleRights*: seq[CastleRights]
  24. fiftyMoveCounter: int
  25. ## Move as object
  26. Move* = object
  27. start: int
  28. dest: int
  29. color: Color
  30. prom: int
  31. ## Amount of pieces of a player
  32. Pieces = tuple
  33. p: int
  34. k: int
  35. b: int
  36. r: int
  37. q: int
  38. const
  39. # IDs for piece
  40. BlockID* = 999
  41. PawnID* = 1
  42. KnightID* = 2
  43. BishopID* = 3
  44. RookID* = 4
  45. QueenID* = 5
  46. KingID* = 6
  47. EnPassantID* = 7
  48. # IDs that are saved in the array
  49. Block* = BlockID
  50. WPawn* = PawnID
  51. WKnight* = KnightID
  52. WBishop* = BishopID
  53. WRook* = RookID
  54. WQueen* = QueenID
  55. WKing* = KingID
  56. WEnPassant* = EnPassantID
  57. BPawn* = -PawnID
  58. BKnight* = -KnightID
  59. BBishop* = -BishopID
  60. BRook* = -RookID
  61. BQueen* = -QueenID
  62. BKing* = -KingID
  63. BEnPassant* = EnPassantID
  64. # Directions of movement
  65. N = 10
  66. S = -N
  67. W = 1
  68. E = -W
  69. # Movement options for pieces (Bishop/Rook/Queen can repeat in the same direction)
  70. Knight_Moves = [N+N+E, N+N+W, E+E+N, E+E+S, S+S+E, S+S+W, W+W+N, W+W+S]
  71. Bishop_Moves = [N+E, N+W, S+E, S+W]
  72. Rook_Moves = [N, E, S, W]
  73. Queen_Moves = [N, E, S, W, N+E, N+W, S+E, S+W]
  74. King_Moves = [N, E, S, W, N+E, N+W, S+E, S+W]
  75. King_Moves_White_Castle = [E+E, W+W]
  76. Pawn_Moves_White = [N]
  77. Pawn_Moves_White_Double = [N+N]
  78. Pawn_Moves_White_Attack = [N+E, N+W]
  79. let PieceChar = {
  80. 0: " ",
  81. 1: "P",
  82. 2: "N",
  83. 3: "B",
  84. 4: "R",
  85. 5: "Q",
  86. 6: "K",
  87. 7: " ",
  88. -1: "p",
  89. -2: "n",
  90. -3: "b",
  91. -4: "r",
  92. -5: "q",
  93. -6: "k",
  94. -7: " ",
  95. 999: "-"
  96. }.newTable
  97. let FileChar = {
  98. "a": 7,
  99. "b": 6,
  100. "c": 5,
  101. "d": 4,
  102. "e": 3,
  103. "f": 2,
  104. "g": 1,
  105. "h": 0
  106. }.newTable
  107. const InsufficientMaterial = @[
  108. #p, n, b, r, q
  109. # lone kings
  110. (0, 0, 0, 0, 0),
  111. # knight only
  112. (0, 0, 1, 0, 0),
  113. # bishop only
  114. (0, 1, 0, 0, 0),
  115. # 2 knights
  116. (0, 2, 0, 0, 0)
  117. ]
  118. proc getField*(board: Board, field: int): int =
  119. return board[field]
  120. proc setField(board: var Board, field: int, val: int): bool {.discardable.} =
  121. if (val in PieceChar):
  122. try:
  123. board[field] = val
  124. return true
  125. except Exception:
  126. return false
  127. proc getField*(moved: Moved, field: int): bool =
  128. return moved[field]
  129. proc setField(moved: var Moved, field: int, val: bool): bool {.discardable.} =
  130. try:
  131. moved[field] = val
  132. return true
  133. except Exception:
  134. return false
  135. proc checkInsufficientMaterial(board: Board): bool =
  136. ## Checks for combinations of pieces on a `board`, where no checkmate can be forced
  137. var wp = 0
  138. var wn = 0
  139. var wb = 0
  140. var wr = 0
  141. var wq = 0
  142. var bp = 0
  143. var bn = 0
  144. var bb = 0
  145. var br = 0
  146. var bq = 0
  147. for field in board.low..board.high:
  148. case board.getField(field):
  149. of WPawn:
  150. wp = wp + 1
  151. of BPawn:
  152. bp = bp + 1
  153. of WKnight:
  154. wn = wn + 1
  155. of BKnight:
  156. bn = bn + 1
  157. of WBishop:
  158. wb = wb + 1
  159. of BBishop:
  160. bb = bb + 1
  161. of WRook:
  162. wr = wr + 1
  163. of BRook:
  164. br = br + 1
  165. of WQueen:
  166. wq = wq + 1
  167. of BQueen:
  168. bq = bq + 1
  169. else:
  170. continue
  171. let wpieces = (wp, wn, wb, wr, wq)
  172. let bpieces = (bp, bn, bb, br, bq)
  173. return (wpieces in InsufficientMaterial) and (bpieces in InsufficientMaterial)
  174. proc initBoard(): Board =
  175. ## Create and return a board with pieces in starting position.
  176. let board = [
  177. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  178. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  179. Block, WRook, WKnight, WBishop, WKing, WQueen, WBishop, WKnight, WRook, Block,
  180. Block, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, Block,
  181. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  182. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  183. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  184. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  185. Block, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, Block,
  186. Block, BRook, BKnight, BBishop, BKing, BQueen, BBishop, BKnight, BRook, Block,
  187. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  188. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block]
  189. return board
  190. proc initBoard(board: array[0..63, int]): Board =
  191. ## Create and return a board with pieces in position of choice
  192. let board = [
  193. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  194. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  195. Block, board[0], board[1], board[2], board[3], board[4], board[5],
  196. board[6], board[7], Block,
  197. Block, board[8], board[9], board[10], board[11], board[12], board[13],
  198. board[14], board[15], Block,
  199. Block, board[16], board[17], board[18], board[19], board[20], board[
  200. 21], board[22], board[23], Block,
  201. Block, board[24], board[25], board[26], board[27], board[28], board[
  202. 29], board[30], board[31], Block,
  203. Block, board[32], board[33], board[34], board[35], board[36], board[
  204. 37], board[38], board[39], Block,
  205. Block, board[40], board[41], board[42], board[43], board[44], board[
  206. 45], board[46], board[47], Block,
  207. Block, board[48], board[49], board[50], board[51], board[52], board[
  208. 53], board[54], board[55], Block,
  209. Block, board[56], board[57], board[58], board[59], board[60], board[
  210. 61], board[62], board[63], Block,
  211. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  212. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block]
  213. return board
  214. proc initMoved(): Moved =
  215. ## Create and return a board of pieces moved.
  216. var moved: Moved
  217. return moved
  218. proc initGame*(): Game =
  219. ## Create and return a Game object.
  220. let game = Game(board: initBoard(), moved: initMoved(),
  221. to_move: Color.White, previousBoard: @[], previousCastleRights: @[], fiftyMoveCounter: 0)
  222. return game
  223. proc initGame*(board: array[0..63, int], color: Color): Game =
  224. ## Create ad return a Game object based on a position of choice.
  225. let board = initBoard(board)
  226. let compare = initBoard()
  227. var moved = initMoved()
  228. var same_piece: bool
  229. for ind in board.low..board.high:
  230. same_piece = (board[ind] != compare[ind])
  231. moved.setField(ind, same_piece)
  232. let game = Game(board: board, moved: moved,
  233. to_move: color, previousBoard: @[], previousCastleRights: @[], fiftyMoveCounter: 0)
  234. return game
  235. proc getMove*(start: int, dest: int, prom: int, color: Color): Move =
  236. ## Get a move object from `start` to `dest` with an eventual promition to `prom`
  237. var move = Move(start: start, dest: dest, prom: prom * ord(color), color: color)
  238. if (KnightID > prom or QueenID < prom):
  239. move.prom = QueenID
  240. return move
  241. proc getMove*(start: int, dest: int, color: Color): Move =
  242. ## Get a move object from `start` to `dest` with automatic promition to `queen`
  243. var move = Move(start: start, dest: dest, prom: QueenID * ord(color), color: color)
  244. return move
  245. proc echoBoard*(game: Game, color: Color) =
  246. ## Prints out the given `board` with its pieces as characters and line indices from perspecive of `color`.
  247. var line_str = ""
  248. if (color == Color.Black):
  249. for i in countup(0, len(game.board)-1):
  250. if (game.board.getField(i) == 999):
  251. continue
  252. line_str &= PieceChar[game.board[i]] & " "
  253. if ((i+2) %% 10 == 0):
  254. line_str &= $((int)((i)/10)-1) & "\n"
  255. echo line_str
  256. echo "h g f e d c b a"
  257. else:
  258. for i in countdown(len(game.board)-1, 0):
  259. if (game.board.getField(i) == 999):
  260. continue
  261. line_str &= PieceChar[game.board[i]] & " "
  262. if ((i-1) %% 10 == 0):
  263. line_str &= $((int)((i)/10)-1) & "\n"
  264. echo line_str
  265. echo "a b c d e f g h"
  266. proc fieldToInd*(file: string, line: int): int =
  267. ## Calculate board index from `file` and `line` of a chess board.
  268. try:
  269. return 1+(line+1)*10+FileChar[file]
  270. except IndexDefect, ValueError:
  271. return -1
  272. proc fieldToInd*(field: string): int =
  273. ## Calculate board index from `field` of a chess board.
  274. try:
  275. return fieldToInd($field[0], parseInt($field[1]))
  276. except IndexDefect, ValueError:
  277. return -1
  278. proc indToField*(ind: int): string =
  279. ## Calculate field name from board index `ind`.
  280. let line = (int)ind/10-1
  281. let file_ind = (ind)%%10-1
  282. for file, i in FileChar:
  283. if FileChar[file] == file_ind:
  284. return $file & $line
  285. proc genCastleRights(moved: Moved): CastleRights =
  286. ## Generate rights to castle from given `moved`
  287. let wk = not moved.getField(fieldToInd("e1")) and not moved.getField(
  288. fieldToInd("h1"))
  289. let wq = not moved.getField(fieldToInd("e1")) and not moved.getField(
  290. fieldToInd("a1"))
  291. let bk = not moved.getField(fieldToInd("e8")) and not moved.getField(
  292. fieldToInd("h8"))
  293. let bq = not moved.getField(fieldToInd("e8")) and not moved.getField(
  294. fieldToInd("a8"))
  295. return (wk, wq, bk, bq)
  296. proc notationToMove*(notation: string, color: Color): Move =
  297. ## Convert simplified algebraic chess `notation` to a move object, color of player is `color`.
  298. try:
  299. var move: Move
  300. var start = fieldToInd(notation[0..1])
  301. var dest = fieldToInd(notation[2..3])
  302. move = getMove(start, dest, color)
  303. if (len(notation) > 4):
  304. var promStr = $notation[4]
  305. var prom: int
  306. case promStr:
  307. of "Q":
  308. prom = QueenID * ord(color)
  309. of "R":
  310. prom = RookID * ord(color)
  311. of "B":
  312. prom = BishopID * ord(color)
  313. of "N":
  314. prom = KnightID * ord(color)
  315. move = getMove(start, dest, prom, color)
  316. return move
  317. except IndexError:
  318. var move: Move
  319. return move
  320. proc genBishopDests(game: Game, field: int, color: Color): seq[int] =
  321. ## Generate possible destinations for a bishop with specific `color` located at index `field` of `game`.
  322. ## Returns a sequence of possible indices to move to.
  323. try:
  324. var res = newSeq[int]()
  325. var dest: int
  326. var target: int
  327. for move in Bishop_Moves:
  328. dest = field+move
  329. target = game.board.getField(dest)
  330. while (target != 999 and (ord(color) * target <= 0) or target ==
  331. EnPassantID or target == -EnPassantID):
  332. res.add(dest)
  333. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  334. break
  335. dest = dest+move
  336. target = game.board.getField(dest)
  337. return res
  338. except IndexDefect:
  339. return @[]
  340. proc genRookDests(game: Game, field: int, color: Color): seq[int] =
  341. ## Generate possible destinations for a rook with specific `color` located at index `field` of `game`.
  342. ## Returns a sequence of possible indices to move to.
  343. try:
  344. var res = newSeq[int]()
  345. var dest: int
  346. var target: int
  347. for move in Rook_Moves:
  348. dest = field+move
  349. target = game.board.getField(dest)
  350. while (target != 999 and (ord(color) * target <= 0) or target ==
  351. EnPassantID or target == -EnPassantID):
  352. res.add(dest)
  353. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  354. break
  355. dest = dest+move
  356. target = game.board.getField(dest)
  357. return res
  358. except IndexDefect:
  359. return @[]
  360. proc genQueenDests(game: Game, field: int, color: Color): seq[int] =
  361. ## Generate possible destinations for a queen with specific `color` located at index `field` of `game`.
  362. ## Returns a sequence of possible indices to move to.
  363. try:
  364. var res = newSeq[int]()
  365. var dest: int
  366. var target: int
  367. for move in Queen_Moves:
  368. dest = field+move
  369. target = game.board.getField(dest)
  370. while (target != 999 and (ord(color) * target <= 0) or target ==
  371. EnPassantID or target == -EnPassantID):
  372. res.add(dest)
  373. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  374. break
  375. dest = dest+move
  376. target = game.board.getField(dest)
  377. return res
  378. except IndexDefect:
  379. return @[]
  380. proc genKingCastleDest(game: Game, field: int, color: Color): seq[int] =
  381. ## Generate possible castle destinations for a king with specific `color` located at index `field` of `game`
  382. ## Returns a sequence of possible indices to move to.
  383. try:
  384. var res = newSeq[int]()
  385. var dest: int
  386. var target: int
  387. var half_dest: int
  388. var half_target: int
  389. for castle in King_Moves_White_Castle:
  390. dest = field + castle
  391. target = game.board.getField(dest)
  392. half_dest = field + (int)castle/2
  393. half_target = game.board.getField(half_dest)
  394. if (target == 999 or (target != 0)):
  395. continue
  396. if (half_target == 999 or (half_target != 0)):
  397. continue
  398. res.add(dest)
  399. return res
  400. except IndexDefect:
  401. return @[]
  402. proc genKingDests(game: Game, field: int, color: Color): seq[int] =
  403. ## Generate possible destinations for a king with specific `color` located at index `field` of `game`.
  404. ## Returns a sequence of possible indices to move to.
  405. try:
  406. var res = newSeq[int]()
  407. var dest: int
  408. var target: int
  409. for move in King_Moves:
  410. dest = field + move
  411. target = game.board.getField(dest)
  412. if (target == 999 or (ord(color) * target > 0 and ord(color) * target != EnPassantID)):
  413. continue
  414. res.add(dest)
  415. res.add(game.genKingCastleDest(field, color))
  416. return res
  417. except IndexDefect:
  418. return @[]
  419. proc genKnightDests(game: Game, field: int, color: Color): seq[int] =
  420. ## Generate possible destinations for a knight with specific `color` located at index `field` of `game`.
  421. ## Returns a sequence of possible indices to move to.
  422. try:
  423. var res = newSeq[int]()
  424. var dest: int
  425. var target: int
  426. for move in Knight_Moves:
  427. dest = field + move
  428. target = game.board.getField(dest)
  429. if (target == 999 or (ord(color) * target > 0 and ord(color) * target != EnPassantID)):
  430. continue
  431. res.add(dest)
  432. return res
  433. except IndexDefect:
  434. return @[]
  435. proc genPawnAttackDests(game: Game, field: int, color: Color): seq[int] =
  436. ## Generate possible attack destinations for a pawn with specific `color` located at index `field` of `game`.
  437. ## Returns a sequence of possible indices to move to.
  438. try:
  439. var res = newSeq[int]()
  440. var dest: int
  441. var target: int
  442. for attacks in Pawn_Moves_White_Attack:
  443. dest = field + (attacks * ord(color))
  444. target = game.board.getField(dest)
  445. if (target == 999 or ord(color) * target >= 0):
  446. continue
  447. res.add(dest)
  448. return res
  449. except IndexDefect:
  450. return @[]
  451. proc genPawnDoubleDests(game: Game, field: int, color: Color): seq[int] =
  452. ## Generate possible double destinations for a pawn with specific `color` located at index `field` of `game`.
  453. ## Returns a sequence of possible indices to move to.
  454. try:
  455. var res = newSeq[int]()
  456. var dest: int
  457. var target: int
  458. for doubles in Pawn_Moves_White_Double:
  459. dest = field + doubles * ord(color)
  460. target = game.board.getField(dest)
  461. if (game.moved.getField(field) or (target != 0) or (
  462. game.board.getField(dest+(S*ord(color))) != 0)):
  463. continue
  464. res.add(dest)
  465. return res
  466. except IndexDefect:
  467. return @[]
  468. proc genPawnDests(game: Game, field: int, color: Color): seq[int] =
  469. ## Generate possible destinations for a pawn with specific `color` located at index `field` of `game`.
  470. ## Returns a sequence of possible indices to move to.
  471. try:
  472. var res = newSeq[int]()
  473. var dest: int
  474. var target: int
  475. for move in Pawn_Moves_White:
  476. dest = field + move * ord(color)
  477. target = game.board.getField(dest)
  478. if (target != 0 and target != ord(color) * EnPassantID):
  479. continue
  480. res.add(dest)
  481. res.add(game.genPawnAttackDests(field, color))
  482. res.add(game.genPawnDoubleDests(field, color))
  483. return res
  484. except IndexDefect:
  485. return @[]
  486. proc pieceOn(game: Game, color: Color, sequence: seq[int],
  487. pieceID: int): bool =
  488. ## Check if a piece with `pieceID` of a given `color` is in a field described in a `sequence` in a `game`.
  489. for check in sequence:
  490. if game.board.getField(check) == ord(color) * -1 * pieceID:
  491. return true
  492. return false
  493. proc isAttacked(game: Game, position: int, color: Color): bool =
  494. ## Check if a field is attacked by the opposite of `color` in a `game`.
  495. var attacked = false
  496. attacked = attacked or game.pieceOn(color, game.genPawnAttackDests(
  497. position, color), PawnID)
  498. attacked = attacked or game.pieceOn(color, game.genQueenDests(position,
  499. color), QueenID)
  500. attacked = attacked or game.pieceOn(color, game.genKingDests(position,
  501. color), KingID)
  502. attacked = attacked or game.pieceOn(color, game.genRookDests(position,
  503. color), RookID)
  504. attacked = attacked or game.pieceOn(color, game.genBishopDests(position,
  505. color), BishopID)
  506. attacked = attacked or game.pieceOn(color, game.genKnightDests(position,
  507. color), KnightID)
  508. return attacked
  509. proc isInCheck*(game: Game, color: Color): bool =
  510. ## Check if the King of a given `color` is in check in a `game`.
  511. var king_pos: int
  512. for i in countup(0, game.board.high):
  513. if game.board.getField(i) == ord(color) * KingID:
  514. king_pos = i
  515. return game.isAttacked(king_pos, color)
  516. proc uncheckedMove(game: var Game, start: int, dest: int): bool {.discardable.} =
  517. ## Moves a piece if possible from `start` position to `dest` position.
  518. ## Doesnt check boundaries, checks, movement.
  519. ## returns true if the piece moved, else false
  520. try:
  521. let piece = game.board.getField(start)
  522. if game.board.setField(start, 0):
  523. if game.board.setField(dest, piece):
  524. game.moved.setField(start, true)
  525. game.moved.setField(dest, true)
  526. return true
  527. else:
  528. game.board.setField(start, piece)
  529. except IndexDefect, ValueError:
  530. return false
  531. proc moveLeadsToCheck(game: Game, start: int, dest: int,
  532. color: Color): bool =
  533. ## Checks in a `game` if a move from `start` to `dest` puts the `color` king in check.
  534. var check = game
  535. check.uncheckedMove(start, dest)
  536. return check.isInCheck(color)
  537. proc removeEnPassant(board: var Board, color: Color): void =
  538. ## Removes every en passant of given `color` from the `game`.
  539. for field in board.low..board.high:
  540. if board.getField(field) == ord(color) * EnPassantID:
  541. board.setField(field, 0)
  542. proc genLegalKnightMoves(game: Game, field: int, color: Color): seq[Move] =
  543. ## Generates all legal knight moves starting from `field` in a `game` for a `color`.
  544. if game.board.getField(field) != KnightID * ord(color):
  545. return @[]
  546. var res = newSeq[Move]()
  547. var moves = game.genKnightDests(field, color)
  548. for dest in moves:
  549. if not game.moveLeadsToCheck(field, dest, color):
  550. res.add(getMove(field, dest, color))
  551. return res
  552. proc genLegalBishopMoves(game: Game, field: int, color: Color): seq[Move] =
  553. ## Generates all legal bishop moves starting from `field` in a `game` for a `color`.
  554. if game.board.getField(field) != BishopID * ord(color):
  555. return @[]
  556. var res = newSeq[Move]()
  557. var moves = game.genBishopDests(field, color)
  558. for dest in moves:
  559. if not game.moveLeadsToCheck(field, dest, color):
  560. res.add(getMove(field, dest, color))
  561. return res
  562. proc genLegalRookMoves(game: Game, field: int, color: Color): seq[Move] =
  563. ## Generates all legal rook moves starting from `field` in a `game` for a `color`.
  564. if game.board.getField(field) != RookID * ord(color):
  565. return @[]
  566. var res = newSeq[Move]()
  567. var moves = game.genRookDests(field, color)
  568. for dest in moves:
  569. if not game.moveLeadsToCheck(field, dest, color):
  570. res.add(getMove(field, dest, color))
  571. return res
  572. proc genLegalQueenMoves(game: Game, field: int, color: Color): seq[Move] =
  573. ## Generates all legal queen moves starting from `field` in a `game` for a `color`.
  574. if game.board.getField(field) != QueenID * ord(color):
  575. return @[]
  576. var res = newSeq[Move]()
  577. var moves = game.genQueenDests(field, color)
  578. for dest in moves:
  579. if not game.moveLeadsToCheck(field, dest, color):
  580. res.add(getMove(field, dest, color))
  581. return res
  582. proc genLegalKingMoves(game: Game, field: int, color: Color): seq[Move] =
  583. ## Generates all legal king moves starting from `field` in a `game` for a `color`.
  584. if game.board.getField(field) != KingID * ord(color):
  585. return @[]
  586. var res = newSeq[Move]()
  587. var moves = game.genKingDests(field, color)
  588. for dest in moves:
  589. if field - dest == W+W and game.isAttacked(dest+W, color):
  590. continue
  591. if field - dest == E+E and game.isAttacked(dest+E, color):
  592. continue
  593. if not game.moveLeadsToCheck(field, dest, color):
  594. res.add(getMove(field, dest, color))
  595. return res
  596. proc genPawnPromotion(move: Move, color: Color): seq[Move] =
  597. ## Generate all possible promotions of a `move` by `color`.
  598. var promotions = newSeq[Move]()
  599. let start = move.start
  600. let dest = move.dest
  601. if (90 < dest and dest < 99) or (20 < dest and dest < 29):
  602. for piece in KnightID..QueenID:
  603. promotions.add(getMove(start, dest, piece, color))
  604. return promotions
  605. proc genLegalPawnMoves(game: Game, field: int, color: Color): seq[Move] =
  606. ## Generates all legal pawn moves starting from `field` in a `game` for a `color`.
  607. if game.board.getField(field) != PawnID * ord(color):
  608. return @[]
  609. var res = newSeq[Move]()
  610. var moves = game.genPawnDests(field, color)
  611. for dest in moves:
  612. if not game.moveLeadsToCheck(field, dest, color):
  613. var promotions = genPawnPromotion(getMove(field, dest, color), color)
  614. if promotions != @[]:
  615. res.add(promotions)
  616. else:
  617. res.add(getMove(field, dest, color))
  618. return res
  619. proc genLegalMoves*(game: Game, field: int, color: Color): seq[Move] =
  620. ## Generates all legal moves starting from `field` in a `game` for a `color`.
  621. var legal_moves = newSeq[Move]()
  622. var target = ord(color) * game.board.getField(field)
  623. if 0 < target and target < EnPassantID:
  624. legal_moves = case target:
  625. of PawnID:
  626. game.genLegalPawnMoves(field, color)
  627. of KnightID:
  628. game.genLegalKnightMoves(field, color)
  629. of BishopID:
  630. game.genLegalBishopMoves(field, color)
  631. of RookID:
  632. game.genLegalRookMoves(field, color)
  633. of QueenID:
  634. game.genLegalQueenMoves(field, color)
  635. of KingID:
  636. game.genLegalKingMoves(field, color)
  637. else:
  638. @[]
  639. return legal_moves
  640. proc genLegalMoves*(game: Game, color: Color): seq[Move] =
  641. ## Generates all legal moves in a `game` for a `color`.
  642. var legal_moves = newSeq[Move]()
  643. for field in game.board.low..game.board.high:
  644. legal_moves.add(game.genLegalMoves(field, color))
  645. return legal_moves
  646. proc castling(game: var Game, kstart: int, dest_kingside: bool,
  647. color: Color): bool {.discardable.} =
  648. ## Tries to castle in a given `game` with the king of a given `color` from `start`.
  649. ## `dest_kingside` for kingside castling, else castling is queenside.
  650. ## This process checks for the legality of the move and performs the switch of `game.to_move`
  651. try:
  652. if game.toMove != color:
  653. return false
  654. var kdest = kstart
  655. var rstart: int
  656. var rdest: int
  657. if (dest_kingside):
  658. kdest = kstart + (E+E)
  659. rstart = kstart + (E+E+E)
  660. rdest = rstart + (W+W)
  661. else:
  662. rstart = kstart + (W+W+W+W)
  663. rdest = rstart + (E+E+E)
  664. kdest = kstart + (W+W)
  665. if not game.moved.getField(kstart) and not game.moved.getField(rstart):
  666. var check = false
  667. if (dest_kingside):
  668. check = check or game.isAttacked(kstart, color)
  669. check = check or game.isAttacked(kstart+(E), color)
  670. check = check or game.isAttacked(kstart+(E+E), color)
  671. else:
  672. check = check or game.isAttacked(kstart, color)
  673. check = check or game.isAttacked(kstart+(W), color)
  674. check = check or game.isAttacked(kstart+(W+W), color)
  675. if check:
  676. return false
  677. game.uncheckedMove(kstart, kdest)
  678. game.uncheckedMove(rstart, rdest)
  679. game.toMove = Color(ord(game.toMove)*(-1))
  680. return true
  681. return false
  682. except IndexDefect, ValueError:
  683. return false
  684. proc getPrevBoard*(game: Game): seq[Board] =
  685. return game.previousBoard
  686. proc checkedMove*(game: var Game, move: Move): bool {.discardable.} =
  687. ## Tries to make a move in a given `game` with the piece of a given `color` from `start` to `dest`.
  688. ## This process checks for the legality of the move and performs the switch of `game.to_move`
  689. try:
  690. let start = move.start
  691. let dest = move.dest
  692. let color = move.color
  693. let prom = move.prom
  694. if game.toMove != color:
  695. return false
  696. var sequence = newSeq[Move]()
  697. let piece = game.board.getField(start)
  698. var createEnPassant = false
  699. var capturedEnPassant = false
  700. var fiftyMoveRuleReset = false
  701. var move: Move
  702. move = getMove(start, dest, color)
  703. if (piece == PawnID * ord(color)):
  704. createEnPassant = dest in game.genPawnDoubleDests(start, color)
  705. capturedEnPassant = (game.board.getField(dest) == -1 * ord(color) * EnPassantID)
  706. fiftyMoveRuleReset = true
  707. if (game.board.getField(move.dest) != 0):
  708. fiftyMoveRuleReset = true
  709. sequence.add(game.genLegalMoves(start, color))
  710. if (move in sequence):
  711. game.board.removeEnPassant(color)
  712. if (piece == KingID * ord(color) and (start - dest == (W+W))):
  713. return game.castling(start, true, color)
  714. elif (piece == KingID * ord(color) and (start - dest == (E+E))):
  715. return game.castling(start, false, color)
  716. else:
  717. game.uncheckedMove(start, dest)
  718. game.toMove = Color(ord(game.toMove)*(-1))
  719. if createEnPassant:
  720. game.board.setField(dest-(N*ord(color)), EnPassantID * ord(color))
  721. if capturedEnPassant:
  722. game.board.setField(dest-(N*ord(color)), 0)
  723. if ((90 < dest and dest < 99) or (20 < dest and dest < 29)) and
  724. game.board.getField(dest) == PawnID * ord(color):
  725. game.board.setField(dest, prom)
  726. var prevBoard = game.previousBoard
  727. var prevCastle = game.previousCastleRights
  728. game.previousBoard.add(game.board)
  729. game.previousCastleRights.add(game.moved.genCastleRights())
  730. game.fiftyMoveCounter = game.fiftyMoveCounter + 1
  731. if fiftyMoveRuleReset:
  732. game.fiftyMoveCounter = 0
  733. return true
  734. except IndexDefect, ValueError:
  735. return false
  736. proc hasNoMoves(game: Game, color: Color): bool =
  737. ## Checks if a player of a given `color` has no legal moves in a `game`.
  738. return (game.genLegalMoves(color) == @[])
  739. proc isCheckmate*(game: Game, color: Color): bool =
  740. ## Checks if a player of a given `color` in a `game` is checkmate.
  741. return game.hasNoMoves(color) and game.isInCheck(color)
  742. proc threeMoveRep(game: Game): bool =
  743. ## Checks if a `rep`-times repitition happened on the last move of the `game`.
  744. var lastState = game.previousBoard[game.previousBoard.high]
  745. var lastCastleRights = game.previousCastleRights[game.previousBoard.high]
  746. var reps = 0
  747. for stateInd in (game.previousBoard.low)..(game.previousBoard.high):
  748. if (game.previousBoard[stateInd] == lastState and game.previousCastleRights[
  749. stateInd] == lastCastleRights):
  750. reps = reps + 1
  751. return reps >= 3
  752. proc fiftyMoveRule(game: Game): bool =
  753. ## Checks if a draw with the fifty move rule is available in a `game`.
  754. return game.fiftyMoveCounter >= 100
  755. proc isDrawClaimable*(game: Game): bool =
  756. ## Checks if a draw is claimable by either player.
  757. return game.threeMoveRep() or game.fiftyMoveRule()
  758. proc isStalemate*(game: Game, color: Color): bool =
  759. ## Checks if a player of a given `color` in a `game` is stalemate.
  760. return (game.hasNoMoves(color) and not game.isInCheck(color)) or
  761. game.board.checkInsufficientMaterial()