mirror of
https://github.com/pcvolkmer/winelounge.git
synced 2025-09-13 17:42:51 +00:00
Initial changes for multiplayer mode
This commit is contained in:
12
src/bin/winelounge-server/main.rs
Normal file
12
src/bin/winelounge-server/main.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(":7888").await.expect("Cannot open socket");
|
||||
|
||||
'listener: loop {
|
||||
|
||||
let (socket, _) = listener.accept().await.expect("Cannot accept connection");
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -11,6 +11,7 @@ use crate::world::World;
|
||||
mod player;
|
||||
mod sprite;
|
||||
mod world;
|
||||
mod net;
|
||||
|
||||
const GLASS_SPACE: u8 = 5;
|
||||
|
||||
@@ -40,6 +41,8 @@ fn main() {
|
||||
|
||||
let mut world = World::init();
|
||||
|
||||
world.spawn_player("Test".to_string(), 100, 100);
|
||||
|
||||
'running: loop {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
|
183
src/net.rs
Normal file
183
src/net.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use crate::world::{BoxAreaContent, BoxAreaPosition, Command, Direction};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
impl Display for Direction {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Direction::Up => write!(f, "Up"),
|
||||
Direction::Down => write!(f, "Down"),
|
||||
Direction::Left => write!(f, "Left"),
|
||||
Direction::Right => write!(f, "Right"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Direction {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"Up" => Ok(Direction::Up),
|
||||
"Down" => Ok(Direction::Down),
|
||||
"Left" => Ok(Direction::Left),
|
||||
"Right" => Ok(Direction::Right),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BoxAreaPosition {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BoxAreaPosition::RightTop => write!(f, "RightTop"),
|
||||
BoxAreaPosition::RightBottom => write!(f, "RightBottom"),
|
||||
BoxAreaPosition::LeftBottom => write!(f, "LeftBottom"),
|
||||
BoxAreaPosition::LeftTop => write!(f, "LeftTop"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BoxAreaPosition {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"RightTop" => Ok(BoxAreaPosition::RightTop),
|
||||
"RightBottom" => Ok(BoxAreaPosition::RightBottom),
|
||||
"LeftBottom" => Ok(BoxAreaPosition::LeftBottom),
|
||||
"LeftTop" => Ok(BoxAreaPosition::LeftTop),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BoxAreaContent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BoxAreaContent::Nothing => write!(f, "Nothing"),
|
||||
BoxAreaContent::HiddenBox => write!(f, "HiddenBox"),
|
||||
BoxAreaContent::EmptyGlass => write!(f, "EmptyGlass"),
|
||||
BoxAreaContent::FilledBottle => write!(f, "FilledBottle"),
|
||||
BoxAreaContent::EmptyBottle => write!(f, "EmptyBottle"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BoxAreaContent {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"Nothing" => Ok(BoxAreaContent::Nothing),
|
||||
"HiddenBox" => Ok(BoxAreaContent::HiddenBox),
|
||||
"EmptyGlass" => Ok(BoxAreaContent::EmptyGlass),
|
||||
"FilledBottle" => Ok(BoxAreaContent::FilledBottle),
|
||||
"EmptyBottle" => Ok(BoxAreaContent::EmptyBottle),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Command {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Command::SpawnPlayer(player_id, x, y) => write!(f, "Spawn {}", player_id),
|
||||
Command::RemovePlayer(player_id) => write!(f, "Face {}", player_id),
|
||||
Command::FacePlayer(player_id, direction) => write!(f, "Face {} {}", player_id, direction),
|
||||
Command::MovePlayer(player_id, direction) => write!(f, "Move {} {}", player_id, direction),
|
||||
Command::StopPlayer(player_id) => write!(f, "Stop {}", player_id),
|
||||
Command::UpdateBoxArea(pos, content) => write!(f, "UpdateBoxArea {} {}", pos, content),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Command {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut parts = s.split(' ');
|
||||
|
||||
match parts.next() {
|
||||
Some("Spawn") => match parts.next() {
|
||||
Some(player_id) => match parts.next() {
|
||||
Some(x) => match parts.next() {
|
||||
Some(y) => Ok(Command::SpawnPlayer(player_id.to_string(), x.parse().unwrap(), y.parse().unwrap())),
|
||||
_ => Err(())
|
||||
},
|
||||
_ => Err(())
|
||||
}
|
||||
_ => Err(())
|
||||
},
|
||||
Some("Remove") => match parts.next() {
|
||||
Some(player_id) => Ok(Command::RemovePlayer(player_id.to_string())),
|
||||
_ => Err(())
|
||||
},
|
||||
Some("Face") => match parts.next() {
|
||||
Some(player_id) => match parts.next() {
|
||||
Some(direction) => Ok(Command::FacePlayer(player_id.to_string(), direction.parse::<Direction>().unwrap())),
|
||||
_ => Err(()),
|
||||
},
|
||||
_ => Err(())
|
||||
},
|
||||
Some("Move") => match parts.next() {
|
||||
Some(player_id) => match parts.next() {
|
||||
Some(direction) => Ok(Command::MovePlayer(player_id.to_string(), direction.parse::<Direction>().unwrap())),
|
||||
_ => Err(()),
|
||||
},
|
||||
_ => Err(())
|
||||
},
|
||||
Some("Stop") => match parts.next() {
|
||||
Some(player_id) => Ok(Command::StopPlayer(player_id.to_string())),
|
||||
_ => Err(())
|
||||
},
|
||||
Some("UpdateBoxArea") => match parts.next() {
|
||||
Some(position) => {
|
||||
let position = position.parse::<BoxAreaPosition>().unwrap();
|
||||
match parts.next() {
|
||||
Some(content) => {
|
||||
let content = content.parse::<BoxAreaContent>().unwrap();
|
||||
Ok(Command::UpdateBoxArea(position, content))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
_ => Err(()),
|
||||
},
|
||||
Some(_) | None => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::world::Direction::{Left, Up};
|
||||
use crate::world::{BoxAreaContent, BoxAreaPosition, Command};
|
||||
|
||||
#[test]
|
||||
fn should_deserialize_command_line() {
|
||||
assert_eq!(
|
||||
Command::SpawnPlayer("1234".to_string(), 100, 200),
|
||||
"Spawn 1234 100 200".parse::<Command>().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Command::RemovePlayer("1234".to_string()),
|
||||
"Remove 1234".parse::<Command>().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Command::FacePlayer("1234".to_string(), Left),
|
||||
"Face 1234 Left".parse::<Command>().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Command::MovePlayer("1234".to_string(), Up),
|
||||
"Move 1234 Up".parse::<Command>().unwrap()
|
||||
);
|
||||
assert_eq!(Command::StopPlayer("1234".to_string()), "Stop 1234".parse::<Command>().unwrap());
|
||||
assert_eq!(
|
||||
Command::UpdateBoxArea(BoxAreaPosition::RightBottom, BoxAreaContent::HiddenBox),
|
||||
"UpdateBoxArea RightBottom HiddenBox"
|
||||
.parse::<Command>()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
use rand::random;
|
||||
use crate::sprite::Sprite;
|
||||
use crate::{sprite, GLASS_SPACE};
|
||||
use sdl2::rect::{Point, Rect};
|
||||
@@ -5,6 +6,7 @@ use sdl2::render::{Texture, WindowCanvas};
|
||||
|
||||
/// The player with his position, direction ...
|
||||
pub struct Player {
|
||||
pub id: String,
|
||||
position: Point,
|
||||
direction: PlayerDirection,
|
||||
footstep: u8,
|
||||
@@ -24,8 +26,13 @@ enum PlayerDirection {
|
||||
impl Player {
|
||||
/// Initializes Player with fixed position and direction.
|
||||
pub fn init() -> Player {
|
||||
Self::spawn(random::<u32>().to_string().as_str(), 380, 250)
|
||||
}
|
||||
|
||||
pub fn spawn(player_id: &str, x: u32, y: u32) -> Player {
|
||||
Player {
|
||||
position: Point::new(380, 250),
|
||||
id: player_id.to_string(),
|
||||
position: Point::new(x as i32, y as i32),
|
||||
direction: PlayerDirection::Down,
|
||||
footstep: 0,
|
||||
empty_glasses: 0,
|
||||
|
161
src/world.rs
161
src/world.rs
@@ -10,6 +10,7 @@ use crate::{Player, GLASS_SPACE};
|
||||
|
||||
pub struct World {
|
||||
player: Player,
|
||||
remote_player: Option<Player>,
|
||||
right_top_box_area: BoxArea,
|
||||
right_bottom_box_area: BoxArea,
|
||||
left_bottom_box_area: BoxArea,
|
||||
@@ -23,6 +24,7 @@ impl World {
|
||||
pub fn init() -> World {
|
||||
World {
|
||||
player: Player::init(),
|
||||
remote_player: None,
|
||||
right_top_box_area: BoxArea::new(BoxAreaPosition::RightTop, BoxAreaContent::EmptyGlass),
|
||||
right_bottom_box_area: BoxArea::new(
|
||||
BoxAreaPosition::RightBottom,
|
||||
@@ -43,6 +45,14 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_player(&mut self, player_id: String, x: u32, y: u32) {
|
||||
self.remote_player = Some(Player::spawn(player_id.as_str(), x, y));
|
||||
}
|
||||
|
||||
pub fn get_player(&mut self, _: String) -> &mut Player {
|
||||
&mut self.player
|
||||
}
|
||||
|
||||
pub fn playable_rect() -> Rect {
|
||||
Rect::new(0, 50, 800, 550)
|
||||
}
|
||||
@@ -52,58 +62,120 @@ impl World {
|
||||
/// This checks if player collides with any stop item or will move out of world.
|
||||
/// If player can move, move him and turn him to the correct side.
|
||||
pub fn handle_event(&mut self, event: Event) {
|
||||
let player_id = self.player.id.clone();
|
||||
match event {
|
||||
Event::KeyDown {
|
||||
keycode: Some(Keycode::Up) | Some(Keycode::W),
|
||||
..
|
||||
} => {
|
||||
self.player.move_up();
|
||||
self.execute_command(Command::MovePlayer(player_id.clone(), Direction::Up));
|
||||
if self.collides_with_stop() || !self.player.within_rect(&Self::playable_rect()) {
|
||||
self.player.move_down();
|
||||
self.player.face_up();
|
||||
self.execute_command(Command::MovePlayer(player_id.clone(), Direction::Down));
|
||||
self.execute_command(Command::FacePlayer(player_id.clone(), Direction::Up));
|
||||
}
|
||||
}
|
||||
Event::KeyDown {
|
||||
keycode: Some(Keycode::Down) | Some(Keycode::S),
|
||||
..
|
||||
} => {
|
||||
self.player.move_down();
|
||||
self.execute_command(Command::MovePlayer(player_id.clone(), Direction::Down));
|
||||
if self.collides_with_stop() || !self.player.within_rect(&Self::playable_rect()) {
|
||||
self.player.move_up();
|
||||
self.player.face_down();
|
||||
self.execute_command(Command::MovePlayer(player_id.clone(), Direction::Up));
|
||||
self.execute_command(Command::FacePlayer(player_id.clone(), Direction::Down));
|
||||
}
|
||||
}
|
||||
Event::KeyDown {
|
||||
keycode: Some(Keycode::Left) | Some(Keycode::A),
|
||||
..
|
||||
} => {
|
||||
self.player.move_left();
|
||||
self.execute_command(Command::MovePlayer(player_id.clone(), Direction::Left));
|
||||
if self.collides_with_stop() || !self.player.within_rect(&Self::playable_rect()) {
|
||||
self.player.move_right();
|
||||
self.player.face_left();
|
||||
self.execute_command(Command::MovePlayer(player_id.clone(), Direction::Right));
|
||||
self.execute_command(Command::FacePlayer(player_id.clone(), Direction::Left));
|
||||
}
|
||||
}
|
||||
Event::KeyDown {
|
||||
keycode: Some(Keycode::Right) | Some(Keycode::D),
|
||||
..
|
||||
} => {
|
||||
self.player.move_right();
|
||||
self.execute_command(Command::MovePlayer(player_id.clone(), Direction::Right));
|
||||
if self.collides_with_stop() || !self.player.within_rect(&Self::playable_rect()) {
|
||||
self.player.move_left();
|
||||
self.player.face_right();
|
||||
self.execute_command(Command::MovePlayer(player_id.clone(), Direction::Left));
|
||||
self.execute_command(Command::FacePlayer(player_id.clone(), Direction::Right));
|
||||
}
|
||||
}
|
||||
Event::KeyUp { .. } => self.player.stop(),
|
||||
Event::KeyUp { .. } => self.execute_command(Command::StopPlayer(player_id.clone())),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a command for world update.
|
||||
pub fn execute_command(&mut self, command: Command) {
|
||||
println!("{}", command);
|
||||
|
||||
match command {
|
||||
Command::SpawnPlayer(player_id, x, y) => &mut {
|
||||
// TBD
|
||||
},
|
||||
Command::RemovePlayer(player_id) => &mut {
|
||||
// TBD
|
||||
},
|
||||
Command::FacePlayer(player_id, Direction::Down) => {
|
||||
&mut self.get_player(player_id).face_down()
|
||||
},
|
||||
Command::FacePlayer(player_id, Direction::Up) => {
|
||||
&mut self.get_player(player_id).face_up()
|
||||
},
|
||||
Command::FacePlayer(player_id, Direction::Left) => {
|
||||
&mut self.get_player(player_id).face_left()
|
||||
},
|
||||
Command::FacePlayer(player_id, Direction::Right) => {
|
||||
&mut self.get_player(player_id).face_right()
|
||||
},
|
||||
Command::MovePlayer(player_id, Direction::Down) => {
|
||||
&mut self.get_player(player_id).move_down()
|
||||
},
|
||||
Command::MovePlayer(player_id, Direction::Up) => {
|
||||
&mut self.get_player(player_id).move_up()
|
||||
},
|
||||
Command::MovePlayer(player_id, Direction::Left) => {
|
||||
&mut self.get_player(player_id).move_left()
|
||||
},
|
||||
Command::MovePlayer(player_id, Direction::Right) => {
|
||||
&mut self.get_player(player_id).move_right()
|
||||
},
|
||||
Command::StopPlayer(player_id) => {
|
||||
&mut self.get_player(player_id).stop()
|
||||
},
|
||||
Command::UpdateBoxArea(position, content) => &mut {
|
||||
match position {
|
||||
BoxAreaPosition::RightTop => {
|
||||
self.right_top_box_area.update_content(content);
|
||||
self.right_top_box_area.last_update = chrono::Utc::now().timestamp();
|
||||
}
|
||||
BoxAreaPosition::RightBottom => {
|
||||
self.right_bottom_box_area.update_content(content);
|
||||
self.right_top_box_area.last_update = chrono::Utc::now().timestamp();
|
||||
}
|
||||
BoxAreaPosition::LeftBottom => {
|
||||
self.left_bottom_box_area.update_content(content);
|
||||
self.right_top_box_area.last_update = chrono::Utc::now().timestamp();
|
||||
}
|
||||
BoxAreaPosition::LeftTop => {
|
||||
self.left_top_box_area.update_content(content);
|
||||
self.right_top_box_area.last_update = chrono::Utc::now().timestamp();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Updates box areas to provide new boxes and remove items after some time
|
||||
pub fn update_box_areas(&mut self) {
|
||||
World::update_box_area(&mut self.right_top_box_area);
|
||||
World::update_box_area(&mut self.right_bottom_box_area);
|
||||
World::update_box_area(&mut self.left_bottom_box_area);
|
||||
World::update_box_area(&mut self.left_top_box_area);
|
||||
self.update_box_area(BoxAreaPosition::RightTop);
|
||||
self.update_box_area(BoxAreaPosition::RightBottom);
|
||||
self.update_box_area(BoxAreaPosition::LeftBottom);
|
||||
self.update_box_area(BoxAreaPosition::LeftTop);
|
||||
}
|
||||
|
||||
/// Handles both, collisions with lounge and any box area
|
||||
@@ -159,6 +231,11 @@ impl World {
|
||||
// Player
|
||||
self.player.render(canvas, texture);
|
||||
|
||||
// Remote/other player
|
||||
if let Some(remote_player) = &self.remote_player {
|
||||
remote_player.render(canvas, texture);
|
||||
}
|
||||
|
||||
// Points
|
||||
let x = font
|
||||
.render(format!("Score: {:#04}", self.player.points).as_str())
|
||||
@@ -177,17 +254,28 @@ impl World {
|
||||
canvas.present();
|
||||
}
|
||||
|
||||
fn update_box_area(box_area: &mut BoxArea) {
|
||||
fn update_box_area(&mut self, box_area_position: BoxAreaPosition) {
|
||||
let box_area = match box_area_position {
|
||||
BoxAreaPosition::RightTop => &self.right_top_box_area,
|
||||
BoxAreaPosition::RightBottom => &self.right_bottom_box_area,
|
||||
BoxAreaPosition::LeftBottom => &self.left_bottom_box_area,
|
||||
BoxAreaPosition::LeftTop => &self.left_top_box_area,
|
||||
};
|
||||
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let r: i64 = (rand::random::<i64>() % 10) + 3;
|
||||
|
||||
if box_area.content == BoxAreaContent::Nothing && box_area.last_update + 10 < now {
|
||||
box_area.content = BoxAreaContent::HiddenBox;
|
||||
box_area.last_update = now;
|
||||
self.execute_command(Command::UpdateBoxArea(
|
||||
box_area_position,
|
||||
BoxAreaContent::HiddenBox,
|
||||
));
|
||||
} else if box_area.content != BoxAreaContent::Nothing && box_area.last_update + 30 < now - r
|
||||
{
|
||||
box_area.content = BoxAreaContent::Nothing;
|
||||
box_area.last_update = now;
|
||||
self.execute_command(Command::UpdateBoxArea(
|
||||
box_area_position,
|
||||
BoxAreaContent::Nothing,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +327,7 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Commands
|
||||
fn handle_boxarea_collisions(&mut self) {
|
||||
if let Collision::BoxArea(bap) = self.has_player_collision() {
|
||||
let ba = match bap {
|
||||
@@ -256,20 +345,38 @@ impl World {
|
||||
};
|
||||
|
||||
if content == BoxAreaContent::EmptyGlass && self.player.can_pick_glass() {
|
||||
ba.update_content(BoxAreaContent::Nothing);
|
||||
self.execute_command(Command::UpdateBoxArea(bap, BoxAreaContent::Nothing));
|
||||
self.player.pick_glass();
|
||||
} else if content == BoxAreaContent::EmptyGlass && !self.player.can_pick_glass() {
|
||||
ba.update_content(BoxAreaContent::EmptyGlass);
|
||||
self.execute_command(Command::UpdateBoxArea(bap, BoxAreaContent::EmptyGlass));
|
||||
} else if content == BoxAreaContent::FilledBottle && self.player.can_fill_glass() {
|
||||
ba.update_content(BoxAreaContent::EmptyBottle);
|
||||
self.execute_command(Command::UpdateBoxArea(bap, BoxAreaContent::EmptyBottle));
|
||||
self.player.fill_glass();
|
||||
} else if content == BoxAreaContent::FilledBottle && !self.player.can_fill_glass() {
|
||||
ba.update_content(BoxAreaContent::FilledBottle);
|
||||
self.execute_command(Command::UpdateBoxArea(bap, BoxAreaContent::FilledBottle));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Command {
|
||||
SpawnPlayer(String, u32, u32),
|
||||
RemovePlayer(String),
|
||||
FacePlayer(String, Direction),
|
||||
MovePlayer(String, Direction),
|
||||
StopPlayer(String),
|
||||
UpdateBoxArea(BoxAreaPosition, BoxAreaContent),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Collision {
|
||||
BoxArea(BoxAreaPosition),
|
||||
@@ -317,7 +424,7 @@ impl BoxArea {
|
||||
Rect::new(x_offset, y_offset, 110, 110)
|
||||
}
|
||||
|
||||
/// Checks if player collides with this BoxSrea
|
||||
/// Checks if player collides with this BoxArea
|
||||
fn collides_with(&self, player: &Player) -> bool {
|
||||
self.bounding_rect().contains_point(player.center())
|
||||
}
|
||||
|
Reference in New Issue
Block a user