|
|
|
@ -1,79 +1,13 @@
|
|
|
|
|
use crate::DatabaseCreationError;
|
|
|
|
|
use crate::lookup;
|
|
|
|
|
use crate::Word;
|
|
|
|
|
use crate::Database;
|
|
|
|
|
use crate::client::{Client, ClientInfo};
|
|
|
|
|
use crate::database::{DatabaseCreationError, DatabaseSettings, DatabaseType};
|
|
|
|
|
use crate::response::MessageResponseData;
|
|
|
|
|
use crate::room::{Room, RoomSettings};
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
event: String,
|
|
|
|
|
data: MessageResponseData,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MessageResponse {
|
|
|
|
|
fn to_message(&self) -> Message {
|
|
|
|
|
Message::Text(serde_json::to_string(&self).unwrap())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
|
#[serde(untagged)]
|
|
|
|
|
enum MessageResponseData {
|
|
|
|
|
Greeting {
|
|
|
|
|
id: u64,
|
|
|
|
|
next_mora: Option<String>,
|
|
|
|
|
},
|
|
|
|
|
Word {
|
|
|
|
|
author: u64,
|
|
|
|
|
word: Word,
|
|
|
|
|
next_mora: String,
|
|
|
|
|
},
|
|
|
|
|
History {
|
|
|
|
|
words: Vec<Word>,
|
|
|
|
|
},
|
|
|
|
|
PlayerCount {
|
|
|
|
|
players: u64,
|
|
|
|
|
},
|
|
|
|
|
Error {
|
|
|
|
|
message: String,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MessageResponseData {
|
|
|
|
|
fn get_name(&self) -> String {
|
|
|
|
|
String::from(match self {
|
|
|
|
|
Self::Greeting { .. } => "greeting",
|
|
|
|
|
Self::Word { .. } => "word",
|
|
|
|
|
Self::History { .. } => "history",
|
|
|
|
|
Self::PlayerCount { .. } => "playerCount",
|
|
|
|
|
Self::Error { .. } => "error",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn into_response(self) -> MessageResponse {
|
|
|
|
|
MessageResponse {
|
|
|
|
|
event: self.get_name(),
|
|
|
|
|
data: self,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn into_message(self) -> Message {
|
|
|
|
|
self.into_response().to_message()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct Server {
|
|
|
|
|
event_hub: EventHub,
|
|
|
|
|
database: Database,
|
|
|
|
|
clients: HashMap<u64, Responder>,
|
|
|
|
|
next_mora: Option<String>,
|
|
|
|
|
last_client_id: Option<u64>,
|
|
|
|
|
}
|
|
|
|
|
use simple_websockets::{Event, EventHub, Message, Responder};
|
|
|
|
|
use std::cell::RefCell;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
|
|
#[derive(From, Debug)]
|
|
|
|
|
pub enum ServerCreationError {
|
|
|
|
@ -86,196 +20,203 @@ 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(),
|
|
|
|
|
}
|
|
|
|
|
pub struct ServerSettings {
|
|
|
|
|
pub port: u16,
|
|
|
|
|
pub testing: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
impl Default for ServerSettings {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
port: 8080,
|
|
|
|
|
testing: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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(),
|
|
|
|
|
}
|
|
|
|
|
pub struct Server {
|
|
|
|
|
event_hub: EventHub,
|
|
|
|
|
lobby: Rc<RefCell<Room>>,
|
|
|
|
|
rooms: HashMap<String, Room>,
|
|
|
|
|
clients: HashMap<u64, Client>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Server {
|
|
|
|
|
pub fn new(port: u16, testing: bool) -> Result<Self, ServerCreationError> {
|
|
|
|
|
Ok(Server {
|
|
|
|
|
event_hub: simple_websockets::launch(port)?,
|
|
|
|
|
database: Database::new(testing)?,
|
|
|
|
|
pub fn new(settings: &ServerSettings) -> Result<Self, ServerCreationError> {
|
|
|
|
|
Ok(Self {
|
|
|
|
|
event_hub: simple_websockets::launch(settings.port)?,
|
|
|
|
|
lobby: Rc::new(RefCell::new(Room::new(RoomSettings {
|
|
|
|
|
database_settings: DatabaseSettings {
|
|
|
|
|
db_type: match settings.testing {
|
|
|
|
|
true => DatabaseType::InMemory,
|
|
|
|
|
false => DatabaseType::OnDisk("shiritori.sb".to_string()),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
..Default::default()
|
|
|
|
|
})?)),
|
|
|
|
|
rooms: HashMap::new(),
|
|
|
|
|
clients: HashMap::new(),
|
|
|
|
|
next_mora: None,
|
|
|
|
|
last_client_id: None,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_client(&self, id: u64) -> Option<&Client> {
|
|
|
|
|
self.clients.get(&id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run(&mut self) {
|
|
|
|
|
loop {
|
|
|
|
|
match match self.event_hub.poll_event() {
|
|
|
|
|
Event::Connect(client_id, responder) => self.handle_connection(client_id, responder),
|
|
|
|
|
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(()) => {},
|
|
|
|
|
Ok(()) => {}
|
|
|
|
|
Err(error) => println!("{:?}", error),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn broadcast_player_count(&self) {
|
|
|
|
|
let response = MessageResponseData::PlayerCount { players: self.clients.len() as u64 }.into_response();
|
|
|
|
|
for (_client, responder) in self.clients.iter() {
|
|
|
|
|
responder.send(response.to_message());
|
|
|
|
|
}
|
|
|
|
|
fn new_client(&mut self, client_id: u64, responder: Responder) -> &Client {
|
|
|
|
|
let client = Client::new(
|
|
|
|
|
ClientInfo::Ws {
|
|
|
|
|
id: client_id,
|
|
|
|
|
responder,
|
|
|
|
|
discord_info: None,
|
|
|
|
|
},
|
|
|
|
|
self.lobby.clone(),
|
|
|
|
|
);
|
|
|
|
|
self.clients.insert(client_id, client);
|
|
|
|
|
self.clients.get(&client_id).unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_connection(&mut self, client_id: u64, responder: Responder) -> Result<(), ServerError> {
|
|
|
|
|
fn handle_connection(
|
|
|
|
|
&mut self,
|
|
|
|
|
client_id: u64,
|
|
|
|
|
responder: Responder,
|
|
|
|
|
) -> Result<(), ServerError> {
|
|
|
|
|
// Debug
|
|
|
|
|
println!("A client connected with id #{}", client_id);
|
|
|
|
|
responder.send(MessageResponseData::Greeting {
|
|
|
|
|
id: client_id,
|
|
|
|
|
next_mora: self.next_mora.clone(),
|
|
|
|
|
}.into_message());
|
|
|
|
|
responder.send(MessageResponseData::History {
|
|
|
|
|
words: self.database.load_words_before(self.database.last_word_id + 1)?
|
|
|
|
|
}.into_message());
|
|
|
|
|
self.clients.insert(client_id, responder);
|
|
|
|
|
self.broadcast_player_count();
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// Initialize client
|
|
|
|
|
let client = self.new_client(client_id, responder);
|
|
|
|
|
|
|
|
|
|
// Get immutable access to room
|
|
|
|
|
let room = client.room.borrow();
|
|
|
|
|
|
|
|
|
|
// Send client greeting
|
|
|
|
|
client.send(
|
|
|
|
|
MessageResponseData::Greeting {
|
|
|
|
|
id: client_id,
|
|
|
|
|
next_mora: room.next_mora().clone(),
|
|
|
|
|
}
|
|
|
|
|
.into_message(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Sent recent message history
|
|
|
|
|
client.send(
|
|
|
|
|
MessageResponseData::History {
|
|
|
|
|
words: room.get_history()?,
|
|
|
|
|
}
|
|
|
|
|
.into_message(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Number of clients on Rc<RefCell<Room>> reference counter will be one more
|
|
|
|
|
self.broadcast_player_count(&self.lobby);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_disconnection(&mut self, client_id: u64) -> Result<(), ServerError> {
|
|
|
|
|
// Debug
|
|
|
|
|
println!("Client #{} disconnected.", client_id);
|
|
|
|
|
self.clients.remove(&client_id);
|
|
|
|
|
self.broadcast_player_count();
|
|
|
|
|
|
|
|
|
|
// Remove client
|
|
|
|
|
// At this point, client should be dropped
|
|
|
|
|
let client = self.clients.remove(&client_id).unwrap();
|
|
|
|
|
|
|
|
|
|
// Get room
|
|
|
|
|
let room = client.room;
|
|
|
|
|
|
|
|
|
|
// Number of clients on Rc<RefCell<Room>> reference counter will be one less
|
|
|
|
|
self.broadcast_player_count(&room);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn for_client_in_room(&self, room: &Rc<RefCell<Room>>, mut closure: impl FnMut(&Client) -> ()) {
|
|
|
|
|
for (_id, client) in self
|
|
|
|
|
.clients
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(_id, client)| Rc::<RefCell<Room>>::ptr_eq(room, &client.room))
|
|
|
|
|
{
|
|
|
|
|
closure(client);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn client_count_in_room(&self, room: &Rc<RefCell<Room>>) -> usize {
|
|
|
|
|
let mut count: usize = 0;
|
|
|
|
|
self.for_client_in_room(room, |_| count += 1);
|
|
|
|
|
count
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn broadcast_player_count(&self, room: &Rc<RefCell<Room>>) {
|
|
|
|
|
let response = MessageResponseData::PlayerCount {
|
|
|
|
|
players: self.client_count_in_room(room) as u64,
|
|
|
|
|
}
|
|
|
|
|
.into_response();
|
|
|
|
|
for (_id, client) in self.clients.iter() {
|
|
|
|
|
client.send(response.to_message());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn announce_to_room(&self, room: &Rc<RefCell<Room>>, response_data: MessageResponseData) {
|
|
|
|
|
let response = response_data.into_response();
|
|
|
|
|
self.for_client_in_room(room, |client| {
|
|
|
|
|
client.send(response.to_message());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_message(&mut self, client_id: u64, message: Message) -> Result<(), ServerError> {
|
|
|
|
|
// Ignore binary messages
|
|
|
|
|
let message = match message {
|
|
|
|
|
Message::Text(message) => message,
|
|
|
|
|
Message::Binary(_) => return Ok(()),
|
|
|
|
|
Message::Text(message) => {
|
|
|
|
|
// Debug
|
|
|
|
|
println!(
|
|
|
|
|
"Received a message from client #{}: {:?}",
|
|
|
|
|
client_id, message
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
message
|
|
|
|
|
}
|
|
|
|
|
Message::Binary(message) => {
|
|
|
|
|
// Debug
|
|
|
|
|
println!(
|
|
|
|
|
"Received a binary message from client #{}: {:?}",
|
|
|
|
|
client_id, message
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Debug
|
|
|
|
|
println!("Received a message from client #{}: {:?}", client_id, message);
|
|
|
|
|
let client = self.get_client(client_id).unwrap();
|
|
|
|
|
|
|
|
|
|
let response = if Some(client_id) == self.last_client_id {
|
|
|
|
|
MessageResponseData::Error {
|
|
|
|
|
message: String::from("It's not your turn!"),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let query = if message.is_japanese() {
|
|
|
|
|
Some(message)
|
|
|
|
|
} else {
|
|
|
|
|
let kana = message.to_kana();
|
|
|
|
|
if kana.is_japanese() {
|
|
|
|
|
Some(kana)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// For some reason result will keep a reference to
|
|
|
|
|
// katakana_query and hiragana_query, meaning that
|
|
|
|
|
// these need to be kept around as a variable
|
|
|
|
|
// to prevent them from being dropped.
|
|
|
|
|
let (katakana_query, hiragana_query) = match &query {
|
|
|
|
|
Some(query) => (
|
|
|
|
|
Some(query.to_katakana()),
|
|
|
|
|
Some(query.to_hiragana())
|
|
|
|
|
),
|
|
|
|
|
None => (None, None),
|
|
|
|
|
};
|
|
|
|
|
let result = match &query {
|
|
|
|
|
Some(query) => match lookup(query) {
|
|
|
|
|
Some(result) => Some(result),
|
|
|
|
|
None => {
|
|
|
|
|
if query.is_hiragana() {
|
|
|
|
|
// looking up ごりら doesn't return ゴリラ
|
|
|
|
|
lookup(katakana_query.as_ref().unwrap())
|
|
|
|
|
} else if query.is_katakana() {
|
|
|
|
|
// looking up シリトリ doesn't return 尻取り
|
|
|
|
|
lookup(hiragana_query.as_ref().unwrap())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
|
|
|
|
match result {
|
|
|
|
|
Some(entry) => {
|
|
|
|
|
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)?;
|
|
|
|
|
MessageResponseData::Word {
|
|
|
|
|
author: client_id,
|
|
|
|
|
word,
|
|
|
|
|
next_mora: self.next_mora.as_deref().unwrap().to_owned(),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
MessageResponseData::Error {
|
|
|
|
|
message: String::from("Wrong starting mora!"),
|
|
|
|
|
}
|
|
|
|
|
match client.room.borrow_mut().handle_query(&message, client_id) {
|
|
|
|
|
// Broadcast new words to all clients in same room
|
|
|
|
|
Ok(response) => self.announce_to_room(&client.room, response),
|
|
|
|
|
// Send errors to only this client
|
|
|
|
|
Err(message) => {
|
|
|
|
|
client.send(
|
|
|
|
|
MessageResponseData::Error {
|
|
|
|
|
message: message.to_string(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => MessageResponseData::Error {
|
|
|
|
|
message: String::from("Not in dictionary!"),
|
|
|
|
|
},
|
|
|
|
|
.into_message(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}.into_response();
|
|
|
|
|
|
|
|
|
|
match response.data {
|
|
|
|
|
// Send errors to only this client
|
|
|
|
|
MessageResponseData::Error { .. } => {
|
|
|
|
|
self.clients.get(&client_id).unwrap().send(response.to_message());
|
|
|
|
|
},
|
|
|
|
|
// Broadcast everything else to all clients
|
|
|
|
|
_ => {
|
|
|
|
|
for (_client, responder) in self.clients.iter() {
|
|
|
|
|
responder.send(response.to_message());
|
|
|
|
|
}
|
|
|
|
|
self.last_client_id = Some(client_id);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|