Repository: thomas-mauran/chess-tui (language: rust)

Issue:
Ascii mode
## Problem

Old school ascii mode, just like in gnuchess. The player has uppercase letters and the enemy has lowercase letters.
p = pawn, k = king, q = queen, b = bishop, r = rook, n = knight


![image](https://github.com/thomas-mauran/chess-tui/assets/105480088/9845dd05-098a-424e-858a-afb11226c200)


Discussion / hints:
This is a good idea and could be implemented easily.
Maybe we could have another clap cli argument like --ascii to run chess-tui in ascii mode and replace every piece by it's character, we also will need to see how it renders on the board making sure it is not too small

Fix patch:
diff --git a/src/app.rs b/src/app.rs
index bb73cd0..8c21fee 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,4 +1,4 @@
-use crate::{board::Board, constants::Pages};
+use crate::{board::Board, board::DisplayMode, constants::Pages};
 use std::error;
 
 /// Application result type.
@@ -66,12 +66,12 @@ impl App {
         if self.menu_cursor > 0 {
             self.menu_cursor -= 1
         } else {
-            self.menu_cursor = 3
+            self.menu_cursor = 4
         }
     }
 
     pub fn menu_cursor_down(&mut self) {
-        if self.menu_cursor < 3 {
+        if self.menu_cursor < 4 {
             self.menu_cursor += 1
         } else {
             self.menu_cursor = 0
@@ -88,8 +88,14 @@ impl App {
         match self.menu_cursor {
             0 => self.current_page = Pages::Solo,
             1 => self.current_page = Pages::Bot,
-            2 => self.current_page = Pages::Help,
-            3 => self.current_page = Pages::Credit,
+            2 => {
+                self.board.display_mode = match self.board.display_mode {
+                    DisplayMode::ASCII => DisplayMode::DEFAULT,
+                    DisplayMode::DEFAULT => DisplayMode::ASCII,
+                };
+            }
+            3 => self.current_page = Pages::Help,
+            4 => self.current_page = Pages::Credit,
             _ => {}
         }
     }
diff --git a/src/board.rs b/src/board.rs
index 6964d57..9a68abb 100644
--- a/src/board.rs
+++ b/src/board.rs
@@ -2,10 +2,9 @@ use crate::{
     constants::{BLACK, UNDEFINED_POSITION, WHITE},
     pieces::{PieceColor, PieceType},
     utils::{
-        col_to_letter, color_to_ratatui_enum, convert_notation_into_position,
-        convert_position_into_notation, did_piece_already_move, get_int_from_char,
-        get_king_coordinates, get_piece_color, get_piece_type, get_player_turn_in_modulo,
-        is_getting_checked, is_valid,
+        col_to_letter, convert_notation_into_position, convert_position_into_notation,
+        did_piece_already_move, get_cell_paragraph, get_int_from_char, get_king_coordinates,
+        get_piece_color, get_piece_type, get_player_turn_in_modulo, is_getting_checked, is_valid,
     },
 };
 use ratatui::{
@@ -17,6 +16,11 @@ use ratatui::{
 };
 use uci::Engine;
 
+pub enum DisplayMode {
+    DEFAULT,
+    ASCII,
+}
+
 pub struct Board {
     pub board: [[Option<(PieceType, PieceColor)>; 8]; 8],
     pub cursor_coordinates: [i8; 2],
@@ -32,6 +36,7 @@ pub struct Board {
     pub consecutive_non_pawn_or_capture: i32,
     pub engine: Option<Engine>,
     pub is_game_against_bot: bool,
+    pub display_mode: DisplayMode,
 }
 
 impl Default for Board {
@@ -96,6 +101,7 @@ impl Default for Board {
             consecutive_non_pawn_or_capture: 0,
             engine: None,
             is_game_against_bot: false,
+            display_mode: DisplayMode::DEFAULT,
         }
     }
 }
@@ -121,6 +127,7 @@ impl Board {
             consecutive_non_pawn_or_capture: 0,
             engine: None,
             is_game_against_bot: false,
+            display_mode: DisplayMode::DEFAULT,
         }
     }
 
@@ -767,23 +774,22 @@ impl Board {
                     let cell = Block::default().bg(Color::LightGreen);
                     frame.render_widget(cell.clone(), square);
                 } else {
-                    let cell = Block::default().bg(cell_color);
+                    let mut cell = Block::default();
+                    cell = match self.display_mode {
+                        DisplayMode::DEFAULT => cell.bg(cell_color),
+                        DisplayMode::ASCII => match cell_color {
+                            WHITE => cell.bg(Color::White).fg(Color::Black),
+                            BLACK => cell.bg(Color::Black).fg(Color::White),
+                            _ => cell.bg(cell_color),
+                        },
+                    };
+
                     frame.render_widget(cell.clone(), square);
                 }
 
-                // We check if the current king is getting checked
-
                 // Get piece and color
-                let piece_color = get_piece_color(self.board, [i, j]);
-                let piece_type = get_piece_type(self.board, [i, j]);
-
-                let color_enum = color_to_ratatui_enum(piece_color);
-                let piece_enum = PieceType::piece_type_to_string_enum(piece_type);
+                let paragraph = get_cell_paragraph(self, [i, j], square);
 
-                // Place the pieces on the board
-                let paragraph = Paragraph::new(piece_enum)
-                    .alignment(Alignment::Center)
-                    .fg(color_enum);
                 frame.render_widget(paragraph, square);
             }
         }
diff --git a/src/pieces/bishop.rs b/src/pieces/bishop.rs
index 6c93b5b..f1a01ae 100644
--- a/src/pieces/bishop.rs
+++ b/src/pieces/bishop.rs
@@ -1,4 +1,5 @@
 use super::{Movable, PieceColor, PieceType, Position};
+use crate::board::DisplayMode;
 use crate::utils::{
     cleaned_positions, get_piece_color, impossible_positions_king_checked, is_cell_color_ally,
     is_piece_opposite_king, is_valid,
@@ -190,14 +191,19 @@ impl Position for Bishop {
 }
 
 impl Bishop {
-    pub fn to_string() -> &'static str {
-        "\
+    pub fn to_string(display_mode: &DisplayMode) -> &'static str {
+        match display_mode {
+            DisplayMode::DEFAULT => {
+                "\
     \n\
        ⭘\n\
       █✝█\n\
       ███\n\
     ▗█████▖\n\
     "
+            }
+            DisplayMode::ASCII => "B",
+        }
     }
 }
 
diff --git a/src/pieces/king.rs b/src/pieces/king.rs
index 5c7479f..34d4d70 100644
--- a/src/pieces/king.rs
+++ b/src/pieces/king.rs
@@ -1,9 +1,9 @@
 use super::{Movable, PieceColor, PieceType, Position};
+use crate::board::DisplayMode;
 use crate::utils::{
     cleaned_positions, did_piece_already_move, get_all_protected_cells, get_piece_type,
     is_cell_color_ally, is_valid, is_vec_in_array,
 };
-
 pub struct King;
 
 impl Movable for King {
@@ -104,14 +104,19 @@ impl Position for King {
 }
 
 impl King {
-    pub fn to_string() -> &'static str {
-        "\
+    pub fn to_string(display_mode: &DisplayMode) -> &'static str {
+        match display_mode {
+            DisplayMode::DEFAULT => {
+                "\
       ✚\n\
     ▞▀▄▀▚\n\
     ▙▄█▄▟\n\
     ▐███▌\n\
    ▗█████▖\n\
     "
+            }
+            DisplayMode::ASCII => "K",
+        }
     }
 
     // Check if nothing is in between the king and a rook and if none of those cells are getting checked
diff --git a/src/pieces/knight.rs b/src/pieces/knight.rs
index 5509860..7f0c433 100644
--- a/src/pieces/knight.rs
+++ b/src/pieces/knight.rs
@@ -1,4 +1,5 @@
 use super::{Movable, PieceColor, PieceType, Position};
+use crate::board::DisplayMode;
 use crate::utils::{
     cleaned_positions, impossible_positions_king_checked, is_cell_color_ally, is_valid,
 };
@@ -74,14 +75,19 @@ impl Position for Knight {
 }
 
 impl Knight {
-    pub fn to_string() -> &'static str {
-        "\
+    pub fn to_string(display_mode: &DisplayMode) -> &'static str {
+        match display_mode {
+            DisplayMode::DEFAULT => {
+                "\
     \n\
     ▟▛██▙\n\
    ▟█████\n\
    ▀▀▟██▌\n\
     ▟████\n\
     "
+            }
+            DisplayMode::ASCII => "N",
+        }
     }
 }
 
diff --git a/src/pieces/mod.rs b/src/pieces/mod.rs
index 23eef6a..961f236 100644
--- a/src/pieces/mod.rs
+++ b/src/pieces/mod.rs
@@ -1,4 +1,5 @@
 use self::{bishop::Bishop, king::King, knight::Knight, pawn::Pawn, queen::Queen, rook::Rook};
+use super::board::DisplayMode;
 
 pub mod bishop;
 pub mod king;
@@ -131,14 +132,17 @@ impl PieceType {
         }
     }
 
-    pub fn piece_type_to_string_enum(piece_type: Option<PieceType>) -> &'static str {
+    pub fn piece_type_to_string_enum(
+        piece_type: Option<PieceType>,
+        display_mode: &DisplayMode,
+    ) -> &'static str {
         match piece_type {
-            Some(PieceType::Queen) => Queen::to_string(),
-            Some(PieceType::King) => King::to_string(),
-            Some(PieceType::Rook) => Rook::to_string(),
-            Some(PieceType::Bishop) => Bishop::to_string(),
-            Some(PieceType::Knight) => Knight::to_string(),
-            Some(PieceType::Pawn) => Pawn::to_string(),
+            Some(PieceType::Queen) => Queen::to_string(display_mode),
+            Some(PieceType::King) => King::to_string(display_mode),
+            Some(PieceType::Rook) => Rook::to_string(display_mode),
+            Some(PieceType::Bishop) => Bishop::to_string(display_mode),
+            Some(PieceType::Knight) => Knight::to_string(display_mode),
+            Some(PieceType::Pawn) => Pawn::to_string(display_mode),
             None => " ",
         }
     }
