mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 15:15:48 -06:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 023697dcd7 | |||
| 87ee12543e | |||
| b308bc0ef7 | |||
| 9d5ca54234 |
21
.github/workflows/build.yaml
vendored
21
.github/workflows/build.yaml
vendored
@@ -104,11 +104,10 @@ jobs:
|
|||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Setup Bun
|
||||||
uses: pnpm/action-setup@v3
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
version: 8
|
bun-version: latest
|
||||||
run_install: true
|
|
||||||
|
|
||||||
- name: Build with Emscripten
|
- name: Build with Emscripten
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -121,7 +120,7 @@ jobs:
|
|||||||
echo "Build attempt $attempt of $MAX_RETRIES"
|
echo "Build attempt $attempt of $MAX_RETRIES"
|
||||||
|
|
||||||
# Capture output and check for specific error while preserving real-time output
|
# Capture output and check for specific error while preserving real-time output
|
||||||
if cargo build --target=wasm32-unknown-emscripten --release 2>&1 | tee /tmp/build_output.log; then
|
if bun run web.build.ts 2>&1 | tee /tmp/build_output.log; then
|
||||||
echo "Build successful on attempt $attempt"
|
echo "Build successful on attempt $attempt"
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
@@ -148,18 +147,6 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Assemble
|
|
||||||
run: |
|
|
||||||
echo "Generating CSS"
|
|
||||||
pnpx postcss-cli ./assets/site/styles.scss -o ./assets/site/build.css
|
|
||||||
|
|
||||||
echo "Copying WASM files"
|
|
||||||
|
|
||||||
mkdir -p dist
|
|
||||||
cp assets/site/{build.css,favicon.ico,index.html} dist
|
|
||||||
output_folder="target/wasm32-unknown-emscripten/release"
|
|
||||||
cp $output_folder/pacman.{wasm,js} $output_folder/deps/pacman.data dist
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v3
|
||||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ dist/
|
|||||||
emsdk/
|
emsdk/
|
||||||
.idea
|
.idea
|
||||||
rust-sdl2-emscripten/
|
rust-sdl2-emscripten/
|
||||||
|
assets/site/build.css
|
||||||
|
|||||||
@@ -2,12 +2,25 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Pac-Man Arcade</title>
|
<title>Pac-Man in Rust</title>
|
||||||
<link rel="stylesheet" href="build.css" />
|
<link rel="stylesheet" href="build.css" />
|
||||||
<style>
|
<style>
|
||||||
|
/* Minimal fallback to prevent white flash and canvas pop-in before CSS loads */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background: #000;
|
||||||
|
color: #facc15;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#canvas {
|
||||||
|
display: block;
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-black text-yellow-400 text-center">
|
<body class="bg-black text-yellow-400 text-center min-h-screen">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/Xevion/Pac-Man"
|
href="https://github.com/Xevion/Pac-Man"
|
||||||
class="absolute top-0 right-0"
|
class="absolute top-0 right-0"
|
||||||
@@ -17,7 +30,7 @@
|
|||||||
width="80"
|
width="80"
|
||||||
height="80"
|
height="80"
|
||||||
viewBox="0 0 250 250"
|
viewBox="0 0 250 250"
|
||||||
class="fill-yellow-400 text-black"
|
class="fill-yellow-400 text-white"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
||||||
@@ -31,33 +44,54 @@
|
|||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-4xl mt-10 scaled-text">Pac-Man Arcade</h1>
|
|
||||||
<p class="text-lg mt-5 scaled-text">
|
<div class="min-h-screen flex flex-col">
|
||||||
Welcome to the Pac-Man Arcade! Use the controls below to play.
|
<header class="pt-10">
|
||||||
</p>
|
<h1 class="text-4xl arcade-title scaled-text">Pac-Man in Rust</h1>
|
||||||
<canvas
|
</header>
|
||||||
id="canvas"
|
|
||||||
class="block mx-auto mt-5"
|
<main class="flex-1 flex items-center justify-center px-4">
|
||||||
width="800"
|
<div class="w-full max-w-5xl">
|
||||||
height="600"
|
<canvas
|
||||||
></canvas>
|
id="canvas"
|
||||||
<div class="mt-10">
|
class="block mx-auto bg-black w-full max-w-[90vw] h-auto mt-5 rounded-xl shadow-[inset_0_0_0_2px_rgba(255,255,255,0.12),0_10px_30px_rgba(0,0,0,0.8)]"
|
||||||
<span
|
></canvas>
|
||||||
class="inline-block mx-2 px-4 py-2 bg-yellow-400 text-black rounded scaled-text"
|
|
||||||
>← ↑ → ↓ Move</span
|
<div
|
||||||
>
|
class="mt-8 flex flex-wrap gap-3 justify-center items-center text-sm"
|
||||||
<span
|
>
|
||||||
class="inline-block mx-2 px-4 py-2 bg-yellow-400 text-black rounded scaled-text"
|
<span class="code">← ↑ → ↓</span>
|
||||||
>Space Change Sprite</span
|
<span class="opacity-70">Move</span>
|
||||||
>
|
|
||||||
<span
|
<span class="mx-2 opacity-30">|</span>
|
||||||
class="inline-block mx-2 px-4 py-2 bg-yellow-400 text-black rounded scaled-text"
|
|
||||||
>Shift + ↑↓ Change Volume</span
|
<span class="code">Space</span>
|
||||||
>
|
<span class="opacity-70">Toggle Debug</span>
|
||||||
|
|
||||||
|
<span class="mx-2 opacity-30">|</span>
|
||||||
|
|
||||||
|
<span class="code">P</span>
|
||||||
|
<span class="opacity-70">Pause / Unpause</span>
|
||||||
|
|
||||||
|
<span class="mx-2 opacity-30">|</span>
|
||||||
|
|
||||||
|
<span class="code">M</span>
|
||||||
|
<span class="opacity-70">Mute / Unmute</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
const canvas = document.getElementById("canvas");
|
||||||
var Module = {
|
var Module = {
|
||||||
canvas: document.getElementById("canvas"),
|
canvas: canvas,
|
||||||
|
preRun: [
|
||||||
|
() => {
|
||||||
|
[...canvas.classList]
|
||||||
|
.filter((className) => className.includes("shadow-"))
|
||||||
|
.forEach((className) => canvas.classList.remove(className));
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="pacman.js"></script>
|
<script type="text/javascript" src="pacman.js"></script>
|
||||||
|
|||||||
28
assets/site/styles.css
Normal file
28
assets/site/styles.css
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "TerminalVector";
|
||||||
|
src: url("TerminalVector.ttf");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Key badge styling */
|
||||||
|
.code {
|
||||||
|
@apply px-3 py-1 rounded-md font-mono text-[0.9em] lowercase inline-block align-middle;
|
||||||
|
background: rgba(250, 204, 21, 0.08); /* yellow-400 at low opacity */
|
||||||
|
border: 1px solid rgba(250, 204, 21, 0.25);
|
||||||
|
color: #fde68a; /* lighter yellow for readability */
|
||||||
|
font-family: "TerminalVector", ui-monospace, Consolas, "Courier New",
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title styling */
|
||||||
|
.arcade-title {
|
||||||
|
font-family: "TerminalVector", ui-monospace, Consolas, "Courier New",
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-shadow: 0 0 18px rgba(250, 204, 21, 0.15),
|
||||||
|
0 0 2px rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Liberation Mono";
|
|
||||||
src:
|
|
||||||
url("LiberationMono.woff2") format("woff2"),
|
|
||||||
url("LiberationMono.woff") format("woff");
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
@apply w-full h-[65vh] min-h-[200px] block mx-auto bg-black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code {
|
|
||||||
@apply px-1 rounded font-mono bg-zinc-900 border border-zinc-700 lowercase;
|
|
||||||
}
|
|
||||||
153
src/audio.rs
153
src/audio.rs
@@ -162,156 +162,3 @@ impl Audio {
|
|||||||
self.disabled
|
self.disabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use std::sync::Once;
|
|
||||||
|
|
||||||
static INIT: Once = Once::new();
|
|
||||||
|
|
||||||
fn init_sdl() -> Result<(), String> {
|
|
||||||
INIT.call_once(|| {
|
|
||||||
if let Err(e) = sdl2::init() {
|
|
||||||
eprintln!("Failed to initialize SDL2: {}", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sound_assets_array() {
|
|
||||||
assert_eq!(SOUND_ASSETS.len(), 4);
|
|
||||||
assert_eq!(SOUND_ASSETS[0], Asset::Wav1);
|
|
||||||
assert_eq!(SOUND_ASSETS[1], Asset::Wav2);
|
|
||||||
assert_eq!(SOUND_ASSETS[2], Asset::Wav3);
|
|
||||||
assert_eq!(SOUND_ASSETS[3], Asset::Wav4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_audio_asset_paths() {
|
|
||||||
// Test that all sound assets have valid paths
|
|
||||||
for asset in SOUND_ASSETS.iter() {
|
|
||||||
let path = asset.path();
|
|
||||||
assert!(!path.is_empty());
|
|
||||||
assert!(path.contains("sound/waka/"));
|
|
||||||
assert!(path.ends_with(".ogg"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only run SDL2-dependent tests if SDL2 initialization succeeds
|
|
||||||
#[test]
|
|
||||||
fn test_audio_basic_functionality() {
|
|
||||||
if let Err(_) = init_sdl() {
|
|
||||||
eprintln!("Skipping SDL2-dependent tests due to initialization failure");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test basic audio creation
|
|
||||||
let audio = Audio::new();
|
|
||||||
assert_eq!(audio.is_muted(), false);
|
|
||||||
assert_eq!(audio.next_sound_index, 0);
|
|
||||||
|
|
||||||
// Audio might be disabled if initialization failed
|
|
||||||
if !audio.is_disabled() {
|
|
||||||
assert_eq!(audio.sounds.len(), 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_audio_mute_functionality() {
|
|
||||||
if let Err(_) = init_sdl() {
|
|
||||||
eprintln!("Skipping SDL2-dependent tests due to initialization failure");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut audio = Audio::new();
|
|
||||||
|
|
||||||
// Test mute/unmute
|
|
||||||
assert_eq!(audio.is_muted(), false);
|
|
||||||
audio.set_mute(true);
|
|
||||||
assert_eq!(audio.is_muted(), true);
|
|
||||||
audio.set_mute(false);
|
|
||||||
assert_eq!(audio.is_muted(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_audio_sound_rotation() {
|
|
||||||
if let Err(_) = init_sdl() {
|
|
||||||
eprintln!("Skipping SDL2-dependent tests due to initialization failure");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut audio = Audio::new();
|
|
||||||
|
|
||||||
// Skip test if audio is disabled
|
|
||||||
if audio.is_disabled() {
|
|
||||||
eprintln!("Skipping sound rotation test due to disabled audio");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let initial_index = audio.next_sound_index;
|
|
||||||
|
|
||||||
// Test sound rotation
|
|
||||||
for i in 0..4 {
|
|
||||||
audio.eat();
|
|
||||||
assert_eq!(audio.next_sound_index, (initial_index + i + 1) % 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(audio.next_sound_index, initial_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_audio_sound_index_bounds() {
|
|
||||||
if let Err(_) = init_sdl() {
|
|
||||||
eprintln!("Skipping SDL2-dependent tests due to initialization failure");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let audio = Audio::new();
|
|
||||||
|
|
||||||
// Skip test if audio is disabled
|
|
||||||
if audio.is_disabled() {
|
|
||||||
eprintln!("Skipping sound index bounds test due to disabled audio");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(audio.next_sound_index < audio.sounds.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_audio_default_impl() {
|
|
||||||
if let Err(_) = init_sdl() {
|
|
||||||
eprintln!("Skipping SDL2-dependent tests due to initialization failure");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let audio = Audio::default();
|
|
||||||
assert_eq!(audio.is_muted(), false);
|
|
||||||
assert_eq!(audio.next_sound_index, 0);
|
|
||||||
|
|
||||||
// Audio might be disabled if initialization failed
|
|
||||||
if !audio.is_disabled() {
|
|
||||||
assert_eq!(audio.sounds.len(), 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_audio_disabled_state() {
|
|
||||||
if let Err(_) = init_sdl() {
|
|
||||||
eprintln!("Skipping SDL2-dependent tests due to initialization failure");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that disabled audio doesn't crash when calling functions
|
|
||||||
let mut audio = Audio::new();
|
|
||||||
|
|
||||||
// These should not panic even if audio is disabled
|
|
||||||
audio.eat();
|
|
||||||
audio.set_mute(true);
|
|
||||||
audio.set_mute(false);
|
|
||||||
|
|
||||||
// Test that we can check the disabled state
|
|
||||||
let _is_disabled = audio.is_disabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
176
src/constants.rs
176
src/constants.rs
@@ -75,179 +75,3 @@ pub const RAW_BOARD: [&str; BOARD_CELL_SIZE.y as usize] = [
|
|||||||
"#..........................#",
|
"#..........................#",
|
||||||
"############################",
|
"############################",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_loop_time() {
|
|
||||||
// 60 FPS = 16.67ms per frame
|
|
||||||
let expected_nanos = (1_000_000_000.0 / 60.0) as u64;
|
|
||||||
assert_eq!(LOOP_TIME.as_nanos() as u64, expected_nanos);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cell_size() {
|
|
||||||
assert_eq!(CELL_SIZE, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_board_cell_size() {
|
|
||||||
assert_eq!(BOARD_CELL_SIZE.x, 28);
|
|
||||||
assert_eq!(BOARD_CELL_SIZE.y, 31);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_scale() {
|
|
||||||
assert_eq!(SCALE, 2.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_board_cell_offset() {
|
|
||||||
assert_eq!(BOARD_CELL_OFFSET.x, 0);
|
|
||||||
assert_eq!(BOARD_CELL_OFFSET.y, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_board_pixel_offset() {
|
|
||||||
let expected = UVec2::new(0 * CELL_SIZE, 3 * CELL_SIZE);
|
|
||||||
assert_eq!(BOARD_PIXEL_OFFSET, expected);
|
|
||||||
assert_eq!(BOARD_PIXEL_OFFSET.x, 0);
|
|
||||||
assert_eq!(BOARD_PIXEL_OFFSET.y, 24); // 3 * 8
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_board_pixel_size() {
|
|
||||||
let expected = UVec2::new(28 * CELL_SIZE, 31 * CELL_SIZE);
|
|
||||||
assert_eq!(BOARD_PIXEL_SIZE, expected);
|
|
||||||
assert_eq!(BOARD_PIXEL_SIZE.x, 224); // 28 * 8
|
|
||||||
assert_eq!(BOARD_PIXEL_SIZE.y, 248); // 31 * 8
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_canvas_size() {
|
|
||||||
let expected = UVec2::new((28 + 0) * CELL_SIZE, (31 + 3) * CELL_SIZE);
|
|
||||||
assert_eq!(CANVAS_SIZE, expected);
|
|
||||||
assert_eq!(CANVAS_SIZE.x, 224); // (28 + 0) * 8
|
|
||||||
assert_eq!(CANVAS_SIZE.y, 272); // (31 + 3) * 8
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_tile_variants() {
|
|
||||||
assert_ne!(MapTile::Empty, MapTile::Wall);
|
|
||||||
assert_ne!(MapTile::Pellet, MapTile::PowerPellet);
|
|
||||||
assert_ne!(MapTile::Tunnel, MapTile::Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_tile_clone() {
|
|
||||||
let original = MapTile::Wall;
|
|
||||||
let cloned = original;
|
|
||||||
assert_eq!(original, cloned);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw_board_dimensions() {
|
|
||||||
assert_eq!(RAW_BOARD.len(), BOARD_CELL_SIZE.y as usize);
|
|
||||||
assert_eq!(RAW_BOARD.len(), 31);
|
|
||||||
|
|
||||||
for row in RAW_BOARD.iter() {
|
|
||||||
assert_eq!(row.len(), BOARD_CELL_SIZE.x as usize);
|
|
||||||
assert_eq!(row.len(), 28);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw_board_boundaries() {
|
|
||||||
// First row should be all walls
|
|
||||||
assert!(RAW_BOARD[0].chars().all(|c| c == '#'));
|
|
||||||
|
|
||||||
// Last row should be all walls
|
|
||||||
let last_row = RAW_BOARD[RAW_BOARD.len() - 1];
|
|
||||||
assert!(last_row.chars().all(|c| c == '#'));
|
|
||||||
|
|
||||||
// First and last character of each row should be walls (except tunnel rows and rows with spaces)
|
|
||||||
for (i, row) in RAW_BOARD.iter().enumerate() {
|
|
||||||
if i != 14 && !row.starts_with(' ') {
|
|
||||||
// Skip tunnel row and rows that start with spaces
|
|
||||||
assert_eq!(row.chars().next().unwrap(), '#');
|
|
||||||
assert_eq!(row.chars().last().unwrap(), '#');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw_board_tunnel_row() {
|
|
||||||
// Row 14 should have tunnel characters 'T' at the edges
|
|
||||||
let tunnel_row = RAW_BOARD[14];
|
|
||||||
assert_eq!(tunnel_row.chars().next().unwrap(), 'T');
|
|
||||||
assert_eq!(tunnel_row.chars().last().unwrap(), 'T');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw_board_power_pellets() {
|
|
||||||
// Power pellets are represented by 'o'
|
|
||||||
let mut power_pellet_count = 0;
|
|
||||||
for row in RAW_BOARD.iter() {
|
|
||||||
power_pellet_count += row.chars().filter(|&c| c == 'o').count();
|
|
||||||
}
|
|
||||||
assert_eq!(power_pellet_count, 4); // Should have exactly 4 power pellets
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw_board_starting_position() {
|
|
||||||
// Should have a starting position 'X' for Pac-Man
|
|
||||||
let mut found_starting_position = false;
|
|
||||||
for row in RAW_BOARD.iter() {
|
|
||||||
if row.contains('X') {
|
|
||||||
found_starting_position = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(found_starting_position);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw_board_ghost_house() {
|
|
||||||
// The ghost house area should be present (the == characters)
|
|
||||||
let mut found_ghost_house = false;
|
|
||||||
for row in RAW_BOARD.iter() {
|
|
||||||
if row.contains("==") {
|
|
||||||
found_ghost_house = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(found_ghost_house);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw_board_symmetry() {
|
|
||||||
// The board should be roughly symmetrical
|
|
||||||
let mid_point = RAW_BOARD[0].len() / 2;
|
|
||||||
|
|
||||||
for row in RAW_BOARD.iter() {
|
|
||||||
let left_half = &row[..mid_point];
|
|
||||||
let right_half = &row[mid_point..];
|
|
||||||
|
|
||||||
// Check that the halves are symmetrical (accounting for the center column)
|
|
||||||
assert_eq!(left_half.len(), right_half.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_constants_consistency() {
|
|
||||||
// Verify that derived constants are calculated correctly
|
|
||||||
let calculated_pixel_offset = UVec2::new(BOARD_CELL_OFFSET.x * CELL_SIZE, BOARD_CELL_OFFSET.y * CELL_SIZE);
|
|
||||||
assert_eq!(BOARD_PIXEL_OFFSET, calculated_pixel_offset);
|
|
||||||
|
|
||||||
let calculated_pixel_size = UVec2::new(BOARD_CELL_SIZE.x * CELL_SIZE, BOARD_CELL_SIZE.y * CELL_SIZE);
|
|
||||||
assert_eq!(BOARD_PIXEL_SIZE, calculated_pixel_size);
|
|
||||||
|
|
||||||
let calculated_canvas_size = UVec2::new(
|
|
||||||
(BOARD_CELL_SIZE.x + BOARD_CELL_OFFSET.x) * CELL_SIZE,
|
|
||||||
(BOARD_CELL_SIZE.y + BOARD_CELL_OFFSET.y) * CELL_SIZE,
|
|
||||||
);
|
|
||||||
assert_eq!(CANVAS_SIZE, calculated_canvas_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -35,67 +35,3 @@ impl From<Direction> for IVec2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const DIRECTIONS: [Direction; 4] = [Direction::Up, Direction::Down, Direction::Left, Direction::Right];
|
pub const DIRECTIONS: [Direction; 4] = [Direction::Up, Direction::Down, Direction::Left, Direction::Right];
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_direction_opposite() {
|
|
||||||
assert_eq!(Direction::Up.opposite(), Direction::Down);
|
|
||||||
assert_eq!(Direction::Down.opposite(), Direction::Up);
|
|
||||||
assert_eq!(Direction::Left.opposite(), Direction::Right);
|
|
||||||
assert_eq!(Direction::Right.opposite(), Direction::Left);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_direction_as_ivec2() {
|
|
||||||
assert_eq!(Direction::Up.as_ivec2(), -IVec2::Y);
|
|
||||||
assert_eq!(Direction::Down.as_ivec2(), IVec2::Y);
|
|
||||||
assert_eq!(Direction::Left.as_ivec2(), -IVec2::X);
|
|
||||||
assert_eq!(Direction::Right.as_ivec2(), IVec2::X);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_direction_from_ivec2() {
|
|
||||||
assert_eq!(IVec2::from(Direction::Up), -IVec2::Y);
|
|
||||||
assert_eq!(IVec2::from(Direction::Down), IVec2::Y);
|
|
||||||
assert_eq!(IVec2::from(Direction::Left), -IVec2::X);
|
|
||||||
assert_eq!(IVec2::from(Direction::Right), IVec2::X);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_directions_constant() {
|
|
||||||
assert_eq!(DIRECTIONS.len(), 4);
|
|
||||||
assert!(DIRECTIONS.contains(&Direction::Up));
|
|
||||||
assert!(DIRECTIONS.contains(&Direction::Down));
|
|
||||||
assert!(DIRECTIONS.contains(&Direction::Left));
|
|
||||||
assert!(DIRECTIONS.contains(&Direction::Right));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_direction_equality() {
|
|
||||||
assert_eq!(Direction::Up, Direction::Up);
|
|
||||||
assert_ne!(Direction::Up, Direction::Down);
|
|
||||||
assert_ne!(Direction::Left, Direction::Right);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_direction_clone() {
|
|
||||||
let dir = Direction::Up;
|
|
||||||
let cloned = dir;
|
|
||||||
assert_eq!(dir, cloned);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_direction_hash() {
|
|
||||||
use std::collections::HashMap;
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
map.insert(Direction::Up, "up");
|
|
||||||
map.insert(Direction::Down, "down");
|
|
||||||
|
|
||||||
assert_eq!(map.get(&Direction::Up), Some(&"up"));
|
|
||||||
assert_eq!(map.get(&Direction::Down), Some(&"down"));
|
|
||||||
assert_eq!(map.get(&Direction::Left), None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -441,366 +441,3 @@ impl Traverser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::entity::direction::Direction;
|
|
||||||
|
|
||||||
fn create_test_graph() -> Graph {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
let node2 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(16.0, 0.0),
|
|
||||||
});
|
|
||||||
let node3 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 16.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
|
||||||
graph.connect(node1, node3, false, None, Direction::Down).unwrap();
|
|
||||||
|
|
||||||
graph
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_graph_new() {
|
|
||||||
let graph = Graph::new();
|
|
||||||
assert_eq!(graph.node_count(), 0);
|
|
||||||
assert!(graph.adjacency_list.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_graph_add_node() {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node = Node {
|
|
||||||
position: glam::Vec2::new(10.0, 20.0),
|
|
||||||
};
|
|
||||||
let id = graph.add_node(node);
|
|
||||||
|
|
||||||
assert_eq!(id, 0);
|
|
||||||
assert_eq!(graph.node_count(), 1);
|
|
||||||
assert_eq!(graph.adjacency_list.len(), 1);
|
|
||||||
|
|
||||||
let retrieved_node = graph.get_node(id).unwrap();
|
|
||||||
assert_eq!(retrieved_node.position, glam::Vec2::new(10.0, 20.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_graph_node_count() {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
assert_eq!(graph.node_count(), 0);
|
|
||||||
|
|
||||||
graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
assert_eq!(graph.node_count(), 1);
|
|
||||||
|
|
||||||
graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(1.0, 1.0),
|
|
||||||
});
|
|
||||||
assert_eq!(graph.node_count(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_graph_get_node() {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node = Node {
|
|
||||||
position: glam::Vec2::new(5.0, 10.0),
|
|
||||||
};
|
|
||||||
let id = graph.add_node(node);
|
|
||||||
|
|
||||||
let retrieved = graph.get_node(id).unwrap();
|
|
||||||
assert_eq!(retrieved.position, glam::Vec2::new(5.0, 10.0));
|
|
||||||
|
|
||||||
assert!(graph.get_node(999).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_graph_connect() {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
let node2 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(16.0, 0.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = graph.connect(node1, node2, false, None, Direction::Right);
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
// Check that edges were added in both directions
|
|
||||||
let edge1 = graph.find_edge_in_direction(node1, Direction::Right);
|
|
||||||
let edge2 = graph.find_edge_in_direction(node2, Direction::Left);
|
|
||||||
|
|
||||||
assert!(edge1.is_some());
|
|
||||||
assert!(edge2.is_some());
|
|
||||||
assert_eq!(edge1.unwrap().target, node2);
|
|
||||||
assert_eq!(edge2.unwrap().target, node1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_graph_connect_invalid_nodes() {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Try to connect to non-existent node
|
|
||||||
let result = graph.connect(node1, 999, false, None, Direction::Right);
|
|
||||||
assert!(result.is_err());
|
|
||||||
|
|
||||||
// Try to connect from non-existent node
|
|
||||||
let result = graph.connect(999, node1, false, None, Direction::Right);
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_graph_find_edge() {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
let node2 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(16.0, 0.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
|
||||||
|
|
||||||
let edge = graph.find_edge(node1, node2);
|
|
||||||
assert!(edge.is_some());
|
|
||||||
assert_eq!(edge.unwrap().target, node2);
|
|
||||||
|
|
||||||
// Test non-existent edge
|
|
||||||
assert!(graph.find_edge(node1, 999).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_graph_find_edge_in_direction() {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
let node2 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(16.0, 0.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
|
||||||
|
|
||||||
let edge = graph.find_edge_in_direction(node1, Direction::Right);
|
|
||||||
assert!(edge.is_some());
|
|
||||||
assert_eq!(edge.unwrap().target, node2);
|
|
||||||
|
|
||||||
// Test non-existent direction
|
|
||||||
assert!(graph.find_edge_in_direction(node1, Direction::Up).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_intersection_edges() {
|
|
||||||
let mut intersection = Intersection::default();
|
|
||||||
intersection.set(
|
|
||||||
Direction::Up,
|
|
||||||
Edge {
|
|
||||||
target: 1,
|
|
||||||
distance: 10.0,
|
|
||||||
direction: Direction::Up,
|
|
||||||
permissions: EdgePermissions::All,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
intersection.set(
|
|
||||||
Direction::Right,
|
|
||||||
Edge {
|
|
||||||
target: 2,
|
|
||||||
distance: 15.0,
|
|
||||||
direction: Direction::Right,
|
|
||||||
permissions: EdgePermissions::All,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let edges: Vec<_> = intersection.edges().collect();
|
|
||||||
assert_eq!(edges.len(), 2);
|
|
||||||
|
|
||||||
let up_edge = edges.iter().find(|e| e.direction == Direction::Up).unwrap();
|
|
||||||
let right_edge = edges.iter().find(|e| e.direction == Direction::Right).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(up_edge.target, 1);
|
|
||||||
assert_eq!(up_edge.distance, 10.0);
|
|
||||||
assert_eq!(right_edge.target, 2);
|
|
||||||
assert_eq!(right_edge.distance, 15.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_intersection_get() {
|
|
||||||
let mut intersection = Intersection::default();
|
|
||||||
let edge = Edge {
|
|
||||||
target: 1,
|
|
||||||
distance: 10.0,
|
|
||||||
direction: Direction::Up,
|
|
||||||
permissions: EdgePermissions::All,
|
|
||||||
};
|
|
||||||
intersection.set(Direction::Up, edge);
|
|
||||||
|
|
||||||
let retrieved = intersection.get(Direction::Up);
|
|
||||||
assert!(retrieved.is_some());
|
|
||||||
assert_eq!(retrieved.unwrap().target, 1);
|
|
||||||
|
|
||||||
assert!(intersection.get(Direction::Down).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_intersection_set() {
|
|
||||||
let mut intersection = Intersection::default();
|
|
||||||
let edge = Edge {
|
|
||||||
target: 1,
|
|
||||||
distance: 10.0,
|
|
||||||
direction: Direction::Left,
|
|
||||||
permissions: EdgePermissions::All,
|
|
||||||
};
|
|
||||||
|
|
||||||
intersection.set(Direction::Left, edge);
|
|
||||||
let retrieved = intersection.get(Direction::Left);
|
|
||||||
assert!(retrieved.is_some());
|
|
||||||
assert_eq!(retrieved.unwrap().target, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_position_is_at_node() {
|
|
||||||
let pos = Position::AtNode(5);
|
|
||||||
assert!(pos.is_at_node());
|
|
||||||
|
|
||||||
let pos = Position::BetweenNodes {
|
|
||||||
from: 1,
|
|
||||||
to: 2,
|
|
||||||
traversed: 5.0,
|
|
||||||
};
|
|
||||||
assert!(!pos.is_at_node());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_position_from_node_id() {
|
|
||||||
let pos = Position::AtNode(5);
|
|
||||||
assert_eq!(pos.from_node_id(), 5);
|
|
||||||
|
|
||||||
let pos = Position::BetweenNodes {
|
|
||||||
from: 1,
|
|
||||||
to: 2,
|
|
||||||
traversed: 5.0,
|
|
||||||
};
|
|
||||||
assert_eq!(pos.from_node_id(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_position_to_node_id() {
|
|
||||||
let pos = Position::AtNode(5);
|
|
||||||
assert_eq!(pos.to_node_id(), None);
|
|
||||||
|
|
||||||
let pos = Position::BetweenNodes {
|
|
||||||
from: 1,
|
|
||||||
to: 2,
|
|
||||||
traversed: 5.0,
|
|
||||||
};
|
|
||||||
assert_eq!(pos.to_node_id(), Some(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_position_is_stopped() {
|
|
||||||
let pos = Position::AtNode(5);
|
|
||||||
assert!(pos.is_stopped());
|
|
||||||
|
|
||||||
let pos = Position::BetweenNodes {
|
|
||||||
from: 1,
|
|
||||||
to: 2,
|
|
||||||
traversed: 5.0,
|
|
||||||
};
|
|
||||||
assert!(!pos.is_stopped());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_traverser_new() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let traverser = Traverser::new(&graph, 0, Direction::Left, &|_| true);
|
|
||||||
|
|
||||||
assert_eq!(traverser.direction, Direction::Left);
|
|
||||||
// The next_direction might be consumed immediately when the traverser starts moving
|
|
||||||
// So we just check that the direction is set correctly
|
|
||||||
assert_eq!(traverser.direction, Direction::Left);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_traverser_set_next_direction() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let mut traverser = Traverser::new(&graph, 0, Direction::Left, &|_| true);
|
|
||||||
|
|
||||||
traverser.set_next_direction(Direction::Up);
|
|
||||||
assert!(traverser.next_direction.is_some());
|
|
||||||
assert_eq!(traverser.next_direction.unwrap().0, Direction::Up);
|
|
||||||
|
|
||||||
// Setting same direction should not change anything
|
|
||||||
traverser.set_next_direction(Direction::Up);
|
|
||||||
assert_eq!(traverser.next_direction.unwrap().0, Direction::Up);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_traverser_advance_at_node() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let mut traverser = Traverser::new(&graph, 0, Direction::Right, &|_| true);
|
|
||||||
|
|
||||||
// Should start moving in the initial direction
|
|
||||||
traverser.advance(&graph, 5.0, &|_| true);
|
|
||||||
|
|
||||||
match traverser.position {
|
|
||||||
Position::BetweenNodes { from, to, traversed } => {
|
|
||||||
assert_eq!(from, 0);
|
|
||||||
assert_eq!(to, 1);
|
|
||||||
assert_eq!(traversed, 5.0);
|
|
||||||
}
|
|
||||||
_ => panic!("Expected to be between nodes"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_traverser_advance_between_nodes() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let mut traverser = Traverser::new(&graph, 0, Direction::Right, &|_| true);
|
|
||||||
|
|
||||||
// Move to between nodes
|
|
||||||
traverser.advance(&graph, 5.0, &|_| true);
|
|
||||||
|
|
||||||
// Advance further
|
|
||||||
traverser.advance(&graph, 3.0, &|_| true);
|
|
||||||
|
|
||||||
match traverser.position {
|
|
||||||
Position::BetweenNodes { from, to, traversed } => {
|
|
||||||
assert_eq!(from, 0);
|
|
||||||
assert_eq!(to, 1);
|
|
||||||
assert_eq!(traversed, 8.0);
|
|
||||||
}
|
|
||||||
_ => panic!("Expected to be between nodes"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_edge_structure() {
|
|
||||||
let edge = Edge {
|
|
||||||
target: 5,
|
|
||||||
distance: 10.5,
|
|
||||||
direction: Direction::Up,
|
|
||||||
permissions: EdgePermissions::All,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(edge.target, 5);
|
|
||||||
assert_eq!(edge.distance, 10.5);
|
|
||||||
assert_eq!(edge.direction, Direction::Up);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_node_structure() {
|
|
||||||
let node = Node {
|
|
||||||
position: glam::Vec2::new(10.0, 20.0),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(node.position, glam::Vec2::new(10.0, 20.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -100,219 +100,3 @@ impl Pacman {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::entity::graph::{Graph, Node};
|
|
||||||
use crate::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
|
|
||||||
use sdl2::keyboard::Keycode;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
fn create_test_graph() -> Graph {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
let node2 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(16.0, 0.0),
|
|
||||||
});
|
|
||||||
let node3 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 16.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
|
||||||
graph.connect(node1, node3, false, None, Direction::Down).unwrap();
|
|
||||||
|
|
||||||
graph
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_test_atlas() -> SpriteAtlas {
|
|
||||||
// Create a minimal test atlas with required tiles
|
|
||||||
let mut frames = HashMap::new();
|
|
||||||
frames.insert(
|
|
||||||
"pacman/up_a.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"pacman/up_b.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 16,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"pacman/down_a.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 32,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"pacman/down_b.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 48,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"pacman/left_a.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 64,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"pacman/left_b.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 80,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"pacman/right_a.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 96,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"pacman/right_b.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 112,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"pacman/full.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 128,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
|
||||||
// Create a dummy texture (we won't actually render, just test the logic)
|
|
||||||
let dummy_texture = unsafe { std::mem::zeroed() };
|
|
||||||
SpriteAtlas::new(dummy_texture, mapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pacman_new() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
|
|
||||||
let pacman = Pacman::new(&graph, 0, &atlas);
|
|
||||||
|
|
||||||
assert_eq!(pacman.traverser.direction, Direction::Left);
|
|
||||||
assert!(matches!(pacman.traverser.position, crate::entity::graph::Position::AtNode(0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_handle_key_valid_directions() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let mut pacman = Pacman::new(&graph, 0, &atlas);
|
|
||||||
|
|
||||||
// Test that direction keys are handled correctly
|
|
||||||
// The traverser might consume next_direction immediately, so we check the actual direction
|
|
||||||
pacman.handle_key(Keycode::Up);
|
|
||||||
// Check that the direction was set (either in next_direction or current direction)
|
|
||||||
assert!(pacman.traverser.next_direction.is_some() || pacman.traverser.direction == Direction::Up);
|
|
||||||
|
|
||||||
pacman.handle_key(Keycode::Down);
|
|
||||||
assert!(pacman.traverser.next_direction.is_some() || pacman.traverser.direction == Direction::Down);
|
|
||||||
|
|
||||||
pacman.handle_key(Keycode::Left);
|
|
||||||
assert!(pacman.traverser.next_direction.is_some() || pacman.traverser.direction == Direction::Left);
|
|
||||||
|
|
||||||
pacman.handle_key(Keycode::Right);
|
|
||||||
assert!(pacman.traverser.next_direction.is_some() || pacman.traverser.direction == Direction::Right);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_handle_key_invalid_direction() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let mut pacman = Pacman::new(&graph, 0, &atlas);
|
|
||||||
|
|
||||||
let original_direction = pacman.traverser.direction;
|
|
||||||
let original_next_direction = pacman.traverser.next_direction;
|
|
||||||
|
|
||||||
// Test invalid key
|
|
||||||
pacman.handle_key(Keycode::Space);
|
|
||||||
|
|
||||||
// Should not change direction
|
|
||||||
assert_eq!(pacman.traverser.direction, original_direction);
|
|
||||||
assert_eq!(pacman.traverser.next_direction, original_next_direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_pixel_pos_at_node() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let pacman = Pacman::new(&graph, 0, &atlas);
|
|
||||||
|
|
||||||
let pos = pacman.get_pixel_pos(&graph);
|
|
||||||
assert_eq!(pos, glam::Vec2::new(0.0, 0.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_pixel_pos_between_nodes() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let mut pacman = Pacman::new(&graph, 0, &atlas);
|
|
||||||
|
|
||||||
// Move pacman between nodes - need to advance with a larger distance to ensure movement
|
|
||||||
pacman.traverser.advance(&graph, 5.0, &can_pacman_traverse); // Larger advance to ensure movement
|
|
||||||
|
|
||||||
let pos = pacman.get_pixel_pos(&graph);
|
|
||||||
// Should be between (0,0) and (16,0), but not exactly at (8,0) due to advance distance
|
|
||||||
assert!(pos.x >= 0.0 && pos.x <= 16.0);
|
|
||||||
assert_eq!(pos.y, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_updates_texture() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let mut pacman = Pacman::new(&graph, 0, &atlas);
|
|
||||||
|
|
||||||
// Test that tick doesn't panic
|
|
||||||
pacman.tick(0.016, &graph); // 60 FPS frame time
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pacman_initial_direction() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let pacman = Pacman::new(&graph, 0, &atlas);
|
|
||||||
|
|
||||||
// Pacman should start with the initial direction (Left)
|
|
||||||
assert_eq!(pacman.traverser.direction, Direction::Left);
|
|
||||||
// The next_direction might be consumed immediately when the traverser starts moving
|
|
||||||
// So we just check that the direction is set correctly
|
|
||||||
assert_eq!(pacman.traverser.direction, Direction::Left);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
230
src/game.rs
230
src/game.rs
@@ -152,233 +152,3 @@ impl Game {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use sdl2::keyboard::Keycode;
|
|
||||||
use sdl2::pixels::Color;
|
|
||||||
|
|
||||||
fn create_test_game() -> Game {
|
|
||||||
// Create a minimal test game without SDL dependencies
|
|
||||||
// This is a simplified version for testing basic logic
|
|
||||||
let map = Map::new(RAW_BOARD);
|
|
||||||
let pacman_start_pos = map.find_starting_position(0).unwrap();
|
|
||||||
let pacman_start_node = *map
|
|
||||||
.grid_to_node
|
|
||||||
.get(&glam::IVec2::new(pacman_start_pos.x as i32, pacman_start_pos.y as i32))
|
|
||||||
.expect("Pac-Man starting position not found in graph");
|
|
||||||
|
|
||||||
// Create a dummy atlas for testing
|
|
||||||
let mut mapper = std::collections::HashMap::new();
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/up_a.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/up_b.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 16,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/down_a.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 32,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/down_b.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 48,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/left_a.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 64,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/left_b.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 80,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/right_a.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 96,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/right_b.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 112,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"pacman/full.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 128,
|
|
||||||
y: 0,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
mapper.insert(
|
|
||||||
"maze/full.png".to_string(),
|
|
||||||
crate::texture::sprite::MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 224,
|
|
||||||
height: 248,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let atlas_mapper = crate::texture::sprite::AtlasMapper { frames: mapper };
|
|
||||||
let dummy_texture = unsafe { std::mem::zeroed() };
|
|
||||||
let atlas = crate::texture::sprite::SpriteAtlas::new(dummy_texture, atlas_mapper);
|
|
||||||
|
|
||||||
let mut map_texture = crate::texture::sprite::SpriteAtlas::get_tile(&atlas, "maze/full.png").unwrap();
|
|
||||||
map_texture.color = Some(Color::RGB(0x20, 0x20, 0xf9));
|
|
||||||
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
let audio = Audio::new();
|
|
||||||
let pacman = Pacman::new(&map.graph, pacman_start_node, &atlas);
|
|
||||||
|
|
||||||
Game {
|
|
||||||
score: 0,
|
|
||||||
map,
|
|
||||||
pacman,
|
|
||||||
debug_mode: false,
|
|
||||||
map_texture,
|
|
||||||
text_texture,
|
|
||||||
audio,
|
|
||||||
atlas,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_keyboard_event_direction_keys() {
|
|
||||||
let mut game = create_test_game();
|
|
||||||
|
|
||||||
// Test that direction keys are handled
|
|
||||||
game.keyboard_event(Keycode::Up);
|
|
||||||
game.keyboard_event(Keycode::Down);
|
|
||||||
game.keyboard_event(Keycode::Left);
|
|
||||||
game.keyboard_event(Keycode::Right);
|
|
||||||
|
|
||||||
// Should not panic
|
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_keyboard_event_mute_toggle() {
|
|
||||||
let mut game = create_test_game();
|
|
||||||
|
|
||||||
let initial_mute_state = game.audio.is_muted();
|
|
||||||
|
|
||||||
// Toggle mute
|
|
||||||
game.keyboard_event(Keycode::M);
|
|
||||||
|
|
||||||
// Mute state should have changed
|
|
||||||
assert_eq!(game.audio.is_muted(), !initial_mute_state);
|
|
||||||
|
|
||||||
// Toggle again
|
|
||||||
game.keyboard_event(Keycode::M);
|
|
||||||
|
|
||||||
// Should be back to original state
|
|
||||||
assert_eq!(game.audio.is_muted(), initial_mute_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_tick() {
|
|
||||||
let mut game = create_test_game();
|
|
||||||
|
|
||||||
// Test that tick doesn't panic
|
|
||||||
game.tick(0.016); // 60 FPS frame time
|
|
||||||
|
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_initial_state() {
|
|
||||||
let game = create_test_game();
|
|
||||||
|
|
||||||
assert_eq!(game.score, 0);
|
|
||||||
assert!(!game.debug_mode);
|
|
||||||
assert!(game.map.graph.node_count() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_debug_mode_toggle() {
|
|
||||||
let mut game = create_test_game();
|
|
||||||
|
|
||||||
assert!(!game.debug_mode);
|
|
||||||
|
|
||||||
// Toggle debug mode (this would normally be done via Space key in the app)
|
|
||||||
game.debug_mode = !game.debug_mode;
|
|
||||||
|
|
||||||
assert!(game.debug_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_score_increment() {
|
|
||||||
let mut game = create_test_game();
|
|
||||||
|
|
||||||
let initial_score = game.score;
|
|
||||||
game.score += 10;
|
|
||||||
|
|
||||||
assert_eq!(game.score, initial_score + 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_pacman_initialization() {
|
|
||||||
let game = create_test_game();
|
|
||||||
|
|
||||||
// Check that Pac-Man was initialized
|
|
||||||
assert_eq!(game.pacman.traverser.direction, crate::entity::direction::Direction::Left);
|
|
||||||
// The traverser might start moving immediately, so we just check the direction
|
|
||||||
assert_eq!(game.pacman.traverser.direction, crate::entity::direction::Direction::Left);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_map_initialization() {
|
|
||||||
let game = create_test_game();
|
|
||||||
|
|
||||||
// Check that map was initialized
|
|
||||||
assert!(game.map.graph.node_count() > 0);
|
|
||||||
assert!(!game.map.grid_to_node.is_empty());
|
|
||||||
|
|
||||||
// Check that Pac-Man's starting position exists
|
|
||||||
let pacman_pos = game.map.find_starting_position(0);
|
|
||||||
assert!(pacman_pos.is_some());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,43 +9,3 @@ pub fn centered_with_size(pixel_pos: IVec2, size: UVec2) -> Rect {
|
|||||||
size.y,
|
size.y,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_centered_with_size_basic() {
|
|
||||||
let rect = centered_with_size(IVec2::new(100, 100), UVec2::new(50, 30));
|
|
||||||
assert_eq!(rect.origin(), (75, 85));
|
|
||||||
assert_eq!(rect.size(), (50, 30));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_centered_with_size_odd_dimensions() {
|
|
||||||
let rect = centered_with_size(IVec2::new(50, 50), UVec2::new(51, 31));
|
|
||||||
assert_eq!(rect.origin(), (25, 35));
|
|
||||||
assert_eq!(rect.size(), (51, 31));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_centered_with_size_zero_position() {
|
|
||||||
let rect = centered_with_size(IVec2::new(0, 0), UVec2::new(100, 100));
|
|
||||||
assert_eq!(rect.origin(), (-50, -50));
|
|
||||||
assert_eq!(rect.size(), (100, 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_centered_with_size_negative_position() {
|
|
||||||
let rect = centered_with_size(IVec2::new(-100, -50), UVec2::new(80, 40));
|
|
||||||
assert_eq!(rect.origin(), (-140, -70));
|
|
||||||
assert_eq!(rect.size(), (80, 40));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_centered_with_size_large_dimensions() {
|
|
||||||
let rect = centered_with_size(IVec2::new(1000, 1000), UVec2::new(1000, 1000));
|
|
||||||
assert_eq!(rect.origin(), (500, 500));
|
|
||||||
assert_eq!(rect.size(), (1000, 1000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -368,162 +368,3 @@ impl Map {
|
|||||||
.expect("Failed to connect left tunnel hidden node to right tunnel hidden node");
|
.expect("Failed to connect left tunnel hidden node to right tunnel hidden node");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::constants::{BOARD_CELL_SIZE, CELL_SIZE};
|
|
||||||
use glam::{IVec2, Vec2};
|
|
||||||
|
|
||||||
fn create_minimal_test_board() -> [&'static str; BOARD_CELL_SIZE.y as usize] {
|
|
||||||
let mut board = [""; BOARD_CELL_SIZE.y as usize];
|
|
||||||
// Create a minimal valid board with house doors
|
|
||||||
board[0] = "############################";
|
|
||||||
board[1] = "#............##............#";
|
|
||||||
board[2] = "#.####.#####.##.#####.####.#";
|
|
||||||
board[3] = "#o####.#####.##.#####.####o#";
|
|
||||||
board[4] = "#.####.#####.##.#####.####.#";
|
|
||||||
board[5] = "#..........................#";
|
|
||||||
board[6] = "#.####.##.########.##.####.#";
|
|
||||||
board[7] = "#.####.##.########.##.####.#";
|
|
||||||
board[8] = "#......##....##....##......#";
|
|
||||||
board[9] = "######.##### ## #####.######";
|
|
||||||
board[10] = " #.##### ## #####.# ";
|
|
||||||
board[11] = " #.## == ##.# ";
|
|
||||||
board[12] = " #.## ######## ##.# ";
|
|
||||||
board[13] = "######.## ######## ##.######";
|
|
||||||
board[14] = "T . ######## . T";
|
|
||||||
board[15] = "######.## ######## ##.######";
|
|
||||||
board[16] = " #.## ######## ##.# ";
|
|
||||||
board[17] = " #.## ##.# ";
|
|
||||||
board[18] = " #.## ######## ##.# ";
|
|
||||||
board[19] = "######.## ######## ##.######";
|
|
||||||
board[20] = "#............##............#";
|
|
||||||
board[21] = "#.####.#####.##.#####.####.#";
|
|
||||||
board[22] = "#.####.#####.##.#####.####.#";
|
|
||||||
board[23] = "#o..##.......X .......##..o#";
|
|
||||||
board[24] = "###.##.##.########.##.##.###";
|
|
||||||
board[25] = "###.##.##.########.##.##.###";
|
|
||||||
board[26] = "#......##....##....##......#";
|
|
||||||
board[27] = "#.##########.##.##########.#";
|
|
||||||
board[28] = "#.##########.##.##########.#";
|
|
||||||
board[29] = "#..........................#";
|
|
||||||
board[30] = "############################";
|
|
||||||
board
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_new() {
|
|
||||||
let board = create_minimal_test_board();
|
|
||||||
let map = Map::new(board);
|
|
||||||
|
|
||||||
assert!(map.graph.node_count() > 0);
|
|
||||||
assert!(!map.grid_to_node.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_find_starting_position_pacman() {
|
|
||||||
let board = create_minimal_test_board();
|
|
||||||
let map = Map::new(board);
|
|
||||||
|
|
||||||
let pacman_pos = map.find_starting_position(0);
|
|
||||||
assert!(pacman_pos.is_some());
|
|
||||||
|
|
||||||
let pos = pacman_pos.unwrap();
|
|
||||||
// Pacman should be found somewhere in the board
|
|
||||||
assert!(pos.x < BOARD_CELL_SIZE.x);
|
|
||||||
assert!(pos.y < BOARD_CELL_SIZE.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_find_starting_position_ghost() {
|
|
||||||
let board = create_minimal_test_board();
|
|
||||||
let map = Map::new(board);
|
|
||||||
|
|
||||||
// Test for ghost 1 (might not exist in this board)
|
|
||||||
let ghost_pos = map.find_starting_position(1);
|
|
||||||
// Ghost 1 might not exist, so this could be None
|
|
||||||
if let Some(pos) = ghost_pos {
|
|
||||||
assert!(pos.x < BOARD_CELL_SIZE.x);
|
|
||||||
assert!(pos.y < BOARD_CELL_SIZE.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_find_starting_position_nonexistent() {
|
|
||||||
let board = create_minimal_test_board();
|
|
||||||
let map = Map::new(board);
|
|
||||||
|
|
||||||
let pos = map.find_starting_position(99); // Non-existent entity
|
|
||||||
assert!(pos.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_graph_construction() {
|
|
||||||
let board = create_minimal_test_board();
|
|
||||||
let map = Map::new(board);
|
|
||||||
|
|
||||||
// Check that nodes were created
|
|
||||||
assert!(map.graph.node_count() > 0);
|
|
||||||
|
|
||||||
// Check that grid_to_node mapping was created
|
|
||||||
assert!(!map.grid_to_node.is_empty());
|
|
||||||
|
|
||||||
// Check that some connections were made
|
|
||||||
let mut has_connections = false;
|
|
||||||
for intersection in &map.graph.adjacency_list {
|
|
||||||
if intersection.edges().next().is_some() {
|
|
||||||
has_connections = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(has_connections);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_grid_to_node_mapping() {
|
|
||||||
let board = create_minimal_test_board();
|
|
||||||
let map = Map::new(board);
|
|
||||||
|
|
||||||
// Check that Pac-Man's position is mapped
|
|
||||||
let pacman_pos = map.find_starting_position(0).unwrap();
|
|
||||||
let grid_pos = IVec2::new(pacman_pos.x as i32, pacman_pos.y as i32);
|
|
||||||
|
|
||||||
assert!(map.grid_to_node.contains_key(&grid_pos));
|
|
||||||
let node_id = map.grid_to_node[&grid_pos];
|
|
||||||
assert!(map.graph.get_node(node_id).is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_node_positions() {
|
|
||||||
let board = create_minimal_test_board();
|
|
||||||
let map = Map::new(board);
|
|
||||||
|
|
||||||
// Check that node positions are correctly calculated
|
|
||||||
for (grid_pos, &node_id) in &map.grid_to_node {
|
|
||||||
let node = map.graph.get_node(node_id).unwrap();
|
|
||||||
let expected_pos = Vec2::new((grid_pos.x * CELL_SIZE as i32) as f32, (grid_pos.y * CELL_SIZE as i32) as f32)
|
|
||||||
+ Vec2::splat(CELL_SIZE as f32 / 2.0);
|
|
||||||
|
|
||||||
assert_eq!(node.position, expected_pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_adjacent_connections() {
|
|
||||||
let board = create_minimal_test_board();
|
|
||||||
let map = Map::new(board);
|
|
||||||
|
|
||||||
// Check that adjacent walkable tiles are connected
|
|
||||||
// Find any node that has connections
|
|
||||||
let mut found_connected_node = false;
|
|
||||||
for &node_id in map.grid_to_node.values() {
|
|
||||||
let intersection = &map.graph.adjacency_list[node_id];
|
|
||||||
if intersection.edges().next().is_some() {
|
|
||||||
found_connected_node = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(found_connected_node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -118,56 +118,3 @@ impl MapTileParser {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::constants::RAW_BOARD;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_character() {
|
|
||||||
assert!(matches!(MapTileParser::parse_character('#').unwrap(), MapTile::Wall));
|
|
||||||
assert!(matches!(MapTileParser::parse_character('.').unwrap(), MapTile::Pellet));
|
|
||||||
assert!(matches!(MapTileParser::parse_character('o').unwrap(), MapTile::PowerPellet));
|
|
||||||
assert!(matches!(MapTileParser::parse_character(' ').unwrap(), MapTile::Empty));
|
|
||||||
assert!(matches!(MapTileParser::parse_character('T').unwrap(), MapTile::Tunnel));
|
|
||||||
assert!(matches!(MapTileParser::parse_character('X').unwrap(), MapTile::Empty));
|
|
||||||
assert!(matches!(MapTileParser::parse_character('=').unwrap(), MapTile::Wall));
|
|
||||||
|
|
||||||
// Test invalid character
|
|
||||||
assert!(MapTileParser::parse_character('Z').is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_board() {
|
|
||||||
let result = MapTileParser::parse_board(RAW_BOARD);
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let parsed = result.unwrap();
|
|
||||||
|
|
||||||
// Verify we have tiles
|
|
||||||
assert_eq!(parsed.tiles.len(), BOARD_CELL_SIZE.x as usize);
|
|
||||||
assert_eq!(parsed.tiles[0].len(), BOARD_CELL_SIZE.y as usize);
|
|
||||||
|
|
||||||
// Verify we found house door positions
|
|
||||||
assert!(parsed.house_door[0].is_some());
|
|
||||||
assert!(parsed.house_door[1].is_some());
|
|
||||||
|
|
||||||
// Verify we found tunnel ends
|
|
||||||
assert!(parsed.tunnel_ends[0].is_some());
|
|
||||||
assert!(parsed.tunnel_ends[1].is_some());
|
|
||||||
|
|
||||||
// Verify we found Pac-Man's starting position
|
|
||||||
assert!(parsed.pacman_start.is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_board_invalid_character() {
|
|
||||||
let mut invalid_board = RAW_BOARD.clone();
|
|
||||||
invalid_board[0] = "###########################Z";
|
|
||||||
|
|
||||||
let result = MapTileParser::parse_board(invalid_board);
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert!(matches!(result.unwrap_err(), ParseError::UnknownCharacter('Z')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -65,80 +65,3 @@ impl MapRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::entity::graph::{Graph, Node};
|
|
||||||
use crate::texture::sprite::{AtlasMapper, MapperFrame};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
fn create_test_graph() -> Graph {
|
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
let node2 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(16.0, 0.0),
|
|
||||||
});
|
|
||||||
let node3 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 16.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
graph
|
|
||||||
.connect(node1, node2, false, None, crate::entity::direction::Direction::Right)
|
|
||||||
.unwrap();
|
|
||||||
graph
|
|
||||||
.connect(node1, node3, false, None, crate::entity::direction::Direction::Down)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
graph
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_test_atlas() -> SpriteAtlas {
|
|
||||||
let mut frames = HashMap::new();
|
|
||||||
frames.insert(
|
|
||||||
"maze/full.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 224,
|
|
||||||
height: 248,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let mapper = AtlasMapper { frames };
|
|
||||||
let dummy_texture = unsafe { std::mem::zeroed() };
|
|
||||||
SpriteAtlas::new(dummy_texture, mapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_render_map_does_not_panic() {
|
|
||||||
// This test just ensures the function doesn't panic
|
|
||||||
// We can't easily test the actual rendering without SDL context
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let _map_texture = SpriteAtlas::get_tile(&atlas, "maze/full.png").unwrap();
|
|
||||||
|
|
||||||
// The function should not panic even with dummy data
|
|
||||||
// Note: We can't actually call render_map without a canvas, but we can test the logic
|
|
||||||
assert!(true); // Placeholder test
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_debug_render_nodes_does_not_panic() {
|
|
||||||
// This test just ensures the function doesn't panic
|
|
||||||
// We can't easily test the actual rendering without SDL context
|
|
||||||
let _graph = create_test_graph();
|
|
||||||
|
|
||||||
// The function should not panic even with dummy data
|
|
||||||
// Note: We can't actually call debug_render_nodes without a canvas, but we can test the logic
|
|
||||||
assert!(true); // Placeholder test
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_renderer_structure() {
|
|
||||||
// Test that MapRenderer is a unit struct
|
|
||||||
let _renderer = MapRenderer;
|
|
||||||
// This should compile and not panic
|
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -74,127 +74,3 @@ impl AnimatedTexture {
|
|||||||
self.tiles.len()
|
self.tiles.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use glam::U16Vec2;
|
|
||||||
use sdl2::pixels::Color;
|
|
||||||
|
|
||||||
impl AtlasTile {
|
|
||||||
fn mock(id: u32) -> Self {
|
|
||||||
AtlasTile {
|
|
||||||
pos: U16Vec2::new(0, 0),
|
|
||||||
size: U16Vec2::new(16, 16),
|
|
||||||
color: Some(Color::RGB(id as u8, 0, 0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_animated_texture() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2), AtlasTile::mock(3)];
|
|
||||||
let texture = AnimatedTexture::new(tiles.clone(), 0.1).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(texture.current_frame(), 0);
|
|
||||||
assert_eq!(texture.time_bank(), 0.0);
|
|
||||||
assert_eq!(texture.frame_duration(), 0.1);
|
|
||||||
assert_eq!(texture.tiles_len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_animated_texture_zero_duration() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2)];
|
|
||||||
let result = AnimatedTexture::new(tiles, 0.0);
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert!(matches!(result.unwrap_err(), AnimatedTextureError::InvalidFrameDuration(0.0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_animated_texture_negative_duration() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2)];
|
|
||||||
let result = AnimatedTexture::new(tiles, -0.1);
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert!(matches!(
|
|
||||||
result.unwrap_err(),
|
|
||||||
AnimatedTextureError::InvalidFrameDuration(-0.1)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_no_frame_change() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2)];
|
|
||||||
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
|
||||||
|
|
||||||
// Tick with less than frame duration
|
|
||||||
texture.tick(0.05);
|
|
||||||
assert_eq!(texture.current_frame(), 0);
|
|
||||||
assert_eq!(texture.time_bank(), 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_single_frame_change() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2)];
|
|
||||||
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
|
||||||
|
|
||||||
// Tick with exactly frame duration
|
|
||||||
texture.tick(0.1);
|
|
||||||
assert_eq!(texture.current_frame(), 1);
|
|
||||||
assert_eq!(texture.time_bank(), 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_multiple_frame_changes() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2), AtlasTile::mock(3)];
|
|
||||||
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
|
||||||
|
|
||||||
// Tick with 2.5 frame durations
|
|
||||||
texture.tick(0.25);
|
|
||||||
assert_eq!(texture.current_frame(), 2);
|
|
||||||
assert!((texture.time_bank() - 0.05).abs() < 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_wrap_around() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2)];
|
|
||||||
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
|
||||||
|
|
||||||
// Advance to last frame
|
|
||||||
texture.tick(0.1);
|
|
||||||
assert_eq!(texture.current_frame(), 1);
|
|
||||||
|
|
||||||
// Advance again to wrap around
|
|
||||||
texture.tick(0.1);
|
|
||||||
assert_eq!(texture.current_frame(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_current_tile() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2)];
|
|
||||||
let texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
|
||||||
|
|
||||||
// Should return first tile initially
|
|
||||||
assert_eq!(texture.current_tile().color.unwrap().r, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_current_tile_after_frame_change() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1), AtlasTile::mock(2)];
|
|
||||||
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
|
||||||
|
|
||||||
// Advance one frame
|
|
||||||
texture.tick(0.1);
|
|
||||||
assert_eq!(texture.current_tile().color.unwrap().r, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_single_tile_animation() {
|
|
||||||
let tiles = vec![AtlasTile::mock(1)];
|
|
||||||
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
|
||||||
|
|
||||||
// Should stay on same frame
|
|
||||||
texture.tick(0.1);
|
|
||||||
assert_eq!(texture.current_frame(), 0);
|
|
||||||
assert_eq!(texture.current_tile().color.unwrap().r, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,135 +44,3 @@ impl BlinkingTexture {
|
|||||||
self.blink_duration
|
self.blink_duration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use glam::U16Vec2;
|
|
||||||
use sdl2::pixels::Color;
|
|
||||||
|
|
||||||
fn mock_atlas_tile(id: u32) -> AtlasTile {
|
|
||||||
AtlasTile {
|
|
||||||
pos: U16Vec2::new(0, 0),
|
|
||||||
size: U16Vec2::new(16, 16),
|
|
||||||
color: Some(Color::RGB(id as u8, 0, 0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_blinking_texture() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let texture = BlinkingTexture::new(tile, 0.5);
|
|
||||||
|
|
||||||
assert_eq!(texture.is_on(), true);
|
|
||||||
assert_eq!(texture.time_bank(), 0.0);
|
|
||||||
assert_eq!(texture.blink_duration(), 0.5);
|
|
||||||
assert_eq!(texture.tile().color.unwrap().r, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_no_blink_change() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
|
||||||
|
|
||||||
// Tick with less than blink duration
|
|
||||||
texture.tick(0.25);
|
|
||||||
assert_eq!(texture.is_on(), true);
|
|
||||||
assert_eq!(texture.time_bank(), 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_single_blink_change() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
|
||||||
|
|
||||||
// Tick with exactly blink duration
|
|
||||||
texture.tick(0.5);
|
|
||||||
assert_eq!(texture.is_on(), false);
|
|
||||||
assert_eq!(texture.time_bank(), 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_multiple_blink_changes() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
|
||||||
|
|
||||||
// First blink
|
|
||||||
texture.tick(0.5);
|
|
||||||
assert_eq!(texture.is_on(), false);
|
|
||||||
|
|
||||||
// Second blink (back to on)
|
|
||||||
texture.tick(0.5);
|
|
||||||
assert_eq!(texture.is_on(), true);
|
|
||||||
|
|
||||||
// Third blink (back to off)
|
|
||||||
texture.tick(0.5);
|
|
||||||
assert_eq!(texture.is_on(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_partial_blink_duration() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
|
||||||
|
|
||||||
// Tick with 1.25 blink durations
|
|
||||||
texture.tick(0.625);
|
|
||||||
assert_eq!(texture.is_on(), false);
|
|
||||||
assert_eq!(texture.time_bank(), 0.125);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_with_zero_duration() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.0);
|
|
||||||
|
|
||||||
// Should not cause issues - skip the test if blink_duration is 0
|
|
||||||
if texture.blink_duration() > 0.0 {
|
|
||||||
texture.tick(0.1);
|
|
||||||
assert_eq!(texture.is_on(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_with_negative_duration() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let mut texture = BlinkingTexture::new(tile, -0.5);
|
|
||||||
|
|
||||||
// Should not cause issues - skip the test if blink_duration is negative
|
|
||||||
if texture.blink_duration() > 0.0 {
|
|
||||||
texture.tick(0.1);
|
|
||||||
assert_eq!(texture.is_on(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick_with_negative_delta_time() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
|
||||||
|
|
||||||
// Should not cause issues
|
|
||||||
texture.tick(-0.1);
|
|
||||||
assert_eq!(texture.is_on(), true);
|
|
||||||
assert_eq!(texture.time_bank(), -0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tile_access() {
|
|
||||||
let tile = mock_atlas_tile(42);
|
|
||||||
let texture = BlinkingTexture::new(tile, 0.5);
|
|
||||||
|
|
||||||
assert_eq!(texture.tile().color.unwrap().r, 42);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_clone() {
|
|
||||||
let tile = mock_atlas_tile(1);
|
|
||||||
let texture = BlinkingTexture::new(tile, 0.5);
|
|
||||||
let cloned = texture.clone();
|
|
||||||
|
|
||||||
assert_eq!(texture.is_on(), cloned.is_on());
|
|
||||||
assert_eq!(texture.time_bank(), cloned.time_bank());
|
|
||||||
assert_eq!(texture.blink_duration(), cloned.blink_duration());
|
|
||||||
assert_eq!(texture.tile().color.unwrap().r, cloned.tile().color.unwrap().r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -79,112 +79,3 @@ impl DirectionalAnimatedTexture {
|
|||||||
self.stopped_textures.len()
|
self.stopped_textures.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::texture::sprite::AtlasTile;
|
|
||||||
use glam::U16Vec2;
|
|
||||||
use sdl2::pixels::Color;
|
|
||||||
|
|
||||||
fn mock_atlas_tile(id: u32) -> AtlasTile {
|
|
||||||
AtlasTile {
|
|
||||||
pos: U16Vec2::new(0, 0),
|
|
||||||
size: U16Vec2::new(16, 16),
|
|
||||||
color: Some(Color::RGB(id as u8, 0, 0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mock_animated_texture(id: u32) -> AnimatedTexture {
|
|
||||||
AnimatedTexture::new(vec![mock_atlas_tile(id)], 0.1).expect("Invalid frame duration")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_directional_animated_texture() {
|
|
||||||
let mut textures = HashMap::new();
|
|
||||||
let mut stopped_textures = HashMap::new();
|
|
||||||
|
|
||||||
textures.insert(Direction::Up, mock_animated_texture(1));
|
|
||||||
textures.insert(Direction::Down, mock_animated_texture(2));
|
|
||||||
stopped_textures.insert(Direction::Up, mock_animated_texture(3));
|
|
||||||
stopped_textures.insert(Direction::Down, mock_animated_texture(4));
|
|
||||||
|
|
||||||
let texture = DirectionalAnimatedTexture::new(textures, stopped_textures);
|
|
||||||
|
|
||||||
assert_eq!(texture.texture_count(), 2);
|
|
||||||
assert_eq!(texture.stopped_texture_count(), 2);
|
|
||||||
assert!(texture.has_direction(Direction::Up));
|
|
||||||
assert!(texture.has_direction(Direction::Down));
|
|
||||||
assert!(!texture.has_direction(Direction::Left));
|
|
||||||
assert!(texture.has_stopped_direction(Direction::Up));
|
|
||||||
assert!(texture.has_stopped_direction(Direction::Down));
|
|
||||||
assert!(!texture.has_stopped_direction(Direction::Left));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tick() {
|
|
||||||
let mut textures = HashMap::new();
|
|
||||||
textures.insert(Direction::Up, mock_animated_texture(1));
|
|
||||||
textures.insert(Direction::Down, mock_animated_texture(2));
|
|
||||||
|
|
||||||
let mut texture = DirectionalAnimatedTexture::new(textures, HashMap::new());
|
|
||||||
|
|
||||||
// Should not panic
|
|
||||||
texture.tick(0.1);
|
|
||||||
assert_eq!(texture.texture_count(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_texture() {
|
|
||||||
let texture = DirectionalAnimatedTexture::new(HashMap::new(), HashMap::new());
|
|
||||||
|
|
||||||
assert_eq!(texture.texture_count(), 0);
|
|
||||||
assert_eq!(texture.stopped_texture_count(), 0);
|
|
||||||
assert!(!texture.has_direction(Direction::Up));
|
|
||||||
assert!(!texture.has_stopped_direction(Direction::Up));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_partial_directions() {
|
|
||||||
let mut textures = HashMap::new();
|
|
||||||
textures.insert(Direction::Up, mock_animated_texture(1));
|
|
||||||
|
|
||||||
let texture = DirectionalAnimatedTexture::new(textures, HashMap::new());
|
|
||||||
|
|
||||||
assert_eq!(texture.texture_count(), 1);
|
|
||||||
assert!(texture.has_direction(Direction::Up));
|
|
||||||
assert!(!texture.has_direction(Direction::Down));
|
|
||||||
assert!(!texture.has_direction(Direction::Left));
|
|
||||||
assert!(!texture.has_direction(Direction::Right));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_clone() {
|
|
||||||
let mut textures = HashMap::new();
|
|
||||||
textures.insert(Direction::Up, mock_animated_texture(1));
|
|
||||||
|
|
||||||
let texture = DirectionalAnimatedTexture::new(textures, HashMap::new());
|
|
||||||
let cloned = texture.clone();
|
|
||||||
|
|
||||||
assert_eq!(texture.texture_count(), cloned.texture_count());
|
|
||||||
assert_eq!(texture.stopped_texture_count(), cloned.stopped_texture_count());
|
|
||||||
assert_eq!(texture.has_direction(Direction::Up), cloned.has_direction(Direction::Up));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_all_directions() {
|
|
||||||
let mut textures = HashMap::new();
|
|
||||||
textures.insert(Direction::Up, mock_animated_texture(1));
|
|
||||||
textures.insert(Direction::Down, mock_animated_texture(2));
|
|
||||||
textures.insert(Direction::Left, mock_animated_texture(3));
|
|
||||||
textures.insert(Direction::Right, mock_animated_texture(4));
|
|
||||||
|
|
||||||
let texture = DirectionalAnimatedTexture::new(textures, HashMap::new());
|
|
||||||
|
|
||||||
assert_eq!(texture.texture_count(), 4);
|
|
||||||
assert!(texture.has_direction(Direction::Up));
|
|
||||||
assert!(texture.has_direction(Direction::Down));
|
|
||||||
assert!(texture.has_direction(Direction::Left));
|
|
||||||
assert!(texture.has_direction(Direction::Right));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -134,236 +134,3 @@ impl SpriteAtlas {
|
|||||||
pub unsafe fn texture_to_static(texture: Texture) -> Texture<'static> {
|
pub unsafe fn texture_to_static(texture: Texture) -> Texture<'static> {
|
||||||
std::mem::transmute(texture)
|
std::mem::transmute(texture)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use sdl2::pixels::Color;
|
|
||||||
|
|
||||||
// Mock texture for testing - we'll use a dummy approach since we can't create real SDL2 textures
|
|
||||||
fn mock_texture() -> Texture<'static> {
|
|
||||||
// This is unsafe and only for testing - in real usage this would be a proper texture
|
|
||||||
unsafe { std::mem::transmute(0usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_atlas_tile_new() {
|
|
||||||
let pos = U16Vec2::new(10, 20);
|
|
||||||
let size = U16Vec2::new(32, 32);
|
|
||||||
let tile = AtlasTile::new(pos, size, None);
|
|
||||||
|
|
||||||
assert_eq!(tile.pos, pos);
|
|
||||||
assert_eq!(tile.size, size);
|
|
||||||
assert_eq!(tile.color, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_atlas_tile_with_color() {
|
|
||||||
let pos = U16Vec2::new(10, 20);
|
|
||||||
let size = U16Vec2::new(32, 32);
|
|
||||||
let color = Color::RGB(255, 0, 0);
|
|
||||||
let tile = AtlasTile::new(pos, size, None).with_color(color);
|
|
||||||
|
|
||||||
assert_eq!(tile.pos, pos);
|
|
||||||
assert_eq!(tile.size, size);
|
|
||||||
assert_eq!(tile.color, Some(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_mapper_frame() {
|
|
||||||
let frame = MapperFrame {
|
|
||||||
x: 10,
|
|
||||||
y: 20,
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(frame.x, 10);
|
|
||||||
assert_eq!(frame.y, 20);
|
|
||||||
assert_eq!(frame.width, 32);
|
|
||||||
assert_eq!(frame.height, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_atlas_mapper_new() {
|
|
||||||
let mut frames = HashMap::new();
|
|
||||||
frames.insert(
|
|
||||||
"test".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
|
||||||
|
|
||||||
assert_eq!(mapper.frames.len(), 1);
|
|
||||||
assert!(mapper.frames.contains_key("test"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sprite_atlas_new() {
|
|
||||||
let mut frames = HashMap::new();
|
|
||||||
frames.insert(
|
|
||||||
"test".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
|
||||||
let texture = mock_texture();
|
|
||||||
let atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
assert_eq!(atlas.tiles_count(), 1);
|
|
||||||
assert!(atlas.has_tile("test"));
|
|
||||||
assert_eq!(atlas.default_color(), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sprite_atlas_get_tile() {
|
|
||||||
let mut frames = HashMap::new();
|
|
||||||
frames.insert(
|
|
||||||
"test".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 10,
|
|
||||||
y: 20,
|
|
||||||
width: 32,
|
|
||||||
height: 64,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
|
||||||
let texture = mock_texture();
|
|
||||||
let atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
let tile = atlas.get_tile("test");
|
|
||||||
assert!(tile.is_some());
|
|
||||||
|
|
||||||
let tile = tile.unwrap();
|
|
||||||
assert_eq!(tile.pos, U16Vec2::new(10, 20));
|
|
||||||
assert_eq!(tile.size, U16Vec2::new(32, 64));
|
|
||||||
assert_eq!(tile.color, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sprite_atlas_get_tile_nonexistent() {
|
|
||||||
let mapper = AtlasMapper { frames: HashMap::new() };
|
|
||||||
let texture = mock_texture();
|
|
||||||
let atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
let tile = atlas.get_tile("nonexistent");
|
|
||||||
assert!(tile.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sprite_atlas_set_color() {
|
|
||||||
let mapper = AtlasMapper { frames: HashMap::new() };
|
|
||||||
let texture = mock_texture();
|
|
||||||
let mut atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
assert_eq!(atlas.default_color(), None);
|
|
||||||
|
|
||||||
let color = Color::RGB(255, 0, 0);
|
|
||||||
atlas.set_color(color);
|
|
||||||
|
|
||||||
assert_eq!(atlas.default_color(), Some(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sprite_atlas_empty() {
|
|
||||||
let mapper = AtlasMapper { frames: HashMap::new() };
|
|
||||||
let texture = mock_texture();
|
|
||||||
let atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
assert_eq!(atlas.tiles_count(), 0);
|
|
||||||
assert!(!atlas.has_tile("any"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sprite_atlas_multiple_tiles() {
|
|
||||||
let mut frames = HashMap::new();
|
|
||||||
frames.insert(
|
|
||||||
"tile1".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"tile2".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 32,
|
|
||||||
y: 0,
|
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
|
||||||
let texture = mock_texture();
|
|
||||||
let atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
assert_eq!(atlas.tiles_count(), 2);
|
|
||||||
assert!(atlas.has_tile("tile1"));
|
|
||||||
assert!(atlas.has_tile("tile2"));
|
|
||||||
assert!(!atlas.has_tile("tile3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_atlas_tile_clone() {
|
|
||||||
let pos = U16Vec2::new(10, 20);
|
|
||||||
let size = U16Vec2::new(32, 32);
|
|
||||||
let color = Color::RGB(255, 0, 0);
|
|
||||||
let tile = AtlasTile::new(pos, size, Some(color));
|
|
||||||
let cloned = tile;
|
|
||||||
|
|
||||||
assert_eq!(tile.pos, cloned.pos);
|
|
||||||
assert_eq!(tile.size, cloned.size);
|
|
||||||
assert_eq!(tile.color, cloned.color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_mapper_frame_clone() {
|
|
||||||
let frame = MapperFrame {
|
|
||||||
x: 10,
|
|
||||||
y: 20,
|
|
||||||
width: 32,
|
|
||||||
height: 64,
|
|
||||||
};
|
|
||||||
let cloned = frame;
|
|
||||||
|
|
||||||
assert_eq!(frame.x, cloned.x);
|
|
||||||
assert_eq!(frame.y, cloned.y);
|
|
||||||
assert_eq!(frame.width, cloned.width);
|
|
||||||
assert_eq!(frame.height, cloned.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_atlas_mapper_clone() {
|
|
||||||
let mut frames = HashMap::new();
|
|
||||||
frames.insert(
|
|
||||||
"test".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
|
||||||
let cloned = mapper.clone();
|
|
||||||
|
|
||||||
assert_eq!(mapper.frames.len(), cloned.frames.len());
|
|
||||||
assert!(mapper.frames.contains_key("test"));
|
|
||||||
assert!(cloned.frames.contains_key("test"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -151,228 +151,3 @@ impl TextTexture {
|
|||||||
(8.0 * self.scale) as u32
|
(8.0 * self.scale) as u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
fn create_mock_atlas() -> SpriteAtlas {
|
|
||||||
let mut frames = HashMap::new();
|
|
||||||
frames.insert(
|
|
||||||
"text/A.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"text/1.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 8,
|
|
||||||
y: 0,
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"text/!.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 16,
|
|
||||||
y: 0,
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"text/-.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 24,
|
|
||||||
y: 0,
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"text/_double_quote.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 32,
|
|
||||||
y: 0,
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
frames.insert(
|
|
||||||
"text/_forward_slash.png".to_string(),
|
|
||||||
MapperFrame {
|
|
||||||
x: 40,
|
|
||||||
y: 0,
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
|
||||||
// Note: In real tests, we'd need a proper texture, but for unit tests we can work around this
|
|
||||||
unsafe { SpriteAtlas::new(std::mem::zeroed(), mapper) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_texture_new() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
assert_eq!(text_texture.scale(), 1.0);
|
|
||||||
assert!(text_texture.char_map.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_texture_new_with_scale() {
|
|
||||||
let text_texture = TextTexture::new(2.5);
|
|
||||||
assert_eq!(text_texture.scale(), 2.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_char_to_tile_name_letters() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('A'), Some("text/A.png".to_string()));
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('Z'), Some("text/Z.png".to_string()));
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('a'), None); // lowercase not supported
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_char_to_tile_name_numbers() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('0'), Some("text/0.png".to_string()));
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('9'), Some("text/9.png".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_char_to_tile_name_special_characters() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('!'), Some("text/!.png".to_string()));
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('-'), Some("text/-.png".to_string()));
|
|
||||||
assert_eq!(
|
|
||||||
text_texture.char_to_tile_name('"'),
|
|
||||||
Some("text/_double_quote.png".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
text_texture.char_to_tile_name('/'),
|
|
||||||
Some("text/_forward_slash.png".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_char_to_tile_name_unsupported() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
|
|
||||||
assert_eq!(text_texture.char_to_tile_name(' '), None);
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('@'), None);
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('a'), None);
|
|
||||||
assert_eq!(text_texture.char_to_tile_name('z'), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_set_scale() {
|
|
||||||
let mut text_texture = TextTexture::new(1.0);
|
|
||||||
assert_eq!(text_texture.scale(), 1.0);
|
|
||||||
|
|
||||||
text_texture.set_scale(3.0);
|
|
||||||
assert_eq!(text_texture.scale(), 3.0);
|
|
||||||
|
|
||||||
text_texture.set_scale(0.5);
|
|
||||||
assert_eq!(text_texture.scale(), 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_width_empty_string() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
assert_eq!(text_texture.text_width(""), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_width_single_character() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
assert_eq!(text_texture.text_width("A"), 8); // 8 pixels per character at scale 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_width_multiple_characters() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
assert_eq!(text_texture.text_width("ABC"), 24); // 3 * 8 = 24 pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_width_with_scale() {
|
|
||||||
let text_texture = TextTexture::new(2.0);
|
|
||||||
assert_eq!(text_texture.text_width("A"), 16); // 8 * 2 = 16 pixels
|
|
||||||
assert_eq!(text_texture.text_width("ABC"), 48); // 3 * 16 = 48 pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_width_with_unsupported_characters() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
// Only supported characters should be counted
|
|
||||||
assert_eq!(text_texture.text_width("A B"), 16); // A and B only, space ignored
|
|
||||||
assert_eq!(text_texture.text_width("A@B"), 16); // A and B only, @ ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_height() {
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
|
||||||
assert_eq!(text_texture.text_height(), 8); // 8 pixels per character at scale 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_height_with_scale() {
|
|
||||||
let text_texture = TextTexture::new(2.0);
|
|
||||||
assert_eq!(text_texture.text_height(), 16); // 8 * 2 = 16 pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_text_height_with_fractional_scale() {
|
|
||||||
let text_texture = TextTexture::new(1.5);
|
|
||||||
assert_eq!(text_texture.text_height(), 12); // 8 * 1.5 = 12 pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_char_tile_caching() {
|
|
||||||
let mut text_texture = TextTexture::new(1.0);
|
|
||||||
let atlas = create_mock_atlas();
|
|
||||||
|
|
||||||
// First call should cache the tile
|
|
||||||
let tile1 = text_texture.get_char_tile(&atlas, 'A');
|
|
||||||
assert!(tile1.is_some());
|
|
||||||
|
|
||||||
// Second call should use cached tile
|
|
||||||
let tile2 = text_texture.get_char_tile(&atlas, 'A');
|
|
||||||
assert!(tile2.is_some());
|
|
||||||
|
|
||||||
// Both should be the same tile
|
|
||||||
assert_eq!(tile1.unwrap().pos, tile2.unwrap().pos);
|
|
||||||
assert_eq!(tile1.unwrap().size, tile2.unwrap().size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_char_tile_unsupported_character() {
|
|
||||||
let mut text_texture = TextTexture::new(1.0);
|
|
||||||
let atlas = create_mock_atlas();
|
|
||||||
|
|
||||||
let tile = text_texture.get_char_tile(&atlas, ' ');
|
|
||||||
assert!(tile.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_char_tile_missing_from_atlas() {
|
|
||||||
let mut text_texture = TextTexture::new(1.0);
|
|
||||||
let atlas = create_mock_atlas();
|
|
||||||
|
|
||||||
// 'B' is not in our mock atlas
|
|
||||||
let tile = text_texture.get_char_tile(&atlas, 'B');
|
|
||||||
assert!(tile.is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
61
tests/animated.rs
Normal file
61
tests/animated.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use glam::U16Vec2;
|
||||||
|
use pacman::texture::animated::{AnimatedTexture, AnimatedTextureError};
|
||||||
|
use pacman::texture::sprite::AtlasTile;
|
||||||
|
use sdl2::pixels::Color;
|
||||||
|
|
||||||
|
fn mock_atlas_tile(id: u32) -> AtlasTile {
|
||||||
|
AtlasTile {
|
||||||
|
pos: U16Vec2::new(0, 0),
|
||||||
|
size: U16Vec2::new(16, 16),
|
||||||
|
color: Some(Color::RGB(id as u8, 0, 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_animated_texture_creation_errors() {
|
||||||
|
let tiles = vec![mock_atlas_tile(1), mock_atlas_tile(2)];
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
AnimatedTexture::new(tiles.clone(), 0.0).unwrap_err(),
|
||||||
|
AnimatedTextureError::InvalidFrameDuration(0.0)
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
AnimatedTexture::new(tiles, -0.1).unwrap_err(),
|
||||||
|
AnimatedTextureError::InvalidFrameDuration(-0.1)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_animated_texture_advancement() {
|
||||||
|
let tiles = vec![mock_atlas_tile(1), mock_atlas_tile(2), mock_atlas_tile(3)];
|
||||||
|
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(texture.current_frame(), 0);
|
||||||
|
|
||||||
|
texture.tick(0.25);
|
||||||
|
assert_eq!(texture.current_frame(), 2);
|
||||||
|
assert!((texture.time_bank() - 0.05).abs() < 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_animated_texture_wrap_around() {
|
||||||
|
let tiles = vec![mock_atlas_tile(1), mock_atlas_tile(2)];
|
||||||
|
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
||||||
|
|
||||||
|
texture.tick(0.1);
|
||||||
|
assert_eq!(texture.current_frame(), 1);
|
||||||
|
|
||||||
|
texture.tick(0.1);
|
||||||
|
assert_eq!(texture.current_frame(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_animated_texture_single_frame() {
|
||||||
|
let tiles = vec![mock_atlas_tile(1)];
|
||||||
|
let mut texture = AnimatedTexture::new(tiles, 0.1).unwrap();
|
||||||
|
|
||||||
|
texture.tick(0.1);
|
||||||
|
assert_eq!(texture.current_frame(), 0);
|
||||||
|
assert_eq!(texture.current_tile().color.unwrap().r, 1);
|
||||||
|
}
|
||||||
49
tests/blinking.rs
Normal file
49
tests/blinking.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use glam::U16Vec2;
|
||||||
|
use pacman::texture::blinking::BlinkingTexture;
|
||||||
|
use pacman::texture::sprite::AtlasTile;
|
||||||
|
use sdl2::pixels::Color;
|
||||||
|
|
||||||
|
fn mock_atlas_tile(id: u32) -> AtlasTile {
|
||||||
|
AtlasTile {
|
||||||
|
pos: U16Vec2::new(0, 0),
|
||||||
|
size: U16Vec2::new(16, 16),
|
||||||
|
color: Some(Color::RGB(id as u8, 0, 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blinking_texture() {
|
||||||
|
let tile = mock_atlas_tile(1);
|
||||||
|
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||||
|
|
||||||
|
assert_eq!(texture.is_on(), true);
|
||||||
|
|
||||||
|
texture.tick(0.5);
|
||||||
|
assert_eq!(texture.is_on(), false);
|
||||||
|
|
||||||
|
texture.tick(0.5);
|
||||||
|
assert_eq!(texture.is_on(), true);
|
||||||
|
|
||||||
|
texture.tick(0.5);
|
||||||
|
assert_eq!(texture.is_on(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blinking_texture_partial_duration() {
|
||||||
|
let tile = mock_atlas_tile(1);
|
||||||
|
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||||
|
|
||||||
|
texture.tick(0.625);
|
||||||
|
assert_eq!(texture.is_on(), false);
|
||||||
|
assert_eq!(texture.time_bank(), 0.125);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blinking_texture_negative_time() {
|
||||||
|
let tile = mock_atlas_tile(1);
|
||||||
|
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||||
|
|
||||||
|
texture.tick(-0.1);
|
||||||
|
assert_eq!(texture.is_on(), true);
|
||||||
|
assert_eq!(texture.time_bank(), -0.1);
|
||||||
|
}
|
||||||
28
tests/constants.rs
Normal file
28
tests/constants.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use pacman::constants::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw_board_structure() {
|
||||||
|
assert_eq!(RAW_BOARD.len(), BOARD_CELL_SIZE.y as usize);
|
||||||
|
|
||||||
|
for row in RAW_BOARD.iter() {
|
||||||
|
assert_eq!(row.len(), BOARD_CELL_SIZE.x as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test boundaries
|
||||||
|
assert!(RAW_BOARD[0].chars().all(|c| c == '#'));
|
||||||
|
assert!(RAW_BOARD[RAW_BOARD.len() - 1].chars().all(|c| c == '#'));
|
||||||
|
|
||||||
|
// Test tunnel row
|
||||||
|
let tunnel_row = RAW_BOARD[14];
|
||||||
|
assert_eq!(tunnel_row.chars().next().unwrap(), 'T');
|
||||||
|
assert_eq!(tunnel_row.chars().last().unwrap(), 'T');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw_board_content() {
|
||||||
|
let power_pellet_count = RAW_BOARD.iter().flat_map(|row| row.chars()).filter(|&c| c == 'o').count();
|
||||||
|
assert_eq!(power_pellet_count, 4);
|
||||||
|
|
||||||
|
assert!(RAW_BOARD.iter().any(|row| row.contains('X')));
|
||||||
|
assert!(RAW_BOARD.iter().any(|row| row.contains("==")));
|
||||||
|
}
|
||||||
31
tests/direction.rs
Normal file
31
tests/direction.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use glam::IVec2;
|
||||||
|
use pacman::entity::direction::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_direction_opposite() {
|
||||||
|
let test_cases = [
|
||||||
|
(Direction::Up, Direction::Down),
|
||||||
|
(Direction::Down, Direction::Up),
|
||||||
|
(Direction::Left, Direction::Right),
|
||||||
|
(Direction::Right, Direction::Left),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (dir, expected) in test_cases {
|
||||||
|
assert_eq!(dir.opposite(), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_direction_as_ivec2() {
|
||||||
|
let test_cases = [
|
||||||
|
(Direction::Up, -IVec2::Y),
|
||||||
|
(Direction::Down, IVec2::Y),
|
||||||
|
(Direction::Left, -IVec2::X),
|
||||||
|
(Direction::Right, IVec2::X),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (dir, expected) in test_cases {
|
||||||
|
assert_eq!(dir.as_ivec2(), expected);
|
||||||
|
assert_eq!(IVec2::from(dir), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
tests/directional.rs
Normal file
55
tests/directional.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use glam::U16Vec2;
|
||||||
|
use pacman::entity::direction::Direction;
|
||||||
|
use pacman::texture::animated::AnimatedTexture;
|
||||||
|
use pacman::texture::directional::DirectionalAnimatedTexture;
|
||||||
|
use pacman::texture::sprite::AtlasTile;
|
||||||
|
use sdl2::pixels::Color;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn mock_atlas_tile(id: u32) -> AtlasTile {
|
||||||
|
AtlasTile {
|
||||||
|
pos: U16Vec2::new(0, 0),
|
||||||
|
size: U16Vec2::new(16, 16),
|
||||||
|
color: Some(Color::RGB(id as u8, 0, 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mock_animated_texture(id: u32) -> AnimatedTexture {
|
||||||
|
AnimatedTexture::new(vec![mock_atlas_tile(id)], 0.1).expect("Invalid frame duration")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_directional_texture_partial_directions() {
|
||||||
|
let mut textures = HashMap::new();
|
||||||
|
textures.insert(Direction::Up, mock_animated_texture(1));
|
||||||
|
|
||||||
|
let texture = DirectionalAnimatedTexture::new(textures, HashMap::new());
|
||||||
|
|
||||||
|
assert_eq!(texture.texture_count(), 1);
|
||||||
|
assert!(texture.has_direction(Direction::Up));
|
||||||
|
assert!(!texture.has_direction(Direction::Down));
|
||||||
|
assert!(!texture.has_direction(Direction::Left));
|
||||||
|
assert!(!texture.has_direction(Direction::Right));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_directional_texture_all_directions() {
|
||||||
|
let mut textures = HashMap::new();
|
||||||
|
let directions = [
|
||||||
|
(Direction::Up, 1),
|
||||||
|
(Direction::Down, 2),
|
||||||
|
(Direction::Left, 3),
|
||||||
|
(Direction::Right, 4),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (direction, id) in directions {
|
||||||
|
textures.insert(direction, mock_animated_texture(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture = DirectionalAnimatedTexture::new(textures, HashMap::new());
|
||||||
|
|
||||||
|
assert_eq!(texture.texture_count(), 4);
|
||||||
|
for direction in &[Direction::Up, Direction::Down, Direction::Left, Direction::Right] {
|
||||||
|
assert!(texture.has_direction(*direction));
|
||||||
|
}
|
||||||
|
}
|
||||||
21
tests/game.rs
Normal file
21
tests/game.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use pacman::constants::RAW_BOARD;
|
||||||
|
use pacman::map::Map;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_game_map_creation() {
|
||||||
|
let map = Map::new(RAW_BOARD);
|
||||||
|
|
||||||
|
assert!(map.graph.node_count() > 0);
|
||||||
|
assert!(!map.grid_to_node.is_empty());
|
||||||
|
|
||||||
|
// Should find Pac-Man's starting position
|
||||||
|
let pacman_pos = map.find_starting_position(0);
|
||||||
|
assert!(pacman_pos.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_game_score_initialization() {
|
||||||
|
// This would require creating a full Game instance, but we can test the concept
|
||||||
|
let map = Map::new(RAW_BOARD);
|
||||||
|
assert!(map.find_starting_position(0).is_some());
|
||||||
|
}
|
||||||
149
tests/graph.rs
Normal file
149
tests/graph.rs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
use pacman::entity::direction::Direction;
|
||||||
|
use pacman::entity::graph::{EdgePermissions, Graph, Node, Position, Traverser};
|
||||||
|
|
||||||
|
fn create_test_graph() -> Graph {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
let node2 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(16.0, 0.0),
|
||||||
|
});
|
||||||
|
let node3 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 16.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
||||||
|
graph.connect(node1, node3, false, None, Direction::Down).unwrap();
|
||||||
|
|
||||||
|
graph
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_graph_basic_operations() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
let node2 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(16.0, 0.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(graph.node_count(), 2);
|
||||||
|
assert!(graph.get_node(node1).is_some());
|
||||||
|
assert!(graph.get_node(node2).is_some());
|
||||||
|
assert!(graph.get_node(999).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_graph_connect() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
let node2 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(16.0, 0.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(graph.connect(node1, node2, false, None, Direction::Right).is_ok());
|
||||||
|
|
||||||
|
let edge1 = graph.find_edge_in_direction(node1, Direction::Right);
|
||||||
|
let edge2 = graph.find_edge_in_direction(node2, Direction::Left);
|
||||||
|
|
||||||
|
assert!(edge1.is_some());
|
||||||
|
assert!(edge2.is_some());
|
||||||
|
assert_eq!(edge1.unwrap().target, node2);
|
||||||
|
assert_eq!(edge2.unwrap().target, node1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_graph_connect_errors() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(graph.connect(node1, 999, false, None, Direction::Right).is_err());
|
||||||
|
assert!(graph.connect(999, node1, false, None, Direction::Right).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_graph_edge_permissions() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
let node2 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(16.0, 0.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
graph
|
||||||
|
.add_edge(node1, node2, false, None, Direction::Right, EdgePermissions::GhostsOnly)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let edge = graph.find_edge_in_direction(node1, Direction::Right).unwrap();
|
||||||
|
assert_eq!(edge.permissions, EdgePermissions::GhostsOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_traverser_basic() {
|
||||||
|
let graph = create_test_graph();
|
||||||
|
let mut traverser = Traverser::new(&graph, 0, Direction::Left, &|_| true);
|
||||||
|
|
||||||
|
traverser.set_next_direction(Direction::Up);
|
||||||
|
assert!(traverser.next_direction.is_some());
|
||||||
|
assert_eq!(traverser.next_direction.unwrap().0, Direction::Up);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_traverser_advance() {
|
||||||
|
let graph = create_test_graph();
|
||||||
|
let mut traverser = Traverser::new(&graph, 0, Direction::Right, &|_| true);
|
||||||
|
|
||||||
|
traverser.advance(&graph, 5.0, &|_| true);
|
||||||
|
|
||||||
|
match traverser.position {
|
||||||
|
Position::BetweenNodes { from, to, traversed } => {
|
||||||
|
assert_eq!(from, 0);
|
||||||
|
assert_eq!(to, 1);
|
||||||
|
assert_eq!(traversed, 5.0);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected to be between nodes"),
|
||||||
|
}
|
||||||
|
|
||||||
|
traverser.advance(&graph, 3.0, &|_| true);
|
||||||
|
|
||||||
|
match traverser.position {
|
||||||
|
Position::BetweenNodes { from, to, traversed } => {
|
||||||
|
assert_eq!(from, 0);
|
||||||
|
assert_eq!(to, 1);
|
||||||
|
assert_eq!(traversed, 8.0);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected to be between nodes"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_traverser_with_permissions() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
let node2 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(16.0, 0.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
graph
|
||||||
|
.add_edge(node1, node2, false, None, Direction::Right, EdgePermissions::GhostsOnly)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Pacman can't traverse ghost-only edges
|
||||||
|
let mut traverser = Traverser::new(&graph, node1, Direction::Right, &|edge| {
|
||||||
|
matches!(edge.permissions, EdgePermissions::All)
|
||||||
|
});
|
||||||
|
|
||||||
|
traverser.advance(&graph, 5.0, &|edge| matches!(edge.permissions, EdgePermissions::All));
|
||||||
|
|
||||||
|
// Should still be at the node since it can't traverse
|
||||||
|
assert!(traverser.position.is_at_node());
|
||||||
|
}
|
||||||
19
tests/helpers.rs
Normal file
19
tests/helpers.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use glam::{IVec2, UVec2};
|
||||||
|
use pacman::helpers::centered_with_size;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_centered_with_size() {
|
||||||
|
let test_cases = [
|
||||||
|
((100, 100), (50, 30), (75, 85)),
|
||||||
|
((50, 50), (51, 31), (25, 35)),
|
||||||
|
((0, 0), (100, 100), (-50, -50)),
|
||||||
|
((-100, -50), (80, 40), (-140, -70)),
|
||||||
|
((1000, 1000), (1000, 1000), (500, 500)),
|
||||||
|
];
|
||||||
|
|
||||||
|
for ((pos_x, pos_y), (size_x, size_y), (expected_x, expected_y)) in test_cases {
|
||||||
|
let rect = centered_with_size(IVec2::new(pos_x, pos_y), UVec2::new(size_x, size_y));
|
||||||
|
assert_eq!(rect.origin(), (expected_x, expected_y));
|
||||||
|
assert_eq!(rect.size(), (size_x, size_y));
|
||||||
|
}
|
||||||
|
}
|
||||||
86
tests/map_builder.rs
Normal file
86
tests/map_builder.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
use glam::Vec2;
|
||||||
|
use pacman::constants::{BOARD_CELL_SIZE, CELL_SIZE};
|
||||||
|
use pacman::map::Map;
|
||||||
|
|
||||||
|
fn create_minimal_test_board() -> [&'static str; BOARD_CELL_SIZE.y as usize] {
|
||||||
|
let mut board = [""; BOARD_CELL_SIZE.y as usize];
|
||||||
|
board[0] = "############################";
|
||||||
|
board[1] = "#............##............#";
|
||||||
|
board[2] = "#.####.#####.##.#####.####.#";
|
||||||
|
board[3] = "#o####.#####.##.#####.####o#";
|
||||||
|
board[4] = "#.####.#####.##.#####.####.#";
|
||||||
|
board[5] = "#..........................#";
|
||||||
|
board[6] = "#.####.##.########.##.####.#";
|
||||||
|
board[7] = "#.####.##.########.##.####.#";
|
||||||
|
board[8] = "#......##....##....##......#";
|
||||||
|
board[9] = "######.##### ## #####.######";
|
||||||
|
board[10] = " #.##### ## #####.# ";
|
||||||
|
board[11] = " #.## == ##.# ";
|
||||||
|
board[12] = " #.## ######## ##.# ";
|
||||||
|
board[13] = "######.## ######## ##.######";
|
||||||
|
board[14] = "T . ######## . T";
|
||||||
|
board[15] = "######.## ######## ##.######";
|
||||||
|
board[16] = " #.## ######## ##.# ";
|
||||||
|
board[17] = " #.## ##.# ";
|
||||||
|
board[18] = " #.## ######## ##.# ";
|
||||||
|
board[19] = "######.## ######## ##.######";
|
||||||
|
board[20] = "#............##............#";
|
||||||
|
board[21] = "#.####.#####.##.#####.####.#";
|
||||||
|
board[22] = "#.####.#####.##.#####.####.#";
|
||||||
|
board[23] = "#o..##.......X .......##..o#";
|
||||||
|
board[24] = "###.##.##.########.##.##.###";
|
||||||
|
board[25] = "###.##.##.########.##.##.###";
|
||||||
|
board[26] = "#......##....##....##......#";
|
||||||
|
board[27] = "#.##########.##.##########.#";
|
||||||
|
board[28] = "#.##########.##.##########.#";
|
||||||
|
board[29] = "#..........................#";
|
||||||
|
board[30] = "############################";
|
||||||
|
board
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_creation() {
|
||||||
|
let board = create_minimal_test_board();
|
||||||
|
let map = Map::new(board);
|
||||||
|
|
||||||
|
assert!(map.graph.node_count() > 0);
|
||||||
|
assert!(!map.grid_to_node.is_empty());
|
||||||
|
|
||||||
|
// Check that some connections were made
|
||||||
|
let mut has_connections = false;
|
||||||
|
for intersection in &map.graph.adjacency_list {
|
||||||
|
if intersection.edges().next().is_some() {
|
||||||
|
has_connections = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(has_connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_starting_positions() {
|
||||||
|
let board = create_minimal_test_board();
|
||||||
|
let map = Map::new(board);
|
||||||
|
|
||||||
|
let pacman_pos = map.find_starting_position(0);
|
||||||
|
assert!(pacman_pos.is_some());
|
||||||
|
assert!(pacman_pos.unwrap().x < BOARD_CELL_SIZE.x);
|
||||||
|
assert!(pacman_pos.unwrap().y < BOARD_CELL_SIZE.y);
|
||||||
|
|
||||||
|
let nonexistent_pos = map.find_starting_position(99);
|
||||||
|
assert_eq!(nonexistent_pos, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_node_positions() {
|
||||||
|
let board = create_minimal_test_board();
|
||||||
|
let map = Map::new(board);
|
||||||
|
|
||||||
|
for (grid_pos, &node_id) in &map.grid_to_node {
|
||||||
|
let node = map.graph.get_node(node_id).unwrap();
|
||||||
|
let expected_pos = Vec2::new((grid_pos.x * CELL_SIZE as i32) as f32, (grid_pos.y * CELL_SIZE as i32) as f32)
|
||||||
|
+ Vec2::splat(CELL_SIZE as f32 / 2.0);
|
||||||
|
|
||||||
|
assert_eq!(node.position, expected_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
107
tests/pacman.rs
Normal file
107
tests/pacman.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use pacman::entity::direction::Direction;
|
||||||
|
use pacman::entity::graph::{Graph, Node};
|
||||||
|
use pacman::entity::pacman::Pacman;
|
||||||
|
use pacman::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
|
||||||
|
use sdl2::keyboard::Keycode;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn create_test_graph() -> Graph {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
let node2 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(16.0, 0.0),
|
||||||
|
});
|
||||||
|
let node3 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 16.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
||||||
|
graph.connect(node1, node3, false, None, Direction::Down).unwrap();
|
||||||
|
|
||||||
|
graph
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_test_atlas() -> SpriteAtlas {
|
||||||
|
let mut frames = HashMap::new();
|
||||||
|
let directions = ["up", "down", "left", "right"];
|
||||||
|
|
||||||
|
for (i, dir) in directions.iter().enumerate() {
|
||||||
|
frames.insert(
|
||||||
|
format!("pacman/{dir}_a.png"),
|
||||||
|
MapperFrame {
|
||||||
|
x: i as u16 * 16,
|
||||||
|
y: 0,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
frames.insert(
|
||||||
|
format!("pacman/{dir}_b.png"),
|
||||||
|
MapperFrame {
|
||||||
|
x: i as u16 * 16,
|
||||||
|
y: 16,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.insert(
|
||||||
|
"pacman/full.png".to_string(),
|
||||||
|
MapperFrame {
|
||||||
|
x: 64,
|
||||||
|
y: 0,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mapper = AtlasMapper { frames };
|
||||||
|
let dummy_texture = unsafe { std::mem::zeroed() };
|
||||||
|
SpriteAtlas::new(dummy_texture, mapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pacman_creation() {
|
||||||
|
let graph = create_test_graph();
|
||||||
|
let atlas = create_test_atlas();
|
||||||
|
let pacman = Pacman::new(&graph, 0, &atlas);
|
||||||
|
|
||||||
|
assert!(pacman.traverser.position.is_at_node());
|
||||||
|
assert_eq!(pacman.traverser.direction, Direction::Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pacman_key_handling() {
|
||||||
|
let graph = create_test_graph();
|
||||||
|
let atlas = create_test_atlas();
|
||||||
|
let mut pacman = Pacman::new(&graph, 0, &atlas);
|
||||||
|
|
||||||
|
let test_cases = [
|
||||||
|
(Keycode::Up, Direction::Up),
|
||||||
|
(Keycode::Down, Direction::Down),
|
||||||
|
(Keycode::Left, Direction::Left),
|
||||||
|
(Keycode::Right, Direction::Right),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (key, expected_direction) in test_cases {
|
||||||
|
pacman.handle_key(key);
|
||||||
|
assert!(pacman.traverser.next_direction.is_some() || pacman.traverser.direction == expected_direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pacman_invalid_key() {
|
||||||
|
let graph = create_test_graph();
|
||||||
|
let atlas = create_test_atlas();
|
||||||
|
let mut pacman = Pacman::new(&graph, 0, &atlas);
|
||||||
|
|
||||||
|
let original_direction = pacman.traverser.direction;
|
||||||
|
let original_next_direction = pacman.traverser.next_direction;
|
||||||
|
|
||||||
|
pacman.handle_key(Keycode::Space);
|
||||||
|
assert_eq!(pacman.traverser.direction, original_direction);
|
||||||
|
assert_eq!(pacman.traverser.next_direction, original_next_direction);
|
||||||
|
}
|
||||||
46
tests/parser.rs
Normal file
46
tests/parser.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use pacman::constants::{BOARD_CELL_SIZE, RAW_BOARD};
|
||||||
|
use pacman::map::parser::{MapTileParser, ParseError};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_character() {
|
||||||
|
let test_cases = [
|
||||||
|
('#', pacman::constants::MapTile::Wall),
|
||||||
|
('.', pacman::constants::MapTile::Pellet),
|
||||||
|
('o', pacman::constants::MapTile::PowerPellet),
|
||||||
|
(' ', pacman::constants::MapTile::Empty),
|
||||||
|
('T', pacman::constants::MapTile::Tunnel),
|
||||||
|
('X', pacman::constants::MapTile::Empty),
|
||||||
|
('=', pacman::constants::MapTile::Wall),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (char, _expected) in test_cases {
|
||||||
|
assert!(matches!(MapTileParser::parse_character(char).unwrap(), _expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(MapTileParser::parse_character('Z').is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_board() {
|
||||||
|
let result = MapTileParser::parse_board(RAW_BOARD);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let parsed = result.unwrap();
|
||||||
|
assert_eq!(parsed.tiles.len(), BOARD_CELL_SIZE.x as usize);
|
||||||
|
assert_eq!(parsed.tiles[0].len(), BOARD_CELL_SIZE.y as usize);
|
||||||
|
assert!(parsed.house_door[0].is_some());
|
||||||
|
assert!(parsed.house_door[1].is_some());
|
||||||
|
assert!(parsed.tunnel_ends[0].is_some());
|
||||||
|
assert!(parsed.tunnel_ends[1].is_some());
|
||||||
|
assert!(parsed.pacman_start.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_board_invalid_character() {
|
||||||
|
let mut invalid_board = RAW_BOARD.clone();
|
||||||
|
invalid_board[0] = "###########################Z";
|
||||||
|
|
||||||
|
let result = MapTileParser::parse_board(invalid_board);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), ParseError::UnknownCharacter('Z')));
|
||||||
|
}
|
||||||
78
tests/sprite.rs
Normal file
78
tests/sprite.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use pacman::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
|
||||||
|
use sdl2::pixels::Color;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn mock_texture() -> sdl2::render::Texture<'static> {
|
||||||
|
unsafe { std::mem::transmute(0usize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sprite_atlas_basic() {
|
||||||
|
let mut frames = HashMap::new();
|
||||||
|
frames.insert(
|
||||||
|
"test".to_string(),
|
||||||
|
MapperFrame {
|
||||||
|
x: 10,
|
||||||
|
y: 20,
|
||||||
|
width: 32,
|
||||||
|
height: 64,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mapper = AtlasMapper { frames };
|
||||||
|
let texture = mock_texture();
|
||||||
|
let atlas = SpriteAtlas::new(texture, mapper);
|
||||||
|
|
||||||
|
let tile = atlas.get_tile("test");
|
||||||
|
assert!(tile.is_some());
|
||||||
|
let tile = tile.unwrap();
|
||||||
|
assert_eq!(tile.pos, glam::U16Vec2::new(10, 20));
|
||||||
|
assert_eq!(tile.size, glam::U16Vec2::new(32, 64));
|
||||||
|
assert_eq!(tile.color, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sprite_atlas_multiple_tiles() {
|
||||||
|
let mut frames = HashMap::new();
|
||||||
|
frames.insert(
|
||||||
|
"tile1".to_string(),
|
||||||
|
MapperFrame {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
frames.insert(
|
||||||
|
"tile2".to_string(),
|
||||||
|
MapperFrame {
|
||||||
|
x: 32,
|
||||||
|
y: 0,
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mapper = AtlasMapper { frames };
|
||||||
|
let texture = mock_texture();
|
||||||
|
let atlas = SpriteAtlas::new(texture, mapper);
|
||||||
|
|
||||||
|
assert_eq!(atlas.tiles_count(), 2);
|
||||||
|
assert!(atlas.has_tile("tile1"));
|
||||||
|
assert!(atlas.has_tile("tile2"));
|
||||||
|
assert!(!atlas.has_tile("tile3"));
|
||||||
|
assert!(atlas.get_tile("nonexistent").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sprite_atlas_color() {
|
||||||
|
let mapper = AtlasMapper { frames: HashMap::new() };
|
||||||
|
let texture = mock_texture();
|
||||||
|
let mut atlas = SpriteAtlas::new(texture, mapper);
|
||||||
|
|
||||||
|
assert_eq!(atlas.default_color(), None);
|
||||||
|
|
||||||
|
let color = Color::RGB(255, 0, 0);
|
||||||
|
atlas.set_color(color);
|
||||||
|
assert_eq!(atlas.default_color(), Some(color));
|
||||||
|
}
|
||||||
17
web.build.ts
17
web.build.ts
@@ -42,8 +42,9 @@ async function build(release: boolean, env: Record<string, string>) {
|
|||||||
release ? "--release" : ""
|
release ? "--release" : ""
|
||||||
}`.env(env);
|
}`.env(env);
|
||||||
|
|
||||||
log("Generating CSS");
|
log("Invoking @tailwindcss/cli");
|
||||||
await $`pnpx postcss-cli ./assets/site/styles.scss -o ./assets/site/build.css`;
|
// unfortunately, bunx doesn't seem to work with @tailwindcss/cli, so we have to use npx directly
|
||||||
|
await $`npx --yes @tailwindcss/cli --minify --input styles.css --output build.css --cwd assets/site`;
|
||||||
|
|
||||||
const buildType = release ? "release" : "debug";
|
const buildType = release ? "release" : "debug";
|
||||||
const siteFolder = resolve("assets/site");
|
const siteFolder = resolve("assets/site");
|
||||||
@@ -52,11 +53,13 @@ async function build(release: boolean, env: Record<string, string>) {
|
|||||||
|
|
||||||
// The files to copy into 'dist'
|
// The files to copy into 'dist'
|
||||||
const files = [
|
const files = [
|
||||||
...["index.html", "favicon.ico", "build.css"].map((file) => ({
|
...["index.html", "favicon.ico", "build.css", "TerminalVector.ttf"].map(
|
||||||
src: join(siteFolder, file),
|
(file) => ({
|
||||||
dest: join(dist, file),
|
src: join(siteFolder, file),
|
||||||
optional: false,
|
dest: join(dist, file),
|
||||||
})),
|
optional: false,
|
||||||
|
})
|
||||||
|
),
|
||||||
...["pacman.wasm", "pacman.js", "deps/pacman.data"].map((file) => ({
|
...["pacman.wasm", "pacman.js", "deps/pacman.data"].map((file) => ({
|
||||||
src: join(outputFolder, file),
|
src: join(outputFolder, file),
|
||||||
dest: join(dist, file.split("/").pop() || file),
|
dest: join(dist, file.split("/").pop() || file),
|
||||||
|
|||||||
Reference in New Issue
Block a user