From 157ee0de5124ba4396f255ecec5ad91a1cef18b5 Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Thu, 3 Apr 2025 16:05:08 +0200 Subject: [PATCH] add bounding area score to solution results --- CHANGELOG.md | 2 ++ README.md | 2 +- src/editor.rs | 14 ++++++++----- src/marble_engine/grid.rs | 42 +++++++++++++++++++++++++++++++++++++++ src/solution.rs | 7 ++++++- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ded18..7a95f3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,11 @@ Game store page: https://crispypin.itch.io/marble-machinations ## [Unreleased] ### added +- score number: bounding area - configurable key bindings for many editor actions - QWERTY+ASDFGH keybindings for the tile tools - OS clipboard copy/paste, with fallback to old behavior when copying +- cut selection - in-grid text comments (not yet editable in-game) - changelog file - (dev) sub-tick visualisation in debug mode diff --git a/README.md b/README.md index 625a5eb..ff84493 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble - background colour setting - hotkeys for everything (no mouse needed to play) - more levels -- footprint and bounding box stats (instead of area) - scroll output bytes - timestamps in solutions and blueprints - lock tile types for early levels to make it less overwhelming @@ -31,6 +30,7 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble - show histograms - author name in solutions and blueprints #### undecided +- footprint score (tiles that were non-empty at any point in the run) - option to use 8-bit marbles? - blueprint rotation? - settable marble start direction? diff --git a/src/editor.rs b/src/editor.rs index ed07b31..282f57d 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -24,7 +24,7 @@ const HEADER_HEIGHT: i32 = 40; const FOOTER_HEIGHT: i32 = 95; const SIDEBAR_WIDTH: i32 = 200 + 32 * 2 + 5 * 4; const END_POPUP_WIDTH: i32 = 320; -const END_POPUP_HEIGHT: i32 = 165; +const END_POPUP_HEIGHT: i32 = 225; const MAX_ZOOM: f32 = 8.; const MIN_ZOOM: f32 = 0.25; @@ -335,6 +335,7 @@ impl Editor { self.score = Some(Score { cycles: self.total_steps + self.machine.step_count(), tiles: self.source_board.grid.count_tiles(), + bounds_area: self.source_board.grid.used_bounds_area(), }); } } else if !stage.output().as_bytes().starts_with(self.machine.output()) { @@ -744,11 +745,14 @@ impl Editor { if self.popup == Popup::Success { d.draw_text("Level Complete!", x + 45, y + 10, 30, Color::LIME); if let Some(score) = &self.score { - d.draw_text("cycles", x + 15, y + 45, 20, Color::WHITE); - draw_usize(d, textures, score.cycles, (x + 10, y + 70), 9, 2); - d.draw_text("tiles", x + 215, y + 45, 20, Color::WHITE); - draw_usize(d, textures, score.tiles, (x + 210, y + 70), 5, 2); + d.draw_text("cycles", x + 15, y + 40, 20, Color::WHITE); + draw_usize(d, textures, score.cycles, (x + 110, y + 40), 9, 2); + d.draw_text("tiles", x + 15, y + 80, 20, Color::WHITE); + draw_usize(d, textures, score.tiles, (x + 110, y + 80), 9, 2); + d.draw_text("bounds", x + 15, y + 120, 20, Color::WHITE); + draw_usize(d, textures, score.bounds_area, (x + 110, y + 120), 9, 2); } + let y = y + 60; if simple_button((d, &self.mouse), x + 10, y + 110, 140, 45) { self.popup = Popup::None; self.dismissed_end = true; diff --git a/src/marble_engine/grid.rs b/src/marble_engine/grid.rs index 30a04cb..1e0bc8d 100644 --- a/src/marble_engine/grid.rs +++ b/src/marble_engine/grid.rs @@ -123,6 +123,48 @@ impl Grid { sum } + pub fn used_bounds_area(&self) -> usize { + let row_clear = |a, max, f: fn(usize, usize) -> (usize, usize)| { + for b in 0..max { + if !self.get_unchecked(f(a, b).into()).is_blank() { + return false; + } + } + true + }; + let mut height = self.height; + for y in 0..self.height { + if row_clear(y, self.width, |y, x| (x, y)) { + height -= 1; + } else { + break; + } + } + for y in (0..self.height).rev() { + if row_clear(y, self.width, |y, x| (x, y)) { + height -= 1; + } else { + break; + } + } + let mut width = self.width; + for x in 0..self.width { + if row_clear(x, self.height, |x, y| (x, y)) { + width -= 1; + } else { + break; + } + } + for x in (0..self.width).rev() { + if row_clear(x, self.width, |x, y| (x, y)) { + width -= 1; + } else { + break; + } + } + width * height + } + fn in_bounds(&self, p: Pos) -> bool { p.x >= 0 && p.y >= 0 && p.x < self.width as PosInt && p.y < self.height as PosInt } diff --git a/src/solution.rs b/src/solution.rs index b353d13..9601c82 100644 --- a/src/solution.rs +++ b/src/solution.rs @@ -22,6 +22,8 @@ pub struct Solution { pub struct Score { pub cycles: usize, pub tiles: usize, + #[serde(default)] + pub bounds_area: usize, } impl Solution { @@ -68,7 +70,10 @@ impl Solution { pub fn score_text(&self) -> String { if let Some(score) = &self.score { - format!("C: {} T: {}", score.cycles, score.tiles) + format!( + "C: {} T: {} B: {}", + score.cycles, score.tiles, score.bounds_area + ) } else { "unsolved".into() }