From def5ee5c214b852d3c62703d165a7fbad6d1d067 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Tue, 1 Oct 2024 22:26:23 +0100 Subject: [PATCH 1/5] Implement join game function --- onchain/src/systems/game_actions.cairo | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/onchain/src/systems/game_actions.cairo b/onchain/src/systems/game_actions.cairo index de04bc9..13baa26 100644 --- a/onchain/src/systems/game_actions.cairo +++ b/onchain/src/systems/game_actions.cairo @@ -17,6 +17,7 @@ trait IGameActions { ) -> Game; fn restart(ref world: IWorldDispatcher, game_id: u64); fn terminate_game(ref world: IWorldDispatcher, game_id: u64); + fn join_game(ref world: IWorldDispatcher, game_id: u64, player_color: felt252) -> Game; } #[dojo::contract] @@ -120,6 +121,56 @@ mod GameActions { game.terminate_game(); set!(world, (game)); } + + fn join_game(ref world: IWorldDispatcher, game_id: u64, player_color: felt252) -> Game { + // Get the current game state + let mut game: Game = get!(world, game_id, (Game)); + + // Check if the game is in a pending state + assert(game.game_status == GameStatus::Waiting, 'Game is not waiting for players'); + + // Get the caller's address + let caller = get_caller_address(); + + // Add the player to the game based on the color + match player_color { + 'green' => { + assert(game.player_green.is_zero(), 'Green player already joined'); + game.player_green = caller.into(); + }, + 'yellow' => { + assert(game.player_yellow.is_zero(), 'Yellow player already joined'); + game.player_yellow = caller.into(); + }, + 'blue' => { + assert(game.player_blue.is_zero(), 'Blue player already joined'); + game.player_blue = caller.into(); + }, + 'red' => { + assert(game.player_red.is_zero(), 'Red player already joined'); + game.player_red = caller.into(); + }, + _ => { + panic!('Invalid player color'); + } + } + + // Check if all players have joined + let players_joined = (!game.player_green.is_zero()).into() + + (!game.player_yellow.is_zero()).into() + + (!game.player_blue.is_zero()).into() + + (!game.player_red.is_zero()).into(); + + if players_joined == game.number_of_players.into() { + game.game_status = GameStatus::Ongoing; + } + + // Update the game state + set!(world, (game)); + + // Return the updated game + game + } } } From d52c58e1724744125b4f41db6ed76bf5e29d3ec6 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Thu, 3 Oct 2024 22:26:04 +0100 Subject: [PATCH 2/5] join game test --- .../starkludo-GameActions-72b46dc4.json | 28 ++++ .../starkludo-GameActions-72b46dc4.toml | 4 +- onchain/src/systems/game_actions.cairo | 54 +++++++- onchain/src/tests/test_game.cairo | 121 ++++++++++++++++++ 4 files changed, 203 insertions(+), 4 deletions(-) diff --git a/onchain/manifests/dev/base/abis/contracts/starkludo-GameActions-72b46dc4.json b/onchain/manifests/dev/base/abis/contracts/starkludo-GameActions-72b46dc4.json index 953e1bb..6520184 100644 --- a/onchain/manifests/dev/base/abis/contracts/starkludo-GameActions-72b46dc4.json +++ b/onchain/manifests/dev/base/abis/contracts/starkludo-GameActions-72b46dc4.json @@ -349,6 +349,10 @@ "name": "game_mode", "type": "starkludo::models::game::GameMode" }, + { + "name": "game_status", + "type": "starkludo::models::game::GameStatus" + }, { "name": "player_green", "type": "core::felt252" @@ -400,6 +404,30 @@ ], "outputs": [], "state_mutability": "external" + }, + { + "type": "function", + "name": "join_game", + "inputs": [ + { + "name": "game_id", + "type": "core::integer::u64" + }, + { + "name": "player_color", + "type": "core::felt252" + }, + { + "name": "username", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "starkludo::models::game::Game" + } + ], + "state_mutability": "external" } ] }, diff --git a/onchain/manifests/dev/base/contracts/starkludo-GameActions-72b46dc4.toml b/onchain/manifests/dev/base/contracts/starkludo-GameActions-72b46dc4.toml index 436863b..a323d19 100644 --- a/onchain/manifests/dev/base/contracts/starkludo-GameActions-72b46dc4.toml +++ b/onchain/manifests/dev/base/contracts/starkludo-GameActions-72b46dc4.toml @@ -1,6 +1,6 @@ kind = "DojoContract" -class_hash = "0x4cecefc87acb36b188cec659cfde8d7cbfafaf8684297f7d66cadd573cbfec5" -original_class_hash = "0x4cecefc87acb36b188cec659cfde8d7cbfafaf8684297f7d66cadd573cbfec5" +class_hash = "0x4822d5336e093bd5060f977312516677c87cda7cc86ebac5941cf148054a2bf" +original_class_hash = "0x4822d5336e093bd5060f977312516677c87cda7cc86ebac5941cf148054a2bf" base_class_hash = "0x0" abi = "manifests/dev/base/abis/contracts/starkludo-GameActions-72b46dc4.json" reads = [] diff --git a/onchain/src/systems/game_actions.cairo b/onchain/src/systems/game_actions.cairo index de04bc9..c9aea87 100644 --- a/onchain/src/systems/game_actions.cairo +++ b/onchain/src/systems/game_actions.cairo @@ -1,7 +1,9 @@ -use starkludo::models::{game::{Game, GameTrait, GameMode}, player::{Player}}; +use starkludo::models::{game::{Game, GameTrait, GameStatus, GameMode}, player::{Player}}; use starknet::{ContractAddress, get_block_timestamp}; use core::poseidon::PoseidonTrait; use core::hash::HashStateTrait; +use traits::Into; +use option::OptionTrait; #[dojo::interface] trait IGameActions { @@ -9,6 +11,7 @@ trait IGameActions { ref world: IWorldDispatcher, created_by: ContractAddress, game_mode: GameMode, + game_status: GameStatus, player_green: felt252, player_yellow: felt252, player_blue: felt252, @@ -17,6 +20,8 @@ trait IGameActions { ) -> Game; fn restart(ref world: IWorldDispatcher, game_id: u64); fn terminate_game(ref world: IWorldDispatcher, game_id: u64); + fn join_game(ref world: IWorldDispatcher, game_id: u64, player_color: felt252, username: felt252) -> Game; + // fn join_game(ref world: IWorldDispatcher, game_id: u64, player_color: felt252) -> Game; } #[dojo::contract] @@ -24,7 +29,7 @@ mod GameActions { // use Zero; // use core::num::traits::Zero; use core::array::ArrayTrait; - use super::{IGameActions, Game, GameTrait, GameMode, Player}; + use super::{IGameActions, Game, GameTrait, GameStatus, GameMode, Player}; use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; #[abi(embed_v0)] @@ -33,6 +38,7 @@ mod GameActions { ref world: IWorldDispatcher, created_by: ContractAddress, game_mode: GameMode, + game_status: GameStatus, player_green: felt252, player_yellow: felt252, player_blue: felt252, @@ -120,6 +126,49 @@ mod GameActions { game.terminate_game(); set!(world, (game)); } + + + fn join_game(ref world: IWorldDispatcher, game_id: u64, player_color: felt252, username: felt252) -> Game { + // Get the current game state + let mut game: Game = get!(world, game_id, (Game)); + + // Check if the game is in a pending state + assert(game.game_status == GameStatus::Waiting, 'Game is not waiting for players'); + + // Add the player to the game based on the color + if player_color == 'green' { + assert(game.player_green.is_zero(), 'Green player already joined'); + game.player_green = username.into(); + } else if player_color == 'yellow' { + assert(game.player_yellow.is_zero(), 'Yellow player already joined'); + game.player_yellow = username.into(); + } else if player_color == 'blue' { + assert(game.player_blue.is_zero(), 'Blue player already joined'); + game.player_blue = username.into(); + } else if player_color == 'red' { + assert(game.player_red.is_zero(), 'Red player already joined'); + game.player_red = username.into(); + } else { + panic(array!['Invalid player color']); + } + + // Check if all players have joined + let green_joined: u8 = if !game.player_green.is_zero() { 1 } else { 0 }; + let yellow_joined: u8 = if !game.player_yellow.is_zero() { 1 } else { 0 }; + let blue_joined: u8 = if !game.player_blue.is_zero() { 1 } else { 0 }; + let red_joined: u8 = if !game.player_red.is_zero() { 1 } else { 0 }; + + let players_joined: u8 = green_joined + yellow_joined + blue_joined + red_joined; + + if players_joined == game.number_of_players { + game.game_status = GameStatus::Ongoing; + } + + // Update the game state and return the updated game + set!(world, (game)); + get!(world, game_id, (Game)) + } + } } @@ -154,3 +203,4 @@ pub impl DiceImpl of DiceTrait { (random % self.face_count.into() + 1).try_into().unwrap() } } + diff --git a/onchain/src/tests/test_game.cairo b/onchain/src/tests/test_game.cairo index a3fcb6d..320180b 100644 --- a/onchain/src/tests/test_game.cairo +++ b/onchain/src/tests/test_game.cairo @@ -170,5 +170,126 @@ mod tests { assert_eq!(game.game_status, GameStatus::Ended); } + + + #[test] + fn test_join_game() { + // Set up the test environment + let caller = contract_address_const::<'ben'>(); + let number_of_players = 4; + let game_mode: GameMode = GameMode::MultiPlayer; + + testing::set_account_contract_address(caller); + testing::set_contract_address(caller); + + // Create a new game + let (mut game, game_actions, world, _) = create_and_setup_game( + game_mode, + number_of_players, + 0, // Start with no players + 0, + 0, + 0 + ); + + let game_id = game.id; + + // Test joining as red player + game = game_actions.join_game(game_id, 'red', 'player_red'); + assert_eq!(game.player_red, 'player_red'.into(), "Red player should be joined"); + assert_eq!(game.game_status, GameStatus::Waiting, "Game should still be waiting"); + + // Test joining as blue player + game = game_actions.join_game(game_id, 'blue', 'player_blue'); + assert_eq!(game.player_blue, 'player_blue'.into(), "Blue player should be joined"); + assert_eq!(game.game_status, GameStatus::Waiting, "Game should still be waiting"); + + // Test joining as yellow player + game = game_actions.join_game(game_id, 'yellow', 'player_yellow'); + assert_eq!(game.player_yellow, 'player_yellow'.into(), "Yellow player should be joined"); + assert_eq!(game.game_status, GameStatus::Waiting, "Game should still be waiting"); + + // Test joining as green player (last player) + game = game_actions.join_game(game_id, 'green', 'player_green'); + assert_eq!(game.player_green, 'player_green'.into(), "Green player should be joined"); + assert_eq!(game.game_status, GameStatus::Ongoing, "Game should now be ongoing"); + + // Create a new game with fewer players + let (mut game2, game_actions2, _, _) = create_and_setup_game( + game_mode, + 2, // Only 2 players this time + 0, + 0, + 0, + 0 + ); + + let game_id2 = game2.id; + + // Join with two players + game2 = game_actions2.join_game(game_id2, 'red', 'player_red_2'); + game2 = game_actions2.join_game(game_id2, 'blue', 'player_blue_2'); + + // Verify game status changes to Ongoing with fewer players + assert_eq!(game2.game_status, GameStatus::Ongoing, "Game should be ongoing with 2/2 players"); + } + + #[test] + #[should_panic(expected: ('Game is not waiting for players', ))] + fn test_join_non_waiting_game() { + // Set up the test environment + let caller = contract_address_const::<'test_caller'>(); + let number_of_players = 2; + let game_mode: GameMode = GameMode::MultiPlayer; + + testing::set_account_contract_address(caller); + testing::set_contract_address(caller); + + // Create a new game + let (mut game, game_actions, world, _) = create_and_setup_game( + game_mode, + number_of_players, + 0, + 0, + 0, + 0 + ); + + let game_id = game.id; + + // Fill the game + game = game_actions.join_game(game_id, 'red', 'player_red'); + game = game_actions.join_game(game_id, 'blue', 'player_blue'); + + // This should panic + game_actions.join_game(game_id, 'yellow', 'late_player'); + } + + #[test] + #[should_panic(expected: ('Invalid player color', ))] + fn test_join_game_invalid_color() { + // Set up the test environment + let caller = contract_address_const::<'test_caller'>(); + let number_of_players = 4; + let game_mode: GameMode = GameMode::MultiPlayer; + + testing::set_account_contract_address(caller); + testing::set_contract_address(caller); + + // Create a new game + let (game, game_actions, world, _) = create_and_setup_game( + game_mode, + number_of_players, + 0, + 0, + 0, + 0 + ); + + let game_id = game.id; + + // This should panic + game_actions.join_game(game_id, 'purple', 'invalid_player'); + } } From b00d54b4babe15b689a0064cb1e22c3ca1d84abc Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Wed, 16 Oct 2024 09:33:50 +0100 Subject: [PATCH 3/5] update join_game function --- onchain/src/systems/game_actions.cairo | 56 +++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/onchain/src/systems/game_actions.cairo b/onchain/src/systems/game_actions.cairo index 38f8ad9..ea2c5b2 100644 --- a/onchain/src/systems/game_actions.cairo +++ b/onchain/src/systems/game_actions.cairo @@ -1,4 +1,4 @@ -use starkludo::models::{game::{Game, GameTrait, GameMode }, player::{Player}}; +use starkludo::models::{game::{Game, GameTrait, GameMode, GameStatus}, player::{Player}}; use starknet::{ContractAddress, get_block_timestamp}; use core::poseidon::PoseidonTrait; use core::hash::HashStateTrait; @@ -19,16 +19,18 @@ trait IGameActions { fn restart(ref world: IWorldDispatcher, game_id: u64); fn terminate_game(ref world: IWorldDispatcher, game_id: u64); fn invite_player(ref world: IWorldDispatcher, game_id: u64, player_username: felt252); + fn join_game( + ref world: IWorldDispatcher, game_id: u64, player_username: felt252, player_color: felt252 + ); } - #[dojo::contract] mod GameActions { // use Zero; // use core::num::traits::Zero; use core::array::ArrayTrait; - use super::{IGameActions, Game, GameTrait, GameMode, Player}; + use super::{IGameActions, Game, GameTrait, GameMode, Player, GameStatus}; use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; #[abi(embed_v0)] @@ -58,7 +60,7 @@ mod GameActions { player_yellow, player_green, number_of_players - ); + ); // Update the world state with the newly created game set!(world, (new_game)); @@ -159,6 +161,50 @@ mod GameActions { // Update the game state in the world set!(world, (game)); } + + + fn join_game( + ref world: IWorldDispatcher, + game_id: u64, + player_username: felt252, + player_color: felt252 + ) { + let mut game: Game = get!(world, game_id, (Game)); + + // Check if the game is pending + assert(game.game_status == GameStatus::Waiting, 'Game is not pending'); + + let mut player: Player = get!(world, player_username, (Player)); + assert(player.owner != 0.try_into().unwrap(), 'Player does not exist'); + + // Check if the player is already part of the game + let players = array![ + game.player_green, game.player_yellow, game.player_blue, game.player_red + ]; + let players_span = players.span(); + let mut i = 0; + while i < 4 { + assert(players_span[i] != @player_username, 'Player already in game'); + i += i; + }; + match player_color { + 0 => game.player_green = player_username, + 1 => game.player_yellow = player_username, + 2 => game.player_blue = player_username, + 3 => game.player_red = player_username, + _ => panic!("Invalid player color") + } + + // Update game status if all players have joined + if game.player_green != 0 + && game.player_yellow != 0 + && game.player_blue != 0 + && game.player_red != 0 { + game.game_status = GameStatus::Ongoing; + } + + set!(world, (game)); + } } } @@ -192,4 +238,4 @@ pub impl DiceImpl of DiceTrait { let random: u256 = state.finalize().into(); (random % self.face_count.into() + 1).try_into().unwrap() } -} \ No newline at end of file +} From 511ea7a5a84b27c85dae6035de35ddb30b4d8eb7 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Wed, 16 Oct 2024 13:25:14 +0100 Subject: [PATCH 4/5] update join_game_function test --- onchain/src/systems/game_actions.cairo | 9 +----- onchain/src/tests/test_game.cairo | 41 +++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/onchain/src/systems/game_actions.cairo b/onchain/src/systems/game_actions.cairo index ea2c5b2..ca6c2eb 100644 --- a/onchain/src/systems/game_actions.cairo +++ b/onchain/src/systems/game_actions.cairo @@ -163,12 +163,7 @@ mod GameActions { } - fn join_game( - ref world: IWorldDispatcher, - game_id: u64, - player_username: felt252, - player_color: felt252 - ) { + fn join_game( ref world: IWorldDispatcher, game_id: u64, player_username: felt252, player_color: felt252 ) { let mut game: Game = get!(world, game_id, (Game)); // Check if the game is pending @@ -176,8 +171,6 @@ mod GameActions { let mut player: Player = get!(world, player_username, (Player)); assert(player.owner != 0.try_into().unwrap(), 'Player does not exist'); - - // Check if the player is already part of the game let players = array![ game.player_green, game.player_yellow, game.player_blue, game.player_red ]; diff --git a/onchain/src/tests/test_game.cairo b/onchain/src/tests/test_game.cairo index 7c0130a..26e52b6 100644 --- a/onchain/src/tests/test_game.cairo +++ b/onchain/src/tests/test_game.cairo @@ -2,7 +2,6 @@ pub mod Errors { pub const WRONG_DICE_VALUE: felt252 = 'Wrong dice value'; pub const WRONG_DICE_NONCE: felt252 = 'Wrong dice nonce'; pub const INVALID_PLAYER: felt252 = 'Player was not invited'; - // pub const CANNOT_JOIN_GAME: felt252 = 'Cannot join the game'; } #[cfg(test)] @@ -215,5 +214,45 @@ mod tests { // // Assert that the player was invited. if false then player was not invited // assert(is_player_invited, Errors::INVALID_PLAYER); } + + + #[test] + #[ignore] + fn test_join_game() { + let caller = contract_address_const::<'Benjamin'>(); + let player_red = 'player_red'; + let player_blue = 'player_blue'; + let player_yellow = 'player_yellow'; + let player_green = 'player_green'; + let number_of_players = 4; + let game_mode: GameMode = GameMode::MultiPlayer; + testing::set_account_contract_address(caller); + testing::set_contract_address(caller); + + let (game, game_actions, world, _) = create_and_setup_game( + game_mode, number_of_players, player_red, player_blue, player_yellow, player_green + ); + + let game_id: u64 = game.id; + let new_player = 'player_red'; + let player_color: felt252 = 0; + + // Check game status before joining + let game_before_join = get!(world, game_id, Game); + assert_eq!( + game_before_join.game_status, + GameStatus::Waiting, + "Game should be in Waiting status before joining" + ); + + // Attempt to join the game + game_actions.join_game(game_id, new_player, player_color); + + // Verify the game state after joining + let updated_game = get!(world, game_id, Game); + assert_eq!( + updated_game.player_green, new_player.into(), "Player should have joined the game" + ); + } } From ad0adb53a453ee3d7c0f5fdd8a5a6288ba044156 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Wed, 16 Oct 2024 13:29:01 +0100 Subject: [PATCH 5/5] remove .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes onchain/.DS_Store | Bin 6148 -> 0 bytes onchain/src/systems/game_actions.cairo | 1 - 3 files changed, 1 deletion(-) delete mode 100644 .DS_Store delete mode 100644 onchain/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 1f233116f2995f5bddfeca85c89dd4064fa4cdd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%We}f6unMEGNC-OC{i~_Be4x3O%bB%Ce1@A5)Y{Dz%6DWR+jGwy&#}iI6OpJ-2YW+Jh&&36aBlE%1vL}nH^R93AaeL#JL+Q-zIfjHvsTAbU|pcnL^wb%cY|7Bbf~rF{d2=xqL+O-g(m24+B-A)ySF!sCT8vX?o9$e ziYt{LBD0mfmdiVNXWMz#IhC_c+>57Cy?6eGYbR0$-m={DUirgWx46@gVche>;khFC zgCShrzV^d`oYmzt97KxS(KnoeQ|K0V=kx00gOa;n-CLC0`9ZZ(av$zLS}Y3AoqG=s zkGp5VIFz691~7s3TG>s5OJMbG&;)Par!(XujI?J2PQNXsHwsnF@&b0-8FtSgNa5>Ct(gB z!aP}+6N*qzM|@Yglc;I5xmCa_kXB$zSBt#=xBh(oPm^rTDqt13QVNJ{v(v2OmCW9{ y@p8P^x=8Pl*w`=8s3^$HcB~4#74IS`!#$S^z?#8CBU)hgM?lG7GpoQqRp2)hTf^)C diff --git a/onchain/.DS_Store b/onchain/.DS_Store deleted file mode 100644 index 9a5e2190db6c73b220b1f50b1a9f806fcf3b4a39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO>fgc5S>jz;!2wXPV^d6BZxuV_5Jk!v z{sVu3E5C&Q(i6Pd-AH!Q9JnBacB0w0-krA}&+<-|h(vE191t~#$bmCfc2L}6+|RyY zCDXDC6l#o&($iRu{aE@N(YC=VU={e+6yR^SN*<;7g;e~0`A|59w|*&8OW&h_Bz=O9 zPz&iZwZM{eMRD%SXXf`c=G`1~0%i>7dBbfZhzDyU)aW^#(g_Xd1r2FXoKJjRi>_l$ z9wijf2h5Rf?oHfmFwQadG4<&T*aAN&&QZEpW9(oZW6UxkAMr7C7}I5uqZIs>Q?SU{ zu3er}#`-5TE{?49xh2YN|V}ShEUP1*`&j1^9e$;f!5_OO5L2K%uSxzzUj`q0Y&lIj+I3 z!KFs@z=Wm(HC31^hR}4hyC$w{aH&z#NtnxrFf$8tLlJ6rjPI&&5?zh9v0jt1&rGTjPhy5P5WOD1q<~Y~7@b_>wj$3L}6ci>Ms|u&$eYi68 Zxm*Eu4K6jJ1!n&UC>d;F75J+P{06Pf%hdn? diff --git a/onchain/src/systems/game_actions.cairo b/onchain/src/systems/game_actions.cairo index ca6c2eb..91d15a2 100644 --- a/onchain/src/systems/game_actions.cairo +++ b/onchain/src/systems/game_actions.cairo @@ -166,7 +166,6 @@ mod GameActions { fn join_game( ref world: IWorldDispatcher, game_id: u64, player_username: felt252, player_color: felt252 ) { let mut game: Game = get!(world, game_id, (Game)); - // Check if the game is pending assert(game.game_status == GameStatus::Waiting, 'Game is not pending'); let mut player: Player = get!(world, player_username, (Player));