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.

511 lines
17 KiB

  1. import tables
  2. from strutils import parseInt
  3. type
  4. Color* = enum
  5. Black = -1, White = 1
  6. ## Board that saves the pieces
  7. Pieces* = array[0..119, int]
  8. ## Board that checks if pieces moved
  9. Moved* = array[0..119, bool]
  10. ## Game as object of different values
  11. Game* = object
  12. pieces: Pieces
  13. moved: Moved
  14. to_move: Color
  15. const
  16. # IDs for piece
  17. BlockID* = 999
  18. PawnID* = 1
  19. KnightID* = 2
  20. BishopID* = 3
  21. RookID* = 4
  22. QueenID* = 5
  23. KingID* = 6
  24. EnPassantID* = 7
  25. # IDs that are saved in the array
  26. Block* = BlockID
  27. WPawn* = PawnID
  28. WKnight* = KnightID
  29. WBishop* = BishopID
  30. WRook* = RookID
  31. WQueen* = QueenID
  32. WKing* = KingID
  33. WEnPassant* = EnPassantID
  34. BPawn* = -PawnID
  35. BKnight* = -KnightID
  36. BBishop* = -BishopID
  37. BRook* = -RookID
  38. BQueen* = -QueenID
  39. BKing* = -KingID
  40. BEnPassant* = EnPassantID
  41. # Directions of movement
  42. N = 10
  43. S = -N
  44. W = 1
  45. E = -W
  46. # Movement options for pieces (Bishop/Rook/Queen can repeat in the same direction)
  47. 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]
  48. Bishop_Moves = [N+E, N+W, S+E, S+W]
  49. Rook_Moves = [N, E, S, W]
  50. Queen_Moves = [N, E, S, W, N+E, N+W, S+E, S+W]
  51. King_Moves = [N, E, S, W, N+E, N+W, S+E, S+W]
  52. Pawn_Moves_White = [N]
  53. Pawn_Moves_White_Double = [N+N]
  54. Pawn_Moves_White_Attack = [N+E, N+W]
  55. var PieceChar = {
  56. 0: " ",
  57. 1: "P",
  58. 2: "N",
  59. 3: "B",
  60. 4: "R",
  61. 5: "Q",
  62. 6: "K",
  63. 7: " ",
  64. -1: "p",
  65. -2: "n",
  66. -3: "b",
  67. -4: "r",
  68. -5: "q",
  69. -6: "k",
  70. -7: " ",
  71. 999: "-"
  72. }.newTable
  73. var FileChar = {
  74. "a": 7,
  75. "b": 6,
  76. "c": 5,
  77. "d": 4,
  78. "e": 3,
  79. "f": 2,
  80. "g": 1,
  81. "h": 0
  82. }.newTable
  83. proc init_board(): Pieces =
  84. ## Create and return a board with pieces in starting position.
  85. let board = [
  86. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  87. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  88. Block, WRook, WKnight, WBishop, WKing, WQueen, WBishop, WKnight, WRook, Block,
  89. Block, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, Block,
  90. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  91. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  92. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  93. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  94. Block, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, Block,
  95. Block, BRook, BKnight, BBishop, BKing, BQueen, BBishop, BKnight, BRook, Block,
  96. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  97. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block]
  98. return board
  99. proc init_moved(): Moved =
  100. ## Create and return a board of pieces moved.
  101. var moved: Moved
  102. return moved
  103. proc init_game*(): Game =
  104. ## Create and return a Game object.
  105. let game = Game(pieces: init_board(), moved: init_moved(),
  106. to_move: Color.White)
  107. return game
  108. proc get_field*(pieces: Pieces, field: int): int =
  109. return pieces[field]
  110. proc set_field(pieces: var Pieces, field: int, val: int): bool {.discardable.} =
  111. if (val in PieceChar):
  112. try:
  113. pieces[field] = val
  114. return true
  115. except Exception:
  116. return false
  117. proc get_field*(moved: Moved, field: int): bool =
  118. return moved[field]
  119. proc set_field(moved: var Moved, field: int, val: bool): bool {.discardable.} =
  120. try:
  121. moved[field] = val
  122. return true
  123. except Exception:
  124. return false
  125. proc echo_board*(game: Game, color: Color) =
  126. ## Prints out the given `board` with its pieces as characters and line indices from perspecive of `color`.
  127. var line_str = ""
  128. if (color == Color.Black):
  129. for i in countup(0, len(game.pieces)-1):
  130. if (game.pieces.get_field(i) == 999):
  131. continue
  132. line_str &= PieceChar[game.pieces[i]] & " "
  133. if ((i+2) %% 10 == 0):
  134. line_str &= $((int)((i)/10)-1) & "\n"
  135. echo line_str
  136. echo "h g f e d c b a"
  137. else:
  138. for i in countdown(len(game.pieces)-1, 0):
  139. if (game.pieces.get_field(i) == 999):
  140. continue
  141. line_str &= PieceChar[game.pieces[i]] & " "
  142. if ((i-1) %% 10 == 0):
  143. line_str &= $((int)((i)/10)-1) & "\n"
  144. echo line_str
  145. echo "a b c d e f g h"
  146. proc field_to_ind*(file: string, line: int): int =
  147. ## Calculate board index from `file` and `line` of a chess board.
  148. try:
  149. return 1+(line+1)*10+FileChar[file]
  150. except IndexDefect, ValueError:
  151. return -1
  152. proc field_to_ind*(field: string): int =
  153. ## Calculate board index from `field` of a chess board.
  154. try:
  155. return field_to_ind($field[0], parseInt($field[1]))
  156. except IndexDefect, ValueError:
  157. return -1
  158. proc ind_to_field*(ind: int): string =
  159. ## Calculate field name from board index `ind`.
  160. let line = (int)ind/10-1
  161. let file_ind = (ind)%%10-1
  162. for file, i in FileChar:
  163. if FileChar[file] == file_ind:
  164. return $file & $line
  165. proc gen_bishop_moves(game: Game, field: int, color: Color): seq[int] =
  166. ## Generate possible moves for a bishop with specific `color` located at index `field` of `board`.
  167. ## Returns a sequence of possible indices to move to.
  168. try:
  169. var res = newSeq[int]()
  170. var dest: int
  171. var target: int
  172. for move in Bishop_Moves:
  173. dest = field+move
  174. target = game.pieces.get_field(dest)
  175. while (target != 999 and (ord(color) * target <= 0) or target == EnPassantID):
  176. res.add(dest)
  177. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  178. break
  179. dest = dest+move
  180. target = game.pieces.get_field(dest)
  181. return res
  182. except IndexDefect:
  183. return @[]
  184. proc gen_rook_moves(game: Game, field: int, color: Color): seq[int] =
  185. ## Generate possible moves for a rook with specific `color` located at index `field` of `board`.
  186. ## Returns a sequence of possible indices to move to.
  187. try:
  188. var res = newSeq[int]()
  189. var dest: int
  190. var target: int
  191. for move in Rook_Moves:
  192. dest = field+move
  193. target = game.pieces.get_field(dest)
  194. while (target != 999 and (ord(color) * target <= 0) or target == EnPassantID):
  195. res.add(dest)
  196. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  197. break
  198. dest = dest+move
  199. target = game.pieces.get_field(dest)
  200. return res
  201. except IndexDefect:
  202. return @[]
  203. proc gen_queen_moves(game: Game, field: int, color: Color): seq[int] =
  204. ## Generate possible moves for a queen with specific `color` located at index `field` of `board`.
  205. ## Returns a sequence of possible indices to move to.
  206. try:
  207. var res = newSeq[int]()
  208. var dest: int
  209. var target: int
  210. for move in Queen_Moves:
  211. dest = field+move
  212. target = game.pieces.get_field(dest)
  213. while (target != 999 and (ord(color) * target <= 0) or target == EnPassantID):
  214. res.add(dest)
  215. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  216. break
  217. dest = dest+move
  218. target = game.pieces.get_field(dest)
  219. return res
  220. except IndexDefect:
  221. return @[]
  222. proc gen_king_moves(game: Game, field: int, color: Color): seq[int] =
  223. ## Generate possible moves for a king with specific `color` located at index `field` of `board`.
  224. ## Returns a sequence of possible indices to move to.
  225. try:
  226. var res = newSeq[int]()
  227. var dest: int
  228. var target: int
  229. for move in King_Moves:
  230. dest = field + move
  231. target = game.pieces.get_field(dest)
  232. if (target == 999 or (ord(color) * target > 0 and ord(color) * target != EnPassantID)):
  233. continue
  234. res.add(dest)
  235. return res
  236. except IndexDefect:
  237. return @[]
  238. proc gen_knight_moves(game: Game, field: int, color: Color): seq[int] =
  239. ## Generate possible moves for a knight with specific `color` located at index `field` of `board`.
  240. ## Returns a sequence of possible indices to move to.
  241. try:
  242. var res = newSeq[int]()
  243. var dest: int
  244. var target: int
  245. for move in Knight_Moves:
  246. dest = field + move
  247. target = game.pieces.get_field(dest)
  248. if (target == 999 or (ord(color) * target > 0 and ord(color) * target != EnPassantID)):
  249. continue
  250. res.add(dest)
  251. return res
  252. except IndexDefect:
  253. return @[]
  254. proc gen_pawn_attacks(game: Game, field: int, color: Color): seq[int] =
  255. ## Generate possible attacks for a pawn with specific `color` located at index `field` of `board`.
  256. ## Returns a sequence of possible indices to move to.
  257. try:
  258. var res = newSeq[int]()
  259. var dest: int
  260. var target: int
  261. for attacks in Pawn_Moves_White_Attack:
  262. dest = field + attacks * ord(color)
  263. target = game.pieces.get_field(dest)
  264. if (target == 999 or ord(color) * target >= 0):
  265. continue
  266. res.add(dest)
  267. return res
  268. except IndexDefect:
  269. return @[]
  270. proc gen_pawn_doubles(game: Game, field: int, color: Color): seq[int] =
  271. ## Generate possible double moves for a pawn with specific `color` located at index `field` of `board`.
  272. ## Returns a sequence of possible indices to move to.
  273. try:
  274. var res = newSeq[int]()
  275. var dest: int
  276. var target: int
  277. for doubles in Pawn_Moves_White_Double:
  278. dest = field + doubles * ord(color)
  279. target = game.pieces.get_field(dest)
  280. if (game.moved.get_field(field) or (target != 0) or (game.pieces.get_field(dest+(S*ord(color))) != 0)):
  281. continue
  282. res.add(dest)
  283. return res
  284. except IndexDefect:
  285. return @[]
  286. proc gen_pawn_moves(game: Game, field: int, color: Color): seq[int] =
  287. ## Generate possible moves for a pawn with specific `color` located at index `field` of `board`.
  288. ## Returns a sequence of possible indices to move to.
  289. try:
  290. var res = newSeq[int]()
  291. var dest: int
  292. var target: int
  293. for move in Pawn_Moves_White:
  294. dest = field + move * ord(color)
  295. target = game.pieces.get_field(dest)
  296. if (target != 0 and target != ord(color) * EnPassantID):
  297. continue
  298. res.add(dest)
  299. res.add(game.gen_pawn_attacks(field, color))
  300. res.add(game.gen_pawn_doubles(field, color))
  301. return res
  302. except IndexDefect:
  303. return @[]
  304. proc piece_on(game: Game, color: Color, sequence: seq[int],
  305. pieceID: int): bool =
  306. ## Check if a piece with `pieceID` of a given `color` is in a field described in a `sequence` in a `game`.
  307. for check in sequence:
  308. if game.pieces.get_field(check) == ord(color) * -1 * pieceID:
  309. return true
  310. return false
  311. proc is_attacked(game: Game, position: int, color: Color): bool =
  312. ## Check if a field is attacked by the opposite of `color` in a `game`.
  313. var attacked = false
  314. attacked = attacked or game.piece_on(color, game.gen_pawn_attacks(position,
  315. color), PawnID)
  316. attacked = attacked or game.piece_on(color, game.gen_queen_moves(position,
  317. color), QueenID)
  318. attacked = attacked or game.piece_on(color, game.gen_king_moves(position,
  319. color), KingID)
  320. attacked = attacked or game.piece_on(color, game.gen_rook_moves(position,
  321. color), RookID)
  322. attacked = attacked or game.piece_on(color, game.gen_bishop_moves(position,
  323. color), BishopID)
  324. attacked = attacked or game.piece_on(color, game.gen_knight_moves(position,
  325. color), KnightID)
  326. return attacked
  327. proc is_in_check(game: Game, color: Color): bool =
  328. ## Check if the King of a given `color` is in check in a `game`.
  329. var king_pos: int
  330. for i in countup(0, game.pieces.high):
  331. if game.pieces.get_field(i) == ord(color) * KingID:
  332. king_pos = i
  333. return game.is_attacked(king_pos, color)
  334. proc simple_move(game: var Game, start: int, dest: int): bool {.discardable.} =
  335. ## Moves a piece if possible from `start` position to `dest` position.
  336. ## Doesnt check boundaries, checks, movement.
  337. ## returns true if the piece moved, else false
  338. try:
  339. let piece = game.pieces.get_field(start)
  340. if game.pieces.set_field(start, 0):
  341. if game.pieces.set_field(dest, piece):
  342. game.moved.set_field(start, true)
  343. game.moved.set_field(dest, true)
  344. return true
  345. else:
  346. game.pieces.set_field(start, piece)
  347. except IndexDefect, ValueError:
  348. return false
  349. proc move_leads_to_check(game: Game, start: int, dest: int,
  350. color: Color): bool =
  351. ## Checks in a game if a move from `start` to `dest` puts the `color` king in check.
  352. var check = game
  353. check.simple_move(start, dest)
  354. return check.is_in_check(color)
  355. proc remove_en_passant(pieces: var Pieces, color: Color): void =
  356. ## Removes every en passant of given `color` from the `game`.
  357. for field in pieces.low..pieces.high:
  358. if pieces.get_field(field) == ord(color) * EnPassantID:
  359. pieces.set_field(field,0)
  360. proc checked_move*(game: var Game, start: int, dest: int, color: Color): bool {.discardable.} =
  361. ## Tries to make a move in a given `game` with the piece of a given `color` from `start` to `dest`.
  362. ## This process checks for the legality of the move and performs the switch of `game.to_move`
  363. try:
  364. if game.to_move != color:
  365. return false
  366. var sequence = newSeq[int]()
  367. let piece = game.pieces.get_field(start)
  368. var create_en_passant = false
  369. var captured_en_passant = false
  370. if (piece == PawnID * ord(color)):
  371. sequence.add(game.gen_pawn_moves(start, color))
  372. create_en_passant = dest in game.gen_pawn_doubles(start,color)
  373. captured_en_passant = (game.pieces.get_field(dest) == -1 * ord(color) * EnPassantID)
  374. if (piece == KnightID * ord(color)):
  375. sequence.add(game.gen_knight_moves(start, color))
  376. if (piece == BishopID * ord(color)):
  377. sequence.add(game.gen_bishop_moves(start, color))
  378. if (piece == RookID * ord(color)):
  379. sequence.add(game.gen_rook_moves(start, color))
  380. if (piece == QueenID * ord(color)):
  381. sequence.add(game.gen_queen_moves(start, color))
  382. if (piece == KingID * ord(color)):
  383. sequence.add(game.gen_king_moves(start, color))
  384. if (dest in sequence) and not game.move_leads_to_check(start, dest, color):
  385. game.pieces.remove_en_passant(color)
  386. game.simple_move(start, dest)
  387. game.to_move = Color(ord(game.to_move)*(-1))
  388. if create_en_passant:
  389. game.pieces.set_field(dest-(N*ord(color)),EnPassantID * ord(color))
  390. if captured_en_passant:
  391. game.pieces.set_field(dest-(N*ord(color)),0)
  392. return true
  393. except IndexDefect, ValueError:
  394. return false
  395. proc checked_promotion*(game: var Game, start: int, dest: int, color: Color,
  396. prom: int): bool {.discardable.} =
  397. ## Tries to make a promotion to `prom` in a given `game` with the piece of a given `color` from `start` to `dest`.
  398. ## This process checks for the legality of the move and performs the switch of `game.to_move`
  399. try:
  400. if game.pieces.get_field(start) != PawnID * ord(color) or (1 > prom or
  401. prom > 5):
  402. return false
  403. if (90 < dest and dest < 99) or (20 < dest and dest < 29):
  404. if (game.checked_move(start, dest, color)):
  405. game.pieces.set_field(dest, prom)
  406. return false
  407. except IndexDefect, ValueError:
  408. return false
  409. proc castling*(game: var Game, kstart: int, dest_kingside: bool,
  410. color: Color): bool {.discardable.} =
  411. ## Tries to castle in a given `game` with the king of a given `color` from `start`.
  412. ## `dest_kingside` for kingside castling, else castling is queenside.
  413. ## This process checks for the legality of the move and performs the switch of `game.to_move`
  414. try:
  415. if game.to_move != color:
  416. return false
  417. var kdest = kstart
  418. var rstart: int
  419. var rdest: int
  420. if (dest_kingside):
  421. kdest = kstart + (E+E) * ord(color)
  422. rstart = kstart + (E+E+E) * ord(color)
  423. rdest = rstart + (W+W) * ord(color)
  424. else:
  425. rstart = kstart + (W+W+W+W) * ord(color)
  426. rdest = rstart + (E+E+E) * ord(color)
  427. kdest = kstart + (W+W) * ord(color)
  428. if not game.moved.get_field(kstart) and not game.moved.get_field(rstart):
  429. var check = false
  430. if (dest_kingside):
  431. check = check or game.is_attacked(kstart, color)
  432. check = check or game.is_attacked(kstart+(E)*ord(color), color)
  433. check = check or game.is_attacked(kstart+(E+E)*ord(color), color)
  434. else:
  435. check = check or game.is_attacked(kstart, color)
  436. check = check or game.is_attacked(kstart+(W)*ord(color), color)
  437. check = check or game.is_attacked(kstart+(W+W)*ord(color), color)
  438. if check:
  439. return false
  440. game.simple_move(kstart, kdest)
  441. game.simple_move(rstart, rdest)
  442. return false
  443. except IndexDefect, ValueError:
  444. return false
  445. proc has_no_moves(game: Game, color: Color): bool =
  446. ## Checks if a player of a given `color` has no legal moves in a `game`.
  447. var sequence = newSeq[(int,int)]()
  448. for field_ind in game.pieces.low..game.pieces.high:
  449. var target = ord(color) * game.pieces.get_field(field_ind)
  450. if 0 < target and target < EnPassantID:
  451. var possibilities = newSeq[int]()
  452. case target:
  453. of PawnID:
  454. possibilities = game.gen_pawn_moves(field_ind, color)
  455. of KnightID:
  456. possibilities = game.gen_knight_moves(field_ind, color)
  457. of BishopID:
  458. possibilities = game.gen_bishop_moves(field_ind, color)
  459. of RookID:
  460. possibilities = game.gen_rook_moves(field_ind, color)
  461. of QueenID:
  462. possibilities = game.gen_queen_moves(field_ind, color)
  463. of KingID:
  464. possibilities = game.gen_king_moves(field_ind, color)
  465. else:
  466. continue
  467. for dest in possibilities:
  468. if (not game.move_leads_to_check(field_ind,dest,color)):
  469. return false
  470. return true
  471. proc is_checkmate*(game: Game, color: Color): bool =
  472. ## Checks if a player of a given `color` in a `game` is checkmate.
  473. return game.has_no_moves(color) and game.is_in_check(color)
  474. proc is_stalemate*(game: Game, color: Color): bool =
  475. ## Checks if a player of a given `color` in a `game` is stalemate.
  476. return game.has_no_moves(color) and not game.is_in_check(color)