diff --git a/Readme.md b/Readme.md index 2b1a3d7..91d43a6 100644 --- a/Readme.md +++ b/Readme.md @@ -30,9 +30,8 @@ There's also an easy mode for kids (slower ball) and a color-blind mode (no colo ## Perk ideas - wrap left / right -- puck bounce predictions (using particles) - n% of the broken bricks respawn when the ball touches the puck -- bricks take twice as many hits but drop 50% more coins +- bricks break 50% of the time but drop 50% more coins - wind (puck positions adds force to coins and balls) - balls repulse coins - n% of coins missed respawn at the top @@ -61,6 +60,7 @@ There's also an easy mode for kids (slower ball) and a color-blind mode (no colo - new ball spawns when reaching combo X - missing with combo triggers explosive lightning strike - correction : pick one past upgrade to remove and replace by something else +- puck bounce predictions rendered with particles or lines (requires big refactor) ## Engine ideas diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e787fff..6275ee3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "me.lecaro.breakout" minSdk = 21 targetSdk = 34 - versionCode = 29000827 - versionName = "29000827" + versionCode = 29002295 + versionName = "29002295" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true diff --git a/app/src/main/assets/game.js b/app/src/main/assets/game.js index 7de4238..7cf6b49 100644 --- a/app/src/main/assets/game.js +++ b/app/src/main/assets/game.js @@ -259,14 +259,16 @@ function resetBalls() { sx: 0, sy: 0, color: currentLevelInfo()?.black_puck ? '#000' : "#FFF", - hitSinceBounce: 0, - piercedSinceBounce: 0, sparks: 0, + piercedSinceBounce: 0, + hitSinceBounce: 0, + hitItem:[], }); } } function putBallsAtPuck() { + // This reset could be abused to cheat quite easily const count = balls.length; const perBall = puckWidth / (count + 1); balls.forEach((ball, i) => { @@ -280,6 +282,10 @@ function putBallsAtPuck() { vy: -baseSpeed, sx: 0, sy: 0, + hitItem:[], + hitSinceBounce: 0, + piercedSinceBounce: 0, + // piercedSinceBounce: 0, }); }); } @@ -352,6 +358,7 @@ async function openUpgradesPicker() { let {text, repeats, choices} = getLevelStats(); scoreStory.push(`Finished level ${currentLevel + 1} (${currentLevelInfo().name}): ${text}`,); + while (repeats--) { const actions = pickRandomUpgrades(choices); if (!actions.length) break @@ -659,6 +666,20 @@ const upgrades = [ "max": 3, "help": "Puck position creates wind.", }, + { + "threshold": 40000, + "id": "sturdy_bricks", + "name": "Sturdy bricks", + "max": 4, + "help": "Bricks sometimes resist hits but drop more coins.", + }, + { + "threshold": 45000, + "id": "respawn", + "name": "Respawn", + "max": 4, + "help": "The first brick hit will respawn.", + }, ] @@ -1104,10 +1125,8 @@ function ballTick(ball, delta) { ball.vy *= (1 + .02 / speedLimitDampener); } else { ball.vx *= (1 - .02 / speedLimitDampener); - ; if (Math.abs(ball.vy) > 0.5 * baseSpeed) { ball.vy *= (1 - .02 / speedLimitDampener); - ; } } @@ -1158,21 +1177,16 @@ function ballTick(ball, delta) { if (perks.streak_shots) { resetCombo(ball.x, ball.y); } + + if(perks.respawn){ + ball.hitItem.slice(0,-1).slice(0,perks.respawn) + .forEach(({index,color})=>bricks[index]=bricks[index]||color) + } + ball.hitItem=[] if (!ball.hitSinceBounce) { incrementRunStatistics('miss') levelMisses++; const loss = resetCombo(ball.x, ball.y) - // - // flashes.push({ - // type: "text", - // text: 'miss', - // time: levelTime, - // color: ball.color, - // x: ball.x, - // y: ball.y - ballSize, - // duration: 450, - // size: puckHeight, - // }) if (ball.bouncesList?.length) { ball.bouncesList.push({ x: ball.previousx, @@ -1237,12 +1251,16 @@ function ballTick(ball, delta) { } const hitBrick = brickHitCheck(ball, ballSize / 2, true); if (typeof hitBrick !== "undefined") { - const wasABomb = bricks[hitBrick] === "black"; + const initialBrickColor = bricks[hitBrick] + explodeBrick(hitBrick, ball, false); - if (perks.sapper && !wasABomb) { + if (perks.sapper && initialBrickColor !== "black" && + // don't replace a brick that bounced with sturdy_bricks + !bricks[hitBrick]) { bricks[hitBrick] = "black"; } + } if (!isSettingOn("basic")) { @@ -1419,6 +1437,15 @@ function explodeBrick(index, ball, isExplosion) { spawnExplosion(7 * (1 + perks.bigger_explosions), x, y, 'white', 150, coinSize,); ball.hitSinceBounce++; } else if (color) { + // Even if it bounces we don't want to count that as a miss + ball.hitSinceBounce++; + + if(perks.sturdy_bricks && perks.sturdy_bricks*2>Math.random()*10){ + // Resist + sounds.coinBounce(ball.x, 1) + return + } + // Flashing is take care of by the tick loop const x = brickCenterX(index), y = brickCenterY(index); @@ -1429,7 +1456,13 @@ function explodeBrick(index, ball, isExplosion) { incrementRunStatistics('spawned_coins', combo) coins = coins.filter((c) => !c.destroyed); - for (let i = 0; i < combo; i++) { + let coinsToSpawn=combo + if(perks.sturdy_bricks){ + // +10% per level + coinsToSpawn+=Math.ceil((10+perks.sturdy_bricks) / 10 * coinsToSpawn) + } + + while (coinsToSpawn-- ) { // Avoids saturating the canvas with coins if (coins.length > MAX_COINS * (isSettingOn("basic") ? 0.5 : 1)) { // Just pick a random one @@ -1471,13 +1504,19 @@ function explodeBrick(index, ball, isExplosion) { sounds.comboIncreaseMaybe(ball.x, 1); } } - ball.hitSinceBounce++; flashes.push({ type: "ball", duration: 40, time: levelTime, size: brickWidth, color: color, x, y, }); spawnExplosion(5 + combo, x, y, color, 100, coinSize / 2); } + + if(!bricks[index] ){ + ball.hitItem?.push({ + index, + color + }) + } } function max_levels() { @@ -1654,15 +1693,6 @@ function render() { if (!type || type === "black" || okColors.has(type)) return; const x = brickCenterX(index), y = brickCenterY(index); drawFuzzyBall(ctx, "red", brickWidth, x, y); - // - // baseParticle && flashes.push({ - // ...baseParticle, - // duration: 100, - // x, - // y, - // vx: (0.5 - Math.random()) * 10, - // vy: (0.5 - Math.random()) * 10, - // }) }); } ctx.globalAlpha = 1; @@ -1728,7 +1758,6 @@ function render() { } }); // The puck - ctx.globalAlpha = 1 ctx.globalCompositeOperation = "source-over"; drawPuck(ctx, puckColor, puckWidth, puckHeight) @@ -2037,7 +2066,8 @@ const sounds = { comboDecrease() { if (!isSettingOn("sound")) return; playShepard(-1, 0.5, 0.5); - }, coinBounce: (pan, volume) => { + }, + coinBounce: (pan, volume) => { if (!isSettingOn("sound")) return; createSingleBounceSound(1200, pixelsToPan(pan), volume); }, explode: (pan) => { @@ -2497,12 +2527,14 @@ Click an item above to start a test run with it. } ], textAfterButtons: ` -
Made in France by Renan LE CARO
- privacy policy -
- Google Play -
- itch.io
- v.${window.appVersion}
-
+
+ Made in France by Renan LE CARO. + Privacy Policy + Google Play + itch.io + Gitlab + Web version + v.${window.appVersion}
` }) diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html index 9fdc78e..70d16c1 100644 --- a/app/src/main/assets/index.html +++ b/app/src/main/assets/index.html @@ -8,16 +8,16 @@ />