mirror of https://github.com/tiyn/yeschess
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.master
parent
0e27d0a6b5
commit
4ba3e06d93
@ -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
|
||||||
|
|
||||||
|
|
@ -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()
|
Loading…
Reference in new issue