commit b8e5176a875a15b95b15908f7a40baaa0fa1ae33
parent f7b8519646a7ea44b3d9d375bc2f7ce225694070
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Sat, 15 Aug 2026 10:21:40 -0400
Give every player an ID
Diffstat:
10 files changed, 219 insertions(+), 66 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -33,6 +33,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
name = "bytemuck"
version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -60,6 +66,7 @@ dependencies = [
"mio",
"postcard",
"serde",
+ "uuid",
]
[[package]]
@@ -85,6 +92,7 @@ dependencies = [
"glam",
"postcard",
"serde",
+ "uuid",
]
[[package]]
@@ -159,6 +167,41 @@ dependencies = [
]
[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+]
+
+[[package]]
name = "glam"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -192,6 +235,17 @@ dependencies = [
]
[[package]]
+name = "js-sys"
+version = "0.3.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -297,6 +351,18 @@ dependencies = [
]
[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -346,6 +412,18 @@ dependencies = [
]
[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -372,7 +450,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[package]]
@@ -394,6 +472,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -421,7 +516,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[package]]
@@ -440,12 +535,69 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
+name = "uuid"
+version = "1.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
+dependencies = [
+ "getrandom",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
+name = "wasm-bindgen"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/client/Cargo.toml b/client/Cargo.toml
@@ -11,3 +11,4 @@ macroquad = { version = "0.4.16", features = ["glam-serde"] }
mio = { version = "1.2.2", features = ["net", "os-poll"] }
postcard = { version = "1.1.3", default-features = false, features = ["use-std"] }
serde = { version = "1.0.229", features = ["derive"] }
+uuid = { version = "1.24.1", features = ["serde", "v4"] }
diff --git a/client/src/game.rs b/client/src/game.rs
@@ -1,7 +1,8 @@
-use common::game::{PlayerAction, PlayerActionKind};
+use common::game::{Action, ActionKind};
use macroquad::prelude::*;
+use uuid::Uuid;
-pub(crate) fn handle_input(player_name: &str) -> Option<PlayerAction> {
+pub(crate) fn handle_input(local_player_id: &Uuid) -> Option<Action> {
let velocity_x = if is_key_down(KeyCode::A) {
-1.
} else if is_key_down(KeyCode::D) {
@@ -17,9 +18,9 @@ pub(crate) fn handle_input(player_name: &str) -> Option<PlayerAction> {
0.
};
if velocity_x != 0. || velocity_y != 0. {
- Some(PlayerAction::new(
- player_name.to_string(),
- PlayerActionKind::Move(Vec2::new(velocity_x, velocity_y)),
+ Some(Action::new(
+ local_player_id.clone(),
+ ActionKind::Move(Vec2::new(velocity_x, velocity_y)),
))
} else {
None
diff --git a/client/src/main.rs b/client/src/main.rs
@@ -6,12 +6,10 @@ use std::{
use common::{
codec::Codec,
- game::{GameState, PlayerAction, PlayerActionKind},
-};
-use macroquad::{
- prelude::*,
- rand::{gen_range, srand},
+ game::{Action, ActionKind, GameState},
};
+use macroquad::{prelude::*, rand::srand};
+use uuid::Uuid;
use crate::{
game::handle_input,
@@ -46,20 +44,20 @@ async fn main() {
.subsec_nanos() as u64,
);
- // choose a random three-letter lowercase string as our player name (for now)
- let local_player_name = (0..3)
- .map(|_| gen_range('a' as u8, 'z' as u8))
- .fold(String::new(), |acc, el| acc + &(el as char).to_string());
+ // TODO: generate this on the server
+ let local_player_id = Uuid::new_v4();
- let _ = client_to_server_tx.send(PlayerAction::new(
- local_player_name.to_string(),
- PlayerActionKind::Join,
+ let _ = client_to_server_tx.send(Action::new(
+ local_player_id,
+ ActionKind::Join {
+ name: local_player_id.to_string().chars().take(3).collect(),
+ },
));
let mut camera = Camera::default();
let mut codec = Codec::default();
let mut game = GameState::default();
- let _on_shutdown = OnShutdown::new(client_to_server_tx.clone(), local_player_name.to_string());
+ let _on_shutdown = OnShutdown::new(client_to_server_tx.clone(), local_player_id);
loop {
let bytes: Vec<u8> = server_to_client_rx.try_iter().flatten().collect();
@@ -68,12 +66,12 @@ async fn main() {
game = g;
}
}
- if let Some(action) = handle_input(&local_player_name) {
+ if let Some(action) = handle_input(&local_player_id) {
if let Err(_) = client_to_server_tx.send(action) {
return;
}
}
- follow_local_player(&mut camera, &game, &local_player_name);
+ follow_local_player(&mut camera, &game, &local_player_id);
render(&camera, &game, &spritesheet);
next_frame().await;
}
diff --git a/client/src/net.rs b/client/src/net.rs
@@ -11,13 +11,14 @@ use std::{
use common::{
codec::Codec,
- game::{PlayerAction, PlayerActionKind},
+ game::{Action, ActionKind},
};
use mio::{Events, Interest, Poll, Token, net::TcpStream};
+use uuid::Uuid;
const TOKEN: Token = Token(0);
-pub(crate) fn handle_connection(addr: SocketAddr, tx: Sender<Vec<u8>>, rx: Receiver<PlayerAction>) {
+pub(crate) fn handle_connection(addr: SocketAddr, tx: Sender<Vec<u8>>, rx: Receiver<Action>) {
let mut stream = TcpStream::connect(addr).unwrap();
let poll = Poll::new().unwrap();
poll.registry()
@@ -56,7 +57,7 @@ pub(crate) fn handle_server_read(
}
}
-fn handle_server_write(stream: Arc<Mutex<TcpStream>>, rx: Receiver<PlayerAction>) {
+fn handle_server_write(stream: Arc<Mutex<TcpStream>>, rx: Receiver<Action>) {
let mut all_sent;
loop {
match rx.recv() {
@@ -90,24 +91,23 @@ fn handle_server_write(stream: Arc<Mutex<TcpStream>>, rx: Receiver<PlayerAction>
}
pub(crate) struct OnShutdown {
- tx: Sender<PlayerAction>,
- local_player_name: String,
+ tx: Sender<Action>,
+ local_player_id: Uuid,
}
impl OnShutdown {
- pub(crate) fn new(tx: Sender<PlayerAction>, local_player_name: String) -> Self {
+ pub(crate) fn new(tx: Sender<Action>, local_player_id: Uuid) -> Self {
OnShutdown {
tx,
- local_player_name,
+ local_player_id,
}
}
}
impl Drop for OnShutdown {
fn drop(&mut self) {
- let _ = self.tx.send(PlayerAction::new(
- self.local_player_name.clone(),
- PlayerActionKind::Leave,
- ));
+ let _ = self
+ .tx
+ .send(Action::new(self.local_player_id, ActionKind::Leave));
}
}
diff --git a/client/src/render.rs b/client/src/render.rs
@@ -1,5 +1,6 @@
use common::game::{GameState, SpriteIndex};
use macroquad::prelude::*;
+use uuid::Uuid;
#[derive(Debug, Default)]
pub(crate) struct Camera {
@@ -57,14 +58,14 @@ pub(crate) fn load_sprites() -> Spritesheet {
pub(crate) fn render(camera: &Camera, game: &GameState, spritesheet: &Spritesheet) {
clear_background(MAGENTA);
- for (name, player) in game.players.iter() {
+ for player in game.players.values() {
let sprite = match player.sprite_index {
SpriteIndex::Meeple => spritesheet.meeple.clone(),
};
let position_camera = player.position - camera.position;
sprite.render(position_camera);
draw_text(
- name,
+ player.name.clone(),
position_camera.x + 12.,
position_camera.y + sprite.size.y + 16.,
16.,
@@ -73,13 +74,9 @@ pub(crate) fn render(camera: &Camera, game: &GameState, spritesheet: &Spriteshee
}
}
-pub(crate) fn follow_local_player(camera: &mut Camera, game: &GameState, local_player_name: &str) {
+pub(crate) fn follow_local_player(camera: &mut Camera, game: &GameState, local_player_id: &Uuid) {
let screen_center = Vec2::new(screen_width() / 2., screen_height() / 2.);
- if let Some((_, player)) = game
- .players
- .iter()
- .find(|(name, _)| *name == local_player_name)
- {
+ if let Some((_, player)) = game.players.iter().find(|(id, _)| *id == local_player_id) {
camera.position = player.position - screen_center;
}
}
diff --git a/common/Cargo.toml b/common/Cargo.toml
@@ -8,3 +8,4 @@ anyhow = "1.0.104"
glam = { version = "0.27.0", features = ["scalar-math", "serde"] }
postcard = { version = "1.1.3", default-features = false, features = ["use-std"] }
serde = { version = "1.0.229", features = ["derive"] }
+uuid = { version = "1.24.1", features = ["v4", "serde"] }
diff --git a/common/src/game.rs b/common/src/game.rs
@@ -2,18 +2,21 @@ use std::{collections::HashMap, time::SystemTime};
use glam::Vec2;
use serde::{Deserialize, Serialize};
+use uuid::Uuid;
#[derive(Serialize, Deserialize, Debug)]
pub struct GameState {
pub timestamp: SystemTime,
- pub players: HashMap<String, Player>,
+ pub sequence_number: u32,
+ pub players: HashMap<Uuid, Player>,
}
impl Default for GameState {
fn default() -> Self {
Self {
timestamp: SystemTime::now(),
- players: Default::default(),
+ sequence_number: 0,
+ players: HashMap::with_capacity(32),
}
}
}
@@ -26,20 +29,20 @@ pub struct Player {
}
#[derive(Serialize, Deserialize, Debug)]
-pub struct PlayerAction {
- pub player_name: String,
- pub kind: PlayerActionKind,
+pub struct Action {
+ pub player_id: Uuid,
+ pub kind: ActionKind,
}
-impl PlayerAction {
- pub fn new(player_name: String, kind: PlayerActionKind) -> Self {
- PlayerAction { player_name, kind }
+impl Action {
+ pub fn new(player_id: Uuid, kind: ActionKind) -> Self {
+ Action { player_id, kind }
}
}
#[derive(Serialize, Deserialize, Debug)]
-pub enum PlayerActionKind {
- Join,
+pub enum ActionKind {
+ Join { name: String },
Move(Vec2),
Leave,
}
diff --git a/server/src/main.rs b/server/src/main.rs
@@ -7,7 +7,7 @@ use std::{
use common::{
codec::Codec,
- game::{GameState, Player, PlayerActionKind, SpriteIndex},
+ game::{ActionKind, GameState, Player, SpriteIndex},
};
use glam::Vec2;
use postcard::to_stdvec;
@@ -29,26 +29,26 @@ fn main() {
let mut send_game_status = false;
// Process all pending player actions
- for player_action in handler_to_main_rx.try_iter() {
+ for action in handler_to_main_rx.try_iter() {
send_game_status = true;
- match player_action.kind {
- PlayerActionKind::Join => {
+ match action.kind {
+ ActionKind::Join { name } => {
game.players.insert(
- player_action.player_name.clone(),
+ action.player_id.clone(),
Player {
- name: player_action.player_name.clone(),
+ name,
position: Vec2::ZERO,
sprite_index: SpriteIndex::Meeple,
},
);
}
- PlayerActionKind::Move(vec2) => {
- if let Some(player) = game.players.get_mut(&player_action.player_name) {
- player.position += vec2;
+ ActionKind::Move(velocity) => {
+ if let Some(player) = game.players.get_mut(&action.player_id) {
+ player.position += velocity;
}
}
- PlayerActionKind::Leave => {
- game.players.remove(&player_action.player_name);
+ ActionKind::Leave => {
+ game.players.remove(&action.player_id);
}
}
}
diff --git a/server/src/net.rs b/server/src/net.rs
@@ -10,7 +10,7 @@ use std::{
time::Duration,
};
-use common::{codec::Codec, game::PlayerAction};
+use common::{codec::Codec, game::Action};
use mio::{
Events, Interest, Poll, Token,
net::{TcpListener, TcpStream},
@@ -25,7 +25,7 @@ pub(crate) struct Handler {
pub tx: Sender<Vec<u8>>,
}
-pub(crate) fn listen(handlers: Arc<Mutex<LinkedList<Handler>>>, tx: Sender<PlayerAction>) {
+pub(crate) fn listen(handlers: Arc<Mutex<LinkedList<Handler>>>, tx: Sender<Action>) {
let mut poll = Poll::new().unwrap();
let mut events = Events::with_capacity(128);
let mut listener = TcpListener::bind("0.0.0.0:6767".parse().unwrap()).unwrap();
@@ -52,7 +52,7 @@ pub(crate) fn listen(handlers: Arc<Mutex<LinkedList<Handler>>>, tx: Sender<Playe
}
}
-fn handle_connection(mut stream: TcpStream, rx: Receiver<Vec<u8>>, tx: Sender<PlayerAction>) {
+fn handle_connection(mut stream: TcpStream, rx: Receiver<Vec<u8>>, tx: Sender<Action>) {
let poll = Poll::new().unwrap();
poll.registry()
.register(&mut stream, TOKEN, Interest::READABLE)
@@ -67,7 +67,7 @@ fn handle_connection(mut stream: TcpStream, rx: Receiver<Vec<u8>>, tx: Sender<Pl
}
}
-fn handle_client_read(mut poll: Poll, stream: Arc<Mutex<TcpStream>>, tx: Sender<PlayerAction>) {
+fn handle_client_read(mut poll: Poll, stream: Arc<Mutex<TcpStream>>, tx: Sender<Action>) {
let mut events = Events::with_capacity(128);
let mut codec = Codec::default();
let mut buffer = [0x00; 1024];