mirror of
				https://github.com/tiyn/yeschess.git
				synced 2025-11-04 12:21:15 +01:00 
			
		
		
		
	engine: first push for engine
I created an engine, that uses a basic version of the minimax algorithm to evaluate a position. It then evaluates all the possible next moves in a given position and picks the one that has the best evaluation. Basic test cases were also added.
This commit is contained in:
		@@ -24,8 +24,8 @@ type
 | 
				
			|||||||
    castleRights: CastleRights
 | 
					    castleRights: CastleRights
 | 
				
			||||||
  Move* = object
 | 
					  Move* = object
 | 
				
			||||||
    ## `Move` stores all important information for a move.
 | 
					    ## `Move` stores all important information for a move.
 | 
				
			||||||
    start: int
 | 
					    start*: int
 | 
				
			||||||
    dest: int
 | 
					    dest*: int
 | 
				
			||||||
    color: Color
 | 
					    color: Color
 | 
				
			||||||
    prom: int
 | 
					    prom: int
 | 
				
			||||||
  PieceAmount = tuple
 | 
					  PieceAmount = tuple
 | 
				
			||||||
@@ -136,7 +136,7 @@ let
 | 
				
			|||||||
    "e": 3,
 | 
					    "e": 3,
 | 
				
			||||||
    "f": 2,
 | 
					    "f": 2,
 | 
				
			||||||
    "g": 1,
 | 
					    "g": 1,
 | 
				
			||||||
    "h": 0
 | 
					    "h": 0,
 | 
				
			||||||
  }.newTable ## \
 | 
					  }.newTable ## \
 | 
				
			||||||
  # `FileChar` maps the files of the chessboard to numbers for better
 | 
					  # `FileChar` maps the files of the chessboard to numbers for better
 | 
				
			||||||
  # conversion.
 | 
					  # conversion.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										111
									
								
								src/engine.nim
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/engine.nim
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					import sequtils
 | 
				
			||||||
 | 
					import ./chess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type
 | 
				
			||||||
 | 
					  MoveTree* = object
 | 
				
			||||||
 | 
					    ## `Movetree` is a visualization for possible moves.
 | 
				
			||||||
 | 
					    game*: Game
 | 
				
			||||||
 | 
					    evaluation: float
 | 
				
			||||||
 | 
					    children*: seq[Movetree]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const
 | 
				
			||||||
 | 
					  PawnVal = 1           ## `PawnVal` is the engines value for a pawn.
 | 
				
			||||||
 | 
					  KnightVal = 3         ## `KnightVal` is the engines value for a knight.
 | 
				
			||||||
 | 
					  BishopVal = 3         ## `BishopVal` is the engines value for a bishop.
 | 
				
			||||||
 | 
					  RookVal = 5           ## `RookVal` is the engines value for a rook.
 | 
				
			||||||
 | 
					  QueenVal = 9          ## `QueenVal` is the engines value for a queen.
 | 
				
			||||||
 | 
					  CheckmateVal = 1000   ## `CheckmateVal` is the engines value for a checkmate.
 | 
				
			||||||
 | 
					  DrawVal = 0           ## `DrawVal` is the engines value for a draw.
 | 
				
			||||||
 | 
					  HiVal = 1000000       ## `HiVal` is the highest possible value (used in minimax).
 | 
				
			||||||
 | 
					  LoVal = -HiVal        ## `LoVal` is the lowest possible value (used in minimax).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					proc pieceEval*(game: Game): int =
 | 
				
			||||||
 | 
					  ## Returns the evaluation of existing pieces on the `board`
 | 
				
			||||||
 | 
					  var evaluation = DrawVal
 | 
				
			||||||
 | 
					  for square in game.board:
 | 
				
			||||||
 | 
					    case square:
 | 
				
			||||||
 | 
					      of WPawn:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.White) * PawnVal
 | 
				
			||||||
 | 
					      of WKnight:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.White) * KnightVal
 | 
				
			||||||
 | 
					      of WBishop:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.White) * BishopVal
 | 
				
			||||||
 | 
					      of WRook:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.White) * RookVal
 | 
				
			||||||
 | 
					      of WQueen:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.White) * QueenVal
 | 
				
			||||||
 | 
					      of BPawn:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.Black) * PawnVal
 | 
				
			||||||
 | 
					      of BKnight:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.Black) * KnightVal
 | 
				
			||||||
 | 
					      of BBishop:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.Black) * BishopVal
 | 
				
			||||||
 | 
					      of BRook:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.Black) * RookVal
 | 
				
			||||||
 | 
					      of BQueen:
 | 
				
			||||||
 | 
					        evaluation += ord(Color.Black) * QueenVal
 | 
				
			||||||
 | 
					      else:
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					  return evaluation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					proc evaluate(game: Game): int =
 | 
				
			||||||
 | 
					  ## Returns a complete evaluation of a `game` with player `toMove` about to make
 | 
				
			||||||
 | 
					  ## a move.
 | 
				
			||||||
 | 
					  var evaluation = game.pieceEval()
 | 
				
			||||||
 | 
					  return evaluation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					proc spanMoveTree*(game: Game, depth: int): MoveTree =
 | 
				
			||||||
 | 
					  ## Create and return a Movetree of a given `game` with a given maximum `depth`.
 | 
				
			||||||
 | 
					  var mTree: MoveTree
 | 
				
			||||||
 | 
					  mTree.game = game
 | 
				
			||||||
 | 
					  if depth != 1 and not game.isCheckmate(game.toMove) and not game.isStalemate(game.toMove):
 | 
				
			||||||
 | 
					    let possibleMoves = game.genLegalMoves(game.toMove)
 | 
				
			||||||
 | 
					    for move in possibleMoves:
 | 
				
			||||||
 | 
					      var tmpGame = game
 | 
				
			||||||
 | 
					      tmpGame.checkedMove(move)
 | 
				
			||||||
 | 
					      mTree.children.add(spanMoveTree(tmpGame, depth-1))
 | 
				
			||||||
 | 
					  return mTree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					proc minimax*(mTree: MoveTree): int =
 | 
				
			||||||
 | 
					  ## Return the value of the root node of a given `MoveTree`
 | 
				
			||||||
 | 
					  if mTree.children == []:
 | 
				
			||||||
 | 
					    return evaluate(mTree.game)
 | 
				
			||||||
 | 
					  var bestVal: int
 | 
				
			||||||
 | 
					  var tmpVal: int
 | 
				
			||||||
 | 
					  if mTree.game.toMove == Color.White:
 | 
				
			||||||
 | 
					    bestVal = LoVal
 | 
				
			||||||
 | 
					    for child in mTree.children:
 | 
				
			||||||
 | 
					      tmpVal = minimax(child)
 | 
				
			||||||
 | 
					      bestVal = max(bestVal, tmpVal)
 | 
				
			||||||
 | 
					  else:
 | 
				
			||||||
 | 
					    bestVal = HiVal
 | 
				
			||||||
 | 
					    for child in mTree.children:
 | 
				
			||||||
 | 
					      tmpVal = minimax(child)
 | 
				
			||||||
 | 
					      bestVal = min(bestVal, tmpVal)
 | 
				
			||||||
 | 
					  return bestVal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					proc bestMove*(game: Game, depth: int): Move =
 | 
				
			||||||
 | 
					  ## Generate a MoveTree of a `game` with a given `depth`, run Minimax and return
 | 
				
			||||||
 | 
					  ## the best evaluated move.
 | 
				
			||||||
 | 
					  var moves = game.genLegalMoves(game.toMove)
 | 
				
			||||||
 | 
					  var bestMove: Move
 | 
				
			||||||
 | 
					  var bestEval: int
 | 
				
			||||||
 | 
					  if game.toMove == Color.White:
 | 
				
			||||||
 | 
					    bestEval = LoVal
 | 
				
			||||||
 | 
					  else:
 | 
				
			||||||
 | 
					    bestEval = HiVal
 | 
				
			||||||
 | 
					  for move in moves:
 | 
				
			||||||
 | 
					    var tmpGame = game
 | 
				
			||||||
 | 
					    tmpGame.checkedMove(move)
 | 
				
			||||||
 | 
					    var tmpEval = tmpGame.evaluate()
 | 
				
			||||||
 | 
					    if game.toMove == Color.White:
 | 
				
			||||||
 | 
					      if tmpEval > bestEval:
 | 
				
			||||||
 | 
					        bestEval = tmpEval
 | 
				
			||||||
 | 
					        bestMove = move
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					      if tmpEval < bestEval:
 | 
				
			||||||
 | 
					        bestEval = tmpEval
 | 
				
			||||||
 | 
					        bestMove = move
 | 
				
			||||||
 | 
					  return bestMove
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										75
									
								
								src/engineTest.nim
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/engineTest.nim
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					import einheit
 | 
				
			||||||
 | 
					import algorithm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ./chess
 | 
				
			||||||
 | 
					import ./engine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					testSuite GameTest of TestSuite:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var
 | 
				
			||||||
 | 
					    game: Game
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  method setup() =
 | 
				
			||||||
 | 
					    self.game = initGame()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  method testPieceEval() =
 | 
				
			||||||
 | 
					    self.game = initGame([
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, WKing, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, BKing, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0
 | 
				
			||||||
 | 
					    ], Color.Black)
 | 
				
			||||||
 | 
					    var pieceEvaluation = self.game.pieceEval()
 | 
				
			||||||
 | 
					    self.check(pieceEvaluation == 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  method testSpanMoveTree() =
 | 
				
			||||||
 | 
					    self.game = initGame([
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, WKing, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, BKing, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0
 | 
				
			||||||
 | 
					    ], Color.Black)
 | 
				
			||||||
 | 
					    var mTree = self.game.spanMoveTree(2)
 | 
				
			||||||
 | 
					    self.check(mTree.children == [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  method testManualMiniMaxEval() =
 | 
				
			||||||
 | 
					    self.game = initGame([
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, WKing, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, WPawn, BKing, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0
 | 
				
			||||||
 | 
					    ], Color.Black)
 | 
				
			||||||
 | 
					    var mTree = self.game.spanMoveTree(2)
 | 
				
			||||||
 | 
					    var evaluation = mTree.minimax()
 | 
				
			||||||
 | 
					    self.check(evaluation == 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  method testBestMove() =
 | 
				
			||||||
 | 
					    var testGame = initGame([
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, WKing, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, WPawn, BKing, 0, 0,
 | 
				
			||||||
 | 
					      0, 0, 0, 0, 0, 0, 0, 0
 | 
				
			||||||
 | 
					    ], Color.Black)
 | 
				
			||||||
 | 
					    var testBestMove = testGame.bestMove(2)
 | 
				
			||||||
 | 
					    self.check(indToField(testBestMove.start) == "c7")
 | 
				
			||||||
 | 
					    self.check(indToField(testBestMove.dest) == "d7")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					when isMainModule:
 | 
				
			||||||
 | 
					  einheit.runTests()
 | 
				
			||||||
		Reference in New Issue
	
	Block a user