From 1e57b563280582c9bd1c385d4b44458d66885b8b Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 11 Jul 2026 14:01:49 +0200 Subject: end game screen, fix occlusion --- src/game.rs | 38 +++++++++++++++++++++++++------ src/main.rs | 5 ++++- src/renderer.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 96 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/game.rs b/src/game.rs index a17526b..f66ca40 100644 --- a/src/game.rs +++ b/src/game.rs @@ -544,7 +544,11 @@ fn place_entities( } // --- hallucinated pickups --- - let fake_count = if depth >= 3 { rng.random_range(1..=2) } else { 0 }; + let fake_count = if depth >= 3 { + rng.random_range(1..=2) + } else { + 0 + }; for _ in 0..fake_count { if next >= candidates.len() { break; @@ -673,6 +677,9 @@ pub struct Game { // ---- controls overlay ---- pub show_controls: bool, + + // ---- death screen ---- + pub death_frames: u32, } fn smoothstep(t: f64) -> f64 { @@ -724,6 +731,7 @@ impl Game { flash_color: 0, flash_alpha: 0.0, show_controls: true, + death_frames: 0, } } @@ -802,10 +810,11 @@ impl Game { if done { self.state = GameState::ResolvingTurn; self.resolve_turn(); - self.state = GameState::WaitingForInput; + if self.state != GameState::GameOver { + self.state = GameState::WaitingForInput; + } return true; } - false } GameState::AnimatingEnemies => { @@ -814,14 +823,22 @@ impl Game { self.enemy_anim_t = 0.0; self.state = GameState::ResolvingTurn; self.resolve_turn(); - self.state = GameState::WaitingForInput; + if self.state != GameState::GameOver { + self.state = GameState::WaitingForInput; + } return true; } - false } - _ => false, + _ => {} + } + + // Count frames since death so the death screen has a minimum display time. + if self.state == GameState::GameOver { + self.death_frames += 1; } + + false } /// Attempt to walk one tile in grid direction (dx, dy). @@ -1160,7 +1177,9 @@ impl Game { fn advance_turn(&mut self) { self.state = GameState::ResolvingTurn; self.resolve_turn(); - self.state = GameState::WaitingForInput; + if self.state != GameState::GameOver { + self.state = GameState::WaitingForInput; + } } /// Apply all per-turn effects (weakness, heartbeat, HP drain, message decay). @@ -1248,4 +1267,9 @@ impl Game { pub fn weakness_factor(&self) -> f64 { (self.weakness as f64 / MAX_WEAKNESS as f64) * 2.0 } + + /// Compute the final score. + pub fn score(&self) -> u64 { + (self.kills * 100) + (self.depth.pow(2) * 50) + (self.weapon_base_damage as u64 * 500) + } } diff --git a/src/main.rs b/src/main.rs index a59edcd..e82d939 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,7 +150,10 @@ fn main() { // ---- death check ---- if game.state == GameState::GameOver { - std::process::exit(0); + // Minimum 30 frames (~0.5s) before input is accepted. + if game.death_frames >= 30 && cur_any { + std::process::exit(0); + } } // ---- win check (placeholder — becomes staircase in Phase 1) ---- diff --git a/src/renderer.rs b/src/renderer.rs index 6eff9fa..aa87b54 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,4 +1,4 @@ -use crate::game::{Entity, Game, PickupType}; +use crate::game::{Entity, Game, GameState, PickupType}; use crate::texture::TEX_SIZE; // ---- simple 5×5 ASCII glyphs ---- @@ -287,6 +287,11 @@ impl Renderer { } } } + + // ---- death screen ---- + if game.state == GameState::GameOver { + self.draw_death_screen(buffer, game); + } } // ---- entity sprites --------------------------------------------------- @@ -350,13 +355,6 @@ impl Renderer { let entity_h = (self.screen_h as f64 / cam_z * 0.4) as isize; let sy = (half_h - entity_h as f64 * 0.3) as isize; - // Occlusion: entity is visible only if its centre column is not behind a wall. - // Compare Euclidean distance to entity vs Euclidean distance to wall. - let col = sx.clamp(0, self.screen_w as isize - 1) as usize; - if cam_z >= wall_euclidean[col] { - continue; - } - let scale = (entity_h.max(6) as f64 / LETTER_H as f64).max(0.5); let (ch, color) = entity_char_and_color(entity); @@ -585,6 +583,61 @@ impl Renderer { let hint_w = hint.len() * (LETTER_W + 1) - 1; self.draw_text(buffer, cx - hint_w / 2, y, hint, hint_color); } + + fn draw_death_screen(&self, buffer: &mut [u32], game: &Game) { + // Full darken. + for py in 0..self.screen_h { + for px in 0..self.screen_w { + let idx = py * self.screen_w + px; + let c = buffer[idx]; + let r = ((c >> 16) & 0xFF) as u32; + let g = ((c >> 8) & 0xFF) as u32; + let b = (c & 0xFF) as u32; + buffer[idx] = 0xFF00_0000 | ((r / 5) << 16) | ((g / 5) << 8) | (b / 5); + } + } + + let cx = self.screen_w / 2; + let mut y = self.screen_h as usize / 2 - 40; + + let title_color = 0xFF_CC3333; + let text_color = 0xFF_CCCCCC; + let score_color = 0xFF_CC8844; + let hint_color = 0xFF_666666; + + let title = "YOU HAVE DIED"; + let title_w = title.len() * (LETTER_W + 1) - 1; + self.draw_text(buffer, cx - title_w / 2, y, title, title_color); + y += LETTER_H + 12; + + let msg = "YOU HAD BECOME YET ANOTHER"; + let msg_w = msg.len() * (LETTER_W + 1) - 1; + self.draw_text(buffer, cx - msg_w / 2, y, msg, text_color); + y += LETTER_H + 4; + + let msg2 = "LEGEND OF THIS DUNGEON."; + let msg2_w = msg2.len() * (LETTER_W + 1) - 1; + self.draw_text(buffer, cx - msg2_w / 2, y, msg2, text_color); + y += LETTER_H + 16; + + let score = game.score(); + let score_str = format!("SCORE: {}", score); + let score_w = score_str.len() * (LETTER_W + 1) - 1; + self.draw_text(buffer, cx - score_w / 2, y, &score_str, score_color); + y += LETTER_H + 4; + + let kills_str = format!( + "KILLS: {} DEPTH: {} WPN:{}", + game.kills, game.depth, game.weapon_base_damage + ); + let kills_w = kills_str.len() * (LETTER_W + 1) - 1; + self.draw_text(buffer, cx - kills_w / 2, y, &kills_str, text_color); + y += LETTER_H + 20; + + let hint = "PRESS ANY KEY TO EXIT"; + let hint_w = hint.len() * (LETTER_W + 1) - 1; + self.draw_text(buffer, cx - hint_w / 2, y, hint, hint_color); + } } // ---- helpers ---- -- cgit v1.2.3