Skip to content

Instantly share code, notes, and snippets.

@auycro
Created September 7, 2025 09:44
Show Gist options
  • Select an option

  • Save auycro/2632670821a4fe7d721aac07d9941796 to your computer and use it in GitHub Desktop.

Select an option

Save auycro/2632670821a4fe7d721aac07d9941796 to your computer and use it in GitHub Desktop.
gomoku game written in go. cross check by claude ai
package main
import (
"fmt"
"math/rand"
"time"
)
const BoardSize = 6
type Position struct {
Row int
Column int
}
type Board = map[Position]rune
type Game struct {
Board Board
CurrentTurn rune
Winner rune
IsOver bool
Players map[rune]string
MoveCount int
TotalMoves int
MaxMoves int
Mode string // "PvP" or "PvE"
AILevel string // "easy", "medium", "hard"
}
func NewGame(mode string, aiLevel string, maxMoves int) *Game {
return &Game{
Board: make(Board),
CurrentTurn: 'X',
Winner: ' ',
IsOver: false,
Players: map[rune]string{'X': "Player 1", 'O': "Player 2"},
MoveCount: 0,
TotalMoves: 0,
MaxMoves: maxMoves,
Mode: mode,
AILevel: aiLevel,
}
}
func (g *Game) Reset() {
g.Board = make(Board)
g.CurrentTurn = 'X'
g.Winner = ' '
g.IsOver = false
g.MoveCount = 0
g.TotalMoves = 0
}
func (g *Game) SwitchTurn() {
if g.CurrentTurn == 'X' {
g.CurrentTurn = 'O'
} else {
g.CurrentTurn = 'X'
}
g.MoveCount++
g.TotalMoves++
if g.MaxMoves > 0 && g.TotalMoves >= g.MaxMoves {
g.IsOver = true
}
}
func (g *Game) MakeMove(row, column int) bool {
if g.IsOver {
return false
}
pos := Position{row, column}
if _, exists := g.Board[pos]; exists {
return false // Position already taken
}
g.Board[pos] = g.CurrentTurn
g.checkWinner(row, column)
if !g.IsOver {
g.SwitchTurn()
}
return true
}
func (g *Game) checkWinner(row, column int) {
directions := [][]Position{
{{0, 1}, {0, -1}}, // Horizontal
{{1, 0}, {-1, 0}}, // Vertical
{{1, 1}, {-1, -1}}, // Diagonal \
{{1, -1}, {-1, 1}}, // Diagonal /
}
for _, direction := range directions {
count := 1 // Count the current piece
for _, dir := range direction {
r, c := row, column
for {
r += dir.Row
c += dir.Column
if r < 0 || r >= BoardSize || c < 0 || c >= BoardSize {
break
}
if g.Board[Position{r, c}] == g.CurrentTurn {
count++
} else {
break
}
}
}
if count >= 5 {
g.Winner = g.CurrentTurn
g.IsOver = true
return
}
}
if len(g.Board) == BoardSize*BoardSize {
g.IsOver = true // Draw
}
}
func (g *Game) GetBoardState() [][]rune {
state := make([][]rune, BoardSize)
for i := range state {
state[i] = make([]rune, BoardSize)
for j := range state[i] {
state[i][j] = ' '
}
}
for pos, val := range g.Board {
state[pos.Row][pos.Column] = val
}
return state
}
func (g *Game) GetCurrentPlayerName() string {
return g.Players[g.CurrentTurn]
}
func (g *Game) GetWinnerName() string {
if g.Winner == ' ' {
return "Draw"
}
return g.Players[g.Winner]
}
func (g *Game) IsDraw() bool {
return g.IsOver && g.Winner == ' '
}
func (g *Game) GetAvailableMoves() []Position {
var moves []Position
for r := 0; r < BoardSize; r++ {
for c := 0; c < BoardSize; c++ {
pos := Position{r, c}
if _, exists := g.Board[pos]; !exists {
moves = append(moves, pos)
}
}
}
return moves
}
func (g *Game) SetPlayerNames(player1, player2 string) {
g.Players['X'] = player1
g.Players['O'] = player2
}
func (g *Game) Clone() *Game {
newGame := NewGame(g.Mode, g.AILevel, g.MaxMoves)
newGame.CurrentTurn = g.CurrentTurn
newGame.Winner = g.Winner
newGame.IsOver = g.IsOver
newGame.MoveCount = g.MoveCount
newGame.TotalMoves = g.TotalMoves
newGame.Players = map[rune]string{'X': g.Players['X'], 'O': g.Players['O']}
for pos, val := range g.Board {
newGame.Board[pos] = val
}
return newGame
}
func (g *Game) GetScore() int {
if g.Winner == 'X' {
return 10 - g.MoveCount
} else if g.Winner == 'O' {
return g.MoveCount - 10
}
return 0
}
func (g *Game) GetAIMove() Position {
if g.Mode != "PvE" || g.IsOver {
return Position{-1, -1}
}
availableMoves := g.GetAvailableMoves()
if len(availableMoves) == 0 {
return Position{-1, -1}
}
rand.Seed(time.Now().UnixNano())
switch g.AILevel {
case "easy":
return availableMoves[rand.Intn(len(availableMoves))]
case "medium":
// Check for winning move
for _, move := range availableMoves {
clone := g.Clone()
if clone.MakeMove(move.Row, move.Column) && clone.Winner == 'O' {
return move
}
}
// Block opponent's winning move
for _, move := range availableMoves {
clone := g.Clone()
clone.CurrentTurn = 'X'
if clone.MakeMove(move.Row, move.Column) && clone.Winner == 'X' {
return move
}
}
return availableMoves[rand.Intn(len(availableMoves))]
case "hard":
bestScore := -1000
var bestMove Position
for _, move := range availableMoves {
clone := g.Clone()
clone.MakeMove(move.Row, move.Column)
score := minimax(clone, 0, false)
if score > bestScore {
bestScore = score
bestMove = move
}
}
return bestMove
default:
return availableMoves[0]
}
}
func minimax(game *Game, depth int, isMaximizing bool) int {
if game.IsOver {
return game.GetScore()
}
if isMaximizing {
bestScore := -1000
for _, move := range game.GetAvailableMoves() {
clone := game.Clone()
clone.MakeMove(move.Row, move.Column)
score := minimax(clone, depth+1, false)
if score > bestScore {
bestScore = score
}
}
return bestScore
} else {
bestScore := 1000
for _, move := range game.GetAvailableMoves() {
clone := game.Clone()
clone.MakeMove(move.Row, move.Column)
score := minimax(clone, depth+1, true)
if score < bestScore {
bestScore = score
}
}
return bestScore
}
}
func (g *Game) PrintBoard() {
state := g.GetBoardState()
// Print column numbers
fmt.Print(" ")
for c := 0; c < BoardSize; c++ {
fmt.Printf("%d ", c)
}
fmt.Println()
// Print board with row numbers
for r := 0; r < BoardSize; r++ {
fmt.Printf("%d ", r)
for c := 0; c < BoardSize; c++ {
if state[r][c] == ' ' {
fmt.Print(". ")
} else {
fmt.Printf("%c ", state[r][c])
}
}
fmt.Println()
}
fmt.Println()
}
func (g *Game) GetGameStatus() string {
if g.IsOver {
if g.Winner != ' ' {
return g.GetWinnerName() + " wins!"
}
return "It's a draw!"
}
return g.GetCurrentPlayerName() + "'s turn (" + string(g.CurrentTurn) + ")"
}
func (g *Game) IsPositionValid(row, column int) bool {
return row >= 0 && row < BoardSize && column >= 0 && column < BoardSize
}
func (g *Game) IsCellEmpty(row, column int) bool {
if !g.IsPositionValid(row, column) {
return false
}
pos := Position{row, column}
_, exists := g.Board[pos]
return !exists
}
// Simple game loop for demonstration
func playGame() {
fmt.Println("Welcome to Gomoku!")
fmt.Println("Connect 5 pieces in a row to win!")
fmt.Println()
// Create a new PvP game
game := NewGame("PvP", "", 0)
game.SetPlayerNames("Alice", "Bob")
for !game.IsOver {
game.PrintBoard()
fmt.Println(game.GetGameStatus())
var row, col int
fmt.Printf("Enter row and column (0-%d): ", BoardSize-1)
// Simple input handling - in a real game you'd want better error handling
_, err := fmt.Scanf("%d %d", &row, &col)
if err != nil {
fmt.Println("Invalid input. Please enter two numbers.")
continue
}
if !game.IsPositionValid(row, col) {
fmt.Printf("Invalid position. Please enter values between 0 and %d.\n", BoardSize-1)
continue
}
if !game.IsCellEmpty(row, col) {
fmt.Println("Position already taken. Try another position.")
continue
}
if game.MakeMove(row, col) {
fmt.Printf("Move made at (%d, %d)\n", row, col)
} else {
fmt.Println("Invalid move. Try again.")
}
fmt.Println()
}
game.PrintBoard()
fmt.Println("Game Over!")
fmt.Println(game.GetGameStatus())
}
// AI vs Player demo
func playAIGame() {
fmt.Println("Welcome to Gomoku vs AI!")
fmt.Println("You are X, AI is O")
fmt.Println()
game := NewGame("PvE", "medium", 0)
game.SetPlayerNames("Human", "AI")
for !game.IsOver {
game.PrintBoard()
fmt.Println(game.GetGameStatus())
if game.CurrentTurn == 'X' {
// Human turn
var row, col int
fmt.Printf("Enter your move (row col, 0-%d): ", BoardSize-1)
_, err := fmt.Scanf("%d %d", &row, &col)
if err != nil {
fmt.Println("Invalid input. Please enter two numbers.")
continue
}
if !game.IsPositionValid(row, col) {
fmt.Printf("Invalid position. Please enter values between 0 and %d.\n", BoardSize-1)
continue
}
if !game.IsCellEmpty(row, col) {
fmt.Println("Position already taken. Try another position.")
continue
}
if game.MakeMove(row, col) {
fmt.Printf("You played at (%d, %d)\n", row, col)
}
} else {
// AI turn
aiMove := game.GetAIMove()
if aiMove.Row != -1 && aiMove.Column != -1 {
game.MakeMove(aiMove.Row, aiMove.Column)
fmt.Printf("AI played at (%d, %d)\n", aiMove.Row, aiMove.Column)
}
}
fmt.Println()
}
game.PrintBoard()
fmt.Println("Game Over!")
fmt.Println(game.GetGameStatus())
}
func main() {
fmt.Println("Choose game mode:")
fmt.Println("1. Player vs Player")
fmt.Println("2. Player vs AI")
fmt.Print("Enter choice (1 or 2): ")
var choice int
fmt.Scanf("%d", &choice)
switch choice {
case 1:
playGame()
case 2:
playAIGame()
default:
fmt.Println("Invalid choice. Starting Player vs Player mode.")
playGame()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment