Files
coorl-lost-cities/rust/lost-cities-core/src/config.rs
T
coolguy b43bb5917f Rust 크레이트를 루트로 이동
맥락:
- GUI 이식 전에 Rust crate와 proto schema 위치를 Python package 내부에서 분리한다.
- Cargo 작업, IDE 인식, 빌드 산출물 관리를 루트 구조에 맞춘다.

변경:
- rust_core를 rust/lost-cities-core로 이동하고 proto/lost_cities.proto를 루트 proto 디렉터리로 옮겼다.
- Rust build.rs, Python Rust backend, Rust parity 테스트의 경로를 새 위치로 수정했다.
- Python package-data에서 Rust crate와 proto 항목을 제거하고 README/port notes를 갱신했다.

확인:
- uv sync --extra gui --reinstall-package coolrl-lost-cities
- uv run pytest tests/games/classic
- uv run lost-cities-classic
2026-05-06 19:50:32 +09:00

132 lines
3.8 KiB
Rust

use crate::error::EngineError;
use crate::proto;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Config {
pub n_colors: usize,
pub n_ranks: u32,
pub min_rank: u32,
pub n_handshakes: u32,
pub hand_size: usize,
pub expedition_penalty: i32,
pub bonus_threshold: usize,
pub bonus_amount: i32,
pub seed: Option<u64>,
}
impl Default for Config {
fn default() -> Self {
Self {
n_colors: 5,
n_ranks: 9,
min_rank: 2,
n_handshakes: 3,
hand_size: 8,
expedition_penalty: -20,
bonus_threshold: 8,
bonus_amount: 20,
seed: None,
}
}
}
impl Config {
pub fn validate(&self) -> Result<(), EngineError> {
if self.n_colors == 0 {
return Err(EngineError::invalid_argument("n_colors must be positive"));
}
if self.n_ranks == 0 {
return Err(EngineError::invalid_argument("n_ranks must be positive"));
}
if self.min_rank == 0 {
return Err(EngineError::invalid_argument("min_rank must be positive"));
}
if self.hand_size == 0 {
return Err(EngineError::invalid_argument("hand_size must be positive"));
}
if self.bonus_threshold == 0 {
return Err(EngineError::invalid_argument(
"bonus_threshold must be positive",
));
}
if self.deck_size() < 2 * self.hand_size {
return Err(EngineError::invalid_argument(
"deck must contain at least both initial hands",
));
}
Ok(())
}
pub fn with_seed(mut self, seed: Option<u64>) -> Self {
self.seed = seed;
self
}
pub fn deck_size(&self) -> usize {
self.n_colors * (self.n_ranks as usize + self.n_handshakes as usize)
}
pub fn card_action_size(&self) -> usize {
self.hand_size * 2
}
pub fn draw_action_size(&self) -> usize {
1 + self.n_colors
}
pub fn action_space_size(&self) -> usize {
self.card_action_size() + self.draw_action_size()
}
}
impl TryFrom<&proto::GameConfig> for Config {
type Error = EngineError;
fn try_from(value: &proto::GameConfig) -> Result<Self, Self::Error> {
let n_colors = usize::try_from(value.n_colors)
.map_err(|_| EngineError::invalid_argument("n_colors is out of range"))?;
let hand_size = usize::try_from(value.hand_size)
.map_err(|_| EngineError::invalid_argument("hand_size is out of range"))?;
let bonus_threshold = usize::try_from(value.bonus_threshold)
.map_err(|_| EngineError::invalid_argument("bonus_threshold is out of range"))?;
let config = Self {
n_colors,
n_ranks: value.n_ranks,
min_rank: value.min_rank,
n_handshakes: value.n_handshakes,
hand_size,
expedition_penalty: value.expedition_penalty,
bonus_threshold,
bonus_amount: value.bonus_amount,
seed: value.seed,
};
config.validate()?;
Ok(config)
}
}
impl TryFrom<proto::GameConfig> for Config {
type Error = EngineError;
fn try_from(value: proto::GameConfig) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
impl From<&Config> for proto::GameConfig {
fn from(value: &Config) -> Self {
Self {
n_colors: value.n_colors as u32,
n_ranks: value.n_ranks,
min_rank: value.min_rank,
n_handshakes: value.n_handshakes,
hand_size: value.hand_size as u32,
expedition_penalty: value.expedition_penalty,
bonus_threshold: value.bonus_threshold as u32,
bonus_amount: value.bonus_amount,
seed: value.seed,
}
}
}