v0.1.0 chess game in go, using external stockfish engine
This commit is contained in:
248
internal/transport/cli/handler.go
Normal file
248
internal/transport/cli/handler.go
Normal file
@ -0,0 +1,248 @@
|
||||
// FILE: internal/transport/cli/handler.go
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"chess/internal/cli"
|
||||
"chess/internal/core"
|
||||
"chess/internal/service"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CLIHandler struct {
|
||||
svc *service.Service
|
||||
view *cli.CLI
|
||||
gameID string
|
||||
}
|
||||
|
||||
func New(svc *service.Service, view *cli.CLI) *CLIHandler {
|
||||
return &CLIHandler{
|
||||
svc: svc,
|
||||
view: view,
|
||||
}
|
||||
}
|
||||
|
||||
// Main game loop - simple command processing
|
||||
func (h *CLIHandler) Run() {
|
||||
for {
|
||||
// Generate prompt based on current game state
|
||||
prompt := h.getPrompt()
|
||||
h.view.ShowPrompt(prompt)
|
||||
|
||||
// Get command (blocking)
|
||||
cmd, err := h.view.GetCommand()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Process command - returns false to exit
|
||||
if !h.ProcessCommand(cmd) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generates the appropriate command prompt
|
||||
func (h *CLIHandler) getPrompt() string {
|
||||
prompt := "> "
|
||||
if h.gameID != "" {
|
||||
g, err := h.svc.GetGame(h.gameID)
|
||||
if err == nil && g.State() == core.StateOngoing {
|
||||
// Always show whose turn it is
|
||||
prompt = fmt.Sprintf("[%c]> ", g.NextTurn())
|
||||
if g.NextPlayer().Type == core.PlayerComputer {
|
||||
prompt = "ENTER to execute computer move\n" + prompt
|
||||
}
|
||||
}
|
||||
}
|
||||
return prompt
|
||||
}
|
||||
|
||||
// Handles user commands - returns false to exit
|
||||
func (h *CLIHandler) ProcessCommand(cmd *cli.Command) bool {
|
||||
switch cmd.Type {
|
||||
case cli.CmdQuit:
|
||||
return false
|
||||
|
||||
case cli.CmdNone:
|
||||
// Empty command triggers computer move if it's computer's turn
|
||||
if h.gameID != "" {
|
||||
g, err := h.svc.GetGame(h.gameID)
|
||||
if err == nil && g.State() == core.StateOngoing &&
|
||||
g.NextPlayer().Type == core.PlayerComputer {
|
||||
h.executeComputerMove()
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case cli.CmdNew:
|
||||
return h.handleNewGame("")
|
||||
|
||||
case cli.CmdResume:
|
||||
if len(cmd.Args) < 1 {
|
||||
h.view.ShowMessage("Usage: resume <FEN string>")
|
||||
return true
|
||||
}
|
||||
fen := strings.Join(cmd.Args, " ")
|
||||
return h.handleNewGame(fen)
|
||||
|
||||
case cli.CmdMove:
|
||||
if h.gameID == "" {
|
||||
h.view.ShowMessage("No active game. Use 'new' or 'resume <FEN>'.")
|
||||
return true
|
||||
}
|
||||
|
||||
g, _ := h.svc.GetGame(h.gameID)
|
||||
if g.NextPlayer().Type != core.PlayerHuman {
|
||||
h.view.ShowMessage("It's not a human player's turn. Press ENTER to execute computer move.")
|
||||
return true
|
||||
}
|
||||
|
||||
if err := h.svc.MakeHumanMove(h.gameID, cmd.Args[0]); err != nil {
|
||||
h.view.ShowError(fmt.Errorf("invalid move: %v", err))
|
||||
return true
|
||||
}
|
||||
|
||||
// Get result and display human move
|
||||
g, _ = h.svc.GetGame(h.gameID)
|
||||
result := g.LastResult()
|
||||
if result != nil {
|
||||
h.view.ShowHumanMove(result.Move)
|
||||
}
|
||||
|
||||
board, _ := h.svc.GetCurrentBoard(h.gameID)
|
||||
h.view.DisplayBoard(board)
|
||||
|
||||
if result != nil && result.GameState != core.StateOngoing {
|
||||
h.view.ShowGameOver(result.GameState)
|
||||
h.gameID = ""
|
||||
}
|
||||
|
||||
case cli.CmdUndo:
|
||||
if h.gameID == "" {
|
||||
h.view.ShowMessage("No active game.")
|
||||
return true
|
||||
}
|
||||
|
||||
// Parse undo count
|
||||
count := 1
|
||||
if len(cmd.Args) > 0 {
|
||||
if n, err := strconv.Atoi(cmd.Args[0]); err == nil && n > 0 {
|
||||
count = n
|
||||
} else {
|
||||
h.view.ShowMessage("Invalid undo count. Usage: undo [count]")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.svc.Undo(h.gameID, count); err != nil {
|
||||
h.view.ShowError(err)
|
||||
} else {
|
||||
if count == 1 {
|
||||
h.view.ShowMessage("Move undone")
|
||||
} else {
|
||||
h.view.ShowMessage(fmt.Sprintf("%d moves undone", count))
|
||||
}
|
||||
|
||||
board, _ := h.svc.GetCurrentBoard(h.gameID)
|
||||
h.view.DisplayBoard(board)
|
||||
}
|
||||
|
||||
case cli.CmdColor:
|
||||
if len(cmd.Args) < 1 {
|
||||
h.view.ShowMessage("Usage: color <off|brown|green|gray>")
|
||||
return true
|
||||
}
|
||||
|
||||
theme := cli.ColorTheme(cmd.Args[0])
|
||||
if err := h.view.SetTheme(theme); err != nil {
|
||||
h.view.ShowError(err)
|
||||
} else {
|
||||
h.view.ShowMessage(fmt.Sprintf("Color theme set to: %s", theme))
|
||||
if h.gameID != "" {
|
||||
board, _ := h.svc.GetCurrentBoard(h.gameID)
|
||||
h.view.DisplayBoard(board)
|
||||
}
|
||||
}
|
||||
|
||||
case cli.CmdVerbose:
|
||||
verbose := h.view.ToggleVerbose()
|
||||
h.view.ShowMessage(fmt.Sprintf("Verbose mode: %t", verbose))
|
||||
|
||||
case cli.CmdHistory:
|
||||
if h.gameID == "" {
|
||||
h.view.ShowMessage("No active game.")
|
||||
return true
|
||||
}
|
||||
g, _ := h.svc.GetGame(h.gameID)
|
||||
h.view.ShowGameHistory(g)
|
||||
|
||||
case cli.CmdHelp:
|
||||
h.view.ShowHelp()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *CLIHandler) executeComputerMove() {
|
||||
result, err := h.svc.MakeComputerMove(h.gameID)
|
||||
if err != nil {
|
||||
h.view.ShowError(fmt.Errorf("engine error: %v", err))
|
||||
h.gameID = ""
|
||||
return
|
||||
}
|
||||
|
||||
h.view.ShowComputerMove(result)
|
||||
board, _ := h.svc.GetCurrentBoard(h.gameID)
|
||||
h.view.DisplayBoard(board)
|
||||
|
||||
if result.GameState != core.StateOngoing {
|
||||
h.view.ShowGameOver(result.GameState)
|
||||
h.gameID = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Starts a new game with player type selection
|
||||
func (h *CLIHandler) handleNewGame(fen string) bool {
|
||||
// Get player types
|
||||
h.view.ShowPrompt("Select White player (h/c): ")
|
||||
whiteInput := h.view.ReadLine()
|
||||
var whiteType core.PlayerType
|
||||
if whiteInput == "c" || whiteInput == "computer" {
|
||||
whiteType = core.PlayerComputer
|
||||
} else {
|
||||
whiteType = core.PlayerHuman
|
||||
}
|
||||
|
||||
h.view.ShowPrompt("Select Black player (h/c): ")
|
||||
blackInput := h.view.ReadLine()
|
||||
var blackType core.PlayerType
|
||||
if blackInput == "c" || blackInput == "computer" {
|
||||
blackType = core.PlayerComputer
|
||||
} else {
|
||||
blackType = core.PlayerHuman
|
||||
}
|
||||
|
||||
// Create new game
|
||||
h.gameID = uuid.New().String()
|
||||
var fenArray []string
|
||||
if fen != "" {
|
||||
fenArray = []string{fen}
|
||||
}
|
||||
|
||||
if err := h.svc.NewGame(h.gameID, whiteType, blackType, fenArray...); err != nil {
|
||||
h.view.ShowError(fmt.Errorf("could not start the game: %v", err))
|
||||
h.gameID = ""
|
||||
return true
|
||||
}
|
||||
|
||||
h.view.ShowMessage("Game started.")
|
||||
board, _ := h.svc.GetCurrentBoard(h.gameID)
|
||||
h.view.DisplayBoard(board)
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user