diff --git a/onchain/src/models/game.cairo b/onchain/src/models/game.cairo index 6cf5739..12a62b9 100644 --- a/onchain/src/models/game.cairo +++ b/onchain/src/models/game.cairo @@ -16,13 +16,13 @@ pub enum GameStatus { #[derive(Serde, Copy, Drop, Introspect, PartialEq)] pub enum GameMode { SinglePlayer, // Play with computer - MultiPlayer, // Play online with friends + MultiPlayer // Play online with friends } #[derive(Serde, Copy, Drop, Introspect, PartialEq)] pub enum PiecePosition { SinglePlayer, // Play with computer - MultiPlayer, // Play online with friends + MultiPlayer // Play online with friends } // Game model @@ -64,7 +64,7 @@ pub struct Game { pub y0: felt252, // yellow piece position on board pub y1: felt252, // yellow piece position on board pub y2: felt252, // yellow piece position on board - pub y3: felt252, // yellow piece position on board + pub y3: felt252 // yellow piece position on board } pub trait GameTrait { @@ -77,7 +77,7 @@ pub trait GameTrait { player_blue: felt252, player_yellow: felt252, player_green: felt252, - number_of_players: u8 + number_of_players: u8, ) -> Game; fn restart(ref self: Game); fn terminate_game(ref self: Game); @@ -92,7 +92,7 @@ impl GameImpl of GameTrait { player_blue: felt252, player_yellow: felt252, player_green: felt252, - number_of_players: u8 + number_of_players: u8, ) -> Game { let zero_address = contract_address_const::<0x0>(); Game { @@ -120,7 +120,7 @@ impl GameImpl of GameTrait { 2 => 'B01', 3 => 'B01', 4 => 'B01', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, b1: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -128,7 +128,7 @@ impl GameImpl of GameTrait { 2 => 'B02', 3 => 'B02', 4 => 'B02', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, b2: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -136,7 +136,7 @@ impl GameImpl of GameTrait { 2 => 'B03', 3 => 'B03', 4 => 'B03', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, b3: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -144,7 +144,7 @@ impl GameImpl of GameTrait { 2 => 'B04', 3 => 'B04', 4 => 'B04', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, g0: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -152,7 +152,7 @@ impl GameImpl of GameTrait { 2 => 'G01', 3 => 'G01', 4 => 'G01', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, g1: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -160,7 +160,7 @@ impl GameImpl of GameTrait { 2 => 'G02', 3 => 'G02', 4 => 'G02', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, g2: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -168,7 +168,7 @@ impl GameImpl of GameTrait { 2 => 'G03', 3 => 'G03', 4 => 'G03', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, g3: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -176,7 +176,7 @@ impl GameImpl of GameTrait { 2 => 'GO4', 3 => 'GO4', 4 => 'GO4', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, r0: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -184,7 +184,7 @@ impl GameImpl of GameTrait { 2 => 0, 3 => 'R01', 4 => 'R01', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, r1: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -192,7 +192,7 @@ impl GameImpl of GameTrait { 2 => 0, 3 => 'R02', 4 => 'R02', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, r2: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -200,7 +200,7 @@ impl GameImpl of GameTrait { 2 => 0, 3 => 'R03', 4 => 'R03', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, r3: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -208,7 +208,7 @@ impl GameImpl of GameTrait { 2 => 0, 3 => 'R04', 4 => 'R04', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, y0: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -216,7 +216,7 @@ impl GameImpl of GameTrait { 2 => 0, 3 => 0, 4 => 'Y01', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, y1: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -224,7 +224,7 @@ impl GameImpl of GameTrait { 2 => 0, 3 => 0, 4 => 'Y02', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, y2: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -232,7 +232,7 @@ impl GameImpl of GameTrait { 2 => 0, 3 => 0, 4 => 'Y03', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, y3: match number_of_players { 0 => panic!("number of players cannot be 0"), @@ -240,7 +240,7 @@ impl GameImpl of GameTrait { 2 => 0, 3 => 0, 4 => 'Y04', - _ => panic!("invalid number of players") + _ => panic!("invalid number of players"), }, } } diff --git a/onchain/src/models/player.cairo b/onchain/src/models/player.cairo index 35e3e46..65a165d 100644 --- a/onchain/src/models/player.cairo +++ b/onchain/src/models/player.cairo @@ -9,7 +9,7 @@ pub struct Player { pub owner: ContractAddress, // Account owner of player. An account can own multiple players pub is_bot: bool, pub total_games_played: u256, // Count of total games played by this player - pub total_games_won: u256, // Count of total games won by this player + pub total_games_won: u256 // Count of total games won by this player } #[derive(Drop, Copy, Serde)] @@ -17,7 +17,7 @@ pub struct Player { pub struct UsernameToAddress { #[key] pub username: felt252, - pub address: ContractAddress + pub address: ContractAddress, } #[derive(Drop, Copy, Serde)] diff --git a/onchain/src/systems/game_actions.cairo b/onchain/src/systems/game_actions.cairo index 2dcb751..e677a43 100644 --- a/onchain/src/systems/game_actions.cairo +++ b/onchain/src/systems/game_actions.cairo @@ -10,7 +10,7 @@ trait IGameActions { player_yellow: felt252, player_blue: felt252, player_red: felt252, - number_of_players: u8 + number_of_players: u8, ) -> usize; fn start(ref self: T); @@ -42,15 +42,15 @@ pub mod GameActions { pub struct GameCreated { #[key] pub game_id: usize, - pub timestamp: u64 + pub timestamp: u64, } #[derive(Copy, Drop, Serde)] #[dojo::event] - pub struct GameStarted{ + pub struct GameStarted { #[key] pub game_id: usize, - pub time_stamp: u64 + pub time_stamp: u64, } #[abi(embed_v0)] @@ -84,7 +84,7 @@ pub mod GameActions { player_blue, player_yellow, player_green, - number_of_players + number_of_players, ); world.write_model(@new_game); @@ -95,41 +95,41 @@ pub mod GameActions { } fn start(ref self: ContractState) { - // Get world state - let mut world = self.world_default(); - - // Get caller address - let caller: ContractAddress = get_caller_address(); - - // Assign a game id - let mut game_id: usize = 999; - - //get the game state - let mut game: Game = world.read_model(game_id); - - // get the caller's user name - let caller_username: felt252 = self.get_username_from_address(caller); - - //assert that caller with the user_name is game creator - assert(game.created_by == caller_username, Errors::ONLY_GAME_CREATOR); - - //ensure that game status is pending - assert(game.game_status == GameStatus::Pending, Errors::GAME_NOT_PENDING); - - //change game status to Ongoing - game.game_status = GameStatus::Ongoing; - - //make player_green the first player by making player green the nest player - game.player_green = game.next_player; - - //update the game model - world.write_model(@game); - - //get the current block timestamp - let time_stamp: u64 = get_block_timestamp(); - - //emit event - world.emit_event(@GameStarted { game_id, time_stamp }); + // Get world state + let mut world = self.world_default(); + + // Get caller address + let caller: ContractAddress = get_caller_address(); + + // Assign a game id + let mut game_id: usize = 999; + + //get the game state + let mut game: Game = world.read_model(game_id); + + // get the caller's user name + let caller_username: felt252 = self.get_username_from_address(caller); + + //assert that caller with the user_name is game creator + assert(game.created_by == caller_username, Errors::ONLY_GAME_CREATOR); + + //ensure that game status is pending + assert(game.game_status == GameStatus::Pending, Errors::GAME_NOT_PENDING); + + //change game status to Ongoing + game.game_status = GameStatus::Ongoing; + + //make player_green the first player by making player green the nest player + game.player_green = game.next_player; + + //update the game model + world.write_model(@game); + + //get the current block timestamp + let time_stamp: u64 = get_block_timestamp(); + + //emit event + world.emit_event(@GameStarted { game_id, time_stamp }); } fn join(ref self: ContractState) {} @@ -159,7 +159,7 @@ pub mod GameActions { } fn get_address_from_username( - ref self: ContractState, username: felt252 + ref self: ContractState, username: felt252, ) -> ContractAddress { let mut world = self.world_default(); diff --git a/onchain/src/tests/test_game.cairo b/onchain/src/tests/test_game.cairo index 25efb49..c1d79ac 100644 --- a/onchain/src/tests/test_game.cairo +++ b/onchain/src/tests/test_game.cairo @@ -4,14 +4,17 @@ mod tests { use dojo::model::{ModelStorage, ModelValueStorage, ModelStorageTest}; use dojo::world::WorldStorageTrait; use dojo_cairo_test::{ - spawn_test_world, NamespaceDef, TestResource, ContractDefTrait, ContractDef + spawn_test_world, NamespaceDef, TestResource, ContractDefTrait, ContractDef, }; use starkludo::systems::game_actions::{ - GameActions, IGameActionsDispatcher, IGameActionsDispatcherTrait + GameActions, IGameActionsDispatcher, IGameActionsDispatcherTrait, }; use starkludo::models::game::{Game, m_Game}; - use starkludo::models::player::{Player, m_Player, AddressToUsername, UsernameToAddress, m_AddressToUsername, m_UsernameToAddress}; + use starkludo::models::player::{ + Player, m_Player, AddressToUsername, UsernameToAddress, m_AddressToUsername, + m_UsernameToAddress, + }; use starkludo::models::game::{GameMode, GameStatus}; use starkludo::errors::Errors; @@ -19,29 +22,26 @@ mod tests { /// Defines the namespace configuration for the Starkludo game system /// Returns a NamespaceDef struct containing namespace name and associated resources fn namespace_def() -> NamespaceDef { - // Creates a new NamespaceDef struct with: // Namespace name "starkludo" // Array of TestResource enums for models, contracts and events let ndef = NamespaceDef { - namespace: "starkludo", resources: [ - + namespace: "starkludo", + resources: [ // Register the Game model's class hash TestResource::Model(m_Game::TEST_CLASS_HASH), - // Register the Player model's class hash TestResource::Model(m_Player::TEST_CLASS_HASH), TestResource::Model(m_AddressToUsername::TEST_CLASS_HASH), TestResource::Model(m_UsernameToAddress::TEST_CLASS_HASH), - // Register the main contract containing game actions TestResource::Contract(GameActions::TEST_CLASS_HASH), - // Register the GameCreated event's class hash TestResource::Event(GameActions::e_GameCreated::TEST_CLASS_HASH), TestResource::Event(GameActions::e_GameStarted::TEST_CLASS_HASH), - ].span() // Convert array to a Span type + ] + .span() // Convert array to a Span type }; // Return the namespace definition @@ -56,11 +56,11 @@ mod tests { // Create a new contract definition for the StarKLudo game's actions // using the ContractDefTrait builder pattern ContractDefTrait::new(@"starkludo", @"GameActions") - - // Configure write permissions by specifying which addresses can modify the contract - // Here, only the address derived from hashing "starkludo" has write access - .with_writer_of([dojo::utils::bytearray_hash(@"starkludo")].span()) - ].span() // Convert the array to a Span container for return + // Configure write permissions by specifying which addresses can modify the contract + // Here, only the address derived from hashing "starkludo" has write access + .with_writer_of([dojo::utils::bytearray_hash(@"starkludo")].span()) + ] + .span() // Convert the array to a Span container for return } #[test] @@ -105,28 +105,25 @@ mod tests { #[test] fn test_get_username_from_address() { - let ndef = namespace_def(); let mut world = spawn_test_world([ndef].span()); world.sync_perms_and_inits(contract_defs()); - + let (contract_address, _) = world.dns(@"GameActions").unwrap(); let game_action_system = IGameActionsDispatcher { contract_address }; - + let test_address1 = starknet::contract_address_const::<'test_user1'>(); let test_address2 = starknet::contract_address_const::<'test_user2'>(); let username1: felt252 = 'alice'; let username2: felt252 = 'bob'; - let address_to_username1 = AddressToUsername { - address: test_address1, - username: username1 + let address_to_username1 = AddressToUsername { + address: test_address1, username: username1, }; - let address_to_username2 = AddressToUsername { - address: test_address2, - username: username2 + let address_to_username2 = AddressToUsername { + address: test_address2, username: username2, }; - + world.write_model(@address_to_username1); world.write_model(@address_to_username2); @@ -137,11 +134,48 @@ mod tests { assert(retrieved_username2 == username2, 'Wrong username for address2'); let non_existent_address = starknet::contract_address_const::<'non_existent'>(); - let retrieved_username3 = game_action_system.get_username_from_address(non_existent_address); + let retrieved_username3 = game_action_system + .get_username_from_address(non_existent_address); + assert(retrieved_username3 == 0, 'Non-existent should return 0'); + } + + #[test] + fn test_get_address_from_username() { + let ndef = namespace_def(); + let mut world = spawn_test_world([ndef].span()); + world.sync_perms_and_inits(contract_defs()); + + let (contract_address, _) = world.dns(@"GameActions").unwrap(); + let game_action_system = IGameActionsDispatcher { contract_address }; + + let bob_address = starknet::contract_address_const::<'bob'>(); + let alice_address = starknet::contract_address_const::<'alice'>(); + let bob_username: felt252 = 'bob'; + let alice_username: felt252 = 'alice'; + + let address_to_username1 = UsernameToAddress { + username: bob_username, address: bob_address, + }; + let address_to_username2 = UsernameToAddress { + username: alice_username, address: alice_address, + }; + + world.write_model(@address_to_username1); + world.write_model(@address_to_username2); + + let address_1 = game_action_system.get_address_from_username(bob_username); + let address_2 = game_action_system.get_address_from_username(alice_username); + + assert(address_1 == bob_address, 'Wrong address 1'); + assert(address_2 == alice_address, 'Wrong address 2'); + + let non_existent_address = starknet::contract_address_const::<'non_existent'>(); + let retrieved_username3 = game_action_system + .get_username_from_address(non_existent_address); assert(retrieved_username3 == 0, 'Non-existent should return 0'); } -fn test_start_game_success() { + fn test_start_game_success() { // Setup world and contract let caller = starknet::contract_address_const::<'Mr_T'>(); let ndef = namespace_def(); @@ -159,14 +193,15 @@ fn test_start_game_success() { world.write_model(@username_address); // Create new game - let game_id = game_action_system.create( - GameMode::MultiPlayer, - username, // green player (creator) - 'player2', - 'player3', - 'player4', - 4 - ); + let game_id = game_action_system + .create( + GameMode::MultiPlayer, + username, // green player (creator) + 'player2', + 'player3', + 'player4', + 4, + ); // Start game game_action_system.start(); @@ -176,5 +211,4 @@ fn test_start_game_success() { assert(game.game_status == GameStatus::Ongoing, 'Game should be ongoing'); assert(game.next_player == game.player_green, 'Green should be next player'); } - }