Last active
October 4, 2017 17:27
-
-
Save reeFridge/264bf039a086f1cdce30a78b84a1f3ac to your computer and use it in GitHub Desktop.
Side-scrolling game with multiplayer (so ugly to read - but it works pretty well!)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate ggez; | |
extern crate byteorder; | |
use ggez::conf; | |
use ggez::event; | |
use ggez::event::{Keycode, Mod}; | |
use ggez::{GameResult, Context}; | |
use ggez::graphics; | |
use ggez::graphics::{DrawMode, Point, Rect, Color}; | |
use std::time::Duration; | |
use std::net::TcpStream; | |
use std::io::{Read, Write, BufReader, BufRead}; | |
use std::collections::HashMap; | |
use byteorder::{ByteOrder, BigEndian}; | |
enum Direction { | |
Up, | |
Down, | |
Left, | |
Right | |
} | |
const W_HEIGHT: f32 = 1000.0; | |
const W_WIDTH: f32 = 1000.0; | |
struct ViewPort { | |
pos: Point, | |
w: u32, | |
h: u32, | |
} | |
impl ViewPort { | |
fn new(x: f32, y: f32, w: u32, h: u32) -> ViewPort { | |
ViewPort { pos: Point { x: x, y: y }, w: w, h: h } | |
} | |
fn convert_world_pos(&self, world_pos: Point) -> Point { | |
Point { x: world_pos.x - self.pos.x, y: world_pos.y - self.pos.y } | |
} | |
fn move_to(&mut self, direction: Direction, units: f32) { | |
match direction { | |
Direction::Up => self.pos.y -= units, | |
Direction::Down => self.pos.y += units, | |
Direction::Left => self.pos.x -= units, | |
Direction::Right => self.pos.x += units, | |
}; | |
} | |
} | |
struct GameObject { | |
pos: Point, | |
color: Color | |
} | |
impl GameObject { | |
fn new(x: f32, y: f32, color: Color) -> GameObject { | |
GameObject { pos: Point { x: x, y: y}, color: color } | |
} | |
fn move_to(&mut self, direction: Direction, units: f32) -> Option<Point> { | |
match direction { | |
Direction::Up => { | |
if self.pos.y - units > 0.0 { | |
self.pos.y -= units; | |
Some(self.pos.clone()) | |
} else { None } | |
}, | |
Direction::Down => { | |
if self.pos.y + units < W_HEIGHT { | |
self.pos.y += units; | |
Some(self.pos.clone()) | |
} else { None } | |
}, | |
Direction::Left => { | |
if self.pos.x - units > 0.0 { | |
self.pos.x -= units; | |
Some(self.pos.clone()) | |
} else { None } | |
}, | |
Direction::Right => { | |
if self.pos.x + units < W_WIDTH { | |
self.pos.x += units; | |
Some(self.pos.clone()) | |
} else { None } | |
} | |
} | |
} | |
} | |
enum EventType { | |
Spawn, | |
UpdatePos | |
} | |
type NetToken = usize; | |
// if connection is not established player will be at players[0] | |
// else controllable player will be at players[connection.token] | |
struct MainState { | |
free_area: Rect, | |
viewport: ViewPort, | |
objects: Vec<GameObject>, | |
players: HashMap<NetToken, Player>, | |
connection: Option<Connection> | |
} | |
struct Player { | |
name: String, | |
obj_index: usize | |
} | |
struct Connection { | |
socket: TcpStream, | |
token: NetToken | |
} | |
impl Connection { | |
fn new(mut socket: TcpStream) -> Result<Connection, String> { | |
let mut buf = [0u8; 8]; | |
match socket.read(&mut buf) { | |
Ok(_) => Ok(Connection { | |
socket: socket, | |
token: BigEndian::read_u64(&buf) as usize | |
}), | |
Err(e) => Err(format!("{:?}", e.kind())) | |
} | |
} | |
fn send_spawn_event(&mut self, name: String, pos: Point, color: graphics::Color) -> Result<(), String> { | |
let token = self.token.clone(); | |
self.socket.write_all(format!("SPWN {}|{}|{}x{}|{}\r\n", token, name, pos.x, pos.y, u32::from(color)).as_bytes()).unwrap(); | |
self.socket.flush().unwrap(); | |
Ok(()) | |
} | |
fn send_update_pos_event(&mut self, pos: Point) -> Result<(), String> { | |
let token = self.token.clone(); | |
self.socket.write_all(format!("UPDP {}|{}x{}\r\n", token, pos.x, pos.y).as_bytes()).unwrap(); | |
self.socket.flush().unwrap(); | |
Ok(()) | |
} | |
fn parse_event_type(buf: &[u8]) -> Option<EventType> { | |
let cow_str = String::from_utf8_lossy(buf).into_owned(); | |
let (event_name, _) = cow_str.as_str().split_at(4); | |
match event_name { | |
"SPWN" => Some(EventType::Spawn), | |
"UPDP" => Some(EventType::UpdatePos), | |
_ => None | |
} | |
} | |
fn parse_update_pos_event(buf: &[u8]) -> Result<(usize, Point), String> { | |
let cow_str = String::from_utf8_lossy(buf).into_owned(); | |
let str: Vec<&str> = cow_str.as_str().split("\r\n").collect(); | |
let data_str = str[0].trim(); | |
let data_parts: Vec<&str> = data_str.split("|").collect(); | |
let token = data_parts[0].parse::<u64>().expect("token") as usize; | |
let coords: Vec<&str> = data_parts[1].split("x").collect(); | |
let pos = Point::new(coords[0].parse::<f32>().expect("x"), coords[1].parse::<f32>().expect("y")); | |
Ok((token, pos)) | |
} | |
fn parse_spawn_event(buf: &[u8]) -> Result<(usize, String, Point, Color), String> { | |
let cow_str = String::from_utf8_lossy(buf).into_owned(); | |
let str: Vec<&str> = cow_str.as_str().split("\r\n").collect(); | |
let data_str = str[0].trim(); | |
let data_parts: Vec<&str> = data_str.split("|").collect(); | |
let token = data_parts[0].parse::<u64>().expect("token") as usize; | |
let name = data_parts[1].to_string(); | |
let coords: Vec<&str> = data_parts[2].split("x").collect(); | |
let pos = Point::new(coords[0].parse::<f32>().expect("x"), coords[1].parse::<f32>().expect("y")); | |
let color_u = data_parts[3].parse::<u32>().expect("color"); | |
let color = { | |
let rp = (color_u >> 24) as u8; | |
let gp = (color_u >> 16) as u8; | |
let bp = (color_u >> 8) as u8; | |
let ap = color_u as u8; | |
Color::from((rp, gp, bp, ap)) | |
}; | |
Ok((token, name, pos, color)) | |
} | |
} | |
impl MainState { | |
fn new() -> GameResult<MainState> { | |
let objects = vec![ | |
GameObject::new(200.0, 300.0, Color::from((255, 255, 255))), | |
GameObject::new(500.0, 100.0, Color::from((255, 255, 255))), | |
GameObject::new(50.0, 40.0, Color::from((255, 255, 255))) | |
]; | |
let s = MainState { | |
objects: objects, | |
viewport: ViewPort::new(0.0, 0.0, 800, 600), | |
players: HashMap::new(), | |
free_area: Rect { | |
x: 200.0, | |
y: 150.0, | |
w: 400.0, | |
h: 300.0, | |
}, | |
connection: None | |
}; | |
Ok(s) | |
} | |
fn connect(&mut self, host: String) -> Result<(), String> { | |
match TcpStream::connect(host) { | |
Ok(stream) => match Connection::new(stream) { | |
Ok(connection) => { | |
println!("connection established, net_token= {}", connection.token); | |
self.connection = Some(connection); | |
Ok(()) | |
}, | |
Err(err) => Err(err) | |
}, | |
Err(e) => Err(format!("{:?}", e.kind())) | |
} | |
} | |
fn spawn_player(&mut self, token: NetToken, name: String, pos: Point, color: Color) { | |
let idx = self.objects.len(); | |
self.objects.push(GameObject::new(pos.x, pos.y, color.clone())); | |
self.players.insert(token.clone(), Player { | |
name: name.clone(), | |
obj_index: idx | |
}); | |
} | |
fn update_player_pos(&mut self, token: NetToken, new_pos: Point) { | |
match self.players.get_mut(&token) { | |
Some(&mut Player { obj_index: ref idx, .. }) => self.objects.get_mut(idx.clone()), | |
None => None | |
}.and_then(|obj| { | |
obj.pos = new_pos; | |
Some(()) | |
}); | |
} | |
fn is_connected(&self) -> bool { | |
self.connection.is_some() | |
} | |
fn player(&mut self) -> Option<&mut GameObject> { | |
let token = match self.connection { | |
Some(Connection { ref token, .. }) => token.clone(), | |
None => 0 as NetToken | |
}; | |
match self.players.get_mut(&token) { | |
Some(&mut Player { obj_index: ref idx, .. }) => self.objects.get_mut(idx.clone()), | |
None => None | |
} | |
} | |
} | |
impl event::EventHandler for MainState { | |
fn update(&mut self, _ctx: &mut Context, _dt: Duration) -> GameResult<()> { | |
match self.player() { | |
Some(&mut GameObject { pos: ref player_pos, .. }) => Some(player_pos.clone()), | |
None => None | |
}.and_then(|world| { | |
let local = self.viewport.convert_world_pos(world.clone()); | |
let free_area = &self.free_area; | |
let delta = Point { | |
x: free_area.x + (free_area.w - local.x), | |
y: free_area.y + (free_area.h - local.y) | |
}; | |
if world.x > 0.0 && world.y > 0.0 { | |
if delta.x < 0.0 { | |
Some((Direction::Right, -delta.x)) | |
} else if delta.x > free_area.w { | |
Some((Direction::Left, free_area.x - local.x)) | |
} else if delta.y < 0.0 { | |
Some((Direction::Down, -delta.y)) | |
} else if delta.y > free_area.h { | |
Some((Direction::Up, free_area.y - local.y)) | |
} else { | |
None | |
} | |
} else { | |
None | |
} | |
}).and_then(|(direction, units)| { | |
self.viewport.move_to(direction, units); | |
Some(()) | |
}); | |
let mut buf = [0u8; 64]; | |
if let Some(Connection { socket: ref mut socket, .. }) = self.connection { | |
socket.set_read_timeout(Some(Duration::from_millis(10))); | |
match socket.read(&mut buf) { | |
Ok(_) => { | |
let (event, raw_data) = buf.split_at(5); | |
Some((Connection::parse_event_type(&event), raw_data)) | |
}, | |
Err(_) => None | |
} | |
} else { None }.and_then(|(event_type, raw_data)| { | |
match event_type { | |
Some(EventType::Spawn) => { | |
let (token, name, pos, color) = Connection::parse_spawn_event(&raw_data).unwrap(); | |
self.spawn_player(token, name, pos, color); | |
}, | |
Some(EventType::UpdatePos) => { | |
let (token, pos) = Connection::parse_update_pos_event(&raw_data).unwrap(); | |
self.update_player_pos(token, pos); | |
}, | |
None => () | |
}; | |
Some(()) | |
}); | |
Ok(()) | |
} | |
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { | |
graphics::set_background_color(ctx, Color::from((0, 0, 0))); | |
graphics::clear(ctx); | |
for obj in self.objects.iter() { | |
graphics::set_color(ctx, obj.color.clone())?; | |
let screen_pos = self.viewport.convert_world_pos(obj.pos.clone()); | |
graphics::rectangle(ctx, DrawMode::Fill, Rect { | |
x: screen_pos.x, | |
y: screen_pos.y, | |
w: 20.0, | |
h: 20.0 | |
})?; | |
} | |
//Side-scroll area | |
graphics::set_color(ctx, Color::from((0, 0, 255)))?; | |
graphics::rectangle(ctx, DrawMode::Line, Rect { | |
x: self.free_area.w, | |
y: self.free_area.h, | |
w: self.free_area.w, | |
h: self.free_area.h | |
})?; | |
let world_edges = vec![ | |
self.viewport.convert_world_pos(Point::new(0.0, 0.0)), | |
self.viewport.convert_world_pos(Point::new(W_WIDTH, 0.0)), | |
self.viewport.convert_world_pos(Point::new(W_WIDTH, W_HEIGHT)), | |
self.viewport.convert_world_pos(Point::new(0.0, W_HEIGHT)) | |
]; | |
//World area | |
graphics::set_color(ctx, Color::from((255, 255, 255)))?; | |
graphics::polygon(ctx, DrawMode::Line, &world_edges)?; | |
graphics::present(ctx); | |
Ok(()) | |
} | |
fn key_down_event(&mut self, _keycode: Keycode, _: Mod, _repeat: bool) { | |
match self.player() { | |
Some(ref mut player) => { | |
match _keycode { | |
event::Keycode::Up => player.move_to(Direction::Up, 10.0), | |
event::Keycode::Down => player.move_to(Direction::Down, 10.0), | |
event::Keycode::Left => player.move_to(Direction::Left, 10.0), | |
event::Keycode::Right => player.move_to(Direction::Right, 10.0), | |
_ => None | |
} | |
}, | |
None => None | |
}.and_then(|new_pos| { | |
if let Some(ref mut connection) = self.connection { | |
connection.send_update_pos_event(new_pos).unwrap(); | |
} | |
Some(()) | |
}).or_else(|| { | |
match _keycode { | |
event::Keycode::Space => { | |
let token = match self.connection { | |
Some(Connection { ref token, .. }) => token.clone(), | |
None => 0 as NetToken | |
}; | |
// let name = "Fratyz".to_string(); | |
// let start_pos = Point::new(self.viewport.w as f32 / 2.0, self.viewport.h as f32 / 2.0); | |
// let color = Color::from((255, 0, 255)); | |
// let name = "Reef".to_string(); | |
// let start_pos = Point::new(self.viewport.w as f32 / 2.0, self.viewport.h as f32 / 2.0); | |
// let color = Color::from((0, 255, 0)); | |
let name = "Fridge".to_string(); | |
let start_pos = Point::new(self.viewport.w as f32 / 2.0, self.viewport.h as f32 / 2.0); | |
let color = Color::from((255, 0, 0)); | |
self.spawn_player(token, name.clone(), start_pos.clone(), color.clone()); | |
if let Some(ref mut connection) = self.connection { | |
connection.send_spawn_event(name, start_pos, color).unwrap(); | |
} | |
}, | |
_ => () | |
}; | |
None | |
}); | |
} | |
} | |
pub fn main() { | |
let c = conf::Conf::new(); | |
let ctx = &mut Context::load_from_conf("super_simple", "ggez", c).unwrap(); | |
let mut state = &mut MainState::new().unwrap(); | |
match state.connect("127.0.0.1:7001".to_string()) { | |
Ok(_) => (), | |
Err(e) => println!("Failed to connect: {}", e) | |
}; | |
event::run(ctx, state).unwrap(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment