gdritter repos chenoska / master src / board / mod.rs
master

Tree @master (Download .tar.gz)

mod.rs @master

8bda3a4
 
 
 
 
 
 
 
 
03e569d
 
 
 
8bda3a4
 
 
03e569d
8bda3a4
 
 
 
 
 
 
 
2068804
 
 
 
 
8bda3a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03e569d
 
2068804
8bda3a4
 
 
03e569d
 
 
 
8bda3a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03e569d
8bda3a4
03e569d
 
8bda3a4
 
03e569d
 
 
8bda3a4
03e569d
 
8bda3a4
 
 
 
 
 
 
 
 
 
 
 
 
 
03e569d
 
8bda3a4
 
 
 
 
03e569d
 
8bda3a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03e569d
 
 
 
 
 
 
8bda3a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2068804
8bda3a4
 
 
 
 
 
 
 
 
 
 
 
2068804
8bda3a4
 
 
2068804
8bda3a4
 
 
03e569d
 
 
8bda3a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2068804
 
 
 
 
 
 
 
 
 
8bda3a4
 
 
03e569d
 
 
 
 
 
 
 
 
 
2068804
 
 
 
 
 
 
 
 
 
 
 
8bda3a4
#![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);
                }
            }
        }
    }
}