feat: module 01 handles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1453a00b9c
commit
78dc09b303
32 files changed
+2561
No files matched your search
@@ -0,0 +1,36 @@
|
||||
//! Card definitions as the engine sees them. The definitions themselves live in `content`.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// What the engine needs to know about a card. Module 1: only its name.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CardDef {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl CardDef {
|
||||
pub fn new(name: &str) -> CardDef {
|
||||
CardDef {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Every card the game can create, looked up by name.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CardLibrary {
|
||||
cards: HashMap<String, CardDef>,
|
||||
}
|
||||
|
||||
impl CardLibrary {
|
||||
pub fn new(defs: Vec<CardDef>) -> CardLibrary {
|
||||
let cards = defs.into_iter().map(|d| (d.name.clone(), d)).collect();
|
||||
CardLibrary { cards }
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> &CardDef {
|
||||
self.cards
|
||||
.get(name)
|
||||
.unwrap_or_else(|| panic!("unknown card {name:?}: define it in src/content"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//! Engine core: generic rules machinery. Never name a specific card here.
|
||||
//!
|
||||
//! Module 1: every card in the game is an entity addressed by an id handle.
|
||||
//! The engine knows which zone each entity is in, who owns it, and when it
|
||||
//! entered play.
|
||||
|
||||
mod card;
|
||||
mod world;
|
||||
|
||||
pub use card::{CardDef, CardLibrary};
|
||||
pub use world::{EntityId, PlayerId, World, Zone};
|
||||
@@ -0,0 +1,133 @@
|
||||
//! Entities, zones and order of play.
|
||||
|
||||
use super::card::CardLibrary;
|
||||
|
||||
/// A handle to an entity. Ids are never reused and stay valid when the entity changes zone.
|
||||
pub type EntityId = u32;
|
||||
pub type PlayerId = u8;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Zone {
|
||||
Deck,
|
||||
Hand,
|
||||
Board,
|
||||
Graveyard,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Entity {
|
||||
card: String,
|
||||
owner: PlayerId,
|
||||
zone: Zone,
|
||||
/// Stamped each time the entity enters the board; `None` until it first does.
|
||||
order_of_play: Option<u32>,
|
||||
}
|
||||
|
||||
/// One player's zones. Each is an ordered list of ids (the board is left to right).
|
||||
#[derive(Debug, Default)]
|
||||
struct Side {
|
||||
deck: Vec<EntityId>,
|
||||
hand: Vec<EntityId>,
|
||||
board: Vec<EntityId>,
|
||||
graveyard: Vec<EntityId>,
|
||||
}
|
||||
|
||||
impl Side {
|
||||
fn zone(&self, zone: Zone) -> &Vec<EntityId> {
|
||||
match zone {
|
||||
Zone::Deck => &self.deck,
|
||||
Zone::Hand => &self.hand,
|
||||
Zone::Board => &self.board,
|
||||
Zone::Graveyard => &self.graveyard,
|
||||
}
|
||||
}
|
||||
|
||||
fn zone_mut(&mut self, zone: Zone) -> &mut Vec<EntityId> {
|
||||
match zone {
|
||||
Zone::Deck => &mut self.deck,
|
||||
Zone::Hand => &mut self.hand,
|
||||
Zone::Board => &mut self.board,
|
||||
Zone::Graveyard => &mut self.graveyard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct World {
|
||||
library: CardLibrary,
|
||||
/// Indexed by `EntityId`.
|
||||
entities: Vec<Entity>,
|
||||
sides: [Side; 2],
|
||||
next_order_of_play: u32,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(library: CardLibrary) -> World {
|
||||
World {
|
||||
library,
|
||||
entities: Vec::new(),
|
||||
sides: Default::default(),
|
||||
next_order_of_play: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an entity of `card` for `owner` at the end of `zone` (the right end for the board).
|
||||
pub fn create(&mut self, card: &str, owner: PlayerId, zone: Zone) -> EntityId {
|
||||
assert!(owner < 2, "no player {owner}");
|
||||
let card = self.library.get(card).name.clone();
|
||||
let id = self.entities.len() as EntityId;
|
||||
self.entities.push(Entity {
|
||||
card,
|
||||
owner,
|
||||
zone,
|
||||
order_of_play: None,
|
||||
});
|
||||
self.enter(id, zone);
|
||||
id
|
||||
}
|
||||
|
||||
/// Move an entity to the end of `zone` on its owner's side, keeping its id.
|
||||
pub fn move_to(&mut self, id: EntityId, zone: Zone) {
|
||||
let (owner, from) = (self.entity(id).owner, self.entity(id).zone);
|
||||
self.sides[owner as usize]
|
||||
.zone_mut(from)
|
||||
.retain(|&e| e != id);
|
||||
self.enter(id, zone);
|
||||
}
|
||||
|
||||
/// The single place an entity arrives in a zone. Entering the board starts a new order of play.
|
||||
fn enter(&mut self, id: EntityId, zone: Zone) {
|
||||
let owner = self.entity(id).owner;
|
||||
self.sides[owner as usize].zone_mut(zone).push(id);
|
||||
let entity = &mut self.entities[id as usize];
|
||||
entity.zone = zone;
|
||||
if zone == Zone::Board {
|
||||
self.next_order_of_play += 1;
|
||||
entity.order_of_play = Some(self.next_order_of_play);
|
||||
}
|
||||
}
|
||||
|
||||
fn entity(&self, id: EntityId) -> &Entity {
|
||||
self.entities
|
||||
.get(id as usize)
|
||||
.unwrap_or_else(|| panic!("unknown entity id {id}"))
|
||||
}
|
||||
|
||||
pub fn zone_of(&self, id: EntityId) -> Zone {
|
||||
self.entity(id).zone
|
||||
}
|
||||
|
||||
pub fn card_name(&self, id: EntityId) -> &str {
|
||||
&self.entity(id).card
|
||||
}
|
||||
|
||||
/// When the entity last entered the board (larger is later); `None` if it never has.
|
||||
pub fn order_of_play(&self, id: EntityId) -> Option<u32> {
|
||||
self.entity(id).order_of_play
|
||||
}
|
||||
|
||||
/// The ids in one player's zone, in zone order (left to right for the board).
|
||||
pub fn zone(&self, owner: PlayerId, zone: Zone) -> &[EntityId] {
|
||||
assert!(owner < 2, "no player {owner}");
|
||||
self.sides[owner as usize].zone(zone)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user