From 202c11e01bd8edfc2fd2f9bed6855f10ede2f77e Mon Sep 17 00:00:00 2001 From: Jagadeesh B Date: Sun, 9 Feb 2025 19:18:01 +0530 Subject: [PATCH] Feat: Dynamic Move Function Implementation (#205) * fix: resolve compilation errors * refactor: auto-detect player color from caller address * refactor: use active_colors for dynamic color handling in pos_reducer * refactor: remove players_length logic and set default tokens for all colors * refactor: use active_colors in move and capture logic * test: add test for extra move on dice 6 and winner-turn skip scenarios * refactor: format the code * refactor: pass game_id via parameter --- onchain/src/constants.cairo | 33 +++- onchain/src/models/game.cairo | 150 +++------------ onchain/src/systems/game_actions.cairo | 243 ++++++++++++++++--------- onchain/src/tests/test_game.cairo | 204 +++++++++++++++++---- 4 files changed, 374 insertions(+), 256 deletions(-) diff --git a/onchain/src/constants.cairo b/onchain/src/constants.cairo index d717273..c137261 100644 --- a/onchain/src/constants.cairo +++ b/onchain/src/constants.cairo @@ -202,30 +202,47 @@ fn get_cap_colors() -> Array { array!['R', 'G', 'Y', 'B'] } -fn pos_reducer(data: Array, players_length: u32) -> Array { +fn pos_reducer(data: Array, active_colors: Array) -> Array { let mut game: Array = ArrayTrait::new(); let cap_colors = get_cap_colors(); + let mut active_piece_count: u32 = 0; let mut i: u32 = 0; loop { if i >= data.len() { break; } - if i < players_length * 4 { - let d = *data.at(i); - let color = *cap_colors.at((i / 4).try_into().unwrap()); + + let d = *data.at(i); + + // Determine the color index based on the current index + let color_index = i / 4; + let piece_index = i % 4; + + let mut active_colors_u32: Array = ArrayTrait::new(); + for color in active_colors.clone() { + active_colors_u32.append((color).into()); + }; + + // Check if the current color is in the active colors + if contains(active_colors_u32, color_index) { + let color_id = color_index; + + let color_char = *cap_colors.at(color_id.into()); let value = if d == 0 { - // Format: color + "0" + (i % 4 + 1) - color * 100 + (i % 4 + 1).into() + let piece_offset = piece_index + 1; + color_char * 100 + piece_offset.into() } else if d > 1000 { - // Format: color + (d % 1000) - color * 1000 + (d % 1000).into() + let remainder = d % 1000; + color_char * 1000 + remainder.into() } else { d.into() }; game.append(value); + + active_piece_count += 1; } i += 1; }; diff --git a/onchain/src/models/game.cairo b/onchain/src/models/game.cairo index 17289d4..d4b2c3e 100644 --- a/onchain/src/models/game.cairo +++ b/onchain/src/models/game.cairo @@ -68,7 +68,7 @@ pub struct Game { pub b0: felt252, // blue piece position on board pub b1: felt252, // blue piece position on board pub b2: felt252, // blue piece position on board - pub b3: felt252, + pub b3: felt252, // blue piece position on board } pub trait GameTrait { @@ -138,134 +138,26 @@ impl GameImpl of GameTrait { 0_u32, 0_u32, ], - r0: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 'R01', - 3 => 'R01', - 4 => 'R01', - _ => panic!("invalid number of players"), - }, - r1: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 'R02', - 3 => 'R02', - 4 => 'R02', - _ => panic!("invalid number of players"), - }, - r2: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 'R03', - 3 => 'R03', - 4 => 'R03', - _ => panic!("invalid number of players"), - }, - r3: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 'R04', - 3 => 'R04', - 4 => 'R04', - _ => panic!("invalid number of players"), - }, - g0: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 'G01', - 3 => 'G01', - 4 => 'G01', - _ => panic!("invalid number of players"), - }, - g1: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 'G02', - 3 => 'G02', - 4 => 'G02', - _ => panic!("invalid number of players"), - }, - g2: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 'G03', - 3 => 'G03', - 4 => 'G03', - _ => panic!("invalid number of players"), - }, - g3: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 'GO4', - 3 => 'GO4', - 4 => 'GO4', - _ => panic!("invalid number of players"), - }, - y0: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 0, - 3 => 'Y01', - 4 => 'Y01', - _ => panic!("invalid number of players"), - }, - y1: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 0, - 3 => 'Y02', - 4 => 'Y02', - _ => panic!("invalid number of players"), - }, - y2: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 0, - 3 => 'Y03', - 4 => 'Y03', - _ => panic!("invalid number of players"), - }, - y3: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 0, - 3 => 'Y04', - 4 => 'Y04', - _ => panic!("invalid number of players"), - }, - b0: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 0, - 3 => 0, - 4 => 'B01', - _ => panic!("invalid number of players"), - }, - b1: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 0, - 3 => 0, - 4 => 'B02', - _ => panic!("invalid number of players"), - }, - b2: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 0, - 3 => 0, - 4 => 'B03', - _ => panic!("invalid number of players"), - }, - b3: match number_of_players { - 0 => panic!("number of players cannot be 0"), - 1 => panic!("number of players cannot be 1"), - 2 => 0, - 3 => 0, - 4 => 'B04', - _ => panic!("invalid number of players"), - }, + // default tokens for red pieces + r0: 'R01', + r1: 'R02', + r2: 'R03', + r3: 'R04', + // Default tokens for green pieces + g0: 'G01', + g1: 'G02', + g2: 'G03', + g3: 'G04', + // Default tokens for yellow pieces + y0: 'Y01', + y1: 'Y02', + y2: 'Y03', + y3: 'Y04', + // Default tokens for blue pieces + b0: 'B01', + b1: 'B02', + b2: 'B03', + b3: 'B04', } } diff --git a/onchain/src/systems/game_actions.cairo b/onchain/src/systems/game_actions.cairo index a0fcf48..34a93ea 100644 --- a/onchain/src/systems/game_actions.cairo +++ b/onchain/src/systems/game_actions.cairo @@ -10,7 +10,7 @@ trait IGameActions { ref self: T, game_mode: GameMode, player_color: PlayerColor, number_of_players: u8, ) -> u64; fn join(ref self: T, player_color: PlayerColor, game_id: u64); - fn move(ref self: T, pos: felt252, color: u8); + fn move(ref self: T, pos: felt252, game_id: u64); fn roll(ref self: T) -> (u8, u8); fn get_current_game_id(self: @T) -> u64; @@ -20,6 +20,8 @@ trait IGameActions { fn get_username_from_address(self: @T, address: ContractAddress) -> felt252; fn get_address_from_username(self: @T, username: felt252) -> ContractAddress; fn move_deducer(ref self: T, val: u32, dice_throw: u32) -> (u32, bool, bool); + fn get_next_color(ref self: T, current_color: u8, isChance: bool, game_id: u64) -> u8; + fn get_active_colors(self: @T, game_id: u64) -> Array; } #[dojo::contract] @@ -110,7 +112,7 @@ pub mod GameActions { }; // Create a new game - let new_game: Game = GameTrait::new( + let mut new_game: Game = GameTrait::new( game_id, caller_username, game_mode, @@ -121,6 +123,14 @@ pub mod GameActions { number_of_players, ); + // If it's a multiplayer game, set status to Pending, + // else mark it as Ongoing (for single-player). + if game_mode == GameMode::MultiPlayer { + new_game.status = GameStatus::Pending; + } else { + new_game.status = GameStatus::Ongoing; + } + world.write_model(@new_game); world.emit_event(@GameCreated { game_id, timestamp }); @@ -161,6 +171,7 @@ pub mod GameActions { // Verify that color is available // Assign color to player if available + match player_color { PlayerColor::Red => { if (game.player_red == 0) { @@ -185,7 +196,7 @@ pub mod GameActions { }, PlayerColor::Yellow => { if (game.player_yellow == 0) { - game.player_yellow = caller_username + game.player_yellow = caller_username; } else { panic!("YELLOW already selected"); } @@ -271,16 +282,28 @@ pub mod GameActions { world.write_model(@game); } - fn move(ref self: ContractState, pos: felt252, color: u8) { + fn move(ref self: ContractState, pos: felt252, game_id: u64) { // Get world state let mut world = self.world_default(); - // Get the current game ID - let game_id = self.get_current_game_id(); - // Retrieve the game state let mut game: Game = world.read_model(game_id); + let caller_address = get_caller_address(); + let caller_username = self.get_username_from_address(caller_address); + + let color: u8 = if caller_username == game.player_red.try_into().unwrap() { + 0_u8 + } else if caller_username == game.player_green.try_into().unwrap() { + 1_u8 + } else if caller_username == game.player_yellow.try_into().unwrap() { + 2_u8 + } else if caller_username == game.player_blue.try_into().unwrap() { + 3_u8 + } else { + panic!("CALLER NOT REGISTERED AS A PLAYER") + }; + // Get the dice throw value let diceThrow: u32 = game.dice_face.into(); @@ -327,35 +350,51 @@ pub mod GameActions { // Get the safe positions array let safe_pos = get_safe_positions(); - // Get the number of players - let players_length = game.number_of_players.try_into().unwrap(); + let active_colors = self.get_active_colors(game_id); // Check if the new position is not a safe position if !contains(safe_pos, *val) { - let mut i: u32 = 0; + // Loop over each active color block (active_colors holds the colors in use) + let active_colors_len = active_colors.len(); + let mut block_idx: u32 = 0; loop { - if i >= (players_length * 4) { + if block_idx >= active_colors_len { break; } - // Check if the position is occupied by an opponent's piece - if color != (i / 4).try_into().unwrap() && *condition.at(i) == *val { - isChance = true; - let mut new_condition = ArrayTrait::new(); - let mut j: u32 = 0; - loop { - if j >= condition.len() { - break; - } - if j == i { - new_condition.append(0); // Capture the opponent's piece - } else { - new_condition.append(*condition.at(j)); - } - j += 1; - }; - condition = new_condition; - } - i += 1; + // Get the color id from active_colors for the current block + let current_active_color = *active_colors.at(block_idx); + let mut piece: u32 = 0; + loop { + if piece >= 4 { + break; + } + let global_index = current_active_color.into() * 4 + piece; + + // Check if this piece does not belong to the caller and if its position + // equals *val. + if (current_active_color != color) + && (*condition.at(global_index) == *val) { + isChance = true; + // Rebuild condition array with captured piece replaced with 0. + let mut new_condition = ArrayTrait::new(); + let mut k: u32 = 0; + loop { + if k >= condition.len() { + break; + } + if k == global_index { + new_condition.append(0); // Capture opponent's piece. + } else { + new_condition.append(*condition.at(k)); + } + k += 1; + }; + condition = new_condition; + break; + } + piece += 1; + }; + block_idx += 1; }; } @@ -367,59 +406,51 @@ pub mod GameActions { // Update the game condition game.game_condition = condition.clone(); + let active_colors = self.get_active_colors(game_id); + // Convert the condition back to array positions let current_condition = condition; - let deref = pos_reducer(current_condition, players_length); + let deref = pos_reducer(current_condition, active_colors.clone()); let output = deref.clone(); - // Update the game state with the new positions - match players_length { - 0 => {}, - 1 => {}, - 2 => { - game.r0 = *output.get(0).unwrap().unbox(); - game.r1 = *output.get(1).unwrap().unbox(); - game.r2 = *output.get(2).unwrap().unbox(); - game.r3 = *output.get(3).unwrap().unbox(); - game.g0 = *output.get(4).unwrap().unbox(); - game.g1 = *output.get(5).unwrap().unbox(); - game.g2 = *output.get(6).unwrap().unbox(); - game.g3 = *output.get(7).unwrap().unbox(); - }, - 3 => { - game.r0 = *output.get(0).unwrap().unbox(); - game.r1 = *output.get(1).unwrap().unbox(); - game.r2 = *output.get(2).unwrap().unbox(); - game.r3 = *output.get(3).unwrap().unbox(); - game.g0 = *output.get(4).unwrap().unbox(); - game.g1 = *output.get(5).unwrap().unbox(); - game.g2 = *output.get(6).unwrap().unbox(); - game.g3 = *output.get(7).unwrap().unbox(); - game.y0 = *output.get(8).unwrap().unbox(); - game.y1 = *output.get(9).unwrap().unbox(); - game.y2 = *output.get(10).unwrap().unbox(); - game.y3 = *output.get(11).unwrap().unbox(); - }, - 4 => { - game.r0 = *output.get(0).unwrap().unbox(); - game.r1 = *output.get(1).unwrap().unbox(); - game.r2 = *output.get(2).unwrap().unbox(); - game.r3 = *output.get(3).unwrap().unbox(); - game.g0 = *output.get(4).unwrap().unbox(); - game.g1 = *output.get(5).unwrap().unbox(); - game.g2 = *output.get(6).unwrap().unbox(); - game.g3 = *output.get(7).unwrap().unbox(); - game.y0 = *output.get(8).unwrap().unbox(); - game.y1 = *output.get(9).unwrap().unbox(); - game.y2 = *output.get(10).unwrap().unbox(); - game.y3 = *output.get(11).unwrap().unbox(); - game.b0 = *output.get(12).unwrap().unbox(); - game.b1 = *output.get(13).unwrap().unbox(); - game.b2 = *output.get(14).unwrap().unbox(); - game.b3 = *output.get(15).unwrap().unbox(); - }, - _ => {}, - } + let mut offset: usize = 0; + + for c_i in 0..active_colors.len() { + let c = *active_colors.at(c_i); + match c { + 0 => { + game.r0 = *output.get(offset).unwrap().unbox(); + game.r1 = *output.get(offset + 1).unwrap().unbox(); + game.r2 = *output.get(offset + 2).unwrap().unbox(); + game.r3 = *output.get(offset + 3).unwrap().unbox(); + }, + 1 => { + game.g0 = *output.get(offset).unwrap().unbox(); + game.g1 = *output.get(offset + 1).unwrap().unbox(); + game.g2 = *output.get(offset + 2).unwrap().unbox(); + game.g3 = *output.get(offset + 3).unwrap().unbox(); + }, + 2 => { + game.y0 = *output.get(offset).unwrap().unbox(); + game.y1 = *output.get(offset + 1).unwrap().unbox(); + game.y2 = *output.get(offset + 2).unwrap().unbox(); + game.y3 = *output.get(offset + 3).unwrap().unbox(); + }, + 3 => { + game.b0 = *output.get(offset).unwrap().unbox(); + game.b1 = *output.get(offset + 1).unwrap().unbox(); + game.b2 = *output.get(offset + 2).unwrap().unbox(); + game.b3 = *output.get(offset + 3).unwrap().unbox(); + }, + _ => {}, + }; + offset += 4; + }; + + world.write_model(@game); + + // Retrieve the game state + let mut game: Game = world.read_model(game_id); // Get the current player's pieces let mut color_state = ArrayTrait::new(); @@ -452,12 +483,7 @@ pub mod GameActions { k += 1; }; - // Determine the next player - let mut new_color = if isChance { - color - } else { - (color + 1) % players_length.try_into().unwrap() - }; + let mut new_color = self.get_next_color(color, isChance, game_id); // Get the player addresses let red_address = game.player_red; @@ -485,7 +511,7 @@ pub mod GameActions { while next_player_address == winner_1 || next_player_address == winner_2 || next_player_address == winner_3 { - new_chance = (new_chance + 1) % players_length.try_into().unwrap(); + new_chance = (new_chance + 1) % active_colors.len().try_into().unwrap(); next_player_address = match new_chance { 0 => red_address, 1 => green_address, @@ -633,6 +659,51 @@ pub mod GameActions { username_map.address } + + fn get_next_color(ref self: ContractState, current_color: u8, isChance: bool, game_id: u64) -> u8 { + // Gather only active colors + let mut active_colors: Array = self.get_active_colors(game_id); + + // Find the index of current_color + let mut idx = 0_usize; + loop { + if idx >= active_colors.len() { + panic!("Current color not found in active colors"); + } + if *active_colors.at(idx) == current_color { + break; + } + idx += 1; + }; + + // move to the next color + if !isChance { + idx = (idx + 1) % active_colors.len(); + } + + let mut new_color = *active_colors.at(idx); + new_color + } + + fn get_active_colors(self: @ContractState, game_id: u64) -> Array { + let mut world = self.world_default(); + let game_id = self.get_current_game_id(); + let game: Game = world.read_model(game_id); + let mut colors: Array = ArrayTrait::new(); + if game.player_red != 0 { + colors.append(0); + } + if game.player_green != 0 { + colors.append(1); + } + if game.player_yellow != 0 { + colors.append(2); + } + if game.player_blue != 0 { + colors.append(3); + } + colors + } } #[generate_trait] diff --git a/onchain/src/tests/test_game.cairo b/onchain/src/tests/test_game.cairo index a43163c..6fe713b 100644 --- a/onchain/src/tests/test_game.cairo +++ b/onchain/src/tests/test_game.cairo @@ -309,7 +309,7 @@ mod tests { assert(created_game.number_of_players == 3, 'Wrong number of players'); assert(created_game.player_blue == username, 'Wrong player color assignment'); assert(created_game.player_red == 0, 'Red should not be assigned'); - assert(created_game.status == GameStatus::Pending, 'Wrong game status'); + assert(created_game.status == GameStatus::Ongoing, 'Wrong game status'); } #[test] @@ -368,9 +368,9 @@ mod tests { #[test] fn test_pos_reducer() { let data = array![0, 1001, 23, 32, 2001, 2006, 23, 43, 12, 3006]; - let players_length = 2; + let active_colors = array![0, 1]; let expected_output = array![8201, 82001, 23, 32, 71001, 71006, 23, 43]; - let output = pos_reducer(data, players_length); + let output = pos_reducer(data, active_colors); assert(output == expected_output, 'Pos reducer failed'); } @@ -433,8 +433,9 @@ mod tests { testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); + testing::set_contract_address(caller); // Move piece from initial position with dice throw 6 - game_action_system.move('r0', 0); + game_action_system.move('r0', game_id); let game: Game = world.read_model(game_id); @@ -467,7 +468,9 @@ mod tests { testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('r0', 0); + + testing::set_contract_address(caller); + game_action_system.move('r0', game_id); // Verify the new position let game: Game = world.read_model(game_id); @@ -484,34 +487,46 @@ mod tests { // roll. let (mut world, game_action_system) = setup_world(); - let caller = contract_address_const::<'test_alice'>(); - let username = 'alice'; + let caller_blue = contract_address_const::<'test_blue'>(); + let username_blue = 'blue'; - testing::set_contract_address(caller); - game_action_system.create_new_player(username, false); + let caller_yellow = contract_address_const::<'test_bob'>(); + let username_yellow = 'yellow'; + + testing::set_contract_address(caller_blue); + game_action_system.create_new_player(username_blue, false); + + testing::set_contract_address(caller_yellow); + game_action_system.create_new_player(username_yellow, false); + testing::set_contract_address(caller_blue); // Setup initial game state let game_id = game_action_system - .create_new_game(GameMode::MultiPlayer, PlayerColor::Red, 2); + .create_new_game(GameMode::MultiPlayer, PlayerColor::Blue, 2); + + testing::set_contract_address(caller_yellow); + game_action_system.join(PlayerColor::Yellow, game_id); let mut game: Game = world.read_model(game_id); game.dice_face = 6; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('r1', 0); // move from its initial position to 1. + testing::set_contract_address(caller_blue); + game_action_system.move('b1', game_id); // move from its initial position to 1. let mut game: Game = world.read_model(game_id); game.dice_face = 5; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('r1', 0); // move from 1 to 6. + testing::set_contract_address(caller_blue); + game_action_system.move('b1', game_id); // move from 1 to 6. // Verify the new position let game: Game = world.read_model(game_id); - let game_condition = array![0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let game_condition = array![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0]; - assert(game.r1 == 6, 'Piece should move to 6'); + assert(game.b1 == 45, 'Piece should move to 6'); assert(game.game_condition == game_condition, 'Game Condition should match'); } @@ -534,19 +549,22 @@ mod tests { game.dice_face = 6; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('g1', 1); // move from its initial position to 14. + testing::set_contract_address(caller); + game_action_system.move('g1', game_id); // move from its initial position to 14. let mut game: Game = world.read_model(game_id); game.dice_face = 5; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('g1', 1); // move from 14 to 19. + testing::set_contract_address(caller); + game_action_system.move('g1', game_id); // move from 14 to 19. let mut game: Game = world.read_model(game_id); game.dice_face = 3; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('g1', 1); // move from 19 to 22. + testing::set_contract_address(caller); + game_action_system.move('g1', game_id); // move from 19 to 22. // Verify the new position let game: Game = world.read_model(game_id); @@ -575,25 +593,29 @@ mod tests { game.dice_face = 6; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('g1', 1); // move g1 from its initial position to 14. + testing::set_contract_address(caller); + game_action_system.move('g1', game_id); // move g1 from its initial position to 14. let mut game: Game = world.read_model(game_id); game.dice_face = 6; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('g2', 1); // move g2 from its initial position to 14. + testing::set_contract_address(caller); + game_action_system.move('g2', game_id); // move g2 from its initial position to 14. let mut game: Game = world.read_model(game_id); game.dice_face = 5; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('g1', 1); // move from 14 to 19. + testing::set_contract_address(caller); + game_action_system.move('g1', game_id); // move from 14 to 19. let mut game: Game = world.read_model(game_id); game.dice_face = 5; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('g2', 1); // move from 14 to 19. + testing::set_contract_address(caller); + game_action_system.move('g2', game_id); // move from 14 to 19. // Verify the new position let game: Game = world.read_model(game_id); @@ -612,41 +634,53 @@ mod tests { let caller_red = contract_address_const::<'test_red'>(); let username_red = 'red'; + let caller_blue = contract_address_const::<'test_blue'>(); + let username_blue = 'blue'; + testing::set_contract_address(caller_red); game_action_system.create_new_player(username_red, false); + testing::set_contract_address(caller_blue); + game_action_system.create_new_player(username_blue, false); + + testing::set_contract_address(caller_red); // Setup initial game state let game_id = game_action_system .create_new_game(GameMode::MultiPlayer, PlayerColor::Red, 2); + testing::set_contract_address(caller_blue); + game_action_system.join(PlayerColor::Blue, game_id); + let mut game: Game = world.read_model(game_id); - game.game_condition = array![13, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - game.r0 = 13; - game.g0 = 15; + game.game_condition = array![43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0]; + game.r0 = 43; + game.b0 = 45; game.dice_face = 2; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); game = world.read_model(game_id); - assert(game.r0 == 13, 'Red piece should be at 13'); - assert(game.g0 == 15, 'Green piece should be at 15'); + assert(game.r0 == 43, 'Red piece should be at 43'); + assert(game.b0 == 45, 'Blue piece should be at 45'); // Verify the game condition - let game_condition = array![13, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let game_condition = array![43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0]; assert(game.game_condition == game_condition, 'Game Condition should match'); + testing::set_contract_address(caller_red); + // Move red piece to position 15 - game_action_system.move('r0', 0); + game_action_system.move('r0', game_id); // Verify the new positions game = world.read_model(game_id); - assert(game.r0 == 15, 'Red piece should move to 15'); - assert(game.g0 == 7101, 'Green piece should be captured'); + assert(game.r0 == 45, 'Red piece should move to 45'); + assert(game.b0 == 6601, 'Blue piece should be captured'); // Verify the game condition - let game_condition = array![15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let game_condition = array![45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; assert(game.game_condition == game_condition, 'Game Condition should match'); } @@ -683,7 +717,8 @@ mod tests { let game_condition = array![13, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; assert(game.game_condition == game_condition, 'Game Condition should match'); - game_action_system.move('r0', 0); + testing::set_contract_address(caller_red); + game_action_system.move('r0', game_id); // Verify the new positions game = world.read_model(game_id); @@ -718,7 +753,9 @@ mod tests { game.dice_face = 3; testing::set_contract_address(game_action_system.contract_address); world.write_model(@game); - game_action_system.move('r3', 0); + + testing::set_contract_address(caller_red); + game_action_system.move('r3', game_id); // Verify the new positions and winning state game = world.read_model(game_id); @@ -731,6 +768,107 @@ mod tests { assert(game.game_condition == game_condition, 'Game Condition should match'); } + #[test] + fn test_skip_winner_turn() { + // Setup world & game_action_system. + let (mut world, game_action_system) = setup_world(); + + // addresses for red, yellow, and blue players. + let caller_red = contract_address_const::<'red'>(); + let caller_yellow = contract_address_const::<'yellow'>(); + let caller_blue = contract_address_const::<'blue'>(); + + // Create players. + testing::set_contract_address(caller_red); + game_action_system.create_new_player('red', false); + + testing::set_contract_address(caller_yellow); + game_action_system.create_new_player('yellow', false); + + testing::set_contract_address(caller_blue); + game_action_system.create_new_player('blue', false); + + // Red creates a new 3-player multiplayer game picking Red. + testing::set_contract_address(caller_red); + let game_id = game_action_system + .create_new_game(GameMode::MultiPlayer, PlayerColor::Red, 4); + + // Yellow joins with Yellow color. + testing::set_contract_address(caller_yellow); + game_action_system.join(PlayerColor::Yellow, game_id); + + // Blue joins with Blue color. + testing::set_contract_address(caller_blue); + game_action_system.join(PlayerColor::Blue, game_id); + + let mut game: Game = world.read_model(game_id); + + testing::set_contract_address(game_action_system.contract_address); + game.winner_1 = game.player_red; + game.dice_face = 6; + world.write_model(@game); + + game = world.read_model(game_id); + + // Assume it is blue's turn; blue makes a move. + testing::set_contract_address(caller_blue); + // Using 'b0' token to simulate blue's move. + game_action_system.move('b0', game_id); + + let mut game: Game = world.read_model(game_id); + game.dice_face = 5; + testing::set_contract_address(game_action_system.contract_address); + world.write_model(@game); + testing::set_contract_address(caller_blue); + game_action_system.move('b0', game_id); // move from 1 to 6. + + let mut game: Game = world.read_model(game_id); + + // After blue's move, the game logic should skip red (the winner) + // and assign next turn to yellow. + assert(game.next_player == game.player_yellow, 'Next player should be yellow'); + } + + #[test] + fn test_extra_move_on_dice_six() { + // Setup world and system. + let (mut world, game_action_system) = setup_world(); + // Create three players. + let caller_red = contract_address_const::<'red'>(); + let caller_yellow = contract_address_const::<'yellow'>(); + let caller_blue = contract_address_const::<'blue'>(); + + testing::set_contract_address(caller_red); + game_action_system.create_new_player('red', false); + + testing::set_contract_address(caller_yellow); + game_action_system.create_new_player('yellow', false); + + testing::set_contract_address(caller_blue); + game_action_system.create_new_player('blue', false); + + // Red creates a 3-player game and yellow, blue join. + testing::set_contract_address(caller_red); + let game_id = game_action_system + .create_new_game(GameMode::MultiPlayer, PlayerColor::Red, 3); + + testing::set_contract_address(caller_yellow); + game_action_system.join(PlayerColor::Yellow, game_id); + + testing::set_contract_address(caller_blue); + game_action_system.join(PlayerColor::Blue, game_id); + + let mut game: Game = world.read_model(game_id); + game.dice_face = 6; + testing::set_contract_address(game_action_system.contract_address); + world.write_model(@game); + testing::set_contract_address(caller_red); + game_action_system.move('r0', game_id); + + game = world.read_model(game_id); + assert(game.next_player == game.player_red, 'Red should get an extra turn'); + } + #[test] #[should_panic(expected: ('GAME NOT INITIALISED', 'ENTRYPOINT_FAILED'))] fn test_join_game_not_initialized() {