Automatic deploy 29007124

This commit is contained in:
Renan LE CARO 2025-02-24 21:04:31 +01:00
parent c5351bfd35
commit 1cec9a7a0c
4 changed files with 132 additions and 144 deletions

View file

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

View file

@ -209,7 +209,6 @@ function spawnExplosion(count, x, y, color, duration = 150, size = coinSize) {
let score = 0;
let scoreStory = [];
let lastexplosion = 0;
let highScore = parseFloat(localStorage.getItem("breakout-3-hs") || "0");
@ -307,8 +306,20 @@ let levelStartScore = 0;
let levelMisses = 0;
let levelSpawnedCoins = 0;
function getLevelStats() {
const catchRate = (score - levelStartScore) / (levelSpawnedCoins || 1);
function pickedUpgradesHTMl() {
let list = ''
for (let u of upgrades) {
for (let i = 0; i < perks[u.id]; i++)
list += u.icon + ' '
}
return list
}
async function openUpgradesPicker() {
const catchRate = (score - levelStartScore) / (levelSpawnedCoins || 1);
let repeats = 1;
let choices = 3;
@ -339,42 +350,23 @@ function getLevelStats() {
missesGain = " (+1 choice)"
}
let stats = `
You caught ${score - levelStartScore} coins ${catchGain} out of ${levelSpawnedCoins} in ${Math.round(levelTime / 1000)} seconds${timeGain}.
You missed ${levelMisses} times ${missesGain}.
`;
let text = [stats];
return {
stats, text: text.map(t => '<p>' + t + '</p>').join('\n'), repeats, choices,
};
}
function pickedUpgradesHTMl() {
let list = ''
for (let u of upgrades) {
for (let i = 0; i < perks[u.id]; i++)
list += u.icon + ' '
}
return list
}
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
let textAfterButtons = `<p>Upgrades picked so far : </p><p>${pickedUpgradesHTMl()}</p>
<div id="level-recording-container"></div>`;
let textAfterButtons = `
<p>Upgrades picked so far : </p><p>${pickedUpgradesHTMl()}</p>
<div id="level-recording-container"></div>
`;
const cb = await asyncAlert({
title: "Pick an upgrade " + (repeats ? "(" + (repeats + 1) + ")" : ""), actions, text, allowClose: false,
title: "Pick an upgrade " + (repeats ? "(" + (repeats + 1) + ")" : ""), actions,
text: `<p>
You caught ${score - levelStartScore} coins ${catchGain} out of ${levelSpawnedCoins} in ${Math.round(levelTime / 1000)} seconds${timeGain}.
You missed ${levelMisses} times ${missesGain}.</p>`,
allowClose: false,
textAfterButtons
});
cb();
@ -785,7 +777,6 @@ function pickRandomUpgrades(count) {
icon: u.icon,
value: () => {
perks[u.id]++;
scoreStory.push("Picked upgrade : " + u.name);
},
help: (perks[u.id] && u.extraLevelsHelp) || u.help,
// max: u.max,
@ -807,17 +798,12 @@ function restart() {
shuffleLevels(levelTime || score ? currentLevelInfo().name : null);
resetRunStatistics()
score = 0;
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();
dontOfferTooSoon(randomGift)
setLevel(0);
scoreStory.push(`You started playing with the upgrade "${upgrades.find(u => u.id === randomGift)?.name}" on the level "${runLevels[0].name}". `,);
pauseRecording()
}
@ -1336,14 +1322,6 @@ function gameOver(title, intro) {
runStatistics.max_level = currentLevel+1
const {stats} = getLevelStats();
scoreStory.push(`During level ${currentLevel + 1} ${stats}`);
if (balls.find((b) => !b.destroyed)) {
scoreStory.push(`You cleared the last level and won. `);
} else {
scoreStory.push(`You dropped the ball and finished your run early. `);
}
let animationDelay = -300
const getDelay = () => {
animationDelay += 800
@ -1388,78 +1366,6 @@ function gameOver(title, intro) {
})
}
let runStats = ''
if (!hadOverrides) {
try {
// Stores only top 100 runs
let runsHistory = JSON.parse(localStorage.getItem('breakout_71_runs_history') || '[]');
runsHistory.sort((a,b)=>a.score-b.score).reverse()
runsHistory=runsHistory.slice(0, 100)
console.log(runsHistory.map(r=>r.score))
runsHistory.push(runStatistics)
// Generate some histogram
localStorage.setItem('breakout_71_runs_history', JSON.stringify(runsHistory, null, 2))
const makeHistogram = (title, getter, unit) => {
let values = runsHistory.map(h => getter(h) || 0)
const min = Math.min(...values)
const max = Math.max(...values)
// No point
if(min===max) return ''
// One bin per unique value, max 10
const binsCount = Math.min(values.length,10)
if(binsCount<3) return ''
const bins = []
const binsTotal = []
for(let i=0;i<binsCount;i++){
bins.push(0)
binsTotal.push(0)
}
const binSize = (max - min) / bins.length
const binIndexOf = v => Math.min(bins.length - 1, Math.floor((v - min) / binSize))
values.forEach(v => {
if(isNaN(v)) return
const index=binIndexOf(v)
bins[index]++
binsTotal[index]+=v
})
if(bins.filter(b=>b).length<3) return ''
const maxBin = Math.max(...bins)
const lastValue = values[values.length - 1]
const activeBin = binIndexOf(lastValue)
return `<h2 class="histogram-title">${title} : <strong>${lastValue}${unit}</strong></h2><div class="histogram">
${bins.map((v, vi) => `<span class="${vi === activeBin ? 'active' : ''}"><span style="height:${v / maxBin * 80}px" title="${v} run${v>1 ? 's':''} between ${
Math.floor(min + vi * binSize)} and ${Math.floor(min + (vi + 1) * binSize)}${unit}"
><span>${
(!v && ' ') || (vi==activeBin && lastValue+unit) || (Math.round(binsTotal[vi]/v)+unit)
}</span></span></span>`).join('')}
</div>
`
}
runStats += makeHistogram('Total score', r => r.score, '')
runStats += makeHistogram('Catch rate', r => Math.round(r.score / r.coins_spawned * 100), '%')
runStats += makeHistogram('Bricks broken', r => r.bricks_broken, '')
runStats += makeHistogram('Bricks broken per minute', r =>Math.round(r.bricks_broken/r.runTime*1000*60), ' bpm')
runStats += makeHistogram('Hit rate', r => Math.round((1-r.misses / r.puck_bounces) * 100), '%')
runStats += makeHistogram('Duration per level', r => Math.round(r.runTime/1000/r.levelsPlayed), 's')
runStats += makeHistogram('Level reached', r => r.levelsPlayed, '')
runStats += makeHistogram('Upgrades applied', r => r.upgrades_picked, '')
runStats += makeHistogram('Balls lost', r => r.balls_lost, '')
runStats += makeHistogram('Average combo', r => Math.round(r.coins_spawned /r.bricks_broken) , '')
runStats += makeHistogram('Max combo', r => r.max_combo , '')
if(runStats){
runStats= `<p>Find below your run statistics compared to past runs.</p>`+ runStats
}
} catch (e) {
console.warn(e)
}
}
// Avoid the sad sound right as we restart a new games
@ -1469,13 +1375,88 @@ function gameOver(title, intro) {
<p>${intro}</p>
${unlocksInfo}
`, textAfterButtons: `
${runStats}
<div id="level-recording-container"></div>
${scoreStory.map((t) => "<p>" + t + "</p>").join("")}
${getHistograms(true)}
`
}).then(() => restart());
}
function getHistograms(saveStats){
if (hadOverrides) {return''}
let runStats=''
try {
// Stores only top 100 runs
let runsHistory = JSON.parse(localStorage.getItem('breakout_71_runs_history') || '[]');
runsHistory.sort((a,b)=>a.score-b.score).reverse()
runsHistory=runsHistory.slice(0, 100)
runsHistory.push(runStatistics)
// Generate some histogram
if(saveStats) {
localStorage.setItem('breakout_71_runs_history', JSON.stringify(runsHistory, null, 2))
}
const makeHistogram = (title, getter, unit) => {
let values = runsHistory.map(h => getter(h) || 0)
const min = Math.min(...values)
const max = Math.max(...values)
// No point
if(min===max) return ''
// One bin per unique value, max 10
const binsCount = Math.min(values.length,10)
if(binsCount<3) return ''
const bins = []
const binsTotal = []
for(let i=0;i<binsCount;i++){
bins.push(0)
binsTotal.push(0)
}
const binSize = (max - min) / bins.length
const binIndexOf = v => Math.min(bins.length - 1, Math.floor((v - min) / binSize))
values.forEach(v => {
if(isNaN(v)) return
const index=binIndexOf(v)
bins[index]++
binsTotal[index]+=v
})
if(bins.filter(b=>b).length<3) return ''
const maxBin = Math.max(...bins)
const lastValue = values[values.length - 1]
const activeBin = binIndexOf(lastValue)
return `<h2 class="histogram-title">${title} : <strong>${lastValue}${unit}</strong></h2><div class="histogram">
${bins.map((v, vi) => `<span class="${vi === activeBin ? 'active' : ''}"><span style="height:${v / maxBin * 80}px" title="${v} run${v>1 ? 's':''} between ${
Math.floor(min + vi * binSize)} and ${Math.floor(min + (vi + 1) * binSize)}${unit}"
><span>${
(!v && ' ') || (vi==activeBin && lastValue+unit) || (Math.round(binsTotal[vi]/v)+unit)
}</span></span></span>`).join('')}
</div>
`
}
runStats += makeHistogram('Total score', r => r.score, '')
runStats += makeHistogram('Catch rate', r => Math.round(r.score / r.coins_spawned * 100), '%')
runStats += makeHistogram('Bricks broken', r => r.bricks_broken, '')
runStats += makeHistogram('Bricks broken per minute', r =>Math.round(r.bricks_broken/r.runTime*1000*60), ' bpm')
runStats += makeHistogram('Hit rate', r => Math.round((1-r.misses / r.puck_bounces) * 100), '%')
runStats += makeHistogram('Duration per level', r => Math.round(r.runTime/1000/r.levelsPlayed), 's')
runStats += makeHistogram('Level reached', r => r.levelsPlayed, '')
runStats += makeHistogram('Upgrades applied', r => r.upgrades_picked, '')
runStats += makeHistogram('Balls lost', r => r.balls_lost, '')
runStats += makeHistogram('Average combo', r => Math.round(r.coins_spawned /r.bricks_broken) , '')
runStats += makeHistogram('Max combo', r => r.max_combo , '')
if(runStats){
runStats= `<p>Find below your run statistics compared to past runs.</p>`+ runStats
}
} catch (e) {
console.warn(e)
}
return runStats
}
function resetRunStatistics() {
runStatistics = {
started: Date.now(),
@ -2435,9 +2416,10 @@ scoreDisplay.addEventListener("click", async (e) => {
const cb = await asyncAlert({
title: ` ${score} points at level ${currentLevel + 1} / ${max_levels()}`,
text: `
<p>${pickedUpgradesHTMl()}</p>
${scoreStory.map((t) => "<p>" + t + "</p>").join("")}
<p>Upgrades picked so far : </p>
<p>${pickedUpgradesHTMl()}</p>
`, allowClose: true, actions: [{
text: "New run", help: "Start a brand new run.", value: () => {
restart();
@ -2470,11 +2452,11 @@ const options = {
disabled: () => false
},
basic: {
default: false, name: `Fast mode`, help: `Simpler graphics for older devices.`,
default: false, name: `Basic graphics`, help: `Better performance on older devices.`,
disabled: () => false
},
"easy": {
default: false, name: `Easy mode`, help: `Slower ball as starting perk.`, restart: true,
default: false, name: `Kids mode`, help: `Starting perk always "slower ball".`, restart: true,
disabled: () => false
}, "color_blind": {
default: false, name: `Color blind mode`, help: `Removes mechanics about colors.`, restart: true,
@ -2634,6 +2616,7 @@ Click an item above to start a test run with it.
<a href="./privacy.html" target="_blank">Privacy Policy</a>
<a href="https://play.google.com/store/apps/details?id=me.lecaro.breakout" target="_blank">Google Play</a>
<a href="https://renanlecaro.itch.io/breakout71" target="_blank">itch.io</a>
<a href="https://www.reddit.com/r/Breakout71/" target="_blank">Subreddit</a>
<a href="https://gitlab.com/lecarore/breakout71" target="_blank">Gitlab</a>
<a href="https://breakout.lecaro.me/" target="_blank">Web version</a>
<span>v.${window.appVersion}</span>
@ -2803,7 +2786,8 @@ function recordOneFrame() {
function drawMainCanvasOnSmallCanvas() {
recordCanvasCtx?.drawImage(canvas, offsetXRoundedDown, 0, gameZoneWidthRoundedUp, gameZoneHeight, 0, 0, recordCanvas.width, recordCanvas.height)
if(!recordCanvasCtx) return
recordCanvasCtx.drawImage(canvas, offsetXRoundedDown, 0, gameZoneWidthRoundedUp, gameZoneHeight, 0, 0, recordCanvas.width, recordCanvas.height)
recordCanvasCtx.fillStyle = currentLevelInfo()?.black_puck ? '#000' : '#FFF'
recordCanvasCtx.textBaseline = "top";
recordCanvasCtx.font = "12px monospace";
@ -2843,15 +2827,10 @@ function startRecordingGame() {
gifCtx = gifCanvas.getContext("2d", {antialias: false, alpha: false})
}
let scale = 1
while (Math.max(gameZoneWidthRoundedUp, gameZoneHeight) * scale > 400 * 2) {
scale = scale / 2
}
console.log('Recording at scale ' + scale)
recordCanvas.width = gameZoneWidthRoundedUp * scale
recordCanvas.height = gameZoneHeight * scale
gifCanvas.width = Math.floor(gameZoneWidthRoundedUp * scale / 2)
gifCanvas.height = Math.floor(gameZoneHeight * scale / 2)
recordCanvas.width = gameZoneWidthRoundedUp
recordCanvas.height = gameZoneHeight
gifCanvas.width = 400
gifCanvas.height = Math.floor( gameZoneHeight *(400/gameZoneWidthRoundedUp) )
// Gif worker won't work there
@ -2882,8 +2861,13 @@ function startRecordingGame() {
}
instance.onstop = async function () {
let targetDiv = document.getElementById("level-recording-container")
if (!targetDiv) return
let targetDiv ;
let blob = new Blob(recordedChunks, {type: "video/webm"});
if(blob.size< 200000) return // under 0.2MB, probably bugged out or pointlessly short
while(!(targetDiv = document.getElementById("level-recording-container"))){
await new Promise(r=>setTimeout(r, 200))
}
const video = document.createElement("video")
video.autoplay = true
video.controls = false
@ -2896,7 +2880,6 @@ function startRecordingGame() {
video.loop = true
video.muted = true
video.playsinline = true
let blob = new Blob(recordedChunks, {type: "video/webm"});
video.src = URL.createObjectURL(blob);
const a = document.createElement("a")

View file

@ -8,16 +8,16 @@
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Breakout 71</title>
<link rel="stylesheet" href="style.css?v=29005750" />
<link rel="stylesheet" href="style.css?v=29007124" />
<link rel="icon" href="./icon.svg" />
</head>
<body>
<button id="menu"><span> menu</span></button>
<button id="score"></button>
<canvas id="game"></canvas>
<script>window.appVersion="?v=29005750".slice(3)</script>
<script>window.appVersion="?v=29007124".slice(3)</script>
<script src="gif.js"></script>
<script src="levels.js?v=29005750"></script>
<script src="game.js?v=29005750"></script>
<script src="levels.js?v=29007124"></script>
<script src="game.js?v=29007124"></script>
</body>
</html>