Skip to content

Commit

Permalink
Add AI difficulty setting
Browse files Browse the repository at this point in the history
  • Loading branch information
armsnyder committed Oct 27, 2020
1 parent 9ce098c commit b3b0e72
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 15 deletions.
3 changes: 2 additions & 1 deletion pkg/client/scenes/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Game struct {
nickname string
whoseTurn common.Disk
multiplayer bool
difficulty int
}

func (g *Game) Setup(changeScene ChangeScene, sendMessage SendMessage) error {
Expand All @@ -30,7 +31,7 @@ func (g *Game) Setup(changeScene ChangeScene, sendMessage SendMessage) error {

var message interface{}
if g.player == 1 {
message = common.NewNewGameMessage(g.multiplayer)
message = common.NewNewGameMessage(g.multiplayer, g.difficulty)
} else {
message = common.NewJoinGameMessage()
}
Expand Down
41 changes: 32 additions & 9 deletions pkg/client/scenes/menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
)

const (
buttonSingleplayer = iota
buttonNormal = iota
buttonEasy
buttonHard
buttonHostGame
buttonJoinGame
buttonChangeName
Expand All @@ -28,24 +30,32 @@ func (m *Menu) OnTerminalEvent(event termbox.Event) error {
case buttonChangeName:
m.button = buttonHostGame
case buttonHostGame, buttonJoinGame:
m.button = buttonSingleplayer
m.button = buttonNormal
}
case dx == 1:
switch m.button {
case buttonSingleplayer:
case buttonEasy, buttonNormal, buttonHard:
m.button = buttonHostGame
case buttonHostGame, buttonJoinGame:
m.button = buttonChangeName
}
case dy == -1:
switch m.button {
case buttonNormal:
m.button = buttonEasy
case buttonHard:
m.button = buttonNormal
case buttonJoinGame:
m.button = buttonHostGame
default:
m.button = buttonChangeName
}
case dy == 1:
switch m.button {
case buttonEasy:
m.button = buttonNormal
case buttonNormal:
m.button = buttonHard
case buttonHostGame:
m.button = buttonJoinGame
case buttonChangeName:
Expand All @@ -55,8 +65,12 @@ func (m *Menu) OnTerminalEvent(event termbox.Event) error {

if event.Key == termbox.KeyEnter {
switch m.button {
case buttonSingleplayer:
return m.ChangeScene(&Game{player: 1, multiplayer: false, nickname: m.nickname})
case buttonEasy:
return m.ChangeScene(&Game{player: 1, difficulty: 0, nickname: m.nickname})
case buttonNormal:
return m.ChangeScene(&Game{player: 1, difficulty: 1, nickname: m.nickname})
case buttonHard:
return m.ChangeScene(&Game{player: 1, difficulty: 2, nickname: m.nickname})
case buttonHostGame:
return m.ChangeScene(&Game{player: 1, multiplayer: true, nickname: m.nickname})
case buttonJoinGame:
Expand All @@ -75,18 +89,27 @@ func (m *Menu) Draw() {

draw(topRight, normal, fmt.Sprintf("Did you know? Your name is %s!", m.nickname))

buttonColors := [4]color{normal, normal, normal, normal}
buttonColors := [6]color{normal, normal, normal, normal, normal, normal}
buttonColors[m.button] = inverted

multiplayerButtonColor := normal
multiplayerOffset := offset(centerRight, 1, 3)
if m.button == buttonHostGame || m.button == buttonJoinGame {
multiplayerButtonColor = inverted
draw(offset(multiplayerOffset, 0, 2), buttonColors[buttonHostGame], "[ HOST GAME ]")
draw(offset(multiplayerOffset, 0, 4), buttonColors[buttonJoinGame], "[ JOIN GAME ]")
draw(offset(multiplayerOffset, 1, 2), buttonColors[buttonHostGame], "[ HOST GAME ]")
draw(offset(multiplayerOffset, 1, 4), buttonColors[buttonJoinGame], "[ JOIN GAME ]")
}

draw(offset(centerLeft, -1, 3), buttonColors[buttonSingleplayer], "[ SINGLEPLAYER ]")
singleplayerButtonColor := normal
singleplayerOffset := offset(centerLeft, -1, 3)
if m.button == buttonEasy || m.button == buttonNormal || m.button == buttonHard {
singleplayerButtonColor = inverted
draw(offset(singleplayerOffset, -4, 2), buttonColors[buttonEasy], "[ EASY ]")
draw(offset(singleplayerOffset, -3, 4), buttonColors[buttonNormal], "[ NORMAL ]")
draw(offset(singleplayerOffset, -4, 6), buttonColors[buttonHard], "[ HARD ]")
}

draw(offset(centerLeft, -1, 3), singleplayerButtonColor, "[ SINGLEPLAYER ]")
draw(multiplayerOffset, multiplayerButtonColor, "[ MULTIPLAYER ]")
draw(offset(topRight, 0, 2), buttonColors[buttonChangeName], "[ CHANGE NAME ]")
}
4 changes: 3 additions & 1 deletion pkg/common/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ func NewUpdateBoardMessage(board Board, player Disk) UpdateBoardMessage {
type NewGameMessage struct {
Action string `json:"action"`
Multiplayer bool `json:"multiplayer"`
Difficulty int `json:"difficulty"`
}

func NewNewGameMessage(multiplayer bool) NewGameMessage {
func NewNewGameMessage(multiplayer bool, difficulty int) NewGameMessage {
return NewGameMessage{
Action: NewGameAction,
Multiplayer: multiplayer,
Difficulty: difficulty,
}
}

Expand Down
16 changes: 13 additions & 3 deletions pkg/server/ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,26 @@ import (
)

// doAIPlayerMove takes a turn as the AI player.
func doAIPlayerMove(ctx context.Context, board common.Board, player common.Disk) common.Board {
func doAIPlayerMove(ctx context.Context, board common.Board, player common.Disk, difficulty int) common.Board {
aiState := &aiGameState{
board: board,
player: player,
}

ctx2, cancel := context.WithTimeout(ctx, time.Second)
ctx2, cancel := context.WithTimeout(ctx, time.Second*2)
defer cancel()

move := minimaxWithIterativeDeepening(ctx2, aiState, 64)
var depth int
switch difficulty {
default:
depth = 1
case 1:
depth = 5
case 2:
depth = common.BoardSize * common.BoardSize // No limit
}

move := minimaxWithIterativeDeepening(ctx2, aiState, depth)
return aiState.moves[move]
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/server/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ type gameState struct {
board common.Board
player common.Disk
multiplayer bool
difficulty int
}

type gameItem struct {
ID string `json:"id"`
Board []byte `json:"board"`
Player int `json:"player"`
Multiplayer bool `json:"multiplayer"`
Difficulty int `json:"difficulty"`
}

func getAllConnectionIDs(ctx context.Context) ([]string, error) {
Expand Down Expand Up @@ -108,6 +110,7 @@ func loadGame(ctx context.Context) (gameState, error) {
board: board,
player: common.Disk(gameItem.Player),
multiplayer: gameItem.Multiplayer,
difficulty: gameItem.Difficulty,
}, err
}

Expand All @@ -124,6 +127,7 @@ func saveGame(ctx context.Context, game gameState) error {
Board: b,
Player: int(game.player),
Multiplayer: game.multiplayer,
Difficulty: game.difficulty,
}

item, err := dynamodbattribute.MarshalMap(gameItem)
Expand Down
10 changes: 9 additions & 1 deletion pkg/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"log"
"time"

"github.com/aws/aws-lambda-go/events"

Expand Down Expand Up @@ -84,7 +85,13 @@ func handlePlaceDisk(ctx context.Context, req events.APIGatewayWebsocketProxyReq
for !game.multiplayer && game.player == 2 && common.HasMoves(game.board, 2) {
log.Println("Taking AI turn")

game.board = doAIPlayerMove(ctx, game.board, game.player)
turnStartedAt := time.Now()
game.board = doAIPlayerMove(ctx, game.board, game.player, game.difficulty)

// Pad the turn time in case the AI was very quick, so the player doesn't stress or know
// they're losing.
time.Sleep(time.Second - time.Since(turnStartedAt))

if common.HasMoves(game.board, game.player%2+1) {
game.player = game.player%2 + 1
}
Expand Down Expand Up @@ -116,6 +123,7 @@ func handleNewGame(ctx context.Context, req events.APIGatewayWebsocketProxyReque
board: board,
player: 1,
multiplayer: message.Multiplayer,
difficulty: message.Difficulty,
}

if err := saveGame(ctx, game); err != nil {
Expand Down

0 comments on commit b3b0e72

Please sign in to comment.