book.rs (8258B)
1 use std::{ 2 collections::{HashMap, hash_map::Keys}, 3 fmt::{Display, Write}, 4 slice::Iter, 5 }; 6 7 use anyhow::{Result, anyhow}; 8 use lazy_static::lazy_static; 9 use macroquad::math::{Rect, Vec2}; 10 use serde::{Deserialize, Serialize}; 11 use uuid::Uuid; 12 13 use crate::game::RectSerde; 14 15 lazy_static! { 16 pub static ref BOOKS: BookDatabase = 17 BookDatabase::from_csv(include_str!("../../assets/books.csv")) 18 .expect("Failed to parse book database"); 19 } 20 21 pub type BookId = Uuid; 22 pub type BookshelfId = Uuid; 23 24 #[derive( 25 Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, 26 )] 27 pub struct SlotAddress { 28 bookshelf_id: BookshelfId, 29 shelf_index: usize, 30 position: usize, 31 } 32 33 impl SlotAddress { 34 pub fn new(bookshelf_id: BookshelfId, shelf_index: usize, position: usize) -> Self { 35 SlotAddress { 36 bookshelf_id, 37 shelf_index, 38 position, 39 } 40 } 41 pub fn bookshelf_id(&self) -> BookshelfId { 42 self.bookshelf_id 43 } 44 pub fn shelf_index(&self) -> usize { 45 self.shelf_index 46 } 47 pub fn position(&self) -> usize { 48 self.position 49 } 50 } 51 52 #[derive( 53 Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, 54 )] 55 pub enum Slot { 56 Book { 57 book_id: BookId, 58 }, 59 #[default] 60 Empty, 61 } 62 63 #[derive(Clone, Debug, Default, Deserialize, PartialEq, PartialOrd, Serialize)] 64 pub struct Bookshelf { 65 bounds: RectSerde, 66 shelves: Vec<Vec<Slot>>, 67 } 68 69 impl Bookshelf { 70 pub const BOOK_WIDTH: f32 = 10.; 71 pub const BOOK_HEIGHT: f32 = 30.; 72 pub const FRAME_THICKNESS: f32 = 4.; 73 pub const SHELF_THICKNESS: f32 = 4.; 74 pub(crate) fn new(position: Vec2, shelf_count: usize, books_per_shelf: usize) -> Self { 75 Self { 76 bounds: Rect::new( 77 position.x, 78 position.y, 79 books_per_shelf as f32 * Self::BOOK_WIDTH + Self::FRAME_THICKNESS * 2., 80 shelf_count as f32 * Self::BOOK_HEIGHT 81 + shelf_count.saturating_sub(1) as f32 * Self::SHELF_THICKNESS 82 + Self::FRAME_THICKNESS * 2., 83 ) 84 .into(), 85 shelves: vec![vec![Slot::Empty; books_per_shelf]; shelf_count], 86 } 87 } 88 pub fn rect(&self) -> Rect { 89 self.bounds.into() 90 } 91 pub fn iter(&self) -> Iter<'_, Vec<Slot>> { 92 self.shelves.iter() 93 } 94 pub fn shelf_count(&self) -> usize { 95 self.shelves.len() 96 } 97 pub fn get_shelf_mut(&mut self, index: usize) -> Option<&mut Vec<Slot>> { 98 self.shelves.get_mut(index) 99 } 100 pub fn get_slot(&self, slot_address: &SlotAddress) -> Option<&Slot> { 101 if let Some(shelf) = self.shelves.get(slot_address.shelf_index) { 102 shelf.get(slot_address.position) 103 } else { 104 None 105 } 106 } 107 pub fn get_slot_mut(&mut self, slot_address: &SlotAddress) -> Option<&mut Slot> { 108 if let Some(shelf) = self.shelves.get_mut(slot_address.shelf_index) { 109 shelf.get_mut(slot_address.position) 110 } else { 111 None 112 } 113 } 114 } 115 116 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] 117 pub struct FloorBook { 118 id: BookId, 119 position: Vec2, 120 collider: RectSerde, 121 } 122 123 impl FloorBook { 124 pub(crate) fn new(id: BookId, position: Vec2, collider: Rect) -> Self { 125 Self { 126 id, 127 position, 128 collider: collider.into(), 129 } 130 } 131 pub fn id(&self) -> BookId { 132 self.id 133 } 134 pub fn position(&self) -> Vec2 { 135 self.position 136 } 137 pub fn collider(&self) -> RectSerde { 138 self.collider 139 } 140 } 141 142 pub struct BookDatabase { 143 books: HashMap<BookId, BookMetadata>, 144 } 145 146 fn _csv_escape(input: &String) -> String { 147 if input.contains(',') { 148 format!("\"{input}\"") 149 } else { 150 input.clone() 151 } 152 } 153 154 impl BookDatabase { 155 const CSV_HEADER: &str = "id,title,author,publish_year"; 156 157 fn new() -> Self { 158 BookDatabase { 159 books: HashMap::new(), 160 } 161 } 162 pub(crate) fn keys(&self) -> Keys<'_, BookId, BookMetadata> { 163 self.books.keys() 164 } 165 fn _to_csv(&self, output: &mut impl Write) -> Result<()> { 166 writeln!(output, "{}", BookDatabase::CSV_HEADER)?; 167 for (id, metadata) in self.books.iter() { 168 let BookMetadata { 169 title, 170 author, 171 publish_year, 172 } = metadata; 173 writeln!( 174 output, 175 "{},{},{},{}", 176 id, 177 _csv_escape(title), 178 _csv_escape(author), 179 _csv_escape(publish_year), 180 )?; 181 } 182 Ok(()) 183 } 184 fn from_csv(input: &str) -> Result<Self> { 185 let mut output = BookDatabase::new(); 186 for (line_index, line) in input.lines().enumerate() { 187 if line_index == 0 { 188 if line != BookDatabase::CSV_HEADER { 189 return Err(anyhow!( 190 "Invalid CSV header: expected '{}' but got '{}'", 191 BookDatabase::CSV_HEADER, 192 line 193 )); 194 } 195 continue; 196 } 197 let mut fields = vec![]; 198 #[derive(Clone, Copy, Debug)] 199 enum State { 200 Start, 201 FieldData, 202 Quote, 203 FieldEnd, 204 } 205 let mut state = State::Start; 206 let mut new_field = String::new(); 207 let mut quoted_field = false; 208 for c in line.chars() { 209 match (c, state) { 210 ('"', State::Start | State::FieldEnd) => { 211 quoted_field = true; 212 state = State::FieldData; 213 } 214 ('"', State::FieldData) => state = State::Quote, 215 ('"', State::Quote) => { 216 if quoted_field { 217 new_field.push('"'); 218 } 219 } 220 (',', State::Start | State::FieldEnd) => state = State::FieldEnd, 221 (',', State::FieldData) => { 222 if quoted_field { 223 new_field.push(','); 224 } else { 225 state = State::FieldEnd; 226 } 227 } 228 (',', State::Quote) => { 229 if quoted_field { 230 state = State::FieldEnd; 231 } 232 } 233 (d, _) => { 234 new_field.push(d); 235 state = State::FieldData; 236 } 237 } 238 if let State::FieldEnd = state { 239 fields.push(new_field.clone()); 240 new_field.clear(); 241 quoted_field = false; 242 } 243 } 244 fields.push(new_field); 245 if fields.len() != 4 { 246 return Err(anyhow!( 247 "Error on line {}: expected 4 fields but got {}", 248 line_index + 1, 249 fields.len() 250 )); 251 } 252 let id = fields[0].parse()?; 253 let title = fields[1].clone(); 254 let author = fields[2].clone(); 255 let publish_year = fields[3].clone(); 256 output.books.insert( 257 id, 258 BookMetadata { 259 title, 260 author, 261 publish_year, 262 }, 263 ); 264 } 265 Ok(output) 266 } 267 pub fn get(&self, k: &BookId) -> Option<&BookMetadata> { 268 self.books.get(k) 269 } 270 } 271 272 #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 273 pub struct BookMetadata { 274 title: String, 275 author: String, 276 publish_year: String, 277 } 278 279 impl Display for BookMetadata { 280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 281 write!( 282 f, 283 "{} by {} ({})", 284 self.title, self.author, self.publish_year 285 ) 286 } 287 }