Problem with Boardgame AI (Minimax Algorithm)
BlitzMax Forums/BlitzMax Programming/Problem with Boardgame AI (Minimax Algorithm)
| ||
Hi, I'm currently trying to do a simple 2-Player board game, with very simple rules: - 8x8 board play field - each player has his own color (red gems, blue gems) - each player's aim is to making 2x2 squares of his color to remove them - the game is won when all gems of one colour are removed I wanted to implement a simple minimax algorithm, but run into problems, so I've done a small test/debug app that renders all my nodes the shows me what is the best next move. With a click on a node I can show / hide the childs of it. When I press "M" the best move the player can do is becoming the new root node. When the mouse pointer is over a node and you press "Q" this will become the new root node (this way you can checkout how the KI reacts to several board states). So I can watch the ki playing against itself, by clicking M and can surf through the nodes to see why a decision for the best move was made. However, the AI seems to do very dumb moves very often, and I can't figure out whats wrong with my code. Probably you can have a look at my demo app and give me a hint. Or is everthing fine with the code and the algorithm doesn't fit my game so well?? Global mh:Int Const DEPTH:Int = 4 Graphics 1024, 768 SetBlend(ALPHABLEND) Type BoardNode Const RED:Int = -1 Const BLUE:Int = 1 Global nodes:TList Field grid:Int[8,8] Field childs:TList Field player:Int = -1 Field renderChilds:Byte = False Method CreateNewNode:BoardNode() Local b:BoardNode = New BoardNode For Local x:Int = 0 To 7 For Local y:Int = 0 To 7 b.grid[x,y] = grid[x,y] b.player = -player Next Next Return b End Method Method InitGrid() grid = New Int[8,8] For Local x:Int = 0 To 7 For Local y:Int = 0 To 7 grid[x,y] = 0 Next Next grid[3,3] = RED grid[4,3] = BLUE grid[3,4] = BLUE grid[4,4] = RED End Method Method RenderNode(offX:Int, offY:Int) If (w = 0) Then w = GraphicsWidth() SetColor(255,255,255) SetAlpha(0.3) DrawRect(offX, offY, 32, 32) SetAlpha(1) For Local x:Int = 0 To 7 For Local y:Int = 0 To 7 If (grid[x,y] = -1) SetColor(255,0,0) DrawRect(offX + x*4, offY + y*4, 4, 4) Else If grid[x,y] = 1 SetColor(0,0,255) DrawRect(offX + x*4, offY + y*4, 4, 4) End If Next SetColor(0,255,0) SetAlpha(0.2) Local s:String = alpha+"/"+GetScore() DrawText(s, offX + 7, offY + 20) SetAlpha(1) Next SetColor(255,255,255) If (MouseX() > offX And MouseY() > offY And MouseX() < offx+32 And MouseY() < offY+32) If mh Then renderChilds = Not renderChilds If KeyDown(KEY_Q) rootNode = Self rootNode.MiniMax(DEPTH) End If End If If (renderChilds And childs) Local ox:Int = offX - (CountList(childs)/2 * 40) For Local n:BoardNode = EachIn childs DrawLine(offX + 20, offY + 32, ox + 20, offY + 40) n.RenderNode(ox, offY + 40) ox :+ 40 Next End If End Method Method GetScore:Int() 'some simple heuristics Local score:Int = 0 Local c1:Int = 0, c2:Int = 0 For Local x:Int = 0 To 7 For Local y:Int = 0 To 7 If (grid[x,y] = -1) Then c1 :+ 1 If (grid[x,y] = 1) Then c2 :+ 1 score :+ grid[x,y] Next Next ' a player has won If (c1 = 0) Then Return 1000 If (c2 = 0) Then Return -1000 'check for neighboring tiles (this is better then sepearted tiles) ' For x:Int = 0 To 6 ' For y:Int = 0 To 6 ' If (grid[x,y] = grid[x+1,y]) Then score :+ grid[x,y] ' If (grid[x,y] = grid[x, y+1]) Then score :+ grid[x,y] ' Next ' Next For x:Int = 0 To 6 For y:Int = 0 To 6 Local c:Int = 0 c :+ grid[x,y] c :+ grid[x+1,y] c :+ grid[x,y+1] c :+ grid[x+1,y+1] If (c = 3) Then score :- 1 If (c = -3) Then score :+ 1 Next Next Return score End Method Method SetIfFieldNotEmpty:Byte(x:Int, y:Int, player:Int) If (x > 0 And y > 0 And x < 8 And y < 8 And grid[x,y] = 0) grid[x,y] = player Return True End If Return False End Method Method CalculateChilds() childs = CreateList() Local b:BoardNode For Local x:Int = 0 To 7 For Local y:Int = 0 To 7 If grid[x,y] = player b = CreateNewNode() If b.SetIfFieldNotEmpty(x-1, y, player) Then ListAddLast(childs, b) ; b.RemoveMatches() b = CreateNewNode() If b.SetIfFieldNotEmpty(x+1, y, player) Then ListAddLast(childs, b) ; b.RemoveMatches() b = CreateNewNode() If b.SetIfFieldNotEmpty(x, y-1, player) Then ListAddLast(childs, b) ; b.RemoveMatches() b = CreateNewNode() If b.SetIfFieldNotEmpty(x, y+1, player) Then ListAddLast(childs, b) ; b.RemoveMatches() End If Next Next DebugLog CountList(childs) + " childs calculated" End Method Method RemoveMatches() For Local x:Int = 0 To 6 For Local y:Int = 0 To 6 If grid[x,y] <> 0 And grid[x,y] = grid[x+1,y] And grid[x,y] = grid[x,y+1] And grid[x,y] = grid[x+1,y+1] grid[x,y] = 0 grid[x+1,y] = 0 grid[x,y+1] = 0 grid[x+1,y+1] = 0 End If Next Next End Method Field alpha:Int Method MiniMax:BoardNode(depth:Int = 0) If (player = -1) Then alpha = -99999 If (player = 1) Then alpha = 99999 If (depth = 0) alpha = GetScore() Return Self Else CalculateChilds() For Local node:BoardNode = EachIn childs node.MiniMax(depth - 1) node.alpha = GetScore() If (player = -1) If (node.alpha > alpha) Then alpha = node.alpha ; bestMove = node Else If (node.alpha < alpha) Then alpha = node.alpha ; bestMove = node End If Next End If End Method Method GetBest:BoardNode() Local b:BoardNode If (player = -1) Then a = 99999 If (player = 1) Then a = -99999 For Local node:BoardNode = EachIn childs If b = Null Then b = node If (player = -1 And node.GetScore() >= 1000) Then Return node If (player = 1 And node.GetScore() <= -1000) Then Return node If (player = 1 And node.alpha < a) Then b = node If (player = -1 And node.alpha > a) Then b = node If (node.alpha = b.alpha) If (player = 1 And node.GetScore() < b.GetScore()) Then b = node If (player = -1 And node.GetScore() > b.GetScore()) Then b = node End If Next Return b End Method Method RenderNodeBig(offX:Int, offY:Int) If (w = 0) Then w = GraphicsWidth() SetColor(255,255,255) SetAlpha(0.3) DrawRect(offX, offY, 16*8, 16*8) SetAlpha(1) For Local x:Int = 0 To 7 For Local y:Int = 0 To 7 If (grid[x,y] = -1) SetColor(255,0,0) DrawRect(offX + x*16, offY + y*16, 16, 16) Else If grid[x,y] = 1 SetColor(0,0,255) DrawRect(offX + x*16, offY + y*16, 16, 16) End If Next SetColor(0,255,0) SetAlpha(0.2) Local s:String = alpha+"/"+GetScore() DrawText(s, offX + 7, offY + 20) SetAlpha(1) Next SetColor(255,255,255) End Method End Type Global rootNode:BoardNode = New BoardNode rootNode.InitGrid() rootNode.MiniMax(DEPTH) While (Not KeyDown(KEY_ESCAPE)) Cls mh = MouseHit(1) rootNode.RenderNode(GraphicsWidth() / 2, 5) rootNode.RenderNodeBig(0,0) DrawText("CurrentNode", 0,0) rootNode.GetBest().RenderNodeBig(0,140) If (rootNode.player = -1) DrawText("Best Move (Player RED )", 0, 140) Else DrawText("Best Move (Player BLUE )", 0, 140) End If If (KeyHit(KEY_M)) Then rootNode = rootNode.GetBest() ; rootNode.MiniMax(DEPTH) If KeyDown(KEY_P) DebugStop rootNode.GetScore() End If Flip Wend Last edited 2011 |
| ||
Okay, I've fixed a few bugs in scoring the current board state node. I also added, that if serveral "bestMoves" are available because of the same alpha value, the one with the best Score will be returned. At first sight, it now seems to work quite good. I've updated the code above! |