diff --git a/src/kilogram.salt b/src/kilogram.salt new file mode 100644 index 0000000..8d2c6ed --- /dev/null +++ b/src/kilogram.salt @@ -0,0 +1,94 @@ +// Kilogram — weightlifting plate calculator (IWF standard) +// Usage: kilogram [m|w] + +Plate { weight float, color int, width int, height int } + +// IWF competition plates: heaviest to lightest, ANSI 256-color codes +PLATES = [ + Plate { weight: 25.0, color: 9, width: 3, height: 7 }, + Plate { weight: 20.0, color: 12, width: 2, height: 7 }, + Plate { weight: 15.0, color: 11, width: 1, height: 7 }, + Plate { weight: 10.0, color: 10, width: 1, height: 7 }, + Plate { weight: 5.0, color: 15, width: 1, height: 5 }, + Plate { weight: 2.5, color: 9, width: 1, height: 5 }, + Plate { weight: 2.0, color: 12, width: 1, height: 3 }, + Plate { weight: 1.5, color: 11, width: 1, height: 3 }, + Plate { weight: 1.0, color: 10, width: 1, height: 1 }, + Plate { weight: 0.5, color: 15, width: 1, height: 1 } +] + +ART = ["|", "||", "|¦|"] +BLANK = [" ", " ", " "] + +// Whether a plate is visible at row (0-6, centered at row 3) +visible(p Plate, row int) bool { + half = p.height / 2 + return row >= 3 - half and row <= 3 + half +} + +// Render one plate cell: colored art or blank +cell(p Plate, row int) string { + return cond { + visible(p, row): "{ansi_fg(p.color)}{ART[p.width - 1]}{ansi_reset()}" + else: BLANK[p.width - 1] + } +} + +// Greedy plate allocation (one side of the bar) +allocate(side float) Array(Plate) { + rem mut = side + result mut Array(Plate) = [] + for spec in PLATES { + qty = to_int(floor(rem / spec.weight)) + rem -= to_float(qty) * spec.weight + for _ in range(0, qty) { + result = push(result, spec) + } + } + return result +} + +// Render the full barbell with loaded plates +render(plates Array(Plate)) { + n = len(plates) + for row in range(0, 7) { + sb = string_builder() + ch = cond { row == 3: "="; else: " " } + collar = cond { row == 3: "O"; else: " " } + bar = cond { row == 3: "=----------------="; else: " " } + + sb_write(sb, "{ch}{ch}{collar}") + for i in range(0, n) { sb_write(sb, cell(plates[n - 1 - i], row)) } + sb_write(sb, bar) + for p in plates { sb_write(sb, cell(p, row)) } + sb_write(sb, "{collar}{ch}{ch}") + + print(sb_build(sb)) + } +} + +// --- CLI --- +weight_str = arg(1) else { + print("Usage: kilogram [m|w]") + print(" m = men's 20kg bar (default)") + print(" w = women's 15kg bar") + exit(0) +} + +total float = parse_float(weight_str) else { + print("Weight must be a valid number") + exit(1) +} + +bar_weight = cond (arg(2) else "m") { + "w": 15.0 + else: 20.0 +} + +cond total < bar_weight + 5.0 { + print("Minimum weight: {bar_weight + 5.0}kg (bar + collars)") + exit(1) +} + +side = (total - bar_weight) / 2.0 - 2.5 +allocate(side) | render