use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(MarioRun),
Box::new(ZeldaHearts),
Box::new(MetroidETanks),
Box::new(TetrisWell),
Box::new(DuckHunt),
Box::new(Excitebike),
Box::new(PunchOutStars),
Box::new(ContraSpread),
Box::new(MegaManWeapon),
Box::new(IceClimberUp),
Box::new(DonkeyBarrel),
]
}
struct MarioRun;
impl ProgressStyle for MarioRun {
fn name(&self) -> &str {
"mario-run"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Mario sprints right in braille dots; flagpole drops at 100%"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
draw::hline(grid, 0, dw.saturating_sub(1), dh.saturating_sub(1));
let mario_x =
((ctx.eased * (dw.saturating_sub(4)) as f32) as usize).min(dw.saturating_sub(1));
let ground_y = dh.saturating_sub(1);
if ctx.progress >= 0.999 {
let pole_x = dw.saturating_sub(2);
draw::vline(grid, pole_x, 0, ground_y);
if dh >= 3 {
draw::fill_rect(grid, pole_x + 1, 0, 1, (dh / 3).max(1));
}
draw::dot(grid, pole_x, ground_y);
if pole_x > 0 {
draw::dot(grid, pole_x.saturating_sub(1), ground_y);
}
} else {
let body_h = ((dh as f32 * 0.6) as usize).max(1);
let body_y = ground_y.saturating_sub(body_h);
draw::fill_rect(
grid,
mario_x,
body_y,
2.min(dw.saturating_sub(mario_x)),
body_h,
);
let step = ((ctx.time * 8.0) as usize) % 2;
if dh >= 2 {
let lx = mario_x + step;
draw::dot(grid, lx.min(dw.saturating_sub(1)), ground_y);
if mario_x + 1 < dw {
draw::dot(grid, (mario_x + 1).saturating_sub(step), ground_y);
}
}
}
let coins = (ctx.eased * (mario_x / 4).max(1) as f32) as usize;
for i in 0..coins {
let cx = i * 4;
if cx + 1 < mario_x && cx < dw {
draw::dot(grid, cx, ground_y.saturating_sub(dh / 3));
}
}
Ok(())
}
}
struct ZeldaHearts;
impl ProgressStyle for ZeldaHearts {
fn name(&self) -> &str {
"zelda-hearts"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Zelda heart-container row — fills one half-heart at a time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let heart_cell_w = 3usize;
let gap = 1usize;
let slot_w = heart_cell_w + gap;
let n_hearts = (cw / slot_w).max(1);
let half_hearts_total = n_hearts * 2;
let half_filled = (ctx.eased * half_hearts_total as f32).round() as usize;
let row = ch / 2;
for i in 0..n_hearts {
let x0 = i * slot_w;
let full_halves = (half_filled).saturating_sub(i * 2);
let state = full_halves.min(2);
let dx = x0 * 2;
let (dw, dh) = draw::dot_dims(grid);
let dy = (row * 4).min(dh.saturating_sub(3));
if dx + 1 < dw && dy < dh {
draw::dot(grid, dx + 1, dy);
}
if dx + 4 < dw && dy < dh {
draw::dot(grid, dx + 4, dy);
}
for xx in dx..(dx + 6).min(dw) {
if dy + 1 < dh {
draw::dot(grid, xx, dy + 1);
}
}
for xx in (dx + 1)..(dx + 5).min(dw) {
if dy + 2 < dh {
draw::dot(grid, xx, dy + 2);
}
}
if dx + 2 < dw && dy + 3 < dh {
draw::dot(grid, dx + 2, dy + 3);
}
if dx + 3 < dw && dy + 3 < dh {
draw::dot(grid, dx + 3, dy + 3);
}
if state == 2 {
for xx in (dx + 1)..(dx + 5).min(dw) {
if dy + 1 < dh {
draw::dot(grid, xx, dy + 1);
}
}
for xx in (dx + 1)..(dx + 5).min(dw) {
if dy + 2 < dh {
draw::dot(grid, xx, dy + 2);
}
}
} else if state == 1 {
for xx in (dx + 1)..(dx + 3).min(dw) {
if dy + 1 < dh {
draw::dot(grid, xx, dy + 1);
}
if dy + 2 < dh {
draw::dot(grid, xx, dy + 2);
}
}
}
}
Ok(())
}
}
struct MetroidETanks;
impl ProgressStyle for MetroidETanks {
fn name(&self) -> &str {
"metroid-etanks"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Metroid energy-tank segments — each tank charges then the next activates"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let tank_dot_w = ((dw / 6).max(3)).min(dw);
let gap_dots = 1usize;
let n_tanks = (dw / (tank_dot_w + gap_dots)).max(1);
let filled_f = ctx.eased * n_tanks as f32;
let full_tanks = filled_f as usize;
let partial_f = filled_f.fract();
for i in 0..n_tanks {
let x0 = i * (tank_dot_w + gap_dots);
if x0 >= dw {
break;
}
let actual_w = tank_dot_w.min(dw.saturating_sub(x0));
draw::rect_outline(grid, x0, 0, actual_w, dh);
if i < full_tanks {
let iw = actual_w.saturating_sub(2);
let ih = dh.saturating_sub(2);
if iw > 0 && ih > 0 {
draw::fill_rect(grid, x0 + 1, 1, iw, ih);
}
} else if i == full_tanks && partial_f > 0.001 {
let iw = actual_w.saturating_sub(2);
let ih = dh.saturating_sub(2);
if iw > 0 && ih > 0 {
let charge_h = ((partial_f * ih as f32).round() as usize).max(1).min(ih);
let y0 = ih + 1 - charge_h; draw::fill_rect(grid, x0 + 1, y0, iw, charge_h);
}
}
}
Ok(())
}
}
struct TetrisWell;
impl ProgressStyle for TetrisWell {
fn name(&self) -> &str {
"tetris-well"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Tetris well fills upward with a settling block stack"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
draw::vline(grid, 0, 0, dh.saturating_sub(1));
draw::vline(grid, dw.saturating_sub(1), 0, dh.saturating_sub(1));
draw::hline(grid, 0, dw.saturating_sub(1), dh.saturating_sub(1));
let inner_w = dw.saturating_sub(2);
let inner_h = dh.saturating_sub(1);
if inner_w == 0 || inner_h == 0 {
return Ok(());
}
let stack_dots = (ctx.eased * inner_h as f32) as usize;
let stack_dots = stack_dots.min(inner_h);
let stack_y0 = inner_h.saturating_sub(stack_dots);
for y in stack_y0..inner_h {
let row_mod = (inner_h - 1 - y) % 4;
if row_mod == 3 {
for x in (1..inner_w + 1).step_by(3) {
draw::dot(grid, x, y);
}
} else {
draw::hline(grid, 1, inner_w, y);
}
}
if stack_dots < inner_h {
let piece_y = stack_y0.saturating_sub(1);
let blink = ((ctx.time * 4.0) as usize) % 2 == 0;
if blink && piece_y < inner_h {
let shape = ((ctx.time * 0.5) as usize) % 5;
let pw = (inner_w.min(8)).max(1);
let px0 = 1 + (inner_w.saturating_sub(pw)) / 2;
match shape {
0 => draw::hline(grid, px0, (px0 + 3).min(dw.saturating_sub(1)), piece_y), 1 => {
draw::hline(grid, px0, (px0 + 2).min(dw.saturating_sub(1)), piece_y);
if piece_y >= 1 {
draw::dot(grid, px0 + 2, piece_y.saturating_sub(1));
}
}
2 => {
draw::hline(grid, px0 + 1, (px0 + 3).min(dw.saturating_sub(1)), piece_y);
if piece_y >= 1 {
draw::hline(
grid,
px0,
(px0 + 2).min(dw.saturating_sub(1)),
piece_y.saturating_sub(1),
);
}
}
3 => {
draw::hline(grid, px0, (px0 + 2).min(dw.saturating_sub(1)), piece_y);
if piece_y >= 1 {
draw::dot(grid, px0 + 1, piece_y.saturating_sub(1));
}
}
_ => {
draw::hline(grid, px0, (px0 + 1).min(dw.saturating_sub(1)), piece_y);
if piece_y >= 1 {
draw::hline(
grid,
px0,
(px0 + 1).min(dw.saturating_sub(1)),
piece_y.saturating_sub(1),
);
}
}
}
}
}
Ok(())
}
}
struct DuckHunt;
impl ProgressStyle for DuckHunt {
fn name(&self) -> &str {
"duck-hunt"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Ducks arc across the sky; progress = ducks shot, complete ducks fall"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
draw::hline(grid, 0, dw.saturating_sub(1), dh.saturating_sub(1));
let n_ducks = 5usize;
let shot_count = (ctx.eased * n_ducks as f32).round() as usize;
for i in 0..n_ducks {
let phase = i as f32 * 0.7 + ctx.time * 0.8;
let t_norm = (phase * 0.4).fract();
if i < shot_count {
let fall = ((ctx.time - (i as f32 * 0.3)).max(0.0) * 12.0) as i32;
let dx = (t_norm * dw as f32) as i32;
let ground = dh as i32 - 2;
let dy = (fall).min(ground);
draw::dot_i(grid, dx, dy);
draw::dot_i(grid, dx + 1, dy);
draw::dot_i(grid, dx, dy + 1);
} else {
let dx = (t_norm * dw as f32) as i32;
let dy = ((PI * t_norm).sin() * (dh.saturating_sub(3)) as f32 * 0.6 + 1.0) as i32;
draw::dot_i(grid, dx, dy);
draw::dot_i(grid, dx + 1, dy);
draw::dot_i(grid, dx, dy - 1);
let flap = (((ctx.time * 6.0 + i as f32) as i32) % 2) as i32;
draw::dot_i(grid, dx + 2, dy - flap);
}
}
Ok(())
}
}
struct Excitebike;
impl ProgressStyle for Excitebike {
fn name(&self) -> &str {
"excitebike"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Bike races right; row 2 is a turbo-heat gauge that must not overheat"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cw, ch) = grid.dimensions();
if dw == 0 || dh == 0 {
return Ok(());
}
draw::hline(grid, 0, dw.saturating_sub(1), dh.saturating_sub(1));
let bike_x =
((ctx.eased * (dw.saturating_sub(4)) as f32) as usize).min(dw.saturating_sub(1));
let wheel_y = dh.saturating_sub(2);
draw::dot(grid, bike_x, wheel_y);
if bike_x >= 3 {
draw::dot(grid, bike_x.saturating_sub(3), wheel_y);
}
for i in 0..3usize {
draw::dot_i(grid, bike_x as i32 - i as i32, wheel_y as i32 - i as i32);
}
if wheel_y >= 3 {
draw::dot(grid, bike_x, wheel_y.saturating_sub(3));
}
if ch >= 2 && cw > 0 {
let heat = (ctx.time * 1.2).sin() * 0.3 + ctx.eased * 0.7;
let heat = heat.clamp(0.0, 1.0);
let overheat = heat > 0.85;
let gauge_row = ch.saturating_sub(2).min(ch.saturating_sub(1));
let heat_cells = (heat * cw as f32) as usize;
for cx in 0..heat_cells.min(cw) {
let shade = if overheat { 4usize } else { 2 };
draw::shade(grid, cx, gauge_row, shade);
}
if overheat {
let hot_color = crate::Color::rgb(255, 40, 0);
draw::tint_row(grid, gauge_row, 0, cw.saturating_sub(1), hot_color);
} else {
let warm_color = crate::Color::rgb(255, 200, 0);
draw::tint_row(
grid,
gauge_row,
0,
heat_cells.min(cw.saturating_sub(1)),
warm_color,
);
}
}
Ok(())
}
}
struct PunchOutStars;
impl ProgressStyle for PunchOutStars {
fn name(&self) -> &str {
"punchout-stars"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Punch-Out stamina bar depletes then surges back as star-power at 75%"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let star_power = ctx.progress > 0.75;
let bar_frac = if star_power {
(ctx.progress - 0.75) / 0.25
} else {
1.0 - ctx.progress / 0.75
};
let bar_frac = bar_frac.clamp(0.0, 1.0);
let filled_cells = (bar_frac * cw as f32).round() as usize;
for cx in 0..cw {
for cy in 0..ch {
let level = if cx < filled_cells { 8usize } else { 0 };
if level > 0 {
if star_power {
let pulse = ((ctx.time * 8.0 + cx as f32 * 0.5) as usize) % 2;
draw::shade(grid, cx, cy, if pulse == 0 { 4 } else { 3 });
} else {
draw::vblock(grid, cx, cy, level);
}
} else {
draw::shade(grid, cx, cy, 1);
}
}
}
let color = if star_power {
crate::Color::rgb(255, 255, 180)
} else {
crate::Color::rgb(255, 220, 0)
};
for cy in 0..ch {
draw::tint_row(grid, cy, 0, filled_cells.min(cw).saturating_sub(1), color);
}
Ok(())
}
}
struct ContraSpread;
impl ProgressStyle for ContraSpread {
fn name(&self) -> &str {
"contra-spread"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Contra spread-shot bullets fan out rightward; progress = bullet travel"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx_f = 0.0f32; let cy_f = (dh as f32 - 1.0) / 2.0; let n_rays = 5usize;
let reach = (ctx.eased * dw as f32) as usize;
let flash = ((ctx.time * 12.0) as usize) % 2 == 0;
if flash {
draw::dot(grid, 0, cy_f as usize);
}
for i in 0..n_rays {
let angle_idx = i as i32 - (n_rays as i32 / 2);
let angle = angle_idx as f32 * PI / 8.0; let dx_f = angle.cos();
let dy_f = angle.sin();
let steps = reach.min(dw);
for s in (0..steps).step_by(2) {
let bx = (cx_f + s as f32 * dx_f) as i32;
let by = (cy_f + s as f32 * dy_f) as i32;
draw::dot_i(grid, bx, by);
}
if reach > 0 {
let bx = (cx_f + reach as f32 * dx_f) as i32;
let by = (cy_f + reach as f32 * dy_f) as i32;
draw::dot_i(grid, bx, by);
draw::dot_i(grid, bx + 1, by);
}
}
Ok(())
}
}
struct MegaManWeapon;
impl ProgressStyle for MegaManWeapon {
fn name(&self) -> &str {
"megaman-weapon"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Mega Man weapon-energy bar — segmented column charges from bottom up"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let n_segs = ch;
let seg_f = ctx.eased * n_segs as f32;
let full_segs = seg_f as usize;
let partial = seg_f.fract();
let bar_w = cw.min(2);
for seg in 0..n_segs {
let cell_y = n_segs.saturating_sub(1).saturating_sub(seg);
if seg < full_segs {
for cx in 0..bar_w {
draw::glyph(grid, cx, cell_y, '█');
}
} else if seg == full_segs && partial > 0.001 {
let level = (partial * 8.0).round() as usize;
for cx in 0..bar_w {
draw::vblock(grid, cx, cell_y, level);
}
} else {
for cx in 0..bar_w {
draw::shade(grid, cx, cell_y, 1);
}
}
}
for cx in bar_w..cw {
for cy in 0..ch {
draw::shade(grid, cx, cy, 0);
}
}
let bar_color = ctx.palette.sample(ctx.eased);
for seg in 0..full_segs.min(ch) {
let cell_y = ch.saturating_sub(1).saturating_sub(seg);
draw::tint_row(grid, cell_y, 0, bar_w.saturating_sub(1), bar_color);
}
Ok(())
}
}
struct IceClimberUp;
impl ProgressStyle for IceClimberUp {
fn name(&self) -> &str {
"iceclimber-up"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"Ice Climber ascends upward platform by platform driven by eased progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let scroll_offset = (ctx.time * 2.0) as usize % (dh.max(1));
let plat_spacing = (dh / 4).max(2);
let plat_count = (dh / plat_spacing) + 2;
for p in 0..plat_count {
let base_y = p * plat_spacing;
let y = (base_y + scroll_offset) % (dh + plat_spacing);
if y >= dh {
continue;
}
let is_left = p % 2 == 0;
let pw = dw * 3 / 4;
let px0 = if is_left { 0 } else { dw.saturating_sub(pw) };
draw::hline(
grid,
px0,
(px0 + pw).saturating_sub(1).min(dw.saturating_sub(1)),
y,
);
if y + 1 < dh {
draw::dot(grid, px0, y + 1);
let right = (px0 + pw).saturating_sub(1).min(dw.saturating_sub(1));
draw::dot(grid, right, y + 1);
}
}
let climber_y = (dh.saturating_sub(3) as f32 * (1.0 - ctx.eased)) as usize;
let climber_x = dw / 2;
draw::dot(grid, climber_x, climber_y);
draw::dot(grid, climber_x + 1, climber_y);
if climber_y + 1 < dh {
draw::dot(grid, climber_x, climber_y + 1);
draw::dot(grid, climber_x + 1, climber_y + 1);
}
if climber_y + 2 < dh {
let leg_step = ((ctx.time * 6.0) as usize) % 2;
draw::dot(grid, climber_x + leg_step, climber_y + 2);
}
Ok(())
}
}
struct DonkeyBarrel;
impl ProgressStyle for DonkeyBarrel {
fn name(&self) -> &str {
"donkey-barrel"
}
fn theme(&self) -> &str {
"nintendo"
}
fn describe(&self) -> &str {
"DK barrels roll down girders; Mario climbs a ladder as progress rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let n_girders = ((dh / 3).max(1)).min(4);
let girder_gap = dh / (n_girders + 1).max(1);
for g in 0..n_girders {
let gy = dh.saturating_sub(1).saturating_sub((g + 1) * girder_gap);
draw::hline(grid, 0, dw.saturating_sub(1), gy);
}
draw::vline(grid, 1, 0, dh.saturating_sub(1));
let rung_spacing = 3usize;
let mut ry = 0usize;
while ry < dh {
draw::dot(grid, 0, ry);
draw::dot(grid, 2, ry);
ry += rung_spacing;
}
for g in 0..n_girders {
let gy = dh.saturating_sub(1).saturating_sub((g + 1) * girder_gap);
let phase = g as f32 * 0.4 + ctx.time * 0.7;
let barrel_t = 1.0 - (phase * 0.3).fract(); let bx = (barrel_t * (dw.saturating_sub(3)) as f32) as usize;
if bx < dw && gy >= 1 {
draw::dot(grid, bx, gy.saturating_sub(1)); draw::dot(grid, bx.saturating_sub(1), gy); let barrel_y = gy.saturating_sub(1);
draw::dot(grid, bx, barrel_y);
if bx + 1 < dw {
draw::dot(grid, bx + 1, barrel_y);
}
let roll = ((ctx.time * 5.0 + g as f32) as usize) % 2;
let side_dot = if roll == 0 {
bx.saturating_sub(1)
} else {
(bx + 2).min(dw.saturating_sub(1))
};
if barrel_y >= 1 {
draw::dot(grid, side_dot, barrel_y);
}
}
}
let mario_y = (dh.saturating_sub(3) as f32 * (1.0 - ctx.eased)) as usize;
draw::dot(grid, 1, mario_y);
draw::dot(grid, 2, mario_y);
if mario_y + 1 < dh {
draw::dot(grid, 1, mario_y + 1);
draw::dot(grid, 2, mario_y + 1);
}
Ok(())
}
}