commit d2cfce2a0e3979d2c20029686f7ad342df4bd7a8 Author: Renan LE CARO Date: Sat Feb 15 19:21:00 2025 +0100 Initial commit I cleared the project history to start fresh on the public version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96519fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +node_modules \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..1805ac1 --- /dev/null +++ b/Readme.md @@ -0,0 +1,84 @@ +# Breakout 71 + +A simple, single player, challenging arcade breakout game. +The goal is to break all the bricks of 7 levels, which catching as many coins as you can. +You have only one life, if you lose your ball you'll go back to the start +At the end of each level, you get to select an upgrade. + +[Play now](https://breakout.lecaro.me/) - +[Google Play](https://play.google.com/store/apps/details?id=me.lecaro.breakout) - +[itch.io](https://renanlecaro.itch.io/breakout71) - +[GitLab](https://gitlab.com/lecarore/breakout71) + +## TODO + +- show total score on end screen (score added to total) +- show stats on end screen compared to other runs +- handle back bouton in menu +- more levels : famous simple games, letters, fruits, animals +- perk : elastic between balls +- perk : wrap left / right +- 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 : missing makes you loose all score of level, but otherwise multiplier goes up after each breaking +- 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 : wind (puck positions adds force to coins and balls) + +## maybe + +- Make a small mp4 of game which can be shown on gameover and shared. https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder +- perk : soft reset, cut combo in half instead of zero +- perk : missile goes when you catch coin +- perk : missile goes when you break a brick +- when game resumes near bottom, be unvulnerable for .5s ? , once per level +- accelerometer controls coins and balls +- mouvement relatif du puck +- balls should collide with each other +- randomize coins gravity a bit, to make fall more appealing +- apply global curve / brightness to canvas when things blow, or just always to make neon effect better +- perk: bricks attract coins +- perk : puck bounce +1 combo, hit nothing resets +- manifest for PWA (android and apple) +- publish on fdroid +- nerf the hot start a bit +- brick parts fly around with trailing effect ? +- trailing white lines behind ball +- some 3d ish effect ? +- shrink brick at beaking time ? +- perk : multiple hits on the same brick (show remaining resistance as number) +- particle effect around ball when loosing some combo (looks bad) +- Make bricks shadow the light ? using a "fill path" in screen mode, with a gradient background...would get very laggy, maybe just for the ball +- keyboard support +- perk : bricks attract ball +- perk : replay last level (remove score, restores lives if any, and rebuild ) +- perk: breaking bricks stains neighbours +- perk: extra kick after bouncing on puck +- perk: transparent coins +- perk: coins of different colors repulse +- 2x coins when ball goes downward ? +- engine: Offline mode web for iphone +- engine: webgl rendering (not with sdf though, that's super slow) + + + +## Credits + +I pulled many background patterns from https://pattern.monster/ +They are displayed in [patterns.html](patterns.html) for easy inclusion. +Some of the sound generating code was written by ChatGPT, and heavily +adapted to my usage over time. Some of the pixel art is taken from google +image search. I hope to replace it by my own over time. + +[Heart](https://www.youtube.com/watch?v=gdWiTfzXb1g) +[Sonic](https://www.deviantart.com/graystripe2000/art/Pixel-art-16x16-Sonic-936384096) +[Finn](https://at.pinterest.com/pin/finn-the-human-pixel-art--140806230775275/) +[Mushroom](https://pixelartmaker.com/art/cce4295a92035ea) + +## APK version + +The web app is around 50kb, compressed down to 10kb with gzip +I wanted an APK to start in fullscreen and be able to list it on fdroid and the play store. + +I stated with an empty view and went to work trimming it down, with the help of that tutorial +https://github.com/fractalwrench/ApkGolf/blob/master/blog/BLOG_POST.md diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..455304b --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,83 @@ +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.ZoneId + +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsKotlinAndroid) +} + +android { + namespace = "me.lecaro.breakout" + compileSdk = 34 + + defaultConfig { + applicationId = "me.lecaro.breakout" + minSdk = 21 + targetSdk = 34 +// versionCode = 7 +// versionName = "7.0" + + // Get the current Unix timestamp in seconds + versionCode = (System.currentTimeMillis() / 1000/60).toInt() + // Get the current date as a string + versionName = ZonedDateTime.now(ZoneId.of("CET")) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + + + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + signingConfig = signingConfigs.getByName("debug") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { +// compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +//dependencies { +// +// implementation(libs.androidx.core.ktx) +// implementation(libs.androidx.lifecycle.runtime.ktx) +// implementation(libs.androidx.activity.compose) +// implementation(platform(libs.androidx.compose.bom)) +// implementation(libs.androidx.ui) +// implementation(libs.androidx.ui.graphics) +// implementation(libs.androidx.ui.tooling.preview) +// implementation(libs.androidx.material3) +// testImplementation(libs.junit) +// androidTestImplementation(libs.androidx.junit) +// androidTestImplementation(libs.androidx.espresso.core) +// androidTestImplementation(platform(libs.androidx.compose.bom)) +// androidTestImplementation(libs.androidx.ui.test.junit4) +// debugImplementation(libs.androidx.ui.tooling) +// debugImplementation(libs.androidx.ui.test.manifest) +//} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2700098 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/game.js b/app/src/main/assets/game.js new file mode 100644 index 0000000..19c69c3 --- /dev/null +++ b/app/src/main/assets/game.js @@ -0,0 +1,2200 @@ +const MAX_COINS = 400; +const canvas = document.getElementById("game"); +let ctx = canvas.getContext("2d", {alpha: false}); + +let ballSize = 20; +const coinSize = Math.round(ballSize * 0.8); +const puckHeight = ballSize; + +if (allLevels.find(l => l.focus)) { + allLevels = allLevels.filter(l => l.focus) +} +allLevels=allLevels.filter(l=>!l.draft) + + +let runLevels = [] + +let currentLevel = 0; + +const bombSVG = document.createElement('img') +bombSVG.src = 'data:image/svg+xml;base64,' + btoa(` + +`); + + +// Whatever +let puckWidth = 200; +const perks = {}; + +let baseSpeed = 12; // applied to x and y +let combo = 1; + +function baseCombo() { + return 1 + perks.base_combo * 3; +} + +function resetCombo(x, y) { + const prev = combo; + combo = baseCombo(); + if (!levelTime) { + combo += perks.hot_start * 15; + } + if (prev > combo && perks.soft_reset) { + combo += Math.floor((prev - combo) / (1 + perks.soft_reset)) + } + const lost = Math.max(0, prev - combo); + if (lost) { + incrementRunStatistics('combo_resets', 1) + for (let i = 0; i < lost && i < 8; i++) { + setTimeout(() => sounds.comboDecrease(), i * 100); + } + if (typeof x !== "undefined" && typeof y !== "undefined") { + flashes.push({ + type: "text", + text: "-" + lost, + time: levelTime, + color: "red", + x: x, + y: y, + duration: 150, + size: puckHeight, + }); + } + } +} + +function decreaseCombo(by, x, y) { + const prev = combo; + combo = Math.max(baseCombo(), combo - by); + const lost = Math.max(0, prev - combo); + + if (lost) { + sounds.comboDecrease(); + if (typeof x !== "undefined" && typeof y !== "undefined") { + flashes.push({ + type: "text", + text: "-" + lost, + time: levelTime, + color: "red", + x: x, + y: y, + duration: 300, + size: puckHeight, + }); + } + } +} + +let gridSize = 12; + +let running = false, puck = 400; + +let offsetX, gameZoneWidth, gameZoneHeight, brickWidth, needsRender = true; + +const background = document.createElement("img"); +const backgroundCanvas = document.createElement("canvas"); +background.addEventListener("load", () => { + needsRender = true +}) + +const fitSize = () => { + const {width, height} = canvas.getBoundingClientRect(); + canvas.width = width; + canvas.height = height; + backgroundCanvas.width = width; + backgroundCanvas.height = height; + + + gameZoneHeight = isSettingOn("mobile-mode") ? (height * 80) / 100 : height; + const baseWidth = Math.round(Math.min(canvas.width, gameZoneHeight * 0.73)); + brickWidth = Math.floor(baseWidth / gridSize / 2) * 2; + gameZoneWidth = brickWidth * gridSize; + offsetX = Math.floor((canvas.width - gameZoneWidth) / 2); + backgroundCanvas.title = 'resized' + // Ensure puck stays within bounds + setMousePos(puck); + coins = []; + flashes = []; + running = false; + needsRender = true; + putBallsAtPuck(); +}; +window.addEventListener("resize", fitSize); + +function recomputeTargetBaseSpeed() { + baseSpeed = gameZoneWidth / 12 / 10 + currentLevel / 3 + levelTime / (30 * 1000) - perks.slow_down * 2; +} + + +function brickCenterX(index) { + return offsetX + ((index % gridSize) + 0.5) * brickWidth; +} + +function brickCenterY(index) { + return (Math.floor(index / gridSize) + 0.5) * brickWidth; +} + +function getRowCol(index) { + return { + col: index % gridSize, row: Math.floor(index / gridSize), + }; +} + +function getRowColIndex(row, col) { + if (row < 0 || col < 0 || row >= gridSize || col >= gridSize) return -1; + return row * gridSize + col; +} + + +function spawnExplosion(count, x, y, color, duration = 150, size = coinSize) { + if (!!isSettingOn("basic")) return; + for (let i = 0; i < count; i++) { + flashes.push({ + type: "particle", + duration, + time: levelTime, + size, + color, + x: x + ((Math.random() - 0.5) * brickWidth) / 2, + y: y + ((Math.random() - 0.5) * brickWidth) / 2, + vx: (Math.random() - 0.5) * 30, + vy: (Math.random() - 0.5) * 30, + }); + } +} + + +let score = 0; +let scoreStory = []; + +let lastexplosion = 0; +let highScore = parseFloat(localStorage.getItem("breakout-3-hs") || "0"); + +let lastPlayedCoinGrab = 0 + +function addToScore(coin) { + coin.destroyed = true + score += coin.points; + addToTotalScore(coin.points) + if (score > highScore) { + highScore = score; + localStorage.setItem("breakout-3-hs", score); + } + if (!isSettingOn('basic')) { + flashes.push({ + type: "particle", + duration: 100 + Math.random() * 50, + time: levelTime, + size: coinSize / 2, + color: coin.color, + x: coin.previousx, + y: coin.previousy, + vx: (canvas.width - coin.x) / 100, + vy: -coin.y / 100, + ethereal: true, + }) + } + + if (Date.now() - lastPlayedCoinGrab > 16) { + lastPlayedCoinGrab = Date.now() + sounds.coinCatch(coin.x) + } + incrementRunStatistics('caught_coins', coin.points) + +} + +let balls = []; + +function resetBalls() { + const count = 1 + (perks?.multiball || 0); + const perBall = puckWidth / (count + 1); + balls = []; + for (let i = 0; i < count; i++) { + const x = puck - puckWidth / 2 + perBall * (i + 1); + balls.push({ + x, + previousx: x, + y: gameZoneHeight - 1.5 * ballSize, + previousy: gameZoneHeight - 1.5 * ballSize, + vx: Math.random() > 0.5 ? baseSpeed : -baseSpeed, + vy: -baseSpeed, + sx: 0, + sy: 0, + color: currentLevelInfo()?.black_puck ? '#000' : "#FFF", + hitSinceBounce: 0, + piercedSinceBounce: 0, + sparks: 0, + }); + } +} + +function putBallsAtPuck() { + const count = balls.length; + const perBall = puckWidth / (count + 1); + balls.forEach((ball, i) => { + const x = puck - puckWidth / 2 + perBall * (i + 1); + Object.assign(ball, { + x, + previousx: x, + y: gameZoneHeight - 1.5 * ballSize, + previousy: gameZoneHeight - 1.5 * ballSize, + vx: Math.random() > 0.5 ? baseSpeed : -baseSpeed, + vy: -baseSpeed, + sx: 0, + sy: 0, + }); + }); +} + +resetBalls(); +// Default, recomputed at each level load +let bricks = []; +let flashes = []; +let coins = []; +let levelStartScore = 0; +let levelMisses = 0; +let levelSpawnedCoins = 0; + +function getLevelStats() { + const catchRate = (score - levelStartScore) / (levelSpawnedCoins || 1); + let stats = ` + you caught ${score - levelStartScore} coins out of ${levelSpawnedCoins} in ${Math.round(levelTime / 1000)} seconds. + `; + stats += levelMisses ? `You missed ${levelMisses} times. ` : ""; + let text = [stats]; + let repeats = 1; + let choices = 3; + + if (levelTime < 30 * 1000) { + repeats++; + choices++; + text.push("speed bonus: +1 upgrade and choice"); + } else if (levelTime < 60 * 1000) { + choices++; + text.push("speed bonus: +1 choice"); + } + if (catchRate === 1) { + repeats++; + choices++; + text.push("coins bonus: +1 upgrade and choice"); + } else if (catchRate > 0.9) { + choices++; + text.push("coins bonus: +1 choice."); + } + if (levelMisses === 0) { + repeats++; + choices++; + text.push("accuracy bonus: +1 upgrade and choice"); + } else if (levelMisses <= 3) { + choices++; + text.push("accuracy bonus:+1 choice"); + } + + return { + stats, text: text.map(t => '

' + t + '

').join('\n'), repeats, choices, + }; +} + +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; + if (actions.length < choices) { + textAfterButtons = `

You are running out of upgrades, more will be unlocked when you catch lots of coins.

