Build 29035725
13
Readme.md
|
@ -47,6 +47,7 @@ There's also an easy mode for kids (slower ball).
|
|||
- translation
|
||||
- when game resumes near bottom, be unvulnerable for .5s ? , once per level
|
||||
|
||||
|
||||
# Game engine features
|
||||
|
||||
- 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
|
||||
- coins that hit the puck disappear, missed ones are scored
|
||||
- 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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
# increase skill ceiling
|
||||
|
||||
- reroll mechanic, rerolls are reward for better play
|
||||
- make puck smaller as combo increases ?
|
||||
- nerf coin magnet :
|
||||
- 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)
|
||||
|
||||
* 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"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 29033878
|
||||
versionName = "29033878"
|
||||
versionCode = 29035725
|
||||
versionName = "29035725"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
|
|
9
build.sh
|
@ -35,10 +35,10 @@ sed -i -e "s/^[[:space:]]*versionCode = .*/ versionCode = $versionCode/"
|
|||
-e "s/^[[:space:]]*versionName = .*/ versionName = \"$versionCode\"/" \
|
||||
./app/build.gradle.kts
|
||||
|
||||
echo "\"$versionCode\"" > src/version.json
|
||||
echo "\"$versionCode\"" > src/data/version.json
|
||||
|
||||
# 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/
|
||||
|
||||
npm run build
|
||||
|
||||
npx jest
|
||||
rm -rf dist/*
|
||||
npx parcel build src/index.html
|
||||
rm -rf ./app/src/main/assets/*
|
||||
cp public/* dist
|
||||
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;
|
||||
}
|
||||
}
|
||||
})({"9zw4T":[function(require,module,exports,__globalThis) {
|
||||
require("6f2db3dd8d20283b")(require("27e61996b32b4a9a").getBundleURL('ouAZg') + "index.c0fd3053.js");
|
||||
})({"7Iayr":[function(require,module,exports,__globalThis) {
|
||||
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";
|
||||
var cacheLoader = require("ca2a84f7fa4a3bb0");
|
||||
module.exports = cacheLoader(function(bundle) {
|
||||
|
@ -242,16 +242,16 @@ exports.getBundleURL = getBundleURLCached;
|
|||
exports.getBaseURL = getBaseURL;
|
||||
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 _jsxDevRuntime = require("react/jsx-dev-runtime");
|
||||
var _backgroundsJson = require("./backgrounds.json");
|
||||
var _backgroundsJson = require("../data/backgrounds.json");
|
||||
var _backgroundsJsonDefault = parcelHelpers.interopDefault(_backgroundsJson);
|
||||
var _paletteJson = require("./palette.json");
|
||||
var _paletteJson = require("../data/palette.json");
|
||||
var _paletteJsonDefault = parcelHelpers.interopDefault(_paletteJson);
|
||||
var _levelsJson = require("./levels.json");
|
||||
var _levelsJson = require("../data/levels.json");
|
||||
var _levelsJsonDefault = parcelHelpers.interopDefault(_levelsJson);
|
||||
var _getLevelBackground = require("./getLevelBackground");
|
||||
var _getLevelBackground = require("../getLevelBackground");
|
||||
var _client = require("react-dom/client");
|
||||
var _react = require("react");
|
||||
var _levelsEditorUtil = require("./levels_editor_util");
|
||||
|
@ -273,7 +273,7 @@ function App() {
|
|||
}, []);
|
||||
(0, _react.useEffect)(()=>{
|
||||
const timoutId = setTimeout(()=>{
|
||||
return fetch("http://localhost:4400/src/levels.json", {
|
||||
return fetch("http://localhost:4400/src/data/levels.json", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "text/plain"
|
||||
|
@ -317,7 +317,7 @@ function App() {
|
|||
position: "absolute"
|
||||
}
|
||||
}, index, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 63,
|
||||
columnNumber: 17
|
||||
}, this));
|
||||
|
@ -338,7 +338,7 @@ function App() {
|
|||
name: e.target.value
|
||||
})
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 99,
|
||||
columnNumber: 15
|
||||
}, this),
|
||||
|
@ -348,7 +348,7 @@ function App() {
|
|||
onClick: ()=>deleteLevel(li),
|
||||
children: "Delete"
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 105,
|
||||
columnNumber: 17
|
||||
}, this),
|
||||
|
@ -356,7 +356,7 @@ function App() {
|
|||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.resizeLevel)(level, -1)),
|
||||
children: "-"
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 106,
|
||||
columnNumber: 17
|
||||
}, this),
|
||||
|
@ -364,7 +364,7 @@ function App() {
|
|||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.resizeLevel)(level, 1)),
|
||||
children: "+"
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 109,
|
||||
columnNumber: 17
|
||||
}, this),
|
||||
|
@ -372,7 +372,7 @@ function App() {
|
|||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, -1, 0)),
|
||||
children: "L"
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 112,
|
||||
columnNumber: 17
|
||||
}, this),
|
||||
|
@ -380,7 +380,7 @@ function App() {
|
|||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 1, 0)),
|
||||
children: "R"
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 117,
|
||||
columnNumber: 17
|
||||
}, this),
|
||||
|
@ -388,7 +388,7 @@ function App() {
|
|||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 0, -1)),
|
||||
children: "U"
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 120,
|
||||
columnNumber: 17
|
||||
}, this),
|
||||
|
@ -396,7 +396,7 @@ function App() {
|
|||
onClick: ()=>updateLevel(li, (0, _levelsEditorUtil.moveLevel)(level, 0, 1)),
|
||||
children: "D"
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 125,
|
||||
columnNumber: 17
|
||||
}, this),
|
||||
|
@ -407,7 +407,7 @@ function App() {
|
|||
color: e.target.value
|
||||
})
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 128,
|
||||
columnNumber: 17
|
||||
}, this),
|
||||
|
@ -419,13 +419,13 @@ function App() {
|
|||
svg: parseFloat(e.target.value)
|
||||
})
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 135,
|
||||
columnNumber: 17
|
||||
}, this)
|
||||
]
|
||||
}, void 0, true, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 104,
|
||||
columnNumber: 15
|
||||
}, this),
|
||||
|
@ -438,19 +438,19 @@ function App() {
|
|||
},
|
||||
children: brickButtons
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 147,
|
||||
columnNumber: 15
|
||||
}, this)
|
||||
]
|
||||
}, li, true, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 98,
|
||||
columnNumber: 13
|
||||
}, this);
|
||||
})
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 54,
|
||||
columnNumber: 7
|
||||
}, this),
|
||||
|
@ -467,12 +467,12 @@ function App() {
|
|||
},
|
||||
onClick: ()=>setSelected(code)
|
||||
}, code, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 163,
|
||||
columnNumber: 11
|
||||
}, this))
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 161,
|
||||
columnNumber: 7
|
||||
}, this),
|
||||
|
@ -494,25 +494,25 @@ function App() {
|
|||
},
|
||||
children: "new"
|
||||
}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 177,
|
||||
columnNumber: 7
|
||||
}, this)
|
||||
]
|
||||
}, void 0, true, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 49,
|
||||
columnNumber: 5
|
||||
}, this);
|
||||
}
|
||||
const root = (0, _client.createRoot)(document.getElementById("app"));
|
||||
root.render(/*#__PURE__*/ (0, _jsxDevRuntime.jsxDEV)(App, {}, void 0, false, {
|
||||
fileName: "src/levels_editor.tsx",
|
||||
fileName: "src/level_editor/levels_editor.tsx",
|
||||
lineNumber: 203,
|
||||
columnNumber: 13
|
||||
}, 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';
|
||||
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());
|
||||
})();
|
||||
|
||||
},{"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");
|
||||
parcelHelpers.defineInteropFlag(exports);
|
||||
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) {
|
||||
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>
|
||||
<html lang="en">
|
||||
<head><script src="/index.c0fd3053.js"></script>
|
||||
<head><script src="/editor.1350aee5.js"></script>
|
||||
<meta charset="UTF-8">
|
||||
<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="stylesheet" href="/levels_editor.de5e7f9b.css">
|
||||
<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="/editor.9680328c.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="/levels_editor.ef3c2e1a.js" defer=""></script>
|
||||
<script src="/editor.1ec04b8f.js" defer=""></script>
|
||||
</body>
|
||||
</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'
|
||||
}));
|
||||
|
||||
app.post('/src/levels.json', (req, res) => {
|
||||
app.post('/src/data/levels.json', (req, res) => {
|
||||
if(req.body?.trim()) {
|
||||
fs.writeFileSync('src/levels.json', req.body)
|
||||
fs.writeFileSync('src/data/levels.json', req.body)
|
||||
}
|
||||
res.end('OK')
|
||||
})
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
"start": "rm -rf .parcel-cache && run-p dev:*",
|
||||
"dev:game-fe": "parcel src/*.html --lazy --no-hmr",
|
||||
"dev:editor-be": "nodemon editserver.js --watch editserver.js",
|
||||
"test": "jest --watch",
|
||||
"build": "npx jest && rm -f dist/* && parcel build src/index.html"
|
||||
"test": "jest --watch"
|
||||
},
|
||||
"browserslist": "since 2009",
|
||||
"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.
|
||||
const VERSION = "29033878";
|
||||
const VERSION = "29035725";
|
||||
|
||||
// The name of the cache
|
||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
|
@ -1,4 +1,4 @@
|
|||
import {t} from "./i18n/i18n";
|
||||
import { t } from "./i18n/i18n";
|
||||
|
||||
export let alertsOpen = 0,
|
||||
closeModal: null | (() => void) = null;
|
||||
|
@ -12,7 +12,6 @@ export type AsyncAlertAction<t> = {
|
|||
className?: string;
|
||||
};
|
||||
|
||||
|
||||
export function asyncAlert<t>({
|
||||
title,
|
||||
text,
|
||||
|
@ -20,7 +19,7 @@ export function asyncAlert<t>({
|
|||
allowClose = true,
|
||||
textAfterButtons = "",
|
||||
actionsAsGrid = false,
|
||||
}: {
|
||||
}: {
|
||||
title?: string;
|
||||
text?: string;
|
||||
actions?: AsyncAlertAction<t>[];
|
||||
|
@ -44,7 +43,7 @@ export function asyncAlert<t>({
|
|||
|
||||
if (allowClose) {
|
||||
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.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
@ -75,7 +74,7 @@ export function asyncAlert<t>({
|
|||
|
||||
actions
|
||||
?.filter((i) => i)
|
||||
.forEach(({text, value, help, disabled, className = "", icon = ""}) => {
|
||||
.forEach(({ text, value, help, disabled, className = "", icon = "" }) => {
|
||||
const button = document.createElement("button");
|
||||
|
||||
button.innerHTML = `
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
{
|
||||
"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,
|
||||
"svg": 20,
|
||||
"color": ""
|
||||
|
@ -834,5 +834,12 @@
|
|||
"size": 6,
|
||||
"bricks": "_W__W_WW__WW____________WW__WW_W__W_",
|
||||
"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>
|
||||
<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>"
|
||||
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>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="module" src="levels_editor.tsx"></script>
|
||||
<script type="module" src="./level_editor/levels_editor.tsx"></script>
|
||||
</body>
|
||||
</html>
|
292
src/game.ts
|
@ -1,11 +1,24 @@
|
|||
import {allLevels, appVersion, icons, upgrades} from "./loadGameData";
|
||||
import {Ball, Coin, GameState, OptionId, PerkId, RunParams, Upgrade,} from "./types";
|
||||
import {getAudioContext} from "./sounds";
|
||||
import {currentLevelInfo, getRowColIndex, max_levels, pickedUpgradesHTMl} from "./game_utils";
|
||||
import { allLevels, appVersion, icons, upgrades } from "./loadGameData";
|
||||
import {
|
||||
Ball,
|
||||
Coin,
|
||||
GameState,
|
||||
OptionId,
|
||||
PerkId,
|
||||
RunParams,
|
||||
Upgrade,
|
||||
} from "./types";
|
||||
import { getAudioContext } from "./sounds";
|
||||
import {
|
||||
currentLevelInfo,
|
||||
getRowColIndex,
|
||||
max_levels,
|
||||
pickedUpgradesHTMl,
|
||||
} from "./game_utils";
|
||||
|
||||
import "./sw_loader";
|
||||
import {getCurrentLang, t} from "./i18n/i18n";
|
||||
import {getSettingValue, getTotalScore, setSettingValue} from "./settings";
|
||||
import "./PWA/sw_loader";
|
||||
import { getCurrentLang, t } from "./i18n/i18n";
|
||||
import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
|
||||
import {
|
||||
gameStateTick,
|
||||
normalizeGameState,
|
||||
|
@ -14,13 +27,30 @@ import {
|
|||
resetBalls,
|
||||
resetCombo,
|
||||
setLevel,
|
||||
setMousePos
|
||||
setMousePos,
|
||||
} from "./gameStateMutators";
|
||||
import {backgroundCanvas, bombSVG, ctx, gameCanvas, render, scoreDisplay} from "./render";
|
||||
import {pauseRecording, recordOneFrame, resumeRecording, startRecordingGame} from "./recording";
|
||||
import {newGameState} from "./newGameState";
|
||||
import {alertsOpen, asyncAlert, AsyncAlertAction, closeModal} from "./asyncAlert";
|
||||
import {isOptionOn, options, toggleOption} from "./options";
|
||||
import {
|
||||
backgroundCanvas,
|
||||
bombSVG,
|
||||
ctx,
|
||||
gameCanvas,
|
||||
render,
|
||||
scoreDisplay,
|
||||
} from "./render";
|
||||
import {
|
||||
pauseRecording,
|
||||
recordOneFrame,
|
||||
resumeRecording,
|
||||
startRecordingGame,
|
||||
} from "./recording";
|
||||
import { newGameState } from "./newGameState";
|
||||
import {
|
||||
alertsOpen,
|
||||
asyncAlert,
|
||||
AsyncAlertAction,
|
||||
closeModal,
|
||||
} from "./asyncAlert";
|
||||
import { isOptionOn, options, toggleOption } from "./options";
|
||||
|
||||
bombSVG.src =
|
||||
"data:image/svg+xml;base64," +
|
||||
|
@ -68,9 +98,8 @@ export function pause(playerAskedForPause: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
export const fitSize = () => {
|
||||
const {width, height} = gameCanvas.getBoundingClientRect();
|
||||
const { width, height } = gameCanvas.getBoundingClientRect();
|
||||
gameState.canvasWidth = width;
|
||||
gameState.canvasHeight = height;
|
||||
gameCanvas.width = width;
|
||||
|
@ -113,13 +142,12 @@ window.addEventListener("fullscreenchange", fitSize);
|
|||
|
||||
setInterval(() => {
|
||||
// Sometimes, the page changes size without triggering the event (when switching to fullscreen, closing debug panel...)
|
||||
const {width, height} = gameCanvas.getBoundingClientRect();
|
||||
const { width, height } = gameCanvas.getBoundingClientRect();
|
||||
if (width !== gameState.canvasWidth || height !== gameState.canvasHeight)
|
||||
fitSize();
|
||||
}, 1000);
|
||||
|
||||
|
||||
export async function openUpgradesPicker(gameState:GameState) {
|
||||
export async function openUpgradesPicker(gameState: GameState) {
|
||||
const catchRate =
|
||||
(gameState.score - gameState.levelStartScore) /
|
||||
(gameState.levelSpawnedCoins || 1);
|
||||
|
@ -129,64 +157,86 @@ export async function openUpgradesPicker(gameState:GameState) {
|
|||
|
||||
let timeGain = "",
|
||||
catchGain = "",
|
||||
wallHitsGain = "",
|
||||
missesGain = "";
|
||||
|
||||
if (gameState.levelWallBounces == 0) {
|
||||
repeats++;
|
||||
choices++;
|
||||
wallHitsGain = t("level_up.plus_one_upgrade");
|
||||
} else if (gameState.levelWallBounces < 5) {
|
||||
choices++;
|
||||
wallHitsGain = t("level_up.plus_one_choice");
|
||||
}
|
||||
if (gameState.levelTime < 30 * 1000) {
|
||||
repeats++;
|
||||
choices++;
|
||||
timeGain = t('level_up.plus_one_upgrade');
|
||||
timeGain = t("level_up.plus_one_upgrade");
|
||||
} else if (gameState.levelTime < 60 * 1000) {
|
||||
choices++;
|
||||
timeGain = t('level_up.plus_one_choice');
|
||||
timeGain = t("level_up.plus_one_choice");
|
||||
}
|
||||
if (catchRate === 1) {
|
||||
repeats++;
|
||||
choices++;
|
||||
catchGain = t('level_up.plus_one_upgrade');
|
||||
catchGain = t("level_up.plus_one_upgrade");
|
||||
} else if (catchRate > 0.9) {
|
||||
choices++;
|
||||
catchGain = t('level_up.plus_one_choice');
|
||||
catchGain = t("level_up.plus_one_choice");
|
||||
}
|
||||
if (gameState.levelMisses === 0) {
|
||||
repeats++;
|
||||
choices++;
|
||||
missesGain = t('level_up.plus_one_upgrade');
|
||||
missesGain = t("level_up.plus_one_upgrade");
|
||||
} else if (gameState.levelMisses <= 3) {
|
||||
choices++;
|
||||
missesGain = t('level_up.plus_one_choice');
|
||||
missesGain = t("level_up.plus_one_choice");
|
||||
}
|
||||
|
||||
while (repeats--) {
|
||||
const actions = pickRandomUpgrades(gameState,
|
||||
const actions = pickRandomUpgrades(
|
||||
gameState,
|
||||
choices +
|
||||
gameState.perks.one_more_choice -
|
||||
gameState.perks.instant_upgrade,
|
||||
);
|
||||
if (!actions.length) break;
|
||||
let textAfterButtons = `
|
||||
<p>${t('level_up.after_buttons', {
|
||||
<p>${t("level_up.after_buttons", {
|
||||
level: gameState.currentLevel + 1,
|
||||
max: max_levels(gameState)
|
||||
max: max_levels(gameState),
|
||||
})} </p>
|
||||
<p>${pickedUpgradesHTMl(gameState)}</p>
|
||||
<div id="level-recording-container"></div>
|
||||
|
||||
`;
|
||||
|
||||
const compliment = (timeGain && catchGain && missesGain && t("level_up.compliment_perfect")) ||
|
||||
((timeGain || catchGain || missesGain) && t("level_up.compliment_good")) ||
|
||||
t('level_up.compliment_advice');
|
||||
const compliment =
|
||||
(timeGain &&
|
||||
catchGain &&
|
||||
missesGain &&
|
||||
wallHitsGain &&
|
||||
t("level_up.compliment_perfect")) ||
|
||||
((timeGain || catchGain || missesGain || wallHitsGain) &&
|
||||
t("level_up.compliment_good")) ||
|
||||
t("level_up.compliment_advice");
|
||||
|
||||
const upgradeId = (await asyncAlert<PerkId>({
|
||||
title: t('level_up.pick_upgrade_title') + (repeats ? " (" + (repeats + 1) + ")" : ""),
|
||||
title:
|
||||
t("level_up.pick_upgrade_title") +
|
||||
(repeats ? " (" + (repeats + 1) + ")" : ""),
|
||||
actions,
|
||||
text: `<p>${t('level_up.before_buttons', {
|
||||
text: `<p>${t("level_up.before_buttons", {
|
||||
score: gameState.score - gameState.levelStartScore,
|
||||
catchGain,
|
||||
levelSpawnedCoins: gameState.levelSpawnedCoins,
|
||||
time: Math.round(gameState.levelTime / 1000),
|
||||
timeGain,
|
||||
levelMisses: gameState.levelMisses, missesGain,
|
||||
compliment
|
||||
levelMisses: gameState.levelMisses,
|
||||
missesGain,
|
||||
levelWallBounces: gameState.levelWallBounces,
|
||||
wallHitsGain,
|
||||
compliment,
|
||||
})}
|
||||
</p>`,
|
||||
allowClose: false,
|
||||
|
@ -244,7 +294,8 @@ gameCanvas.addEventListener("touchmove", (e) => {
|
|||
});
|
||||
|
||||
export function brickIndex(x: number, y: number) {
|
||||
return getRowColIndex(gameState,
|
||||
return getRowColIndex(
|
||||
gameState,
|
||||
Math.floor(y / gameState.brickWidth),
|
||||
Math.floor((x - gameState.offsetX) / gameState.brickWidth),
|
||||
);
|
||||
|
@ -293,7 +344,7 @@ export function shouldPierceByColor(
|
|||
export function coinBrickHitCheck(coin: Coin) {
|
||||
// Make ball/coin bonce, and return bricks that were hit
|
||||
const radius = coin.size / 2;
|
||||
const {x, y, previousX, previousY} = coin;
|
||||
const { x, y, previousX, previousY } = coin;
|
||||
|
||||
const vhit = hitsSomething(previousX, y, radius);
|
||||
const hhit = hitsSomething(x, previousY, radius);
|
||||
|
@ -384,24 +435,25 @@ export function bordersHitCheck(
|
|||
}
|
||||
|
||||
export function tick() {
|
||||
|
||||
const currentTick = performance.now();
|
||||
const timeDeltaMs = currentTick - gameState.lastTick
|
||||
const timeDeltaMs = currentTick - gameState.lastTick;
|
||||
gameState.lastTick = currentTick;
|
||||
|
||||
const frames = Math.min(4, (timeDeltaMs) / (1000 / 60));
|
||||
|
||||
const frames = Math.min(4, timeDeltaMs / (1000 / 60));
|
||||
|
||||
if (gameState.keyboardPuckSpeed) {
|
||||
setMousePos(gameState, gameState.puckPosition + gameState.keyboardPuckSpeed);
|
||||
setMousePos(
|
||||
gameState,
|
||||
gameState.puckPosition + gameState.keyboardPuckSpeed,
|
||||
);
|
||||
}
|
||||
|
||||
normalizeGameState(gameState)
|
||||
normalizeGameState(gameState);
|
||||
|
||||
if (gameState.running) {
|
||||
gameState.levelTime += timeDeltaMs;
|
||||
gameState.runStatistics.runTime += timeDeltaMs;
|
||||
gameStateTick(gameState, frames)
|
||||
gameStateTick(gameState, frames);
|
||||
}
|
||||
render(gameState);
|
||||
recordOneFrame(gameState);
|
||||
|
@ -428,27 +480,28 @@ document.addEventListener("visibilitychange", () => {
|
|||
async function openScorePanel() {
|
||||
pause(true);
|
||||
const cb = await asyncAlert({
|
||||
title: t('score_panel.title', {
|
||||
score: gameState.score, level: gameState.currentLevel + 1, max: max_levels(gameState)
|
||||
title: t("score_panel.title", {
|
||||
score: gameState.score,
|
||||
level: gameState.currentLevel + 1,
|
||||
max: max_levels(gameState),
|
||||
}),
|
||||
text: `
|
||||
${gameState.isCreativeModeRun ? "<p>${t('score_panel.test_run}</p>" : ""}
|
||||
<p>${t('score_panel.upgrades_picked')}</p>
|
||||
<p>${t("score_panel.upgrades_picked")}</p>
|
||||
<p>${pickedUpgradesHTMl(gameState)}</p>
|
||||
`,
|
||||
allowClose: true,
|
||||
actions: [
|
||||
{
|
||||
text: t('score_panel.resume'),
|
||||
help: t('score_panel.resume_help'),
|
||||
value: () => {
|
||||
},
|
||||
text: t("score_panel.resume"),
|
||||
help: t("score_panel.resume_help"),
|
||||
value: () => {},
|
||||
},
|
||||
{
|
||||
text: t('score_panel.restart'),
|
||||
help: t('score_panel.restart_help'),
|
||||
text: t("score_panel.restart"),
|
||||
help: t("score_panel.restart_help"),
|
||||
value: () => {
|
||||
restart({levelToAvoid: currentLevelInfo(gameState).name});
|
||||
restart({ levelToAvoid: currentLevelInfo(gameState).name });
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -468,14 +521,13 @@ async function openSettingsPanel() {
|
|||
|
||||
const actions: AsyncAlertAction<() => void>[] = [
|
||||
{
|
||||
text: t('main_menu.resume'),
|
||||
help: t('main_menu.resume_help'),
|
||||
value() {
|
||||
},
|
||||
text: t("main_menu.resume"),
|
||||
help: t("main_menu.resume_help"),
|
||||
value() {},
|
||||
},
|
||||
{
|
||||
text: t("main_menu.unlocks"),
|
||||
help: t('main_menu.unlocks_help'),
|
||||
help: t("main_menu.unlocks_help"),
|
||||
value() {
|
||||
openUnlocksList();
|
||||
},
|
||||
|
@ -493,7 +545,7 @@ async function openSettingsPanel() {
|
|||
help: options[key].help,
|
||||
value: () => {
|
||||
toggleOption(key);
|
||||
if (key === "mobile-mode") fitSize()
|
||||
if (key === "mobile-mode") fitSize();
|
||||
|
||||
openSettingsPanel();
|
||||
},
|
||||
|
@ -504,8 +556,8 @@ async function openSettingsPanel() {
|
|||
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
|
||||
if (document.fullscreenElement !== null) {
|
||||
actions.push({
|
||||
text: t('main_menu.fullscreen_exit'),
|
||||
help: t('main_menu.fullscreen_exit_help'),
|
||||
text: t("main_menu.fullscreen_exit"),
|
||||
help: t("main_menu.fullscreen_exit_help"),
|
||||
icon: icons["icon:exit_fullscreen"],
|
||||
value() {
|
||||
toggleFullScreen();
|
||||
|
@ -513,9 +565,8 @@ async function openSettingsPanel() {
|
|||
});
|
||||
} else {
|
||||
actions.push({
|
||||
|
||||
text: t('main_menu.fullscreen'),
|
||||
help: t('main_menu.fullscreen_help'),
|
||||
text: t("main_menu.fullscreen"),
|
||||
help: t("main_menu.fullscreen_help"),
|
||||
|
||||
icon: icons["icon:fullscreen"],
|
||||
value() {
|
||||
|
@ -526,22 +577,21 @@ async function openSettingsPanel() {
|
|||
}
|
||||
|
||||
actions.push({
|
||||
text: t('sandbox.title'),
|
||||
text: t("sandbox.title"),
|
||||
help:
|
||||
getTotalScore() < creativeModeThreshold
|
||||
? t('sandbox.unlocks_at', {score: creativeModeThreshold})
|
||||
: t('sandbox.help'),
|
||||
? t("sandbox.unlocks_at", { score: creativeModeThreshold })
|
||||
: t("sandbox.help"),
|
||||
disabled: getTotalScore() < creativeModeThreshold,
|
||||
async value() {
|
||||
|
||||
let creativeModePerks: Partial<{ [id in PerkId]: number }> = getSettingValue('creativeModePerks', {}),
|
||||
let creativeModePerks: Partial<{ [id in PerkId]: number }> =
|
||||
getSettingValue("creativeModePerks", {}),
|
||||
choice: "start" | Upgrade | void;
|
||||
|
||||
|
||||
while (
|
||||
(choice = await asyncAlert<"start" | Upgrade>({
|
||||
title: t('sandbox.title'),
|
||||
text: t('sandbox.instructions'),
|
||||
title: t("sandbox.title"),
|
||||
text: t("sandbox.instructions"),
|
||||
actionsAsGrid: true,
|
||||
actions: [
|
||||
...upgrades.map((u) => ({
|
||||
|
@ -554,15 +604,15 @@ async function openSettingsPanel() {
|
|||
: "grey-out-unless-hovered",
|
||||
})),
|
||||
{
|
||||
text: t('sandbox.start'),
|
||||
text: t("sandbox.start"),
|
||||
value: "start",
|
||||
},
|
||||
],
|
||||
}))
|
||||
) {
|
||||
if (choice === "start") {
|
||||
setSettingValue('creativeModePerks', creativeModePerks)
|
||||
restart({perks: creativeModePerks});
|
||||
setSettingValue("creativeModePerks", creativeModePerks);
|
||||
restart({ perks: creativeModePerks });
|
||||
|
||||
break;
|
||||
} else if (choice) {
|
||||
|
@ -573,20 +623,20 @@ async function openSettingsPanel() {
|
|||
},
|
||||
});
|
||||
actions.push({
|
||||
text: t('main_menu.reset'),
|
||||
help: t('main_menu.reset_help'),
|
||||
text: t("main_menu.reset"),
|
||||
help: t("main_menu.reset_help"),
|
||||
async value() {
|
||||
if (
|
||||
await asyncAlert({
|
||||
title: t('main_menu.reset'),
|
||||
text: t('main_menu.reset_instruction'),
|
||||
title: t("main_menu.reset"),
|
||||
text: t("main_menu.reset_instruction"),
|
||||
actions: [
|
||||
{
|
||||
text: t('main_menu.reset_confirm'),
|
||||
text: t("main_menu.reset_confirm"),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
text: t('main_menu.reset_cancel'),
|
||||
text: t("main_menu.reset_cancel"),
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
|
@ -600,41 +650,37 @@ async function openSettingsPanel() {
|
|||
});
|
||||
|
||||
actions.push({
|
||||
text: t('main_menu.language'),
|
||||
help: t('main_menu.language_help'),
|
||||
text: t("main_menu.language"),
|
||||
help: t("main_menu.language_help"),
|
||||
async value() {
|
||||
const pick = await asyncAlert({
|
||||
title: t('main_menu.language'),
|
||||
text: t('main_menu.language_help'),
|
||||
title: t("main_menu.language"),
|
||||
text: t("main_menu.language_help"),
|
||||
actions: [
|
||||
{
|
||||
text: 'English',
|
||||
value: 'en',
|
||||
text: "English",
|
||||
value: "en",
|
||||
},
|
||||
{
|
||||
text: 'Français',
|
||||
value: 'fr',
|
||||
text: "Français",
|
||||
value: "fr",
|
||||
},
|
||||
],
|
||||
allowClose: true,
|
||||
})
|
||||
if (
|
||||
pick && pick !== getCurrentLang() &&
|
||||
await confirmRestart()
|
||||
) {
|
||||
|
||||
setSettingValue('lang', pick)
|
||||
window.location.reload()
|
||||
});
|
||||
if (pick && pick !== getCurrentLang() && (await confirmRestart())) {
|
||||
setSettingValue("lang", pick);
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const cb = await asyncAlert<() => void>({
|
||||
title: t('main_menu.title'),
|
||||
title: t("main_menu.title"),
|
||||
text: ``,
|
||||
allowClose: true,
|
||||
actions,
|
||||
textAfterButtons: t('main_menu.footer_html', {appVersion}),
|
||||
textAfterButtons: t("main_menu.footer_html", { appVersion }),
|
||||
});
|
||||
if (cb) {
|
||||
cb();
|
||||
|
@ -646,12 +692,12 @@ async function openUnlocksList() {
|
|||
const actions = [
|
||||
...upgrades
|
||||
.sort((a, b) => a.threshold - b.threshold)
|
||||
.map(({name, id, threshold, icon, fullHelp}) => ({
|
||||
.map(({ name, id, threshold, icon, fullHelp }) => ({
|
||||
text: name,
|
||||
help:
|
||||
ts >= threshold ? fullHelp : t('unlocks.unlocks_at', {threshold}),
|
||||
ts >= threshold ? fullHelp : t("unlocks.unlocks_at", { threshold }),
|
||||
disabled: ts < threshold,
|
||||
value: {perks: {[id]: 1}} as RunParams,
|
||||
value: { perks: { [id]: 1 } } as RunParams,
|
||||
icon,
|
||||
})),
|
||||
...allLevels
|
||||
|
@ -661,12 +707,13 @@ async function openUnlocksList() {
|
|||
return {
|
||||
text: l.name,
|
||||
help: available
|
||||
|
||||
?
|
||||
t('unlocks.level_description', {size: l.size, bricks: l.bricks.filter((i) => i).length})
|
||||
: t('unlocks.unlocks_at', {threshold: l.threshold}),
|
||||
? t("unlocks.level_description", {
|
||||
size: l.size,
|
||||
bricks: l.bricks.filter((i) => i).length,
|
||||
})
|
||||
: t("unlocks.unlocks_at", { threshold: l.threshold }),
|
||||
disabled: !available,
|
||||
value: {level: l.name} as RunParams,
|
||||
value: { level: l.name } as RunParams,
|
||||
icon: icons[l.name],
|
||||
};
|
||||
}),
|
||||
|
@ -676,9 +723,9 @@ async function openUnlocksList() {
|
|||
(actions.filter((a) => !a.disabled).length / actions.length) * 100,
|
||||
);
|
||||
const tryOn = await asyncAlert<RunParams>({
|
||||
title: t('unlocks.title', {percentUnlock}),
|
||||
text: `<p>${t('unlocks.intro', {ts})}
|
||||
${percentUnlock < 100 ? t('unlocks.greyed_out_help') : ""}</p>
|
||||
title: t("unlocks.title", { percentUnlock }),
|
||||
text: `<p>${t("unlocks.intro", { ts })}
|
||||
${percentUnlock < 100 ? t("unlocks.greyed_out_help") : ""}</p>
|
||||
`,
|
||||
textAfterButtons: `<p>
|
||||
Your high score is ${gameState.highScore}.
|
||||
|
@ -688,33 +735,29 @@ Click an item above to start a run with it.
|
|||
allowClose: true,
|
||||
});
|
||||
if (tryOn) {
|
||||
if (
|
||||
await confirmRestart()
|
||||
) {
|
||||
if (await confirmRestart()) {
|
||||
restart(tryOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function confirmRestart() {
|
||||
if (!gameState.currentLevel) return true
|
||||
if (!gameState.currentLevel) return true;
|
||||
|
||||
return asyncAlert({
|
||||
title: t('confirmRestart.title'),
|
||||
text: t('confirmRestart.text'),
|
||||
title: t("confirmRestart.title"),
|
||||
text: t("confirmRestart.text"),
|
||||
actions: [
|
||||
{
|
||||
value: true,
|
||||
text: t('confirmRestart.yes'),
|
||||
text: t("confirmRestart.yes"),
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
text: t('confirmRestart.no'),
|
||||
text: t("confirmRestart.no"),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleFullScreen() {
|
||||
|
@ -795,7 +838,7 @@ document.addEventListener("keyup", async (e) => {
|
|||
openScorePanel().then();
|
||||
} else if (e.key.toLowerCase() === "r" && !alertsOpen) {
|
||||
if (await confirmRestart()) {
|
||||
restart({levelToAvoid: currentLevelInfo(gameState).name});
|
||||
restart({ levelToAvoid: currentLevelInfo(gameState).name });
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
|
@ -817,4 +860,5 @@ tick();
|
|||
|
||||
// @ts-ignore
|
||||
// window.stressTest= ()=>restart({level:'Shark',perks:{base_combo:100, pierce:10, multiball:8}})
|
||||
window.stressTest = () => restart({level: 'Shark', perks: {sapper: 2, pierce: 10, multiball: 3}})
|
||||
window.stressTest = () =>
|
||||
restart({ level: "Shark", perks: { sapper: 2, pierce: 10, multiball: 3 } });
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {allLevels, appVersion, upgrades} from "./loadGameData";
|
||||
import {t} from "./i18n/i18n";
|
||||
import {RunHistoryItem} from "./types";
|
||||
import {gameState, pause, restart} from "./game";
|
||||
import {currentLevelInfo, findLast} from "./game_utils";
|
||||
import {getTotalScore} from "./settings";
|
||||
import {stopRecording} from "./recording";
|
||||
import {asyncAlert} from "./asyncAlert";
|
||||
import { allLevels, appVersion, upgrades } from "./loadGameData";
|
||||
import { t } from "./i18n/i18n";
|
||||
import { RunHistoryItem } from "./types";
|
||||
import { gameState, pause, restart } from "./game";
|
||||
import { currentLevelInfo, findLast } from "./game_utils";
|
||||
import { getTotalScore } from "./settings";
|
||||
import { stopRecording } from "./recording";
|
||||
import { asyncAlert } from "./asyncAlert";
|
||||
|
||||
export function getUpgraderUnlockPoints() {
|
||||
let list = [] as { threshold: number; title: string }[];
|
||||
|
@ -14,7 +14,7 @@ export function getUpgraderUnlockPoints() {
|
|||
if (u.threshold) {
|
||||
list.push({
|
||||
threshold: u.threshold,
|
||||
title: u.name + ' ' + t('level_up.unlocked_perk'),
|
||||
title: u.name + " " + t("level_up.unlocked_perk"),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ export function getUpgraderUnlockPoints() {
|
|||
allLevels.forEach((l) => {
|
||||
list.push({
|
||||
threshold: l.threshold,
|
||||
title: l.name + ' ' + t('level_up.unlocked_level'),
|
||||
title: l.name + " " + t("level_up.unlocked_level"),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -40,8 +40,7 @@ export function addToTotalPlayTime(ms: number) {
|
|||
ms,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export function gameOver(title: string, intro: string) {
|
||||
|
@ -79,7 +78,9 @@ export function gameOver(title: string, intro: string) {
|
|||
const total = nextUnlock?.threshold - 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);
|
||||
unlocksInfo += `
|
||||
|
@ -105,8 +106,7 @@ export function gameOver(title: string, intro: string) {
|
|||
(u) => u.threshold > startTs && u.threshold < endTs,
|
||||
);
|
||||
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
|
||||
|
@ -116,22 +116,22 @@ export function gameOver(title: string, intro: string) {
|
|||
allowClose: true,
|
||||
title,
|
||||
text: `
|
||||
${gameState.isCreativeModeRun ? `<p>${t('gameOver.test_run')}</p> ` : ""}
|
||||
${gameState.isCreativeModeRun ? `<p>${t("gameOver.test_run")}</p> ` : ""}
|
||||
<p>${intro}</p>
|
||||
<p>${t('gameOver.cumulative_total', {startTs, endTs})}</p>
|
||||
<p>${t("gameOver.cumulative_total", { startTs, endTs })}</p>
|
||||
${unlocksInfo}
|
||||
`,
|
||||
actions: [
|
||||
{
|
||||
value: null,
|
||||
text: t('gameOver.restart'),
|
||||
text: t("gameOver.restart"),
|
||||
help: "",
|
||||
},
|
||||
],
|
||||
textAfterButtons: `<div id="level-recording-container"></div>
|
||||
${getHistograms()}
|
||||
`,
|
||||
}).then(() => restart({levelToAvoid: currentLevelInfo(gameState).name}));
|
||||
}).then(() => restart({ levelToAvoid: currentLevelInfo(gameState).name }));
|
||||
}
|
||||
|
||||
export function getHistograms() {
|
||||
|
@ -208,40 +208,65 @@ export function getHistograms() {
|
|||
`;
|
||||
};
|
||||
|
||||
runStats += makeHistogram(t('gameOver.stats.total_score'), (r) => r.score, "");
|
||||
runStats += makeHistogram(t('gameOver.stats.catch_rate'),
|
||||
runStats += makeHistogram(
|
||||
t("gameOver.stats.total_score"),
|
||||
(r) => r.score,
|
||||
"",
|
||||
);
|
||||
runStats += makeHistogram(
|
||||
t("gameOver.stats.catch_rate"),
|
||||
(r) => Math.round((r.score / r.coins_spawned) * 100),
|
||||
"%",
|
||||
);
|
||||
runStats += makeHistogram(t('gameOver.stats.bricks_broken'), (r) => r.bricks_broken, "");
|
||||
runStats += makeHistogram(
|
||||
t('gameOver.stats.bricks_per_minute'),
|
||||
t("gameOver.stats.bricks_broken"),
|
||||
(r) => r.bricks_broken,
|
||||
"",
|
||||
);
|
||||
runStats += makeHistogram(
|
||||
t("gameOver.stats.bricks_per_minute"),
|
||||
(r) => Math.round((r.bricks_broken / r.runTime) * 1000 * 60),
|
||||
"",
|
||||
);
|
||||
runStats += makeHistogram(
|
||||
t('gameOver.stats.hit_rate'),
|
||||
t("gameOver.stats.hit_rate"),
|
||||
(r) => Math.round((1 - r.misses / r.puck_bounces) * 100),
|
||||
"%",
|
||||
);
|
||||
runStats += makeHistogram(
|
||||
t('gameOver.stats.duration_per_level'),
|
||||
t("gameOver.stats.duration_per_level"),
|
||||
(r) => Math.round(r.runTime / 1000 / r.levelsPlayed),
|
||||
"s",
|
||||
);
|
||||
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'),
|
||||
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, "");
|
||||
runStats += makeHistogram(
|
||||
t("gameOver.stats.combo_max"),
|
||||
(r) => r.max_combo,
|
||||
"",
|
||||
);
|
||||
|
||||
if (runStats) {
|
||||
runStats =
|
||||
`<p>${t('gameOver.stats.intro', {count: runsHistory.length - 1})}</p>` +
|
||||
`<p>${t("gameOver.stats.intro", { count: runsHistory.length - 1 })}</p>` +
|
||||
runStats;
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import {Ball, BallLike, Coin, colorString, GameState, PerkId} from "./types";
|
||||
import {sounds} from "./sounds";
|
||||
import { Ball, BallLike, Coin, colorString, GameState, PerkId } from "./types";
|
||||
import { sounds } from "./sounds";
|
||||
import {
|
||||
brickCenterX,
|
||||
brickCenterY,
|
||||
currentLevelInfo, distanceBetween,
|
||||
currentLevelInfo,
|
||||
distanceBetween,
|
||||
getMajorityValue,
|
||||
getPossibleUpgrades,
|
||||
getRowColIndex,
|
||||
isTelekinesisActive,
|
||||
max_levels
|
||||
max_levels,
|
||||
} from "./game_utils";
|
||||
import {t} from "./i18n/i18n";
|
||||
import {icons} from "./loadGameData";
|
||||
import { t } from "./i18n/i18n";
|
||||
import { icons } from "./loadGameData";
|
||||
|
||||
import {addToTotalScore} from "./settings";
|
||||
import {background} from "./render";
|
||||
import {gameOver} from "./gameOver";
|
||||
import { addToTotalScore } from "./settings";
|
||||
import { background } from "./render";
|
||||
import { gameOver } from "./gameOver";
|
||||
import {
|
||||
bordersHitCheck,
|
||||
brickIndex,
|
||||
|
@ -26,16 +27,22 @@ import {
|
|||
hitsSomething,
|
||||
openUpgradesPicker,
|
||||
pause,
|
||||
shouldPierceByColor
|
||||
shouldPierceByColor,
|
||||
} from "./game";
|
||||
import {stopRecording} from "./recording";
|
||||
import {isOptionOn} from "./options";
|
||||
import { stopRecording } from "./recording";
|
||||
import { isOptionOn } from "./options";
|
||||
|
||||
export function setMousePos(gameState: GameState, x: number) {
|
||||
// Sets the puck position, and updates the ball position if they are supposed to follow it
|
||||
gameState.puckPosition = x;
|
||||
}
|
||||
|
||||
function getBallDefaultVx(gameState: GameState) {
|
||||
return (
|
||||
(gameState.perks.concave_puck ? 0 : 1) *
|
||||
(Math.random() > 0.5 ? gameState.baseSpeed : -gameState.baseSpeed)
|
||||
);
|
||||
}
|
||||
export function resetBalls(gameState: GameState) {
|
||||
const count = 1 + (gameState.perks?.multiball || 0);
|
||||
const perBall = gameState.puckWidth / (count + 1);
|
||||
|
@ -48,7 +55,7 @@ export function resetBalls(gameState: GameState) {
|
|||
for (let i = 0; i < count; i++) {
|
||||
const x =
|
||||
gameState.puckPosition - gameState.puckWidth / 2 + perBall * (i + 1);
|
||||
const vx = Math.random() > 0.5 ? gameState.baseSpeed : -gameState.baseSpeed;
|
||||
const vx = getBallDefaultVx(gameState);
|
||||
|
||||
gameState.balls.push({
|
||||
x,
|
||||
|
@ -66,7 +73,6 @@ export function resetBalls(gameState: GameState) {
|
|||
piercedSinceBounce: 0,
|
||||
hitSinceBounce: 0,
|
||||
hitItem: [],
|
||||
bouncesList: [],
|
||||
sapperUses: 0,
|
||||
});
|
||||
}
|
||||
|
@ -76,6 +82,7 @@ export function putBallsAtPuck(gameState: GameState) {
|
|||
// This reset could be abused to cheat quite easily
|
||||
const count = gameState.balls.length;
|
||||
const perBall = gameState.puckWidth / (count + 1);
|
||||
const vx = getBallDefaultVx(gameState);
|
||||
gameState.balls.forEach((ball, i) => {
|
||||
const x =
|
||||
gameState.puckPosition - gameState.puckWidth / 2 + perBall * (i + 1);
|
||||
|
@ -84,7 +91,7 @@ export function putBallsAtPuck(gameState: GameState) {
|
|||
ball.previousX = x;
|
||||
ball.y = gameState.gameZoneHeight - 1.5 * gameState.ballSize;
|
||||
ball.previousY = ball.y;
|
||||
ball.vx = Math.random() > 0.5 ? gameState.baseSpeed : -gameState.baseSpeed;
|
||||
ball.vx = vx;
|
||||
ball.previousVX = ball.vx;
|
||||
ball.vy = -gameState.baseSpeed;
|
||||
ball.previousVY = ball.vy;
|
||||
|
@ -111,7 +118,6 @@ export function normalizeGameState(gameState: GameState) {
|
|||
(gameState.gameZoneWidth / 12) *
|
||||
(3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck);
|
||||
|
||||
|
||||
if (
|
||||
gameState.puckPosition <
|
||||
gameState.offsetXRoundedDown + gameState.puckWidth / 2
|
||||
|
@ -152,7 +158,7 @@ export function resetCombo(
|
|||
}
|
||||
if (prev > gameState.combo && gameState.perks.soft_reset) {
|
||||
gameState.combo += Math.floor(
|
||||
(prev - gameState.combo) * (gameState.perks.soft_reset*10)/100,
|
||||
((prev - gameState.combo) * (gameState.perks.soft_reset * 10)) / 100,
|
||||
);
|
||||
}
|
||||
const lost = Math.max(0, prev - gameState.combo);
|
||||
|
@ -233,7 +239,12 @@ export function spawnExplosion(
|
|||
}
|
||||
}
|
||||
|
||||
export function explodeBrick(gameState: GameState, index: number, ball: Ball, isExplosion: boolean) {
|
||||
export function explodeBrick(
|
||||
gameState: GameState,
|
||||
index: number,
|
||||
ball: Ball,
|
||||
isExplosion: boolean,
|
||||
) {
|
||||
const color = gameState.bricks[index];
|
||||
if (!color) return;
|
||||
|
||||
|
@ -282,7 +293,8 @@ export function explodeBrick(gameState: GameState, index: number, ball: Ball, is
|
|||
x,
|
||||
y,
|
||||
});
|
||||
spawnExplosion(gameState,
|
||||
spawnExplosion(
|
||||
gameState,
|
||||
7 * (1 + gameState.perks.bigger_explosions),
|
||||
x,
|
||||
y,
|
||||
|
@ -325,7 +337,7 @@ export function explodeBrick(gameState: GameState, index: number, ball: Ball, is
|
|||
while (coinsToSpawn > 0) {
|
||||
const points = Math.min(pointsPerCoin, coinsToSpawn);
|
||||
if (points < 0 || isNaN(points)) {
|
||||
console.error({points});
|
||||
console.error({ points });
|
||||
debugger;
|
||||
}
|
||||
|
||||
|
@ -364,7 +376,7 @@ export function explodeBrick(gameState: GameState, index: number, ball: Ball, is
|
|||
gameState.perks.left_is_lava +
|
||||
gameState.perks.right_is_lava +
|
||||
gameState.perks.top_is_lava +
|
||||
gameState.perks.picky_eater
|
||||
gameState.perks.picky_eater,
|
||||
);
|
||||
|
||||
if (!isExplosion) {
|
||||
|
@ -382,7 +394,15 @@ export function explodeBrick(gameState: GameState, index: number, ball: Ball, is
|
|||
gameState.ballsColor = color;
|
||||
if (!isOptionOn("basic")) {
|
||||
gameState.balls.forEach((ball) => {
|
||||
spawnExplosion(gameState,7, ball.previousX, ball.previousY, color, 150, 15);
|
||||
spawnExplosion(
|
||||
gameState,
|
||||
7,
|
||||
ball.previousX,
|
||||
ball.previousY,
|
||||
color,
|
||||
150,
|
||||
15,
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -399,7 +419,8 @@ export function explodeBrick(gameState: GameState, index: number, ball: Ball, is
|
|||
x,
|
||||
y,
|
||||
});
|
||||
spawnExplosion(gameState,
|
||||
spawnExplosion(
|
||||
gameState,
|
||||
5 + Math.min(gameState.combo, 30),
|
||||
x,
|
||||
y,
|
||||
|
@ -439,7 +460,11 @@ export function pickRandomUpgrades(gameState: GameState, count: number) {
|
|||
return list.map((u) => ({
|
||||
text:
|
||||
u.name +
|
||||
(gameState.perks[u.id] ? t('level_up.upgrade_perk_to_level', {level: gameState.perks[u.id] + 1}) : ""),
|
||||
(gameState.perks[u.id]
|
||||
? t("level_up.upgrade_perk_to_level", {
|
||||
level: gameState.perks[u.id] + 1,
|
||||
})
|
||||
: ""),
|
||||
icon: icons["icon:" + u.id],
|
||||
value: u.id as PerkId,
|
||||
help: u.help(gameState.perks[u.id] + 1),
|
||||
|
@ -487,6 +512,7 @@ export function setLevel(gameState: GameState, l: number) {
|
|||
gameState.currentLevel = l;
|
||||
|
||||
gameState.levelTime = 0;
|
||||
gameState.levelWallBounces = 0;
|
||||
gameState.autoCleanUses = 0;
|
||||
gameState.lastTickDown = gameState.levelTime;
|
||||
gameState.levelStartScore = gameState.score;
|
||||
|
@ -515,7 +541,8 @@ export function rainbowColor(): colorString {
|
|||
return `hsl(${(Math.round(gameState.levelTime / 4) * 2) % 360},100%,70%)`;
|
||||
}
|
||||
|
||||
export function repulse(gameState: GameState,
|
||||
export function repulse(
|
||||
gameState: GameState,
|
||||
a: Ball,
|
||||
b: BallLike,
|
||||
power: number,
|
||||
|
@ -622,10 +649,11 @@ export function attract(gameState: GameState, a: Ball, b: Ball, power: number) {
|
|||
});
|
||||
}
|
||||
|
||||
export function gameStateTick(gameState: GameState,
|
||||
export function gameStateTick(
|
||||
gameState: GameState,
|
||||
// How many frames to compute at once, can go above 1 to compensate lag
|
||||
frames = 1) {
|
||||
|
||||
frames = 1,
|
||||
) {
|
||||
gameState.runStatistics.max_combo = Math.max(
|
||||
gameState.runStatistics.max_combo,
|
||||
gameState.combo,
|
||||
|
@ -667,8 +695,8 @@ export function gameStateTick(gameState: GameState,
|
|||
setLevel(gameState, gameState.currentLevel + 1);
|
||||
} else {
|
||||
gameOver(
|
||||
t('gameOver.win.title'),
|
||||
t('gameOver.win.summary', {score: gameState.score}),
|
||||
t("gameOver.win.title"),
|
||||
t("gameOver.win.summary", { score: gameState.score }),
|
||||
);
|
||||
}
|
||||
} else if (gameState.running || gameState.levelTime) {
|
||||
|
@ -693,12 +721,10 @@ export function gameStateTick(gameState: GameState,
|
|||
|
||||
coin.vy *= ratio;
|
||||
coin.vx *= ratio;
|
||||
if (coin.vx > 7 * gameState.baseSpeed)
|
||||
coin.vx = 7 * gameState.baseSpeed;
|
||||
if (coin.vx > 7 * gameState.baseSpeed) coin.vx = 7 * gameState.baseSpeed;
|
||||
if (coin.vx < -7 * gameState.baseSpeed)
|
||||
coin.vx = -7 * gameState.baseSpeed;
|
||||
if (coin.vy > 7 * gameState.baseSpeed)
|
||||
coin.vy = 7 * gameState.baseSpeed;
|
||||
if (coin.vy > 7 * gameState.baseSpeed) coin.vy = 7 * gameState.baseSpeed;
|
||||
if (coin.vy < -7 * gameState.baseSpeed)
|
||||
coin.vy = -7 * gameState.baseSpeed;
|
||||
coin.a += coin.sa;
|
||||
|
@ -710,8 +736,7 @@ export function gameStateTick(gameState: GameState,
|
|||
const hitBorder = bordersHitCheck(coin, coin.size / 2, frames);
|
||||
|
||||
if (
|
||||
coin.y >
|
||||
gameState.gameZoneHeight - coinRadius - gameState.puckHeight &&
|
||||
coin.y > gameState.gameZoneHeight - coinRadius - gameState.puckHeight &&
|
||||
coin.y < gameState.gameZoneHeight + gameState.puckHeight + coin.vy &&
|
||||
Math.abs(coin.x - gameState.puckPosition) <
|
||||
coinRadius +
|
||||
|
@ -917,7 +942,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
for (let b2 of gameState.balls) {
|
||||
// avoid computing this twice, and repulsing itself
|
||||
if (b2.x >= ball.x) continue;
|
||||
repulse(gameState,ball, b2, gameState.perks.ball_repulse_ball, true);
|
||||
repulse(gameState, ball, b2, gameState.perks.ball_repulse_ball, true);
|
||||
}
|
||||
}
|
||||
if (gameState.perks.ball_attract_ball) {
|
||||
|
@ -933,7 +958,8 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
gameState.puckWidth / 2 +
|
||||
(gameState.ballSize * (9 + gameState.perks.puck_repulse_ball)) / 10
|
||||
) {
|
||||
repulse(gameState,
|
||||
repulse(
|
||||
gameState,
|
||||
ball,
|
||||
{
|
||||
x: gameState.puckPosition,
|
||||
|
@ -954,7 +980,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
i < ball.hitItem?.length - 1 && i < gameState.perks.respawn;
|
||||
i++
|
||||
) {
|
||||
const {index, color} = ball.hitItem[i];
|
||||
const { index, color } = ball.hitItem[i];
|
||||
if (gameState.bricks[index] || color === "black") continue;
|
||||
const vertical = Math.random() > 0.5;
|
||||
const dx = Math.random() > 0.5 ? 1 : -1;
|
||||
|
@ -997,7 +1023,8 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
resetCombo(gameState, ball.x, ball.y + gameState.ballSize);
|
||||
}
|
||||
sounds.wallBeep(ball.x);
|
||||
ball.bouncesList?.push({x: ball.previousX, y: ball.previousY});
|
||||
gameState.levelWallBounces++;
|
||||
gameState.runStatistics.wall_bounces++;
|
||||
}
|
||||
|
||||
// Puck collision
|
||||
|
@ -1017,7 +1044,8 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
|
||||
const angle = Math.atan2(
|
||||
-gameState.puckWidth / 2,
|
||||
ball.x - gameState.puckPosition,
|
||||
(ball.x - gameState.puckPosition) *
|
||||
(gameState.perks.concave_puck ? -0.5 : 1),
|
||||
);
|
||||
ball.vx = speed * Math.cos(angle);
|
||||
ball.vy = speed * Math.sin(angle);
|
||||
|
@ -1051,7 +1079,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
ball.hitItem
|
||||
.slice(0, -1)
|
||||
.slice(0, gameState.perks.respawn)
|
||||
.forEach(({index, color}) => {
|
||||
.forEach(({ index, color }) => {
|
||||
if (!gameState.bricks[index] && color !== "black")
|
||||
gameState.bricks[index] = color;
|
||||
});
|
||||
|
@ -1063,7 +1091,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
resetCombo(gameState, ball.x, ball.y);
|
||||
gameState.flashes.push({
|
||||
type: "text",
|
||||
text: t('play.missed_ball'),
|
||||
text: t("play.missed_ball"),
|
||||
duration: 500,
|
||||
time: gameState.levelTime,
|
||||
size: gameState.puckHeight * 1.5,
|
||||
|
@ -1076,12 +1104,6 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
ball.hitSinceBounce = 0;
|
||||
ball.sapperUses = 0;
|
||||
ball.piercedSinceBounce = 0;
|
||||
ball.bouncesList = [
|
||||
{
|
||||
x: ball.previousX,
|
||||
y: ball.previousY,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -1092,13 +1114,14 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
gameState.runStatistics.balls_lost++;
|
||||
if (!gameState.balls.find((b) => !b.destroyed)) {
|
||||
gameOver(
|
||||
t('gameOver.lost.title'),
|
||||
t('gameOver.lost.summary', {score: gameState.score}))
|
||||
t("gameOver.lost.title"),
|
||||
t("gameOver.lost.summary", { score: gameState.score }),
|
||||
);
|
||||
}
|
||||
}
|
||||
const radius = gameState.ballSize / 2;
|
||||
// Make ball/coin bonce, and return bricks that were hit
|
||||
const {x, y, previousX, previousY} = ball;
|
||||
const { x, y, previousX, previousY } = ball;
|
||||
|
||||
const vhit = hitsSomething(previousX, y, radius);
|
||||
const hhit = hitsSomething(x, previousY, radius);
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
sample,
|
||||
sumOfKeys,
|
||||
} from "./game_utils";
|
||||
import { Upgrade } from "./types";
|
||||
|
||||
describe("getMajorityValue", () => {
|
||||
it("returns the most common string", () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Ball, GameState, PerkId, PerksMap} from "./types";
|
||||
import {icons, upgrades} from "./loadGameData";
|
||||
import { Ball, GameState, PerkId, PerksMap } from "./types";
|
||||
import { icons, upgrades } from "./loadGameData";
|
||||
|
||||
export function getMajorityValue(arr: string[]): string {
|
||||
const count: { [k: string]: number } = {};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { RawLevel } from "./types";
|
||||
|
||||
import _backgrounds from "./backgrounds.json";
|
||||
import _backgrounds from "./data/backgrounds.json";
|
||||
const backgrounds = _backgrounds as string[];
|
||||
|
||||
export function getLevelBackground(level: RawLevel) {
|
||||
|
|
|
@ -1792,6 +1792,56 @@
|
|||
</concept_node>
|
||||
</children>
|
||||
</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>
|
||||
<name>extra_levels</name>
|
||||
<children>
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
"gameOver.win.summary": "You cleared all levels for this run, catching {{score}} coins in total.",
|
||||
"gameOver.win.title": "Run finished",
|
||||
"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.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.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, 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_perfect": "Impressive, keep it up !",
|
||||
"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.start": "Start test run",
|
||||
"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_help": "Start a brand new run",
|
||||
"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.help": "+1 combo per brick broken, resets on coin lost",
|
||||
"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.help": "Play {{count}} levels instead of 7",
|
||||
"upgrades.extra_levels.name": "+1 level",
|
||||
|
@ -187,7 +190,7 @@
|
|||
"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.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.help": "Slower coin fall",
|
||||
"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.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.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.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.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, 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_perfect": "Impressionnant, continuez comme ça !",
|
||||
"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.start": "Démarrer la partie de test",
|
||||
"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_help": "Commencer une nouvelle 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.help": "+1 combo par brique cassée, remise à zéro quand une pièce est perdu",
|
||||
"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.help": "Jouer {{count}} niveaux au lieu de 7",
|
||||
"upgrades.extra_levels.name": "+1 niveau",
|
||||
|
@ -186,8 +189,8 @@
|
|||
"upgrades.telekinesis.help_plural": "Effet plus fort sur la balle",
|
||||
"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.help": "Plus de pièces si vous ne touchez pas le sommet.",
|
||||
"upgrades.top_is_lava.name": "Icare",
|
||||
"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.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.name": "Fluide visqueux ",
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
import fr from './fr.json'
|
||||
import en from './en.json'
|
||||
import {getSettingValue} from "../settings";
|
||||
import fr from "./fr.json";
|
||||
import en from "./en.json";
|
||||
import { getSettingValue } from "../settings";
|
||||
|
||||
type translationKeys = keyof typeof en
|
||||
type translation= { [key in translationKeys] : string }
|
||||
const languages:Record<string, translation>= {fr,en}
|
||||
export function getCurrentLang(){
|
||||
return getSettingValue('lang',getFirstBrowserLanguage())
|
||||
type translationKeys = keyof typeof en;
|
||||
type translation = { [key in translationKeys]: string };
|
||||
const languages: Record<string, translation> = { fr, en };
|
||||
export function getCurrentLang() {
|
||||
return getSettingValue("lang", getFirstBrowserLanguage());
|
||||
}
|
||||
|
||||
export function t(key: translationKeys, params: {[key:string]:any} = {}):string {
|
||||
const lang = getCurrentLang()
|
||||
let template=languages[lang]?.[key] || languages.en[key]
|
||||
for(let key in params){
|
||||
template=template.split('{{'+key+'}}').join(`${params[key]}`)
|
||||
export function t(
|
||||
key: translationKeys,
|
||||
params: { [key: string]: any } = {},
|
||||
): string {
|
||||
const lang = getCurrentLang();
|
||||
let template = languages[lang]?.[key] || languages.en[key];
|
||||
for (let key in params) {
|
||||
template = template.split("{{" + key + "}}").join(`${params[key]}`);
|
||||
}
|
||||
return template
|
||||
return template;
|
||||
}
|
||||
|
||||
function getFirstBrowserLanguage() {
|
||||
const preferred_languages = [
|
||||
...navigator.languages,
|
||||
navigator.language,
|
||||
'en'
|
||||
].filter(i => i)
|
||||
.map(i => i.slice(0, 2).toLowerCase())
|
||||
const supported = Object.keys(languages)
|
||||
const preferred_languages = [...navigator.languages, navigator.language, "en"]
|
||||
.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"
|
||||
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>
|
||||
@import "game.less";
|
||||
</style>
|
||||
<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="./PWA/icon.svg" />
|
||||
</head>
|
||||
<body>
|
||||
<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 _backgrounds from "./backgrounds.json";
|
||||
import _palette from "./palette.json";
|
||||
import _allLevels from "./levels.json";
|
||||
import { getLevelBackground, hashCode } from "./getLevelBackground";
|
||||
import { Palette, RawLevel } from "../types";
|
||||
import _backgrounds from "../data/backgrounds.json";
|
||||
import _palette from "../data/palette.json";
|
||||
import _allLevels from "../data/levels.json";
|
||||
import { getLevelBackground, hashCode } from "../getLevelBackground";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { moveLevel, resizeLevel, setBrick } from "./levels_editor_util";
|
||||
|
@ -34,7 +34,7 @@ function App() {
|
|||
|
||||
useEffect(() => {
|
||||
const timoutId = setTimeout(() => {
|
||||
return fetch("http://localhost:4400/src/levels.json", {
|
||||
return fetch("http://localhost:4400/src/data/levels.json", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
|
@ -1,4 +1,4 @@
|
|||
import { RawLevel } from "./types";
|
||||
import { RawLevel } from "../types";
|
||||
|
||||
export function resizeLevel(level: RawLevel, sizeDelta: number) {
|
||||
const { size, bricks } = level;
|
|
@ -1,6 +1,6 @@
|
|||
import _palette from "./palette.json";
|
||||
import _rawLevelsList from "./levels.json";
|
||||
import _appVersion from "./version.json";
|
||||
import _palette from "./data/palette.json";
|
||||
import _rawLevelsList from "./data/levels.json";
|
||||
import _appVersion from "./data/version.json";
|
||||
|
||||
describe("json data checks", () => {
|
||||
it("_rawLevelsList has icon levels", () => {
|
||||
|
|
|
@ -1,53 +1,17 @@
|
|||
import { Level, Palette, RawLevel, Upgrade } from "./types";
|
||||
import _palette from "./palette.json";
|
||||
import _rawLevelsList from "./levels.json";
|
||||
import _appVersion from "./version.json";
|
||||
import _palette from "./data/palette.json";
|
||||
import _rawLevelsList from "./data/levels.json";
|
||||
import _appVersion from "./data/version.json";
|
||||
import { rawUpgrades } from "./rawUpgrades";
|
||||
import { getLevelBackground } from "./getLevelBackground";
|
||||
import { levelIconHTML } from "./levelIcon";
|
||||
|
||||
const palette = _palette as Palette;
|
||||
|
||||
const rawLevelsList = _rawLevelsList as RawLevel[];
|
||||
|
||||
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 allLevels = rawLevelsList
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import {GameState, RunParams} from "./types";
|
||||
import {getTotalScore} from "./settings";
|
||||
import {allLevels, upgrades} from "./loadGameData";
|
||||
import {getPossibleUpgrades, makeEmptyPerksMap, sumOfKeys} from "./game_utils";
|
||||
import {dontOfferTooSoon, resetBalls} from "./gameStateMutators";
|
||||
import {isOptionOn} from "./options";
|
||||
import { GameState, RunParams } from "./types";
|
||||
import { getTotalScore } from "./settings";
|
||||
import { allLevels, upgrades } from "./loadGameData";
|
||||
import {
|
||||
getPossibleUpgrades,
|
||||
makeEmptyPerksMap,
|
||||
sumOfKeys,
|
||||
} from "./game_utils";
|
||||
import { dontOfferTooSoon, resetBalls } from "./gameStateMutators";
|
||||
import { isOptionOn } from "./options";
|
||||
|
||||
export function newGameState(params: RunParams): GameState {
|
||||
const totalScoreAtRunStart = getTotalScore();
|
||||
|
@ -21,7 +25,7 @@ export function newGameState(params: RunParams): GameState {
|
|||
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 = {
|
||||
runLevels,
|
||||
|
@ -77,6 +81,7 @@ export function newGameState(params: RunParams): GameState {
|
|||
misses: 0,
|
||||
balls_lost: 0,
|
||||
puck_bounces: 0,
|
||||
wall_bounces: 0,
|
||||
upgrades_picked: 1,
|
||||
max_combo: 1,
|
||||
max_level: 0,
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
import {t} from "./i18n/i18n";
|
||||
import { t } from "./i18n/i18n";
|
||||
|
||||
import {OptionDef, OptionId} from "./types";
|
||||
import {getSettingValue, setSettingValue} from "./settings";
|
||||
import { OptionDef, OptionId } from "./types";
|
||||
import { getSettingValue, setSettingValue } from "./settings";
|
||||
|
||||
export const options = {
|
||||
sound: {
|
||||
default: true,
|
||||
name: t('main_menu.sounds'),
|
||||
help: t('main_menu.sounds_help'),
|
||||
name: t("main_menu.sounds"),
|
||||
help: t("main_menu.sounds_help"),
|
||||
disabled: () => false,
|
||||
},
|
||||
"mobile-mode": {
|
||||
default: window.innerHeight > window.innerWidth,
|
||||
name: t('main_menu.mobile'),
|
||||
help: t('main_menu.mobile_help'),
|
||||
name: t("main_menu.mobile"),
|
||||
help: t("main_menu.mobile_help"),
|
||||
disabled: () => false,
|
||||
},
|
||||
basic: {
|
||||
default: false,
|
||||
name: t('main_menu.basic'),
|
||||
help: t('main_menu.basic_help'),
|
||||
name: t("main_menu.basic"),
|
||||
help: t("main_menu.basic_help"),
|
||||
disabled: () => false,
|
||||
},
|
||||
pointerLock: {
|
||||
default: false,
|
||||
name: t('main_menu.pointer_lock'),
|
||||
help: t('main_menu.pointer_lock_help'),
|
||||
name: t("main_menu.pointer_lock"),
|
||||
help: t("main_menu.pointer_lock_help"),
|
||||
disabled: () => !document.body.requestPointerLock,
|
||||
},
|
||||
easy: {
|
||||
default: false,
|
||||
name: t('main_menu.kid'),
|
||||
help: t('main_menu.kid_help'),
|
||||
name: t("main_menu.kid"),
|
||||
help: t("main_menu.kid_help"),
|
||||
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
|
||||
record: {
|
||||
default: false,
|
||||
name: t('main_menu.record'),
|
||||
help: t('main_menu.record_help'),
|
||||
name: t("main_menu.record"),
|
||||
help: t("main_menu.record_help"),
|
||||
disabled() {
|
||||
return window.location.search.includes("isInWebView=true");
|
||||
},
|
||||
|
@ -46,9 +46,12 @@ export const options = {
|
|||
} as const satisfies { [k: string]: OptionDef };
|
||||
|
||||
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) {
|
||||
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 = [
|
||||
{
|
||||
|
@ -7,9 +7,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "extra_life",
|
||||
max: 7,
|
||||
name: t('upgrades.extra_life.name'),
|
||||
help: (lvl: number) => lvl === 1 ? t('upgrades.extra_life.help'): t('upgrades.extra_life.help_plural',{lvl}),
|
||||
fullHelp: t('upgrades.extra_life.fullHelp'),
|
||||
name: t("upgrades.extra_life.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl === 1
|
||||
? t("upgrades.extra_life.help")
|
||||
: t("upgrades.extra_life.help_plural", { lvl }),
|
||||
fullHelp: t("upgrades.extra_life.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -17,9 +20,9 @@ export const rawUpgrades = [
|
|||
id: "streak_shots",
|
||||
giftable: true,
|
||||
max: 1,
|
||||
name: t('upgrades.streak_shots.name'),
|
||||
help: (lvl: number) => t('upgrades.streak_shots.help',{lvl}) ,
|
||||
fullHelp: t('upgrades.streak_shots.fullHelp'),
|
||||
name: t("upgrades.streak_shots.name"),
|
||||
help: (lvl: number) => t("upgrades.streak_shots.help", { lvl }),
|
||||
fullHelp: t("upgrades.streak_shots.fullHelp"),
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -28,9 +31,10 @@ export const rawUpgrades = [
|
|||
id: "base_combo",
|
||||
giftable: true,
|
||||
max: 7,
|
||||
name: t('upgrades.base_combo.name'),
|
||||
help: (lvl: number) => t('upgrades.base_combo.help',{coins:1 + lvl * 3}),
|
||||
fullHelp: t('upgrades.base_combo.fullHelp'),
|
||||
name: t("upgrades.base_combo.name"),
|
||||
help: (lvl: number) =>
|
||||
t("upgrades.base_combo.help", { coins: 1 + lvl * 3 }),
|
||||
fullHelp: t("upgrades.base_combo.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -38,9 +42,9 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "slow_down",
|
||||
max: 2,
|
||||
name: t('upgrades.slow_down.name'),
|
||||
help: () => t('upgrades.slow_down.help' ),
|
||||
fullHelp: t('upgrades.slow_down.fullHelp'),
|
||||
name: t("upgrades.slow_down.name"),
|
||||
help: () => t("upgrades.slow_down.help"),
|
||||
fullHelp: t("upgrades.slow_down.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -48,9 +52,9 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "bigger_puck",
|
||||
max: 2,
|
||||
name: t('upgrades.bigger_puck.name'),
|
||||
help: () => t('upgrades.bigger_puck.help' ),
|
||||
fullHelp: t('upgrades.bigger_puck.fullHelp'),
|
||||
name: t("upgrades.bigger_puck.name"),
|
||||
help: () => t("upgrades.bigger_puck.help"),
|
||||
fullHelp: t("upgrades.bigger_puck.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -59,9 +63,9 @@ export const rawUpgrades = [
|
|||
id: "viscosity",
|
||||
max: 3,
|
||||
|
||||
name: t('upgrades.viscosity.name'),
|
||||
help: () => t('upgrades.viscosity.help' ),
|
||||
fullHelp: t('upgrades.viscosity.fullHelp'),
|
||||
name: t("upgrades.viscosity.name"),
|
||||
help: () => t("upgrades.viscosity.help"),
|
||||
fullHelp: t("upgrades.viscosity.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -70,10 +74,9 @@ export const rawUpgrades = [
|
|||
giftable: true,
|
||||
max: 1,
|
||||
|
||||
name: t('upgrades.left_is_lava.name'),
|
||||
help: () => t('upgrades.left_is_lava.help' ),
|
||||
fullHelp: t('upgrades.left_is_lava.fullHelp'),
|
||||
|
||||
name: t("upgrades.left_is_lava.name"),
|
||||
help: () => t("upgrades.left_is_lava.help"),
|
||||
fullHelp: t("upgrades.left_is_lava.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -81,10 +84,9 @@ export const rawUpgrades = [
|
|||
id: "right_is_lava",
|
||||
giftable: true,
|
||||
max: 1,
|
||||
name: t('upgrades.right_is_lava.name'),
|
||||
help: () => t('upgrades.right_is_lava.help' ),
|
||||
fullHelp: t('upgrades.right_is_lava.fullHelp'),
|
||||
|
||||
name: t("upgrades.right_is_lava.name"),
|
||||
help: () => t("upgrades.right_is_lava.help"),
|
||||
fullHelp: t("upgrades.right_is_lava.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -92,10 +94,9 @@ export const rawUpgrades = [
|
|||
id: "top_is_lava",
|
||||
giftable: true,
|
||||
max: 1,
|
||||
name: t('upgrades.top_is_lava.name'),
|
||||
help: () => t('upgrades.top_is_lava.help' ),
|
||||
fullHelp: t('upgrades.top_is_lava.fullHelp'),
|
||||
|
||||
name: t("upgrades.top_is_lava.name"),
|
||||
help: () => t("upgrades.top_is_lava.help"),
|
||||
fullHelp: t("upgrades.top_is_lava.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -103,10 +104,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "skip_last",
|
||||
max: 7,
|
||||
name: t('upgrades.skip_last.name'),
|
||||
help: (lvl: number) => lvl==1 ? t('upgrades.skip_last.help' ) : t('upgrades.skip_last.help_plural', {lvl} ),
|
||||
fullHelp: t('upgrades.skip_last.fullHelp'),
|
||||
|
||||
name: t("upgrades.skip_last.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1
|
||||
? t("upgrades.skip_last.help")
|
||||
: t("upgrades.skip_last.help_plural", { lvl }),
|
||||
fullHelp: t("upgrades.skip_last.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -114,9 +117,12 @@ export const rawUpgrades = [
|
|||
id: "telekinesis",
|
||||
giftable: true,
|
||||
max: 2,
|
||||
name: t('upgrades.telekinesis.name'),
|
||||
help: (lvl: number) => lvl == 1 ? t('upgrades.telekinesis.help'): t('upgrades.telekinesis.help_plural'),
|
||||
fullHelp: t('upgrades.telekinesis.fullHelp'),
|
||||
name: t("upgrades.telekinesis.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1
|
||||
? t("upgrades.telekinesis.help")
|
||||
: t("upgrades.telekinesis.help_plural"),
|
||||
fullHelp: t("upgrades.telekinesis.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -124,9 +130,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "coin_magnet",
|
||||
max: 3,
|
||||
name: t('upgrades.coin_magnet.name'),
|
||||
help: (lvl: number) => lvl == 1 ? t('upgrades.coin_magnet.help'): t('upgrades.coin_magnet.help_plural'),
|
||||
fullHelp: t('upgrades.coin_magnet.fullHelp'),
|
||||
name: t("upgrades.coin_magnet.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1
|
||||
? t("upgrades.coin_magnet.help")
|
||||
: t("upgrades.coin_magnet.help_plural"),
|
||||
fullHelp: t("upgrades.coin_magnet.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -134,11 +143,9 @@ export const rawUpgrades = [
|
|||
id: "multiball",
|
||||
giftable: true,
|
||||
max: 6,
|
||||
name: t('upgrades.multiball.name'),
|
||||
help: (lvl: number) => t('upgrades.multiball.help',{count:lvl+1}) ,
|
||||
fullHelp: t('upgrades.multiball.fullHelp'),
|
||||
|
||||
|
||||
name: t("upgrades.multiball.name"),
|
||||
help: (lvl: number) => t("upgrades.multiball.help", { count: lvl + 1 }),
|
||||
fullHelp: t("upgrades.multiball.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -146,11 +153,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "smaller_puck",
|
||||
max: 2,
|
||||
name: t('upgrades.smaller_puck.name'),
|
||||
help: (lvl: number) => lvl == 1 ? t('upgrades.smaller_puck.help'): t('upgrades.smaller_puck.help_plural'),
|
||||
fullHelp: t('upgrades.smaller_puck.fullHelp'),
|
||||
|
||||
|
||||
name: t("upgrades.smaller_puck.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1
|
||||
? t("upgrades.smaller_puck.help")
|
||||
: t("upgrades.smaller_puck.help_plural"),
|
||||
fullHelp: t("upgrades.smaller_puck.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -158,9 +166,9 @@ export const rawUpgrades = [
|
|||
id: "pierce",
|
||||
giftable: true,
|
||||
max: 3,
|
||||
name: t('upgrades.pierce.name'),
|
||||
help: (lvl: number) => t('upgrades.pierce.help',{count:3 * lvl}) ,
|
||||
fullHelp: t('upgrades.pierce.fullHelp'),
|
||||
name: t("upgrades.pierce.name"),
|
||||
help: (lvl: number) => t("upgrades.pierce.help", { count: 3 * lvl }),
|
||||
fullHelp: t("upgrades.pierce.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -168,10 +176,9 @@ export const rawUpgrades = [
|
|||
id: "picky_eater",
|
||||
giftable: true,
|
||||
max: 1,
|
||||
name: t('upgrades.picky_eater.name'),
|
||||
help: (lvl: number) => t('upgrades.picky_eater.help') ,
|
||||
fullHelp: t('upgrades.picky_eater.fullHelp'),
|
||||
|
||||
name: t("upgrades.picky_eater.name"),
|
||||
help: (lvl: number) => t("upgrades.picky_eater.help"),
|
||||
fullHelp: t("upgrades.picky_eater.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -179,11 +186,9 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "metamorphosis",
|
||||
max: 1,
|
||||
name: t('upgrades.metamorphosis.name'),
|
||||
help: (lvl: number) => t('upgrades.metamorphosis.help'),
|
||||
fullHelp: t('upgrades.metamorphosis.fullHelp'),
|
||||
|
||||
|
||||
name: t("upgrades.metamorphosis.name"),
|
||||
help: (lvl: number) => t("upgrades.metamorphosis.help"),
|
||||
fullHelp: t("upgrades.metamorphosis.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -191,9 +196,9 @@ export const rawUpgrades = [
|
|||
id: "compound_interest",
|
||||
giftable: true,
|
||||
max: 1,
|
||||
name: t('upgrades.compound_interest.name'),
|
||||
help: (lvl: number) => t('upgrades.compound_interest.help') ,
|
||||
fullHelp: t('upgrades.compound_interest.fullHelp'),
|
||||
name: t("upgrades.compound_interest.name"),
|
||||
help: (lvl: number) => t("upgrades.compound_interest.help"),
|
||||
fullHelp: t("upgrades.compound_interest.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -201,13 +206,13 @@ export const rawUpgrades = [
|
|||
id: "hot_start",
|
||||
giftable: true,
|
||||
max: 3,
|
||||
name: t('upgrades.hot_start.name'),
|
||||
help: (lvl: number) => t('upgrades.hot_start.help',{
|
||||
start:lvl * 15 + 1,
|
||||
lvl
|
||||
name: t("upgrades.hot_start.name"),
|
||||
help: (lvl: number) =>
|
||||
t("upgrades.hot_start.help", {
|
||||
start: lvl * 15 + 1,
|
||||
lvl,
|
||||
}),
|
||||
fullHelp: t('upgrades.hot_start.fullHelp'),
|
||||
|
||||
fullHelp: t("upgrades.hot_start.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -215,9 +220,12 @@ export const rawUpgrades = [
|
|||
id: "sapper",
|
||||
giftable: true,
|
||||
max: 7,
|
||||
name: t('upgrades.sapper.name'),
|
||||
help: (lvl: number) => lvl == 1 ? t('upgrades.sapper.help'): t('upgrades.sapper.help_plural',{lvl}),
|
||||
fullHelp: t('upgrades.sapper.fullHelp'),
|
||||
name: t("upgrades.sapper.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1
|
||||
? t("upgrades.sapper.help")
|
||||
: t("upgrades.sapper.help_plural", { lvl }),
|
||||
fullHelp: t("upgrades.sapper.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -225,9 +233,9 @@ export const rawUpgrades = [
|
|||
id: "bigger_explosions",
|
||||
giftable: false,
|
||||
max: 1,
|
||||
name: t('upgrades.bigger_explosions.name'),
|
||||
help: (lvl: number) => t('upgrades.bigger_explosions.help'),
|
||||
fullHelp: t('upgrades.bigger_explosions.fullHelp'),
|
||||
name: t("upgrades.bigger_explosions.name"),
|
||||
help: (lvl: number) => t("upgrades.bigger_explosions.help"),
|
||||
fullHelp: t("upgrades.bigger_explosions.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -235,9 +243,9 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "extra_levels",
|
||||
max: 3,
|
||||
name: t('upgrades.extra_levels.name'),
|
||||
help: (lvl: number) => t('upgrades.extra_levels.help',{count:lvl + 7}) ,
|
||||
fullHelp: t('upgrades.extra_levels.fullHelp'),
|
||||
name: t("upgrades.extra_levels.name"),
|
||||
help: (lvl: number) => t("upgrades.extra_levels.help", { count: lvl + 7 }),
|
||||
fullHelp: t("upgrades.extra_levels.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -245,10 +253,9 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "pierce_color",
|
||||
max: 1,
|
||||
name: t('upgrades.pierce_color.name'),
|
||||
help: (lvl: number) => t('upgrades.pierce_color.help') ,
|
||||
fullHelp: t('upgrades.pierce_color.fullHelp'),
|
||||
|
||||
name: t("upgrades.pierce_color.name"),
|
||||
help: (lvl: number) => t("upgrades.pierce_color.help"),
|
||||
fullHelp: t("upgrades.pierce_color.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -256,11 +263,9 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "soft_reset",
|
||||
max: 9,
|
||||
name: t('upgrades.soft_reset.name'),
|
||||
help: (lvl: number) => t('upgrades.soft_reset.help',{percent:10*lvl}),
|
||||
fullHelp: t('upgrades.soft_reset.fullHelp'),
|
||||
|
||||
|
||||
name: t("upgrades.soft_reset.name"),
|
||||
help: (lvl: number) => t("upgrades.soft_reset.help", { percent: 10 * lvl }),
|
||||
fullHelp: t("upgrades.soft_reset.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "multiball",
|
||||
|
@ -268,11 +273,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "ball_repulse_ball",
|
||||
max: 3,
|
||||
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'),
|
||||
fullHelp: t('upgrades.ball_repulse_ball.fullHelp'),
|
||||
|
||||
|
||||
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"),
|
||||
fullHelp: t("upgrades.ball_repulse_ball.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "multiball",
|
||||
|
@ -280,9 +286,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "ball_attract_ball",
|
||||
max: 3,
|
||||
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'),
|
||||
fullHelp: t('upgrades.ball_attract_ball.fullHelp'),
|
||||
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"),
|
||||
fullHelp: t("upgrades.ball_attract_ball.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -290,10 +299,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "puck_repulse_ball",
|
||||
max: 2,
|
||||
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'),
|
||||
fullHelp: t('upgrades.puck_repulse_ball.fullHelp'),
|
||||
|
||||
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"),
|
||||
fullHelp: t("upgrades.puck_repulse_ball.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -301,11 +312,10 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "wind",
|
||||
max: 3,
|
||||
name: t('upgrades.wind.name'),
|
||||
help: (lvl: number) => lvl == 1 ? t('upgrades.wind.help'): t('upgrades.wind.help_plural'),
|
||||
fullHelp: t('upgrades.wind.fullHelp'),
|
||||
|
||||
|
||||
name: t("upgrades.wind.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1 ? t("upgrades.wind.help") : t("upgrades.wind.help_plural"),
|
||||
fullHelp: t("upgrades.wind.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -313,10 +323,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "sturdy_bricks",
|
||||
max: 4,
|
||||
name: t('upgrades.telekinesis.name'),
|
||||
help: (lvl: number) => lvl == 1 ? t('upgrades.telekinesis.help'): t('upgrades.telekinesis.help_plural'),
|
||||
fullHelp: t('upgrades.telekinesis.fullHelp'),
|
||||
|
||||
name: t("upgrades.telekinesis.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1
|
||||
? t("upgrades.telekinesis.help")
|
||||
: t("upgrades.telekinesis.help_plural"),
|
||||
fullHelp: t("upgrades.telekinesis.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -324,11 +336,10 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "respawn",
|
||||
max: 4,
|
||||
name: t('upgrades.respawn.name'),
|
||||
help: (lvl: number) => lvl == 1 ? t('upgrades.respawn.help'): t('upgrades.respawn.help_plural'),
|
||||
fullHelp: t('upgrades.respawn.fullHelp'),
|
||||
|
||||
|
||||
name: t("upgrades.respawn.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1 ? t("upgrades.respawn.help") : t("upgrades.respawn.help_plural"),
|
||||
fullHelp: t("upgrades.respawn.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -336,10 +347,9 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "one_more_choice",
|
||||
max: 3,
|
||||
name: t('upgrades.one_more_choice.name'),
|
||||
help: (lvl: number) => t('upgrades.one_more_choice.help'),
|
||||
fullHelp: t('upgrades.one_more_choice.fullHelp'),
|
||||
|
||||
name: t("upgrades.one_more_choice.name"),
|
||||
help: (lvl: number) => t("upgrades.one_more_choice.help"),
|
||||
fullHelp: t("upgrades.one_more_choice.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
@ -347,9 +357,18 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "instant_upgrade",
|
||||
max: 2,
|
||||
name: t('upgrades.instant_upgrade.name'),
|
||||
help: (lvl: number) => t('upgrades.instant_upgrade.help') ,
|
||||
fullHelp: t('upgrades.instant_upgrade.fullHelp'),
|
||||
|
||||
name: t("upgrades.instant_upgrade.name"),
|
||||
help: (lvl: number) => t("upgrades.instant_upgrade.help"),
|
||||
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;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {gameCanvas} from "./render";
|
||||
import {max_levels} from "./game_utils";
|
||||
import {getAudioRecordingTrack} from "./sounds";
|
||||
import {t} from "./i18n/i18n";
|
||||
import {GameState} from "./types";
|
||||
import {isOptionOn} from "./options";
|
||||
import { gameCanvas } from "./render";
|
||||
import { max_levels } from "./game_utils";
|
||||
import { getAudioRecordingTrack } from "./sounds";
|
||||
import { t } from "./i18n/i18n";
|
||||
import { GameState } from "./types";
|
||||
import { isOptionOn } from "./options";
|
||||
|
||||
let mediaRecorder: MediaRecorder | null,
|
||||
captureStream: MediaStream,
|
||||
|
@ -11,7 +11,7 @@ let mediaRecorder: MediaRecorder | null,
|
|||
recordCanvas: HTMLCanvasElement,
|
||||
recordCanvasCtx: CanvasRenderingContext2D;
|
||||
|
||||
export function recordOneFrame(gameState:GameState) {
|
||||
export function recordOneFrame(gameState: GameState) {
|
||||
if (!isOptionOn("record")) {
|
||||
return;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export function recordOneFrame(gameState:GameState) {
|
|||
}
|
||||
}
|
||||
|
||||
export function drawMainCanvasOnSmallCanvas(gameState:GameState) {
|
||||
export function drawMainCanvasOnSmallCanvas(gameState: GameState) {
|
||||
if (!recordCanvasCtx) return;
|
||||
recordCanvasCtx.drawImage(
|
||||
gameCanvas,
|
||||
|
@ -58,7 +58,7 @@ export function drawMainCanvasOnSmallCanvas(gameState:GameState) {
|
|||
);
|
||||
}
|
||||
|
||||
export function startRecordingGame(gameState:GameState) {
|
||||
export function startRecordingGame(gameState: GameState) {
|
||||
if (!isOptionOn("record")) {
|
||||
return;
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ export function startRecordingGame(gameState:GameState) {
|
|||
|
||||
instance.onstop = async function () {
|
||||
let targetDiv: HTMLElement | null;
|
||||
let blob = new Blob(recordedChunks, {type: "video/webm"});
|
||||
let blob = new Blob(recordedChunks, { type: "video/webm" });
|
||||
if (blob.size < 200000) return; // under 0.2MB, probably bugged out or pointlessly short
|
||||
|
||||
while (
|
||||
|
@ -123,15 +123,15 @@ export function startRecordingGame(gameState:GameState) {
|
|||
a.download = captureFileName("webm");
|
||||
a.target = "_blank";
|
||||
a.href = video.src;
|
||||
a.textContent = t('main_menu.record_download', {
|
||||
size: (blob.size / 1000000).toFixed(2)
|
||||
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")) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import {baseCombo} from "./gameStateMutators";
|
||||
import {brickCenterX, brickCenterY, currentLevelInfo, isTelekinesisActive, max_levels} from "./game_utils";
|
||||
import {colorString, GameState} from "./types";
|
||||
import {t} from "./i18n/i18n";
|
||||
import {gameState} from "./game";
|
||||
import {isOptionOn} from "./options";
|
||||
import { baseCombo } from "./gameStateMutators";
|
||||
import {
|
||||
brickCenterX,
|
||||
brickCenterY,
|
||||
currentLevelInfo,
|
||||
isTelekinesisActive,
|
||||
max_levels,
|
||||
} from "./game_utils";
|
||||
import { colorString, GameState } from "./types";
|
||||
import { t } from "./i18n/i18n";
|
||||
import { gameState } from "./game";
|
||||
import { isOptionOn } from "./options";
|
||||
|
||||
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
|
||||
export const ctx = gameCanvas.getContext("2d", {
|
||||
|
@ -14,18 +20,17 @@ export const background = document.createElement("img");
|
|||
export const backgroundCanvas = document.createElement("canvas");
|
||||
|
||||
export function render(gameState: GameState) {
|
||||
|
||||
const level = currentLevelInfo(gameState);
|
||||
const {width, height} = gameCanvas;
|
||||
const { width, height } = gameCanvas;
|
||||
if (!width || !height) return;
|
||||
|
||||
if (gameState.currentLevel || gameState.levelTime) {
|
||||
menuLabel.innerText = t('play.current_lvl', {
|
||||
menuLabel.innerText = t("play.current_lvl", {
|
||||
level: gameState.currentLevel + 1,
|
||||
max: max_levels(gameState)
|
||||
max: max_levels(gameState),
|
||||
});
|
||||
} else {
|
||||
menuLabel.innerText = t('play.menu_label')
|
||||
menuLabel.innerText = t("play.menu_label");
|
||||
}
|
||||
scoreDisplay.innerText = `$${gameState.score}`;
|
||||
|
||||
|
@ -70,7 +75,7 @@ export function render(gameState: GameState) {
|
|||
});
|
||||
ctx.globalAlpha = 1;
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const {x, y, time, color, size, type, duration} = flash;
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
if (type === "ball") {
|
||||
|
@ -118,7 +123,7 @@ export function render(gameState: GameState) {
|
|||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const {x, y, time, color, size, type, duration} = flash;
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
if (type === "particle") {
|
||||
|
@ -130,7 +135,7 @@ export function render(gameState: GameState) {
|
|||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5;
|
||||
const shaked = lastExplosionDelay < 200 && !isOptionOn('basic');
|
||||
const shaked = lastExplosionDelay < 200 && !isOptionOn("basic");
|
||||
if (shaked) {
|
||||
const amplitude =
|
||||
((gameState.perks.bigger_explosions + 1) * 50) / lastExplosionDelay;
|
||||
|
@ -139,11 +144,12 @@ export function render(gameState: GameState) {
|
|||
Math.sin(Date.now() + 36) * amplitude,
|
||||
);
|
||||
}
|
||||
if (gameState.perks.bigger_explosions && !isOptionOn('basic')) {
|
||||
if (gameState.perks.bigger_explosions && !isOptionOn("basic")) {
|
||||
if (shaked) {
|
||||
gameCanvas.style.filter = 'brightness(' + (1 + 100 / (1 + lastExplosionDelay)) + ')';
|
||||
gameCanvas.style.filter =
|
||||
"brightness(" + (1 + 100 / (1 + lastExplosionDelay)) + ")";
|
||||
} else {
|
||||
gameCanvas.style.filter = ''
|
||||
gameCanvas.style.filter = "";
|
||||
}
|
||||
}
|
||||
// Coins
|
||||
|
@ -189,7 +195,7 @@ export function render(gameState: GameState) {
|
|||
);
|
||||
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const {x, y, time, color, size, type, duration} = flash;
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2));
|
||||
if (type === "text") {
|
||||
|
@ -247,9 +253,23 @@ export function render(gameState: GameState) {
|
|||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
if (gameState.perks.streak_shots && gameState.combo > baseCombo(gameState)) {
|
||||
drawPuck(ctx, "red", gameState.puckWidth, gameState.puckHeight, -2);
|
||||
drawPuck(
|
||||
ctx,
|
||||
"red",
|
||||
gameState.puckWidth,
|
||||
gameState.puckHeight,
|
||||
-2,
|
||||
!!gameState.perks.concave_puck,
|
||||
);
|
||||
}
|
||||
drawPuck(ctx, gameState.puckColor, gameState.puckWidth, gameState.puckHeight);
|
||||
drawPuck(
|
||||
ctx,
|
||||
gameState.puckColor,
|
||||
gameState.puckWidth,
|
||||
gameState.puckHeight,
|
||||
0,
|
||||
!!gameState.perks.concave_puck,
|
||||
);
|
||||
|
||||
if (gameState.combo > 1) {
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
|
@ -328,7 +348,7 @@ export function render(gameState: GameState) {
|
|||
if (!gameState.running) {
|
||||
drawText(
|
||||
ctx,
|
||||
t('play.mobile_press_to_play'),
|
||||
t("play.mobile_press_to_play"),
|
||||
gameState.puckColor,
|
||||
gameState.puckHeight,
|
||||
gameState.canvasWidth / 2,
|
||||
|
@ -348,7 +368,6 @@ export function render(gameState: GameState) {
|
|||
if (shaked) {
|
||||
ctx.resetTransform();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let cachedBricksRender = document.createElement("canvas");
|
||||
|
@ -358,7 +377,9 @@ export function renderAllBricks() {
|
|||
ctx.globalAlpha = 1;
|
||||
|
||||
const redBorderOnBricksWithWrongColor =
|
||||
gameState.combo > baseCombo(gameState) && gameState.perks.picky_eater && !isOptionOn('basic');
|
||||
gameState.combo > baseCombo(gameState) &&
|
||||
gameState.perks.picky_eater &&
|
||||
!isOptionOn("basic");
|
||||
|
||||
const newKey =
|
||||
gameState.gameZoneWidth +
|
||||
|
@ -415,8 +436,10 @@ export function drawPuck(
|
|||
puckWidth: number,
|
||||
puckHeight: number,
|
||||
yOffset = 0,
|
||||
flipped: boolean,
|
||||
) {
|
||||
const key = "puck" + color + "_" + puckWidth + "_" + puckHeight;
|
||||
const key =
|
||||
"puck" + color + "_" + puckWidth + "_" + puckHeight + "_" + flipped;
|
||||
|
||||
if (!cachedGraphics[key]) {
|
||||
const can = document.createElement("canvas");
|
||||
|
@ -427,6 +450,19 @@ export function drawPuck(
|
|||
|
||||
canctx.beginPath();
|
||||
canctx.moveTo(0, puckHeight * 2);
|
||||
|
||||
if (flipped) {
|
||||
canctx.lineTo(0, puckHeight * 0.75);
|
||||
canctx.bezierCurveTo(
|
||||
puckWidth / 2,
|
||||
puckHeight,
|
||||
puckWidth / 2,
|
||||
puckHeight * 1,
|
||||
puckWidth,
|
||||
puckHeight * 0.75,
|
||||
);
|
||||
canctx.lineTo(puckWidth, puckHeight * 2);
|
||||
} else {
|
||||
canctx.lineTo(0, puckHeight * 1.25);
|
||||
canctx.bezierCurveTo(
|
||||
0,
|
||||
|
@ -437,6 +473,8 @@ export function drawPuck(
|
|||
puckHeight * 1.25,
|
||||
);
|
||||
canctx.lineTo(puckWidth, puckHeight * 2);
|
||||
}
|
||||
|
||||
canctx.fill();
|
||||
cachedGraphics[key] = can;
|
||||
}
|
||||
|
@ -713,5 +751,7 @@ export function drawText(
|
|||
);
|
||||
}
|
||||
|
||||
export const scoreDisplay = document.getElementById("score") as HTMLButtonElement;
|
||||
export const scoreDisplay = document.getElementById(
|
||||
"score",
|
||||
) as HTMLButtonElement;
|
||||
const menuLabel = document.getElementById("menuLabel") as HTMLButtonElement;
|
|
@ -1,37 +1,35 @@
|
|||
// Settings
|
||||
|
||||
import {GameState} from "./types";
|
||||
import { GameState } from "./types";
|
||||
|
||||
let cachedSettings: { [key: string]: unknown } = {};
|
||||
|
||||
export function getSettingValue<T>(key: string, defaultValue: T) {
|
||||
if (typeof cachedSettings[key] == "undefined") {
|
||||
try {
|
||||
const ls = localStorage.getItem( key);
|
||||
const ls = localStorage.getItem(key);
|
||||
if (ls) cachedSettings[key] = JSON.parse(ls) as T;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
return cachedSettings[key] as T ?? defaultValue;
|
||||
return (cachedSettings[key] as T) ?? defaultValue;
|
||||
}
|
||||
|
||||
export function setSettingValue<T>(key: string, value: T) {
|
||||
cachedSettings[key] = value
|
||||
cachedSettings[key] = value;
|
||||
try {
|
||||
localStorage.setItem( key, JSON.stringify(value));
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function getTotalScore() {
|
||||
return getSettingValue('breakout_71_total_score', 0)
|
||||
|
||||
return getSettingValue("breakout_71_total_score", 0);
|
||||
}
|
||||
|
||||
export function addToTotalScore(gameState: GameState, points: number) {
|
||||
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 {isOptionOn} from "./options";
|
||||
import { isOptionOn } from "./options";
|
||||
|
||||
export const sounds = {
|
||||
wallBeep: (pan: number) => {
|
||||
|
|
7
src/types.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
import {rawUpgrades} from "./rawUpgrades";
|
||||
import {options} from "./options";
|
||||
import { rawUpgrades } from "./rawUpgrades";
|
||||
import { options } from "./options";
|
||||
|
||||
export type colorString = string;
|
||||
|
||||
|
@ -98,7 +98,6 @@ export type Ball = {
|
|||
piercedSinceBounce: number;
|
||||
hitSinceBounce: number;
|
||||
hitItem: { index: number; color: string }[];
|
||||
bouncesList: { x: number; y: number }[];
|
||||
sapperUses: number;
|
||||
destroyed?: boolean;
|
||||
};
|
||||
|
@ -141,6 +140,7 @@ export type RunStats = {
|
|||
misses: number;
|
||||
balls_lost: number;
|
||||
puck_bounces: number;
|
||||
wall_bounces: number;
|
||||
upgrades_picked: number;
|
||||
max_combo: number;
|
||||
max_level: number;
|
||||
|
@ -233,6 +233,7 @@ export type GameState = {
|
|||
runStatistics: RunStats;
|
||||
lastOffered: Partial<{ [k in PerkId]: number }>;
|
||||
levelTime: number;
|
||||
levelWallBounces: number;
|
||||
autoCleanUses: number;
|
||||
};
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
"29033878"
|