![]()
Origami
Origami is a collection of essential primitives designed to facilitate the development of onchain games using the Dojo engine. It provides a set of powerful tools and libraries that enable game developers to create complex, engaging, and secure blockchain-based games.
Each crate focuses on a specific aspect of game development, from mathematical operations and procedural generation to economic mechanisms and security primitives. For advanced mathematical computations that complement these game development primitives, consider also using Alexandria's extended math library.
Installation
Add the crates you need to your Scarb.toml:
[dependencies]
origami_algebra = { git = "https://github.com/dojoengine/origami" }
origami_map = { git = "https://github.com/dojoengine/origami" }
origami_random = { git = "https://github.com/dojoengine/origami" }
# Add others as needed...Algebra
Mathematical structures and operations for 2D game development including vector operations and algebraic computations.
Key Features: Vec2 operations (dot product, swizzling), vector utilities, matrix operations
// Example: checking if an enemy is within a player's field of view
use origami_algebra::vec2::{Vec2, Vec2Trait};
#[derive(Copy, Drop, Serde, IntrospectPacked)]
pub struct GameVec2 { pub x: u32, pub y: u32 }
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Position {
#[key] pub entity: ContractAddress,
pub vec: GameVec2, // Where the entity is
}
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Facing {
#[key] pub entity: ContractAddress,
pub direction: GameVec2, // Where the entity is facing
}
fn can_see_enemy(world: WorldStorage, player: ContractAddress, enemy: ContractAddress) -> bool {
let player_pos: Position = world.read_model(player);
let enemy_pos: Position = world.read_model(enemy);
let player_facing: Facing = world.read_model(player);
// Calculate direction from player to enemy
let to_enemy = Vec2Trait::new(
enemy_pos.vec.x - player_pos.vec.x,
enemy_pos.vec.y - player_pos.vec.y
);
let facing_vec = Vec2Trait::new(
player_facing.direction.x,
player_facing.direction.y
);
// If dot > 0, enemy is in front of player (within 180° field of view)
facing_vec.dot(to_enemy) > 0
}Map
Tools for generating and manipulating 2D grid-based worlds with procedural generation algorithms and pathfinding.
Key Features: Maze/cave/random walk generation, A* pathfinding, object distribution, hex grids
// Example: creating and exploring a dungeon with hidden treasures
use origami_map::map::{Map, MapTrait};
use origami_map::helpers::bitmap::{Bitmap, BitmapTrait};
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct GameWorld {
#[key] pub game_id: u32,
pub map: Map,
}
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Treasure {
#[key] pub game_id: u32,
#[key] pub position: u8,
pub discovered: bool,
}
// Create dungeon with entrance and hidden treasures
fn generate_dungeon(ref world: WorldStorage, game_id: u32) {
let seed = get_block_timestamp().into() + game_id.into();
// Generate closed maze with few branching paths
let mut map = MapTrait::new_maze(18, 14, 0, seed);
// Add entrance corridor from edge position 63
map.open_with_corridor(63, 0);
// Distribute 5 treasures randomly across walkable tiles
let treasure_positions = map.compute_distribution(5, seed);
// Store treasures at computed positions
let mut pos: u8 = 1;
while pos < 252 { // 252 = 18 * 14
if Bitmap::get(treasure_positions, pos) == 1 {
world.write_model(@Treasure {
game_id,
position: pos,
discovered: false
});
}
pos += 1;
};
world.write_model(@GameWorld { game_id, map });
}
// Find path between two points
fn find_path(world: WorldStorage, game_id: u32, start: u8, end: u8) -> Span<u8> {
let game_world: GameWorld = world.read_model(game_id);
game_world.map.search_path(start, end)
}Random
Pseudo-random generation for unpredictable game mechanics including dice rolling and deck management.
Key Features: Customizable dice, card deck shuffling/drawing, seeded generation
// Example: opening loot boxes with random rarity and item selection
use origami_random::dice::{Dice, DiceTrait};
use origami_random::deck::{Deck, DeckTrait};
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct LootBox {
#[key] pub player: ContractAddress,
#[key] pub box_id: u32,
pub rarity: u8,
pub item_id: u8,
}
fn open_loot_box(ref world: WorldStorage, player: ContractAddress, box_id: u32) {
let seed = get_block_timestamp().into() + box_id.into();
// Roll for rarity and determine loot quality
let mut rarity_dice = DiceTrait::new(6, seed);
let rarity = rarity_dice.roll();
let deck_size = match rarity { 6 => 10, 5 => 20, 4 => 40, _ => 100 };
// Draw items from appropriate deck
let mut item_deck = DeckTrait::new(seed, deck_size);
let item_count = if rarity >= 5 { 3 } else if rarity >= 3 { 2 } else { 1 };
let mut i = 0;
while i < item_count {
let loot = LootBox {
player, box_id: box_id + i.into(),
rarity: rarity.try_into().unwrap(),
item_id: item_deck.draw().try_into().unwrap(),
};
world.write_model(@loot);
i += 1;
}
}DeFi
Sophisticated auction mechanisms for in-game economies using Gradual Dutch Auctions (GDA) and Variable Rate GDAs (VRGDA).
Key Features: Discrete/continuous GDA, linear/logistic VRGDA, dynamic pricing
// Example: setting up and querying a GDA auction
use origami_defi::auction::gda::{DiscreteGDA, DiscreteGDATrait};
use cubit::f128::types::fixed::{Fixed, FixedTrait};
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct ItemAuction {
#[key] pub item_id: u32,
pub gda: DiscreteGDA,
pub start_time: u64,
}
fn setup_auction(ref world: WorldStorage, item_id: u32) {
let gda = DiscreteGDA {
sold: FixedTrait::new_unscaled(0, false), // 0 items sold
initial_price: FixedTrait::new_unscaled(1000, false), // 1000 units available
scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), // 1.1 - each item costs more
decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(100, false), // 0.01 - price decays over time
};
world.write_model(@ItemAuction { item_id, gda, start_time: get_block_timestamp() });
}
fn get_current_price(world: WorldStorage, item_id: u32, quantity: u32) -> u128 {
let auction: ItemAuction = world.read_model(item_id);
let time_elapsed = get_block_timestamp() - auction.start_time;
// Calculate current GDA price for the quantity
let price = auction.gda.purchase_price(
FixedTrait::new(time_elapsed.into(), false),
FixedTrait::new_unscaled(quantity.into(), false)
);
price.mag // Convert to u128
}Rating
Competitive ranking systems for multiplayer games using the industry-standard Elo rating system.
Key Features: Elo rating calculation, configurable K-factors, win/loss/draw support
// Example: resolving a match and updating player ratings
use origami_rating::elo::EloTrait;
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct PlayerRating {
#[key] pub player: ContractAddress,
pub rating: u64,
pub games_played: u32,
}
fn resolve_match(
ref world: WorldStorage,
player1: ContractAddress,
player2: ContractAddress,
outcome: u8 // 0 = p2 wins, 50 = draw, 100 = p1 wins
) {
let mut rating1: PlayerRating = world.read_model(player1);
let mut rating2: PlayerRating = world.read_model(player2);
// Calculate Elo changes
let k_factor = if rating1.games_played < 30 { 40 } else { 20 };
let (change1, is_negative1) = EloTrait::rating_change(
rating1.rating, rating2.rating, outcome.into(), k_factor
);
// Apply rating changes
rating1.rating = if is_negative1 { rating1.rating - change1 } else { rating1.rating + change1 };
rating2.rating = if is_negative1 { rating2.rating + change1 } else { rating2.rating - change1 };
rating1.games_played += 1;
rating2.games_played += 1;
world.write_model(@rating1);
world.write_model(@rating2);
}Security
Cryptographic primitives for secure game mechanics including commitment schemes for trustless gameplay.
Key Features: Commit-reveal schemes, cryptographic verification, front-running prevention
// Example: committing and revealing an action with a salt
use origami_security::commitment::{Commitment, CommitmentTrait};
use core::poseidon::poseidon_hash_span;
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct PlayerCommitment {
#[key] pub game_id: u32,
#[key] pub player: ContractAddress,
pub commitment_hash: felt252,
pub revealed: bool,
pub action: u8,
}
fn commit_action(ref world: WorldStorage, game_id: u32, player: ContractAddress, commitment_hash: felt252) {
// Player calculates hash(action + salt) off-chain and submits only the hash
world.write_model(@PlayerCommitment {
game_id, player, commitment_hash, revealed: false, action: 0
});
}
fn reveal_action(ref world: WorldStorage, game_id: u32, player: ContractAddress, action: u8, salt: felt252) -> bool {
let mut commitment: PlayerCommitment = world.read_model((game_id, player));
assert(!commitment.revealed, 'Already revealed');
// Verify commitment
let mut reveal_data = array![];
action.serialize(ref reveal_data);
salt.serialize(ref reveal_data);
let is_valid = poseidon_hash_span(reveal_data.span()) == commitment.commitment_hash;
if is_valid {
commitment.revealed = true;
commitment.action = action;
world.write_model(@commitment);
}
is_valid
}