|
25 | 25 | koPoint *game.Point // Track Ko point (nil if no Ko) |
26 | 26 | passCount int // Track consecutive passes |
27 | 27 | gameOver bool // Track if the game is over |
| 28 | + engineEnabled bool // Play against engine if true |
| 29 | + engine game.Engine // The engine instance |
28 | 30 | ) |
29 | 31 |
|
30 | 32 | func loadConfig(path string) (Config, error) { |
@@ -232,6 +234,17 @@ func placeStone(g *gocui.Gui, v *gocui.View) error { |
232 | 234 | passCount = 0 // Reset pass count on a move |
233 | 235 |
|
234 | 236 | currentPlayer = 3 - currentPlayer // Switch player only after a legal move |
| 237 | + |
| 238 | + // If engine is enabled and it's the engine's turn, make engine move |
| 239 | + if engineEnabled && !gameOver && currentPlayer == 2 { |
| 240 | + go func() { |
| 241 | + time.Sleep(300 * time.Millisecond) |
| 242 | + g.Update(func(g *gocui.Gui) error { |
| 243 | + engineMove(g) |
| 244 | + return nil |
| 245 | + }) |
| 246 | + }() |
| 247 | + } |
235 | 248 | return nil |
236 | 249 | } |
237 | 250 |
|
@@ -287,14 +300,75 @@ func quit(g *gocui.Gui, v *gocui.View) error { |
287 | 300 | return gocui.ErrQuit |
288 | 301 | } |
289 | 302 |
|
| 303 | +func engineMove(g *gocui.Gui) { |
| 304 | + // Use the engine interface to get a move for White |
| 305 | + if engine == nil { |
| 306 | + return |
| 307 | + } |
| 308 | + move := engine.Move(gui.Grid, game.White, koPoint) |
| 309 | + if move != nil { |
| 310 | + // Do not move the cursor for the engine, just place the stone directly |
| 311 | + row, col := move.Row, move.Col |
| 312 | + gui.Grid[row][col] = game.White |
| 313 | + |
| 314 | + // Simulate captures and ko logic as in placeStone |
| 315 | + var nextBoard game.Board |
| 316 | + copy(nextBoard[:], gui.Grid[:]) |
| 317 | + opp := game.Black |
| 318 | + captured := []game.Point{} |
| 319 | + for _, n := range game.Neighbors(game.Point{Row: row, Col: col}) { |
| 320 | + if nextBoard[n.Row][n.Col] == opp { |
| 321 | + group, libs := game.Group(nextBoard, n) |
| 322 | + if len(libs) == 0 { |
| 323 | + for stonePt := range group { |
| 324 | + nextBoard[stonePt.Row][stonePt.Col] = game.Empty |
| 325 | + captured = append(captured, stonePt) |
| 326 | + } |
| 327 | + } |
| 328 | + } |
| 329 | + } |
| 330 | + for _, pt := range captured { |
| 331 | + gui.Grid[pt.Row][pt.Col] = game.Empty |
| 332 | + } |
| 333 | + // Ko rule: set koPoint if exactly one stone was captured and the group size is 1 |
| 334 | + if len(captured) == 1 { |
| 335 | + koPoint = &captured[0] |
| 336 | + } else { |
| 337 | + koPoint = nil |
| 338 | + } |
| 339 | + // Update prevBoard for next move (for Ko rule) |
| 340 | + if prevBoard == nil { |
| 341 | + prev := game.Board{} |
| 342 | + copy(prev[:], gui.Grid[:]) |
| 343 | + prevBoard = &prev |
| 344 | + } |
| 345 | + copy(prevBoard[:], gui.Grid[:]) |
| 346 | + passCount = 0 |
| 347 | + currentPlayer = 1 // Switch back to player |
| 348 | + } else { |
| 349 | + _ = passTurn(g, nil) |
| 350 | + } |
| 351 | +} |
| 352 | + |
290 | 353 | func main() { |
291 | 354 | g, err := gocui.NewGui(gocui.OutputNormal) |
| 355 | + if err != nil { |
| 356 | + log.Panicln(err) |
| 357 | + } |
292 | 358 | cfg, err := loadConfig("config.json") |
293 | 359 | if err != nil { |
294 | 360 | log.Panicln("Failed to load config:", err) |
295 | 361 | } |
296 | 362 | keybindings = cfg.Keybindings |
297 | 363 |
|
| 364 | + engine = &game.RandomEngine{} |
| 365 | + engineEnabled = true // Enable engine by default |
| 366 | + |
| 367 | + defer g.Close() |
| 368 | + keybindings = cfg.Keybindings |
| 369 | + |
| 370 | + engine = &game.RandomEngine{} |
| 371 | + |
298 | 372 | if err != nil { |
299 | 373 | log.Panicln(err) |
300 | 374 | } |
@@ -359,8 +433,32 @@ func main() { |
359 | 433 | if err := g.SetKeybinding("", quitKey, gocui.ModNone, quit); err != nil { |
360 | 434 | log.Panicln(err) |
361 | 435 | } |
| 436 | + // Engine toggle: enable for second player |
| 437 | + enableEngineKey := []rune(keybindings["enableEngine"])[0] |
| 438 | + if err := g.SetKeybinding("", enableEngineKey, gocui.ModNone, toggleEngine); err != nil { |
| 439 | + log.Panicln(err) |
| 440 | + } |
362 | 441 |
|
363 | 442 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { |
364 | 443 | log.Panicln(err) |
365 | 444 | } |
366 | 445 | } |
| 446 | + |
| 447 | +func toggleEngine(g *gocui.Gui, v *gocui.View) error { |
| 448 | + engineEnabled = !engineEnabled |
| 449 | + if v, err := g.View("prompt"); err == nil && v != nil { |
| 450 | + v.Clear() |
| 451 | + printMovePrompt(v) |
| 452 | + } |
| 453 | + // If toggled on and it's engine's turn (player 2/White), make engine move |
| 454 | + if engineEnabled && !gameOver && currentPlayer == 2 { |
| 455 | + go func() { |
| 456 | + time.Sleep(300 * time.Millisecond) |
| 457 | + g.Update(func(g *gocui.Gui) error { |
| 458 | + engineMove(g) |
| 459 | + return nil |
| 460 | + }) |
| 461 | + }() |
| 462 | + } |
| 463 | + return nil |
| 464 | +} |
0 commit comments