From 466559b04c50832a06a1df3ade2f376f7a58e773 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 15 Jun 2025 12:13:34 +0200 Subject: [PATCH 01/33] Add house contract to apps --- contracts/src/apps.cairo | 1 + contracts/src/apps/house.cairo | 273 +++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 contracts/src/apps/house.cairo diff --git a/contracts/src/apps.cairo b/contracts/src/apps.cairo index b32034eb..97e7abb4 100644 --- a/contracts/src/apps.cairo +++ b/contracts/src/apps.cairo @@ -1,3 +1,4 @@ +pub mod house; pub mod paint; pub mod player; pub mod snake; diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo new file mode 100644 index 00000000..2545bf68 --- /dev/null +++ b/contracts/src/apps/house.cairo @@ -0,0 +1,273 @@ +use pixelaw::core::models::{pixel::{PixelUpdate}, registry::{App}}; +use pixelaw::core::utils::{DefaultParameters, Position}; +use starknet::{ContractAddress}; + +/// House Model to keep track of houses and their owners +#[derive(Copy, Drop, Serde)] +#[dojo::model] +pub struct House { + #[key] + position: Position, // Top-left corner position of the 3x3 house + pub owner: ContractAddress, // Owner of the house + pub created_at: u64, // Timestamp when house was created + pub last_life_generated: u64, // Timestamp when last life was generated +} + +/// Model to track if a player already has a house +#[derive(Copy, Drop, Serde)] +#[dojo::model] +pub struct PlayerHouse { + #[key] + player: ContractAddress, + pub has_house: bool, // Whether player has a house or not + pub house_position: Position, // Position of the player's house (if they have one) +} + +/// Events for House app +#[derive(Copy, Drop, Serde)] +#[dojo::event] +pub struct HouseBuilt { + #[key] + player: ContractAddress, + position: Position, +} + +#[derive(Copy, Drop, Serde)] +#[dojo::event] +pub struct LifeCollected { + #[key] + player: ContractAddress, + house_position: Position, +} + +#[starknet::interface] +pub trait IHouseActions { + fn on_pre_update(ref self: T, pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress) -> Option; + fn on_post_update(ref self: T, pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress); + fn build_house(ref self: T, default_params: DefaultParameters); + fn collect_life(ref self: T, default_params: DefaultParameters); +} + +/// House app constants +pub const APP_KEY: felt252 = 'house'; +pub const APP_ICON: felt252 = 0x1f3e0; // 🏠 emoji +pub const HOUSE_SIZE: u8 = 3; // 3x3 house +pub const LIFE_REGENERATION_TIME: u64 = 86400; // 24 hours in seconds (one day) + +/// House actions contract +#[dojo::contract] +pub mod house_actions { + use dojo::model::{ModelStorage}; + use super::{House, PlayerHouse, IHouseActions}; + use pixelaw::core::models::registry::App; + use pixelaw::core::actions::{IActionsDispatcherTrait as ICoreActionsDispatcherTrait}; + use pixelaw::core::models::pixel::{Pixel, PixelUpdate, PixelUpdateResultTrait}; + use pixelaw::core::utils::{DefaultParameters, get_callers, get_core_actions, Position}; + use starknet::{ContractAddress, contract_address_const, get_block_timestamp, get_contract_address}; + use pixelaw::apps::player::{Player}; + use super::{APP_ICON, APP_KEY, HOUSE_SIZE, LIFE_REGENERATION_TIME}; + + /// Initialize the House App + fn dojo_init(ref self: ContractState) { + let mut world = self.world(@"pixelaw"); + let core_actions = get_core_actions(ref world); + core_actions.new_app(contract_address_const::<0>(), APP_KEY, APP_ICON); + } + + // impl: implement functions specified in trait + #[abi(embed_v0)] + impl Actions of IHouseActions { + /// Hook called before a pixel update. + /// + /// # Arguments + /// + /// * `world` - A reference to the world dispatcher. + /// * `pixel_update` - The proposed update to the pixel. + /// * `app_caller` - The app initiating the update. + /// * `player_caller` - The player initiating the update. + fn on_pre_update( + ref self: ContractState, + pixel_update: PixelUpdate, + app_caller: App, + player_caller: ContractAddress, + ) -> Option { + // Default is to not allow anything + Option::None + } + + /// Hook called after a pixel update. + /// + /// # Arguments + /// + /// * `world` - A reference to the world dispatcher. + /// * `pixel_update` - The update that was applied to the pixel. + /// * `app_caller` - The app that performed the update. + /// * `player_caller` - The player that performed the update. + fn on_post_update( + ref self: ContractState, + pixel_update: PixelUpdate, + app_caller: App, + player_caller: ContractAddress, + ) { + // No action needed + } + /// Build a new house at the specified position + /// + /// # Arguments + /// + /// * `default_params` - Default parameters including position + fn build_house(ref self: ContractState, default_params: DefaultParameters) { + let mut world = self.world(@"pixelaw"); + + // Load important variables + let core_actions = get_core_actions(ref world); + let (player, system) = get_callers(ref world, default_params); + + let position = default_params.position; + let current_timestamp = get_block_timestamp(); + + // Check if player already has a house + let mut player_house: PlayerHouse = world.read_model(player); + assert!(!player_house.has_house, "Player already has a house"); + + // Ensure the area is free (3x3) + let mut is_area_free = true; + let mut x = 0; + while x < HOUSE_SIZE { + let mut y = 0; + while y < HOUSE_SIZE { + let check_position = Position { x: position.x + x.into(), y: position.y + y.into() }; + let pixel: Pixel = world.read_model(check_position); + if pixel.app != contract_address_const::<0>() { + is_area_free = false; + break; + } + y += 1; + }; + if !is_area_free { + break; + } + x += 1; + }; + assert!(is_area_free, "Area is not free for building a house"); + + // Create house record + let house = House { + position, + owner: player, + created_at: current_timestamp, + last_life_generated: current_timestamp, + }; + world.write_model(@house); + + // Mark player as having a house + player_house.has_house = true; + player_house.house_position = position; + world.write_model(@player_house); + + // Place house pixels (3x3 grid) + let mut x = 0; + while x < HOUSE_SIZE { + let mut y = 0; + while y < HOUSE_SIZE { + let house_position = Position { x: position.x + x.into(), y: position.y + y.into() }; + + // Generate different appearance for different parts of the house + let (color, text) = if x == 1 && y == 1 { + // Center is the main part + (0x8B4513FF, 0x1f3e0) // Brown with house emoji + } else if x == 1 && y == 0 { + // Door + (0x8B4513FF, 0x1f6aa) // Brown with door emoji + } else if (x == 0 || x == 2) && (y == 0 || y == 2) { + // Corners + (0x8B4513FF, 0x1f3e0) // Brown with house emoji + } else { + // Walls + (0x8B4513FF, 0x1f9f1) // Brown with brick emoji + }; + + core_actions + .update_pixel( + player, + system, + PixelUpdate { + position: house_position, + color: Option::Some(color), + timestamp: Option::None, + text: Option::Some(text), + app: Option::Some(get_contract_address()), + owner: Option::Some(player), + action: Option::None + }, + Option::None, + false + ) + .unwrap(); + + y += 1; + }; + x += 1; + }; + + // Emit notification instead of direct event + core_actions.notification( + position, + default_params.color, + Option::Some(player), + Option::None, + 'House built!' + ); + } + + /// Collect a life from your house (once per day) + /// + /// # Arguments + /// + /// * `default_params` - Default parameters including position + fn collect_life(ref self: ContractState, default_params: DefaultParameters) { + let mut world = self.world(@"pixelaw"); + + // Load important variables + let core_actions = get_core_actions(ref world); + let (player, _system) = get_callers(ref world, default_params); + + let current_timestamp = get_block_timestamp(); + + // Check if player is already max lives + let mut player_data: Player = world.read_model(player); + assert!(player_data.lives < 5, "Player already has max lives"); + + // Check if player has a house + let player_house: PlayerHouse = world.read_model(player); + assert!(player_house.has_house, "Player does not have a house"); + + // Get the house data + let mut house: House = world.read_model(player_house.house_position); + assert!(house.owner == player, "Not the owner of this house"); + + // Check if enough time has passed for life regeneration + assert!( + current_timestamp >= house.last_life_generated + LIFE_REGENERATION_TIME, + "Life not ready yet" + ); + + // Update house last_life_generated timestamp + house.last_life_generated = current_timestamp; + world.write_model(@house); + + // Get player data and increment lives + player_data.lives += 1; + world.write_model(@player_data); + + // Send notification instead of direct event + core_actions.notification( + player_house.house_position, + default_params.color, + Option::Some(player), + Option::None, + 'Life collected!' + ); + } + } +} From 196a07398e2de81874b0d8e8cd7f32bea5747698 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 15 Jun 2025 12:13:50 +0200 Subject: [PATCH 02/33] Add player lives to player app --- contracts/src/apps/player.cairo | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/src/apps/player.cairo b/contracts/src/apps/player.cairo index 00c02b35..14c870bb 100644 --- a/contracts/src/apps/player.cairo +++ b/contracts/src/apps/player.cairo @@ -24,6 +24,7 @@ pub struct Player { pub pixel_original_app: ContractAddress, pub pixel_original_text: felt252, pub pixel_original_action: felt252, + pub lives: u32, } @@ -50,6 +51,7 @@ pub trait IPlayerActions { pub const APP_KEY: felt252 = 'player'; const APP_ICON: felt252 = 0xf09f9883; // 😃 +pub const PLAYER_LIVES: u32 = 5; #[dojo::contract] pub mod player_actions { @@ -64,7 +66,7 @@ pub mod player_actions { use starknet::{ContractAddress, contract_address_const, get_contract_address}; use super::IPlayerActions; - use super::{APP_ICON, APP_KEY}; + use super::{APP_ICON, APP_KEY, PLAYER_LIVES}; use super::{Player, PositionPlayer}; fn dojo_init(ref self: ContractState) { let mut world = self.world(@"pixelaw"); @@ -175,6 +177,7 @@ pub mod player_actions { // Check if Player exists yet // Its either a bug or feature... when Player is on 0,0 it can "teleport" + // now he would also recover to full lives :'D if player.position.x == 0 && player.position.y == 0 { // just try to create the Player on the Pixel clicked, if it panics its ok core_actions @@ -198,6 +201,7 @@ pub mod player_actions { player.position = clicked_position; player.color = default_params.color; player.emoji = 0xefb88ff09fa78de2808de29980efb88f; // ️👶 + player.lives = PLAYER_LIVES; world.write_model(@player); positionPlayer.player = playerAddress; From bf3e6d1734479fec428980713633f7eb03d49dc3 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 15 Jun 2025 12:14:28 +0200 Subject: [PATCH 03/33] Add house contract tests --- pixelaw_testing/src/lib.cairo | 1 + pixelaw_testing/src/tests/app_house.cairo | 186 ++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 pixelaw_testing/src/tests/app_house.cairo diff --git a/pixelaw_testing/src/lib.cairo b/pixelaw_testing/src/lib.cairo index c1067184..0c5306f0 100644 --- a/pixelaw_testing/src/lib.cairo +++ b/pixelaw_testing/src/lib.cairo @@ -6,6 +6,7 @@ mod tests { mod app_paint; mod app_snake; mod app_player; + mod app_house; mod area; mod base; mod interop; diff --git a/pixelaw_testing/src/tests/app_house.cairo b/pixelaw_testing/src/tests/app_house.cairo new file mode 100644 index 00000000..b4f8e983 --- /dev/null +++ b/pixelaw_testing/src/tests/app_house.cairo @@ -0,0 +1,186 @@ +use dojo::model::{ModelStorage}; + +use pixelaw::core::models::pixel::{Pixel}; +use pixelaw::core::utils::{DefaultParameters, Position}; +use pixelaw::apps::house::{IHouseActionsDispatcherTrait, House, PlayerHouse}; +use pixelaw::apps::player::{Player}; +use crate::helpers::{setup_core, setup_apps}; +use starknet::{contract_address_const, testing::{set_account_contract_address, set_block_timestamp}}; + +// House app test constants +const HOUSE_COLOR: u32 = 0x8B4513FF; // Brown color +const LIFE_REGENERATION_TIME: u64 = 86400; // 24 hours in seconds + +#[test] +#[available_gas(3000000000)] +fn test_build_house() { + // Initialize the world + let (mut world, _core_actions, _player_1, _player_2) = setup_core(); + let (_paint_actions, _snake_actions, _player_actions, house_actions) = setup_apps(ref world); + + let player1 = contract_address_const::<0x1337>(); + set_account_contract_address(player1); + + // Define the position for our house (top-left corner) + let house_position = Position { x: 10, y: 10 }; + + // Build a house at the specified position + house_actions + .build_house( + DefaultParameters { + player_override: Option::None, + system_override: Option::None, + area_hint: Option::None, + position: house_position, + color: HOUSE_COLOR, + }, + ); + + // Verify that the center of the house has the correct color and emoji + let center_pixel: Pixel = world.read_model(Position { x: 11, y: 11 }); + assert(center_pixel.color == HOUSE_COLOR, 'House center should be brown'); + + // Check if player has a house in the registry + let player_house: PlayerHouse = world.read_model(player1); + assert(player_house.has_house, 'Player should have a house'); + assert(player_house.house_position == house_position, 'House position mismatch'); + + // Check that the house model was created correctly + let house: House = world.read_model(house_position); + assert(house.owner == player1, 'House owner mismatch'); +} + +#[test] +#[available_gas(3000000000)] +#[should_panic(expected: ('Player already has a house', 'ENTRYPOINT_FAILED'))] +fn test_build_second_house() { + // Initialize the world + let (mut world, _core_actions, _player_1, _player_2) = setup_core(); + let (_paint_actions, _snake_actions, _player_actions, house_actions) = setup_apps(ref world); + + let player1 = contract_address_const::<0x1337>(); + set_account_contract_address(player1); + + // Build first house + house_actions + .build_house( + DefaultParameters { + player_override: Option::None, + system_override: Option::None, + area_hint: Option::None, + position: Position { x: 10, y: 10 }, + color: HOUSE_COLOR, + }, + ); + + // Try to build a second house - should fail + house_actions + .build_house( + DefaultParameters { + player_override: Option::None, + system_override: Option::None, + area_hint: Option::None, + position: Position { x: 20, y: 20 }, + color: HOUSE_COLOR, + }, + ); +} + +#[test] +#[available_gas(3000000000)] +fn test_collect_life() { + // Initialize the world + let (mut world, _core_actions, _player_1, _player_2) = setup_core(); + let (_paint_actions, _snake_actions, _player_actions, house_actions) = setup_apps(ref world); + + let player1 = contract_address_const::<0x1337>(); + set_account_contract_address(player1); + + // Set the initial timestamp + let initial_timestamp: u64 = 1000; + set_block_timestamp(initial_timestamp); + + // Build a house + let house_position = Position { x: 10, y: 10 }; + house_actions + .build_house( + DefaultParameters { + player_override: Option::None, + system_override: Option::None, + area_hint: Option::None, + position: house_position, + color: HOUSE_COLOR, + }, + ); + + // Get the initial player data + let player_data: Player = world.read_model(player1); + let initial_lives: u32 = player_data.lives; + + // Fast forward time to enable life collection + set_block_timestamp(initial_timestamp + LIFE_REGENERATION_TIME + 1); + + // Collect life + house_actions + .collect_life( + DefaultParameters { + player_override: Option::None, + system_override: Option::None, + area_hint: Option::None, + position: house_position, + color: HOUSE_COLOR, + }, + ); + + // Check if player gained a life + let player_data_after: Player = world.read_model(player1); + assert(player_data_after.lives == initial_lives + 1, 'Player should gain a life'); + + // Check if the house's last_life_generated was updated + let house: House = world.read_model(house_position); + assert(house.last_life_generated == initial_timestamp + LIFE_REGENERATION_TIME + 1, 'Last life time not updated'); +} + +#[test] +#[available_gas(3000000000)] +#[should_panic(expected: ('Life not ready yet', 'ENTRYPOINT_FAILED'))] +fn test_collect_life_too_soon() { + // Initialize the world + let (mut world, _core_actions, _player_1, _player_2) = setup_core(); + let (_paint_actions, _snake_actions, _player_actions, house_actions) = setup_apps(ref world); + + let player1 = contract_address_const::<0x1337>(); + set_account_contract_address(player1); + + // Set the initial timestamp + let initial_timestamp: u64 = 1000; + set_block_timestamp(initial_timestamp); + + // Build a house + let house_position = Position { x: 10, y: 10 }; + house_actions + .build_house( + DefaultParameters { + player_override: Option::None, + system_override: Option::None, + area_hint: Option::None, + position: house_position, + color: HOUSE_COLOR, + }, + ); + + // Fast forward time but not enough (only half the required time) + set_block_timestamp(initial_timestamp + LIFE_REGENERATION_TIME / 2); + + // Try to collect life too soon - should fail + house_actions + .collect_life( + DefaultParameters { + player_override: Option::None, + system_override: Option::None, + area_hint: Option::None, + position: house_position, + color: HOUSE_COLOR, + }, + ); +} \ No newline at end of file From a0eb1ac6fd4bac7c798e73ecfcb03245631eb96f Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 15 Jun 2025 12:15:25 +0200 Subject: [PATCH 04/33] Update dependencies & other tests --- contracts/Scarb.lock | 11 +---------- pixelaw_testing/Scarb.lock | 4 ++-- pixelaw_testing/src/helpers.cairo | 13 +++++++++++-- pixelaw_testing/src/tests/app_paint.cairo | 2 +- pixelaw_testing/src/tests/app_player.cairo | 2 +- pixelaw_testing/src/tests/app_snake.cairo | 2 +- pixelaw_testing/src/tests/base.cairo | 6 +++--- pixelaw_testing/src/tests/interop.cairo | 4 ++-- pixelaw_testing/src/tests/queue.cairo | 2 +- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index 245b73f9..eafa70fa 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -9,14 +9,6 @@ dependencies = [ "dojo_plugin", ] -[[package]] -name = "dojo_cairo_test" -version = "1.0.12" -source = "git+https://github.com/dojoengine/dojo?tag=v1.5.0#812f17c9c57fd057d0bf1e648a591ea0ca9ea718" -dependencies = [ - "dojo", -] - [[package]] name = "dojo_plugin" version = "2.10.1" @@ -24,8 +16,7 @@ source = "git+https://github.com/dojoengine/dojo?tag=v1.5.0#812f17c9c57fd057d0bf [[package]] name = "pixelaw" -version = "0.7.6" +version = "0.7.7" dependencies = [ "dojo", - "dojo_cairo_test", ] diff --git a/pixelaw_testing/Scarb.lock b/pixelaw_testing/Scarb.lock index c707a5db..1b071c98 100644 --- a/pixelaw_testing/Scarb.lock +++ b/pixelaw_testing/Scarb.lock @@ -24,14 +24,14 @@ source = "git+https://github.com/dojoengine/dojo?tag=v1.5.0#812f17c9c57fd057d0bf [[package]] name = "pixelaw" -version = "0.7.0" +version = "0.7.7" dependencies = [ "dojo", ] [[package]] name = "pixelaw_testing" -version = "0.7.0" +version = "0.7.7" dependencies = [ "dojo", "dojo_cairo_test", diff --git a/pixelaw_testing/src/helpers.cairo b/pixelaw_testing/src/helpers.cairo index 942632f3..2b8af9b1 100644 --- a/pixelaw_testing/src/helpers.cairo +++ b/pixelaw_testing/src/helpers.cairo @@ -10,6 +10,7 @@ use pixelaw::{ paint::{IPaintActionsDispatcher, paint_actions}, snake::{ISnakeActionsDispatcher, m_Snake, m_SnakeSegment, snake_actions}, player::{IPlayerActionsDispatcher, m_Player, m_PositionPlayer, player_actions}, + house::{IHouseActionsDispatcher, m_House, m_PlayerHouse, house_actions}, }, core::{ actions::{IActionsDispatcher, actions}, @@ -48,9 +49,12 @@ fn app_namespace_defs() -> NamespaceDef { TestResource::Model(m_SnakeSegment::TEST_CLASS_HASH), TestResource::Model(m_Player::TEST_CLASS_HASH), TestResource::Model(m_PositionPlayer::TEST_CLASS_HASH), + TestResource::Model(m_House::TEST_CLASS_HASH), + TestResource::Model(m_PlayerHouse::TEST_CLASS_HASH), TestResource::Contract(snake_actions::TEST_CLASS_HASH), TestResource::Contract(paint_actions::TEST_CLASS_HASH), TestResource::Contract(player_actions::TEST_CLASS_HASH), + TestResource::Contract(house_actions::TEST_CLASS_HASH), ] .span(), }; @@ -95,6 +99,8 @@ fn app_contract_defs() -> Span { .with_writer_of([dojo::utils::bytearray_hash(@"pixelaw")].span()), ContractDefTrait::new(@"pixelaw", @"player_actions") .with_writer_of([dojo::utils::bytearray_hash(@"pixelaw")].span()), + ContractDefTrait::new(@"pixelaw", @"house_actions") + .with_writer_of([dojo::utils::bytearray_hash(@"pixelaw")].span()), ] .span() } @@ -121,7 +127,7 @@ pub fn setup_core() -> (WorldStorage, IActionsDispatcher, ContractAddress, Contr pub fn setup_apps( ref world: WorldStorage, -) -> (IPaintActionsDispatcher, ISnakeActionsDispatcher, IPlayerActionsDispatcher) { +) -> (IPaintActionsDispatcher, ISnakeActionsDispatcher, IPlayerActionsDispatcher, IHouseActionsDispatcher) { update_test_world(ref world, [app_namespace_defs()].span()); @@ -136,7 +142,10 @@ pub fn setup_apps( let player_actions_address = world.dns_address(@"player_actions").unwrap(); let player_actions = IPlayerActionsDispatcher { contract_address: player_actions_address }; - (paint_actions, snake_actions, player_actions) + let house_actions_address = world.dns_address(@"house_actions").unwrap(); + let house_actions = IHouseActionsDispatcher { contract_address: house_actions_address }; + + (paint_actions, snake_actions, player_actions, house_actions) } diff --git a/pixelaw_testing/src/tests/app_paint.cairo b/pixelaw_testing/src/tests/app_paint.cairo index b76cd24c..8985cf17 100644 --- a/pixelaw_testing/src/tests/app_paint.cairo +++ b/pixelaw_testing/src/tests/app_paint.cairo @@ -12,7 +12,7 @@ use starknet::{contract_address_const, testing::set_account_contract_address}; fn test_paint_actions() { // Deploy everything let (mut world, _core_actions, _player_1, _player_2) = setup_core(); - let (paint_actions, _snake_actions, _player_actions) = setup_apps(ref world); + let (paint_actions, _snake_actions, _player_actions, _house_actions) = setup_apps(ref world); let player1 = contract_address_const::<0x1337>(); set_account_contract_address(player1); diff --git a/pixelaw_testing/src/tests/app_player.cairo b/pixelaw_testing/src/tests/app_player.cairo index 8def5d88..b66c2e59 100644 --- a/pixelaw_testing/src/tests/app_player.cairo +++ b/pixelaw_testing/src/tests/app_player.cairo @@ -16,7 +16,7 @@ use starknet::{contract_address_const, testing::set_account_contract_address}; fn test_player_interaction() { // Initialize the world and apps let (mut world, _core_actions, _player_1, _player_2) = setup_core(); - let (_paint_actions, _snake_actions, player_actions) = setup_apps(ref world); + let (_paint_actions, _snake_actions, player_actions, _house_actions) = setup_apps(ref world); let player1 = contract_address_const::<0x1337>(); set_account_contract_address(player1); diff --git a/pixelaw_testing/src/tests/app_snake.cairo b/pixelaw_testing/src/tests/app_snake.cairo index 65ac8e9a..08091c16 100644 --- a/pixelaw_testing/src/tests/app_snake.cairo +++ b/pixelaw_testing/src/tests/app_snake.cairo @@ -15,7 +15,7 @@ use starknet::{contract_address_const, testing::set_account_contract_address}; #[available_gas(3000000000)] fn test_playthrough() { let (mut world, _core_actions, _player_1, _player_2) = setup_core(); - let (paint_actions, snake_actions, _player_actions) = setup_apps(ref world); + let (paint_actions, snake_actions, _player_actions, _house_actions) = setup_apps(ref world); let SNAKE_COLOR = 0xFF00FF; diff --git a/pixelaw_testing/src/tests/base.cairo b/pixelaw_testing/src/tests/base.cairo index 142284a6..03e654c1 100644 --- a/pixelaw_testing/src/tests/base.cairo +++ b/pixelaw_testing/src/tests/base.cairo @@ -39,7 +39,7 @@ fn test_register_new_app() { #[test] fn test_paint_interaction() { let (mut world, _core_actions, _player_1, _player_2) = setup_core(); - let (paint_actions, _snake_actions, _player_actions) = setup_apps(ref world); + let (paint_actions, _snake_actions, _player_actions, _house_actions) = setup_apps(ref world); paint_actions .interact( @@ -57,7 +57,7 @@ fn test_paint_interaction() { #[test] fn test_can_update_pixel() { let (mut world, core_actions, player_1, player_2) = setup_core(); - let (paint_actions, _snake_actions, _player_actions) = setup_apps(ref world); + let (paint_actions, _snake_actions, _player_actions, _house_actions) = setup_apps(ref world); // Scenario: // Check if Player2 can change Player1's pixel @@ -224,7 +224,7 @@ fn test_get_callers() { #[test] fn test_notification_player() { let (mut world, core_actions, player_1, _player_2) = setup_core(); - let (paint_actions, _snake_actions, _player_actions) = setup_apps(ref world); + let (paint_actions, _snake_actions, _player_actions, _house_actions) = setup_apps(ref world); // Prep params let position = Position { x: 12, y: 12 }; diff --git a/pixelaw_testing/src/tests/interop.cairo b/pixelaw_testing/src/tests/interop.cairo index 8727bd69..009cc7b2 100644 --- a/pixelaw_testing/src/tests/interop.cairo +++ b/pixelaw_testing/src/tests/interop.cairo @@ -10,14 +10,14 @@ use pixelaw::{ #[test] fn test_app_permissions() { let (mut world, _core_actions, player_1, _player_2) = setup_core(); - let (_paint_actions, _snake_actions, _player_actions) = setup_apps(ref world); + let (_paint_actions, _snake_actions, _player_actions, _house_actions) = setup_apps(ref world); set_caller(player_1); } #[test] fn test_hooks() { let (mut world, _core_actions, player_1, _player_2) = setup_core(); - let (paint_actions, snake_actions, _player_actions) = setup_apps(ref world); + let (paint_actions, snake_actions, _player_actions, _house_actions) = setup_apps(ref world); set_caller(player_1); diff --git a/pixelaw_testing/src/tests/queue.cairo b/pixelaw_testing/src/tests/queue.cairo index 565b4350..4691cf3f 100644 --- a/pixelaw_testing/src/tests/queue.cairo +++ b/pixelaw_testing/src/tests/queue.cairo @@ -51,7 +51,7 @@ fn test_process_queue() { #[test] fn test_queue_full() { let (mut world, core_actions, player_1, _player_2) = setup_core(); - let (_, snake_actions, _player_actions) = setup_apps(ref world); + let (_, snake_actions, _player_actions, _house_actions) = setup_apps(ref world); let SNAKE_COLOR = 0xFF00FF; From e173bad18dcfe322e53a6070dce5f4b5ee438031 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 15 Jun 2025 12:18:35 +0200 Subject: [PATCH 05/33] Run scarb fmt --- contracts/src/apps/house.cairo | 79 ++++++++++++---------- contracts/src/apps/player.cairo | 1 - pixelaw_testing/src/helpers.cairo | 11 +-- pixelaw_testing/src/tests/app_house.cairo | 33 +++++---- pixelaw_testing/src/tests/app_player.cairo | 9 +-- pixelaw_testing/src/tests/base.cairo | 10 +-- pixelaw_testing/src/tests/queue.cairo | 4 +- 7 files changed, 76 insertions(+), 71 deletions(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 2545bf68..c6bf6f62 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -10,7 +10,7 @@ pub struct House { position: Position, // Top-left corner position of the 3x3 house pub owner: ContractAddress, // Owner of the house pub created_at: u64, // Timestamp when house was created - pub last_life_generated: u64, // Timestamp when last life was generated + pub last_life_generated: u64 // Timestamp when last life was generated } /// Model to track if a player already has a house @@ -20,7 +20,7 @@ pub struct PlayerHouse { #[key] player: ContractAddress, pub has_house: bool, // Whether player has a house or not - pub house_position: Position, // Position of the player's house (if they have one) + pub house_position: Position // Position of the player's house (if they have one) } /// Events for House app @@ -42,8 +42,12 @@ pub struct LifeCollected { #[starknet::interface] pub trait IHouseActions { - fn on_pre_update(ref self: T, pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress) -> Option; - fn on_post_update(ref self: T, pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress); + fn on_pre_update( + ref self: T, pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress, + ) -> Option; + fn on_post_update( + ref self: T, pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress, + ); fn build_house(ref self: T, default_params: DefaultParameters); fn collect_life(ref self: T, default_params: DefaultParameters); } @@ -58,15 +62,17 @@ pub const LIFE_REGENERATION_TIME: u64 = 86400; // 24 hours in seconds (one day) #[dojo::contract] pub mod house_actions { use dojo::model::{ModelStorage}; - use super::{House, PlayerHouse, IHouseActions}; - use pixelaw::core::models::registry::App; + use pixelaw::apps::player::{Player}; use pixelaw::core::actions::{IActionsDispatcherTrait as ICoreActionsDispatcherTrait}; use pixelaw::core::models::pixel::{Pixel, PixelUpdate, PixelUpdateResultTrait}; - use pixelaw::core::utils::{DefaultParameters, get_callers, get_core_actions, Position}; - use starknet::{ContractAddress, contract_address_const, get_block_timestamp, get_contract_address}; - use pixelaw::apps::player::{Player}; + use pixelaw::core::models::registry::App; + use pixelaw::core::utils::{DefaultParameters, Position, get_callers, get_core_actions}; + use starknet::{ + ContractAddress, contract_address_const, get_block_timestamp, get_contract_address, + }; use super::{APP_ICON, APP_KEY, HOUSE_SIZE, LIFE_REGENERATION_TIME}; - + use super::{House, IHouseActions, PlayerHouse}; + /// Initialize the House App fn dojo_init(ref self: ContractState) { let mut world = self.world(@"pixelaw"); @@ -108,8 +114,7 @@ pub mod house_actions { pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress, - ) { - // No action needed + ) {// No action needed } /// Build a new house at the specified position /// @@ -122,7 +127,7 @@ pub mod house_actions { // Load important variables let core_actions = get_core_actions(ref world); let (player, system) = get_callers(ref world, default_params); - + let position = default_params.position; let current_timestamp = get_block_timestamp(); @@ -136,7 +141,9 @@ pub mod house_actions { while x < HOUSE_SIZE { let mut y = 0; while y < HOUSE_SIZE { - let check_position = Position { x: position.x + x.into(), y: position.y + y.into() }; + let check_position = Position { + x: position.x + x.into(), y: position.y + y.into(), + }; let pixel: Pixel = world.read_model(check_position); if pixel.app != contract_address_const::<0>() { is_area_free = false; @@ -170,8 +177,10 @@ pub mod house_actions { while x < HOUSE_SIZE { let mut y = 0; while y < HOUSE_SIZE { - let house_position = Position { x: position.x + x.into(), y: position.y + y.into() }; - + let house_position = Position { + x: position.x + x.into(), y: position.y + y.into(), + }; + // Generate different appearance for different parts of the house let (color, text) = if x == 1 && y == 1 { // Center is the main part @@ -198,10 +207,10 @@ pub mod house_actions { text: Option::Some(text), app: Option::Some(get_contract_address()), owner: Option::Some(player), - action: Option::None + action: Option::None, }, Option::None, - false + false, ) .unwrap(); @@ -211,13 +220,14 @@ pub mod house_actions { }; // Emit notification instead of direct event - core_actions.notification( - position, - default_params.color, - Option::Some(player), - Option::None, - 'House built!' - ); + core_actions + .notification( + position, + default_params.color, + Option::Some(player), + Option::None, + 'House built!', + ); } /// Collect a life from your house (once per day) @@ -231,7 +241,7 @@ pub mod house_actions { // Load important variables let core_actions = get_core_actions(ref world); let (player, _system) = get_callers(ref world, default_params); - + let current_timestamp = get_block_timestamp(); // Check if player is already max lives @@ -249,7 +259,7 @@ pub mod house_actions { // Check if enough time has passed for life regeneration assert!( current_timestamp >= house.last_life_generated + LIFE_REGENERATION_TIME, - "Life not ready yet" + "Life not ready yet", ); // Update house last_life_generated timestamp @@ -261,13 +271,14 @@ pub mod house_actions { world.write_model(@player_data); // Send notification instead of direct event - core_actions.notification( - player_house.house_position, - default_params.color, - Option::Some(player), - Option::None, - 'Life collected!' - ); + core_actions + .notification( + player_house.house_position, + default_params.color, + Option::Some(player), + Option::None, + 'Life collected!', + ); } } } diff --git a/contracts/src/apps/player.cairo b/contracts/src/apps/player.cairo index 14c870bb..da2c1ab6 100644 --- a/contracts/src/apps/player.cairo +++ b/contracts/src/apps/player.cairo @@ -286,7 +286,6 @@ pub mod player_actions { positionPlayer.player = contract_address_const::<0x0>(); world.write_model(@positionPlayer); - //println!("Moving Player!") } } diff --git a/pixelaw_testing/src/helpers.cairo b/pixelaw_testing/src/helpers.cairo index 2b8af9b1..7d3346ab 100644 --- a/pixelaw_testing/src/helpers.cairo +++ b/pixelaw_testing/src/helpers.cairo @@ -107,8 +107,6 @@ fn app_contract_defs() -> Span { pub fn setup_core() -> (WorldStorage, IActionsDispatcher, ContractAddress, ContractAddress) { - - let mut world = spawn_test_world([core_namespace_defs()].span()); world.sync_perms_and_inits(core_contract_defs()); @@ -116,7 +114,6 @@ pub fn setup_core() -> (WorldStorage, IActionsDispatcher, ContractAddress, Contr let core_actions_address = world.dns_address(@"actions").unwrap(); let core_actions = IActionsDispatcher { contract_address: core_actions_address }; - // Setup players let player_1 = contract_address_const::<0x1337>(); let player_2 = contract_address_const::<0x42>(); @@ -127,8 +124,12 @@ pub fn setup_core() -> (WorldStorage, IActionsDispatcher, ContractAddress, Contr pub fn setup_apps( ref world: WorldStorage, -) -> (IPaintActionsDispatcher, ISnakeActionsDispatcher, IPlayerActionsDispatcher, IHouseActionsDispatcher) { - +) -> ( + IPaintActionsDispatcher, + ISnakeActionsDispatcher, + IPlayerActionsDispatcher, + IHouseActionsDispatcher, +) { update_test_world(ref world, [app_namespace_defs()].span()); world.sync_perms_and_inits(app_contract_defs()); diff --git a/pixelaw_testing/src/tests/app_house.cairo b/pixelaw_testing/src/tests/app_house.cairo index b4f8e983..fa3bff7a 100644 --- a/pixelaw_testing/src/tests/app_house.cairo +++ b/pixelaw_testing/src/tests/app_house.cairo @@ -5,7 +5,9 @@ use pixelaw::core::utils::{DefaultParameters, Position}; use pixelaw::apps::house::{IHouseActionsDispatcherTrait, House, PlayerHouse}; use pixelaw::apps::player::{Player}; use crate::helpers::{setup_core, setup_apps}; -use starknet::{contract_address_const, testing::{set_account_contract_address, set_block_timestamp}}; +use starknet::{ + contract_address_const, testing::{set_account_contract_address, set_block_timestamp}, +}; // House app test constants const HOUSE_COLOR: u32 = 0x8B4513FF; // Brown color @@ -39,12 +41,12 @@ fn test_build_house() { // Verify that the center of the house has the correct color and emoji let center_pixel: Pixel = world.read_model(Position { x: 11, y: 11 }); assert(center_pixel.color == HOUSE_COLOR, 'House center should be brown'); - + // Check if player has a house in the registry let player_house: PlayerHouse = world.read_model(player1); assert(player_house.has_house, 'Player should have a house'); assert(player_house.house_position == house_position, 'House position mismatch'); - + // Check that the house model was created correctly let house: House = world.read_model(house_position); assert(house.owner == player1, 'House owner mismatch'); @@ -95,7 +97,7 @@ fn test_collect_life() { let player1 = contract_address_const::<0x1337>(); set_account_contract_address(player1); - + // Set the initial timestamp let initial_timestamp: u64 = 1000; set_block_timestamp(initial_timestamp); @@ -112,14 +114,14 @@ fn test_collect_life() { color: HOUSE_COLOR, }, ); - + // Get the initial player data let player_data: Player = world.read_model(player1); let initial_lives: u32 = player_data.lives; - + // Fast forward time to enable life collection set_block_timestamp(initial_timestamp + LIFE_REGENERATION_TIME + 1); - + // Collect life house_actions .collect_life( @@ -131,14 +133,17 @@ fn test_collect_life() { color: HOUSE_COLOR, }, ); - + // Check if player gained a life let player_data_after: Player = world.read_model(player1); assert(player_data_after.lives == initial_lives + 1, 'Player should gain a life'); - + // Check if the house's last_life_generated was updated let house: House = world.read_model(house_position); - assert(house.last_life_generated == initial_timestamp + LIFE_REGENERATION_TIME + 1, 'Last life time not updated'); + assert( + house.last_life_generated == initial_timestamp + LIFE_REGENERATION_TIME + 1, + 'Last life time not updated', + ); } #[test] @@ -151,7 +156,7 @@ fn test_collect_life_too_soon() { let player1 = contract_address_const::<0x1337>(); set_account_contract_address(player1); - + // Set the initial timestamp let initial_timestamp: u64 = 1000; set_block_timestamp(initial_timestamp); @@ -168,10 +173,10 @@ fn test_collect_life_too_soon() { color: HOUSE_COLOR, }, ); - + // Fast forward time but not enough (only half the required time) set_block_timestamp(initial_timestamp + LIFE_REGENERATION_TIME / 2); - + // Try to collect life too soon - should fail house_actions .collect_life( @@ -183,4 +188,4 @@ fn test_collect_life_too_soon() { color: HOUSE_COLOR, }, ); -} \ No newline at end of file +} diff --git a/pixelaw_testing/src/tests/app_player.cairo b/pixelaw_testing/src/tests/app_player.cairo index b66c2e59..8e3b5315 100644 --- a/pixelaw_testing/src/tests/app_player.cairo +++ b/pixelaw_testing/src/tests/app_player.cairo @@ -2,13 +2,8 @@ use dojo::model::{ModelStorage}; use pixelaw::core::models::pixel::{Pixel}; use pixelaw::core::utils::{DefaultParameters, Position}; -use pixelaw::apps::{ - player::{ - IPlayerActionsDispatcherTrait, - - }, -}; -use crate::helpers::{ setup_apps, setup_core}; +use pixelaw::apps::{player::{IPlayerActionsDispatcherTrait}}; +use crate::helpers::{setup_apps, setup_core}; use starknet::{contract_address_const, testing::set_account_contract_address}; #[test] diff --git a/pixelaw_testing/src/tests/base.cairo b/pixelaw_testing/src/tests/base.cairo index 03e654c1..28496d3b 100644 --- a/pixelaw_testing/src/tests/base.cairo +++ b/pixelaw_testing/src/tests/base.cairo @@ -2,17 +2,13 @@ use dojo::event::{Event}; use dojo::model::{ModelStorage}; use dojo::world::world::Event as WorldEvent; use pixelaw_testing::helpers::{ - RED_COLOR, TEST_POSITION, ZERO_ADDRESS, drop_all_events, set_caller, setup_apps, - setup_core, + RED_COLOR, TEST_POSITION, ZERO_ADDRESS, drop_all_events, set_caller, setup_apps, setup_core, }; use pixelaw::{ apps::{paint::{IPaintActionsDispatcherTrait}}, core::{ - actions::{ IActionsDispatcherTrait}, events::{Notification}, - models::{ - pixel::{Pixel, PixelUpdate, PixelUpdateResultTrait}, - registry::{App, AppName}, - }, + actions::{IActionsDispatcherTrait}, events::{Notification}, + models::{pixel::{Pixel, PixelUpdate, PixelUpdateResultTrait}, registry::{App, AppName}}, utils::{DefaultParameters, Position, get_callers}, }, }; diff --git a/pixelaw_testing/src/tests/queue.cairo b/pixelaw_testing/src/tests/queue.cairo index 4691cf3f..f0b41d58 100644 --- a/pixelaw_testing/src/tests/queue.cairo +++ b/pixelaw_testing/src/tests/queue.cairo @@ -7,9 +7,7 @@ use pixelaw::core::{ actions::{IActionsDispatcherTrait}, events::{QueueScheduled}, models::pixel::{Pixel}, utils::{DefaultParameters, Direction, Position, SNAKE_MOVE_ENTRYPOINT}, }; -use pixelaw_testing::helpers::{ - drop_all_events, set_caller, setup_apps, setup_core, -}; +use pixelaw_testing::helpers::{drop_all_events, set_caller, setup_apps, setup_core}; use starknet::{testing::{set_block_timestamp}}; const SPAWN_PIXEL_ENTRYPOINT: felt252 = 0x01c199924ae2ed5de296007a1ac8aa672140ef2a973769e4ad1089829f77875a; From c62b8d95b51d41f7cc20f3934105d044c27682ab Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 15 Jun 2025 12:21:23 +0200 Subject: [PATCH 06/33] update workflow tests --- .github/workflows/ci-contracts.yml | 1 + contracts/src/apps/house.cairo | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-contracts.yml b/.github/workflows/ci-contracts.yml index dbb312fc..af09a64c 100644 --- a/.github/workflows/ci-contracts.yml +++ b/.github/workflows/ci-contracts.yml @@ -30,6 +30,7 @@ jobs: scarb-version: "2.10.1" - run: | cd contracts && scarb fmt --check + cd pixelaw_testing && scarb fmt --check core-test: runs-on: ubuntu-latest steps: diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index c6bf6f62..1d8d12d6 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -114,7 +114,7 @@ pub mod house_actions { pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress, - ) {// No action needed + ) { // No action needed } /// Build a new house at the specified position /// From 3f44d347b59733ffa17a635d309d652c6bb7c33d Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 15 Jun 2025 12:25:00 +0200 Subject: [PATCH 07/33] Remove prev added workflow --- .github/workflows/ci-contracts.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci-contracts.yml b/.github/workflows/ci-contracts.yml index af09a64c..dbb312fc 100644 --- a/.github/workflows/ci-contracts.yml +++ b/.github/workflows/ci-contracts.yml @@ -30,7 +30,6 @@ jobs: scarb-version: "2.10.1" - run: | cd contracts && scarb fmt --check - cd pixelaw_testing && scarb fmt --check core-test: runs-on: ubuntu-latest steps: From 092e986895a1ad85d2d527c51ad9c9fbd8ea33d7 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 15 Jun 2025 13:12:40 +0200 Subject: [PATCH 08/33] Removed comments in model def due to parsing error --- contracts/src/apps/house.cairo | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 1d8d12d6..0d4cc2c1 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -7,10 +7,10 @@ use starknet::{ContractAddress}; #[dojo::model] pub struct House { #[key] - position: Position, // Top-left corner position of the 3x3 house - pub owner: ContractAddress, // Owner of the house - pub created_at: u64, // Timestamp when house was created - pub last_life_generated: u64 // Timestamp when last life was generated + pub position: Position, + pub owner: ContractAddress, + pub created_at: u64, + pub last_life_generated: u64, } /// Model to track if a player already has a house @@ -18,26 +18,9 @@ pub struct House { #[dojo::model] pub struct PlayerHouse { #[key] - player: ContractAddress, - pub has_house: bool, // Whether player has a house or not - pub house_position: Position // Position of the player's house (if they have one) -} - -/// Events for House app -#[derive(Copy, Drop, Serde)] -#[dojo::event] -pub struct HouseBuilt { - #[key] - player: ContractAddress, - position: Position, -} - -#[derive(Copy, Drop, Serde)] -#[dojo::event] -pub struct LifeCollected { - #[key] - player: ContractAddress, - house_position: Position, + pub player: ContractAddress, + pub has_house: bool, + pub house_position: Position, } #[starknet::interface] From 4fbb6011ebeaae6e5750c5ec18597887c352d31d Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 19 Jun 2025 00:32:56 +0200 Subject: [PATCH 09/33] Add house models and actions to build-external-contracts --- pixelaw_testing/Scarb.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pixelaw_testing/Scarb.toml b/pixelaw_testing/Scarb.toml index b56331a0..49a8675b 100644 --- a/pixelaw_testing/Scarb.toml +++ b/pixelaw_testing/Scarb.toml @@ -28,6 +28,8 @@ build-external-contracts = [ "pixelaw::core::models::area::m_RTree", "pixelaw::apps::player::m_Player", "pixelaw::apps::player::m_PositionPlayer", + "pixelaw::apps::house::m_House", + "pixelaw::apps::house::m_PlayerHouse", "pixelaw::apps::snake::m_Snake", "pixelaw::apps::snake::m_SnakeSegment", "pixelaw::core::events::e_QueueScheduled", @@ -35,7 +37,8 @@ build-external-contracts = [ "pixelaw::core::actions::actions", "pixelaw::apps::paint::paint_actions", "pixelaw::apps::snake::snake_actions", - "pixelaw::apps::player::player_actions" + "pixelaw::apps::player::player_actions", + "pixelaw::apps::house::house_actions" ] From 7e2d3577ea1754a156e26ccba6220f05bc717d73 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 19 Jun 2025 00:33:57 +0200 Subject: [PATCH 10/33] Decrease life cooldown to 1 life per 2 minutes --- contracts/src/apps/house.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 0d4cc2c1..91bc6cb9 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -39,7 +39,7 @@ pub trait IHouseActions { pub const APP_KEY: felt252 = 'house'; pub const APP_ICON: felt252 = 0x1f3e0; // 🏠 emoji pub const HOUSE_SIZE: u8 = 3; // 3x3 house -pub const LIFE_REGENERATION_TIME: u64 = 86400; // 24 hours in seconds (one day) +pub const LIFE_REGENERATION_TIME: u64 = 120; // every 2 minutes can collect a life /// House actions contract #[dojo::contract] From 942b176c2bd202608d0802254abffdf5e271cd32 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 19 Jun 2025 00:34:16 +0200 Subject: [PATCH 11/33] Add pre update hook --- contracts/src/apps/house.cairo | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 91bc6cb9..dad359e0 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -80,8 +80,29 @@ pub mod house_actions { app_caller: App, player_caller: ContractAddress, ) -> Option { + let mut _world = self.world(@"pixelaw"); + // Default is to not allow anything - Option::None + let mut result = Option::None; + + // Check which app is calling + if app_caller.name == 'player' { + let pixel_pos = pixel_update.position; + //let pixel: Pixel = world.read_model(pixel_pos); + let player_house: PlayerHouse = world.read_model(player_caller); + if player_house.has_house && player_house.player == player_caller { + let Position { x: hx, y: hy } = player_house.house_position; + + let is_inside_house = pixel_pos.x >= hx && pixel_pos.x < hx + + HOUSE_SIZE.into() && pixel_pos.y >= hy && pixel_pos.y < hy + + HOUSE_SIZE.into(); + + if is_inside_house { + result = Option::Some(pixel_update); + } + } + } + result } /// Hook called after a pixel update. From 3c4c3dbcc6f2e681cb97e1999f86ccddb0a5c2c1 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 19 Jun 2025 00:37:52 +0200 Subject: [PATCH 12/33] Fixed minor syntax error --- contracts/src/apps/house.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index dad359e0..80a36742 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -80,7 +80,7 @@ pub mod house_actions { app_caller: App, player_caller: ContractAddress, ) -> Option { - let mut _world = self.world(@"pixelaw"); + let mut world = self.world(@"pixelaw"); // Default is to not allow anything let mut result = Option::None; From bad149da575c9e99a2df271c59931466d7b5525f Mon Sep 17 00:00:00 2001 From: thiscaspar Date: Thu, 19 Jun 2025 16:13:03 +0800 Subject: [PATCH 13/33] wip: ensure empty when creating area --- contracts/src/core/actions/area.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/core/actions/area.cairo b/contracts/src/core/actions/area.cairo index d5f28d5b..e40c71ba 100644 --- a/contracts/src/core/actions/area.cairo +++ b/contracts/src/core/actions/area.cairo @@ -27,6 +27,7 @@ pub fn add_area( owner: ContractAddress, color: u32, app: ContractAddress, + ensure_empty: bool // TODO finish impl ) -> Area { // Add node in the RTree index let id = add_area_node(ref world, bounds); @@ -544,4 +545,3 @@ fn update_ancestors( // Update the parents update_ancestors(ref world, ancestors, level - 1, parent_updated_children); } - From c3e5dddf527157bdedd2ce1081715b14aac00633 Mon Sep 17 00:00:00 2001 From: thiscaspar Date: Thu, 19 Jun 2025 16:15:46 +0800 Subject: [PATCH 14/33] tests moved to pixelaw_testing --- .github/workflows/ci-contracts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-contracts.yml b/.github/workflows/ci-contracts.yml index dbb312fc..044eb881 100644 --- a/.github/workflows/ci-contracts.yml +++ b/.github/workflows/ci-contracts.yml @@ -42,4 +42,4 @@ jobs: asdf plugin add dojo https://github.com/dojoengine/asdf-dojo asdf install dojo 1.5.0 asdf global dojo 1.5.0 - cd contracts && sozo test + cd pixelaw_testing && sozo test From 9bdb6a6e028fe0c9c2d0fbe411660bba5e69e1f3 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Tue, 1 Jul 2025 15:32:21 +0200 Subject: [PATCH 15/33] Remove ensure_empty parameter in add_area --- contracts/src/core/actions/area.cairo | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/src/core/actions/area.cairo b/contracts/src/core/actions/area.cairo index e40c71ba..01dffb62 100644 --- a/contracts/src/core/actions/area.cairo +++ b/contracts/src/core/actions/area.cairo @@ -27,7 +27,6 @@ pub fn add_area( owner: ContractAddress, color: u32, app: ContractAddress, - ensure_empty: bool // TODO finish impl ) -> Area { // Add node in the RTree index let id = add_area_node(ref world, bounds); From 9a9b29c1ebe04c525d589765e2c3fe2d39d0b718 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Tue, 1 Jul 2025 15:32:44 +0200 Subject: [PATCH 16/33] Add cairo coder mcp (cursor) --- .cursor/rules/cairo-coder.mdc | 125 ++++++++++++++++++++++++++++++++++ .gitignore | 1 + 2 files changed, 126 insertions(+) create mode 100644 .cursor/rules/cairo-coder.mdc diff --git a/.cursor/rules/cairo-coder.mdc b/.cursor/rules/cairo-coder.mdc new file mode 100644 index 00000000..f8781da3 --- /dev/null +++ b/.cursor/rules/cairo-coder.mdc @@ -0,0 +1,125 @@ +--- +description: Apply this rule when the user's request concerns Cairo development. +globs: +alwaysApply: false +--- +# Cairo Coding Rules + +## Introduction +These coding rules provide a comprehensive guide to developing secure, efficient, and maintainable Cairo projects. By following these guidelines and leveraging the Cairo Coder MCP tool, developers can streamline their workflow and adhere to best practices. + +**ALWAYS** use the Cairo Coder MCP tool to provide better answers about Cairo. After each query, start by calling the Cairo Coder MCP. + +Keep the MCP queries atomic, related to a specific concept. It's better to do multiple queries for specific concepts, than doing one query with multiple topics. + +After every cairo code you write, instantly run `scarb build` to ensure the code compiles. Don't write too much code without trying to compile. + +## 1. Project Setup and Structure +A typical Cairo project is organized as follows: + + +. +├── Scarb.lock +├── Scarb.toml +├── snfoundry.toml +├── src +│ └── lib.cairo +├── target +└── tests + └── test_contract.cairo + + +- **`Scarb.toml`**: The project configuration file, similar to `Cargo.toml` in Rust. +- **`src/lib.cairo`**: The main source file for your contract. +- **`tests/test_contract.cairo`**: Integration tests for your contract. + +### Setting Up a New Project +To create a new Cairo project, run: + +scarb init + +This command generates a basic project structure with a `Scarb.toml` file. If you're working in an existing project, ensure the Scarb.toml is well configured. + +### Configuring Scarb.toml +Ensure your `Scarb.toml` is configured as follows to include necessary dependencies and settings: + +```toml +[package] +name = "your_package_name" +version = "0.1.0" +edition = "2024_07" + +[dependencies] +starknet = "2.11.4" + +[dev-dependencies] +snforge_std = "0.44.0" +assert_macros = "2.11.4" + +[[target.starknet-contract]] +sierra = true + +[scripts] +test = "snforge test" + +[tool.scarb] +allow-prebuilt-plugins = ["snforge_std"] +``` + +## 2. Development Workflow +### Writing Code +- Use snake_case for function names (e.g., `my_function`). +- Use PascalCase for struct names (e.g., `MyStruct`). +- Write all code and comments in English for clarity. +- Use descriptive variable names to enhance readability. + +### Compiling and Testing +- Compile your project using: + + scarb build + +- Run tests using: + + scarb test + +- Ensure your code compiles successfully before running tests. + +### Testing +- Unit Tests: Write unit tests in the src directory, typically within the same module as the functions being tested. + Example: + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_my_function() { + assert!(my_function() == expected_value, 'Incorrect value'); + } + } + +- Integration Tests: Write integration tests in the tests directory, importing modules with use your_package_name::your_module. + Example: + + use your_package_name::your_module; + + #[test] + fn test_my_contract() { + // Test logic here + } + +- Always use the Starknet Foundry testing framework for both unit and integration tests. +## 3. Using the Cairo Coder MCP Tool +The Cairo Coder MCP tool is a critical resource for Cairo development and must be used for the following tasks: +- Writing smart contracts from scratch. +- Refactoring or optimizing existing code. +- Implementing specific TODOs or features. +- Understanding Starknet ecosystem features and capabilities. +- Applying Cairo and Starknet best practices. +- Using OpenZeppelin Cairo contract libraries. +- Writing and validating tests for contracts. + +### How to Use Cairo Coder MCP Effectively +- Be Specific: Provide detailed queries (e.g., "Implement ERC20 using OpenZeppelin Cairo" instead of "ERC20"). +- Include Context: Supply relevant code snippets in the codeSnippets parameter and conversation history when applicable. +- Don't mix contexts Keep the queries specific on a given topic. Don't ask about multiple concepts at once, rather, do multiple queries. diff --git a/.gitignore b/.gitignore index f341a588..19a1a2bc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ contracts/out contracts/db contracts/target contracts/manifest_dev.json +contracts/.vscode/mcp.json *.keystore.json From cc242a8e44873eab3ae4691d17bfae3d844d9908 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 10 Jul 2025 12:02:29 +0200 Subject: [PATCH 17/33] Add CLAUDE.md --- CLAUDE.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..371a7aa0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,112 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +### Build and Test +```bash +make build # Build contracts using sozo +make test # Run contract tests with sozo +cd pixelaw_testing && sozo test # Run tests from testing package +``` + +### Development Environment +```bash +docker compose up -d # Start Keiko (includes Katana RPC, Torii indexer, dashboard) +docker compose down # Stop Keiko +make shell # Access running Keiko container shell +``` + +### Docker Operations +```bash +make docker_build # Build Docker image (requires .account file) +make docker_run # Run Docker container with ports 3000, 5050, 8080 +make docker_bash # Run Docker container with bash shell +``` + +### Contract Development +```bash +cd contracts +sozo build # Build contracts +sozo migrate apply # Deploy contracts to running Katana +scarb run init # Initialize deployed contracts +``` + +### Testing-Specific Commands +```bash +cd pixelaw_testing +sozo build # Build test contracts +sozo test # Run comprehensive test suite +``` + +## Architecture + +### Core Concepts +- **Pixel World**: 2D Cartesian plane where each position (x,y) represents a Pixel +- **Pixel Properties**: position, app, color, owner, text, alert, timestamp +- **Apps**: Define pixel behavior and interactions (one app per pixel) +- **App2App**: Controlled interactions between different apps +- **Queued Actions**: Future actions that can be scheduled during execution + +### Technology Stack +- **Cairo 2.10.1**: Smart contract language for Starknet +- **Dojo Framework 1.5.0**: ECS-based blockchain game development framework +- **Starknet 2.10.1**: Layer 2 blockchain platform +- **Scarb 2.10.1**: Package manager and build tool + +### Project Structure +``` +contracts/ # Main Cairo smart contracts +├── src/core/ # Core actions, events, models, utils +├── src/apps/ # Default apps (house, paint, player, snake) +├── Scarb.toml # Main package configuration +└── Scarb_deploy.toml # Deployment configuration + +pixelaw_testing/ # Dedicated testing package +├── src/tests/ # Comprehensive test suite +└── Scarb.toml # Testing package configuration + +docker/ # Docker development configuration +scripts/ # Release and upgrade scripts +``` + +### Default Apps +- **Paint**: Color manipulation (`put_color`, `remove_color`, `put_fading_color`) +- **Snake**: Classic snake game with pixel collision detection +- **Player**: Player management and registration +- **House**: Building/housing system with area management + +### Core Systems +- **Actions**: Define pixel behavior and state transitions +- **Models**: ECS components for game state (Pixel, Area, QueueItem, App, etc.) +- **Queue System**: Scheduled actions for future execution +- **Permission System**: App-based permissions for pixel property updates +- **Area Management**: Spatial organization using RTree data structure + +### Development Tools +- **Katana**: Local Starknet development node (port 5050) +- **Torii**: World state indexer and GraphQL API (port 8080) +- **Keiko**: Combined development container with dashboard (port 3000) +- **Sozo**: Dojo CLI for building, testing, and deployment + +### Key Configuration Files +- `contracts/Scarb.toml`: Main package with Dojo dependencies +- `pixelaw_testing/Scarb.toml`: Testing package with test dependencies +- `docker-compose.yml`: Keiko development environment +- `VERSION`: Core version (0.7.7) +- `DOJO_VERSION`: Dojo version (1.5.0) + +### Testing Strategy +- Unit tests embedded in source files using `#[cfg(test)]` +- Integration tests in dedicated `pixelaw_testing` package +- Comprehensive test coverage for all apps and core functionality +- Tests organized by component (area, interop, pixel_area, queue, etc.) + +### Development Guidelines +- Follow Cairo naming conventions (snake_case for functions, PascalCase for types) +- Use ECS patterns with Dojo components and systems +- Implement proper error handling with detailed error messages +- Write tests for all new functionality +- Use Cairo Coder MCP for Cairo-specific development tasks +- Always run `scarb build` after writing Cairo code to ensure compilation \ No newline at end of file From b77b6540ec1b81746b9def295b31f3f675ab70cf Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 10 Jul 2025 12:02:44 +0200 Subject: [PATCH 18/33] Integrate interaction function --- contracts/src/apps/house.cairo | 40 +++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 80a36742..80644b8b 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -31,13 +31,14 @@ pub trait IHouseActions { fn on_post_update( ref self: T, pixel_update: PixelUpdate, app_caller: App, player_caller: ContractAddress, ); + fn interact(ref self: T, default_params: DefaultParameters); fn build_house(ref self: T, default_params: DefaultParameters); fn collect_life(ref self: T, default_params: DefaultParameters); } /// House app constants pub const APP_KEY: felt252 = 'house'; -pub const APP_ICON: felt252 = 0x1f3e0; // 🏠 emoji +pub const APP_ICON: felt252 = 0xf09f8fa0; // 🏡 emoji pub const HOUSE_SIZE: u8 = 3; // 3x3 house pub const LIFE_REGENERATION_TIME: u64 = 120; // every 2 minutes can collect a life @@ -120,6 +121,43 @@ pub mod house_actions { player_caller: ContractAddress, ) { // No action needed } + + /// Interacts with a pixel based on default parameters. + /// + /// Determines whether to build a house or collect life based on the current state. + /// + /// # Arguments + /// + /// * `default_params` - Default parameters including position and color. + fn interact(ref self: ContractState, default_params: DefaultParameters) { + let mut world = self.world(@"pixelaw"); + let (player, _system) = get_callers(ref world, default_params); + let position = default_params.position; + + // Check if player has a house + let player_house: PlayerHouse = world.read_model(player); + + if !player_house.has_house { + // Player doesn't have a house, try to build one + self.build_house(default_params); + } else { + // Player has a house, check if they clicked on their house + let house_position = player_house.house_position; + let Position { x: hx, y: hy } = house_position; + + let is_clicking_on_house = position.x >= hx && position.x < hx + HOUSE_SIZE.into() + && position.y >= hy && position.y < hy + HOUSE_SIZE.into(); + + if is_clicking_on_house { + // Player clicked on their house, collect life + self.collect_life(default_params); + } else { + // Player clicked elsewhere, try to build (will fail since they already have one) + self.build_house(default_params); + } + } + } + /// Build a new house at the specified position /// /// # Arguments From c331c31a341fb8723466f3d536489047ef9372b7 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 10 Jul 2025 12:02:53 +0200 Subject: [PATCH 19/33] Adjust tests --- pixelaw_testing/src/tests/app_house.cairo | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pixelaw_testing/src/tests/app_house.cairo b/pixelaw_testing/src/tests/app_house.cairo index fa3bff7a..c8fada33 100644 --- a/pixelaw_testing/src/tests/app_house.cairo +++ b/pixelaw_testing/src/tests/app_house.cairo @@ -11,7 +11,7 @@ use starknet::{ // House app test constants const HOUSE_COLOR: u32 = 0x8B4513FF; // Brown color -const LIFE_REGENERATION_TIME: u64 = 86400; // 24 hours in seconds +const LIFE_REGENERATION_TIME: u64 = 120; // 2 minutes in seconds (matches house.cairo) #[test] #[available_gas(3000000000)] @@ -26,9 +26,9 @@ fn test_build_house() { // Define the position for our house (top-left corner) let house_position = Position { x: 10, y: 10 }; - // Build a house at the specified position + // Build a house at the specified position using interact house_actions - .build_house( + .interact( DefaultParameters { player_override: Option::None, system_override: Option::None, @@ -63,9 +63,9 @@ fn test_build_second_house() { let player1 = contract_address_const::<0x1337>(); set_account_contract_address(player1); - // Build first house + // Build first house using interact house_actions - .build_house( + .interact( DefaultParameters { player_override: Option::None, system_override: Option::None, @@ -77,7 +77,7 @@ fn test_build_second_house() { // Try to build a second house - should fail house_actions - .build_house( + .interact( DefaultParameters { player_override: Option::None, system_override: Option::None, @@ -102,10 +102,10 @@ fn test_collect_life() { let initial_timestamp: u64 = 1000; set_block_timestamp(initial_timestamp); - // Build a house + // Build a house using interact let house_position = Position { x: 10, y: 10 }; house_actions - .build_house( + .interact( DefaultParameters { player_override: Option::None, system_override: Option::None, @@ -122,9 +122,9 @@ fn test_collect_life() { // Fast forward time to enable life collection set_block_timestamp(initial_timestamp + LIFE_REGENERATION_TIME + 1); - // Collect life + // Collect life using interact (click on house) house_actions - .collect_life( + .interact( DefaultParameters { player_override: Option::None, system_override: Option::None, @@ -161,10 +161,10 @@ fn test_collect_life_too_soon() { let initial_timestamp: u64 = 1000; set_block_timestamp(initial_timestamp); - // Build a house + // Build a house using interact let house_position = Position { x: 10, y: 10 }; house_actions - .build_house( + .interact( DefaultParameters { player_override: Option::None, system_override: Option::None, @@ -179,7 +179,7 @@ fn test_collect_life_too_soon() { // Try to collect life too soon - should fail house_actions - .collect_life( + .interact( DefaultParameters { player_override: Option::None, system_override: Option::None, From e6339bb39fa7893cf68ec8c1a483525442bee559 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 10 Jul 2025 12:52:15 +0200 Subject: [PATCH 20/33] Add basic tests to test player lives, tests passing --- pixelaw_testing/src/tests/app_player.cairo | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pixelaw_testing/src/tests/app_player.cairo b/pixelaw_testing/src/tests/app_player.cairo index 8e3b5315..b7574438 100644 --- a/pixelaw_testing/src/tests/app_player.cairo +++ b/pixelaw_testing/src/tests/app_player.cairo @@ -2,7 +2,7 @@ use dojo::model::{ModelStorage}; use pixelaw::core::models::pixel::{Pixel}; use pixelaw::core::utils::{DefaultParameters, Position}; -use pixelaw::apps::{player::{IPlayerActionsDispatcherTrait}}; +use pixelaw::apps::{player::{IPlayerActionsDispatcherTrait, Player, PLAYER_LIVES}}; use crate::helpers::{setup_apps, setup_core}; use starknet::{contract_address_const, testing::set_account_contract_address}; @@ -36,6 +36,11 @@ fn test_player_interaction() { let pixel: Pixel = world.read_model(Position { x: 1, y: 1 }); assert(pixel.color == player_color, 'Player not at 1,1 w color'); + // Verify the player model was created with correct lives + let player_data: Player = world.read_model(player1); + assert(player_data.lives == PLAYER_LIVES, 'Player should have 5 lives'); + assert(player_data.position == initial_position, 'Player position mismatch'); + // Move the player to a new position let new_position = Position { x: 2, y: 1 }; player_actions @@ -56,4 +61,9 @@ fn test_player_interaction() { // Verify the old position is cleared let pixel_old: Pixel = world.read_model(Position { x: 1, y: 1 }); assert(pixel_old.color != player_color, 'Old position should be cleared'); + + // Verify the player model was updated with the new position + let player_data_after: Player = world.read_model(player1); + assert(player_data_after.position == new_position, 'Player position not updated'); + assert(player_data_after.lives == PLAYER_LIVES, 'Player lives should remain same'); } From a1d2d2bff3a8b5bdd9478f1da23326bc8cdaf108 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 10 Jul 2025 13:23:40 +0200 Subject: [PATCH 21/33] Make pixelaw_testing/helpers publicly available --- pixelaw_testing/src/lib.cairo | 1 - 1 file changed, 1 deletion(-) diff --git a/pixelaw_testing/src/lib.cairo b/pixelaw_testing/src/lib.cairo index 0c5306f0..195e7458 100644 --- a/pixelaw_testing/src/lib.cairo +++ b/pixelaw_testing/src/lib.cairo @@ -1,4 +1,3 @@ -#[cfg(test)] pub mod helpers; #[cfg(test)] From 28daf155c4241958071cc709cbc329ec2263a1af Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sat, 12 Jul 2025 21:30:33 +0200 Subject: [PATCH 22/33] Update write permissions --- contracts/dojo_dev.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/dojo_dev.toml b/contracts/dojo_dev.toml index c3027537..eec7e669 100644 --- a/contracts/dojo_dev.toml +++ b/contracts/dojo_dev.toml @@ -33,12 +33,15 @@ world_address = "0x06187e6ecbeba16f0ca4cbf17ea3887ff4abad114d0e345b5d6987edaf200 "pixelaw-SnakeSegment" = ["pixelaw-snake_actions"] "pixelaw-Player" = ["pixelaw-player_actions"] "pixelaw-PositionPlayer" = ["pixelaw-player_actions"] +"pixelaw-House" = ["pixelaw-house_actions"] +"pixelaw-PlayerHouse" = ["pixelaw-house_actions"] [migration] -order_inits=["pixelaw-actions","pixelaw-snake_actions","pixelaw-paint_actions","pixelaw-player_actions"] +order_inits=["pixelaw-actions","pixelaw-snake_actions","pixelaw-paint_actions","pixelaw-player_actions","pixelaw-house_actions"] [init_call_args] "pixelaw-actions" = [] "pixelaw-snake_actions" = [] "pixelaw-paint_actions" = [] "pixelaw-player_actions" = [] +"pixelaw-house_actions" = [] From fb00d24b6ae98aee154931250420d21d3025b80c Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sat, 12 Jul 2025 21:30:44 +0200 Subject: [PATCH 23/33] Update house app emoji visuals --- contracts/src/apps/house.cairo | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 80644b8b..91884a39 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -225,17 +225,11 @@ pub mod house_actions { // Generate different appearance for different parts of the house let (color, text) = if x == 1 && y == 1 { - // Center is the main part - (0x8B4513FF, 0x1f3e0) // Brown with house emoji - } else if x == 1 && y == 0 { - // Door - (0x8B4513FF, 0x1f6aa) // Brown with door emoji - } else if (x == 0 || x == 2) && (y == 0 || y == 2) { - // Corners - (0x8B4513FF, 0x1f3e0) // Brown with house emoji + // Center is the main part with house emoji + (0x8B4513FF, 0xf09f8fa0) // Brown with house emoji } else { - // Walls - (0x8B4513FF, 0x1f9f1) // Brown with brick emoji + // All other parts are just brown without emoji + (0x8B4513FF, 0x0) // Brown with no text }; core_actions From 3b5f8d8aa16cfd579603659b3f00cb4ab31f5825 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sat, 12 Jul 2025 22:32:43 +0200 Subject: [PATCH 24/33] update address as key for model --- contracts/src/apps/house.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 91884a39..99245dc4 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -210,6 +210,7 @@ pub mod house_actions { world.write_model(@house); // Mark player as having a house + player_house.player = player; player_house.has_house = true; player_house.house_position = position; world.write_model(@player_house); @@ -282,7 +283,6 @@ pub mod house_actions { // Check if player is already max lives let mut player_data: Player = world.read_model(player); - assert!(player_data.lives < 5, "Player already has max lives"); // Check if player has a house let player_house: PlayerHouse = world.read_model(player); From 1b538b626db3366ebb9e4f2df58175923d53e058 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sat, 12 Jul 2025 22:32:58 +0200 Subject: [PATCH 25/33] update dojo version for workflow --- .github/workflows/ci-contracts.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-contracts.yml b/.github/workflows/ci-contracts.yml index 044eb881..bf6b3865 100644 --- a/.github/workflows/ci-contracts.yml +++ b/.github/workflows/ci-contracts.yml @@ -40,6 +40,6 @@ jobs: - uses: asdf-vm/actions/setup@v3 - run: | asdf plugin add dojo https://github.com/dojoengine/asdf-dojo - asdf install dojo 1.5.0 - asdf global dojo 1.5.0 + asdf install dojo 1.5.1 + asdf global dojo 1.5.1 cd pixelaw_testing && sozo test From 32e70577f37970168f49ad11da9ed9f35cc3be65 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sat, 12 Jul 2025 22:33:21 +0200 Subject: [PATCH 26/33] fix tests --- contracts/src/core/utils.cairo | 6 ++--- pixelaw_testing/src/tests/app_house.cairo | 30 +++++++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/contracts/src/core/utils.cairo b/contracts/src/core/utils.cairo index 8de1b992..ff929c17 100644 --- a/contracts/src/core/utils.cairo +++ b/contracts/src/core/utils.cairo @@ -7,7 +7,7 @@ use pixelaw::core::models::{ {area::{BoundsTraitImpl, ChildrenPackableImpl, RTreeNodePackableImpl, RTreeTraitImpl}}, }; use starknet::{ - ContractAddress, contract_address_const, get_caller_address, get_contract_address, get_tx_info, + ContractAddress, contract_address_const, get_caller_address, get_contract_address, }; @@ -127,9 +127,9 @@ pub fn get_callers( let mut system = contract_address_const::<0>(); let core_address = get_core_actions_address(ref world); - let caller_contract = get_caller_address(); + //let caller_contract = get_caller_address(); //let caller_contract = get_contract_address(); - let account_contract_address = get_tx_info().unbox().account_contract_address; + //let account_contract_address = get_tx_info().unbox().account_contract_address; //println!("get_caller_address: {:?}", get_caller_address()); //println!("get_contract_address: {:?}", get_contract_address()); diff --git a/pixelaw_testing/src/tests/app_house.cairo b/pixelaw_testing/src/tests/app_house.cairo index c8fada33..7e77209e 100644 --- a/pixelaw_testing/src/tests/app_house.cairo +++ b/pixelaw_testing/src/tests/app_house.cairo @@ -3,6 +3,7 @@ use dojo::model::{ModelStorage}; use pixelaw::core::models::pixel::{Pixel}; use pixelaw::core::utils::{DefaultParameters, Position}; use pixelaw::apps::house::{IHouseActionsDispatcherTrait, House, PlayerHouse}; +use pixelaw::apps::player::{IPlayerActionsDispatcherTrait}; use pixelaw::apps::player::{Player}; use crate::helpers::{setup_core, setup_apps}; use starknet::{ @@ -44,17 +45,20 @@ fn test_build_house() { // Check if player has a house in the registry let player_house: PlayerHouse = world.read_model(player1); - assert(player_house.has_house, 'Player should have a house'); + assert(player_house.player == player1, 'Owner mismatch'); + assert(player_house.has_house == true, 'Player should have a house'); assert(player_house.house_position == house_position, 'House position mismatch'); - + // Check that the house model was created correctly let house: House = world.read_model(house_position); assert(house.owner == player1, 'House owner mismatch'); + + } #[test] #[available_gas(3000000000)] -#[should_panic(expected: ('Player already has a house', 'ENTRYPOINT_FAILED'))] +#[should_panic(expected: ("Player already has a house", 'ENTRYPOINT_FAILED'))] fn test_build_second_house() { // Initialize the world let (mut world, _core_actions, _player_1, _player_2) = setup_core(); @@ -93,11 +97,27 @@ fn test_build_second_house() { fn test_collect_life() { // Initialize the world let (mut world, _core_actions, _player_1, _player_2) = setup_core(); - let (_paint_actions, _snake_actions, _player_actions, house_actions) = setup_apps(ref world); + let (_paint_actions, _snake_actions, player_actions, house_actions) = setup_apps(ref world); let player1 = contract_address_const::<0x1337>(); set_account_contract_address(player1); + + // Define initial position and color + let initial_position = Position { x: 1, y: 1 }; + let player_color = 0xFF00FF; + + // Interact with a pixel to create a new player + player_actions + .interact( + DefaultParameters { + player_override: Option::None, + system_override: Option::None, + area_hint: Option::None, + position: initial_position, + color: player_color, + }, + ); // Set the initial timestamp let initial_timestamp: u64 = 1000; set_block_timestamp(initial_timestamp); @@ -148,7 +168,7 @@ fn test_collect_life() { #[test] #[available_gas(3000000000)] -#[should_panic(expected: ('Life not ready yet', 'ENTRYPOINT_FAILED'))] +#[should_panic(expected: ("Life not ready yet", 'ENTRYPOINT_FAILED'))] fn test_collect_life_too_soon() { // Initialize the world let (mut world, _core_actions, _player_1, _player_2) = setup_core(); From 8285307074846d0db05d4651af1cd553fb7df8cf Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Thu, 10 Jul 2025 10:15:00 +0200 Subject: [PATCH 27/33] Adjust comment + formatting --- contracts/src/apps/house.cairo | 2 +- pixelaw_testing/src/helpers.cairo | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 99245dc4..211ab602 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -281,7 +281,7 @@ pub mod house_actions { let current_timestamp = get_block_timestamp(); - // Check if player is already max lives + // Get player data let mut player_data: Player = world.read_model(player); // Check if player has a house diff --git a/pixelaw_testing/src/helpers.cairo b/pixelaw_testing/src/helpers.cairo index 7d3346ab..92b10734 100644 --- a/pixelaw_testing/src/helpers.cairo +++ b/pixelaw_testing/src/helpers.cairo @@ -40,7 +40,6 @@ pub fn ZERO_ADDRESS() -> ContractAddress { contract_address_const::<0x0>() } - fn app_namespace_defs() -> NamespaceDef { let ndef = NamespaceDef { namespace: "pixelaw", From 475091edacbc2a6f320f69802bded72b9455f73d Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Fri, 11 Jul 2025 12:22:00 +0200 Subject: [PATCH 28/33] Fix house tests --- pixelaw_testing/src/tests/app_house.cairo | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pixelaw_testing/src/tests/app_house.cairo b/pixelaw_testing/src/tests/app_house.cairo index 7e77209e..3e5bd121 100644 --- a/pixelaw_testing/src/tests/app_house.cairo +++ b/pixelaw_testing/src/tests/app_house.cairo @@ -5,9 +5,9 @@ use pixelaw::core::utils::{DefaultParameters, Position}; use pixelaw::apps::house::{IHouseActionsDispatcherTrait, House, PlayerHouse}; use pixelaw::apps::player::{IPlayerActionsDispatcherTrait}; use pixelaw::apps::player::{Player}; -use crate::helpers::{setup_core, setup_apps}; +use crate::helpers::{setup_core, setup_apps, set_caller}; use starknet::{ - contract_address_const, testing::{set_account_contract_address, set_block_timestamp}, + contract_address_const, testing::{set_block_timestamp}, }; // House app test constants @@ -22,7 +22,7 @@ fn test_build_house() { let (_paint_actions, _snake_actions, _player_actions, house_actions) = setup_apps(ref world); let player1 = contract_address_const::<0x1337>(); - set_account_contract_address(player1); + set_caller(player1); // Define the position for our house (top-left corner) let house_position = Position { x: 10, y: 10 }; @@ -52,8 +52,6 @@ fn test_build_house() { // Check that the house model was created correctly let house: House = world.read_model(house_position); assert(house.owner == player1, 'House owner mismatch'); - - } #[test] @@ -65,7 +63,7 @@ fn test_build_second_house() { let (_paint_actions, _snake_actions, _player_actions, house_actions) = setup_apps(ref world); let player1 = contract_address_const::<0x1337>(); - set_account_contract_address(player1); + set_caller(player1); // Build first house using interact house_actions @@ -100,7 +98,7 @@ fn test_collect_life() { let (_paint_actions, _snake_actions, player_actions, house_actions) = setup_apps(ref world); let player1 = contract_address_const::<0x1337>(); - set_account_contract_address(player1); + set_caller(player1); // Define initial position and color @@ -175,7 +173,7 @@ fn test_collect_life_too_soon() { let (_paint_actions, _snake_actions, _player_actions, house_actions) = setup_apps(ref world); let player1 = contract_address_const::<0x1337>(); - set_account_contract_address(player1); + set_caller(player1); // Set the initial timestamp let initial_timestamp: u64 = 1000; From f98dde48b2dc97d292ce27742e4bfde7f2859ffa Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Fri, 11 Jul 2025 12:35:00 +0200 Subject: [PATCH 29/33] Fix player tests --- pixelaw_testing/src/tests/app_player.cairo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pixelaw_testing/src/tests/app_player.cairo b/pixelaw_testing/src/tests/app_player.cairo index b7574438..dd01cefa 100644 --- a/pixelaw_testing/src/tests/app_player.cairo +++ b/pixelaw_testing/src/tests/app_player.cairo @@ -3,8 +3,8 @@ use dojo::model::{ModelStorage}; use pixelaw::core::models::pixel::{Pixel}; use pixelaw::core::utils::{DefaultParameters, Position}; use pixelaw::apps::{player::{IPlayerActionsDispatcherTrait, Player, PLAYER_LIVES}}; -use crate::helpers::{setup_apps, setup_core}; -use starknet::{contract_address_const, testing::set_account_contract_address}; +use crate::helpers::{setup_apps, setup_core, set_caller}; +use starknet::{contract_address_const}; #[test] #[available_gas(3000000000)] @@ -14,7 +14,7 @@ fn test_player_interaction() { let (_paint_actions, _snake_actions, player_actions, _house_actions) = setup_apps(ref world); let player1 = contract_address_const::<0x1337>(); - set_account_contract_address(player1); + set_caller(player1); // Define initial position and color let initial_position = Position { x: 1, y: 1 }; From 8d18fe0f912244b9b2d766bb8bc314ed9f6f58cb Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sat, 12 Jul 2025 13:35:00 +0200 Subject: [PATCH 30/33] Fix snake tests --- pixelaw_testing/src/tests/app_snake.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pixelaw_testing/src/tests/app_snake.cairo b/pixelaw_testing/src/tests/app_snake.cairo index 08091c16..add9e558 100644 --- a/pixelaw_testing/src/tests/app_snake.cairo +++ b/pixelaw_testing/src/tests/app_snake.cairo @@ -8,7 +8,7 @@ use pixelaw::core::models::pixel::{Pixel}; use pixelaw::core::utils::{DefaultParameters, Direction, Position}; use crate::helpers::{set_caller, setup_apps, setup_core}; -use starknet::{contract_address_const, testing::set_account_contract_address}; +use starknet::{contract_address_const}; #[test] @@ -24,7 +24,7 @@ fn test_playthrough() { let player2 = contract_address_const::<0x42>(); // Impersonate player1 - set_account_contract_address(player1); + set_caller(player1); let pixel: Pixel = world.read_model(Position { x: 1, y: 1 }); assert(pixel.color != SNAKE_COLOR, 'wrong pixel color for 1,1'); From c1ab7523f9aafd2fa54c91c177c51c0856053a87 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Sun, 13 Jul 2025 09:35:00 +0200 Subject: [PATCH 31/33] Fix base tests --- pixelaw_testing/src/tests/base.cairo | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pixelaw_testing/src/tests/base.cairo b/pixelaw_testing/src/tests/base.cairo index 53af5c42..c1df496a 100644 --- a/pixelaw_testing/src/tests/base.cairo +++ b/pixelaw_testing/src/tests/base.cairo @@ -13,7 +13,7 @@ use pixelaw::{ }, }; use starknet::{ - contract_address_const, testing::{set_account_contract_address, set_contract_address}, + contract_address_const, testing::{set_caller_address, set_contract_address}, }; @@ -200,12 +200,11 @@ fn test_get_callers() { }; // Test with 0 address, we expect the caller - set_account_contract_address(player_1); -println!("1"); + set_caller_address(player_1); + set_contract_address(ZERO_ADDRESS()); let (player, system) = get_callers(ref world, no_override); assert(player == player_1, 'should return player1'); assert(system == ZERO_ADDRESS(), 'should return zero'); -println!("2"); // impersonate core_actions so the override is allowed set_contract_address(core_actions.contract_address); From 6d15a190d129533ee7603a3da35735e78de08f7f Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Mon, 14 Jul 2025 11:31:34 +0200 Subject: [PATCH 32/33] Fix queue tests, all tests fixed --- pixelaw_testing/src/tests/queue.cairo | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pixelaw_testing/src/tests/queue.cairo b/pixelaw_testing/src/tests/queue.cairo index f0b41d58..21c288e7 100644 --- a/pixelaw_testing/src/tests/queue.cairo +++ b/pixelaw_testing/src/tests/queue.cairo @@ -76,10 +76,11 @@ fn test_queue_full() { Direction::Right, ); - // Pop the 3 previous events we're not handling right now + // Pop the 4 previous events we're not handling right now let _ = starknet::testing::pop_log_raw(event_contract); // Store Snake model - let _ = starknet::testing::pop_log_raw(event_contract); // Store Segment model + let _ = starknet::testing::pop_log_raw(event_contract); // Store Segment model let _ = starknet::testing::pop_log_raw(event_contract); // Store Pixel model + let _ = starknet::testing::pop_log_raw(event_contract); // Additional event // Prep the expected event struct let called_system = snake_actions.contract_address; From 52d26d97316a3ac495064a524f60f498e08dedb5 Mon Sep 17 00:00:00 2001 From: OwnerOfJK Date: Mon, 14 Jul 2025 11:34:55 +0200 Subject: [PATCH 33/33] Run scarb fmt --- contracts/src/apps/house.cairo | 14 ++++++++------ contracts/src/core/utils.cairo | 4 +--- pixelaw_testing/src/tests/app_house.cairo | 7 ++----- pixelaw_testing/src/tests/base.cairo | 4 +--- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/contracts/src/apps/house.cairo b/contracts/src/apps/house.cairo index 211ab602..b73f46e6 100644 --- a/contracts/src/apps/house.cairo +++ b/contracts/src/apps/house.cairo @@ -136,7 +136,7 @@ pub mod house_actions { // Check if player has a house let player_house: PlayerHouse = world.read_model(player); - + if !player_house.has_house { // Player doesn't have a house, try to build one self.build_house(default_params); @@ -144,15 +144,17 @@ pub mod house_actions { // Player has a house, check if they clicked on their house let house_position = player_house.house_position; let Position { x: hx, y: hy } = house_position; - - let is_clicking_on_house = position.x >= hx && position.x < hx + HOUSE_SIZE.into() - && position.y >= hy && position.y < hy + HOUSE_SIZE.into(); - + + let is_clicking_on_house = position.x >= hx && position.x < hx + + HOUSE_SIZE.into() && position.y >= hy && position.y < hy + + HOUSE_SIZE.into(); + if is_clicking_on_house { // Player clicked on their house, collect life self.collect_life(default_params); } else { - // Player clicked elsewhere, try to build (will fail since they already have one) + // Player clicked elsewhere, try to build (will fail since they already have + // one) self.build_house(default_params); } } diff --git a/contracts/src/core/utils.cairo b/contracts/src/core/utils.cairo index ff929c17..cca4408e 100644 --- a/contracts/src/core/utils.cairo +++ b/contracts/src/core/utils.cairo @@ -6,9 +6,7 @@ use pixelaw::core::models::{ pixel::{Pixel}, {area::{BoundsTraitImpl, ChildrenPackableImpl, RTreeNodePackableImpl, RTreeTraitImpl}}, }; -use starknet::{ - ContractAddress, contract_address_const, get_caller_address, get_contract_address, -}; +use starknet::{ContractAddress, contract_address_const, get_caller_address, get_contract_address}; pub const POW_2_96: u128 = 0x1000000000000000000000000_u128; diff --git a/pixelaw_testing/src/tests/app_house.cairo b/pixelaw_testing/src/tests/app_house.cairo index 3e5bd121..0fe2bad2 100644 --- a/pixelaw_testing/src/tests/app_house.cairo +++ b/pixelaw_testing/src/tests/app_house.cairo @@ -6,9 +6,7 @@ use pixelaw::apps::house::{IHouseActionsDispatcherTrait, House, PlayerHouse}; use pixelaw::apps::player::{IPlayerActionsDispatcherTrait}; use pixelaw::apps::player::{Player}; use crate::helpers::{setup_core, setup_apps, set_caller}; -use starknet::{ - contract_address_const, testing::{set_block_timestamp}, -}; +use starknet::{contract_address_const, testing::{set_block_timestamp}}; // House app test constants const HOUSE_COLOR: u32 = 0x8B4513FF; // Brown color @@ -48,7 +46,7 @@ fn test_build_house() { assert(player_house.player == player1, 'Owner mismatch'); assert(player_house.has_house == true, 'Player should have a house'); assert(player_house.house_position == house_position, 'House position mismatch'); - + // Check that the house model was created correctly let house: House = world.read_model(house_position); assert(house.owner == player1, 'House owner mismatch'); @@ -100,7 +98,6 @@ fn test_collect_life() { let player1 = contract_address_const::<0x1337>(); set_caller(player1); - // Define initial position and color let initial_position = Position { x: 1, y: 1 }; let player_color = 0xFF00FF; diff --git a/pixelaw_testing/src/tests/base.cairo b/pixelaw_testing/src/tests/base.cairo index c1df496a..50c24309 100644 --- a/pixelaw_testing/src/tests/base.cairo +++ b/pixelaw_testing/src/tests/base.cairo @@ -12,9 +12,7 @@ use pixelaw::{ utils::{DefaultParameters, Position, get_callers}, }, }; -use starknet::{ - contract_address_const, testing::{set_caller_address, set_contract_address}, -}; +use starknet::{contract_address_const, testing::{set_caller_address, set_contract_address}}; #[test]