Initial changes for multiplayer mode

This commit is contained in:
2022-05-04 13:50:34 +02:00
parent ccf90b4da7
commit b59b6d62a7
7 changed files with 620 additions and 30 deletions

View 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");
}
}

View File

@@ -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
View 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()
);
}
}

View File

@@ -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,

View File

@@ -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())
}