diff --git a/src/pieces/pawn.rs b/src/pieces/pawn.rs
index 7bc1169..4641c76 100644
--- a/src/pieces/pawn.rs
+++ b/src/pieces/pawn.rs
@@ -1,4 +1,5 @@
 use super::{Movable, PieceColor, PieceType, Position};
+use crate::board::DisplayMode;
 use crate::utils::{
     cleaned_positions, get_int_from_char, get_latest_move, get_piece_color,
     impossible_positions_king_checked, is_cell_color_ally, is_valid,
@@ -151,14 +152,19 @@ impl Position for Pawn {
 }
 
 impl Pawn {
-    pub fn to_string() -> &'static str {
-        "\
+    pub fn to_string(display_mode: &DisplayMode) -> &'static str {
+        match display_mode {
+            DisplayMode::DEFAULT => {
+                "\
         \n\
         \n\
       ▟█▙\n\
       ▜█▛\n\
      ▟███▙\n\
     "
+            }
+            DisplayMode::ASCII => "P",
+        }
     }
 }
 
diff --git a/src/pieces/queen.rs b/src/pieces/queen.rs
index f54add9..7fa9ad9 100644
--- a/src/pieces/queen.rs
+++ b/src/pieces/queen.rs
@@ -1,5 +1,6 @@
 use super::rook::Rook;
 use super::{Movable, PieceColor, PieceType, Position};
