@@ -2,14 +2,20 @@ package engine
22
33import "github.com/RubikNube/GoInGo/cmd/game"
44
5- // AlphaBetaEngine implements Engine using an evaluation function and alpha-beta pruning.
6- type AlphaBetaEngine struct {}
5+ // AlphaBetaEngine implements Engine using alpha-beta pruning with killer move heuristic.
6+ type AlphaBetaEngine struct {
7+ killerMoves map [int ]* game.Point // depth -> killer move
8+ }
9+
10+ func NewAlphaBetaEngine () * AlphaBetaEngine {
11+ return & AlphaBetaEngine {killerMoves : make (map [int ]* game.Point )}
12+ }
713
814// Move in AlphaBetaEngine uses alpha-beta pruning to select the best move or pass if no beneficial move exists.
915func (e * AlphaBetaEngine ) Move (board game.Board , player game.FieldState , ko * game.Point ) * game.Point {
1016 bestScore := - 1 << 30
1117 var bestMove * game.Point
12- depth := 3 // Shallow for performance; increase for stronger play
18+ depth := 4 // Shallow for performance; increase for stronger play
1319 moveFound := false
1420
1521 for i := 0 ; i < 9 ; i ++ {
@@ -42,7 +48,7 @@ func (e *AlphaBetaEngine) Move(board game.Board, player game.FieldState, ko *gam
4248 if len (libs ) == 0 {
4349 continue
4450 }
45- score := - alphaBeta (nextBoard , opp , player , ko , depth - 1 , - 1 << 30 , 1 << 30 )
51+ score := - e . alphaBeta (nextBoard , opp , player , ko , depth - 1 , - 1 << 30 , 1 << 30 )
4652 moveFound = true
4753 if score > bestScore {
4854 bestScore = score
@@ -52,7 +58,7 @@ func (e *AlphaBetaEngine) Move(board game.Board, player game.FieldState, ko *gam
5258 }
5359 }
5460 // Pass if no move found or if passing is as good or better than any move
55- passScore := - alphaBeta (board , opponent (player ), player , ko , depth - 1 , - 1 << 30 , 1 << 30 )
61+ passScore := - e . alphaBeta (board , opponent (player ), player , ko , depth - 1 , - 1 << 30 , 1 << 30 )
5662 if ! moveFound || passScore >= bestScore {
5763 return nil // pass
5864 }
@@ -67,12 +73,46 @@ func opponent(player game.FieldState) game.FieldState {
6773 return game .Black
6874}
6975
70- // alphaBeta is a minimax search with alpha-beta pruning and pass support .
71- func alphaBeta (board game.Board , player , opp game.FieldState , ko * game.Point , depth , alpha , beta int ) int {
76+ // alphaBeta is a minimax search with alpha-beta pruning and killer move heuristic .
77+ func ( e * AlphaBetaEngine ) alphaBeta (board game.Board , player , opp game.FieldState , ko * game.Point , depth , alpha , beta int ) int {
7278 if depth == 0 {
7379 return evaluate (board , player , opp )
7480 }
7581 foundMove := false
82+
83+ // Try killer move first if available
84+ if killer , ok := e .killerMoves [depth ]; ok && killer != nil && board [killer.Row ][killer.Col ] == game .Empty {
85+ pt := * killer
86+ if ko == nil || pt .Row != ko .Row || pt .Col != ko .Col {
87+ var nextBoard game.Board
88+ copy (nextBoard [:], board [:])
89+ nextBoard [pt.Row ][pt.Col ] = player
90+ for _ , n := range game .Neighbors (pt ) {
91+ if nextBoard [n.Row ][n.Col ] == opp {
92+ group , libs := game .Group (nextBoard , n )
93+ if len (libs ) == 0 {
94+ for stonePt := range group {
95+ nextBoard [stonePt.Row ][stonePt.Col ] = game .Empty
96+ }
97+ }
98+ }
99+ }
100+ _ , libs := game .Group (nextBoard , pt )
101+ if len (libs ) != 0 {
102+ foundMove = true
103+ score := - e .alphaBeta (nextBoard , opp , player , ko , depth - 1 , - beta , - alpha )
104+ if score > alpha {
105+ alpha = score
106+ // Update killer move if this move caused a beta cutoff
107+ if alpha >= beta {
108+ e .killerMoves [depth ] = & pt
109+ return alpha
110+ }
111+ }
112+ }
113+ }
114+ }
115+
76116 for i := 0 ; i < 9 ; i ++ {
77117 for j := 0 ; j < 9 ; j ++ {
78118 if board [i ][j ] != game .Empty {
@@ -82,6 +122,10 @@ func alphaBeta(board game.Board, player, opp game.FieldState, ko *game.Point, de
82122 if ko != nil && pt .Row == ko .Row && pt .Col == ko .Col {
83123 continue
84124 }
125+ // Skip killer move (already tried)
126+ if killer , ok := e .killerMoves [depth ]; ok && killer != nil && pt .Row == killer .Row && pt .Col == killer .Col {
127+ continue
128+ }
85129 var nextBoard game.Board
86130 copy (nextBoard [:], board [:])
87131 nextBoard [pt.Row ][pt.Col ] = player
@@ -100,17 +144,20 @@ func alphaBeta(board game.Board, player, opp game.FieldState, ko *game.Point, de
100144 continue
101145 }
102146 foundMove = true
103- score := - alphaBeta (nextBoard , opp , player , ko , depth - 1 , - beta , - alpha )
147+ score := - e . alphaBeta (nextBoard , opp , player , ko , depth - 1 , - beta , - alpha )
104148 if score > alpha {
105149 alpha = score
106- }
107- if alpha >= beta {
108- return alpha
150+ // Update killer move if this move caused a beta cutoff
151+ if alpha >= beta {
152+ move := pt
153+ e .killerMoves [depth ] = & move
154+ return alpha
155+ }
109156 }
110157 }
111158 }
112159 // Consider passing if no move found or passing is better
113- passScore := - alphaBeta (board , opp , player , ko , depth - 1 , - beta , - alpha )
160+ passScore := - e . alphaBeta (board , opp , player , ko , depth - 1 , - beta , - alpha )
114161 if ! foundMove || passScore > alpha {
115162 alpha = passScore
116163 }
0 commit comments