1use std::collections::{HashMap, HashSet};
4
5use chrono::{DateTime, Utc};
6use macros::make_path_parts;
7use serde::{Deserialize, Serialize};
8
9use crate::{api::endpoints::PathPart, domain::module::StableModuleId};
10
11use super::{JigId, JigPlayerSettings, JigResponse};
12
13#[derive(Serialize, Deserialize, Debug, Copy, Clone, PathPart, PartialEq, Eq)]
15#[cfg_attr(feature = "backend", derive(sqlx::Type))]
16#[cfg_attr(feature = "backend", sqlx(transparent))]
17pub struct JigCode(pub i32);
18
19impl ToString for JigCode {
20 fn to_string(&self) -> String {
21 format!("{:06}", self.0)
22 }
23}
24
25make_path_parts!(JigPlayerSessionCreatePath => "/v1/jig/codes");
26
27#[derive(Serialize, Deserialize, Debug, Clone)]
29#[serde(rename_all = "camelCase")]
30pub struct JigPlayerSessionCreateRequest {
31 pub jig_id: JigId,
33
34 pub name: Option<String>,
36
37 pub settings: JigPlayerSettings,
39}
40
41#[derive(Serialize, Deserialize, Debug, Clone)]
43#[serde(rename_all = "camelCase")]
44pub struct JigPlayerSessionCreateResponse {
45 pub index: JigCode,
47}
48
49make_path_parts!(JigCodePath => "/v1/jig/codes/{}" => JigCode);
50
51#[derive(Serialize, Deserialize, Debug, Clone, Default)]
53#[serde(rename_all = "camelCase")]
54pub struct JigCodeUpdateRequest {
55 pub name: Option<Option<String>>,
57
58 pub settings: Option<JigPlayerSettings>,
60}
61
62#[derive(Serialize, Deserialize, Debug, Clone)]
64pub struct JigCodeResponse {
65 pub index: JigCode,
67
68 pub jig_id: JigId,
70
71 pub name: Option<String>,
73
74 pub settings: JigPlayerSettings,
76
77 pub created_at: DateTime<Utc>,
79
80 pub expires_at: DateTime<Utc>,
82}
83
84make_path_parts!(JigCodeListPath => "/v1/jig/codes");
85
86#[derive(Serialize, Deserialize, Clone, Debug, Default)]
88#[serde(rename_all = "camelCase")]
89pub struct JigCodeListRequest {
90 #[serde(default)]
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub jig_id: Option<JigId>,
94}
95
96#[derive(Serialize, Deserialize, Debug, Clone)]
98pub struct JigCodeListResponse {
99 pub codes: Vec<JigCodeResponse>,
101}
102
103make_path_parts!(JigsWithCodesPath => "/v1/jig/codes/jig-codes");
104
105#[derive(Serialize, Deserialize, Debug, Clone)]
107pub struct JigsWithCodesResponse {
108 pub jigs: Vec<JigWithCodes>,
110}
111
112#[derive(Serialize, Deserialize, Debug, Clone)]
114pub struct JigWithCodes {
115 pub jig: JigResponse,
117 pub codes: Vec<JigCodeResponse>,
119}
120
121make_path_parts!(JigCodeSessionsPath => "/v1/jig/codes/{}/sessions" => JigCode);
122
123#[derive(Serialize, Deserialize, Debug, Clone)]
125pub struct JigCodeSessionsListResponse {
126 pub sessions: Vec<JigCodeSessionResponse>,
128}
129
130#[derive(Serialize, Deserialize, Debug, Clone)]
132pub struct JigCodeSessionResponse {
133 pub code: JigCode,
135 pub players_name: Option<String>,
137 pub started_at: DateTime<Utc>,
139 pub finished_at: Option<DateTime<Utc>>,
141 pub info: Option<JigPlaySession>,
143}
144
145#[derive(Clone, Debug, Serialize, Deserialize, Default)]
147pub struct JigPlaySession {
148 #[serde(default)]
150 pub modules: Vec<JigPlaySessionModule>,
151 #[serde(default)]
153 pub visited: HashSet<StableModuleId>,
154}
155
156impl JigPlaySessionModuleGetPointsEarned for JigPlaySession {
157 fn get_points_earned(&self) -> PointsEarned {
158 let mut available = 0.0;
159 let mut earned = 0.0;
160 for module in &self.modules {
161 let module_points = module.get_points_earned();
162 available += module_points.available;
163 earned += module_points.earned;
164 }
165 PointsEarned { available, earned }
166 }
167}
168
169#[derive(Clone, Debug, Serialize, Deserialize)]
171pub enum JigPlaySessionModule {
172 Matching(JigPlaySessionMatching),
174 CardQuiz(JigPlaySessionCardQuiz),
176 DragDrop(JigPlaySessionDragDrop),
178 FindAnswer(JigPlaySessionFindAnswer),
180}
181
182impl JigPlaySessionModule {
183 pub fn stable_module_id(&self) -> StableModuleId {
185 match self {
186 Self::Matching(module) => module.stable_module_id,
187 Self::CardQuiz(module) => module.stable_module_id,
188 Self::DragDrop(module) => module.stable_module_id,
189 Self::FindAnswer(module) => module.stable_module_id,
190 }
191 }
192}
193
194impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionModule {
195 fn get_points_earned(&self) -> PointsEarned {
196 match self {
197 JigPlaySessionModule::Matching(module) => module.get_points_earned(),
198 JigPlaySessionModule::CardQuiz(module) => module.get_points_earned(),
199 JigPlaySessionModule::DragDrop(module) => module.get_points_earned(),
200 JigPlaySessionModule::FindAnswer(module) => module.get_points_earned(),
201 }
202 }
203}
204
205pub trait JigPlaySessionModuleGetPointsEarned {
207 fn get_points_earned(&self) -> PointsEarned;
209}
210
211#[derive(Clone, Debug, Serialize, Deserialize)]
213pub struct PointsEarned {
214 pub available: f32,
216 pub earned: f32,
218}
219impl std::fmt::Display for PointsEarned {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 write!(f, "{}|{}", self.earned, self.available)
222 }
223}
224impl PointsEarned {
225 pub fn percent(&self) -> u16 {
227 let output = (self.earned / self.available) * 100.00;
228 output as u16
229 }
230}
231
232#[derive(Clone, Debug, Serialize, Deserialize)]
234pub struct JigPlaySessionMatching {
235 pub stable_module_id: StableModuleId,
237
238 pub rounds: Vec<HashMap<usize, JigPlaySessionMatchingCard>>,
240}
241
242impl JigPlaySessionMatching {
243 pub fn new(stable_module_id: StableModuleId) -> Self {
245 Self {
246 stable_module_id,
247 rounds: vec![],
248 }
249 }
250}
251
252impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionMatching {
253 fn get_points_earned(&self) -> PointsEarned {
254 let mut available = 0.0;
255 let mut earned = 0.0;
256 for round in &self.rounds {
257 for (_, card) in round {
258 available += 1.0;
259 earned += match card.failed_tries {
260 0 => 1.0,
261 1 => 0.5,
262 _ => 0.0,
263 };
264 }
265 }
266 PointsEarned { available, earned }
267 }
268}
269
270#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
272pub struct JigPlaySessionMatchingCard {
273 pub failed_tries: u16,
275}
276
277#[derive(Clone, Debug, Serialize, Deserialize)]
279pub struct JigPlaySessionCardQuiz {
280 pub stable_module_id: StableModuleId,
282
283 pub rounds: Vec<JigPlaySessionCardQuizRound>,
285}
286
287impl JigPlaySessionCardQuiz {
288 pub fn new(stable_module_id: StableModuleId) -> Self {
290 Self {
291 stable_module_id,
292 rounds: Vec::new(),
293 }
294 }
295}
296
297impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionCardQuiz {
298 fn get_points_earned(&self) -> PointsEarned {
299 let mut available = 0.0;
300 let mut earned = 0.0;
301 for card in &self.rounds {
302 available += 1.0;
303 earned += match card.failed_tries {
304 0 => 1.0,
305 1 => 0.5,
306 _ => 0.0,
307 };
308 }
309 PointsEarned { available, earned }
310 }
311}
312
313#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
315pub struct JigPlaySessionCardQuizRound {
316 pub card_index: usize,
318
319 pub failed_tries: u16,
321}
322
323#[derive(Clone, Debug, Serialize, Deserialize)]
325pub struct JigPlaySessionDragDrop {
326 pub stable_module_id: StableModuleId,
328
329 pub items: HashMap<usize, JigPlaySessionDragDropItem>,
332}
333
334impl JigPlaySessionDragDrop {
335 pub fn new(stable_module_id: StableModuleId) -> Self {
337 Self {
338 stable_module_id,
339 items: HashMap::new(),
340 }
341 }
342}
343
344impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionDragDrop {
345 fn get_points_earned(&self) -> PointsEarned {
346 let mut available = 0.0;
347 let mut earned = 0.0;
348 for card in self.items.values() {
349 available += 1.0;
350 earned += match card.failed_tries {
351 0 => 1.0,
352 1 => 0.5,
353 _ => 0.0,
354 };
355 }
356 PointsEarned { available, earned }
357 }
358}
359
360#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
362pub struct JigPlaySessionDragDropItem {
363 pub failed_tries: u16,
365}
366
367#[derive(Clone, Debug, Serialize, Deserialize)]
369pub struct JigPlaySessionFindAnswer {
370 pub stable_module_id: StableModuleId,
372
373 pub items: Vec<JigPlaySessionFindAnswerItem>,
375}
376
377impl JigPlaySessionFindAnswer {
378 pub fn new(stable_module_id: StableModuleId) -> Self {
380 Self {
381 stable_module_id,
382 items: Vec::new(),
383 }
384 }
385}
386
387impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionFindAnswer {
388 fn get_points_earned(&self) -> PointsEarned {
389 let mut available = 0.0;
390 let mut earned = 0.0;
391 for card in &self.items {
392 available += 1.0;
393 earned += match card.failed_tries {
394 0 => 1.0,
395 1 => 0.5,
396 _ => 0.0,
397 };
398 }
399 PointsEarned { available, earned }
400 }
401}
402
403#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
405pub struct JigPlaySessionFindAnswerItem {
406 pub failed_tries: u16,
408}
409
410pub mod instance {
412 use macros::make_path_parts;
413 use serde::{Deserialize, Serialize};
414
415 use crate::domain::jig::{
416 codes::{JigCode, JigPlaySession},
417 JigId, JigPlayerSettings,
418 };
419
420 make_path_parts!(PlayerSessionInstanceCreatePath => "/v1/jig/codes/instance");
421
422 #[derive(Serialize, Deserialize, Debug, Clone)]
424 pub struct PlayerSessionInstanceCreateRequest {
425 pub code: JigCode,
427 }
428
429 #[derive(Serialize, Deserialize, Debug, Clone)]
431 #[serde(rename_all = "camelCase")]
432 pub struct PlayerSessionInstanceResponse {
433 pub jig_id: JigId,
435
436 pub settings: JigPlayerSettings,
438
439 pub token: String,
441 }
442
443 make_path_parts!(PlayerSessionInstanceCompletePath => "/v1/jig/codes/instance/complete");
444
445 #[derive(Serialize, Deserialize, Debug, Clone)]
447 #[serde(rename_all = "camelCase")]
448 pub struct PlayerSessionInstanceCompleteRequest {
449 pub token: String,
451
452 pub session: JigPlaySession,
454
455 pub players_name: Option<String>,
457 }
458}