diff --git a/game/gameloop.go b/game/gameloop.go index f876790..cc9c31e 100644 --- a/game/gameloop.go +++ b/game/gameloop.go @@ -1 +1,122 @@ package game + +import ( + "tictactoe/gamecontrol" + "tictactoe/opponent" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" +) + +var board = [3][3]string{ + {" ", " ", " "}, + {" ", " ", " "}, + {" ", " ", " "}, +} + +const ( + PLAYER_X = "X" + PLAYER_O = "O" +) + +var gridContainer *fyne.Container + +// creates the button grid for the tictactoe game +func MakeButtonGrid(rows, cols int) *fyne.Container { + // generates layout and grid container + gridLayout := layout.NewGridLayout(cols) + gridContainer = container.New(gridLayout) + // create the 3x3 Button grid + for row := 0; row < rows; row++ { + for col := 0; col < cols; col++ { + button := CreateButton(row, col) + gridContainer.Add(button) + } + } + + return gridContainer +} + +// create the button grid and adds key press handler for each grid-button +func CreateButton(row, col int) *widget.Button { + button := widget.NewButton(" ", nil) + + button.OnTapped = func() { + if button.Text == " " { + button.SetText(PLAYER_X) + Gameloop(row, col) + } + } + return button +} + +// reset the game by clearing the buttons and the board array +func ResetButtonTexts(container *fyne.Container) { + for _, child := range container.Objects { + if btn, ok := child.(*widget.Button); ok { + btn.SetText(" ") + } + } + board = [3][3]string{ + {" ", " ", " "}, + {" ", " ", " "}, + {" ", " ", " "}, + } + EnableAllButtons(gridContainer) + TextLabel.SetText("TicTacToe") +} + +// apply AI opponent text to the button +func ChangeButtonText(row, col int) { + gridContainer.Objects[col+row*3].(*widget.Button).SetText("O") +} + +// gameloop function for the game process: +// human makes a move, AI counters +func Gameloop(row, col int) { + // starting with the human making a move + board[row][col] = PLAYER_X + // checking if the game has been won by the human player + if gamecontrol.PlayerHasWon(board, PLAYER_X) { + TextLabel.SetText("X Wins") + } + // checking if the game is a draw + if gamecontrol.BoardisFull(board) { + TextLabel.SetText("Draw") + } + // AI opponent makes a move + bestMove := opponent.Minimax(board, 0, true, PLAYER_O) + // add the move to the board + board[bestMove.Row][bestMove.Col] = PLAYER_O + ChangeButtonText(bestMove.Row, bestMove.Col) + + // check if AI opponent has won the game + if gamecontrol.PlayerHasWon(board, PLAYER_O) { + TextLabel.SetText("O Wins") + DisableAllButtons(gridContainer) + } + // checking if the game is a draw + if gamecontrol.BoardisFull(board) { + TextLabel.SetText("Draw") + } +} + +// function to disable all buttons inside the grid +func DisableAllButtons(container *fyne.Container) { + for _, child := range container.Objects { + if btn, ok := child.(*widget.Button); ok { + btn.Disable() + } + } +} + +// function to enable all buttons inside the grid +func EnableAllButtons(container *fyne.Container) { + for _, child := range container.Objects { + if btn, ok := child.(*widget.Button); ok { + btn.Enable() + } + } +} diff --git a/game/window.go b/game/window.go index c68a8e5..791a4f5 100644 --- a/game/window.go +++ b/game/window.go @@ -20,17 +20,18 @@ func CreateWindow() fyne.Window { // Set the text of our TextLabel to TicTacToe TextLabel = widget.NewLabel("TicTacToe") - - // Create a new button with the text "Restart" and a handler function, if it gets pressed. + // create the 3x3 Button grid + matchField := MakeButtonGrid(3, 3) + // Create a new button and a handler function, if it gets pressed. restartButton := widget.NewButton("Restart", func() { - // Add logic here to restart the game. + ResetButtonTexts(matchField) }) // vertical box layout containg the top Items, here the textlabel and the button. topItems := container.NewVBox(TextLabel, restartButton) - // Generate content for the window using a border layout with the topItems as the only child. - content := container.New(layout.NewBorderLayout(topItems, nil, nil, nil), topItems) + // Generate content for the window using a border layout with the topItems and matchField as the children + content := container.New(layout.NewBorderLayout(topItems, nil, nil, nil), topItems, matchField) // Set the content and size of the window. myWindow.SetContent(content)