use std::collections::{HashMap, VecDeque, BTreeMap};
use std::time::{Duration, Instant};
use crate::events::{Event, EventBus, EventResult};
use crate::coordinates::{Distance, Neighbors};
pub struct TurnBasedGame {
participants: BTreeMap<u32, TurnParticipant>,
turn_order: VecDeque<u32>,
current_turn_index: usize,
round_number: u32,
turn_time_limit: Option<Duration>,
turn_start_time: Option<Instant>,
}
#[ derive( Debug, Clone ) ]
pub struct TurnParticipant
{
pub entity_id: u32,
pub initiative: u32,
pub action_points: u32,
pub max_action_points: u32,
pub can_act: bool,
pub status_effects: Vec<StatusEffect>,
}
#[ derive( Debug, Clone ) ]
pub struct StatusEffect
{
pub id: String,
pub name: String,
pub description: String,
pub duration: u32,
pub magnitude: f32,
pub is_beneficial: bool,
pub category: EffectCategory,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EffectCategory {
DamageOverTime,
HealingOverTime,
MovementSpeed,
AttackPower,
Defense,
CrowdControl,
Vision,
Regeneration,
Custom(String),
}
impl TurnBasedGame
{
pub fn new() -> Self
{
Self
{
participants: BTreeMap::new(),
turn_order: VecDeque::new(),
current_turn_index: 0,
round_number: 1,
turn_time_limit: None,
turn_start_time: None,
}
}
pub fn with_turn_time_limit(mut self, duration: Duration) -> Self {
self.turn_time_limit = Some(duration);
self
}
pub fn add_participant(&mut self, entity_id: u32, initiative: u32) {
let participant = TurnParticipant {
entity_id,
initiative,
action_points: 3, max_action_points: 3,
can_act: true,
status_effects: Vec::new(),
};
self.participants.insert(entity_id, participant);
self.rebuild_turn_order();
}
pub fn remove_participant(&mut self, entity_id: u32) {
self.participants.remove(&entity_id);
self.rebuild_turn_order();
}
pub fn current_turn(&self) -> Option<u32> {
if self.turn_order.is_empty() {
return None;
}
let index = self.current_turn_index % self.turn_order.len();
self.turn_order.get(index).copied()
}
pub fn current_participant(&self) -> Option<&TurnParticipant> {
self.current_turn().and_then(|id| self.participants.get(&id))
}
pub fn current_participant_mut(&mut self) -> Option<&mut TurnParticipant> {
if let Some(id) = self.current_turn() {
self.participants.get_mut(&id)
} else {
None
}
}
pub fn end_turn(&mut self) {
if self.turn_order.is_empty() {
return;
}
if let Some(participant) = self.current_participant_mut() {
participant.action_points = participant.max_action_points;
}
self.current_turn_index += 1;
if self.current_turn_index >= self.turn_order.len() {
self.round_number += 1;
self.current_turn_index = 0;
self.process_end_of_round();
}
self.turn_start_time = Some(Instant::now());
}
pub fn spend_action_points(&mut self, cost: u32) -> bool {
if let Some(participant) = self.current_participant_mut() {
if participant.action_points >= cost {
participant.action_points -= cost;
true
} else {
false
}
} else {
false
}
}
pub fn is_turn_timed_out(&self) -> bool {
if let (Some(limit), Some(start)) = (self.turn_time_limit, self.turn_start_time) {
start.elapsed() > limit
} else {
false
}
}
pub fn round_number(&self) -> u32 {
self.round_number
}
pub fn apply_status_effect(&mut self, entity_id: u32, effect: StatusEffect) {
if let Some(participant) = self.participants.get_mut(&entity_id) {
if let Some(existing_index) = participant.status_effects
.iter()
.position(|e| e.category == effect.category && e.id == effect.id) {
participant.status_effects[existing_index] = effect;
} else {
participant.status_effects.push(effect);
}
}
}
pub fn participants_in_order(&self) -> Vec<&TurnParticipant> {
self.turn_order
.iter()
.filter_map(|&id| self.participants.get(&id))
.collect()
}
fn rebuild_turn_order(&mut self) {
let mut participants: Vec<_> = self.participants.values().collect();
participants.sort_by(|a, b| b.initiative.cmp(&a.initiative));
self.turn_order = participants.into_iter()
.map(|p| p.entity_id)
.collect();
if !self.turn_order.is_empty() {
self.current_turn_index = self.current_turn_index.min(self.turn_order.len() - 1);
}
}
fn process_end_of_round(&mut self) {
for participant in self.participants.values_mut() {
participant.status_effects.retain_mut(|effect| {
effect.duration = effect.duration.saturating_sub(1);
effect.duration > 0
});
}
}
}
impl Default for TurnBasedGame {
fn default() -> Self {
Self::new()
}
}
pub struct GameStateMachine {
current_state: GameState,
previous_state: Option<GameState>,
state_data: HashMap<String, String>,
transitions: HashMap<(GameState, GameStateEvent), GameState>,
state_enter_handlers: HashMap<GameState, Box<dyn Fn(&mut Self)>>,
state_exit_handlers: HashMap<GameState, Box<dyn Fn(&mut Self)>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GameState {
Initialize,
MainMenu,
Loading,
Playing,
Paused,
PlayerTurn,
AITurn,
Combat,
Cutscene,
GameOver,
Victory,
Settings,
Inventory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GameStateEvent {
InitComplete,
StartGame,
LoadGame,
Pause,
Resume,
PlayerActionComplete,
AIActionComplete,
EnterCombat,
ExitCombat,
ShowCutscene,
CutsceneComplete,
PlayerDefeated,
VictoryAchieved,
OpenSettings,
CloseSettings,
OpenInventory,
CloseInventory,
ReturnToMenu,
QuitGame,
}
impl GameStateMachine {
pub fn new(initial_state: GameState) -> Self {
let mut machine = Self {
current_state: initial_state,
previous_state: None,
state_data: HashMap::new(),
transitions: HashMap::new(),
state_enter_handlers: HashMap::new(),
state_exit_handlers: HashMap::new(),
};
machine.setup_default_transitions();
machine
}
pub fn current_state(&self) -> GameState {
self.current_state
}
pub fn previous_state(&self) -> Option<GameState> {
self.previous_state
}
pub fn add_transition(&mut self, from: GameState, event: GameStateEvent, to: GameState) {
self.transitions.insert((from, event), to);
}
pub fn process_event(&mut self, event: GameStateEvent) -> bool {
if let Some(&new_state) = self.transitions.get(&(self.current_state, event)) {
self.transition_to(new_state);
true
} else {
false
}
}
pub fn transition_to(&mut self, new_state: GameState) {
let old_state = self.current_state;
if let Some(_handler) = self.state_exit_handlers.get(&old_state) {
}
self.previous_state = Some(old_state);
self.current_state = new_state;
if let Some(_handler) = self.state_enter_handlers.get(&new_state) {
}
}
pub fn set_state_data(&mut self, key: String, value: String) {
self.state_data.insert(key, value);
}
pub fn get_state_data(&self, key: &str) -> Option<&String> {
self.state_data.get(key)
}
pub fn can_transition(&self, event: GameStateEvent) -> bool {
self.transitions.contains_key(&(self.current_state, event))
}
fn setup_default_transitions(&mut self) {
self.add_transition(GameState::Initialize, GameStateEvent::InitComplete, GameState::MainMenu);
self.add_transition(GameState::MainMenu, GameStateEvent::StartGame, GameState::Loading);
self.add_transition(GameState::MainMenu, GameStateEvent::LoadGame, GameState::Loading);
self.add_transition(GameState::MainMenu, GameStateEvent::OpenSettings, GameState::Settings);
self.add_transition(GameState::Loading, GameStateEvent::StartGame, GameState::Playing);
self.add_transition(GameState::Playing, GameStateEvent::Pause, GameState::Paused);
self.add_transition(GameState::Playing, GameStateEvent::EnterCombat, GameState::Combat);
self.add_transition(GameState::Playing, GameStateEvent::OpenInventory, GameState::Inventory);
self.add_transition(GameState::Playing, GameStateEvent::PlayerDefeated, GameState::GameOver);
self.add_transition(GameState::Playing, GameStateEvent::VictoryAchieved, GameState::Victory);
self.add_transition(GameState::Paused, GameStateEvent::Resume, GameState::Playing);
self.add_transition(GameState::Paused, GameStateEvent::ReturnToMenu, GameState::MainMenu);
self.add_transition(GameState::Combat, GameStateEvent::ExitCombat, GameState::Playing);
self.add_transition(GameState::Combat, GameStateEvent::PlayerDefeated, GameState::GameOver);
self.add_transition(GameState::Combat, GameStateEvent::VictoryAchieved, GameState::Victory);
self.add_transition(GameState::Settings, GameStateEvent::CloseSettings, GameState::MainMenu);
self.add_transition(GameState::Inventory, GameStateEvent::CloseInventory, GameState::Playing);
self.add_transition(GameState::GameOver, GameStateEvent::ReturnToMenu, GameState::MainMenu);
self.add_transition(GameState::Victory, GameStateEvent::ReturnToMenu, GameState::MainMenu);
}
}
pub struct ResourceManager {
resources: HashMap<u32, EntityResources>,
}
#[derive(Debug, Clone)]
pub struct EntityResources {
pub entity_id: u32,
pub health: Resource,
pub mana: Resource,
pub experience: u64,
pub level: u32,
pub currency: u64,
pub custom: HashMap<String, f32>,
}
#[derive(Debug, Clone)]
pub struct Resource {
pub current: f32,
pub maximum: f32,
pub regeneration: f32,
}
impl Resource {
pub fn new(maximum: f32) -> Self {
Self {
current: maximum,
maximum,
regeneration: 0.0,
}
}
pub fn with_regeneration(maximum: f32, regeneration: f32) -> Self {
Self {
current: maximum,
maximum,
regeneration,
}
}
pub fn percentage(&self) -> f32 {
if self.maximum > 0.0 {
(self.current / self.maximum).clamp(0.0, 1.0)
} else {
0.0
}
}
pub fn modify(&mut self, amount: f32) {
self.current = (self.current + amount).clamp(0.0, self.maximum);
}
pub fn set_current(&mut self, value: f32) {
self.current = value.clamp(0.0, self.maximum);
}
pub fn set_maximum(&mut self, value: f32) {
self.maximum = value.max(0.0);
self.current = self.current.min(self.maximum);
}
pub fn update(&mut self, delta_time: f32) {
if self.regeneration != 0.0 {
self.modify(self.regeneration * delta_time);
}
}
pub fn is_depleted(&self) -> bool {
self.current <= 0.0
}
pub fn is_full(&self) -> bool {
(self.current - self.maximum).abs() < f32::EPSILON
}
}
impl ResourceManager {
pub fn new() -> Self {
Self {
resources: HashMap::new(),
}
}
pub fn add_entity(&mut self, entity_id: u32, health: f32, mana: f32) {
let resources = EntityResources {
entity_id,
health: Resource::new(health),
mana: Resource::new(mana),
experience: 0,
level: 1,
currency: 0,
custom: HashMap::new(),
};
self.resources.insert(entity_id, resources);
}
pub fn remove_entity(&mut self, entity_id: u32) {
self.resources.remove(&entity_id);
}
pub fn get_resources(&self, entity_id: u32) -> Option<&EntityResources> {
self.resources.get(&entity_id)
}
pub fn get_resources_mut(&mut self, entity_id: u32) -> Option<&mut EntityResources> {
self.resources.get_mut(&entity_id)
}
pub fn modify_health(&mut self, entity_id: u32, amount: f32) -> bool {
if let Some(resources) = self.resources.get_mut(&entity_id) {
resources.health.modify(amount);
true
} else {
false
}
}
pub fn modify_mana(&mut self, entity_id: u32, amount: f32) -> bool {
if let Some(resources) = self.resources.get_mut(&entity_id) {
resources.mana.modify(amount);
true
} else {
false
}
}
pub fn update_all(&mut self, delta_time: f32) {
for resources in self.resources.values_mut() {
resources.health.update(delta_time);
resources.mana.update(delta_time);
}
}
pub fn get_defeated_entities(&self) -> Vec<u32> {
self.resources
.iter()
.filter(|(_, r)| r.health.is_depleted())
.map(|(&id, _)| id)
.collect()
}
}
impl Default for ResourceManager {
fn default() -> Self {
Self::new()
}
}
pub struct QuestManager {
quests: HashMap<String, Quest>,
active_quests: Vec<String>,
completed_quests: Vec<String>,
global_flags: HashMap<String, bool>,
}
#[derive(Debug, Clone)]
pub struct Quest {
pub id: String,
pub name: String,
pub description: String,
pub status: QuestStatus,
pub objectives: Vec<QuestObjective>,
pub prerequisites: Vec<QuestCondition>,
pub rewards: Vec<QuestReward>,
pub data: HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum QuestStatus {
Locked,
Available,
Active,
Completed,
Failed,
}
#[derive(Debug, Clone)]
pub struct QuestObjective {
pub id: String,
pub description: String,
pub completed: bool,
pub objective_type: ObjectiveType,
pub optional: bool,
}
#[derive(Debug, Clone)]
pub enum ObjectiveType {
KillTargets { target_type: String, count: u32, current: u32 },
ReachLocation { x: i32, y: i32, radius: u32 },
CollectItems { item_id: String, count: u32, current: u32 },
TalkToNPC { npc_id: u32 },
Survive { duration_seconds: u32 },
Custom { data: HashMap<String, String> },
}
#[derive(Debug, Clone)]
pub enum QuestCondition {
MinLevel(u32),
QuestCompleted(String),
FlagSet(String),
HasItems(String, u32),
}
#[derive(Debug, Clone)]
pub enum QuestReward {
Experience(u64),
Currency(u64),
Items(String, u32),
UnlockQuest(String),
SetFlag(String),
}
impl QuestManager {
pub fn new() -> Self {
Self {
quests: HashMap::new(),
active_quests: Vec::new(),
completed_quests: Vec::new(),
global_flags: HashMap::new(),
}
}
pub fn add_quest(&mut self, quest: Quest) {
self.quests.insert(quest.id.clone(), quest);
}
pub fn start_quest(&mut self, quest_id: &str, player_level: u32) -> bool {
let can_start = if let Some(quest) = self.quests.get(quest_id) {
quest.status == QuestStatus::Available &&
self.check_prerequisites(&quest.prerequisites, player_level)
} else {
false
};
if can_start {
if let Some(quest) = self.quests.get_mut(quest_id) {
quest.status = QuestStatus::Active;
self.active_quests.push(quest_id.to_string());
return true;
}
}
false
}
pub fn complete_quest(&mut self, quest_id: &str) -> Vec<QuestReward> {
if let Some(quest) = self.quests.get_mut(quest_id) {
if quest.status == QuestStatus::Active {
quest.status = QuestStatus::Completed;
self.active_quests.retain(|id| id != quest_id);
self.completed_quests.push(quest_id.to_string());
return quest.rewards.clone();
}
}
Vec::new()
}
pub fn update_objective(&mut self, quest_id: &str, objective_id: &str, progress: u32) {
if let Some(quest) = self.quests.get_mut(quest_id) {
if quest.status == QuestStatus::Active {
for objective in &mut quest.objectives {
if objective.id == objective_id {
match &mut objective.objective_type {
ObjectiveType::KillTargets { count, current, .. } => {
*current = (*current + progress).min(*count);
objective.completed = *current >= *count;
},
ObjectiveType::CollectItems { count, current, .. } => {
*current = (*current + progress).min(*count);
objective.completed = *current >= *count;
},
_ => {}
}
}
}
let all_required_complete = quest.objectives
.iter()
.filter(|obj| !obj.optional)
.all(|obj| obj.completed);
if all_required_complete {
self.complete_quest(quest_id);
}
}
}
}
pub fn set_flag(&mut self, flag: String, value: bool) {
self.global_flags.insert(flag, value);
}
pub fn get_flag(&self, flag: &str) -> bool {
self.global_flags.get(flag).copied().unwrap_or(false)
}
pub fn active_quests(&self) -> Vec<&Quest> {
self.active_quests
.iter()
.filter_map(|id| self.quests.get(id))
.collect()
}
pub fn completed_quests(&self) -> Vec<&Quest> {
self.completed_quests
.iter()
.filter_map(|id| self.quests.get(id))
.collect()
}
pub fn completed_quest_count(&self) -> usize {
self.completed_quests.len()
}
pub fn is_quest_completed(&self, quest_id: &str) -> bool {
self.completed_quests.contains(&quest_id.to_string())
}
fn check_prerequisites(&self, prerequisites: &[QuestCondition], player_level: u32) -> bool {
prerequisites.iter().all(|condition| {
match condition {
QuestCondition::MinLevel(level) => player_level >= *level,
QuestCondition::QuestCompleted(quest_id) => {
self.completed_quests.contains(quest_id)
},
QuestCondition::FlagSet(flag) => self.get_flag(flag),
QuestCondition::HasItems(_, _) => true, }
})
}
}
impl Default for QuestManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TurnStartedEvent {
pub entity_id: u32,
pub round_number: u32,
pub action_points: u32,
}
#[derive(Debug, Clone)]
pub struct TurnEndedEvent {
pub entity_id: u32,
pub actions_taken: u32,
}
#[derive(Debug, Clone)]
pub struct ResourceChangedEvent {
pub entity_id: u32,
pub resource_type: String,
pub old_value: f32,
pub new_value: f32,
}
#[derive(Debug, Clone)]
pub struct QuestCompletedEvent {
pub quest_id: String,
pub rewards: Vec<QuestReward>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_turn_based_game_creation() {
let game = TurnBasedGame::new();
assert_eq!(game.round_number(), 1);
assert!(game.current_turn().is_none());
}
#[test]
fn test_turn_based_participants() {
let mut game = TurnBasedGame::new();
game.add_participant(1, 100);
game.add_participant(2, 85);
game.add_participant(3, 95);
assert_eq!(game.current_turn(), Some(1));
game.end_turn();
assert_eq!(game.current_turn(), Some(3));
game.end_turn();
assert_eq!(game.current_turn(), Some(2));
game.end_turn();
assert_eq!(game.current_turn(), Some(1)); assert_eq!(game.round_number(), 2);
}
#[test]
fn test_action_points() {
let mut game = TurnBasedGame::new();
game.add_participant(1, 100);
assert_eq!(game.current_participant().unwrap().action_points, 3);
assert!(game.spend_action_points(2));
assert_eq!(game.current_participant().unwrap().action_points, 1);
assert!(!game.spend_action_points(2));
assert_eq!(game.current_participant().unwrap().action_points, 1);
}
#[test]
fn test_game_state_machine() {
let mut machine = GameStateMachine::new(GameState::Initialize);
assert_eq!(machine.current_state(), GameState::Initialize);
assert!(machine.process_event(GameStateEvent::InitComplete));
assert_eq!(machine.current_state(), GameState::MainMenu);
assert!(machine.process_event(GameStateEvent::StartGame));
assert_eq!(machine.current_state(), GameState::Loading);
assert!(!machine.process_event(GameStateEvent::Pause));
assert_eq!(machine.current_state(), GameState::Loading);
}
#[test]
fn test_resource_management() {
let mut resource = Resource::new(100.0);
assert_eq!(resource.current, 100.0);
assert_eq!(resource.percentage(), 1.0);
resource.modify(-30.0);
assert_eq!(resource.current, 70.0);
assert_eq!(resource.percentage(), 0.7);
resource.modify(-200.0);
assert_eq!(resource.current, 0.0);
assert!(resource.is_depleted());
resource.set_current(50.0);
assert_eq!(resource.current, 50.0);
assert!(!resource.is_depleted());
assert!(!resource.is_full());
}
#[test]
fn test_resource_manager() {
let mut manager = ResourceManager::new();
manager.add_entity(1, 100.0, 50.0);
assert!(manager.modify_health(1, -25.0));
assert_eq!(manager.get_resources(1).unwrap().health.current, 75.0);
assert!(manager.modify_mana(1, -10.0));
assert_eq!(manager.get_resources(1).unwrap().mana.current, 40.0);
manager.modify_health(1, -100.0);
let defeated = manager.get_defeated_entities();
assert_eq!(defeated, vec![1]);
}
#[test]
fn test_quest_system() {
let mut quest_manager = QuestManager::new();
let quest = Quest {
id: "test_quest".to_string(),
name: "Test Quest".to_string(),
description: "A simple test quest".to_string(),
status: QuestStatus::Available,
objectives: vec![QuestObjective {
id: "kill_enemies".to_string(),
description: "Kill 5 enemies".to_string(),
completed: false,
objective_type: ObjectiveType::KillTargets {
target_type: "orc".to_string(),
count: 5,
current: 0,
},
optional: false,
}],
prerequisites: vec![],
rewards: vec![QuestReward::Experience(100)],
data: HashMap::new(),
};
quest_manager.add_quest(quest);
assert!(quest_manager.start_quest("test_quest", 1));
assert_eq!(quest_manager.active_quests().len(), 1);
quest_manager.update_objective("test_quest", "kill_enemies", 3);
quest_manager.update_objective("test_quest", "kill_enemies", 2);
assert_eq!(quest_manager.completed_quests.len(), 1);
}
#[test]
fn test_status_effects() {
let mut game = TurnBasedGame::new();
game.add_participant(1, 100);
let poison = StatusEffect {
id: "poison".to_string(),
name: "Poison".to_string(),
description: "Takes damage over time".to_string(),
duration: 3,
magnitude: 5.0,
is_beneficial: false,
category: EffectCategory::DamageOverTime,
};
game.apply_status_effect(1, poison);
let participant = game.participants.get(&1).unwrap();
assert_eq!(participant.status_effects.len(), 1);
assert_eq!(participant.status_effects[0].duration, 3);
}
}