snake can now move, eat food and grow
This commit is contained in:
45
.vscode/launch.json
vendored
Normal file
45
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Debug unit tests in library 'game_core'",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--package=game_core"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug executable 'game_cli_frontend'",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"run",
|
||||
"--bin=game_cli_frontend",
|
||||
"--package=game_cli_frontend"
|
||||
]
|
||||
},
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "Debug unit tests in executable 'game_cli_frontend'",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--bin=game_cli_frontend",
|
||||
"--package=game_cli_frontend"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
6
game_cli_frontend/src/drawers/common.rs
Normal file
6
game_cli_frontend/src/drawers/common.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub fn reset_cursor() {
|
||||
println!(
|
||||
"{}",
|
||||
termion::cursor::Goto(1, 1),
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,11 @@ const EMPTY_TILE : &'static str = " ";
|
||||
const FOOD_TILE : &'static str = "o";
|
||||
|
||||
pub fn draw_field(field: &Field, color: &dyn termion::color::Color) {
|
||||
println!("{}{}", termion::color::Fg(color), BOUNDARY_TILE.repeat(field.width + 2));
|
||||
print!(
|
||||
"{}",
|
||||
termion::cursor::Goto(1, 1),
|
||||
);
|
||||
print!("{}{}\r\n", termion::color::Fg(color), BOUNDARY_TILE.repeat(field.width + 2));
|
||||
for y in 0..field.height {
|
||||
print!("{}{}", termion::color::Fg(color), BOUNDARY_TILE);
|
||||
for x in 0..field.width {
|
||||
@@ -19,7 +23,7 @@ pub fn draw_field(field: &Field, color: &dyn termion::color::Color) {
|
||||
};
|
||||
print!("{}{}", termion::color::Fg(color), char);
|
||||
}
|
||||
println!("{}{}", termion::color::Fg(color), BOUNDARY_TILE);
|
||||
print!("{}{}\r\n", termion::color::Fg(color), BOUNDARY_TILE);
|
||||
}
|
||||
println!("{}{}", termion::color::Fg(color), BOUNDARY_TILE.repeat(field.width + 2));
|
||||
println!("{}{}\r\n", termion::color::Fg(color), BOUNDARY_TILE.repeat(field.width + 2));
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod field;
|
||||
pub mod snake;
|
||||
pub mod snake;
|
||||
pub mod common;
|
||||
33
game_cli_frontend/src/input_mapping.rs
Normal file
33
game_cli_frontend/src/input_mapping.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use game_core::inputs::{InputKey, Inputs};
|
||||
use std::io::{Write, Read, stdout, stdin};
|
||||
|
||||
extern crate termion;
|
||||
|
||||
use termion::event::{Key, Event, MouseEvent};
|
||||
use termion::input::{TermRead, MouseTerminal};
|
||||
use termion::raw::IntoRawMode;
|
||||
|
||||
|
||||
fn map_to_input_key(key_event: termion::event::Key) -> Option<InputKey> {
|
||||
match key_event {
|
||||
termion::event::Key::Up => Some(InputKey::Up),
|
||||
termion::event::Key::Down => Some(InputKey::Down),
|
||||
termion::event::Key::Left => Some(InputKey::Left),
|
||||
termion::event::Key::Right => Some(InputKey::Right),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_events<R: Read>(reader: &mut R, inputs: &mut Inputs) {
|
||||
inputs.clear();
|
||||
|
||||
for c in reader.events() {
|
||||
if let Ok(Event::Key(key_pressed)) = c {
|
||||
if let Some(input_key) = map_to_input_key(key_pressed) {
|
||||
inputs.set_pressed(input_key, true);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
mod drawers;
|
||||
mod renderers;
|
||||
mod input_mapping;
|
||||
|
||||
use crate::renderers::renderer::main_loop;
|
||||
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
use std::io::{self, Write};
|
||||
use std::env;
|
||||
use std::time::Duration;
|
||||
|
||||
use game_core::common::position::Position;
|
||||
use game_core::field::loader::loader::FieldLoader;
|
||||
use game_core::field::loader::plain_text_loader::PlainTextFieldLoader;
|
||||
use game_core::snake::snake::{Node, Snake};
|
||||
use game_core::game_handler::game_handler::{GameData, GameManager};
|
||||
use game_core::game_handler::states::{PausedState, PlayingState};
|
||||
use game_core::inputs::Inputs;
|
||||
use game_core::snake::snake::{Node, Snake, SnakeHandler};
|
||||
use game_core::snake::states::{MovingState, StoppedState};
|
||||
use game_core::timer::Timer;
|
||||
use termion::async_stdin;
|
||||
use termion::raw::IntoRawMode;
|
||||
use crate::drawers::common::reset_cursor;
|
||||
use crate::drawers::field::draw_field;
|
||||
use crate::drawers::snake::draw_snake;
|
||||
use crate::input_mapping::process_events;
|
||||
use std::io::{Read, stdout, stdin};
|
||||
|
||||
fn clear_screen() {
|
||||
// Clear the entire screen and move cursor to top-left
|
||||
@@ -16,17 +26,15 @@ fn clear_screen() {
|
||||
io::stdout().flush().unwrap();
|
||||
}
|
||||
|
||||
pub fn main_loop() {
|
||||
|
||||
pub fn initialize_main_game() -> GameManager {
|
||||
let path = env::current_dir().unwrap();
|
||||
let field_loader = PlainTextFieldLoader{};
|
||||
let full_path = format!("{}{}", path.display(), "/game_data/fields/maze1");
|
||||
let field = field_loader.load(full_path);
|
||||
|
||||
let mut timer = Timer::new(120);
|
||||
|
||||
let snake = Snake {
|
||||
speed: 1.0,
|
||||
speed: 5.0,
|
||||
moving_direction: Position { x: 1, y: 0 },
|
||||
body: Node {
|
||||
position: Position {
|
||||
@@ -34,23 +42,62 @@ pub fn main_loop() {
|
||||
},
|
||||
next: Some(Box::new(Node {
|
||||
position: Position {
|
||||
x: 5, y: 4,
|
||||
x: 4, y: 3,
|
||||
},
|
||||
next: Some(Box::new(Node {
|
||||
position: Position {
|
||||
x: 5, y: 5,
|
||||
x: 4, y: 2,
|
||||
},
|
||||
next: None,
|
||||
})),
|
||||
})),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let snake_handler = SnakeHandler {
|
||||
snake: snake,
|
||||
state: Box::new(MovingState::new()),
|
||||
};
|
||||
|
||||
let mut game_data = GameData {
|
||||
field: field,
|
||||
snakes: vec![snake_handler],
|
||||
};
|
||||
let mut playing_state = PlayingState::new();
|
||||
playing_state.spawn_food(&mut game_data);
|
||||
let game_manager = GameManager {
|
||||
game_data: game_data,
|
||||
state: Box::new(playing_state),
|
||||
};
|
||||
return game_manager;
|
||||
}
|
||||
|
||||
pub fn main_loop() {
|
||||
let mut game_manager = initialize_main_game();
|
||||
|
||||
let mut stdout = stdout().into_raw_mode().unwrap();
|
||||
let mut reader = async_stdin();
|
||||
let mut timer = Timer::new(120);
|
||||
let mut inputs = Inputs::new();
|
||||
|
||||
|
||||
loop {
|
||||
timer.start();
|
||||
|
||||
process_events(&mut reader, &mut inputs);
|
||||
|
||||
game_manager.update(timer.get_delta_time(), &inputs);
|
||||
|
||||
// TODO: define a "Draw" function?
|
||||
clear_screen();
|
||||
draw_field(&field, &termion::color::White);
|
||||
draw_snake(&snake, &termion::color::Cyan, 1, 1);
|
||||
reset_cursor();
|
||||
draw_field(&game_manager.game_data.field, &termion::color::White);
|
||||
for snake_handler in &game_manager.game_data.snakes {
|
||||
draw_snake(&snake_handler.snake, &termion::color::Cyan, 2, 2);
|
||||
}
|
||||
reset_cursor();
|
||||
|
||||
stdout.flush().unwrap();
|
||||
|
||||
timer.limit_fps();
|
||||
timer.end();
|
||||
|
||||
@@ -27,4 +27,11 @@ impl Field {
|
||||
pub fn set_tile(&mut self, x: usize, y: usize, tile: Tile) {
|
||||
self.tiles[y * self.width + x] = tile;
|
||||
}
|
||||
|
||||
pub fn clear_food(&mut self, x: usize, y: usize) {
|
||||
let current_tile = self.tiles[y * self.width + x];
|
||||
if current_tile == Tile::Food {
|
||||
self.tiles[y * self.width + x] = Tile::Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ impl FieldLoader for PlainTextFieldLoader {
|
||||
let mut tiles = vec![Tile::Empty; width * height];
|
||||
|
||||
for (y, line) in lines.iter().enumerate() {
|
||||
println!("y: {}", y);
|
||||
for (x, ch) in line.chars().enumerate() {
|
||||
println!("x: {}", x);
|
||||
let tile = match ch {
|
||||
'x' => Tile::Wall,
|
||||
_ => Tile::Empty,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
|
||||
use crate::{field::field::{Field, Tile}, game_handler::states::GameManagerState, snake::snake::SnakeHandler};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{field::field::{Field, Tile}, game_handler::states::GameManagerState, inputs::Inputs, snake::snake::{Snake, SnakeHandler}};
|
||||
|
||||
pub struct GameData {
|
||||
pub field: Field,
|
||||
@@ -22,6 +24,16 @@ impl GameData {
|
||||
}
|
||||
|
||||
pub struct GameManager {
|
||||
game_data: GameData,
|
||||
game_manager_state: Box<dyn GameManagerState>,
|
||||
pub game_data: GameData,
|
||||
pub state: Box<dyn GameManagerState>,
|
||||
}
|
||||
|
||||
impl GameManager {
|
||||
|
||||
pub fn update(&mut self, delta: Duration, inputs: &Inputs) {
|
||||
let new_state = self.state.update(delta, inputs, &mut self.game_data);
|
||||
if let Some(new_state) = new_state {
|
||||
self.state = new_state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ impl PlayingState {
|
||||
Self { dt_accumulator: Duration::ZERO }
|
||||
}
|
||||
|
||||
fn spawn_food(&mut self, game_data: &mut GameData) {
|
||||
pub fn spawn_food(&mut self, game_data: &mut GameData) {
|
||||
let mut empty_positions = Vec::new();
|
||||
|
||||
// Get all empty tiles from the field
|
||||
@@ -51,10 +51,14 @@ impl GameManagerState for PlayingState {
|
||||
|
||||
fn update(&mut self, delta: Duration, inputs: &Inputs, game_data: &mut GameData) -> Option<Box<dyn GameManagerState>> {
|
||||
let mut should_spawn_food = false;
|
||||
let mut is_food_available = game_data.get_food_count() >= 1;
|
||||
let all_snakes: Vec<Snake> = game_data.snakes.iter().map(|sh| sh.snake.clone()).collect();
|
||||
for (current_snake_index, snake_handler) in game_data.snakes.iter_mut().enumerate() {
|
||||
if snake_handler.snake.is_eating(&game_data.field) {
|
||||
snake_handler.snake.grow();
|
||||
should_spawn_food = true;
|
||||
game_data.field.clear_food(snake_handler.snake.body.position.x as usize, snake_handler.snake.body.position.y as usize);
|
||||
is_food_available = false;
|
||||
}
|
||||
let other_snakes = all_snakes
|
||||
.iter()
|
||||
@@ -68,7 +72,7 @@ impl GameManagerState for PlayingState {
|
||||
.collect();
|
||||
snake_handler.update(delta, inputs, &other_snakes, &game_data.field);
|
||||
}
|
||||
if should_spawn_food {
|
||||
if should_spawn_food && !is_food_available {
|
||||
self.spawn_food(game_data);
|
||||
}
|
||||
None
|
||||
|
||||
@@ -29,4 +29,8 @@ impl Inputs {
|
||||
pub fn set_pressed(&mut self, input: InputKey, value: bool) {
|
||||
self.inputs_state.insert(input, value);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.inputs_state.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,12 +96,14 @@ impl Snake {
|
||||
}
|
||||
|
||||
pub fn do_move(&mut self) {
|
||||
println!("moving?");
|
||||
// probably a more optimal way of approaching this is just
|
||||
// moving the tail node to head.next, but Rust's borrow checking
|
||||
// system doesn't allow for that in an easy way
|
||||
|
||||
let mut last_position = self.body.position;
|
||||
println!("prev pos: {:?}", self.body.position);
|
||||
self.body.position += self.moving_direction; // the first node in body is always the head
|
||||
println!("next pos: {:?}", self.body.position);
|
||||
let mut current_node = self.body.next.as_deref_mut();
|
||||
while let Some(node) = current_node {
|
||||
let temp = node.position;
|
||||
@@ -126,7 +128,7 @@ impl Snake {
|
||||
|
||||
pub struct SnakeHandler {
|
||||
pub snake: Snake,
|
||||
state: Box<dyn SnakeState>,
|
||||
pub state: Box<dyn SnakeState>,
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -61,12 +61,14 @@ impl SnakeState for MovingState {
|
||||
|
||||
impl SnakeState for StoppedState {
|
||||
fn update(&mut self, _delta: Duration, _inputs: &Inputs, _snake: &mut Snake, _other_snakes: &Vec<Snake>, _field: &Field) -> Option<Box<dyn SnakeState>> {
|
||||
println!("stopped?");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl SnakeState for DeadState {
|
||||
fn update(&mut self, _delta: Duration, _inputs: &Inputs, _snake: &mut Snake, _other_snakes: &Vec<Snake>, _field: &Field) -> Option<Box<dyn SnakeState>> {
|
||||
println!("dead?");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user