working gameplay loop, better UI. Needs a bit of work
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
use macroquad::{
|
||||
text::{TextDimensions, measure_text},
|
||||
window::screen_width,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub enum FontSize {
|
||||
Small,
|
||||
Medium,
|
||||
Large,
|
||||
}
|
||||
|
||||
impl FontSize {
|
||||
pub fn size(&self) -> u16 {
|
||||
let size1 = measure_text("A", None, 10, 1.0);
|
||||
let size2 = measure_text("A", None, 20, 1.0);
|
||||
|
||||
let scale_factor = size2.width - size1.width / 10.0;
|
||||
|
||||
let offset = size2.width - (10.0 * scale_factor);
|
||||
|
||||
let base_font_size = screen_width() * 0.01 - offset / scale_factor;
|
||||
|
||||
(match self {
|
||||
FontSize::Small => base_font_size,
|
||||
FontSize::Medium => base_font_size * 1.0,
|
||||
FontSize::Large => base_font_size * 2.0,
|
||||
}) as u16
|
||||
}
|
||||
|
||||
pub fn measure(&self, text: &str) -> TextDimensions {
|
||||
measure_text(text, None, self.size(), 1.0)
|
||||
}
|
||||
}
|
||||
+107
-15
@@ -1,9 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use macroquad::prelude::*;
|
||||
use macroquad::{
|
||||
prelude::*,
|
||||
ui::{Skin, Ui, hash, root_ui, widgets::Window},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{model::player::Player, screen::Screen};
|
||||
use crate::{font::FontSize, model::player::Player, screen::Screen, ui_scaling::window_width};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Serialize)]
|
||||
@@ -11,35 +14,121 @@ use crate::{model::player::Player, screen::Screen};
|
||||
enum HostClientMessages {
|
||||
ResetAllBuzers,
|
||||
ResetPlayerBuzzer { user_id: u64 },
|
||||
UpdateScore { user_id: u64, diff: u32 },
|
||||
UpdateScore { user_id: u64, diff: i32 },
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum HostServerMessages {
|
||||
Ack,
|
||||
UpdatePlayerStates { players: HashMap<u64, Player> },
|
||||
UpdatePlayerStates { players: HashMap<String, Player> },
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HostScreen {
|
||||
players: HashMap<u64, Player>,
|
||||
players: HashMap<String, Player>,
|
||||
}
|
||||
|
||||
impl HostScreen {
|
||||
fn draw_user(&self, ctx: &mut crate::context::Context, user_id: &str, ui: &mut Ui, width: f32) {
|
||||
let player = &self.players[user_id];
|
||||
let size = Vec2::new(width, screen_height() * 0.05);
|
||||
|
||||
let first_buzzed_in_player = self.get_first_buzzed_in_player();
|
||||
|
||||
let is_first = if let Some(first_player) = first_buzzed_in_player {
|
||||
first_player == user_id
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
ui.group(hash!(user_id), size, |ui| {
|
||||
let player_txt = player.name.clone();
|
||||
let player_txt_size = FontSize::Medium.measure(&player_txt);
|
||||
|
||||
let score_txt = format!("Score: {}", player.score);
|
||||
ui.label(Vec2::new(0.0, 0.0), &player_txt);
|
||||
ui.label(
|
||||
Vec2::new(0.0, player_txt_size.height + size.y * 0.025),
|
||||
&score_txt,
|
||||
);
|
||||
|
||||
if is_first {
|
||||
ui.label(Vec2::new(size.x * 0.45, 0.0), "BUZZED IN");
|
||||
}
|
||||
|
||||
if ui.button(Vec2::new(size.x * 0.70, 0.0), "+") {
|
||||
let msg = HostClientMessages::UpdateScore {
|
||||
user_id: user_id.parse().unwrap(),
|
||||
diff: 1,
|
||||
};
|
||||
ctx.send_msg(&msg);
|
||||
}
|
||||
if ui.button(Vec2::new(size.x * 0.80, 0.0), "-") {
|
||||
let msg = HostClientMessages::UpdateScore {
|
||||
user_id: user_id.parse().unwrap(),
|
||||
diff: -1,
|
||||
};
|
||||
ctx.send_msg(&msg);
|
||||
}
|
||||
if ui.button(Vec2::new(size.x * 0.90, 0.0), "C") {
|
||||
let msg = HostClientMessages::ResetPlayerBuzzer {
|
||||
user_id: user_id.parse().unwrap(),
|
||||
};
|
||||
ctx.send_msg(&msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn menu_skin() -> Skin {
|
||||
let label_style = root_ui()
|
||||
.style_builder()
|
||||
.font_size(FontSize::Medium.size())
|
||||
.text_color(WHITE)
|
||||
.build();
|
||||
|
||||
Skin {
|
||||
label_style,
|
||||
..root_ui().default_skin()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_first_buzzed_in_player(&self) -> Option<String> {
|
||||
self.players
|
||||
.iter()
|
||||
.filter_map(|(id, player)| player.buzz_in_time.map(|buzz_in| (id, buzz_in)))
|
||||
.min_by(|(_, buzz_in), (_, other_buzz_in)| buzz_in.total_cmp(other_buzz_in))
|
||||
.map(|(id, _)| id)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Screen for HostScreen {
|
||||
fn handle_frame(&mut self, ctx: &mut crate::context::Context) {
|
||||
clear_background(WHITE);
|
||||
clear_background(BEIGE);
|
||||
|
||||
draw_text(
|
||||
"I'm the host!",
|
||||
screen_width() / 2.0,
|
||||
screen_height() / 2.0,
|
||||
100.0,
|
||||
BLACK,
|
||||
);
|
||||
let width = window_width();
|
||||
|
||||
let button_size = screen_width() * 0.01;
|
||||
let button_location = Vec2::new(screen_width() * 0.50, screen_height() * 0.70);
|
||||
let menu_skin = &Self::menu_skin();
|
||||
root_ui().push_skin(menu_skin);
|
||||
|
||||
let window_position =
|
||||
Vec2::new(screen_width() * 0.5 - (width * 0.5), screen_height() * 0.1);
|
||||
let window_size = Vec2::new(width, screen_height() * 0.70);
|
||||
|
||||
Window::new(hash!("HostWindow"), window_position, window_size)
|
||||
.movable(false)
|
||||
.ui(&mut root_ui(), |ui| {
|
||||
let mut players: Vec<String> = self.players.keys().cloned().collect();
|
||||
players.sort();
|
||||
|
||||
for user_id in &players {
|
||||
self.draw_user(ctx, user_id, ui, width);
|
||||
}
|
||||
});
|
||||
|
||||
let button_size = screen_width() * 0.025;
|
||||
let button_location = Vec2::new(screen_width() * 0.50, screen_height() * 0.90);
|
||||
|
||||
draw_circle(
|
||||
button_location.x,
|
||||
@@ -56,6 +145,8 @@ impl Screen for HostScreen {
|
||||
ctx.send_msg(&HostClientMessages::ResetAllBuzers);
|
||||
}
|
||||
}
|
||||
|
||||
root_ui().pop_skin();
|
||||
}
|
||||
|
||||
fn handle_messages(
|
||||
@@ -68,6 +159,7 @@ impl Screen for HostScreen {
|
||||
// pass
|
||||
}
|
||||
HostServerMessages::UpdatePlayerStates { players } => {
|
||||
info!("Got new player state: {:?}", players);
|
||||
self.players = players;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::StateTransition;
|
||||
use crate::context::Context;
|
||||
use crate::font::FontSize;
|
||||
use crate::screen::Screen;
|
||||
use crate::ui_scaling::window_width;
|
||||
use macroquad::hash;
|
||||
use macroquad::ui::widgets::Window;
|
||||
use macroquad::{prelude::*, ui::root_ui};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -31,56 +34,65 @@ pub struct LoginScreen {
|
||||
|
||||
impl Screen for LoginScreen {
|
||||
fn handle_frame(&mut self, ctx: &mut Context) {
|
||||
clear_background(WHITE);
|
||||
clear_background(BEIGE);
|
||||
|
||||
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 group_size = Vec2::new(window_width(), screen_height() * 0.05);
|
||||
let window_size = Vec2::new(window_width(), screen_height() * 0.4);
|
||||
|
||||
let mut pressed_join = false;
|
||||
let mut pressed_host = false;
|
||||
|
||||
root_ui().window(
|
||||
Window::new(
|
||||
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
|
||||
);
|
||||
window_size,
|
||||
)
|
||||
.movable(false)
|
||||
.ui(&mut root_ui(), |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| {
|
||||
let dim = FontSize::Large.measure("Join");
|
||||
if ui.button(
|
||||
Vec2::new(group_size.x * 0.50, 0.0) - Vec2::new(dim.width * 0.5, 0.0),
|
||||
"Join",
|
||||
) {
|
||||
info!(
|
||||
"User pressed joined with name={} room_code={}",
|
||||
self.new_username, self.new_room_code
|
||||
);
|
||||
|
||||
pressed_join = true;
|
||||
}
|
||||
});
|
||||
ui.group(hash!("Host Group"), group_size, |ui| {
|
||||
if ui.button(Vec2::new(group_size.x * 0.45, 0.0), "Host Game") {
|
||||
info!(
|
||||
"User pressed host with name={} room_code={}",
|
||||
self.new_username, self.new_room_code
|
||||
);
|
||||
|
||||
pressed_host = 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...");
|
||||
});
|
||||
pressed_join = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
ui.group(hash!("Host Group"), group_size, |ui| {
|
||||
let dim = FontSize::Large.measure("Host Game");
|
||||
if ui.button(
|
||||
Vec2::new(group_size.x * 0.50, 0.0) - Vec2::new(dim.width * 0.5, 0.0),
|
||||
"Host Game",
|
||||
) {
|
||||
info!(
|
||||
"User pressed host with name={} room_code={}",
|
||||
self.new_username, self.new_room_code
|
||||
);
|
||||
|
||||
pressed_host = 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(&NewUserClientMessages::Register {
|
||||
|
||||
+43
-1
@@ -1,16 +1,22 @@
|
||||
use context::Context;
|
||||
use font::FontSize;
|
||||
use host_screen::HostScreen;
|
||||
use login_screen::LoginScreen;
|
||||
use macroquad::prelude::*;
|
||||
use macroquad::{
|
||||
prelude::*,
|
||||
ui::{Skin, root_ui},
|
||||
};
|
||||
use player_screen::PlayerScreen;
|
||||
use screen::Screen;
|
||||
|
||||
mod context;
|
||||
mod font;
|
||||
mod host_screen;
|
||||
mod login_screen;
|
||||
mod model;
|
||||
mod player_screen;
|
||||
mod screen;
|
||||
mod ui_scaling;
|
||||
|
||||
pub enum State {
|
||||
Init,
|
||||
@@ -58,6 +64,38 @@ pub enum StateTransition {
|
||||
LeaveGame,
|
||||
}
|
||||
|
||||
pub fn default_skin() -> Skin {
|
||||
let label_style = root_ui()
|
||||
.style_builder()
|
||||
.font_size(FontSize::Large.size())
|
||||
.text_color(WHITE)
|
||||
.build();
|
||||
let editbox_style = root_ui()
|
||||
.style_builder()
|
||||
.font_size(FontSize::Large.size())
|
||||
.color(BROWN)
|
||||
.color_selected(BROWN)
|
||||
.color_clicked(BROWN)
|
||||
.text_color(WHITE)
|
||||
.build();
|
||||
let window_style = root_ui().style_builder().color(DARKBROWN).build();
|
||||
let button_style = root_ui()
|
||||
.style_builder()
|
||||
.font_size(FontSize::Large.size())
|
||||
.color(GOLD)
|
||||
.text_color(BLACK)
|
||||
.color_hovered(YELLOW)
|
||||
.build();
|
||||
|
||||
Skin {
|
||||
label_style,
|
||||
editbox_style,
|
||||
button_style,
|
||||
window_style,
|
||||
..root_ui().default_skin()
|
||||
}
|
||||
}
|
||||
|
||||
#[macroquad::main("Play of the Game")]
|
||||
async fn main() {
|
||||
let mut context = Context::new("ws://127.0.0.1:8080/ws");
|
||||
@@ -66,12 +104,16 @@ async fn main() {
|
||||
state.transition_state(StateTransition::Init);
|
||||
|
||||
loop {
|
||||
let skin = default_skin();
|
||||
root_ui().push_skin(&skin);
|
||||
|
||||
if let Some(transition) = state.handle_messages(&mut context) {
|
||||
state = state.transition_state(transition);
|
||||
}
|
||||
|
||||
state.handle_frame(&mut context);
|
||||
|
||||
root_ui().pop_skin();
|
||||
next_frame().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ pub struct Player {
|
||||
pub name: String,
|
||||
pub id: u64,
|
||||
pub score: i32,
|
||||
pub buzz_in_time: Option<f64>,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use web_time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::StateTransition;
|
||||
use crate::context::Context;
|
||||
use crate::font::FontSize;
|
||||
use crate::screen::Screen;
|
||||
use macroquad::prelude::*;
|
||||
|
||||
@@ -59,11 +60,24 @@ impl Screen for PlayerScreen {
|
||||
RED
|
||||
};
|
||||
|
||||
let text_measure = FontSize::Large.measure(&ctx.username);
|
||||
|
||||
draw_text(
|
||||
&format!("Score: {}", self.score),
|
||||
screen_width() * 0.5,
|
||||
&ctx.username,
|
||||
screen_width() * 0.5 - text_measure.width * 0.5,
|
||||
screen_height() * 0.15,
|
||||
FontSize::Large.size() as f32,
|
||||
BLACK,
|
||||
);
|
||||
|
||||
let score_text = format!("Score: {}", self.score);
|
||||
let text_measure = FontSize::Large.measure(&score_text);
|
||||
|
||||
draw_text(
|
||||
&score_text,
|
||||
screen_width() * 0.5 - text_measure.width * 0.5,
|
||||
screen_height() * 0.25,
|
||||
50.0,
|
||||
FontSize::Large.size() as f32,
|
||||
BLACK,
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
use macroquad::window::{screen_height, screen_width};
|
||||
|
||||
pub fn window_width() -> f32 {
|
||||
(screen_width() * 0.8).min(screen_height() * 0.5)
|
||||
}
|
||||
Reference in New Issue
Block a user