Creative mode, cleanup loop fix

This commit is contained in:
Renan LE CARO 2025-03-07 20:18:18 +01:00
parent 2d2d4fd963
commit 504fd6649c
8 changed files with 3189 additions and 2901 deletions

View file

@ -38,7 +38,6 @@ quickly destroyed again.
# Game engine features
- the onboarding feels weird, missing a tutorial
- Players can't choose the initial perk
- apk version soft locks at start.
- shinier coins by applying glow to them ?
- ask for permanent storage
@ -124,6 +123,28 @@ quickly destroyed again.
- gravity is flipped on the opposite side to the puck (for coins)
- balls have gravity
- coins don't have gravity
- [colin] yoyo - when the ball falls back down, it curbs towards your puck (after hitting a brick or top)
- [colin] single block combo - get +1 combo if the ball only breaks a single block before reaching the puck
- [colin] mirror puck - a mirrored puck at the top of the screen follows as you move the bottom puck. it helps with keeping combos up and preventing the ball from touching the ceiling. it could appear as a hollow puck so as to not draw too much attention from the main bottom puck.
- [colin] side pucks - same as above but with two side pucks.
- [colin] ball coins - coins share the same physics as coins and bounce on walls and bricks
- [colin] phantom coins - coins pass through bricks
- [colin] drifting coins - coins slowly drift away from the brick they were generated from, and they need to be collected by the ball
- [colin] bigger ball - self-explanatory
- [colin] smaller ball - yes.
- [colin] sturdy ball - does more damage to bricks, to conter sturdy bricks
- [colin] accumulation - coins aglutinate into bigger coins that hold more value
- [colin] forgiving - you can miss several times without losing your combo. or alternatively, include this ability into the soft reset perk.
- [colin] plot - plot the ball's trajectory as you position your puck
- [colin] golden corners - catch coins at the sides of the puck to double their value
- [colin] varied diet - your combo grows if you keep hitting different coloured bricks each time
- [colin] earthquake - when the puck hits any side of the screen with velocity, the screen shakes and a brick explodes/falls from the level. alternatively, any brick you catch with the puck gives you the coins at the current combo rate. each level lowers the amount of hits before a brick falls
- [colin] statue - stand still to make the combo grow. move for too long and thi combo will quickly drop
- [colin] piggy bank - bricks absorb coins that fall onto it, and release them back as they are broken, with added value
- [colin] trickle up - if you first hit is the lowest brick of a column, all bricks above get +1 coin inside
- [colin] wormhole - the puck sometimes don't bounce the ball back up but teleports it to the top of the screen as if it fell through from bottom to top. higher levels reduce the times it takes to reload that effect
- [colin] hitman - hit the marked brick for +5 combo. each level increases the combo you get for it.
- [colin] sweet spot - place your puck directly below a moving spot at the top of the level to increase your combo
# Balancing ideas
@ -165,31 +186,6 @@ I could unlock the "pro stand" at $999 that just holds the play area higher.
# Colin's feedback (cwpute/obigre)
Perks:
* yoyo - when the ball falls back down, it curbs towards your puck (after hitting a brick or top)
* single block combo - get +1 combo if the ball only breaks a single block before reaching the puck
* mirror puck - a mirrored puck at the top of the screen follows as you move the bottom puck. it helps with keeping combos up and preventing the ball from touching the ceiling. it could appear as a hollow puck so as to not draw too much attention from the main bottom puck.
* side pucks - same as above but with two side pucks.
* ball coins - coins share the same physics as coins and bounce on walls and bricks
* phantom coins - coins pass through bricks
* drifting coins - coins slowly drift away from the brick they were generated from, and they need to be collected by the ball
* bigger ball - self-explanatory
* smaller ball - yes.
* sturdy ball - does more damage to bricks, to conter sturdy bricks
* accumulation - coins aglutinate into bigger coins that hold more value
* forgiving - you can miss several times without losing your combo. or alternatively, include this ability into the soft reset perk.
* plot - plot the ball's trajectory as you position your puck
* golden corners - catch coins at the sides of the puck to double their value
* varied diet - your combo grows if you keep hitting different coloured bricks each time
* earthquake - when the puck hits any side of the screen with velocity, the screen shakes and a brick explodes/falls from the level. alternatively, any brick you catch with the puck gives you the coins at the current combo rate. each level lowers the amount of hits before a brick falls
* statue - stand still to make the combo grow. move for too long and thi combo will quickly drop
* piggy bank - bricks absorb coins that fall onto it, and release them back as they are broken, with added value
* trickle up - if you first hit is the lowest brick of a column, all bricks above get +1 coin inside
* wormhole - the puck sometimes don't bounce the ball back up but teleports it to the top of the screen as if it fell through from bottom to top. higher levels reduce the times it takes to reload that effect
* hitman - hit the marked brick for +5 combo. each level increases the combo you get for it.
* sweet spot - place your puck directly below a moving spot at the top of the level to increase your combo
IMPROVEMENTS ON EXISTING PERKS :
* separate the "shoot straight" perk into two : one for left-side, the other for right-side. it will help alleviate the high difficulty of this challenge and provide more interesting ways to play around it. the wind perk could even find a use.