` + } + const cb = await asyncAlert({ + title: "Pick an upgrade " + (repeats ? "(" + (repeats + 1) + ")" : ""), actions, text, allowClose: false, + textAfterButtons + }); + cb(); + } + resetCombo(); + resetBalls(); +} + +function setLevel(l) { + running = false; + needsRender = true + if (l > 0) { + openUpgradesPicker().then(); + } + currentLevel = l; + + levelTime = 0; + lastTickDown = levelTime; + levelStartScore = score; + levelSpawnedCoins = 0; + levelMisses = 0; + + resetCombo(); + recomputeTargetBaseSpeed(); + resetBalls(); + + const lvl = currentLevelInfo(); + if (lvl.size !== gridSize) { + gridSize = lvl.size; + fitSize(); + } + incrementRunStatistics('lvl_size_' + lvl.size, 1) + incrementRunStatistics('lvl_name_' + lvl.name, 1) + coins = []; + bricks = [...lvl.bricks]; + flashes = []; + + background.src = 'data:image/svg+xml;base64,' + btoa(lvl.svg) + +} + +function currentLevelInfo() { + return runLevels[currentLevel % runLevels.length]; +} + +function reset_perks() { + + for (let u of upgrades) { + perks[u.id] = 0; + } + + const giftable = getPossibleUpgrades().filter(u => u.giftable) + if (!giftable.length) { + debugger + } + + const randomGift = isSettingOn('easy') ? 'slow_down' : giftable[Math.floor(Math.random() * giftable.length)].id; + perks[randomGift] = 1; + return randomGift +} + +const upgrades = [{ + minimumTotalScore: 3000, + id: 'multiball', + giftableAfterTotalScore: 20000, + name: "+1 ball", + max: 3, + help: `Start each level with one more balls.`, +}, { + minimumTotalScore: 5000, + id: 'pierce', + giftableAfterTotalScore: 15000, + name: "Ball pierces bricks", + max: 3, + help: `Pierce through 3 blocks after bouncing on the puck.`, +}, { + minimumTotalScore: 500, + id: 'telekinesis', + giftableAfterTotalScore: 900, + name: "Puck controls ball", + max: 2, + help: `Control the ball's trajectory with the puck.`, +}, { + minimumTotalScore: 0, + extra_levels_minimum_total_score: 250, + id: 'extra_life', + name: "+1 life", + max: 3, + help: `Allows you to survive dropping the ball once.`, +}, { + minimumTotalScore: 20000, + id: 'sapper', + giftableAfterTotalScore: 32000, + name: "Bricks become bombs", + max: 1, + help: `Broken blocks are replaced by bombs.`, +}, { + minimumTotalScore: 100000, + id: 'soft_reset', + name: "Soft reset", + max: 2, + help: `Only loose half your combo when it resets.`, +}, + + { + minimumTotalScore: 30000, + id: 'bigger_explosions', + name: "Bigger explosions", + max: 1, + help: `All bombs have larger area of effect.`, + }, + + { + minimumTotalScore: 2000, + id: 'coin_magnet', + name: "Puck attracts coins", + max: 3, + help: `Coins falling are drawn toward the puck.`, + }, + + { + minimumTotalScore: 7000, + id: 'metamorphosis', + name: "Coins stain bricks", + color_blind_exclude: true, + max: 1, + help: `Coins color the bricks they touch.`, + }, + + { + minimumTotalScore: 6000, + id: 'picky_eater', + giftableAfterTotalScore: 9000, + name: "Single color streak", + color_blind_exclude: true, + max: 1, + help: `Hit bricks of the same color for more coins.`, + }, + + { + minimumTotalScore: 80000, + id: 'pierce_color', + name: "Color pierce", + color_blind_exclude: true, + max: 1, + help: `Colored ball pierces bricks of the same color.`, + }, + + { + minimumTotalScore: 0, + id: 'streak_shots', + giftableAfterTotalScore: 1500, + name: "Single puck hit streak", + max: 1, + help: `Break many bricks at once for more coins.`, + }, + + { + minimumTotalScore: 10000, + id: 'hot_start', + giftableAfterTotalScore: 24000, + name: "Hot start", + max: 3, + help: `Clear the level quickly for more coins.`, + }, + + { + minimumTotalScore: 200, + id: 'sides_are_lava', + giftableAfterTotalScore: 500, + name: "Shoot straight", + max: 1, + help: `Avoid the sides for more coins.`, + }, { + minimumTotalScore: 600, + id: 'top_is_lava', + giftableAfterTotalScore: 1200, + name: "Sky is the limit", + max: 1, + help: `Avoid the top for more coins.`, + }, + + { + minimumTotalScore: 8000, + id: 'catch_all_coins', + giftableAfterTotalScore: 16000, + name: "Compound interest", + max: 3, + help: `Catch all coins with your puck for even more coins.`, + }, { + minimumTotalScore: 0, + extra_levels_minimum_total_score: 6250, + id: 'viscosity', + name: "Slower coins fall", + max: 3, + help: `Coins quickly decelerate and fall more slowly.`, + }, + + { + minimumTotalScore: 0, + extra_levels_minimum_total_score: 750, + id: 'base_combo', + giftableAfterTotalScore: 0, + name: "+3 base combo", + max: 3, + help: `Your combo starts 3 points higher.`, + }, + + { + minimumTotalScore: 0, + extra_levels_minimum_total_score: 25, + id: 'slow_down', + name: "Slower ball", + max: 2, + help: `Slows down the ball.`, + }, { + minimumTotalScore: 65000, + id: 'extra_levels', + name: "+1 level", + max: 3, + help: `Play one more level before game over.`, + }, { + minimumTotalScore: 2500, + id: 'skip_last', + name: "Last brick breaks", + max: 3, + help: `The last brick will self-destruct.`, + }, { + minimumTotalScore: 3600, id: 'smaller_puck', name: "Smaller puck", max: 2, help: `Gives you more control.`, + }, { + minimumTotalScore: 0, + extra_levels_minimum_total_score: 0, + id: 'bigger_puck', + name: "Bigger puck", + max: 2, + help: `Catches more coins.`, + }] + +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() { + const ts = getTotalScore() + return upgrades + .filter(u => !(isSettingOn('color_blind') && u.color_blind_exclude)) + .map(u => ({ + ...u, max: computeUpgradeCurrentMaxLevel(u, ts), giftable: ts >= (u.giftableAfterTotalScore ?? Infinity) + })).filter(u => u.max > 0) +} + +function levelTotalScoreCondition(l, li) { + return li < 8 ? 0 : Math.round(Math.pow(10, 1 + (li + l.size) / 30) * (li)) * 10 +} + +function shuffleLevels(nameToAvoid = null) { + const ts = getTotalScore() + runLevels = allLevels + .filter((l, li) => ts >= levelTotalScoreCondition(l, li)) + .filter(l => l.name !== nameToAvoid || allLevels.length === 1) + .sort(() => Math.random() - 0.5) + .slice(0, 7 + 3) + .sort((a, b) => a.bricks.filter(i => i).length - b.bricks.filter(i => i).length) +} + +function getUpgraderUnlockPoints() { + let list = [] + + upgrades + .filter(u => !(isSettingOn('color_blind') && u.color_blind_exclude)) + .forEach(u => { + if (u.minimumTotalScore) { + list.push({ + threshold: u.minimumTotalScore, + title: 'Unlock: ' + u.name, + help: 'This new perks will be added to the choices offered to you.' + }) + } + 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) => { + list.push({ + threshold: levelTotalScoreCondition(l, li), + title: 'Level: ' + l.name, + help: l.name + ' will be added to the list of possible levels.' + }) + }) + + return list.filter(o => o.threshold).sort((a, b) => a.threshold - b.threshold) +} + + +function pickRandomUpgrades(count) { + + + let list = getPossibleUpgrades() + .sort(() => Math.random() - 0.5) + .filter(u => perks[u.id] < u.max) + .slice(0, count) + .sort((a, b) => a.id > b.id ? 1 : -1) + .map(u => { + + incrementRunStatistics('offered_upgrade.' + u.id, 1) + return { + key: u.id, text: u.name, value: () => { + perks[u.id]++; + incrementRunStatistics('picked_upgrade.' + u.id, 1) + scoreStory.push("Picked upgrade : " + u.name); + }, help: u.help, max: u.max, + + checked: perks[u.id], + + } + }) + + + return list; +} + + +function restart() { + console.log("restart") + // When restarting, we want to avoid restarting with the same level we're on, so we exclude from the next + // run's level list + shuffleLevels(levelTime || score ? currentLevelInfo().name : null); + resetRunStatistics() + score = 0; + scoreStory = []; + const randomGift = reset_perks(); + + incrementRunStatistics('starting_upgrade.' + randomGift, 1) + + setLevel(0); + scoreStory.push(`You started playing with the upgrade "${upgrades.find(u => u.id === randomGift)?.name}" on the level "${runLevels[0].name}". `,); +} + +function setMousePos(x) { + + needsRender = true; + puck = x; + + if (offsetX > ballSize) { + // We have borders visible, enforce them + if (puck < offsetX + puckWidth / 2) { + puck = offsetX + puckWidth / 2; + } + if (puck > offsetX + gameZoneWidth - puckWidth / 2) { + puck = offsetX + gameZoneWidth - puckWidth / 2; + } + } else { + // Let puck touch the border of the screen + if (puck < puckWidth / 2) { + puck = puckWidth / 2; + } + if (puck > offsetX * 2 + gameZoneWidth - puckWidth / 2) { + puck = offsetX * 2 + gameZoneWidth - puckWidth / 2; + } + } + if (!running && !levelTime) { + putBallsAtPuck(); + } +} + +canvas.addEventListener("mouseup", (e) => { + if (e.button !== 0) return; + running = !running; +}); + +canvas.addEventListener("mousemove", (e) => { + setMousePos(e.x); +}); + +canvas.addEventListener("touchstart", (e) => { + e.preventDefault(); + if (!e.touches?.length) return; + setMousePos(e.touches[0].pageX); + running = true; +}); +canvas.addEventListener("touchend", (e) => { + e.preventDefault(); + running = false; +}); +canvas.addEventListener("touchcancel", (e) => { + e.preventDefault(); + running = false; + needsRender = true +}); +canvas.addEventListener("touchmove", (e) => { + if (!e.touches?.length) return; + setMousePos(e.touches[0].pageX); +}); + +let lastTick = performance.now(); + +function brickIndex(x, y) { + return getRowColIndex(Math.floor(y / brickWidth), Math.floor((x - offsetX) / brickWidth),); +} + +function hasBrick(index) { + if (bricks[index]) return index; +} + +function hitsSomething(x, y, radius) { + return (hasBrick(brickIndex(x - radius, y - radius)) ?? hasBrick(brickIndex(x + radius, y - radius)) ?? hasBrick(brickIndex(x + radius, y + radius)) ?? hasBrick(brickIndex(x - radius, y + radius))); +} + +function shouldPierceByColor(ballOrCoin, vhit, hhit, chit) { + if (!perks.pierce_color) return false + // if (ballOrCoin.color === 'white') return true + if (typeof vhit !== 'undefined' && bricks[vhit] !== ballOrCoin.color) { + return false + } + if (typeof hhit !== 'undefined' && bricks[hhit] !== ballOrCoin.color) { + return false + } + if (typeof chit !== 'undefined' && bricks[chit] !== ballOrCoin.color) { + return false + } + return true +} + +function brickHitCheck(ballOrCoin, radius, isBall) { + // Make ball/coin bonce, and return bricks that were hit + const {x, y, previousx, previousy, hitSinceBounce} = ballOrCoin; + + const vhit = hitsSomething(previousx, y, radius); + const hhit = hitsSomething(x, previousy, radius); + const chit = (typeof vhit == "undefined" && typeof hhit == "undefined" && hitsSomething(x, y, radius)) || undefined; + + + let pierce = isBall && ballOrCoin.piercedSinceBounce < perks.pierce * 3; + if (pierce && (typeof vhit !== "undefined" || typeof hhit !== "undefined" || typeof chit !== "undefined")) { + ballOrCoin.piercedSinceBounce++ + } + if (isBall && shouldPierceByColor(ballOrCoin, vhit, hhit, chit)) { + pierce = true + } + + + if (typeof vhit !== "undefined" || typeof chit !== "undefined") { + if (!pierce) { + ballOrCoin.y = ballOrCoin.previousy; + ballOrCoin.vy *= -1; + } + + if (!isBall) { + // Roll on corners + const leftHit = bricks[brickIndex(x - radius, y + radius)]; + const rightHit = bricks[brickIndex(x + radius, y + radius)]; + + if (leftHit && !rightHit) { + ballOrCoin.vx += 1; + } + if (!leftHit && rightHit) { + ballOrCoin.vx -= 1; + } + } + } + if (typeof hhit !== "undefined" || typeof chit !== "undefined") { + if (!pierce) { + ballOrCoin.x = ballOrCoin.previousx; + ballOrCoin.vx *= -1; + } + } + + return vhit ?? hhit ?? chit; +} + +function bordersHitCheck(coin, radius, delta) { + if (coin.destroyed) return; + coin.previousx = coin.x; + coin.previousy = coin.y; + coin.x += coin.vx * delta; + coin.y += coin.vy * delta; + coin.sx ||= 0; + coin.sy ||= 0; + coin.sx += coin.previousx - coin.x; + coin.sy += coin.previousy - coin.y; + coin.sx *= 0.9; + coin.sy *= 0.9; + + let vhit = 0, hhit = 0; + + + if (coin.x < (offsetX > ballSize ? offsetX : 0) + radius) { + coin.x = offsetX + radius; + coin.vx *= -1; + hhit = 1; + } + if (coin.y < radius) { + coin.y = radius; + coin.vy *= -1; + vhit = 1; + } + if (coin.x > canvas.width - (offsetX > ballSize ? offsetX : 0) - radius) { + coin.x = canvas.width - offsetX - radius; + coin.vx *= -1; + hhit = 1; + } + + return hhit + vhit * 2; +} + +let lastTickDown = 0; + +function tick() { + + recomputeTargetBaseSpeed(); + const currentTick = performance.now(); + + puckWidth = (gameZoneWidth / 12) * (3 - perks.smaller_puck + perks.bigger_puck); + + if (running) { + + levelTime += currentTick - lastTick; + // How many time to compute + let delta = Math.min(4, (currentTick - lastTick) / (1000 / 60)); + delta *= running ? 1 : 0 + + + coins = coins.filter((coin) => !coin.destroyed); + balls = balls.filter((ball) => !ball.destroyed); + + const remainingBricks = bricks.filter((b) => b && b !== "black").length; + + if (levelTime > lastTickDown + 1000 && perks.hot_start) { + lastTickDown = levelTime; + decreaseCombo(perks.hot_start, puck, gameZoneHeight - 2 * puckHeight); + } + + if (remainingBricks < perks.skip_last) { + bricks.forEach((type, index) => { + if (type) { + explodeBrick(index, balls[0], true); + } + }); + } + if (!remainingBricks && !coins.length) { + incrementRunStatistics('level_time', levelTime) + + if (currentLevel + 1 < max_levels()) { + setLevel(currentLevel + 1); + } else { + gameOver("Run finished with " + score + " points", "You cleared all levels for this run."); + } + } else if (running || levelTime) { + let playedCoinBounce = false; + const coinRadius = Math.round(coinSize / 2); + + coins.forEach((coin) => { + if (coin.destroyed) return; + if (perks.coin_magnet) { + coin.vx += ((delta * (puck - coin.x)) / (100 + Math.pow(coin.y - gameZoneHeight, 2) + Math.pow(coin.x - puck, 2))) * perks.coin_magnet * 100; + } + + const ratio = 1 - (perks.viscosity * 0.03 + 0.005) * delta; + + coin.vy *= ratio; + coin.vx *= ratio; + + // Gravity + coin.vy += delta * coin.weight * 0.8; + + const speed = Math.abs(coin.sx) + Math.abs(coin.sx); + const hitBorder = bordersHitCheck(coin, coinRadius, delta); + + if (coin.y > gameZoneHeight - coinRadius - puckHeight && coin.y < gameZoneHeight + puckHeight + coin.vy && Math.abs(coin.x - puck) < coinRadius + puckWidth / 2 + // a bit of margin to be nice + puckHeight) { + addToScore(coin); + + } else if (coin.y > canvas.height + coinRadius) { + coin.destroyed = true; + if (perks.catch_all_coins) { + decreaseCombo(coin.points * perks.catch_all_coins, coin.x, canvas.height - coinRadius); + } + } + + const hitBrick = brickHitCheck(coin, coinRadius, false); + + if (perks.metamorphosis && typeof hitBrick !== "undefined") { + if (bricks[hitBrick] && coin.color !== bricks[hitBrick] && bricks[hitBrick] !== "black" && !coin.coloredABrick) { + bricks[hitBrick] = coin.color; + coin.coloredABrick = true + } + } + if (typeof hitBrick !== "undefined" || hitBorder) { + coin.vx *= 0.8; + coin.vy *= 0.8; + + if (speed > 20 && !playedCoinBounce) { + playedCoinBounce = true; + sounds.coinBounce(coin.x, 0.2); + } + + if (Math.abs(coin.vy) < 3) { + coin.vy = 0; + } + } + }); + + balls.forEach((ball) => ballTick(ball, delta)); + + flashes.forEach((flash) => { + if (flash.type === "particle") { + flash.x += flash.vx * delta; + flash.y += flash.vy * delta; + if (!flash.ethereal) { + flash.vy += 0.5; + if (hasBrick(brickIndex(flash.x, flash.y))) { + flash.destroyed = true; + } + } + } + }); + } + } + + render(); + + requestAnimationFrame(tick); + lastTick = currentTick; +} + +function isTelekinesisActive(ball) { + return perks.telekinesis && !ball.hitSinceBounce && ball.vy < 0; +} + +function ballTick(ball, delta) { + ball.previousvx = ball.vx; + ball.previousvy = ball.vy; + + if (isTelekinesisActive(ball)) { + ball.vx += ((puck - ball.x) / 1000) * delta * perks.telekinesis; + } else if (ball.vx * ball.vx + ball.vy * ball.vy < baseSpeed * baseSpeed * 2) { + ball.vx *= 1.01; + ball.vy *= 1.01; + } else { + ball.vx *= 0.99; + if (Math.abs(ball.vy) > 0.5 * baseSpeed) { + ball.vy *= 0.99; + } + } + + const borderHitCode = bordersHitCheck(ball, ballSize / 2, delta); + if (borderHitCode) { + if (perks.sides_are_lava && borderHitCode % 2) { + resetCombo(ball.x, ball.y); + } + if (perks.top_is_lava && borderHitCode >= 2) { + resetCombo(ball.x, ball.y + ballSize); + } + sounds.wallBeep(ball.x); + ball.bouncesList?.push({x: ball.previousx, y: ball.previousy}) + } + + // Puck collision + const ylimit = gameZoneHeight - puckHeight - ballSize / 2; + if (ball.y > ylimit && Math.abs(ball.x - puck) < ballSize / 2 + puckWidth / 2 && ball.vy > 0) { + const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); + const angle = Math.atan2(-puckWidth / 2, ball.x - puck); + ball.vx = speed * Math.cos(angle); + ball.vy = speed * Math.sin(angle); + + sounds.wallBeep(ball.x); + if (perks.streak_shots) { + resetCombo(ball.x, ball.y); + } + if (!ball.hitSinceBounce) { + incrementRunStatistics('miss') + levelMisses++; + 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, + y: ball.previousy + }) + for(si=0; si< ball.bouncesList.length-1;si++){ + // segement + const start= ball.bouncesList[si] + const end= ball.bouncesList[si+1] + const distance= Math.sqrt(Math.pow(start.x-end.x,2)+ Math.pow(start.y-end.y,2)) + const parts = distance/30 + for(var i = 0; i gameZoneHeight + ballSize / 2 && running) { + ball.destroyed = true; + if (!balls.find((b) => !b.destroyed)) { + if (perks.extra_life) { + perks.extra_life--; + resetBalls(); + sounds.revive(); + running = false; + coins = []; + flashes.push({ + type: "ball", + duration: 500, + time: levelTime, + size: brickWidth * 2, + color: "white", + x: ball.x, + y: ball.y, + }); + } else { + gameOver("Game Over", "You dropped the ball after catching " + score + " coins. "); + } + } + } + const hitBrick = brickHitCheck(ball, ballSize / 2, true); + if (typeof hitBrick !== "undefined") { + const wasABomb = bricks[hitBrick] === "black"; + explodeBrick(hitBrick, ball, false); + + if (perks.sapper && !wasABomb) { + bricks[hitBrick] = "black"; + } + } + + if (!isSettingOn("basic")) { + ball.sparks += (delta * (combo - 1)) / 30; + if (ball.sparks > 1) { + flashes.push({ + type: "particle", + duration: 100 * ball.sparks, + time: levelTime, + size: coinSize / 2, + color: ball.color, + x: ball.x, + y: ball.y, + vx: (Math.random() - 0.5) * baseSpeed, + vy: (Math.random() - 0.5) * baseSpeed, + }); + ball.sparks = 0; + } + } +} + + +let runStatistics = {}; + +function resetRunStatistics() { + runStatistics = { + started: Date.now(), + ended: null, + width: window.innerWidth, + height: window.innerHeight, + easy: isSettingOn('easy'), + color_blind: isSettingOn('color_blind'), + } +} + + +function incrementRunStatistics(key, amount = 1) { + runStatistics[key + '_total'] = (runStatistics[key + '_total'] || 0) + amount + runStatistics[key + '_lvl_' + currentLevel] = (runStatistics[key + '_lvl_' + currentLevel] || 0) + amount +} + +function getTotalScore() { + try { + return JSON.parse(localStorage.getItem('breakout_71_total_score') || '0') + } catch (e) { + return 0 + } +} + +function addToTotalScore(points) { + try { + localStorage.setItem('breakout_71_total_score', JSON.stringify(getTotalScore() + points)) + } catch (e) { + } +} + +function gameOver(title, intro) { + if (!running) return; + running = false; + needsRender = true + + runStatistics.ended = Date.now() + + 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. `); + } + + try { + // Stores only last 100 runs + const runsHistory = JSON.parse(localStorage.getItem('breakout_71_history') || '[]').slice(0, 99).concat([runStatistics]) + + // Generate some histogram + + localStorage.setItem('breakout_71_history', '
' + JSON.stringify(runsHistory, null, 2) + '
') + } catch { + } + + let animationDelay = -300 + const getDelay = () => { + animationDelay += 800 + return 'animation-delay:' + animationDelay + 'ms;' + } + // unlocks + let unlocksInfo = '' + const endTs = getTotalScore() + const startTs = endTs - score + const list = getUpgraderUnlockPoints() + list.filter(u => u.threshold > startTs && u.threshold < endTs).forEach(u => { + unlocksInfo += ` +

