Popular Post Barney Posted April 17, 2018 Popular Post Posted April 17, 2018 (edited) Hi guys, I just finished a Connect 4 game by using MiniMax with Alpha Beta Pruning. I haven't written a program for a long time, but writing an AI program is always funny! I have to learn how the algorithm works and try to optimize the code to run faster. Let's play and have fun! Oops, I lost the game ... Thanks guys! Download: Connect 4.zip Edited November 25, 2020 by Barney Update version from 2.0 to 3.0 Gianni, aa2zz6, UEZ and 2 others 5
aiter Posted April 17, 2018 Posted April 17, 2018 (edited) Charming game, you can be rightfully proud. The devil AI cannot be beaten! Edited April 17, 2018 by aiter
Barney Posted April 18, 2018 Author Posted April 18, 2018 Thanks aiter! If I can write a faster evaluation function then the AI can search the tree much deeper or write code to do Threat Analysis to make the AI much stronger.
Gianni Posted April 18, 2018 Posted April 18, 2018 ... a little modified version; CPU vs CPU expandcollapse popup#include <Array.au3> #include <GDIPlus.au3> #include <GUIConstantsEx.au3> #include <Misc.au3> #include <Sound.au3> #include <WindowsConstants.au3> #include <WinAPIRes.au3> AutoItSetOption("MouseCoordMode", 0) Opt("GUIOnEventMode", 1) Opt("MustDeclareVars", 1) ;;----------------------------------------------------------------------------------------------------------- Global Const $VERSION = "1.0" ;; Image size ... Global Const $GUI_WIDTH = 800 Global Const $GUI_HEIGHT = 600 Global Const $PIECE_WIDTH = 64 Global Const $PIECE_HEIGHT = 64 Global Const $TINY_PIECE_WIDTH = 36 Global Const $TINY_PIECE_HEIGHT = 36 ;; Coordinates ... ;; to draw the little piece that indicate the color play by Player & CPU. Global Const $PLAYER_COLOR_X = 13 Global Const $PLAYER_COLOR_Y = 127 Global Const $CPU_COLOR_X = 751 Global Const $CPU_COLOR_Y = 127 ;; to draw the number of games won by Player & CPU. Global Const $PLAYER_WON_GAMES_X = 55 Global Const $PLAYER_WON_GAMES_Y = 122 Global Const $CPU_WON_GAMES_X = 709 Global Const $CPU_WON_GAMES_Y = 122 ;; to draw the Player Win image. Global Const $PLAYER_WIN_X = 236 Global Const $PLAYER_WIN_Y = 200 ;; to draw the CPU Win image. Global Const $CPU_WIN_X = 254 Global Const $CPU_WIN_Y = 200 ;; to draw the Draw image. Global Const $DRAW_GAME_X = 235 Global Const $DRAW_GAME_Y = 200 ;; to draw the "Play Again (Y/N)?" image. Global Const $PLAY_AGAIN_X = 155 Global Const $PLAY_AGAIN_Y = 281 ;; to draw the think image. Global Const $PLAYER_THINK_X = 28 Global Const $PLAYER_THINK_Y = 243 Global Const $CPU_THINK_X = 693 Global Const $CPU_THINK_Y = 243 ;; of board Row 0 & Column 0. Global Const $COL0_X = 143 Global Const $ROW0_Y = 480 ;; Board square width. Global Const $SQUARE_WIDTH = 75 ;; Piece drop speed. Global Const $DROP_SPEED = 12 ;; Piece & empty square. Global Const $PLAYER = 'P' Global Const $CPU = 'C' Global Const $DRAW = 'D' Global Const $EMPTY_SQUARE = '-' ;; Values for Minimax & Alpha Beta Pruning. Global Const $INFINITY = 10000 Global Const $MAX_DEPTH = 4 Global Const $CPU_WIN_SCORE = +$INFINITY / 10 Global Const $PLAYER_WIN_SCORE = -$INFINITY / 10 Global Const $DRAW_SCORE = 0 ;; Patterns ... Global Const $3C1E = 3 * Asc($CPU) + Asc($EMPTY_SQUARE) Global Const $3P1E = 3 * Asc($PLAYER) + Asc($EMPTY_SQUARE) Global Const $2C2E = 2 * Asc($CPU) + 2 * Asc($EMPTY_SQUARE) Global Const $2P2E = 2 * Asc($PLAYER) + 2 * Asc($EMPTY_SQUARE) Global Const $1C3E = 1 * Asc($CPU) + 3 * Asc($EMPTY_SQUARE) Global Const $1P3E = 1 * Asc($PLAYER) + 3 * Asc($EMPTY_SQUARE) ;;----------------------------------------------------------------------------------------------------------- Global $aWaitingSound = _SoundOpen(@ScriptDir & "\Sounds\waiting.mid") Global $aPlayerWinSound = _SoundOpen(@ScriptDir & "\Sounds\player_win.wma") Global $aCPUWinSound = _SoundOpen(@ScriptDir & "\Sounds\cpu_win.wma") Global $aDrawSound = _SoundOpen(@ScriptDir & "\Sounds\draw.wma") Global $aMoveSound = _SoundOpen(@ScriptDir & "\Sounds\move.wma") Global $aInValidMoveSound = _SoundOpen(@ScriptDir & "\Sounds\invalid_move.wma") Global $aKeyPressSound = _SoundOpen(@ScriptDir & "\Sounds\key_press.wma") ;;----------------------------------------------------------------------------------------------------------- Global Const $hGUI = GUICreate("Connect 4", $GUI_WIDTH, $GUI_HEIGHT, -1, -1) GUISetIcon(@ScriptDir & "\c4.ico", -1) GUISetState(@SW_SHOW) ;;----------------------------------------------------------------------------------------------------------- GUISetOnEvent($GUI_EVENT_CLOSE, "CloseApp", $hGUI) GUISetOnEvent($GUI_EVENT_RESTORE, "DrawBoard", $hGUI) GUISetOnEvent($GUI_EVENT_PRIMARYDOWN, "PlayerMove", $hGUI) GUIRegisterMsg($WM_MOVE, "WM_MOVE") Func WM_MOVE($hWnd, $Msg, $wParam, $lParam) DrawBoard() EndFunc ;;----------------------------------------------------------------------------------------------------------- _GDIPlus_Startup() Global Const $hFrameImg = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\frame.png") Global Const $hPlayerWinImg = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\player_win.png") Global Const $hCPUWinImg = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\cpu_win.png") Global Const $hDrawImg = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\draw.png") Global Const $hPlayAgainImg = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\play_again.png") Global Const $hThinkImg = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\think.png") Global Const $hMarkerImg = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\marker.png") Global Const $hDigit0Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\0.png") Global Const $hDigit1Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\1.png") Global Const $hDigit2Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\2.png") Global Const $hDigit3Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\3.png") Global Const $hDigit4Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\4.png") Global Const $hDigit5Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\5.png") Global Const $hDigit6Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\6.png") Global Const $hDigit7Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\7.png") Global Const $hDigit8Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\8.png") Global Const $hDigit9Img = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\Images\9.png") Global Const $hGraphics = _GDIPlus_GraphicsCreateFromHWND($hGUI) Global Const $hBitmap = _GDIPlus_BitmapCreateFromGraphics($GUI_WIDTH, $GUI_HEIGHT, $hGraphics) Global Const $hBuffer = _GDIPlus_ImageGetGraphicsContext($hBitmap) Global Const $hBrushRed = _GDIPlus_BrushCreateSolid(0xFFFF0000) Global Const $hBrushYellow = _GDIPlus_BrushCreateSolid(0xFFFFDE00) Global Const $hBrushWhite = _GDIPlus_BrushCreateSolid(0xFFFFFFFF) Global Const $hPenPurple = _GDIPlus_PenCreate(0xFF372248, 8) ;;----------------------------------------------------------------------------------------------------------- Global $g_aBoard[6][7], $g_aState[9] Global $g_iPlayerWonGames, $g_iCPUWonGames Global $g_hPlayerColor, $g_hCPUColor Global $g_sTurn, $g_iMove, $g_bDetectPlayerMove ;;----------------------------------------------------------------------------------------------------------- _GDIPlus_GraphicsSetSmoothingMode($hGraphics, $GDIP_SMOOTHINGMODE_HIGHQUALITY) _Connect4() ;;----------------------------------------------------------------------------------------------------------- ; CPU vs CPU Func PlayerCPUMove() Local $iPieces = 7 For $i = 0 to 6 $iPieces -= $g_aBoard[0][$i] = "-" Next $g_bDetectPlayerMove = False If $iPieces Then $g_iMove = AI($g_aBoard) Else $g_iMove = Random(0, 6, 1) EndIf DrawBoard() _SoundPlay($aMoveSound, 1) $g_bDetectPlayerMove = True Return True EndFunc ;==>PlayerCPUMove Func _Connect4() Initiate() DrawBoard() While True Select ;; Player turn. Case $g_sTurn = $PLAYER And PlayerCPUMove() ; PlayerMoved() $g_aState = CheckState($g_aBoard, $PLAYER) DrawBoard() Switch $g_aState[0] Case "Win" _SoundPlay($aPlayerWinSound) Switch PlayAgain() Case 'Y' Reset() DrawBoard() Case 'N' CloseApp() EndSwitch Case "Draw" _SoundPlay($aDrawSound) Switch PlayAgain() Case 'Y' Reset() DrawBoard() Case 'N' CloseApp() EndSwitch Case "GoOn" $g_sTurn = $CPU DrawBoard() EndSwitch ;; CPU turn. Case $g_sTurn = $CPU And CPUMoved() $g_aState = CheckState($g_aBoard, $CPU) DrawBoard() Switch $g_aState[0] Case "Win" _SoundPlay($aCPUWinSound) Switch PlayAgain() Case 'Y' Reset() DrawBoard() Case 'N' CloseApp() EndSwitch Case "Draw" _SoundPlay($aDrawSound) Switch PlayAgain() Case 'Y' Reset() DrawBoard() Case 'N' CloseApp() EndSwitch Case "GoOn" $g_sTurn = $PLAYER DrawBoard() EndSwitch EndSelect WEnd EndFunc Func AI($aBoard) Local $iRow, $iScore, $iBestMove, $iBestScore = -$INFINITY For $iMove = 0 To 6 If IsValidMove($aBoard, $iMove) Then $iRow = PiecesInColumn($aBoard, $iMove) $aBoard[$iRow][$iMove] = $CPU $iScore = AlphaBeta($aBoard, $MAX_DEPTH, -$INFINITY, +$INFINITY, False) DisplayScore($iMove, $iScore) If $iScore > $iBestScore Then $iBestScore = $iScore $iBestMove = $iMove ElseIf $iScore = $iBestScore Then If Abs($iMove-3) < Abs($iBestMove-3) Then $iBestMove = $iMove EndIf EndIf $aBoard[$iRow][$iMove] = $EMPTY_SQUARE EndIf Next Return $iBestMove EndFunc Func CheckState($aBoard, $sSide) Local $aState[9] = ["GoOn", -1, -1, -1, -1, -1, -1, -1, -1] Local $bBoardFull = True For $iRow = 0 To 5 For $iCol = 0 To 6 If $aBoard[$iRow][$iCol] <> $EMPTY_SQUARE Then ;; Check horizontal. If $iCol+3 <= 6 Then If $aBoard[$iRow][$iCol] = $sSide And $aBoard[$iRow][$iCol+1] = $sSide And _ $aBoard[$iRow][$iCol+2] = $sSide And $aBoard[$iRow][$iCol+3] = $sSide Then $aState[0] = "Win" $aState[1] = $iRow $aState[2] = $iCol $aState[3] = $iRow $aState[4] = $iCol+1 $aState[5] = $iRow $aState[6] = $iCol+2 $aState[7] = $iRow $aState[8] = $iCol+3 Return $aState EndIf EndIf ;; Check vertical. If $iRow+3 <= 5 Then If $aBoard[$iRow][$iCol] = $sSide And $aBoard[$iRow+1][$iCol] = $sSide And _ $aBoard[$iRow+2][$iCol] = $sSide And $aBoard[$iRow+3][$iCol] = $sSide Then $aState[0] = "Win" $aState[1] = $iRow $aState[2] = $iCol $aState[3] = $iRow+1 $aState[4] = $iCol $aState[5] = $iRow+2 $aState[6] = $iCol $aState[7] = $iRow+3 $aState[8] = $iCol Return $aState EndIf EndIf ;; Check ascending diagonal. If $iRow+3 <= 5 And $iCol+3 <= 6 Then If $aBoard[$iRow][$iCol] = $sSide And $aBoard[$iRow+1][$iCol+1] = $sSide And _ $aBoard[$iRow+2][$iCol+2] = $sSide And $aBoard[$iRow+3][$iCol+3] = $sSide Then $aState[0] = "Win" $aState[1] = $iRow $aState[2] = $iCol $aState[3] = $iRow+1 $aState[4] = $iCol+1 $aState[5] = $iRow+2 $aState[6] = $iCol+2 $aState[7] = $iRow+3 $aState[8] = $iCol+3 Return $aState EndIf EndIf ;; Check descending diagonal. If $iRow-3 >= 0 And $iCol+3 <= 6 Then If $aBoard[$iRow][$iCol] = $sSide And $aBoard[$iRow-1][$iCol+1] = $sSide And _ $aBoard[$iRow-2][$iCol+2] = $sSide And $aBoard[$iRow-3][$iCol+3] = $sSide Then $aState[0] = "Win" $aState[1] = $iRow $aState[2] = $iCol $aState[3] = $iRow-1 $aState[4] = $iCol+1 $aState[5] = $iRow-2 $aState[6] = $iCol+2 $aState[7] = $iRow-3 $aState[8] = $iCol+3 Return $aState EndIf EndIf Else $bBoardFull = False EndIf Next Next If $bBoardFull Then $aState[0] = "Draw" EndIf Return $aState EndFunc Func CloseApp() _SoundClose($aWaitingSound) _SoundClose($aPlayerWinSound) _SoundClose($aCPUWinSound) _SoundClose($aDrawSound) _SoundClose($aMoveSound) _SoundClose($aInValidMoveSound) _SoundClose($aKeyPressSound) _GDIPlus_PenDispose($hPenPurple) _GDIPlus_BrushDispose($hBrushWhite) _GDIPlus_BrushDispose($hBrushYellow) _GDIPlus_BrushDispose($hBrushRed) _GDIPlus_GraphicsDispose($hBuffer) _GDIPlus_BitmapDispose($hBitmap) _GDIPlus_GraphicsDispose($hGraphics) _GDIPlus_Shutdown() GUIDelete($hGUI) Exit EndFunc Func CPUMoved() $g_bDetectPlayerMove = False $g_iMove = AI($g_aBoard) DrawBoard() _SoundPlay($aMoveSound, 1) $g_bDetectPlayerMove = True Return True EndFunc Func DisplayScore($iCol, $iScore) Local Static $iLastCol = 0, $sScore If $iCol <= $iLastCol Then $sScore = '' $sScore = $sScore & " " & $iCol+1 & " = " & $iScore Else $sScore = $sScore & " | " & $iCol+1 & " = " & $iScore EndIf $iLastCol = $iCol WinSetTitle($hGUI, "", "Connect 4" & $sScore) EndFunc Func DrawBoard() Local $iX, $iY, $iTopPieceY, $iPiecesInColumn Local Static $iLastMove = -1 $g_bDetectPlayerMove = False If $g_iMove > -1 Then $iX = $COL0_X+$g_iMove*$SQUARE_WIDTH $iY = $ROW0_Y-6*$SQUARE_WIDTH $iPiecesInColumn = PiecesInColumn($g_aBoard, $g_iMove) $iTopPieceY = $ROW0_Y-$iPiecesInColumn * $SQUARE_WIDTH Do DrawBackground() DrawPiecesOnBoard() DrawPieceDropping($iX, $iY) DrawFrame() DrawColorIndicators() DrawWinCounter() DrawWhoseTurn() DrawBufferToGUI() Until $iY > $iTopPieceY $g_aBoard[$iPiecesInColumn][$g_iMove] = $g_sTurn $iLastMove = $g_iMove $g_iMove = -1 EndIf DrawBackground() DrawPiecesOnBoard() DrawFrame() DrawColorIndicators() DrawWinCounter() DrawWhoseTurn() DrawMoveMarker($iLastMove) Switch $g_aState[0] Case "Win" For $iIdx = 1 To 8 Step 2 $iX = $COL0_X+$g_aState[$iIdx+1]*$SQUARE_WIDTH $iY = $ROW0_Y-$g_aState[$iIdx]*$SQUARE_WIDTH _GDIPlus_GraphicsDrawEllipse($hBuffer, $iX, $iY, $PIECE_WIDTH, $PIECE_HEIGHT, $hPenPurple) Next Switch $g_sTurn Case $PLAYER DrawPlayerWin() Case $CPU DrawCPUWin() EndSwitch DrawPlayAgain() $iLastMove = -1 Case "Draw" DrawItsADraw() DrawPlayAgain() $iLastMove = -1 EndSwitch DrawBufferToGUI() $g_bDetectPlayerMove = True EndFunc Func DrawBackground() _GDIPlus_GraphicsFillRect($hBuffer, 0, 0, $GUI_WIDTH, $GUI_HEIGHT, $hBrushWhite) EndFunc Func DrawBufferToGUI() _GDIPlus_GraphicsDrawImageRect($hGraphics, $hBitmap, 0, 0, $GUI_WIDTH, $GUI_HEIGHT) EndFunc Func DrawColorIndicators() _GDIPlus_GraphicsFillEllipse($hBuffer, $PLAYER_COLOR_X, $PLAYER_COLOR_Y, $TINY_PIECE_WIDTH, $TINY_PIECE_HEIGHT, $g_hPlayerColor) _GDIPlus_GraphicsFillEllipse($hBuffer, $CPU_COLOR_X, $CPU_COLOR_Y, $TINY_PIECE_WIDTH, $TINY_PIECE_HEIGHT, $g_hCPUColor) EndFunc Func DrawFrame() _GDIPlus_GraphicsDrawImageRect($hBuffer, $hFrameImg, 0, 0, $GUI_WIDTH, $GUI_HEIGHT) EndFunc Func DrawMoveMarker($iLastMove) Local $aX = [155, 231, 306, 381, 455, 532, 605], $iY = 550 Local $iImgWidth, $iImgHeight If $iLastMove > -1 Then $iImgWidth = _GDIPlus_ImageGetWidth($hMarkerImg) $iImgHeight = _GDIPlus_ImageGetHeight($hMarkerImg) _GDIPlus_GraphicsDrawImageRect($hBuffer, $hMarkerImg, $aX[$iLastMove], $iY, $iImgWidth, $iImgHeight) EndIf EndFunc Func DrawPieceDropping($iX, ByRef $iY) Switch $g_sTurn Case $PLAYER _GDIPlus_GraphicsFillEllipse($hBuffer, $iX, $iY, $PIECE_WIDTH, $PIECE_HEIGHT, $g_hPlayerColor) Case $CPU _GDIPlus_GraphicsFillEllipse($hBuffer, $iX, $iY, $PIECE_WIDTH, $PIECE_HEIGHT, $g_hCPUColor) EndSwitch $iY += $DROP_SPEED EndFunc Func DrawPiecesOnBoard() Local $iX, $iY For $iRow = 0 To 5 For $iCol = 0 To 6 $iX = $COL0_X+$iCol*$SQUARE_WIDTH $iY = $ROW0_Y-$iRow*$SQUARE_WIDTH Switch $g_aBoard[$iRow][$iCol] Case $PLAYER _GDIPlus_GraphicsFillEllipse($hBuffer, $iX, $iY, $PIECE_WIDTH, $PIECE_HEIGHT, $g_hPlayerColor) Case $CPU _GDIPlus_GraphicsFillEllipse($hBuffer, $iX, $iY, $PIECE_WIDTH, $PIECE_HEIGHT, $g_hCPUColor) EndSwitch Next Next EndFunc Func DrawCPUWin() Local $iImgWidth, $iImgHeight $iImgWidth = _GDIPlus_ImageGetWidth($hCPUWinImg) $iImgHeight = _GDIPlus_ImageGetHeight($hCPUWinImg) _GDIPlus_GraphicsDrawImageRect($hBuffer, $hCPUWinImg, $CPU_WIN_X, $CPU_WIN_Y, $iImgWidth, $iImgHeight) EndFunc Func DrawItsADraw() Local $iImgWidth, $iImgHeight $iImgWidth = _GDIPlus_ImageGetWidth($hDrawImg) $iImgHeight = _GDIPlus_ImageGetHeight($hDrawImg) _GDIPlus_GraphicsDrawImageRect($hBuffer, $hDrawImg, $DRAW_GAME_X, $DRAW_GAME_Y, $iImgWidth, $iImgHeight) EndFunc Func DrawPlayAgain() Local $iImgWidth, $iImgHeight $iImgWidth = _GDIPlus_ImageGetWidth($hPlayAgainImg) $iImgHeight = _GDIPlus_ImageGetHeight($hPlayAgainImg) _GDIPlus_GraphicsDrawImageRect($hBuffer, $hPlayAgainImg, $PLAY_AGAIN_X, $PLAY_AGAIN_Y, $iImgWidth, $iImgHeight) EndFunc Func DrawPlayerWin() Local $iImgWidth, $iImgHeight $iImgWidth = _GDIPlus_ImageGetWidth($hPlayerWinImg) $iImgHeight = _GDIPlus_ImageGetHeight($hPlayerWinImg) _GDIPlus_GraphicsDrawImageRect($hBuffer, $hPlayerWinImg, $PLAYER_WIN_X, $PLAYER_WIN_Y, $iImgWidth, $iImgHeight) EndFunc Func DrawWinCounter() Local $aDigitImg[10] = [$hDigit0Img, $hDigit1Img, $hDigit2Img, $hDigit3Img, $hDigit4Img, $hDigit5Img, $hDigit6Img, $hDigit7Img, $hDigit8Img, $hDigit9Img] Local $sPlayerWonGames = String($g_iPlayerWonGames), $sCPUWonGames = StringReverse(String($g_iCPUWonGames)) Local $aDigit, $iDigitImgWidth, $iDigitImgHeight, $iXP = $PLAYER_WON_GAMES_X, $iXC = $CPU_WON_GAMES_X $aDigit = StringSplit($sPlayerWonGames, '') For $iIdx = 1 To $aDigit[0] $iDigitImgWidth = _GDIPlus_ImageGetWidth($aDigitImg[$aDigit[$iIdx]]) $iDigitImgHeight = _GDIPlus_ImageGetHeight($aDigitImg[$aDigit[$iIdx]]) _GDIPlus_GraphicsDrawImageRect($hBuffer, $aDigitImg[$aDigit[$iIdx]], $iXP, $PLAYER_WON_GAMES_Y, $iDigitImgWidth, $iDigitImgHeight) $iXP += $iDigitImgWidth-5 Next $aDigit = StringSplit($sCPUWonGames, '') For $iIdx = 1 To $aDigit[0] $iDigitImgWidth = _GDIPlus_ImageGetWidth($aDigitImg[$aDigit[$iIdx]]) $iDigitImgHeight = _GDIPlus_ImageGetHeight($aDigitImg[$aDigit[$iIdx]]) _GDIPlus_GraphicsDrawImageRect($hBuffer, $aDigitImg[$aDigit[$iIdx]], $iXC, $CPU_WON_GAMES_Y, $iDigitImgWidth, $iDigitImgHeight) If $iIdx < $aDigit[0] Then $iDigitImgWidth = _GDIPlus_ImageGetWidth($aDigitImg[$aDigit[$iIdx+1]]) $iXC -= $iDigitImgWidth-5 EndIf Next EndFunc Func DrawWhoseTurn() Local $iImgWidth, $iImgHeight $iImgWidth = _GDIPlus_ImageGetWidth($hThinkImg) $iImgHeight = _GDIPlus_ImageGetHeight($hThinkImg) Switch $g_sTurn Case $PLAYER _GDIPlus_GraphicsDrawImageRect($hBuffer, $hThinkImg, $PLAYER_THINK_X, $PLAYER_THINK_Y, $iImgWidth, $iImgHeight) Case $CPU _GDIPlus_GraphicsDrawImageRect($hBuffer, $hThinkImg, $CPU_THINK_X, $CPU_THINK_Y, $iImgWidth, $iImgHeight) EndSwitch EndFunc Func Initiate() For $iRow = 0 To 5 For $iCol = 0 To 6 $g_aBoard[$iRow][$iCol] = $EMPTY_SQUARE Next Next $g_iPlayerWonGames = 0 $g_iCPUWonGames = 0 $g_hPlayerColor = $hBrushRed $g_hCPUColor = $hBrushYellow $g_sTurn = $PLAYER $g_iMove = -1 $g_bDetectPlayerMove = True EndFunc Func IsValidMove($aBoard, $iMove) Return ($aBoard[5][$iMove] = $EMPTY_SQUARE) ? True : False EndFunc Func Max($iA, $iB) Return ($iA > $iB) ? $iA : $iB EndFunc Func Min($iA, $iB) Return ($iA < $iB) ? $iA : $iB EndFunc Func PiecesInColumn($aBoard, $iCol) Local $iPiecesInColumn = 0 For $iRow = 0 To 5 If $aBoard[$iRow][$iCol] = $EMPTY_SQUARE Then ExitLoop Else $iPiecesInColumn += 1 EndIf Next Return $iPiecesInColumn EndFunc Func PlayAgain() Local $hDLL = DllOpen("user32.dll"), $sAnswer $g_bDetectPlayerMove = False _SoundPlay($aWaitingSound) While True Select Case _IsPressed('59') $sAnswer = 'Y' ExitLoop Case _IsPressed('4E') $sAnswer = 'N' ExitLoop EndSelect WEnd _SoundPlay($aKeyPressSound, 1) _SoundPause($aWaitingSound) DllClose($hDLL) $g_bDetectPlayerMove = True Return $sAnswer EndFunc Func PlayerMove() Local $iX = MouseGetPos(0), $iY = MouseGetPos(1) If $g_bDetectPlayerMove Then If $iY > 130 And $iY < 568 Then Select Case $iX > 143 And $iX < 207 $g_iMove = 0 Case $iX > 218 And $iX < 282 $g_iMove = 1 Case $iX > 293 And $iX < 357 $g_iMove = 2 Case $iX > 368 And $iX < 432 $g_iMove = 3 Case $iX > 443 And $iX < 507 $g_iMove = 4 Case $iX > 518 And $iX < 582 $g_iMove = 5 Case $iX > 593 And $iX < 657 $g_iMove = 6 EndSelect EndIf If $g_iMove > -1 Then If Not(IsValidMove($g_aBoard, $g_iMove)) Then _SoundPlay($aInValidMoveSound, 1) $g_iMove = -1 EndIf EndIf EndIf EndFunc Func PlayerMoved() If $g_iMove > -1 Then DrawBoard() _SoundPlay($aMoveSound, 1) Return True Else Return False EndIf EndFunc Func Reset() WinSetTitle($hGUI, "", "Connect 4") Select Case $g_sTurn = $PLAYER And $g_aState[0] = "Win" $g_iPlayerWonGames += 1 Case $g_sTurn = $CPU And $g_aState[0] = "Win" $g_iCPUWonGames += 1 EndSelect Swap($g_hPlayerColor, $g_hCPUColor) Select Case $g_hPlayerColor = $hBrushRed $g_sTurn = $PLAYER Case $g_hCPUColor = $hBrushRed $g_sTurn = $CPU EndSelect For $iRow = 0 To 5 For $iCol = 0 To 6 $g_aBoard[$iRow][$iCol] = $EMPTY_SQUARE Next Next $g_aState[0] = "GoOn" For $iIdx = 1 To 8 $g_aState[$iIdx] = -1 Next EndFunc Func Swap(ByRef $A, ByRef $B) Local $Temp = $A $A = $B $B = $Temp EndFunc ;;----------------------------------------------------------------------------------------------------------- Func AlphaBeta($aNode, $iDepth, $iAlpha, $iBeta, $bMaximizingPlayer) Local $aChildNodes[7], $iV, $iNumOfNodes, $sState If $iDepth = 0 Or IsTerminal($aNode, $sState) Then Return Score($aNode, $iDepth, $sState) EndIf If $bMaximizingPlayer Then $iV = -$INFINITY GetChildNodes($aNode, $CPU, $iNumOfNodes, $aChildNodes) For $iIdx = 0 To $iNumOfNodes-1 $iV = Max($iV, AlphaBeta($aChildNodes[$iIdx], $iDepth-1, $iAlpha, $iBeta, False)) $iAlpha = Max($iAlpha, $iV) If $iBeta <= $iAlpha Then ExitLoop EndIf Next Return $iV Else $iV = +$INFINITY GetChildNodes($aNode, $PLAYER, $iNumOfNodes, $aChildNodes) For $iIdx = 0 To $iNumOfNodes-1 $iV = Min($iV, AlphaBeta($aChildNodes[$iIdx], $iDepth-1, $iAlpha, $iBeta, True)) $iBeta = Min($iBeta, $iV) If $iBeta <= $iAlpha Then ExitLoop EndIf Next Return $iV EndIf EndFunc Func GetChildNodes($aNode, $sSide, ByRef $iNumOfNodes, ByRef $aChildNodes) Local $aMoveOrder = [3, 2, 4, 1, 5, 0, 6] Local $iRow $iNumOfNodes = 0 For $iIdx = 0 To 6 If IsValidMove($aNode, $aMoveOrder[$iIdx]) Then $iRow = PiecesInColumn($aNode, $aMoveOrder[$iIdx]) $aNode[$iRow][$aMoveOrder[$iIdx]] = $sSide $aChildNodes[$iNumOfNodes] = $aNode $aNode[$iRow][$aMoveOrder[$iIdx]] = $EMPTY_SQUARE $iNumOfNodes += 1 EndIf Next EndFunc Func IsTerminal($aNode, ByRef $sResult) Local $bBoardFull = True For $iRow = 0 To 5 For $iCol = 0 To 6 If $aNode[$iRow][$iCol] <> $EMPTY_SQUARE Then ;; Check horizontal. If $iCol+3 <= 6 Then If $aNode[$iRow][$iCol] = $aNode[$iRow][$iCol+1] And _ $aNode[$iRow][$iCol+1] = $aNode[$iRow][$iCol+2] And _ $aNode[$iRow][$iCol+2] = $aNode[$iRow][$iCol+3] Then $sResult = $aNode[$iRow][$iCol] Return True EndIf EndIf ;; Check vertical. If $iRow+3 <= 5 Then If $aNode[$iRow][$iCol] = $aNode[$iRow+1][$iCol] And _ $aNode[$iRow+1][$iCol] = $aNode[$iRow+2][$iCol] And _ $aNode[$iRow+2][$iCol] = $aNode[$iRow+3][$iCol] Then $sResult = $aNode[$iRow][$iCol] Return True EndIf EndIf ;; Check ascending diagonal. If $iRow+3 <= 5 And $iCol+3 <= 6 Then If $aNode[$iRow][$iCol] = $aNode[$iRow+1][$iCol+1] And _ $aNode[$iRow+1][$iCol+1] = $aNode[$iRow+2][$iCol+2] And _ $aNode[$iRow+2][$iCol+2] = $aNode[$iRow+3][$iCol+3] Then $sResult = $aNode[$iRow][$iCol] Return True EndIf EndIf ;; Check descending diagonal. If $iRow-3 >= 0 And $iCol+3 <= 6 Then If $aNode[$iRow][$iCol] = $aNode[$iRow-1][$iCol+1] And _ $aNode[$iRow-1][$iCol+1] = $aNode[$iRow-2][$iCol+2] And _ $aNode[$iRow-2][$iCol+2] = $aNode[$iRow-3][$iCol+3] Then $sResult = $aNode[$iRow][$iCol] Return True EndIf EndIf Else $bBoardFull = False Endif Next Next If $bBoardFull Then $sResult = $DRAW Return True Else $sResult = "None" Return False EndIf EndFunc Func Score($aNode, $iDepth, $sState) Local $iScore = 0 Switch $sState Case "None" Case $CPU Return $CPU_WIN_SCORE+$iDepth Case $PLAYER Return $PLAYER_WIN_SCORE-$iDepth Case $DRAW Return $DRAW_SCORE EndSwitch For $iRow = 0 To 5 For $iCol = 0 To 6 If $aNode[$iRow][$iCol] = $EMPTY_SQUARE Then ;; Calculate left. If $iRow-3 >= 0 Then Evaluate(Asc($aNode[$iRow][$iCol])+Asc($aNode[$iRow-1][$iCol])+Asc($aNode[$iRow-2][$iCol])+Asc($aNode[$iRow-3][$iCol]), $iScore) EndIf ;; Calculate upper left. If $iRow+3 <= 5 And $iCol-3 >= 0 Then Evaluate(Asc($aNode[$iRow][$iCol])+Asc($aNode[$iRow+1][$iCol-1])+Asc($aNode[$iRow+2][$iCol-2])+Asc($aNode[$iRow+3][$iCol-3]), $iScore) EndIf ;; Calculate up. If $iRow+3 <= 5 Then Evaluate(Asc($aNode[$iRow][$iCol])+Asc($aNode[$iRow+1][$iCol])+Asc($aNode[$iRow+2][$iCol])+Asc($aNode[$iRow+3][$iCol]), $iScore) EndIf ;; Calculate upper right. If $iRow+3 <= 5 And $iCol+3 <= 6 Then Evaluate(Asc($aNode[$iRow][$iCol])+Asc($aNode[$iRow+1][$iCol+1])+Asc($aNode[$iRow+2][$iCol+2])+Asc($aNode[$iRow+3][$iCol+3]), $iScore) EndIf ;; Calculate right. If $iCol+3 <= 6 Then Evaluate(Asc($aNode[$iRow][$iCol])+Asc($aNode[$iRow][$iCol+1])+Asc($aNode[$iRow][$iCol+2])+Asc($aNode[$iRow][$iCol+3]), $iScore) EndIf EndIf Next Next Return $iScore EndFunc Func Evaluate($iSum, ByRef $iScore) Switch $iSum Case $3C1E $iScore += 24 Case $3P1E $iScore += -24 Case $2C2E $iScore += 8 Case $2P2E $iScore += -8 Case $1C3E $iScore += 2 Case $1P3E $iScore += -2 EndSwitch EndFunc Chimp small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....
UEZ Posted April 18, 2018 Posted April 18, 2018 (edited) Nice implementation. But when when the score is even (all coins were used) the game doesn't end -> no "it is a draw" message. Edited April 18, 2018 by UEZ Please don't send me any personal message and ask for support! I will not reply! Selection of finest graphical examples at Codepen.io The own fart smells best! ✌Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!¯\_(ツ)_/¯ ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ
Barney Posted April 18, 2018 Author Posted April 18, 2018 Hi Chimp, nice play! If we play carefully, we have the chance to win. I set the $MAX_DEPTH to 4, If it's bigger then 4, it's slow ... the evaluation function is the key!
Barney Posted April 18, 2018 Author Posted April 18, 2018 Thanks UEZ! Oh, a bug found?! I will try to fix it.
Barney Posted April 20, 2018 Author Posted April 20, 2018 Hi, I just upload a new version 2.0 to solve a problem, In version 1.0 when we reach an end position(either CPU or Player win) with two groups of "connect 4" pieces join together, the program just highlight one group, now both groups will be highlighted, and I change the search algorithm from MiniMax to NegaMax, it's a bit faster now.
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now