Added save mechanic

This commit is contained in:
Renan LE CARO 2025-03-17 11:50:13 +01:00
parent 7aff5be9cd
commit 659d79bcd0
23 changed files with 333 additions and 20292 deletions

View file

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

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding ="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"

File diff suppressed because one or more lines are too long

View file

@ -1,13 +1,40 @@
package me.lecaro.breakout
import android.app.Activity
import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.Window
import android.view.WindowManager
import android.webkit.ConsoleMessage
import android.webkit.DownloadListener
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.widget.Toast
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
const val CHOOSE_FILE_REQUEST_CODE = 548459
class MainActivity : android.app.Activity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
CHOOSE_FILE_REQUEST_CODE -> {
if (resultCode == RESULT_OK) {
filePathCallback?.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data))
filePathCallback = null
}
}
}
}
var filePathCallback: ValueCallback<Array<Uri>>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE);
@ -15,11 +42,13 @@ class MainActivity : android.app.Activity() {
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
);
// WebView.setWebContentsDebuggingEnabled(true)
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.setSupportZoom(false)
webView.loadUrl("file:///android_asset/index.html?isInWebView=true")
webView.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
Log.d(
@ -28,7 +57,46 @@ class MainActivity : android.app.Activity() {
)
return true
}
override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams?): Boolean {
startActivityForResult(fileChooserParams?.createIntent(), CHOOSE_FILE_REQUEST_CODE)
this@MainActivity.filePathCallback = filePathCallback
return true
}
}
webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
if (!url.startsWith("data:")) {
Log.w("DL","url ignored because it does not start with data:")
return@DownloadListener
}
val sdf = SimpleDateFormat("yyyy-M-dd-hh-mm")
val currentDate = sdf.format(Date())
// Extract filename from contentDisposition if available
if (url.startsWith("data:application/json;base64,")) {
Log.d("DL","saving application/json ")
val base64Data = url.substringAfterLast(',')
val decodedBytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT)
val jsonData = String(decodedBytes)
;
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val fileName = "breakout-71-save-$currentDate.b71"
val file = File(dir, fileName)
file.writeText(jsonData)
Toast.makeText(this, "Saved in $dir", Toast. LENGTH_LONG).show()
Log.d("DL","finished saving application/json ")
}else if (url.startsWith("data:video/webm;base64,")){
Log.d("DL","saving video/webm ")
// TODO
Log.d("DL","finished savign video/webm ")
}else{
Log.w("DL","unexpected type "+url)
}
})
setContentView(webView)
}

2
dist/PWA/sw-b71.js vendored
View file

@ -1,2 +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("29035871"),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}});
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("29036807"),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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16297
dist/editor.1ec04b8f.js vendored

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,56 +0,0 @@
body {
color: #fff;
background: #000;
}
#palette {
width: 80px;
position: fixed;
top: 0;
bottom: 0;
right: 0;
overflow: auto;
}
#palette button.active {
transform: scale(1.2);
}
#levels {
flex-wrap: wrap;
align-items: flex-start;
gap: 40px;
margin-right: 80px;
display: flex;
}
#levels .level-bricks-preview {
position: relative;
}
#levels input[type="number"] {
width: 50px;
}
#levels > div {
grid-template-columns: auto auto;
grid-template-areas: ". name"
"buttons bricks";
display: grid;
}
#levels > div > :first-child {
grid-area: name;
}
#levels > div > div:nth-child(2) {
flex-direction: column;
grid-area: buttons;
align-items: flex-end;
display: flex;
}
#levels > div > div:nth-child(3) {
grid-area: bricks;
}
/*# sourceMappingURL=editor.9680328c.css.map */

View file

@ -1 +0,0 @@
{"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/"}

14
dist/editor.html vendored
View file

@ -1,14 +0,0 @@
<!doctype html>
<html lang="en">
<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="/editor.9680328c.css">
</head>
<body>
<div id="app"></div>
<script src="/editor.1ec04b8f.js" defer=""></script>
</body>
</html>

3687
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
// The version of the cache.
const VERSION = "29035871";
const VERSION = "29036807";
// The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`;

View file

@ -1 +1 @@
"29035871"
"29036807"

View file

