Initial simple-invaders WIP
- Currently animates two flavors of invader in their usual formation - Lots left to do, but this is a good start
This commit is contained in:
parent
2847a8bd39
commit
3b7638a012
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -672,6 +672,14 @@ dependencies = [
|
|||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pcx"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
|
@ -684,6 +692,7 @@ dependencies = [
|
|||
"env_logger 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pixels-mocks 0.1.0",
|
||||
"simple-invaders 0.1.0",
|
||||
"vk-shader-macros 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wgpu 0.3.0 (git+https://github.com/gfx-rs/wgpu-rs?rev=697393df4793e1a58578209885036114adfb9213)",
|
||||
"winit 0.20.0-alpha3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -906,6 +915,13 @@ dependencies = [
|
|||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-invaders"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pcx 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
|
@ -1410,6 +1426,7 @@ dependencies = [
|
|||
"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518"
|
||||
"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
|
||||
"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
|
||||
"checksum pcx 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1b00b062973776578e7863f8395f86e821760d827384f1ad9371d0893c87481a"
|
||||
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
|
||||
"checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8"
|
||||
|
|
|
@ -18,9 +18,11 @@ wgpu = { git = "https://github.com/gfx-rs/wgpu-rs", rev = "697393df4793e1a585782
|
|||
env_logger = "0.7"
|
||||
log = { version = "0.4", features = ["release_max_level_warn"] }
|
||||
pixels-mocks = { path = "pixels-mocks" }
|
||||
simple-invaders = { path = "simple-invaders" }
|
||||
winit = "0.20.0-alpha3"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"pixels-mocks",
|
||||
"simple-invaders",
|
||||
]
|
||||
|
|
|
@ -1,54 +1,21 @@
|
|||
use pixels::{Error, Pixels, SurfaceTexture};
|
||||
use simple_invaders::{World, SCREEN_HEIGHT, SCREEN_WIDTH};
|
||||
use winit::event;
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
|
||||
fn scale_pixel_ferris(width: u32, height: u32) -> Vec<u8> {
|
||||
let mut px = Vec::new();
|
||||
|
||||
const FERRIS_WIDTH: u32 = 11;
|
||||
const FERRIS_HEIGHT: u32 = 5;
|
||||
#[rustfmt::skip]
|
||||
const FERRIS: [u8; (FERRIS_WIDTH * FERRIS_HEIGHT) as usize] = [
|
||||
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
|
||||
1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1,
|
||||
0, 0, 1, 1, 2, 1, 2, 1, 1, 0, 0,
|
||||
0, 1, 3, 1, 1, 2, 1, 1, 3, 1, 0,
|
||||
0, 0, 1, 3, 0, 0, 0, 3, 1, 0, 0,
|
||||
];
|
||||
|
||||
let scale = width / FERRIS_WIDTH;
|
||||
let top = (height - scale * FERRIS_HEIGHT) / 2;
|
||||
let bottom = height - top - 1;
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let rgba = if y < top || y >= bottom || x / scale >= FERRIS_WIDTH {
|
||||
[0xdd, 0xba, 0xdc, 0xff]
|
||||
} else {
|
||||
let i = x / scale + (y - top) / scale * FERRIS_WIDTH;
|
||||
|
||||
match FERRIS[i as usize] {
|
||||
0 => [0xdd, 0xba, 0xdc, 0xff],
|
||||
1 => [0xf7, 0x4c, 0x00, 0xff],
|
||||
2 => [0x00, 0x00, 0x00, 0xff],
|
||||
3 => [0xa5, 0x2b, 0x00, 0xff],
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
px.extend_from_slice(&rgba);
|
||||
}
|
||||
}
|
||||
|
||||
px
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
env_logger::init();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let (window, surface, width, height) = {
|
||||
let window = winit::window::Window::new(&event_loop).unwrap();
|
||||
let scale = 3.0;
|
||||
let width = SCREEN_WIDTH as f64 * scale;
|
||||
let height = SCREEN_HEIGHT as f64 * scale;
|
||||
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(width, height))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
let surface = wgpu::Surface::create(&window);
|
||||
let size = window.inner_size().to_physical(window.hidpi_factor());
|
||||
|
||||
|
@ -56,9 +23,8 @@ fn main() -> Result<(), Error> {
|
|||
};
|
||||
|
||||
let surface_texture = SurfaceTexture::new(width, height, &surface);
|
||||
let mut fb = Pixels::new(320, 240, surface_texture)?;
|
||||
|
||||
let ferris = scale_pixel_ferris(320, 240);
|
||||
let mut fb = Pixels::new(224, 256, surface_texture)?;
|
||||
let mut invaders = World::new();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
event::Event::WindowEvent { event, .. } => match event {
|
||||
|
@ -72,10 +38,13 @@ fn main() -> Result<(), Error> {
|
|||
..
|
||||
}
|
||||
| event::WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
event::WindowEvent::RedrawRequested => fb.render(&ferris),
|
||||
event::WindowEvent::RedrawRequested => fb.render(invaders.draw()),
|
||||
_ => (),
|
||||
},
|
||||
event::Event::EventsCleared => window.request_redraw(),
|
||||
event::Event::EventsCleared => {
|
||||
invaders.update();
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
|
8
simple-invaders/Cargo.toml
Normal file
8
simple-invaders/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "simple-invaders"
|
||||
version = "0.1.0"
|
||||
authors = ["Jay Oster <jay@kodewerx.org>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
pcx = "0.2"
|
BIN
simple-invaders/src/assets/blipjoy1.pcx
Normal file
BIN
simple-invaders/src/assets/blipjoy1.pcx
Normal file
Binary file not shown.
BIN
simple-invaders/src/assets/blipjoy2.pcx
Normal file
BIN
simple-invaders/src/assets/blipjoy2.pcx
Normal file
Binary file not shown.
BIN
simple-invaders/src/assets/ferris1.pcx
Normal file
BIN
simple-invaders/src/assets/ferris1.pcx
Normal file
Binary file not shown.
BIN
simple-invaders/src/assets/ferris2.pcx
Normal file
BIN
simple-invaders/src/assets/ferris2.pcx
Normal file
Binary file not shown.
BIN
simple-invaders/src/assets/player1.pcx
Normal file
BIN
simple-invaders/src/assets/player1.pcx
Normal file
Binary file not shown.
BIN
simple-invaders/src/assets/player2.pcx
Normal file
BIN
simple-invaders/src/assets/player2.pcx
Normal file
Binary file not shown.
BIN
simple-invaders/src/assets/shield.pcx
Normal file
BIN
simple-invaders/src/assets/shield.pcx
Normal file
Binary file not shown.
512
simple-invaders/src/lib.rs
Normal file
512
simple-invaders/src/lib.rs
Normal file
|
@ -0,0 +1,512 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
use std::rc::Rc;
|
||||
|
||||
type CachedSprite = (usize, usize, Rc<Vec<u8>>);
|
||||
|
||||
// Invader positioning
|
||||
const START: Point = Point::new(24, 60);
|
||||
const GRID: Point = Point::new(16, 16);
|
||||
|
||||
// Screen handling
|
||||
pub const SCREEN_WIDTH: usize = 224;
|
||||
pub const SCREEN_HEIGHT: usize = 256;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct World {
|
||||
invaders: Invaders,
|
||||
lasers: Vec<Laser>,
|
||||
shields: Vec<Shield>,
|
||||
player: Player,
|
||||
cannons: Vec<Cannon>,
|
||||
score: u32,
|
||||
assets: Assets,
|
||||
screen: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A list of assets loaded into memory.
|
||||
#[derive(Debug)]
|
||||
struct Assets {
|
||||
// sounds: TODO
|
||||
sprites: HashMap<String, CachedSprite>,
|
||||
}
|
||||
|
||||
/// A tiny position vector
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
struct Point {
|
||||
x: usize,
|
||||
y: usize,
|
||||
}
|
||||
|
||||
/// A collection of invaders.
|
||||
#[derive(Debug)]
|
||||
struct Invaders {
|
||||
grid: Vec<Vec<Option<Invader>>>,
|
||||
stepper: Stepper,
|
||||
bounds: Bounds,
|
||||
}
|
||||
|
||||
/// Everything you ever wanted to know about Invaders
|
||||
#[derive(Debug)]
|
||||
struct Invader {
|
||||
sprite: SpriteRef,
|
||||
pos: Point,
|
||||
score: u32,
|
||||
}
|
||||
|
||||
/// The stepper will linerly walk through the 2D vector of invaders, updating their state along the
|
||||
/// way.
|
||||
#[derive(Debug)]
|
||||
struct Stepper {
|
||||
row: usize,
|
||||
col: usize,
|
||||
}
|
||||
|
||||
/// Creates a boundary around the live invaders.
|
||||
///
|
||||
/// Used for collision detection and minor optimizations.
|
||||
#[derive(Debug)]
|
||||
struct Bounds {
|
||||
left: usize,
|
||||
right: usize,
|
||||
bottom: usize,
|
||||
}
|
||||
|
||||
/// The player entity.
|
||||
#[derive(Debug)]
|
||||
struct Player {
|
||||
sprite: SpriteRef,
|
||||
pos: Point,
|
||||
}
|
||||
|
||||
/// The shield entity.
|
||||
#[derive(Debug)]
|
||||
struct Shield {
|
||||
// Shield sprite is not referenced because we want to deform it when it gets shot
|
||||
sprite: Sprite,
|
||||
pos: Point,
|
||||
}
|
||||
|
||||
/// The laser entity.
|
||||
#[derive(Debug)]
|
||||
struct Laser {
|
||||
sprite: SpriteRef,
|
||||
pos: Point,
|
||||
}
|
||||
|
||||
/// The cannon entity.
|
||||
#[derive(Debug)]
|
||||
struct Cannon {
|
||||
sprite: SpriteRef,
|
||||
pos: Point,
|
||||
}
|
||||
|
||||
/// Sprites can be drawn and animated.
|
||||
#[derive(Debug)]
|
||||
struct Sprite {
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixels: Vec<u8>,
|
||||
frame: String,
|
||||
}
|
||||
|
||||
/// SpriteRefs can be drawn and animated.
|
||||
///
|
||||
/// They reference their pixel data (instead of owning it).
|
||||
#[derive(Debug)]
|
||||
struct SpriteRef {
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixels: Rc<Vec<u8>>,
|
||||
frame: String,
|
||||
}
|
||||
|
||||
trait Sprites {
|
||||
fn width(&self) -> usize;
|
||||
fn height(&self) -> usize;
|
||||
fn pixels(&self) -> &[u8];
|
||||
fn frame(&self) -> &str;
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new() -> World {
|
||||
// Load assets first
|
||||
let assets = load_assets();
|
||||
|
||||
let invaders = Invaders {
|
||||
grid: make_invader_grid(&assets),
|
||||
stepper: Stepper::default(),
|
||||
bounds: Bounds::default(),
|
||||
};
|
||||
let player = Player {
|
||||
sprite: SpriteRef::new(&assets, "player1"),
|
||||
pos: Point::new(80, 216),
|
||||
};
|
||||
let shields = (0..5)
|
||||
.map(|i| Shield {
|
||||
sprite: Sprite::new(&assets, "shield"),
|
||||
pos: Point::new(i * 45 + 32, 192),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Create a screen with the correct size
|
||||
let mut screen = Vec::new();
|
||||
screen.resize_with(SCREEN_WIDTH * SCREEN_HEIGHT * 4, Default::default);
|
||||
|
||||
World {
|
||||
invaders,
|
||||
lasers: Vec::new(),
|
||||
shields,
|
||||
player,
|
||||
cannons: Vec::new(),
|
||||
score: 0,
|
||||
assets,
|
||||
screen,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
// Update the next invader
|
||||
let row = self.invaders.stepper.row;
|
||||
let col = self.invaders.stepper.col;
|
||||
|
||||
// Animate the invader
|
||||
if let Some(invader) = &mut self.invaders.grid[row][col] {
|
||||
invader.sprite.frame = match invader.sprite.frame.as_ref() {
|
||||
"blipjoy1" => {
|
||||
invader.sprite.pixels = self.assets.sprites.get("blipjoy2").unwrap().2.clone();
|
||||
"blipjoy2".into()
|
||||
}
|
||||
"blipjoy2" => {
|
||||
invader.sprite.pixels = self.assets.sprites.get("blipjoy1").unwrap().2.clone();
|
||||
"blipjoy1".into()
|
||||
}
|
||||
"ferris1" => {
|
||||
invader.sprite.pixels = self.assets.sprites.get("ferris2").unwrap().2.clone();
|
||||
"ferris2".into()
|
||||
}
|
||||
"ferris2" => {
|
||||
invader.sprite.pixels = self.assets.sprites.get("ferris1").unwrap().2.clone();
|
||||
"ferris1".into()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
// Find the next invader
|
||||
self.invaders.stepper.col += 1;
|
||||
if self.invaders.stepper.col >= 11 {
|
||||
self.invaders.stepper.col = 0;
|
||||
if self.invaders.stepper.row == 0 {
|
||||
self.invaders.stepper.row = 4;
|
||||
} else {
|
||||
self.invaders.stepper.row -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&mut self) -> &[u8] {
|
||||
// Clear the screen
|
||||
self.clear();
|
||||
|
||||
// Draw the invaders
|
||||
for row in &self.invaders.grid {
|
||||
for col in row {
|
||||
if let Some(invader) = col {
|
||||
blit(&mut self.screen, &invader.pos, &invader.sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&self.screen
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
for (i, byte) in self.screen.iter_mut().enumerate() {
|
||||
*byte = if i % 4 == 3 { 255 } else { 0 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Point {
|
||||
const fn new(x: usize, y: usize) -> Point {
|
||||
Point { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self::new(self.x + other.x, self.y + other.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: Self) -> Self {
|
||||
Self::new(self.x * other.x, self.y * other.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Stepper {
|
||||
fn default() -> Self {
|
||||
Self { row: 4, col: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Bounds {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
left: START.x,
|
||||
right: START.x + 11 * GRID.x,
|
||||
bottom: START.y + 5 * GRID.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sprite {
|
||||
fn new(assets: &Assets, name: &str) -> Sprite {
|
||||
let cached_sprite = assets.sprites.get(name).unwrap();
|
||||
Sprite {
|
||||
width: cached_sprite.0,
|
||||
height: cached_sprite.1,
|
||||
pixels: cached_sprite.2.to_vec(),
|
||||
frame: name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sprites for Sprite {
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn pixels(&self) -> &[u8] {
|
||||
&self.pixels
|
||||
}
|
||||
|
||||
fn frame(&self) -> &str {
|
||||
&self.frame
|
||||
}
|
||||
}
|
||||
|
||||
impl SpriteRef {
|
||||
fn new(assets: &Assets, name: &str) -> SpriteRef {
|
||||
let cached_sprite = assets.sprites.get(name).unwrap();
|
||||
SpriteRef {
|
||||
width: cached_sprite.0,
|
||||
height: cached_sprite.1,
|
||||
pixels: cached_sprite.2.clone(),
|
||||
frame: name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sprites for SpriteRef {
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn pixels(&self) -> &[u8] {
|
||||
&self.pixels
|
||||
}
|
||||
|
||||
fn frame(&self) -> &str {
|
||||
&self.frame
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all static assets into an `Assets` structure
|
||||
fn load_assets() -> Assets {
|
||||
let mut sprites = HashMap::new();
|
||||
|
||||
sprites.insert(
|
||||
"blipjoy1".into(),
|
||||
load_pcx(include_bytes!("assets/blipjoy1.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"blipjoy2".into(),
|
||||
load_pcx(include_bytes!("assets/blipjoy2.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"ferris1".into(),
|
||||
load_pcx(include_bytes!("assets/ferris1.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"ferris2".into(),
|
||||
load_pcx(include_bytes!("assets/ferris2.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"player1".into(),
|
||||
load_pcx(include_bytes!("assets/player1.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"player2".into(),
|
||||
load_pcx(include_bytes!("assets/player2.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"shield".into(),
|
||||
load_pcx(include_bytes!("assets/shield.pcx")),
|
||||
);
|
||||
// sprites.insert("laser1".into(), load_pcx(include_bytes!("assets/laser1.pcx")));
|
||||
// sprites.insert("laser2".into(), load_pcx(include_bytes!("assets/laser2.pcx")));
|
||||
|
||||
Assets { sprites }
|
||||
}
|
||||
|
||||
/// Convert PCX data to raw pixels
|
||||
fn load_pcx(pcx: &[u8]) -> CachedSprite {
|
||||
let mut reader = pcx::Reader::new(Cursor::new(pcx)).unwrap();
|
||||
let width = reader.width() as usize;
|
||||
let height = reader.height() as usize;
|
||||
let mut result = Vec::new();
|
||||
|
||||
if reader.is_paletted() {
|
||||
// Read the raw pixel data
|
||||
let mut buffer = Vec::new();
|
||||
buffer.resize_with(width * height, Default::default);
|
||||
for y in 0..height {
|
||||
let a = y as usize * width;
|
||||
let b = a + width;
|
||||
reader.next_row_paletted(&mut buffer[a..b]).unwrap();
|
||||
}
|
||||
|
||||
// Read the pallete
|
||||
let mut palette = Vec::new();
|
||||
let palette_length = reader.palette_length().unwrap() as usize;
|
||||
palette.resize_with(palette_length * 3, Default::default);
|
||||
reader.read_palette(&mut palette).unwrap();
|
||||
|
||||
// Copy to result with an alpha component
|
||||
let pixels = buffer
|
||||
.into_iter()
|
||||
.map(|pal| {
|
||||
let i = pal as usize * 3;
|
||||
&palette[i..i + 3]
|
||||
})
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect::<Vec<u8>>();
|
||||
result.extend_from_slice(&pixels);
|
||||
} else {
|
||||
for _ in 0..height {
|
||||
// Read the raw pixel data
|
||||
let mut buffer = Vec::new();
|
||||
buffer.resize_with(width * 3, Default::default);
|
||||
reader.next_row_rgb(&mut buffer[..]).unwrap();
|
||||
|
||||
// Copy to result with an alpha component
|
||||
let pixels = buffer
|
||||
.chunks(3)
|
||||
.map(|rgb| {
|
||||
let mut rgb = rgb.to_vec();
|
||||
rgb.push(255);
|
||||
rgb
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<u8>>();
|
||||
result.extend_from_slice(&pixels);
|
||||
}
|
||||
}
|
||||
|
||||
(width, height, Rc::new(result))
|
||||
}
|
||||
|
||||
/// Create a grid of invaders.
|
||||
fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
|
||||
const BLIPJOY_OFFSET: Point = Point::new(3, 4);
|
||||
const FERRIS_OFFSET: Point = Point::new(3, 5);
|
||||
|
||||
(0..1)
|
||||
.map(|y| {
|
||||
(0..11)
|
||||
.map(|x| {
|
||||
Some(Invader {
|
||||
sprite: SpriteRef::new(assets, "blipjoy1"),
|
||||
pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID,
|
||||
score: 10,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.chain((1..3).map(|y| {
|
||||
(0..11)
|
||||
.map(|x| {
|
||||
Some(Invader {
|
||||
sprite: SpriteRef::new(assets, "ferris1"),
|
||||
pos: START + FERRIS_OFFSET + Point::new(x, y) * GRID,
|
||||
score: 10,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}))
|
||||
.chain((3..5).map(|y| {
|
||||
(0..11)
|
||||
.map(|x| {
|
||||
Some(Invader {
|
||||
// TODO: Need a third invader
|
||||
sprite: SpriteRef::new(assets, "blipjoy1"),
|
||||
pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID,
|
||||
score: 10,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn blit<S>(screen: &mut [u8], dest: &Point, sprite: &S)
|
||||
where
|
||||
S: Sprites,
|
||||
{
|
||||
let pixels = sprite.pixels();
|
||||
let width = sprite.width() * 4;
|
||||
|
||||
let mut s = 0;
|
||||
for y in 0..sprite.height() {
|
||||
let i = dest.x * 4 + dest.y * SCREEN_WIDTH * 4 + y * SCREEN_WIDTH * 4;
|
||||
screen[i..i + width].copy_from_slice(&pixels[s..s + width]);
|
||||
s += width;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn test_pcx() {
|
||||
let pixels = load_pcx(include_bytes!("assets/blipjoy1.pcx"));
|
||||
let expected = vec![
|
||||
0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0,
|
||||
255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0,
|
||||
0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
||||
255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
|
||||
0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
||||
0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
||||
0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255,
|
||||
255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255,
|
||||
255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255,
|
||||
255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
||||
0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0,
|
||||
255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
||||
];
|
||||
|
||||
assert_eq!(pixels.0, 10, "Width differs");
|
||||
assert_eq!(pixels.1, 8, "Height differs");
|
||||
assert_eq!(Rc::try_unwrap(pixels.2).unwrap(), expected, "Pixels differ");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue