Skip to content

Instantly share code, notes, and snippets.

@reeFridge
Last active October 4, 2017 17:27
Show Gist options
  • Save reeFridge/264bf039a086f1cdce30a78b84a1f3ac to your computer and use it in GitHub Desktop.
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!)
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