Initial commit
This commit is contained in:
parent
1c60643479
commit
fc7d4e69d6
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "pacman"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pancurses = "0.17.0"
|
||||
rand = "0.8.5"
|
31
src/board.txt
Normal file
31
src/board.txt
Normal file
|
@ -0,0 +1,31 @@
|
|||
█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
|
||||
█ ██ █
|
||||
█ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ██ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄ █
|
||||
█ █ █ █ █ ██ █ █ █ █ █
|
||||
█ ▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀ █
|
||||
█ █
|
||||
█ ▄▄▄▄▄▄ ▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄ ▄▄▄▄▄▄ █
|
||||
█ ▀▀▀▀▀▀ ██ ▀▀▀▀▀▀██▀▀▀▀▀▀ ██ ▀▀▀▀▀▀ █
|
||||
█ ██ ██ ██ █
|
||||
█▄▄▄▄▄▄▄▄▄▄ ██▄▄▄▄▄▄ ██ ▄▄▄▄▄▄██ ▄▄▄▄▄▄▄▄▄▄█
|
||||
█ ██▀▀▀▀▀▀ ▀▀ ▀▀▀▀▀▀██ █
|
||||
█ ██ ██ █
|
||||
█ ██ ▄▄▄▄▄____▄▄▄▄▄ ██ █
|
||||
▀▀▀▀▀▀▀▀▀▀▀ ▀▀ █ █ ▀▀ ▀▀▀▀▀▀▀▀▀▀▀
|
||||
█ █
|
||||
▄▄▄▄▄▄▄▄▄▄▄ ▄▄ █ █ ▄▄ ▄▄▄▄▄▄▄▄▄▄▄
|
||||
█ ██ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ██ █
|
||||
█ ██ ██ █
|
||||
█ ██ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ██ █
|
||||
█▀▀▀▀▀▀▀▀▀▀ ▀▀ ▀▀▀▀▀▀██▀▀▀▀▀▀ ▀▀ ▀▀▀▀▀▀▀▀▀▀█
|
||||
█ ██ █
|
||||
█ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ██ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄ █
|
||||
█ ▀▀▀▀██ ▀▀▀▀▀▀▀▀ ▀▀ ▀▀▀▀▀▀▀▀ ██▀▀▀▀ █
|
||||
█ ██ ██ █
|
||||
█▄▄▄▄ ██ ▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄ ██ ▄▄▄▄█
|
||||
█▀▀▀▀ ▀▀ ██ ▀▀▀▀▀▀██▀▀▀▀▀▀ ██ ▀▀ ▀▀▀▀█
|
||||
█ ██ ██ ██ █
|
||||
█ ▄▄▄▄▄▄▄▄▄▄██▄▄▄▄▄▄ ██ ▄▄▄▄▄▄██▄▄▄▄▄▄▄▄▄▄ █
|
||||
█ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ █
|
||||
█ █
|
||||
█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█
|
64
src/data.rs
Normal file
64
src/data.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use pacman::Vec2;
|
||||
|
||||
const TT: bool = true;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const o: bool = false;
|
||||
|
||||
pub const BOARD_WIDTH: usize = 28;
|
||||
pub const BOARD_HEIGHT: usize = 31;
|
||||
|
||||
pub struct Board(pub [[bool; BOARD_WIDTH]; BOARD_HEIGHT]);
|
||||
|
||||
impl Index<Vec2> for Board {
|
||||
type Output = bool;
|
||||
|
||||
fn index(&self, index: Vec2) -> &Self::Output {
|
||||
&self.0
|
||||
[index.y.rem_euclid(BOARD_HEIGHT as i32) as usize]
|
||||
[index.x.rem_euclid(BOARD_WIDTH as i32) as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<Vec2> for Board {
|
||||
fn index_mut(&mut self, index: Vec2) -> &mut Self::Output {
|
||||
&mut self.0
|
||||
[index.y.rem_euclid(BOARD_HEIGHT as i32) as usize]
|
||||
[index.x.rem_euclid(BOARD_WIDTH as i32) as usize]
|
||||
}
|
||||
}
|
||||
|
||||
pub const BOARD: Board = Board([
|
||||
[TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT],
|
||||
[TT, o, o, o, o, o, o, o, o, o, o, o, o,TT,TT, o, o, o, o, o, o, o, o, o, o, o, o,TT],
|
||||
[TT, o,TT,TT,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT,TT,TT, o,TT],
|
||||
[TT, o,TT,TT,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT,TT,TT, o,TT],
|
||||
[TT, o,TT,TT,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT,TT,TT, o,TT],
|
||||
[TT, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o,TT],
|
||||
[TT, o,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT, o,TT],
|
||||
[TT, o,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT, o,TT],
|
||||
[TT, o, o, o, o, o, o,TT,TT, o, o, o, o,TT,TT, o, o, o, o,TT,TT, o, o, o, o, o, o,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT, o, o, o, o, o, o, o, o, o, o,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[ o, o, o, o, o, o, o, o, o, o,TT,TT,TT,TT,TT,TT,TT,TT, o, o, o, o, o, o, o, o, o, o],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT, o, o, o, o, o, o, o, o, o, o,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT],
|
||||
[TT, o, o, o, o, o, o, o, o, o, o, o, o,TT,TT, o, o, o, o, o, o, o, o, o, o, o, o,TT],
|
||||
[TT, o,TT,TT,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT,TT,TT, o,TT],
|
||||
[TT, o,TT,TT,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT, o,TT,TT,TT,TT, o,TT],
|
||||
[TT, o, o, o,TT,TT, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o,TT,TT, o, o, o,TT],
|
||||
[TT,TT,TT, o,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT, o,TT,TT,TT],
|
||||
[TT,TT,TT, o,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT, o,TT,TT,TT],
|
||||
[TT, o, o, o, o, o, o,TT,TT, o, o, o, o,TT,TT, o, o, o, o,TT,TT, o, o, o, o, o, o,TT],
|
||||
[TT, o,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT, o,TT],
|
||||
[TT, o,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT, o,TT,TT, o,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT, o,TT],
|
||||
[TT, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o,TT],
|
||||
[TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT,TT],
|
||||
]);
|
189
src/lib.rs
Normal file
189
src/lib.rs
Normal file
|
@ -0,0 +1,189 @@
|
|||
use std::ops::{Add, Sub, Mul, Div, AddAssign, SubAssign, MulAssign, DivAssign};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Direction {
|
||||
North,
|
||||
South,
|
||||
East,
|
||||
West
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
pub fn flipped(self) -> Self {
|
||||
use Direction::*;
|
||||
match self {
|
||||
North => South,
|
||||
South => North,
|
||||
East => West,
|
||||
West => East,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Direction> for Vec2 {
|
||||
fn from(v: Direction) -> Self {
|
||||
use Direction::*;
|
||||
match v {
|
||||
North => (0, -1).into(),
|
||||
South => (0, 1).into(),
|
||||
East => (1, 0).into(),
|
||||
West => (-1, 0).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<i32> for Direction {
|
||||
type Output = Vec2;
|
||||
|
||||
fn mul(self, rhs: i32) -> Self::Output {
|
||||
Vec2::from(self) * rhs
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Vec2 {
|
||||
pub x: i32,
|
||||
pub y: i32
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
pub fn sq_magnitude(self) -> i32 {
|
||||
self.x * self.x + self.y * self.y
|
||||
}
|
||||
|
||||
pub fn magnitude(self) -> f32 {
|
||||
(self.sq_magnitude() as f32).sqrt()
|
||||
}
|
||||
|
||||
pub fn sq_dist(self, rhs: Self) -> i32 {
|
||||
(self - rhs).sq_magnitude()
|
||||
}
|
||||
|
||||
pub fn dist(self, rhs: Self) -> f32 {
|
||||
(self - rhs).magnitude()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(i32, i32)> for Vec2 {
|
||||
fn from(v: (i32, i32)) -> Self {
|
||||
Self { x: v.0, y: v.1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec2> for (i32, i32) {
|
||||
fn from(v: Vec2) -> Self {
|
||||
(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self { x: self.x + rhs.x, y: self.y + rhs.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<(i32, i32)> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: (i32, i32)) -> Self::Output {
|
||||
self + Self::from(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Direction> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Direction) -> Self::Output {
|
||||
self + Self::from(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self { x: self.x - rhs.x, y: self.y - rhs.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<(i32, i32)> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: (i32, i32)) -> Self::Output {
|
||||
self - Self::from(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Direction> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Direction) -> Self::Output {
|
||||
self - Self::from(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<i32> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: i32) -> Self::Output {
|
||||
Self { x: self.x * rhs, y: self.y * rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<i32> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: i32) -> Self::Output {
|
||||
Self { x: self.x / rhs, y: self.y / rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vec2 {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = *self + rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<(i32, i32)> for Vec2 {
|
||||
fn add_assign(&mut self, rhs: (i32, i32)) {
|
||||
*self = *self + rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Direction> for Vec2 {
|
||||
fn add_assign(&mut self, rhs: Direction) {
|
||||
*self = *self + rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Vec2 {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = *self - rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<(i32, i32)> for Vec2 {
|
||||
fn sub_assign(&mut self, rhs: (i32, i32)) {
|
||||
*self = *self - rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Direction> for Vec2 {
|
||||
fn sub_assign(&mut self, rhs: Direction) {
|
||||
*self = *self - rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<i32> for Vec2 {
|
||||
fn mul_assign(&mut self, rhs: i32) {
|
||||
*self = *self * rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<i32> for Vec2 {
|
||||
fn div_assign(&mut self, rhs: i32) {
|
||||
*self = *self / rhs
|
||||
}
|
||||
}
|
269
src/main.rs
Normal file
269
src/main.rs
Normal file
|
@ -0,0 +1,269 @@
|
|||
use std::{time::{Instant, Duration}, iter::repeat, collections::HashSet};
|
||||
|
||||
use data::BOARD;
|
||||
use pancurses::{start_color, use_default_colors, init_pair, COLOR_CYAN, COLOR_YELLOW, COLOR_MAGENTA, COLOR_GREEN, COLOR_RED, COLOR_BLUE, init_color, COLOR_WHITE, initscr, noecho, curs_set, cbreak, endwin, Input};
|
||||
use pacman::{Vec2, Direction};
|
||||
use rand::{prelude::IteratorRandom, thread_rng};
|
||||
|
||||
use crate::data::{BOARD_WIDTH, BOARD_HEIGHT};
|
||||
|
||||
mod data;
|
||||
|
||||
const PACMAN: &str = "()";
|
||||
const GHOST: &str = "⎧⎫";
|
||||
const PELLET: &str = "╶ ";
|
||||
const POWER_PELLET: &str = "<>";
|
||||
|
||||
const COLOR_ORANGE: i16 = 8;
|
||||
|
||||
const BLINKY_HOME: Vec2 = Vec2 { x: 25, y: -5 };
|
||||
const INKY_HOME: Vec2 = Vec2 { x: 27, y: 31 };
|
||||
const PINKY_HOME: Vec2 = Vec2 { x: 2, y: -5 };
|
||||
const CLYDE_HOME: Vec2 = Vec2 { x: 0, y: 31 };
|
||||
|
||||
fn init_colors() {
|
||||
start_color();
|
||||
use_default_colors();
|
||||
init_pair(COLOR_CYAN, COLOR_CYAN, -1);
|
||||
init_pair(COLOR_YELLOW, COLOR_YELLOW, -1);
|
||||
init_pair(COLOR_MAGENTA, COLOR_MAGENTA, -1);
|
||||
init_pair(COLOR_GREEN, COLOR_GREEN, -1);
|
||||
init_pair(COLOR_RED, COLOR_RED, -1);
|
||||
init_pair(COLOR_BLUE, COLOR_BLUE, -1);
|
||||
init_color(COLOR_ORANGE, 1000, 843, 0);
|
||||
init_pair(COLOR_ORANGE, COLOR_ORANGE, -1);
|
||||
init_pair(COLOR_WHITE, COLOR_WHITE, -1);
|
||||
}
|
||||
|
||||
fn offset_with_bug(pos: Vec2, dir: Direction, dist: i32) -> Vec2 {
|
||||
pos + match dir {
|
||||
// in the original game, an offset to the north would also offset to the west
|
||||
Direction::North => Vec2::from(Direction::North) + Direction::West,
|
||||
_ => dir.into()
|
||||
} * dist
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum GhostState {
|
||||
Chase,
|
||||
Scatter,
|
||||
Frightened,
|
||||
Eaten
|
||||
}
|
||||
|
||||
struct Ghost {
|
||||
state: GhostState,
|
||||
pos: Vec2,
|
||||
target: Vec2,
|
||||
facing: Direction,
|
||||
}
|
||||
|
||||
impl Ghost {
|
||||
fn new(pos: Vec2, facing: Direction) -> Self {
|
||||
Self { state: GhostState::Chase, pos, target: Default::default(), facing }
|
||||
}
|
||||
|
||||
fn get_movement_dir(&self) -> Direction {
|
||||
// special case that prevents ghosts from turning within a corridor at the bottom
|
||||
let special_cases: &[Vec2] = &[(12, 23).into(), (15, 23).into(), (12, 11).into(), (15, 11).into()];
|
||||
if special_cases.contains(&self.pos) && !BOARD[self.pos + self.facing] {
|
||||
return self.facing;
|
||||
}
|
||||
|
||||
use Direction::*;
|
||||
// particular order of priority
|
||||
let options = [North, West, South, East].into_iter() // all directions
|
||||
.filter(|&v| v != self.facing.flipped()) // that arent backward
|
||||
.filter(|&v| !BOARD[self.pos + v]); // that arent solid
|
||||
|
||||
if self.state == GhostState::Frightened {
|
||||
return options.choose(&mut thread_rng()).unwrap_or(self.facing.flipped());
|
||||
}
|
||||
|
||||
// with the shortest distance to target
|
||||
options.min_by_key(|&v| self.target.sq_dist(self.pos + v))
|
||||
.unwrap_or(self.facing.flipped()) // default to backward
|
||||
}
|
||||
|
||||
fn move_(&mut self) {
|
||||
self.facing = self.get_movement_dir();
|
||||
self.pos += self.facing;
|
||||
self.pos.x = self.pos.x.rem_euclid(BOARD_WIDTH as i32);
|
||||
self.pos.y = self.pos.y.rem_euclid(BOARD_HEIGHT as i32);
|
||||
}
|
||||
|
||||
fn set_target_and_move(&mut self, if_chase: impl FnOnce(&Self) -> Vec2, home: Vec2) {
|
||||
self.target = match self.state {
|
||||
GhostState::Chase => if_chase(self),
|
||||
GhostState::Scatter => home,
|
||||
GhostState::Frightened => Default::default(), // doesnt matter
|
||||
GhostState::Eaten => (13, 11).into(),
|
||||
};
|
||||
|
||||
self.move_();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let window = initscr();
|
||||
|
||||
noecho();
|
||||
curs_set(0);
|
||||
cbreak();
|
||||
init_colors();
|
||||
window.keypad(true);
|
||||
window.nodelay(true);
|
||||
|
||||
window.refresh();
|
||||
|
||||
const TICK_DUR: Duration = Duration::from_millis(160);
|
||||
|
||||
let mut blinky = Ghost::new((6,5).into(), Direction::East);
|
||||
let mut inky = Ghost::new((21,5).into(), Direction::West);
|
||||
let mut pinky = Ghost::new((6,26).into(), Direction::North);
|
||||
let mut clyde = Ghost::new((21,26).into(), Direction::North);
|
||||
|
||||
let mut pellets = BOARD.0.iter()
|
||||
.enumerate()
|
||||
.flat_map(|v| repeat(v.0).zip(v.1.iter().enumerate()))
|
||||
.filter_map(|v| if *v.1.1 { None } else { Some((v.1.0 as i32, v.0 as i32)) })
|
||||
.map(Vec2::from)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut player_pos: Vec2 = (13, 23).into();
|
||||
let mut player_dir: Direction = Direction::South;
|
||||
let mut queued_dir: Option<Direction> = None;
|
||||
let mut time_till_next_tick = TICK_DUR;
|
||||
let mut time_till_next_ghost_tick = TICK_DUR;
|
||||
|
||||
let mut last_iter = Instant::now();
|
||||
|
||||
let mut needs_refresh = true;
|
||||
let mut lost = false;
|
||||
let won = loop {
|
||||
let this_iter = Instant::now();
|
||||
let delta_time = this_iter - last_iter;
|
||||
last_iter = this_iter;
|
||||
time_till_next_tick = time_till_next_tick.saturating_sub(delta_time);
|
||||
time_till_next_ghost_tick = time_till_next_ghost_tick.saturating_sub(delta_time);
|
||||
|
||||
if let Some(key) = window.getch() {
|
||||
use Input::*;
|
||||
match key {
|
||||
KeyUp | KeyDown | KeyLeft | KeyRight => {
|
||||
queued_dir = Some(match key {
|
||||
KeyUp => Direction::North,
|
||||
KeyDown => Direction::South,
|
||||
KeyRight => Direction::East,
|
||||
KeyLeft => Direction::West,
|
||||
_ => unreachable!() // i believe in the compiler to optimize this out
|
||||
});
|
||||
},
|
||||
Character('q') => break false,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if time_till_next_tick.is_zero() {
|
||||
time_till_next_tick = TICK_DUR;
|
||||
|
||||
pellets.remove(&player_pos);
|
||||
|
||||
if let Some(queued_dir_unwrapped) = queued_dir {
|
||||
let potential_next_pos = player_pos + queued_dir_unwrapped;
|
||||
if !BOARD[potential_next_pos] {
|
||||
player_dir = queued_dir_unwrapped;
|
||||
queued_dir = None;
|
||||
}
|
||||
}
|
||||
|
||||
let prev_pos = player_pos;
|
||||
|
||||
player_pos += player_dir;
|
||||
player_pos.x = player_pos.x.rem_euclid(BOARD_WIDTH as i32);
|
||||
player_pos.y = player_pos.y.rem_euclid(BOARD_HEIGHT as i32);
|
||||
|
||||
if BOARD[player_pos] {
|
||||
player_pos = prev_pos;
|
||||
}
|
||||
|
||||
pellets.remove(&player_pos);
|
||||
|
||||
if pellets.is_empty() {
|
||||
break true;
|
||||
}
|
||||
|
||||
if [blinky.pos, inky.pos, pinky.pos, clyde.pos].contains(&player_pos) {
|
||||
time_till_next_ghost_tick = Duration::MAX; // kinda hacky
|
||||
lost = true;
|
||||
}
|
||||
|
||||
needs_refresh = true;
|
||||
}
|
||||
|
||||
if time_till_next_ghost_tick.is_zero() {
|
||||
time_till_next_ghost_tick = TICK_DUR;
|
||||
|
||||
blinky.set_target_and_move(|_| player_pos, BLINKY_HOME);
|
||||
inky.set_target_and_move(|_| {
|
||||
let intermediate = offset_with_bug(player_pos, player_dir, 2);
|
||||
intermediate - (blinky.pos - intermediate) // flip blinky pos around intermediate
|
||||
}, INKY_HOME);
|
||||
pinky.set_target_and_move(|_| offset_with_bug(player_pos, player_dir, 4), PINKY_HOME);
|
||||
clyde.set_target_and_move(|clyde| if player_pos.dist(clyde.pos) > 8f32 { player_pos } else { CLYDE_HOME }, CLYDE_HOME);
|
||||
|
||||
if [blinky.pos, inky.pos, pinky.pos, clyde.pos].contains(&player_pos) {
|
||||
lost = true;
|
||||
}
|
||||
|
||||
needs_refresh = true;
|
||||
}
|
||||
|
||||
if needs_refresh {
|
||||
// board
|
||||
window.color_set(COLOR_BLUE);
|
||||
window.mvprintw(0, 0, include_str!("board.txt"));
|
||||
|
||||
// pellets
|
||||
window.color_set(COLOR_YELLOW);
|
||||
for pellet in &pellets {
|
||||
window.mvprintw(pellet.y, pellet.x * 2, PELLET);
|
||||
}
|
||||
|
||||
// player
|
||||
window.mvprintw(player_pos.y, player_pos.x * 2, PACMAN);
|
||||
|
||||
// blinky
|
||||
window.color_set(COLOR_RED);
|
||||
window.mvprintw(blinky.pos.y, blinky.pos.x * 2, GHOST);
|
||||
|
||||
// pinky
|
||||
window.color_set(COLOR_MAGENTA);
|
||||
window.mvprintw(pinky.pos.y, pinky.pos.x * 2, GHOST);
|
||||
|
||||
// inky
|
||||
window.color_set(COLOR_CYAN);
|
||||
window.mvprintw(inky.pos.y, inky.pos.x * 2, GHOST);
|
||||
|
||||
// clyde
|
||||
window.color_set(COLOR_GREEN);
|
||||
window.mvprintw(clyde.pos.y, clyde.pos.x * 2, GHOST);
|
||||
|
||||
// debug stuff
|
||||
window.color_set(COLOR_WHITE);
|
||||
window.mvprintw(0, 57, format!("{}, {}", player_pos.x, player_pos.y));
|
||||
needs_refresh = false;
|
||||
}
|
||||
|
||||
if lost {
|
||||
break false;
|
||||
}
|
||||
};
|
||||
|
||||
endwin();
|
||||
|
||||
std::process::exit(if won { 0 } else { 1 });
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
compile_error!("diagnosis: skill issue");
|
Loading…
Reference in a new issue