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