### Creating a New Elixir Project Source: https://github.com/iacobson/ecspanse/blob/main/guides/getting_started.md This command initializes a new Elixir project named 'demo' with a supervision tree, which is a common setup for robust Elixir applications. It creates the basic directory structure and configuration files. ```Bash mix new demo --sup ``` -------------------------------- ### Configuring Ecspanse in the Main Module Source: https://github.com/iacobson/ecspanse/blob/main/guides/getting_started.md This Elixir code demonstrates how to configure Ecspanse within the primary `Demo` module using `use Ecspanse`. The `setup/1` callback is mandatory and serves as the entry point for scheduling application systems, receiving initial data. ```Elixir defmodule Demo do use Ecspanse @impl Ecspanse def setup(data) do data end end ``` -------------------------------- ### Setting Up Ecspanse Test Environment and Spawning Entity in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This `setup` block initializes the test environment by starting the Ecspanse server in test mode and enabling the system debugger, which allows manual system execution. It then spawns a `Hero` entity, fetches its `Position` and `Energy` components, and asserts their initial states (position at 0,0 and energy at 50) to ensure a consistent starting point for tests. ```Elixir setup do {:ok, _pid} = start_supervised({DemoTest, :test}) Ecspanse.System.debug() hero_entity = %Ecspanse.Entity{} = Ecspanse.Command.spawn_entity!(Demo.Entities.Hero.new()) {:ok, position_component} = Demo.Components.Position.fetch(hero_entity) assert position_component.x == 0 assert position_component.y == 0 {:ok, energy_component} = Demo.Components.Energy.fetch(hero_entity) assert energy_component.current == 50 {:ok, hero_entity: hero_entity, energy_component: energy_component} end ``` -------------------------------- ### Starting Ecspanse Server via Application Supervision Source: https://github.com/iacobson/ecspanse/blob/main/guides/getting_started.md This Elixir `Application` module defines the supervision tree for the `Demo` application. By including `Demo` (which uses Ecspanse) in the `children` list, the Ecspanse server is started and supervised as part of the application's lifecycle. ```Elixir defmodule Demo.Application do @moduledoc false use Application @impl true def start(_type, _args) do children = [ Demo ] opts = [strategy: :one_for_one, name: Demo.Supervisor] Supervisor.start_link(children, opts) end end ``` -------------------------------- ### Starting the Elixir Application in IEx Source: https://github.com/iacobson/ecspanse/blob/main/guides/getting_started.md This Bash command starts the Elixir application within an interactive Elixir shell (IEx). The `-S mix` flag ensures that the `mix` environment is loaded, allowing the application's supervision tree, including the Ecspanse server, to be initialized. ```Bash iex -S mix ``` -------------------------------- ### Adding Resource Finding System to Ecspanse Application Setup (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md Integrates the `MaybeFindResources` system into the main `Demo` application's setup function. This ensures the system is initialized and active within the Ecspanse frame processing, running after `MoveHero`. ```Elixir defmodule Demo do use Ecspanse # ... def setup(data) do data # ... |> Ecspanse.add_system(Systems.MoveHero, run_after: [Systems.RestoreEnergy]) |> Ecspanse.add_system(Systems.MaybeFindResources) |> Ecspanse.add_frame_end_system(Ecspanse.System.Timer) end end ``` -------------------------------- ### Testing Hero Entity Fetch - IEx Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This IEx console snippet demonstrates how to test the `Demo.Entities.Hero.fetch/0` function after starting the server. It shows the expected output of a successful fetch operation, returning an `{:ok, entity}` tuple with the fetched `Ecspanse.Entity` struct. ```IEx iex(1)> Demo.Entities.Hero.fetch() {:ok, %Ecspanse.Entity{id: "e950bf44-16d5-46b5-bd21-85aabae50ce8"}} ``` -------------------------------- ### Example Output of Hero Details with Accumulated Resources (iex) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md Demonstrates the expected output when calling `Demo.API.fetch_hero_details` in an `iex` session after the hero has moved and accumulated resources. It shows the `resources` field containing `Gems` and `Gold` with their respective amounts. ```iex iex(14)> Demo.API.fetch_hero_details %{ name: "Hero", resources: [%{name: "Gems", amount: 2}, %{name: "Gold", amount: 5}], energy: 56, max_energy: 100, pos_x: -3, pos_y: -5 } ``` -------------------------------- ### Registering Frame-Based Systems in Ecspanse Source: https://github.com/iacobson/ecspanse/blob/main/guides/save_load.md This Elixir snippet illustrates how to register `Demo.Systems.Load` to run at the start of each frame and `Demo.Systems.Save` to run at the end of each frame within the `setup` function. This approach is useful for scenarios requiring frequent, on-demand saving and loading based on game loop events. ```Elixir def setup(data) do data |> add_frame_start_system(Demo.Systems.Load) # ... |> add_frame_end_system(Demo.Systems.Save) end ``` -------------------------------- ### Registering Startup and Shutdown Systems in Ecspanse Source: https://github.com/iacobson/ecspanse/blob/main/guides/save_load.md This Elixir code demonstrates how to register `Demo.Systems.Load` as a startup system and `Demo.Systems.Save` as a shutdown system within the `setup` function of an Ecspanse application. This pattern ensures that data loading occurs when the application starts and data saving occurs upon shutdown, suitable for persistent state management. ```Elixir def setup(data) do data |> add_startup_system(Demo.Systems.Load) # ... |> add_shutdown_system(Demo.Systems.Save) end ``` -------------------------------- ### Defining Ecspanse Test Module and Inner Setup in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet defines the `Demo.Systems.MoveHeroTest` module using `ExUnit.Case` for asynchronous tests and includes an inner `DemoTest` module. The `DemoTest` module implements a basic `setup/1` function required by Ecspanse, which is crucial for configuring the test environment without automatically scheduling systems. ```Elixir defmodule Demo.Systems.MoveHeroTest do use ExUnit.Case, async: false defmodule DemoTest do use Ecspanse @impl true def setup(data) do data end end ``` -------------------------------- ### Adding Ecspanse Dependency to mix.exs Source: https://github.com/iacobson/ecspanse/blob/main/guides/getting_started.md This Elixir code snippet adds the Ecspanse library as a dependency to the project's `mix.exs` file. The `~> 0.9.0` specifies that the project will use a version of Ecspanse compatible with 0.9.0. ```Elixir def deps do [ {:ecspanse, "~> 0.9.0"} ] end ``` -------------------------------- ### Scheduling Asynchronous Move System in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir code snippet demonstrates how to schedule the `MoveHero` system to run asynchronously within the Ecspanse application's setup phase. It uses `Ecspanse.add_system/2` to integrate the system into the game loop, ensuring it processes events and updates components in parallel. ```Elixir defmodule Demo do #... def setup(data) do data #... |> Ecspanse.add_system(Systems.MoveHero) end end ``` -------------------------------- ### Filtering Entities by Component Type in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/save_load.md This example shows how to configure a component, `Demo.Components.Leaf`, to filter out entire entities that possess it during export. This is useful for 'tag' components where the presence of the component signifies that the whole entity should not be saved. ```Elixir defmodule Demo.Components.Leaf do use Ecspanse.Component, export_filter: :entity end ``` -------------------------------- ### Scheduling Startup System in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir module, `Demo`, serves as the main application entry point for Ecspanse. It uses `Ecspanse` and implements the `setup/1` callback. This function is invoked during application startup and is used to configure the game world. Here, it adds `Demo.Systems.SpawnHero` as a startup system using `Ecspanse.add_startup_system`, ensuring the hero entity is spawned automatically when the game initializes. ```Elixir defmodule Demo do use Ecspanse alias Demo.Systems @impl Ecspanse def setup(data) do data |> Ecspanse.add_startup_system(Systems.SpawnHero) end end ``` -------------------------------- ### Registering SpawnMarket as a Startup System (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet illustrates how to register the newly created `SpawnMarket` system as a startup system within the Ecspanse application's `setup/1` function. This ensures the market entity and its items are spawned when the application initializes. ```Elixir #... |> Ecspanse.add_startup_system(Systems.SpawnMarket) #... ``` -------------------------------- ### Testing Hero Movement in IEx Console (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir `iex` console session demonstrates how to interact with the `Demo.API` to fetch hero details and simulate hero movement. It shows the initial hero state, subsequent moves (up, right), and the updated state reflecting energy consumption and position changes, verifying the system's functionality. ```Elixir iex(1)> Demo.API.fetch_hero_details() %{name: "Hero", energy: 50, max_energy: 100, pos_x: 0, pos_y: 0} iex(2)> Demo.API.move_hero(:up) :ok iex(3)> Demo.API.move_hero(:right) :ok iex(4)> Demo.API.fetch_hero_details() %{name: "Hero", energy: 48, max_energy: 100, pos_x: 1, pos_y: 1} ``` -------------------------------- ### Testing Fetch Hero Details in IEx Console (IEx) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet demonstrates testing the `Demo.API.fetch_hero_details()` function in an `iex` console. It shows the expected output, including the hero's name, resources, inventory, energy, and position, verifying the API's functionality. ```IEx iex(1)> Demo.API.fetch_hero_details() %{ name: "Hero", resources: [%{name: "Gems", amount: 0}, %{name: "Gold", amount: 0}], inventory: [%{name: "Boots"}, %{name: "Potion"}, %{name: "Potion"}], energy: 60, max_energy: 100, pos_x: 0, pos_y: 0 } ``` -------------------------------- ### Creating Specific Resource Components from Template in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md These Elixir modules, `Demo.Components.Gems` and `Demo.Components.Gold`, demonstrate how to create concrete components by reusing the `Demo.Components.Resource` template. They inherit the template's structure and validation logic, while providing specific initial state values and tags, showcasing polymorphism and standardized resource definition within Ecspanse. ```Elixir defmodule Demo.Components.Gems do use Demo.Components.Resource, state: [id: :gems, name: "Gems", amount: 0], tags: [:resource] end defmodule Demo.Components.Gold do use Demo.Components.Resource, state: [id: :gold, name: "Gold", amount: 0], tags: [:resource] end ``` -------------------------------- ### Testing Fetch Market Items in IEx Console (IEx) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet illustrates testing the `Demo.API.fetch_market_items()` function in an `iex` console. It displays the structured output of market items, including their names, entity IDs, and associated costs, confirming the API's ability to retrieve market data. ```IEx iex(1)> Demo.API.fetch_market_items() [ %{ name: "Map", entity_id: "361c00ba-4dd3-4be8-b171-00e99c0b8ef7", cost: [%{name: "Gold", amount: 2}] }, %{ name: "Compass", entity_id: "b027fd01-d4fe-4ac1-9736-6b6f8c58fbd1", cost: [%{name: "Gold", amount: 3}, %{name: "Gems", amount: 2}] } ] ``` -------------------------------- ### Testing Hero Details Fetch - IEx Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This IEx console snippet demonstrates how to test the `Demo.API.fetch_hero_details/0` function. It shows the expected output, which is a map containing the hero's name, current energy, maximum energy, and position coordinates, as extracted from the queried components. ```IEx iex(2)> Demo.API.fetch_hero_details() %{name: "Hero", energy: 50, max_energy: 100, pos_x: 0, pos_y: 0} ``` -------------------------------- ### Listing Hero Resources by Tags in Ecspanse API (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md Defines a private helper function `list_hero_resources` within the `Demo.API` module. It queries the hero entity for all components tagged with `:resource` and `:available`, then maps them to a simplified format containing `name` and `amount`. ```Elixir defp list_hero_resources(hero_entity) do hero_entity |> Ecspanse.Query.list_tagged_components_for_entity([:resource, :available]) |> Enum.map(&%{name: &1.name, amount: &1.amount}) end ``` -------------------------------- ### Creating SpawnMarket System with Pre-attached Children (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet defines a new `SpawnMarket` system responsible for spawning a market entity. It showcases an alternative method of entity creation where child entities (a compass and a map) are attached directly during the `Ecspanse.Command.spawn_entity!` call, demonstrating entity composition at spawn time. ```Elixir defmodule Demo.Systems.SpawnMarket do use Ecspanse.System @impl true def run(_frame) do compass_entity = %Ecspanse.Entity{} = Ecspanse.Command.spawn_entity!(Demo.Entities.Inventory.new_compass()) map_entity = %Ecspanse.Entity{} = Ecspanse.Command.spawn_entity!(Demo.Entities.Inventory.new_map()) Ecspanse.Command.spawn_entity!({ Ecspanse.Entity, components: [Demo.Components.Market], children: [compass_entity, map_entity] }) end end ``` -------------------------------- ### Extending Demo.API for Inventory and Market Queries (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet extends the `Demo.API` module with functions to query hero inventory and market items. It includes `list_hero_inventory` (private helper), `fetch_market_items` (public API to retrieve market items and their costs), `list_market_items` (private helper to process market entity children), and `item_cost` (private helper to extract cost components from an item entity). ```Elixir defmodule Demo.API do #... defp list_hero_inventory(hero_entity) do hero_entity |> Demo.Entities.Inventory.list_inventory_components() |> Enum.map(&%{name: &1.name}) end @spec fetch_market_items() :: {:ok, list(map())} | {:error, :not_found} def fetch_market_items do Ecspanse.Query.select({Ecspanse.Entity}, with: [Demo.Components.Market]) |> Ecspanse.Query.one() |> case do {market_entity} -> list_market_items(market_entity) _ -> {:error, :not_found} end end defp list_market_items(market_entity) do market_entity |> Demo.Entities.Inventory.list_inventory_components() |> Enum.map(fn item_component -> item_entity = Ecspanse.Query.get_component_entity(item_component) %{entity_id: item_entity.id, name: item_component.name, cost: item_cost(item_entity)} end) end defp item_cost(item_entity) do item_entity |> Ecspanse.Query.list_tagged_components_for_entity([:resource, :cost]) |> Enum.map(&%{name: &1.name, amount: &1.amount}) end end ``` -------------------------------- ### Configuring Ecspanse Systems and Conditional Execution in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet redefines the main `Demo` module, setting up the game's systems. It adds `SpawnHero`, `RestoreEnergy` (with a conditional `run_if` based on `energy_not_max?`), `MoveHero` (with `run_after` `RestoreEnergy`), and `Ecspanse.System.Timer` as a frame-end system. It also defines `energy_not_max?/0` to check if the hero's energy is below its maximum. ```Elixir defmodule Demo do use Ecspanse alias Demo.Systems @impl Ecspanse def setup(data) do data |> Ecspanse.add_startup_system(Systems.SpawnHero) |> Ecspanse.add_system(Systems.RestoreEnergy, run_if: [{__MODULE__, :energy_not_max?}]) |> Ecspanse.add_system(Systems.MoveHero, run_after: [Systems.RestoreEnergy]) |> Ecspanse.add_frame_end_system(Ecspanse.System.Timer) end def energy_not_max? do Ecspanse.Query.select({Demo.Components.Energy}, with: [Demo.Components.Hero]) |> Ecspanse.Query.one() |> case do {%Demo.Components.Energy{current: current, max: max}} -> current < max _ -> false end end end ``` -------------------------------- ### Updating SpawnHero System to Add Inventory Items (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet updates the `SpawnHero` system to initialize a hero entity with inventory items (two potions and a pair of boots) as child entities. It demonstrates the use of `Ecspanse.Command.spawn_entity!` to create entities and `Ecspanse.Command.add_children!` to establish parent-child relationships, composing complex entities from smaller ones. ```Elixir defmodule Demo.Systems.SpawnHero do use Ecspanse.System @impl true def run(_frame) do hero_entity = %Ecspanse.Entity{} = Ecspanse.Command.spawn_entity!(Demo.Entities.Hero.new()) potion_entity_1 = %Ecspanse.Entity{} = Ecspanse.Command.spawn_entity!(Demo.Entities.Inventory.new_potion()) potion_entity_2 = %Ecspanse.Entity{} = Ecspanse.Command.spawn_entity!(Demo.Entities.Inventory.new_potion()) boots_entity = %Ecspanse.Entity{} = Ecspanse.Command.spawn_entity!(Demo.Entities.Inventory.new_boots()) Ecspanse.Command.add_children!([ {hero_entity, [potion_entity_1, potion_entity_2, boots_entity]} ]) end end ``` -------------------------------- ### Implementing Item Purchase System in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir module defines the `PurchaseMarketItem` system using the Ecspanse framework. It handles the logic for purchasing an item from the market, including validating entity existence, checking item availability in the market, verifying hero resources, spending resources, and transferring the item from the market to the hero's inventory. It uses `Ecspanse.Query` for data retrieval and `Ecspanse.Command` for state modifications. ```Elixir defmodule Demo.Systems.PurchaseMarketItem do use Ecspanse.System, event_subscriptions: [Demo.Events.PurchaseMarketItem] @impl true def run(%Demo.Events.PurchaseMarketItem{item_entity_id: item_entity_id}, _frame) do with {:ok, item_entity} <- Ecspanse.Query.fetch_entity(item_entity_id), {:ok, market_entity} <- fetch_market_entity(), {:ok, hero_entity} <- Demo.Entities.Hero.fetch(), true <- Ecspanse.Query.is_child_of?(parent: market_entity, child: item_entity), hero_available_resources_components = Ecspanse.Query.list_tagged_components_for_entity(hero_entity, [:resource, :available]), item_cost_components = Ecspanse.Query.list_tagged_components_for_entity(item_entity, [:resource, :cost]), true <- has_enough_resources?(hero_available_resources_components, item_cost_components) do spend_resources(hero_available_resources_components, item_cost_components) Ecspanse.Command.remove_child!(market_entity, item_entity) Ecspanse.Command.add_child!(hero_entity, item_entity) end end defp fetch_market_entity do Ecspanse.Query.select({Ecspanse.Entity}, with: [Demo.Components.Market]) |> Ecspanse.Query.one() |> case do {market_entity} -> {:ok, market_entity} _ -> {:error, :not_found} end end defp has_enough_resources?(available_resources, cost_resources) do Enum.all?(cost_resources, fn cost_resource -> Enum.any?(available_resources, fn available_resource -> available_resource.id == cost_resource.id && available_resource.amount >= cost_resource.amount end) end) end defp spend_resources(available_resources, cost_resources) do Enum.each(cost_resources, fn cost_resource -> available_resource = Enum.find(available_resources, fn available_resource -> available_resource.id == cost_resource.id end) Ecspanse.Command.update_component!(available_resource, amount: available_resource.amount - cost_resource.amount ) end) end end ``` -------------------------------- ### Testing Hero Movement with Sufficient Energy in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This test case verifies that the hero entity moves correctly when it has enough energy. It simulates a 'move up' event, manually runs the `Demo.Systems.MoveHero` system, and then asserts that the hero's position has updated (y-coordinate increased by 1) and its energy has decreased by 1, demonstrating successful system execution and state change. ```Elixir test "hero moves if enough energy", %{hero_entity: hero_entity} do event = move(:up) frame = frame(event) Demo.Systems.MoveHero.run(event, frame) {:ok, position_component} = Demo.Components.Position.fetch(hero_entity) assert position_component.x == 0 assert position_component.y == 1 {:ok, energy_component} = Demo.Components.Energy.fetch(hero_entity) assert energy_component.current == 49 #... end ``` -------------------------------- ### Implementing Resource Finding System in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md Defines the `MaybeFindResources` system which subscribes to `HeroMoved` events. It randomly determines if resources are found at the hero's current position and updates the hero's resource components (Gems, Gold) accordingly. It leverages Ecspanse's component locking and event subscription features. ```Elixir defmodule Demo.Systems.MaybeFindResources do use Ecspanse.System, lock_components: [Demo.Components.Gems, Demo.Components.Gold], event_subscriptions: [Demo.Events.HeroMoved] alias Demo.Components @impl true def run(%Demo.Events.HeroMoved{}, _frame) do with true <- found_resource?(), resource_module <- pick_resource(), {:ok, hero_entity} <- Demo.Entities.Hero.fetch(), {:ok, resource} <- Ecspanse.Query.fetch_component(hero_entity, resource_module) do Ecspanse.Command.update_component!(resource, amount: resource.amount + 1) end end def run(_event, _frame), do: :ok defp found_resource?, do: Enum.random([true, false]) defp pick_resource, do: Enum.random([Components.Gems, Components.Gold]) end ``` -------------------------------- ### Defining Inventory and Market Components in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet defines basic components for a market and various inventory items (Boots, Compass, Map, Potion) using the Ecspanse.Component macro. Inventory items are given a name in their state and tagged with :inventory for easy identification and querying. ```Elixir defmodule Demo.Components.Market do use Ecspanse.Component end defmodule Demo.Components.Boots do use Ecspanse.Component, state: [name: "Boots"], tags: [:inventory] end defmodule Demo.Components.Compass do use Ecspanse.Component, state: [name: "Compass"], tags: [:inventory] end defmodule Demo.Components.Map do use Ecspanse.Component, state: [name: "Map"], tags: [:inventory] end defmodule Demo.Components.Potion do use Ecspanse.Component, state: [name: "Potion"], tags: [:inventory] end ``` -------------------------------- ### Implementing Spawn Hero System in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir module, `Demo.Systems.SpawnHero`, implements an Ecspanse system responsible for spawning the hero entity. It uses `Ecspanse.System` and overrides the `run/1` callback, which receives the current game frame. Inside `run/1`, it calls `Ecspanse.Command.spawn_entity!` with the hero entity specification to create and add the hero to the game world. Commands like `spawn_entity!` must be executed within a system. ```Elixir defmodule Demo.Systems.SpawnHero do use Ecspanse.System @impl true def run(_frame) do %Ecspanse.Entity{} = Ecspanse.Command.spawn_entity!(Demo.Entities.Hero.new()) end end ``` -------------------------------- ### Creating Hero Entity Specification in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir module, `Demo.Entities.Hero`, defines a specification for the hero entity. The `new/0` function returns an `Ecspanse.Entity.entity_spec()` tuple, listing the `Demo.Components.Hero`, `Demo.Components.Energy`, and `Demo.Components.Position` components that will be attached to the hero entity when it is spawned. This function prepares the entity for creation without immediately spawning it. ```Elixir defmodule Demo.Entities.Hero do alias Demo.Components @spec new() :: Ecspanse.Entity.entity_spec() def new do {Ecspanse.Entity, components: [ Components.Hero, Components.Energy, Components.Position ]} end end ``` -------------------------------- ### Querying Components with Ecspanse in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet illustrates two equivalent ways to fetch a component from an entity: using `Ecspanse.Query.fetch_component/2` or directly calling the `fetch/1` function on the component module itself. Both methods achieve the same result of retrieving the specified component instance from the given entity. ```Elixir Ecspanse.Query.fetch_component(entity, Demo.Components.Energy) #and Demo.Components.Energy.fetch(entity) ``` -------------------------------- ### Defining Hero Components in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md These Elixir modules define the core components for the hero entity within the Ecspanse framework. `Demo.Components.Hero` stores generic hero information, `Demo.Components.Energy` manages current and maximum energy points, and `Demo.Components.Position` tracks the hero's x and y coordinates. Each component uses `Ecspanse.Component` with an initial `:state`. ```Elixir defmodule Demo.Components.Hero do use Ecspanse.Component, state: [name: "Hero"] end defmodule Demo.Components.Energy do use Ecspanse.Component, state: [current: 50, max: 100] end defmodule Demo.Components.Position do use Ecspanse.Component, state: [x: 0, y: 0] end ``` -------------------------------- ### Exposing Item Purchase API in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir function defines a public API endpoint `purchase_market_item` that allows initiating an item purchase. It takes an `item_entity_id` as input and dispatches a `Demo.Events.PurchaseMarketItem` event through `Ecspanse.event/1`, triggering the associated purchase system. ```Elixir @spec purchase_market_item(item_entity_id :: Ecspanse.Entity.id()) :: :ok def purchase_market_item(item_entity_id) do Ecspanse.event({Demo.Events.PurchaseMarketItem, item_entity_id: item_entity_id}) end ``` -------------------------------- ### Registering Purchase System in Ecspanse Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet demonstrates how to register the `PurchaseMarketItem` system with the Ecspanse framework. By adding it as a `frame_end_system`, it ensures that the system runs synchronously at the end of a frame, which is beneficial for operations that modify multiple components to avoid individual locking. ```Elixir #... |> Ecspanse.add_frame_end_system(Systems.PurchaseMarketItem) #... ``` -------------------------------- ### Adding Resource Components to Hero Entity in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir code snippet shows how to add the previously defined `Components.Gold` and `Components.Gems` to the `Hero` entity within the Ecspanse framework. It illustrates the use of `component_spec/0` type, where components are added as tuples specifying the module, initial state (empty in this case, inheriting from template), and runtime tags (`:available`), enabling dynamic component configuration. ```Elixir defmodule Demo.Entities.Hero do #... def new do {Ecspanse.Entity, components: [ #... {Components.Gold, [], [:available]}, {Components.Gems, [], [:available]} ]} end #... end ``` -------------------------------- ### Defining Inventory Item Entities and Costs in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This module defines functions to create new inventory item entities (Boots, Compass, Map, Potion) using Ecspanse.Entity, associating them with their respective components and resource costs (Gold, Gems) tagged with :cost. It also includes list_inventory_components/1, a utility function that queries and lists all components tagged with :inventory for a given parent entity's children. ```Elixir defmodule Demo.Entities.Inventory do alias Demo.Components @spec new_boots() :: Ecspanse.Entity.entity_spec() def new_boots do {Ecspanse.Entity, components: [Components.Boots, {Components.Gold, [amount: 3], [:cost]}]} end @spec new_compass() :: Ecspanse.Entity.entity_spec() def new_compass do {Ecspanse.Entity, components: [ Components.Compass, {Components.Gold, [amount: 3], [:cost]}, {Components.Gems, [amount: 2], [:cost]} ]} end @spec new_map() :: Ecspanse.Entity.entity_spec() def new_map do {Ecspanse.Entity, components: [Components.Map, {Components.Gold, [amount: 2], [:cost]}]} end @spec new_potion() :: Ecspanse.Entity.entity_spec() def new_potion do {Ecspanse.Entity, components: [Components.Potion, {Components.Gold, [amount: 1], [:cost]}]} end @spec list_inventory_components(Ecspanse.Entity.t()) :: [component :: struct()] def list_inventory_components(parent) do Ecspanse.Query.list_tagged_components_for_children(parent, [:inventory]) end end ``` -------------------------------- ### Helper Functions for Ecspanse Event and Frame Creation in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md These private helper functions, `move/1` and `frame/1`, are used within the test module to create necessary data structures for system testing. `move/1` constructs a `Demo.Events.MoveHero` event with a specified direction and timestamp, while `frame/1` creates an `Ecspanse.Frame` containing the generated event, facilitating manual system execution. ```Elixir defp move(direction) do %Demo.Events.MoveHero{direction: direction, inserted_at: System.os_time()} end defp frame(event) do %Ecspanse.Frame{event_batches: [[event]], delta: 1} end end ``` -------------------------------- ### Implementing Asynchronous Hero Movement System in Ecspanse (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir module defines the `MoveHero` system using Ecspanse, responsible for handling hero movement events. It checks for sufficient energy, updates the hero's position and energy components, and emits a `HeroMoved` event. The system uses `lock_components` for concurrency control and `event_subscriptions` to run only when a `MoveHero` event occurs. ```Elixir defmodule Demo.Systems.MoveHero do use Ecspanse.System, lock_components: [Demo.Components.Position, Demo.Components.Energy], event_subscriptions: [Demo.Events.MoveHero] alias Demo.Components @impl true def run(%Demo.Events.MoveHero{direction: direction}, _frame) do components = Ecspanse.Query.select({Components.Position, Components.Energy}, with: [Components.Hero]) |> Ecspanse.Query.one() with {position, energy} <- components, :ok <- validate_enough_energy_to_move(energy) do Ecspanse.Command.update_components!([ {energy, current: energy.current - 1}, {position, update_coordinates(position, direction)} ]) Ecspanse.event(Demo.Events.HeroMoved) end end defp validate_enough_energy_to_move(%Components.Energy{current: current_energy}) do if current_energy >= 1 do :ok else {:error, :not_enough_energy} end end defp update_coordinates(%Components.Position{x: x, y: y}, direction) do case direction do :up -> [x: x, y: y + 1] :down -> [x: x, y: y - 1] :left -> [x: x - 1, y: y] :right -> [x: x + 1, y: y] _ -> [x: x, y: y] end end end ``` -------------------------------- ### Fetching Hero Details with Multiple Components - Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir function `fetch_hero_details/0` in `Demo.API` queries for an entity that has `Demo.Components.Hero`, `Demo.Components.Energy`, and `Demo.Components.Position` attached. It uses `Ecspanse.Query.select` to specify multiple components and `Ecspanse.Query.one` to retrieve a single record. The function then extracts relevant data from the returned components and formats it into a map, or returns `{:error, :not_found}` if no matching entity is found. ```Elixir defmodule Demo.API do @spec fetch_hero_details() :: {:ok, map()} | {:error, :not_found} def fetch_hero_details do Ecspanse.Query.select( {Ecspanse.Entity, Demo.Components.Hero, Demo.Components.Energy, Demo.Components.Position} ) |> Ecspanse.Query.one() |> case do {hero_entity, hero, energy, position} -> {:ok, %{name: hero.name, energy: energy.current, max_energy: energy.max, pos_x: position.x, pos_y: position.y}} _ -> {:error, :not_found} end end end ``` -------------------------------- ### Emitting Hero Movement Event via API - Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir function `move_hero/1` is added to the `Demo.API` module to provide an interface for triggering hero movement. It takes a `direction` atom as input and uses `Ecspanse.event/1` to emit a `Demo.Events.MoveHero` event with the specified direction. This allows external systems to easily interact with the game's hero movement logic. ```Elixir defmodule Demo.API do #... @spec move_hero(direction :: :up | :down | :left | :right) :: :ok def move_hero(direction) do Ecspanse.event({Demo.Events.MoveHero, direction: direction}) end end ``` -------------------------------- ### Implementing an Energy Restore System in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet defines the `RestoreEnergy` system, which subscribes to the `EnergyTimerFinished` event and locks the `Energy` component. When the event is triggered, the system fetches the associated entity and its energy component, then increments the `current` energy by 1. It demonstrates how systems react to events and update component states. ```Elixir defmodule Demo.Systems.RestoreEnergy do use Ecspanse.System, lock_components: [Demo.Components.Energy], event_subscriptions: [Demo.Events.EnergyTimerFinished] @impl true def run(%Demo.Events.EnergyTimerFinished{entity_id: entity_id}, _frame) do with {:ok, entity} <- Ecspanse.Query.fetch_entity(entity_id), {:ok, energy} <- Ecspanse.Query.fetch_component(entity, Demo.Components.Energy) do Ecspanse.Command.update_component!(energy, current: energy.current + 1) end end end ``` -------------------------------- ### Defining a Resource Template Component in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir code defines `Demo.Components.Resource` as a template component using `Ecspanse.Template.Component`. It specifies a shared state structure (`:id`, `:name`, `:amount`) and a `:resource` tag. The component includes a `validate/1` callback to ensure the `amount` field is a non-negative integer, enforcing data integrity for all components based on this template. ```Elixir defmodule Demo.Components.Resource do use Ecspanse.Template.Component, state: [:id, :name, amount: 0], tags: [:resource] @impl true def validate(state) do with :ok <- validate_integer_amount(state[:amount]), :ok <- validate_positive_amount(state[:amount]) do :ok end end defp validate_integer_amount(amount) do if is_integer(amount) do :ok else {:error, "#{inspect(amount)} must be an integer"} end end defp validate_positive_amount(amount) do if amount >= 0 do :ok else {:error, "#{inspect(amount)} must be positive"} end end end ``` -------------------------------- ### Updating Hero Details Map with Resources in Ecspanse API (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md Modifies the return map of the `fetch_hero_details/0` function in `Demo.API` to include a `resources` field. This field is populated by calling `list_hero_resources(hero_entity)`, making the hero's collected resources accessible via the API. ```Elixir %{ # ... pos_y: position.y, resources: list_hero_resources(hero_entity), } ``` -------------------------------- ### Testing Hero Movement Failure with Insufficient Energy in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This test case confirms that the hero entity does not move when it lacks sufficient energy. It first updates the hero's energy to zero, then simulates a 'move up' event and runs the `Demo.Systems.MoveHero` system. Finally, it asserts that the hero's position remains unchanged and its energy stays at zero, validating the system's energy-based movement constraint. ```Elixir test "hero doesn not move if not enough energy", %{ hero_entity: hero_entity, energy_component: energy_component } do Ecspanse.Command.update_component!(energy_component, current: 0) event = move(:up) frame = frame(event) Demo.Systems.MoveHero.run(event, frame) {:ok, position_component} = Demo.Components.Position.fetch(hero_entity) assert position_component.x == 0 assert position_component.y == 0 {:ok, energy_component} = Demo.Components.Energy.fetch(hero_entity) assert energy_component.current == 0 end ``` -------------------------------- ### Adding Energy Timer Component to Hero Entity in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet modifies the `Demo.Entities.Hero` module to include the `Components.EnergyTimer` in its list of components when a new `Hero` entity is created. This integrates the timer functionality directly with the Hero entity, allowing it to manage its energy restoration. ```Elixir defmodule Demo.Entities.Hero do #... @spec new() :: Ecspanse.Entity.entity_spec() def new do {Ecspanse.Entity, components: [ Components.Hero, Components.Energy, Components.Position, Components.EnergyTimer ]} end #... end ``` -------------------------------- ### Fetching Single Hero Entity - Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir function `fetch/0` in `Demo.Entities.Hero` uses `Ecspanse.Query.select` to find a single entity that possesses the `Demo.Components.Hero` component. It then uses `Ecspanse.Query.one` to retrieve the first matching record, returning `{:ok, entity}` if found, or `{:error, :not_found}` otherwise. It's important to note that `Ecspanse.Query.one` will raise an error if multiple entities match the query. ```Elixir defmodule Demo.Entities.Hero do alias Demo.Components #... def fetch do Ecspanse.Query.select({Ecspanse.Entity}, with: [Components.Hero]) |> Ecspanse.Query.one() |> case do {%Ecspanse.Entity{} = entity} -> {:ok, entity} _ -> {:error, :not_found} end end end ``` -------------------------------- ### Updating Hero Details Map with Inventory Field (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet shows how to modify the map returned by the `fetch_hero_details/0` function to include the hero's inventory. It adds an `inventory` field, populated by calling the `list_hero_inventory` helper function, enhancing the hero's data representation. ```Elixir %{ # ... pos_y: position.y, resources: list_hero_resources(hero_entity), inventory: list_hero_inventory(hero_entity) } ``` -------------------------------- ### Defining Hero Moved Event - Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir module defines the `Demo.Events.HeroMoved` event using `use Ecspanse.Event`. This event is intended to be emitted after the hero has successfully moved, serving as a signal for other systems to handle various side effects or update game state based on the hero's new position. ```Elixir defmodule Demo.Events.HeroMoved do use Ecspanse.Event end ``` -------------------------------- ### Defining PurchaseMarketItem Event (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet defines a new `PurchaseMarketItem` event using `Ecspanse.Event`. This event is designed to trigger an item purchase action and stores only the `item_entity_id` field, which identifies the specific item being purchased from the market. ```Elixir defmodule Demo.Events.PurchaseMarketItem do use Ecspanse.Event, fields: [:item_entity_id] end ``` -------------------------------- ### Setting Application Version in Ecspanse Module Source: https://github.com/iacobson/ecspanse/blob/main/guides/save_load.md This Elixir snippet shows how to define an application-level version using the `use Ecspanse` macro. The `version: 1` option sets an integer field in `Ecspanse.Snapshot.EntitySnapshot` and `Ecspanse.Snapshot.ResourceSnapshot` structs, which is crucial for managing backwards compatibility during data restoration. ```Elixir defmodule TestServer1 do use Ecspanse, fps_limit: 60, version: 1 def setup(data) do # ... end end ``` -------------------------------- ### Exporting All Ecspanse Entities to Snapshots Source: https://github.com/iacobson/ecspanse/blob/main/guides/save_load.md This Elixir line of code exports all existing entities and their components into a list of `Ecspanse.Snapshot.EntitySnapshot` structs. This function is a straightforward way to capture the entire game state related to entities for saving or serialization. ```Elixir snapshots = Ecspanse.Snapshot.export_entities!() ``` -------------------------------- ### Defining Hero Movement Event - Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This Elixir module defines the `Demo.Events.MoveHero` event using `use Ecspanse.Event`. It specifies a `:direction` field, which will be used to convey the intended movement direction (e.g., `:up`, `:down`, `:left`, `:right`). Events in Ecspanse are structs that serve as a mechanism for external input. ```Elixir defmodule Demo.Events.MoveHero do use Ecspanse.Event, fields: [:direction] end ``` -------------------------------- ### Defining an Energy Timer Component in Elixir Source: https://github.com/iacobson/ecspanse/blob/main/guides/tutorial.md This snippet defines the `EnergyTimer` component using `Ecspanse.Template.Component.Timer`. It configures the timer with a 3-second duration, a `Demo.Events.EnergyTimerFinished` event to trigger, and sets it to repeat indefinitely. This component is used to schedule recurring events. ```Elixir defmodule Demo.Components.EnergyTimer do use Ecspanse.Template.Component.Timer, state: [duration: 3000, time: 3000, event: Demo.Events.EnergyTimerFinished, mode: :repeat] end ``` -------------------------------- ### Handling Component Renaming During Ecspanse Snapshot Restoration (Elixir) Source: https://github.com/iacobson/ecspanse/blob/main/guides/save_load.md This code demonstrates a custom load mechanism to handle component renaming during snapshot restoration. It iterates through `component_specs` in an `EntitySnapshot`, transforming `Demo.Components.OldComponent` to `Demo.Components.NewComponent` before restoring the entity. This ensures compatibility with older saved data. ```Elixir specs = for component_spec <- entity_snapshot.component_specs do case component_spec do {Demo.Components.OldComponent, state, tags} -> {Demo.Components.NewComponent, state, tags} _ -> component_spec end end Ecspanse.Snapshot.restore_entity!(entity_snapshot.id, specs) ``` -------------------------------- ### Restoring Ecspanse Entities from Snapshots Source: https://github.com/iacobson/ecspanse/blob/main/guides/save_load.md This Elixir code restores entities from a previously exported list of `Ecspanse.Snapshot.EntitySnapshot` structs. It overwrites existing entity components or resources with the data from the snapshots, providing a simple mechanism to load a saved game state. ```Elixir Ecspanse.Snapshot.restore_entities_from_snapshots!(snapshots) ``` -------------------------------- ### Despawning Entity Before Restoring in Ecspanse Source: https://github.com/iacobson/ecspanse/blob/main/guides/save_load.md This Elixir code demonstrates a pattern for ensuring an entity is despawned before being restored. It first attempts to fetch the entity by `entity_id` and, if found, despawns it and its descendants. Subsequently, `Ecspanse.Snapshot.restore_entity!` is called to restore the entity with the provided `component_specs_list`, preventing conflicts with existing entities. ```Elixir case Ecspanse.Query.fetch_entity(entity_id) do {:ok, entity} -> Ecspanse.Command.despawn_entity_and_descendants!(entity) _ -> :ok end Ecspanse.Snapshot.restore_entity!(entity_id, component_specs_list) ```