This commit is contained in:
Renan LE CARO 2025-04-23 10:56:50 +02:00
parent 181e156f60
commit 0ec9cdf798
17 changed files with 2089 additions and 2227 deletions

View file

@ -14,10 +14,13 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades !
# Changelog # Changelog
## To do ## To do
- rename hypnosis
- more icons
- save global stats locally (sum of broken bricks, paddle hits, misses,)
## Done ## Done
- - new level : Blinky by Big Goober
- color coded perks (green = noob friendly, red = combo with reset condition) - color coded perks (green = noob friendly, red = combo with reset condition)
- removed : instant_upgrade - removed : instant_upgrade
- nerfed : helium : now need to be level 3 to have the same effect of keeping coins up - nerfed : helium : now need to be level 3 to have the same effect of keeping coins up

View file

@ -29,8 +29,8 @@ android {
applicationId = "me.lecaro.breakout" applicationId = "me.lecaro.breakout"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 29088680 versionCode = 29088937
versionName = "29088680" versionName = "29088937"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true

File diff suppressed because one or more lines are too long

711
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
// The version of the cache. // The version of the cache.
const VERSION = "29088680"; const VERSION = "29088937";
// The name of the cache // The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`; const CACHE_NAME = `breakout-71-${VERSION}`;

View file

@ -100,9 +100,9 @@ export async function openCreativeModePerksPicker() {
"/" + "/" +
(u.max + (creativeModePerks.limitless || 0)), (u.max + (creativeModePerks.limitless || 0)),
value: u, value: u,
className: ' upgrade '+(creativeModePerks[u.id] className:
? " highlight" " upgrade " +
: " not-highlighed"), (creativeModePerks[u.id] ? " highlight" : " not-highlighed"),
tooltip: u.help(creativeModePerks[u.id] || 1), tooltip: u.help(creativeModePerks[u.id] || 1),
})), })),
t("lab.select_level"), t("lab.select_level"),

View file

