basic buzzer working with websocket comms
This commit is contained in:
17 files changed
+1136
-82
No files matched your search
@@ -0,0 +1,66 @@
|
||||
use ewebsock::WsEvent;
|
||||
use macroquad::prelude::{error, info};
|
||||
|
||||
use crate::model::{ClientMessage, ClientRequest, GameResponse};
|
||||
|
||||
pub struct Context {
|
||||
pub sender: ewebsock::WsSender,
|
||||
pub reciever: ewebsock::WsReceiver,
|
||||
pub token: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(url: &str) -> Context {
|
||||
let (sender, reciever) = ewebsock::connect(url, ewebsock::Options::default()).unwrap();
|
||||
|
||||
Self {
|
||||
sender,
|
||||
reciever,
|
||||
token: String::new(),
|
||||
username: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_msg(&mut self, msg: &ClientMessage) {
|
||||
let request = ClientRequest {
|
||||
token: self.token.clone(),
|
||||
msg: msg.clone(),
|
||||
};
|
||||
let msg = serde_json::to_string(&request).unwrap();
|
||||
self.sender.send(ewebsock::WsMessage::Text(msg));
|
||||
}
|
||||
|
||||
pub fn recv_msg(&mut self) -> Option<GameResponse> {
|
||||
if let Some(msg) = self.reciever.try_recv() {
|
||||
match msg {
|
||||
WsEvent::Opened => {
|
||||
info!("Web socket opened!");
|
||||
None
|
||||
}
|
||||
WsEvent::Message(ws_message) => match ws_message {
|
||||
ewebsock::WsMessage::Text(msg) => {
|
||||
let game_resp: GameResponse = serde_json::from_str(&msg).unwrap();
|
||||
|
||||
if let GameResponse::JoinResponse { username, token } = &game_resp {
|
||||
self.username = username.clone();
|
||||
self.token = token.clone();
|
||||
}
|
||||
|
||||
Some(game_resp)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
WsEvent::Error(err) => {
|
||||
error!("Something hecky going on: {}", err);
|
||||
None
|
||||
}
|
||||
WsEvent::Closed => {
|
||||
panic!("Socket closed, something up...");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
use crate::StateTransition;
|
||||
use crate::context::Context;
|
||||
use crate::model::register::Register;
|
||||
use crate::model::{ClientMessage, GameResponse};
|
||||
use crate::screen::Screen;
|
||||
use macroquad::hash;
|
||||
use macroquad::{prelude::*, ui::root_ui};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LoginScreen {
|
||||
new_username: String,
|
||||
new_room_code: String,
|
||||
sent_join: bool,
|
||||
retry_count: usize,
|
||||
error_msg: bool,
|
||||
}
|
||||
|
||||
impl Screen for LoginScreen {
|
||||
fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> {
|
||||
clear_background(WHITE);
|
||||
|
||||
let group_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.05);
|
||||
let window_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.25);
|
||||
|
||||
let mut pressed_join = false;
|
||||
root_ui().window(
|
||||
hash!("Window"),
|
||||
Vec2::new(
|
||||
screen_width() * 0.5 - window_size.x * 0.5,
|
||||
screen_height() * 0.5 - window_size.x * 0.5,
|
||||
),
|
||||
Vec2::new(screen_width() * 0.25, screen_height() * 0.25),
|
||||
|ui| {
|
||||
ui.group(hash!("User Group"), group_size, |ui| {
|
||||
ui.input_text(hash!("Username In"), "Username", &mut self.new_username);
|
||||
});
|
||||
ui.group(hash!("Room Group"), group_size, |ui| {
|
||||
ui.input_text(hash!("Room Code In"), "Room Code", &mut self.new_room_code);
|
||||
});
|
||||
ui.group(hash!("Join Group"), group_size, |ui| {
|
||||
if ui.button(Vec2::new(group_size.x * 0.45, 0.0), "Join") {
|
||||
info!(
|
||||
"User pressed joined with name={} room_code={}",
|
||||
self.new_username, self.new_room_code
|
||||
);
|
||||
|
||||
pressed_join = true;
|
||||
}
|
||||
});
|
||||
|
||||
if self.error_msg {
|
||||
ui.group(hash!("Error Group"), group_size, |ui| {
|
||||
ui.label(Vec2::new(0.0, 0.0), "Failed to join game, try again...");
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if pressed_join {
|
||||
ctx.send_msg(&ClientMessage::Register(Register {
|
||||
game_code: self.new_room_code.clone(),
|
||||
username: self.new_username.clone(),
|
||||
}));
|
||||
|
||||
self.sent_join = true;
|
||||
}
|
||||
|
||||
if self.sent_join {
|
||||
let msg = ctx.recv_msg();
|
||||
|
||||
if let Some(GameResponse::JoinResponse { username, token }) = msg {
|
||||
return Some(StateTransition::JoinAsPlayer { username, token });
|
||||
}
|
||||
|
||||
self.retry_count += 1;
|
||||
|
||||
if self.retry_count > get_fps() as usize * 5 {
|
||||
warn!("Join failed, try again...");
|
||||
self.retry_count = 0;
|
||||
self.sent_join = false;
|
||||
self.error_msg = true;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
+52
-34
@@ -1,41 +1,59 @@
|
||||
use macroquad::hash;
|
||||
use macroquad::{prelude::*, ui::root_ui};
|
||||
use context::Context;
|
||||
use login_screen::LoginScreen;
|
||||
use macroquad::prelude::*;
|
||||
use player_screen::PlayerScreen;
|
||||
use screen::Screen;
|
||||
|
||||
mod context;
|
||||
mod login_screen;
|
||||
mod model;
|
||||
mod player_screen;
|
||||
mod screen;
|
||||
|
||||
pub enum State {
|
||||
Init,
|
||||
Login(LoginScreen),
|
||||
PlayerScreen(PlayerScreen),
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn transition_state(&self, transition: StateTransition) -> Self {
|
||||
match transition {
|
||||
StateTransition::Init | StateTransition::LeaveGame => {
|
||||
Self::Login(LoginScreen::default())
|
||||
}
|
||||
StateTransition::JoinAsPlayer { username, token } => {
|
||||
Self::PlayerScreen(PlayerScreen::new(&username, &token))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> {
|
||||
match self {
|
||||
Self::Init => Some(StateTransition::Init),
|
||||
Self::Login(login_screen) => login_screen.handle_frame(ctx),
|
||||
Self::PlayerScreen(player_screen) => player_screen.handle_frame(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum StateTransition {
|
||||
Init,
|
||||
JoinAsPlayer { username: String, token: String },
|
||||
LeaveGame,
|
||||
}
|
||||
|
||||
#[macroquad::main("Play of the Game")]
|
||||
async fn main() {
|
||||
let mut username = String::new();
|
||||
let mut rooom_code = String::new();
|
||||
let mut context = Context::new("ws://127.0.0.1:8080/ws");
|
||||
|
||||
let mut state = State::Init;
|
||||
state.transition_state(StateTransition::Init);
|
||||
|
||||
loop {
|
||||
clear_background(WHITE);
|
||||
|
||||
let group_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.05);
|
||||
let window_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.25);
|
||||
|
||||
root_ui().window(
|
||||
hash!("Window"),
|
||||
Vec2::new(
|
||||
screen_width() * 0.5 - window_size.x * 0.5,
|
||||
screen_height() * 0.5 - window_size.x * 0.5,
|
||||
),
|
||||
Vec2::new(screen_width() * 0.25, screen_height() * 0.25),
|
||||
|ui| {
|
||||
ui.group(hash!("User Group"), group_size, |ui| {
|
||||
ui.input_text(hash!("Username In"), "Username", &mut username);
|
||||
});
|
||||
ui.group(hash!("Room Group"), group_size, |ui| {
|
||||
ui.input_text(hash!("Room Code In"), "Room Code", &mut rooom_code);
|
||||
});
|
||||
ui.group(hash!("Join Group"), group_size, |ui| {
|
||||
if ui.button(Vec2::new(group_size.x * 0.45, 0.0), "Join") {
|
||||
info!(
|
||||
"User pressed joined with name={} room_code={}",
|
||||
username, rooom_code
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(transition) = state.handle_frame(&mut context) {
|
||||
state = state.transition_state(transition);
|
||||
}
|
||||
next_frame().await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct BuzzIn {
|
||||
pub time: f64,
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use buzz_in::BuzzIn;
|
||||
use register::Register;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod buzz_in;
|
||||
pub mod register;
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ClientMessage {
|
||||
BuzzIn(BuzzIn),
|
||||
Register(Register),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ClientRequest {
|
||||
pub token: String,
|
||||
pub msg: ClientMessage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum GameResponse {
|
||||
JoinResponse { username: String, token: String },
|
||||
AckBuzzer,
|
||||
ResetBuzzer,
|
||||
UpdatePountTotal { score: i32 },
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct Register {
|
||||
pub game_code: String,
|
||||
pub username: String,
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use web_time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::StateTransition;
|
||||
use crate::context::Context;
|
||||
use crate::model::ClientMessage;
|
||||
use crate::model::buzz_in::BuzzIn;
|
||||
use crate::screen::Screen;
|
||||
use macroquad::prelude::*;
|
||||
|
||||
pub struct PlayerScreen {
|
||||
token: String,
|
||||
username: String,
|
||||
score: i32,
|
||||
button_pressed: bool,
|
||||
}
|
||||
|
||||
impl PlayerScreen {
|
||||
pub fn new(token: &str, username: &str) -> Self {
|
||||
Self {
|
||||
token: token.to_string(),
|
||||
username: username.to_string(),
|
||||
score: 0,
|
||||
button_pressed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Screen for PlayerScreen {
|
||||
fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> {
|
||||
clear_background(BEIGE);
|
||||
|
||||
let button_color = if self.button_pressed {
|
||||
Color::from_hex(0xaa0000)
|
||||
} else {
|
||||
RED
|
||||
};
|
||||
|
||||
let button_size = (screen_width() * 0.33).min(screen_height() * 0.20);
|
||||
draw_circle(
|
||||
screen_width() * 0.5,
|
||||
screen_height() * 0.5,
|
||||
button_size * 1.1,
|
||||
DARKBROWN,
|
||||
);
|
||||
draw_circle(
|
||||
screen_width() * 0.5,
|
||||
screen_height() * 0.5,
|
||||
button_size,
|
||||
button_color,
|
||||
);
|
||||
|
||||
if is_mouse_button_pressed(MouseButton::Left) {
|
||||
let loc = Vec2::from(mouse_position());
|
||||
let normalize = loc - Vec2::new(screen_width() * 0.5, screen_height() * 0.5);
|
||||
|
||||
if normalize.length() < button_size {
|
||||
let timestamp = SystemTime::now();
|
||||
let unix_timestamp = timestamp.duration_since(UNIX_EPOCH).expect("Time invalid");
|
||||
|
||||
let time = unix_timestamp.as_secs_f64();
|
||||
|
||||
info!("Button pressed @ {}", time);
|
||||
self.button_pressed = true;
|
||||
let buzz_in = BuzzIn { time };
|
||||
|
||||
ctx.send_msg(&ClientMessage::BuzzIn(buzz_in));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use crate::{StateTransition, context::Context};
|
||||
|
||||
pub trait Screen {
|
||||
fn init_frame(&mut self) {}
|
||||
fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition>;
|
||||
}
|
||||
Reference in new issue
Block a user