+ ${u.title} + +

+` + }) + + const previousUnlockAt = list.findLast(u => u.threshold <= endTs)?.threshold || 0 + const nextUnlock = list.find(u => u.threshold > endTs) + + if (nextUnlock) { + const total = nextUnlock?.threshold - previousUnlockAt + const done = endTs - previousUnlockAt + intro += `Score ${nextUnlock.threshold - endTs} more points to reach the next unlock.` + + unlocksInfo += ` +

+ ${nextUnlock.title} + +

+ +` + list.slice(list.indexOf(nextUnlock) + 1).slice(0, 3).forEach(u => { + unlocksInfo += ` +

+ ${u.title} +

+` + }) + } + + + asyncAlert({ + allowClose: true, title, text: ` +

${intro}

+ ${unlocksInfo} + `, textAfterButtons: ` + + ${scoreStory.map((t) => "

" + t + "

").join("")} + ` + }).then(() => restart()); +} + +function explodeBrick(index, ball, isExplosion) { + const color = bricks[index]; + if (color === 'black') { + delete bricks[index]; + const x = brickCenterX(index), y = brickCenterY(index); + + incrementRunStatistics('explosion', 1) + sounds.explode(ball.x); + const {col, row} = getRowCol(index); + const size = 1 + perks.bigger_explosions; + // Break bricks around + for (let dx = -size; dx <= size; dx++) { + for (let dy = -size; dy <= size; dy++) { + const i = getRowColIndex(row + dy, col + dx); + if (bricks[i] && i !== -1) { + explodeBrick(i, ball, true) + } + } + } + // Blow nearby coins + coins.forEach((c) => { + const dx = c.x - x; + const dy = c.y - y; + const d2 = Math.max(brickWidth, Math.abs(dx) + Math.abs(dy)); + c.vx += (dx / d2) * 10 * size / c.weight; + c.vy += (dy / d2) * 10 * size / c.weight; + }); + lastexplosion = Date.now(); + + flashes.push({ + type: "ball", duration: 150, time: levelTime, size: brickWidth * 2, color: "white", x, y, + }); + spawnExplosion(7 * (1 + perks.bigger_explosions), x, y, "white", 150, coinSize,); + ball.hitSinceBounce++; + } else if (color) { + // Flashing is take care of by the tick loop + const x = brickCenterX(index), y = brickCenterY(index); + + bricks[index] = ""; + + levelSpawnedCoins += combo; + + incrementRunStatistics('spawned_coins', combo) + + coins = coins.filter((c) => !c.destroyed); + for (let i = 0; i < combo; i++) { + // Avoids saturating the canvas with coins + if (coins.length > MAX_COINS * (isSettingOn("basic") ? 0.5 : 1)) { + // Just pick a random one + coins[Math.floor(Math.random() * coins.length)].points++; + continue; + } + + const coord = { + x: x + (Math.random() - 0.5) * (brickWidth - coinSize), + y: y + (Math.random() - 0.5) * (brickWidth - coinSize), + }; + + coins.push({ + points: 1, + color, ...coord, + previousx: coord.x, + previousy: coord.y, + vx: ball.vx * (0.5 + Math.random()), + vy: ball.vy * (0.5 + Math.random()), + sx: 0, + sy: 0, + weight: 0.8 + Math.random() * 0.2 + }); + } + + combo += perks.streak_shots + perks.catch_all_coins + perks.sides_are_lava + perks.top_is_lava + perks.picky_eater; + + if (!isExplosion) { + // color change + if ((perks.picky_eater || perks.pierce_color) && color !== ball.color) { + // reset streak + if (perks.picky_eater) resetCombo(ball.x, ball.y); + ball.color = color; + } else { + 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); + } +} + +function max_levels() { + return 7 + perks.extra_levels; +} + +function render() { + if (running) needsRender = true + if (!needsRender) { + return + } + needsRender = false; + + const level = currentLevelInfo(); + const {width, height} = canvas; + if (!width || !height) return; + + let scoreInfo = ""; + for (let i = 0; i < perks.extra_life; i++) { + scoreInfo += "🖤 "; + } + + scoreInfo += score.toString(); + scoreDisplay.innerText = scoreInfo; + + + if (!isSettingOn("basic") && !level.color && level.svg && !level.black_puck) { + + ctx.globalCompositeOperation = "source-over"; + ctx.globalAlpha = 0.7 + ctx.fillStyle = "#000"; + ctx.fillRect(0, 0, width, height); + + ctx.globalCompositeOperation = "multiply"; + ctx.globalAlpha = 0.3; + const gradient = ctx.createLinearGradient(offsetX, gameZoneHeight - puckHeight, offsetX, height - puckHeight * 3,); + gradient.addColorStop(0, "black"); + gradient.addColorStop(1, "transparent"); + ctx.fillStyle = gradient; + ctx.fillRect(offsetX, gameZoneHeight - puckHeight * 3, gameZoneWidth, puckHeight * 4,); + + ctx.globalCompositeOperation = "screen"; + ctx.globalAlpha = 0.6; + coins.forEach((coin) => { + if (!coin.destroyed) drawFuzzyBall(ctx, coin.color, coinSize * 2, coin.x, coin.y); + }); + balls.forEach((ball) => { + drawFuzzyBall(ctx, ball.color, ballSize * 2, ball.x, ball.y); + }); + ctx.globalAlpha = 0.5; + bricks.forEach((color, index) => { + if (!color) return; + const x = brickCenterX(index), y = brickCenterY(index); + drawFuzzyBall(ctx, color == 'black' ? '#666' : color, brickWidth, x, y); + }); + ctx.globalAlpha = 1; + flashes.forEach((flash) => { + const {x, y, time, color, size, type, duration} = flash; + const elapsed = levelTime - time; + ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); + if (type === "ball" || type === "particle") { + drawFuzzyBall(ctx, color, size, x, y); + } + }); + + ctx.globalAlpha = 0.9; + ctx.globalCompositeOperation = "multiply"; + if (level.svg && background.complete) { + if (backgroundCanvas.title !== level.name) { + backgroundCanvas.title = level.name + backgroundCanvas.width = canvas.width + backgroundCanvas.height = canvas.height + const bgctx = backgroundCanvas.getContext("2d") + bgctx.fillStyle = level.color + bgctx.fillRect(0, 0, canvas.width, canvas.height) + bgctx.fillStyle = ctx.createPattern(background, "repeat"); + bgctx.fillRect(0, 0, width, height); + console.log("redrew context") + } + ctx.drawImage(backgroundCanvas, 0, 0) + + + } + } else { + + ctx.globalCompositeOperation = "source-over"; + ctx.globalAlpha = 1 + ctx.fillStyle = level.color || "#000"; + ctx.fillRect(0, 0, width, height); + + flashes.forEach((flash) => { + const {x, y, time, color, size, type, duration} = flash; + const elapsed = levelTime - time; + ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); + if (type === "particle") { + drawBall(ctx, color, size, x, y); + } + }); + } + + if (combo > baseCombo()) { + ctx.globalCompositeOperation = "screen"; + ctx.globalAlpha = (2 + combo - baseCombo()) / 50; + + if (perks.top_is_lava) { + drawRedGradientSquare(ctx, offsetX, 0, gameZoneWidth, ballSize, 0, 0, 0, ballSize,); + } + if (perks.sides_are_lava) { + drawRedGradientSquare(ctx, offsetX, 0, ballSize, gameZoneHeight, 0, 0, ballSize, 0,); + drawRedGradientSquare(ctx, offsetX + gameZoneWidth - ballSize, 0, ballSize, gameZoneHeight, ballSize, 0, 0, 0,); + } + if (perks.catch_all_coins) { + drawRedGradientSquare(ctx, offsetX, gameZoneHeight - ballSize, gameZoneWidth, ballSize, 0, ballSize, 0, 0,); + } + if (perks.streak_shots) { + drawRedGradientSquare(ctx, puck - puckWidth / 2, gameZoneHeight - puckHeight - ballSize, puckWidth, ballSize, 0, ballSize, 0, 0,); + } + if (perks.picky_eater) { + let okColors = new Set(balls.map((b) => b.color)); + + bricks.forEach((type, index) => { + if (!type || type === "black" || okColors.has(type)) return; + const x = brickCenterX(index), y = brickCenterY(index); + drawFuzzyBall(ctx, "red", brickWidth, x, y); + }); + } + ctx.globalAlpha = 1; + } + + ctx.globalAlpha = 1; + ctx.globalCompositeOperation = "source-over"; + const lastExplosionDelay = Date.now() - lastexplosion + 5; + const shaked = lastExplosionDelay < 200; + if (shaked) { + ctx.translate((Math.sin(Date.now()) * 50) / lastExplosionDelay, (Math.sin(Date.now() + 36) * 50) / lastExplosionDelay,); + } + + ctx.globalCompositeOperation = "source-over"; + renderAllBricks(ctx); + + ctx.globalCompositeOperation = "screen"; + flashes = flashes.filter((f) => levelTime - f.time < f.duration && !f.destroyed,); + + flashes.forEach((flash) => { + const {x, y, time, color, size, type, text, duration, points} = flash; + const elapsed = levelTime - time; + ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2)); + if (type === "text") { + ctx.globalCompositeOperation = "source-over"; + drawText(ctx, text, color, size, {x, y: y - elapsed / 10}); + } else if (type === "particle") { + ctx.globalCompositeOperation = "screen"; + drawBall(ctx, color, size, x, y); + drawFuzzyBall(ctx, color, size, x, y); + } + }); + + // Coins + ctx.globalAlpha = 1; + ctx.globalCompositeOperation = "source-over"; + coins.forEach((coin) => { + if (!coin.destroyed) drawCoin(ctx, coin.color, coinSize, coin, level.color || 'black'); + }); + + + // Black shadow around balls + if (coins.length > 10 && !isSettingOn('basic')) { + ctx.globalAlpha = Math.min(0.8, (coins.length - 10) / 50); + balls.forEach((ball) => { + drawBall(ctx, level.color || "#000", ballSize * 6, ball.x, ball.y); + }); + } + + + ctx.globalAlpha = 1 + ctx.globalCompositeOperation = "source-over"; + const puckColor = level.black_puck ? '#000' : '#FFF' + balls.forEach((ball) => { + drawBall(ctx, ball.color, ballSize, ball.x, ball.y); + // effect + if (isTelekinesisActive(ball)) { + ctx.strokeStyle = puckColor; + ctx.beginPath(); + ctx.bezierCurveTo(puck, gameZoneHeight, puck, ball.y, ball.x, ball.y); + ctx.stroke(); + } + }); + // The puck + + ctx.globalAlpha = 1 + ctx.globalCompositeOperation = "source-over"; + drawPuck(ctx, puckColor, puckWidth, puckHeight) + + + if (combo > 1) { + ctx.globalCompositeOperation = "destination-out"; + drawText(ctx, "x " + combo, "white", puckHeight, { + x: puck, y: gameZoneHeight - puckHeight / 2, + }); + } + // Borders + ctx.fillStyle = puckColor; + ctx.globalCompositeOperation = "source-over"; + if (offsetX > ballSize) { + ctx.fillRect(offsetX, 0, 1, height); + ctx.fillRect(width - offsetX - 1, 0, 1, height); + } + if (isSettingOn("mobile-mode")) { + ctx.fillRect(offsetX, gameZoneHeight, gameZoneWidth, 1); + if (!running) { + drawText(ctx, "Keep pressing here to play", puckColor, puckHeight, { + x: canvas.width / 2, y: gameZoneHeight + (canvas.height - gameZoneHeight) / 2, + }); + } + } + + if (shaked) { + ctx.resetTransform(); + } +} + +let cachedBricksRender = document.createElement("canvas"); +let cachedBricksRenderKey = null; + +function renderAllBricks(destinationCtx) { + ctx.globalAlpha = 1; + + const level = currentLevelInfo(); + + const newKey = gameZoneWidth + "_" + bricks.join("_") + bombSVG.complete; + if (newKey !== cachedBricksRenderKey) { + cachedBricksRenderKey = newKey; + + cachedBricksRender.width = gameZoneWidth; + cachedBricksRender.height = gameZoneWidth + 1; + const ctx = cachedBricksRender.getContext("2d"); + ctx.clearRect(0, 0, gameZoneWidth, gameZoneWidth); + ctx.resetTransform(); + ctx.translate(-offsetX, 0); + // Bricks + bricks.forEach((color, index) => { + const x = brickCenterX(index), y = brickCenterY(index); + + if (!color) return; + drawBrick(ctx, color, x, y, level.squared || false); + if (color === 'black') { + ctx.globalCompositeOperation = "source-over"; + drawIMG(ctx, bombSVG, brickWidth, x, y); + } + }); + } + + destinationCtx.drawImage(cachedBricksRender, offsetX, 0); +} + +let cachedGraphics = {}; + +function drawPuck(ctx, color, puckWidth, puckHeight) { + + const key = "puck" + color + "_" + puckWidth + '_' + puckHeight; + + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = puckWidth; + can.height = puckHeight * 2; + const canctx = can.getContext("2d"); + canctx.fillStyle = color; + + + canctx.beginPath(); + canctx.moveTo(0, puckHeight * 2) + canctx.lineTo(0, puckHeight * 1.25) + canctx.bezierCurveTo(0, puckHeight * .75, puckWidth, puckHeight * .75, puckWidth, puckHeight * 1.25) + canctx.lineTo(puckWidth, puckHeight * 2) + canctx.fill(); + cachedGraphics[key] = can; + } + + ctx.drawImage(cachedGraphics[key], Math.round(puck - puckWidth / 2), gameZoneHeight - puckHeight * 2,); + + +} + +function drawBall(ctx, color, width, x, y) { + const key = "ball" + color + "_" + width; + + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + const size = Math.round(width); + can.width = size; + can.height = size; + + const canctx = can.getContext("2d"); + canctx.beginPath(); + canctx.arc(size / 2, size / 2, Math.round(size / 2), 0, 2 * Math.PI); + canctx.fillStyle = color; + canctx.fill(); + cachedGraphics[key] = can; + } + ctx.drawImage(cachedGraphics[key], Math.round(x - width / 2), Math.round(y - width / 2),); +} + +function drawCoin(ctx, color, width, ball, bg) { + const key = "coin with halo" + "_" + color + "_" + width + '_' + bg; + + const size = width * 3; + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = size; + can.height = size; + + const canctx = can.getContext("2d"); + + // coin + canctx.beginPath(); + canctx.arc(size / 2, size / 2, width / 2, 0, 2 * Math.PI); + canctx.fillStyle = color; + canctx.fill(); + + canctx.strokeStyle = bg; + canctx.stroke(); + + cachedGraphics[key] = can; + } + ctx.drawImage(cachedGraphics[key], Math.round(ball.x - size / 2), Math.round(ball.y - size / 2),); +} + +function drawFuzzyBall(ctx, color, width, x, y) { + const key = "fuzzy-circle" + color + "_" + width; + + const size = Math.round(width * 3); + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = size; + can.height = size; + + const canctx = can.getContext("2d"); + const gradient = canctx.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2,); + gradient.addColorStop(0, color); + gradient.addColorStop(1, "transparent"); + canctx.fillStyle = gradient; + canctx.fillRect(0, 0, size, size); + cachedGraphics[key] = can; + } + ctx.drawImage(cachedGraphics[key], Math.round(x - size / 2), Math.round(y - size / 2),); +} + +function drawBrick(ctx, color, x, y, squared) { + const tlx = Math.ceil(x - brickWidth / 2); + const tly = Math.ceil(y - brickWidth / 2); + const brx = Math.ceil(x + brickWidth / 2) - 1; + const bry = Math.ceil(y + brickWidth / 2) - 1; + + const width = brx - tlx, height = bry - tly; + const key = "brick" + color + "_" + width + "_" + height + '_' + squared + // "_" + + // isSettingOn("rounded-bricks"); + + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = width; + can.height = height; + const canctx = can.getContext("2d"); + + + if (squared) { + + canctx.fillStyle = color; + canctx.fillRect(0, 0, width, height); + } else { + + const bord = Math.floor(brickWidth / 6); + canctx.strokeStyle = color; + canctx.lineJoin = "round"; + canctx.lineWidth = bord * 1.5; + canctx.strokeRect(bord, bord, width - bord * 2, height - bord * 2); + + canctx.fillStyle = color; + canctx.fillRect(bord, bord, width - bord * 2, height - bord * 2); + } + + cachedGraphics[key] = can; + } + ctx.drawImage(cachedGraphics[key], tlx, tly, width, height); + // It's not easy to have a 1px gap between bricks without antialiasing +} + +function drawRedGradientSquare(ctx, x, y, width, height, redX, redY, blackX, blackY, color = "red",) { + const key = "gradient" + width + "_" + height + "_" + redX + "_" + redY + "_" + blackX + "_" + blackY + "_" + color; + + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = width; + can.height = height; + const canctx = can.getContext("2d"); + + const gradient = canctx.createLinearGradient(redX, redY, blackX, blackY); + gradient.addColorStop(0, color); + gradient.addColorStop(1, "black"); + canctx.fillStyle = gradient; + canctx.fillRect(0, 0, width, height); + cachedGraphics[key] = can; + } + ctx.drawImage(cachedGraphics[key], x, y, width, height); +} + + +function drawIMG(ctx, img, size, x, y) { + const key = "svg" + img + "_" + size + '_' + img.complete; + + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = size; + can.height = size; + + const canctx = can.getContext("2d"); + + const ratio = size / Math.max(img.width, img.height); + const w = img.width * ratio; + const h = img.height * ratio; + canctx.drawImage(img, (size - w) / 2, (size - h) / 2, w, h); + + cachedGraphics[key] = can; + } + ctx.drawImage(cachedGraphics[key], Math.round(x - size / 2), Math.round(y - size / 2),); +} + +function drawText(ctx, text, color, fontSize, {x, y}) { + const key = "text" + text + "_" + color + "_" + fontSize; + + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = fontSize * text.length; + can.height = fontSize; + const canctx = can.getContext("2d"); + canctx.fillStyle = color; + canctx.textAlign = "center"; + canctx.textBaseline = "middle"; + canctx.font = fontSize + "px monospace"; + + canctx.fillText(text, can.width / 2, can.height / 2, can.width); + + cachedGraphics[key] = can; + } + ctx.drawImage(cachedGraphics[key], Math.round(x - cachedGraphics[key].width / 2), Math.round(y - cachedGraphics[key].height / 2),); +} + +function pixelsToPan(pan) { + return (pan - offsetX) / gameZoneWidth; +} + +let lastComboPlayed = NaN, shepard = 6; + +function playShepard(delta, pan, volume) { + const shepardMax = 11, factor = 1.05945594920268, baseNote = 392; + shepard += delta; + if (shepard > shepardMax) shepard = 0; + if (shepard < 0) shepard = shepardMax; + + const play = (note) => { + const freq = baseNote * Math.pow(factor, note); + const diff = Math.abs(note - shepardMax * 0.5); + const maxDistanceToIdeal = 1.5 * shepardMax; + const vol = Math.max(0, volume * (1 - diff / maxDistanceToIdeal)); + createSingleBounceSound(freq, pan, vol); + return freq.toFixed(2) + " at " + Math.floor(vol * 100) + "% diff " + diff; + }; + + play(1 + shepardMax + shepard); + play(shepard); + play(-1 - shepardMax + shepard); +} + +const sounds = { + wallBeep: (pan) => { + if (!isSettingOn("sound")) return; + createSingleBounceSound(800, pixelsToPan(pan)); + }, + + comboIncreaseMaybe: (x, volume) => { + if (!isSettingOn("sound")) return; + let delta = 0; + if (!isNaN(lastComboPlayed)) { + if (lastComboPlayed < combo) delta = 1; + if (lastComboPlayed > combo) delta = -1; + } + playShepard(delta, pixelsToPan(x), volume); + lastComboPlayed = combo; + }, + + comboDecrease() { + if (!isSettingOn("sound")) return; + playShepard(-1, 0.5, 0.5); + }, coinBounce: (pan, volume) => { + if (!isSettingOn("sound")) return; + createSingleBounceSound(1200, pixelsToPan(pan), volume); + }, explode: (pan) => { + if (!isSettingOn("sound")) return; + createExplosionSound(pixelsToPan(pan)); + }, revive: () => { + if (!isSettingOn("sound")) return; + createRevivalSound(500); + }, coinCatch(pan) { + if (!isSettingOn("sound")) return; + createSingleBounceSound(440, pixelsToPan(pan), .8) + } +}; + +// How to play the code on the leftconst context = new window.AudioContext(); +let audioContext, delayNode; + +function getAudioContext() { + if (!audioContext) { + audioContext = new (window.AudioContext || window.webkitAudioContext)(); + } + return audioContext; +} + +function createSingleBounceSound(baseFreq = 800, pan = 0.5, volume = 1, duration = 0.1,) { + const context = getAudioContext(); + // Frequency for the metal "ping" + const baseFrequency = baseFreq; // Hz + + // Create an oscillator for the impact sound + const oscillator = context.createOscillator(); + oscillator.type = "sine"; + oscillator.frequency.setValueAtTime(baseFrequency, context.currentTime); + + // Create a gain node to control the volume + const gainNode = context.createGain(); + oscillator.connect(gainNode); + + // Create a stereo panner node for left-right panning + const panner = context.createStereoPanner(); + panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime); + gainNode.connect(panner); + panner.connect(context.destination); + + // Set up the gain envelope to simulate the impact and quick decay + gainNode.gain.setValueAtTime(0.8 * volume, context.currentTime); // Initial impact + gainNode.gain.exponentialRampToValueAtTime(0.001, context.currentTime + duration,); // Quick decay + + // Start the oscillator + oscillator.start(context.currentTime); + + // Stop the oscillator after the decay + oscillator.stop(context.currentTime + duration); +} + +function createRevivalSound(baseFreq = 440) { + const context = getAudioContext(); + + // Create multiple oscillators for a richer sound + const oscillators = [context.createOscillator(), context.createOscillator(), context.createOscillator(),]; + + // Set the type and frequency for each oscillator + oscillators.forEach((osc, index) => { + osc.type = "sine"; + osc.frequency.setValueAtTime(baseFreq + index * 2, context.currentTime); // Slight detuning + }); + + // Create a gain node to control the volume + const gainNode = context.createGain(); + + // Connect all oscillators to the gain node + oscillators.forEach((osc) => osc.connect(gainNode)); + + // Create a stereo panner node for left-right panning + const panner = context.createStereoPanner(); + panner.pan.setValueAtTime(0, context.currentTime); // Center panning + gainNode.connect(panner); + panner.connect(context.destination); + + // Set up the gain envelope to simulate a smooth attack and decay + gainNode.gain.setValueAtTime(0, context.currentTime); // Start at zero + gainNode.gain.linearRampToValueAtTime(0.8, context.currentTime + 0.5); // Ramp up to full volume + gainNode.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 2); // Slow decay + + // Start all oscillators + oscillators.forEach((osc) => osc.start(context.currentTime)); + + // Stop all oscillators after the decay + oscillators.forEach((osc) => osc.stop(context.currentTime + 2)); +} + +let noiseBuffer; + +function createExplosionSound(pan = 0.5) { + const context = getAudioContext(); + // Create an audio buffer + if (!noiseBuffer) { + const bufferSize = context.sampleRate * 2; // 2 seconds + noiseBuffer = context.createBuffer(1, bufferSize, context.sampleRate); + const output = noiseBuffer.getChannelData(0); + + // Fill the buffer with random noise + for (let i = 0; i < bufferSize; i++) { + output[i] = Math.random() * 2 - 1; + } + } + + // Create a noise source + const noiseSource = context.createBufferSource(); + noiseSource.buffer = noiseBuffer; + + // Create a gain node to control the volume + const gainNode = context.createGain(); + noiseSource.connect(gainNode); + + // Create a filter to shape the explosion sound + const filter = context.createBiquadFilter(); + filter.type = "lowpass"; + filter.frequency.setValueAtTime(1000, context.currentTime); // Set the initial frequency + gainNode.connect(filter); + + // Create a stereo panner node for left-right panning + const panner = context.createStereoPanner(); + panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime); // pan 0 to 1 maps to -1 to 1 + + // Connect filter to panner and then to the destination (speakers) + filter.connect(panner); + panner.connect(context.destination); + + // Ramp down the gain to simulate the explosion's fade-out + gainNode.gain.setValueAtTime(1, context.currentTime); + gainNode.gain.exponentialRampToValueAtTime(0.01, context.currentTime + 1); + + // Lower the filter frequency over time to create the "explosive" effect + filter.frequency.exponentialRampToValueAtTime(60, context.currentTime + 1); + + // Start the noise source + noiseSource.start(context.currentTime); + + // Stop the noise source after the sound has played + noiseSource.stop(context.currentTime + 1); +} + +let levelTime = 0; + +setInterval(() => { + document.body.className = (running ? " running " : " paused ") + (currentLevelInfo().black_puck ? ' black_puck ' : ' '); +}, 100); + +window.addEventListener("visibilitychange", () => { + if (document.hidden) { + running = false; + needsRender = true + } +}); + +const scoreDisplay = document.getElementById("score"); + + +function asyncAlert({ + title, + text, + actions = [{text: "OK", value: "ok", help: ""}], + allowClose = true, + textAfterButtons = '' + }) { + return new Promise((resolve) => { + const popupWrap = document.createElement("div"); + document.body.appendChild(popupWrap); + popupWrap.className = "popup"; + + function closeWithResult(value) { + resolve(value); + // Doing this async lets the menu scroll persist if it's shown a second time + setTimeout(() => { + document.body.removeChild(popupWrap); + }); + } + + if (allowClose) { + const closeButton = document.createElement("button"); + closeButton.title = "close" + closeButton.className = "close-modale" + closeButton.addEventListener('click', (e) => { + e.preventDefault() + closeWithResult(null) + }) + popupWrap.appendChild(closeButton) + } + + const popup = document.createElement("div"); + + if (title) { + const p = document.createElement("h2"); + p.innerHTML = title; + popup.appendChild(p); + } + + if (text) { + const p = document.createElement("div"); + p.innerHTML = text; + popup.appendChild(p); + } + + actions.filter(i => i).forEach(({text, value, help, checked = 0, max = 0, disabled}) => { + const button = document.createElement("button"); + let checkMark = '' + if (max) { + checkMark += '' + for (let i = 0; i < max; i++) { + checkMark += ''; + } + checkMark += '' + } + button.innerHTML = `${checkMark} +
+ ${text} + ${help || ''} +
`; + + + if (disabled) { + button.setAttribute("disabled", "disabled"); + } else { + button.addEventListener("click", (e) => { + e.preventDefault(); + closeWithResult(value) + }); + } + popup.appendChild(button); + }); + + if (textAfterButtons) { + const p = document.createElement("div"); + p.className = 'textAfterButtons' + p.innerHTML = textAfterButtons; + popup.appendChild(p); + } + + + popupWrap.appendChild(popup); + }); +} + +// Settings +let cachedSettings = {}; + +function isSettingOn(key) { + if (typeof cachedSettings[key] == "undefined") { + try { + cachedSettings[key] = JSON.parse(localStorage.getItem("breakout-settings-enable-" + key),); + } catch (e) { + console.warn(e); + } + } + return cachedSettings[key] ?? options[key]?.default ?? false; +} + +function toggleSetting(key) { + cachedSettings[key] = !isSettingOn(key); + try { + const lskey = "breakout-settings-enable-" + key; + localStorage.setItem(lskey, JSON.stringify(cachedSettings[key])); + } catch (e) { + console.warn(e); + } + if (options[key].afterChange) options[key].afterChange(); +} + +scoreDisplay.addEventListener("click", async (e) => { + e.preventDefault(); + const cb = await asyncAlert({ + title: `You scored ${score} points so far`, text: ` +

You are playing level ${currentLevel + 1} out of ${max_levels()}.

+ ${scoreStory.map((t) => "

" + t + "

").join("")} +

You high score is ${highScore}.

+ `, allowClose: true, actions: [{ + text: "New run", help: "Start a brand new run.", value: () => { + restart(); + return true; + }, + }], + }); + if (cb) { + await cb() + } +}); + +document.getElementById("menu").addEventListener("click", (e) => { + e.preventDefault(); + openSettingsPanel(); +}); + +const options = { + sound: { + default: true, name: `Game sounds`, help: `Can slow down some phones.`, + }, "mobile-mode": { + default: window.innerHeight > window.innerWidth, + name: `Mobile mode`, + help: `Leaves space for your thumb.`, + afterChange() { + fitSize(); + }, + }, + basic: { + default: false, name: `Fast mode`, help: `Simpler graphics for older devices.`, + }, + "easy": { + default: false, name: `Easy mode`, help: `Slower ball as starting perk.`, restart: true, + }, "color_blind": { + default: false, name: `Color blind mode`, help: `Removes mechanics about colors.`, restart: true, + }, +}; + +async function openSettingsPanel() { + running = false; + needsRender = true + + const optionsList = []; + for (const key in options) { + optionsList.push({ + checked: isSettingOn(key) ? 1 : 0, max: 1, text: options[key].name, help: options[key].help, value: () => { + toggleSetting(key) + if (options[key].restart) { + restart() + } else { + openSettingsPanel(); + } + }, + }); + } + + const cb = await asyncAlert({ + title: "Breakout 71", text: ` + `, allowClose: true, actions: [ + ...optionsList, + + (window.screenTop || window.screenY) && { + text: "Fullscreen", + help: "Might not work on some machines", + value() { + const docel = document.documentElement + if (docel.requestFullscreen) { + docel.requestFullscreen(); + } else if (docel.webkitRequestFullscreen) { + docel.webkitRequestFullscreen(); + } + } + }, + { + text: 'Reset Game', + help: "Erase high score and statistics", + async value() { + if (await asyncAlert({ + title: 'Reset', + actions: [ + { + text: 'Yes', + value: true + }, + { + text: 'No', + value: false + } + ], + allowClose: true, + })) { + localStorage.clear() + window.location.reload() + } + + } + + } + ], + textAfterButtons: ` +

Made in France by Renan LE CARO
+ privacy policy - + Google Play - + itch.io + +

+ ` + }) + if (cb) { + cb() + } +} + +fitSize() +restart() +tick(); \ No newline at end of file diff --git a/app/src/main/assets/icon.svg b/app/src/main/assets/icon.svg new file mode 100644 index 0000000..f411508 --- /dev/null +++ b/app/src/main/assets/icon.svg @@ -0,0 +1,146 @@ + + + + diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html new file mode 100644 index 0000000..c1b6dfa --- /dev/null +++ b/app/src/main/assets/index.html @@ -0,0 +1,21 @@ + + + + + + + Breakout 𝟕𝟏 + + + + + + + + + + + diff --git a/app/src/main/assets/levels.js b/app/src/main/assets/levels.js new file mode 100644 index 0000000..f4c46c9 --- /dev/null +++ b/app/src/main/assets/levels.js @@ -0,0 +1,6458 @@ +let allLevels=[ + { + "name": "71 mini", + "size": 5, + "bricks": [ + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "#e32119", + "", + "", + "#ffd300", + "#e32119", + "#e32119", + "", + "", + "#ffd300", + "", + "#e32119", + "", + "", + "", + "#e32119", + "#e32119", + "#e32119" + ], + "svg": "", + "color": "" + }, + { + "name": "Butterfly ?", + "bricks": [ + "#59EEA3", + "", + null, + "black", + "black", + null, + null, + "#59EEA3", + "#59EEA3", + "#59EEA3", + null, + "#F29E4A", + "#F0F04C", + null, + "#59EEA3", + "#59EEA3", + "#A1F051", + "#59EEA3", + "#59EEA3", + "#F0F04C", + "#F29E4A", + "#59EEA3", + "#59EEA3", + "#A1F051", + "#A1F051", + "black", + "#59EEA3", + "#F29E4A", + "#F0F04C", + "#59EEA3", + "black", + "#A1F051", + "#A1F051", + "black", + "#53EE53", + "#F0F04C", + "#F29E4A", + "#53EE53", + "black", + "#A1F051", + "#A1F051", + "#53EE53", + "#53EE53", + "#F29E4A", + "#F0F04C", + "#53EE53", + "#53EE53", + "#53EE53", + "#53EE53", + "#53EE53", + "", + "#F0F04C", + "#F29E4A", + "", + "#53EE53", + "#53EE53", + "#53EE53", + null, + null, + null, + "", + "", + "", + "#53EE53" + ], + "size": 8, + "svg": "", + "focus": false + }, + { + "name": "Castle", + "size": 7, + "bricks": [ + "#E67070", + "", + "#E67070", + "", + "#E67070", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "black", + "black", + "black", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "black", + "black", + "black", + "#E67070", + "#E67070", + "#5DA3EA", + "#5DA3EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#6262EA", + "#5DA3EA", + "#6262EA", + "#5DA3EA", + "#6262EA", + "#5DA3EA" + ], + "svg": "", + "focus": false + }, + { + "name": "Yin yang", + "size": 8, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + "", + "white", + "white", + "white", + "white", + "white", + "#333", + "", + "", + "white", + "black", + "white", + "white", + "#333", + "#333", + "", + "", + "white", + "black", + "white", + "#333", + "#333", + "#333", + "", + "", + "white", + "white", + "white", + "#333", + "white", + "#333", + "", + "", + "white", + "white", + "#333", + "#333", + "white", + "#333", + "", + "", + "white", + "#333", + "#333", + "#333", + "#333", + "#333", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Creeper", + "size": 10, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#59EEA3", + "#59EEA3", + "#A1F051", + "#A1F051", + "#59EEA3", + "#59EEA3", + "#A1F051", + "#A1F051", + "", + "", + "#59EEA3", + "#A1F051", + "#59EEA3", + "#59EEA3", + "#A1F051", + "#59EEA3", + "#A1F051", + "#59EEA3", + "", + "", + "#A1F051", + "black", + "black", + "#59EEA3", + "#59EEA3", + "black", + "black", + "#59EEA3", + "", + "", + "#59EEA3", + "black", + "black", + "#A1F051", + "#59EEA3", + "black", + "black", + "#59EEA3", + "", + "", + "#A1F051", + "#59EEA3", + "#59EEA3", + "black", + "black", + "#A1F051", + "#A1F051", + "#59EEA3", + "", + "", + "#59EEA3", + "#59EEA3", + "black", + "black", + "black", + "black", + "#59EEA3", + "#A1F051", + "", + "", + "#A1F051", + "#A1F051", + "black", + "black", + "black", + "black", + "#59EEA3", + "#A1F051", + "", + "", + "#59EEA3", + "#A1F051", + "black", + "#59EEA3", + "#59EEA3", + "black", + "#A1F051", + "#59EEA3", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false + }, + { + "name": "Blocky Stairs", + "size": 8, + "bricks": [ + "#5DA3EA", + "#5DA3EA", + null, + null, + null, + null, + null, + null, + "#5DA3EA", + "#5DA3EA", + null, + null, + null, + null, + null, + null, + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + null, + null, + null, + null, + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + null, + null, + null, + null, + "#A664E8", + "#A664E8", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + null, + null, + "#A664E8", + "#A664E8", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + null, + null, + "#E869E8", + "#E869E8", + "#A664E8", + "#A664E8", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + "#E869E8", + "#E869E8", + "#A664E8", + "#A664E8", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA" + ], + "svg": "", + "focus": false + }, + { + "name": "Dots", + "size": 9, + "bricks": [ + "#6262EA", + null, + "#5DA3EA", + null, + "#5BECEC", + null, + "#59EEA3", + null, + "#53EE53", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#6262EA", + null, + "#5DA3EA", + null, + "#5BECEC", + null, + "#59EEA3", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#A664E8", + null, + "#6262EA", + null, + "#5DA3EA", + null, + "#5BECEC", + null, + "#59EEA3", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#A664E8", + null, + "#6262EA", + null, + "#5DA3EA", + null, + "#5BECEC", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#E869E8", + null, + "#A664E8", + null, + "#6262EA", + null, + "#5DA3EA", + null, + "#5BECEC" + ], + "svg": "", + "focus": false + }, + { + "name": "Lines", + "size": 9, + "bricks": [ + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Heart", + "size": 15, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "", + "", + null, + "", + "", + "#ab0c0c", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + "", + "#ab0c0c", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + "", + null, + "", + "#ab0c0c", + "#F44848", + "white", + "white", + "#F44848", + "#F44848", + "#ab0c0c", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + null, + "", + "#ab0c0c", + "#F44848", + "white", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + null, + "", + "#ab0c0c", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + null, + "", + "#ab0c0c", + "#F44848", + "white", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + null, + "", + "", + "#ab0c0c", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + "", + null, + "", + "", + "", + "#ab0c0c", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + "", + "", + null, + "", + "", + "", + "", + "#ab0c0c", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + "", + "", + "", + null, + "", + "", + "", + "", + "", + "#ab0c0c", + "#F44848", + "#F44848", + "#F44848", + "#ab0c0c", + "", + "", + "", + "", + null, + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#F44848", + "#ab0c0c", + "", + "", + "", + "", + "", + null, + "", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + null, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Swiss", + "size": 7, + "bricks": [ + "", + null, + null, + null, + null, + null, + null, + null, + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + null, + null, + "#ab0c0c", + "#ab0c0c", + "white", + "#ab0c0c", + "#ab0c0c", + null, + null, + "#ab0c0c", + "white", + "white", + "white", + "#ab0c0c", + null, + null, + "#ab0c0c", + "#ab0c0c", + "white", + "#ab0c0c", + "#ab0c0c", + null, + null, + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c" + ], + "svg": "", + "color": "", + "focus": false, + "squared": true + }, + { + "name": "Germany", + "size": 6, + "bricks": [ + "", + null, + null, + null, + null, + null, + null, + "#231f20", + "#231f20", + "#231f20", + "#231f20", + null, + null, + "#e32119", + "#e32119", + "#e32119", + "#e32119", + null, + null, + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300" + ], + "svg": "", + "squared": false, + "color": "#ffffff", + "focus": false, + "black_puck": true + }, + { + "name": "France", + "size": 8, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + null, + "", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "#e32119", + "#e32119", + null, + "", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "#e32119", + "#e32119", + null, + "", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "#e32119", + "#e32119", + null, + "", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "#e32119", + "#e32119", + null, + "", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "#e32119", + "#e32119", + null, + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Smiley", + "size": 8, + "bricks": [ + "", + null, + null, + null, + null, + null, + null, + null, + null, + "#ffd300", + "#ffd300", + null, + null, + "#ffd300", + "#ffd300", + null, + null, + "#ffd300", + "black", + null, + null, + "black", + "#ffd300", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#ffd300", + null, + null, + null, + null, + "#ffd300", + null, + null, + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + null, + null, + null, + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300" + ], + "svg": "", + "focus": false + }, + { + "name": "Labirynth", + "size": 11, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#F44848", + "", + "#F44848", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "#F44848", + "", + "", + null, + "", + "", + "", + "", + "", + "#5DA3EA", + "", + "", + "", + "#F44848", + null, + "", + "#F44848", + "#5DA3EA", + "#5DA3EA", + "", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + null, + "", + "", + "", + "#5DA3EA", + "", + "", + "", + "", + "", + "#F44848", + null, + "", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "#F44848", + "", + "#F44848", + "", + "", + null, + "", + "#5DA3EA", + "", + "", + "", + "#5DA3EA", + "", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#F44848", + "", + "#5DA3EA", + "", + "", + "", + "", + "#5DA3EA", + "#F44848", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "", + "", + "", + null, + null, + null, + null, + "#F44848", + "", + "#F44848" + ], + "svg": "", + "focus": false + }, + { + "name": "Temple", + "size": 11, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "white", + "white", + "white", + "", + "", + "", + "", + "", + "", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "", + "", + "", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "", + "", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "", + "", + "", + "#6262EA", + "", + "#6262EA", + "", + "#6262EA", + "", + "#6262EA", + "", + "", + "", + "", + "#A664E8", + "", + "#A664E8", + "", + "#A664E8", + "", + "#A664E8", + "", + "", + "", + "", + "#E869E8", + "", + "#E869E8", + "", + "#E869E8", + "", + "#E869E8", + "", + "", + "", + "", + "#E66BA8", + "", + "#E66BA8", + "", + "#E66BA8", + "", + "#E66BA8", + "", + "", + "", + "", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "", + "", + "", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Pacman", + "size": 12, + "bricks": [ + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + null, + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + null, + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "black", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + null, + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + null, + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + null, + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + null, + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "#F44848", + "", + "#F44848", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + null, + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + null, + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + null, + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + null, + null, + null, + null, + null, + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Ship", + "size": 11, + "bricks": [ + "", + "", + "", + "", + "#E67070", + "white", + "white", + "", + "", + "", + null, + "", + "", + "", + "", + "#E67070", + "white", + "white", + "white", + "", + "", + null, + "", + "", + "", + "", + "#E67070", + "white", + "white", + "white", + "", + "", + null, + "", + "", + "", + "", + "#E67070", + "", + "", + "", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "", + "#F29E4A", + "black", + "#F29E4A", + "black", + "#F29E4A", + "black", + "#F29E4A", + "black", + "#F29E4A", + "#F29E4A", + "", + "", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + null, + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#333", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#333", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#333", + "#333", + "#6262EA", + "#6262EA", + "#6262EA", + "#333", + "#333", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA" + ], + "svg": "", + "focus": false + }, + { + "name": "We come in peace", + "size": 13, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + "", + "", + "", + "#5BECEC", + "", + "", + "", + "", + "", + "#5BECEC", + "", + "", + null, + "", + "", + "", + "", + "#5BECEC", + "", + "", + "", + "#5BECEC", + "", + "", + "", + null, + "", + "", + "", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "", + "", + null, + "", + "", + "#5BECEC", + "#5BECEC", + "black", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "black", + "#5BECEC", + "#5BECEC", + "", + null, + "", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + null, + "", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + null, + "", + "#5BECEC", + "", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "", + "#5BECEC", + null, + "", + "#5BECEC", + "", + "#5BECEC", + "", + "", + "", + "", + "", + "#5BECEC", + "", + "#5BECEC", + null, + "", + "", + "", + "", + "#5BECEC", + "#5BECEC", + "", + "#5BECEC", + "#5BECEC", + "", + "", + "", + null, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Space mushroom", + "size": 10, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + "", + "", + "", + "", + "white", + "white", + "", + "", + "", + null, + "", + "", + "", + "white", + "white", + "white", + "white", + "", + "", + null, + "", + "", + "white", + "white", + "white", + "white", + "white", + "white", + "", + null, + "", + "white", + "white", + "black", + "white", + "white", + "black", + "white", + "white", + null, + "", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + null, + "", + "", + "", + "white", + "", + "", + "white", + "", + "", + null, + "", + "", + "white", + "", + "white", + "white", + "", + "white", + "", + null, + "", + "white", + "", + "white", + "", + "", + "white", + "", + "white" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Sonic", + "size": 16, + "bricks": [ + "", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "", + "", + "#231f20", + "#231f20", + "", + null, + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#231f20", + "#231f20", + "#5DA3EA", + "#231f20", + "", + null, + "", + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#231f20", + "#F29E4A", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#231f20", + "", + null, + "", + "", + "#231f20", + "#5DA3EA", + "#231f20", + "#F29E4A", + "#F29E4A", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#231f20", + "#231f20", + null, + "", + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#231f20", + "#F29E4A", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + null, + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "white", + "white", + "#5DA3EA", + "white", + "white", + null, + "", + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "white", + "#A1F051", + "#5DA3EA", + "white", + "#A1F051", + null, + "", + "", + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "white", + "#A1F051", + "#5DA3EA", + "white", + "#231f20", + null, + "", + "", + "", + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "#A1F051", + "white", + "#231f20", + "#231f20", + "#231f20", + "", + "", + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "white", + "white", + "white", + "#F29E4A", + "#F29E4A", + "#231f20", + null, + "", + "#231f20", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#231f20", + "", + null, + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#F29E4A", + "#231f20", + "", + "", + null, + "", + "", + "", + "", + "", + "", + "", + "", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "", + "", + "", + null, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "#57e389", + "focus": false + }, + { + "name": "Small heart", + "size": 15, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + "#ab0c0c", + "#e32119", + "white", + "white", + "#e32119", + "#ab0c0c", + "", + "#ab0c0c", + "white", + "white", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "", + "#ab0c0c", + "white", + "white", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "white", + "white", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "", + "#ab0c0c", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "", + "#ab0c0c", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "", + "", + "#ab0c0c", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "", + "", + "", + "", + "#ab0c0c", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#e32119", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Eye", + "size": 9, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#231f20", + "#231f20", + "#231f20", + "", + "", + "", + "", + "", + "#231f20", + "white", + "white", + "white", + "#231f20", + "", + "", + "", + "#231f20", + "white", + "#6262EA", + "#6262EA", + "#6262EA", + "white", + "#231f20", + "", + "#231f20", + "white", + "white", + "#6262EA", + "black", + "#6262EA", + "white", + "white", + "#231f20", + "", + "#231f20", + "white", + "#6262EA", + "#6262EA", + "#6262EA", + "white", + "#231f20", + "", + "", + "", + "#231f20", + "white", + "white", + "white", + "#231f20", + "", + "", + "", + "", + "", + "#231f20", + "#231f20", + "#231f20", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "#99c1f1", + "focus": false + }, + { + "name": "enderman", + "size": 10, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "", + "#231f20", + "#333", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#333", + "#231f20", + "", + "", + "#231f20", + "#333", + "#333", + "#231f20", + "#231f20", + "#333", + "#333", + "#231f20", + "", + "", + "#333", + "#231f20", + "#333", + "#333", + "#333", + "#333", + "#231f20", + "#333", + "", + "", + "#231f20", + "#231f20", + "#333", + "#333", + "#333", + "#333", + "#231f20", + "#231f20", + "", + "", + "#E66BA8", + "#E869E8", + "#E66BA8", + "#333", + "#333", + "#E66BA8", + "#E869E8", + "#E66BA8", + "", + "", + "#333", + "#231f20", + "#231f20", + "#333", + "#333", + "#231f20", + "#231f20", + "#333", + "", + "", + "#231f20", + "#333", + "#333", + "#231f20", + "#231f20", + "#333", + "#333", + "#231f20", + "", + "", + "#333", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#333", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "#9141ac", + "focus": false, + "squared": true + }, + { + "name": "Mushroom", + "size": 16, + "bricks": [ + "", + "", + "", + "", + "", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "", + "", + "", + "", + "", + "", + "", + "", + "#231f20", + "#231f20", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "white", + "white", + "#231f20", + "#231f20", + "", + "", + "", + "", + "", + "#231f20", + "white", + "white", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "#231f20", + "", + "", + "", + "#231f20", + "white", + "white", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "#231f20", + "", + "", + "#231f20", + "white", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "#e32119", + "#e32119", + "white", + "white", + "white", + "#231f20", + "", + "#231f20", + "#e32119", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "white", + "white", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#231f20", + "#231f20", + "#e32119", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "white", + "white", + "#e32119", + "#e32119", + "white", + "white", + "#e32119", + "#231f20", + "#231f20", + "white", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "white", + "white", + "#e32119", + "white", + "white", + "white", + "white", + "#231f20", + "#231f20", + "white", + "white", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "#231f20", + "#231f20", + "white", + "white", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "white", + "white", + "#e32119", + "#231f20", + "#231f20", + "white", + "#e32119", + "#e32119", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#e32119", + "#e32119", + "#e32119", + "#231f20", + "", + "#231f20", + "#231f20", + "#231f20", + "white", + "white", + "#231f20", + "white", + "white", + "#231f20", + "white", + "white", + "#231f20", + "#231f20", + "#231f20", + "", + "", + "", + "#231f20", + "white", + "white", + "white", + "#231f20", + "white", + "white", + "#231f20", + "white", + "white", + "white", + "#231f20", + "", + "", + "", + "", + "#231f20", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "#231f20", + "", + "", + "", + "", + "", + "#231f20", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "#231f20", + "", + "", + "", + "", + "", + "", + "", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "#231f20", + "", + "", + "", + "" + ], + "svg": "", + "color": "#62a0ea", + "focus": false, + "black_puck": false + }, + { + "name": "tulip", + "size": 11, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "", + "", + "", + "#ab0c0c", + "", + "#ab0c0c", + "", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "#618227", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "#618227", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Gold chain", + "size": 7, + "bricks": [ + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "black", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "black", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "black", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Marion", + "size": 9, + "bricks": [ + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + "", + "", + "#ab0c0c", + "#ab0c0c", + "", + "#e32119", + "#ab0c0c", + "", + "", + "", + "#ab0c0c", + "#e32119", + null, + "", + "#e32119", + "#e32119", + "#ab0c0c", + "", + "#ab0c0c", + "#e32119", + "#e32119", + null, + "", + "#e32119", + "#e32119", + "#e32119", + "#ab0c0c", + "#e32119", + "#e32119", + "#e32119", + null, + "", + "#e32119", + "#e32119", + "", + "#e32119", + "", + "#e32119", + "#e32119", + null, + "", + "#e32119", + "#e32119", + "", + "", + "", + "#e32119", + "#e32119", + null, + "", + "#e32119", + "#e32119", + "", + "", + "", + "#e32119", + "#e32119", + null, + "", + "#e32119", + "#e32119", + "", + "", + "", + "#e32119", + "#e32119", + null, + "#ab0c0c", + "#e32119", + "#e32119", + null, + null, + null, + "#e32119", + "#e32119", + "#ab0c0c" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Renan", + "size": 9, + "bricks": [ + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "#ffd300" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "pairs", + "size": 8, + "bricks": [ + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#5DA3EA", + null, + "#5DA3EA", + null, + "#5DA3EA", + null, + "#5DA3EA", + null, + "#5DA3EA", + null, + "#5DA3EA", + null, + "#5DA3EA", + null, + "#5DA3EA", + null, + null, + null, + null, + null, + null, + null, + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA", + null, + "#6262EA" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "cups", + "size": 11, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + "#e32119", + "black", + "#e32119", + "", + "#e32119", + "black", + "#e32119", + "", + "#e32119", + "black", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + "#e32119", + "", + "#e32119", + "black", + "#e32119", + "", + "#e32119", + "black", + "#e32119", + "", + "#e32119", + "#e32119", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "#e32119", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + null, + "#e32119", + "black", + "#e32119", + "", + "#e32119", + "black", + "#e32119", + "", + "#e32119", + "black", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "cactus", + "size": 10, + "bricks": [ + null, + "", + "", + "", + "#A1F051", + "", + "", + "", + "", + "", + "", + "", + "#A1F051", + "", + "#A1F051", + "#618227", + "", + "", + "", + "", + "", + "", + "#A1F051", + "", + "#A1F051", + "#618227", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "", + "#A1F051", + "", + "", + "", + "", + "", + "", + "#A1F051", + "#618227", + "#A1F051", + "#618227", + "", + "", + "", + "", + "", + "", + "#A1F051", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "#A1F051", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "sun", + "size": 11, + "bricks": [ + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + null, + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + null, + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + null, + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + null, + "#ffd300", + "#ffd300", + "#ffd300", + "white", + "white", + "#ffd300", + "white", + "white", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "#ffd300", + "#ffd300", + "white", + "white", + "white", + "white", + "white", + "#ffd300", + "#ffd300", + null, + "", + "#ffd300", + "#ffd300", + "#ffd300", + "white", + "white", + "white", + "#ffd300", + "#ffd300", + "#ffd300", + null, + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + null, + null, + null, + null, + null, + "#ffd300", + "#ffd300", + "#ffd300" + ], + "svg": "", + "focus": false, + "color": "#62a0ea", + "squared": true + }, + { + "name": "Mountain", + "size": 9, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + "", + "", + "", + "", + "", + "", + "white", + "", + "", + "", + "", + "", + "", + "", + "white", + "white", + "white", + "", + "", + "", + "", + "", + "", + "#A1F051", + "#A1F051", + "white", + "white", + "", + "", + "white", + "", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "", + "#618227", + "#618227", + "#618227", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "", + "#618227", + "#618227", + "#618227", + "#618227", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#A1F051", + "#A1F051", + "#A1F051", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Dollar", + "size": 17, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#618227", + "", + "#618227", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "#f6d32d", + "focus": false + }, + { + "name": "Waves", + "size": 8, + "bricks": [ + "", + "", + "", + "#6262EA", + "#6262EA", + "#6262EA", + "", + "", + "", + "", + "#6262EA", + "#6262EA", + "#6262EA", + "", + "", + "", + "", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5BECEC", + "#5BECEC", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5BECEC", + "#5BECEC", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC", + "#5BECEC" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Box", + "size": 8, + "bricks": [ + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "", + "#ffd300", + "#ffd300", + "", + "#6262EA", + "", + "", + "#6262EA", + "", + "#ffd300", + "#ffd300", + "", + "#6262EA", + "", + "", + "#6262EA", + "", + "#ffd300", + "#ffd300", + "", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300" + ], + "svg": "", + "color": "", + "focus": false, + "squared": false + }, + { + "name": "Rose", + "size": 9, + "bricks": [ + "", + "", + "#F44848", + "#F44848", + "", + "", + "", + "", + null, + "", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "", + "", + "", + null, + "", + "#e32119", + "#F44848", + "#F44848", + "#ab0c0c", + "", + "", + "", + null, + "", + "#e32119", + "#e32119", + "#ab0c0c", + "#ab0c0c", + "", + "", + "", + null, + "", + "", + "#e32119", + "#ab0c0c", + "", + "#A1F051", + "", + "", + null, + "", + "", + "", + "#618227", + "", + "#A1F051", + "#618227", + "", + null, + "", + "", + "", + "#618227", + "#618227", + "", + "#618227", + "", + null, + "", + "", + "", + "", + "#618227", + "#618227", + "", + "", + null, + null, + null, + null, + null, + null, + "#618227" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Time", + "size": 9, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + "", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "", + "", + "", + "white", + "white", + "white", + "white", + "white", + "", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "white", + "#ffd300", + "white", + "", + "", + "", + "", + "", + "white", + "#ffd300", + "#ffd300", + "#ffd300", + "white", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "", + "focus": false, + "squared": false + }, + { + "name": "Watermelon", + "size": 8, + "bricks": [ + "", + "", + "", + "", + "", + "#F44848", + "#618227", + "", + "", + "", + "", + "", + "#F44848", + "#F44848", + "black", + "#618227", + "", + "", + "", + "#F44848", + "black", + "#F44848", + "#F44848", + "#618227", + "", + "", + "#F44848", + "#F44848", + "#F44848", + "#F44848", + "#A1F051", + "#618227", + "", + "#F44848", + "#F44848", + "black", + "#F44848", + "#F44848", + "#618227", + "", + "#F44848", + "black", + "#F44848", + "#F44848", + "#A1F051", + "#A1F051", + "#618227", + "", + "#618227", + "#618227", + "#A1F051", + "#A1F051", + "#618227", + "#618227", + "", + "", + "", + "#618227", + "#618227", + "#618227", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Worms", + "size": 13, + "bricks": [ + null, + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "", + "white", + "white", + "#E67070", + "white", + "white", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "white", + "black", + "#E67070", + "black", + "white", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "white", + "black", + "#E67070", + "black", + "white", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "white", + "white", + "#E67070", + "white", + "white", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "white", + "white", + "white", + "white", + "white", + "white", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "", + "", + "white", + "#E67070", + "#E67070", + "white", + "#E67070", + "", + "", + "#E67070", + "", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "#E67070", + "#E67070" + ], + "svg": "\n \n \n \n \n\n", + "color": "", + "focus": false, + "squared": false + }, + { + "name": "sunrise", + "size": 8, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#e32119", + "#e32119", + "", + "", + "", + "", + "", + "#e32119", + "#ffd300", + "#ffd300", + "#e32119", + "", + "", + "", + "#e32119", + "#ffd300", + "white", + "white", + "#ffd300", + "#e32119", + "", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5BECEC", + "#5BECEC", + "#5DA3EA", + "#6262EA", + "#6262EA", + "", + "#6262EA", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + "#6262EA", + "#6262EA", + "", + "", + "", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "crosses", + "size": 13, + "bricks": [ + "#6262EA", + "", + "", + "", + "#6262EA", + "", + "", + "", + "#6262EA", + "", + "", + "", + "#6262EA", + "", + "", + "#A664E8", + "", + "", + "", + "#A664E8", + "", + "", + "", + "#A664E8", + "", + "", + "", + "#A664E8", + "#A664E8", + "#A664E8", + "", + "#A664E8", + "#A664E8", + "#A664E8", + "", + "#A664E8", + "#A664E8", + "#A664E8", + "", + "", + "", + "#A664E8", + "", + "", + "", + "#A664E8", + "", + "", + "", + "#A664E8", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "#E869E8", + "#E869E8", + "", + "#E869E8", + "#E869E8", + "#E869E8", + "", + "#E869E8", + "#E869E8", + "#E869E8", + "", + "#E869E8", + "#E869E8", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "#E66BA8", + "", + "", + "", + "#E66BA8", + "", + "", + "", + "#E66BA8", + "", + "", + "", + "#E66BA8", + "#E66BA8", + "#E66BA8", + "", + "#E66BA8", + "#E66BA8", + "#E66BA8", + "", + "#E66BA8", + "#E66BA8", + "#E66BA8", + "", + "", + "", + "#E66BA8", + "", + "", + "", + "#E66BA8", + "", + "", + "", + "#E66BA8", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "#E869E8", + "#E869E8", + "", + "#E869E8", + "#E869E8", + "#E869E8", + "", + "#E869E8", + "#E869E8", + "#E869E8", + "", + "#E869E8", + "#E869E8", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8", + "", + "", + "", + "#E869E8" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "Negative space", + "size": 9, + "bricks": [ + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#6262EA", + "", + "#6262EA", + "", + "#6262EA", + "", + "#6262EA", + "", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "", + "#6262EA", + "", + "#6262EA", + "", + "#6262EA", + "", + "#6262EA", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#5DA3EA", + "", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false + }, + { + "name": "UK", + "size": 11, + "bricks": [ + "#6262EA", + "#e32119", + "#6262EA", + "#6262EA", + "white", + "#e32119", + "white", + "#6262EA", + "#6262EA", + "#e32119", + "#6262EA", + "#6262EA", + "#6262EA", + "#e32119", + "#6262EA", + "white", + "#e32119", + "white", + "#6262EA", + "#e32119", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#e32119", + "white", + "#e32119", + "white", + "#e32119", + "#6262EA", + "#6262EA", + "#6262EA", + "white", + "white", + "white", + "white", + "white", + "#e32119", + "white", + "white", + "white", + "white", + "white", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "white", + "white", + "white", + "white", + "white", + "#e32119", + "white", + "white", + "white", + "white", + "white", + "#6262EA", + "#6262EA", + "#6262EA", + "#e32119", + "white", + "#e32119", + "white", + "#e32119", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#6262EA", + "#e32119", + "#6262EA", + "white", + "#e32119", + "white", + "#6262EA", + "#e32119", + "#6262EA", + "#6262EA", + "#6262EA", + "#e32119", + "#6262EA", + "#6262EA", + "white", + "#e32119", + "white", + "#6262EA", + "#6262EA", + "#e32119", + "#6262EA", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Greece", + "size": 11, + "bricks": [ + "#5DA3EA", + "#5DA3EA", + "white", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "white", + "white", + "white", + "white", + "#5DA3EA", + "#5DA3EA", + "white", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "squared": true, + "color": "" + }, + { + "name": "Russia", + "size": 8, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "white", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "squared": true, + "color": "" + }, + { + "name": "Ukraine", + "size": 8, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "" + }, + { + "name": "Poland", + "size": 7, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "white", + "white", + "white", + "white", + "white", + "", + "", + "white", + "white", + "white", + "white", + "white", + "", + "", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "", + "", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "", + "squared": true + }, + { + "name": "yellow 71", + "size": 9, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "#ffd300", + "#ffd300", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "71 on white", + "size": 6, + "bricks": [ + "white", + "white", + "white", + "white", + "white", + "white", + "#e32119", + "#e32119", + "#e32119", + "white", + "white", + "#e32119", + "white", + "white", + "#e32119", + "white", + "#e32119", + "#e32119", + "white", + "#e32119", + "white", + "white", + "white", + "#e32119", + "white", + "#e32119", + "white", + "white", + "white", + "#e32119", + "white", + "white", + "white", + "white", + "white", + "white", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false + }, + { + "name": "blue 71", + "size": 8, + "bricks": [ + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "", + "#6262EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "#5DA3EA", + "", + "#6262EA", + "#6262EA", + null, + null, + "", + "#5DA3EA", + "#5DA3EA", + "#6262EA", + "#6262EA", + "#6262EA", + null, + null, + "#5DA3EA", + "#5DA3EA", + "", + null, + "#6262EA", + "#6262EA", + null, + "", + "#5DA3EA", + "#5DA3EA", + "", + null, + "#6262EA", + "#6262EA", + null, + "#5DA3EA", + "#5DA3EA", + "", + null, + null, + "#6262EA", + "#6262EA", + "", + "#5DA3EA", + "#5DA3EA", + "", + null, + null, + "#6262EA", + "#6262EA", + "", + "#5DA3EA", + "#5DA3EA", + null, + null, + null, + "#6262EA", + "#6262EA" + ], + "svg": "", + "color": "", + "focus": false + }, + { + "name": "seventy one", + "size": 21, + "bricks": [ + "#e32119", + "#e32119", + "", + "#ffd300", + "#ffd300", + "", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#e32119", + "", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "", + "", + "#ffd300", + "", + "#ffd300", + "#e32119", + "", + "#e32119", + "#ffd300", + "", + "#ffd300", + "", + "#e32119", + null, + "#e32119", + "#e32119", + "", + "#ffd300", + "#ffd300", + "", + "#e32119", + "#e32119", + "", + "#ffd300", + "#ffd300", + "", + "#e32119", + "", + "#e32119", + "#ffd300", + "", + "#ffd300", + "", + "#e32119", + null, + "#e32119", + "", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "", + "", + "#ffd300", + "", + "#ffd300", + "#e32119", + "", + "#e32119", + "#ffd300", + "", + "#ffd300", + "", + "#e32119", + null, + "#e32119", + "#e32119", + "", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "#e32119", + null, + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#e32119", + "", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "#ffd300", + "#ffd300", + "#e32119", + "#e32119", + "", + "#ffd300", + "", + "#ffd300", + "", + "", + "#ffd300", + "#e32119", + "#e32119", + "", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "", + "#ffd300", + "", + "#ffd300", + "#e32119", + "", + "", + "#ffd300", + "", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "", + "#ffd300", + "", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "#ffd300", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#ffd300", + "#ffd300", + null, + "#e32119", + "", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "", + "#ffd300", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false + }, + { + "name": "B71", + "size": 10, + "bricks": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#e32119", + "#e32119", + "#e32119", + "#ffd300", + "#ffd300", + "#ffd300", + "#ffd300", + "", + "#e32119", + "", + "#e32119", + "", + "", + "#e32119", + "", + "", + "#ffd300", + "#e32119", + "#e32119", + "", + "#e32119", + "", + "", + "#e32119", + "", + "", + "#ffd300", + "", + "#e32119", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "", + "#ffd300", + "", + "", + "#e32119", + "", + "#e32119", + "", + "", + "#e32119", + "", + "#ffd300", + "", + "", + "#e32119", + "", + "#e32119", + "", + "", + "#e32119", + "#ffd300", + "", + "", + "", + "#e32119", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "#ffd300", + "", + "", + "#e32119", + "#e32119", + "#e32119", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "" + }, + { + "name": "Pig", + "size": 9, + "bricks": [ + null, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "#E66BA8", + "#E66BA8", + "", + "", + "", + "#E66BA8", + "#E66BA8", + "", + "", + "#E66BA8", + "#E66BA8", + "#E66BA8", + "", + "#E66BA8", + "#E66BA8", + "#E66BA8", + "", + "", + "white", + "white", + "#E66BA8", + "#E66BA8", + "#E66BA8", + "white", + "white", + "", + "", + "white", + "black", + "#E66BA8", + "#E66BA8", + "#E66BA8", + "black", + "white", + "", + "", + "#E66BA8", + "#E66BA8", + "#E67070", + "#E67070", + "#E67070", + "#E66BA8", + "#E66BA8", + "", + "", + "#E66BA8", + "#E67070", + "black", + "#E67070", + "black", + "#E67070", + "#E66BA8", + "", + "", + "#E66BA8", + "#E66BA8", + "#E67070", + "#E67070", + "#E67070", + "#E66BA8", + "#E66BA8", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false + }, + { + "name": "Big pig", + "size": 15, + "bricks": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "#E67070", + "white", + "black", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "black", + "white", + "#E67070", + "", + "", + "", + "#E67070", + "#E67070", + "black", + "black", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "black", + "black", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "#E67070", + "black", + "#E67070", + "black", + "#E67070", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "", + "", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "", + "", + "#A1F051", + "#A1F051", + "#A1F051", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#E67070", + "#A1F051", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#E67070", + "#A1F051", + "#E67070", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#E67070", + "#E67070", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#A1F051", + "#E67070", + "#E67070", + "#A1F051", + "#A1F051", + "#A1F051", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "svg": "", + "focus": false, + "color": "", + "squared": true + } +] \ No newline at end of file diff --git a/app/src/main/assets/privacy.html b/app/src/main/assets/privacy.html new file mode 100644 index 0000000..6f1a94f --- /dev/null +++ b/app/src/main/assets/privacy.html @@ -0,0 +1,39 @@ + + + + + + + Breakout privacy policy + + + + + +

Privacy policy

+

+ Breakout 71 is published + by Renan LE CARO, a French citizen and programmer. You can contact me at + this adress : breakout71@lecaro.me +

+

+ If you access breakout.lecaro.me though a web browser, your IP address + will be logged on my server to prevent abuses. + My server is hosted by Hetzner + Online GmbH in germany. +

+

+ If you install the app through google play, no information will + be collected at all by me. +

+ + diff --git a/app/src/main/assets/style.css b/app/src/main/assets/style.css new file mode 100644 index 0000000..f7353b8 --- /dev/null +++ b/app/src/main/assets/style.css @@ -0,0 +1,227 @@ +* { + font-family: + Courier New, + Courier, + Lucida Sans Typewriter, + Lucida Typewriter, + monospace; + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + overflow: hidden; + width: 100vw; + height: 100vh; + color: white; + background-size: 120px 120px; + background-color: var(--background1); + --background1: #030c23; + --background2: #03112a; +} + +#game { + position: absolute; + top: 0; + left: 0; + height: 100vh; + width: 100vw; +} +#score, +#menu { + position: absolute; + top: 0; + z-index: 1; + padding: 10px; + appearance: none; + background: none; + border: none; + font: inherit; + color: white; + min-width: 40px; + min-height: 40px; + line-height: 20px; +} +body.black_puck #score, +body.black_puck #menu { + color:black; +} +#score:hover, +#score:focus, +#menu:hover, +#menu:focus { + background: rgba(0,0,0,0.3); + cursor: pointer; +} + +#score { + right: 0; +} +#menu { + left: 0; +} +@media screen and (orientation: portrait) { + #menu > span { + display: none; + } +} + +.popup { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.95); + z-index: 10; + display: flex; + overflow: auto; +} + +.popup > div { + margin: auto; + padding: 20px; + /*border: 1px solid white;*/ + transform-origin: center; + display: flex; + flex-direction: column; + align-items: stretch; + width: 90%; + max-width: 450px; +} + +.popup > div > * { + padding: 0; + margin: 0; +} + +.popup > div > h2, +.popup > div > p { + 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; + display: flex; + gap: 10px; + margin-top: -1px; +} + +.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; + position: absolute; + top:0; + right:0; + width: 60px; + height: 60px; + background:none; + border: none; + cursor: pointer; + background: rgba(0,0,0,0.2); + overflow: hidden; +} +.popup button.close-modale:before { + content: "+"; + position: absolute; + transform: translate(-50%, -50%) rotate(45deg) ; + font-size: 80px; + display: inline-block; +} +.popup button.close-modale:hover { + font-weight: bold; +} + +.popup > div > button[disabled] { + /*border: 1px solid #666;*/ + opacity: 0.5; +} + +.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; +} + +.popup .textAfterButtons{ + color: rgba(255, 255, 255, 0.58); +} + +.popup a[href]{ + color:inherit; +} +.popup a[href]:hover, +.popup a[href]:focus +{ + color:white; +} + +/*Unlocks progress bar*/ +.progress { + display: block; + padding: 5px 10px; + background: #1c1c2f; + color:#fff; + box-shadow: inset 3px 3px 5px rgba(0,0,0,0.5); + border-radius: 5px; + text-align: center; + position: relative; + overflow: hidden; +} +.progress >.progress_bar_part{ + display: block; + background: #4049ca; + box-shadow: inset 3px 3px 5px rgba(0,0,0,0.5); + left: 0; + position: absolute; + right: 0; + top: 0; + bottom: 0; + transform-origin: top left; + animation: grow 1s both ease-out; + z-index: 1; +} +.progress> span { + display: block; + position: relative; + z-index: 2; +} +@keyframes grow { + 0%{ + transform: scale(0,1); + } +} \ No newline at end of file diff --git a/app/src/main/java/me/lecaro/breakout/MainActivity.kt b/app/src/main/java/me/lecaro/breakout/MainActivity.kt new file mode 100644 index 0000000..d67ba02 --- /dev/null +++ b/app/src/main/java/me/lecaro/breakout/MainActivity.kt @@ -0,0 +1,33 @@ +package me.lecaro.breakout +import android.os.Bundle +import android.util.Log +import android.view.Window +import android.view.WindowManager +import android.webkit.ConsoleMessage +import android.webkit.WebChromeClient +import android.webkit.WebView +class MainActivity : android.app.Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestWindowFeature(Window.FEATURE_NO_TITLE); + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ); +// WebView.setWebContentsDebuggingEnabled(true) + val webView = WebView(this) + webView.settings.javaScriptEnabled = true + webView.settings.domStorageEnabled = true + webView.loadUrl("file:///android_asset/index.html") + webView.webChromeClient = object : WebChromeClient() { + override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { + Log.d( + "WebView", "${consoleMessage.message()} -- From line " + + "${consoleMessage.lineNumber()} of ${consoleMessage.sourceId()}" + ) + return true + } + } + setContentView(webView) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/icon.xml b/app/src/main/res/drawable/icon.xml new file mode 100644 index 0000000..fb58588 --- /dev/null +++ b/app/src/main/res/drawable/icon.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..7123164 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Breakout + \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a0985ef --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,5 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.androidApplication) apply false + alias(libs.plugins.jetbrainsKotlinAndroid) apply false +} \ No newline at end of file diff --git a/cover.png b/cover.png new file mode 100644 index 0000000..0c098b4 Binary files /dev/null and b/cover.png differ diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..78ca82d --- /dev/null +++ b/deploy.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +#Randomly shuffle the version ids +# Generate a random number between 1000 and 9999 +random_number=$(shuf -i 1000-9999 -n 1) + +# Use sed to replace the pattern with the random number +sed -i "s/\?v=[0-9]*/\?v=$random_number/g" ./app/src/main/assets/index.html + + +DOMAIN="breakout.lecaro.me" +PUBLIC_CONTENT="./app/src/main/assets/" +ssh staging "mkdir -p /opt/mup-nginx-proxy/config/html/static_sites/$DOMAIN" +rsync -avz --delete --delete-excluded --exclude="*.sh" --exclude="node_modules" --exclude="android" --exclude=".*" $PUBLIC_CONTENT staging:/opt/mup-nginx-proxy/config/html/static_sites/$DOMAIN + + +# generate zip for itch +rm -f breakout.zip +zip -j breakout.zip app/src/main/assets/* \ No newline at end of file diff --git a/editclient.css b/editclient.css new file mode 100644 index 0000000..2d8069e --- /dev/null +++ b/editclient.css @@ -0,0 +1,29 @@ + +body { + background: black; + color: white; +} + +#palette button.active { + transform: scale(1.2); +} + +.level-bricks-preview { + position: relative; +} + +#palette { + position: fixed; + top: 0; + right: 0; + width: 80px; + bottom: 0; + overflow: auto; +} + +#levels { + display: flex; + gap: 40px; + flex-wrap: wrap; + margin-right: 80px; +} \ No newline at end of file diff --git a/editclient.js b/editclient.js new file mode 100644 index 0000000..f6d961c --- /dev/null +++ b/editclient.js @@ -0,0 +1,274 @@ +let currentColor = '' + +const colorsList = [ + 'white', + 'black', + '', + '#F44848', + '#ab0c0c', + '#F29E4A', + '#F0F04C', + '#A1F051', + '#53EE53', + '#59EEA3', + '#5BECEC', + '#5DA3EA', + '#6262EA', + '#A664E8', + '#E869E8', + '#E66BA8', + '#E67070', + "#333", + '#231f20', + '#e32119', + '#ffd300', + '#e1c8b4', + '#618227' +] +const palette = document.getElementById('palette'); + +colorsList.forEach(color => { + const btn = document.createElement('button') + Object.assign(btn.style, { + background: color, + display: 'inline-block', + width: '40px', + height: '40px', + border: '1px solid black' + }) + if (color === currentColor) { + btn.className = 'active' + } + palette.appendChild(btn) + btn.addEventListener('click', (e) => { + currentColor = color + e.preventDefault() + document.querySelector('#palette button.active')?.classList.remove('active'); + btn.classList.add('active') + }) +}) + +function renderAllLevels() { + allLevels.forEach((level, levelIndex) => { + addLevelEditorToList(level, levelIndex) + + }) +} + +function addLevelEditorToList(level, levelIndex) { + const {name, bricks, size, svg,color} = level + let div = document.createElement('div') + + + div.innerHTML = ` +
+ + + + + + + + + + + + + + +
+ +
+
+ `; + document.getElementById('levels').appendChild(div) + + renderLevelBricks(levelIndex) + updateLevelBackground(levelIndex) + +} + +function updateLevelBackground(levelIndex){ + const div=document.getElementById("bricks-of-"+levelIndex) + const {svg, color}= allLevels[levelIndex] + Object.assign(div.style, svg ? + {backgroundImage:`url('data:image/svg+xml,${encodeURIComponent(svg)}')`, backgroundColor:'transparent'} : + {backgroundImage:'none', backgroundColor:color||'#111'} + ) +} + +function renderLevelBricks(levelIndex) { + const {size, bricks} = allLevels[levelIndex] + + const buttons = [] + for (let x = 0; x < size; x++) { + for (let y = 0; y < size; y++) { + const index = y * size + x + buttons.push(``) + } + } + const div = document.getElementById("bricks-of-" + levelIndex) + div.innerHTML = buttons.join('') + Object.assign(div.style, { + width: size * 40 + 'px', + height: size * 40 + 'px' + }) +} + + + +document.getElementById('levels').addEventListener('change', e => { + const levelIndexStr = e.target.getAttribute('data-level') + if ( levelIndexStr) { + const levelIndex = parseInt(levelIndexStr) + const level = allLevels[levelIndex] + if( e.target.getAttribute('type') === 'color'){ + + level.color = e.target.value + level.svg = '' + updateLevelBackground(levelIndex) + }else if( e.target.getAttribute('type') === 'checkbox' && e.target.hasAttribute('data-field')){ + level[e.target.getAttribute('data-field')] = !!e.target.checked + } + + save() + } + +}) +document.getElementById('levels').addEventListener('click', e => { + const resize = e.target.getAttribute('data-offset-level-size') + const moveX = e.target.getAttribute('data-offset-x') + const moveY = e.target.getAttribute('data-offset-y') + const levelIndexStr = e.target.getAttribute('data-level') + if (!levelIndexStr) return + if (e.target.tagName!=='BUTTON') return + + + const levelIndex = parseInt(levelIndexStr) + const level = allLevels[levelIndex] + const {bricks, size} = level; + + if (resize) { + const newSize = size + parseInt(resize) + const newBricks = [] + for (let x = 0; x < Math.min(size, newSize); x++) { + for (let y = 0; y < Math.min(size, newSize); y++) { + newBricks[y * newSize + x] = bricks[y * size + x] || '' + } + } + + level.size = newSize; + level.bricks = newBricks; + } else if (moveX && moveY) { + const dx = parseInt(moveX), dy = parseInt(moveY) + const moved = [] + for (let x = 0; x < size; x++) { + for (let y = 0; y < size; y++) { + moved[(y + dy) * size + (x + dx)] = bricks[y * size + x] || '' + } + } + + level.bricks = moved; + } else if (e.target.getAttribute('data-rename')) { + const newName = prompt('Name ? ', level.name) + if (newName) { + level.name = newName + e.target.textContent = newName + } + }else if (e.target.getAttribute('data-delete')) { + + if (confirm('Delete level')) { + allLevels=allLevels.filter((l,i)=>i!==levelIndex) + save().then(()=>window.location.reload()) + } + }else if(e.target.getAttribute('data-set-bg-svg')){ + const newBg = prompt('New svg code',level.svg||'') + if(newBg){ + level.svg=newBg + level.color = '' + } + + updateLevelBackground(levelIndex) + } + renderLevelBricks(levelIndex) + save() + + +}, true) + +let applying = undefined + +function colorPixel(e) { + if (typeof applying === 'undefined') return + const index = e.target.getAttribute('data-set-color-of') + const level = e.target.getAttribute('data-level') + if (index && level) { + const levelIndex = parseInt(level) + e.target.style.background = applying || 'transparent' + allLevels[levelIndex].bricks[index] = applying + } +} + +document.getElementById('levels').addEventListener('mousedown', e => { + const index = e.target.getAttribute('data-set-color-of') + const level = e.target.getAttribute('data-level') + if (index && level) { + const before = allLevels[parseInt(level)].bricks[parseInt(index)] || '' + applying = before === currentColor ? '' : currentColor + colorPixel(e) + } +}) + +document.getElementById('levels').addEventListener('mouseenter', e => { + if (typeof applying !== undefined) { + + colorPixel(e) + } +}, true) + +document.addEventListener('mouseup', e => { + applying = undefined + save() +}) + + +document.getElementById('new-level').addEventListener('click', e => { + + const name = prompt("Name ? ") + if (!name) return; + + allLevels.push({ + name, + size: 8, + bricks: ['white'], + svg: '', + }) + const levelIndex = allLevels.length - 1 + addLevelEditorToList(allLevels[levelIndex], levelIndex) + save() +}, true) + +renderAllLevels() + + +function save() { + return fetch('/', { + method: 'POST', headers: { + 'Content-Type': 'text/plain' + }, + body: 'let allLevels=' + JSON.stringify(allLevels, null, 2) + }) +} diff --git a/editserver.js b/editserver.js new file mode 100644 index 0000000..d9ff80b --- /dev/null +++ b/editserver.js @@ -0,0 +1,50 @@ +const express = require('express') +const bodyParser = require('body-parser'); +const fs = require('fs') +const app = express() +const port = 4400 + +const srcPath = 'app/src/main/assets/levels.js' +app.use(bodyParser.text({ + type: 'text/plain', + limit:'1MB' +})); + + +app.get('/', (req, res) => { + res.end(` + + + + Level editor + + + + +
+
+ + +
+ + + + + + `) +}) +app.post('/', (req, res) => { + console.log(req.body) + if(req.body?.trim()) { + fs.writeFileSync(srcPath, req.body) + } + res.end('OK') +}) + +app.listen(port, () => { + console.log(`Example app listening on port http://localhost:${port}`) +}) diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..20e2a01 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..a641cb5 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,31 @@ +[versions] +agp = "8.3.1" +kotlin = "1.9.0" +coreKtx = "1.13.1" +junit = "4.13.2" +junitVersion = "1.1.5" +espressoCore = "3.5.1" +lifecycleRuntimeKtx = "2.6.1" +activityCompose = "1.7.0" +composeBom = "2023.08.00" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } + +[plugins] +androidApplication = { id = "com.android.application", version.ref = "agp" } +jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b080ee6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Jan 05 13:08:47 CET 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..a3a77ee Binary files /dev/null and b/icon.png differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c34bd64 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1079 @@ +{ + "name": "breakout.lecaro.me", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "breakout.lecaro.me", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "body-parser": "^1.20.3", + "express": "^4.21.2", + "nodemon": "^3.1.9" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c9a817b --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "breakout.lecaro.me", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "nodemon editserver.js --watch editserver.js " + }, + "author": "", + "license": "ISC", + "dependencies": { + "body-parser": "^1.20.3", + "express": "^4.21.2", + "nodemon": "^3.1.9" + } +} diff --git a/patterns.html b/patterns.html new file mode 100644 index 0000000..7f7dfb3 --- /dev/null +++ b/patterns.html @@ -0,0 +1,163 @@ + + + + + Patterns preview + + + + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..73efb8a --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,24 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Breakout" +include(":app") + \ No newline at end of file