diff options
| author | salmonik <salmonik@tuta.io> | 2026-07-11 14:01:49 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-07-11 14:01:49 +0200 |
| commit | 1e57b563280582c9bd1c385d4b44458d66885b8b (patch) | |
| tree | 54c81a13b73511ff6419f5385e36e4ee3f680311 | |
| parent | Create AGENTS.md (diff) | |
| download | dungeon-1e57b563280582c9bd1c385d4b44458d66885b8b.tar.gz dungeon-1e57b563280582c9bd1c385d4b44458d66885b8b.zip | |
end game screen, fix occlusion
| -rw-r--r-- | src/game.rs | 38 | ||||
| -rw-r--r-- | src/main.rs | 5 | ||||
| -rw-r--r-- | src/renderer.rs | 69 |
3 files changed, 96 insertions, 16 deletions
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( | |||
| 544 | } | 544 | } |
| 545 | 545 | ||
| 546 | // --- hallucinated pickups --- | 546 | // --- hallucinated pickups --- |
| 547 | let fake_count = if depth >= 3 { rng.random_range(1..=2) } else { 0 }; | 547 | let fake_count = if depth >= 3 { |
| 548 | rng.random_range(1..=2) | ||
| 549 | } else { | ||
| 550 | 0 | ||
| 551 | }; | ||
| 548 | for _ in 0..fake_count { | 552 | for _ in 0..fake_count { |
| 549 | if next >= candidates.len() { | 553 | if next >= candidates.len() { |
| 550 | break; | 554 | break; |
| @@ -673,6 +677,9 @@ pub struct Game { | |||
| 673 | 677 | ||
| 674 | // ---- controls overlay ---- | 678 | // ---- controls overlay ---- |
| 675 | pub show_controls: bool, | 679 | pub show_controls: bool, |
| 680 | |||
| 681 | // ---- death screen ---- | ||
| 682 | pub death_frames: u32, | ||
| 676 | } | 683 | } |
| 677 | 684 | ||
| 678 | fn smoothstep(t: f64) -> f64 { | 685 | fn smoothstep(t: f64) -> f64 { |
| @@ -724,6 +731,7 @@ impl Game { | |||
| 724 | flash_color: 0, | 731 | flash_color: 0, |
| 725 | flash_alpha: 0.0, | 732 | flash_alpha: 0.0, |
| 726 | show_controls: true, | 733 | show_controls: true, |
| 734 | death_frames: 0, | ||
| 727 | } | 735 | } |
| 728 | } | 736 | } |
| 729 | 737 | ||
| @@ -802,10 +810,11 @@ impl Game { | |||
| 802 | if done { | 810 | if done { |
| 803 | self.state = GameState::ResolvingTurn; | 811 | self.state = GameState::ResolvingTurn; |
| 804 | self.resolve_turn(); | 812 | self.resolve_turn(); |
| 805 | self.state = GameState::WaitingForInput; | 813 | if self.state != GameState::GameOver { |
| 814 | self.state = GameState::WaitingForInput; | ||
| 815 | } | ||
| 806 | return true; | 816 | return true; |
| 807 | } | 817 | } |
| 808 | false | ||
| 809 | } | 818 | } |
| 810 | 819 | ||
| 811 | GameState::AnimatingEnemies => { | 820 | GameState::AnimatingEnemies => { |
| @@ -814,14 +823,22 @@ impl Game { | |||
| 814 | self.enemy_anim_t = 0.0; | 823 | self.enemy_anim_t = 0.0; |
| 815 | self.state = GameState::ResolvingTurn; | 824 | self.state = GameState::ResolvingTurn; |
| 816 | self.resolve_turn(); | 825 | self.resolve_turn(); |
| 817 | self.state = GameState::WaitingForInput; | 826 | if self.state != GameState::GameOver { |
| 827 | self.state = GameState::WaitingForInput; | ||
| 828 | } | ||
| 818 | return true; | 829 | return true; |
| 819 | } | 830 | } |
| 820 | false | ||
| 821 | } | 831 | } |
| 822 | 832 | ||
| 823 | _ => false, | 833 | _ => {} |
| 834 | } | ||
| 835 | |||
| 836 | // Count frames since death so the death screen has a minimum display time. | ||
| 837 | if self.state == GameState::GameOver { | ||
| 838 | self.death_frames += 1; | ||
| 824 | } | 839 | } |
| 840 | |||
| 841 | false | ||
| 825 | } | 842 | } |
| 826 | 843 | ||
| 827 | /// Attempt to walk one tile in grid direction (dx, dy). | 844 | /// Attempt to walk one tile in grid direction (dx, dy). |
| @@ -1160,7 +1177,9 @@ impl Game { | |||
| 1160 | fn advance_turn(&mut self) { | 1177 | fn advance_turn(&mut self) { |
| 1161 | self.state = GameState::ResolvingTurn; | 1178 | self.state = GameState::ResolvingTurn; |
| 1162 | self.resolve_turn(); | 1179 | self.resolve_turn(); |
| 1163 | self.state = GameState::WaitingForInput; | 1180 | if self.state != GameState::GameOver { |
| 1181 | self.state = GameState::WaitingForInput; | ||
| 1182 | } | ||
| 1164 | } | 1183 | } |
| 1165 | 1184 | ||
| 1166 | /// Apply all per-turn effects (weakness, heartbeat, HP drain, message decay). | 1185 | /// Apply all per-turn effects (weakness, heartbeat, HP drain, message decay). |
| @@ -1248,4 +1267,9 @@ impl Game { | |||
| 1248 | pub fn weakness_factor(&self) -> f64 { | 1267 | pub fn weakness_factor(&self) -> f64 { |
| 1249 | (self.weakness as f64 / MAX_WEAKNESS as f64) * 2.0 | 1268 | (self.weakness as f64 / MAX_WEAKNESS as f64) * 2.0 |
| 1250 | } | 1269 | } |
| 1270 | |||
| 1271 | /// Compute the final score. | ||
| 1272 | pub fn score(&self) -> u64 { | ||
| 1273 | (self.kills * 100) + (self.depth.pow(2) * 50) + (self.weapon_base_damage as u64 * 500) | ||
| 1274 | } | ||
| 1251 | } | 1275 | } |
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() { | |||
| 150 | 150 | ||
| 151 | // ---- death check ---- | 151 | // ---- death check ---- |
| 152 | if game.state == GameState::GameOver { | 152 | if game.state == GameState::GameOver { |
| 153 | std::process::exit(0); | 153 | // Minimum 30 frames (~0.5s) before input is accepted. |
| 154 | if game.death_frames >= 30 && cur_any { | ||
| 155 | std::process::exit(0); | ||
| 156 | } | ||
| 154 | } | 157 | } |
| 155 | 158 | ||
| 156 | // ---- win check (placeholder — becomes staircase in Phase 1) ---- | 159 | // ---- 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 @@ | |||
| 1 | use crate::game::{Entity, Game, PickupType}; | 1 | use crate::game::{Entity, Game, GameState, PickupType}; |
| 2 | use crate::texture::TEX_SIZE; | 2 | use crate::texture::TEX_SIZE; |
| 3 | 3 | ||
| 4 | // ---- simple 5×5 ASCII glyphs ---- | 4 | // ---- simple 5×5 ASCII glyphs ---- |
| @@ -287,6 +287,11 @@ impl Renderer { | |||
| 287 | } | 287 | } |
| 288 | } | 288 | } |
| 289 | } | 289 | } |
| 290 | |||
| 291 | // ---- death screen ---- | ||
| 292 | if game.state == GameState::GameOver { | ||
| 293 | self.draw_death_screen(buffer, game); | ||
| 294 | } | ||
| 290 | } | 295 | } |
| 291 | 296 | ||
| 292 | // ---- entity sprites --------------------------------------------------- | 297 | // ---- entity sprites --------------------------------------------------- |
| @@ -350,13 +355,6 @@ impl Renderer { | |||
| 350 | let entity_h = (self.screen_h as f64 / cam_z * 0.4) as isize; | 355 | let entity_h = (self.screen_h as f64 / cam_z * 0.4) as isize; |
| 351 | let sy = (half_h - entity_h as f64 * 0.3) as isize; | 356 | let sy = (half_h - entity_h as f64 * 0.3) as isize; |
| 352 | 357 | ||
| 353 | // Occlusion: entity is visible only if its centre column is not behind a wall. | ||
| 354 | // Compare Euclidean distance to entity vs Euclidean distance to wall. | ||
| 355 | let col = sx.clamp(0, self.screen_w as isize - 1) as usize; | ||
| 356 | if cam_z >= wall_euclidean[col] { | ||
| 357 | continue; | ||
| 358 | } | ||
| 359 | |||
| 360 | let scale = (entity_h.max(6) as f64 / LETTER_H as f64).max(0.5); | 358 | let scale = (entity_h.max(6) as f64 / LETTER_H as f64).max(0.5); |
| 361 | let (ch, color) = entity_char_and_color(entity); | 359 | let (ch, color) = entity_char_and_color(entity); |
| 362 | 360 | ||
| @@ -585,6 +583,61 @@ impl Renderer { | |||
| 585 | let hint_w = hint.len() * (LETTER_W + 1) - 1; | 583 | let hint_w = hint.len() * (LETTER_W + 1) - 1; |
| 586 | self.draw_text(buffer, cx - hint_w / 2, y, hint, hint_color); | 584 | self.draw_text(buffer, cx - hint_w / 2, y, hint, hint_color); |
| 587 | } | 585 | } |
| 586 | |||
| 587 | fn draw_death_screen(&self, buffer: &mut [u32], game: &Game) { | ||
| 588 | // Full darken. | ||
| 589 | for py in 0..self.screen_h { | ||
| 590 | for px in 0..self.screen_w { | ||
| 591 | let idx = py * self.screen_w + px; | ||
| 592 | let c = buffer[idx]; | ||
| 593 | let r = ((c >> 16) & 0xFF) as u32; | ||
| 594 | let g = ((c >> 8) & 0xFF) as u32; | ||
| 595 | let b = (c & 0xFF) as u32; | ||
| 596 | buffer[idx] = 0xFF00_0000 | ((r / 5) << 16) | ((g / 5) << 8) | (b / 5); | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | let cx = self.screen_w / 2; | ||
| 601 | let mut y = self.screen_h as usize / 2 - 40; | ||
| 602 | |||
| 603 | let title_color = 0xFF_CC3333; | ||
| 604 | let text_color = 0xFF_CCCCCC; | ||
| 605 | let score_color = 0xFF_CC8844; | ||
| 606 | let hint_color = 0xFF_666666; | ||
| 607 | |||
| 608 | let title = "YOU HAVE DIED"; | ||
| 609 | let title_w = title.len() * (LETTER_W + 1) - 1; | ||
| 610 | self.draw_text(buffer, cx - title_w / 2, y, title, title_color); | ||
| 611 | y += LETTER_H + 12; | ||
| 612 | |||
| 613 | let msg = "YOU HAD BECOME YET ANOTHER"; | ||
| 614 | let msg_w = msg.len() * (LETTER_W + 1) - 1; | ||
| 615 | self.draw_text(buffer, cx - msg_w / 2, y, msg, text_color); | ||
| 616 | y += LETTER_H + 4; | ||
| 617 | |||
| 618 | let msg2 = "LEGEND OF THIS DUNGEON."; | ||
| 619 | let msg2_w = msg2.len() * (LETTER_W + 1) - 1; | ||
| 620 | self.draw_text(buffer, cx - msg2_w / 2, y, msg2, text_color); | ||
| 621 | y += LETTER_H + 16; | ||
| 622 | |||
| 623 | let score = game.score(); | ||
| 624 | let score_str = format!("SCORE: {}", score); | ||
| 625 | let score_w = score_str.len() * (LETTER_W + 1) - 1; | ||
| 626 | self.draw_text(buffer, cx - score_w / 2, y, &score_str, score_color); | ||
| 627 | y += LETTER_H + 4; | ||
| 628 | |||
| 629 | let kills_str = format!( | ||
| 630 | "KILLS: {} DEPTH: {} WPN:{}", | ||
| 631 | game.kills, game.depth, game.weapon_base_damage | ||
| 632 | ); | ||
| 633 | let kills_w = kills_str.len() * (LETTER_W + 1) - 1; | ||
| 634 | self.draw_text(buffer, cx - kills_w / 2, y, &kills_str, text_color); | ||
| 635 | y += LETTER_H + 20; | ||
| 636 | |||
| 637 | let hint = "PRESS ANY KEY TO EXIT"; | ||
| 638 | let hint_w = hint.len() * (LETTER_W + 1) - 1; | ||
| 639 | self.draw_text(buffer, cx - hint_w / 2, y, hint, hint_color); | ||
| 640 | } | ||
| 588 | } | 641 | } |
| 589 | 642 | ||
| 590 | // ---- helpers ---- | 643 | // ---- helpers ---- |
