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()) .split(f.size());
let turn_str = match app.board.side_to_move() {
ChessColor::White => "Turn: WHITE",
ChessColor::Black => "Turn: BLACK",
};
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]);
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 } else {
board.side_to_move() };
let cell_width = 5;
let cell_height = 3;
let board_pixel_width = 8 * cell_width;
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 {
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);
let is_light_square = (render_rank.to_index() + render_file.to_index()) % 2 != 0;
let mut bg = if is_light_square { Color::Gray } else { Color::Rgb(50, 50, 50) };
let mut is_in_check = false;
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;
}
}
}
if is_in_check {
bg = Color::Magenta; } 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 };
}
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;
}
}
let symbol = match board.piece_on(sq) {
Some(p) => match p {
Piece::Pawn => " ♟ ",
Piece::Rook => " ♜ ",
Piece::Knight => " ♞ ",
Piece::Bishop => " ♝ ",
Piece::Queen => " ♛ ",
Piece::King => " ♚ ",
},
None => " "
};
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);
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
);
}
}
}
}