#![allow(dead_code)]
use std::ops::{Index, IndexMut};
use tiled;
pub mod coord;
use board::coord::{Coord, Pair};
pub const BOARD_WIDTH: u16 = 16;
pub const BOARD_HEIGHT: u16 = 12;
pub const TILE_SIZE: u16 = 24;
pub const HALF_TILE_SIZE: f32 = 24.0;
#[derive(PartialEq, Eq, Debug)]
pub struct Tile {
pub sprite: Coord,
}
impl Tile {
fn passable(&self) -> bool {
true
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct Esse {
pub sprite: Coord,
}
#[derive(Debug)]
pub struct Entity {
loc: Pair<f32>,
dir: Pair<f32>,
}
impl Entity {
fn blank() -> Entity {
Entity {
loc: Pair::zero(),
dir: Pair::zero(),
}
}
fn check_collision(&self, b: &Board, dir: Pair<f32>) -> bool {
// so, the deal here is that we are looking at the position of
// the top-left corner of a sprite, not at the center. In the
// optimal case (if the target is in the top-left corner of a
// tile) we only need to check passability for a single tile,
// but usually we'll need to check two to four:
//
// +---+---+
// | | |
// | @---+|
// +--| |+
// | | ||
// | +---+|
// +---+---+
//
// The point @ in the above diagram is what's called `tgt`
// here: the tile it's in is called `tl`, the tile to its
// right is `tr`, the tile below is `bl`, and the tile below
// and to the right is `br`.
let tgt = (self.loc + dir).to_discrete(TILE_SIZE as f32);
let tl = || b.passable(tgt);
let tr = || b.passable(tgt + Pair::new(1, 0));
let bl = || b.passable(tgt + Pair::new(0, 1));
let br = || b.passable(tgt + Pair::new(1, 1));
// If we're exactly boundary-aligned, we can check fewer of
// these, but otherwise, we can only pass into the place in
// question iff all of them are free. (They're all closures so
// we can avoid doing unnecessary lookups if we don't have
// to.)
match tgt {
Pair { x: 0, y: 0 } => tl(),
Pair { x: 0, y: _ } => tl() && bl(),
Pair { x: _, y: 0 } => tl() && tr(),
Pair { x: _, y: _ } => tl() && tr() && bl() && br(),
}
}
/// Get the set of entities present at the board location that
/// this entity is currently looking at
fn get_focus<'a>(&self, b: &'a Board) -> &'a Vec<Esse> {
b.get_entity(self.game_coords())
}
/// Attempt to move this entity, figuring out whether it can move
/// and how far to move if so. This does collision detection based
/// on the grid and also does some nudging to push the player
/// towards being grid-aligned.
fn mv(&self, b: &Board) -> Delta {
if self.dir == Pair::zero() {
return Delta::zero();
}
let mut delta = Delta { dx: 0.0, dy: 0.0 };
// figure out which directions we _can_ travel based on
// collision data: if we're trying to move NW, but W is
// blocked, then we should still be able to move N.
if self.check_collision(b, self.dir) {
delta.dx = self.dir.x;
delta.dy = self.dir.y;
} else if self.check_collision(b, Pair { x: 0.0, ..self.dir }) {
delta.dy = self.dir.y;
} else if self.check_collision(b, Pair { y: 0.0, ..self.dir }) {
delta.dx = self.dir.x;
}
// if we're not aligned along a half-square, we should try to
// push towards aligning there!
if self.dir.x.eq(&0.0) && !self.dir.y.eq(&0.0) {
let xm = self.loc.x % HALF_TILE_SIZE;
if 0.0 < xm &&
xm < HALF_TILE_SIZE &&
self.check_collision(b, Pair { x: -1.0, y: 0.0 }) {
delta.dx = delta.dx - 1.0;
} else if xm >= HALF_TILE_SIZE &&
self.check_collision(b, Pair { x: 1.0, y: 0.0 }) {
delta.dx = delta.dx + 1.0;
}
} else if !self.dir.x.eq(&0.0) && self.dir.y.eq(&0.0) {
let ym = self.loc.y % HALF_TILE_SIZE;
if 0.0 < ym && ym < HALF_TILE_SIZE && self.check_collision(b, Pair { x: 0.0, y: -1.0 }) {
delta.dy = delta.dy - 1.0;
} else if ym >= HALF_TILE_SIZE && self.check_collision(b, Pair { x: 0.0, y: -1.0 }) {
delta.dy = delta.dy + 1.0;
}
}
delta
}
/// Get this entity's current location in terms of the game
/// coordinates
fn game_coords(&self) -> Coord {
self.loc.to_discrete(TILE_SIZE as f32)
}
}
/// A `Delta` is the amount of calculated movement found for a given
/// entity when it attempts to move.
#[derive(Debug)]
struct Delta {
dx: f32,
dy: f32,
}
impl Delta {
fn zero() -> Delta {
Delta { dx: 0.0, dy: 0.0 }
}
}
#[derive(Debug)]
pub struct Spritesheet {
image: ::imagefmt::Image<u8>,
}
#[derive(Debug)]
pub struct Grid<T> {
width: u16,
height: u16,
elems: Vec<T>,
}
impl<T> Grid<T> {
pub fn new_with<F>(
width: u16,
height: u16,
elem: F,
) -> Grid<T>
where F: Fn(u16, u16) -> T
{
let mut elems = Vec::with_capacity((width * height) as usize);
for y in 0..height {
for x in 0..width {
elems.push(elem(x, y))
}
}
Grid { elems, width, height }
}
}
impl<T: Clone> Grid<T> {
pub fn new(width: u16, height: u16, elem: &T) -> Grid<T> {
let mut elems = Vec::with_capacity((width * height) as usize);
for _ in 0..height {
for _ in 0..width {
elems.push(elem.clone())
}
}
Grid { elems, width, height }
}
}
impl<T> Index<Coord> for Grid<T> {
type Output = T;
fn index(&self, Pair { x, y }: Coord) -> &T {
&self.elems[(y * self.width + x) as usize]
}
}
impl<T> IndexMut<Coord> for Grid<T> {
fn index_mut(&mut self, Pair { x, y }: Coord) -> &mut T {
&mut self.elems[(y * self.width + x) as usize]
}
}
#[derive(Debug)]
pub struct Board {
base: Grid<Tile>,
entity: Grid<Vec<Esse>>,
}
impl Board {
pub fn passable(&self, c: Coord) -> bool {
self.base[c].passable()
}
pub fn tiles(&mut self) -> &mut Grid<Tile> {
&mut self.base
}
pub fn esses(&mut self) -> &mut Grid<Vec<Esse>> {
&mut self.entity
}
pub fn get_entity(&self, c: Coord) -> &Vec<Esse> {
&self.entity[c]
}
/// Take a Tiled-format map and ingest it into our internal map
/// format. (Might be a good idea eventually to use a custom
/// format, but Tiled is great for now.)
pub fn from_tiled(map: &tiled::Map) -> Board {
fn offset_from_gid(n: u32) -> Option<Coord> {
if n < 1 || n > 1024 {
return None;
}
let x = (n - 1) as u16 % 32;
let y = (n - 1) as u16 / 32;
Some(Pair { x, y })
}
let w = map.width as u16;
let h = map.height as u16;
let base = Grid::new_with(w, h, |x, y| {
let o = map.layers[0].tiles[y as usize][x as usize];
Tile { sprite: offset_from_gid(o).unwrap() }
});
let entity = Grid::new_with(w, h, |x, y| {
let o1 = map.layers[1].tiles[y as usize][x as usize];
let o2 = map.layers[2].tiles[y as usize][x as usize];
let mut v = Vec::new();
if let Some(coord) = offset_from_gid(o1) {
v.push(Esse { sprite: coord });
}
if let Some(coord) = offset_from_gid(o2) {
v.push(Esse { sprite: coord });
}
v
});
Board { base, entity }
}
pub fn each_tile<F>(&self, mut f: F)
where F: FnMut(usize, usize, &Tile)
{
for x in 0..self.base.width {
for y in 0..self.base.height {
f(x as usize, y as usize, &self.base[Pair::new(x, y)]);
}
}
}
pub fn each_esse<F>(&self, mut f: F)
where F: FnMut(usize, usize, &Esse)
{
for x in 0..self.base.width {
for y in 0..self.base.height {
for e in self.entity[Pair::new(x, y)].iter() {
f(x as usize, y as usize, &e);
}
}
}
}
}