mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 09:15:46 -06:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a230d15ffc |
2
Justfile
2
Justfile
@@ -1,7 +1,7 @@
|
||||
set shell := ["bash", "-c"]
|
||||
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
||||
|
||||
coverage_exclude_pattern := "app.rs|audio.rs"
|
||||
coverage_exclude_pattern := "app.rs|audio.rs|error.rs"
|
||||
|
||||
# Display report (for humans)
|
||||
report-coverage: coverage
|
||||
|
||||
@@ -49,37 +49,13 @@ use glam::UVec2;
|
||||
use sdl2::render::{Canvas, RenderTarget};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
||||
|
||||
/// A text texture that renders characters from the atlas.
|
||||
pub struct TextTexture {
|
||||
char_map: HashMap<char, AtlasTile>,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
impl TextTexture {
|
||||
/// Creates a new text texture with the given atlas and scale.
|
||||
pub fn new(scale: f32) -> Self {
|
||||
Self {
|
||||
char_map: HashMap::new(),
|
||||
scale,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a character to its atlas tile, handling special characters.
|
||||
fn get_char_tile(&mut self, atlas: &SpriteAtlas, c: char) -> Option<AtlasTile> {
|
||||
if let Some(tile) = self.char_map.get(&c) {
|
||||
return Some(*tile);
|
||||
}
|
||||
|
||||
let tile_name = self.char_to_tile_name(c)?;
|
||||
let tile = atlas.get_tile(&tile_name)?;
|
||||
self.char_map.insert(c, tile);
|
||||
Some(tile)
|
||||
}
|
||||
use crate::{
|
||||
error::{GameError, TextureError},
|
||||
texture::sprite::{AtlasTile, SpriteAtlas},
|
||||
};
|
||||
|
||||
/// Converts a character to its tile name in the atlas.
|
||||
fn char_to_tile_name(&self, c: char) -> Option<String> {
|
||||
fn char_to_tile_name(c: char) -> Option<String> {
|
||||
let name = match c {
|
||||
// Letters A-Z
|
||||
'A'..='Z' | '0'..='9' => format!("text/{c}.png"),
|
||||
@@ -98,6 +74,51 @@ impl TextTexture {
|
||||
Some(name)
|
||||
}
|
||||
|
||||
/// A text texture that renders characters from the atlas.
|
||||
#[derive(Debug)]
|
||||
pub struct TextTexture {
|
||||
char_map: HashMap<char, AtlasTile>,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
impl Default for TextTexture {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scale: 1.0,
|
||||
char_map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextTexture {
|
||||
/// Creates a new text texture with the given scale.
|
||||
pub fn new(scale: f32) -> Self {
|
||||
Self {
|
||||
scale,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_char_map(&self) -> &HashMap<char, AtlasTile> {
|
||||
&self.char_map
|
||||
}
|
||||
|
||||
pub fn get_tile(&mut self, c: char, atlas: &mut SpriteAtlas) -> Result<Option<&mut AtlasTile>> {
|
||||
if self.char_map.contains_key(&c) {
|
||||
return Ok(self.char_map.get_mut(&c));
|
||||
}
|
||||
|
||||
if let Some(tile_name) = char_to_tile_name(c) {
|
||||
let tile = atlas
|
||||
.get_tile(&tile_name)
|
||||
.ok_or(GameError::Texture(TextureError::AtlasTileNotFound(tile_name)))?;
|
||||
self.char_map.insert(c, tile);
|
||||
Ok(self.char_map.get_mut(&c))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a string of text at the given position.
|
||||
pub fn render<C: RenderTarget>(
|
||||
&mut self,
|
||||
@@ -108,13 +129,16 @@ impl TextTexture {
|
||||
) -> Result<()> {
|
||||
let mut x_offset = 0;
|
||||
let char_width = (8.0 * self.scale) as u32;
|
||||
let char_height = (8.0 * self.scale) as u32;
|
||||
let char_height = self.text_height();
|
||||
|
||||
for c in text.chars() {
|
||||
if let Some(mut tile) = self.get_char_tile(atlas, c) {
|
||||
// Get the tile from the char_map, or insert it if it doesn't exist
|
||||
if let Some(tile) = self.get_tile(c, atlas)? {
|
||||
// Render the tile if it exists
|
||||
let dest = sdl2::rect::Rect::new((position.x + x_offset) as i32, position.y as i32, char_width, char_height);
|
||||
tile.render(canvas, atlas, dest)?;
|
||||
}
|
||||
|
||||
// Always advance x_offset for all characters (including spaces)
|
||||
x_offset += char_width;
|
||||
}
|
||||
@@ -138,7 +162,7 @@ impl TextTexture {
|
||||
let mut width = 0;
|
||||
|
||||
for c in text.chars() {
|
||||
if self.char_to_tile_name(c).is_some() {
|
||||
if char_to_tile_name(c).is_some() || c == ' ' {
|
||||
width += char_width;
|
||||
}
|
||||
}
|
||||
|
||||
39
tests/common/mod.rs
Normal file
39
tests/common/mod.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pacman::{
|
||||
asset::{get_asset_bytes, Asset},
|
||||
texture::sprite::SpriteAtlas,
|
||||
};
|
||||
use sdl2::{
|
||||
image::LoadTexture,
|
||||
render::{Canvas, Texture, TextureCreator},
|
||||
video::{Window, WindowContext},
|
||||
Sdl,
|
||||
};
|
||||
|
||||
pub fn setup_sdl() -> Result<(Canvas<Window>, TextureCreator<WindowContext>, Sdl), String> {
|
||||
let sdl_context = sdl2::init()?;
|
||||
let video_subsystem = sdl_context.video()?;
|
||||
let window = video_subsystem
|
||||
.window("test", 800, 600)
|
||||
.position_centered()
|
||||
.hidden()
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
|
||||
let texture_creator = canvas.texture_creator();
|
||||
Ok((canvas, texture_creator, sdl_context))
|
||||
}
|
||||
|
||||
pub fn create_atlas(canvas: &mut sdl2::render::Canvas<sdl2::video::Window>) -> SpriteAtlas {
|
||||
let texture_creator = canvas.texture_creator();
|
||||
let atlas_bytes = get_asset_bytes(Asset::Atlas).unwrap();
|
||||
let atlas_json = get_asset_bytes(Asset::AtlasJson).unwrap();
|
||||
|
||||
let texture = texture_creator.load_texture_bytes(&atlas_bytes).unwrap();
|
||||
let texture: Texture<'static> = unsafe { std::mem::transmute(texture) };
|
||||
|
||||
let mapper: pacman::texture::sprite::AtlasMapper = serde_json::from_slice(&atlas_json).unwrap();
|
||||
|
||||
SpriteAtlas::new(texture, mapper)
|
||||
}
|
||||
109
tests/text.rs
Normal file
109
tests/text.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use pacman::texture::{sprite::SpriteAtlas, text::TextTexture};
|
||||
|
||||
use crate::common::create_atlas;
|
||||
|
||||
mod common;
|
||||
|
||||
/// Helper function to get all characters that should be in the atlas
|
||||
fn get_all_chars() -> String {
|
||||
let mut chars = Vec::new();
|
||||
chars.extend('A'..='Z');
|
||||
chars.extend('0'..='9');
|
||||
chars.extend(['!', '-', '"', '/']);
|
||||
chars.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Helper function to check if a character is in the atlas and char_map
|
||||
fn check_char(text_texture: &mut TextTexture, atlas: &mut SpriteAtlas, c: char) {
|
||||
// Check that the character is not in the char_map yet
|
||||
assert!(
|
||||
!text_texture.get_char_map().contains_key(&c),
|
||||
"Character {c} should not yet be in char_map"
|
||||
);
|
||||
|
||||
// Get the tile from the atlas, which caches the tile in the char_map
|
||||
let tile = text_texture.get_tile(c, atlas);
|
||||
|
||||
assert!(tile.is_ok(), "Failed to get tile for character {c}");
|
||||
assert!(tile.unwrap().is_some(), "Tile for character {c} not found in atlas");
|
||||
|
||||
// Check that the tile is now cached in the char_map
|
||||
assert!(
|
||||
text_texture.get_char_map().contains_key(&c),
|
||||
"Tile for character {c} was not cached in char_map"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chars() -> Result<(), String> {
|
||||
let (mut canvas, ..) = common::setup_sdl().map_err(|e| e.to_string())?;
|
||||
let mut atlas = create_atlas(&mut canvas);
|
||||
let mut text_texture = TextTexture::default();
|
||||
|
||||
get_all_chars()
|
||||
.chars()
|
||||
.for_each(|c| check_char(&mut text_texture, &mut atlas, c));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render() -> Result<(), String> {
|
||||
let (mut canvas, ..) = common::setup_sdl().map_err(|e| e.to_string())?;
|
||||
let mut atlas = create_atlas(&mut canvas);
|
||||
let mut text_texture = TextTexture::default();
|
||||
|
||||
let test_strings = vec!["Hello, world!".to_string(), get_all_chars()];
|
||||
|
||||
for string in test_strings {
|
||||
if let Err(e) = text_texture.render(&mut canvas, &mut atlas, &string, glam::UVec2::new(0, 0)) {
|
||||
return Err(e.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_width() -> Result<(), String> {
|
||||
let text_texture = TextTexture::default();
|
||||
|
||||
let test_strings = vec!["Hello, world!".to_string(), get_all_chars()];
|
||||
|
||||
for string in test_strings {
|
||||
let width = text_texture.text_width(&string);
|
||||
let height = text_texture.text_height();
|
||||
|
||||
assert!(width > 0, "Width for string {string} should be greater than 0");
|
||||
assert!(height > 0, "Height for string {string} should be greater than 0");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_scale() -> Result<(), String> {
|
||||
let string = "ABCDEFG !-/\"";
|
||||
let base_width = (string.len() * 8) as u32;
|
||||
|
||||
let mut text_texture = TextTexture::new(0.5);
|
||||
|
||||
assert_eq!(text_texture.scale(), 0.5);
|
||||
assert_eq!(text_texture.text_height(), 4);
|
||||
assert_eq!(text_texture.text_width(""), 0);
|
||||
assert_eq!(text_texture.text_width(string), base_width / 2);
|
||||
|
||||
text_texture.set_scale(2.0);
|
||||
assert_eq!(text_texture.scale(), 2.0);
|
||||
assert_eq!(text_texture.text_height(), 16);
|
||||
assert_eq!(text_texture.text_width(string), base_width * 2);
|
||||
assert_eq!(text_texture.text_width(""), 0);
|
||||
|
||||
text_texture.set_scale(1.0);
|
||||
assert_eq!(text_texture.scale(), 1.0);
|
||||
assert_eq!(text_texture.text_height(), 8);
|
||||
assert_eq!(text_texture.text_width(string), base_width);
|
||||
assert_eq!(text_texture.text_width(""), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user