@ -52,12 +52,6 @@ import {
} from "./asyncAlert";
import { isOptionOn, options, toggleOption } from "./options";
bombSVG.src =
"data:image/svg+xml;base64," +
btoa(`<svg width="144" height="144" viewBox="0 0 38.101 38.099" xmlns="http://www.w3.org/2000/svg">
<path d="m6.1528 26.516c-2.6992-3.4942-2.9332-8.281-.58305-11.981a10.454 10.454 0 017.3701-4.7582c1.962-.27726 4.1646.05953 5.8835.90027l.45013.22017.89782-.87417c.83748-.81464.91169-.87499 1.0992-.90271.40528-.058713.58876.03425 1.1971.6116l.55451.52679 1.0821-1.0821c1.1963-1.1963 1.383-1.3357 2.1039-1.5877.57898-.20223 1.5681-.19816 2.1691.00897 1.4613.50314 2.3673 1.7622 2.3567 3.2773-.0058.95654-.24464 1.5795-.90924 2.3746-.40936.48928-.55533.81057-.57898 1.2737-.02039.41018.1109.77714.42322 1.1792.30172.38816.3694.61323.2797.93044-.12803.45666-.56674.71598-1.0242.60507-.601-.14597-1.3031-1.3088-1.3969-2.3126-.09459-1.0161.19245-1.8682.92392-2.7432.42567-.50885.5643-.82851.5643-1.3031 0-.50151-.14026-.83177-.51211-1.2028-.50966-.50966-1.0968-.64829-1.781-.41996l-.37348.12477-2.1006 2.1006.52597.55696c.45421.48194.5325.58876.57898.78855.09622.41588.07502.45014-.88396 1.4548l-.87173.9125.26339.57979a10.193 10.193 0 01.9231 4.1001c.03996 2.046-.41996 3.8082-1.4442 5.537-.55044.928-1.0185 1.5013-1.8968 2.3241-.83503.78284-1.5526 1.2827-2.4904 1.7361-3.4266 1.657-7.4721 1.3422-10.549-.82035-.73473-.51782-1.7312-1.4621-2.2515-2.1357zm21.869-4.5584c-.0579-.19734-.05871-2.2662 0-2.4545.11906-.39142.57898-.63361 1.0038-.53005.23812.05708.54147.32455.6116.5382.06279.19163.06769 2.1805.0065 2.3811-.12558.40773-.61649.67602-1.0462.57164-.234-.05708-.51615-.30498-.57568-.50722m3.0417-2.6013c-.12313-.6222.37837-1.1049 1.0479-1.0079.18348.0261.25279.08399 1.0071.83911.75838.75838.81301.82362.84074 1.0112.10193.68499-.40365 1.1938-1.034 1.0405-.1949-.0473-.28786-.12558-1.0144-.85216-.7649-.76409-.80241-.81057-.84645-1.0316m.61323-3.0629a.85623.85623 0 01.59284-.99975c.28949-.09214 2.1814-.08318 2.3917.01141.38734.17369.6279.61078.53984.98181-.06035.25606-.35391.57327-.60181.64992-.25279.07747-2.2278.053-2.4097-.03017-.26013-.11906-.46318-.36125-.51374-.61323" fill="#fff" opacity="0.3"/>
</svg>`);
export function play() {
if (gameState.running) return;
gameState.running = true;
@ -261,8 +255,8 @@ gameCanvas.addEventListener("mouseup", (e) => {
pause(true);
} else {
play();
if (isOptionOn("pointerLock")) {
gameCanvas.requestPointerLock();
if (isOptionOn("pointerLock") && gameCanvas.requestPointerLock) {
gameCanvas.requestPointerLock().then();
}
}
});
@ -543,7 +537,6 @@ async function openSettingsPanel() {
for (const key of Object.keys(options) as OptionId[]) {
if (options[key])
actions.push({
disabled: options[key].disabled(),
icon: isOptionOn(key)
? icons["icon:checkmark_checked"]
: icons["icon:checkmark_unchecked"],
@ -655,6 +648,116 @@ async function openSettingsPanel() {
},
});
actions.push({
text: t("main_menu.download_save_file"),
help: t("main_menu.download_save_file_help"),
async value() {
const localStorageContent: Record<string, string> = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i) as string;
const value = localStorage.getItem(key) as string;
// Store the key-value pair in the object
localStorageContent[key] = value;
}
const dlLink = document.createElement("a");
dlLink.setAttribute(
"href",
"data:application/json;base64," +
btoa(
JSON.stringify({
fileType: "B71-save-file",
appVersion,
localStorageContent,
}),
),
);
dlLink.setAttribute(
"download",
"b71-save-" +
new Date()
.toISOString()
.slice(0, 19)
.replace(/[^0-9]+/gi, "-") +
".b71",
);
document.body.appendChild(dlLink);
dlLink.click();
setTimeout(() => document.body.removeChild(dlLink), 1000);
},
});
actions.push({
text: t("main_menu.load_save_file"),
help: t("main_menu.load_save_file_help"),
async value() {
if (!document.getElementById("save_file_picker")) {
let input: HTMLInputElement = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("id", "save_file_picker");
input.setAttribute("accept", ".b71");
input.style.position = "absolute";
input.style.left = "-1000px";
input.addEventListener("change", async (e) => {
try {
const file = input && input.files?.item(0);
if (file) {
const content = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = function () {
resolve(reader.result?.toString() || "");
};
reader.onerror = function () {
reject(reader.error);
};
// Read the file as a text string
reader.readAsText(file);
});
const {
fileType,
appVersion: fileVersion,
localStorageContent,
} = JSON.parse(content);
if (fileType !== "B71-save-file")
throw new Error("Not a B71 save file");
if (fileVersion > appVersion)
throw new Error(
"Please update your app first, this file is for version " +
fileVersion +
" or newer.",
);
localStorage.clear();
for (let key in localStorageContent) {
localStorage.setItem(key, localStorageContent[key]);
}
await asyncAlert({
title: t("main_menu.save_file_loaded"),
text: t("main_menu.save_file_loaded_help"),
actions: [{ text: t("main_menu.save_file_loaded_ok") }],
});
window.location.reload();
}
} catch (e: any) {
await asyncAlert({
title: t("main_menu.save_file_error"),
text: e.message,
actions: [{ text: t("main_menu.save_file_loaded_ok") }],
});
}
input.value = "";
});
document.body.appendChild(input);
}
document.getElementById("save_file_picker")?.click();
},
});
actions.push({
text: t("main_menu.language"),
help: t("main_menu.language_help"),

View file

@ -622,6 +622,36 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>download_save_file</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>download_save_file_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>footer_html</name>
<description/>
@ -757,6 +787,36 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>load_save_file</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>load_save_file_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>mobile</name>
<description/>
@ -967,6 +1027,66 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>save_file_error</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>save_file_loaded</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>save_file_loaded_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>save_file_loaded_ok</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>sounds</name>
<description/>

View file

@ -37,6 +37,8 @@
"level_up.upgrade_perk_to_level": " lvl {{level}}",
"main_menu.basic": "Basic graphics",
"main_menu.basic_help": "Fewer particles and flashes, better performance.",
"main_menu.download_save_file": "Download save file",
"main_menu.download_save_file_help": "Get a transferable .b71 file with your score and stats",
"main_menu.footer_html": " <p> <span>Made in France by <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> \n <a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Privacy Policy</a>\n <a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n <a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n <a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> \n <a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n <a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Web version</a>\n <a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n <span>v.{{appVersion}}</span></p>",
"main_menu.fullscreen": "Fullscreen",
"main_menu.fullscreen_exit": "Exit Fullscreen",
@ -46,6 +48,8 @@
"main_menu.kid_help": "Start future runs with \"slower ball\".",
"main_menu.language": "Language",
"main_menu.language_help": "Choose the game's language",
"main_menu.load_save_file": "Load save file",
"main_menu.load_save_file_help": "Select a .b71 file on your device",
"main_menu.mobile": "Mobile mode",
"main_menu.mobile_help": "Leaves space for your thumb under the puck.",
"main_menu.pointer_lock": "Mouse pointer lock",
@ -60,6 +64,10 @@
"main_menu.reset_instruction": "You will loose all progress you made in the game, are you sure ?",
"main_menu.resume": "Resume",
"main_menu.resume_help": "Return to your run",
"main_menu.save_file_error": "Error loading save file",
"main_menu.save_file_loaded": "Save file loaded",
"main_menu.save_file_loaded_help": "The app will now reload to apply your save",
"main_menu.save_file_loaded_ok": "Ok",
"main_menu.sounds": "Game sounds",
"main_menu.sounds_help": "Can slow down some phones.",
"main_menu.title": "Breakout 71",

View file

@ -37,6 +37,8 @@
"level_up.upgrade_perk_to_level": " niveau {{level}}",
"main_menu.basic": "Graphismes simplifiés",
"main_menu.basic_help": "Moins de particules et effets, meilleures performances.",
"main_menu.download_save_file": "Sauvegarder mes progrès",
"main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde .b71 transférable",
"main_menu.footer_html": " <p> <span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> <a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> <a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a> <a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a> <a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> <a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a> <a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a> <a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a> <span>v.{{appVersion}}</span></p>",
"main_menu.fullscreen": "Plein écran",
"main_menu.fullscreen_exit": "Quitter le plein écran",
@ -46,6 +48,8 @@
"main_menu.kid_help": "Balle plus lente",
"main_menu.language": "Langue",
"main_menu.language_help": "Changer la langue d'affichage",
"main_menu.load_save_file": "Charger une sauvegarde",
"main_menu.load_save_file_help": "Sélectionnez un fichier .b71 sur votre appareil ",
"main_menu.mobile": "Mode mobile",
"main_menu.mobile_help": "Laisse un espace pour le pouce sous le palet.",
"main_menu.pointer_lock": "Verrouillage du pointeur de la souris",
@ -60,6 +64,10 @@
"main_menu.reset_instruction": "Vous perdrez tous les progrès que vous avez faits dans le jeu, êtes-vous sûr ?",
"main_menu.resume": "Retourner à la partie",
"main_menu.resume_help": "Continuer la partie en cours",
"main_menu.save_file_error": "Erreur lors du chargement du fichier de sauvegarde",
"main_menu.save_file_loaded": "Sauvegarde chargée",
"main_menu.save_file_loaded_help": "L'appli va redémarrer",
"main_menu.save_file_loaded_ok": "Ok",
"main_menu.sounds": "Sons du jeu",
"main_menu.sounds_help": "Ralentis certains téléphones.",
"main_menu.title": "Breakout 71",

View file

@ -8,40 +8,32 @@ export const options = {
default: true,
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"),
disabled: () => false,
},
basic: {
default: false,
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"),
disabled: () => !document.body.requestPointerLock,
},
easy: {
default: false,
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"),
disabled() {
return window.location.search.includes("isInWebView=true");
},
},
} as const satisfies { [k: string]: OptionDef };

View file

@ -16,6 +16,13 @@ export const ctx = gameCanvas.getContext("2d", {
alpha: false,
}) as CanvasRenderingContext2D;
export const bombSVG = document.createElement("img");
bombSVG.src =
"data:image/svg+xml;base64," +
btoa(`<svg width="144" height="144" viewBox="0 0 38.101 38.099" xmlns="http://www.w3.org/2000/svg">
<path d="m6.1528 26.516c-2.6992-3.4942-2.9332-8.281-.58305-11.981a10.454 10.454 0 017.3701-4.7582c1.962-.27726 4.1646.05953 5.8835.90027l.45013.22017.89782-.87417c.83748-.81464.91169-.87499 1.0992-.90271.40528-.058713.58876.03425 1.1971.6116l.55451.52679 1.0821-1.0821c1.1963-1.1963 1.383-1.3357 2.1039-1.5877.57898-.20223 1.5681-.19816 2.1691.00897 1.4613.50314 2.3673 1.7622 2.3567 3.2773-.0058.95654-.24464 1.5795-.90924 2.3746-.40936.48928-.55533.81057-.57898 1.2737-.02039.41018.1109.77714.42322 1.1792.30172.38816.3694.61323.2797.93044-.12803.45666-.56674.71598-1.0242.60507-.601-.14597-1.3031-1.3088-1.3969-2.3126-.09459-1.0161.19245-1.8682.92392-2.7432.42567-.50885.5643-.82851.5643-1.3031 0-.50151-.14026-.83177-.51211-1.2028-.50966-.50966-1.0968-.64829-1.781-.41996l-.37348.12477-2.1006 2.1006.52597.55696c.45421.48194.5325.58876.57898.78855.09622.41588.07502.45014-.88396 1.4548l-.87173.9125.26339.57979a10.193 10.193 0 01.9231 4.1001c.03996 2.046-.41996 3.8082-1.4442 5.537-.55044.928-1.0185 1.5013-1.8968 2.3241-.83503.78284-1.5526 1.2827-2.4904 1.7361-3.4266 1.657-7.4721 1.3422-10.549-.82035-.73473-.51782-1.7312-1.4621-2.2515-2.1357zm21.869-4.5584c-.0579-.19734-.05871-2.2662 0-2.4545.11906-.39142.57898-.63361 1.0038-.53005.23812.05708.54147.32455.6116.5382.06279.19163.06769 2.1805.0065 2.3811-.12558.40773-.61649.67602-1.0462.57164-.234-.05708-.51615-.30498-.57568-.50722m3.0417-2.6013c-.12313-.6222.37837-1.1049 1.0479-1.0079.18348.0261.25279.08399 1.0071.83911.75838.75838.81301.82362.84074 1.0112.10193.68499-.40365 1.1938-1.034 1.0405-.1949-.0473-.28786-.12558-1.0144-.85216-.7649-.76409-.80241-.81057-.84645-1.0316m.61323-3.0629a.85623.85623 0 01.59284-.99975c.28949-.09214 2.1814-.08318 2.3917.01141.38734.17369.6279.61078.53984.98181-.06035.25606-.35391.57327-.60181.64992-.25279.07747-2.2278.053-2.4097-.03017-.26013-.11906-.46318-.36125-.51374-.61323" fill="#fff" opacity="0.3"/>
</svg>`);
export const background = document.createElement("img");
export const backgroundCanvas = document.createElement("canvas");

1
src/types.d.ts vendored
View file

@ -248,6 +248,5 @@ export type OptionDef = {
default: boolean;
name: string;
help: string;
disabled: () => boolean;
};
export type OptionId = keyof typeof options;