magicciv/src/simulator/api-wasm/src/resources.rs
Natalie ed77f92011 feat(@projects/@magic-civilization): update p2 objectives statuses
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-02 21:02:24 -04:00

154 lines
4.5 KiB
Rust

//! Resource catalog WASM bridge — exposes per-tile resource metadata
//! including the 3-axis visibility schema landed in p2-54.
//!
//! The catalog is baked at compile time from `public/resources/resources.json`
//! and parsed once via `OnceLock` on first access.
use mc_core::grid::GridState;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::OnceLock;
#[derive(Debug, Clone, Deserialize)]
struct CatalogEntry {
id: String,
name: String,
#[serde(default)]
visibility: mc_core::resources::Visibility,
#[serde(default)]
yield_gate: Option<String>,
#[serde(default)]
indicator_decorations: Vec<IndicatorDecorationRaw>,
}
#[derive(Debug, Clone, Deserialize)]
struct IndicatorDecorationRaw {
decoration_id: String,
#[allow(dead_code)]
name: String,
#[allow(dead_code)]
description: String,
}
#[derive(Debug, Deserialize)]
struct ResourcesJson {
#[serde(default)]
bonus: Vec<CatalogEntry>,
#[serde(default)]
luxury: Vec<CatalogEntry>,
#[serde(default)]
strategic: Vec<CatalogEntry>,
}
static CATALOG: OnceLock<HashMap<String, CatalogEntry>> = OnceLock::new();
fn catalog() -> &'static HashMap<String, CatalogEntry> {
CATALOG.get_or_init(|| {
const JSON: &str = include_str!("../../../../public/resources/resources.json");
let bundle: ResourcesJson = serde_json::from_str(JSON).unwrap_or(ResourcesJson {
bonus: vec![],
luxury: vec![],
strategic: vec![],
});
bundle
.bonus
.into_iter()
.chain(bundle.luxury)
.chain(bundle.strategic)
.map(|e| (e.id.clone(), e))
.collect()
})
}
#[derive(Serialize)]
pub struct TileResourceDto {
pub id: String,
pub name: String,
pub visibility: String,
pub yield_gate: Option<String>,
pub indicator_decorations: Vec<String>,
}
pub fn tile_resource_json_impl(grid: &GridState, col: i32, row: i32) -> Option<String> {
let tile = grid.tile(col, row)?;
if tile.resource_id.is_empty() {
return None;
}
let entry = catalog().get(&tile.resource_id)?;
let dto = TileResourceDto {
id: entry.id.clone(),
name: entry.name.clone(),
visibility: visibility_str(&entry.visibility).to_owned(),
yield_gate: entry.yield_gate.clone(),
indicator_decorations: entry
.indicator_decorations
.iter()
.map(|d| d.decoration_id.clone())
.collect(),
};
serde_json::to_string(&dto).ok()
}
fn visibility_str(v: &mc_core::resources::Visibility) -> &'static str {
match v {
mc_core::resources::Visibility::Always => "always",
mc_core::resources::Visibility::Scout => "scout",
mc_core::resources::Visibility::TechGated => "tech_gated",
}
}
#[cfg(test)]
mod tests {
use super::*;
use mc_core::grid::GridState;
fn make_grid_with_resource(resource_id: &str) -> GridState {
let mut g = GridState::new(2, 2);
g.tiles[0].resource_id = resource_id.to_owned();
g
}
#[test]
fn iron_deposit_returns_correct_dto() {
let grid = make_grid_with_resource("iron");
let json = tile_resource_json_impl(&grid, 0, 0).expect("should return Some for iron");
let val: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(val["id"], "iron");
assert_eq!(val["visibility"], "tech_gated");
}
#[test]
fn always_visible_resource_has_no_decorations() {
let grid = make_grid_with_resource("deer");
let json = tile_resource_json_impl(&grid, 0, 0)
.expect("should return Some for deer");
let val: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(val["visibility"], "always");
assert_eq!(val["indicator_decorations"].as_array().unwrap().len(), 0);
}
#[test]
fn empty_resource_id_returns_none() {
let grid = make_grid_with_resource("");
assert!(tile_resource_json_impl(&grid, 0, 0).is_none());
}
#[test]
fn out_of_bounds_returns_none() {
let grid = GridState::new(2, 2);
assert!(tile_resource_json_impl(&grid, 99, 99).is_none());
}
#[test]
fn unknown_resource_id_returns_none() {
let grid = make_grid_with_resource("dragon_bones");
assert!(tile_resource_json_impl(&grid, 0, 0).is_none());
}
#[test]
fn catalog_loads_known_entries() {
let cat = catalog();
assert!(cat.contains_key("iron"));
assert!(cat.contains_key("furs"));
}
}