mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-21 04:26:14 -04:00
wip
This commit is contained in:
parent
0ff6ae1fdf
commit
d3296c4f0f
2 changed files with 262 additions and 152 deletions
|
@ -14,12 +14,12 @@ At the end of each level, you get to select an upgrade.
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Fdroid
|
- Fdroid
|
||||||
- pause/resume audio context
|
- perk : elastic between balls
|
||||||
|
- easily start a test game with specific upgrades or levels (with query string or through menu)
|
||||||
- show total score on end screen (score added to total)
|
- show total score on end screen (score added to total)
|
||||||
- show stats on end screen compared to other runs
|
- show stats on end screen compared to other runs
|
||||||
- handle back bouton in menu
|
- handle back bouton in menu
|
||||||
- more levels : famous simple games, letters, fruits, animals
|
- more levels : famous simple games, letters, fruits, animals
|
||||||
- perk : elastic between balls
|
|
||||||
- perk : wrap left / right
|
- perk : wrap left / right
|
||||||
- perk : twice as many coins after a wall bounce, twice as little otherwise
|
- perk : twice as many coins after a wall bounce, twice as little otherwise
|
||||||
- perk : fusion reactor (gather coins in one spot to triple their value)
|
- perk : fusion reactor (gather coins in one spot to triple their value)
|
||||||
|
@ -27,8 +27,8 @@ At the end of each level, you get to select an upgrade.
|
||||||
- perk : n/10 of the broken bricks respawn when the ball comes back
|
- perk : n/10 of the broken bricks respawn when the ball comes back
|
||||||
- perk : bricks take twice as many hits but drop 50% more coins
|
- perk : bricks take twice as many hits but drop 50% more coins
|
||||||
- perk : wind (puck positions adds force to coins and balls)
|
- perk : wind (puck positions adds force to coins and balls)
|
||||||
- perk : balls repulse each other
|
|
||||||
- perk : balls repulse coins
|
- perk : balls repulse coins
|
||||||
|
- missing triggers and explosive lighting strike around ball path
|
||||||
|
|
||||||
## maybe
|
## maybe
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,7 @@ function decreaseCombo(by, x, y) {
|
||||||
let gridSize = 12;
|
let gridSize = 12;
|
||||||
|
|
||||||
let running = false, puck = 400;
|
let running = false, puck = 400;
|
||||||
|
|
||||||
function play() {
|
function play() {
|
||||||
if (running) return
|
if (running) return
|
||||||
running = true
|
running = true
|
||||||
|
@ -95,6 +96,7 @@ function play(){
|
||||||
audioContext.resume()
|
audioContext.resume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause() {
|
function pause() {
|
||||||
if (!running) return
|
if (!running) return
|
||||||
running = false
|
running = false
|
||||||
|
@ -191,7 +193,7 @@ function addToScore(coin) {
|
||||||
coin.destroyed = true
|
coin.destroyed = true
|
||||||
score += coin.points;
|
score += coin.points;
|
||||||
addToTotalScore(coin.points)
|
addToTotalScore(coin.points)
|
||||||
if (score > highScore) {
|
if (score > highScore && !hadOverrides) {
|
||||||
highScore = score;
|
highScore = score;
|
||||||
localStorage.setItem("breakout-3-hs", score);
|
localStorage.setItem("breakout-3-hs", score);
|
||||||
}
|
}
|
||||||
|
@ -373,46 +375,49 @@ function reset_perks() {
|
||||||
perks[u.id] = 0;
|
perks[u.id] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const giftable = getPossibleUpgrades().filter(u => u.giftable)
|
if (nextRunOverrides.perks) {
|
||||||
if (!giftable.length) {
|
const first = Object.keys(nextRunOverrides.perks)[0]
|
||||||
debugger
|
Object.assign(perks, nextRunOverrides.perks)
|
||||||
|
nextRunOverrides.perks = null
|
||||||
|
return first
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const giftable = getPossibleUpgrades().filter(u => u.giftable && u.max > 0)
|
||||||
const randomGift = isSettingOn('easy') ? 'slow_down' : giftable[Math.floor(Math.random() * giftable.length)].id;
|
const randomGift = isSettingOn('easy') ? 'slow_down' : giftable[Math.floor(Math.random() * giftable.length)].id;
|
||||||
perks[randomGift] = 1;
|
perks[randomGift] = 1;
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// perks.puck_repulse_ball=3
|
// perks.puck_repulse_ball=3
|
||||||
// perks.multiball = 1
|
// perks.ball_repulse_ball=3
|
||||||
// perks.ball_repulse_ball = 1
|
// perks.ball_attract_ball=3
|
||||||
|
// perks.multiball=3
|
||||||
|
|
||||||
return randomGift
|
return randomGift
|
||||||
}
|
}
|
||||||
|
|
||||||
const upgrades = [{
|
const upgrades = [
|
||||||
|
{
|
||||||
minimumTotalScore: 3000,
|
minimumTotalScore: 3000,
|
||||||
id: 'multiball',
|
id: 'multiball',
|
||||||
giftableAfterTotalScore: 20000,
|
giftable: true,
|
||||||
name: "+1 ball",
|
name: "+1 ball",
|
||||||
max: 3,
|
max: 3,
|
||||||
help: `Start each level with one more balls.`,
|
help: `Start each level with one more balls.`,
|
||||||
}, {
|
}, {
|
||||||
minimumTotalScore: 5000,
|
minimumTotalScore: 5000,
|
||||||
id: 'pierce',
|
id: 'pierce',
|
||||||
giftableAfterTotalScore: 15000,
|
giftable: true,
|
||||||
name: "Ball pierces bricks",
|
name: "Ball pierces bricks",
|
||||||
max: 3,
|
max: 3,
|
||||||
help: `Pierce through 3 blocks after bouncing on the puck.`,
|
help: `Pierce through 3 blocks after bouncing on the puck.`,
|
||||||
}, {
|
}, {
|
||||||
minimumTotalScore: 500,
|
minimumTotalScore: 500,
|
||||||
id: 'telekinesis',
|
id: 'telekinesis',
|
||||||
giftableAfterTotalScore: 900,
|
giftable: true,
|
||||||
name: "Puck controls ball",
|
name: "Puck controls ball",
|
||||||
max: 2,
|
max: 2,
|
||||||
help: `Control the ball's trajectory with the puck.`,
|
help: `Control the ball's trajectory with the puck.`,
|
||||||
}, {
|
}, {
|
||||||
minimumTotalScore: 0,
|
minimumTotalScore: 0,
|
||||||
extra_levels_minimum_total_score: 250,
|
|
||||||
id: 'extra_life',
|
id: 'extra_life',
|
||||||
name: "+1 life",
|
name: "+1 life",
|
||||||
max: 3,
|
max: 3,
|
||||||
|
@ -420,7 +425,7 @@ const upgrades = [{
|
||||||
}, {
|
}, {
|
||||||
minimumTotalScore: 20000,
|
minimumTotalScore: 20000,
|
||||||
id: 'sapper',
|
id: 'sapper',
|
||||||
giftableAfterTotalScore: 32000,
|
giftable: true,
|
||||||
name: "Bricks become bombs",
|
name: "Bricks become bombs",
|
||||||
max: 1,
|
max: 1,
|
||||||
help: `Broken blocks are replaced by bombs.`,
|
help: `Broken blocks are replaced by bombs.`,
|
||||||
|
@ -460,7 +465,7 @@ const upgrades = [{
|
||||||
{
|
{
|
||||||
minimumTotalScore: 6000,
|
minimumTotalScore: 6000,
|
||||||
id: 'picky_eater',
|
id: 'picky_eater',
|
||||||
giftableAfterTotalScore: 9000,
|
giftable: true,
|
||||||
name: "Single color streak",
|
name: "Single color streak",
|
||||||
color_blind_exclude: true,
|
color_blind_exclude: true,
|
||||||
max: 1,
|
max: 1,
|
||||||
|
@ -479,7 +484,7 @@ const upgrades = [{
|
||||||
{
|
{
|
||||||
minimumTotalScore: 0,
|
minimumTotalScore: 0,
|
||||||
id: 'streak_shots',
|
id: 'streak_shots',
|
||||||
giftableAfterTotalScore: 1500,
|
giftable: true,
|
||||||
name: "Single puck hit streak",
|
name: "Single puck hit streak",
|
||||||
max: 1,
|
max: 1,
|
||||||
help: `Break many bricks at once for more coins.`,
|
help: `Break many bricks at once for more coins.`,
|
||||||
|
@ -488,7 +493,7 @@ const upgrades = [{
|
||||||
{
|
{
|
||||||
minimumTotalScore: 10000,
|
minimumTotalScore: 10000,
|
||||||
id: 'hot_start',
|
id: 'hot_start',
|
||||||
giftableAfterTotalScore: 24000,
|
giftable: true,
|
||||||
name: "Hot start",
|
name: "Hot start",
|
||||||
max: 3,
|
max: 3,
|
||||||
help: `Clear the level quickly for more coins.`,
|
help: `Clear the level quickly for more coins.`,
|
||||||
|
@ -497,14 +502,14 @@ const upgrades = [{
|
||||||
{
|
{
|
||||||
minimumTotalScore: 200,
|
minimumTotalScore: 200,
|
||||||
id: 'sides_are_lava',
|
id: 'sides_are_lava',
|
||||||
giftableAfterTotalScore: 500,
|
giftable: true,
|
||||||
name: "Shoot straight",
|
name: "Shoot straight",
|
||||||
max: 1,
|
max: 1,
|
||||||
help: `Avoid the sides for more coins.`,
|
help: `Avoid the sides for more coins.`,
|
||||||
}, {
|
}, {
|
||||||
minimumTotalScore: 600,
|
minimumTotalScore: 600,
|
||||||
id: 'top_is_lava',
|
id: 'top_is_lava',
|
||||||
giftableAfterTotalScore: 1200,
|
giftable: true,
|
||||||
name: "Sky is the limit",
|
name: "Sky is the limit",
|
||||||
max: 1,
|
max: 1,
|
||||||
help: `Avoid the top for more coins.`,
|
help: `Avoid the top for more coins.`,
|
||||||
|
@ -513,13 +518,12 @@ const upgrades = [{
|
||||||
{
|
{
|
||||||
minimumTotalScore: 8000,
|
minimumTotalScore: 8000,
|
||||||
id: 'catch_all_coins',
|
id: 'catch_all_coins',
|
||||||
giftableAfterTotalScore: 16000,
|
giftable: true,
|
||||||
name: "Compound interest",
|
name: "Compound interest",
|
||||||
max: 3,
|
max: 3,
|
||||||
help: `Catch all coins with your puck for even more coins.`,
|
help: `Catch all coins with your puck for even more coins.`,
|
||||||
}, {
|
}, {
|
||||||
minimumTotalScore: 0,
|
minimumTotalScore: 0,
|
||||||
extra_levels_minimum_total_score: 6250,
|
|
||||||
id: 'viscosity',
|
id: 'viscosity',
|
||||||
name: "Slower coins fall",
|
name: "Slower coins fall",
|
||||||
max: 3,
|
max: 3,
|
||||||
|
@ -528,9 +532,8 @@ const upgrades = [{
|
||||||
|
|
||||||
{
|
{
|
||||||
minimumTotalScore: 0,
|
minimumTotalScore: 0,
|
||||||
extra_levels_minimum_total_score: 750,
|
|
||||||
id: 'base_combo',
|
id: 'base_combo',
|
||||||
giftableAfterTotalScore: 0,
|
giftable: true,
|
||||||
name: "+3 base combo",
|
name: "+3 base combo",
|
||||||
max: 3,
|
max: 3,
|
||||||
help: `Your combo starts 3 points higher.`,
|
help: `Your combo starts 3 points higher.`,
|
||||||
|
@ -538,7 +541,6 @@ const upgrades = [{
|
||||||
|
|
||||||
{
|
{
|
||||||
minimumTotalScore: 0,
|
minimumTotalScore: 0,
|
||||||
extra_levels_minimum_total_score: 25,
|
|
||||||
id: 'slow_down',
|
id: 'slow_down',
|
||||||
name: "Slower ball",
|
name: "Slower ball",
|
||||||
max: 2,
|
max: 2,
|
||||||
|
@ -559,7 +561,6 @@ const upgrades = [{
|
||||||
minimumTotalScore: 3600, id: 'smaller_puck', name: "Smaller puck", max: 2, help: `Gives you more control.`,
|
minimumTotalScore: 3600, id: 'smaller_puck', name: "Smaller puck", max: 2, help: `Gives you more control.`,
|
||||||
}, {
|
}, {
|
||||||
minimumTotalScore: 0,
|
minimumTotalScore: 0,
|
||||||
extra_levels_minimum_total_score: 0,
|
|
||||||
id: 'bigger_puck',
|
id: 'bigger_puck',
|
||||||
name: "Bigger puck",
|
name: "Bigger puck",
|
||||||
max: 2,
|
max: 2,
|
||||||
|
@ -570,6 +571,12 @@ const upgrades = [{
|
||||||
name: "Balls repulse balls",
|
name: "Balls repulse balls",
|
||||||
max: 3,
|
max: 3,
|
||||||
help: `Only has an effect when 2+ balls.`,
|
help: `Only has an effect when 2+ balls.`,
|
||||||
|
}, {
|
||||||
|
minimumTotalScore: 2000,
|
||||||
|
id: 'ball_attract_ball',
|
||||||
|
name: "Balls attract balls",
|
||||||
|
max: 3,
|
||||||
|
help: `Only has an effect when 2+ balls.`,
|
||||||
}, {
|
}, {
|
||||||
minimumTotalScore: 4000,
|
minimumTotalScore: 4000,
|
||||||
id: 'puck_repulse_ball',
|
id: 'puck_repulse_ball',
|
||||||
|
@ -579,30 +586,15 @@ const upgrades = [{
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
function computeUpgradeCurrentMaxLevel(u, ts) {
|
|
||||||
let max = 0
|
|
||||||
const setMax = (v) => max = Math.max(max, v)
|
|
||||||
if (u.max && ts >= u.minimumTotalScore) {
|
|
||||||
setMax(1)
|
|
||||||
}
|
|
||||||
if (u.max > 1) {
|
|
||||||
if (u.minimumTotalScore) {
|
|
||||||
setMax(Math.min(u.max, Math.floor(ts / u.minimumTotalScore)))
|
|
||||||
} else if (u.extra_levels_minimum_total_score) {
|
|
||||||
setMax(Math.min(u.max, Math.floor(ts / u.extra_levels_minimum_total_score) + 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPossibleUpgrades() {
|
function getPossibleUpgrades() {
|
||||||
const ts = getTotalScore()
|
const ts = getTotalScore()
|
||||||
return upgrades
|
return upgrades
|
||||||
.filter(u => !(isSettingOn('color_blind') && u.color_blind_exclude))
|
.filter(u => !(isSettingOn('color_blind') && u.color_blind_exclude))
|
||||||
.map(u => ({
|
.map(u => ({
|
||||||
...u, max: computeUpgradeCurrentMaxLevel(u, ts), giftable: ts >= (u.giftableAfterTotalScore ?? Infinity)
|
...u, max: ts > u.minimumTotalScore ? u.max:0, originalMax: u.max
|
||||||
})).filter(u => u.max > 0)
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
function levelTotalScoreCondition(l, li) {
|
function levelTotalScoreCondition(l, li) {
|
||||||
|
@ -610,13 +602,16 @@ function levelTotalScoreCondition(l, li) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffleLevels(nameToAvoid = null) {
|
function shuffleLevels(nameToAvoid = null) {
|
||||||
const ts = getTotalScore()
|
const ts = getTotalScore();
|
||||||
runLevels = allLevels
|
runLevels = allLevels
|
||||||
|
.filter(l => nextRunOverrides.level ? l.name === nextRunOverrides.level : true)
|
||||||
.filter((l, li) => ts >= levelTotalScoreCondition(l, li))
|
.filter((l, li) => ts >= levelTotalScoreCondition(l, li))
|
||||||
.filter(l => l.name !== nameToAvoid || allLevels.length === 1)
|
.filter(l => l.name !== nameToAvoid || allLevels.length === 1)
|
||||||
.sort(() => Math.random() - 0.5)
|
.sort(() => Math.random() - 0.5)
|
||||||
.slice(0, 7 + 3)
|
.slice(0, 7 + 3)
|
||||||
.sort((a, b) => a.bricks.filter(i => i).length - b.bricks.filter(i => i).length)
|
.sort((a, b) => a.bricks.filter(i => i).length - b.bricks.filter(i => i).length);
|
||||||
|
|
||||||
|
nextRunOverrides.level = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUpgraderUnlockPoints() {
|
function getUpgraderUnlockPoints() {
|
||||||
|
@ -628,32 +623,18 @@ function getUpgraderUnlockPoints() {
|
||||||
if (u.minimumTotalScore) {
|
if (u.minimumTotalScore) {
|
||||||
list.push({
|
list.push({
|
||||||
threshold: u.minimumTotalScore,
|
threshold: u.minimumTotalScore,
|
||||||
title: 'Unlock: ' + u.name,
|
title: u.name + ' (Perk)',
|
||||||
help: 'This new perks will be added to the choices offered to you.'
|
help: u.help,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (u.max > 1) {
|
|
||||||
for (var l = 1; l < u.max; l++) list.push({
|
|
||||||
threshold: l * (u.minimumTotalScore || u.extra_levels_minimum_total_score || 0),
|
|
||||||
title: 'Upgrade: ' + u.name,
|
|
||||||
help: 'You will be able to take this perk ' + (l + 1) + ' times for greater effect.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (u.giftableAfterTotalScore) {
|
|
||||||
list.push({
|
|
||||||
threshold: u.giftableAfterTotalScore,
|
|
||||||
title: 'Start: ' + u.name,
|
|
||||||
help: u.name + ' will be added to the list of possible starting perks.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
allLevels.forEach((l, li) => {
|
allLevels.forEach((l, li) => {
|
||||||
list.push({
|
list.push({
|
||||||
threshold: levelTotalScoreCondition(l, li),
|
threshold: levelTotalScoreCondition(l, li),
|
||||||
title: 'Level: ' + l.name,
|
title: l.name + ' (Level)',
|
||||||
help: l.name + ' will be added to the list of possible levels.'
|
// help: 'Adds level "'+l.name + '" to the list of possible levels.',
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -663,7 +644,6 @@ function getUpgraderUnlockPoints() {
|
||||||
|
|
||||||
function pickRandomUpgrades(count) {
|
function pickRandomUpgrades(count) {
|
||||||
|
|
||||||
|
|
||||||
let list = getPossibleUpgrades()
|
let list = getPossibleUpgrades()
|
||||||
.sort(() => Math.random() - 0.5)
|
.sort(() => Math.random() - 0.5)
|
||||||
.filter(u => perks[u.id] < u.max)
|
.filter(u => perks[u.id] < u.max)
|
||||||
|
@ -688,15 +668,21 @@ function pickRandomUpgrades(count) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nextRunOverrides = {level: null, perks: null}
|
||||||
|
let hadOverrides = false
|
||||||
|
|
||||||
function restart() {
|
function restart() {
|
||||||
console.log("restart")
|
console.log("restart")
|
||||||
|
hadOverrides = !!(nextRunOverrides.level || nextRunOverrides.perks)
|
||||||
// When restarting, we want to avoid restarting with the same level we're on, so we exclude from the next
|
// When restarting, we want to avoid restarting with the same level we're on, so we exclude from the next
|
||||||
// run's level list
|
// run's level list
|
||||||
shuffleLevels(levelTime || score ? currentLevelInfo().name : null);
|
shuffleLevels(levelTime || score ? currentLevelInfo().name : null);
|
||||||
resetRunStatistics()
|
resetRunStatistics()
|
||||||
score = 0;
|
score = 0;
|
||||||
scoreStory = [];
|
scoreStory = [];
|
||||||
|
if (hadOverrides) {
|
||||||
|
scoreStory.push(`This is a test run, started from the unlocks menu. It stops after one level and is not recorded in the stats. `)
|
||||||
|
}
|
||||||
const randomGift = reset_perks();
|
const randomGift = reset_perks();
|
||||||
|
|
||||||
incrementRunStatistics('starting_upgrade.' + randomGift, 1)
|
incrementRunStatistics('starting_upgrade.' + randomGift, 1)
|
||||||
|
@ -922,6 +908,7 @@ function tick() {
|
||||||
let playedCoinBounce = false;
|
let playedCoinBounce = false;
|
||||||
const coinRadius = Math.round(coinSize / 2);
|
const coinRadius = Math.round(coinSize / 2);
|
||||||
|
|
||||||
|
|
||||||
coins.forEach((coin) => {
|
coins.forEach((coin) => {
|
||||||
if (coin.destroyed) return;
|
if (coin.destroyed) return;
|
||||||
if (perks.coin_magnet) {
|
if (perks.coin_magnet) {
|
||||||
|
@ -1008,14 +995,16 @@ function ballTick(ball, delta) {
|
||||||
ball.vx += ((puck - ball.x) / 1000) * delta * perks.telekinesis;
|
ball.vx += ((puck - ball.x) / 1000) * delta * perks.telekinesis;
|
||||||
}
|
}
|
||||||
|
|
||||||
const speedLimitDampener =1+ perks.telekinesis+perks.ball_repulse_ball +perks.puck_repulse_ball
|
const speedLimitDampener = 1 + perks.telekinesis + perks.ball_repulse_ball + perks.puck_repulse_ball + perks.ball_attract_ball
|
||||||
if (ball.vx * ball.vx + ball.vy * ball.vy < baseSpeed * baseSpeed * 2) {
|
if (ball.vx * ball.vx + ball.vy * ball.vy < baseSpeed * baseSpeed * 2) {
|
||||||
ball.vx *= (1 + .02 / speedLimitDampener);
|
ball.vx *= (1 + .02 / speedLimitDampener);
|
||||||
ball.vy *= (1 + .02 / speedLimitDampener);
|
ball.vy *= (1 + .02 / speedLimitDampener);
|
||||||
} else {
|
} else {
|
||||||
ball.vx *= (1 - .02/speedLimitDampener);;
|
ball.vx *= (1 - .02 / speedLimitDampener);
|
||||||
|
;
|
||||||
if (Math.abs(ball.vy) > 0.5 * baseSpeed) {
|
if (Math.abs(ball.vy) > 0.5 * baseSpeed) {
|
||||||
ball.vy *= (1 - .02/speedLimitDampener);;
|
ball.vy *= (1 - .02 / speedLimitDampener);
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1023,18 +1012,22 @@ function ballTick(ball, delta) {
|
||||||
for (b2 of balls) {
|
for (b2 of balls) {
|
||||||
// avoid computing this twice, and repulsing itself
|
// avoid computing this twice, and repulsing itself
|
||||||
if (b2.x >= ball.x) continue
|
if (b2.x >= ball.x) continue
|
||||||
repulse(ball,b2,15* perks.ball_repulse_ball )
|
repulse(ball, b2, perks.ball_repulse_ball, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (perks.ball_attract_ball) {
|
||||||
|
for (b2 of balls) {
|
||||||
|
// avoid computing this twice, and repulsing itself
|
||||||
|
if (b2.x >= ball.x) continue
|
||||||
|
attract(ball, b2, 2 * perks.ball_attract_ball)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (perks.puck_repulse_ball) {
|
if (perks.puck_repulse_ball) {
|
||||||
repulse(ball, {
|
repulse(ball, {
|
||||||
x: puck,
|
x: puck,
|
||||||
y: gameZoneHeight,
|
y: gameZoneHeight,
|
||||||
vx:0,
|
|
||||||
vy:0,
|
|
||||||
color: currentLevelInfo().black_puck ? '#000' : '#FFF',
|
color: currentLevelInfo().black_puck ? '#000' : '#FFF',
|
||||||
},15* perks.puck_repulse_ball )
|
}, perks.puck_repulse_ball, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1173,6 +1166,7 @@ function resetRunStatistics() {
|
||||||
runStatistics = {
|
runStatistics = {
|
||||||
started: Date.now(),
|
started: Date.now(),
|
||||||
ended: null,
|
ended: null,
|
||||||
|
hadOverrides,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
easy: isSettingOn('easy'),
|
easy: isSettingOn('easy'),
|
||||||
|
@ -1195,6 +1189,7 @@ function getTotalScore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToTotalScore(points) {
|
function addToTotalScore(points) {
|
||||||
|
if (hadOverrides) return
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('breakout_71_total_score', JSON.stringify(getTotalScore() + points))
|
localStorage.setItem('breakout_71_total_score', JSON.stringify(getTotalScore() + points))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1238,7 +1233,7 @@ function gameOver(title, intro) {
|
||||||
const list = getUpgraderUnlockPoints()
|
const list = getUpgraderUnlockPoints()
|
||||||
list.filter(u => u.threshold > startTs && u.threshold < endTs).forEach(u => {
|
list.filter(u => u.threshold > startTs && u.threshold < endTs).forEach(u => {
|
||||||
unlocksInfo += `
|
unlocksInfo += `
|
||||||
<p class="progress" title=${JSON.stringify(u.help)}>
|
<p class="progress" title=${JSON.stringify(u.help || '')}>
|
||||||
<span>${u.title}</span>
|
<span>${u.title}</span>
|
||||||
<span class="progress_bar_part" style="${getDelay()}"></span>
|
<span class="progress_bar_part" style="${getDelay()}"></span>
|
||||||
</p>
|
</p>
|
||||||
|
@ -1253,10 +1248,11 @@ function gameOver(title, intro) {
|
||||||
const done = endTs - previousUnlockAt
|
const done = endTs - previousUnlockAt
|
||||||
intro += `Score ${nextUnlock.threshold - endTs} more points to reach the next unlock.`
|
intro += `Score ${nextUnlock.threshold - endTs} more points to reach the next unlock.`
|
||||||
|
|
||||||
|
const scaleX=(done / total).toFixed(2)
|
||||||
unlocksInfo += `
|
unlocksInfo += `
|
||||||
<p class="progress" title=${JSON.stringify(unlocksInfo.help)}>
|
<p class="progress" title=${JSON.stringify(unlocksInfo.help)}>
|
||||||
<span>${nextUnlock.title}</span>
|
<span>${nextUnlock.title}</span>
|
||||||
<span style="transform: scale(${(done / total).toFixed(2)},1);${getDelay()}" class="progress_bar_part"></span>
|
<span style="transform: scale(${scaleX},1);${getDelay()}" class="progress_bar_part"></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
`
|
`
|
||||||
|
@ -1375,6 +1371,7 @@ function explodeBrick(index, ball, isExplosion) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function max_levels() {
|
function max_levels() {
|
||||||
|
if (hadOverrides) return 1
|
||||||
return 7 + perks.extra_levels;
|
return 7 + perks.extra_levels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1432,9 +1429,13 @@ function render() {
|
||||||
const {x, y, time, color, size, type, duration} = flash;
|
const {x, y, time, color, size, type, duration} = flash;
|
||||||
const elapsed = levelTime - time;
|
const elapsed = levelTime - time;
|
||||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||||
if (type === "ball" || type === "particle") {
|
if (type === "ball") {
|
||||||
drawFuzzyBall(ctx, color, size, x, y);
|
drawFuzzyBall(ctx, color, size, x, y);
|
||||||
}
|
}
|
||||||
|
if (type === "particle") {
|
||||||
|
drawFuzzyBall(ctx, color, size * 3, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.globalAlpha = 0.9;
|
ctx.globalAlpha = 0.9;
|
||||||
|
@ -1565,10 +1566,10 @@ function render() {
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
drawPuck(ctx, puckColor, puckWidth, puckHeight)
|
drawPuck(ctx, puckColor, puckWidth, puckHeight)
|
||||||
|
|
||||||
|
|
||||||
if (combo > 1) {
|
if (combo > 1) {
|
||||||
ctx.globalCompositeOperation = "destination-out";
|
|
||||||
drawText(ctx, "x " + combo, "white", puckHeight, {
|
ctx.globalCompositeOperation = "source-over";
|
||||||
|
drawText(ctx, "x " + combo, !level.black_puck ? '#000' : '#FFF', puckHeight, {
|
||||||
x: puck, y: gameZoneHeight - puckHeight / 2,
|
x: puck, y: gameZoneHeight - puckHeight / 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2141,7 +2142,6 @@ scoreDisplay.addEventListener("click", async (e) => {
|
||||||
title: `You scored ${score} points so far`, text: `
|
title: `You scored ${score} points so far`, text: `
|
||||||
<p>You are playing level ${currentLevel + 1} out of ${max_levels()}. </p>
|
<p>You are playing level ${currentLevel + 1} out of ${max_levels()}. </p>
|
||||||
${scoreStory.map((t) => "<p>" + t + "</p>").join("")}
|
${scoreStory.map((t) => "<p>" + t + "</p>").join("")}
|
||||||
<p>You high score is ${highScore}.</p>
|
|
||||||
`, allowClose: true, actions: [{
|
`, allowClose: true, actions: [{
|
||||||
text: "New run", help: "Start a brand new run.", value: () => {
|
text: "New run", help: "Start a brand new run.", value: () => {
|
||||||
restart();
|
restart();
|
||||||
|
@ -2200,6 +2200,57 @@ async function openSettingsPanel() {
|
||||||
const cb = await asyncAlert({
|
const cb = await asyncAlert({
|
||||||
title: "Breakout 71", text: `
|
title: "Breakout 71", text: `
|
||||||
`, allowClose: true, actions: [
|
`, allowClose: true, actions: [
|
||||||
|
{
|
||||||
|
text: 'Unlocks',
|
||||||
|
help: "See and try what you've unlocked",
|
||||||
|
async value() {
|
||||||
|
const ts = getTotalScore()
|
||||||
|
const tryOn = await asyncAlert({
|
||||||
|
title: 'Your unlocks',
|
||||||
|
text: `
|
||||||
|
<p>Your high score is ${highScore}. In total, you've cought ${ts} coins. Click an upgrade below to start a test run with it (stops after 1 level).</p>
|
||||||
|
`,
|
||||||
|
actions: [...getPossibleUpgrades()
|
||||||
|
.sort((a,b)=>a.minimumTotalScore-b.minimumTotalScore)
|
||||||
|
.map(({
|
||||||
|
originalMax,
|
||||||
|
name,
|
||||||
|
max,
|
||||||
|
help,id,
|
||||||
|
minimumTotalScore
|
||||||
|
}) =>
|
||||||
|
({
|
||||||
|
text: name,
|
||||||
|
help:`${help} (${minimumTotalScore} coins)`,
|
||||||
|
disabled: !max,
|
||||||
|
value: {perks: {[id]: 1}}
|
||||||
|
|
||||||
|
}))
|
||||||
|
,
|
||||||
|
...allLevels.map((l, li) => {
|
||||||
|
const threshold=levelTotalScoreCondition(l, li)
|
||||||
|
const avaliable= ts >= threshold
|
||||||
|
return ({
|
||||||
|
text: l.name,
|
||||||
|
help:`A ${l.size}x${l.size} level (${threshold} coins)`,
|
||||||
|
disabled: !avaliable,
|
||||||
|
value: {level: l.name}
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
,
|
||||||
|
allowClose: true,
|
||||||
|
})
|
||||||
|
if (tryOn) {
|
||||||
|
nextRunOverrides = tryOn
|
||||||
|
restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
...optionsList,
|
...optionsList,
|
||||||
|
|
||||||
(window.screenTop || window.screenY) && {
|
(window.screenTop || window.screenY) && {
|
||||||
|
@ -2237,7 +2288,6 @@ async function openSettingsPanel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
textAfterButtons: `
|
textAfterButtons: `
|
||||||
|
@ -2257,20 +2307,79 @@ async function openSettingsPanel() {
|
||||||
function distance2(a, b) {
|
function distance2(a, b) {
|
||||||
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)
|
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
function distanceBetween(a, b) {
|
function distanceBetween(a, b) {
|
||||||
return Math.sqrt(distance2(a, b))
|
return Math.sqrt(distance2(a, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
function repulse(a,b,power){
|
function rainbowColor() {
|
||||||
|
return `hsl(${(levelTime / 2) % 360},100%,70%)`
|
||||||
|
}
|
||||||
|
|
||||||
|
function repulse(a, b, power, impactsBToo) {
|
||||||
|
|
||||||
const distance = distanceBetween(a, b)
|
const distance = distanceBetween(a, b)
|
||||||
// Ensure we don't get soft locked
|
// Ensure we don't get soft locked
|
||||||
if(distance>gameZoneWidth/2) return
|
const max = gameZoneWidth / 2
|
||||||
|
if (distance > max) return
|
||||||
|
// Unit vector
|
||||||
|
const dx = (a.x - b.x) / distance
|
||||||
|
const dy = (a.y - b.y) / distance
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
const fact = -power * (max - distance) / (max * 1.2) / 3 * Math.min(500, levelTime) / 500
|
||||||
|
if (impactsBToo) {
|
||||||
|
b.vx += dx * fact
|
||||||
|
b.vy += dy * fact
|
||||||
|
}
|
||||||
|
a.vx -= dx * fact
|
||||||
|
a.vy -= dy * fact
|
||||||
|
|
||||||
|
if (!isSettingOn('basic')) {
|
||||||
|
const speed = 10
|
||||||
|
const rand = 2
|
||||||
|
flashes.push({
|
||||||
|
type: "particle",
|
||||||
|
duration: 100,
|
||||||
|
time: levelTime,
|
||||||
|
size: coinSize / 2,
|
||||||
|
color: rainbowColor(),
|
||||||
|
ethereal: true,
|
||||||
|
x: a.x,
|
||||||
|
y: a.y,
|
||||||
|
vx: -dx * speed + a.vx + (Math.random() - 0.5) * rand,
|
||||||
|
vy: -dy * speed + a.vy + (Math.random() - 0.5) * rand,
|
||||||
|
})
|
||||||
|
if (impactsBToo) {
|
||||||
|
|
||||||
|
flashes.push({
|
||||||
|
type: "particle",
|
||||||
|
duration: 100,
|
||||||
|
time: levelTime,
|
||||||
|
size: coinSize / 2,
|
||||||
|
color: rainbowColor(),
|
||||||
|
ethereal: true,
|
||||||
|
x: b.x,
|
||||||
|
y: b.y,
|
||||||
|
vx: dx * speed + b.vx + (Math.random() - 0.5) * rand,
|
||||||
|
vy: dy * speed + b.vy + (Math.random() - 0.5) * rand,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function attract(a, b, power) {
|
||||||
|
|
||||||
|
const distance = distanceBetween(a, b)
|
||||||
|
// Ensure we don't get soft locked
|
||||||
|
const min = gameZoneWidth * .5
|
||||||
|
if (distance < min) return
|
||||||
// Unit vector
|
// Unit vector
|
||||||
const dx = (a.x - b.x) / distance
|
const dx = (a.x - b.x) / distance
|
||||||
const dy = (a.y - b.y) / distance
|
const dy = (a.y - b.y) / distance
|
||||||
|
|
||||||
const fact= - power / (1+Math.max(1, distance))
|
const fact = power * (distance - min) / min * Math.min(500, levelTime) / 500
|
||||||
b.vx += dx * fact
|
b.vx += dx * fact
|
||||||
b.vy += dy * fact
|
b.vy += dy * fact
|
||||||
a.vx -= dx * fact
|
a.vx -= dx * fact
|
||||||
|
@ -2281,31 +2390,32 @@ function repulse(a,b,power){
|
||||||
const rand = 2
|
const rand = 2
|
||||||
flashes.push({
|
flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration:150,
|
duration: 100,
|
||||||
time: levelTime,
|
time: levelTime,
|
||||||
size: coinSize / 2,
|
size: coinSize / 2,
|
||||||
color:b.color,
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
x: a.x,
|
x: a.x,
|
||||||
y: a.y,
|
y: a.y,
|
||||||
vx:-dx*speed+a.vx+(Math.random()-0.5)*rand,
|
vx: dx * speed + a.vx + (Math.random() - 0.5) * rand,
|
||||||
vy:-dy*speed+a.vy+(Math.random()-0.5)*rand,
|
vy: dy * speed + a.vy + (Math.random() - 0.5) * rand,
|
||||||
})
|
})
|
||||||
flashes.push({
|
flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration:150,
|
duration: 100,
|
||||||
time: levelTime,
|
time: levelTime,
|
||||||
size: coinSize / 2,
|
size: coinSize / 2,
|
||||||
color:a.color,
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
x: b.x,
|
x: b.x,
|
||||||
y: b.y,
|
y: b.y,
|
||||||
vx:dx*speed+b.vx+(Math.random()-0.5)*rand,
|
vx: -dx * speed + b.vx + (Math.random() - 0.5) * rand,
|
||||||
vy:dy*speed+b.vy+(Math.random()-0.5)*rand,
|
vy: -dy * speed + b.vy + (Math.random() - 0.5) * rand,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fitSize()
|
fitSize()
|
||||||
restart()
|
restart()
|
||||||
tick();
|
tick();
|
Loading…
Add table
Add a link
Reference in a new issue