]> git.seodisparate.com - EN605.607.81.SP22_ASDM_Project/commitdiff
Impl use of win/draw check, fixes
authorStephen Seo <seo.disparate@gmail.com>
Wed, 9 Mar 2022 08:29:53 +0000 (17:29 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Wed, 9 Mar 2022 08:29:53 +0000 (17:29 +0900)
Also added unit tests for win/draw checks.

front_end/src/game_logic.rs
front_end/src/html_helper.rs [new file with mode: 0644]
front_end/src/main.rs
front_end/src/state.rs
front_end/src/yew_components.rs

index 98ab8721f19d20c9d10e148c2087dda2e305b087..e2c51110afb21316b514fdd9ec5a5ab6b675ce32 100644 (file)
@@ -14,8 +14,8 @@ pub fn check_win_draw(board: &BoardType) -> Option<BoardState> {
         }
     }
 
-    if has_empty_slot {
-        return None;
+    if !has_empty_slot {
+        return Some(BoardState::Empty);
     }
 
     let check_result = |state| -> Option<BoardState> {
@@ -130,3 +130,200 @@ fn has_right_down_diagonal_at_idx(idx: usize, board: &BoardType) -> BoardState {
     }
     BoardState::Empty
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::state::{new_empty_board, BoardState};
+
+    use super::*;
+
+    #[test]
+    fn test_horizontal_check() {
+        let board = new_empty_board();
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                assert_eq!(
+                    has_right_horizontal_at_idx(x + y * (COLS as usize), &board),
+                    BoardState::Empty
+                );
+            }
+        }
+
+        board[50].replace(BoardState::Cyan);
+        board[51].replace(BoardState::Cyan);
+        board[52].replace(BoardState::Cyan);
+        board[53].replace(BoardState::Cyan);
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                let idx = x + y * (COLS as usize);
+                if idx == 50 {
+                    assert_eq!(has_right_horizontal_at_idx(idx, &board), BoardState::Cyan);
+                } else {
+                    assert_eq!(has_right_horizontal_at_idx(idx, &board), BoardState::Empty);
+                }
+            }
+        }
+
+        board[51].replace(BoardState::Magenta);
+
+        board[43].replace(BoardState::Magenta);
+        board[44].replace(BoardState::Magenta);
+        board[45].replace(BoardState::Magenta);
+        board[46].replace(BoardState::Magenta);
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                let idx = x + y * (COLS as usize);
+                if idx == 43 {
+                    assert_eq!(
+                        has_right_horizontal_at_idx(idx, &board),
+                        BoardState::Magenta
+                    );
+                } else {
+                    assert_eq!(has_right_horizontal_at_idx(idx, &board), BoardState::Empty);
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn test_vertical_check() {
+        let board = new_empty_board();
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                assert_eq!(
+                    has_down_vertical_at_idx(x + y * (COLS as usize), &board),
+                    BoardState::Empty
+                );
+            }
+        }
+
+        board[30].replace(BoardState::Cyan);
+        board[37].replace(BoardState::Cyan);
+        board[44].replace(BoardState::Cyan);
+        board[51].replace(BoardState::Cyan);
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                let idx = x + y * (COLS as usize);
+                if idx == 30 {
+                    assert_eq!(has_down_vertical_at_idx(idx, &board), BoardState::Cyan);
+                } else {
+                    assert_eq!(has_down_vertical_at_idx(idx, &board), BoardState::Empty);
+                }
+            }
+        }
+
+        board[16].replace(BoardState::Magenta);
+        board[23].replace(BoardState::Magenta);
+        board[30].replace(BoardState::Magenta);
+        board[37].replace(BoardState::Magenta);
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                let idx = x + y * (COLS as usize);
+                if idx == 16 {
+                    assert_eq!(has_down_vertical_at_idx(idx, &board), BoardState::Magenta);
+                } else {
+                    assert_eq!(has_down_vertical_at_idx(idx, &board), BoardState::Empty);
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn test_upper_diagonal_check() {
+        let board = new_empty_board();
+
+        board[44].replace(BoardState::Cyan);
+        board[38].replace(BoardState::Cyan);
+        board[32].replace(BoardState::Cyan);
+        board[26].replace(BoardState::Cyan);
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                let idx = x + y * (COLS as usize);
+                if idx == 44 {
+                    assert_eq!(has_right_up_diagonal_at_idx(idx, &board), BoardState::Cyan);
+                } else {
+                    assert_eq!(has_right_up_diagonal_at_idx(idx, &board), BoardState::Empty);
+                }
+            }
+        }
+
+        board[38].replace(BoardState::Magenta);
+
+        board[28].replace(BoardState::Magenta);
+        board[22].replace(BoardState::Magenta);
+        board[16].replace(BoardState::Magenta);
+        board[10].replace(BoardState::Magenta);
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                let idx = x + y * (COLS as usize);
+                if idx == 28 {
+                    assert_eq!(
+                        has_right_up_diagonal_at_idx(idx, &board),
+                        BoardState::Magenta
+                    );
+                } else {
+                    assert_eq!(has_right_up_diagonal_at_idx(idx, &board), BoardState::Empty);
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn test_lower_diagonal_check() {
+        let board = new_empty_board();
+
+        board[17].replace(BoardState::Cyan);
+        board[25].replace(BoardState::Cyan);
+        board[33].replace(BoardState::Cyan);
+        board[41].replace(BoardState::Cyan);
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                let idx = x + y * (COLS as usize);
+                if idx == 17 {
+                    assert_eq!(
+                        has_right_down_diagonal_at_idx(idx, &board),
+                        BoardState::Cyan
+                    );
+                } else {
+                    assert_eq!(
+                        has_right_down_diagonal_at_idx(idx, &board),
+                        BoardState::Empty
+                    );
+                }
+            }
+        }
+
+        board[25].replace(BoardState::Magenta);
+
+        board[28].replace(BoardState::Magenta);
+        board[36].replace(BoardState::Magenta);
+        board[44].replace(BoardState::Magenta);
+        board[52].replace(BoardState::Magenta);
+
+        for y in 0..(ROWS as usize) {
+            for x in 0..(COLS as usize) {
+                let idx = x + y * (COLS as usize);
+                if idx == 28 {
+                    assert_eq!(
+                        has_right_down_diagonal_at_idx(idx, &board),
+                        BoardState::Magenta
+                    );
+                } else {
+                    assert_eq!(
+                        has_right_down_diagonal_at_idx(idx, &board),
+                        BoardState::Empty
+                    );
+                }
+            }
+        }
+    }
+}
diff --git a/front_end/src/html_helper.rs b/front_end/src/html_helper.rs
new file mode 100644 (file)
index 0000000..6e4efdf
--- /dev/null
@@ -0,0 +1,54 @@
+use web_sys::{window, Document, Window};
+
+pub fn get_window_document() -> Result<(Window, Document), String> {
+    let window = window().ok_or_else(|| String::from("Failed to get window"))?;
+    let document = window
+        .document()
+        .ok_or_else(|| String::from("Failed to get document"))?;
+
+    Ok((window, document))
+}
+
+pub fn append_to_info_text(
+    document: &Document,
+    id: &str,
+    msg: &str,
+    limit: u32,
+) -> Result<(), String> {
+    let info_text = document
+        .get_element_by_id(id)
+        .ok_or_else(|| format!("Failed to get info_text \"{}\"", id))?;
+
+    let height = info_text.client_height();
+
+    // create the new text to be appended in the text
+    let p = document
+        .create_element("p")
+        .map_err(|e| format!("{:?}", e))?;
+
+    p.set_inner_html(msg);
+
+    // check if scrolled to top
+    let at_top: bool = info_text.scroll_top() <= height - info_text.scroll_height();
+
+    // append text to output
+    info_text
+        .append_with_node_1(&p)
+        .map_err(|e| format!("{:?}", e))?;
+
+    while info_text.child_element_count() > limit {
+        info_text
+            .remove_child(
+                &info_text.first_child().ok_or_else(|| {
+                    format!("Failed to get first_child() of info_text \"{}\"", id)
+                })?,
+            )
+            .map_err(|e| format!("{:?}", e))?;
+    }
+
+    if at_top {
+        info_text.set_scroll_top(height - info_text.scroll_height());
+    }
+
+    Ok(())
+}
index eeca10f2a360b0c5e4435b4b19169a222a32f09b..a200b4e8eacd8c84737c22d34cc6324d376c2613 100644 (file)
@@ -1,6 +1,7 @@
 mod ai;
 mod constants;
 mod game_logic;
