| 1 |
#![allow(dead_code)]
|
| 2 |
|
| 3 |
use std::ops::{Index, IndexMut};
|
| 4 |
use tiled;
|
| 5 |
|
| 6 |
pub mod coord;
|
| 7 |
|
| 8 |
use board::coord::{Coord, Pair};
|
| 9 |
|
| 10 |
const BOARD_WIDTH: u16 = 16;
|
| 11 |
const BOARD_HEIGHT: u16 = 12;
|
| 12 |
const TILE_SIZE: u16 = 24;
|
| 13 |
const HALF_TILE_SIZE: f32 = 24.0;
|
| 14 |
|
| 15 |
#[derive(PartialEq, Eq, Debug)]
|
| 16 |
pub struct Tile {
|
| 17 |
sprite: Coord,
|
| 18 |
}
|
| 19 |
|
| 20 |
impl Tile {
|
| 21 |
fn passable(&self) -> bool {
|
| 22 |
true
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
| 26 |
#[derive(Debug)]
|
| 27 |
pub struct Entity {
|
| 28 |
loc: Pair<f32>,
|
| 29 |
dir: Pair<f32>,
|
| 30 |
}
|
| 31 |
|
| 32 |
impl Entity {
|
| 33 |
fn blank() -> Entity {
|
| 34 |
Entity {
|
| 35 |
loc: Pair::zero(),
|
| 36 |
dir: Pair::zero(),
|
| 37 |
}
|
| 38 |
}
|
| 39 |
|
| 40 |
fn check_collision(&self, b: &Board, dir: Pair<f32>) -> bool {
|
| 41 |
// so, the deal here is that we are looking at the position of
|
| 42 |
// the top-left corner of a sprite, not at the center. In the
|
| 43 |
// optimal case (if the target is in the top-left corner of a
|
| 44 |
// tile) we only need to check passability for a single tile,
|
| 45 |
// but usually we'll need to check two to four:
|
| 46 |
//
|
| 47 |
// +---+---+
|
| 48 |
// | | |
|
| 49 |
// | @---+|
|
| 50 |
// +--| |+
|
| 51 |
// | | ||
|
| 52 |
// | +---+|
|
| 53 |
// +---+---+
|
| 54 |
//
|
| 55 |
// The point @ in the above diagram is what's called `tgt`
|
| 56 |
// here: the tile it's in is called `tl`, the tile to its
|
| 57 |
// right is `tr`, the tile below is `bl`, and the tile below
|
| 58 |
// and to the right is `br`.
|
| 59 |
let tgt = (self.loc + dir).to_discrete(TILE_SIZE as f32);
|
| 60 |
|
| 61 |
let tl = || b.passable(tgt);
|
| 62 |
let tr = || b.passable(tgt + Pair::new(1, 0));
|
| 63 |
let bl = || b.passable(tgt + Pair::new(0, 1));
|
| 64 |
let br = || b.passable(tgt + Pair::new(1, 1));
|
| 65 |
|
| 66 |
// If we're exactly boundary-aligned, we can check fewer of
|
| 67 |
// these, but otherwise, we can only pass into the place in
|
| 68 |
// question iff all of them are free. (They're all closures so
|
| 69 |
// we can avoid doing unnecessary lookups if we don't have
|
| 70 |
// to.)
|
| 71 |
match tgt {
|
| 72 |
Pair { x: 0, y: 0 } => tl(),
|
| 73 |
Pair { x: 0, y: _ } => tl() && bl(),
|
| 74 |
Pair { x: _, y: 0 } => tl() && tr(),
|
| 75 |
Pair { x: _, y: _ } => tl() && tr() && bl() && br(),
|
| 76 |
}
|
| 77 |
}
|
| 78 |
|
| 79 |
fn get_focus<'a>(&self, b: &'a Board) -> &'a Vec<Entity> {
|
| 80 |
b.get_entity(self.game_coords())
|
| 81 |
}
|
| 82 |
|
| 83 |
fn mv(&self, b: &Board) -> Delta {
|
| 84 |
if self.dir == Pair::zero() {
|
| 85 |
return Delta::zero();
|
| 86 |
}
|
| 87 |
let mut delta = Delta { dx: 0.0, dy: 0.0 };
|
| 88 |
|
| 89 |
// figure out which directions we _can_ travel based on
|
| 90 |
// collision data: if we're trying to move NW, but W is
|
| 91 |
// blocked, then we should still be able to move N.
|
| 92 |
if self.check_collision(b, self.dir) {
|
| 93 |
delta.dx = self.dir.x;
|
| 94 |
delta.dy = self.dir.y;
|
| 95 |
} else if self.check_collision(b, Pair { x: 0.0, ..self.dir }) {
|
| 96 |
delta.dy = self.dir.y;
|
| 97 |
} else if self.check_collision(b, Pair { y: 0.0, ..self.dir }) {
|
| 98 |
delta.dx = self.dir.x;
|
| 99 |
} else {
|
| 100 |
// zeroes are fine
|
| 101 |
};
|
| 102 |
|
| 103 |
if self.dir.x.eq(&0.0) && !self.dir.y.eq(&0.0) {
|
| 104 |
let xm = self.loc.x % HALF_TILE_SIZE;
|
| 105 |
if 0.0 < xm && xm < HALF_TILE_SIZE && self.check_collision(b, Pair { x: -1.0, y: 0.0 }) {
|
| 106 |
delta.dx = delta.dx - 1.0;
|
| 107 |
} else if xm >= HALF_TILE_SIZE && self.check_collision(b, Pair { x: 1.0, y: 0.0 }) {
|
| 108 |
delta.dx = delta.dx + 1.0;
|
| 109 |
}
|
| 110 |
} else if !self.dir.x.eq(&0.0) && self.dir.y.eq(&0.0) {
|
| 111 |
let ym = self.loc.y % HALF_TILE_SIZE;
|
| 112 |
if 0.0 < ym && ym < HALF_TILE_SIZE && self.check_collision(b, Pair { x: 0.0, y: -1.0 }) {
|
| 113 |
delta.dy = delta.dy - 1.0;
|
| 114 |
} else if ym >= HALF_TILE_SIZE && self.check_collision(b, Pair { x: 0.0, y: -1.0 }) {
|
| 115 |
delta.dy = delta.dy + 1.0;
|
| 116 |
}
|
| 117 |
}
|
| 118 |
|
| 119 |
delta
|
| 120 |
}
|
| 121 |
|
| 122 |
fn game_coords(&self) -> Coord {
|
| 123 |
self.loc.to_discrete(TILE_SIZE as f32)
|
| 124 |
}
|
| 125 |
}
|
| 126 |
|
| 127 |
#[derive(Debug)]
|
| 128 |
struct Delta {
|
| 129 |
dx: f32,
|
| 130 |
dy: f32,
|
| 131 |
}
|
| 132 |
|
| 133 |
impl Delta {
|
| 134 |
fn zero() -> Delta {
|
| 135 |
Delta { dx: 0.0, dy: 0.0 }
|
| 136 |
}
|
| 137 |
}
|
| 138 |
|
| 139 |
#[derive(Debug)]
|
| 140 |
pub struct Spritesheet {
|
| 141 |
image: ::imagefmt::Image<u8>,
|
| 142 |
}
|
| 143 |
|
| 144 |
#[derive(Debug)]
|
| 145 |
pub struct Grid<T> {
|
| 146 |
width: u16,
|
| 147 |
height: u16,
|
| 148 |
elems: Vec<T>,
|
| 149 |
}
|
| 150 |
|
| 151 |
impl<T> Grid<T> {
|
| 152 |
pub fn new_with<F: Fn(u16, u16) -> T>(width: u16, height: u16, elem: F) -> Grid<T> {
|
| 153 |
let mut elems = Vec::with_capacity((width * height) as usize);
|
| 154 |
for y in 0..height {
|
| 155 |
for x in 0..width {
|
| 156 |
elems.push(elem(x, y))
|
| 157 |
}
|
| 158 |
}
|
| 159 |
Grid { elems, width, height }
|
| 160 |
}
|
| 161 |
}
|
| 162 |
|
| 163 |
impl<T: Clone> Grid<T> {
|
| 164 |
pub fn new(width: u16, height: u16, elem: &T) -> Grid<T> {
|
| 165 |
let mut elems = Vec::with_capacity((width * height) as usize);
|
| 166 |
for _ in 0..height {
|
| 167 |
for _ in 0..width {
|
| 168 |
elems.push(elem.clone())
|
| 169 |
}
|
| 170 |
}
|
| 171 |
Grid { elems, width, height }
|
| 172 |
}
|
| 173 |
}
|
| 174 |
|
| 175 |
impl<T> Index<Coord> for Grid<T> {
|
| 176 |
type Output = T;
|
| 177 |
|
| 178 |
fn index(&self, Pair { x, y }: Coord) -> &T {
|
| 179 |
&self.elems[(y * self.width + x) as usize]
|
| 180 |
}
|
| 181 |
}
|
| 182 |
|
| 183 |
impl<T> IndexMut<Coord> for Grid<T> {
|
| 184 |
fn index_mut(&mut self, Pair { x, y }: Coord) -> &mut T {
|
| 185 |
&mut self.elems[(y * self.width + x) as usize]
|
| 186 |
}
|
| 187 |
}
|
| 188 |
|
| 189 |
#[derive(Debug)]
|
| 190 |
pub struct Board {
|
| 191 |
base: Grid<Tile>,
|
| 192 |
entity: Grid<Vec<Entity>>,
|
| 193 |
}
|
| 194 |
|
| 195 |
|
| 196 |
impl Board {
|
| 197 |
pub fn passable(&self, c: Coord) -> bool {
|
| 198 |
self.base[c].passable()
|
| 199 |
}
|
| 200 |
|
| 201 |
pub fn tiles(&mut self) -> &mut Grid<Tile> {
|
| 202 |
&mut self.base
|
| 203 |
}
|
| 204 |
|
| 205 |
pub fn entities(&mut self) -> &mut Grid<Vec<Entity>> {
|
| 206 |
&mut self.entity
|
| 207 |
}
|
| 208 |
|
| 209 |
pub fn get_entity(&self, c: Coord) -> &Vec<Entity> {
|
| 210 |
&self.entity[c]
|
| 211 |
}
|
| 212 |
|
| 213 |
pub fn from_tiled(map: &tiled::Map) -> Board {
|
| 214 |
fn offset_from_gid(n: u32) -> Option<Coord> {
|
| 215 |
if n < 1 || n > 1024 {
|
| 216 |
return None;
|
| 217 |
}
|
| 218 |
let x = (n - 1) as u16 % 32;
|
| 219 |
let y = (n - 1) as u16 / 32;
|
| 220 |
Some(Pair { x, y })
|
| 221 |
}
|
| 222 |
|
| 223 |
let w = map.width as u16;
|
| 224 |
let h = map.height as u16;
|
| 225 |
let base = Grid::new_with(w, h, |x, y| {
|
| 226 |
let o = map.layers[0].tiles[y as usize][x as usize];
|
| 227 |
Tile { sprite: offset_from_gid(o).unwrap() }
|
| 228 |
});
|
| 229 |
|
| 230 |
let entity = Grid::new_with(w, h, |x, y| {
|
| 231 |
let _o1 = map.layers[1].tiles[y as usize][x as usize];
|
| 232 |
let _o2 = map.layers[1].tiles[y as usize][x as usize];
|
| 233 |
vec![
|
| 234 |
Entity::blank(),
|
| 235 |
Entity::blank(),
|
| 236 |
]
|
| 237 |
});
|
| 238 |
Board { base, entity }
|
| 239 |
}
|
| 240 |
}
|