Build 29035725
13
Readme.md
|
@ -47,6 +47,7 @@ There's also an easy mode for kids (slower ball).
|
||||||
- translation
|
- translation
|
||||||
- when game resumes near bottom, be unvulnerable for .5s ? , once per level
|
- when game resumes near bottom, be unvulnerable for .5s ? , once per level
|
||||||
|
|
||||||
|
|
||||||
# Game engine features
|
# Game engine features
|
||||||
|
|
||||||
- ask for permanent storage
|
- ask for permanent storage
|
||||||
|
@ -153,6 +154,14 @@ There's also an easy mode for kids (slower ball).
|
||||||
- level flips horizontally every time a ball bounces on puck
|
- level flips horizontally every time a ball bounces on puck
|
||||||
- coins that hit the puck disappear, missed ones are scored
|
- coins that hit the puck disappear, missed ones are scored
|
||||||
- squirell : keep coins on screen to have a higher combo
|
- squirell : keep coins on screen to have a higher combo
|
||||||
|
- [colin] peaceful combo - le combo monte chaque seconde tant que les 2+ balles ne se touchent pas OU qu'on ne touche pas de bloc explosif.
|
||||||
|
- [colin] close quarters - balle attirée par tous les blocs/par un bloc aléatoire, actif à portée de bloc (+1bloc au lvlup)/proportionnel à une force (+puissance au lvlup)…
|
||||||
|
- [colin] shocks - balls can bounce off of each others and produce a shock that destroys a random block at the current combo
|
||||||
|
- [colin] plusieurs perks qui déclenchent des effets quand une balle est perdue. par ex: +3 combo à chaque balle perdue, 5 blocs transformés en bombe, balle et coins ralentis, blocs régénérés…
|
||||||
|
- [colin] faster style - augmente le combo en fonction de la vitesse de la balle
|
||||||
|
- [colin] perk: analyzer - permet de voir les caractéristiques cachées des blocs (sturdy…)
|
||||||
|
- [colin] perk: roulette - gagne instantanément 2 perks aléatoires
|
||||||
|
|
||||||
|
|
||||||
# extra levels
|
# extra levels
|
||||||
|
|
||||||
|
@ -195,7 +204,7 @@ This requires recording a bit more info about each run.
|
||||||
I could unlock the "pro stand" at $999 that just holds the play area higher.
|
I could unlock the "pro stand" at $999 that just holds the play area higher.
|
||||||
|
|
||||||
# increase skill ceiling
|
# increase skill ceiling
|
||||||
|
- reroll mechanic, rerolls are reward for better play
|
||||||
- make puck smaller as combo increases ?
|
- make puck smaller as combo increases ?
|
||||||
- nerf coin magnet :
|
- nerf coin magnet :
|
||||||
- no effect when too close
|
- no effect when too close
|
||||||
|
@ -216,8 +225,6 @@ I could unlock the "pro stand" at $999 that just holds the play area higher.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Colin's feedback (cwpute/obigre)
|
# Colin's feedback (cwpute/obigre)
|
||||||
|
|
||||||
* reward the player with more choices/perks for breaking a brick while having reached an increasing combo thresholds. 5 combo, then 10, then 20, then 40 etc… once a threshold is reached you aren't rewarded for that threshold again until you start a rew run
|
* reward the player with more choices/perks for breaking a brick while having reached an increasing combo thresholds. 5 combo, then 10, then 20, then 40 etc… once a threshold is reached you aren't rewarded for that threshold again until you start a rew run
|
||||||
|
|
|
@ -11,8 +11,8 @@ android {
|
||||||
applicationId = "me.lecaro.breakout"
|
applicationId = "me.lecaro.breakout"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 29033878
|
versionCode = 29035725
|
||||||
versionName = "29033878"
|
versionName = "29035725"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
|
|
9
build.sh
|
@ -35,10 +35,10 @@ sed -i -e "s/^[[:space:]]*versionCode = .*/ versionCode = $versionCode/"
|
||||||
-e "s/^[[:space:]]*versionName = .*/ versionName = \"$versionCode\"/" \
|
-e "s/^[[:space:]]*versionName = .*/ versionName = \"$versionCode\"/" \
|
||||||
./app/build.gradle.kts
|
./app/build.gradle.kts
|
||||||
|
|
||||||
echo "\"$versionCode\"" > src/version.json
|
echo "\"$versionCode\"" > src/data/version.json
|
||||||
|
|
||||||
# Update service worker
|
# Update service worker
|
||||||
sed -i -e "s/VERSION = .*/ VERSION = '$versionCode'/" ./src/sw-b71.js
|
sed -i -e "s/VERSION = .*/ VERSION = '$versionCode'/" ./src/PWA/sw-b71.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,8 +48,9 @@ find -name '*.jp*g' -o -name '*.png' | xargs exiftool -all=
|
||||||
|
|
||||||
npx prettier --write src/
|
npx prettier --write src/
|
||||||
|
|
||||||
npm run build
|
npx jest
|
||||||
|
rm -rf dist/*
|
||||||
|
npx parcel build src/index.html
|
||||||
rm -rf ./app/src/main/assets/*
|
rm -rf ./app/src/main/assets/*
|
||||||
cp public/* dist
|
cp public/* dist
|
||||||
rm -rf ./app/src/main/assets/*
|
rm -rf ./app/src/main/assets/*
|
||||||
|
|
2
dist/PWA/sw-b71.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
function e(e,t,n,r,a,i,c){try{var o=e[i](c),u=o.value}catch(e){n(e);return}o.done?t(u):Promise.resolve(u).then(r,a)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(a,i){var c=t.apply(n,r);function o(t){e(c,a,i,o,u,"next",t)}function u(t){e(c,a,i,o,u,"throw",t)}o(void 0)})}}function n(e,t){var n,r,a,i,c={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return i={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function o(i){return function(o){return function(i){if(n)throw TypeError("Generator is already executing.");for(;c;)try{if(n=1,r&&(a=2&i[0]?r.return:i[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,i[1])).done)return a;switch(r=0,a&&(i=[2&i[0],a.value]),i[0]){case 0:case 1:a=i;break;case 4:return c.label++,{value:i[1],done:!1};case 5:c.label++,r=i[1],i=[0];continue;case 7:i=c.ops.pop(),c.trys.pop();continue;default:if(!(a=(a=c.trys).length>0&&a[a.length-1])&&(6===i[0]||2===i[0])){c=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]<a[3])){c.label=i[1];break}if(6===i[0]&&c.label<a[1]){c.label=a[1],a=i;break}if(a&&c.label<a[2]){c.label=a[2],c.ops.push(i);break}a[2]&&c.ops.pop(),c.trys.pop();continue}i=t.call(e,c)}catch(e){i=[6,e],r=0}finally{n=a=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,o])}}}var r="breakout-71-".concat("29035725"),a=["/"];self.addEventListener("install",function(e){e.waitUntil(t(function(){return n(this,function(e){switch(e.label){case 0:return[4,caches.open(r)];case 1:return e.sent().addAll(a),[2]}})})())}),self.addEventListener("activate",function(e){e.waitUntil(t(function(){return n(this,function(e){switch(e.label){case 0:return[4,caches.keys()];case 1:return[4,Promise.all(e.sent().map(function(e){if(e!==r)return caches.delete(e)}))];case 2:return e.sent(),[4,clients.claim()];case 3:return e.sent(),[2]}})})())}),self.addEventListener("fetch",function(e){if("navigate"===e.request.mode&&e.request.url.endsWith("/index.html?isPWA=true")){e.respondWith(caches.match("/"));return}});
|
||||||
|
//# sourceMappingURL=sw-b71.js.map
|
1
dist/PWA/sw-b71.js.map
vendored
Normal file
62
dist/index.c0fd3053.js → dist/editor.1350aee5.js
vendored
1
dist/editor.1350aee5.js.map
vendored
Normal file
|
@ -142,10 +142,10 @@
|
||||||
this[globalName] = mainExports;
|
this[globalName] = mainExports;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})({"9zw4T":[function(require,module,exports,__globalThis) {
|
})({"7Iayr":[function(require,module,exports,__globalThis) {
|
||||||
require("6f2db3dd8d20283b")(require("27e61996b32b4a9a").getBundleURL('ouAZg') + "index.c0fd3053.js");
|
require("4e14309168f23be0")(require("275cab9bde4ab8f8").getBundleURL('jo05F') + "editor.1350aee5.js");
|
||||||
|
|
||||||
},{"6f2db3dd8d20283b":"61B45","27e61996b32b4a9a":"lgJ39"}],"61B45":[function(require,module,exports,__globalThis) {
|
},{"4e14309168f23be0":"61B45","275cab9bde4ab8f8":"lgJ39"}],"61B45":[function(require,module,exports,__globalThis) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var cacheLoader = require("ca2a84f7fa4a3bb0");
|
var cacheLoader = require("ca2a84f7fa4a3bb0");
|
||||||
module.exports = cacheLoader(function(bundle) {
|
module.exports = cacheLoader(function(bundle) {
|
||||||
|
@ -242,16 +242,16 @@ exports.getBundleURL = getBundleURLCached;
|
||||||
exports.getBaseURL = getBaseURL;
|
exports.getBaseURL = getBaseURL;
|
||||||
exports.getOrigin = getOrigin;
|
exports.getOrigin = getOrigin;
|
||||||
|
|
||||||
},{}],"iSxqL":[function(require,module,exports,__globalThis) {
|
},{}],"9Ly5x":[function(require,module,exports,__globalThis) {
|
||||||
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
|
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
|
||||||
var _jsxDevRuntime = require("react/jsx-dev-runtime");
|
var _jsxDevRuntime = require("react/jsx-dev-runtime");
|
||||||
var _backgroundsJson = require("./backgrounds.json");
|
var _backgroundsJson = require("../data/backgrounds.json");
|
||||||
var _backgroundsJsonDefault = parcelHelpers.interopDefault(_backgroundsJson);
|
var _backgroundsJsonDefault = parcelHelpers.interopDefault(_backgroundsJson);
|
||||||
var _paletteJson = require("./palette.json");
|
var _paletteJson = require("../data/palette.json");
|
||||||
var _paletteJsonDefault = parcelHelpers.interopDefault(_paletteJson);
|
var _paletteJsonDefault = parcelHelpers.interopDefault(_paletteJson);
|
||||||
var _levelsJson = require("./levels.json");
|
var _levelsJson = require("../data/levels.json");
|
||||||
var _levelsJsonDefault = parcelHelpers.interopDefault(_levelsJson);
|
var _levelsJsonDefault = parcelHelpers.interopDefault(_levelsJson);
|
||||||
var _getLevelBackground = require("./getLevelBackground");
|
var _getLevelBackground = require("../getLevelBackground");
|
||||||
var _client = require("react-dom/client");
|
var _client = require("react-dom/client");
|
||||||
var _react = require("react");
|
var _react = require("react");
|
||||||
var _levelsEditorUtil = require("./levels_editor_util");
|
var _levelsEditorUtil = require("./levels_editor_util");
|
||||||
|
@ -273,7 +273,7 @@ function App() {
|
||||||
}, []);
|
}, []);
|
||||||
(0, _react.useEffect)(()=>{
|
(0, _react.useEffect)(()=>{
|
||||||
const timoutId = setTimeout(()=>{
|
const timoutId = setTimeout(()=>{
|
||||||
return fetch("http://localhost:4400/src/levels.json", {
|
return fetch("http://localhost:4400/src/data/levels.json", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "text/plain"
|
"Content-Type": "text/plain"
|
||||||
|
@ -317,7 +317,7 @@ function App() {
|
||||||
position: "absolute"
|
position: "absolute"
|
||||||
}
|
}
|
||||||
}, index, false, {
|
}, index, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 63,
|
lineNumber: 63,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this));
|
}, this));
|
||||||
|
@ -338,7 +338,7 @@ function App() {
|
||||||
name: e.target.value
|
name: e.target.value
|
||||||
})
|
})
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 99,
|
lineNumber: 99,
|
||||||
columnNumber: 15
|
columnNumber: 15
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -348,7 +348,7 @@ function App() {
|
||||||
onClick: ()=>deleteLevel(li),
|
onClick: ()=>deleteLevel(li),
|
||||||
children: "Delete"
|
children: "Delete"
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 105,
|
lineNumber: 105,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -356,7 +356,7 @@ function App() {
|
||||||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.resizeLevel)(level, -1)),
|
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.resizeLevel)(level, -1)),
|
||||||
children: "-"
|
children: "-"
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 106,
|
lineNumber: 106,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -364,7 +364,7 @@ function App() {
|
||||||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.resizeLevel)(level, 1)),
|
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.resizeLevel)(level, 1)),
|
||||||
children: "+"
|
children: "+"
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 109,
|
lineNumber: 109,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -372,7 +372,7 @@ function App() {
|
||||||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, -1, 0)),
|
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, -1, 0)),
|
||||||
children: "L"
|
children: "L"
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 112,
|
lineNumber: 112,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -380,7 +380,7 @@ function App() {
|
||||||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 1, 0)),
|
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 1, 0)),
|
||||||
children: "R"
|
children: "R"
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 117,
|
lineNumber: 117,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -388,7 +388,7 @@ function App() {
|
||||||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 0, -1)),
|
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 0, -1)),
|
||||||
children: "U"
|
children: "U"
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 120,
|
lineNumber: 120,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -396,7 +396,7 @@ function App() {
|
||||||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 0, 1)),
|
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 0, 1)),
|
||||||
children: "D"
|
children: "D"
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 125,
|
lineNumber: 125,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -407,7 +407,7 @@ function App() {
|
||||||
color: e.target.value
|
color: e.target.value
|
||||||
})
|
})
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 128,
|
lineNumber: 128,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -419,13 +419,13 @@ function App() {
|
||||||
svg: parseFloat(e.target.value)
|
svg: parseFloat(e.target.value)
|
||||||
})
|
})
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 135,
|
lineNumber: 135,
|
||||||
columnNumber: 17
|
columnNumber: 17
|
||||||
}, this)
|
}, this)
|
||||||
]
|
]
|
||||||
}, void 0, true, {
|
}, void 0, true, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 104,
|
lineNumber: 104,
|
||||||
columnNumber: 15
|
columnNumber: 15
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -438,19 +438,19 @@ function App() {
|
||||||
},
|
},
|
||||||
children: brickButtons
|
children: brickButtons
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 147,
|
lineNumber: 147,
|
||||||
columnNumber: 15
|
columnNumber: 15
|
||||||
}, this)
|
}, this)
|
||||||
]
|
]
|
||||||
}, li, true, {
|
}, li, true, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 98,
|
lineNumber: 98,
|
||||||
columnNumber: 13
|
columnNumber: 13
|
||||||
}, this);
|
}, this);
|
||||||
})
|
})
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 54,
|
lineNumber: 54,
|
||||||
columnNumber: 7
|
columnNumber: 7
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -467,12 +467,12 @@ function App() {
|
||||||
},
|
},
|
||||||
onClick: ()=>setSelected(code)
|
onClick: ()=>setSelected(code)
|
||||||
}, code, false, {
|
}, code, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 163,
|
lineNumber: 163,
|
||||||
columnNumber: 11
|
columnNumber: 11
|
||||||
}, this))
|
}, this))
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 161,
|
lineNumber: 161,
|
||||||
columnNumber: 7
|
columnNumber: 7
|
||||||
}, this),
|
}, this),
|
||||||
|
@ -494,25 +494,25 @@ function App() {
|
||||||
},
|
},
|
||||||
children: "new"
|
children: "new"
|
||||||
}, void 0, false, {
|
}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 177,
|
lineNumber: 177,
|
||||||
columnNumber: 7
|
columnNumber: 7
|
||||||
}, this)
|
}, this)
|
||||||
]
|
]
|
||||||
}, void 0, true, {
|
}, void 0, true, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 49,
|
lineNumber: 49,
|
||||||
columnNumber: 5
|
columnNumber: 5
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
const root = (0, _client.createRoot)(document.getElementById("app"));
|
const root = (0, _client.createRoot)(document.getElementById("app"));
|
||||||
root.render(/*#__PURE__*/ (0, _jsxDevRuntime.jsxDEV)(App, {}, void 0, false, {
|
root.render(/*#__PURE__*/ (0, _jsxDevRuntime.jsxDEV)(App, {}, void 0, false, {
|
||||||
fileName: "src/levels_editor.tsx",
|
fileName: "src/level_editor/levels_editor.tsx",
|
||||||
lineNumber: 203,
|
lineNumber: 203,
|
||||||
columnNumber: 13
|
columnNumber: 13
|
||||||
}, undefined));
|
}, undefined));
|
||||||
|
|
||||||
},{"react/jsx-dev-runtime":"iTorj","./backgrounds.json":"el6Kx","./palette.json":"jhnsJ","./levels.json":"kqnNl","./getLevelBackground":"7OIPf","react-dom/client":"lOjBx","react":"21dqq","./levels_editor_util":"lt8Nt","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"iTorj":[function(require,module,exports,__globalThis) {
|
},{"react/jsx-dev-runtime":"iTorj","../data/backgrounds.json":"31wW4","../data/palette.json":"ktRBU","../data/levels.json":"8JSUc","../getLevelBackground":"7OIPf","react-dom/client":"lOjBx","react":"21dqq","./levels_editor_util":"lfafp","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"iTorj":[function(require,module,exports,__globalThis) {
|
||||||
'use strict';
|
'use strict';
|
||||||
module.exports = require("ee51401569654d91");
|
module.exports = require("ee51401569654d91");
|
||||||
|
|
||||||
|
@ -16252,7 +16252,7 @@ module.exports = require("b0f0e6b9e8349dac");
|
||||||
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
|
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
|
||||||
})();
|
})();
|
||||||
|
|
||||||
},{"6f0162e9ab224cd4":"21dqq"}],"lt8Nt":[function(require,module,exports,__globalThis) {
|
},{"6f0162e9ab224cd4":"21dqq"}],"lfafp":[function(require,module,exports,__globalThis) {
|
||||||
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
|
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
|
||||||
parcelHelpers.defineInteropFlag(exports);
|
parcelHelpers.defineInteropFlag(exports);
|
||||||
parcelHelpers.export(exports, "resizeLevel", ()=>resizeLevel);
|
parcelHelpers.export(exports, "resizeLevel", ()=>resizeLevel);
|
||||||
|
@ -16292,6 +16292,6 @@ function setBrick(level, index, colorCode) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
},{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["9zw4T","iSxqL"], "iSxqL", "parcelRequire94c2")
|
},{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["7Iayr","9Ly5x"], "9Ly5x", "parcelRequire94c2")
|
||||||
|
|
||||||
//# sourceMappingURL=levels_editor.ef3c2e1a.js.map
|
//# sourceMappingURL=editor.1ec04b8f.js.map
|
1
dist/editor.1ec04b8f.js.map
vendored
Normal file
|
@ -53,4 +53,4 @@ body {
|
||||||
#levels > div > div:nth-child(3) {
|
#levels > div > div:nth-child(3) {
|
||||||
grid-area: bricks;
|
grid-area: bricks;
|
||||||
}
|
}
|
||||||
/*# sourceMappingURL=levels_editor.de5e7f9b.css.map */
|
/*# sourceMappingURL=editor.9680328c.css.map */
|
1
dist/editor.9680328c.css.map
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"mappings":"AAAA;;;;;AAKA;;;;;;;;;AAAA;;;;AAaA;;;;;;;;AAAA;;;;AAAA;;;;AAcE;;;;;;;AAKE;;;;AAIA;;;;;;;AAOA","sources":["src/level_editor/levels_editor.less"],"sourcesContent":["body {\n background: black;\n color: white;\n}\n\n#palette {\n position: fixed;\n top: 0;\n right: 0;\n width: 80px;\n bottom: 0;\n overflow: auto;\n\n button.active {\n transform: scale(1.2);\n }\n}\n\n#levels {\n display: flex;\n gap: 40px;\n align-items: flex-start;\n flex-wrap: wrap;\n margin-right: 80px;\n\n .level-bricks-preview {\n position: relative;\n }\n input[type=\"number\"] {\n width: 50px;\n }\n\n & > div {\n display: grid;\n grid-template-columns: auto auto;\n grid-template-areas: \". name\" \"buttons bricks\";\n\n & > *:nth-child(1) {\n grid-area: name;\n }\n\n & > div:nth-child(2) {\n grid-area: buttons;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n }\n\n & > div:nth-child(3) {\n grid-area: bricks;\n }\n }\n}\n"],"names":[],"version":3,"file":"editor.9680328c.css.map","sourceRoot":"/__parcel_source_root/"}
|
8
dist/levels_editor.html → dist/editor.html
vendored
|
@ -1,14 +1,14 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head><script src="/index.c0fd3053.js"></script>
|
<head><script src="/editor.1350aee5.js"></script>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Level editor</title>
|
<title>Level editor</title>
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎨</text></svg>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔨</text></svg>">
|
||||||
<link rel="stylesheet" href="/levels_editor.de5e7f9b.css">
|
<link rel="stylesheet" href="/editor.9680328c.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
<script src="/levels_editor.ef3c2e1a.js" defer=""></script>
|
<script src="/editor.1ec04b8f.js" defer=""></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
8
dist/icon.7be7e26e.svg
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="50" width="50">
|
||||||
|
<rect x="0" y="0" width="30" height="10" fill="#6262EA"></rect>
|
||||||
|
<rect x="20" y="10" width="10" height="10" fill="#6262EA"></rect>
|
||||||
|
<rect x="10" y="20" width="10" height="20" fill="#6262EA"></rect>
|
||||||
|
<rect x="20" y="20" width="10" height="10" fill="#5DA3EA"></rect>
|
||||||
|
<rect x="30" y="10" width="10" height="30" fill="#5DA3EA"></rect>
|
||||||
|
<rect x="20" y="40" width="40" height="30" fill="#5DA3EA"></rect>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 464 B |
1
dist/icon.939d1a8f.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50"><path fill="#6262EA" d="M0 0h30v10H0zM20 10h10v10H20zM10 20h10v20H10z"/><path fill="#5DA3EA" d="M20 20h10v10H20zM30 10h10v30H30zM20 40h40v30H20z"/></svg>
|
After Width: | Height: | Size: 216 B |
1
dist/index.c0fd3053.js.map
vendored
3646
dist/index.html
vendored
1
dist/levels_editor.de5e7f9b.css.map
vendored
|
@ -1 +0,0 @@
|
||||||
{"mappings":"AAAA;;;;;AAKA;;;;;;;;;AAAA;;;;AAaA;;;;;;;;AAAA;;;;AAAA;;;;AAcE;;;;;;;AAKE;;;;AAIA;;;;;;;AAOA","sources":["src/levels_editor.less"],"sourcesContent":["body {\n background: black;\n color: white;\n}\n\n#palette {\n position: fixed;\n top: 0;\n right: 0;\n width: 80px;\n bottom: 0;\n overflow: auto;\n\n button.active {\n transform: scale(1.2);\n }\n}\n\n#levels {\n display: flex;\n gap: 40px;\n align-items: flex-start;\n flex-wrap: wrap;\n margin-right: 80px;\n\n .level-bricks-preview {\n position: relative;\n }\n input[type=\"number\"] {\n width: 50px;\n }\n\n & > div {\n display: grid;\n grid-template-columns: auto auto;\n grid-template-areas: \". name\" \"buttons bricks\";\n\n & > *:nth-child(1) {\n grid-area: name;\n }\n\n & > div:nth-child(2) {\n grid-area: buttons;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n }\n\n & > div:nth-child(3) {\n grid-area: bricks;\n }\n }\n}\n"],"names":[],"version":3,"file":"levels_editor.de5e7f9b.css.map","sourceRoot":"/__parcel_source_root/"}
|
|
1
dist/levels_editor.ef3c2e1a.js.map
vendored
33
dist/sw-b71.js
vendored
|
@ -1,33 +0,0 @@
|
||||||
// The version of the cache.
|
|
||||||
const VERSION = "29033878";
|
|
||||||
// The name of the cache
|
|
||||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
|
||||||
// The static resources that the app needs to function.
|
|
||||||
const APP_STATIC_RESOURCES = [
|
|
||||||
"/"
|
|
||||||
];
|
|
||||||
// On install, cache the static resources
|
|
||||||
self.addEventListener("install", (event)=>{
|
|
||||||
event.waitUntil((async ()=>{
|
|
||||||
const cache = await caches.open(CACHE_NAME);
|
|
||||||
cache.addAll(APP_STATIC_RESOURCES);
|
|
||||||
})());
|
|
||||||
});
|
|
||||||
// delete old caches on activate
|
|
||||||
self.addEventListener("activate", (event)=>{
|
|
||||||
event.waitUntil((async ()=>{
|
|
||||||
const names = await caches.keys();
|
|
||||||
await Promise.all(names.map((name)=>{
|
|
||||||
if (name !== CACHE_NAME) return caches.delete(name);
|
|
||||||
}));
|
|
||||||
await clients.claim();
|
|
||||||
})());
|
|
||||||
});
|
|
||||||
self.addEventListener("fetch", (event)=>{
|
|
||||||
if (event.request.mode === "navigate" && event.request.url.endsWith("/index.html?isPWA=true")) {
|
|
||||||
event.respondWith(caches.match("/"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//# sourceMappingURL=sw-b71.js.map
|
|
1
dist/sw-b71.js.map
vendored
|
@ -1 +0,0 @@
|
||||||
{"mappings":"AAAA,4BAA4B;AAC5B,MAAM,UAAU;AAEhB,wBAAwB;AACxB,MAAM,aAAa,CAAC,YAAY,EAAE,SAAS;AAE3C,uDAAuD;AACvD,MAAM,uBAAuB;IAAC;CAAI;AAElC,yCAAyC;AACzC,KAAK,gBAAgB,CAAC,WAAW,CAAC;IAChC,MAAM,SAAS,CACb,AAAC,CAAA;QACC,MAAM,QAAQ,MAAM,OAAO,IAAI,CAAC;QAChC,MAAM,MAAM,CAAC;IACf,CAAA;AAEJ;AAEA,gCAAgC;AAChC,KAAK,gBAAgB,CAAC,YAAY,CAAC;IACjC,MAAM,SAAS,CACb,AAAC,CAAA;QACC,MAAM,QAAQ,MAAM,OAAO,IAAI;QAC/B,MAAM,QAAQ,GAAG,CACf,MAAM,GAAG,CAAC,CAAC;YACT,IAAI,SAAS,YACX,OAAO,OAAO,MAAM,CAAC;QAEzB;QAEF,MAAM,QAAQ,KAAK;IACrB,CAAA;AAEJ;AAEA,KAAK,gBAAgB,CAAC,SAAS,CAAC;IAC9B,IACE,MAAM,OAAO,CAAC,IAAI,KAAK,cACvB,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,2BAC3B;QACA,MAAM,WAAW,CAAC,OAAO,KAAK,CAAC;QAC/B;IACF;AACF","sources":["src/sw-b71.js"],"sourcesContent":["// The version of the cache.\nconst VERSION = \"29033878\";\n\n// The name of the cache\nconst CACHE_NAME = `breakout-71-${VERSION}`;\n\n// The static resources that the app needs to function.\nconst APP_STATIC_RESOURCES = [\"/\"];\n\n// On install, cache the static resources\nself.addEventListener(\"install\", (event) => {\n event.waitUntil(\n (async () => {\n const cache = await caches.open(CACHE_NAME);\n cache.addAll(APP_STATIC_RESOURCES);\n })(),\n );\n});\n\n// delete old caches on activate\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(\n (async () => {\n const names = await caches.keys();\n await Promise.all(\n names.map((name) => {\n if (name !== CACHE_NAME) {\n return caches.delete(name);\n }\n }),\n );\n await clients.claim();\n })(),\n );\n});\n\nself.addEventListener(\"fetch\", (event) => {\n if (\n event.request.mode === \"navigate\" &&\n event.request.url.endsWith(\"/index.html?isPWA=true\")\n ) {\n event.respondWith(caches.match(\"/\"));\n return;\n }\n});\n"],"names":[],"version":3,"file":"sw-b71.js.map","sourceRoot":"/__parcel_source_root/"}
|
|
|
@ -9,9 +9,9 @@ app.use(bodyParser.text({
|
||||||
limit:'1MB'
|
limit:'1MB'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.post('/src/levels.json', (req, res) => {
|
app.post('/src/data/levels.json', (req, res) => {
|
||||||
if(req.body?.trim()) {
|
if(req.body?.trim()) {
|
||||||
fs.writeFileSync('src/levels.json', req.body)
|
fs.writeFileSync('src/data/levels.json', req.body)
|
||||||
}
|
}
|
||||||
res.end('OK')
|
res.end('OK')
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
"start": "rm -rf .parcel-cache && run-p dev:*",
|
"start": "rm -rf .parcel-cache && run-p dev:*",
|
||||||
"dev:game-fe": "parcel src/*.html --lazy --no-hmr",
|
"dev:game-fe": "parcel src/*.html --lazy --no-hmr",
|
||||||
"dev:editor-be": "nodemon editserver.js --watch editserver.js",
|
"dev:editor-be": "nodemon editserver.js --watch editserver.js",
|
||||||
"test": "jest --watch",
|
"test": "jest --watch"
|
||||||
"build": "npx jest && rm -f dist/* && parcel build src/index.html"
|
|
||||||
},
|
},
|
||||||
"browserslist": "since 2009",
|
"browserslist": "since 2009",
|
||||||
"author": "Renan LE CARO",
|
"author": "Renan LE CARO",
|
||||||
|
|
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 445 B After Width: | Height: | Size: 445 B |
8
src/PWA/icon.svg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="50" width="50">
|
||||||
|
<rect x="0" y="0" width="30" height="10" fill="#6262EA"/>
|
||||||
|
<rect x="20" y="10" width="10" height="10" fill="#6262EA"/>
|
||||||
|
<rect x="10" y="20" width="10" height="20" fill="#6262EA"/>
|
||||||
|
<rect x="20" y="20" width="10" height="10" fill="#5DA3EA"/>
|
||||||
|
<rect x="30" y="10" width="10" height="30" fill="#5DA3EA"/>
|
||||||
|
<rect x="20" y="40" width="40" height="30" fill="#5DA3EA"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 428 B |
|
@ -1,5 +1,5 @@
|
||||||
// The version of the cache.
|
// The version of the cache.
|
||||||
const VERSION = "29033878";
|
const VERSION = "29035725";
|
||||||
|
|
||||||
// The name of the cache
|
// The name of the cache
|
||||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
const CACHE_NAME = `breakout-71-${VERSION}`;
|
|
@ -1,122 +1,121 @@
|
||||||
import {t} from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
|
|
||||||
export let alertsOpen = 0,
|
export let alertsOpen = 0,
|
||||||
closeModal: null | (() => void) = null;
|
closeModal: null | (() => void) = null;
|
||||||
|
|
||||||
export type AsyncAlertAction<t> = {
|
export type AsyncAlertAction<t> = {
|
||||||
text?: string;
|
text?: string;
|
||||||
value?: t;
|
value?: t;
|
||||||
help?: string;
|
help?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export function asyncAlert<t>({
|
export function asyncAlert<t>({
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
actions,
|
actions,
|
||||||
allowClose = true,
|
allowClose = true,
|
||||||
textAfterButtons = "",
|
textAfterButtons = "",
|
||||||
actionsAsGrid = false,
|
actionsAsGrid = false,
|
||||||
}: {
|
}: {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
actions?: AsyncAlertAction<t>[];
|
actions?: AsyncAlertAction<t>[];
|
||||||
textAfterButtons?: string;
|
textAfterButtons?: string;
|
||||||
allowClose?: boolean;
|
allowClose?: boolean;
|
||||||
actionsAsGrid?: boolean;
|
actionsAsGrid?: boolean;
|
||||||
}): Promise<t | void> {
|
}): Promise<t | void> {
|
||||||
alertsOpen++;
|
alertsOpen++;
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const popupWrap = document.createElement("div");
|
const popupWrap = document.createElement("div");
|
||||||
document.body.appendChild(popupWrap);
|
document.body.appendChild(popupWrap);
|
||||||
popupWrap.className = "popup " + (actionsAsGrid ? "actionsAsGrid " : "");
|
popupWrap.className = "popup " + (actionsAsGrid ? "actionsAsGrid " : "");
|
||||||
|
|
||||||
function closeWithResult(value: t | undefined) {
|
function closeWithResult(value: t | undefined) {
|
||||||
resolve(value);
|
resolve(value);
|
||||||
// Doing this async lets the menu scroll persist if it's shown a second time
|
// Doing this async lets the menu scroll persist if it's shown a second time
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.body.removeChild(popupWrap);
|
document.body.removeChild(popupWrap);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowClose) {
|
if (allowClose) {
|
||||||
const closeButton = document.createElement("button");
|
const closeButton = document.createElement("button");
|
||||||
closeButton.title = t('play.close_modale_window_tooltip');
|
closeButton.title = t("play.close_modale_window_tooltip");
|
||||||
closeButton.className = "close-modale";
|
closeButton.className = "close-modale";
|
||||||
closeButton.addEventListener("click", (e) => {
|
closeButton.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
closeWithResult(undefined);
|
closeWithResult(undefined);
|
||||||
});
|
});
|
||||||
closeModal = () => {
|
closeModal = () => {
|
||||||
closeWithResult(undefined);
|
closeWithResult(undefined);
|
||||||
};
|
};
|
||||||
popupWrap.appendChild(closeButton);
|
popupWrap.appendChild(closeButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
const popup = document.createElement("div");
|
const popup = document.createElement("div");
|
||||||
|
|
||||||
if (title) {
|
if (title) {
|
||||||
const p = document.createElement("h2");
|
const p = document.createElement("h2");
|
||||||
p.innerHTML = title;
|
p.innerHTML = title;
|
||||||
popup.appendChild(p);
|
popup.appendChild(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
const p = document.createElement("div");
|
const p = document.createElement("div");
|
||||||
p.innerHTML = text;
|
p.innerHTML = text;
|
||||||
popup.appendChild(p);
|
popup.appendChild(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttons = document.createElement("section");
|
const buttons = document.createElement("section");
|
||||||
popup.appendChild(buttons);
|
popup.appendChild(buttons);
|
||||||
|
|
||||||
actions
|
actions
|
||||||
?.filter((i) => i)
|
?.filter((i) => i)
|
||||||
.forEach(({text, value, help, disabled, className = "", icon = ""}) => {
|
.forEach(({ text, value, help, disabled, className = "", icon = "" }) => {
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
|
|
||||||
button.innerHTML = `
|
button.innerHTML = `
|
||||||
${icon}
|
${icon}
|
||||||
<div>
|
<div>
|
||||||
<strong>${text}</strong>
|
<strong>${text}</strong>
|
||||||
<em>${help || ""}</em>
|
<em>${help || ""}</em>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
button.setAttribute("disabled", "disabled");
|
button.setAttribute("disabled", "disabled");
|
||||||
} else {
|
} else {
|
||||||
button.addEventListener("click", (e) => {
|
button.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
closeWithResult(value);
|
closeWithResult(value);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
button.className = className;
|
|
||||||
buttons.appendChild(button);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (textAfterButtons) {
|
|
||||||
const p = document.createElement("div");
|
|
||||||
p.className = "textAfterButtons";
|
|
||||||
p.innerHTML = textAfterButtons;
|
|
||||||
popup.appendChild(p);
|
|
||||||
}
|
}
|
||||||
|
button.className = className;
|
||||||
|
buttons.appendChild(button);
|
||||||
|
});
|
||||||
|
|
||||||
popupWrap.appendChild(popup);
|
if (textAfterButtons) {
|
||||||
(
|
const p = document.createElement("div");
|
||||||
popup.querySelector("button:not([disabled])") as HTMLButtonElement
|
p.className = "textAfterButtons";
|
||||||
)?.focus();
|
p.innerHTML = textAfterButtons;
|
||||||
}).then(
|
popup.appendChild(p);
|
||||||
(v: unknown) => {
|
}
|
||||||
alertsOpen--;
|
|
||||||
closeModal = null;
|
popupWrap.appendChild(popup);
|
||||||
return v as t | undefined;
|
(
|
||||||
},
|
popup.querySelector("button:not([disabled])") as HTMLButtonElement
|
||||||
() => {
|
)?.focus();
|
||||||
closeModal = null;
|
}).then(
|
||||||
alertsOpen--;
|
(v: unknown) => {
|
||||||
},
|
alertsOpen--;
|
||||||
);
|
closeModal = null;
|
||||||
|
return v as t | undefined;
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
closeModal = null;
|
||||||
|
alertsOpen--;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Butterfly",
|
"name": "Butterfly",
|
||||||
"bricks": "_________bb_t_t_bbbbb_t_bbbbbbbtbbbb_bbbtbbb____btb____bbbtbbb__bb_t_bb___________________",
|
"bricks": "_________bb_t_t_bbbbb_t_bbbbbbbtbbbb_bbbtbbb____btb____bbbtbbb__bb_t_bb__________",
|
||||||
"size": 9,
|
"size": 9,
|
||||||
"svg": 20,
|
"svg": 20,
|
||||||
"color": ""
|
"color": ""
|
||||||
|
@ -834,5 +834,12 @@
|
||||||
"size": 6,
|
"size": 6,
|
||||||
"bricks": "_W__W_WW__WW____________WW__WW_W__W_",
|
"bricks": "_W__W_WW__WW____________WW__WW_W__W_",
|
||||||
"svg": null
|
"svg": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "icon:concave_puck",
|
||||||
|
"size": 8,
|
||||||
|
"bricks": "___________W_______________W_______________W_____________WWWWW__",
|
||||||
|
"svg": null,
|
||||||
|
"color": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
1
src/data/version.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"29035725"
|
|
@ -5,13 +5,13 @@
|
||||||
<title>Level editor</title>
|
<title>Level editor</title>
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎨</text></svg>"
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔨</text></svg>"
|
||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="./levels_editor.less" />
|
<link rel="stylesheet" href="./level_editor/levels_editor.less" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
<script type="module" src="levels_editor.tsx"></script>
|
<script type="module" src="./level_editor/levels_editor.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
1340
src/game.ts
421
src/gameOver.ts
|
@ -1,251 +1,276 @@
|
||||||
import {allLevels, appVersion, upgrades} from "./loadGameData";
|
import { allLevels, appVersion, upgrades } from "./loadGameData";
|
||||||
import {t} from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
import {RunHistoryItem} from "./types";
|
import { RunHistoryItem } from "./types";
|
||||||
import {gameState, pause, restart} from "./game";
|
import { gameState, pause, restart } from "./game";
|
||||||
import {currentLevelInfo, findLast} from "./game_utils";
|
import { currentLevelInfo, findLast } from "./game_utils";
|
||||||
import {getTotalScore} from "./settings";
|
import { getTotalScore } from "./settings";
|
||||||
import {stopRecording} from "./recording";
|
import { stopRecording } from "./recording";
|
||||||
import {asyncAlert} from "./asyncAlert";
|
import { asyncAlert } from "./asyncAlert";
|
||||||
|
|
||||||
export function getUpgraderUnlockPoints() {
|
export function getUpgraderUnlockPoints() {
|
||||||
let list = [] as { threshold: number; title: string }[];
|
let list = [] as { threshold: number; title: string }[];
|
||||||
|
|
||||||
upgrades.forEach((u) => {
|
upgrades.forEach((u) => {
|
||||||
if (u.threshold) {
|
if (u.threshold) {
|
||||||
list.push({
|
list.push({
|
||||||
threshold: u.threshold,
|
threshold: u.threshold,
|
||||||
title: u.name + ' ' + t('level_up.unlocked_perk'),
|
title: u.name + " " + t("level_up.unlocked_perk"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
allLevels.forEach((l) => {
|
||||||
|
list.push({
|
||||||
|
threshold: l.threshold,
|
||||||
|
title: l.name + " " + t("level_up.unlocked_level"),
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
allLevels.forEach((l) => {
|
return list
|
||||||
list.push({
|
.filter((o) => o.threshold)
|
||||||
threshold: l.threshold,
|
.sort((a, b) => a.threshold - b.threshold);
|
||||||
title: l.name + ' ' + t('level_up.unlocked_level'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return list
|
|
||||||
.filter((o) => o.threshold)
|
|
||||||
.sort((a, b) => a.threshold - b.threshold);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addToTotalPlayTime(ms: number) {
|
export function addToTotalPlayTime(ms: number) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"breakout_71_total_play_time",
|
"breakout_71_total_play_time",
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
JSON.parse(localStorage.getItem("breakout_71_total_play_time") || "0") +
|
JSON.parse(localStorage.getItem("breakout_71_total_play_time") || "0") +
|
||||||
ms,
|
ms,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gameOver(title: string, intro: string) {
|
export function gameOver(title: string, intro: string) {
|
||||||
if (!gameState.running) return;
|
if (!gameState.running) return;
|
||||||
pause(true);
|
pause(true);
|
||||||
stopRecording();
|
stopRecording();
|
||||||
addToTotalPlayTime(gameState.runStatistics.runTime);
|
addToTotalPlayTime(gameState.runStatistics.runTime);
|
||||||
gameState.runStatistics.max_level = gameState.currentLevel + 1;
|
gameState.runStatistics.max_level = gameState.currentLevel + 1;
|
||||||
|
|
||||||
let animationDelay = -300;
|
let animationDelay = -300;
|
||||||
const getDelay = () => {
|
const getDelay = () => {
|
||||||
animationDelay += 800;
|
animationDelay += 800;
|
||||||
return "animation-delay:" + animationDelay + "ms;";
|
return "animation-delay:" + animationDelay + "ms;";
|
||||||
};
|
};
|
||||||
// unlocks
|
// unlocks
|
||||||
let unlocksInfo = "";
|
let unlocksInfo = "";
|
||||||
const endTs = getTotalScore();
|
const endTs = getTotalScore();
|
||||||
const startTs = endTs - gameState.score;
|
const startTs = endTs - gameState.score;
|
||||||
const list = getUpgraderUnlockPoints();
|
const list = getUpgraderUnlockPoints();
|
||||||
list
|
list
|
||||||
.filter((u) => u.threshold > startTs && u.threshold < endTs)
|
.filter((u) => u.threshold > startTs && u.threshold < endTs)
|
||||||
.forEach((u) => {
|
.forEach((u) => {
|
||||||
unlocksInfo += `
|
unlocksInfo += `
|
||||||
<p class="progress" >
|
<p class="progress" >
|
||||||
<span>${u.title}</span>
|
<span>${u.title}</span>
|
||||||
<span class="progress_bar_part" style="${getDelay()}"></span>
|
<span class="progress_bar_part" style="${getDelay()}"></span>
|
||||||
</p>
|
</p>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
const previousUnlockAt =
|
const previousUnlockAt =
|
||||||
findLast(list, (u) => u.threshold <= endTs)?.threshold || 0;
|
findLast(list, (u) => u.threshold <= endTs)?.threshold || 0;
|
||||||
const nextUnlock = list.find((u) => u.threshold > endTs);
|
const nextUnlock = list.find((u) => u.threshold > endTs);
|
||||||
|
|
||||||
if (nextUnlock) {
|
if (nextUnlock) {
|
||||||
const total = nextUnlock?.threshold - previousUnlockAt;
|
const total = nextUnlock?.threshold - previousUnlockAt;
|
||||||
const done = endTs - previousUnlockAt;
|
const done = endTs - previousUnlockAt;
|
||||||
|
|
||||||
intro += t('gameOver.next_unlock', {points: nextUnlock.threshold - endTs});
|
intro += t("gameOver.next_unlock", {
|
||||||
|
points: nextUnlock.threshold - endTs,
|
||||||
|
});
|
||||||
|
|
||||||
const scaleX = (done / total).toFixed(2);
|
const scaleX = (done / total).toFixed(2);
|
||||||
unlocksInfo += `
|
unlocksInfo += `
|
||||||
<p class="progress" >
|
<p class="progress" >
|
||||||
<span>${nextUnlock.title}</span>
|
<span>${nextUnlock.title}</span>
|
||||||
<span style="transform: scale(${scaleX},1);${getDelay()}" class="progress_bar_part"></span>
|
<span style="transform: scale(${scaleX},1);${getDelay()}" class="progress_bar_part"></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
list
|
list
|
||||||
.slice(list.indexOf(nextUnlock) + 1)
|
.slice(list.indexOf(nextUnlock) + 1)
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
.forEach((u) => {
|
.forEach((u) => {
|
||||||
unlocksInfo += `
|
unlocksInfo += `
|
||||||
<p class="progress" >
|
<p class="progress" >
|
||||||
<span>${u.title}</span>
|
<span>${u.title}</span>
|
||||||
</p>
|
</p>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let unlockedItems = list.filter(
|
let unlockedItems = list.filter(
|
||||||
(u) => u.threshold > startTs && u.threshold < endTs,
|
(u) => u.threshold > startTs && u.threshold < endTs,
|
||||||
);
|
);
|
||||||
if (unlockedItems.length) {
|
if (unlockedItems.length) {
|
||||||
|
unlocksInfo += `<p>${t("gameOver.unlocked_count", { count: unlockedItems.length })} ${unlockedItems.map((u) => u.title).join(", ")}</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
unlocksInfo += `<p>${t('gameOver.unlocked_count', {count: unlockedItems.length})} ${unlockedItems.map((u) => u.title).join(", ")}</p>`;
|
// Avoid the sad sound right as we restart a new games
|
||||||
}
|
gameState.combo = 1;
|
||||||
|
|
||||||
// Avoid the sad sound right as we restart a new games
|
asyncAlert({
|
||||||
gameState.combo = 1;
|
allowClose: true,
|
||||||
|
title,
|
||||||
asyncAlert({
|
text: `
|
||||||
allowClose: true,
|
${gameState.isCreativeModeRun ? `<p>${t("gameOver.test_run")}</p> ` : ""}
|
||||||
title,
|
|
||||||
text: `
|
|
||||||
${gameState.isCreativeModeRun ? `<p>${t('gameOver.test_run')}</p> ` : ""}
|
|
||||||
<p>${intro}</p>
|
<p>${intro}</p>
|
||||||
<p>${t('gameOver.cumulative_total', {startTs, endTs})}</p>
|
<p>${t("gameOver.cumulative_total", { startTs, endTs })}</p>
|
||||||
${unlocksInfo}
|
${unlocksInfo}
|
||||||
`,
|
`,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
value: null,
|
value: null,
|
||||||
text: t('gameOver.restart'),
|
text: t("gameOver.restart"),
|
||||||
help: "",
|
help: "",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
textAfterButtons: `<div id="level-recording-container"></div>
|
textAfterButtons: `<div id="level-recording-container"></div>
|
||||||
${getHistograms()}
|
${getHistograms()}
|
||||||
`,
|
`,
|
||||||
}).then(() => restart({levelToAvoid: currentLevelInfo(gameState).name}));
|
}).then(() => restart({ levelToAvoid: currentLevelInfo(gameState).name }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHistograms() {
|
export function getHistograms() {
|
||||||
let runStats = "";
|
let runStats = "";
|
||||||
try {
|
try {
|
||||||
// Stores only top 100 runs
|
// Stores only top 100 runs
|
||||||
let runsHistory = JSON.parse(
|
let runsHistory = JSON.parse(
|
||||||
localStorage.getItem("breakout_71_runs_history") || "[]",
|
localStorage.getItem("breakout_71_runs_history") || "[]",
|
||||||
) as RunHistoryItem[];
|
) as RunHistoryItem[];
|
||||||
runsHistory.sort((a, b) => a.score - b.score).reverse();
|
runsHistory.sort((a, b) => a.score - b.score).reverse();
|
||||||
runsHistory = runsHistory.slice(0, 100);
|
runsHistory = runsHistory.slice(0, 100);
|
||||||
|
|
||||||
runsHistory.push({
|
runsHistory.push({
|
||||||
...gameState.runStatistics,
|
...gameState.runStatistics,
|
||||||
perks: gameState.perks,
|
perks: gameState.perks,
|
||||||
appVersion,
|
appVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate some histogram
|
// Generate some histogram
|
||||||
if (!gameState.isCreativeModeRun)
|
if (!gameState.isCreativeModeRun)
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"breakout_71_runs_history",
|
"breakout_71_runs_history",
|
||||||
JSON.stringify(runsHistory, null, 2),
|
JSON.stringify(runsHistory, null, 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
const makeHistogram = (
|
const makeHistogram = (
|
||||||
title: string,
|
title: string,
|
||||||
getter: (hi: RunHistoryItem) => number,
|
getter: (hi: RunHistoryItem) => number,
|
||||||
unit: string,
|
unit: string,
|
||||||
) => {
|
) => {
|
||||||
let values = runsHistory.map((h) => getter(h) || 0);
|
let values = runsHistory.map((h) => getter(h) || 0);
|
||||||
let min = Math.min(...values);
|
let min = Math.min(...values);
|
||||||
let max = Math.max(...values);
|
let max = Math.max(...values);
|
||||||
// No point
|
// No point
|
||||||
if (min === max) return "";
|
if (min === max) return "";
|
||||||
if (max - min < 10) {
|
if (max - min < 10) {
|
||||||
// This is mostly useful for levels
|
// This is mostly useful for levels
|
||||||
min = Math.max(0, max - 10);
|
min = Math.max(0, max - 10);
|
||||||
max = Math.max(max, min + 10);
|
max = Math.max(max, min + 10);
|
||||||
}
|
}
|
||||||
// One bin per unique value, max 10
|
// One bin per unique value, max 10
|
||||||
const binsCount = Math.min(values.length, 10);
|
const binsCount = Math.min(values.length, 10);
|
||||||
if (binsCount < 3) return "";
|
if (binsCount < 3) return "";
|
||||||
const bins = [] as number[];
|
const bins = [] as number[];
|
||||||
const binsTotal = [] as number[];
|
const binsTotal = [] as number[];
|
||||||
for (let i = 0; i < binsCount; i++) {
|
for (let i = 0; i < binsCount; i++) {
|
||||||
bins.push(0);
|
bins.push(0);
|
||||||
binsTotal.push(0);
|
binsTotal.push(0);
|
||||||
}
|
}
|
||||||
const binSize = (max - min) / bins.length;
|
const binSize = (max - min) / bins.length;
|
||||||
const binIndexOf = (v: number) =>
|
const binIndexOf = (v: number) =>
|
||||||
Math.min(bins.length - 1, Math.floor((v - min) / binSize));
|
Math.min(bins.length - 1, Math.floor((v - min) / binSize));
|
||||||
values.forEach((v) => {
|
values.forEach((v) => {
|
||||||
if (isNaN(v)) return;
|
if (isNaN(v)) return;
|
||||||
const index = binIndexOf(v);
|
const index = binIndexOf(v);
|
||||||
bins[index]++;
|
bins[index]++;
|
||||||
binsTotal[index] += v;
|
binsTotal[index] += v;
|
||||||
});
|
});
|
||||||
if (bins.filter((b) => b).length < 3) return "";
|
if (bins.filter((b) => b).length < 3) return "";
|
||||||
const maxBin = Math.max(...bins);
|
const maxBin = Math.max(...bins);
|
||||||
const lastValue = values[values.length - 1];
|
const lastValue = values[values.length - 1];
|
||||||
const activeBin = binIndexOf(lastValue);
|
const activeBin = binIndexOf(lastValue);
|
||||||
|
|
||||||
const bars = bins
|
const bars = bins
|
||||||
.map((v, vi) => {
|
.map((v, vi) => {
|
||||||
const style = `height: ${(v / maxBin) * 80}px`;
|
const style = `height: ${(v / maxBin) * 80}px`;
|
||||||
return `<span class="${vi === activeBin ? "active" : ""}"><span style="${style}" title="${v} run${v > 1 ? "s" : ""} between ${Math.floor(min + vi * binSize)} and ${Math.floor(min + (vi + 1) * binSize)}${unit}"
|
return `<span class="${vi === activeBin ? "active" : ""}"><span style="${style}" title="${v} run${v > 1 ? "s" : ""} between ${Math.floor(min + vi * binSize)} and ${Math.floor(min + (vi + 1) * binSize)}${unit}"
|
||||||
><span>${(!v && " ") || (vi == activeBin && lastValue + unit) || Math.round(binsTotal[vi] / v) + unit}</span></span></span>`;
|
><span>${(!v && " ") || (vi == activeBin && lastValue + unit) || Math.round(binsTotal[vi] / v) + unit}</span></span></span>`;
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
return `<h2 class="histogram-title">${title} : <strong>${lastValue}${unit}</strong></h2>
|
return `<h2 class="histogram-title">${title} : <strong>${lastValue}${unit}</strong></h2>
|
||||||
<div class="histogram">${bars}</div>
|
<div class="histogram">${bars}</div>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
runStats += makeHistogram(t('gameOver.stats.total_score'), (r) => r.score, "");
|
runStats += makeHistogram(
|
||||||
runStats += makeHistogram(t('gameOver.stats.catch_rate'),
|
t("gameOver.stats.total_score"),
|
||||||
(r) => Math.round((r.score / r.coins_spawned) * 100),
|
(r) => r.score,
|
||||||
"%",
|
"",
|
||||||
);
|
);
|
||||||
runStats += makeHistogram(t('gameOver.stats.bricks_broken'), (r) => r.bricks_broken, "");
|
runStats += makeHistogram(
|
||||||
runStats += makeHistogram(
|
t("gameOver.stats.catch_rate"),
|
||||||
t('gameOver.stats.bricks_per_minute'),
|
(r) => Math.round((r.score / r.coins_spawned) * 100),
|
||||||
(r) => Math.round((r.bricks_broken / r.runTime) * 1000 * 60),
|
"%",
|
||||||
"",
|
);
|
||||||
);
|
runStats += makeHistogram(
|
||||||
runStats += makeHistogram(
|
t("gameOver.stats.bricks_broken"),
|
||||||
t('gameOver.stats.hit_rate'),
|
(r) => r.bricks_broken,
|
||||||
(r) => Math.round((1 - r.misses / r.puck_bounces) * 100),
|
"",
|
||||||
"%",
|
);
|
||||||
);
|
runStats += makeHistogram(
|
||||||
runStats += makeHistogram(
|
t("gameOver.stats.bricks_per_minute"),
|
||||||
t('gameOver.stats.duration_per_level'),
|
(r) => Math.round((r.bricks_broken / r.runTime) * 1000 * 60),
|
||||||
(r) => Math.round(r.runTime / 1000 / r.levelsPlayed),
|
"",
|
||||||
"s",
|
);
|
||||||
);
|
runStats += makeHistogram(
|
||||||
runStats += makeHistogram(t('gameOver.stats.level_reached'), (r) => r.levelsPlayed, "");
|
t("gameOver.stats.hit_rate"),
|
||||||
runStats += makeHistogram(t('gameOver.stats.upgrades_applied'), (r) => r.upgrades_picked, "");
|
(r) => Math.round((1 - r.misses / r.puck_bounces) * 100),
|
||||||
runStats += makeHistogram(t('gameOver.stats.balls_lost'), (r) => r.balls_lost, "");
|
"%",
|
||||||
runStats += makeHistogram(
|
);
|
||||||
t('gameOver.stats.combo_avg'),
|
runStats += makeHistogram(
|
||||||
(r) => Math.round(r.coins_spawned / r.bricks_broken),
|
t("gameOver.stats.duration_per_level"),
|
||||||
"",
|
(r) => Math.round(r.runTime / 1000 / r.levelsPlayed),
|
||||||
);
|
"s",
|
||||||
runStats += makeHistogram(t('gameOver.stats.combo_max'), (r) => r.max_combo, "");
|
);
|
||||||
|
runStats += makeHistogram(
|
||||||
|
t("gameOver.stats.level_reached"),
|
||||||
|
(r) => r.levelsPlayed,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
runStats += makeHistogram(
|
||||||
|
t("gameOver.stats.upgrades_applied"),
|
||||||
|
(r) => r.upgrades_picked,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
runStats += makeHistogram(
|
||||||
|
t("gameOver.stats.balls_lost"),
|
||||||
|
(r) => r.balls_lost,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
runStats += makeHistogram(
|
||||||
|
t("gameOver.stats.combo_avg"),
|
||||||
|
(r) => Math.round(r.coins_spawned / r.bricks_broken),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
runStats += makeHistogram(
|
||||||
|
t("gameOver.stats.combo_max"),
|
||||||
|
(r) => r.max_combo,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
if (runStats) {
|
if (runStats) {
|
||||||
runStats =
|
runStats =
|
||||||
`<p>${t('gameOver.stats.intro', {count: runsHistory.length - 1})}</p>` +
|
`<p>${t("gameOver.stats.intro", { count: runsHistory.length - 1 })}</p>` +
|
||||||
runStats;
|
runStats;
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e);
|
|
||||||
}
|
}
|
||||||
return runStats;
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
return runStats;
|
||||||
}
|
}
|
|
@ -4,7 +4,6 @@ import {
|
||||||
sample,
|
sample,
|
||||||
sumOfKeys,
|
sumOfKeys,
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
import { Upgrade } from "./types";
|
|
||||||
|
|
||||||
describe("getMajorityValue", () => {
|
describe("getMajorityValue", () => {
|
||||||
it("returns the most common string", () => {
|
it("returns the most common string", () => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Ball, GameState, PerkId, PerksMap} from "./types";
|
import { Ball, GameState, PerkId, PerksMap } from "./types";
|
||||||
import {icons, upgrades} from "./loadGameData";
|
import { icons, upgrades } from "./loadGameData";
|
||||||
|
|
||||||
export function getMajorityValue(arr: string[]): string {
|
export function getMajorityValue(arr: string[]): string {
|
||||||
const count: { [k: string]: number } = {};
|
const count: { [k: string]: number } = {};
|
||||||
|
@ -26,8 +26,8 @@ export const makeEmptyPerksMap = (upgrades: { id: PerkId }[]) => {
|
||||||
|
|
||||||
export function brickCenterX(gameState: GameState, index: number) {
|
export function brickCenterX(gameState: GameState, index: number) {
|
||||||
return (
|
return (
|
||||||
gameState.offsetX +
|
gameState.offsetX +
|
||||||
((index % gameState.gridSize) + 0.5) * gameState.brickWidth
|
((index % gameState.gridSize) + 0.5) * gameState.brickWidth
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,10 +37,10 @@ export function brickCenterY(gameState: GameState, index: number) {
|
||||||
|
|
||||||
export function getRowColIndex(gameState: GameState, row: number, col: number) {
|
export function getRowColIndex(gameState: GameState, row: number, col: number) {
|
||||||
if (
|
if (
|
||||||
row < 0 ||
|
row < 0 ||
|
||||||
col < 0 ||
|
col < 0 ||
|
||||||
row >= gameState.gridSize ||
|
row >= gameState.gridSize ||
|
||||||
col >= gameState.gridSize
|
col >= gameState.gridSize
|
||||||
)
|
)
|
||||||
return -1;
|
return -1;
|
||||||
return row * gameState.gridSize + col;
|
return row * gameState.gridSize + col;
|
||||||
|
@ -48,8 +48,8 @@ export function getRowColIndex(gameState: GameState, row: number, col: number) {
|
||||||
|
|
||||||
export function getPossibleUpgrades(gameState: GameState) {
|
export function getPossibleUpgrades(gameState: GameState) {
|
||||||
return upgrades
|
return upgrades
|
||||||
.filter((u) => gameState.totalScoreAtRunStart >= u.threshold)
|
.filter((u) => gameState.totalScoreAtRunStart >= u.threshold)
|
||||||
.filter((u) => !u?.requires || gameState.perks[u?.requires]);
|
.filter((u) => !u?.requires || gameState.perks[u?.requires]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function max_levels(gameState: GameState) {
|
export function max_levels(gameState: GameState) {
|
||||||
|
@ -67,8 +67,8 @@ export function pickedUpgradesHTMl(gameState: GameState) {
|
||||||
|
|
||||||
export function currentLevelInfo(gameState: GameState) {
|
export function currentLevelInfo(gameState: GameState) {
|
||||||
return gameState.runLevels[
|
return gameState.runLevels[
|
||||||
gameState.currentLevel % gameState.runLevels.length
|
gameState.currentLevel % gameState.runLevels.length
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTelekinesisActive(gameState: GameState, ball: Ball) {
|
export function isTelekinesisActive(gameState: GameState, ball: Ball) {
|
||||||
|
@ -76,8 +76,8 @@ export function isTelekinesisActive(gameState: GameState, ball: Ball) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findLast<T>(
|
export function findLast<T>(
|
||||||
arr: T[],
|
arr: T[],
|
||||||
predicate: (item: T, index: number, array: T[]) => boolean,
|
predicate: (item: T, index: number, array: T[]) => boolean,
|
||||||
) {
|
) {
|
||||||
let i = arr.length;
|
let i = arr.length;
|
||||||
while (--i)
|
while (--i)
|
||||||
|
@ -87,15 +87,15 @@ export function findLast<T>(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function distance2(
|
export function distance2(
|
||||||
a: { x: number; y: number },
|
a: { x: number; y: number },
|
||||||
b: { x: number; y: number },
|
b: { x: number; y: number },
|
||||||
) {
|
) {
|
||||||
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
|
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function distanceBetween(
|
export function distanceBetween(
|
||||||
a: { x: number; y: number },
|
a: { x: number; y: number },
|
||||||
b: { x: number; y: number },
|
b: { x: number; y: number },
|
||||||
) {
|
) {
|
||||||
return Math.sqrt(distance2(a, b));
|
return Math.sqrt(distance2(a, b));
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { RawLevel } from "./types";
|
import { RawLevel } from "./types";
|
||||||
|
|
||||||
import _backgrounds from "./backgrounds.json";
|
import _backgrounds from "./data/backgrounds.json";
|
||||||
const backgrounds = _backgrounds as string[];
|
const backgrounds = _backgrounds as string[];
|
||||||
|
|
||||||
export function getLevelBackground(level: RawLevel) {
|
export function getLevelBackground(level: RawLevel) {
|
||||||
|
|
|
@ -1792,6 +1792,56 @@
|
||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
|
<folder_node>
|
||||||
|
<name>concave_puck</name>
|
||||||
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>fullHelp</name>
|
||||||
|
<description/>
|
||||||
|
<comment/>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-FR</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>help</name>
|
||||||
|
<description/>
|
||||||
|
<comment/>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-FR</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>name</name>
|
||||||
|
<description/>
|
||||||
|
<comment/>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-FR</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
</children>
|
||||||
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
<name>extra_levels</name>
|
<name>extra_levels</name>
|
||||||
<children>
|
<children>
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
"gameOver.win.summary": "You cleared all levels for this run, catching {{score}} coins in total.",
|
"gameOver.win.summary": "You cleared all levels for this run, catching {{score}} coins in total.",
|
||||||
"gameOver.win.title": "Run finished",
|
"gameOver.win.title": "Run finished",
|
||||||
"level_up.after_buttons": "You just finished level {{level}}/{{max}} and picked those upgrades so far :",
|
"level_up.after_buttons": "You just finished level {{level}}/{{max}} and picked those upgrades so far :",
|
||||||
"level_up.before_buttons": "You caught {{score}} coins {{catchGain}} out of {{levelSpawnedCoins}} in {{time}} seconds ${timeGain}.\n\nYou missed {{levelMisses}} times {{missesGain}}.\n\n{{compliment}}",
|
"level_up.before_buttons": "You caught {{score}} coins {{catchGain}} out of {{levelSpawnedCoins}} in {{time}} seconds {{timeGain}}.\n\nYou missed {{levelMisses}} times {{missesGain}} and hit the walls or ceiling {{levelWallBounces}} times{{wallHitsGain}}.\n\n{{compliment}}",
|
||||||
"level_up.compliment_advice": "Try to catch all coins, never miss the bricks or clear the level under 30s to gain additional choices and upgrades.",
|
"level_up.compliment_advice": "Try to catch all coins, never miss the bricks, never hit the walls/ceiling or clear the level under 30s to gain additional choices and upgrades.",
|
||||||
"level_up.compliment_good": "Well done !",
|
"level_up.compliment_good": "Well done !",
|
||||||
"level_up.compliment_perfect": "Impressive, keep it up !",
|
"level_up.compliment_perfect": "Impressive, keep it up !",
|
||||||
"level_up.pick_upgrade_title": "Pick an upgrade",
|
"level_up.pick_upgrade_title": "Pick an upgrade",
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
"sandbox.instructions": "Select perks below and press \"start run\" to try them out in a test run. Scores and stats are not recorded.",
|
"sandbox.instructions": "Select perks below and press \"start run\" to try them out in a test run. Scores and stats are not recorded.",
|
||||||
"sandbox.start": "Start test run",
|
"sandbox.start": "Start test run",
|
||||||
"sandbox.title": "Sandbox mode",
|
"sandbox.title": "Sandbox mode",
|
||||||
"sandbox.unlocks_at": "Unlocks at total score ${{score}}",
|
"sandbox.unlocks_at": "Unlocks at total score {{score}}",
|
||||||
"score_panel.restart": "Restart",
|
"score_panel.restart": "Restart",
|
||||||
"score_panel.restart_help": "Start a brand new run",
|
"score_panel.restart_help": "Start a brand new run",
|
||||||
"score_panel.resume": "Resume",
|
"score_panel.resume": "Resume",
|
||||||
|
@ -111,6 +111,9 @@
|
||||||
"upgrades.compound_interest.fullHelp": "Your combo will grow by one every time you break a brick, spawning more and more coin with every brick you break. \n\nBe sure however to catch every one of those coins with your puck, as any lost coin will reset your combo. \n\nOnce your combo is above the minimum, the bottom of the play area will have a red line to remind you that coins should not go there.\n\nThis perk combines with other combo perks, the combo will rise faster but reset more easily.",
|
"upgrades.compound_interest.fullHelp": "Your combo will grow by one every time you break a brick, spawning more and more coin with every brick you break. \n\nBe sure however to catch every one of those coins with your puck, as any lost coin will reset your combo. \n\nOnce your combo is above the minimum, the bottom of the play area will have a red line to remind you that coins should not go there.\n\nThis perk combines with other combo perks, the combo will rise faster but reset more easily.",
|
||||||
"upgrades.compound_interest.help": "+1 combo per brick broken, resets on coin lost",
|
"upgrades.compound_interest.help": "+1 combo per brick broken, resets on coin lost",
|
||||||
"upgrades.compound_interest.name": "Compound interest",
|
"upgrades.compound_interest.name": "Compound interest",
|
||||||
|
"upgrades.concave_puck.fullHelp": "Balls starts the level going straight up, and bounces with less angle.",
|
||||||
|
"upgrades.concave_puck.help": " Helps with aiming straight up",
|
||||||
|
"upgrades.concave_puck.name": "Concave puck",
|
||||||
"upgrades.extra_levels.fullHelp": "The default run can last a max of 7 levels, after which the game is over and whatever score you reached is your run score. \n\nEach level of this perk lets you go one level higher. The last levels are often the ones where you make the most score, so the difference can be dramatic.",
|
"upgrades.extra_levels.fullHelp": "The default run can last a max of 7 levels, after which the game is over and whatever score you reached is your run score. \n\nEach level of this perk lets you go one level higher. The last levels are often the ones where you make the most score, so the difference can be dramatic.",
|
||||||
"upgrades.extra_levels.help": "Play {{count}} levels instead of 7",
|
"upgrades.extra_levels.help": "Play {{count}} levels instead of 7",
|
||||||
"upgrades.extra_levels.name": "+1 level",
|
"upgrades.extra_levels.name": "+1 level",
|
||||||
|
@ -187,7 +190,7 @@
|
||||||
"upgrades.telekinesis.name": "Telekinesis",
|
"upgrades.telekinesis.name": "Telekinesis",
|
||||||
"upgrades.top_is_lava.fullHelp": "Whenever you break a brick, your combo will increase by one. However, your combo will reset as soon as your ball hit the top of the screen. \n\nWhen your combo is above the minimum, a red bar will appear at the top to remind you that you should avoid hitting it. \n\nThe effect stacks with other combo perks.",
|
"upgrades.top_is_lava.fullHelp": "Whenever you break a brick, your combo will increase by one. However, your combo will reset as soon as your ball hit the top of the screen. \n\nWhen your combo is above the minimum, a red bar will appear at the top to remind you that you should avoid hitting it. \n\nThe effect stacks with other combo perks.",
|
||||||
"upgrades.top_is_lava.help": "More coins if you don't touch the top.",
|
"upgrades.top_is_lava.help": "More coins if you don't touch the top.",
|
||||||
"upgrades.top_is_lava.name": "Icarus",
|
"upgrades.top_is_lava.name": "Sky is the limit",
|
||||||
"upgrades.viscosity.fullHelp": "Coins normally accelerate with gravity and explosions to pretty high speeds. \n\nThis perk constantly makes them slow down, as if they were in some sort of viscous liquid. \n\nThis makes catching them easier, and combines nicely with perks that influence the coin's movement.",
|
"upgrades.viscosity.fullHelp": "Coins normally accelerate with gravity and explosions to pretty high speeds. \n\nThis perk constantly makes them slow down, as if they were in some sort of viscous liquid. \n\nThis makes catching them easier, and combines nicely with perks that influence the coin's movement.",
|
||||||
"upgrades.viscosity.help": "Slower coin fall",
|
"upgrades.viscosity.help": "Slower coin fall",
|
||||||
"upgrades.viscosity.name": "Viscosity",
|
"upgrades.viscosity.name": "Viscosity",
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
"gameOver.win.summary": "Vous avez nettoyé tous les niveaux pour cette partie, en attrapant {{score}} pièces au total.",
|
"gameOver.win.summary": "Vous avez nettoyé tous les niveaux pour cette partie, en attrapant {{score}} pièces au total.",
|
||||||
"gameOver.win.title": "Partie terminée",
|
"gameOver.win.title": "Partie terminée",
|
||||||
"level_up.after_buttons": "Vous venez de terminer le niveau {{level}}/{{max}} et vous avez choisi ces améliorations jusqu'à présent :",
|
"level_up.after_buttons": "Vous venez de terminer le niveau {{level}}/{{max}} et vous avez choisi ces améliorations jusqu'à présent :",
|
||||||
"level_up.before_buttons": "Vous avez attrapé {{score}} pièces {{catchGain}} sur {{levelSpawnedCoins}} en {{time}} secondes ${timeGain}.\n\nVous avez raté les briques {{levelMisses}} fois {{missesGain}}.\n\n{{compliment}}",
|
"level_up.before_buttons": "Vous avez attrapé {{score}} pièces {{catchGain}} sur {{levelSpawnedCoins}} en {{time}} secondes {{timeGain}}.\n\nVous avez raté les briques {{levelMisses}} fois {{missesGain} et touché les cotés et le haut de la zone de jeu {{levelWallBounces}} fois {{wallHitsGain}}.\n\n{{compliment}}",
|
||||||
"level_up.compliment_advice": "Essayez d'attraper toutes les pièces, de ne jamais rater les briques ou de terminer le niveau en moins de 30 secondes pour obtenir des choix supplémentaires et des améliorations.",
|
"level_up.compliment_advice": "Essayez d'attraper toutes les pièces, de ne jamais rater les briques, de ne pas toucher les murs ou de terminer le niveau en moins de 30 secondes pour obtenir des choix supplémentaires et des améliorations.",
|
||||||
"level_up.compliment_good": "Bravo !",
|
"level_up.compliment_good": "Bravo !",
|
||||||
"level_up.compliment_perfect": "Impressionnant, continuez comme ça !",
|
"level_up.compliment_perfect": "Impressionnant, continuez comme ça !",
|
||||||
"level_up.pick_upgrade_title": "Choisir une amélioration",
|
"level_up.pick_upgrade_title": "Choisir une amélioration",
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
"sandbox.instructions": "Sélectionnez les amélioration ci-dessous et appuyez sur \"Démarrer la partie de test\" pour les tester. Les scores et les statistiques ne seront pas enregistrés.",
|
"sandbox.instructions": "Sélectionnez les amélioration ci-dessous et appuyez sur \"Démarrer la partie de test\" pour les tester. Les scores et les statistiques ne seront pas enregistrés.",
|
||||||
"sandbox.start": "Démarrer la partie de test",
|
"sandbox.start": "Démarrer la partie de test",
|
||||||
"sandbox.title": "Mode bac à sable",
|
"sandbox.title": "Mode bac à sable",
|
||||||
"sandbox.unlocks_at": "Déverrouillé à partir d'un score total de ${{score}}",
|
"sandbox.unlocks_at": "Déverrouillé à partir d'un score total de {{score}}",
|
||||||
"score_panel.restart": "Redémarrer",
|
"score_panel.restart": "Redémarrer",
|
||||||
"score_panel.restart_help": "Commencer une nouvelle partie",
|
"score_panel.restart_help": "Commencer une nouvelle partie",
|
||||||
"score_panel.resume": "Continuer la partie",
|
"score_panel.resume": "Continuer la partie",
|
||||||
|
@ -111,6 +111,9 @@
|
||||||
"upgrades.compound_interest.fullHelp": "Votre combo augmentera d'une unité à chaque fois que vous casserez une brique, générant de plus en plus de pièces à chaque fois que vous casserez une brique. Veillez cependant à attraper chacune de ces pièces avec votre palet, car toute pièce perdue remettra votre combo à zéro. \n \nSi votre combinaison est supérieure au minimum, une ligne rouge s'affichera au bas de la zone de jeu pour vous le rappeler que les pièces ne doivent pas aller à cet endroit.\n\nCet avantage se combine avec d'autres avantages de combo, le combo augmentera plus rapidement mais se réinitialisera plus souvent.",
|
"upgrades.compound_interest.fullHelp": "Votre combo augmentera d'une unité à chaque fois que vous casserez une brique, générant de plus en plus de pièces à chaque fois que vous casserez une brique. Veillez cependant à attraper chacune de ces pièces avec votre palet, car toute pièce perdue remettra votre combo à zéro. \n \nSi votre combinaison est supérieure au minimum, une ligne rouge s'affichera au bas de la zone de jeu pour vous le rappeler que les pièces ne doivent pas aller à cet endroit.\n\nCet avantage se combine avec d'autres avantages de combo, le combo augmentera plus rapidement mais se réinitialisera plus souvent.",
|
||||||
"upgrades.compound_interest.help": "+1 combo par brique cassée, remise à zéro quand une pièce est perdu",
|
"upgrades.compound_interest.help": "+1 combo par brique cassée, remise à zéro quand une pièce est perdu",
|
||||||
"upgrades.compound_interest.name": "Intérêts",
|
"upgrades.compound_interest.name": "Intérêts",
|
||||||
|
"upgrades.concave_puck.fullHelp": " Les balles démarrent verticalement en début de niveau, et rebondi sur le palet de manière plus verticale et inversée.",
|
||||||
|
"upgrades.concave_puck.help": "Aide à éviter les bords.",
|
||||||
|
"upgrades.concave_puck.name": "Palet concave",
|
||||||
"upgrades.extra_levels.fullHelp": "La partie dure normalement 7 niveaux, après quoi le jeu est terminé et le score que vous avez atteint est votre score de partie.\n\nChoisir cette amélioration vous permet de prolonger la partie d'un niveau. Les derniers niveaux sont souvent ceux où vous faites le plus de points, la différence peut donc être spectaculaire.",
|
"upgrades.extra_levels.fullHelp": "La partie dure normalement 7 niveaux, après quoi le jeu est terminé et le score que vous avez atteint est votre score de partie.\n\nChoisir cette amélioration vous permet de prolonger la partie d'un niveau. Les derniers niveaux sont souvent ceux où vous faites le plus de points, la différence peut donc être spectaculaire.",
|
||||||
"upgrades.extra_levels.help": "Jouer {{count}} niveaux au lieu de 7",
|
"upgrades.extra_levels.help": "Jouer {{count}} niveaux au lieu de 7",
|
||||||
"upgrades.extra_levels.name": "+1 niveau",
|
"upgrades.extra_levels.name": "+1 niveau",
|
||||||
|
@ -186,8 +189,8 @@
|
||||||
"upgrades.telekinesis.help_plural": "Effet plus fort sur la balle",
|
"upgrades.telekinesis.help_plural": "Effet plus fort sur la balle",
|
||||||
"upgrades.telekinesis.name": "Télékinésie",
|
"upgrades.telekinesis.name": "Télékinésie",
|
||||||
"upgrades.top_is_lava.fullHelp": "Chaque fois que vous cassez une brique, votre combo augmente d'une unité. Cependant, votre combo sera réinitialisé dès que votre balle atteindra le haut de l'écran.\n\nLorsque votre combo est supérieur au minimum, une barre rouge apparaît en haut de l'écran pour vous rappeler que vous devez éviter de la frapper.\n\nCet effet s'ajoute aux autres avantages du combo.",
|
"upgrades.top_is_lava.fullHelp": "Chaque fois que vous cassez une brique, votre combo augmente d'une unité. Cependant, votre combo sera réinitialisé dès que votre balle atteindra le haut de l'écran.\n\nLorsque votre combo est supérieur au minimum, une barre rouge apparaît en haut de l'écran pour vous rappeler que vous devez éviter de la frapper.\n\nCet effet s'ajoute aux autres avantages du combo.",
|
||||||
"upgrades.top_is_lava.help": "Plus de pièces si vous ne touchez pas le sommet.",
|
"upgrades.top_is_lava.help": "Plus de pièces si vous ne touchez pas le haut de la zone de jeu",
|
||||||
"upgrades.top_is_lava.name": "Icare",
|
"upgrades.top_is_lava.name": "Icare ",
|
||||||
"upgrades.viscosity.fullHelp": "Les pièces accélèrent normalement avec la gravité et les explosions pour atteindre des vitesses assez élevées. \n\nCette compétence les ralentit constamment, comme si elles se trouvaient dans une sorte de liquide visqueux.\n\nCela permet de les attraper plus facilement et se combine bien avec les améliorations qui influencent le mouvement de la pièce.",
|
"upgrades.viscosity.fullHelp": "Les pièces accélèrent normalement avec la gravité et les explosions pour atteindre des vitesses assez élevées. \n\nCette compétence les ralentit constamment, comme si elles se trouvaient dans une sorte de liquide visqueux.\n\nCela permet de les attraper plus facilement et se combine bien avec les améliorations qui influencent le mouvement de la pièce.",
|
||||||
"upgrades.viscosity.help": "Chute plus lente des pièces",
|
"upgrades.viscosity.help": "Chute plus lente des pièces",
|
||||||
"upgrades.viscosity.name": "Fluide visqueux ",
|
"upgrades.viscosity.name": "Fluide visqueux ",
|
||||||
|
|
|
@ -1,32 +1,31 @@
|
||||||
import fr from './fr.json'
|
import fr from "./fr.json";
|
||||||
import en from './en.json'
|
import en from "./en.json";
|
||||||
import {getSettingValue} from "../settings";
|
import { getSettingValue } from "../settings";
|
||||||
|
|
||||||
type translationKeys = keyof typeof en
|
type translationKeys = keyof typeof en;
|
||||||
type translation= { [key in translationKeys] : string }
|
type translation = { [key in translationKeys]: string };
|
||||||
const languages:Record<string, translation>= {fr,en}
|
const languages: Record<string, translation> = { fr, en };
|
||||||
export function getCurrentLang(){
|
export function getCurrentLang() {
|
||||||
return getSettingValue('lang',getFirstBrowserLanguage())
|
return getSettingValue("lang", getFirstBrowserLanguage());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function t(key: translationKeys, params: {[key:string]:any} = {}):string {
|
export function t(
|
||||||
const lang = getCurrentLang()
|
key: translationKeys,
|
||||||
let template=languages[lang]?.[key] || languages.en[key]
|
params: { [key: string]: any } = {},
|
||||||
for(let key in params){
|
): string {
|
||||||
template=template.split('{{'+key+'}}').join(`${params[key]}`)
|
const lang = getCurrentLang();
|
||||||
}
|
let template = languages[lang]?.[key] || languages.en[key];
|
||||||
return template
|
for (let key in params) {
|
||||||
|
template = template.split("{{" + key + "}}").join(`${params[key]}`);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFirstBrowserLanguage() {
|
function getFirstBrowserLanguage() {
|
||||||
const preferred_languages = [
|
const preferred_languages = [...navigator.languages, navigator.language, "en"]
|
||||||
...navigator.languages,
|
.filter((i) => i)
|
||||||
navigator.language,
|
.map((i) => i.slice(0, 2).toLowerCase());
|
||||||
'en'
|
const supported = Object.keys(languages);
|
||||||
].filter(i => i)
|
|
||||||
.map(i => i.slice(0, 2).toLowerCase())
|
|
||||||
const supported = Object.keys(languages)
|
|
||||||
|
|
||||||
return preferred_languages.find(k=>supported.includes(k)) || 'en'
|
return preferred_languages.find((k) => supported.includes(k)) || "en";
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
|
@ -12,15 +12,12 @@
|
||||||
name="description"
|
name="description"
|
||||||
content="A breakout game with roguelite mechanics. Break bricks, catch coins, pick upgrades, repeat. Play for free on mobile and desktop."
|
content="A breakout game with roguelite mechanics. Break bricks, catch coins, pick upgrades, repeat. Play for free on mobile and desktop."
|
||||||
/>
|
/>
|
||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="./PWA/manifest.json" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import "game.less";
|
@import "game.less";
|
||||||
</style>
|
</style>
|
||||||
<link
|
<link rel="icon" href="./PWA/icon.svg" />
|
||||||
rel="icon"
|
|
||||||
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🕹️</text></svg>"
|
|
||||||
/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<button id="menu">☰ <span id="menuLabel">menu</span></button>
|
<button id="menu">☰ <span id="menuLabel">menu</span></button>
|
||||||
|
|
46
src/levelIcon.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
let levelIconHTMLCanvas = document.createElement("canvas");
|
||||||
|
|
||||||
|
const levelIconHTMLCanvasCtx =
|
||||||
|
process.env.NODE_ENV !== "test" &&
|
||||||
|
(levelIconHTMLCanvas.getContext("2d", {
|
||||||
|
antialias: false,
|
||||||
|
alpha: true,
|
||||||
|
}) as CanvasRenderingContext2D);
|
||||||
|
|
||||||
|
export function levelIconHTML(
|
||||||
|
bricks: string[],
|
||||||
|
levelSize: number,
|
||||||
|
color: string,
|
||||||
|
) {
|
||||||
|
const size = 40;
|
||||||
|
const c = levelIconHTMLCanvas;
|
||||||
|
const ctx = levelIconHTMLCanvasCtx;
|
||||||
|
|
||||||
|
if (!ctx) return "";
|
||||||
|
c.width = size;
|
||||||
|
c.height = size;
|
||||||
|
|
||||||
|
if (color) {
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.fillRect(0, 0, size, size);
|
||||||
|
} else {
|
||||||
|
ctx.clearRect(0, 0, size, size);
|
||||||
|
}
|
||||||
|
const pxSize = size / levelSize;
|
||||||
|
for (let x = 0; x < levelSize; x++) {
|
||||||
|
for (let y = 0; y < levelSize; y++) {
|
||||||
|
const c = bricks[y * levelSize + x];
|
||||||
|
if (c) {
|
||||||
|
ctx.fillStyle = c;
|
||||||
|
ctx.fillRect(
|
||||||
|
Math.floor(pxSize * x),
|
||||||
|
Math.floor(pxSize * y),
|
||||||
|
Math.ceil(pxSize),
|
||||||
|
Math.ceil(pxSize),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<img alt="" width="${size}" height="${size}" src="${c.toDataURL()}"/>`;
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { Palette, RawLevel } from "./types";
|
import { Palette, RawLevel } from "../types";
|
||||||
import _backgrounds from "./backgrounds.json";
|
import _backgrounds from "../data/backgrounds.json";
|
||||||
import _palette from "./palette.json";
|
import _palette from "../data/palette.json";
|
||||||
import _allLevels from "./levels.json";
|
import _allLevels from "../data/levels.json";
|
||||||
import { getLevelBackground, hashCode } from "./getLevelBackground";
|
import { getLevelBackground, hashCode } from "../getLevelBackground";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { moveLevel, resizeLevel, setBrick } from "./levels_editor_util";
|
import { moveLevel, resizeLevel, setBrick } from "./levels_editor_util";
|
||||||
|
@ -34,7 +34,7 @@ function App() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timoutId = setTimeout(() => {
|
const timoutId = setTimeout(() => {
|
||||||
return fetch("http://localhost:4400/src/levels.json", {
|
return fetch("http://localhost:4400/src/data/levels.json", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "text/plain",
|
"Content-Type": "text/plain",
|
|
@ -1,4 +1,4 @@
|
||||||
import { RawLevel } from "./types";
|
import { RawLevel } from "../types";
|
||||||
|
|
||||||
export function resizeLevel(level: RawLevel, sizeDelta: number) {
|
export function resizeLevel(level: RawLevel, sizeDelta: number) {
|
||||||
const { size, bricks } = level;
|
const { size, bricks } = level;
|
|
@ -1,6 +1,6 @@
|
||||||
import _palette from "./palette.json";
|
import _palette from "./data/palette.json";
|
||||||
import _rawLevelsList from "./levels.json";
|
import _rawLevelsList from "./data/levels.json";
|
||||||
import _appVersion from "./version.json";
|
import _appVersion from "./data/version.json";
|
||||||
|
|
||||||
describe("json data checks", () => {
|
describe("json data checks", () => {
|
||||||
it("_rawLevelsList has icon levels", () => {
|
it("_rawLevelsList has icon levels", () => {
|
||||||
|
|
|
@ -1,53 +1,17 @@
|
||||||
import { Level, Palette, RawLevel, Upgrade } from "./types";
|
import { Level, Palette, RawLevel, Upgrade } from "./types";
|
||||||
import _palette from "./palette.json";
|
import _palette from "./data/palette.json";
|
||||||
import _rawLevelsList from "./levels.json";
|
import _rawLevelsList from "./data/levels.json";
|
||||||
import _appVersion from "./version.json";
|
import _appVersion from "./data/version.json";
|
||||||
import { rawUpgrades } from "./rawUpgrades";
|
import { rawUpgrades } from "./rawUpgrades";
|
||||||
import { getLevelBackground } from "./getLevelBackground";
|
import { getLevelBackground } from "./getLevelBackground";
|
||||||
|
import { levelIconHTML } from "./levelIcon";
|
||||||
|
|
||||||
const palette = _palette as Palette;
|
const palette = _palette as Palette;
|
||||||
|
|
||||||
const rawLevelsList = _rawLevelsList as RawLevel[];
|
const rawLevelsList = _rawLevelsList as RawLevel[];
|
||||||
|
|
||||||
export const appVersion = _appVersion as string;
|
export const appVersion = _appVersion as string;
|
||||||
|
|
||||||
let levelIconHTMLCanvas = document.createElement("canvas");
|
|
||||||
const levelIconHTMLCanvasCtx = levelIconHTMLCanvas.getContext("2d", {
|
|
||||||
antialias: false,
|
|
||||||
alpha: true,
|
|
||||||
}) as CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
function levelIconHTML(bricks: string[], levelSize: number, color: string) {
|
|
||||||
const size = 40;
|
|
||||||
const c = levelIconHTMLCanvas;
|
|
||||||
const ctx = levelIconHTMLCanvasCtx;
|
|
||||||
c.width = size;
|
|
||||||
c.height = size;
|
|
||||||
|
|
||||||
if (color) {
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.fillRect(0, 0, size, size);
|
|
||||||
} else {
|
|
||||||
ctx.clearRect(0, 0, size, size);
|
|
||||||
}
|
|
||||||
const pxSize = size / levelSize;
|
|
||||||
for (let x = 0; x < levelSize; x++) {
|
|
||||||
for (let y = 0; y < levelSize; y++) {
|
|
||||||
const c = bricks[y * levelSize + x];
|
|
||||||
if (c) {
|
|
||||||
ctx.fillStyle = c;
|
|
||||||
ctx.fillRect(
|
|
||||||
Math.floor(pxSize * x),
|
|
||||||
Math.floor(pxSize * y),
|
|
||||||
Math.ceil(pxSize),
|
|
||||||
Math.ceil(pxSize),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<img alt="" width="${size}" height="${size}" src="${c.toDataURL()}"/>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const icons = {} as { [k: string]: string };
|
export const icons = {} as { [k: string]: string };
|
||||||
|
|
||||||
export const allLevels = rawLevelsList
|
export const allLevels = rawLevelsList
|
||||||
|
|
|
@ -1,104 +1,109 @@
|
||||||
import {GameState, RunParams} from "./types";
|
import { GameState, RunParams } from "./types";
|
||||||
import {getTotalScore} from "./settings";
|
import { getTotalScore } from "./settings";
|
||||||
import {allLevels, upgrades} from "./loadGameData";
|
import { allLevels, upgrades } from "./loadGameData";
|
||||||
import {getPossibleUpgrades, makeEmptyPerksMap, sumOfKeys} from "./game_utils";
|
import {
|
||||||
import {dontOfferTooSoon, resetBalls} from "./gameStateMutators";
|
getPossibleUpgrades,
|
||||||
import {isOptionOn} from "./options";
|
makeEmptyPerksMap,
|
||||||
|
sumOfKeys,
|
||||||
|
} from "./game_utils";
|
||||||
|
import { dontOfferTooSoon, resetBalls } from "./gameStateMutators";
|
||||||
|
import { isOptionOn } from "./options";
|
||||||
|
|
||||||
export function newGameState(params: RunParams): GameState {
|
export function newGameState(params: RunParams): GameState {
|
||||||
const totalScoreAtRunStart = getTotalScore();
|
const totalScoreAtRunStart = getTotalScore();
|
||||||
const firstLevel = params?.level
|
const firstLevel = params?.level
|
||||||
? allLevels.filter((l) => l.name === params?.level)
|
? allLevels.filter((l) => l.name === params?.level)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const restInRandomOrder = allLevels
|
const restInRandomOrder = allLevels
|
||||||
.filter((l) => totalScoreAtRunStart >= l.threshold)
|
.filter((l) => totalScoreAtRunStart >= l.threshold)
|
||||||
.filter((l) => l.name !== params?.level)
|
.filter((l) => l.name !== params?.level)
|
||||||
.filter((l) => l.name !== params?.levelToAvoid)
|
.filter((l) => l.name !== params?.levelToAvoid)
|
||||||
.sort(() => Math.random() - 0.5);
|
.sort(() => Math.random() - 0.5);
|
||||||
|
|
||||||
const runLevels = firstLevel.concat(
|
const runLevels = firstLevel.concat(
|
||||||
restInRandomOrder.slice(0, 7 + 3).sort((a, b) => a.sortKey - b.sortKey),
|
restInRandomOrder.slice(0, 7 + 3).sort((a, b) => a.sortKey - b.sortKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
const perks = {...makeEmptyPerksMap(upgrades), ...(params?.perks || {})};
|
const perks = { ...makeEmptyPerksMap(upgrades), ...(params?.perks || {}) };
|
||||||
|
|
||||||
const gameState: GameState = {
|
const gameState: GameState = {
|
||||||
runLevels,
|
runLevels,
|
||||||
currentLevel: 0,
|
currentLevel: 0,
|
||||||
perks,
|
perks,
|
||||||
puckWidth: 200,
|
puckWidth: 200,
|
||||||
baseSpeed: 12,
|
baseSpeed: 12,
|
||||||
combo: 1,
|
combo: 1,
|
||||||
gridSize: 12,
|
gridSize: 12,
|
||||||
running: false,
|
running: false,
|
||||||
puckPosition: 400,
|
puckPosition: 400,
|
||||||
pauseTimeout: null,
|
pauseTimeout: null,
|
||||||
canvasWidth: 0,
|
canvasWidth: 0,
|
||||||
canvasHeight: 0,
|
canvasHeight: 0,
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
offsetXRoundedDown: 0,
|
offsetXRoundedDown: 0,
|
||||||
gameZoneWidth: 0,
|
gameZoneWidth: 0,
|
||||||
gameZoneWidthRoundedUp: 0,
|
gameZoneWidthRoundedUp: 0,
|
||||||
gameZoneHeight: 0,
|
gameZoneHeight: 0,
|
||||||
brickWidth: 0,
|
brickWidth: 0,
|
||||||
score: 0,
|
score: 0,
|
||||||
lastScoreIncrease: -1000,
|
lastScoreIncrease: -1000,
|
||||||
lastExplosion: -1000,
|
lastExplosion: -1000,
|
||||||
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
|
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
|
||||||
balls: [],
|
balls: [],
|
||||||
ballsColor: "white",
|
ballsColor: "white",
|
||||||
bricks: [],
|
bricks: [],
|
||||||
flashes: [],
|
flashes: [],
|
||||||
coins: [],
|
coins: [],
|
||||||
levelStartScore: 0,
|
levelStartScore: 0,
|
||||||
levelMisses: 0,
|
levelMisses: 0,
|
||||||
levelSpawnedCoins: 0,
|
levelSpawnedCoins: 0,
|
||||||
lastPlayedCoinGrab: 0,
|
lastPlayedCoinGrab: 0,
|
||||||
MAX_COINS: 400,
|
MAX_COINS: 400,
|
||||||
MAX_PARTICLES: 600,
|
MAX_PARTICLES: 600,
|
||||||
puckColor: "#FFF",
|
puckColor: "#FFF",
|
||||||
ballSize: 20,
|
ballSize: 20,
|
||||||
coinSize: 14,
|
coinSize: 14,
|
||||||
puckHeight: 20,
|
puckHeight: 20,
|
||||||
totalScoreAtRunStart,
|
totalScoreAtRunStart,
|
||||||
isCreativeModeRun: sumOfKeys(perks) > 1,
|
isCreativeModeRun: sumOfKeys(perks) > 1,
|
||||||
pauseUsesDuringRun: 0,
|
pauseUsesDuringRun: 0,
|
||||||
keyboardPuckSpeed: 0,
|
keyboardPuckSpeed: 0,
|
||||||
lastTick: performance.now(),
|
lastTick: performance.now(),
|
||||||
lastTickDown: 0,
|
lastTickDown: 0,
|
||||||
runStatistics: {
|
runStatistics: {
|
||||||
started: Date.now(),
|
started: Date.now(),
|
||||||
levelsPlayed: 0,
|
levelsPlayed: 0,
|
||||||
runTime: 0,
|
runTime: 0,
|
||||||
coins_spawned: 0,
|
coins_spawned: 0,
|
||||||
score: 0,
|
score: 0,
|
||||||
bricks_broken: 0,
|
bricks_broken: 0,
|
||||||
misses: 0,
|
misses: 0,
|
||||||
balls_lost: 0,
|
balls_lost: 0,
|
||||||
puck_bounces: 0,
|
puck_bounces: 0,
|
||||||
upgrades_picked: 1,
|
wall_bounces: 0,
|
||||||
max_combo: 1,
|
upgrades_picked: 1,
|
||||||
max_level: 0,
|
max_combo: 1,
|
||||||
},
|
max_level: 0,
|
||||||
lastOffered: {},
|
},
|
||||||
levelTime: 0,
|
lastOffered: {},
|
||||||
autoCleanUses: 0,
|
levelTime: 0,
|
||||||
};
|
autoCleanUses: 0,
|
||||||
resetBalls(gameState);
|
};
|
||||||
|
resetBalls(gameState);
|
||||||
|
|
||||||
if (!sumOfKeys(gameState.perks)) {
|
if (!sumOfKeys(gameState.perks)) {
|
||||||
const giftable = getPossibleUpgrades(gameState).filter((u) => u.giftable);
|
const giftable = getPossibleUpgrades(gameState).filter((u) => u.giftable);
|
||||||
const randomGift =
|
const randomGift =
|
||||||
(isOptionOn("easy") && "slow_down") ||
|
(isOptionOn("easy") && "slow_down") ||
|
||||||
giftable[Math.floor(Math.random() * giftable.length)].id;
|
giftable[Math.floor(Math.random() * giftable.length)].id;
|
||||||
perks[randomGift] = 1;
|
perks[randomGift] = 1;
|
||||||
dontOfferTooSoon(gameState, randomGift);
|
dontOfferTooSoon(gameState, randomGift);
|
||||||
|
}
|
||||||
|
for (let perk of upgrades) {
|
||||||
|
if (gameState.perks[perk.id]) {
|
||||||
|
dontOfferTooSoon(gameState, perk.id);
|
||||||
}
|
}
|
||||||
for (let perk of upgrades) {
|
}
|
||||||
if (gameState.perks[perk.id]) {
|
return gameState;
|
||||||
dontOfferTooSoon(gameState, perk.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gameState;
|
|
||||||
}
|
}
|
|
@ -1,54 +1,57 @@
|
||||||
import {t} from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
|
|
||||||
import {OptionDef, OptionId} from "./types";
|
import { OptionDef, OptionId } from "./types";
|
||||||
import {getSettingValue, setSettingValue} from "./settings";
|
import { getSettingValue, setSettingValue } from "./settings";
|
||||||
|
|
||||||
export const options = {
|
export const options = {
|
||||||
sound: {
|
sound: {
|
||||||
default: true,
|
default: true,
|
||||||
name: t('main_menu.sounds'),
|
name: t("main_menu.sounds"),
|
||||||
help: t('main_menu.sounds_help'),
|
help: t("main_menu.sounds_help"),
|
||||||
disabled: () => false,
|
disabled: () => false,
|
||||||
},
|
},
|
||||||
"mobile-mode": {
|
"mobile-mode": {
|
||||||
default: window.innerHeight > window.innerWidth,
|
default: window.innerHeight > window.innerWidth,
|
||||||
name: t('main_menu.mobile'),
|
name: t("main_menu.mobile"),
|
||||||
help: t('main_menu.mobile_help'),
|
help: t("main_menu.mobile_help"),
|
||||||
disabled: () => false,
|
disabled: () => false,
|
||||||
},
|
},
|
||||||
basic: {
|
basic: {
|
||||||
default: false,
|
default: false,
|
||||||
name: t('main_menu.basic'),
|
name: t("main_menu.basic"),
|
||||||
help: t('main_menu.basic_help'),
|
help: t("main_menu.basic_help"),
|
||||||
disabled: () => false,
|
disabled: () => false,
|
||||||
},
|
},
|
||||||
pointerLock: {
|
pointerLock: {
|
||||||
default: false,
|
default: false,
|
||||||
name: t('main_menu.pointer_lock'),
|
name: t("main_menu.pointer_lock"),
|
||||||
help: t('main_menu.pointer_lock_help'),
|
help: t("main_menu.pointer_lock_help"),
|
||||||
disabled: () => !document.body.requestPointerLock,
|
disabled: () => !document.body.requestPointerLock,
|
||||||
},
|
},
|
||||||
easy: {
|
easy: {
|
||||||
default: false,
|
default: false,
|
||||||
name: t('main_menu.kid'),
|
name: t("main_menu.kid"),
|
||||||
help: t('main_menu.kid_help'),
|
help: t("main_menu.kid_help"),
|
||||||
disabled: () => false,
|
disabled: () => false,
|
||||||
},
|
},
|
||||||
// Could not get the sharing to work without loading androidx and all the modern android things so for now I'll just disable sharing in the android app
|
// Could not get the sharing to work without loading androidx and all the modern android things so for now I'll just disable sharing in the android app
|
||||||
record: {
|
record: {
|
||||||
default: false,
|
default: false,
|
||||||
name: t('main_menu.record'),
|
name: t("main_menu.record"),
|
||||||
help: t('main_menu.record_help'),
|
help: t("main_menu.record_help"),
|
||||||
disabled() {
|
disabled() {
|
||||||
return window.location.search.includes("isInWebView=true");
|
return window.location.search.includes("isInWebView=true");
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
} as const satisfies { [k: string]: OptionDef };
|
} as const satisfies { [k: string]: OptionDef };
|
||||||
|
|
||||||
export function isOptionOn(key: OptionId) {
|
export function isOptionOn(key: OptionId) {
|
||||||
return getSettingValue("breakout-settings-enable-" + key, options[key]?.default)
|
return getSettingValue(
|
||||||
|
"breakout-settings-enable-" + key,
|
||||||
|
options[key]?.default,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleOption(key: OptionId) {
|
export function toggleOption(key: OptionId) {
|
||||||
setSettingValue("breakout-settings-enable-" +key, !isOptionOn(key))
|
setSettingValue("breakout-settings-enable-" + key, !isOptionOn(key));
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import {t} from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
|
|
||||||
export const rawUpgrades = [
|
export const rawUpgrades = [
|
||||||
{
|
{
|
||||||
|
@ -7,9 +7,12 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "extra_life",
|
id: "extra_life",
|
||||||
max: 7,
|
max: 7,
|
||||||
name: t('upgrades.extra_life.name'),
|
name: t("upgrades.extra_life.name"),
|
||||||
help: (lvl: number) => lvl === 1 ? t('upgrades.extra_life.help'): t('upgrades.extra_life.help_plural',{lvl}),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.extra_life.fullHelp'),
|
lvl === 1
|
||||||
|
? t("upgrades.extra_life.help")
|
||||||
|
: t("upgrades.extra_life.help_plural", { lvl }),
|
||||||
|
fullHelp: t("upgrades.extra_life.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -17,9 +20,9 @@ export const rawUpgrades = [
|
||||||
id: "streak_shots",
|
id: "streak_shots",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 1,
|
max: 1,
|
||||||
name: t('upgrades.streak_shots.name'),
|
name: t("upgrades.streak_shots.name"),
|
||||||
help: (lvl: number) => t('upgrades.streak_shots.help',{lvl}) ,
|
help: (lvl: number) => t("upgrades.streak_shots.help", { lvl }),
|
||||||
fullHelp: t('upgrades.streak_shots.fullHelp'),
|
fullHelp: t("upgrades.streak_shots.fullHelp"),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -28,9 +31,10 @@ export const rawUpgrades = [
|
||||||
id: "base_combo",
|
id: "base_combo",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 7,
|
max: 7,
|
||||||
name: t('upgrades.base_combo.name'),
|
name: t("upgrades.base_combo.name"),
|
||||||
help: (lvl: number) => t('upgrades.base_combo.help',{coins:1 + lvl * 3}),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.base_combo.fullHelp'),
|
t("upgrades.base_combo.help", { coins: 1 + lvl * 3 }),
|
||||||
|
fullHelp: t("upgrades.base_combo.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -38,9 +42,9 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "slow_down",
|
id: "slow_down",
|
||||||
max: 2,
|
max: 2,
|
||||||
name: t('upgrades.slow_down.name'),
|
name: t("upgrades.slow_down.name"),
|
||||||
help: () => t('upgrades.slow_down.help' ),
|
help: () => t("upgrades.slow_down.help"),
|
||||||
fullHelp: t('upgrades.slow_down.fullHelp'),
|
fullHelp: t("upgrades.slow_down.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -48,9 +52,9 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "bigger_puck",
|
id: "bigger_puck",
|
||||||
max: 2,
|
max: 2,
|
||||||
name: t('upgrades.bigger_puck.name'),
|
name: t("upgrades.bigger_puck.name"),
|
||||||
help: () => t('upgrades.bigger_puck.help' ),
|
help: () => t("upgrades.bigger_puck.help"),
|
||||||
fullHelp: t('upgrades.bigger_puck.fullHelp'),
|
fullHelp: t("upgrades.bigger_puck.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -59,9 +63,9 @@ export const rawUpgrades = [
|
||||||
id: "viscosity",
|
id: "viscosity",
|
||||||
max: 3,
|
max: 3,
|
||||||
|
|
||||||
name: t('upgrades.viscosity.name'),
|
name: t("upgrades.viscosity.name"),
|
||||||
help: () => t('upgrades.viscosity.help' ),
|
help: () => t("upgrades.viscosity.help"),
|
||||||
fullHelp: t('upgrades.viscosity.fullHelp'),
|
fullHelp: t("upgrades.viscosity.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -70,10 +74,9 @@ export const rawUpgrades = [
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 1,
|
max: 1,
|
||||||
|
|
||||||
name: t('upgrades.left_is_lava.name'),
|
name: t("upgrades.left_is_lava.name"),
|
||||||
help: () => t('upgrades.left_is_lava.help' ),
|
help: () => t("upgrades.left_is_lava.help"),
|
||||||
fullHelp: t('upgrades.left_is_lava.fullHelp'),
|
fullHelp: t("upgrades.left_is_lava.fullHelp"),
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -81,10 +84,9 @@ export const rawUpgrades = [
|
||||||
id: "right_is_lava",
|
id: "right_is_lava",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 1,
|
max: 1,
|
||||||
name: t('upgrades.right_is_lava.name'),
|
name: t("upgrades.right_is_lava.name"),
|
||||||
help: () => t('upgrades.right_is_lava.help' ),
|
help: () => t("upgrades.right_is_lava.help"),
|
||||||
fullHelp: t('upgrades.right_is_lava.fullHelp'),
|
fullHelp: t("upgrades.right_is_lava.fullHelp"),
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -92,10 +94,9 @@ export const rawUpgrades = [
|
||||||
id: "top_is_lava",
|
id: "top_is_lava",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 1,
|
max: 1,
|
||||||
name: t('upgrades.top_is_lava.name'),
|
name: t("upgrades.top_is_lava.name"),
|
||||||
help: () => t('upgrades.top_is_lava.help' ),
|
help: () => t("upgrades.top_is_lava.help"),
|
||||||
fullHelp: t('upgrades.top_is_lava.fullHelp'),
|
fullHelp: t("upgrades.top_is_lava.fullHelp"),
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -103,10 +104,12 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "skip_last",
|
id: "skip_last",
|
||||||
max: 7,
|
max: 7,
|
||||||
name: t('upgrades.skip_last.name'),
|
name: t("upgrades.skip_last.name"),
|
||||||
help: (lvl: number) => lvl==1 ? t('upgrades.skip_last.help' ) : t('upgrades.skip_last.help_plural', {lvl} ),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.skip_last.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.skip_last.help")
|
||||||
|
: t("upgrades.skip_last.help_plural", { lvl }),
|
||||||
|
fullHelp: t("upgrades.skip_last.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -114,9 +117,12 @@ export const rawUpgrades = [
|
||||||
id: "telekinesis",
|
id: "telekinesis",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 2,
|
max: 2,
|
||||||
name: t('upgrades.telekinesis.name'),
|
name: t("upgrades.telekinesis.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.telekinesis.help'): t('upgrades.telekinesis.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.telekinesis.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.telekinesis.help")
|
||||||
|
: t("upgrades.telekinesis.help_plural"),
|
||||||
|
fullHelp: t("upgrades.telekinesis.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -124,9 +130,12 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "coin_magnet",
|
id: "coin_magnet",
|
||||||
max: 3,
|
max: 3,
|
||||||
name: t('upgrades.coin_magnet.name'),
|
name: t("upgrades.coin_magnet.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.coin_magnet.help'): t('upgrades.coin_magnet.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.coin_magnet.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.coin_magnet.help")
|
||||||
|
: t("upgrades.coin_magnet.help_plural"),
|
||||||
|
fullHelp: t("upgrades.coin_magnet.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -134,11 +143,9 @@ export const rawUpgrades = [
|
||||||
id: "multiball",
|
id: "multiball",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 6,
|
max: 6,
|
||||||
name: t('upgrades.multiball.name'),
|
name: t("upgrades.multiball.name"),
|
||||||
help: (lvl: number) => t('upgrades.multiball.help',{count:lvl+1}) ,
|
help: (lvl: number) => t("upgrades.multiball.help", { count: lvl + 1 }),
|
||||||
fullHelp: t('upgrades.multiball.fullHelp'),
|
fullHelp: t("upgrades.multiball.fullHelp"),
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -146,11 +153,12 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "smaller_puck",
|
id: "smaller_puck",
|
||||||
max: 2,
|
max: 2,
|
||||||
name: t('upgrades.smaller_puck.name'),
|
name: t("upgrades.smaller_puck.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.smaller_puck.help'): t('upgrades.smaller_puck.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.smaller_puck.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.smaller_puck.help")
|
||||||
|
: t("upgrades.smaller_puck.help_plural"),
|
||||||
|
fullHelp: t("upgrades.smaller_puck.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -158,9 +166,9 @@ export const rawUpgrades = [
|
||||||
id: "pierce",
|
id: "pierce",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 3,
|
max: 3,
|
||||||
name: t('upgrades.pierce.name'),
|
name: t("upgrades.pierce.name"),
|
||||||
help: (lvl: number) => t('upgrades.pierce.help',{count:3 * lvl}) ,
|
help: (lvl: number) => t("upgrades.pierce.help", { count: 3 * lvl }),
|
||||||
fullHelp: t('upgrades.pierce.fullHelp'),
|
fullHelp: t("upgrades.pierce.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -168,10 +176,9 @@ export const rawUpgrades = [
|
||||||
id: "picky_eater",
|
id: "picky_eater",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 1,
|
max: 1,
|
||||||
name: t('upgrades.picky_eater.name'),
|
name: t("upgrades.picky_eater.name"),
|
||||||
help: (lvl: number) => t('upgrades.picky_eater.help') ,
|
help: (lvl: number) => t("upgrades.picky_eater.help"),
|
||||||
fullHelp: t('upgrades.picky_eater.fullHelp'),
|
fullHelp: t("upgrades.picky_eater.fullHelp"),
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -179,11 +186,9 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "metamorphosis",
|
id: "metamorphosis",
|
||||||
max: 1,
|
max: 1,
|
||||||
name: t('upgrades.metamorphosis.name'),
|
name: t("upgrades.metamorphosis.name"),
|
||||||
help: (lvl: number) => t('upgrades.metamorphosis.help'),
|
help: (lvl: number) => t("upgrades.metamorphosis.help"),
|
||||||
fullHelp: t('upgrades.metamorphosis.fullHelp'),
|
fullHelp: t("upgrades.metamorphosis.fullHelp"),
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -191,9 +196,9 @@ export const rawUpgrades = [
|
||||||
id: "compound_interest",
|
id: "compound_interest",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 1,
|
max: 1,
|
||||||
name: t('upgrades.compound_interest.name'),
|
name: t("upgrades.compound_interest.name"),
|
||||||
help: (lvl: number) => t('upgrades.compound_interest.help') ,
|
help: (lvl: number) => t("upgrades.compound_interest.help"),
|
||||||
fullHelp: t('upgrades.compound_interest.fullHelp'),
|
fullHelp: t("upgrades.compound_interest.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -201,13 +206,13 @@ export const rawUpgrades = [
|
||||||
id: "hot_start",
|
id: "hot_start",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 3,
|
max: 3,
|
||||||
name: t('upgrades.hot_start.name'),
|
name: t("upgrades.hot_start.name"),
|
||||||
help: (lvl: number) => t('upgrades.hot_start.help',{
|
help: (lvl: number) =>
|
||||||
start:lvl * 15 + 1,
|
t("upgrades.hot_start.help", {
|
||||||
lvl
|
start: lvl * 15 + 1,
|
||||||
}),
|
lvl,
|
||||||
fullHelp: t('upgrades.hot_start.fullHelp'),
|
}),
|
||||||
|
fullHelp: t("upgrades.hot_start.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -215,9 +220,12 @@ export const rawUpgrades = [
|
||||||
id: "sapper",
|
id: "sapper",
|
||||||
giftable: true,
|
giftable: true,
|
||||||
max: 7,
|
max: 7,
|
||||||
name: t('upgrades.sapper.name'),
|
name: t("upgrades.sapper.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.sapper.help'): t('upgrades.sapper.help_plural',{lvl}),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.sapper.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.sapper.help")
|
||||||
|
: t("upgrades.sapper.help_plural", { lvl }),
|
||||||
|
fullHelp: t("upgrades.sapper.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -225,9 +233,9 @@ export const rawUpgrades = [
|
||||||
id: "bigger_explosions",
|
id: "bigger_explosions",
|
||||||
giftable: false,
|
giftable: false,
|
||||||
max: 1,
|
max: 1,
|
||||||
name: t('upgrades.bigger_explosions.name'),
|
name: t("upgrades.bigger_explosions.name"),
|
||||||
help: (lvl: number) => t('upgrades.bigger_explosions.help'),
|
help: (lvl: number) => t("upgrades.bigger_explosions.help"),
|
||||||
fullHelp: t('upgrades.bigger_explosions.fullHelp'),
|
fullHelp: t("upgrades.bigger_explosions.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -235,9 +243,9 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "extra_levels",
|
id: "extra_levels",
|
||||||
max: 3,
|
max: 3,
|
||||||
name: t('upgrades.extra_levels.name'),
|
name: t("upgrades.extra_levels.name"),
|
||||||
help: (lvl: number) => t('upgrades.extra_levels.help',{count:lvl + 7}) ,
|
help: (lvl: number) => t("upgrades.extra_levels.help", { count: lvl + 7 }),
|
||||||
fullHelp: t('upgrades.extra_levels.fullHelp'),
|
fullHelp: t("upgrades.extra_levels.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -245,10 +253,9 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "pierce_color",
|
id: "pierce_color",
|
||||||
max: 1,
|
max: 1,
|
||||||
name: t('upgrades.pierce_color.name'),
|
name: t("upgrades.pierce_color.name"),
|
||||||
help: (lvl: number) => t('upgrades.pierce_color.help') ,
|
help: (lvl: number) => t("upgrades.pierce_color.help"),
|
||||||
fullHelp: t('upgrades.pierce_color.fullHelp'),
|
fullHelp: t("upgrades.pierce_color.fullHelp"),
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -256,11 +263,9 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "soft_reset",
|
id: "soft_reset",
|
||||||
max: 9,
|
max: 9,
|
||||||
name: t('upgrades.soft_reset.name'),
|
name: t("upgrades.soft_reset.name"),
|
||||||
help: (lvl: number) => t('upgrades.soft_reset.help',{percent:10*lvl}),
|
help: (lvl: number) => t("upgrades.soft_reset.help", { percent: 10 * lvl }),
|
||||||
fullHelp: t('upgrades.soft_reset.fullHelp'),
|
fullHelp: t("upgrades.soft_reset.fullHelp"),
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "multiball",
|
requires: "multiball",
|
||||||
|
@ -268,11 +273,12 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "ball_repulse_ball",
|
id: "ball_repulse_ball",
|
||||||
max: 3,
|
max: 3,
|
||||||
name: t('upgrades.ball_repulse_ball.name'),
|
name: t("upgrades.ball_repulse_ball.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.ball_repulse_ball.help'): t('upgrades.ball_repulse_ball.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.ball_repulse_ball.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.ball_repulse_ball.help")
|
||||||
|
: t("upgrades.ball_repulse_ball.help_plural"),
|
||||||
|
fullHelp: t("upgrades.ball_repulse_ball.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "multiball",
|
requires: "multiball",
|
||||||
|
@ -280,9 +286,12 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "ball_attract_ball",
|
id: "ball_attract_ball",
|
||||||
max: 3,
|
max: 3,
|
||||||
name: t('upgrades.ball_attract_ball.name'),
|
name: t("upgrades.ball_attract_ball.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.ball_attract_ball.help'): t('upgrades.ball_attract_ball.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.ball_attract_ball.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.ball_attract_ball.help")
|
||||||
|
: t("upgrades.ball_attract_ball.help_plural"),
|
||||||
|
fullHelp: t("upgrades.ball_attract_ball.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -290,10 +299,12 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "puck_repulse_ball",
|
id: "puck_repulse_ball",
|
||||||
max: 2,
|
max: 2,
|
||||||
name: t('upgrades.puck_repulse_ball.name'),
|
name: t("upgrades.puck_repulse_ball.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.puck_repulse_ball.help'): t('upgrades.puck_repulse_ball.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.puck_repulse_ball.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.puck_repulse_ball.help")
|
||||||
|
: t("upgrades.puck_repulse_ball.help_plural"),
|
||||||
|
fullHelp: t("upgrades.puck_repulse_ball.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -301,11 +312,10 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "wind",
|
id: "wind",
|
||||||
max: 3,
|
max: 3,
|
||||||
name: t('upgrades.wind.name'),
|
name: t("upgrades.wind.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.wind.help'): t('upgrades.wind.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.wind.fullHelp'),
|
lvl == 1 ? t("upgrades.wind.help") : t("upgrades.wind.help_plural"),
|
||||||
|
fullHelp: t("upgrades.wind.fullHelp"),
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -313,10 +323,12 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "sturdy_bricks",
|
id: "sturdy_bricks",
|
||||||
max: 4,
|
max: 4,
|
||||||
name: t('upgrades.telekinesis.name'),
|
name: t("upgrades.telekinesis.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.telekinesis.help'): t('upgrades.telekinesis.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.telekinesis.fullHelp'),
|
lvl == 1
|
||||||
|
? t("upgrades.telekinesis.help")
|
||||||
|
: t("upgrades.telekinesis.help_plural"),
|
||||||
|
fullHelp: t("upgrades.telekinesis.fullHelp"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -324,11 +336,10 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "respawn",
|
id: "respawn",
|
||||||
max: 4,
|
max: 4,
|
||||||
name: t('upgrades.respawn.name'),
|
name: t("upgrades.respawn.name"),
|
||||||
help: (lvl: number) => lvl == 1 ? t('upgrades.respawn.help'): t('upgrades.respawn.help_plural'),
|
help: (lvl: number) =>
|
||||||
fullHelp: t('upgrades.respawn.fullHelp'),
|
lvl == 1 ? t("upgrades.respawn.help") : t("upgrades.respawn.help_plural"),
|
||||||
|
fullHelp: t("upgrades.respawn.fullHelp"),
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -336,10 +347,9 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "one_more_choice",
|
id: "one_more_choice",
|
||||||
max: 3,
|
max: 3,
|
||||||
name: t('upgrades.one_more_choice.name'),
|
name: t("upgrades.one_more_choice.name"),
|
||||||
help: (lvl: number) => t('upgrades.one_more_choice.help'),
|
help: (lvl: number) => t("upgrades.one_more_choice.help"),
|
||||||
fullHelp: t('upgrades.one_more_choice.fullHelp'),
|
fullHelp: t("upgrades.one_more_choice.fullHelp"),
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
requires: "",
|
requires: "",
|
||||||
|
@ -347,9 +357,18 @@ export const rawUpgrades = [
|
||||||
giftable: false,
|
giftable: false,
|
||||||
id: "instant_upgrade",
|
id: "instant_upgrade",
|
||||||
max: 2,
|
max: 2,
|
||||||
name: t('upgrades.instant_upgrade.name'),
|
name: t("upgrades.instant_upgrade.name"),
|
||||||
help: (lvl: number) => t('upgrades.instant_upgrade.help') ,
|
help: (lvl: number) => t("upgrades.instant_upgrade.help"),
|
||||||
fullHelp: t('upgrades.instant_upgrade.fullHelp'),
|
fullHelp: t("upgrades.instant_upgrade.fullHelp"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
requires: "",
|
||||||
|
threshold: 60000,
|
||||||
|
giftable: false,
|
||||||
|
id: "concave_puck",
|
||||||
|
max: 1,
|
||||||
|
name: t("upgrades.concave_puck.name"),
|
||||||
|
help: (lvl: number) => t("upgrades.concave_puck.help"),
|
||||||
|
fullHelp: t("upgrades.concave_puck.fullHelp"),
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
282
src/recording.ts
|
@ -1,168 +1,168 @@
|
||||||
import {gameCanvas} from "./render";
|
import { gameCanvas } from "./render";
|
||||||
import {max_levels} from "./game_utils";
|
import { max_levels } from "./game_utils";
|
||||||
import {getAudioRecordingTrack} from "./sounds";
|
import { getAudioRecordingTrack } from "./sounds";
|
||||||
import {t} from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
import {GameState} from "./types";
|
import { GameState } from "./types";
|
||||||
import {isOptionOn} from "./options";
|
import { isOptionOn } from "./options";
|
||||||
|
|
||||||
let mediaRecorder: MediaRecorder | null,
|
let mediaRecorder: MediaRecorder | null,
|
||||||
captureStream: MediaStream,
|
captureStream: MediaStream,
|
||||||
captureTrack: CanvasCaptureMediaStreamTrack,
|
captureTrack: CanvasCaptureMediaStreamTrack,
|
||||||
recordCanvas: HTMLCanvasElement,
|
recordCanvas: HTMLCanvasElement,
|
||||||
recordCanvasCtx: CanvasRenderingContext2D;
|
recordCanvasCtx: CanvasRenderingContext2D;
|
||||||
|
|
||||||
export function recordOneFrame(gameState:GameState) {
|
export function recordOneFrame(gameState: GameState) {
|
||||||
if (!isOptionOn("record")) {
|
if (!isOptionOn("record")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!gameState.running) return;
|
if (!gameState.running) return;
|
||||||
if (!captureStream) return;
|
if (!captureStream) return;
|
||||||
drawMainCanvasOnSmallCanvas(gameState);
|
drawMainCanvasOnSmallCanvas(gameState);
|
||||||
if (captureTrack?.requestFrame) {
|
if (captureTrack?.requestFrame) {
|
||||||
captureTrack?.requestFrame();
|
captureTrack?.requestFrame();
|
||||||
} else if (captureStream?.requestFrame) {
|
} else if (captureStream?.requestFrame) {
|
||||||
captureStream.requestFrame();
|
captureStream.requestFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function drawMainCanvasOnSmallCanvas(gameState:GameState) {
|
export function drawMainCanvasOnSmallCanvas(gameState: GameState) {
|
||||||
if (!recordCanvasCtx) return;
|
if (!recordCanvasCtx) return;
|
||||||
recordCanvasCtx.drawImage(
|
recordCanvasCtx.drawImage(
|
||||||
gameCanvas,
|
gameCanvas,
|
||||||
gameState.offsetXRoundedDown,
|
gameState.offsetXRoundedDown,
|
||||||
0,
|
0,
|
||||||
gameState.gameZoneWidthRoundedUp,
|
gameState.gameZoneWidthRoundedUp,
|
||||||
gameState.gameZoneHeight,
|
gameState.gameZoneHeight,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
recordCanvas.width,
|
recordCanvas.width,
|
||||||
recordCanvas.height,
|
recordCanvas.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Here we don't use drawText as we don't want to cache a picture for each distinct value of score
|
// Here we don't use drawText as we don't want to cache a picture for each distinct value of score
|
||||||
recordCanvasCtx.fillStyle = "#FFF";
|
recordCanvasCtx.fillStyle = "#FFF";
|
||||||
recordCanvasCtx.textBaseline = "top";
|
recordCanvasCtx.textBaseline = "top";
|
||||||
recordCanvasCtx.font = "12px monospace";
|
recordCanvasCtx.font = "12px monospace";
|
||||||
recordCanvasCtx.textAlign = "right";
|
recordCanvasCtx.textAlign = "right";
|
||||||
recordCanvasCtx.fillText(
|
recordCanvasCtx.fillText(
|
||||||
gameState.score.toString(),
|
gameState.score.toString(),
|
||||||
recordCanvas.width - 12,
|
recordCanvas.width - 12,
|
||||||
12,
|
12,
|
||||||
);
|
);
|
||||||
|
|
||||||
recordCanvasCtx.textAlign = "left";
|
recordCanvasCtx.textAlign = "left";
|
||||||
recordCanvasCtx.fillText(
|
recordCanvasCtx.fillText(
|
||||||
"Level " + (gameState.currentLevel + 1) + "/" + max_levels(gameState),
|
"Level " + (gameState.currentLevel + 1) + "/" + max_levels(gameState),
|
||||||
12,
|
12,
|
||||||
12,
|
12,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startRecordingGame(gameState:GameState) {
|
export function startRecordingGame(gameState: GameState) {
|
||||||
if (!isOptionOn("record")) {
|
if (!isOptionOn("record")) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
if (mediaRecorder) return;
|
||||||
|
if (!recordCanvas) {
|
||||||
|
// Smaller canvas with fewer details
|
||||||
|
recordCanvas = document.createElement("canvas");
|
||||||
|
recordCanvasCtx = recordCanvas.getContext("2d", {
|
||||||
|
antialias: false,
|
||||||
|
alpha: false,
|
||||||
|
}) as CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
captureStream = recordCanvas.captureStream(0);
|
||||||
|
captureTrack =
|
||||||
|
captureStream.getVideoTracks()[0] as CanvasCaptureMediaStreamTrack;
|
||||||
|
|
||||||
|
const track = getAudioRecordingTrack();
|
||||||
|
if (track) {
|
||||||
|
captureStream.addTrack(track.stream.getAudioTracks()[0]);
|
||||||
}
|
}
|
||||||
if (mediaRecorder) return;
|
}
|
||||||
if (!recordCanvas) {
|
|
||||||
// Smaller canvas with fewer details
|
|
||||||
recordCanvas = document.createElement("canvas");
|
|
||||||
recordCanvasCtx = recordCanvas.getContext("2d", {
|
|
||||||
antialias: false,
|
|
||||||
alpha: false,
|
|
||||||
}) as CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
captureStream = recordCanvas.captureStream(0);
|
recordCanvas.width = gameState.gameZoneWidthRoundedUp;
|
||||||
captureTrack =
|
recordCanvas.height = gameState.gameZoneHeight;
|
||||||
captureStream.getVideoTracks()[0] as CanvasCaptureMediaStreamTrack;
|
|
||||||
|
|
||||||
const track = getAudioRecordingTrack();
|
// drawMainCanvasOnSmallCanvas()
|
||||||
if (track) {
|
const recordedChunks: Blob[] = [];
|
||||||
captureStream.addTrack(track.stream.getAudioTracks()[0]);
|
|
||||||
}
|
const instance = new MediaRecorder(captureStream, {
|
||||||
|
videoBitsPerSecond: 3500000,
|
||||||
|
});
|
||||||
|
mediaRecorder = instance;
|
||||||
|
instance.start();
|
||||||
|
mediaRecorder.pause();
|
||||||
|
instance.ondataavailable = function (event) {
|
||||||
|
recordedChunks.push(event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.onstop = async function () {
|
||||||
|
let targetDiv: HTMLElement | null;
|
||||||
|
let blob = new Blob(recordedChunks, { type: "video/webm" });
|
||||||
|
if (blob.size < 200000) return; // under 0.2MB, probably bugged out or pointlessly short
|
||||||
|
|
||||||
|
while (
|
||||||
|
!(targetDiv = document.getElementById("level-recording-container"))
|
||||||
|
) {
|
||||||
|
await new Promise((r) => setTimeout(r, 200));
|
||||||
}
|
}
|
||||||
|
const video = document.createElement("video");
|
||||||
|
video.autoplay = true;
|
||||||
|
video.controls = false;
|
||||||
|
video.disablePictureInPicture = true;
|
||||||
|
video.disableRemotePlayback = true;
|
||||||
|
video.width = recordCanvas.width;
|
||||||
|
video.height = recordCanvas.height;
|
||||||
|
video.loop = true;
|
||||||
|
video.muted = true;
|
||||||
|
video.playsInline = true;
|
||||||
|
video.src = URL.createObjectURL(blob);
|
||||||
|
|
||||||
recordCanvas.width = gameState.gameZoneWidthRoundedUp;
|
const a = document.createElement("a");
|
||||||
recordCanvas.height = gameState.gameZoneHeight;
|
a.download = captureFileName("webm");
|
||||||
|
a.target = "_blank";
|
||||||
// drawMainCanvasOnSmallCanvas()
|
a.href = video.src;
|
||||||
const recordedChunks: Blob[] = [];
|
a.textContent = t("main_menu.record_download", {
|
||||||
|
size: (blob.size / 1000000).toFixed(2),
|
||||||
const instance = new MediaRecorder(captureStream, {
|
|
||||||
videoBitsPerSecond: 3500000,
|
|
||||||
});
|
});
|
||||||
mediaRecorder = instance;
|
targetDiv.appendChild(video);
|
||||||
instance.start();
|
targetDiv.appendChild(a);
|
||||||
mediaRecorder.pause();
|
};
|
||||||
instance.ondataavailable = function (event) {
|
|
||||||
recordedChunks.push(event.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
instance.onstop = async function () {
|
|
||||||
let targetDiv: HTMLElement | null;
|
|
||||||
let blob = new Blob(recordedChunks, {type: "video/webm"});
|
|
||||||
if (blob.size < 200000) return; // under 0.2MB, probably bugged out or pointlessly short
|
|
||||||
|
|
||||||
while (
|
|
||||||
!(targetDiv = document.getElementById("level-recording-container"))
|
|
||||||
) {
|
|
||||||
await new Promise((r) => setTimeout(r, 200));
|
|
||||||
}
|
|
||||||
const video = document.createElement("video");
|
|
||||||
video.autoplay = true;
|
|
||||||
video.controls = false;
|
|
||||||
video.disablePictureInPicture = true;
|
|
||||||
video.disableRemotePlayback = true;
|
|
||||||
video.width = recordCanvas.width;
|
|
||||||
video.height = recordCanvas.height;
|
|
||||||
video.loop = true;
|
|
||||||
video.muted = true;
|
|
||||||
video.playsInline = true;
|
|
||||||
video.src = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.download = captureFileName("webm");
|
|
||||||
a.target = "_blank";
|
|
||||||
a.href = video.src;
|
|
||||||
a.textContent = t('main_menu.record_download', {
|
|
||||||
size: (blob.size / 1000000).toFixed(2)
|
|
||||||
});
|
|
||||||
targetDiv.appendChild(video);
|
|
||||||
targetDiv.appendChild(a);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pauseRecording( ) {
|
export function pauseRecording() {
|
||||||
if (!isOptionOn("record")) {
|
if (!isOptionOn("record")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mediaRecorder?.state === "recording") {
|
if (mediaRecorder?.state === "recording") {
|
||||||
mediaRecorder?.pause();
|
mediaRecorder?.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resumeRecording() {
|
export function resumeRecording() {
|
||||||
if (!isOptionOn("record")) {
|
if (!isOptionOn("record")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mediaRecorder?.state === "paused") {
|
if (mediaRecorder?.state === "paused") {
|
||||||
mediaRecorder.resume();
|
mediaRecorder.resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopRecording() {
|
export function stopRecording() {
|
||||||
if (!isOptionOn("record")) {
|
if (!isOptionOn("record")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!mediaRecorder) return;
|
if (!mediaRecorder) return;
|
||||||
mediaRecorder?.stop();
|
mediaRecorder?.stop();
|
||||||
mediaRecorder = null;
|
mediaRecorder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function captureFileName(ext = "webm") {
|
export function captureFileName(ext = "webm") {
|
||||||
return (
|
return (
|
||||||
"breakout-71-capture-" +
|
"breakout-71-capture-" +
|
||||||
new Date().toISOString().replace(/[^0-9\-]+/gi, "-") +
|
new Date().toISOString().replace(/[^0-9\-]+/gi, "-") +
|
||||||
"." +
|
"." +
|
||||||
ext
|
ext
|
||||||
);
|
);
|
||||||
}
|
}
|
1268
src/render.ts
|
@ -1,37 +1,35 @@
|
||||||
// Settings
|
// Settings
|
||||||
|
|
||||||
import {GameState} from "./types";
|
import { GameState } from "./types";
|
||||||
|
|
||||||
let cachedSettings: { [key: string]: unknown } = {};
|
let cachedSettings: { [key: string]: unknown } = {};
|
||||||
|
|
||||||
export function getSettingValue<T>(key: string, defaultValue: T) {
|
export function getSettingValue<T>(key: string, defaultValue: T) {
|
||||||
if (typeof cachedSettings[key] == "undefined") {
|
if (typeof cachedSettings[key] == "undefined") {
|
||||||
try {
|
try {
|
||||||
const ls = localStorage.getItem( key);
|
const ls = localStorage.getItem(key);
|
||||||
if (ls) cachedSettings[key] = JSON.parse(ls) as T;
|
if (ls) cachedSettings[key] = JSON.parse(ls) as T;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return cachedSettings[key] as T ?? defaultValue;
|
}
|
||||||
|
return (cachedSettings[key] as T) ?? defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setSettingValue<T>(key: string, value: T) {
|
export function setSettingValue<T>(key: string, value: T) {
|
||||||
cachedSettings[key] = value
|
cachedSettings[key] = value;
|
||||||
try {
|
try {
|
||||||
localStorage.setItem( key, JSON.stringify(value));
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTotalScore() {
|
export function getTotalScore() {
|
||||||
return getSettingValue('breakout_71_total_score', 0)
|
return getSettingValue("breakout_71_total_score", 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addToTotalScore(gameState: GameState, points: number) {
|
export function addToTotalScore(gameState: GameState, points: number) {
|
||||||
if (gameState.isCreativeModeRun) return;
|
if (gameState.isCreativeModeRun) return;
|
||||||
setSettingValue('breakout_71_total_score', getTotalScore() + points)
|
setSettingValue("breakout_71_total_score", getTotalScore() + points);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { gameState } from "./game";
|
import { gameState } from "./game";
|
||||||
|
|
||||||
|
import { isOptionOn } from "./options";
|
||||||
import {isOptionOn} from "./options";
|
|
||||||
|
|
||||||
export const sounds = {
|
export const sounds = {
|
||||||
wallBeep: (pan: number) => {
|
wallBeep: (pan: number) => {
|
||||||
|
|
7
src/types.d.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
import {rawUpgrades} from "./rawUpgrades";
|
import { rawUpgrades } from "./rawUpgrades";
|
||||||
import {options} from "./options";
|
import { options } from "./options";
|
||||||
|
|
||||||
export type colorString = string;
|
export type colorString = string;
|
||||||
|
|
||||||
|
@ -98,7 +98,6 @@ export type Ball = {
|
||||||
piercedSinceBounce: number;
|
piercedSinceBounce: number;
|
||||||
hitSinceBounce: number;
|
hitSinceBounce: number;
|
||||||
hitItem: { index: number; color: string }[];
|
hitItem: { index: number; color: string }[];
|
||||||
bouncesList: { x: number; y: number }[];
|
|
||||||
sapperUses: number;
|
sapperUses: number;
|
||||||
destroyed?: boolean;
|
destroyed?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -141,6 +140,7 @@ export type RunStats = {
|
||||||
misses: number;
|
misses: number;
|
||||||
balls_lost: number;
|
balls_lost: number;
|
||||||
puck_bounces: number;
|
puck_bounces: number;
|
||||||
|
wall_bounces: number;
|
||||||
upgrades_picked: number;
|
upgrades_picked: number;
|
||||||
max_combo: number;
|
max_combo: number;
|
||||||
max_level: number;
|
max_level: number;
|
||||||
|
@ -233,6 +233,7 @@ export type GameState = {
|
||||||
runStatistics: RunStats;
|
runStatistics: RunStats;
|
||||||
lastOffered: Partial<{ [k in PerkId]: number }>;
|
lastOffered: Partial<{ [k in PerkId]: number }>;
|
||||||
levelTime: number;
|
levelTime: number;
|
||||||
|
levelWallBounces: number;
|
||||||
autoCleanUses: number;
|
autoCleanUses: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
"29033878"
|
|