From: Geoffrey Allott Date: Sat, 6 Feb 2021 17:06:13 +0000 (+0000) Subject: game list and chat ui are functioning X-Git-Url: https://git.pointlesshacks.com/?a=commitdiff_plain;h=b813e4a456f5261eb335cc5fe68829f055e5c717;p=pokerwave.git game list and chat ui are functioning --- diff --git a/site/index.html b/site/index.html index 5dee3ab..9727051 100644 --- a/site/index.html +++ b/site/index.html @@ -17,33 +17,6 @@
-
diff --git a/site/main.js b/site/main.js index 662230a..bfee369 100644 --- a/site/main.js +++ b/site/main.js @@ -3,23 +3,6 @@ import { Socket } from "./modules/socket.js"; var sockets = []; -function hide_login() { - document.getElementById("login-background").classList.add("hidden"); -} - -function show_login(error) { - if (error) { - document.getElementById("login-error").classList.remove("hidden"); - document.getElementById("login-error").innerText = error; - } else { - document.getElementById("login-error").classList.add("hidden"); - document.getElementById("login-error").innerText = ""; - } - document.getElementById("login-background").classList.remove("hidden"); - document.getElementById("login-button").onclick = login; - document.getElementById("password-input").onchange = login; -} - function login() { var username = document.getElementById("username-input").value; var password = document.getElementById("password-input").value; @@ -33,6 +16,6 @@ function login() { window.onload = function() { var container = document.getElementById("game-tile-background"); - sockets.push(new Socket(container)); - show_login(); + sockets.push(new Socket(container, login)); + sockets[0].show_login(); }; diff --git a/site/modules/socket.js b/site/modules/socket.js index 342677f..7f25db1 100644 --- a/site/modules/socket.js +++ b/site/modules/socket.js @@ -1,6 +1,7 @@ export class Socket { - constructor(container) { + constructor(container, login_all_sockets) { this.container = container; + this.login_all_sockets = login_all_sockets; let proto = window.location.protocol === "https:" ? "wss:" : "ws:"; let uri = proto + "//" + window.location.host + "/api"; this.socket = new WebSocket(uri); @@ -35,26 +36,52 @@ export class Socket { this.state = "LoginAuthResponseSent"; break; case "LoginFailure": - this.auth = null; + delete this.auth; this.show_login(message.reason); this.state = "Connected"; break; case "LoginSuccess": this.hide_login(); this.send({type: "JoinLobby", filter: ""}); - this.state = "JoinLobbySent"; + this.state = "LoggedIn"; + break; + case "JoinLobbyFailure": + console.error("Joining lobby failed: " + message.reason); + this.state = "LoggedIn"; break; case "JoinLobbySuccess": - this.create_game_list(); this.games = message.games; - this.redraw_games(); + this.create_game_list(); this.state = "InLobby"; break; - case "AddGame": - this.add_game(message.game); + case "NewGame": + this.new_game(message.game); + break; + case "JoinGameFailure": + console.error("Joining game failed: " + message.reason); + this.state = "LoggedIn"; + break; + case "JoinGameSuccess": + this.game = message.game; + this.create_game_display(); + this.state = "InGame"; + break; + case "TakeActionSuccess": + this.add_action({username: this.auth.username, action: this.action}); + if (this.action.action === "Leave") { + this.send({type: "JoinLobby", filter: ""}); + } + delete this.action; + break; + case "TakeActionFailure": + console.error("Taking action failed: " + message.reason, this.action); + delete this.action; + break; + case "NewAction": + this.add_action(message.action); break; case "LogoutSuccess": - this.auth = null; + delete this.auth; this.show_login(); this.state = "Connected"; break; @@ -74,56 +101,204 @@ export class Socket { document.getElementById("login-error").innerText = ""; } document.getElementById("login-background").classList.remove("hidden"); - document.getElementById("login-button").onclick = login; - document.getElementById("password-input").onchange = login; + document.getElementById("login-button").onclick = this.login_all_sockets; + document.getElementById("password-input").onchange = this.login_all_sockets; } game_element(game) { console.log("getting game element for ", game); + var game_element = document.createElement("div"); game_element.classList.add("game-summary"); + var id = document.createElement("div"); id.innerText = "#" + game.id; id.classList.add("game-id"); - game_element.appendChild(id); + game_element.append(id); + var title = document.createElement("div"); - title.innerText = game.settings.format; // TODO + title.innerText = game.settings.title; title.classList.add("game-title"); - game_element.appendChild(title); + game_element.append(title); + var format = document.createElement("div"); format.innerText = game.settings.format; format.classList.add("game-format"); - game_element.appendChild(format); + game_element.append(format); + var settings = document.createElement("ul"); settings.classList.add("game-settings"); for (var setting of Object.keys(game.settings)) { if (setting !== "id" && setting !== "title" && setting !== "format") { var li = document.createElement("li"); li.innerText = setting + ": " + game.settings[setting]; - settings.appendChild(li); + settings.append(li); } } - game_element.appendChild(settings); + game_element.append(settings); + + game_element.onclick = () => this.join_game(game); + return game_element; } - add_game(game) { + new_game(game) { + var is_at_end = this.game_list_container.scrollTop + this.game_list_container.clientHeight == this.game_list_container.scrollHeight; this.games.push(game); var game_element = this.game_element(game); - this.game_list.prepend(game_element); + this.game_list.append(game_element); + if (is_at_end) { + this.game_list_container.scrollTo({ + top: this.game_list_container.scrollHeight - this.game_list_container.clientHeight, + behavior: "smooth" + }); + } + } + + join_game(game) { + this.send({type: "JoinGame", id: game.id}); + this.state = "JoinGameSent"; } create_game_list() { this.container.textContent = ""; + this.game_list_container = document.createElement("div"); + this.game_list_container.classList.add("game-list-container"); this.game_list = document.createElement("div"); this.game_list.classList.add("game-list"); - this.container.append(this.game_list); + this.game_list_container.append(this.game_list); + this.container.append(this.game_list_container); + this.redraw_games(); } redraw_games() { + this.game_list.textContent = ""; for (var game of this.games) { var game_element = this.game_element(game); - this.game_list.prepend(game_element); + this.game_list.append(game_element); + } + this.game_list_container.scrollTo({ + top: this.game_list.clientHeight - this.container.clientHeight, + behavior: "auto" + }); + } + + take_action(action) { + this.action = action; + this.send({type: "TakeAction", action: action}); + } + + create_chatroom() { + this.container.textContent = ""; + this.chatroom = document.createElement("div"); + this.chatroom.classList.add("chatroom"); + this.chatroom_chat = document.createElement("div"); + this.chatroom_chat.classList.add("chatroom-chat"); + this.chatroom.append(this.chatroom_chat); + this.chatroom_input = document.createElement("input"); + this.chatroom_input.onchange = () => { + this.take_action({action: "Message", message: this.chatroom_input.value}); + this.chatroom_input.value = ""; + }; + this.chatroom.append(this.chatroom_input); + var chatroom_close = document.createElement("button"); + chatroom_close.classList.add("chatroom-close"); + chatroom_close.innerText = "✗"; + chatroom_close.onclick = () => this.take_action({action: "Leave"}); + this.chatroom.append(chatroom_close); + this.container.append(this.chatroom); + this.redraw_chatroom(); + } + + join_element(username) { + var join_element = document.createElement("div"); + join_element.classList.add("chatroom-join"); + var username_element = document.createElement("div"); + username_element.classList.add("chatroom-username"); + username_element.innerText = username; + join_element.append(username_element); + var text_element = document.createElement("div"); + text_element.classList.add("chatroom-text"); + text_element.innerText = "Joined"; + join_element.append(text_element); + return join_element; + } + + message_element(username, message) { + var message_element = document.createElement("div"); + message_element.classList.add("chatroom-message"); + var username_element = document.createElement("div"); + username_element.classList.add("chatroom-username"); + username_element.innerText = username; + message_element.append(username_element); + var text_element = document.createElement("div"); + text_element.classList.add("chatroom-text"); + text_element.innerText = message; + message_element.append(text_element); + return message_element; + } + + leave_element(username) { + var leave_element = document.createElement("div"); + leave_element.classList.add("chatroom-leave"); + var username_element = document.createElement("div"); + username_element.classList.add("chatroom-username"); + username_element.innerText = username; + leave_element.append(username_element); + var text_element = document.createElement("div"); + text_element.classList.add("chatroom-text"); + text_element.innerText = "Left"; + leave_element.append(text_element); + return leave_element; + } + + add_action(user_action) { + switch (this.game.summary.settings.format) { + case "Chatroom": + var is_at_end = this.chatroom_chat.scrollTop + this.chatroom_chat.clientHeight == this.chatroom_chat.scrollHeight; + switch (user_action.action.action) { + case "Join": + this.game.users.add(user_action.username); + this.chatroom_chat.append(this.join_element(user_action.username)); + break; + case "Message": + this.chatroom_chat.append(this.message_element(user_action.username, user_action.action.message)); + break; + case "Leave": + this.game.users.delete(user_action.username); + this.chatroom_chat.append(this.leave_element(user_action.username)); + break; + default: + console.error("Unknown action for chatroom", user_action); + break; + } + if (is_at_end) { + this.chatroom_chat.scrollTo({ + top: this.chatroom_chat.scrollHeight - this.chatroom_chat.clientHeight, + behavior: "smooth" + }); + } + break; + } + } + + redraw_chatroom() { + this.chatroom_chat.textContent = ""; + for (var user_action of this.game.state.actions) { + this.add_action(user_action); + } + } + + create_game_display() { + this.container.textContent = ""; + switch (this.game.summary.settings.format) { + case "Chatroom": + this.game.users = new Set(); + this.create_chatroom(); + if (!this.game.users.has(this.auth.username)) { + this.take_action({action: "Join", seat: 0, chips: 0}); + } + break; } } diff --git a/site/style.css b/site/style.css index 3d9d22a..a661bdc 100644 --- a/site/style.css +++ b/site/style.css @@ -1,96 +1,22 @@ +@import url("style/login.css"); +@import url("style/game-list.css"); +@import url("style/chatroom.css"); + html, body { width: 100%; height: 100%; margin: 0; + overflow: hidden; } .hidden { display: none !important; } -#login-background { - background-color: rgba(0, 0, 100, 0.5); - width: 100%; - height: 100%; - position: fixed; - display: grid; - grid: 1fr 1fr 1fr / 1fr 1fr 1fr; -} - -#login { - grid-column: 2 / 3; - grid-row: 2 / 3; - background-color: skyblue; - border-radius: 2vmax; - padding: 4vmax; - display: grid; - grid: 1fr; - font-size: 4vmax; -} - -#login > label { - font-family: monospace; -} - -#login > input { - font-size: inherit; - margin-bottom: 1vmax; -} - -#login > button { - font-size: inherit; - margin-top: 2vmax; -} - -#login > #login-error { - font-size: 2vmax; - color: red; - margin-bottom: 0; -} - #game-tile-background { width: 100%; height: 100%; display: grid; grid: 1fr / 1fr; -} - -.game-list { - width: 100%; - display: grid; - grid-auto-rows: 30vw; -} - -.game-summary { - border: 1px solid grey; - border-bottom: none; - font-family: sans; - font-size: 4vw; - display: grid; - grid: 'id title blank' - 'format format format' - 'settings settings settings' - 'settings settings settings'; -} - -.game-id { - font-style: italic; - color: grey; - grid-area: id; -} - -.game-title { - text-align: center; - font-weight: bold; - grid-area: title; -} - -.game-format { - text-align: left; - margin-left: 5vw; - grid-area: format; -} - -.game-settings { - grid-area: settings; + overflow: hidden; } diff --git a/site/style/chatroom.css b/site/style/chatroom.css new file mode 100644 index 0000000..e5846e5 --- /dev/null +++ b/site/style/chatroom.css @@ -0,0 +1,65 @@ +.chatroom { + width: 100%; + height: 100%; + display: grid; + font-size: 1.5rem; + overflow: hidden; + position: relative; + background-color: #dfefff; +} + +.chatroom-chat { + width: 100%; + height: 100%; + overflow-y: scroll; +} + +.chatroom > input { + width: 100%; + height: 3rem; + font-size: inherit; + align-self: end; +} + +.chatroom-join, .chatroom-message, .chatroom-leave { + display: flex; +} + +.chatroom-username { + justify-self: left; + width: 12rem; + height: 100%; + margin: 0.25rem; + background-color: grey; + color: white; + text-align: center; + border-radius: 1rem; +} + +.chatroom-join > .chatroom-username { + background-color: limegreen; +} + +.chatroom-join > .chatroom-text, .chatroom-leave > .chatroom-text { + font-style: italic; + color: grey; +} + +.chatroom-leave > .chatroom-username { + background-color: red; +} + +.chatroom-text { + justify-self: right; + text-align: left; + height: 100%; + margin: 0.25rem; + width: calc(100% - 12rem); +} + +.chatroom-close { + display: block; + position: absolute; + right: 0; + top: 0; +} diff --git a/site/style/game-list.css b/site/style/game-list.css new file mode 100644 index 0000000..6780aaf --- /dev/null +++ b/site/style/game-list.css @@ -0,0 +1,69 @@ +.game-list-container { + width: 100%; + height: 100%; + overflow-x: clip; + overflow-y: scroll; +} + +.game-list { + width: 100%; + display: grid; + grid-auto-rows: 30vw; +} + +@keyframes new-game { + from { + background-color: limegreen; + transform: translateY(100%); + } + 50% { + background-color: limegreen; + transform: translateY(0); + } + to { + background-color: inherit; + transform: translateY(0); + } +} + +.game-summary { + border: 1px solid grey; + border-top: none; + font-family: sans; + font-size: 4vw; + display: grid; + grid: 'id title format' + 'settings settings settings' + 'settings settings settings'; + animation: new-game 2s; + cursor: pointer; +} + +.game-summary:hover { + background-color: #dfefff; +} + +.game-id { + width: 25vw; + font-style: italic; + color: grey; + grid-area: id; +} + +.game-title { + text-align: center; + font-weight: bold; + grid-area: title; + justify-self: center; +} + +.game-format { + width: 25vw; + grid-area: format; + justify-self: end; + text-align: right; +} + +.game-settings { + grid-area: settings; +} diff --git a/site/style/login.css b/site/style/login.css new file mode 100644 index 0000000..53d4020 --- /dev/null +++ b/site/style/login.css @@ -0,0 +1,39 @@ +#login-background { + background-color: rgba(0, 0, 100, 0.5); + width: 100%; + height: 100%; + position: fixed; + display: grid; + grid: 1fr 1fr 1fr / 1fr 1fr 1fr; +} + +#login { + grid-column: 2 / 3; + grid-row: 2 / 3; + background-color: skyblue; + border-radius: 2vmax; + padding: 4vmax; + display: grid; + grid: 1fr; + font-size: 4vmax; +} + +#login > label { + font-family: monospace; +} + +#login > input { + font-size: inherit; + margin-bottom: 1vmax; +} + +#login > button { + font-size: inherit; + margin-top: 2vmax; +} + +#login > #login-error { + font-size: 2vmax; + color: red; + margin-bottom: 0; +} diff --git a/src/client.rs b/src/client.rs index d50873a..1f06635 100644 --- a/src/client.rs +++ b/src/client.rs @@ -92,6 +92,17 @@ impl ConnectionState { } pub async fn apply_message(&mut self, message: ClientMessage) -> ServerMessage { + let interests_before = self.interests(); + let response = self.message_response(message).await; + let interests_after = self.interests(); + if interests_before != interests_after { + debug!("Client interests changed from {:?} to {:?}", interests_before, interests_after); + self.server.register_interests(interests_after).await; + } + response + } + + async fn message_response(&mut self, message: ClientMessage) -> ServerMessage { match (&mut self.client, message) { (_, ClientMessage::CreateUser{username, auth, nickname}) => { match self.server.create_user(&username, auth, &nickname).await { @@ -107,14 +118,13 @@ impl ConnectionState { (ClientState::LoginAuthIssued{username, challenge}, ClientMessage::LoginAuthResponse{signature}) => { if self.server.verify(&username, &challenge, &signature).await { self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle}; - self.server.register_interests(self.interests()).await; ServerMessage::LoginSuccess } else { self.client = ClientState::Connected; ServerMessage::LoginFailure{reason: "Invalid username or password".to_string()} } } - (ClientState::LoggedIn{username, state: LoggedInState::Idle}, ClientMessage::JoinLobby{filter}) => { + (ClientState::LoggedIn{username, ..}, ClientMessage::JoinLobby{filter}) => { let mut game_list = GameList::new(filter); match self.server.update_game_list(&mut game_list).await { Ok(()) => { @@ -136,7 +146,6 @@ impl ConnectionState { Ok(game) => { let game_view = game.view_for(&username); self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InGame{game}}; - self.server.register_interests(self.interests()).await; ServerMessage::JoinGameSuccess{game: game_view} } Err(err) => ServerMessage::JoinGameFailure{reason: err.to_string()}, @@ -166,17 +175,14 @@ impl ConnectionState { } (ClientState::LoggedIn{username, state: LoggedInState::InLobby{..}}, ClientMessage::LeaveLobby) => { self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle}; - self.server.register_interests(self.interests()).await; ServerMessage::LeaveGameSuccess } (ClientState::LoggedIn{username, state: LoggedInState::InGame{..}}, ClientMessage::LeaveGame) => { self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle}; - self.server.register_interests(self.interests()).await; ServerMessage::LeaveGameSuccess } (ClientState::LoggedIn{..}, ClientMessage::Logout) => { self.client = ClientState::Connected; - self.server.register_interests(self.interests()).await; ServerMessage::LogoutSuccess } (ClientState::LoggedIn{username, ..}, ClientMessage::ChangeAuth{auth}) => { diff --git a/src/game.rs b/src/game.rs index 39e87e5..27fb131 100644 --- a/src/game.rs +++ b/src/game.rs @@ -7,7 +7,10 @@ use crate::gamestate::GameState; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "format")] pub enum GameSettings { - Chatroom {max_users: u32}, + Chatroom { + title: String, + max_users: u32, + }, TexasHoldEm { // TODO }, @@ -156,7 +159,7 @@ impl Game { pub fn verify(&self, UserAction{ref username, ref action}: &UserAction) -> Result<(), ActionError> { debug!("Verifying action: UserAction {{ username: {:?}, action: {:?} }}", username, action); match self.summary.settings { - GameSettings::Chatroom{max_users} => match action { + GameSettings::Chatroom{max_users, ..} => match action { Action::Join{seat: 0, chips: 0} if self.state.user_has_joined(username) => Err(ActionError::AlreadyJoined), Action::Join{seat: 0, chips: 0} if self.state.num_players() + 1 > max_users => Err(ActionError::NoSeatAvailable), Action::Join{seat: 0, chips: 0} => Ok(()),