@ -436,7 +436,7 @@
{ {
"name": "icon:smaller_puck", "name": "icon:smaller_puck",
"size": 8, "size": 8,
"bricks": "_________tttttt__tttttt_____________W_____________________WW____" "bricks": "_________yyyyyy__yyyyyy_____________W_____________________WW____"
}, },
{ {
"name": "icon:pierce", "name": "icon:pierce",
@ -446,7 +446,7 @@
{ {
"name": "icon:picky_eater", "name": "icon:picky_eater",
"size": 8, "size": 8,
"bricks": "_rrr_______ry_____ryy_____r_y______yyy______________y_____WWWW__" "bricks": "_rrr_______rt_____rtt_____r_t______ttt_______W____________WWWW__"
}, },
{ {
"name": "icon:metamorphosis", "name": "icon:metamorphosis",
@ -456,12 +456,12 @@
{ {
"name": "icon:compound_interest", "name": "icon:compound_interest",
"size": 8, "size": 8,
"bricks": "_________tttttt__ttt__t______y_____________W__y_________rrWWWrrr" "bricks": "_________tttttt__ttt__t_____W________________r___________WWW__r_"
}, },
{ {
"name": "icon:hot_start", "name": "icon:hot_start",
"size": 7, "size": 7,
"bricks": "_______rry_rrryyr_ryryry_ryryyr_ryrrry_rrr_______" "bricks": "tt__ttt__t_trt_t__tttt_____ttttWttt________WWW___"
}, },
{ {
"name": "icon:sapper", "name": "icon:sapper",
@ -472,7 +472,7 @@
{ {
"name": "icon:bigger_explosions", "name": "icon:bigger_explosions",
"size": 8, "size": 8,
"bricks": "__O_______Oy_OO___OyOy__OyyyWBOO_OOWyyy__ByOyOO__yOOyBOO_OO_____" "bricks": "__O__Oy___Oyy_____OyOy__OyyyByOO_OOBBBy___yyByO__yOOy_OO_OO_____"
}, },
{ {
"name": "icon:extra_levels", "name": "icon:extra_levels",
@ -537,12 +537,12 @@
{ {
"name": "icon:sturdy_bricks", "name": "icon:sturdy_bricks",
"size": 7, "size": 7,
"bricks": "ttbttttbtttbtt____W_____W_W___W___W_______WWW____" "bricks": "yyyyyyyyyyyyyy____W_____W_W___W___W_______WWW____"
}, },
{ {
"name": "icon:respawn", "name": "icon:respawn",
"size": 9, "size": 9,
"bricks": "tttt___ttttt__t__ttta_ttt_______________________________W_________________WWW" "bricks": "tttt___ttttt__t__tttt_ttt_t_____________________________W_________________WWW____"
}, },
{ {
"name": "Elephant", "name": "Elephant",
@ -723,13 +723,13 @@
{ {
"name": "icon:helium", "name": "icon:helium",
"size": 8, "size": 8,
"bricks": "_y____y_yP____PyPP___yPPP____P_P_____P____y_y______y______WWW___", "bricks": "_y____y_yP____PyPP___yPPP____P_P_____P____________________WWW___",
"color": "" "color": ""
}, },
{ {
"name": "icon:asceticism", "name": "icon:asceticism",
"size": 8, "size": 8,
"bricks": "_yyyyyy__yy__yy_____W_______r_________r____r_________r__WWW___r_", "bricks": "_tttttt__tt__tt_____W_______r______________r_________r_____WWW__",
"color": "" "color": ""
}, },
{ {
@ -741,7 +741,7 @@
{ {
"name": "icon:shunt", "name": "icon:shunt",
"size": 8, "size": 8,
"bricks": "_______y______yy______yy__yyyyyy__y__yyy_yy__yyy_yy__yyyyyy__yyy", "bricks": "_______y______yy______yy__yttyyy__y__yyy_yy__yyy_yy__yyyyyy__yyy",
"color": "" "color": ""
}, },
{ {
@ -752,14 +752,14 @@
}, },
{ {
"name": "icon:nbricks", "name": "icon:nbricks",
"size": 6, "size": 7,
"bricks": "yy__rryyy_yyyyyyyyyyyyyyyy_yyyrr__yy", "bricks": "________tttrt__ttr_r____________W__________WWW___",
"color": "" "color": ""
}, },
{ {
"name": "icon:etherealcoins", "name": "icon:etherealcoins",
"size": 11, "size": 11,
"bricks": "_____y_________yyy________WWW________WWW_______yWWWy_____yyWWWyy____yyWWWyy____yyWWWyy____y_WyW_y_______W________________", "bricks": "_____y_________yyy________ttt________ttt_______yttty_____yytttyy____yytttyy____yytttyy____y__y__y________________________",
"color": "" "color": ""
}, },
{ {
@ -771,25 +771,25 @@
{ {
"name": "icon:zen", "name": "icon:zen",
"size": 12, "size": 12,
"bricks": "________________bbbb_______bbbbbb_______bbbb________BrrB_______tttttt_____tttttttt_____tttttt______BrrrrB_____bbbbbbbb___bbbbbbbbbb___bbbbbbbb__", "bricks": "________________WWWW_______WWWWWW_______WWWW________BrrB_______tttttt_____tttttttt_____tttttt______BrrrrB_____WWWWWWWW___WWWWWWWWWW___WWWWWWWW__",
"color": "" "color": ""
}, },
{ {
"name": "icon:sacrifice", "name": "icon:sacrifice",
"size": 9, "size": 9,
"bricks": "__t___t___ttt_ttt_ttWWWWWttttWbWbWttttWWbWWtt_ttWWWtt___tWtWt_____ttt_______t____", "bricks": "__t___t___ttt_ttt_ttWWWWWttttWtWtWttttWWtWWtt_ttWWWtt___tWtWt_____ttt_______t____",
"color": "" "color": ""
}, },
{ {
"name": "icon:trampoline", "name": "icon:trampoline",
"size": 8, "size": 8,
"bricks": "rrrrrrrrrttttttrrttttttrr______rr___W__rr______rr______r__yyy___", "bricks": "_r_r_r_rrtttttt__ttttttrr___________W__rr______________r__WWW___",
"color": "" "color": ""
}, },
{ {
"name": "icon:ghost_coins", "name": "icon:ghost_coins",
"size": 7, "size": 7,
"bricks": "__yyy___yyyyy_yyOyOyyyyyyyyyyyOOOyyyyyyyyyyy_y_yy", "bricks": "__yyy___yyyyy_yy_y_yyyyyyyyyyy___yyyyyyyyyyy_y_yy",
"color": "" "color": ""
}, },
{ {
@ -812,8 +812,8 @@
}, },
{ {
"name": "icon:passive_income", "name": "icon:passive_income",
"size": 7, "size": 8,
"bricks": "_ttttt__ttt_t______W____y____________y_____rgggr_", "bricks": "_ttttt___ttt_t______yW_____________y______________ggg_______y___",
"color": "" "color": ""
}, },
{ {
@ -849,7 +849,7 @@
{ {
"name": "icon:addiction", "name": "icon:addiction",
"size": 9, "size": 9,
"bricks": "__________________________l__WWWWW_lWWWrrllllr_WWWWW_lr_______l__________________", "bricks": "__________________________t__WWWWW_tWWWrrttttr_WWWWW_tr_______t__________________",
"color": "" "color": ""
}, },
{ {
@ -931,7 +931,7 @@
{ {
"name": "icon:fountain_toss", "name": "icon:fountain_toss",
"size": 12, "size": 12,
"bricks": "WWWWW_______WWWWW____y_________y______________y______y__y_____WWWWWWWW___WttttttttW_WtytttytyttWWtttyttttttWlWtyttttytWl_lWWWWWWWWl___llllllll__", "bricks": "_____________________y_________y______________y______y__y_____WWWWWWWW___WttttttttW_WtytttytyttWWtttyttttttWlWtyttttytWl_lWWWWWWWWl___llllllll__",
"color": "" "color": ""
}, },
{ {
@ -1097,19 +1097,19 @@
{ {
"name": "icon:minefield", "name": "icon:minefield",
"size": 7, "size": 7,
"bricks": "W__B__WWWBBBWWB__W__BBBWWWBBW__B__WWWBBBWW_______", "bricks": "yB___Byyy___yy__ByB____yyy__yB___Byyy___yy_______",
"color": "" "color": ""
}, },
{ {
"name": "icon:side_flip", "name": "icon:side_flip",
"size": 7, "size": 7,
"bricks": "________ttttt__rttty__rttty__rttty__ttttt________", "bricks": "________rtttt__rtttt____________W__________WWW___",
"color": "" "color": ""
}, },
{ {
"name": "icon:side_kick", "name": "icon:side_kick",
"size": 7, "size": 7,
"bricks": "________ttttt__ytttr__ytttr__ytttr__ttttt________", "bricks": "________ttttr__ttttr__________W______________WWW_",
"color": "" "color": ""
}, },
{ {
@ -1145,13 +1145,13 @@
{ {
"name": "icon:trickledown", "name": "icon:trickledown",
"size": 8, "size": 8,
"bricks": "_ytttttt_________y_y_y__tttttt____________y_y_y___tttttt_y______", "bricks": "_ytttttt_________y___y__tttttt____________y___y___tttttt_y______",
"color": "" "color": ""
}, },
{ {
"name": "icon:transparency", "name": "icon:transparency",
"size": 9, "size": 9,
"bricks": "__W_W_W___________W_W_W_W_W_________W_W_W_W_W_________W_W_W_W_W___________W_W_W__", "bricks": "__W_W_W___________W_y_y_y_W_________W_y_y_y_W_________W_y_y_y_W___________W_W_W__",
"color": "" "color": ""
}, },
{ {
@ -1163,7 +1163,7 @@
{ {
"name": "icon:bricks_attract_coins", "name": "icon:bricks_attract_coins",
"size": 7, "size": 7,
"bricks": "_y__y___tttttyyttttt__ttttt_yttttty_ttttt___y__y_", "bricks": "_y__y___ttttt__ttttt__ttttt__ttttty_ttttt______y_",
"color": "" "color": ""
}, },
{ {
@ -1175,13 +1175,13 @@
{ {
"name": "icon:hypnosis", "name": "icon:hypnosis",
"size": 8, "size": 8,
"bricks": "___WW______WW_______ay_____c__a______c______y_______a_c____c_y_a", "bricks": "__________WW______WWy______y_y______y_y______y________y_________",
"color": "" "color": ""
}, },
{ {
"name": "icon:bricks_attract_ball", "name": "icon:bricks_attract_ball",
"size": 8, "size": 8,
"bricks": "llW_____ll_P________P________Pll____P_ll___P____llP_____ll_P____", "bricks": "ttW_____tt_P________P________Ptt____P_tt___P____ttP_____tt_P____",
"color": "" "color": ""
}, },
{ {
@ -1331,7 +1331,7 @@
{ {
"name": "icon:buoy", "name": "icon:buoy",
"size": 7, "size": 7,
"bricks": "___b______b_____bbb__abbbbbaaatttaaaaataaaaaaaaaa", "bricks": "___y______y_____yyy__tyyyyytttOOOtttttOtttttttttt",
"svg": null, "svg": null,
"color": "" "color": ""
}, },
@ -1344,8 +1344,8 @@
}, },
{ {
"name": "icon:three_cushion", "name": "icon:three_cushion",
"size": 8, "size": 7,
"bricks": "BkkkkkkBk___W__kk__W_W_kk_W___WkkW___y_kk_W____kk__W___kBkkkkkkB", "bricks": "tttttttttttttt____r______r______r______r__B__WWW_",
"svg": null, "svg": null,
"color": "" "color": ""
}, },
@ -1359,7 +1359,7 @@
{ {
"name": "icon:double_or_nothing", "name": "icon:double_or_nothing",
"size": 7, "size": 7,
"bricks": "__yyy___yrrry_yOOOrOyyOOrOOyyOOOOOy_yOrOy___yyy__", "bricks": "__yyy___yyyyy_yyyyggyyyygggyyyggggy_ygggy___yyy__",
"svg": null, "svg": null,
"color": "" "color": ""
}, },
@ -1394,8 +1394,15 @@
{ {
"name": "icon:happy_family", "name": "icon:happy_family",
"size": 9, "size": 9,
"bricks": "___________tt_tt____tt_tt____tt_tt__W_tt_tt_W__________W_____W____W_W____________", "bricks": "__tt_tt____tt_tt____tt_tt____________________W_______W__W_W_W___________rrrWWWrrr",
"svg": null, "svg": null,
"color": "" "color": ""
},
{
"color": "#115988",
"size": 20,
"bricks": "____________________________gggg______________ggrrrrgg___________grrrrrrrrg_________grrrrrrrrrrg_______grrrWWrrrrWWrg______grrWWWWrrWWWWg______grrWWbbrrWWbbg_____grrrWWbbrrWWbbrg____grrrrWWrrrrWWrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrgrrrggrrrgrrg____grg_grg__grg_grg_____g___g____g___g_______________________________________________________________",
"name": "Blinky",
"credit": "Suggested by Big Goober. The red ghost, Blinky, from the arcade game \"Pac Man\""
} }
] ]

View file

@ -1 +1 @@
"29088680" "29088937"

View file

@ -297,11 +297,8 @@ export async function openUpgradesPicker(gameState: GameState) {
icon: string; icon: string;
value: PerkId | "reroll"; value: PerkId | "reroll";
help: string; help: string;
className:string; className: string;
}> = pickRandomUpgrades( }> = pickRandomUpgrades(gameState, 3 + gameState.perks.one_more_choice);
gameState,
3 + gameState.perks.one_more_choice ,
);
if (!actions.length) break; if (!actions.length) break;
if (gameState.rerolls) if (gameState.rerolls)

View file

@ -607,8 +607,7 @@ export function pickRandomUpgrades(gameState: GameState, count: number) {
icon: icons["icon:" + u.id], icon: icons["icon:" + u.id],
value: u.id as PerkId, value: u.id as PerkId,
help: u.help(gameState.perks[u.id] + 1), help: u.help(gameState.perks[u.id] + 1),
className: 'upgrade ' className: "upgrade ",
})); }));
} }
@ -1183,7 +1182,10 @@ export function gameStateTick(
Math.abs(coin.x - gameState.puckPosition) * 2 > Math.abs(coin.x - gameState.puckPosition) * 2 >
gameState.puckWidth + coin.size; gameState.puckWidth + coin.size;
let dvy = let dvy =
frames * coin.weight * 0.8 * (flip ? 1-gameState.perks.helium*0.6 : 1); frames *
coin.weight *
0.8 *
(flip ? 1 - gameState.perks.helium * 0.6 : 1);
if (gameState.perks.etherealcoins) { if (gameState.perks.etherealcoins) {
if (gameState.perks.helium) { if (gameState.perks.helium) {

View file

@ -1,270 +1,274 @@
import {icons, transformRawLevel} from "./loadGameData"; import { icons, transformRawLevel } from "./loadGameData";
import {t} from "./i18n/i18n"; import { t } from "./i18n/i18n";
import {getSettingValue, getTotalScore, setSettingValue} from "./settings"; import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
import {asyncAlert} from "./asyncAlert"; import { asyncAlert } from "./asyncAlert";
import {Palette, RawLevel} from "./types"; import { Palette, RawLevel } from "./types";
import {levelIconHTML} from "./levelIcon"; import { levelIconHTML } from "./levelIcon";
import _palette from "./data/palette.json"; import _palette from "./data/palette.json";
import {restart} from "./game"; import { restart } from "./game";
import {describeLevel} from "./game_utils"; import { describeLevel } from "./game_utils";
import {automaticBackgroundColor, levelCodeToRawLevel, MAX_LEVEL_SIZE, MIN_LEVEL_SIZE} from "./pure_functions"; import {
automaticBackgroundColor,
levelCodeToRawLevel,
MAX_LEVEL_SIZE,
MIN_LEVEL_SIZE,
} from "./pure_functions";
const palette = _palette as Palette; const palette = _palette as Palette;
export function levelEditorMenuEntry() { export function levelEditorMenuEntry() {
const min = 10000; const min = 10000;
const disabled = getTotalScore() < min; const disabled = getTotalScore() < min;
return { return {
icon: icons["icon:editor"], icon: icons["icon:editor"],
text: t("editor.title"), text: t("editor.title"),
disabled, disabled,
help: disabled ? t("editor.locked", {min}) : t("editor.help"), help: disabled ? t("editor.locked", { min }) : t("editor.help"),
async value() { async value() {
openLevelEditorLevelsList().then(); openLevelEditorLevelsList().then();
}, },
}; };
} }
async function openLevelEditorLevelsList() { async function openLevelEditorLevelsList() {
const rawList = getSettingValue("custom_levels", []) as RawLevel[]; const rawList = getSettingValue("custom_levels", []) as RawLevel[];
const customLevels = rawList.map(transformRawLevel); const customLevels = rawList.map(transformRawLevel);
let choice = await asyncAlert({ let choice = await asyncAlert({
title: t("editor.title"), title: t("editor.title"),
content: [ content: [
...customLevels.map((l, li) => ({ ...customLevels.map((l, li) => ({
text: l.name, text: l.name,
icon: levelIconHTML(l.bricks, l.size, l.color), icon: levelIconHTML(l.bricks, l.size, l.color),
value() { value() {
editRawLevelList(li); editRawLevelList(li);
}, },
help: l.credit || describeLevel(l), help: l.credit || describeLevel(l),
})), })),
{ {
text: t("editor.new_level"), text: t("editor.new_level"),
icon: icons["icon:editor"], icon: icons["icon:editor"],
value() { value() {
rawList.push({ rawList.push({
color: "", color: "",
size: 6, size: 6,
bricks: "____________________________________", bricks: "____________________________________",
name: "custom level" + (rawList.length + 1), name: "custom level" + (rawList.length + 1),
credit: "", credit: "",
}); });
setSettingValue("custom_levels", rawList); setSettingValue("custom_levels", rawList);
editRawLevelList(rawList.length - 1); editRawLevelList(rawList.length - 1);
}, },
}, },
{ {
text: t("editor.import"), text: t("editor.import"),
help: t("editor.import_instruction"), help: t("editor.import_instruction"),
value() { value() {
const code = prompt(t("editor.import_instruction"))?.trim(); const code = prompt(t("editor.import_instruction"))?.trim();
if (code) { if (code) {
const lvl = levelCodeToRawLevel(code) const lvl = levelCodeToRawLevel(code);
if (lvl) { if (lvl) {
rawList.push(lvl); rawList.push(lvl);
setSettingValue("custom_levels", rawList); setSettingValue("custom_levels", rawList);
} }
} }
openLevelEditorLevelsList(); openLevelEditorLevelsList();
}, },
}, },
], ],
}); });
if (typeof choice == "function") choice(); if (typeof choice == "function") choice();
} }
export async function editRawLevelList(nth: number, color = "W") { export async function editRawLevelList(nth: number, color = "W") {
let rawList = getSettingValue("custom_levels", []) as RawLevel[]; let rawList = getSettingValue("custom_levels", []) as RawLevel[];
const level = rawList[nth]; const level = rawList[nth];
const bricks = level.bricks.split(""); const bricks = level.bricks.split("");
let grid = ""; let grid = "";
for (let y = 0; y < level.size; y++) { for (let y = 0; y < level.size; y++) {
grid += '<div style="background: ' + (level.color || "black") + ';">'; grid += '<div style="background: ' + (level.color || "black") + ';">';
for (let x = 0; x < level.size; x++) {
const c = bricks[y * level.size + x];
grid += `<span data-resolve-to="paint_brick:${x}:${y}" style="background: ${palette[c]}">${c == "B" ? "💣" : ""}</span>`;
}
grid += "</div>";
}
const levelColors = new Set(bricks);
levelColors.delete("_");
levelColors.delete("B");
let colorList =
'<div class="palette">' +
Object.entries(palette)
.filter(([key, value]) => key !== "_")
.filter(
([key, value]) =>
levelColors.size < 5 || levelColors.has(key) || key === "B",
)
.map(
([key, value]) =>
`<span data-resolve-to="set_color:${key}" data-selected="${key == color}" style="background: ${value}">${key == "B" ? "💣" : ""}</span>`,
)
.join("") +
"</div>";
const clicked = await asyncAlert<string | null>({
title: t("editor.editing.title", { name: level.name }),
content: [
t("editor.editing.color"),
colorList,
t("editor.editing.help"),
`<div class="gridEdit" style="--grid-size:${level.size};">${grid}</div>`,
{
icon: icons["icon:new_run"],
text: t("editor.editing.play"),
value: "play",
},
{
text: t("editor.editing.rename"),
value: "rename",
help: level.name,
},
{
text: t("editor.editing.credit"),
value: "credit",
help: level.credit,
},
{
text: t("editor.editing.delete"),
value: "delete",
},
{
text: t("editor.editing.copy"),
value: "copy",
help: t("editor.editing.copy_help"),
},
{
text: t("editor.editing.bigger"),
value: "size:+1",
disabled: level.size >= MAX_LEVEL_SIZE,
},
{
text: t("editor.editing.smaller"),
value: "size:-1",
disabled: level.size <= MIN_LEVEL_SIZE,
},
{
text: t("editor.editing.left"),
value: "move:-1:0",
},
{
text: t("editor.editing.right"),
value: "move:1:0",
},
{
text: t("editor.editing.up"),
value: "move:0:-1",
},
{
text: t("editor.editing.down"),
value: "move:0:1",
},
],
});
if (!clicked) return;
if (typeof clicked === "string") {
const [action, a, b] = clicked.split(":");
if (action == "paint_brick") {
const x = parseInt(a),
y = parseInt(b);
bricks[y * level.size + x] =
bricks[y * level.size + x] === color ? "_" : color;
level.bricks = bricks.join("");
}
if (action == "set_color") {
color = a;
}
if (action == "size") {
const newSize = level.size + parseInt(a);
const newBricks = [];
for (let y = 0; y < newSize; y++) {
for (let x = 0; x < newSize; x++) {
newBricks.push(
(x < level.size && y < level.size && bricks[y * level.size + x]) ||
"_",
);
}
}
level.size = newSize;
level.bricks = newBricks.join("");
}
if (action == "move") {
const dx = parseInt(a),
dy = parseInt(b);
const newBricks = [];
for (let y = 0; y < level.size; y++) {
for (let x = 0; x < level.size; x++) { for (let x = 0; x < level.size; x++) {
const c = bricks[y * level.size + x]; const tx = x - dx;
grid += `<span data-resolve-to="paint_brick:${x}:${y}" style="background: ${palette[c]}">${c == "B" ? "💣" : ""}</span>`; const ty = y - dy;
if (tx < 0 || tx >= level.size || ty < 0 || ty >= level.size) {
newBricks.push("_");
} else {
newBricks.push(bricks[ty * level.size + tx]);
}
} }
grid += "</div>"; }
level.bricks = newBricks.join("");
} }
if (action === "play") {
const levelColors = new Set(bricks); restart({
levelColors.delete("_"); level: transformRawLevel(level),
levelColors.delete("B"); isEditorTrialRun: nth,
perks: {
let colorList = base_combo: 7,
'<div class="palette">' + },
Object.entries(palette) });
.filter(([key, value]) => key !== "_") return;
.filter(
([key, value]) =>
levelColors.size < 5 || levelColors.has(key) || key === "B",
)
.map(
([key, value]) =>
`<span data-resolve-to="set_color:${key}" data-selected="${key == color}" style="background: ${value}">${key == "B" ? "💣" : ""}</span>`,
)
.join("") +
"</div>";
const clicked = await asyncAlert<string | null>({
title: t("editor.editing.title", {name: level.name}),
content: [
t("editor.editing.color"),
colorList,
t("editor.editing.help"),
`<div class="gridEdit" style="--grid-size:${level.size};">${grid}</div>`,
{
icon: icons["icon:new_run"],
text: t("editor.editing.play"),
value: "play",
},
{
text: t("editor.editing.rename"),
value: "rename",
help: level.name,
},
{
text: t("editor.editing.credit"),
value: "credit",
help: level.credit,
},
{
text: t("editor.editing.delete"),
value: "delete",
},
{
text: t("editor.editing.copy"),
value: "copy",
help: t("editor.editing.copy_help"),
},
{
text: t("editor.editing.bigger"),
value: "size:+1",
disabled: level.size >= MAX_LEVEL_SIZE,
},
{
text: t("editor.editing.smaller"),
value: "size:-1",
disabled: level.size <= MIN_LEVEL_SIZE,
},
{
text: t("editor.editing.left"),
value: "move:-1:0",
},
{
text: t("editor.editing.right"),
value: "move:1:0",
},
{
text: t("editor.editing.up"),
value: "move:0:-1",
},
{
text: t("editor.editing.down"),
value: "move:0:1",
},
],
});
if (!clicked) return;
if (typeof clicked === "string") {
const [action, a, b] = clicked.split(":");
if (action == "paint_brick") {
const x = parseInt(a),
y = parseInt(b);
bricks[y * level.size + x] =
bricks[y * level.size + x] === color ? "_" : color;
level.bricks = bricks.join("");
}
if (action == "set_color") {
color = a;
}
if (action == "size") {
const newSize = level.size + parseInt(a);
const newBricks = [];
for (let y = 0; y < newSize; y++) {
for (let x = 0; x < newSize; x++) {
newBricks.push(
(x < level.size && y < level.size && bricks[y * level.size + x]) ||
"_",
);
}
}
level.size = newSize;
level.bricks = newBricks.join("");
}
if (action == "move") {
const dx = parseInt(a),
dy = parseInt(b);
const newBricks = [];
for (let y = 0; y < level.size; y++) {
for (let x = 0; x < level.size; x++) {
const tx = x - dx;
const ty = y - dy;
if (tx < 0 || tx >= level.size || ty < 0 || ty >= level.size) {
newBricks.push("_");
} else {
newBricks.push(bricks[ty * level.size + tx]);
}
}
}
level.bricks = newBricks.join("");
}
if (action === "play") {
restart({
level: transformRawLevel(level),
isEditorTrialRun: nth,
perks: {
base_combo: 7,
},
});
return;
}
if (action === "copy") {
let text =
"```\n[" +
(level.name || "unnamed level")?.replace(/\[|\]/gi, " ") +
"]";
bricks.forEach((b, bi) => {
if (!(bi % level.size)) text += "\n";
text += b;
});
text +=
"\n[" +
(level.credit?.replace(/\[|\]/gi, " ") || "Missing credits") +
"]\n```";
navigator.clipboard.writeText(text);
// return
}
if (action === "rename") {
const name = prompt(t("editor.editing.rename_prompt"), level.name);
if (name) {
level.name = name;
}
}
if (action === "credit") {
const credit = prompt(
t("editor.editing.credit_prompt"),
level.credit || "",
);
if (credit !== "null") {
level.credit = credit || "";
}
}
if (action === "delete") {
rawList = rawList.filter((l, li) => li !== nth);
setSettingValue("custom_levels", rawList);
openLevelEditorLevelsList();
return;
}
} }
if (action === "copy") {
let text =
"```\n[" +
(level.name || "unnamed level")?.replace(/\[|\]/gi, " ") +
"]";
bricks.forEach((b, bi) => {
if (!(bi % level.size)) text += "\n";
text += b;
});
text +=
"\n[" +
(level.credit?.replace(/\[|\]/gi, " ") || "Missing credits") +
"]\n```";
navigator.clipboard.writeText(text);
// return
}
if (action === "rename") {
const name = prompt(t("editor.editing.rename_prompt"), level.name);
if (name) {
level.name = name;
}
}
if (action === "credit") {
const credit = prompt(
t("editor.editing.credit_prompt"),
level.credit || "",
);
if (credit !== "null") {
level.credit = credit || "";
}
}
if (action === "delete") {
rawList = rawList.filter((l, li) => li !== nth);
setSettingValue("custom_levels", rawList);
openLevelEditorLevelsList();
return;
}
}
level.color = automaticBackgroundColor(bricks); level.color = automaticBackgroundColor(bricks);
setSettingValue("custom_levels", rawList); setSettingValue("custom_levels", rawList);
editRawLevelList(nth, color); editRawLevelList(nth, color);
} }

View file

@ -6,7 +6,10 @@ import { getLevelBackground, hashCode } from "../getLevelBackground";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { moveLevel, resizeLevel, setBrick } from "./levels_editor_util"; import { moveLevel, resizeLevel, setBrick } from "./levels_editor_util";
import {levelCodeToRawLevel} from "../pure_functions"; import {
automaticBackgroundColor,
levelCodeToRawLevel,
} from "../pure_functions";
const backgrounds = _backgrounds as string[]; const backgrounds = _backgrounds as string[];
@ -91,12 +94,13 @@ function App() {
height: 40, height: 40,
position: "absolute", position: "absolute",
}} }}
>{ (color=="black" && '💣')||' '}</button>, >
{(palette[bricks[index]] == "black" && "💣") || " "}
</button>,
); );
} }
} }
return ( return (
<div key={li}> <div key={li}>
<input <input
@ -141,7 +145,8 @@ function App() {
className="level-bricks-preview" className="level-bricks-preview"
style={{ style={{
width: size * 40, width: size * 40,
height: size * 40 height: size * 40,
background: automaticBackgroundColor(bricks.split("")),
}} }}
> >
{brickButtons} {brickButtons}
@ -163,7 +168,9 @@ function App() {
border: "1px solid black", border: "1px solid black",
}} }}
onClick={() => setSelected(code)} onClick={() => setSelected(code)}
>{(color=='' && 'x') || (color=="black" && '💣')||' '}</button> >
{(color == "" && "x") || (color == "black" && "💣") || " "}
</button>
))} ))}
</div> </div>
<button <button
@ -191,13 +198,10 @@ function App() {
id="import-level" id="import-level"
onClick={() => { onClick={() => {
const code = prompt("Level Code ? "); const code = prompt("Level Code ? ");
if(!code) return; if (!code) return;
const l=levelCodeToRawLevel(code) const l = levelCodeToRawLevel(code);
if(!l)return; if (!l) return;
setLevels((list) => [ setLevels((list) => [...list, l]);
...list,
l
]);
}} }}
> >
import import

View file

@ -6,7 +6,7 @@ import { rawUpgrades } from "./upgrades";
import { getLevelBackground } from "./getLevelBackground"; import { getLevelBackground } from "./getLevelBackground";
import { levelIconHTML } from "./levelIcon"; import { levelIconHTML } from "./levelIcon";
import {automaticBackgroundColor} from "./pure_functions"; import { automaticBackgroundColor } from "./pure_functions";
const palette = _palette as Palette; const palette = _palette as Palette;
@ -17,8 +17,7 @@ export const appVersion = _appVersion as string;
export const icons = {} as { [k: string]: string }; export const icons = {} as { [k: string]: string };
export function transformRawLevel(level: RawLevel) { export function transformRawLevel(level: RawLevel) {
const splitBricks=level.bricks const splitBricks = level.bricks.split("");
.split("")
const bricks = splitBricks const bricks = splitBricks
.map((c) => palette[c]) .map((c) => palette[c])
.slice(0, level.size * level.size); .slice(0, level.size * level.size);
@ -30,7 +29,7 @@ export function transformRawLevel(level: RawLevel) {
bricks, bricks,
bricksCount, bricksCount,
icon, icon,
color: automaticBackgroundColor(splitBricks) , color: automaticBackgroundColor(splitBricks),
svg: getLevelBackground(level), svg: getLevelBackground(level),
sortKey: ((Math.random() + 3) / 3.5) * bricksCount, sortKey: ((Math.random() + 3) / 3.5) * bricksCount,
}; };

View file

@ -1,4 +1,4 @@
import {Ball, GameState} from "./types"; import { Ball, GameState } from "./types";
export function clamp(value: number, min: number, max: number) { export function clamp(value: number, min: number, max: number) {
return Math.max(min, Math.min(value, max)); return Math.max(min, Math.min(value, max));
@ -107,33 +107,31 @@ export const MAX_LEVEL_SIZE = 21;
export const MIN_LEVEL_SIZE = 2; export const MIN_LEVEL_SIZE = 2;
export function automaticBackgroundColor(bricks: string[]) { export function automaticBackgroundColor(bricks: string[]) {
return bricks.filter((b) => b === "g").length > return bricks.filter((b) => b === "g").length >
bricks.filter((b) => b !== "_").length * 0.05 bricks.filter((b) => b !== "_").length * 0.05
? "#115988" ? "#115988"
: ""; : "";
} }
export function levelCodeToRawLevel(code: string) { export function levelCodeToRawLevel(code: string) {
let [name, credit] = code.match(/\[([^\]]+)]/gi) || ["", ""];
let [name, credit] = code.match(/\[([^\]]+)]/gi); let bricks = code.split(name)[1].split(credit)[0].replace(/\s/gi, "");
name = name.slice(1, -1);
let bricks = code credit = credit.slice(1, -1);
.split(name)[1] name ||= "Imported on " + new Date().toISOString().slice(0, 10);
.split(credit)[0] credit ||= "";
.replace(/\s/gi, ""); const size = Math.sqrt(bricks.length);
name = name.slice(1, -1); if (
credit = credit.slice(1, -1); Math.floor(size) === size &&
name ||= "Imported on " + new Date().toISOString().slice(0, 10); size >= MIN_LEVEL_SIZE &&
credit ||= ""; size <= MAX_LEVEL_SIZE
const size = Math.sqrt(bricks.length); )
if (Math.floor(size) === size && return {
size >= MIN_LEVEL_SIZE && color: automaticBackgroundColor(bricks.split("")),
size <= MAX_LEVEL_SIZE) size,
return { bricks,
color: automaticBackgroundColor(bricks.split("")), name,
size, credit,
bricks, };
name, }
credit,
}
}

File diff suppressed because it is too large Load diff

View file

@ -18,10 +18,7 @@ export function startingPerkMenuButton() {
} }
export function isBlackListedForStart(u: Upgrade) { export function isBlackListedForStart(u: Upgrade) {
return !!( return !!(u.requires || u.threshold > getTotalScore());
u.requires ||
u.threshold > getTotalScore()
);
} }
export function isStartingPerk(u: Upgrade): boolean { export function isStartingPerk(u: Upgrade): boolean {
return ( return (
@ -38,7 +35,7 @@ export async function openStartingPerksEditor() {
text: u.name, text: u.name,
tooltip: u.help(1), tooltip: u.help(1),
value: [u], value: [u],
checked checked,
}; };
}); });
const checkedList = buttons.filter((b) => b.checked); const checkedList = buttons.filter((b) => b.checked);

File diff suppressed because it is too large Load diff