use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EnvironmentInfo {
pub game_id: String,
pub title: Option<String>,
pub default_fps: Option<u32>,
pub tags: Option<Vec<String>>,
#[serde(skip_serializing)]
pub private_tags: Option<Vec<String>>,
#[serde(skip_serializing)]
pub level_tags: Option<Vec<Vec<String>>>,
pub baseline_actions: Option<Vec<u32>>,
pub date_downloaded: Option<DateTime<Utc>>,
pub class_name: Option<String>,
#[serde(skip)]
pub local_dir: Option<String>,
}
impl EnvironmentInfo {
pub fn base_id(&self) -> &str {
self.game_id
.split_once('-')
.map(|(base, _)| base)
.unwrap_or(&self.game_id)
}
pub fn version(&self) -> Option<&str> {
self.game_id.split_once('-').map(|(_, ver)| ver)
}
pub fn effective_fps(&self) -> u32 {
self.default_fps.unwrap_or(5)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum OperationMode {
#[default]
Normal,
Online,
Offline,
Competition,
}
impl OperationMode {
pub fn as_str(&self) -> &'static str {
match self {
OperationMode::Normal => "normal",
OperationMode::Online => "online",
OperationMode::Offline => "offline",
OperationMode::Competition => "competition",
}
}
}
impl FromStr for OperationMode {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_lowercase().as_str() {
"normal" => Ok(OperationMode::Normal),
"online" => Ok(OperationMode::Online),
"offline" => Ok(OperationMode::Offline),
"competition" => Ok(OperationMode::Competition),
_ => Err(()),
}
}
}
impl fmt::Display for OperationMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GameState {
#[default]
NotPlayed,
NotFinished,
Win,
GameOver,
}
impl GameState {
pub fn as_str(&self) -> &'static str {
match self {
GameState::NotPlayed => "NOT_PLAYED",
GameState::NotFinished => "NOT_FINISHED",
GameState::Win => "WIN",
GameState::GameOver => "GAME_OVER",
}
}
}
impl FromStr for GameState {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"NOT_PLAYED" => Ok(GameState::NotPlayed),
"NOT_FINISHED" => Ok(GameState::NotFinished),
"WIN" => Ok(GameState::Win),
"GAME_OVER" => Ok(GameState::GameOver),
_ => Err(()),
}
}
}
impl fmt::Display for GameState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ActionInput {
pub id: u32,
pub data: Option<Value>,
pub reasoning: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FrameData {
pub game_id: String,
pub guid: Option<String>,
pub frame: Vec<Value>,
pub state: GameState,
pub levels_completed: u32,
pub win_levels: u32,
pub available_actions: Vec<u32>,
pub action_input: Option<ActionInput>,
#[serde(default)]
pub full_reset: bool,
}
impl FrameData {
pub fn is_empty(&self) -> bool {
self.frame.is_empty() && self.state == GameState::NotPlayed
}
}