initial
This commit is contained in:
commit
7f22b4f4b3
|
|
@ -0,0 +1,294 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const aiDepth = 3
|
||||
|
||||
var pieceValues = map[int]int{
|
||||
Empty: 0, Pawn: 100, Knight: 320, Bishop: 330,
|
||||
Rook: 500, Queen: 900, King: 20000,
|
||||
}
|
||||
|
||||
var pawnTable = [8][8]int{
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{50, 50, 50, 50, 50, 50, 50, 50},
|
||||
{10, 10, 20, 30, 30, 20, 10, 10},
|
||||
{5, 5, 10, 25, 25, 10, 5, 5},
|
||||
{0, 0, 0, 20, 20, 0, 0, 0},
|
||||
{5, -5, -10, 0, 0, -10, -5, 5},
|
||||
{5, 10, 10, -20, -20, 10, 10, 5},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
}
|
||||
|
||||
var knightTable = [8][8]int{
|
||||
{-50, -40, -30, -30, -30, -30, -40, -50},
|
||||
{-40, -20, 0, 0, 0, 0, -20, -40},
|
||||
{-30, 0, 10, 15, 15, 10, 0, -30},
|
||||
{-30, 5, 15, 20, 20, 15, 5, -30},
|
||||
{-30, 0, 15, 20, 20, 15, 0, -30},
|
||||
{-30, 5, 10, 15, 15, 10, 5, -30},
|
||||
{-40, -20, 0, 5, 5, 0, -20, -40},
|
||||
{-50, -40, -30, -30, -30, -30, -40, -50},
|
||||
}
|
||||
|
||||
var bishopTable = [8][8]int{
|
||||
{-20, -10, -10, -10, -10, -10, -10, -20},
|
||||
{-10, 0, 0, 0, 0, 0, 0, -10},
|
||||
{-10, 0, 5, 10, 10, 5, 0, -10},
|
||||
{-10, 5, 5, 10, 10, 5, 5, -10},
|
||||
{-10, 0, 10, 10, 10, 10, 0, -10},
|
||||
{-10, 10, 10, 10, 10, 10, 10, -10},
|
||||
{-10, 5, 0, 0, 0, 0, 5, -10},
|
||||
{-20, -10, -10, -10, -10, -10, -10, -20},
|
||||
}
|
||||
|
||||
var rookTable = [8][8]int{
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{5, 10, 10, 10, 10, 10, 10, 5},
|
||||
{-5, 0, 0, 0, 0, 0, 0, -5},
|
||||
{-5, 0, 0, 0, 0, 0, 0, -5},
|
||||
{-5, 0, 0, 0, 0, 0, 0, -5},
|
||||
{-5, 0, 0, 0, 0, 0, 0, -5},
|
||||
{-5, 0, 0, 0, 0, 0, 0, -5},
|
||||
{0, 0, 0, 5, 5, 0, 0, 0},
|
||||
}
|
||||
|
||||
var queenTable = [8][8]int{
|
||||
{-20, -10, -10, -5, -5, -10, -10, -20},
|
||||
{-10, 0, 0, 0, 0, 0, 0, -10},
|
||||
{-10, 0, 5, 5, 5, 5, 0, -10},
|
||||
{-5, 0, 5, 5, 5, 5, 0, -5},
|
||||
{0, 0, 5, 5, 5, 5, 0, -5},
|
||||
{-10, 5, 5, 5, 5, 5, 0, -10},
|
||||
{-10, 0, 5, 0, 0, 0, 0, -10},
|
||||
{-20, -10, -10, -5, -5, -10, -10, -20},
|
||||
}
|
||||
|
||||
var kingTable = [8][8]int{
|
||||
{-30, -40, -40, -50, -50, -40, -40, -30},
|
||||
{-30, -40, -40, -50, -50, -40, -40, -30},
|
||||
{-30, -40, -40, -50, -50, -40, -40, -30},
|
||||
{-30, -40, -40, -50, -50, -40, -40, -30},
|
||||
{-20, -30, -30, -40, -40, -30, -30, -20},
|
||||
{-10, -20, -20, -20, -20, -20, -20, -10},
|
||||
{20, 20, 0, 0, 0, 0, 20, 20},
|
||||
{20, 30, 10, 0, 0, 10, 30, 20},
|
||||
}
|
||||
|
||||
func evaluate(b *Board) int {
|
||||
score := 0
|
||||
for r := 0; r < 8; r++ {
|
||||
for c := 0; c < 8; c++ {
|
||||
s := b.Grid[r][c]
|
||||
if s.Piece == Empty {
|
||||
continue
|
||||
}
|
||||
val := pieceValues[s.Piece]
|
||||
var bonus int
|
||||
switch s.Piece {
|
||||
case Pawn:
|
||||
if s.Color == White {
|
||||
bonus = pawnTable[r][c]
|
||||
} else {
|
||||
bonus = pawnTable[7-r][c]
|
||||
}
|
||||
case Knight:
|
||||
if s.Color == White {
|
||||
bonus = knightTable[r][c]
|
||||
} else {
|
||||
bonus = knightTable[7-r][c]
|
||||
}
|
||||
case Bishop:
|
||||
if s.Color == White {
|
||||
bonus = bishopTable[r][c]
|
||||
} else {
|
||||
bonus = bishopTable[7-r][c]
|
||||
}
|
||||
case Rook:
|
||||
if s.Color == White {
|
||||
bonus = rookTable[r][c]
|
||||
} else {
|
||||
bonus = rookTable[7-r][c]
|
||||
}
|
||||
case Queen:
|
||||
if s.Color == White {
|
||||
bonus = queenTable[r][c]
|
||||
} else {
|
||||
bonus = queenTable[7-r][c]
|
||||
}
|
||||
case King:
|
||||
if s.Color == White {
|
||||
bonus = kingTable[r][c]
|
||||
} else {
|
||||
bonus = kingTable[7-r][c]
|
||||
}
|
||||
}
|
||||
if s.Color == White {
|
||||
score += val + bonus
|
||||
} else {
|
||||
score -= val + bonus
|
||||
}
|
||||
}
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
func minimax(b *Board, depth int, alpha, beta int, color int) int {
|
||||
if depth == 0 {
|
||||
return evaluate(b)
|
||||
}
|
||||
moves := b.generateLegalMoves(color)
|
||||
if len(moves) == 0 {
|
||||
if b.isInCheck(color) {
|
||||
if color == White {
|
||||
return -100000 - depth
|
||||
}
|
||||
return 100000 + depth
|
||||
}
|
||||
return 0
|
||||
}
|
||||
oppColor := Black
|
||||
if color == White {
|
||||
oppColor = Black
|
||||
} else {
|
||||
oppColor = White
|
||||
}
|
||||
if color == White {
|
||||
maxEval := -999999
|
||||
for _, m := range moves {
|
||||
clone := b.Clone()
|
||||
clone.applyMove(m)
|
||||
eval := minimax(clone, depth-1, alpha, beta, oppColor)
|
||||
if eval > maxEval {
|
||||
maxEval = eval
|
||||
}
|
||||
if maxEval > alpha {
|
||||
alpha = maxEval
|
||||
}
|
||||
if alpha >= beta {
|
||||
break
|
||||
}
|
||||
}
|
||||
return maxEval
|
||||
} else {
|
||||
minEval := 999999
|
||||
for _, m := range moves {
|
||||
clone := b.Clone()
|
||||
clone.applyMove(m)
|
||||
eval := minimax(clone, depth-1, alpha, beta, oppColor)
|
||||
if eval < minEval {
|
||||
minEval = eval
|
||||
}
|
||||
if minEval < beta {
|
||||
beta = minEval
|
||||
}
|
||||
if alpha >= beta {
|
||||
break
|
||||
}
|
||||
}
|
||||
return minEval
|
||||
}
|
||||
}
|
||||
|
||||
func GetBestMove(b *Board, color int) (string, error) {
|
||||
moves := b.generateLegalMoves(color)
|
||||
if len(moves) == 0 {
|
||||
return "", fmt.Errorf("no legal moves")
|
||||
}
|
||||
oppColor := Black
|
||||
if color == White {
|
||||
oppColor = Black
|
||||
} else {
|
||||
oppColor = White
|
||||
}
|
||||
var bestMove *Move
|
||||
var bestScore int
|
||||
first := true
|
||||
alpha := -999999
|
||||
beta := 999999
|
||||
sort.Slice(moves, func(i, j int) bool {
|
||||
vi := 0
|
||||
vj := 0
|
||||
if b.Grid[moves[i].To.Row][moves[i].To.Col].Piece != Empty {
|
||||
vi = pieceValues[b.Grid[moves[i].To.Row][moves[i].To.Col].Piece]
|
||||
}
|
||||
if b.Grid[moves[j].To.Row][moves[j].To.Col].Piece != Empty {
|
||||
vj = pieceValues[b.Grid[moves[j].To.Row][moves[j].To.Col].Piece]
|
||||
}
|
||||
return vi > vj
|
||||
})
|
||||
for _, m := range moves {
|
||||
clone := b.Clone()
|
||||
clone.applyMove(m)
|
||||
score := minimax(clone, aiDepth-1, alpha, beta, oppColor)
|
||||
if first {
|
||||
bestMove = &m
|
||||
bestScore = score
|
||||
first = false
|
||||
} else if color == White && score > bestScore {
|
||||
bestScore = score
|
||||
bestMove = &m
|
||||
alpha = score
|
||||
} else if color == Black && score < bestScore {
|
||||
bestScore = score
|
||||
bestMove = &m
|
||||
beta = score
|
||||
}
|
||||
}
|
||||
if bestMove == nil {
|
||||
return "", fmt.Errorf("no move found")
|
||||
}
|
||||
return MoveToAlgebraic(*bestMove), nil
|
||||
}
|
||||
|
||||
func PlayVsComputer() {
|
||||
fmt.Println("=== CHESS VS COMPUTER ===")
|
||||
fmt.Println("You play WHITE. Enter moves like: e2e4, g1f3, e7e8q")
|
||||
fmt.Println("Type 'quit' to exit.")
|
||||
fmt.Println()
|
||||
g := NewGame()
|
||||
fmt.Print(g.Render())
|
||||
for !g.Over {
|
||||
fmt.Print("White> ")
|
||||
var input string
|
||||
fmt.Scanln(&input)
|
||||
if input == "quit" || input == "q" {
|
||||
fmt.Println("Goodbye!")
|
||||
return
|
||||
}
|
||||
_, err := g.MakeMove(input)
|
||||
if err != nil {
|
||||
fmt.Printf(" Error: %v\n\n", err)
|
||||
continue
|
||||
}
|
||||
fmt.Print(g.Render())
|
||||
if g.Over {
|
||||
break
|
||||
}
|
||||
fmt.Print(" Black thinking... ")
|
||||
aiMove, err := GetBestMove(g.Board, Black)
|
||||
if err != nil {
|
||||
fmt.Printf("AI error: %v\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("Black plays %s\n", aiMove)
|
||||
_, err = g.MakeMove(aiMove)
|
||||
if err != nil {
|
||||
fmt.Printf(" Error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
fmt.Print(g.Render())
|
||||
}
|
||||
switch g.Result {
|
||||
case WhiteWins:
|
||||
fmt.Println(" *** YOU WIN! ***")
|
||||
case BlackWins:
|
||||
fmt.Println(" *** COMPUTER WINS! ***")
|
||||
case Draw:
|
||||
fmt.Println(" *** DRAW! ***")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,505 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
Empty = 0
|
||||
Pawn = 1
|
||||
Knight = 2
|
||||
Bishop = 3
|
||||
Rook = 4
|
||||
Queen = 5
|
||||
King = 6
|
||||
)
|
||||
|
||||
const (
|
||||
White = 1
|
||||
Black = 2
|
||||
)
|
||||
|
||||
type Square struct {
|
||||
Piece int
|
||||
Color int
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
Row int
|
||||
Col int
|
||||
}
|
||||
|
||||
type Board struct {
|
||||
Grid [8][8]Square
|
||||
Castling map[string]bool
|
||||
EnPassantTarget *Position
|
||||
MoveHistory []string
|
||||
}
|
||||
|
||||
func NewBoard() *Board {
|
||||
b := &Board{
|
||||
Castling: map[string]bool{"K": true, "Q": true, "k": true, "q": true},
|
||||
}
|
||||
backRank := []int{Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook}
|
||||
for col, p := range backRank {
|
||||
b.Grid[0][col] = Square{Piece: p, Color: Black}
|
||||
b.Grid[7][col] = Square{Piece: p, Color: White}
|
||||
}
|
||||
for col := 0; col < 8; col++ {
|
||||
b.Grid[1][col] = Square{Piece: Pawn, Color: Black}
|
||||
b.Grid[6][col] = Square{Piece: Pawn, Color: White}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Board) Clone() *Board {
|
||||
clone := *b
|
||||
clone.Castling = make(map[string]bool)
|
||||
for k, v := range b.Castling {
|
||||
clone.Castling[k] = v
|
||||
}
|
||||
if b.EnPassantTarget != nil {
|
||||
pt := *b.EnPassantTarget
|
||||
clone.EnPassantTarget = &pt
|
||||
}
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (b *Board) inBounds(r, c int) bool {
|
||||
return r >= 0 && r < 8 && c >= 0 && c < 8
|
||||
}
|
||||
|
||||
func (b *Board) isSquareAttacked(pos Position, byColor int) bool {
|
||||
r, c := pos.Row, pos.Col
|
||||
pawnDir := 1
|
||||
if byColor == Black {
|
||||
pawnDir = -1
|
||||
}
|
||||
for dc := -1; dc <= 1; dc += 2 {
|
||||
nr, nc := r-pawnDir, c+dc
|
||||
if b.inBounds(nr, nc) {
|
||||
if b.Grid[nr][nc].Piece == Pawn && b.Grid[nr][nc].Color == byColor {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
knightMoves := [][2]int{{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}}
|
||||
for _, dm := range knightMoves {
|
||||
nr, nc := r+dm[0], c+dm[1]
|
||||
if b.inBounds(nr, nc) {
|
||||
if b.Grid[nr][nc].Piece == Knight && b.Grid[nr][nc].Color == byColor {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
for dr := -1; dr <= 1; dr++ {
|
||||
for dc := -1; dc <= 1; dc++ {
|
||||
if dr == 0 && dc == 0 {
|
||||
continue
|
||||
}
|
||||
nr, nc := r+dr, c+dc
|
||||
if b.inBounds(nr, nc) {
|
||||
if b.Grid[nr][nc].Piece == King && b.Grid[nr][nc].Color == byColor {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ortho := [][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
|
||||
for _, dir := range ortho {
|
||||
nr, nc := r+dir[0], c+dir[1]
|
||||
for b.inBounds(nr, nc) {
|
||||
s := b.Grid[nr][nc]
|
||||
if s.Piece != Empty {
|
||||
if s.Color == byColor && (s.Piece == Rook || s.Piece == Queen) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
}
|
||||
nr += dir[0]
|
||||
nc += dir[1]
|
||||
}
|
||||
}
|
||||
diag := [][2]int{{-1, -1}, {-1, 1}, {1, -1}, {1, 1}}
|
||||
for _, dir := range diag {
|
||||
nr, nc := r+dir[0], c+dir[1]
|
||||
for b.inBounds(nr, nc) {
|
||||
s := b.Grid[nr][nc]
|
||||
if s.Piece != Empty {
|
||||
if s.Color == byColor && (s.Piece == Bishop || s.Piece == Queen) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
}
|
||||
nr += dir[0]
|
||||
nc += dir[1]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Board) findKing(color int) Position {
|
||||
for r := 0; r < 8; r++ {
|
||||
for c := 0; c < 8; c++ {
|
||||
if b.Grid[r][c].Piece == King && b.Grid[r][c].Color == color {
|
||||
return Position{r, c}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Position{}
|
||||
}
|
||||
|
||||
func (b *Board) isInCheck(color int) bool {
|
||||
kingPos := b.findKing(color)
|
||||
oppColor := Black
|
||||
if color == Black {
|
||||
oppColor = White
|
||||
}
|
||||
return b.isSquareAttacked(kingPos, oppColor)
|
||||
}
|
||||
|
||||
type Move struct {
|
||||
From Position
|
||||
To Position
|
||||
Promotion int
|
||||
}
|
||||
|
||||
func (b *Board) generatePseudoLegalMoves(color int) []Move {
|
||||
var moves []Move
|
||||
for r := 0; r < 8; r++ {
|
||||
for c := 0; c < 8; c++ {
|
||||
s := b.Grid[r][c]
|
||||
if s.Piece == Empty || s.Color != color {
|
||||
continue
|
||||
}
|
||||
from := Position{r, c}
|
||||
switch s.Piece {
|
||||
case Pawn:
|
||||
moves = append(moves, b.pawnMoves(from, color)...)
|
||||
case Knight:
|
||||
moves = append(moves, b.knightMoves(from, color)...)
|
||||
case Bishop:
|
||||
moves = append(moves, b.slideMoves(from, color, diagDirs)...)
|
||||
case Rook:
|
||||
moves = append(moves, b.slideMoves(from, color, orthoDirs)...)
|
||||
case Queen:
|
||||
all := append(append([][2]int{}, diagDirs...), orthoDirs...)
|
||||
moves = append(moves, b.slideMoves(from, color, all)...)
|
||||
case King:
|
||||
moves = append(moves, b.kingMoves(from, color)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return moves
|
||||
}
|
||||
|
||||
var orthoDirs = [][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
|
||||
var diagDirs = [][2]int{{-1, -1}, {-1, 1}, {1, -1}, {1, 1}}
|
||||
|
||||
func (b *Board) pawnMoves(from Position, color int) []Move {
|
||||
var moves []Move
|
||||
r, c := from.Row, from.Col
|
||||
dir := 1
|
||||
if color == Black {
|
||||
dir = -1
|
||||
}
|
||||
nr := r + dir
|
||||
if b.inBounds(nr, c) && b.Grid[nr][c].Piece == Empty {
|
||||
if (color == White && nr == 0) || (color == Black && nr == 7) {
|
||||
for _, p := range []int{Queen, Rook, Bishop, Knight} {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, c}, Promotion: p})
|
||||
}
|
||||
} else {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, c}})
|
||||
}
|
||||
startRow := 6
|
||||
if color == Black {
|
||||
startRow = 1
|
||||
}
|
||||
if r == startRow {
|
||||
nr2 := r + 2*dir
|
||||
if b.inBounds(nr2, c) && b.Grid[nr2][c].Piece == Empty {
|
||||
moves = append(moves, Move{From: from, To: Position{nr2, c}})
|
||||
}
|
||||
}
|
||||
}
|
||||
for dc := -1; dc <= 1; dc += 2 {
|
||||
nc := c + dc
|
||||
if !b.inBounds(nr, nc) {
|
||||
continue
|
||||
}
|
||||
target := b.Grid[nr][nc]
|
||||
if target.Piece != Empty && target.Color != color {
|
||||
if (color == White && nr == 0) || (color == Black && nr == 7) {
|
||||
for _, p := range []int{Queen, Rook, Bishop, Knight} {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, nc}, Promotion: p})
|
||||
}
|
||||
} else {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, nc}})
|
||||
}
|
||||
}
|
||||
if b.EnPassantTarget != nil && b.EnPassantTarget.Row == nr && b.EnPassantTarget.Col == nc {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, nc}})
|
||||
}
|
||||
}
|
||||
return moves
|
||||
}
|
||||
|
||||
func (b *Board) knightMoves(from Position, color int) []Move {
|
||||
var moves []Move
|
||||
kd := [][2]int{{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}}
|
||||
for _, d := range kd {
|
||||
nr, nc := from.Row+d[0], from.Col+d[1]
|
||||
if !b.inBounds(nr, nc) {
|
||||
continue
|
||||
}
|
||||
if b.Grid[nr][nc].Color != color {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, nc}})
|
||||
}
|
||||
}
|
||||
return moves
|
||||
}
|
||||
|
||||
func (b *Board) slideMoves(from Position, color int, dirs [][2]int) []Move {
|
||||
var moves []Move
|
||||
for _, d := range dirs {
|
||||
nr, nc := from.Row+d[0], from.Col+d[1]
|
||||
for b.inBounds(nr, nc) {
|
||||
if b.Grid[nr][nc].Piece == Empty {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, nc}})
|
||||
} else {
|
||||
if b.Grid[nr][nc].Color != color {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, nc}})
|
||||
}
|
||||
break
|
||||
}
|
||||
nr += d[0]
|
||||
nc += d[1]
|
||||
}
|
||||
}
|
||||
return moves
|
||||
}
|
||||
|
||||
func (b *Board) kingMoves(from Position, color int) []Move {
|
||||
var moves []Move
|
||||
for dr := -1; dr <= 1; dr++ {
|
||||
for dc := -1; dc <= 1; dc++ {
|
||||
if dr == 0 && dc == 0 {
|
||||
continue
|
||||
}
|
||||
nr, nc := from.Row+dr, from.Col+dc
|
||||
if !b.inBounds(nr, nc) {
|
||||
continue
|
||||
}
|
||||
if b.Grid[nr][nc].Color != color {
|
||||
moves = append(moves, Move{From: from, To: Position{nr, nc}})
|
||||
}
|
||||
}
|
||||
}
|
||||
oppColor := Black
|
||||
if color == White {
|
||||
oppColor = Black
|
||||
} else {
|
||||
oppColor = White
|
||||
}
|
||||
if color == White && from.Row == 7 && from.Col == 4 {
|
||||
if b.Castling["K"] && b.Grid[7][5].Piece == Empty && b.Grid[7][6].Piece == Empty {
|
||||
if !b.isSquareAttacked(Position{7, 4}, oppColor) &&
|
||||
!b.isSquareAttacked(Position{7, 5}, oppColor) &&
|
||||
!b.isSquareAttacked(Position{7, 6}, oppColor) {
|
||||
moves = append(moves, Move{From: from, To: Position{7, 6}})
|
||||
}
|
||||
}
|
||||
if b.Castling["Q"] && b.Grid[7][3].Piece == Empty && b.Grid[7][2].Piece == Empty && b.Grid[7][1].Piece == Empty {
|
||||
if !b.isSquareAttacked(Position{7, 4}, oppColor) &&
|
||||
!b.isSquareAttacked(Position{7, 3}, oppColor) &&
|
||||
!b.isSquareAttacked(Position{7, 2}, oppColor) {
|
||||
moves = append(moves, Move{From: from, To: Position{7, 2}})
|
||||
}
|
||||
}
|
||||
} else if color == Black && from.Row == 0 && from.Col == 4 {
|
||||
if b.Castling["k"] && b.Grid[0][5].Piece == Empty && b.Grid[0][6].Piece == Empty {
|
||||
if !b.isSquareAttacked(Position{0, 4}, oppColor) &&
|
||||
!b.isSquareAttacked(Position{0, 5}, oppColor) &&
|
||||
!b.isSquareAttacked(Position{0, 6}, oppColor) {
|
||||
moves = append(moves, Move{From: from, To: Position{0, 6}})
|
||||
}
|
||||
}
|
||||
if b.Castling["q"] && b.Grid[0][3].Piece == Empty && b.Grid[0][2].Piece == Empty && b.Grid[0][1].Piece == Empty {
|
||||
if !b.isSquareAttacked(Position{0, 4}, oppColor) &&
|
||||
!b.isSquareAttacked(Position{0, 3}, oppColor) &&
|
||||
!b.isSquareAttacked(Position{0, 2}, oppColor) {
|
||||
moves = append(moves, Move{From: from, To: Position{0, 2}})
|
||||
}
|
||||
}
|
||||
}
|
||||
return moves
|
||||
}
|
||||
|
||||
func (b *Board) generateLegalMoves(color int) []Move {
|
||||
pseudo := b.generatePseudoLegalMoves(color)
|
||||
var legal []Move
|
||||
for _, m := range pseudo {
|
||||
clone := b.Clone()
|
||||
clone.applyMove(m)
|
||||
if !clone.isInCheck(color) {
|
||||
legal = append(legal, m)
|
||||
}
|
||||
}
|
||||
return legal
|
||||
}
|
||||
|
||||
func (b *Board) applyMove(m Move) {
|
||||
from, to := m.From, m.To
|
||||
movingPiece := b.Grid[from.Row][from.Col]
|
||||
if movingPiece.Piece == Pawn && to.Col != from.Col && b.Grid[to.Row][to.Col].Piece == Empty {
|
||||
b.Grid[from.Row][to.Col] = Square{}
|
||||
}
|
||||
b.Grid[to.Row][to.Col] = movingPiece
|
||||
b.Grid[from.Row][from.Col] = Square{}
|
||||
if movingPiece.Piece == Pawn && m.Promotion != 0 {
|
||||
b.Grid[to.Row][to.Col].Piece = m.Promotion
|
||||
}
|
||||
if movingPiece.Piece == King && MathAbs(to.Col-from.Col) == 2 {
|
||||
row := from.Row
|
||||
if to.Col == 6 {
|
||||
b.Grid[row][5] = b.Grid[row][7]
|
||||
b.Grid[row][7] = Square{}
|
||||
} else if to.Col == 2 {
|
||||
b.Grid[row][3] = b.Grid[row][0]
|
||||
b.Grid[row][0] = Square{}
|
||||
}
|
||||
}
|
||||
if movingPiece.Piece == King {
|
||||
if movingPiece.Color == White {
|
||||
b.Castling["K"] = false
|
||||
b.Castling["Q"] = false
|
||||
} else {
|
||||
b.Castling["k"] = false
|
||||
b.Castling["q"] = false
|
||||
}
|
||||
}
|
||||
if movingPiece.Piece == Rook {
|
||||
if from.Row == 7 && from.Col == 0 {
|
||||
b.Castling["Q"] = false
|
||||
}
|
||||
if from.Row == 7 && from.Col == 7 {
|
||||
b.Castling["K"] = false
|
||||
}
|
||||
if from.Row == 0 && from.Col == 0 {
|
||||
b.Castling["q"] = false
|
||||
}
|
||||
if from.Row == 0 && from.Col == 7 {
|
||||
b.Castling["k"] = false
|
||||
}
|
||||
}
|
||||
if to.Row == 7 && to.Col == 0 {
|
||||
b.Castling["Q"] = false
|
||||
}
|
||||
if to.Row == 7 && to.Col == 7 {
|
||||
b.Castling["K"] = false
|
||||
}
|
||||
if to.Row == 0 && to.Col == 0 {
|
||||
b.Castling["q"] = false
|
||||
}
|
||||
if to.Row == 0 && to.Col == 7 {
|
||||
b.Castling["k"] = false
|
||||
}
|
||||
b.EnPassantTarget = nil
|
||||
if movingPiece.Piece == Pawn {
|
||||
if MathAbs(to.Row-from.Row) == 2 {
|
||||
b.EnPassantTarget = &Position{(from.Row + to.Row) / 2, from.Col}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MathAbs(x int) int {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (b *Board) Display(checkColor int) string {
|
||||
pieceChars := map[int]string{
|
||||
Pawn: "P", Knight: "N", Bishop: "B", Rook: "R", Queen: "Q", King: "K",
|
||||
}
|
||||
var out string
|
||||
out += " a b c d e f g h\n"
|
||||
for r := 0; r < 8; r++ {
|
||||
out += fmt.Sprintf("%d", 8-r)
|
||||
for c := 0; c < 8; c++ {
|
||||
s := b.Grid[r][c]
|
||||
if s.Piece == Empty {
|
||||
out += " ."
|
||||
} else {
|
||||
ch := pieceChars[s.Piece]
|
||||
if s.Color == White {
|
||||
out += " " + ch
|
||||
} else {
|
||||
out += " " + string(rune(97+int(ch[0])-65))
|
||||
}
|
||||
}
|
||||
}
|
||||
out += fmt.Sprintf(" %d\n", 8-r)
|
||||
}
|
||||
out += " a b c d e f g h\n"
|
||||
if b.isInCheck(checkColor) {
|
||||
out += fmt.Sprintf("\n !! %s IS IN CHECK !!\n", colorName(checkColor))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func colorName(c int) string {
|
||||
if c == White {
|
||||
return "WHITE"
|
||||
}
|
||||
return "BLACK"
|
||||
}
|
||||
|
||||
func (b *Board) ParseMove(input string) (*Move, error) {
|
||||
if len(input) < 4 || len(input) > 5 {
|
||||
return nil, fmt.Errorf("invalid move format: %s (use e.g. e2e4)", input)
|
||||
}
|
||||
fromCol := int(input[0] - 'a')
|
||||
fromRow := 8 - int(input[1] - '0')
|
||||
toCol := int(input[2] - 'a')
|
||||
toRow := 8 - int(input[3] - '0')
|
||||
if fromCol < 0 || fromCol > 7 || toCol < 0 || toCol > 7 {
|
||||
return nil, fmt.Errorf("invalid columns")
|
||||
}
|
||||
if fromRow < 0 || fromRow > 7 || toRow < 0 || toRow > 7 {
|
||||
return nil, fmt.Errorf("invalid rows")
|
||||
}
|
||||
promotion := 0
|
||||
if len(input) == 5 {
|
||||
switch input[4] {
|
||||
case 'q', 'Q':
|
||||
promotion = Queen
|
||||
case 'r', 'R':
|
||||
promotion = Rook
|
||||
case 'b', 'B':
|
||||
promotion = Bishop
|
||||
case 'n', 'N':
|
||||
promotion = Knight
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid promotion piece: %c", input[4])
|
||||
}
|
||||
}
|
||||
m := Move{
|
||||
From: Position{fromRow, fromCol},
|
||||
To: Position{toRow, toCol},
|
||||
Promotion: promotion,
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func MoveToAlgebraic(m Move) string {
|
||||
from := string(rune('a'+m.From.Col)) + string(rune('0'+(8-m.From.Row)))
|
||||
to := string(rune('a'+m.To.Col)) + string(rune('0'+(8-m.To.Row)))
|
||||
s := from + to
|
||||
if m.Promotion != 0 {
|
||||
pieceChars := map[int]byte{Queen: 'q', Rook: 'r', Bishop: 'b', Knight: 'n'}
|
||||
s += string(pieceChars[m.Promotion])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PlayMultiplayer(addr string) {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
fmt.Printf("Error connecting to %s: %v\n", addr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
fmt.Printf("Connected to %s\n", addr)
|
||||
fmt.Fprintln(conn, "HELLO")
|
||||
serverReader := bufio.NewReader(conn)
|
||||
clientReader := bufio.NewReader(os.Stdin)
|
||||
welcome, _ := serverReader.ReadString('\n')
|
||||
fmt.Print(welcome)
|
||||
line, _ := serverReader.ReadString('\n')
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "WAITING" {
|
||||
fmt.Println("Waiting for an opponent...")
|
||||
line, _ = serverReader.ReadString('\n')
|
||||
line = strings.TrimSpace(line)
|
||||
}
|
||||
if !strings.HasPrefix(line, "MATCHED") {
|
||||
fmt.Println("Failed to join a game.")
|
||||
return
|
||||
}
|
||||
parts := strings.Split(line, " ")
|
||||
myColor := parts[2]
|
||||
fmt.Printf("\nMatched! You play %s.\n", myColor)
|
||||
fmt.Println("Moves: e2e4, g1f3, e7e8q | RESIGN | QUIT")
|
||||
fmt.Println()
|
||||
gameOver := false
|
||||
for !gameOver {
|
||||
header, err := serverReader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Println("\nConnection lost.")
|
||||
break
|
||||
}
|
||||
header = strings.TrimSpace(header)
|
||||
switch {
|
||||
case header == "BOARD":
|
||||
var lines []string
|
||||
for {
|
||||
bl, err := serverReader.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
bl = strings.TrimRight(bl, "\n")
|
||||
if bl == "" {
|
||||
break
|
||||
}
|
||||
lines = append(lines, bl)
|
||||
}
|
||||
joined := strings.Join(lines, " ")
|
||||
fmt.Println(strings.Join(lines, "\n"))
|
||||
fmt.Println()
|
||||
if strings.Contains(joined, "WINS") || strings.Contains(joined, "STALEMATE") {
|
||||
gameOver = true
|
||||
}
|
||||
case strings.HasPrefix(header, "ERROR"):
|
||||
fmt.Printf(" %s\n", header)
|
||||
default:
|
||||
}
|
||||
if !gameOver {
|
||||
fmt.Print("You> ")
|
||||
input, _ := clientReader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
if input == "QUIT" {
|
||||
fmt.Println("Disconnecting...")
|
||||
break
|
||||
}
|
||||
fmt.Fprintln(conn, input)
|
||||
}
|
||||
}
|
||||
fmt.Println("Goodbye!")
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type GameResult int
|
||||
|
||||
const (
|
||||
GameOngoing GameResult = iota
|
||||
WhiteWins
|
||||
BlackWins
|
||||
Draw
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
Board *Board
|
||||
Turn int
|
||||
Over bool
|
||||
Result GameResult
|
||||
MoveNum int
|
||||
}
|
||||
|
||||
func NewGame() *Game {
|
||||
return &Game{
|
||||
Board: NewBoard(),
|
||||
Turn: White,
|
||||
Over: false,
|
||||
Result: GameOngoing,
|
||||
MoveNum: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) MakeMove(input string) (string, error) {
|
||||
if g.Over {
|
||||
return "", fmt.Errorf("game is over")
|
||||
}
|
||||
m, err := g.Board.ParseMove(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s := g.Board.Grid[m.From.Row][m.From.Col]
|
||||
if s.Piece == Empty || s.Color != g.Turn {
|
||||
return "", fmt.Errorf("no %s piece on %s", colorName(g.Turn), algebraic(m.From))
|
||||
}
|
||||
legalMoves := g.Board.generateLegalMoves(g.Turn)
|
||||
found := false
|
||||
for _, lm := range legalMoves {
|
||||
if lm.From == m.From && lm.To == m.To && lm.Promotion == m.Promotion {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return "", fmt.Errorf("illegal move: %s", input)
|
||||
}
|
||||
g.Board.MoveHistory = append(g.Board.MoveHistory, input)
|
||||
g.Board.applyMove(*m)
|
||||
if g.Turn == White {
|
||||
g.Turn = Black
|
||||
g.MoveNum++
|
||||
} else {
|
||||
g.Turn = White
|
||||
}
|
||||
oppMoves := g.Board.generateLegalMoves(g.Turn)
|
||||
if len(oppMoves) == 0 {
|
||||
g.Over = true
|
||||
if g.Board.isInCheck(g.Turn) {
|
||||
if g.Turn == White {
|
||||
g.Result = BlackWins
|
||||
} else {
|
||||
g.Result = WhiteWins
|
||||
}
|
||||
} else {
|
||||
g.Result = Draw
|
||||
}
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (g *Game) Render() string {
|
||||
out := g.Board.Display(g.Turn)
|
||||
if g.Over {
|
||||
switch g.Result {
|
||||
case WhiteWins:
|
||||
out += "\n === CHECKMATE! WHITE WINS! ===\n"
|
||||
case BlackWins:
|
||||
out += "\n === CHECKMATE! BLACK WINS! ===\n"
|
||||
case Draw:
|
||||
out += "\n === STALEMATE! DRAW ===\n"
|
||||
}
|
||||
} else {
|
||||
out += fmt.Sprintf("\n Move %d | %s to move\n", g.MoveNum, colorName(g.Turn))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func algebraic(p Position) string {
|
||||
return string(rune('a'+p.Col)) + string(rune('0'+(8-p.Row)))
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "play":
|
||||
PlayVsComputer()
|
||||
case "connect":
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println("Usage: chess connect <host:port>")
|
||||
os.Exit(1)
|
||||
}
|
||||
PlayMultiplayer(os.Args[2])
|
||||
case "server":
|
||||
addr := ":8080"
|
||||
if len(os.Args) >= 3 {
|
||||
addr = os.Args[2]
|
||||
}
|
||||
srv := NewServer()
|
||||
if err := srv.Start(addr); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case "help", "--help", "-h":
|
||||
printUsage()
|
||||
default:
|
||||
fmt.Printf("Unknown command: %s\n\n", os.Args[1])
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println(`
|
||||
CHESS - Go CLI Chess
|
||||
=====================
|
||||
chess play Play against the computer
|
||||
chess server [addr] Start multiplayer server (default :8080)
|
||||
chess connect <addr> Connect and play vs another person
|
||||
chess help Show this help
|
||||
`)
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
ID string
|
||||
Conn net.Conn
|
||||
}
|
||||
|
||||
type GameRoom struct {
|
||||
White *Player
|
||||
Black *Player
|
||||
Game *Game
|
||||
Mutex sync.Mutex
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Listener net.Listener
|
||||
Waiting []*Player
|
||||
Rooms map[string]*GameRoom
|
||||
RoomMutex sync.Mutex
|
||||
RoomCount int
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
Rooms: make(map[string]*GameRoom),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Start(addr string) error {
|
||||
var err error
|
||||
s.Listener, err = net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on %s: %v", addr, err)
|
||||
}
|
||||
fmt.Printf("=== CHESS SERVER ===\n")
|
||||
fmt.Printf("Listening on %s\n", addr)
|
||||
fmt.Printf("Type 'status' to show active games, 'quit' to exit.\n\n")
|
||||
go func() {
|
||||
for {
|
||||
conn, err := s.Listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go s.handleClient(conn)
|
||||
}
|
||||
}()
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
switch line {
|
||||
case "status":
|
||||
s.RoomMutex.Lock()
|
||||
fmt.Printf("Active games: %d | Waiting: %d\n", len(s.Rooms), len(s.Waiting))
|
||||
for _, room := range s.Rooms {
|
||||
fmt.Printf(" %s vs %s (move %d)\n", room.White.ID, room.Black.ID, room.Game.MoveNum)
|
||||
}
|
||||
s.RoomMutex.Unlock()
|
||||
case "quit", "q":
|
||||
s.Listener.Close()
|
||||
fmt.Println("Server shutting down.")
|
||||
return nil
|
||||
case "":
|
||||
continue
|
||||
default:
|
||||
fmt.Printf("Unknown: %s (try 'status' or 'quit')\n", line)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleClient(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
reader := bufio.NewReader(conn)
|
||||
playerID := fmt.Sprintf("Player-%d", s.RoomCount+1)
|
||||
fmt.Printf("[+] %s connected (%s)\n", playerID, conn.RemoteAddr())
|
||||
player := &Player{ID: playerID, Conn: conn}
|
||||
fmt.Fprintln(conn, "CHESS-SERVER v1.0")
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(line) != "HELLO" {
|
||||
fmt.Fprintln(conn, "ERROR expected HELLO")
|
||||
return
|
||||
}
|
||||
s.RoomMutex.Lock()
|
||||
s.Waiting = append(s.Waiting, player)
|
||||
foundOpponent := false
|
||||
for i, p := range s.Waiting {
|
||||
if p == player {
|
||||
continue
|
||||
}
|
||||
s.Waiting = append(s.Waiting[:i], s.Waiting[i+1:]...)
|
||||
foundOpponent = true
|
||||
s.RoomCount++
|
||||
roomID := fmt.Sprintf("game-%d", s.RoomCount)
|
||||
room := &GameRoom{
|
||||
White: player,
|
||||
Black: p,
|
||||
Game: NewGame(),
|
||||
}
|
||||
s.Rooms[roomID] = room
|
||||
fmt.Fprintf(player.Conn, "MATCHED %s WHITE\n", roomID)
|
||||
fmt.Fprintf(p.Conn, "MATCHED %s BLACK\n", roomID)
|
||||
boardStr := room.Game.Render()
|
||||
fmt.Fprintf(player.Conn, "BOARD\n%s\n", boardStr)
|
||||
fmt.Fprintf(p.Conn, "BOARD\n%s\n", boardStr)
|
||||
go s.gameLoop(room)
|
||||
break
|
||||
}
|
||||
s.RoomMutex.Unlock()
|
||||
if !foundOpponent {
|
||||
fmt.Fprintln(player.Conn, "WAITING")
|
||||
fmt.Printf(" %s waiting for opponent...\n", playerID)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) gameLoop(room *GameRoom) {
|
||||
whiteReader := bufio.NewReader(room.White.Conn)
|
||||
blackReader := bufio.NewReader(room.Black.Conn)
|
||||
fmt.Printf("[=] Game: %s (W) vs %s (B)\n", room.White.ID, room.Black.ID)
|
||||
whiteCh := make(chan string, 4)
|
||||
blackCh := make(chan string, 4)
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(whiteCh)
|
||||
for {
|
||||
line, err := whiteReader.ReadString('\n')
|
||||
if err != nil {
|
||||
close(doneCh)
|
||||
return
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "RESIGN" {
|
||||
room.Mutex.Lock()
|
||||
room.Game.Over = true
|
||||
room.Game.Result = BlackWins
|
||||
room.Mutex.Unlock()
|
||||
close(doneCh)
|
||||
return
|
||||
}
|
||||
if line == "QUIT" {
|
||||
close(doneCh)
|
||||
return
|
||||
}
|
||||
whiteCh <- line
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer close(blackCh)
|
||||
for {
|
||||
line, err := blackReader.ReadString('\n')
|
||||
if err != nil {
|
||||
close(doneCh)
|
||||
return
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "RESIGN" {
|
||||
room.Mutex.Lock()
|
||||
room.Game.Over = true
|
||||
room.Game.Result = WhiteWins
|
||||
room.Mutex.Unlock()
|
||||
close(doneCh)
|
||||
return
|
||||
}
|
||||
if line == "QUIT" {
|
||||
close(doneCh)
|
||||
return
|
||||
}
|
||||
blackCh <- line
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-doneCh:
|
||||
s.finishRoom(room)
|
||||
return
|
||||
default:
|
||||
}
|
||||
room.Mutex.Lock()
|
||||
if room.Game.Over {
|
||||
room.Mutex.Unlock()
|
||||
s.sendBoard(room)
|
||||
s.finishRoom(room)
|
||||
return
|
||||
}
|
||||
var moveStr string
|
||||
var ok bool
|
||||
if room.Game.Turn == White {
|
||||
select {
|
||||
case moveStr, ok = <-whiteCh:
|
||||
if !ok {
|
||||
room.Mutex.Unlock()
|
||||
s.finishRoom(room)
|
||||
return
|
||||
}
|
||||
case <-doneCh:
|
||||
room.Mutex.Unlock()
|
||||
s.finishRoom(room)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case moveStr, ok = <-blackCh:
|
||||
if !ok {
|
||||
room.Mutex.Unlock()
|
||||
s.finishRoom(room)
|
||||
return
|
||||
}
|
||||
case <-doneCh:
|
||||
room.Mutex.Unlock()
|
||||
s.finishRoom(room)
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err := room.Game.MakeMove(moveStr)
|
||||
over := room.Game.Over
|
||||
room.Mutex.Unlock()
|
||||
if err != nil {
|
||||
if room.Game.Turn == White {
|
||||
fmt.Fprintf(room.White.Conn, "ERROR %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(room.Black.Conn, "ERROR %v\n", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" %s: %s\n", moveStr, room.Game.Turn)
|
||||
s.sendBoard(room)
|
||||
if over {
|
||||
s.finishRoom(room)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) sendBoard(room *GameRoom) {
|
||||
boardStr := room.Game.Render()
|
||||
fmt.Fprintf(room.White.Conn, "BOARD\n%s\n", boardStr)
|
||||
fmt.Fprintf(room.Black.Conn, "BOARD\n%s\n", boardStr)
|
||||
}
|
||||
|
||||
func (s *Server) finishRoom(room *GameRoom) {
|
||||
s.RoomMutex.Lock()
|
||||
for k, r := range s.Rooms {
|
||||
if r == room {
|
||||
delete(s.Rooms, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.RoomMutex.Unlock()
|
||||
room.White.Conn.Close()
|
||||
room.Black.Conn.Close()
|
||||
fmt.Printf("[=] Game ended: %s vs %s\n", room.White.ID, room.Black.ID)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue