From 0fd25fcc24ddee0dd41e4e7bdf3012e6f39759e2 Mon Sep 17 00:00:00 2001 From: Ajidokwu Sabo Date: Fri, 18 Jul 2025 00:54:45 +0100 Subject: [PATCH] feat: updated property manipulation and end game --- src/interfaces/IActions.cairo | 30 +-- src/model/game_model.cairo | 4 +- src/systems/actions.cairo | 123 ++++++++++ src/tests/test_world.cairo | 426 ++++++++++++++++++++++++++++++++++ 4 files changed, 557 insertions(+), 26 deletions(-) diff --git a/src/interfaces/IActions.cairo b/src/interfaces/IActions.cairo index d32326f..7f8df8f 100644 --- a/src/interfaces/IActions.cairo +++ b/src/interfaces/IActions.cairo @@ -45,6 +45,11 @@ pub trait IActions { fn use_getout_of_jail_chance(ref self: T, game_id: u256) -> bool; fn use_getout_of_jail_community_chest(ref self: T, game_id: u256) -> bool; + fn calculate_net_worth(ref self: T, player: GamePlayer) -> u256; + + fn get_winner_by_net_worth(ref self: T, players: Array) -> ContractAddress; + fn end_game(ref self: T, game: Game) -> ContractAddress; + fn offer_trade( ref self: T, game_id: u256, @@ -77,17 +82,7 @@ pub trait IActions { fn roll_dice(ref self: T) -> (u8, u8); fn move_player(ref self: T, game_id: u256, steps: u8) -> u8; fn pay_jail_fine(ref self: T, game_id: u256) -> bool; - // fn handle_chance(ref self: T, game_id: u256, random_index: u32) -> @ByteArray; - // Handling landings on board - // fn draw_chance_card(ref self: T, game_id: u256) -> Chance; - // fn draw_community_chest_card(ref self: T, game_id: u256) -> CommunityChest; - // fn pay_tax(ref self: T, game_id: u256, tax_id: u8) -> bool; - // fn go_to_jail(ref self: T, game_id: u256) -> bool; - - // Jail specific actions - // fn pay_jail_fee(ref self: T, game_id: u256) -> bool; - // fn use_jail_card(ref self: T, game_id: u256) -> bool; // Property transactions fn buy_property(ref self: T, property: Property) -> bool; @@ -107,18 +102,6 @@ pub trait IActions { fn process_community_chest_card( ref self: T, game: Game, player: GamePlayer, card: ByteArray, ) -> (Game, GamePlayer); - // Trading system - // fn offer_trade( - // ref self: T, - // game_id: u256, - // to: ContractAddress, - // offered_property_ids: Array, - // requested_property_ids: Array, - // cash_offer: u256, - // cash_request: u256 - // ); - // fn accept_trade(ref self: T, game_id: u256, trade_id: u256) -> bool; - // fn decline_trade(ref self: T, game_id: u256, trade_id: u256) -> bool; // // Auctions // fn start_auction(ref self: T, property_id: u8, game_id: u256); @@ -132,6 +115,5 @@ pub trait IActions { fn mint(ref self: T, recepient: ContractAddress, game_id: u256, amount: u256); // Bankruptcy & ending game // fn declare_bankruptcy(ref self: T, game_id: u256) -> bool; -// fn check_winner(self: @T, game_id: u256) -> Option; -// fn end_game(ref self: T, game_id: u256) -> bool; + } diff --git a/src/model/game_model.cairo b/src/model/game_model.cairo index 99e1c07..f5c8de1 100644 --- a/src/model/game_model.cairo +++ b/src/model/game_model.cairo @@ -31,7 +31,7 @@ pub struct Game { pub status: GameStatus, // Status of the game pub mode: GameType, // Mode of the game pub ready_to_start: bool, // Indicate whether game can be started - pub winner: felt252, // First winner position + pub winner: ContractAddress, // First winner position pub next_player: ContractAddress, // Address of the player to make the next move pub number_of_players: u8, // Number of players in the game pub rolls_count: u256, // Sum of all the numbers rolled by the dice @@ -139,7 +139,7 @@ impl GameImpl of GameTrait { player_boot, player_wheelbarrow, next_player: zero_address.into(), - winner: zero_address.into(), + winner: zero_address, rolls_times: 0, rolls_count: 0, number_of_players, diff --git a/src/systems/actions.cairo b/src/systems/actions.cairo index c277252..2171325 100644 --- a/src/systems/actions.cairo +++ b/src/systems/actions.cairo @@ -1426,6 +1426,129 @@ pub mod actions { true } + fn calculate_net_worth(ref self: ContractState, player: GamePlayer) -> u256 { + let mut world = self.world_default(); + + let mut total_property_value: u256 = 0; + let mut total_house_cost: u256 = 0; + let mut total_rent_value: u256 = 0; + let mut card_value: u256 = 0; + let mut i = 0; + let properties_len = player.properties_owned.len(); + + while i < properties_len { + let prop_id = *player.properties_owned.at(i); + let game_id = player.game_id; + let property: Property = self.get_property(prop_id, game_id); + + // Property value (half if mortgaged) + if property.is_mortgaged { + total_property_value += property.cost_of_property / 2; + } else { + total_property_value += property.cost_of_property; + } + + // House/hotel cost + if property.development < 5 { + total_house_cost += property.cost_of_house * property.development.into(); + } else if property.development == 5 { + total_house_cost += property.cost_of_house * 5; + } + + // Rent value (always add — mortgaged or not, since it's dev level based) + let rent = match property.development { + 0 => property.rent_site_only, + 1 => property.rent_one_house, + 2 => property.rent_two_houses, + 3 => property.rent_three_houses, + 4 => property.rent_four_houses, + _ => property.rent_hotel, + }; + total_rent_value += rent; + + i += 1; + }; + + // Jail/Chance card value + if player.chance_jail_card { + card_value += 50; + } + if player.comm_free_card { + card_value += 50; + } + + let net_worth = player.balance + + total_property_value + + total_house_cost + + total_rent_value + + card_value; + + // Debug prints + println!("Balance: {}", player.balance); + println!("Total property value: {}", total_property_value); + println!("Total house cost: {}", total_house_cost); + println!("Total rent value: {}", total_rent_value); + println!("Card value: {}", card_value); + println!("NET WORTH: {}", net_worth); + + net_worth + } + fn get_winner_by_net_worth( + ref self: ContractState, players: Array, + ) -> ContractAddress { + let mut i = 0; + let mut max_net_worth: u256 = 0; + let mut winner_address: ContractAddress = contract_address_const::<'0'>(); + + let players_len = players.len(); + while i < players_len { + let player = players.at(i); + let net_worth = self.calculate_net_worth(player.clone()); + + if net_worth > max_net_worth { + max_net_worth = net_worth; + winner_address = *player.address; + }; + + i += 1; + }; + + winner_address + } + + + fn end_game(ref self: ContractState, game: Game) -> ContractAddress { + let mut world = self.world_default(); + let mut players: Array = ArrayTrait::new(); + + let total_players = game.game_players.len(); + let mut i = 0; + + // Indexed loop over game.players + while i < total_players { + let player_address = game.game_players.at(i); + let player_model: GamePlayer = world.read_model((*player_address, game.id)); + + players.append(player_model); + i += 1; + }; + + // Find the winner by net worth + let winner_address = self.get_winner_by_net_worth(players); + let winner: Player = world.read_model(winner_address); + + // Set game status to ended + let mut updated_game = game; + updated_game.status = GameStatus::Ended; + updated_game.winner = winner.address; + + // Write back the updated game state + world.write_model(@updated_game); + + // Return the winner's address + winner.address + } + fn reject_trade(ref self: ContractState, trade_id: u256, game_id: u256) -> bool { let mut world = self.world_default(); diff --git a/src/tests/test_world.cairo b/src/tests/test_world.cairo index 01a2183..963890a 100644 --- a/src/tests/test_world.cairo +++ b/src/tests/test_world.cairo @@ -3653,5 +3653,431 @@ mod tests { let trade = actions_system.get_trade(trade_id); assert(trade.status == TradeStatus::Rejected, 'Trade not rejected'); } + + #[test] + fn test_player_net_worth() { + let caller_1 = contract_address_const::<'aji'>(); + let caller_2 = contract_address_const::<'collins'>(); + let caller_3 = contract_address_const::<'jerry'>(); + let caller_4 = contract_address_const::<'aliyu'>(); + let username = 'Ajidokwu'; + let username_1 = 'Collins'; + let username_2 = 'Jerry'; + let username_3 = 'Aliyu'; + + let ndef = namespace_def(); + let mut world = spawn_test_world([ndef].span()); + world.sync_perms_and_inits(contract_defs()); + + let (contract_address, _) = world.dns(@"actions").unwrap(); + let actions_system = IActionsDispatcher { contract_address }; + + testing::set_contract_address(caller_2); + actions_system.register_new_player(username_1); + + testing::set_contract_address(caller_1); + actions_system.register_new_player(username); + + testing::set_contract_address(caller_3); + actions_system.register_new_player(username_2); + + testing::set_contract_address(caller_4); + actions_system.register_new_player(username_3); + + testing::set_contract_address(caller_1); + actions_system.create_new_game(GameType::PublicGame, PlayerSymbol::Hat, 4); + + testing::set_contract_address(caller_2); + actions_system.join_game(PlayerSymbol::Dog, 1); + + testing::set_contract_address(caller_3); + actions_system.join_game(PlayerSymbol::Car, 1); + + testing::set_contract_address(caller_4); + actions_system.join_game(PlayerSymbol::Iron, 1); + + testing::set_contract_address(caller_1); + let started = actions_system.start_game(1); + assert(started, 'Game start fail'); + + testing::set_contract_address(caller_1); + actions_system.move_player(1, 1); + let mut property = actions_system.get_property(1, 1); + actions_system.buy_property(property); + + testing::set_contract_address(caller_2); + actions_system.move_player(1, 12); + + let mut game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_3); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_4); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_1); + actions_system.move_player(1, 2); + property = actions_system.get_property(3, 1); + actions_system.buy_property(property); + + testing::set_contract_address(caller_2); + actions_system.move_player(1, 12); + + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_3); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_4); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_1); + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + let success = actions_system.buy_house_or_hotel(property); + assert(success, 'house failed'); + property = actions_system.get_property(3, 1); + assert(property.development == 5, 'dev correct'); + + let aji = actions_system.retrieve_game_player(caller_1, 1); + + assert(aji.total_hotels_owned == 2, 'house count error'); + assert(aji.total_houses_owned == 8, 'house count error'); + + let aji_networth = actions_system.calculate_net_worth(aji); + let collins_networth = actions_system + .calculate_net_worth(actions_system.retrieve_game_player(caller_2, 1)); + let jerry_networth = actions_system + .calculate_net_worth(actions_system.retrieve_game_player(caller_3, 1)); + let ali_networth = actions_system + .calculate_net_worth(actions_system.retrieve_game_player(caller_4, 1)); + println!("aji net worth : {}", aji_networth); + println!("collins net worth : {}", collins_networth); + println!("jerry net worth : {}", jerry_networth); + println!("ali net worth : {}", ali_networth); + } + + #[test] + fn test_winner() { + let caller_1 = contract_address_const::<'aji'>(); + let caller_2 = contract_address_const::<'collins'>(); + let caller_3 = contract_address_const::<'jerry'>(); + let caller_4 = contract_address_const::<'aliyu'>(); + let username = 'Ajidokwu'; + let username_1 = 'Collins'; + let username_2 = 'Jerry'; + let username_3 = 'Aliyu'; + + let ndef = namespace_def(); + let mut world = spawn_test_world([ndef].span()); + world.sync_perms_and_inits(contract_defs()); + + let (contract_address, _) = world.dns(@"actions").unwrap(); + let actions_system = IActionsDispatcher { contract_address }; + + testing::set_contract_address(caller_2); + actions_system.register_new_player(username_1); + + testing::set_contract_address(caller_1); + actions_system.register_new_player(username); + + testing::set_contract_address(caller_3); + actions_system.register_new_player(username_2); + + testing::set_contract_address(caller_4); + actions_system.register_new_player(username_3); + + testing::set_contract_address(caller_1); + actions_system.create_new_game(GameType::PublicGame, PlayerSymbol::Hat, 4); + + testing::set_contract_address(caller_2); + actions_system.join_game(PlayerSymbol::Dog, 1); + + testing::set_contract_address(caller_3); + actions_system.join_game(PlayerSymbol::Car, 1); + + testing::set_contract_address(caller_4); + actions_system.join_game(PlayerSymbol::Iron, 1); + + testing::set_contract_address(caller_1); + let started = actions_system.start_game(1); + assert(started, 'Game start fail'); + + testing::set_contract_address(caller_1); + actions_system.move_player(1, 1); + let mut property = actions_system.get_property(1, 1); + actions_system.buy_property(property); + + testing::set_contract_address(caller_2); + actions_system.move_player(1, 12); + + let mut game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_3); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_4); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_1); + actions_system.move_player(1, 2); + property = actions_system.get_property(3, 1); + actions_system.buy_property(property); + + testing::set_contract_address(caller_2); + actions_system.move_player(1, 12); + + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_3); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_4); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_1); + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + let success = actions_system.buy_house_or_hotel(property); + assert(success, 'house failed'); + property = actions_system.get_property(3, 1); + assert(property.development == 5, 'dev correct'); + + let aji = actions_system.retrieve_game_player(caller_1, 1); + + assert(aji.total_hotels_owned == 2, 'house count error'); + assert(aji.total_houses_owned == 8, 'house count error'); + + let aji_networth = actions_system.calculate_net_worth(aji); + let collins_networth = actions_system + .calculate_net_worth(actions_system.retrieve_game_player(caller_2, 1)); + let jerry_networth = actions_system + .calculate_net_worth(actions_system.retrieve_game_player(caller_3, 1)); + let ali_networth = actions_system + .calculate_net_worth(actions_system.retrieve_game_player(caller_4, 1)); + println!("aji net worth : {}", aji_networth); + println!("collins net worth : {}", collins_networth); + println!("jerry net worth : {}", jerry_networth); + println!("ali net worth : {}", ali_networth); + + let mut players = array![ + actions_system.retrieve_game_player(caller_1, 1), + actions_system.retrieve_game_player(caller_2, 1), + actions_system.retrieve_game_player(caller_3, 1), + actions_system.retrieve_game_player(caller_4, 1), + ]; + + let winner = actions_system.get_winner_by_net_worth(players); + + let winner_felt: felt252 = winner.into(); + println!("Winner is: {}", winner_felt); + assert(winner == caller_1, 'Winner is not Aji'); + } + + + #[test] + fn test_end_game() { + let caller_1 = contract_address_const::<'aji'>(); + let caller_2 = contract_address_const::<'collins'>(); + let caller_3 = contract_address_const::<'jerry'>(); + let caller_4 = contract_address_const::<'aliyu'>(); + let username = 'Ajidokwu'; + let username_1 = 'Collins'; + let username_2 = 'Jerry'; + let username_3 = 'Aliyu'; + + let ndef = namespace_def(); + let mut world = spawn_test_world([ndef].span()); + world.sync_perms_and_inits(contract_defs()); + + let (contract_address, _) = world.dns(@"actions").unwrap(); + let actions_system = IActionsDispatcher { contract_address }; + + testing::set_contract_address(caller_2); + actions_system.register_new_player(username_1); + + testing::set_contract_address(caller_1); + actions_system.register_new_player(username); + + testing::set_contract_address(caller_3); + actions_system.register_new_player(username_2); + + testing::set_contract_address(caller_4); + actions_system.register_new_player(username_3); + + testing::set_contract_address(caller_1); + actions_system.create_new_game(GameType::PublicGame, PlayerSymbol::Hat, 4); + + testing::set_contract_address(caller_2); + actions_system.join_game(PlayerSymbol::Dog, 1); + + testing::set_contract_address(caller_3); + actions_system.join_game(PlayerSymbol::Car, 1); + + testing::set_contract_address(caller_4); + actions_system.join_game(PlayerSymbol::Iron, 1); + + testing::set_contract_address(caller_1); + let started = actions_system.start_game(1); + assert(started, 'Game start fail'); + + testing::set_contract_address(caller_1); + actions_system.move_player(1, 1); + let mut property = actions_system.get_property(1, 1); + actions_system.buy_property(property); + + testing::set_contract_address(caller_2); + actions_system.move_player(1, 12); + + let mut game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_3); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_4); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_1); + actions_system.move_player(1, 2); + property = actions_system.get_property(3, 1); + actions_system.buy_property(property); + + testing::set_contract_address(caller_2); + actions_system.move_player(1, 12); + + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_3); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_4); + actions_system.move_player(1, 8); + game = actions_system.retrieve_game(1); + actions_system.finish_turn(game); + + testing::set_contract_address(caller_1); + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(3, 1); + actions_system.buy_house_or_hotel(property); + + property = actions_system.get_property(1, 1); + let success = actions_system.buy_house_or_hotel(property); + assert(success, 'house failed'); + property = actions_system.get_property(3, 1); + assert(property.development == 5, 'dev correct'); + + let mut game = actions_system.retrieve_game(1); + + let winner = actions_system.end_game(game.clone()); + game = actions_system.retrieve_game(1); + let winner_felt: felt252 = winner.into(); + println!("Winner is: {}", winner_felt); + assert(winner == caller_1, 'Winner is not Aji'); + assert(game.status == GameStatus::Ended, 'Game not finished'); + } }