diff --git a/demo/shiritori.js b/demo/shiritori.js
index f43f5bf..f1ee92c 100644
--- a/demo/shiritori.js
+++ b/demo/shiritori.js
@@ -4,21 +4,6 @@ const input = document.querySelector("#shiritori-input");
const players = document.querySelector("#shiritori-players");
let id = null;
-function displayWord(_word, end, delay) {
- let { word, reading } = _word;
- p = document.createElement("p");
- p.innerHTML = reading || word === reading ? (word === "" ? reading : `${word}`) : word;
- if (delay != 0) {
- p.style.animationDelay = `${delay}s`;
- }
- if (end) {
- div.append(p);
- } else {
- div.prepend(p);
- }
- div.scrollTop = div.offsetHeight;
-}
-
ws.onmessage = e => {
const { event, data } = JSON.parse(e.data);
switch (event) {
@@ -27,17 +12,14 @@ ws.onmessage = e => {
break;
case "word":
let waiting = data.author === id;
- displayWord(data.word, true, 0);
- input.placeholder = waiting ? "Waiting for other players..." : `${data.next_mora}…`;
+ p = document.createElement("p");
+ p.innerHTML = data.reading || data.word === data.reading ? (data.word === "" ? data.reading : `${data.word}${data.reading}`) : data.word;
+ div.appendChild(p);
+ div.scrollTop = div.offsetHeight;
+ input.placeholder = waiting ? "Waiting for other players..." : `${data.next_char}…`;
input.disabled = waiting;
if (!waiting) input.focus();
break;
- case "history":
- console.log(data);
- for (let i = 0; i < data.words.length; i++) {
- displayWord(data.words[i], false, 0.1 * i);
- }
- break;
case "playerCount":
let otherPlayers = data.players - 1;
players.innerHTML = `${otherPlayers === 0 ? "No" : otherPlayers} other player${otherPlayers === 1 ? "" : "s"} online.`;
diff --git a/demo/style.css b/demo/style.css
index 5b1a5e7..3f1984b 100644
--- a/demo/style.css
+++ b/demo/style.css
@@ -12,28 +12,11 @@ div#content {
}
div#shiritori p {
margin: 0;
- animation-duration: 1s;
- animation-name: slidein;
- animation-fill-mode: forwards;
- opacity: 0;
-}
-@keyframes slidein {
- from {
- transform: translateX(-32px);
- opacity: 0;
- }
-
- to {
- transform: none;
- opacity: 1;
- }
}
div#shiritori {
padding: 2px;
- height: 24em;
- font-size: 0.875em;
+ height: 8em;
overflow-y: scroll;
- text-align: center;
}
div#shiritori, input#shiritori-input {
padding: 0.25em;
diff --git a/src/database.rs b/src/database.rs
index 19a3256..59563be 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -6,7 +6,6 @@ use rusqlite::{Connection, Result, params};
pub struct Database {
conn: Connection,
- pub last_word_id: i64,
}
#[derive(From, Debug)]
@@ -29,26 +28,17 @@ impl Database {
conn.execute(
"CREATE TABLE IF NOT EXISTS word (
id INTEGER PRIMARY KEY,
- word TEXT,
- reading TEXT,
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
+ word TEXT, reading TEXT,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
)",
params![],
)?;
- let last_word_id = match conn
- .prepare("SELECT id FROM word ORDER BY id DESC LIMIT 1")?
- .query_map(params![], |row| row.get(0))?
- .collect::>>()?
- .first() {
- Some(id) => *id,
- None => 0, // first database entry is id 1
- };
- Ok(Self { conn, last_word_id })
+ Ok(Self { conn })
}
pub fn load_words_before(&self, before_id: i64) -> Result> {
self.conn
- .prepare("SELECT id, word, reading, timestamp FROM word WHERE id < ? ORDER BY id DESC LIMIT 10")?
+ .prepare("SELECT id, word, reading, timestamp FROM word WHERE id < ? DESC LIMIT 10")?
.query_map(params![before_id], |row| {
Ok(Word {
id: row.get(0)?,
@@ -59,16 +49,4 @@ impl Database {
})?
.collect::>>()
}
-
- pub fn add_word(&mut self, word: &Word) -> Result<()> {
- self.conn.execute(
- "INSERT INTO word (word, reading) VALUES (?1, ?2)",
- params![
- word.word,
- word.reading,
- ],
- )?;
- self.last_word_id += 1;
- Ok(())
- }
}
diff --git a/src/main.rs b/src/main.rs
index 4fea4ce..b51f3f0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,7 +12,7 @@ pub use word::*;
fn main() {
const PORT: u16 = 8080;
- let mut server = Server::new(PORT, true)
- .unwrap_or_else(|error| panic!("Failed to start server at port {PORT}: {:?}", error));
+ let mut server = Server::new(PORT)
+ .unwrap_or_else(|_| panic!("Failed to start server at port {PORT}"));
server.run();
}
diff --git a/src/server.rs b/src/server.rs
index c8ba785..f7efebb 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -1,13 +1,9 @@
-use crate::DatabaseCreationError;
-use crate::lookup;
-use crate::Word;
-use crate::Database;
+use crate::dictionary::lookup;
use simple_websockets::{Event, Responder, Message, EventHub};
use std::collections::HashMap;
use serde::Serialize;
use wana_kana::{ConvertJapanese, IsJapaneseStr};
-use derive_more::From;
#[derive(Serialize)]
struct MessageResponse {
@@ -29,11 +25,9 @@ enum MessageResponseData {
},
Word {
author: u64,
- word: Word,
- next_mora: String,
- },
- History {
- words: Vec,
+ word: String,
+ reading: Option,
+ next_char: char,
},
PlayerCount {
players: u64,
@@ -48,7 +42,6 @@ impl MessageResponseData {
String::from(match self {
Self::Greeting { .. } => "greeting",
Self::Word { .. } => "word",
- Self::History { .. } => "history",
Self::PlayerCount { .. } => "playerCount",
Self::Error { .. } => "error",
})
@@ -68,89 +61,29 @@ impl MessageResponseData {
pub struct Server {
event_hub: EventHub,
- database: Database,
clients: HashMap,
- next_mora: Option,
+ next_char: Option,
+ last_response: Option,
last_client_id: Option,
}
-#[derive(From, Debug)]
-pub enum ServerCreationError {
- SimpleWebsocketsError(simple_websockets::Error),
- DatabaseCreationError(DatabaseCreationError),
-}
-
-#[derive(From, Debug)]
-pub enum ServerError {
- DatabaseError(rusqlite::Error),
-}
-
-fn get_starting_mora(word: &str) -> String {
- if word.is_empty() {
- return String::from("");
- }
- let word = word.to_hiragana();
- let mut iter = word.chars();
- let starting_char = iter.next().unwrap();
- let second_char = iter.next().unwrap();
- match second_char {
- 'ゃ' | 'ゅ' | 'ょ' => format!("{starting_char}{second_char}"),
- _ => starting_char.to_string(),
- }
-}
-
-// Trim off lengtheners and non-kana that are irrelevant to shiritori
-// TODO: Use slices
-fn trim_irrelevant_chars(word: &str) -> String {
- let mut iter = word.chars().rev().peekable();
- while let Some(c) = iter.peek() {
- if *c == 'ー' || !c.to_string().is_kana() {
- iter.next();
- } else {
- break;
- }
- }
- iter.rev().collect()
-}
-
-// Get final mora, which could be multiple chars e.g. しゃ
-// TODO: Use slices
-fn get_final_mora(word: &str) -> String {
- let word = trim_irrelevant_chars(word).to_hiragana();
- if word.is_empty() {
- return String::from("");
- }
- let mut iter = word.chars().rev();
- let final_char = iter.next().unwrap();
- match final_char {
- 'ゃ' | 'ゅ' | 'ょ' => format!("{}{final_char}", match iter.next() {
- Some(c) => c.to_string(),
- None => String::from(""),
- }),
- _ => final_char.to_string(),
- }
-}
-
impl Server {
- pub fn new(port: u16, testing: bool) -> Result {
+ pub fn new(port: u16) -> Result {
Ok(Server {
event_hub: simple_websockets::launch(port)?,
- database: Database::new(testing)?,
clients: HashMap::new(),
- next_mora: None,
+ next_char: None,
+ last_response: None,
last_client_id: None,
})
}
pub fn run(&mut self) {
loop {
- match match self.event_hub.poll_event() {
+ match self.event_hub.poll_event() {
Event::Connect(client_id, responder) => self.handle_connection(client_id, responder),
Event::Disconnect(client_id) => self.handle_disconnection(client_id),
Event::Message(client_id, message) => self.handle_message(client_id, message),
- } {
- Ok(()) => {},
- Err(error) => println!("{:?}", error),
}
}
}
@@ -162,31 +95,27 @@ impl Server {
}
}
- fn handle_connection(&mut self, client_id: u64, responder: Responder) -> Result<(), ServerError> {
+ fn handle_connection(&mut self, client_id: u64, responder: Responder) {
println!("A client connected with id #{}", client_id);
- responder.send(MessageResponseData::Greeting {
- id: client_id
- }.into_message());
- responder.send(MessageResponseData::History {
- words: self.database.load_words_before(self.database.last_word_id + 1)?
- }.into_message());
+ responder.send(MessageResponseData::Greeting { id: client_id }.into_message());
+ if let Some(ref last_response) = self.last_response {
+ responder.send(last_response.to_message());
+ }
self.clients.insert(client_id, responder);
self.broadcast_player_count();
- Ok(())
}
- fn handle_disconnection(&mut self, client_id: u64) -> Result<(), ServerError> {
+ fn handle_disconnection(&mut self, client_id: u64) {
println!("Client #{} disconnected.", client_id);
self.clients.remove(&client_id);
self.broadcast_player_count();
- Ok(())
}
- fn handle_message(&mut self, client_id: u64, message: Message) -> Result<(), ServerError> {
+ fn handle_message(&mut self, client_id: u64, message: Message) {
// Ignore binary messages
let message = match message {
Message::Text(message) => message,
- Message::Binary(_) => return Ok(()),
+ Message::Binary(_) => return,
};
// Debug
@@ -199,23 +128,31 @@ impl Server {
} else {
match lookup(&message) {
Some(entry) => {
- println!("{}, {:?}", get_starting_mora(&entry.reading), self.next_mora);
if entry.reading.chars().last().unwrap().to_string().to_hiragana() == "ん" {
MessageResponseData::Error {
message: String::from("Can't end with ん!"),
}
- } else if self.next_mora.is_none() || get_starting_mora(&entry.reading).eq(self.next_mora.as_deref().unwrap()) {
- let word: Word = entry.clone().into();
- self.next_mora = Some(get_final_mora(&word.reading));
- self.database.add_word(&word)?;
+ } else if self.next_char.is_none() || entry.reading.chars().next().unwrap().to_string().to_hiragana().chars().next().unwrap() == self.next_char.unwrap() {
+ self.next_char = {
+ // If final character is lengthener or not kana
+ // Use semifinal
+ let mut final_chars = entry.reading.chars().rev();
+ let final_char = final_chars.next().unwrap();
+ Some(if final_char == 'ー' || !final_char.to_string().is_kana() {
+ final_chars.next().unwrap()
+ } else {
+ final_char
+ }.to_string().to_hiragana().chars().next().unwrap())
+ };
MessageResponseData::Word {
author: client_id,
- word,
- next_mora: self.next_mora.as_deref().unwrap().to_owned(),
+ word: entry.kanji.to_owned(),
+ reading: Some(entry.reading.to_owned()),
+ next_char: self.next_char.unwrap(),
}
} else {
MessageResponseData::Error {
- message: String::from("Wrong starting mora!"),
+ message: String::from("Wrong starting kana!"),
}
}
}
@@ -235,9 +172,10 @@ impl Server {
for (_client, responder) in self.clients.iter() {
responder.send(response.to_message());
}
+ self.last_response = Some(response);
self.last_client_id = Some(client_id);
},
- };
- Ok(())
+ }
+
}
}
diff --git a/src/word.rs b/src/word.rs
index cfa1701..08bc818 100644
--- a/src/word.rs
+++ b/src/word.rs
@@ -1,26 +1,11 @@
use chrono::{DateTime, Utc};
use serde::Serialize;
-use jisho::Entry;
-use std::convert::From;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Word {
- #[serde(skip_serializing)]
- pub id: Option,
+ pub id: i64,
pub word: String,
pub reading: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub timestamp: Option>,
-}
-
-impl From for Word {
- fn from(entry: Entry) -> Self {
- Self {
- id: None,
- word: entry.kanji,
- reading: entry.reading,
- timestamp: None,
- }
- }
+ pub timestamp: DateTime,
}