Implement robe wrapper and fix tests

This commit is contained in:
Patrick Auernig 2023-08-16 17:36:39 +02:00
parent b482cafbc2
commit e23572ff20
6 changed files with 213 additions and 144 deletions

View File

@ -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
View 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");
}
}
}

View File

@ -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);

View 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)

View File

@ -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),
}

View File

@ -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()