the api surface
This commit is contained in:
@@ -0,0 +1,165 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use cruciverb_core::grid::Cell;
|
||||||
|
use cruciverb_core::puzzle::Puzzle;
|
||||||
|
|
||||||
|
// -- request types --
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct GenerateRequest {
|
||||||
|
pub difficulty: Option<String>,
|
||||||
|
pub size: Option<usize>,
|
||||||
|
pub density: Option<f64>,
|
||||||
|
pub clue_difficulty: Option<u8>,
|
||||||
|
pub seed: Option<u64>,
|
||||||
|
pub theme: Option<String>,
|
||||||
|
pub cryptic: Option<bool>,
|
||||||
|
pub pack: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ValidateRequest {
|
||||||
|
pub cells: Vec<Vec<Option<String>>>,
|
||||||
|
pub elapsed: Option<i64>,
|
||||||
|
// kept for client compatibility; the server now uses its own clock for streaks
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub local_date: Option<String>,
|
||||||
|
pub room_code: Option<String>,
|
||||||
|
pub cell_results: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct HintRequest {
|
||||||
|
pub kind: String,
|
||||||
|
pub row: usize,
|
||||||
|
pub col: usize,
|
||||||
|
pub direction: Option<String>,
|
||||||
|
pub letter: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ProgressRequest {
|
||||||
|
pub cells: Vec<Vec<Option<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct UpdateMeRequest {
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- response types --
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct GenerateResponse {
|
||||||
|
pub id: String,
|
||||||
|
pub short_id: String,
|
||||||
|
pub puzzle: ClientPuzzle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ClientPuzzleResponse {
|
||||||
|
pub id: String,
|
||||||
|
pub short_id: String,
|
||||||
|
pub puzzle: ClientPuzzle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ClientPuzzle {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub cells: Vec<Vec<ClientCell>>,
|
||||||
|
pub clues_across: Vec<ClientClue>,
|
||||||
|
pub clues_down: Vec<ClientClue>,
|
||||||
|
pub difficulty_score: u32,
|
||||||
|
pub cell_numbers: HashMap<String, u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub theme: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub theme_entries: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ClientCell {
|
||||||
|
Black,
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ClientClue {
|
||||||
|
pub number: u32,
|
||||||
|
pub direction: String,
|
||||||
|
pub text: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub definition: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub wordplay_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum HintResponse {
|
||||||
|
CheckCell { is_correct: bool },
|
||||||
|
RevealLetter { row: usize, col: usize, letter: String },
|
||||||
|
RevealWord { letters: Vec<LetterPosition> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct LetterPosition {
|
||||||
|
pub row: usize,
|
||||||
|
pub col: usize,
|
||||||
|
pub letter: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- conversions --
|
||||||
|
|
||||||
|
pub fn to_client_puzzle(puzzle: &Puzzle, _id: Uuid) -> ClientPuzzle {
|
||||||
|
let mut cells = Vec::with_capacity(puzzle.height);
|
||||||
|
for r in 0..puzzle.height {
|
||||||
|
let mut row = Vec::with_capacity(puzzle.width);
|
||||||
|
for c in 0..puzzle.width {
|
||||||
|
match puzzle.grid.get(r, c) {
|
||||||
|
Cell::Black => row.push(ClientCell::Black),
|
||||||
|
_ => row.push(ClientCell::Empty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cells.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cell_numbers: HashMap<String, u32> = HashMap::new();
|
||||||
|
for word in &puzzle.words {
|
||||||
|
let key = format!("{},{}", word.row, word.col);
|
||||||
|
cell_numbers.entry(key).or_insert(word.clue_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
let clues_across = puzzle.clues_across.iter().map(|c| ClientClue {
|
||||||
|
number: c.number,
|
||||||
|
direction: "across".into(),
|
||||||
|
text: c.text.clone(),
|
||||||
|
definition: c.definition.clone(),
|
||||||
|
wordplay_type: c.wordplay_type.clone(),
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let clues_down = puzzle.clues_down.iter().map(|c| ClientClue {
|
||||||
|
number: c.number,
|
||||||
|
direction: "down".into(),
|
||||||
|
text: c.text.clone(),
|
||||||
|
definition: c.definition.clone(),
|
||||||
|
wordplay_type: c.wordplay_type.clone(),
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
ClientPuzzle {
|
||||||
|
width: puzzle.width,
|
||||||
|
height: puzzle.height,
|
||||||
|
cells,
|
||||||
|
clues_across,
|
||||||
|
clues_down,
|
||||||
|
difficulty_score: puzzle.difficulty_score,
|
||||||
|
cell_numbers,
|
||||||
|
theme: puzzle.theme.clone(),
|
||||||
|
theme_entries: puzzle.theme_entries.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user