diff --git a/Scarb.toml b/Scarb.toml index 3545c79..248778d 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -22,5 +22,5 @@ account_address = "0x6162896d1d7ab204c7ccac6dd5f8e9e7c25ecd5ae4fcb4ad32e57786bb4 private_key = "0x1800000000300000180000000000030000000000003006001800006600" # Slot # rpc_url = "https://api.cartridge.gg/x/zconqueror/katana" -# account_address = "0x7790adbbfb0fd9c4ec4c0e31c71615d6c3aaac9369dbb0adc5b11ed85631478" -# private_key = "0x35dd7dfd8a2602b23cda569f0b0793f7268ca99513c76bc88ba11a35eb2a66e" +# account_address = "0x603e42dd660816a9e5c2fc935633018db30df30764d452e6b4064700bc8e6e0" +# private_key = "0x4fd0675d3ec5e0d035c213de935696cdd1284d79525cd3275d24626c32825e7" diff --git a/src/data/v00.cairo b/src/data/v00.cairo index ac35818..ff2284d 100644 --- a/src/data/v00.cairo +++ b/src/data/v00.cairo @@ -35,9 +35,6 @@ fn card_number() -> u32 { /// * The corresponding tile id and unit type. #[inline(always)] fn card(id: u8) -> Option<(u8, u16)> { - // Tile number + 5% if > 20, otherwise add 1 - let count = card_number(); - // ID cannot be 0 if id == 0 { return Option::None; diff --git a/src/data/v01.cairo b/src/data/v01.cairo index f3e6ecd..0ab440a 100644 --- a/src/data/v01.cairo +++ b/src/data/v01.cairo @@ -40,9 +40,6 @@ fn card_number() -> u32 { /// * The corresponding tile id and unit type. #[inline(always)] fn card(id: u8) -> Option<(u8, u16)> { - // Tile number + 5% if > 20, otherwise add 1 - let count = card_number(); - // ID cannot be 0 if id == 0 { return Option::None; diff --git a/src/models/game.cairo b/src/models/game.cairo index a66923e..f6cf4a2 100644 --- a/src/models/game.cairo +++ b/src/models/game.cairo @@ -1,27 +1,27 @@ +use core::zeroable::Zeroable; // Core imports use hash::HashStateTrait; use poseidon::PoseidonTrait; -// Starknet imports +// Internal imports -use starknet::ContractAddress; +use zconqueror::models::player::{Player, PlayerTrait}; // Constants const MINIMUM_PLAYER_COUNT: u8 = 2; -const DEFAULT_PLAYER_COUNT: u8 = 6; +const MAXIMUM_PLAYER_COUNT: u8 = 6; const TURN_COUNT: u8 = 3; #[derive(Model, Copy, Drop, Serde)] struct Game { #[key] id: u32, - host: ContractAddress, + host: felt252, over: bool, seed: felt252, player_count: u8, - slots: u8, nonce: u8, price: u256, } @@ -34,56 +34,65 @@ enum Turn { } mod errors { - const GAME_NO_REMAINING_SLOTS: felt252 = 'Game: no remaining slots'; - const GAME_NOT_ENOUGH_PLAYERS: felt252 = 'Game: not enough players'; + const GAME_NOT_HOST: felt252 = 'Game: user is not the host'; + const GAME_IS_HOST: felt252 = 'Game: user is the host'; + const GAME_TRANSFER_SAME_HOST: felt252 = 'Game: transfer to the same host'; + const GAME_TOO_MANY_PLAYERS: felt252 = 'Game: too many players'; + const GAME_TOO_FEW_PLAYERS: felt252 = 'Game: too few players'; const GAME_IS_FULL: felt252 = 'Game: is full'; - const GAME_IS_NOT_FULL: felt252 = 'Game: is not full'; + const GAME_NOT_FULL: felt252 = 'Game: not full'; const GAME_IS_EMPTY: felt252 = 'Game: is empty'; + const GAME_NOT_ONLY_ONE: felt252 = 'Game: not only one'; const GAME_IS_OVER: felt252 = 'Game: is over'; const GAME_NOT_OVER: felt252 = 'Game: not over'; + const GAME_NOT_STARTED: felt252 = 'Game: not started'; const GAME_HAS_STARTED: felt252 = 'Game: has started'; - const GAME_DOES_NOT_EXSIST: felt252 = 'Game: does not exsist'; + const GAME_NOT_EXISTS: felt252 = 'Game: does not exist'; + const GAME_DOES_EXIST: felt252 = 'Game: does exist'; + const GAME_INVALID_HOST: felt252 = 'Game: invalid host'; } #[generate_trait] impl GameImpl of GameTrait { #[inline(always)] - fn new(id: u32, host: ContractAddress, price: u256) -> Game { - let player_count = DEFAULT_PLAYER_COUNT; - Game { id, host, over: false, seed: 0, player_count, slots: player_count, nonce: 0, price } + fn new(id: u32, host: felt252, price: u256) -> Game { + // [Check] Host is valid + assert(host != 0, errors::GAME_INVALID_HOST); + + // [Return] Default game + Game { id, host, over: false, seed: 0, player_count: 0, nonce: 0, price } } #[inline(always)] - fn reward(self: @Game) -> u256 { + fn reward(self: Game) -> u256 { // [Check] Game is over - assert(*self.over, errors::GAME_NOT_OVER); - *self.price * (*self.player_count).into() - } + self.assert_is_over(); - #[inline(always)] - fn real_player_count(self: @Game) -> u8 { - *self.player_count - *self.slots + // [Return] Calculated reward + self.price * (self.player_count).into() } #[inline(always)] - fn player(self: @Game) -> u8 { - *self.nonce / TURN_COUNT % *self.player_count + fn player(self: Game) -> u32 { + let index = self.nonce / TURN_COUNT % self.player_count; + index.into() } #[inline(always)] - fn turn(self: @Game) -> Turn { - let turn_id = *self.nonce % TURN_COUNT; + fn turn(self: Game) -> Turn { + let turn_id = self.nonce % TURN_COUNT; turn_id.into() } #[inline(always)] - fn next_player(self: @Game) -> u8 { - (*self.nonce / TURN_COUNT + 1) % *self.player_count + fn next_player(self: Game) -> u32 { + let index = (self.nonce / TURN_COUNT + 1) % self.player_count; + index.into() } #[inline(always)] - fn next_turn(self: @Game) -> Turn { - let turn_id = (*self.nonce + 1) % TURN_COUNT; + fn next_turn(self: Game) -> Turn { + let turn_id = (self.nonce + 1) % TURN_COUNT; turn_id.into() } @@ -94,13 +103,13 @@ impl GameImpl of GameTrait { /// * The new index of the player. #[inline(always)] fn join(ref self: Game) -> u8 { - assert(self.player_count > 0, errors::GAME_DOES_NOT_EXSIST); - assert(!self.over, errors::GAME_IS_OVER); - assert(self.seed == 0, errors::GAME_HAS_STARTED); - assert(self.slots > 0, errors::GAME_IS_FULL); - let index = self.player_count - self.slots; - self.slots -= 1; - index.into() + self.assert_exists(); + self.assert_not_over(); + self.assert_not_started(); + self.assert_not_full(); + let index = self.player_count; + self.player_count += 1; + index } /// Leaves a game and returns the last player index. @@ -110,35 +119,62 @@ impl GameImpl of GameTrait { /// # Returns /// * The last index of the last registered player. #[inline(always)] - fn leave(ref self: Game, account: ContractAddress) -> u8 { - assert(self.player_count > 0, errors::GAME_DOES_NOT_EXSIST); - assert(!self.over, errors::GAME_IS_OVER); - assert(self.seed == 0, errors::GAME_HAS_STARTED); - assert(self.slots < self.player_count, errors::GAME_IS_EMPTY); - if account == self.host { - self.over = account == self.host; - } - self.slots += 1; - let last_index = self.player_count - self.slots; - last_index.into() + fn leave(ref self: Game, address: felt252) -> u32 { + self.assert_exists(); + self.assert_not_over(); + self.assert_not_started(); + self.assert_not_empty(); + self.assert_not_host(address); + self.player_count -= 1; + self.player_count.into() } - fn start(ref self: Game, mut players: Span) { - assert(self.player_count > 0, errors::GAME_DOES_NOT_EXSIST); - assert(!self.over, errors::GAME_IS_OVER); - assert(self.seed == 0, errors::GAME_HAS_STARTED); - assert(self.real_player_count() >= MINIMUM_PLAYER_COUNT, errors::GAME_HAS_STARTED); + #[inline(always)] + fn kick(ref self: Game, address: felt252) -> u32 { + self.assert_exists(); + self.assert_not_over(); + self.assert_not_started(); + self.assert_not_empty(); + self.assert_not_host(address); + self.player_count -= 1; + self.player_count.into() + } + + #[inline(always)] + fn delete(ref self: Game, address: felt252) -> u32 { + self.assert_exists(); + self.assert_not_over(); + self.assert_not_started(); + self.assert_only_one(); + self.assert_is_host(address); + self.nullify(); + self.player_count.into() + } + + #[inline(always)] + fn transfer(ref self: Game, host: felt252) { + assert(host != 0, errors::GAME_INVALID_HOST); + self.assert_not_host(host); + self.host = host; + } + + fn start(ref self: Game, mut players: Array) { + // [Check] Game is valid + self.assert_exists(); + self.assert_not_over(); + self.assert_not_started(); + self.assert_can_start(); + + // [Effect] Compute seed let mut state = PoseidonTrait::new(); state = state.update(self.id.into()); loop { match players.pop_front() { - Option::Some(player) => { state = state.update((*player).into()); }, + Option::Some(player) => { state = state.update(player); }, Option::None => { break; }, }; }; self.seed = state.finalize(); - self.player_count = self.player_count - self.slots; - self.slots = 0; } #[inline(always)] @@ -151,6 +187,16 @@ impl GameImpl of GameTrait { let turn = self.nonce % TURN_COUNT; self.nonce += TURN_COUNT - turn; } + + #[inline(always)] + fn nullify(ref self: Game) { + self.host = 0; + self.over = false; + self.seed = 0; + self.player_count = 0; + self.nonce = 0; + self.price = 0; + } } impl U8IntoTurn of Into { @@ -178,11 +224,101 @@ impl TurnIntoU8 of Into { } } +#[generate_trait] +impl GameAssert of AssertTrait { + #[inline(always)] + fn assert_is_host(self: Game, address: felt252) { + assert(self.host == address, errors::GAME_NOT_HOST); + } + + #[inline(always)] + fn assert_not_host(self: Game, address: felt252) { + assert(self.host != address, errors::GAME_IS_HOST); + } + + #[inline(always)] + fn assert_is_over(self: Game) { + assert(self.over, errors::GAME_NOT_OVER); + } + + #[inline(always)] + fn assert_not_over(self: Game) { + assert(!self.over, errors::GAME_IS_OVER); + } + + #[inline(always)] + fn assert_has_started(self: Game) { + assert(self.seed != 0, errors::GAME_NOT_STARTED); + } + + #[inline(always)] + fn assert_not_started(self: Game) { + assert(self.seed == 0, errors::GAME_HAS_STARTED); + } + + #[inline(always)] + fn assert_exists(self: Game) { + assert(self.is_non_zero(), errors::GAME_NOT_EXISTS); + } + + #[inline(always)] + fn assert_not_exists(self: Game) { + assert(self.is_zero(), errors::GAME_DOES_EXIST); + } + + #[inline(always)] + fn assert_is_full(self: Game) { + assert(MAXIMUM_PLAYER_COUNT == self.player_count.into(), errors::GAME_NOT_FULL); + } + + #[inline(always)] + fn assert_not_full(self: Game) { + assert(MAXIMUM_PLAYER_COUNT != self.player_count.into(), errors::GAME_IS_FULL); + } + + #[inline(always)] + fn assert_not_empty(self: Game) { + assert(0 != self.player_count.into(), errors::GAME_IS_EMPTY); + } + + #[inline(always)] + fn assert_only_one(self: Game) { + assert(1 == self.player_count.into(), errors::GAME_NOT_ONLY_ONE); + } + + #[inline(always)] + fn assert_can_start(self: Game) { + assert(self.player_count >= MINIMUM_PLAYER_COUNT, errors::GAME_TOO_FEW_PLAYERS); + assert(self.player_count <= MAXIMUM_PLAYER_COUNT, errors::GAME_TOO_MANY_PLAYERS); + } +} + +impl ZeroableGame of Zeroable { + #[inline(always)] + fn zero() -> Game { + Game { id: 0, host: 0, over: false, seed: 0, player_count: 0, nonce: 0, price: 0, } + } + + #[inline(always)] + fn is_zero(self: Game) -> bool { + 0 == self.host + } + + #[inline(always)] + fn is_non_zero(self: Game) -> bool { + !self.is_zero() + } +} + #[cfg(test)] mod tests { + // Core imports + + use debug::PrintTrait; + // Local imports - use super::{Game, GameTrait, Turn, TURN_COUNT, DEFAULT_PLAYER_COUNT}; + use super::{Game, GameTrait, Turn, TURN_COUNT, MAXIMUM_PLAYER_COUNT, MINIMUM_PLAYER_COUNT}; // Constants @@ -190,56 +326,36 @@ mod tests { const PRICE: u256 = 1_000_000_000_000_000_000; const SEED: felt252 = 'SEED'; const PLAYER_COUNT: u8 = 4; - - fn HOST() -> starknet::ContractAddress { - starknet::contract_address_const::<'HOST'>() - } - - fn PLAYER() -> starknet::ContractAddress { - starknet::contract_address_const::<'PLAYER'>() - } - - fn ZERO() -> starknet::ContractAddress { - starknet::contract_address_const::<0>() - } + const HOST: felt252 = 'HOST'; + const PLAYER: felt252 = 'PLAYER'; #[test] #[available_gas(100_000)] fn test_game_new() { - let game = GameTrait::new(ID, HOST(), PRICE); - assert(game.host == HOST(), 'Game: wrong account'); + let game = GameTrait::new(ID, HOST, PRICE); + assert(game.host == HOST, 'Game: wrong account'); assert(game.id == ID, 'Game: wrong id'); assert(game.over == false, 'Game: wrong over'); assert(game.seed == 0, 'Game: wrong seed'); - assert(game.player_count == DEFAULT_PLAYER_COUNT, 'Game: wrong player_count'); - assert(game.slots == DEFAULT_PLAYER_COUNT, 'Game: wrong slots'); + assert(game.player_count == 0, 'Game: wrong player_count'); assert(game.nonce == 0, 'Game: wrong nonce'); } #[test] #[available_gas(100_000)] fn test_game_join() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); game.join(); let index = game.join(); - assert(game.real_player_count() == 2, 'Game: wrong count'); + assert(game.player_count == 2, 'Game: wrong count'); assert(index == 1, 'Game: wrong index'); } #[test] #[available_gas(100_000)] - #[should_panic(expected: ('Game: does not exsist',))] + #[should_panic(expected: ('Game: does not exist',))] fn test_game_join_revert_does_not_exist() { - let mut game = Game { - id: 0, - host: ZERO(), - over: false, - seed: 0, - player_count: 0, - slots: 0, - nonce: 0, - price: 0, - }; + let mut game: Game = Zeroable::zero(); game.join(); } @@ -247,7 +363,7 @@ mod tests { #[available_gas(100_000)] #[should_panic(expected: ('Game: is over',))] fn test_game_join_revert_is_over() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); game.over = true; game.join(); } @@ -256,7 +372,7 @@ mod tests { #[available_gas(100_000)] #[should_panic(expected: ('Game: has started',))] fn test_game_join_revert_has_started() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); game.seed = 1; game.join(); } @@ -265,8 +381,8 @@ mod tests { #[available_gas(150_000)] #[should_panic(expected: ('Game: is full',))] fn test_game_join_revert_no_remaining_slots() { - let mut game = GameTrait::new(ID, HOST(), PRICE); - let mut index = DEFAULT_PLAYER_COUNT + 1; + let mut game = GameTrait::new(ID, HOST, PRICE); + let mut index = MAXIMUM_PLAYER_COUNT + 1; loop { if index == 0 { break; @@ -279,67 +395,76 @@ mod tests { #[test] #[available_gas(100_000)] fn test_game_leave() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); game.join(); - let index = game.leave(PLAYER()); - assert(game.real_player_count() == 0, 'Game: wrong count'); + let index = game.leave(PLAYER); + assert(game.player_count == 0, 'Game: wrong count'); assert(index == 0, 'Game: wrong index'); } #[test] #[available_gas(100_000)] - fn test_game_leave_host() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + #[should_panic(expected: ('Game: user is the host',))] + fn test_game_leave_host_revert_host() { + let mut game = GameTrait::new(ID, HOST, PRICE); game.join(); - game.leave(HOST()); + game.leave(HOST); assert(game.over, 'Game: wrong status'); } #[test] #[available_gas(100_000)] - #[should_panic(expected: ('Game: does not exsist',))] + #[should_panic(expected: ('Game: is empty',))] fn test_game_leave_revert_does_not_exist() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); game.join(); game.player_count = 0; - game.leave(PLAYER()); + game.leave(PLAYER); } #[test] #[available_gas(100_000)] #[should_panic(expected: ('Game: is over',))] fn test_game_leave_revert_over() { - let mut game = GameTrait::new(ID, HOST(), PRICE); - game.join(); + let mut game = GameTrait::new(ID, HOST, PRICE); game.join(); - game.leave(HOST()); - game.leave(PLAYER()); + game.over = true; + game.leave(PLAYER); } #[test] #[available_gas(100_000)] #[should_panic(expected: ('Game: has started',))] fn test_game_leave_revert_has_started() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); game.seed = 1; game.join(); - game.leave(PLAYER()); + game.leave(PLAYER); } #[test] #[available_gas(100_000)] #[should_panic(expected: ('Game: is empty',))] fn test_game_leave_revert_is_empty() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); + game.join(); + game.leave(PLAYER); + game.leave(PLAYER); + } + + #[test] + #[available_gas(200_000)] + fn test_game_delete_host() { + let mut game = GameTrait::new(ID, HOST, PRICE); game.join(); - game.leave(PLAYER()); - game.leave(PLAYER()); + game.delete(HOST); + assert(game.is_zero(), 'Game: not zero'); } #[test] #[available_gas(200_000)] fn test_game_start() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); let mut index = PLAYER_COUNT; loop { if index == 0 { @@ -348,45 +473,46 @@ mod tests { index -= 1; game.join(); }; - let players = array![HOST(), PLAYER()]; - game.start(players.span()); + let players = array![HOST, PLAYER]; + game.start(players); assert(game.seed != 0, 'Game: wrong seed'); } #[test] #[available_gas(200_000)] - #[should_panic(expected: ('Game: does not exsist',))] - fn test_game_start_revert_does_not_exist() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + #[should_panic(expected: ('Game: too few players',))] + fn test_game_start_revert_too_few_players() { + let mut game = GameTrait::new(ID, HOST, PRICE); game.player_count = 0; - let players = array![HOST(), PLAYER()]; - game.start(players.span()); + let players = array![HOST, PLAYER]; + game.start(players); } #[test] #[available_gas(200_000)] #[should_panic(expected: ('Game: is over',))] fn test_game_start_revert_is_over() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); game.over = true; - let players = array![HOST(), PLAYER()]; - game.start(players.span()); + let players = array![HOST, PLAYER]; + game.start(players); } #[test] #[available_gas(200_000)] #[should_panic(expected: ('Game: has started',))] fn test_game_start_revert_has_started() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); game.seed = 1; - let players = array![HOST(), PLAYER()]; - game.start(players.span()); + let players = array![HOST, PLAYER]; + game.start(players); } #[test] #[available_gas(1_000_000)] fn test_game_get_player_index() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); + game.player_count = 6; assert(game.player() == 0, 'Game: wrong player index 0+0'); game.nonce += 1; assert(game.player() == 0, 'Game: wrong player index 1+0'); @@ -408,7 +534,8 @@ mod tests { #[test] #[available_gas(100_000)] fn test_game_get_next_player_index() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); + game.player_count = 6; assert(game.player() == 0, 'Game: wrong player index 0+0'); assert(game.next_player() == 1, 'Game: wrong next player 0+0'); game.nonce += TURN_COUNT; @@ -419,7 +546,7 @@ mod tests { #[test] #[available_gas(100_000)] fn test_game_get_turn_index() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); assert(game.turn().into() == 0_u8, 'Game: wrong turn index 0'); game.nonce += 1; assert(game.turn().into() == 1_u8, 'Game: wrong turn index 1'); @@ -433,7 +560,8 @@ mod tests { #[test] #[available_gas(100_000)] fn test_game_pass() { - let mut game = GameTrait::new(ID, HOST(), PRICE); + let mut game = GameTrait::new(ID, HOST, PRICE); + game.player_count = 6; game.pass(); assert(game.player() == 1, 'Game: wrong player'); game.nonce += 1; diff --git a/src/models/player.cairo b/src/models/player.cairo index 096bd51..709b9d4 100644 --- a/src/models/player.cairo +++ b/src/models/player.cairo @@ -9,6 +9,8 @@ use zconqueror::store::Store; mod errors { const PLAYER_INVALID_RANK: felt252 = 'Player: invalid rank'; + const PLAYER_NOT_EXISTS: felt252 = 'Player: does not exist'; + const PLAYER_DOES_EXIST: felt252 = 'Player: does exist'; } #[derive(Model, Copy, Drop, Serde)] @@ -17,7 +19,7 @@ struct Player { game_id: u32, #[key] index: u32, - address: ContractAddress, + address: felt252, name: felt252, supply: u32, cards: u128, @@ -28,7 +30,7 @@ struct Player { #[generate_trait] impl PlayerImpl of PlayerTrait { #[inline(always)] - fn new(game_id: u32, index: u32, address: ContractAddress, name: felt252) -> Player { + fn new(game_id: u32, index: u32, address: felt252, name: felt252) -> Player { Player { game_id, index, address, name, supply: 0, cards: 0, conqueror: false, rank: 0 } } @@ -37,15 +39,38 @@ impl PlayerImpl of PlayerTrait { assert(rank > 0, errors::PLAYER_INVALID_RANK); self.rank = rank; } + + #[inline(always)] + fn nullify(ref self: Player) { + self.address = 0; + self.name = 0; + self.supply = 0; + self.cards = 0; + self.conqueror = false; + self.rank = 0; + } } -impl DefaultPlayer of Default { +#[generate_trait] +impl PlayerAssert of AssertTrait { #[inline(always)] - fn default() -> Player { + fn assert_exists(self: Player) { + assert(self.is_non_zero(), errors::PLAYER_NOT_EXISTS); + } + + #[inline(always)] + fn assert_not_exists(self: Player) { + assert(self.is_zero(), errors::PLAYER_DOES_EXIST); + } +} + +impl ZeroablePlayer of Zeroable { + #[inline(always)] + fn zero() -> Player { Player { game_id: 0, index: 0, - address: constants::ZERO(), + address: 0, name: 0, supply: 0, cards: 0, @@ -53,4 +78,14 @@ impl DefaultPlayer of Default { rank: 0, } } + + #[inline(always)] + fn is_zero(self: Player) -> bool { + self.address == 0 + } + + #[inline(always)] + fn is_non_zero(self: Player) -> bool { + !self.is_zero() + } } diff --git a/src/models/tile.cairo b/src/models/tile.cairo index 0cb1534..4d776f0 100644 --- a/src/models/tile.cairo +++ b/src/models/tile.cairo @@ -56,14 +56,6 @@ trait TileTrait { /// # Returns /// * The initialized `Tile`. fn new(game_id: u32, id: u8, army: u32, owner: u32) -> Tile; - /// Returns a new `Option` struct. - /// # Arguments - /// * `id` - The territory id. - /// * `army` - The initial army supply. - /// * `owner` - The owner id of the territory. - /// # Returns - /// * The initialized `Option`. - fn try_new(game_id: u32, id: u8, army: u32, owner: u32) -> Option; /// Check validity. /// # Arguments /// * `self` - The tile. @@ -109,22 +101,10 @@ impl TileImpl of TileTrait { #[inline(always)] fn new(game_id: u32, id: u8, army: u32, owner: u32) -> Tile { assert(config::TILE_NUMBER >= id.into() && id > 0, errors::INVALID_ID); - let neighbors = config::neighbors(id).expect(errors::INVALID_ID); + config::neighbors(id).expect(errors::INVALID_ID); Tile { game_id, id, army, owner, dispatched: 0, to: 0, from: 0, order: 0 } } - #[inline(always)] - fn try_new(game_id: u32, id: u8, army: u32, owner: u32) -> Option { - let wrapped_neighbors = config::neighbors(id); - match wrapped_neighbors { - Option::Some(neighbors) => { - let tile = TileTrait::new(game_id, id, army, owner); - Option::Some(tile) - }, - Option::None => Option::None, - } - } - #[inline(always)] fn check(self: @Tile) -> bool { config::TILE_NUMBER >= (*self.id).into() && *self.id > 0 @@ -245,7 +225,6 @@ impl TileImpl of TileTrait { /// * The defensive and offensive survivors. fn _battle(mut defensives: u32, mut offensives: u32, ref dice: Dice) -> (u32, u32) { // [Compute] Losses - let mut index = 0; loop { if defensives == 0 || offensives == 0 { break; @@ -486,27 +465,10 @@ mod tests { TileTrait::new(GAME_ID, invalid_id, 4, PLAYER_1); } - #[test] - #[available_gas(1_000_000)] - fn test_tile_try_new() { - let wrapped_tile = TileTrait::try_new(GAME_ID, 1, 4, PLAYER_1); - let tile = wrapped_tile.unwrap(); - assert(tile.army == 4, 'Tile: wrong tile army'); - } - - #[test] - #[available_gas(1_000_000)] - #[should_panic(expected: ('Tile: invalid id',))] - fn test_tile_try_new_invalid_id() { - let invalid_id = config::TILE_NUMBER.try_into().unwrap() + 1; - let wrapped_tile = TileTrait::try_new(GAME_ID, invalid_id, 4, PLAYER_1); - wrapped_tile.expect('Tile: invalid id'); - } - #[test] #[available_gas(1_000_000)] fn test_tile_supply() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.supply = 5; let mut tile = TileTrait::new(GAME_ID, 2, 4, PLAYER_1); assert(tile.army == 4, 'Tile: wrong tile army'); @@ -519,7 +481,7 @@ mod tests { #[should_panic(expected: ('Tile: invalid id',))] fn test_tile_supply_invalid_id() { let invalid_id = config::TILE_NUMBER.try_into().unwrap() + 1; - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.supply = 4; let mut tile = TileTrait::new(GAME_ID, invalid_id, 4, PLAYER_1); tile.supply(ref player, 2); @@ -529,7 +491,7 @@ mod tests { #[available_gas(1_000_000)] #[should_panic(expected: ('Tile: invalid supply',))] fn test_tile_supply_invalid_supply() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.supply = 1; let mut tile = TileTrait::new(GAME_ID, 1, 4, PLAYER_1); tile.supply(ref player, 2); @@ -960,7 +922,7 @@ mod tests { #[should_panic(expected: ('Tile: invalid array',))] fn test_tile_sort_revert_len_0() { let array = array![]; - let sorted = _sort(array.span()); + _sort(array.span()); } #[test] @@ -968,7 +930,7 @@ mod tests { #[should_panic(expected: ('Tile: invalid array',))] fn test_tile_sort_revert_len_4() { let array = array![1, 2, 3, 4]; - let sorted = _sort(array.span()); + _sort(array.span()); } #[test] diff --git a/src/store.cairo b/src/store.cairo index a46558c..7e7b28c 100644 --- a/src/store.cairo +++ b/src/store.cairo @@ -35,7 +35,7 @@ impl StoreImpl of StoreTrait { get!(self.world, id, (Game)) } - fn player(ref self: Store, game: Game, index: u8) -> Player { + fn player(ref self: Store, game: Game, index: u32) -> Player { get!(self.world, (game.id, index), (Player)) } @@ -63,12 +63,12 @@ impl StoreImpl of StoreTrait { } fn find_player(ref self: Store, game: Game, account: ContractAddress) -> Option { - let mut index: u32 = game.real_player_count().into(); + let mut index: u32 = game.player_count.into(); loop { index -= 1; let player_key = (game.id, index); - let player = get!(self.world, player_key.into(), (Player)); - if player.address == account { + let player: Player = get!(self.world, player_key.into(), (Player)); + if player.address == account.into() { break Option::Some(player); } if index == 0 { @@ -78,11 +78,11 @@ impl StoreImpl of StoreTrait { } fn find_ranked_player(ref self: Store, game: Game, rank: u8) -> Option { - let mut index: u32 = game.real_player_count().into(); + let mut index: u32 = game.player_count.into(); loop { index -= 1; let player_key = (game.id, index); - let player = get!(self.world, player_key.into(), (Player)); + let player: Player = get!(self.world, player_key.into(), (Player)); if player.rank == rank { break Option::Some(player); } diff --git a/src/systems/host.cairo b/src/systems/host.cairo index 398210b..62324e1 100644 --- a/src/systems/host.cairo +++ b/src/systems/host.cairo @@ -15,6 +15,9 @@ trait IHost { ) -> u32; fn join(self: @TContractState, world: IWorldDispatcher, game_id: u32, player_name: felt252); fn leave(self: @TContractState, world: IWorldDispatcher, game_id: u32); + fn delete(self: @TContractState, world: IWorldDispatcher, game_id: u32); + fn kick(self: @TContractState, world: IWorldDispatcher, game_id: u32, index: u32); + fn transfer(self: @TContractState, world: IWorldDispatcher, game_id: u32, index: u32); fn start(self: @TContractState, world: IWorldDispatcher, game_id: u32); fn claim(self: @TContractState, world: IWorldDispatcher, game_id: u32); } @@ -33,7 +36,9 @@ trait IERC20 { mod host { // Starknet imports - use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use starknet::{ + ContractAddress, get_caller_address, get_contract_address, contract_address_try_from_felt252 + }; // Dojo imports @@ -45,8 +50,8 @@ mod host { // Models imports - use zconqueror::models::game::{Game, GameTrait}; - use zconqueror::models::player::{Player, PlayerTrait}; + use zconqueror::models::game::{Game, GameTrait, GameAssert}; + use zconqueror::models::player::{Player, PlayerTrait, PlayerAssert}; use zconqueror::models::tile::{Tile, TileTrait}; use zconqueror::types::map::{Map, MapTrait}; use zconqueror::types::reward::{Reward, RewardTrait}; @@ -74,7 +79,7 @@ mod host { #[storage] struct Storage {} - #[external(v0)] + #[abi(embed_v0)] impl Host of IHost { fn create( self: @ContractState, world: IWorldDispatcher, player_name: felt252, price: u256 @@ -83,18 +88,18 @@ mod host { let mut store: Store = StoreTrait::new(world); // [Interaction] Pay - let player_address = get_caller_address(); - self._pay(world, player_address, price); + let caller = get_caller_address(); + self._pay(world, caller, price); // [Effect] Game let game_id = world.uuid(); - let mut game = GameTrait::new(id: game_id, host: player_address, price: price); + let mut game = GameTrait::new(id: game_id, host: caller.into(), price: price); let player_index: u32 = game.join().into(); store.set_game(game); // [Effect] Player let player = PlayerTrait::new( - game_id, index: player_index, address: player_address, name: player_name + game_id, index: player_index, address: caller.into(), name: player_name ); store.set_player(player); @@ -108,14 +113,14 @@ mod host { // [Check] Player not in lobby let mut game = store.game(game_id); - let player_address = get_caller_address(); - match store.find_player(game, player_address) { + let caller = get_caller_address(); + match store.find_player(game, caller) { Option::Some(_) => panic(array![errors::HOST_PLAYER_ALREADY_IN_LOBBY]), Option::None => (), }; // [Interaction] Pay - self._pay(world, player_address, game.price); + self._pay(world, caller, game.price); // [Effect] Game let player_index: u32 = game.join().into(); @@ -123,28 +128,79 @@ mod host { // [Effect] Player let player = PlayerTrait::new( - game_id, index: player_index, address: player_address, name: player_name + game_id, index: player_index, address: caller.into(), name: player_name ); store.set_player(player); } + fn transfer(self: @ContractState, world: IWorldDispatcher, game_id: u32, index: u32) { + // [Setup] Datastore + let mut store: Store = StoreTrait::new(world); + + // [Check] Caller is the host + let mut game = store.game(game_id); + let caller = get_caller_address(); + game.assert_is_host(caller.into()); + + // [Check] Player exists + let mut player = store.player(game, index); + player.assert_exists(); + + // [Effect] Update Game + game.transfer(player.address); + store.set_game(game); + } + fn leave(self: @ContractState, world: IWorldDispatcher, game_id: u32,) { // [Setup] Datastore let mut store: Store = StoreTrait::new(world); // [Check] Player in lobby let mut game = store.game(game_id); - let player_address = get_caller_address(); - let player = match store.find_player(game, player_address) { + let caller = get_caller_address(); + let mut player = match store.find_player(game, caller) { Option::Some(player) => player, Option::None => panic(array![errors::HOST_PLAYER_NOT_IN_LOBBY]), }; - // [Effect] Game - let last_index = game.leave(player_address); + // [Effect] Update Game + let last_index = game.leave(caller.into()); store.set_game(game); - // [Effect] Player + // [Effect] Update Player + let mut last_player = store.player(game, last_index); + if last_player.index != player.index { + last_player.index = player.index; + store.set_player(last_player); + } + + // [Interaction] Refund + let recipient = starknet::contract_address_try_from_felt252(player.address).unwrap(); + self._refund(world, recipient, game.price); + + // [Effect] Update Player + player.nullify(); + store.set_player(player); + } + + fn kick(self: @ContractState, world: IWorldDispatcher, game_id: u32, index: u32) { + // [Setup] Datastore + let mut store: Store = StoreTrait::new(world); + + // [Check] Caller is the host + let mut game = store.game(game_id); + let caller = get_caller_address(); + game.assert_is_host(caller.into()); + + // [Check] Player exists + let mut player = store.player(game, index); + player.assert_exists(); + + // [Effect] Update Game + let last_index = game.kick(player.address); + store.set_game(game); + + // [Effect] Update last Player let mut last_player = store.player(game, last_index); if last_player.index != player.index { last_player.index = player.index; @@ -152,7 +208,38 @@ mod host { } // [Interaction] Refund - self._refund(world, player_address, game.price); + let address = starknet::contract_address_try_from_felt252(player.address).unwrap(); + self._refund(world, address, game.price); + + // [Effect] Update Player + player.nullify(); + store.set_player(player); + } + + fn delete(self: @ContractState, world: IWorldDispatcher, game_id: u32) { + // [Setup] Datastore + let mut store: Store = StoreTrait::new(world); + + // [Check] Player exists + let mut game = store.game(game_id); + let caller = get_caller_address(); + let mut player = match store.find_player(game, caller) { + Option::Some(player) => player, + Option::None => panic(array![errors::HOST_PLAYER_NOT_IN_LOBBY]), + }; + player.assert_exists(); + + // [Interaction] Refund + let address = starknet::contract_address_try_from_felt252(player.address).unwrap(); + self._refund(world, address, game.price); + + // [Effect] Update Game + game.delete(player.address); + store.set_game(game); + + // [Effect] Update Player + player.nullify(); + store.set_player(player); } fn start(self: @ContractState, world: IWorldDispatcher, game_id: u32,) { @@ -162,7 +249,7 @@ mod host { // [Check] Caller is the host let mut game = store.game(game_id); let caller = get_caller_address(); - assert(caller == game.host, errors::HOST_CALLER_IS_NOT_THE_HOST); + game.assert_is_host(caller.into()); // [Effect] Start game let mut addresses = array![]; @@ -173,10 +260,12 @@ mod host { Option::None => { break; }, }; }; - game.start(addresses.span()); + + // [Effect] Update Game + game.start(addresses); store.set_game(game); - // [Effect] Tiles + // [Effect] Update Tiles let mut map = MapTrait::new( game_id: game.id, seed: game.seed, @@ -199,7 +288,7 @@ mod host { player_index += 1; }; - // [Effect] Players + // [Effect] Update Players // Use the deck mechanism to define the player order // First player got his supply set let mut deck = DeckTrait::new(game.seed, game.player_count.into()); @@ -210,7 +299,7 @@ mod host { break; }; let index = deck.draw() - 1; - let mut player = store.player(game, index); + let mut player = store.player(game, index.into()); player.index = player_index; if player_index == 0 { let player_score = map.score(player_index.into()); @@ -262,7 +351,7 @@ mod host { } fn _refund( - self: @ContractState, world: IWorldDispatcher, caller: ContractAddress, amount: u256 + self: @ContractState, world: IWorldDispatcher, recipient: ContractAddress, amount: u256 ) { // [Check] Amount is not null, otherwise return if amount == 0 { @@ -270,9 +359,8 @@ mod host { } // [Interaction] Transfer - let contract = get_contract_address(); let erc20 = IERC20Dispatcher { contract_address: constants::ERC20_ADDRESS() }; - let status = erc20.transfer(caller, amount); + let status = erc20.transfer(recipient, amount); // [Check] Status assert(status, errors::ERC20_REFUND_FAILED); @@ -286,20 +374,26 @@ mod host { // [Setup] Top players let first = store.find_ranked_player(game, 1); - let first_address = match first { - Option::Some(player) => { player.address }, + let first_address: ContractAddress = match first { + Option::Some(player) => { + contract_address_try_from_felt252(player.address).unwrap() + }, Option::None => { constants::ZERO() }, }; let second = store.find_ranked_player(game, 2); - let second_address = match second { - Option::Some(player) => { player.address }, + let second_address: ContractAddress = match second { + Option::Some(player) => { + contract_address_try_from_felt252(player.address).unwrap() + }, Option::None => { constants::ZERO() }, }; let third = store.find_ranked_player(game, 3); - let third_address = match third { - Option::Some(player) => { player.address }, + let third_address: ContractAddress = match third { + Option::Some(player) => { + contract_address_try_from_felt252(player.address).unwrap() + }, Option::None => { constants::ZERO() }, }; diff --git a/src/systems/play.cairo b/src/systems/play.cairo index 0639811..4159922 100644 --- a/src/systems/play.cairo +++ b/src/systems/play.cairo @@ -149,7 +149,7 @@ mod play { troops: u32, } - #[external(v0)] + #[abi(embed_v0)] impl Play of IPlay { fn attack( self: @ContractState, @@ -169,7 +169,7 @@ mod play { // [Check] Caller is player let caller = get_caller_address(); let mut player = store.current_player(game); - assert(caller == player.address, errors::ATTACK_INVALID_PLAYER); + assert(player.address == caller.into(), errors::ATTACK_INVALID_PLAYER); // [Check] Tiles owner let mut attacker = store.tile(game, attacker_index); @@ -201,7 +201,7 @@ mod play { // [Check] Caller is player let caller = get_caller_address(); let mut player = store.current_player(game); - assert(caller == player.address, errors::DEFEND_INVALID_PLAYER); + assert(player.address == caller.into(), errors::DEFEND_INVALID_PLAYER); // [Check] Tiles owner let mut attacker = store.tile(game, attacker_index); @@ -250,7 +250,7 @@ mod play { // [Check] Caller is player let caller = get_caller_address(); let mut player = store.current_player(game); - assert(caller == player.address, errors::DISCARD_INVALID_PLAYER); + assert(player.address == caller.into(), errors::DISCARD_INVALID_PLAYER); // [Compute] Discard let tiles = store.tiles(game).span(); @@ -271,7 +271,7 @@ mod play { let caller = get_caller_address(); let mut game: Game = store.game(game_id); let mut player = store.current_player(game); - assert(caller == player.address, errors::FINISH_INVALID_PLAYER); + assert(player.address == caller.into(), errors::FINISH_INVALID_PLAYER); // [Check] Player supply is empty let mut player = store.current_player(game); @@ -296,7 +296,7 @@ mod play { loop { let mut next_player = store.current_player(game); // [Check] Next player is the current player means game is over - if next_player.address == caller { + if next_player.address == caller.into() { // [Effect] Update player next_player.rank(store.get_next_rank(game)); store.set_player(next_player); @@ -355,7 +355,7 @@ mod play { // [Check] Caller is player let caller = get_caller_address(); let mut player = store.current_player(game); - assert(caller == player.address, errors::SUPPLY_INVALID_PLAYER); + assert(player.address == caller.into(), errors::SUPPLY_INVALID_PLAYER); // [Check] Tile owner let mut tile = store.tile(game, tile_index.into()); @@ -397,7 +397,7 @@ mod play { // [Check] Caller is player let caller = get_caller_address(); let mut player = store.current_player(game); - assert(caller == player.address, errors::TRANSFER_INVALID_PLAYER); + assert(player.address == caller.into(), errors::TRANSFER_INVALID_PLAYER); // [Check] Tiles owner let mut from = store.tile(game, from_index); diff --git a/src/tests/attack.cairo b/src/tests/attack.cairo index 25d4a83..28d98b2 100644 --- a/src/tests/attack.cairo +++ b/src/tests/attack.cairo @@ -27,13 +27,13 @@ const HOST_NAME: felt252 = 'HOST'; const PLAYER_NAME: felt252 = 'PLAYER'; const PRICE: u256 = 1_000_000_000_000_000_000; const PLAYER_COUNT: u8 = 2; -const PLAYER_INDEX: u8 = 0; +const PLAYER_INDEX: u32 = 0; #[test] #[available_gas(1_000_000_000)] fn test_attack() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -57,7 +57,8 @@ fn test_attack() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, attacker, supply); // [Finish] @@ -88,7 +89,7 @@ fn test_attack() { #[should_panic(expected: ('Attack: invalid player', 'ENTRYPOINT_FAILED',))] fn test_attack_revert_invalid_player() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -112,7 +113,8 @@ fn test_attack_revert_invalid_player() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Finish] @@ -129,7 +131,7 @@ fn test_attack_revert_invalid_player() { #[should_panic(expected: ('Attack: invalid owner', 'ENTRYPOINT_FAILED',))] fn test_attack_revert_invalid_owner() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -153,7 +155,8 @@ fn test_attack_revert_invalid_owner() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Finish] diff --git a/src/tests/defend.cairo b/src/tests/defend.cairo index 820c2bb..d67fc6d 100644 --- a/src/tests/defend.cairo +++ b/src/tests/defend.cairo @@ -27,13 +27,13 @@ const HOST_NAME: felt252 = 'HOST'; const PLAYER_NAME: felt252 = 'PLAYER'; const PRICE: u256 = 1_000_000_000_000_000_000; const PLAYER_COUNT: u8 = 2; -const PLAYER_INDEX: u8 = 0; +const PLAYER_INDEX: u32 = 0; #[test] #[available_gas(1_000_000_000)] fn test_defend_win() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -58,7 +58,8 @@ fn test_defend_win() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, attacker, supply); // [Finish] @@ -112,7 +113,7 @@ fn test_defend_win() { #[available_gas(1_000_000_000)] fn test_defend_lose() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -137,7 +138,8 @@ fn test_defend_lose() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, attacker, supply); // [Finish] @@ -193,7 +195,7 @@ fn test_defend_lose() { #[should_panic(expected: ('Tile: invalid order status', 'ENTRYPOINT_FAILED',))] fn test_defend_revert_invalid_order() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -217,7 +219,8 @@ fn test_defend_revert_invalid_order() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, attacker, supply); // [Finish] @@ -253,7 +256,7 @@ fn test_defend_revert_invalid_order() { #[should_panic(expected: ('Defend: invalid player', 'ENTRYPOINT_FAILED',))] fn test_defend_revert_invalid_player() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -277,7 +280,8 @@ fn test_defend_revert_invalid_player() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Finish] @@ -294,7 +298,7 @@ fn test_defend_revert_invalid_player() { #[should_panic(expected: ('Defend: invalid owner', 'ENTRYPOINT_FAILED',))] fn test_defend_revert_invalid_owner() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -318,7 +322,8 @@ fn test_defend_revert_invalid_owner() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Finish] diff --git a/src/tests/finish.cairo b/src/tests/finish.cairo index 8eb4011..3054953 100644 --- a/src/tests/finish.cairo +++ b/src/tests/finish.cairo @@ -27,13 +27,13 @@ const HOST_NAME: felt252 = 'HOST'; const PLAYER_NAME: felt252 = 'PLAYER'; const PRICE: u256 = 1_000_000_000_000_000_000; const PLAYER_COUNT: u8 = 2; -const PLAYER_INDEX: u8 = 0; +const PLAYER_INDEX: u32 = 0; #[test] #[available_gas(1_000_000_000)] fn test_finish_next_player() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -61,7 +61,8 @@ fn test_finish_next_player() { }; // [Supply] - set_contract_address(player.address); + let contract_address = starknet::contract_address_try_from_felt252(player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Finish] @@ -99,7 +100,7 @@ fn test_finish_next_player() { #[should_panic(expected: ('Finish: invalid supply', 'ENTRYPOINT_FAILED',))] fn test_finish_revert_invalid_supply() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -112,7 +113,8 @@ fn test_finish_revert_invalid_supply() { // [Finish] let game: Game = store.game(game_id); let player: Player = store.player(game, PLAYER_INDEX); - set_contract_address(player.address); + let contract_address = starknet::contract_address_try_from_felt252(player.address); + set_contract_address(contract_address.unwrap()); systems.play.finish(world, game_id); } @@ -121,7 +123,7 @@ fn test_finish_revert_invalid_supply() { #[should_panic(expected: ('Finish: invalid player', 'ENTRYPOINT_FAILED',))] fn test_finish_revert_invalid_player() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -139,6 +141,7 @@ fn test_finish_revert_invalid_player() { // [Finish] let player_index = 1 - PLAYER_INDEX; let player: Player = store.player(game, player_index); - set_contract_address(player.address); + let contract_address = starknet::contract_address_try_from_felt252(player.address); + set_contract_address(contract_address.unwrap()); systems.play.finish(world, game_id); } diff --git a/src/tests/host.cairo b/src/tests/host.cairo index cc234d4..9e0a534 100644 --- a/src/tests/host.cairo +++ b/src/tests/host.cairo @@ -14,7 +14,7 @@ use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; use zconqueror::config::TILE_NUMBER; use zconqueror::store::{Store, StoreTrait}; -use zconqueror::models::game::{Game, GameTrait, DEFAULT_PLAYER_COUNT}; +use zconqueror::models::game::{Game, GameTrait}; use zconqueror::models::player::Player; use zconqueror::models::tile::Tile; use zconqueror::systems::host::IHostDispatcherTrait; @@ -33,7 +33,7 @@ const PLAYER_COUNT: u8 = 2; #[available_gas(1_000_000_000)] fn test_host_create_and_join() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -60,10 +60,12 @@ fn test_host_create_and_join() { break; } let player: Player = store.player(game, player_index.into()); - let player_name: u256 = player.name.into(); assert(player.game_id == game.id, 'Player: wrong game id'); assert(player.index == player_index.into(), 'Player: wrong order'); - assert(player.address == HOST() || player.address == PLAYER(), 'Player: wrong address'); + assert( + player.address == HOST().into() || player.address == PLAYER().into(), + 'Player: wrong address' + ); assert(player.name == HOST_NAME || player.name == PLAYER_NAME, 'Player: wrong name'); assert( player.supply == 0 || (game.player().into() == player.index && player.supply > 0), @@ -92,25 +94,25 @@ fn test_host_create_and_join() { #[test] #[available_gas(1_000_000_000)] -fn test_host_create_and_host_leaves() { +fn test_host_create_and_host_deletes() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] let game_id = systems.host.create(world, HOST_NAME, PRICE); - systems.host.leave(world, game_id); + systems.host.delete(world, game_id); // [Assert] Game let game: Game = store.game(game_id); - assert(game.slots == DEFAULT_PLAYER_COUNT, 'Game: wrong slots'); + assert(game.player_count == 0, 'Game: wrong player count'); } #[test] #[available_gas(1_000_000_000)] fn test_host_create_and_player_leaves() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -121,7 +123,53 @@ fn test_host_create_and_player_leaves() { // [Assert] Game let game: Game = store.game(game_id); - assert(game.slots == DEFAULT_PLAYER_COUNT - 1, 'Game: wrong slots'); + assert(game.player_count == 1, 'Game: wrong player count'); +} + +#[test] +#[available_gas(1_000_000_000)] +fn test_host_create_and_tranfer_and_host_leaves() { + // [Setup] + let (world, systems, _) = setup::spawn_game(); + let mut store = StoreTrait::new(world); + + // [Create] + let game_id = systems.host.create(world, HOST_NAME, PRICE); + set_contract_address(PLAYER()); + systems.host.join(world, game_id, PLAYER_NAME); + set_contract_address(HOST()); + let game = store.game(game_id); + let player = store.find_player(game, PLAYER()).unwrap(); + systems.host.transfer(world, game_id, player.index); + systems.host.leave(world, game_id); + + // [Assert] Game + let game: Game = store.game(game_id); + assert(game.player_count == 1, 'Game: wrong player count'); +} + +#[test] +#[available_gas(1_000_000_000)] +fn test_host_create_and_tranfer_and_kick_host() { + // [Setup] + let (world, systems, _) = setup::spawn_game(); + let mut store = StoreTrait::new(world); + + // [Create] + let game_id = systems.host.create(world, HOST_NAME, PRICE); + set_contract_address(PLAYER()); + systems.host.join(world, game_id, PLAYER_NAME); + set_contract_address(HOST()); + let game = store.game(game_id); + let player = store.find_player(game, PLAYER()).unwrap(); + systems.host.transfer(world, game_id, player.index); + set_contract_address(PLAYER()); + let player = store.find_player(game, HOST()).unwrap(); + systems.host.kick(world, game_id, player.index); + + // [Assert] Game + let game: Game = store.game(game_id); + assert(game.player_count == 1, 'Game: wrong player count'); } #[test] @@ -129,8 +177,7 @@ fn test_host_create_and_player_leaves() { #[should_panic(expected: ('Game: has started', 'ENTRYPOINT_FAILED',))] fn test_host_start_then_join_revert_started() { // [Setup] - let (world, systems, context) = setup::spawn_game(); - let mut store = StoreTrait::new(world); + let (world, systems, _) = setup::spawn_game(); // [Create] let game_id = systems.host.create(world, HOST_NAME, PRICE); @@ -149,8 +196,7 @@ fn test_host_start_then_join_revert_started() { #[should_panic(expected: ('Game: has started', 'ENTRYPOINT_FAILED',))] fn test_host_start_then_leave_revert_started() { // [Setup] - let (world, systems, context) = setup::spawn_game(); - let mut store = StoreTrait::new(world); + let (world, systems, _) = setup::spawn_game(); // [Create] let game_id = systems.host.create(world, HOST_NAME, PRICE); diff --git a/src/tests/supply.cairo b/src/tests/supply.cairo index a69e220..d13e284 100644 --- a/src/tests/supply.cairo +++ b/src/tests/supply.cairo @@ -27,12 +27,12 @@ const HOST_NAME: felt252 = 'HOST'; const PLAYER_NAME: felt252 = 'PLAYER'; const PRICE: u256 = 1_000_000_000_000_000_000; const PLAYER_COUNT: u8 = 2; -const PLAYER_INDEX: u8 = 0; +const PLAYER_INDEX: u32 = 0; #[test] #[available_gas(1_000_000_000)] fn test_supply() { - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -56,7 +56,8 @@ fn test_supply() { }; // [Supply] - set_contract_address(initial_player.address); + let contract_address = starknet::contract_address_try_from_felt252(initial_player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Assert] Player supply @@ -74,7 +75,7 @@ fn test_supply() { #[should_panic(expected: ('Supply: invalid player', 'ENTRYPOINT_FAILED',))] fn test_supply_revert_invalid_player() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -88,7 +89,8 @@ fn test_supply_revert_invalid_player() { let game: Game = store.game(game_id); let player_index = 1 - PLAYER_INDEX; let player = store.player(game, player_index); - set_contract_address(player.address); + let contract_address = starknet::contract_address_try_from_felt252(player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, 0, 0); } @@ -98,7 +100,7 @@ fn test_supply_revert_invalid_player() { #[should_panic(expected: ('Supply: invalid owner', 'ENTRYPOINT_FAILED',))] fn test_supply_revert_invalid_owner() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -121,6 +123,7 @@ fn test_supply_revert_invalid_owner() { // [Transfer] let player = store.player(game, PLAYER_INDEX); - set_contract_address(player.address); + let contract_address = starknet::contract_address_try_from_felt252(player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, index, 0); } diff --git a/src/tests/transfer.cairo b/src/tests/transfer.cairo index 5248b96..eb9fb93 100644 --- a/src/tests/transfer.cairo +++ b/src/tests/transfer.cairo @@ -27,13 +27,13 @@ const HOST_NAME: felt252 = 'HOST'; const PLAYER_NAME: felt252 = 'PLAYER'; const PRICE: u256 = 1_000_000_000_000_000_000; const PLAYER_COUNT: u8 = 2; -const PLAYER_INDEX: u8 = 0; +const PLAYER_INDEX: u32 = 0; #[test] #[available_gas(1_000_000_000)] fn test_transfer_valid() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -57,7 +57,8 @@ fn test_transfer_valid() { }; // [Supply] - set_contract_address(player.address); + let contract_address = starknet::contract_address_try_from_felt252(player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Finish] @@ -101,7 +102,7 @@ fn test_transfer_valid() { #[should_panic(expected: ('Transfer: invalid player', 'ENTRYPOINT_FAILED',))] fn test_transfer_revert_invalid_player() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -125,7 +126,8 @@ fn test_transfer_revert_invalid_player() { }; // [Supply] - set_contract_address(player.address); + let contract_address = starknet::contract_address_try_from_felt252(player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Finish] @@ -145,7 +147,7 @@ fn test_transfer_revert_invalid_player() { #[should_panic(expected: ('Transfer: invalid owner', 'ENTRYPOINT_FAILED',))] fn test_transfer_revert_invalid_owner() { // [Setup] - let (world, systems, context) = setup::spawn_game(); + let (world, systems, _) = setup::spawn_game(); let mut store = StoreTrait::new(world); // [Create] @@ -169,7 +171,8 @@ fn test_transfer_revert_invalid_owner() { }; // [Supply] - set_contract_address(player.address); + let contract_address = starknet::contract_address_try_from_felt252(player.address); + set_contract_address(contract_address.unwrap()); systems.play.supply(world, game_id, tile_index, supply); // [Finish] diff --git a/src/types/hand.cairo b/src/types/hand.cairo index d892e5a..b8d06bd 100644 --- a/src/types/hand.cairo +++ b/src/types/hand.cairo @@ -232,7 +232,7 @@ mod tests { #[test] #[available_gas(1_000_000)] fn test_hand_load() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x0a09080605040302010b; let hand = HandTrait::load(@player); assert(hand.cards == _unpack(0x0a09080605040302010b), 'Hand: wrong load'); @@ -241,7 +241,7 @@ mod tests { #[test] #[available_gas(1_000_000)] fn test_hand_dump() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x0a09080605040302010b; let hand = HandTrait::load(@player); let cards = hand.dump(); @@ -274,7 +274,7 @@ mod tests { #[test] #[available_gas(1_000_000)] fn test_hand_check() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x01020303; let hand = HandTrait::load(@player); let set = SetTrait::new(1, 2, 3); @@ -284,7 +284,7 @@ mod tests { #[test] #[available_gas(1_000_000)] fn test_hand_uncheck() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x10202; let hand = HandTrait::load(@player); let set = SetTrait::new(1, 2, 3); @@ -295,7 +295,7 @@ mod tests { #[available_gas(1_000_000)] #[should_panic(expected: ('Hand: invalid set',))] fn test_hand_deploy_invalid_set_not_owned() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x10202; let mut hand = HandTrait::load(@player); let set = SetTrait::new(1, 2, 3); @@ -306,7 +306,7 @@ mod tests { #[available_gas(1_000_000)] #[should_panic(expected: ('Hand: invalid set',))] fn test_hand_deploy_invalid_set_not_scored() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x1020203; let mut hand = HandTrait::load(@player); let set = SetTrait::new(1, 2, 2); @@ -316,7 +316,7 @@ mod tests { #[test] #[available_gas(1_000_000)] fn test_hand_deploy() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x1020303; let mut hand = HandTrait::load(@player); let set = SetTrait::new(1, 2, 3); @@ -327,10 +327,10 @@ mod tests { #[test] #[available_gas(1_000_000)] fn test_hand_merge() { - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x1020303; let mut main = HandTrait::load(@player); - let mut player: Player = Default::default(); + let mut player: Player = Zeroable::zero(); player.cards = 0x4050603; let mut hand = HandTrait::load(@player); main.merge(@hand); diff --git a/src/types/map.cairo b/src/types/map.cairo index 662f57e..022cb2e 100644 --- a/src/types/map.cairo +++ b/src/types/map.cairo @@ -89,8 +89,6 @@ impl MapImpl of MapTrait { ) -> Map { // [Check] There is enough army to supply at least 1 unit per tile assert(player_count * army_count >= tile_count, errors::INVALID_ARMY_COUNT); - // [Compute] Seed in u256 for futher operations - let base_seed: u256 = seed.into(); // Use the deck mechanism to shuffle the tiles let mut deck = DeckTrait::new(seed, tile_count); // Each player draw R/N where R is the remaining cards and N the number of players left