+mod html_helper;
 mod random_helper;
 mod state;
 mod yew_components;
index 8b8e2cc98ed18e50f7a08758b774ebb0a0449df4..5c571e6eb70ea71e77080ed93461065750d1f017 100644 (file)
@@ -9,7 +9,7 @@ pub enum GameState {
     SinglePlayer,
     LocalMultiplayer,
     NetworkedMultiplayer,
-    PostGameResults(Turn),
+    PostGameResults(BoardState),
 }
 
 impl Default for GameState {
@@ -108,6 +108,67 @@ impl Turn {
 
 pub type BoardType = [Rc<Cell<BoardState>>; 56];
 
+pub fn new_empty_board() -> BoardType {
+    [
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+        Rc::new(Cell::new(BoardState::default())),
+    ]
+}
+
 #[derive(Clone, Debug, PartialEq)]
 pub struct SharedState {
     pub board: BoardType,
@@ -119,64 +180,7 @@ impl Default for SharedState {
     fn default() -> Self {
         Self {
             // cannot use [<type>; 56] because Rc does not impl Copy
-            board: [
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-                Rc::new(Cell::new(BoardState::default())),
-            ],
+            board: new_empty_board(),
             game_state: Rc::new(Cell::new(GameState::default())),
             turn: Rc::new(Cell::new(Turn::CyanPlayer)),
         }
index 56f6e84fb43e0fcac691e1b75861bec2f27d236c..b6fe872a344b7a1da3c3a8689e1202dc5f60c58b 100644 (file)
@@ -1,7 +1,11 @@
 use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS};
+use crate::game_logic::check_win_draw;
+use crate::html_helper::{append_to_info_text, get_window_document};
 use crate::state::{BoardState, GameState, SharedState, Turn};
+
 use std::cell::Cell;
 use std::rc::Rc;
+
 use yew::prelude::*;
 
 pub struct MainMenu {}
@@ -117,6 +121,13 @@ impl Component for Slot {
             .context::<SharedState>(Callback::noop())
             .expect("state to be set");
 
+        match shared.game_state.get() {
+            GameState::MainMenu => return false,
+            GameState::SinglePlayer
+            | GameState::LocalMultiplayer
+            | GameState::NetworkedMultiplayer => (),
+            GameState::PostGameResults(_) => return false,
+        }
         if shared.game_state.get() == GameState::MainMenu {
             return false;
         }
@@ -228,8 +239,8 @@ impl Component for Wrapper {
             .link()
             .context::<SharedState>(Callback::noop())
             .expect("state to be set");
-        let window = web_sys::window().expect("no window exists");
-        let document = window.document().expect("window should have a document");
+        let (window, document) =
+            get_window_document().expect("Should be able to get Window and Document");
 
         match msg {
             WrapperMsg::Pressed(idx) => {
@@ -268,99 +279,82 @@ impl Component for Wrapper {
                     placed = true;
                 }
 
-                // DEBUG
-                //log::info!("{} is {:?}", idx, shared.board[idx as usize].get());
-
-                // DEBUG
-                //log::info!("{}", &output_str);
-
-                // info text below the grid
-                if let Some(info_text) = document.get_element_by_id("info_text0") {
-                    let height = info_text.client_height();
-
-                    // create the new text to be appended in the output
-                    let p = document
-                        .create_element("p")
-                        .expect("document should be able to create <p>");
-                    let output_str = match placed {
-                        true => format!("{} placed into slot {}", current_player, bottom_idx),
-                        false => "Invalid place to insert".into(),
-                    };
-                    p.set_text_content(Some(&output_str));
-
-                    // DEBUG
-                    //log::info!(
-                    //    "pre: scroll top is {}, scroll height is {}",
-                    //    info_text.scroll_top(),
-                    //    info_text.scroll_height()
-                    //);
-
-                    // check if scrolled to top
-                    let at_top: bool = info_text.scroll_top() <= height - info_text.scroll_height();
-
-                    // append text to output
-                    info_text
-                        .append_with_node_1(&p)
-                        .expect("should be able to append to info_text");
-                    while info_text.child_element_count() > INFO_TEXT_MAX_ITEMS {
-                        info_text
-                            .remove_child(&info_text.first_child().unwrap())
-                            .expect("should be able to limit items in info_text");
+                // check for win
+                let check_win_draw_opt = check_win_draw(&shared.board);
+                if let Some(endgame_state) = check_win_draw_opt {
+                    if endgame_state == BoardState::Empty {
+                        // draw
+                        let text_append_result = append_to_info_text(
+                            &document,
+                            "info_text0",
+                            "Game ended in a draw",
+                            INFO_TEXT_MAX_ITEMS,
+                        );
+                        if let Err(e) = text_append_result {
+                            log::warn!("ERROR: text append to info_text0 failed: {}", e);
+                        }
+                    } else {
+                        // a player won
+                        let turn = Turn::from(endgame_state);
+                        let text_string =
+                            format!("<b class=\"{}\">{} has won</b>", turn.get_color(), turn);
+                        let text_append_result = append_to_info_text(
+                            &document,
+                            "info_text0",
+                            &text_string,
+                            INFO_TEXT_MAX_ITEMS,
+                        );
+                        if let Err(e) = text_append_result {
+                            log::warn!("ERROR: text append to info_text0 failed: {}", e);
+                        }
                     }
 
-                    // DEBUG
-                    //log::info!("at_top is {}", if at_top { "true" } else { "false" });
-
-                    // scroll to top only if at top
-                    if at_top {
-                        info_text.set_scroll_top(height - info_text.scroll_height());
+                    let text_append_result =
+                        append_to_info_text(&document, "info_text1", "<b>Game Over</b>", 1);
+                    if let Err(e) = text_append_result {
+                        log::warn!("ERROR: text append to info_text1 failed: {}", e);
                     }
 
-                    // DEBUG
-                    //log::info!(
-                    //    "post: scroll top is {}, scroll height is {}",
-                    //    info_text.scroll_top(),
-                    //    info_text.scroll_height()
-                    //);
+                    shared
+                        .game_state
+                        .replace(GameState::PostGameResults(endgame_state));
                 } else {
-                    log::warn!("Failed to get bottom \"info_text\"");
-                }
+                    // game is still ongoing
 
-                // info text right of the grid
-                if let Some(info_text) = document.get_element_by_id("info_text1") {
-                    let height = info_text.client_height();
-
-                    // create the new text to be appended in the output
-                    let p = document
-                        .create_element("p")
-                        .expect("document should be able to create <p>");
-                    let turn = shared.turn.get();
-                    p.set_inner_html(&format!(
-                        "<b class=\"{}\">It is {}'s turn</b>",
-                        turn.get_color(),
-                        turn
-                    ));
-
-                    // check if scrolled to top
-                    let at_top: bool = info_text.scroll_top() <= height - info_text.scroll_height();
-
-                    // append text to output
-                    info_text
-                        .append_with_node_1(&p)
-                        .expect("should be able to append to info_text");
-                    while info_text.child_element_count() > 1 {
-                        info_text
-                            .remove_child(&info_text.first_child().unwrap())
-                            .expect("should be able to limit items in info_text");
+                    // info text below the grid
+                    {
+                        let output_str = match placed {
+                            true => format!("{} placed into slot {}", current_player, bottom_idx),
+                            false => "Invalid place to insert".into(),
+                        };
+
+                        let text_append_result = append_to_info_text(
+                            &document,
+                            "info_text0",
+                            &output_str,
+                            INFO_TEXT_MAX_ITEMS,
+                        );
+                        if let Err(e) = text_append_result {
+                            log::warn!("ERROR: text append to info_text0 failed: {}", e);
+                        }
                     }
 
-                    // scroll to top only if at top
-                    if at_top {
-                        info_text.set_scroll_top(height - info_text.scroll_height());
+                    // info text right of the grid
+                    {
+                        let turn = shared.turn.get();
+                        let output_str = format!(
+                            "<b class=\"{}\">It is {}'s turn</b>",
+                            turn.get_color(),
+                            turn
+                        );
+
+                        let text_append_result =
+                            append_to_info_text(&document, "info_text1", &output_str, 1);
+                        if let Err(e) = text_append_result {
+                            log::warn!("ERROR: text append to info_text1 failed: {}", e);
+                        }
                     }
-                } else {
-                    log::warn!("Failed to get side \"info_text\"");
-                }
+                } // else: game is still ongoing after logic check
             } // WrapperMsg::Pressed(idx) =>
         } // match (msg)