+use crate::board::DisplayMode;
 use crate::pieces::bishop::Bishop;
 use crate::utils::{cleaned_positions, impossible_positions_king_checked};
 
@@ -62,14 +63,19 @@ impl Position for Queen {
 }
 
 impl Queen {
-    pub fn to_string() -> &'static str {
-        "\
+    pub fn to_string(display_mode: &DisplayMode) -> &'static str {
+        match display_mode {
+            DisplayMode::DEFAULT => {
+                "\
     \n\
 ◀█▟█▙█▶\n\
   ◥█◈█◤\n\
   ███\n\
 ▗█████▖\n\
     "
+            }
+            DisplayMode::ASCII => "Q",
+        }
     }
 }
 
diff --git a/src/pieces/rook.rs b/src/pieces/rook.rs
index 20c9a61..7a8ad35 100644
--- a/src/pieces/rook.rs
+++ b/src/pieces/rook.rs
@@ -1,9 +1,9 @@
 use super::{Movable, PieceColor, PieceType, Position};
+use crate::board::DisplayMode;
 use crate::utils::{
     cleaned_positions, get_piece_color, impossible_positions_king_checked, is_cell_color_ally,
     is_piece_opposite_king, is_valid,
 };
-
 pub struct Rook;
 
 impl Movable for Rook {
@@ -195,14 +195,19 @@ impl Position for Rook {
 }
 
 impl Rook {
-    pub fn to_string() -> &'static str {
-        "\
+    pub fn to_string(display_mode: &DisplayMode) -> &'static str {
+        match display_mode {
+            DisplayMode::DEFAULT => {
+                "\
     \n\
     █▟█▙█\n\
     ▜███▛\n\
     ▐███▌\n\
    ▗█████▖\n\
     "
+            }
+            DisplayMode::ASCII => "R",
+        }
     }
 }
 
