hyperchess 0.1.1

A fast, terminal-based multiplayer chess game built with Rust.
Documentation
use chess::{Board, ChessMove, Color, Game, MoveGen, Square, BoardStatus};
use std::str::FromStr;

#[derive(Clone)]
pub struct AppState {
    pub game: Game,
    pub board: Board,
    pub selected_square: Option<Square>,
    pub valid_moves: Vec<Square>,
    pub debug_msg: String,
    pub game_over: bool,
    
    // NEW: Multiplayer Identity
    // None = Local Hotseat (Control both sides)
    // Some(Color::White) = I am White (Multiplayer)
    pub my_color: Option<Color>, 
}

impl AppState {
    pub fn new() -> Self {
        Self {
            game: Game::new(),
            board: Board::default(),
            selected_square: None,
            valid_moves: Vec::new(),
            debug_msg: String::from("Welcome! Local Hotseat Mode."),
            game_over: false,
            my_color: None, // Default to Hotseat
        }
    }

    // Call this if Multiplayer logic starts
    pub fn set_player_color(&mut self, color: Color) {
        self.my_color = Some(color);
        self.debug_msg = format!("Multiplayer: You are {:?}.", color);
    }

    pub fn handle_click(&mut self, square: Square) -> Option<(String, String)> {
        if self.game_over { return None; }

        let current_turn = self.board.side_to_move();

        // --- MULTIPLAYER LOCK ---
        // If I have a specific color, and it's NOT my turn, ignore clicks.
        if let Some(my_c) = self.my_color {
            if my_c != current_turn {
                self.debug_msg = String::from("Wait for opponent...");
                return None;
            }
            // Also prevent selecting opponent's pieces
            if self.selected_square.is_none() {
                if let Some(p_color) = self.board.color_on(square) {
                    if p_color != my_c {
                         return None;
                    }
                }
            }
        }
        // ------------------------

        match self.selected_square {
            Some(selected) => {
                if selected == square {
                    self.selected_square = None;
                    self.valid_moves.clear();
                    self.debug_msg = String::from("Deselected.");
                } else {
                    // Try to move
                    let move_result = self.make_move(selected, square);
                    
                    if move_result {
                        self.selected_square = None;
                        self.valid_moves.clear();
                        
                        // Return the move string so Main can send it to network
                        return Some((format!("{}", selected), format!("{}", square)));
                    } else {
                        // Move failed. Switch piece?
                        if let Some(color) = self.board.color_on(square) {
                            if color == current_turn {
                                self.selected_square = Some(square);
                                self.calculate_valid_moves(square);
                                return None;
                            }
                        }
                        self.selected_square = None;
                        self.valid_moves.clear();
                    }
                }
            }
            None => {
                if let Some(color) = self.board.color_on(square) {
                    if color == current_turn {
                        self.selected_square = Some(square);
                        self.calculate_valid_moves(square);
                        self.debug_msg = format!("Selected: {}", square);
                    }
                }
            }
        }
        None
    }

    // Apply a move from the network (Server says: "Opponent moved...")
    pub fn network_apply_move(&mut self, from: String, to: String) {
        if let (Ok(src), Ok(dst)) = (Square::from_str(&from), Square::from_str(&to)) {
             self.make_move(src, dst);
             self.debug_msg = format!("Opponent moved {} -> {}", from, to);
        }
    }

    fn calculate_valid_moves(&mut self, source: Square) {
        self.valid_moves.clear();
        let legal_moves = MoveGen::new_legal(&self.board);
        for m in legal_moves {
            if m.get_source() == source {
                self.valid_moves.push(m.get_dest());
            }
        }
    }

    pub fn make_move(&mut self, source: Square, dest: Square) -> bool {
        let m = ChessMove::new(source, dest, None); 
        let legal_moves = MoveGen::new_legal(&self.board);
        if legal_moves.into_iter().any(|lm| lm == m) {
            self.game.make_move(m);
            self.board = self.game.current_position();
            self.update_status();
            return true;
        }
        false
    }

    fn update_status(&mut self) {
        match self.board.status() {
            BoardStatus::Checkmate => {
                self.debug_msg = format!("CHECKMATE! {:?} WINS!", !self.board.side_to_move());
                self.game_over = true;
            }
            BoardStatus::Stalemate => {
                self.debug_msg = String::from("Stalemate!");
                self.game_over = true;
            }
            BoardStatus::Ongoing => {
                if self.board.checkers().popcnt() > 0 {
                    self.debug_msg = format!("CHECK! {:?} to move", self.board.side_to_move());
                } else {
                    self.debug_msg = format!("{:?}'s turn.", self.board.side_to_move());
                }
            }
        }
    }
}