Implement robe wrapper and fix tests
This commit is contained in:
parent
b482cafbc2
commit
e23572ff20
@ -2,12 +2,12 @@ mod companion;
|
||||
mod glyphs;
|
||||
mod level;
|
||||
mod murals;
|
||||
mod robe;
|
||||
mod scarf;
|
||||
mod symbol;
|
||||
mod test;
|
||||
|
||||
|
||||
use core::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -15,6 +15,7 @@ use std::path::{Path, PathBuf};
|
||||
use binrw::{until_eof, BinRead, BinReaderExt, BinWriterExt};
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use level::Level;
|
||||
use robe::Robe;
|
||||
use scarf::Scarf;
|
||||
use symbol::Symbol;
|
||||
|
||||
@ -22,6 +23,7 @@ use crate::companion::{CompanionSymbols, CompanionWithId, Companions};
|
||||
use crate::glyphs::Glyphs;
|
||||
pub use crate::level::NAMES as LEVEL_NAMES;
|
||||
use crate::murals::Murals;
|
||||
pub use crate::robe::Color as RobeColor;
|
||||
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
@ -53,27 +55,14 @@ pub enum Error {
|
||||
#[error("Symbol id is out of range")]
|
||||
SymbolIdOutOfRange,
|
||||
|
||||
#[error(transparent)]
|
||||
RobeChange(robe::Error),
|
||||
|
||||
#[error("Failed to read file")]
|
||||
FileReadingFailed(io::Error),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RobeColor {
|
||||
Red,
|
||||
White,
|
||||
}
|
||||
|
||||
impl fmt::Display for RobeColor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Red => write!(f, "Red"),
|
||||
Self::White => write!(f, "White"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[binrw::binrw]
|
||||
#[derive(Debug, Clone)]
|
||||
#[brw(little)]
|
||||
@ -84,7 +73,7 @@ pub struct Savefile {
|
||||
#[br(count = 8)]
|
||||
_unknown0: Vec<u8>,
|
||||
|
||||
robe: u32,
|
||||
pub robe: Robe,
|
||||
|
||||
pub symbol: Symbol,
|
||||
|
||||
@ -192,42 +181,6 @@ impl Savefile {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn robe_color(&self) -> RobeColor {
|
||||
if self.robe > 3 {
|
||||
RobeColor::White
|
||||
} else {
|
||||
RobeColor::Red
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_robe_color(&mut self, color: RobeColor) {
|
||||
self.robe = match (self.robe_color(), color) {
|
||||
(RobeColor::Red, RobeColor::White) => self.robe + 4,
|
||||
(RobeColor::White, RobeColor::Red) => self.robe - 4,
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn robe_tier(&self) -> u32 {
|
||||
match self.robe_color() {
|
||||
RobeColor::Red => self.robe + 1,
|
||||
RobeColor::White => self.robe - 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_robe_tier(&mut self, tier: u32) {
|
||||
if tier < 1 || tier > 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.robe = match self.robe_color() {
|
||||
RobeColor::Red => tier - 1,
|
||||
// There can't be a tier 1 white robe, setting it to the lowers possible tier
|
||||
RobeColor::White if tier == 1 => 4,
|
||||
RobeColor::White => tier + 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::parser(reader, endian)]
|
||||
|
192
crates/save/src/robe.rs
Normal file
192
crates/save/src/robe.rs
Normal file
@ -0,0 +1,192 @@
|
||||
use core::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use binrw::{BinRead, BinWrite};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
|
||||
const MIN_TIER: u32 = 1;
|
||||
const MAX_TIER: u32 = 4;
|
||||
const MAX_RED_TIER_ID: u32 = 3;
|
||||
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
#[error("Tier must be in range from 1 to 4")]
|
||||
TierOutOfRange,
|
||||
|
||||
#[error("White tier can not be lower than 2")]
|
||||
WhiteTierMinimum,
|
||||
|
||||
#[error("Invalid color, expected red or white")]
|
||||
InvalidColor,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, BinRead, BinWrite)]
|
||||
pub struct Robe {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl Robe {
|
||||
pub fn color(&self) -> Color {
|
||||
if self.value > MAX_RED_TIER_ID {
|
||||
Color::White
|
||||
} else {
|
||||
Color::Red
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, color: Color) {
|
||||
self.value = match (self.color(), color, self.value) {
|
||||
(Color::Red, Color::White, 0) => MAX_RED_TIER_ID + 1,
|
||||
(Color::Red, Color::White, val) => val + MAX_RED_TIER_ID,
|
||||
(Color::White, Color::Red, val) => val - MAX_RED_TIER_ID,
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap_colors(&mut self) {
|
||||
let new_color = match self.color() {
|
||||
Color::Red => Color::White,
|
||||
Color::White => Color::Red,
|
||||
};
|
||||
|
||||
self.set_color(new_color);
|
||||
}
|
||||
|
||||
pub fn tier(&self) -> u32 {
|
||||
match self.color() {
|
||||
Color::Red => self.value + 1,
|
||||
Color::White => self.value - MAX_RED_TIER_ID + 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_tier(&mut self, tier: u32) -> Result<(), Error> {
|
||||
if tier < MIN_TIER || tier > MAX_TIER {
|
||||
return Err(Error::TierOutOfRange);
|
||||
}
|
||||
|
||||
self.value = match self.color() {
|
||||
Color::Red => tier - 1,
|
||||
Color::White if tier == MIN_TIER => {
|
||||
return Err(Error::WhiteTierMinimum);
|
||||
}
|
||||
Color::White => MAX_RED_TIER_ID + tier - 1,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn increase_tier(&mut self) {
|
||||
let _ = self.set_tier(self.tier() + 1);
|
||||
}
|
||||
|
||||
pub fn decrease_tier(&mut self) {
|
||||
let _ = self.set_tier(self.tier() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Robe {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Color {
|
||||
Red,
|
||||
White,
|
||||
}
|
||||
|
||||
impl FromStr for Color {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"Red" | "red" => Ok(Self::Red),
|
||||
"White" | "white" => Ok(Self::White),
|
||||
_ => Err(Error::InvalidColor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Red => write!(f, "Red"),
|
||||
Self::White => write!(f, "White"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn change_robe_color() {
|
||||
// lowest red tier
|
||||
let mut robe = Robe { value: 0 };
|
||||
|
||||
assert_eq!(robe.color(), Color::Red);
|
||||
assert_eq!(robe.tier(), 1);
|
||||
|
||||
robe.set_color(Color::White);
|
||||
|
||||
assert_eq!(robe.color(), Color::White);
|
||||
assert_eq!(robe.tier(), 2);
|
||||
|
||||
robe.set_color(Color::Red);
|
||||
|
||||
assert_eq!(robe.color(), Color::Red);
|
||||
assert_eq!(robe.tier(), 2);
|
||||
|
||||
// highest red tier
|
||||
let mut robe = Robe {
|
||||
value: MAX_RED_TIER_ID,
|
||||
};
|
||||
|
||||
assert_eq!(robe.color(), Color::Red);
|
||||
assert_eq!(robe.tier(), 4);
|
||||
|
||||
robe.set_color(Color::White);
|
||||
|
||||
assert_eq!(robe.color(), Color::White);
|
||||
assert_eq!(robe.tier(), 4);
|
||||
|
||||
robe.set_color(Color::Red);
|
||||
|
||||
assert_eq!(robe.color(), Color::Red);
|
||||
assert_eq!(robe.tier(), 4);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn change_red_robe_tier() {
|
||||
let mut robe = Robe { value: 0 };
|
||||
|
||||
for tier in 1..=4 {
|
||||
robe.set_tier(tier).unwrap();
|
||||
assert_eq!(robe.tier(), tier, "unexpected tier");
|
||||
assert_eq!(robe.color(), Color::Red, "unexpected color");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_white_robe_tier() {
|
||||
let mut robe = Robe { value: 4 };
|
||||
|
||||
let result = robe.set_tier(1);
|
||||
assert_eq!(result, Err(Error::WhiteTierMinimum));
|
||||
|
||||
for (tier, expected) in (2..=4).zip([2, 3, 4]) {
|
||||
robe.set_tier(tier).unwrap();
|
||||
assert_eq!(robe.tier(), expected, "unexpected tier");
|
||||
assert_eq!(robe.color(), Color::White, "unexpected color");
|
||||
}
|
||||
}
|
||||
}
|
@ -12,9 +12,8 @@ use crate::*;
|
||||
fn general_info() {
|
||||
let savefile = savefile();
|
||||
|
||||
assert_eq!(savefile.robe, 3);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::Red);
|
||||
assert_eq!(savefile.robe_tier(), 4);
|
||||
assert_eq!(savefile.robe.color(), RobeColor::Red);
|
||||
assert_eq!(savefile.robe.tier(), 4);
|
||||
|
||||
assert_eq!(savefile.symbol.as_ref(), &7);
|
||||
assert_eq!(savefile.scarf_length.as_ref(), &27);
|
||||
@ -149,58 +148,6 @@ fn murals() {
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn change_robe_color() {
|
||||
let mut savefile = savefile();
|
||||
|
||||
// lowest tier
|
||||
savefile.robe = 0;
|
||||
|
||||
savefile.set_robe_color(RobeColor::White);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::White);
|
||||
|
||||
savefile.set_robe_color(RobeColor::Red);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::Red);
|
||||
|
||||
// highest tier
|
||||
savefile.robe = 3;
|
||||
|
||||
savefile.set_robe_color(RobeColor::White);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::White);
|
||||
|
||||
savefile.set_robe_color(RobeColor::Red);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::Red);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn change_robe_tier() {
|
||||
let mut savefile = savefile();
|
||||
|
||||
savefile.set_robe_tier(1);
|
||||
assert_eq!(savefile.robe_tier(), 1);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::Red);
|
||||
|
||||
savefile.set_robe_tier(4);
|
||||
assert_eq!(savefile.robe_tier(), 4);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::Red);
|
||||
|
||||
savefile.set_robe_color(RobeColor::White);
|
||||
|
||||
savefile.set_robe_tier(2);
|
||||
assert_eq!(savefile.robe_tier(), 2);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::White);
|
||||
|
||||
savefile.set_robe_tier(1);
|
||||
assert_eq!(savefile.robe_tier(), 2);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::White);
|
||||
|
||||
savefile.set_robe_tier(4);
|
||||
assert_eq!(savefile.robe_tier(), 4);
|
||||
assert_eq!(savefile.robe_color(), RobeColor::White);
|
||||
}
|
||||
|
||||
|
||||
fn savefile() -> Savefile {
|
||||
const TEST_FILE: &[u8] = include_bytes!("../test.bin");
|
||||
let mut savefile = Cursor::new(TEST_FILE);
|
||||
|
@ -68,14 +68,14 @@ fn edit_file(cur_savefile: &Savefile, args: &Args) -> Result<Savefile> {
|
||||
|
||||
if let Some(color) = &args.robe_color {
|
||||
match color.as_ref() {
|
||||
"red" => savefile.set_robe_color(RobeColor::Red),
|
||||
"white" => savefile.set_robe_color(RobeColor::White),
|
||||
"red" => savefile.robe.set_color(RobeColor::Red),
|
||||
"white" => savefile.robe.set_color(RobeColor::White),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tier) = args.robe_tier {
|
||||
savefile.set_robe_tier(tier);
|
||||
savefile.robe.set_tier(tier)?;
|
||||
}
|
||||
|
||||
Ok(savefile)
|
||||
|
@ -6,7 +6,7 @@ use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use jrny_save::{RobeColor, Savefile};
|
||||
use jrny_save::Savefile;
|
||||
use ratatui::widgets::TableState;
|
||||
use tracing::{debug, error};
|
||||
use tui_input::Input;
|
||||
@ -175,15 +175,8 @@ impl State {
|
||||
4 => savefile.companions_met = value.parse()?,
|
||||
5 => savefile.scarf_length.set_length(value.parse()?)?,
|
||||
6 => savefile.symbol.set_by_id(value.parse()?)?,
|
||||
7 => {
|
||||
let new_color = match value {
|
||||
"Red" | "red" => RobeColor::Red,
|
||||
"White" | "white" => RobeColor::White,
|
||||
_ => bail!("invalid robe color"),
|
||||
};
|
||||
savefile.set_robe_color(new_color);
|
||||
}
|
||||
8 => savefile.set_robe_tier(value.parse()?),
|
||||
7 => savefile.robe.set_color(value.parse()?),
|
||||
8 => savefile.robe.set_tier(value.parse()?)?,
|
||||
9 => {}
|
||||
idx => debug!("unknown index {:?}", idx),
|
||||
}
|
||||
@ -213,16 +206,8 @@ impl State {
|
||||
4 => savefile.companions_met += 1,
|
||||
5 => savefile.scarf_length.increase_length()?,
|
||||
6 => savefile.symbol = savefile.symbol.wrapping_next(),
|
||||
7 => {
|
||||
savefile.set_robe_color(match savefile.robe_color() {
|
||||
RobeColor::Red => RobeColor::White,
|
||||
RobeColor::White => RobeColor::Red,
|
||||
});
|
||||
}
|
||||
8 => {
|
||||
let next_tier = savefile.robe_tier() + 1;
|
||||
savefile.set_robe_tier(next_tier);
|
||||
}
|
||||
7 => savefile.robe.swap_colors(),
|
||||
8 => savefile.robe.increase_tier(),
|
||||
9 => {}
|
||||
idx => debug!("unknown index {:?}", idx),
|
||||
}
|
||||
@ -255,16 +240,8 @@ impl State {
|
||||
4 => savefile.companions_met = savefile.companions_met.saturating_sub(1),
|
||||
5 => savefile.scarf_length.decrease_length()?,
|
||||
6 => savefile.symbol = savefile.symbol.wrapping_previous(),
|
||||
7 => {
|
||||
savefile.set_robe_color(match savefile.robe_color() {
|
||||
RobeColor::Red => RobeColor::White,
|
||||
RobeColor::White => RobeColor::Red,
|
||||
});
|
||||
}
|
||||
8 => {
|
||||
let next_tier = savefile.robe_tier().saturating_sub(1);
|
||||
savefile.set_robe_tier(next_tier);
|
||||
}
|
||||
7 => savefile.robe.swap_colors(),
|
||||
8 => savefile.robe.decrease_tier(),
|
||||
9 => {}
|
||||
idx => debug!("unknown index {:?}", idx),
|
||||
}
|
||||
|
@ -56,8 +56,8 @@ pub(super) fn render<'a>(state: &mut State, frame: &mut Frame, area: Rect) {
|
||||
("Companions Met", savefile.companions_met.to_string()),
|
||||
("Scarf Length", savefile.scarf_length.to_string()),
|
||||
("Symbol Number", savefile.symbol.as_ref().to_string()),
|
||||
("Robe Color", savefile.robe_color().to_string()),
|
||||
("Robe Tier", savefile.robe_tier().to_string()),
|
||||
("Robe Color", savefile.robe.color().to_string()),
|
||||
("Robe Tier", savefile.robe.tier().to_string()),
|
||||
("Last Played", savefile.last_played.to_string()),
|
||||
]
|
||||
.into_iter()
|
||||
|
Loading…
Reference in New Issue
Block a user