move player to this repo

This commit is contained in:
Max Bradbury 2020-09-02 17:54:49 +01:00
parent ac5ff9301b
commit a7b75269e8
2 changed files with 303 additions and 0 deletions

View File

@ -7,6 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
ggez = "0.5.1"
toml = "^0.5.6" toml = "^0.5.6"
serde = "^1.0.114" serde = "^1.0.114"
serde_derive = "^1.0.114" serde_derive = "^1.0.114"

302
src/bin/player.rs Normal file
View File

@ -0,0 +1,302 @@
use ggez;
// Next we need to actually `use` the pieces of ggez that we are going
// to need frequently.
use ggez::event::{KeyCode, KeyMods, EventsLoop};
use ggez::{event, graphics, Context, GameResult};
// We'll bring in some things from `std` to help us in the future.
use std::time::{Duration, Instant};
use ggez::graphics::{Rect};
use ggez::conf::FullscreenType;
// The first thing we want to do is set up some constants that will help us out later.
// Here we define the size of our game board in terms of how many grid
// cells it will take up. We choose to make a 30 x 20 game board.
const GRID_SIZE: (u8, u8) = (16, 9);
// dimension, i.e. 8×8 (square)
const GRID_CELL_SIZE: u8 = 8;
const UPDATES_PER_SECOND: f32 = 0.4;
// And we get the milliseconds of delay that this update rate corresponds to.
const MILLIS_PER_UPDATE: u64 = (1.0 / UPDATES_PER_SECOND * 1000.0) as u64;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct GridPosition { x: u8, y: u8 }
impl GridPosition {
/// We make a standard helper function so that we can create a new `GridPosition`
/// more easily.
pub fn new(x: u8, y: u8) -> Self {
GridPosition { x, y }
}
/// We'll make another helper function that takes one grid position and returns a new one after
/// making one move in the direction of `dir`. We use our `SignedModulo` trait
/// above, which is now implemented on `i16` because it satisfies the trait bounds,
/// to automatically wrap around within our grid size if the move would have otherwise
/// moved us off the board to the top, bottom, left, or right.
pub fn new_from_move(pos: GridPosition, dir: Direction) -> Self {
match dir {
Direction::Up => GridPosition::new(pos.x, pos.y - 1),
Direction::Down => GridPosition::new(pos.x, pos.y + 1),
Direction::Left => GridPosition::new(pos.x - 1, pos.y),
Direction::Right => GridPosition::new(pos.x + 1, pos.y),
}
}
}
/// We implement the `From` trait, which in this case allows us to convert easily between
/// a GridPosition and a ggez `graphics::Rect` which fills that grid cell.
/// Now we can just call `.into()` on a `GridPosition` where we want a
/// `Rect` that represents that grid cell.
impl From<GridPosition> for graphics::Rect {
fn from(pos: GridPosition) -> Self {
graphics::Rect::new_i32(
pos.x as i32 * GRID_CELL_SIZE as i32,
pos.y as i32 * GRID_CELL_SIZE as i32,
GRID_CELL_SIZE as i32,
GRID_CELL_SIZE as i32,
)
}
}
impl From<(u8, u8)> for GridPosition {
fn from(pos: (u8, u8)) -> Self {
GridPosition { x: pos.0, y: pos.1 }
}
}
/// Next we create an enum that will represent all the possible
/// directions that our snake could move.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
/// We also create a helper function that will let us convert between a
/// `ggez` `Keycode` and the `Direction` that it represents. Of course,
/// not every keycode represents a direction, so we return `None` if this
/// is the case.
pub fn from_keycode(key: KeyCode) -> Option<Direction> {
match key {
KeyCode::Up => Some(Direction::Up),
KeyCode::Down => Some(Direction::Down),
KeyCode::Left => Some(Direction::Left),
KeyCode::Right => Some(Direction::Right),
_ => None,
}
}
}
struct Avatar {
pos: GridPosition,
}
impl Avatar {
pub fn new(pos: GridPosition) -> Self {
Avatar {
pos
}
}
/// The main update function for our snake which gets called every time
/// we want to update the game state.
fn update(&mut self) {
}
/// Again, note that this approach to drawing is fine for the limited scope of this
/// example, but larger scale games will likely need a more optimized render path
/// using SpriteBatch or something similar that batches draw calls.
fn draw(&self, ctx: &mut Context, multiplier: &u8) -> GameResult<()> {
let dimension = (GRID_CELL_SIZE * multiplier) as f32;
// And then we do the same for the head, instead making it fully red to distinguish it.
let rectangle = graphics::Mesh::new_rectangle(
ctx,
graphics::DrawMode::fill(),
ggez::graphics::Rect {
x: (self.pos.x as u16 * GRID_CELL_SIZE as u16 * *multiplier as u16) as f32,
y: (self.pos.y as u16 * GRID_CELL_SIZE as u16 * *multiplier as u16) as f32,
w: dimension,
h: dimension,
},
[1.0, 0.5, 0.0, 1.0].into(),
)?;
graphics::draw(ctx, &rectangle, (ggez::mint::Point2 { x: 0.0, y: 0.0 },))?;
Ok(())
}
}
/// Now we have the heart of our game, the GameState. This struct
/// will implement ggez's `EventHandler` trait and will therefore drive
/// everything else that happens in our game.
struct GameState {
avatar: Avatar,
/// And we track the last time we updated so that we can limit
/// our update rate.
last_update: Instant,
/// integer multiples for scaling the display
size_multiplier: u8,
fullscreen: bool,
scene_width: u8,
scene_height: u8,
}
impl GameState {
pub fn new() -> Self {
let avatar = Avatar {
pos: (GRID_SIZE.0 / 4, GRID_SIZE.1 / 2).into(),
};
GameState {
avatar,
last_update: Instant::now(),
size_multiplier: 4,
fullscreen: false,
scene_width: 16,
scene_height: 9
}
}
fn toggle_fullscreen(&mut self) {
self.fullscreen = !self.fullscreen;
}
}
/// Now we implement EventHandler for GameState. This provides an interface
/// that ggez will call automatically when different events happen.
impl event::EventHandler for GameState {
/// Update will happen on every frame before it is drawn. This is where we update
/// our game state to react to whatever is happening in the game world.
fn update(&mut self, _ctx: &mut Context) -> GameResult {
// First we check to see if enough time has elapsed since our last update based on
// the update rate we defined at the top.
if Instant::now() - self.last_update >= Duration::from_millis(MILLIS_PER_UPDATE) {
self.avatar.update();
// If we updated, we set our last_update to be now
self.last_update = Instant::now();
}
// Finally we return `Ok` to indicate we didn't run into any errors
Ok(())
}
/// draw is where we should actually render the game's current state.
fn draw(&mut self, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, [0.1, 0.1, 0.1, 1.0].into());
// todo draw the whole game, not just the avatar
self.avatar.draw(ctx, &self.size_multiplier)?;
// Finally we call graphics::present to cycle the GPU's framebuffer and display
// the new frame we just drew.
graphics::present(ctx)?;
// We yield the current thread until the next update
ggez::timer::yield_now();
Ok(())
}
/// key_down_event gets fired when a key gets pressed.
fn key_down_event(
&mut self,
ctx: &mut Context,
keycode: KeyCode,
modifier: KeyMods,
_repeat: bool,
) {
if let Some(dir) = Direction::from_keycode(keycode) {
match dir {
Direction::Up => {
if self.avatar.pos.y > 0 {
self.avatar.pos.y -= 1
}
}
Direction::Right => {
if self.avatar.pos.x < GRID_SIZE.0 - 1 {
self.avatar.pos.x += 1
}
}
Direction::Down => {
// if y is less than 8 it's ok to increment it
if self.avatar.pos.y < GRID_SIZE.1 - 1 {
self.avatar.pos.y += 1
}
}
Direction::Left => {
if self.avatar.pos.x > 0 {
self.avatar.pos.x -= 1
}
}
}
}
// todo handle plus/minus keys
if keycode == KeyCode::Add {
self.size_multiplier += 1;
} else if keycode == KeyCode::Subtract && self.size_multiplier > 1 {
self.size_multiplier -= 1;
}
if keycode == KeyCode::F11 || (modifier == KeyMods::ALT && keycode == KeyCode::Return) {
self.toggle_fullscreen();
if self.fullscreen {
graphics::set_fullscreen(ctx, FullscreenType::True).unwrap();
} else {
graphics::set_fullscreen(ctx, FullscreenType::Windowed).unwrap();
graphics::set_drawable_size(
ctx,
(self.scene_width * self.size_multiplier) as f32,
(self.scene_height * self.size_multiplier) as f32
).unwrap();
}
}
// todo change window size
}
}
fn window_setup(x: u8, y: u8, multiplier: u8) -> (Context, EventsLoop) {
let x = (x as u16 * GRID_CELL_SIZE as u16 * multiplier as u16) as f32;
let y = (y as u16 * GRID_CELL_SIZE as u16 * multiplier as u16) as f32;
// Here we use a ContextBuilder to setup metadata about our game. First the title and author
let (ctx, events_loop) = ggez::ContextBuilder::new("snake", "Gray Olson")
// Next we set up the window. This title will be displayed in the title bar of the window.
.window_setup(
ggez::conf::WindowSetup::default().title("Write your game's title here")
)
// Now we get to set the size of the window, which we use our SCREEN_SIZE constant from earlier to help with
.window_mode(ggez::conf::WindowMode::default().dimensions(x, y))
// And finally we attempt to build the context and create the window. If it fails, we panic with the message
// "Failed to build ggez context"
.build()
.unwrap();
(ctx, events_loop)
}
fn main() -> GameResult {
// Next we create a new instance of our GameState struct, which implements EventHandler
let state = &mut GameState::new();
let (mut ctx, mut events_loop) = window_setup(
GRID_SIZE.0,
GRID_SIZE.1,
state.size_multiplier
);
// And finally we actually run our game, passing in our context and state.
event::run(&mut ctx, &mut events_loop, state)
}