Created
September 7, 2025 09:44
-
-
Save auycro/2632670821a4fe7d721aac07d9941796 to your computer and use it in GitHub Desktop.
gomoku game written in go. cross check by claude ai
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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