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

Issue:
feat: home menu
## Home menu
Implement a home menu when starting the game with the following options: 

- New game
- Play against a bot
- Import a game





Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2c09b0b..e3eae6b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -52,7 +52,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "chess-tui"
-version = "1.0.1"
+version = "1.1.0"
 dependencies = [
  "crossterm 0.26.1",
  "ratatui",
diff --git a/Cargo.toml b/Cargo.toml
index e755b14..60f0f76 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "chess-tui"
-version = "1.0.1"
+version = "1.1.0"
 authors = ["Thomas Mauran"]
 license = "MIT"
 edition = "2021"
diff --git a/examples/demo.gif b/examples/demo.gif
index d5e0863..19cd2c3 100644
Binary files a/examples/demo.gif and b/examples/demo.gif differ
diff --git a/examples/demo.tape b/examples/demo.tape
index 4190e1c..864cb1d 100644
--- a/examples/demo.tape
+++ b/examples/demo.tape
@@ -18,6 +18,11 @@ Set WindowBarSize 40
 Type "cargo run" Sleep 500ms  Enter
 
 Sleep 1s
+Down @0.3s
+Down @0.3s
+Down @0.3s
+Space @0.3s
+Sleep 3s
 
 Down @0.3s
 Down @0.3s
diff --git a/examples/helper.gif b/examples/helper.gif
index 60778ff..3de5446 100644
Binary files a/examples/helper.gif and b/examples/helper.gif differ
diff --git a/examples/helper.tape b/examples/helper.tape
index 90171a9..e380825 100644
--- a/examples/helper.tape
+++ b/examples/helper.tape
@@ -17,6 +17,8 @@ Set WindowBarSize 40
 
 Type "cargo run" Sleep 500ms  Enter
 
+Sleep 2s
+Space @0.3s
 Sleep 1s
 
 Type h
diff --git a/src/app.rs b/src/app.rs
index 2599a3a..6e7babf 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -11,9 +11,14 @@ pub struct App {
     pub running: bool,
     /// board
     pub board: Board,
-
     /// show help popup
-    pub show_popup: bool,
+    pub show_help_popup: bool,
+    /// show credit popup
+    pub show_credit_popup: bool,
+    /// show home menu
+    pub show_home_menu: bool,
+    /// menu current cursor
+    pub menu_cursor: u8,
 }
 
 impl Default for App {
@@ -21,7 +26,10 @@ impl Default for App {
         Self {
             running: true,
             board: Board::default(),
-            show_popup: false,
+            show_help_popup: false,
+            show_credit_popup: false,
+            show_home_menu: true,
+            menu_cursor: 0,
         }
     }
 }
@@ -32,8 +40,11 @@ impl App {
         Self::default()
     }
 
-    pub fn show_popup(&mut self) {
-        self.show_popup = !self.show_popup;
+    pub fn toggle_help_popup(&mut self) {
+        self.show_help_popup = !self.show_help_popup;
+    }
+    pub fn toggle_credit_popup(&mut self) {
+        self.show_credit_popup = !self.show_credit_popup;
     }
 
     /// Handles the tick event of the terminal.
@@ -44,9 +55,34 @@ impl App {
         self.running = false;
     }
 
+    pub fn menu_cursor_up(&mut self) {
+        if self.menu_cursor > 0 {
+            self.menu_cursor -= 1
+        } else {
+            self.menu_cursor = 2
+        }
+    }
+
+    pub fn menu_cursor_down(&mut self) {
+        if self.menu_cursor < 2 {
+            self.menu_cursor += 1
+        } else {
+            self.menu_cursor = 0
+        }
+    }
+
     pub fn restart(&mut self) {
         if self.board.is_draw || self.board.is_checkmate {
             self.board = Board::default()
         }
     }
+
+    pub fn menu_select(&mut self) {
+        match self.menu_cursor {
+            0 => self.show_home_menu = false,
+            1 => self.show_help_popup = true,
+            2 => self.show_credit_popup = true,
+            _ => {}
+        }
+    }
 }
diff --git a/src/board.rs b/src/board.rs
index a200a26..10a5f75 100644
--- a/src/board.rs
+++ b/src/board.rs
@@ -260,7 +260,7 @@ impl Board {
                 let piece_color = get_piece_color(self.board, self.cursor_coordinates);
                 let piece_type = get_piece_type(self.board, self.cursor_coordinates);
 
-                let authorized_positions= 
+                let authorized_positions =
                     self.get_authorized_positions(piece_type, piece_color, self.cursor_coordinates);
 
                 if authorized_positions.is_empty() {
@@ -570,9 +570,10 @@ impl Board {
                         self.selected_coordinates,
                     );
 
+                    // Draw grey if the color is in the authorized positions
                     for coords in positions.clone() {
                         if i == coords[0] && j == coords[1] {
-                            cell_color = Color::LightRed
+                            cell_color = Color::Rgb(100, 100, 100)
                         }
                     }
                 }
diff --git a/src/constants.rs b/src/constants.rs
index a516a6f..89f5641 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -3,3 +3,12 @@ use ratatui::style::Color;
 pub const UNDEFINED_POSITION: i8 = -1;
 pub const WHITE: Color = Color::Rgb(160, 160, 160);
 pub const BLACK: Color = Color::Rgb(128, 95, 69);
+
+pub const TITLE: &str = r#"
+ ██████╗██╗  ██╗███████╗███████╗███████╗   ████████╗██╗   ██╗██╗
+██╔════╝██║  ██║██╔════╝██╔════╝██╔════╝   ╚══██╔══╝██║   ██║██║
+██║     ███████║█████╗  ███████╗███████╗█████╗██║   ██║   ██║██║
+██║     ██╔══██║██╔══╝  ╚════██║╚════██║╚════╝██║   ██║   ██║██║
+╚██████╗██║  ██║███████╗███████║███████║      ██║   ╚██████╔╝██║
+ ╚═════╝╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝      ╚═╝    ╚═════╝ ╚═╝
+"#;
diff --git a/src/handler.rs b/src/handler.rs
index d2624b4..898c33c 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -17,10 +17,36 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
         // Counter handlers
         KeyCode::Right => app.board.cursor_right(),
         KeyCode::Left => app.board.cursor_left(),
-        KeyCode::Up => app.board.cursor_up(),
-        KeyCode::Down => app.board.cursor_down(),
-        KeyCode::Char(' ') => app.board.select_cell(),
-        KeyCode::Char('h') => app.show_popup(),
+        KeyCode::Up => {
+            if app.show_home_menu {
+                app.menu_cursor_up()
+            } else {
+                app.board.cursor_up()
+            }
+        }
+        KeyCode::Down => {
+            if app.show_home_menu {
+                app.menu_cursor_down()
+            } else {
+                app.board.cursor_down()
+            }
+        }
+        KeyCode::Char(' ') | KeyCode::Enter => {
+            if !app.show_home_menu {
+                app.board.select_cell()
+            } else {
+                app.menu_select()
+            }
+        }
+        KeyCode::Char('h') => {
+            if !app.show_home_menu {
+                app.show_help_popup = true
+            }
+        }
+        KeyCode::Char('x') => {
+            app.show_credit_popup = false;
+            app.show_help_popup = false;
+        }
         KeyCode::Char('r') => app.restart(),
         KeyCode::Esc => app.board.unselect_cell(),
         // Other handlers you could add here.
diff --git a/src/lib.rs b/src/lib.rs
index b7995c3..6b57032 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,8 +16,14 @@ pub mod handler;
 // Chess pieces structures
 pub mod pieces;
 
+// Board structure
 pub mod board;
 
+// Constats for the app
 pub mod constants;
 
+// Utils methods for the board
 pub mod utils;
+
+// popups render methods
+pub mod popups;
diff --git a/src/popups.rs b/src/popups.rs
new file mode 100644
index 0000000..9fca2a1
--- /dev/null
+++ b/src/popups.rs
@@ -0,0 +1,219 @@
+use crate::{
+    app::App,
+    constants::WHITE,
+    pieces::{bishop::Bishop, knight::Knight, queen::Queen, rook::Rook},
+    ui::centered_rect,
+};
+use ratatui::{
+    layout::{Alignment, Constraint, Direction, Layout},
+    style::{Color, Style, Stylize},
+    text::Line,
+    widgets::{Block, BorderType, Borders, Clear, Padding, Paragraph, Wrap},
+    Frame,
+};
+
+// This renders the popup for a promotion
+pub fn render_end_popup(frame: &mut Frame, sentence: String) {
+    let block = Block::default()
+        .title("Game ended")
+        .borders(Borders::ALL)
+        .border_type(BorderType::Rounded)
+        .padding(Padding::horizontal(1))
+        .border_style(Style::default().fg(WHITE));
+    let area = centered_rect(40, 40, frame.size());
+
+    let text = vec![
+        Line::from(sentence).alignment(Alignment::Center),
+        Line::from(""),
+        Line::from(""),
+        Line::from("Press R to start a new game").alignment(Alignment::Center),
+    ];
+
+    let paragraph = Paragraph::new(text)
+        .block(block.clone())
+        .alignment(Alignment::Left)
+        .wrap(Wrap { trim: true });
+
+    frame.render_widget(Clear, area); //this clears out the background
+    frame.render_widget(block, area);
+    frame.render_widget(paragraph, area);
+}
+
+pub fn render_promotion_popup(frame: &mut Frame, app: &App) {
+    let block = Block::default()
+        .title("Pawn promotion")
+        .borders(Borders::ALL)
+        .border_type(BorderType::Rounded)
+        .padding(Padding::horizontal(1))
+        .border_style(Style::default().fg(WHITE));
+    let area = centered_rect(40, 40, frame.size());
+
+    let text = vec![
+        Line::from(""),
+        Line::from("-- Choose your pawn promotion --").alignment(Alignment::Center),
+        Line::from(""),
+    ];
+
+    let paragraph = Paragraph::new(text)
+        .block(Block::default())
+        .alignment(Alignment::Left)
+        .wrap(Wrap { trim: true });
+    frame.render_widget(Clear, area); //this clears out the background
+    frame.render_widget(block, area);
+    frame.render_widget(paragraph, area);
+
+    let inner_popup_layout_vertical = Layout::default()
+        .direction(Direction::Vertical)
+        .constraints(
+            [
+                Constraint::Ratio(1, 3),
+                Constraint::Ratio(1, 3),
+                Constraint::Ratio(1, 3),
+            ]
+            .as_ref(),
+        )
+        .split(area);
+
+    let inner_popup_layout_horizontal = Layout::default()
+        .direction(Direction::Horizontal)
+        .constraints(
+            [
+                Constraint::Ratio(1, 4),
+                Constraint::Ratio(1, 4),
+                Constraint::Ratio(1, 4),
+                Constraint::Ratio(1, 4),
+            ]
+            .as_ref(),
+        )
+        .split(inner_popup_layout_vertical[1]);
+
+    let queen_p = Paragraph::new(Queen::to_string())
+        .block(Block::default())
+        .alignment(Alignment::Center)
+        .style(Style::default().bg(if app.board.promotion_cursor == 0 {
+            Color::LightBlue
+        } else {
+            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())
+        .block(Block::default())
+        .alignment(Alignment::Center)
+        .style(Style::default().bg(if app.board.promotion_cursor == 1 {
+            Color::LightBlue
+        } else {
+            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())
+        .block(Block::default())
+        .alignment(Alignment::Center)
+        .style(Style::default().bg(if app.board.promotion_cursor == 2 {
+            Color::LightBlue
+        } else {
+            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())
+        .block(Block::default())
+        .alignment(Alignment::Center)
+        .style(Style::default().bg(if app.board.promotion_cursor == 3 {
+            Color::LightBlue
+        } else {
+            Color::Reset // Set to the default background color when the condition is false
+        }));
+    frame.render_widget(knight_p, inner_popup_layout_horizontal[3]);
+}
+
+// This render the credit popup
+pub fn render_credit_popup(frame: &mut Frame) {
+    let block = Block::default()
+        .title("Credits")
+        .borders(Borders::ALL)
+        .border_type(BorderType::Rounded)
+        .padding(Padding::horizontal(1))
+        .border_style(Style::default().fg(WHITE));
+    let area = centered_rect(40, 40, frame.size());
+
+    let credits_text = vec![
+        Line::from(""),
+        Line::from("Hi 👋, I'm Thomas, a 21-year-old French computer science student."),
+        Line::from("Thank you for playing Chess-tui! This project started as a personal journey to improve my algorithmic skills and learn Rust."),
+        Line::from(""),
+        Line::from("The entire source code is available on GitHub at https://github.com/thomas-mauran/chess-tui"),
+        Line::from("Feel free to contribute by picking an issue or creating a new one."),
+        Line::from(""),
+        Line::from("Special thanks to my classmates for their support and inspiration!"),
+        Line::from(""),
+        Line::from(""),
+        Line::from(""),
+        Line::from(""),
+        Line::from("Press 'x' to close the popup").alignment(Alignment::Center),
+    ];
+
+    // Assuming Alignment is an enum or struct you have defined
+
+    let paragraph = Paragraph::new(credits_text)
+        .block(block.clone())
+        .alignment(Alignment::Left)
+        .wrap(Wrap { trim: true });
+
+    frame.render_widget(Clear, area); //this clears out the background
+    frame.render_widget(block, area);
+    frame.render_widget(paragraph, area);
+}
+
+// This render the help popup
+pub fn render_help_popup(frame: &mut Frame) {
+    let block = Block::default()
+        .title("Help menu")
+        .borders(Borders::ALL)
+        .border_type(BorderType::Rounded)
+        .padding(Padding::horizontal(1))
+        .border_style(Style::default().fg(WHITE));
+    let area = centered_rect(40, 65, frame.size());
+
+    let text = vec![
+        Line::from("Game controls:".underlined().bold()),
+        Line::from(""),
+        Line::from(vec![
+            "← ↑ ↓ →: Use the arrow keys to move the ".into(),
+            "blue".blue(),
+            " cursor".into(),
+        ]),
+        Line::from(""),
+        Line::from("Ctrl + or -: Zoom in or out to adjust pieces sizes"),
+        Line::from(""),
+        Line::from("SPACE_BAR: Select a piece"),
+        Line::from(""),
+        Line::from("ESCAPE: Deselect a piece"),
+        Line::from(""),
+        Line::from("q: Press q to quit the game"),
+        Line::from(""),
+        Line::from(""),
+        Line::from("Color codes:".underlined().bold()),
+        Line::from(""),
+        Line::from(vec!["Blue cell".blue(), ": Your cursor ".into()]),
+        Line::from(""),
+        Line::from(vec!["Green cell".green(), ": Selected Piece ".into()]),
+        Line::from(""),
+        Line::from(vec![
+            "Purple cell".magenta(),
+            ": The king is getting checked ".into(),
+        ]),
+        Line::from(""),
+        Line::from("Grey cell: Available cells for the selected piece"),
+        Line::from(""),
+        Line::from(""),
+        Line::from("press x to close the popup").alignment(Alignment::Center),
+    ];
+
+    let paragraph = Paragraph::new(text)
+        .block(block.clone())
+        .alignment(Alignment::Left)
+        .wrap(Wrap { trim: true });
+
+    frame.render_widget(Clear, area); //this clears out the background
+    frame.render_widget(block, area);
+    frame.render_widget(paragraph, area);
+}
diff --git a/src/ui.rs b/src/ui.rs
index 9096614..93d5570 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -1,24 +1,111 @@
 use ratatui::{
     layout::{Constraint, Direction, Layout},
     prelude::{Alignment, Rect},
-    style::{Color, Style, Stylize},
+    style::{Style, Stylize},
     text::Line,
-    widgets::{Block, BorderType, Borders, Clear, Padding, Paragraph, Wrap},
+    widgets::{Block, Paragraph},
     Frame,
 };
 
 use crate::{
     app::App,
-    constants::WHITE,
-    pieces::{bishop::Bishop, knight::Knight, queen::Queen, rook::Rook, PieceColor},
+    constants::TITLE,
+    pieces::PieceColor,
+    popups::{render_credit_popup, render_end_popup, render_help_popup, render_promotion_popup},
     utils::get_opposite_color,
 };
 
 /// Renders the user interface widgets.
 pub fn render(app: &mut App, frame: &mut Frame) {
-    // Splitting the full tui in 3 vertical boxes and 3 horizontal boxes in the vertical[1]
     let main_area = frame.size();
 
+    if app.show_home_menu {
+        render_menu_ui(frame, app, main_area)
+    } else {
+        render_game_ui(frame, app, main_area)
+    }
+
+    if app.show_help_popup {
+        render_help_popup(frame)
+    }
+
+    if app.show_credit_popup {
+        render_credit_popup(frame)
+    }
+}
+
+/// helper function to create a centered rect using up certain percentage of the available rect `r`
+pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
+    let popup_layout = Layout::default()
+        .direction(Direction::Vertical)
+        .constraints([
+            Constraint::Percentage((100 - percent_y) / 2),
+            Constraint::Percentage(percent_y),
+            Constraint::Percentage((100 - percent_y) / 2),
+        ])
+        .split(r);
+
+    Layout::default()
+        .direction(Direction::Horizontal)
+        .constraints([
+            Constraint::Percentage((100 - percent_x) / 2),
+            Constraint::Percentage(percent_x),
+            Constraint::Percentage((100 - percent_x) / 2),
+        ])
+        .split(popup_layout[1])[1]
+}
+
+// Method to render the home menu and the options
+pub fn render_menu_ui(frame: &mut Frame, app: &App, main_area: Rect) {
+    let main_layout_horizontal = Layout::default()
+        .direction(Direction::Vertical)
+        .constraints(
+            [
+                Constraint::Ratio(1, 5),
+                Constraint::Ratio(1, 5),
+                Constraint::Ratio(3, 5),
+            ]
+            .as_ref(),
+        )
+        .split(main_area);
+
+    // Title
+    let title_paragraph = Paragraph::new(TITLE)
+        .alignment(Alignment::Center)
+        .block(Block::default());
+    frame.render_widget(title_paragraph, main_layout_horizontal[0]);
+
+    // Board block representing the full board div
+    let text: Vec<Line<'_>> = vec![Line::from(""), Line::from("A chess game made in 🦀")];
+    let sub_title = Paragraph::new(text)
+        .alignment(Alignment::Center)
+        .block(Block::default());
+    frame.render_widget(sub_title, main_layout_horizontal[1]);
+
+    // Board block representing the full board div
+    let menu_items = vec!["Normal game", "Help", "Credits"];
+    let mut menu_body: Vec<Line<'_>> = vec![];
+
+    for i in 0..menu_items.len() {
+        menu_body.push(Line::from(""));
+        let mut text = if app.menu_cursor == i as u8 {
+            "> ".to_string()
+        } else {
+            "".to_string()
+        };
+        text.push_str(menu_items[i]);
+        menu_body.push(Line::from(text))
+    }
+
+    let sub_title = Paragraph::new(menu_body)
+        .bold()
+        .alignment(Alignment::Center)
+        .block(Block::default());
+    frame.render_widget(sub_title, main_layout_horizontal[2])
+}
+
+// Method to render the game board and handle game popups
+pub fn render_game_ui(frame: &mut Frame, app: &App, main_area: Rect) {
     let main_layout_horizontal = Layout::default()
         .direction(Direction::Vertical)
         .constraints(
@@ -58,10 +145,6 @@ pub fn render(app: &mut App, frame: &mut Frame) {
     app.board
         .history_render(board_block.inner(main_layout_vertical[3]), frame);
 
-    if app.show_popup {
-        render_help_popup(frame)
-    }
-
     if app.board.is_promotion {
         render_promotion_popup(frame, app)
     }
@@ -81,195 +164,3 @@ pub fn render(app: &mut App, frame: &mut Frame) {
         render_end_popup(frame, format!("{} Won !!!", string_color))
     }
 }
-
-pub fn render_help_popup(frame: &mut Frame) {
-    let block = Block::default()
-        .title("Help menu")
-        .borders(Borders::ALL)
-        .border_type(BorderType::Rounded)
-        .padding(Padding::horizontal(1))
-        .border_style(Style::default().fg(WHITE));
-    let area = centered_rect(40, 60, frame.size());
-
-    let text = vec![
-        Line::from(""),
-        Line::from(""),
-        Line::from("Game controls:".underlined().bold()),
-        Line::from(""),
-        Line::from(vec![
-            "← ↑ ↓ →: Use the arrow keys to move the ".into(),
-            "blue".blue(),
-            " cursor".into(),
-        ]),
-        Line::from(""),
-        Line::from("SPACE_BAR: Select a piece"),
-        Line::from(""),
-        Line::from("ESCAPE: Deselect a piece",),
-        Line::from(""),
-        Line::from("q: Press q to quit the game"),
-        Line::from(""),
-        Line::from("Ctrl + or -: Zoom in or out to adjust pieces sizes"),
-        Line::from(""),
-        Line::from(""),
-        Line::from("Color codes:".underlined().bold()),
-        Line::from(""),
-        Line::from(vec!["Blue cell".blue(), ": Your cursor ".into()]),
-        Line::from(""),
-        Line::from(vec!["Green cell".green(), ": Selected Piece ".into()]),
-        Line::from(""),
-        Line::from(vec![
-            "Purple cell".magenta(),
-            ": The king is getting checked ".into(),
-        ]),
-        Line::from(""),
-        Line::from(vec![
-            "Red cell".light_red(),
-            ": Available cells for the selected piece ".into(),
-        ]),
-        Line::from(""),
-        Line::from(""),
-        Line::from("press h to close the help menu").alignment(Alignment::Center),
-    ];
-
-    let paragraph = Paragraph::new(text)
-        .block(block.clone())
-        .alignment(Alignment::Left)
-        .wrap(Wrap { trim: true });
-
-    frame.render_widget(Clear, area); //this clears out the background
-    frame.render_widget(block, area);
-    frame.render_widget(paragraph, area);
-}
-
-/// helper function to create a centered rect using up certain percentage of the available rect `r`
-pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
-    let popup_layout = Layout::default()
-        .direction(Direction::Vertical)
-        .constraints([
-            Constraint::Percentage((100 - percent_y) / 2),
-            Constraint::Percentage(percent_y),
-            Constraint::Percentage((100 - percent_y) / 2),
-        ])
-        .split(r);
-
-    Layout::default()
-        .direction(Direction::Horizontal)
-        .constraints([
-            Constraint::Percentage((100 - percent_x) / 2),
-            Constraint::Percentage(percent_x),
-            Constraint::Percentage((100 - percent_x) / 2),
-        ])
-        .split(popup_layout[1])[1]
-}
-
-pub fn render_end_popup(frame: &mut Frame, sentence: String) {
-    let block = Block::default()
-        .title("Game ended")
-        .borders(Borders::ALL)
-        .border_type(BorderType::Rounded)
-        .padding(Padding::horizontal(1))
-        .border_style(Style::default().fg(WHITE));
-    let area = centered_rect(40, 40, frame.size());
-
-    let text = vec![
-        Line::from(sentence).alignment(Alignment::Center),
-        Line::from(""),
-        Line::from(""),
-        Line::from("Press R to start a new game").alignment(Alignment::Center),
-    ];
-
-    let paragraph = Paragraph::new(text)
-        .block(block.clone())
-        .alignment(Alignment::Left)
-        .wrap(Wrap { trim: true });
-
-    frame.render_widget(Clear, area); //this clears out the background
-    frame.render_widget(block, area);
-    frame.render_widget(paragraph, area);
-}
-
-pub fn render_promotion_popup(frame: &mut Frame, app: &App) {
-    let block = Block::default()
-        .title("Pawn promotion")
-        .borders(Borders::ALL)
-        .border_type(BorderType::Rounded)
-        .padding(Padding::horizontal(1))
-        .border_style(Style::default().fg(WHITE));
-    let area = centered_rect(40, 40, frame.size());
-
-    let text = vec![
-        Line::from(""),
-        Line::from("-- Choose your pawn promotion --").alignment(Alignment::Center),
-        Line::from(""),
-    ];
-
-    let paragraph = Paragraph::new(text)
-        .block(Block::default())
-        .alignment(Alignment::Left)
-        .wrap(Wrap { trim: true });
-    frame.render_widget(Clear, area); //this clears out the background
-    frame.render_widget(block, area);
-    frame.render_widget(paragraph, area);
-
-    let inner_popup_layout_vertical = Layout::default()
-        .direction(Direction::Vertical)
-        .constraints(
-            [
-                Constraint::Ratio(1, 3),
-                Constraint::Ratio(1, 3),
-                Constraint::Ratio(1, 3),
-            ]
-            .as_ref(),
-        )
-        .split(area);
-
-    let inner_popup_layout_horizontal = Layout::default()
-        .direction(Direction::Horizontal)
-        .constraints(
-            [
-                Constraint::Ratio(1, 4),
-                Constraint::Ratio(1, 4),
-                Constraint::Ratio(1, 4),
-                Constraint::Ratio(1, 4),
-            ]
-            .as_ref(),
-        )
-        .split(inner_popup_layout_vertical[1]);
-
-    let queen_p = Paragraph::new(Queen::to_string())
-        .block(Block::default())
-        .alignment(Alignment::Center)
-        .style(Style::default().bg(if app.board.promotion_cursor == 0 {
-            Color::LightBlue
-        } else {
-            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())
-        .block(Block::default())
-        .alignment(Alignment::Center)
-        .style(Style::default().bg(if app.board.promotion_cursor == 1 {
-            Color::LightBlue
-        } else {
-            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())
-        .block(Block::default())
-        .alignment(Alignment::Center)
-        .style(Style::default().bg(if app.board.promotion_cursor == 2 {
-            Color::LightBlue
-        } else {
-            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())
-        .block(Block::default())
-        .alignment(Alignment::Center)
-        .style(Style::default().bg(if app.board.promotion_cursor == 3 {
-            Color::LightBlue
-        } else {
-            Color::Reset // Set to the default background color when the condition is false
-        }));
-    frame.render_widget(knight_p, inner_popup_layout_horizontal[3]);
-}


Test patch: