hyperchess 0.1.1

A fast, terminal-based multiplayer chess game built with Rust.
Documentation
use ratatui::{
    layout::{Alignment, Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Span, Line},
    widgets::{Block, Borders, Paragraph},
    Frame,
};
use chess::{Piece, Square, Rank, File, Color as ChessColor};
use crate::game::AppState;

pub fn draw_ui(f: &mut Frame, app: &AppState) {
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .margin(1)
        .constraints([Constraint::Length(4), Constraint::Min(0)].as_ref()) // FIX: Changed 3 to 4
        .split(f.size());

    // 1. Status Bar Logic
    let turn_str = match app.board.side_to_move() {
        ChessColor::White => "Turn: WHITE",
        ChessColor::Black => "Turn: BLACK",
    };
    
    // Style for the message
    let msg_style = if app.game_over {
        Style::default().fg(Color::Red).add_modifier(Modifier::BOLD | Modifier::SLOW_BLINK)
    } else if app.debug_msg.contains("CHECK!") {
        Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD)
    } else {
        Style::default().fg(Color::Yellow)
    };

    let status_text = vec![
        Line::from(vec![Span::raw(turn_str)]),
        Line::from(vec![Span::styled(&app.debug_msg, msg_style)]),
    ];
    
    let status_block = Paragraph::new(status_text)
        .block(Block::default().borders(Borders::ALL).title("Status"));
    f.render_widget(status_block, chunks[0]);

    // 2. Draw Board
    draw_board(chunks[1], f, app);
}

fn draw_board(area: Rect, f: &mut Frame, app: &AppState) {
    let board = &app.board;
    let orientation = if let Some(my_c) = app.my_color {
        my_c // Multiplayer: Board is fixed to my color
    } else {
        board.side_to_move() // Hotseat: Board flips
    };

    // Layout Config
    let cell_width = 5; 
    let cell_height = 3; 
    let board_pixel_width = 8 * cell_width;
    
    // Center the board
    let start_x = area.x + (area.width.saturating_sub(board_pixel_width)) / 2;
    let start_y = area.y + 1;

    for row_idx in 0..8 {
        for col_idx in 0..8 {
            
            // Coordinate Mapping
            let (render_rank, render_file) = match orientation {
                ChessColor::White => (Rank::from_index(7 - row_idx), File::from_index(col_idx)),
                ChessColor::Black => (Rank::from_index(row_idx), File::from_index(7 - col_idx)),
            };

            let sq = Square::make_square(render_rank, render_file);
            
            // --- COLOR LOGIC ---
            let is_light_square = (render_rank.to_index() + render_file.to_index()) % 2 != 0;
            
            // Base Background
            let mut bg = if is_light_square { Color::Gray } else { Color::Rgb(50, 50, 50) };

            // Check Detection
            let mut is_in_check = false;
            // Only check for King of the CURRENT turn
            if let Some(p) = board.piece_on(sq) {
                if p == Piece::King && board.color_on(sq) == Some(board.side_to_move()) {
                    if board.checkers().popcnt() > 0 {
                        is_in_check = true;
                    }
                }
            }

            // Priority Colors
            if is_in_check {
                bg = Color::Magenta; // High Contrast for Check
            } else if Some(sq) == app.selected_square {
                bg = Color::Yellow; 
            } else if app.valid_moves.contains(&sq) {
                bg = if is_light_square { Color::LightGreen } else { Color::Green };
            }

            // Foreground (Piece) Colors
            let mut fg = Color::White;
            if let Some(p_color) = board.color_on(sq) {
                if p_color == ChessColor::White {
                    fg = Color::Cyan; 
                } else {
                    fg = Color::Red; 
                }
            }
            
            // --- ICONS ---
            let symbol = match board.piece_on(sq) {
                Some(p) => match p {
                    Piece::Pawn   => "",
                    Piece::Rook   => "",
                    Piece::Knight => "",
                    Piece::Bishop => "",
                    Piece::Queen  => "",
                    Piece::King   => "",
                },
                None => "   " 
            };

            // --- RENDER ---
            let cell_rect = Rect {
                x: start_x + (col_idx as u16 * cell_width),
                y: start_y + (row_idx as u16 * cell_height),
                width: cell_width,
                height: cell_height,
            };

            if cell_rect.right() > area.right() || cell_rect.bottom() > area.bottom() {
                continue;
            }

            f.render_widget(Block::default().style(Style::default().bg(bg)), cell_rect);

            let p_widget = Paragraph::new(symbol)
                .style(Style::default().fg(fg).bg(bg).add_modifier(Modifier::BOLD))
                .alignment(Alignment::Center);

            let text_area = Rect {
                x: cell_rect.x,
                y: cell_rect.y + 1, 
                width: cell_width,
                height: 1,
            };
            
            f.render_widget(p_widget, text_area);
            
            // Dot for valid moves
            if app.valid_moves.contains(&sq) && board.piece_on(sq).is_none() {
                 f.render_widget(
                    Paragraph::new("").alignment(Alignment::Center).style(Style::default().fg(Color::Black).bg(bg)),
                    text_area
                );
            }
        }
    }
}