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