diff --git a/src/popups.rs b/src/popups.rs
index 5b53665..8cef8d3 100644
--- a/src/popups.rs
+++ b/src/popups.rs
@@ -116,7 +116,9 @@ pub fn render_promotion_popup(frame: &mut Frame, app: &App) {
         )
         .split(inner_popup_layout_vertical[1]);
 
-    let queen_p = Paragraph::new(Queen::to_string())
+    let display_mode = &app.board.display_mode;
+
+    let queen_p = Paragraph::new(Queen::to_string(display_mode))
         .block(Block::default())
         .alignment(Alignment::Center)
         .style(Style::default().bg(if app.board.promotion_cursor == 0 {
@@ -125,7 +127,7 @@ pub fn render_promotion_popup(frame: &mut Frame, app: &App) {
             Color::Reset // Set to the default background color when the condition is false
         }));
     frame.render_widget(queen_p, inner_popup_layout_horizontal[0]);
-    let rook_p = Paragraph::new(Rook::to_string())
+    let rook_p = Paragraph::new(Rook::to_string(display_mode))
         .block(Block::default())
         .alignment(Alignment::Center)
         .style(Style::default().bg(if app.board.promotion_cursor == 1 {
@@ -134,7 +136,7 @@ pub fn render_promotion_popup(frame: &mut Frame, app: &App) {
             Color::Reset // Set to the default background color when the condition is false
         }));
     frame.render_widget(rook_p, inner_popup_layout_horizontal[1]);
-    let bishop_p = Paragraph::new(Bishop::to_string())
+    let bishop_p = Paragraph::new(Bishop::to_string(display_mode))
         .block(Block::default())
         .alignment(Alignment::Center)
         .style(Style::default().bg(if app.board.promotion_cursor == 2 {
@@ -143,7 +145,7 @@ pub fn render_promotion_popup(frame: &mut Frame, app: &App) {
             Color::Reset // Set to the default background color when the condition is false
         }));
     frame.render_widget(bishop_p, inner_popup_layout_horizontal[2]);
-    let knight_p = Paragraph::new(Knight::to_string())
+    let knight_p = Paragraph::new(Knight::to_string(display_mode))
         .block(Block::default())
         .alignment(Alignment::Center)
         .style(Style::default().bg(if app.board.promotion_cursor == 3 {
diff --git a/src/ui.rs b/src/ui.rs
index f33d66c..3f3df38 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -9,6 +9,7 @@ use ratatui::{
 
 use crate::{
     app::App,
+    board::DisplayMode,
     constants::{Pages, TITLE},
     pieces::PieceColor,
     popups::{
@@ -97,8 +98,23 @@ pub fn render_menu_ui(frame: &mut Frame, app: &App, main_area: Rect) {
         .block(Block::default());
     frame.render_widget(sub_title, main_layout_horizontal[1]);
 
+    // Determine the "display mode" text
+    let display_mode_menu = {
+        let display_mode = match app.board.display_mode {
+            DisplayMode::DEFAULT => "Default",
+            DisplayMode::ASCII => "ASCII",
+        };
+        format!("Display mode: {}", display_mode)
+    };
+
     // Board block representing the full board div
-    let menu_items = ["Normal game", "Play against a bot", "Help", "Credits"];
+    let menu_items = [
+        "Normal game",
+        "Play against a bot",
+        &display_mode_menu,
+        "Help",
+        "Credits",
+    ];
     let mut menu_body: Vec<Line<'_>> = vec![];
 
     for (i, menu_item) in menu_items.iter().enumerate() {
diff --git a/src/utils.rs b/src/utils.rs
index f6b65bd..f0f3525 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,9 +1,13 @@
 use crate::{
-    board::Board,
+    board::{Board, DisplayMode},
     constants::UNDEFINED_POSITION,
     pieces::{PieceColor, PieceType},
 };
-use ratatui::style::Color;
+use ratatui::{
+    layout::{Alignment, Rect},
+    style::{Color, Stylize},
+    widgets::{Block, Padding, Paragraph},
+};
 
 pub fn get_piece_color(
     board: [[Option<(PieceType, PieceColor)>; 8]; 8],
@@ -272,6 +276,42 @@ pub fn color_to_ratatui_enum(piece_color: Option<PieceColor>) -> Color {
     }
 }
 
+pub fn get_cell_paragraph(
+    board: &Board,
+    cell_coordinates: [i8; 2],
+    bounding_rect: Rect,
+) -> Paragraph<'_> {
+    // Get piece and color
+    let piece_color = get_piece_color(board.board, cell_coordinates);
+    let piece_type = get_piece_type(board.board, cell_coordinates);
+    let piece_enum = PieceType::piece_type_to_string_enum(piece_type, &board.display_mode);
+
+    let paragraph = match board.display_mode {
+        DisplayMode::DEFAULT => {
+            let color_enum = color_to_ratatui_enum(piece_color);
+
+            // Place the pieces on the board
+            Paragraph::new(piece_enum).fg(color_enum)
+        }
+        DisplayMode::ASCII => {
+            // Determine piece letter case
+            let paragraph = match piece_color {
+                // pieces belonging to the player on top will be lower case
+                Some(PieceColor::Black) => Paragraph::new(piece_enum.to_lowercase()),
+                // pieces belonging to the player on bottom will be upper case
+                Some(PieceColor::White) => Paragraph::new(piece_enum.to_uppercase().underlined()),
+                // Pass through original value
+                None => Paragraph::new(piece_enum),
+            };
+
+            // Place the pieces on the board
+            paragraph.block(Block::new().padding(Padding::vertical(bounding_rect.height / 2)))
+        }
+    };
+
+    paragraph.alignment(Alignment::Center)
+}
+
 #[cfg(test)]
 mod tests {
     use crate::utils::{convert_notation_into_position, convert_position_into_notation};


Test patch: