use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActionConsumed {
pub context: String,
pub remaining: u32,
pub depleted: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActionError {
Depleted,
}
impl fmt::Display for ActionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ActionError::Depleted => write!(f, "No action points remaining"),
}
}
}
impl std::error::Error for ActionError {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionPoints {
pub available: u32,
pub max_per_period: u32,
}
impl ActionPoints {
pub fn new(max_per_period: u32) -> Self {
Self {
available: max_per_period,
max_per_period,
}
}
pub fn consume_with(
&mut self,
context: impl Into<String>,
) -> Result<ActionConsumed, ActionError> {
if self.available == 0 {
return Err(ActionError::Depleted);
}
self.available -= 1;
Ok(ActionConsumed {
context: context.into(),
remaining: self.available,
depleted: self.available == 0,
})
}
pub fn consume(&mut self) -> bool {
self.consume_with("").is_ok()
}
pub fn consume_n(&mut self, n: u32) -> bool {
if self.available >= n {
self.available -= n;
true
} else {
false
}
}
pub fn reset(&mut self) {
self.available = self.max_per_period;
}
pub fn is_depleted(&self) -> bool {
self.available == 0
}
pub fn can_consume(&self, n: u32) -> bool {
self.available >= n
}
}
impl Default for ActionPoints {
fn default() -> Self {
Self::new(3)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_points() {
let points = ActionPoints::new(5);
assert_eq!(points.available, 5);
assert_eq!(points.max_per_period, 5);
}
#[test]
fn test_consume() {
let mut points = ActionPoints::new(3);
assert!(points.consume());
assert_eq!(points.available, 2);
assert!(points.consume());
assert_eq!(points.available, 1);
assert!(points.consume());
assert_eq!(points.available, 0);
assert!(!points.consume());
assert_eq!(points.available, 0);
}
#[test]
fn test_consume_n() {
let mut points = ActionPoints::new(5);
assert!(points.consume_n(2));
assert_eq!(points.available, 3);
assert!(points.consume_n(3));
assert_eq!(points.available, 0);
assert!(!points.consume_n(1));
}
#[test]
fn test_consume_n_insufficient() {
let mut points = ActionPoints::new(2);
assert!(!points.consume_n(3));
assert_eq!(points.available, 2); }
#[test]
fn test_reset() {
let mut points = ActionPoints::new(4);
points.consume();
points.consume();
assert_eq!(points.available, 2);
points.reset();
assert_eq!(points.available, 4);
}
#[test]
fn test_is_depleted() {
let mut points = ActionPoints::new(1);
assert!(!points.is_depleted());
points.consume();
assert!(points.is_depleted());
points.reset();
assert!(!points.is_depleted());
}
#[test]
fn test_can_consume() {
let points = ActionPoints::new(3);
assert!(points.can_consume(0));
assert!(points.can_consume(1));
assert!(points.can_consume(3));
assert!(!points.can_consume(4));
}
#[test]
fn test_default() {
let points = ActionPoints::default();
assert_eq!(points.available, 3);
assert_eq!(points.max_per_period, 3);
}
#[test]
fn test_consume_with() {
let mut points = ActionPoints::new(3);
let result = points.consume_with("Deploy troops");
assert!(result.is_ok());
let consumed = result.unwrap();
assert_eq!(consumed.context, "Deploy troops");
assert_eq!(consumed.remaining, 2);
assert!(!consumed.depleted);
let result = points.consume_with("Research tech");
assert!(result.is_ok());
let consumed = result.unwrap();
assert_eq!(consumed.context, "Research tech");
assert_eq!(consumed.remaining, 1);
assert!(!consumed.depleted);
let result = points.consume_with("Build structure");
assert!(result.is_ok());
let consumed = result.unwrap();
assert_eq!(consumed.context, "Build structure");
assert_eq!(consumed.remaining, 0);
assert!(consumed.depleted);
let result = points.consume_with("Extra action");
assert!(result.is_err());
assert!(matches!(result, Err(ActionError::Depleted)));
}
#[test]
fn test_consume_with_empty_context() {
let mut points = ActionPoints::new(1);
let result = points.consume_with("");
assert!(result.is_ok());
let consumed = result.unwrap();
assert_eq!(consumed.context, "");
assert_eq!(consumed.remaining, 0);
assert!(consumed.depleted);
}
#[test]
fn test_action_error_display() {
let error = ActionError::Depleted;
assert_eq!(error.to_string(), "No action points remaining");
}
}