292
dist/index.html vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -450,9 +450,15 @@
"svg": ""
},
{
"name": "icon:sides_are_lava",
"name": "icon:left_is_lava",
"size": 8,
"bricks": "r______rrttttttrrttttttrr______rr______rr____W_rr______rr_WWW__r",
"bricks": "r_______rtttttt_rtttttt_r_______r_______r____W__r_______r_WWW___",
"svg": ""
},
{
"name": "icon:right_is_lava",
"size": 8,
"bricks": "_______r_ttttttr_ttttttr_______r_______r_____W_r_______r__WWW__r",
"svg": ""
},
{

View file

@ -1,54 +1,54 @@
import {fitSize, gameCanvas} from "./game";
import { fitSize, gameCanvas } from "./game";
export const options = {
sound: {
default: true,
name: `Game sounds`,
help: `Can slow down some phones.`,
disabled: () => false,
sound: {
default: true,
name: `Game sounds`,
help: `Can slow down some phones.`,
disabled: () => false,
},
"mobile-mode": {
default: window.innerHeight > window.innerWidth,
name: `Mobile mode`,
help: `Leaves space for your thumb.`,
afterChange() {
fitSize();
},
"mobile-mode": {
default: window.innerHeight > window.innerWidth,
name: `Mobile mode`,
help: `Leaves space for your thumb.`,
afterChange() {
fitSize();
},
disabled: () => false,
disabled: () => false,
},
basic: {
default: false,
name: `Basic graphics`,
help: `Better performance on older devices.`,
disabled: () => false,
},
pointerLock: {
default: false,
name: `Mouse pointer lock`,
help: `Locks and hides the mouse cursor.`,
disabled: () => !gameCanvas.requestPointerLock,
},
easy: {
default: false,
name: `Kids mode`,
help: `Start future runs with "slower ball".`,
disabled: () => false,
}, // Could not get the sharing to work without loading androidx and all the modern android things so for now i'll just disable sharing in the android app
record: {
default: false,
name: `Record gameplay videos`,
help: `Get a video of each level.`,
disabled() {
return window.location.search.includes("isInWebView=true");
},
basic: {
default: false,
name: `Basic graphics`,
help: `Better performance on older devices.`,
disabled: () => false,
},
pointerLock: {
default: false,
name: `Mouse pointer lock`,
help: `Locks and hides the mouse cursor.`,
disabled: () => !gameCanvas.requestPointerLock,
},
easy: {
default: false,
name: `Kids mode`,
help: `Start future runs with "slower ball".`,
disabled: () => false,
}, // Could not get the sharing to work without loading androidx and all the modern android things so for now i'll just disable sharing in the android app
record: {
default: false,
name: `Record gameplay videos`,
help: `Get a video of each level.`,
disabled() {
return window.location.search.includes("isInWebView=true");
},
},
} as {[k:string]:OptionDef}
},
} as { [k: string]: OptionDef };
export type OptionDef = {
default:boolean;
name:string;
help:string;
disabled:()=>boolean
afterChange?:()=>void
}
export type OptionId = keyof (typeof options)
default: boolean;
name: string;
help: string;
disabled: () => boolean;
afterChange?: () => void;
};
export type OptionId = keyof typeof options;

View file

@ -75,15 +75,31 @@ export const rawUpgrades = [
{
requires: "",
threshold: 0,
id: "sides_are_lava",
id: "left_is_lava",
giftable: true,
name: "Shoot straight",
name: "Avoid left side",
max: 1,
help: (lvl) => `More coins if you don't touch the sides.`,
help: (lvl) => `More coins if you don't touch the left side.`,
fullHelp: `Whenever you break a brick, your combo will increase by one, so you'll get one more coin all the next bricks you break.
However, your combo will reset as soon as your ball hits the left or right side.
As soon as your combo rises, the sides become red to remind you that you should avoid hitting them. The effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any
fullHelp: `Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.
However, your combo will reset as soon as your ball hits the left side .
As soon as your combo rises, the left side becomes red to remind you that you should avoid hitting them.
The effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any
of the reset conditions are met.`,
},
{
requires: "",
threshold: 0,
id: "right_is_lava",
giftable: true,
name: "Avoid right side",
max: 1,
help: (lvl) => `More coins if you don't touch the right side.`,
fullHelp: `Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.
However, your combo will reset as soon as your ball hits the right side .
As soon as your combo rises, the right side becomes red to remind you that you should avoid hitting them.
The effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any
of the reset conditions are met.`,
},
{
@ -94,7 +110,6 @@ export const rawUpgrades = [
name: "Sky is the limit",
max: 1,
help: (lvl) => `More coins if you don't touch the top.`,
fullHelp: `Whenever you break a brick, your combo will increase by one. However, your combo will reset as soon as your ball hit the top of the screen.
When your combo is above the minimum, a red bar will appear at the top to remind you that you should avoid hitting it.
The effect stacks with other combo perks.`,

View file

@ -90,6 +90,15 @@ body {
max-width: 450px;
}
.popup.actionsAsGrid > div {
max-width: 800px;
section {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
}
.popup > div > * {
padding: 0;
margin: 0;
@ -100,25 +109,58 @@ body {
margin-bottom: 20px;
}
.popup > div > button {
font: inherit;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
cursor: pointer;
border: 1px solid white;
text-align: left;
.popup > div > section {
display: flex;
gap: 10px;
margin-top: -1px;
flex-direction: column;
align-items: stretch;
margin-top: 20px;
button {
font: inherit;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
cursor: pointer;
border: 1px solid white;
text-align: left;
display: flex;
gap: 10px;
margin-top: -1px;
&:not([disabled]):hover,
&:not([disabled]):focus {
border-color: #f1d33b;
position: relative;
z-index: 1;
}
&[disabled] {
/*border: 1px solid #666;*/
opacity: 0.5;
filter: saturate(0);
pointer-events: none;
}
& > div {
flex-grow: 1;
}
& > div > em {
display: block;
opacity: 0.8;
}
&.grey-out-unless-hovered {
&:not(:hover) {
opacity: 0.6;
img {
filter: saturate(0);
}
}
}
}
}
.popup > div > button:not([disabled]):hover,
.popup > div > button:not([disabled]):focus {
border-color: #f1d33b;
position: relative;
z-index: 1;
}
.popup button.close-modale {
color: white;
@ -131,61 +173,21 @@ body {
border: none;
cursor: pointer;
overflow: hidden;
}
.popup button.close-modale:before {
content: "+";
position: absolute;
transform: translate(-50%, -50%) rotate(45deg);
font-size: 80px;
display: inline-block;
top: 34px;
left: 26px;
}
&:before {
content: "+";
position: absolute;
transform: translate(-50%, -50%) rotate(45deg);
font-size: 80px;
display: inline-block;
top: 34px;
left: 26px;
}
.popup button.close-modale:hover {
font-weight: bold;
background: black;
}
.popup > div > button[disabled] {
/*border: 1px solid #666;*/
opacity: 0.5;
filter: saturate(0);
pointer-events: none;
}
.popup > div > button > div {
flex-grow: 1;
}
.popup > div > button > div > em {
display: block;
opacity: 0.8;
}
.popup > div > button > span.checks {
width: 40px;
height: 40px;
display: inline-flex;
gap: 5px;
flex-grow: 0;
flex-shrink: 0;
}
.popup > div > button > span.checks > span {
flex-basis: 10px;
flex-grow: 1;
flex-shrink: 1;
/*border: 1px solid white;*/
background: white;
opacity: 0.1;
border-radius: 4px;
align-self: stretch;
}
.popup > div > button > span.checks > span.checked {
opacity: 1;
&:hover {
font-weight: bold;
background: black;
}
}
.popup .textAfterButtons {
@ -289,9 +291,11 @@ body {
flex-direction: column;
justify-content: flex-end;
}
.histogram > span.active > span {
background: #4049ca;
}
.histogram > span > span {
/*Visible bar*/
background: #1c1c2f;

222
src/types.d.ts vendored
View file

@ -1,143 +1,139 @@
import {rawUpgrades} from "./rawUpgrades";
import { rawUpgrades } from "./rawUpgrades";
export type colorString = string;
export type RawLevel = {
name: string;
size: number;
bricks: string;
svg: string;
color: string;
name: string;
size: number;
bricks: string;
svg: string;
color: string;
};
export type Level = {
name: string;
size: number;
bricks: colorString[];
svg: string;
color: string;
threshold?: number;
sortKey?: number;
name: string;
size: number;
bricks: colorString[];
svg: string;
color: string;
threshold?: number;
sortKey?: number;
};
export type Palette = { [k: string]: string };
export type Upgrade = {
threshold: number;
giftable: boolean;
id: PerkId;
name: string;
icon: string;
max: number;
help: (lvl: number) => string;
fullHelp: string;
requires: PerkId | "";
threshold: number;
giftable: boolean;
id: PerkId;
name: string;
icon: string;
max: number;
help: (lvl: number) => string;
fullHelp: string;
requires: PerkId | "";
};
export type PerkId = (typeof rawUpgrades)[number]["id"];
declare global {
interface Window {
webkitAudioContext?: typeof AudioContext;
}
interface Window {
webkitAudioContext?: typeof AudioContext;
}
interface Document {
webkitFullscreenEnabled?: boolean;
webkitCancelFullScreen?: () => void;
}
interface Document {
webkitFullscreenEnabled?: boolean;
webkitCancelFullScreen?: () => void;
}
interface Element {
webkitRequestFullscreen: typeof Element.requestFullscreen
}
interface MediaStream {
// https://devdoc.net/web/developer.mozilla.org/en-US/docs/Web/API/CanvasCaptureMediaStream.html
// On firefox, the capture stream has the requestFrame option
// instead of the track, go figure
requestFrame?:()=>void
}
interface Element {
webkitRequestFullscreen: typeof Element.requestFullscreen;
}
interface MediaStream {
// https://devdoc.net/web/developer.mozilla.org/en-US/docs/Web/API/CanvasCaptureMediaStream.html
// On firefox, the capture stream has the requestFrame option
// instead of the track, go figure
requestFrame?: () => void;
}
}
export type BallLike = {
x: number;
y: number;
vx?: number;
vy?: number;
}
x: number;
y: number;
vx?: number;
vy?: number;
};
export type Coin = {
points: number;
color: colorString;
x: number;
y: number;
previousx: number;
previousy: number;
vx: number;
vy: number;
sx: number;
sy: number;
a: number;
sa: number;
weight: number;
destroyed?: boolean;
coloredABrick?: boolean;
}
points: number;
color: colorString;
x: number;
y: number;
previousx: number;
previousy: number;
vx: number;
vy: number;
sx: number;
sy: number;
a: number;
sa: number;
weight: number;
destroyed?: boolean;
coloredABrick?: boolean;
};
export type Ball = {
x: number;
previousx: number;
y: number;
previousy: number;
vx: number;
vy: number;
sx: number;
sy: number;
sparks: number;
piercedSinceBounce: number;
hitSinceBounce: number;
hitItem: { index: number, color: string }[];
bouncesList?: { x: number, y: number }[];
sapperUses: number;
destroyed?: boolean;
previousvx?: number;
previousvy?: number;
}
x: number;
previousx: number;
y: number;
previousy: number;
vx: number;
vy: number;
sx: number;
sy: number;
sparks: number;
piercedSinceBounce: number;
hitSinceBounce: number;
hitItem: { index: number; color: string }[];
bouncesList?: { x: number; y: number }[];
sapperUses: number;
destroyed?: boolean;
previousvx?: number;
previousvy?: number;
};
export type FlashTypes = "text" | "particle" | 'ball'
export type FlashTypes = "text" | "particle" | "ball";
export type Flash = {
type: FlashTypes;
text?: string;
time: number;
color: colorString;
x: number;
y: number;
duration: number;
size: number;
vx?: number;
vy?: number;
ethereal?: boolean;
destroyed?: boolean;
}
type: FlashTypes;
text?: string;
time: number;
color: colorString;
x: number;
y: number;
duration: number;
size: number;
vx?: number;
vy?: number;
ethereal?: boolean;
destroyed?: boolean;
};
export type RunStats= {
started: number;
levelsPlayed: number;
runTime: number;
coins_spawned: number;
score: number;
bricks_broken: number;
misses: number;
balls_lost: number;
puck_bounces: number;
upgrades_picked: number;
max_combo: number;
max_level: number;
}
export type RunHistoryItem =RunStats & {
perks?: {[k in PerkId]:number};
appVersion?:string;
}
export type RunStats = {
started: number;
levelsPlayed: number;
runTime: number;
coins_spawned: number;
score: number;
bricks_broken: number;
misses: number;
balls_lost: number;
puck_bounces: number;
upgrades_picked: number;
max_combo: number;
max_level: number;
};
export type RunHistoryItem = RunStats & {
perks?: { [k in PerkId]: number };
appVersion?: string;
};