golang-chess/server.go

267 lines
5.4 KiB
Go

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)
}