Compare commits

...

29 commits

Author SHA1 Message Date
Renan LE CARO
f1cd138071 Build 29085904 2025-04-20 15:04:53 +02:00
Renan LE CARO
de485e5598 Build 29085898 2025-04-20 14:58:29 +02:00
Renan LE CARO
ae2f43be0e Build 29085883 2025-04-20 14:43:24 +02:00
Renan LE CARO
0cfd75e7b2 Record 2025-04-20 14:37:08 +02:00
Renan LE CARO
c88540488d wip 2025-04-20 13:41:12 +02:00
Renan LE CARO
b3949d8c41 wip 2025-04-20 13:12:10 +02:00
Renan LE CARO
4d7d57f17f wip 2025-04-20 12:56:50 +02:00
Renan LE CARO
1ba55bf2a6 wip 2025-04-20 10:58:26 +02:00
Renan LE CARO
70f3c2307a wip 2025-04-20 09:54:49 +02:00
Renan LE CARO
66952cb1ca Build 29085573 2025-04-20 09:33:55 +02:00
Renan LE CARO
a386c5f3d2 Prob lighting 2025-04-20 09:33:12 +02:00
Renan LE CARO
6adab3d07f Build 29084606 2025-04-19 17:26:45 +02:00
Renan LE CARO
603ebf319a Build 29084571 2025-04-19 16:51:56 +02:00
Renan LE CARO
1252bbca06 Removed precise physics option, uses the fastest ball speed instead 2025-04-19 16:50:26 +02:00
Renan LE CARO
b891e0fafb Build 29083446 2025-04-18 22:06:16 +02:00
Renan LE CARO
a388cd0898 Build 29083397 2025-04-18 21:17:32 +02:00
Renan LE CARO
d43dd90a86 wip 2025-04-18 17:15:47 +02:00
Renan LE CARO
530e94f704 Build 29080170 2025-04-16 15:30:42 +02:00
Renan LE CARO
21fa5f105e wip 2025-04-16 15:30:20 +02:00
Renan LE CARO
277aa5682b Build 29079818 2025-04-16 09:38:43 +02:00
Renan LE CARO
871a7f9c31 Build 29079805 2025-04-16 09:26:10 +02:00
Renan LE CARO
8e4e67e33b Build 29079087 2025-04-15 21:28:00 +02:00
Renan LE CARO
06843047d2 Added particle and sound effect when coin drops below the "waterline" of the puck 2025-04-15 21:25:27 +02:00
Renan LE CARO
354a6490e9 wip 2025-04-15 17:31:57 +02:00
Renan LE CARO
47ad04c49b wip 2025-04-15 16:47:04 +02:00
Renan LE CARO
64a85200b9 wip 2025-04-14 13:39:30 +02:00
Renan LE CARO
11c797bc59 Allow loading saves from newer versions 2025-04-13 14:53:22 +02:00
Renan LE CARO
0cb4945e7d Build 29075517 2025-04-13 09:58:09 +02:00
Renan LE CARO
2d383f02cb wip 2025-04-13 09:52:08 +02:00
43 changed files with 7480 additions and 1108 deletions

View file

@ -13,22 +13,70 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades !
# Current priorities
The goal of this project is to make a game used by many people. The game is already pretty fun. I'm now trying to
translate it to (Lebanese) Arabic, Russian and (Chilean) Spanish. Other translation are very welcome, contact me
if you'd like to submit one.
While translations are being written, I'll try to avoid adding features that require new translations. That means only
bug fixes and optimisations, maybe adding levels. Once we have a nice stable release available in 4
languages, I may add features again.
The goal of this project is to make a game used by many people.
The game is already pretty fun.
I'm now trying to translate it to (Lebanese) Arabic, Russian and (Chilean) Spanish.
Other translation are very welcome, contact me if you'd like to submit one.
# Changelog
## To do
- auto-detect device performance at first startup and adjust settings accordingly
## Done
- toast an error if storage is blocked
- toast an error if migration fails
- in apk, video download doesn't work
- ask for permanent storage
- option: reuse past frame's light in new frame lighting computation when there are 150+ coins on screen, to limit the performance impact of rendering lots of lights
## 29084606
- simpler and more readable encoding for save files
- removed check of payload signature on save file, seemed to fail because of the poor encoding of the name of the "côte d'ivoire" level
- automatic detection of the number of steps required for physics
- trial runs detection fix
## 29083397
- highlight last used creative level
- access autoplay mode from the menu
- access stress test mode from the menu, show real time stats
- Render bottom border differently to show how far the puck can go
- Corner Shot: scale like Need Some Space
- grey out irrelevant options in the settings
- Back to Creative Menu at the end of a Creative level
## 29080170
- don't show unlock toast at first startup for levels that are unlocked by default
- Droplet particle color should be gold for gold coins
- added levels: A Very Dangerous High-Five, The Boys
## 29079818
- Imported levels : Mario, Minesweeper and Target
- Fixed an issue with localstorage saving of custom levels
## 29079805
- combo text on paddle will be grey if we're at the base combo
- transparency now rounds up
- import level up to 21 x 21
- corrected icon of "padding"
## 29079087
- measured and improve the performance (test here https://breakout.lecaro.me/?stresstest)
- added a few levels
- autoplay mode (with wake lock and computer play https://breakout.lecaro.me/?autoplay )
- Added particle and sound effect when coin drops below the "waterline" of the puck
- slower coins fall once they are under the paddle
- in game level editor
- allow loading newer save in outdated app (for rollback)
- game crashes when reaching level 12 (no level info in runLevels)
## 29074385
@ -296,6 +344,7 @@ languages, I may add features again.
- make stats a clairvoyant thing
- [colin]P ocket money — bricks absorb coins that touch them, which are released on brick destruction (with a bonus?)
- [colin] turn ball gravity on after a top bar hit, and until bouncing on puck
- fan : paddle motion creates upward draft that lifts coins and balls
## Medium difficulty perks ideas
- balls collision split them into 4 smaller balls, lvl times (requires rework)
@ -360,6 +409,12 @@ languages, I may add features again.
## UX / gameplay
- chill game mode, to just relax your mind :
- no 7 levels limit
- no upgrades offered at the end of the level
- get a random perk
- every 7 level it's replaced by another random perk
- every 7 levels, +10 base combo and +1 piece
- avoid showing a +1 and -1 at the same time when a combo increase is reset
- explain to iOS users how to add the app to home screen to get fullscreen
- delayed start on mobile to let users place the puck where they want
@ -370,7 +425,6 @@ languages, I may add features again.
## Game engine features ideas
- add a clickable button to allow sound to play in chrome android
- save state in localstorage for easy resume of a game in progress
- ask for permanent storage
- handle back bouton in menu
- Offline mode web for iphone
- controller support on web/mobile
@ -379,6 +433,7 @@ languages, I may add features again.
## Maybe one day
- https://weblate.org/fr/ quite annoying to have merge conflicts while pushing, i'll enable it later.
- auto-detect device performance at first startup and adjust settings accordingly (hard to do in any sort of useful way)
- [jaceys] Move the restart button out of the menu, so that it is more easily accessible (will allow user to choose starting perk instead)
- colored coins only (coins should be of the color of the ball to count, otherwise what ? i'd rather avoid negative points)
- coins avoid ball of different color (pointless)
@ -472,4 +527,5 @@ Breakout 71 can be installed and work offline in many ways:
# System requirements
The game should perform well even on low-end devices. It's very lean and does not take much storage space (Roughly 0.1MB). The web version is supposed to work on iOS safari, Firefox ESR and chrome, on desktop and mobile.
If the app stutters, turn on "fast mode" in the settings to render a simplified view that should be faster. You can adjust many aspects of the game there, go have a look !
If the app stutters, turn on "fast mode" in the settings to render a simplified view that should be faster. You can adjust many aspects of the game there, go have a look !

View file

@ -29,8 +29,8 @@ android {
applicationId = "me.lecaro.breakout"
minSdk = 21
targetSdk = 34
versionCode = 29074738
versionName = "29074738"
versionCode = 29085904
versionName = "29085904"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
@ -67,3 +67,6 @@ android {
}
}
}
dependencies {
implementation(libs.androidx.core)
}

View file

@ -23,6 +23,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,7 @@
package me.lecaro.breakout
import android.app.Activity
import android.app.DownloadManager
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -20,10 +16,12 @@ import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.widget.Toast
import androidx.core.content.FileProvider
import java.io.File
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.util.Date
import java.util.jar.Manifest
const val CHOOSE_FILE_REQUEST_CODE = 548459
@ -50,71 +48,79 @@ class MainActivity : android.app.Activity() {
private fun downloadFile(url: String) {
try {
if (!url.startsWith("data:")) {
Log.w("DL", "url ignored because it does not start with data:")
return
}
val sdf = SimpleDateFormat("yyyy-M-dd-hh-mm")
val currentDate = sdf.format(Date())
val base64Data = url.substringAfterLast(',')
val decodedBytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT)
if (url.startsWith("data:application/json;base64,")) {
writeFile(decodedBytes, "breakout-71-save-$currentDate.json", "application/json")
if (url.startsWith("data:application/json;charset=utf-8,")) {
} else if (url.startsWith("data:video/webm;base64,")) {
writeFile(decodedBytes, "breakout-71-gameplay-capture-$currentDate.webm", "video/webm")
} else {
Log.w("DL", "unexpected type " + url)
val urlEncoded = url.substring("data:application/json;charset=utf-8,".length)
val str = URLDecoder.decode(urlEncoded, StandardCharsets.UTF_8.name())
writeFileAndShare(str.toByteArray(), "breakout-71-save-$currentDate.json", "application/json")
}
if (url.startsWith("data:video/webm;base64,")) {
val base64Data = url.substring("data:video/webm;base64,".length)
val decodedBytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT)
writeFileAndShare(decodedBytes, "breakout-71-capture-$currentDate.webm", "video/webm")
}
} catch (e: Exception) {
Log.e("DL", "Error ${e.message}")
Toast.makeText(this, "Error ${e.message}", Toast.LENGTH_LONG).show()
}
}
fun writeFile(decodedBytes:ByteArray,fileName:String, mime:String){
fun writeFileAndShare(bytes:ByteArray, fileName: String, mime: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// android 10
val contentValues = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
put(MediaStore.Downloads.MIME_TYPE, mime)
put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
val jsonData = String(decodedBytes);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
put(MediaStore.Downloads.MIME_TYPE,mime )
put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
val uri: Uri? = contentResolver.insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues
)
uri?.let {
contentResolver.openOutputStream(it)?.use { outputStream ->
outputStream.write(decodedBytes)
}
}
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
// Example: content://com.google.android.apps.photos.contentprovider/...
putExtra(Intent.EXTRA_STREAM, uri)
type = mime
}
startActivity(Intent.createChooser(shareIntent, null))
} else {
val dir = getExternalFilesDir(null)
val file = File(dir, fileName)
file.writeText(jsonData)
Toast.makeText(this, "Saved in $dir", Toast.LENGTH_LONG).show()
val uri: Uri? = contentResolver.insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues
)
uri?.let {
contentResolver.openOutputStream(it)?.use { outputStream ->
outputStream.write(bytes)
}
}
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = mime
}
startActivity(Intent.createChooser(shareIntent, null))
} else {
val file = File(getExternalFilesDir(null), fileName)
file.writeBytes(bytes)
val uri = FileProvider.getUriForFile(
this,
"$packageName.fileprovider", // Adjust if your authority is different
file
)
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = mime
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
startActivity(Intent.createChooser(shareIntent, null))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE);
@ -155,13 +161,18 @@ class MainActivity : android.app.Activity() {
} catch (e: Exception) {
Log.e("DL", "Error ${e.message}")
Toast.makeText(activity, "Error ${e.message}", Toast.LENGTH_LONG).show()
return false
}
}
}
webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
Log.d("DL", "url: ${url}")
Log.d("DL", "userAgent: ${userAgent}")
Log.d("DL", "contentDisposition: ${contentDisposition}")
Log.d("DL", "mimetype: ${mimetype}")
Log.d("DL", "contentLength: ${contentLength}")
downloadFile(url)
})

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
name="external_files"
path="." />
</paths>

1157
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -8,6 +8,7 @@ espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.7.0"
composeBom = "2023.08.00"
core = "1.13.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -24,6 +25,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-core = { group = "androidx.core", name = "core", version.ref = "core" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }

View file

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

View file

@ -1,5 +1,5 @@
import { GameState, Level, PerkId, Upgrade } from "./types";
import { allLevels, icons, upgrades } from "./loadGameData";
import { GameState, Level, PerkId, RawLevel, Upgrade } from "./types";
import { allLevels, icons, transformRawLevel, upgrades } from "./loadGameData";
import { t } from "./i18n/i18n";
import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
import {
@ -17,6 +17,7 @@ import {
} from "./game_utils";
import { getHistory } from "./gameOver";
import { noCreative } from "./upgrades";
import { levelIconHTML } from "./levelIcon";
export function creativeMode(gameState: GameState) {
return {
@ -39,6 +40,9 @@ export async function openCreativeModePerksPicker() {
{},
),
choice: Upgrade | Level | "reset" | void;
const customLevels = (getSettingValue("custom_levels", []) as RawLevel[]).map(
transformRawLevel,
);
while (
(choice = await asyncAlert<Upgrade | Level | "reset">({
@ -62,8 +66,8 @@ export async function openCreativeModePerksPicker() {
(u.max + (creativeModePerks.limitless || 0)),
value: u,
className: creativeModePerks[u.id]
? "sandbox"
: "sandbox grey-out-unless-hovered",
? "sandbox highlight"
: "sandbox ",
tooltip: u.help(creativeModePerks[u.id] || 1),
})),
t("lab.select_level"),
@ -76,8 +80,19 @@ export async function openCreativeModePerksPicker() {
value: l,
disabled: !!problem,
tooltip: problem || describeLevel(l),
className:
getSettingValue("creativeModeLevel", "") === l.name
? "highlight"
: "",
};
}),
...customLevels.map((l) => ({
icon: levelIconHTML(l.bricks, l.size, l.color),
text: l.name,
value: l,
disabled: !l.bricks.filter((b) => b !== "_").length,
tooltip: describeLevel(l),
})),
],
}))
) {
@ -87,8 +102,13 @@ export async function openCreativeModePerksPicker() {
});
} else if ("bricks" in choice) {
setSettingValue("creativeModePerks", creativeModePerks);
setSettingValue("creativeModeLevel", choice.name);
if (await confirmRestart(gameState)) {
restart({ perks: creativeModePerks, level: choice.name });
restart({
perks: creativeModePerks,
level: choice,
isCreativeRun: true,
});
}
return;
} else if (choice) {

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
"29074738"
"29085904"

View file

@ -8,6 +8,8 @@
box-sizing: border-box;
}
@purple: #6262ea;
body {
margin: 0;
padding: 0;
@ -70,8 +72,9 @@ canvas:not(#game) {
color: gold;
transition: color 0.01s;
}
&.hidden {
display: none;
&.computer_controlled {
pointer-events: none;
}
span {
@ -106,7 +109,7 @@ body:not(.has-alert-open) #popup {
#popup {
&::before {
z-index: 10;
z-index: 4;
content: "";
display: block;
position: fixed;
@ -118,7 +121,7 @@ body:not(.has-alert-open) #popup {
overflow: auto;
& > div {
z-index: 11;
z-index: 5;
position: relative;
margin: auto;
padding: 20px 10px;
@ -221,7 +224,7 @@ body:not(.has-alert-open) #popup {
border: none;
cursor: pointer;
overflow: hidden;
z-index: 12;
z-index: 6;
&:before {
content: "+";
@ -266,45 +269,6 @@ body:not(.has-alert-open) #popup {
}
}
/*Unlocks progress bar*/
.progress {
display: block;
padding: 5px 10px;
background: #1c1c2f;
color: #fff;
box-shadow: inset 3px 3px 5px rgba(0, 0, 0, 0.5);
border-radius: 5px;
text-align: center;
position: relative;
overflow: hidden;
& > .progress_bar_part {
display: block;
background: #4049ca;
box-shadow: inset 3px 3px 5px rgba(0, 0, 0, 0.5);
left: 0;
position: absolute;
right: 0;
top: 0;
bottom: 0;
transform-origin: top left;
animation: grow 1s both ease-out;
z-index: 1;
}
& > span {
display: block;
position: relative;
z-index: 2;
}
@keyframes grow {
0% {
transform: scale(0, 1);
}
}
}
#level-recording-container {
max-width: 400px;
text-align: center;
@ -459,7 +423,7 @@ h2.histogram-title strong {
background: black;
color: white;
padding: 10px;
z-index: 11;
z-index: 5;
border-radius: 2px;
pointer-events: none;
@ -477,6 +441,7 @@ h2.histogram-title strong {
cursor: pointer;
background: black;
}
td,
th {
padding: 0 5px;
@ -488,11 +453,13 @@ h2.histogram-title strong {
td:first-child {
text-align: left;
}
img {
width: 20px;
height: auto;
pointer-events: none;
}
tr:nth-child(2n) {
background: rgba(0, 0, 0, 0.58);
}
@ -508,6 +475,7 @@ h2.histogram-title strong {
height: 7px;
bottom: 2px;
border-radius: 2px;
span {
position: absolute;
inset: 1px;
@ -529,18 +497,91 @@ h2.histogram-title strong {
border-radius: 2px;
padding-right: 10px;
pointer-events: none;
animation: toast forwards;
}
@keyframes toast {
0%,
100% {
transition:
opacity 200ms,
transform 200ms;
z-index: 7;
&.hidden {
opacity: 0;
transform: translate(-20px, -20px) scale(0.5);
}
10%,
90% {
&.visible {
opacity: 0.8;
transform: none;
}
}
.gridEdit > div > span,
.palette > span {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: 1px solid;
cursor: pointer;
&:hover {
border-color: gold;
z-index: 1;
position: relative;
box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.2);
}
}
.gridEdit {
& > div {
display: flex;
& > span {
width: calc(min(500px, 100vw, 100vh - 200px) / var(--grid-size));
height: calc(min(500px, 100vw, 100vh - 200px) / var(--grid-size));
}
}
}
.palette {
display: flex;
flex-wrap: wrap;
& > span {
&[data-selected="true"] {
border: 2px solid white;
}
}
}
#stats {
position: fixed;
left: 0;
top: 40px;
width: 100vw;
max-width: 400px;
color: white;
z-index: 3;
pointer-events: none;
opacity: 1;
& > div {
background: rgba(38, 38, 38, 0.5);
position: relative;
> div {
background: @purple;
position: absolute;
inset: 0;
transform-origin: top left;
}
> strong {
position: relative;
padding: 0 5px;
}
}
}
.highlight {
position: relative;
&:before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(-45deg, @purple, transparent);
mix-blend-mode: screen;
opacity: 0.3;
}
}

View file

@ -1,4 +1,10 @@
import { allLevels, appVersion, icons, upgrades } from "./loadGameData";
import {
allLevels,
allLevelsAndIcons,
appVersion,
icons,
upgrades,
} from "./loadGameData";
import {
Ball,
Coin,
@ -17,18 +23,20 @@ import {
describeLevel,
getRowColIndex,
highScoreText,
isInWebView,
levelsListHTMl,
max_levels,
pickedUpgradesHTMl,
reasonLevelIsLocked,
sample,
sumOfValues,
} from "./game_utils";
import "./PWA/sw_loader";
import { getCurrentLang, languages, t } from "./i18n/i18n";
import {
commitSettingsChangesToLocalStorage,
cycleMaxCoins,
cycleMaxParticles,
getCurrentMaxCoins,
getCurrentMaxParticles,
getSettingValue,
@ -38,6 +46,7 @@ import {
import {
forEachLiveOne,
gameStateTick,
liveCount,
normalizeGameState,
pickRandomUpgrades,
setLevel,
@ -46,8 +55,8 @@ import {
import {
backgroundCanvas,
gameCanvas,
getHaloScale,
haloCanvas,
haloScale,
render,
scoreDisplay,
} from "./render";
@ -66,7 +75,6 @@ import {
requiredAsyncAlert,
} from "./asyncAlert";
import { isOptionOn, options, toggleOption } from "./options";
import { hashCode } from "./getLevelBackground";
import {
catchRateBest,
catchRateGood,
@ -89,6 +97,7 @@ import { generateSaveFileContent } from "./generateSaveFileContent";
import { runHistoryViewerMenuEntry } from "./runHistoryViewer";
import { getNearestUnlockHTML, openScorePanel } from "./openScorePanel";
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
import { levelEditorMenuEntry } from "./levelEditor";
export async function play() {
if (await applyFullScreenChoice()) return;
@ -105,7 +114,12 @@ export async function play() {
export function pause(playerAskedForPause: boolean) {
if (!gameState.running) return;
if (gameState.pauseTimeout) return;
if (gameState.computer_controlled) return;
if (gameState.startParams.computer_controlled) {
if (gameState.startParams?.computer_controlled) {
play();
}
return;
}
const stop = () => {
gameState.running = false;
@ -152,6 +166,7 @@ export const fitSize = (gameState: GameState) => {
gameCanvas.height = height;
backgroundCanvas.width = width;
backgroundCanvas.height = height;
const haloScale = getHaloScale();
haloCanvas.width = width / haloScale;
haloCanvas.height = height / haloScale;
@ -416,12 +431,12 @@ export function hitsSomething(x: number, y: number, radius: number) {
}
export function tick() {
startWork("physics");
const currentTick = performance.now();
const timeDeltaMs = currentTick - gameState.lastTick;
gameState.lastTick = currentTick;
let frames = Math.min(4, timeDeltaMs / (1000 / 60));
if (gameState.keyboardPuckSpeed) {
setMousePos(
gameState,
@ -435,34 +450,88 @@ export function tick() {
1,
);
}
normalizeGameState(gameState);
normalizeGameState(gameState);
if (gameState.running) {
gameState.levelTime += timeDeltaMs * frames;
gameState.runStatistics.runTime += timeDeltaMs * frames;
gameStateTick(gameState, frames);
const maxBallSpeed =
Math.sqrt(
Math.max(0, ...gameState.balls.map(({ vx, vy }) => vx * vx + vy * vy)),
) * frames;
const steps = Math.ceil(maxBallSpeed / 8);
for (let i = 0; i < steps; i++) gameStateTick(gameState, frames / steps);
}
if (gameState.running || gameState.needsRender) {
gameState.needsRender = false;
render(gameState);
}
startWork("record video");
if (gameState.running) {
recordOneFrame(gameState);
}
startWork("sound");
if (isOptionOn("sound")) {
playPendingSounds(gameState);
}
startWork("idle");
requestAnimationFrame(tick);
FPSCounter++;
}
const stats = document.getElementById("stats") as HTMLDivElement;
let total = {};
let lastTick = performance.now();
let doing = "idle";
let FPSCounter = 0;
export let lastMeasuredFPS = 60;
export function startWork(what) {
if (!gameState.startParams.stress) return;
const newNow = performance.now();
if (doing) {
total[doing] = (total[doing] || 0) + (newNow - lastTick);
}
lastTick = newNow;
doing = what;
}
setInterval(() => {
lastMeasuredFPS = FPSCounter;
FPSCounter = 0;
if (!gameState.startParams.stress) {
stats.style.display = "none";
return;
}
stats.style.display = "block";
const totalTime = sumOfValues(total);
stats.innerHTML =
`
<div>
${lastMeasuredFPS} FPS -
${liveCount(gameState.coins)} / ${getCurrentMaxCoins()} Coins -
${liveCount(gameState.particles) + liveCount(gameState.lights) + liveCount(gameState.texts)} / ${getCurrentMaxParticles() * 3} particles
</div>
` +
Object.entries(total)
// .sort((a, b) => b[1] - a[1])
.map(
(t) =>
` <div>
<div style="transform: scale(${clamp(t[1] / totalTime, 0, 1)},1)"></div>
<strong>${t[0]} : ${Math.floor(t[1])} ms</strong>
</div>
`,
)
.join("\n");
total = {};
}, 1000);
setInterval(() => {
@ -518,6 +587,7 @@ export async function openMainMenu() {
},
creativeMode(gameState),
runHistoryViewerMenuEntry(),
levelEditorMenuEntry(),
{
icon: icons["icon:unlocks"],
text: t("main_menu.unlocks"),
@ -608,18 +678,30 @@ async function openSettingsMenu() {
(await confirmRestart(gameState))
) {
setSettingValue("lang", pick);
commitSettingsChangesToLocalStorage();
window.location.reload();
}
},
});
for (const key of Object.keys(options) as OptionId[]) {
if (options[key])
if (options[key]) {
actions.push({
icon: isOptionOn(key)
? icons["icon:checkmark_checked"]
: icons["icon:checkmark_unchecked"],
text: options[key].name,
help: options[key].help,
disabled:
(isOptionOn("basic") &&
[
"extra_bright",
"contrast",
"smooth_lighting",
"precise_lighting",
"probabilistic_lighting",
].includes(key)) ||
// (isInWebView && key == "record") ||
false,
value: () => {
toggleOption(key);
fitSize(gameState);
@ -627,30 +709,23 @@ async function openSettingsMenu() {
openSettingsMenu();
},
});
}
}
actions.push({
icon: icons["icon:download"],
text: t("settings.download_save_file"),
help: t("settings.download_save_file_help"),
async value() {
const signedPayload = generateSaveFileContent();
const dlLink = document.createElement("a");
const obj = {
fileType: "B71-save-file",
appVersion,
payload: generateSaveFileContent(),
};
const json = JSON.stringify(obj, null, 2);
dlLink.setAttribute(
"href",
"data:application/json;base64," +
btoa(
JSON.stringify({
fileType: "B71-save-file",
appVersion,
signedPayload,
key: hashCode(
"Security by obscurity, but really the game is oss so eh" +
signedPayload,
),
}),
),
"data:application/json;charset=utf-8," + encodeURIComponent(json),
);
dlLink.setAttribute(
@ -660,7 +735,7 @@ async function openSettingsMenu() {
.toISOString()
.slice(0, 19)
.replace(/[^0-9]+/gi, "-") +
".b71",
".json",
);
document.body.appendChild(dlLink);
dlLink.click();
@ -697,35 +772,21 @@ async function openSettingsMenu() {
reader.readAsText(file);
});
const {
fileType,
appVersion: fileVersion,
signedPayload,
key,
} = JSON.parse(content);
const { fileType, signedPayload, payload } = 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.",
);
if (
key !==
hashCode(
"Security by obscurity, but really the game is oss so eh" +
signedPayload,
)
) {
throw new Error("Key does not match content.");
}
const localStorageContent = JSON.parse(signedPayload);
localStorage.clear();
for (let key in localStorageContent) {
localStorage.setItem(key, localStorageContent[key]);
if (payload) {
localStorage.clear();
for (let key in payload) {
localStorage.setItem(key, JSON.stringify(payload[key]));
}
} else if (signedPayload) {
// Old file format
const localStorageContent = JSON.parse(signedPayload);
localStorage.clear();
for (let key in localStorageContent) {
localStorage.setItem(key, localStorageContent[key]);
}
}
await asyncAlert({
title: t("settings.save_file_loaded"),
@ -759,15 +820,6 @@ async function openSettingsMenu() {
await openSettingsMenu();
},
});
actions.push({
icon: icons["icon:particles"],
text: t("settings.max_particles", { max: getCurrentMaxParticles() }),
help: t("settings.max_particles_help"),
async value() {
cycleMaxParticles();
await openSettingsMenu();
},
});
actions.push({
icon: icons["icon:reset"],
@ -796,6 +848,20 @@ async function openSettingsMenu() {
}
},
});
actions.push({
text: t("settings.autoplay"),
help: t("settings.autoplay_help"),
async value() {
startComputerControlledGame(false);
},
});
actions.push({
text: t("settings.stress_test"),
help: t("settings.stress_test_help"),
async value() {
startComputerControlledGame(true);
},
});
const cb = await asyncAlert<() => void>({
title: t("main_menu.settings_title"),
@ -848,7 +914,10 @@ async function openUnlocksList() {
.map(({ name, id, threshold, icon, help }) => ({
text: name,
disabled: ts < threshold,
value: { perks: { [id]: 1 }, level: "icon:" + id } as RunParams,
value: {
perks: { [id]: 1 },
level: allLevelsAndIcons.find((l) => l.name === "icon:" + id),
} as RunParams,
icon,
[hintField]:
ts < threshold
@ -870,7 +939,7 @@ async function openUnlocksList() {
return {
text: l.name + percentUnlocked,
disabled: !!lockedBecause,
value: { level: l.name } as RunParams,
value: { level: l } as RunParams,
icon: icons[l.name],
[hintField]: lockedBecause?.text || describeLevel(l),
};
@ -983,8 +1052,8 @@ document.addEventListener("keyup", async (e) => {
!alertsOpen &&
pageLoad < Date.now() - 500
) {
if (gameState.computer_controlled) {
return startComputerControlledGame();
if (gameState.startParams.computer_controlled) {
return startComputerControlledGame(gameState.startParams.stress);
}
// When doing ctrl + R in dev to refresh, i don't want to instantly restart a run
if (await confirmRestart(gameState)) {
@ -1004,30 +1073,43 @@ export function restart(params: RunParams) {
Object.assign(gameState, newGameState(params));
// Recompute brick size according to level
fitSize(gameState);
pauseRecording();
setLevel(gameState, 0);
if (params?.computer_controlled) {
play();
}
}
if (window.location.search.includes("autoplay")) {
startComputerControlledGame();
if (window.location.search.match(/autoplay|stress/)) {
startComputerControlledGame(window.location.search.includes("stress"));
} else {
restart({});
}
export function startComputerControlledGame() {
const perks: Partial<PerksMap> = { base_combo: 7, pierce: 3 };
for (let i = 0; i < 10; i++) {
const u = sample(upgrades);
perks[u.id] = Math.floor(Math.random() * u.max) + 1;
export function startComputerControlledGame(stress: boolean = false) {
const perks: Partial<PerksMap> = { base_combo: 20, pierce: 3 };
if (stress) {
Object.assign(perks, {
base_combo: 5000,
pierce: 20,
rainbow: 3,
sapper: 2,
etherealcoins: 1,
bricks_attract_ball: 1,
respawn: 3,
});
} else {
for (let i = 0; i < 10; i++) {
const u = sample(upgrades);
perks[u.id] ||= Math.floor(Math.random() * u.max) + 1;
}
perks.superhot = 0;
}
perks.superhot = 0;
restart({
level: sample(allLevels)?.name,
level: sample(allLevels.filter((l) => l.color === "#000000")),
computer_controlled: true,
perks,
stress,
});
}

View file

@ -9,32 +9,48 @@ import {
pickedUpgradesHTMl,
reasonLevelIsLocked,
} from "./game_utils";
import { getTotalScore } from "./settings";
import {
askForPersistentStorage,
getSettingValue,
getTotalScore,
setSettingValue,
} from "./settings";
import { stopRecording } from "./recording";
import { asyncAlert } from "./asyncAlert";
import { rawUpgrades } from "./upgrades";
import { run } from "jest";
import { editRawLevelList } from "./levelEditor";
import { openCreativeModePerksPicker } from "./creative";
export function addToTotalPlayTime(ms: number) {
try {
localStorage.setItem(
"breakout_71_total_play_time",
JSON.stringify(
JSON.parse(localStorage.getItem("breakout_71_total_play_time") || "0") +
ms,
),
);
} catch (e) {}
setSettingValue(
"breakout_71_total_play_time",
getSettingValue("breakout_71_total_play_time", 0) + ms,
);
}
export function gameOver(title: string, intro: string) {
if (!gameState.running) return;
if (gameState.isGameOver) return;
gameState.isGameOver = true;
pause(true);
askForPersistentStorage();
stopRecording();
addToTotalPlayTime(gameState.runStatistics.runTime);
if (typeof gameState.startParams.isEditorTrialRun === "number") {
editRawLevelList(gameState.startParams.isEditorTrialRun);
restart({});
return;
}
if (gameState.startParams.isCreativeRun) {
openCreativeModePerksPicker();
restart({});
return;
}
// unlocks
const endTs = getTotalScore();
const startTs = endTs - gameState.score;

View file

@ -19,6 +19,8 @@ import {
distance2,
distanceBetween,
getClosestBall,
getCoinRenderColor,
getCornerOffset,
getMajorityValue,
getPossibleUpgrades,
getRowColIndex,
@ -53,7 +55,7 @@ import { addToTotalScore } from "./addToTotalScore";
import { hashCode } from "./getLevelBackground";
export function setMousePos(gameState: GameState, x: number) {
if (gameState.computer_controlled) return;
if (gameState.startParams.computer_controlled) return;
gameState.puckPosition = x;
// Sets the puck position, and updates the ball position if they are supposed to follow it
@ -105,7 +107,7 @@ function computerControl(gameState: GameState) {
10,
);
if (gameState.levelTime > 30000) {
startComputerControlledGame();
startComputerControlledGame(gameState.startParams.stress);
}
}
@ -182,11 +184,7 @@ export function normalizeGameState(gameState: GameState) {
),
);
const corner =
(gameState.levelTime
? gameState.perks.corner_shot * gameState.puckWidth
: 0) -
gameState.perks.unbounded * gameState.brickWidth;
const corner = getCornerOffset(gameState);
let minX = gameState.offsetXRoundedDown + gameState.puckWidth / 2 - corner;
@ -449,7 +447,7 @@ export function explodeBrick(
);
}
if (gameState.perks.transparency) {
coinsToSpawn = Math.round(
coinsToSpawn = Math.ceil(
coinsToSpawn *
(1 +
(ballTransparency(ball, gameState) * gameState.perks.transparency) /
@ -461,11 +459,11 @@ export function explodeBrick(
gameState.runStatistics.coins_spawned += coinsToSpawn;
gameState.runStatistics.bricks_broken++;
const maxCoins = getCurrentMaxCoins() * (isOptionOn("basic") ? 0.5 : 1);
const maxCoins = getCurrentMaxCoins();
const spawnableCoins =
liveCount(gameState.coins) > getCurrentMaxCoins()
? 1
: Math.floor(maxCoins - liveCount(gameState.coins)) / 3;
: Math.floor((maxCoins - liveCount(gameState.coins)) / 2);
const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins));
@ -637,7 +635,7 @@ export function schedulGameSound(
) {
if (!vol) return;
if (!isOptionOn("sound")) return;
if (gameState.computer_controlled) return;
x ??= gameState.offsetX + gameState.gameZoneWidth / 2;
const ex = gameState.aboutToPlaySound[sound] as { vol: number; x: number };
@ -660,9 +658,7 @@ export function addToScore(gameState: GameState, coin: Coin) {
coin.previousY,
(gameState.canvasWidth - coin.x) / 100,
-coin.y / 100,
gameState.perks.metamorphosis || isOptionOn("colorful_coins")
? coin.color
: "#ffd300",
getCoinRenderColor(gameState, coin),
true,
gameState.coinSize / 2,
@ -992,7 +988,7 @@ export function gameStateTick(
frames = 1,
) {
// Ai movement of puck
if (gameState.computer_controlled) computerControl(gameState);
if (gameState.startParams.computer_controlled) computerControl(gameState);
gameState.runStatistics.max_combo = Math.max(
gameState.runStatistics.max_combo,
@ -1068,8 +1064,8 @@ export function gameStateTick(
// instant win condition
(gameState.levelTime && !remainingBricks && !liveCount(gameState.coins))
) {
if (gameState.computer_controlled) {
startComputerControlledGame();
if (gameState.startParams.computer_controlled) {
startComputerControlledGame(gameState.startParams.stress);
} else if (gameState.currentLevel + 1 < max_levels(gameState)) {
setLevel(gameState, gameState.currentLevel + 1);
} else {
@ -1160,8 +1156,12 @@ export function gameStateTick(
const ratio =
1 -
((gameState.perks.viscosity * 0.03 + 0.002) * frames) /
((gameState.perks.viscosity * 0.03 +
0.002 +
(coin.y > gameState.gameZoneHeight ? 0.2 : 0)) *
frames) /
(1 + gameState.perks.etherealcoins);
if (!gameState.perks.etherealcoins) {
coin.vy *= ratio;
coin.vx *= ratio;
@ -1214,6 +1214,30 @@ export function gameStateTick(
const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10;
const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames);
if (
coin.previousY < gameState.gameZoneHeight &&
coin.y > gameState.gameZoneHeight &&
coin.vy > 0 &&
speed > 20
) {
schedulGameSound(
gameState,
"plouf",
coin.x,
(clamp(speed, 20, 100) / 100) * 0.2,
);
if (!isOptionOn("basic")) {
makeParticle(
gameState,
coin.x,
gameState.gameZoneHeight,
-coin.vx / 5,
-coin.vy / 5,
getCoinRenderColor(gameState, coin),
false,
);
}
}
if (
coin.y > gameState.gameZoneHeight - coinRadius - gameState.puckHeight &&
@ -1710,8 +1734,8 @@ export function ballTick(gameState: GameState, ball: Ball, frames: number) {
ball.destroyed = true;
gameState.runStatistics.balls_lost++;
if (!gameState.balls.find((b) => !b.destroyed)) {
if (gameState.computer_controlled) {
startComputerControlledGame();
if (gameState.startParams.computer_controlled) {
startComputerControlledGame(gameState.startParams.stress);
} else {
gameOver(
t("gameOver.lost.title"),

View file

@ -1,5 +1,6 @@
import {
Ball,
Coin,
GameState,
Level,
PerkId,
@ -13,6 +14,7 @@ import { clamp } from "./pure_functions";
import { rawUpgrades } from "./upgrades";
import { hashCode } from "./getLevelBackground";
import { getTotalScore } from "./settings";
import { isOptionOn } from "./options";
export function describeLevel(level: Level) {
let bricks = 0,
@ -240,6 +242,7 @@ export function defaultSounds() {
explode: { vol: 0, x: 0 },
lifeLost: { vol: 0, x: 0 },
coinCatch: { vol: 0, x: 0 },
plouf: { vol: 0, x: 0 },
colorChange: { vol: 0, x: 0 },
},
};
@ -316,7 +319,6 @@ function isExcluded(id: PerkId) {
}
export function getLevelUnlockCondition(levelIndex: number) {
// Returns "" if level is unlocked, otherwise a string explaining how to unlock it
let required: UpgradeLike[] = [],
forbidden: UpgradeLike[] = [],
minScore = Math.max(-1000 + 100 * levelIndex, 0);
@ -399,3 +401,25 @@ export function ballTransparency(ball: Ball, gameState: GameState) {
1,
);
}
export function getCoinRenderColor(gameState: GameState, coin: Coin) {
if (
gameState.perks.metamorphosis ||
isOptionOn("colorful_coins") ||
gameState.perks.hypnosis ||
gameState.perks.rainbow
)
return coin.color;
return "#ffd300";
}
export function getCornerOffset(gameState: GameState) {
return (
(gameState.levelTime
? gameState.perks.corner_shot * gameState.brickWidth
: 0) -
gameState.perks.unbounded * gameState.brickWidth
);
}
export const isInWebView = !!window.location.href.includes("isInWebView=true");

View file

@ -5,8 +5,10 @@ export function generateSaveFileContent() {
const key = localStorage.key(i) as string;
// Avoid including recovery info in the recovery info
if (["recovery_data"].includes(key)) continue;
const value = localStorage.getItem(key) as string;
localStorageContent[key] = value;
try {
const value = localStorage.getItem(key) as string;
localStorageContent[key] = JSON.parse(value);
} catch (e) {}
}
return JSON.stringify(localStorageContent);
return localStorageContent;
}

View file

@ -3,6 +3,29 @@
"confirmRestart.text": "أنت على وشك بدء لعبة جديدة. هل أنت متأكد من رغبتك في المتابعة؟",
"confirmRestart.title": "بدء لعبة جديدة؟",
"confirmRestart.yes": "إعادة تشغيل اللعبة",
"editor.editing.bigger": "زيادة حجم المستوى",
"editor.editing.color": "اختر لونًا من قائمة الألوان (بحد أقصى 5 لكل مستوى)",
"editor.editing.copy": "نسخ رمز المستوى",
"editor.editing.copy_help": "ألصقه في قناة #levels في Discord الخاص بنا",
"editor.editing.credit": "الاعتمادات والمصدر",
"editor.editing.credit_prompt": "أدخل عنوان URL المصدر أو شرحًا لمستواك.",
"editor.editing.delete": "حذف المستوى",
"editor.editing.down": "انزل كل الطوب إلى الأسفل",
"editor.editing.help": "ثم انقر على البلاط لتلوينه.",
"editor.editing.left": "نقل جميع الطوب إلى اليسار",
"editor.editing.play": "العب هذا المستوى",
"editor.editing.rename": "اسم المستوى",
"editor.editing.rename_prompt": "الرجاء إدخال اسم جديد للمستوى",
"editor.editing.right": "حرك كل الطوب إلى اليمين",
"editor.editing.smaller": "تقليل حجم المستوى",
"editor.editing.title": "مستوى التحرير: {{name}}",
"editor.editing.up": "حرك كل الطوب لأعلى",
"editor.help": "إنشاء مستويات مخصصة ومشاركتها لتضمينها في اللعبة.",
"editor.import": "استيراد المستوى",
"editor.import_instruction": "الصق رمز المستوى لاستيراده في قائمة المستويات الخاصة بك",
"editor.locked": "احصل على مجموع نقاط قدره {{min}} لفتح القفل",
"editor.new_level": "مستوى جديد",
"editor.title": "محرر المستويات",
"gameOver.creative": "لن يتم تسجيل هذا التشغيل.",
"gameOver.cumulative_total": "لقد ارتفع مجموع درجاتك التراكمية من {{startTs}} إلى {{endTs}}.",
"gameOver.lost.summary": "لقد أسقطت الكرة بعد التقاط {{score}} قطعة نقدية.",
@ -139,6 +162,8 @@
"score_panel.title": "{{score}} نقطة في المستوى {{level}}/{{max}} ",
"score_panel.upcoming_levels": "المستويات القادمة :",
"score_panel.upgrades_picked": "الترقيات التي تم اختيارها في هذه اللعبة:",
"settings.autoplay": "",
"settings.autoplay_help": "",
"settings.basic": "الرسومات الأساسية",
"settings.basic_help": "أداء أفضل.",
"settings.colorful_coins": "عملات معدنية ملونة",
@ -163,12 +188,14 @@
"settings.load_save_file_help": "حدد ملف الحفظ على جهازك",
"settings.max_coins": " {{max}} عملات معدنية على الشاشة كحد أقصى",
"settings.max_coins_help": "تجميلي فقط، لا يؤثر على النتيجة",
"settings.max_particles": " {{max}} جسيمات كحد أقصى",
"settings.max_particles_help": "يحدد عدد الجسيمات التي تظهر على الشاشة للتأثير البصري.",
"settings.mobile": "الوضع المحمول",
"settings.mobile_help": "يترك مساحة تحت المجداف.",
"settings.pointer_lock": "قفل مؤشر الماوس",
"settings.pointer_lock_help": "يقوم بقفل وإخفاء مؤشر الماوس.",
"settings.precise_lighting": "",
"settings.precise_lighting_help": "",
"settings.probabilistic_lighting": "",
"settings.probabilistic_lighting_help": "",
"settings.record": "تسجيل مقاطع فيديو للعبة",
"settings.record_download": "تنزيل الفيديو ({{size}} ميجابايت)",
"settings.record_help": "احصل على فيديو لكل مستوى.",
@ -187,8 +214,12 @@
"settings.show_fps_help": "مراقبة أداء التطبيق",
"settings.show_stats": "عرض الإحصائيات في الوقت الحقيقي",
"settings.show_stats_help": "العملات المعدنية، الوقت، الارتدادات، الأخطاء",
"settings.smooth_lighting": "",
"settings.smooth_lighting_help": "",
"settings.sounds": "أصوات اللعبة",
"settings.sounds_help": "قد يؤدي إلى إبطاء بعض الهواتف.",
"settings.sounds_help": "أصوات صفير وبلبل و برررر",
"settings.stress_test": "",
"settings.stress_test_help": "",
"starting_perks.checked": "عند بدء لعبة جديدة، ستُمنح إحدى هذه المزايا. انقر على أي ميزة لاستبعادها.",
"starting_perks.help": "اختر الترقيات الأولية الممكنة",
"starting_perks.random": "لقد تم إزالة جميع المزايا، وسيكون الاختيار عشوائيًا.",

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,29 @@
"confirmRestart.text": "Sie sind dabei, ein neues Spiel zu beginnen. Sind Sie sicher, dass Sie weitermachen wollen?",
"confirmRestart.title": "Ein neues Spiel beginnen?",
"confirmRestart.yes": "Spiel neu starten",
"editor.editing.bigger": "Levelgröße erhöhen",
"editor.editing.color": "Wählen Sie eine Farbe aus der Farbliste (max. 5 pro Level)",
"editor.editing.copy": "Levelcode kopieren",
"editor.editing.copy_help": "Fügen Sie es in den Kanal #levels in unserem Discord ein",
"editor.editing.credit": "Credits und Quelle",
"editor.editing.credit_prompt": "Geben Sie die Quell-URL oder Erklärung Ihres Levels ein.",
"editor.editing.delete": "Ebene löschen",
"editor.editing.down": "Bewegen Sie alle Steine nach unten",
"editor.editing.help": "Klicken Sie dann auf eine Kachel, um sie einzufärben.",
"editor.editing.left": "Bewege alle Steine nach links",
"editor.editing.play": "Spiele dieses Level",
"editor.editing.rename": "Ebenenname",
"editor.editing.rename_prompt": "Bitte geben Sie einen neuen Namen für das Level ein",
"editor.editing.right": "Bewege alle Steine nach rechts",
"editor.editing.smaller": "Verringern der Levelgröße",
"editor.editing.title": "Bearbeitungsebene: {{name}}",
"editor.editing.up": "Bewegen Sie alle Steine nach oben",
"editor.help": "Erstellen Sie benutzerdefinierte Level und geben Sie sie frei, um sie in das Spiel aufzunehmen.",
"editor.import": "Importieren einer Ebene",
"editor.import_instruction": "Fügen Sie einen Levelcode ein, um ihn in Ihre Levelliste zu importieren",
"editor.locked": "Erreichen Sie eine Gesamtpunktzahl von {{min}} , um freizuschalten",
"editor.new_level": "Neues Level",
"editor.title": "Level-Editor",
"gameOver.creative": "Dieser Lauf wird nicht aufgezeichnet.",
"gameOver.cumulative_total": "Ihre kumulative Gesamtpunktzahl ist von {{startTs}} auf {{endTs}}gestiegen.",
"gameOver.lost.summary": "Du hast den Ball fallen lassen, nachdem du {{score}} Münzen gefangen hast.",
@ -15,14 +38,20 @@
"gameOver.stats.combo_max": "Max-Kombo",
"gameOver.stats.duration_per_level": "Dauer pro Stufe",
"gameOver.stats.hit_rate": "Trefferquote",
"gameOver.stats.intro": "Hier finden Sie Ihre Spielstatistik im Vergleich zu Ihren {{count}} besten Spielen.",
"gameOver.stats.intro": "",
"gameOver.stats.level_reached": "Erreichte Stufe",
"gameOver.stats.total_score": "Gesamtpunktzahl",
"gameOver.stats.upgrades_applied": "Angewandte Upgrades",
"gameOver.stats_intro": "Hier finden Sie Ihre Spielstatistik im Vergleich zu Ihren {{count}} besten Spielen.",
"gameOver.unlocked_perk": "Upgrade freigeschaltet",
"gameOver.unlocked_perk_plural": "Du hast soeben {{count}} Vergünstigungen freigeschaltet",
"gameOver.win.summary": "Das Spiel ist vorbei. Du hast {{score}} Münzen versteckt.",
"gameOver.win.title": "Du hast dieses Spiel abgeschlossen",
"help.content": "## Ziel\n\nSammle in 7 Levels so viele Münzen wie möglich ein.\nDie Münzen erscheinen, wenn du Ziegel zerbrichst.\nFangen Sie sie mit Ihrem Paddel auf, um Ihre Punktzahl zu erhöhen.\nIhr Punktestand wird in der oberen rechten Ecke des Bildschirms angezeigt.\nLassen Sie den Ball nicht fallen, sonst ist das Spiel vorbei.\n\nWenn du alle Ziegel zerstört hast, kannst du dir ein Upgrade aussuchen.\n\n## Upgrades\n\nDie Upgrades, die du wählst, gelten bis zum Ende des Laufs.\nEinige können mehrmals ausgewählt werden, um die Wirkung zu verstärken.\nEinige helfen beim Zielen oder machen das Spiel auf andere Weise einfacher.\nEinige sind nur in Kombination nützlich.\n\nZu Beginn eines jeden Spiels erhältst du immer ein Upgrade.\nIhr Symbol dient als Baustein des ersten Levels.\nDu kannst die Start-Upgrades in den Einstellungen auswählen.\n\nViele Upgrades wirken sich auf deine Kombo aus.\n\n## Combo\n\nDeine \"Combo\" ist die Anzahl der Münzen, die beim Zerbrechen eines Steins entstehen.\nSie wird auf deinem Paddel angezeigt, zum Beispiel x4 bedeutet, dass jeder Stein 4 Münzen hervorbringt.\nDie meisten Upgrades, die den Combo erhöhen, fügen auch eine Bedingung hinzu, um ihn zurückzusetzen.\nDie Kombo wird auch zurückgesetzt, wenn der Ball zum Schläger zurückkehrt, ohne einen Stein zu treffen.\nIn diesem Fall wird eine \"Miss\"-Meldung angezeigt.\n\nVersuchen Sie, jedes Mal auf einen Stein zu zielen.\n\n## Anvisieren\n\nNur die Position des Balls auf dem Schläger entscheidet darüber, wie der Ball abprallt.\nWenn der Ball das Paddel genau in der Mitte trifft, prallt er senkrecht nach oben ab.\nWenn du ihn mehr auf einer Seite triffst, hat er einen größeren Winkel.\nDie Paddelgeschwindigkeit und der Auftreffwinkel haben keinen Einfluss auf die Richtung des Balls nach dem Aufprall.\n\nViele Upgrades, die beim Zielen helfen, können freigeschaltet werden.\n\n## Freischaltungen\n\nWenn du Breakout 71 zum ersten Mal spielst, sind die meisten Upgrades und Levels gesperrt.\nUpgrades werden freigeschaltet, indem du einfach spielst und viele Münzen fängst.\nDie ersten Level werden durch das Erreichen einer hohen Punktzahl freigeschaltet.\nSpätere Levels fügen eine Bedingung hinzu, welche Vergünstigungen Sie auswählen können.\n\nEine hohe Punktzahl zu erreichen ist viel einfacher, wenn du nach jedem Level mehrere Upgrades erhältst.\n\n## Re-Rolls und kostenlose Upgrades\n\nWenn du gut spielst, bekommst du ein zusätzliches Upgrade, das du auswählen kannst:\n\n- Schaffe das Level in weniger als {{levelTimeGood}} Sekunden\n- Treffen Sie weniger als {{wallBouncedGood}} Mal die Seiten oder die Spitze\n- Fangen Sie {{catchRateGood}}% der Münzen\n- Verfehle die Steine weniger als {{missesGood}} Mal\n\nDu bekommst auch einen Re-Roll, mit dem du Upgrades überspringen kannst, wenn du noch besser abschneidest:\n\n- Schaffe ein Level in weniger als {{levelTimeBest}} Sekunden\n- Treffen Sie weniger als {{wallBouncedBest}} Mal die Seiten oder die Spitze\n- Fangen Sie {{catchRateBest}}% der Münzen\n- Verfehle die Steine weniger als {{missesBest}} Mal\n\nMit einer Option in den Einstellungen können Sie diese Statistiken anzeigen lassen",
"help.help": "Erfahren Sie mehr über das Spiel",
"help.levels": "Ebenen",
"help.title": "Hilfe",
"help.upgrades": "## Upgrades",
"history.columns.score": "Ergebnis",
"history.columns.started": "Datum",
"history.help": "Sehen Sie Ihre {{count}} besten Spiele.",
@ -45,73 +74,73 @@
"level_up.reroll": "Neu würfeln ({{count}})",
"level_up.reroll_help": "Neue Wahlmöglichkeiten bieten",
"level_up.upgrade_perk_to_level": " lvl {{level}}",
"main_menu.basic": "Grundlegende Grafiken",
"main_menu.basic_help": "Bessere Leistung.",
"main_menu.colorful_coins": "Bunte Münzen",
"main_menu.colorful_coins_help": "Münzen spawnen immer in der Farbe des Steins",
"main_menu.comboIncreaseTexts": "+X in Gold anzeigen",
"main_menu.comboIncreaseTexts_help": "Wenn die Combo zunimmt",
"main_menu.contrast": "Hoher Kontrast",
"main_menu.contrast_help": "Buntes und dunkles Rendering",
"main_menu.credit_levels": "Ebenen",
"main_menu.basic": "",
"main_menu.basic_help": "",
"main_menu.colorful_coins": "",
"main_menu.colorful_coins_help": "",
"main_menu.comboIncreaseTexts": "",
"main_menu.comboIncreaseTexts_help": "",
"main_menu.contrast": "",
"main_menu.contrast_help": "",
"main_menu.credit_levels": "",
"main_menu.donate": "Sie haben seit {{hours}} Stunden gespielt",
"main_menu.donate_help": "Wie wäre es mit einer Spende? Sie können diese Erinnerung in den Einstellungen ausblenden.",
"main_menu.donation_reminder": "Erinnern Sie mich an eine Spende",
"main_menu.donation_reminder_help": "Siehe Spielzeit und Spendenlink im Hauptmenü",
"main_menu.download_save_file": "Spielstand und Statistiken herunterladen",
"main_menu.download_save_file_help": "Abrufen einer Speicherdatei",
"main_menu.extra_bright": "Extra hell",
"main_menu.extra_bright_help": "Erhöht die Größe des Halos um Münzen und Ziegel.",
"main_menu.fullscreen": "Vollbild",
"main_menu.fullscreen_help": "Das Spiel versucht, vor dem Start in den Vollbildmodus zu wechseln",
"main_menu.help_content": "## Ziel\n\nSammle in 7 Levels so viele Münzen wie möglich ein.\nDie Münzen erscheinen, wenn du Ziegel zerbrichst.\nFangen Sie sie mit Ihrem Puck, um Ihre Punktzahl zu erhöhen.\nDein Punktestand wird in der oberen rechten Ecke des Bildschirms angezeigt.\nLassen Sie den Ball nicht fallen, sonst ist das Spiel vorbei.\n\nWenn du alle Ziegel zerstört hast, kannst du dir ein Upgrade aussuchen.\n\n## Upgrades\n\nDie Upgrades, die du wählst, gelten bis zum Ende des Laufs.\nEinige können mehrmals ausgewählt werden, um die Wirkung zu verstärken.\nEinige helfen beim Zielen oder machen das Spiel auf andere Weise einfacher.\nEinige sind nur in Kombination nützlich.\n\nZu Beginn eines jeden Spiels erhältst du immer ein Upgrade.\nIhr Symbol dient als Baustein für die erste Stufe.\nDu kannst die Start-Upgrades in den Einstellungen auswählen.\n\nViele Upgrades wirken sich auf deine Kombo aus.\n\n## Kombo\n\nDeine \"Combo\" ist die Anzahl der Münzen, die beim Zerbrechen eines Steins entstehen.\nSie wird auf deinem Puck angezeigt, z. B. x4 bedeutet, dass jeder Stein 4 Münzen hervorbringt.\nDie meisten Upgrades, die den Combo erhöhen, fügen auch eine Bedingung hinzu, um ihn zurückzusetzen.\nDie Kombo wird auch zurückgesetzt, wenn der Ball zum Puck zurückkehrt, ohne einen Stein zu treffen.\nWenn das passiert, wird eine \"Miss\"-Meldung angezeigt.\n\nVersuche, jedes Mal auf einen Stein zu zielen.\n\n## Anvisieren\n\nNur die Position des Balls auf dem Puck entscheidet darüber, wie er abprallen wird.\nWenn der Ball den Puck genau in der Mitte trifft, prallt er senkrecht nach oben zurück.\nWenn du ihn mehr auf einer Seite triffst, hat er einen größeren Winkel.\nDie Geschwindigkeit des Pucks und der Auftreffwinkel haben keinen Einfluss auf die Richtung des Balls nach dem Aufprall.\n\nViele Upgrades, die beim Zielen helfen, können freigeschaltet werden.\n\n## Freischaltungen\n\nWenn du Breakout 71 zum ersten Mal spielst, sind die meisten Upgrades und Levels gesperrt.\nUpgrades werden freigeschaltet, indem du einfach spielst und viele Münzen fängst.\nDie ersten Level werden durch das Erreichen einer hohen Punktzahl freigeschaltet.\nSpätere Levels fügen eine Bedingung hinzu, welche Vergünstigungen Sie auswählen können.\n\nEine hohe Punktzahl zu erreichen ist viel einfacher, wenn du nach jedem Level mehrere Upgrades erhältst.\n\n## Re-Rolls und kostenlose Upgrades\n\nDu bekommst ein zusätzliches Upgrade, wenn du gut spielst:\n\n- Schaffe das Level in weniger als {{levelTimeGood}} Sekunden\n- Treffen Sie weniger als {{wallBouncedGood}} Mal die Seiten oder die Spitze\n- Fangen Sie {{catchRateGood}}% der Münzen\n- Verfehle die Steine weniger als {{missesGood}} Mal\n\nDu bekommst auch einen Re-Roll, mit dem du Upgrades überspringen kannst, wenn du noch besser abschneidest:\n\n- Schaffe ein Level in weniger als {{levelTimeBest}} Sekunden\n- Treffen Sie weniger als {{wallBouncedBest}} Mal die Seiten oder die Spitze\n- Fangen Sie {{catchRateBest}}% der Münzen\n- Verfehle die Steine weniger als {{missesBest}} Mal\n\nMit einer Option in den Einstellungen können Sie diese Statistiken anzeigen lassen",
"main_menu.help_help": "Erfahren Sie mehr über das Spiel",
"main_menu.help_title": "Hilfe",
"main_menu.help_upgrades": "## Upgrades",
"main_menu.donation_reminder": "",
"main_menu.donation_reminder_help": "",
"main_menu.download_save_file": "",
"main_menu.download_save_file_help": "",
"main_menu.extra_bright": "",
"main_menu.extra_bright_help": "",
"main_menu.fullscreen": "",
"main_menu.fullscreen_help": "",
"main_menu.help_content": "",
"main_menu.help_help": "",
"main_menu.help_title": "",
"main_menu.help_upgrades": "",
"main_menu.high_score": "Hohe Punktzahl : {{score}}",
"main_menu.kid": "Kinder-Modus",
"main_menu.kid_help": "Beginnen Sie künftige Spiele mit einem \"langsameren Ball\".",
"main_menu.language": "Sprache",
"main_menu.language_help": "Wählen Sie die Sprache des Spiels",
"main_menu.load_save_file": "Speicherdatei laden",
"main_menu.load_save_file_help": "Wählen Sie eine Speicherdatei auf Ihrem Gerät",
"main_menu.max_coins": " {{max}} Münzen auf dem Bildschirm maximal",
"main_menu.max_coins_help": "Nur kosmetisch, keine Auswirkung auf das Ergebnis",
"main_menu.max_particles": " {{max}} Teilchen maximal",
"main_menu.max_particles_help": "Begrenzt die Anzahl der auf dem Bildschirm angezeigten Partikel für visuelle Effekte.",
"main_menu.mobile": "Mobiler Modus",
"main_menu.mobile_help": "Lässt Raum unter dem Puck.",
"main_menu.kid": "",
"main_menu.kid_help": "",
"main_menu.language": "",
"main_menu.language_help": "",
"main_menu.load_save_file": "",
"main_menu.load_save_file_help": "",
"main_menu.max_coins": "",
"main_menu.max_coins_help": "",
"main_menu.max_particles": "",
"main_menu.max_particles_help": "",
"main_menu.mobile": "",
"main_menu.mobile_help": "",
"main_menu.normal": "Neues Spiel",
"main_menu.normal_help": "Spiele 7 Levels mit einem zufälligen Startvorteil",
"main_menu.pointer_lock": "Mauszeigersperre",
"main_menu.pointer_lock_help": "Sperrt und versteckt den Mauszeiger.",
"main_menu.record": "Spielvideos aufnehmen",
"main_menu.record_download": "Video herunterladen ({{size}} MB)",
"main_menu.record_help": "Holen Sie sich ein Video von jedem Level.",
"main_menu.red_miss": "Miss Warnung",
"main_menu.red_miss_help": "Zeigen Sie rote Partikel um Bälle, die ohne Treffer zu Boden gehen.",
"main_menu.reset": "Spiel zurücksetzen",
"main_menu.reset_cancel": "Nein",
"main_menu.reset_confirm": "Ja",
"main_menu.reset_help": "Löschen von Highscore, Spielzeit und Statistiken",
"main_menu.reset_instruction": "Sie verlieren alle Fortschritte, die Sie im Spiel gemacht haben, sind Sie sicher?",
"main_menu.save_file_error": "Fehler beim Laden einer Speicherdatei",
"main_menu.save_file_loaded": "Geladene Datei speichern",
"main_menu.save_file_loaded_help": "Die App wird nun neu geladen, um die Speicherung zu übernehmen.",
"main_menu.save_file_loaded_ok": "Ok",
"main_menu.pointer_lock": "",
"main_menu.pointer_lock_help": "",
"main_menu.record": "",
"main_menu.record_download": "",
"main_menu.record_help": "",
"main_menu.red_miss": "",
"main_menu.red_miss_help": "",
"main_menu.reset": "",
"main_menu.reset_cancel": "",
"main_menu.reset_confirm": "",
"main_menu.reset_help": "",
"main_menu.reset_instruction": "",
"main_menu.save_file_error": "",
"main_menu.save_file_loaded": "",
"main_menu.save_file_loaded_help": "",
"main_menu.save_file_loaded_ok": "",
"main_menu.settings_help": "Passen Sie das Spiel an Ihre Bedürfnisse und Ihren Geschmack an",
"main_menu.settings_title": "Einstellungen",
"main_menu.show_fps": "FPS-Zähler",
"main_menu.show_fps_help": "Überwachen Sie die Leistung der Anwendung",
"main_menu.show_stats": "Echtzeit-Statistiken anzeigen",
"main_menu.show_stats_help": "Münzen, Zeit, Sprünge, Fehlschüsse",
"main_menu.sounds": "Spiel-Sounds",
"main_menu.sounds_help": "Kann einige Telefone verlangsamen.",
"main_menu.starting_perks": "Startvorteile",
"main_menu.starting_perks_checked": "Wenn Sie ein neues Spiel beginnen, wird Ihnen eine dieser Vergünstigungen angeboten. Klicken Sie auf eine Vergünstigung, um sie auszuschließen.",
"main_menu.starting_perks_full_random": "Alle Vorteile wurden entfernt, die Auswahl erfolgt nach dem Zufallsprinzip.",
"main_menu.starting_perks_help": "Wählen Sie mögliche Start-Upgrades",
"main_menu.starting_perks_unchecked": "Die folgenden Vergünstigungen werden nicht als Startvergünstigungen angeboten, aber Sie können sie durch Anklicken zum Pool hinzufügen.",
"main_menu.show_fps": "",
"main_menu.show_fps_help": "",
"main_menu.show_stats": "",
"main_menu.show_stats_help": "",
"main_menu.sounds": "",
"main_menu.sounds_help": "",
"main_menu.starting_perks": "",
"main_menu.starting_perks_checked": "",
"main_menu.starting_perks_full_random": "",
"main_menu.starting_perks_help": "",
"main_menu.starting_perks_unchecked": "",
"main_menu.title": "Breakout 71",
"main_menu.unlocks": "Freigegebene Inhalte",
"main_menu.unlocks_help": "Freigegebene Vergünstigungen und Stufen ausprobieren",
@ -133,6 +162,69 @@
"score_panel.title": "{{score}} Punkte auf Stufe {{level}}/{{max}} ",
"score_panel.upcoming_levels": "Kommende Stufen :",
"score_panel.upgrades_picked": "Die in diesem Spiel gewählten Upgrades laufen:",
"settings.autoplay": "",
"settings.autoplay_help": "",
"settings.basic": "Grundlegende Grafiken",
"settings.basic_help": "Bessere Leistung.",
"settings.colorful_coins": "Bunte Münzen",
"settings.colorful_coins_help": "Münzen spawnen immer in der Farbe des Steins",
"settings.comboIncreaseTexts": "+X in Gold anzeigen",
"settings.comboIncreaseTexts_help": "Wenn die Combo zunimmt",
"settings.contrast": "Hoher Kontrast",
"settings.contrast_help": "Buntes und dunkles Rendering",
"settings.donation_reminder": "Erinnern Sie mich an eine Spende",
"settings.donation_reminder_help": "Siehe Spielzeit und Spendenlink im Hauptmenü",
"settings.download_save_file": "Spielstand und Statistiken herunterladen",
"settings.download_save_file_help": "Abrufen einer Speicherdatei",
"settings.extra_bright": "Extra hell",
"settings.extra_bright_help": "Erhöht die Größe des Halos um Münzen und Ziegel.",
"settings.fullscreen": "Vollbild",
"settings.fullscreen_help": "Das Spiel versucht, vor dem Start in den Vollbildmodus zu wechseln",
"settings.kid": "Kinder-Modus",
"settings.kid_help": "Beginnen Sie künftige Spiele mit einem \"langsameren Ball\".",
"settings.language": "Sprache",
"settings.language_help": "Wählen Sie die Sprache des Spiels",
"settings.load_save_file": "Speicherdatei laden",
"settings.load_save_file_help": "Wählen Sie eine Speicherdatei auf Ihrem Gerät",
"settings.max_coins": " {{max}} Münzen auf dem Bildschirm maximal",
"settings.max_coins_help": "Nur kosmetisch, keine Auswirkung auf das Ergebnis",
"settings.mobile": "Mobiler Modus",
"settings.mobile_help": "Lässt Platz unter dem Paddel.",
"settings.pointer_lock": "Mauszeigersperre",
"settings.pointer_lock_help": "Sperrt und versteckt den Mauszeiger.",
"settings.precise_lighting": "",
"settings.precise_lighting_help": "",
"settings.probabilistic_lighting": "",
"settings.probabilistic_lighting_help": "",
"settings.record": "Spielvideos aufnehmen",
"settings.record_download": "Video herunterladen ({{size}} MB)",
"settings.record_help": "Holen Sie sich ein Video von jedem Level.",
"settings.red_miss": "Miss Warnung",
"settings.red_miss_help": "Zeigen Sie rote Partikel um Bälle, die ohne Treffer zu Boden gehen.",
"settings.reset": "Spiel zurücksetzen",
"settings.reset_cancel": "Nein",
"settings.reset_confirm": "Ja",
"settings.reset_help": "Löschen von Highscore, Spielzeit und Statistiken",
"settings.reset_instruction": "Sie verlieren alle Fortschritte, die Sie im Spiel gemacht haben, sind Sie sicher?",
"settings.save_file_error": "Fehler beim Laden einer Speicherdatei",
"settings.save_file_loaded": "Geladene Datei speichern",
"settings.save_file_loaded_help": "Die App wird nun neu geladen, um die Speicherung zu übernehmen.",
"settings.save_file_loaded_ok": "Ok",
"settings.show_fps": "FPS-Zähler",
"settings.show_fps_help": "Überwachen Sie die Leistung der Anwendung",
"settings.show_stats": "Echtzeit-Statistiken anzeigen",
"settings.show_stats_help": "Münzen, Zeit, Sprünge, Fehlschüsse",
"settings.smooth_lighting": "",
"settings.smooth_lighting_help": "",
"settings.sounds": "Spiel-Sounds",
"settings.sounds_help": "Piepsen, Bloops und Brrrr",
"settings.stress_test": "",
"settings.stress_test_help": "",
"starting_perks.checked": "Wenn Sie ein neues Spiel beginnen, wird Ihnen eine dieser Vergünstigungen angeboten. Klicken Sie auf eine Vergünstigung, um sie auszuschließen.",
"starting_perks.help": "Wählen Sie mögliche Start-Upgrades",
"starting_perks.random": "Alle Vorteile wurden gestrichen, die Auswahl erfolgt nach dem Zufallsprinzip.",
"starting_perks.title": "Startvorteile",
"starting_perks.unchecked": "Die folgenden Vergünstigungen werden nicht als Startvergünstigungen angeboten, aber Sie können sie durch Anklicken zum Pool hinzufügen.",
"unlocks.greyed_out_help": "Die ausgegrauten Upgrades können freigeschaltet werden, indem Sie Ihre Gesamtpunktzahl erhöhen. Die Gesamtpunktzahl erhöht sich jedes Mal, wenn Sie im Spiel punkten.",
"unlocks.intro": "Deine Gesamtpunktzahl ist {{ts}}. Nachfolgend finden Sie alle Upgrades und Levels, die das Spiel zu bieten hat. Klicken Sie auf ein Upgrade oder eine Stufe, um ein Testspiel damit zu starten.",
"unlocks.just_unlocked": "Level freigeschaltet",
@ -167,28 +259,34 @@
"upgrades.bigger_explosions.name": "Kaboom",
"upgrades.bigger_explosions.tooltip": "Größere Explosionen",
"upgrades.bigger_explosions.verbose_description": "Die Standardexplosion räumt ein 3x3-Quadrat, mit dieser wird es zu einem 5x5-Quadrat, und der Schlag auf die Münzen ist auch wesentlich stärker. Der Bildschirm blinkt nach jeder Explosion (außer im Basismodus)",
"upgrades.bigger_puck.name": "Größerer Puck",
"upgrades.bigger_puck.name": "Größeres Paddel",
"upgrades.bigger_puck.tooltip": "Einfach mehr Münzen fangen.",
"upgrades.bigger_puck.verbose_description": "Ein größerer Puck macht es einfacher, den Ball nie zu verfehlen und mehr Münzen zu fangen. Außerdem kann man die Abpraller genau ausrichten (der Winkel des Balls hängt nur davon ab, wo er den Puck trifft).\n\nAllerdings ist es mit einem großen Puck schwieriger, die Seiten des Levels zu umspielen, so dass es manchmal unvermeidlich ist, den Ball zu verfehlen.",
"upgrades.bigger_puck.verbose_description": "Ein größeres Paddel macht es einfacher, den Ball nie zu verfehlen und mehr Münzen zu fangen. Außerdem kann man die Abpraller genau ausrichten (der Winkel des Balls hängt nur davon ab, wo er das Paddel trifft).",
"upgrades.bricks_attract_ball.name": "Ziegelsteine ziehen Bälle an",
"upgrades.bricks_attract_ball.tooltip": "Der Ball fliegt zu den ersten {{count}} Steinen, die er trifft.",
"upgrades.bricks_attract_ball.verbose_description": "Die Wirkung ist bei höheren Stufen stärker. Die Anzahl der Steine, die getroffen werden können, bevor der Effekt aufhört, ist ebenfalls höher. Der Effekt setzt wieder ein, wenn der Ball den Puck trifft.",
"upgrades.bricks_attract_coins.name": "Ziegelsteine ziehen Münzen an",
"upgrades.bricks_attract_coins.tooltip": "Hilft ihnen, dort oben zu bleiben",
"upgrades.bricks_attract_coins.verbose_description": "",
"upgrades.clairvoyant.name": "Hellsichtig",
"upgrades.clairvoyant.tooltip": "Sehen Sie die nächsten Levels, die HP der Steine und die Ballrichtung",
"upgrades.clairvoyant.verbose_description": "Hilft dir, die richtigen Upgrades auszuwählen und zu verstehen, was es mit den robusten Steinen auf sich hat. Level 2 und 3 bringen zusätzliches Wissen von zweifelhaftem Nutzen (erreichbar im Loop-Modus)",
"upgrades.coin_magnet.help_plural": "Stärkere Wirkung auf die Münzen",
"upgrades.coin_magnet.name": "Magnet für Münzen",
"upgrades.coin_magnet.tooltip": "Puck zieht Münzen an",
"upgrades.coin_magnet.verbose_description": "Lenkt die Münzen auf den Puck. Der Effekt ist stärker, wenn die Münze bereits in der Nähe ist. Alle Münzen zu fangen, bringt besondere Boni im Spiel.\n\nEine andere Möglichkeit, mehr Münzen zu fangen, besteht darin, Ziegelsteine von unten zu treffen. Die Geschwindigkeit und die Richtung des Balls beeinflussen die Geschwindigkeit der Münzen, die auftauchen.",
"upgrades.coin_magnet.tooltip": "Paddel zieht Münzen an",
"upgrades.coin_magnet.verbose_description": "Lenkt die Münzen auf das Paddel. Der Effekt ist stärker, wenn die Münze bereits in der Nähe ist.",
"upgrades.compound_interest.name": "Zinseszins",
"upgrades.compound_interest.tooltip": "+{{lvl}} Combo pro zerbrochenem Stein, Rücksetzung bei verlorener Münze",
"upgrades.compound_interest.verbose_description": "Mit jedem Ziegelstein, den du zerbrichst, wächst deine Combo um einen Ziegelstein, und mit jedem Ziegelstein, den du zerbrichst, erhältst du mehr und mehr Münzen.\n\nAchte jedoch darauf, jede dieser Münzen mit deinem Puck zu fangen, da jede verlorene Münze deine Kombo zurücksetzt.\n\nSobald deine Kombo über dem Minimum liegt, erscheint am unteren Rand des Spielfelds eine rote Linie, die dich daran erinnert, dass die Münzen nicht dorthin gelangen sollten.",
"upgrades.concave_puck.name": "Konkaver Puck",
"upgrades.compound_interest.verbose_description": "Deine Combo wächst jedes Mal um eins, wenn du einen Stein zerbrichst, und bringt mit jedem Stein, den du zerbrichst, mehr und mehr Münzen hervor.\nAchten Sie jedoch darauf, jede dieser Münzen mit Ihrem Paddel aufzufangen, da jede verlorene Münze Ihre Combo zurücksetzt.\nSobald deine Kombo über dem Minimum liegt, wird der Boden des Spielfelds mit einer roten Linie markiert, um dich daran zu erinnern, dass die Münzen nicht dorthin gehören.",
"upgrades.concave_puck.name": "Konkaves Paddel",
"upgrades.concave_puck.tooltip": "Verbessert die vertikale Zielgenauigkeit",
"upgrades.concave_puck.verbose_description": "Die Bälle gehen zu Beginn des Levels gerade nach oben und prallen in einem geringeren Winkel ab.",
"upgrades.corner_shot.name": "Eckball",
"upgrades.corner_shot.tooltip": "Lässt deinen Puck mit den Rändern des Bildschirms überlappen",
"upgrades.corner_shot.tooltip": "Lässt Ihr Paddel mit den Rändern des Bildschirms überlappen",
"upgrades.corner_shot.verbose_description": "Hilft beim Zielen in den Kurven. Weitere Stufen lassen Sie weiter hinausgehen.",
"upgrades.etherealcoins.name": "Münzen, im Weltraum",
"upgrades.etherealcoins.tooltip": "Münzen werden nicht mehr von der Schwerkraft beeinflusst",
"upgrades.etherealcoins.verbose_description": "",
"upgrades.etherealcoins.verbose_description": "Die Münzen behalten ihre Geschwindigkeit auch nach mehreren Aufprallvorgängen bei und werden nicht mehr durch die Schwerkraft beeinflusst.",
"upgrades.extra_levels.name": "5 min mehr",
"upgrades.extra_levels.tooltip": "Spielen Sie {{count}} Stufen statt 7",
"upgrades.extra_levels.verbose_description": "Das Standardspiel kann maximal 7 Stufen dauern, danach ist das Spiel vorbei.\n\nMit jeder Stufe dieses Vorteils können Sie eine Stufe höher gehen. Die letzten Level sind oft diejenigen, in denen man die meisten Punkte macht, so dass der Unterschied dramatisch sein kann.",
@ -206,16 +304,19 @@
"upgrades.ghost_coins.tooltip": "Münzen gehen langsam durch Ziegelsteine",
"upgrades.ghost_coins.verbose_description": "Das ist kein Bug, sondern ein Feature! Die Münzen fliegen nur langsam durch die Ziegel. Höhere Stufen lassen sie schneller fliegen.",
"upgrades.helium.name": "Helium",
"upgrades.helium.tooltip": "Umgekehrte Schwerkraft links und rechts vom Puck",
"upgrades.helium.tooltip": "Umgekehrte Schwerkraft links und rechts des Paddels",
"upgrades.helium.verbose_description": "Dies wirkt sich auf die Münzen aus und lässt sie nach oben treiben, bis Sie bereit sind, sie aufzuheben.",
"upgrades.hot_start.name": "Heißer Start",
"upgrades.hot_start.tooltip": "Start bei Kombo {{start}}, -{{loss}} Kombo pro Sekunde",
"upgrades.hot_start.verbose_description": "Zu Beginn eines jeden Levels beginnt deine Kombo mit +30 Punkten, aber dann wird sie jede Sekunde um einen Punkt verringert. Der Effekt ist mit anderen Perks stapelbar.",
"upgrades.hypnosis.name": "Hypnose",
"upgrades.hypnosis.tooltip": "Immer wenn ein Ziegelstein seine Farbe ändert, teleportierst du diese Münze zur nächsten Kugel und lädst ihre Fähigkeit, einen Ziegelstein zu färben, wieder auf.",
"upgrades.hypnosis.verbose_description": "",
"upgrades.implosions.name": "Implosionen",
"upgrades.implosions.tooltip": "Explosionen saugen Münzen an, anstatt sie wegzublasen",
"upgrades.implosions.verbose_description": "Die Explosionskraft wird in die andere Richtung angewendet. Weitere Stufen wirken als \"größere Explosion\".",
"upgrades.instant_upgrade.name": "Sofortiges Upgrade",
"upgrades.instant_upgrade.tooltip": "+1 Upgrade jetzt, -1 Wahl bis Spielende.",
"upgrades.instant_upgrade.tooltip": "+2 Aufwertung jetzt, -1 Wahl bis Spielende.",
"upgrades.instant_upgrade.verbose_description": "Wählen Sie sofort zwei Upgrades aus, so dass Sie ein kostenloses Upgrade erhalten und eines, mit dem Sie das Upgrade, mit dem Sie diese Vergünstigung erhalten haben, zurückzahlen. Bei jedem weiteren Menü zur Auswahl von Upgrades gibt es weniger Optionen zur Auswahl.",
"upgrades.left_is_lava.name": "Linke Seite meiden",
"upgrades.left_is_lava.tooltip": "+{{lvl}} Combo pro zerbrochenem Stein. Combo wird zurückgesetzt, wenn der Ball die linke Seite des Bildschirms trifft.",
@ -225,7 +326,7 @@
"upgrades.limitless.verbose_description": "Durch die Wahl dieses Vorteils wird auch sein eigenes Limit um eins erhöht, so dass man es erneut wählen kann.",
"upgrades.metamorphosis.name": "Metamorphose",
"upgrades.metamorphosis.tooltip": "Jede Münze kann {{lvl}} Steine mit ihrer Farbe färben",
"upgrades.metamorphosis.verbose_description": "Mit diesem Vorteil haben die Münzen die Farbe des Steins, aus dem sie stammen, und färben den ersten Stein, den sie berühren, in derselben Farbe.\n\nMünzen spawnen mit der Geschwindigkeit des Balls, der sie zerbrochen hat, was bedeutet, dass du ein bisschen in die Richtung der Ziegelsteine zielen kannst, die du \"malen\" willst.",
"upgrades.metamorphosis.verbose_description": "Mit diesem Vorteil haben die Münzen die Farbe des Ziegels, aus dem sie stammen, und färben den ersten Ziegel, den sie berühren, in derselben Farbe. Die Münzen spawnen mit der Geschwindigkeit des Balls, der sie zerbrochen hat, was bedeutet, dass du ein bisschen in die Richtung der Ziegelsteine zielen kannst, die du \"anmalen\" willst. Auf Stufe 1 kann jede Münze 1 Ziegelstein einfärben, bevor sie \"verbraucht\" wird und hohl erscheint.",
"upgrades.minefield.name": "Minenfeld",
"upgrades.minefield.tooltip": "+{{lvl}} Kombo pro Bombenstein auf dem Bildschirm",
"upgrades.minefield.verbose_description": "Fügt +lvl zur Combo hinzu, wenn ein Ziegelstein platziert wird, -lvl, wenn er zerstört wird, und erhöht die Basiskombo um die Anzahl der Ziegelsteine mal lvl",
@ -233,27 +334,30 @@
"upgrades.multiball.tooltip": "Beginne jedes Level mit {{count}} Bällen.",
"upgrades.multiball.verbose_description": "Sobald du den Ball in Breakout 71 fallen lässt, hast du verloren.\n\nMit diesem Vorteil erhalten Sie zwei Bälle und können es sich daher leisten, einen zu verlieren.\n\nDie verlorenen Bälle kommen im nächsten Level zurück.\n\nWenn du mehr als einen Ball hast, stehen dir weitere Vergünstigungen zur Verfügung, und du kannst das Level natürlich schneller abschließen.",
"upgrades.nbricks.name": "Strenger Stichprobenumfang",
"upgrades.nbricks.tooltip": "Triff genau {{lvl}} Steine pro Puckaufprall für +{{lvl}} Combo, sonst wird er zurückgesetzt",
"upgrades.nbricks.tooltip": "Triff genau {{lvl}} Steine pro Paddle Bounce für +{{lvl}} Combo, sonst wird er zurückgesetzt",
"upgrades.nbricks.verbose_description": "Ihr müsst die Steine nicht unbedingt zerstören, aber ihr müsst sie treffen. Durch Explosionen zerstörte Ziegelsteine zählen nicht.",
"upgrades.one_more_choice.name": "Extra Auswahl",
"upgrades.one_more_choice.tooltip": "Weitere Stufenaufstiege bieten {{lvl}} weitere Option(en) in der Liste",
"upgrades.one_more_choice.verbose_description": "Jedes Upgrade-Menü wird eine weitere Option enthalten. Erhöht nicht die Anzahl der Upgrades, die Sie auswählen können.",
"upgrades.passive_income.name": "Passives Einkommen",
"upgrades.passive_income.tooltip": "+{{lvl}} Combo / Brick, es sei denn, der Puck hat sich in den letzten {{time}}s bewegt, dann wird er stattdessen zurückgesetzt",
"upgrades.passive_income.tooltip": "+{{lvl}} Combo / Brick, es sei denn, das Paddel hat sich in den letzten {{time}}s bewegt, dann wird es stattdessen zurückgesetzt",
"upgrades.passive_income.verbose_description": "Einige Vergünstigungen können den Bällen helfen, das zu tun, was du willst, ohne dass du etwas tun musst.",
"upgrades.picky_eater.name": "Wählerischer Esser",
"upgrades.picky_eater.tooltip": "+{{lvl}} Combo pro zerbrochenem Stein, wird bei Farbwechsel des Balls zurückgesetzt",
"upgrades.picky_eater.verbose_description": "Jedes Mal, wenn du einen Stein der gleichen Farbe wie deine Kugel zerstörst, erhöht sich deine Kombination um eins.\nWenn es eine andere Farbe ist, nimmt der Ball diese neue Farbe an, aber die Kombination wird zurückgesetzt, es sei denn, es sind keine Steine in der Farbe des Balls übrig.\nSobald du eine höhere Kombination als das Minimum erreicht hast, werden die Steine der falschen Farbe rot umrandet.\nWenn du mehr als eine Kugel hast, wechseln sie alle die Farbe, sobald eine von ihnen einen Stein trifft.",
"upgrades.pierce.name": "Piercing",
"upgrades.pierce.tooltip": "Ball durchdringt {{count}} Steine nach einem Puckabprall",
"upgrades.pierce.verbose_description": "Der Ball prallt normalerweise ab, sobald er etwas berührt. Mit diesem Vorteil setzt er seine Flugbahn für bis zu 3 Ziegelsteine gebrochen fort.\n\nDanach prallt er am 4. Ziegelstein ab, und du musst den Puck berühren, um den Zähler zurückzusetzen.",
"upgrades.pierce.tooltip": "Der Ball durchdringt {{count}} Steine nach einem Paddle Bounce",
"upgrades.pierce.verbose_description": "Der Ball prallt normalerweise ab, sobald er etwas berührt. Mit diesem Vorteil setzt er seine Flugbahn für bis zu 3 Ziegelsteine gebrochen fort.\n\nDanach prallt er am 4. Ziegelstein ab, und du musst das Paddel berühren, um den Zähler zurückzusetzen.",
"upgrades.pierce_color.name": "Farbe durchstechen",
"upgrades.pierce_color.tooltip": "+{{lvl}} Schaden an Steinen der Farbe der Kugel",
"upgrades.pierce_color.verbose_description": "Wenn ein Ball auf einen gleichfarbigen Stein trifft, geht er ungehindert durch.\n\nSobald er einen andersfarbigen Stein erreicht, zerbricht er ihn, nimmt seine Farbe an und springt auf.\n\nWenn du robuste Steine hast, kann es sein, dass der Ball trotzdem an einem gleichfarbigen Stein abprallt.",
"upgrades.puck_repulse_ball.help_plural": "Die Abstoßungskraft ist größer",
"upgrades.puck_repulse_ball.help_plural": "Stärkere Abstoßungskraft",
"upgrades.puck_repulse_ball.name": "Weiche Landung",
"upgrades.puck_repulse_ball.tooltip": "Puck stößt Bälle ab",
"upgrades.puck_repulse_ball.verbose_description": "Wenn ein Ball in die Nähe des Pucks kommt, wird er langsamer und springt möglicherweise sogar ab, ohne den Puck zu berühren.",
"upgrades.puck_repulse_ball.tooltip": "Paddel stößt Bälle ab",
"upgrades.puck_repulse_ball.verbose_description": "Wenn ein Ball in die Nähe des Schlägers kommt, wird er langsamer und springt möglicherweise sogar, ohne den Schläger zu berühren.",
"upgrades.rainbow.name": "Regenbogen",
"upgrades.rainbow.tooltip": "Münzen spawnen in Regenbogenfarben.",
"upgrades.rainbow.verbose_description": "Mit jedem Level steigt der Anteil der farbigen Münzen. Die Farbe hängt von der Levelzeit ab.",
"upgrades.reach.name": "Von oben nach unten",
"upgrades.reach.tooltip": " Das Berühren der N Steine der untersten Reihe setzt die Kombo zurück. Andernfalls: +N Combo",
"upgrades.reach.verbose_description": "Wenn es nur eine Reihe von Steinen gibt, oder wenn die unterste Reihe von Steinen die gesamte Breite des Spiels abdeckt, dann bewirkt dieser Vorteil nichts. Andernfalls wird die Combo zurückgesetzt, wenn diese unterste Reihe zerstört wird, während alles andere die Combo um die Anzahl der Steine in dieser untersten Reihe erhöht.\n\nDie unterste Reihe wird in rot hervorgehoben.",
@ -261,7 +365,7 @@
"upgrades.respawn.tooltip": "{{percent}}% der Steine spawnen nach {{delay}}s erneut.",
"upgrades.respawn.verbose_description": "Einige Partikeleffekte lassen Sie wissen, wo Ziegelsteine erscheinen werden.",
"upgrades.right_is_lava.name": "Vermeiden Sie die rechte Seite",
"upgrades.right_is_lava.tooltip": "+{{lvl}} Kombo pro Stein. Combo wird zurückgesetzt, wenn der Ball die linke Seite des Bildschirms trifft.",
"upgrades.right_is_lava.tooltip": "+{{lvl}} Kombo pro Stein. Combo wird zurückgesetzt, wenn der Ball die rechte Seite des Bildschirms trifft.",
"upgrades.right_is_lava.verbose_description": "Jedes Mal, wenn du einen Ziegelstein zerbrichst, erhöht sich deine Kombo um eins, so dass du von allen weiteren Ziegelsteinen, die du zerbrichst, eine Münze mehr bekommst.\n\nAllerdings wird deine Kombo zurückgesetzt, sobald dein Ball die rechte Seite trifft.\n\nSobald deine Combo ansteigt, wird die rechte Seite rot, um dich daran zu erinnern, dass du es vermeiden solltest, sie zu treffen.\n",
"upgrades.sacrifice.help_l1": "Der Verlust eines Lebens löscht alle Steine",
"upgrades.sacrifice.help_over": "Der Verlust eines Lebens {{lvl}}x die Combo löscht dann alle Steine",
@ -270,7 +374,7 @@
"upgrades.sapper.help_plural": "Die ersten {{lvl}} Steine, die zerbrochen werden, werden zu Bomben.",
"upgrades.sapper.name": "Sapper",
"upgrades.sapper.tooltip": "Der erste zerbrochene Ziegelstein wird zu einer Bombe.",
"upgrades.sapper.verbose_description": "Statt einfach zu verschwinden, wird der erste Stein, den du zerbrichst, durch einen Bombenstein ersetzt. Wenn du den Ball auf dem Puck abprallen lässt, wird der Effekt wieder aktiviert. Wenn Sie diese Fähigkeit aufwerten, können Sie mehr Bomben platzieren.\n\nDenken Sie daran, dass Bomben die Geschwindigkeit von Münzen in der Nähe beeinflussen, so dass es bei zu vielen Explosionen schwierig werden könnte, die Früchte Ihrer harten Arbeit zu fangen.",
"upgrades.sapper.verbose_description": "Anstatt einfach zu verschwinden, wird der erste Stein, den du zerbrichst, durch einen Bombenstein ersetzt. Wenn du den Ball auf dem Paddel abprallen lässt, wird der Effekt wieder aktiviert. Wenn Sie diese Fähigkeit aufwerten, können Sie mehr Bomben platzieren.",
"upgrades.shocks.name": "Schocks",
"upgrades.shocks.tooltip": "Kollisionen mit explosiven Kugeln",
"upgrades.shocks.verbose_description": "Wenn zwei Kugeln zusammenstoßen, tauschen sie ihre Geschwindigkeit aus, lösen eine Explosion aus und gewinnen an Geschwindigkeit, um sie zu trennen.",
@ -290,45 +394,50 @@
"upgrades.slow_down.name": "Langsamer Ball",
"upgrades.slow_down.tooltip": "Der Ball bewegt sich langsamer",
"upgrades.slow_down.verbose_description": "Der Ball fängt relativ langsam an, aber mit jeder Spielstufe wird er ein bisschen schneller.\n\nEr wird auch schneller, wenn du viel Zeit in einem Level verbringst.\n\nDieser Vorteil macht ihn überschaubarer.\n\nDu kannst ihn jedes Mal beim Start erhalten, indem du den Kindermodus im Menü aktivierst.",
"upgrades.smaller_puck.help_plural": "Noch kleinerer Puck und höhere Basis-Kombination",
"upgrades.smaller_puck.name": "Kleinerer Puck",
"upgrades.smaller_puck.help_plural": "Noch kleineres Paddel und höhere Basis-Kombination",
"upgrades.smaller_puck.name": "Kleineres Paddel",
"upgrades.smaller_puck.tooltip": "Gibt auch +5 Basis-Kombo",
"upgrades.smaller_puck.verbose_description": "Dadurch wird der Puck kleiner, was theoretisch einige Eckbälle einfacher macht, aber eigentlich nur den Schwierigkeitsgrad erhöht.\n\nDeshalb gibt es auch einen netten Bonus von +5 Münzen pro Ziegelstein für alle Ziegelsteine, die du nach der Auswahl dieses Pucks kaputt machst.",
"upgrades.smaller_puck.verbose_description": "Dadurch wird das Paddel kleiner, was theoretisch einige Eckschüsse einfacher macht, aber eigentlich nur den Schwierigkeitsgrad erhöht.\n\nDeshalb gibt es auch einen netten Bonus von +5 Münzen pro Ziegelstein für alle Ziegelsteine, die du nach der Auswahl dieser Option zerstörst.",
"upgrades.soft_reset.name": "Weicher Reset",
"upgrades.soft_reset.tooltip": "Combo-Rückstellungen halten {{percent}}%",
"upgrades.soft_reset.verbose_description": "Begrenzen Sie die Auswirkungen eines Combo-Resets.",
"upgrades.streak_shots.name": "Glückssträhne",
"upgrades.streak_shots.tooltip": "Mehr Münzen, wenn du viele Steine zerbrichst, bevor du auf den Puck springst.",
"upgrades.streak_shots.verbose_description": "Jedes Mal, wenn du einen Ziegelstein zerbrichst, erhöht sich deine Kombo (Anzahl der Münzen pro Ziegelstein) um eins.\n\nSobald der Ball jedoch deinen Puck berührt, wird die Kombination auf ihren Standardwert zurückgesetzt und du erhältst nur noch eine Münze pro Stein.\n\nSobald dein Combo den Basiswert übersteigt, wird dein Puck rot, um dich daran zu erinnern, dass deine Combo zerstört wird, wenn du ihn mit dem Ball berührst.",
"upgrades.streak_shots.tooltip": "Mehr Münzen, wenn du viele Steine zerbrichst, bevor du auf das Paddel springst.",
"upgrades.streak_shots.verbose_description": "Jedes Mal, wenn du einen Stein zerbrichst, erhöht sich dein Combo um eins.\n\nSobald der Ball jedoch Ihr Paddel berührt, wird die Kombination auf ihren Standardwert zurückgesetzt.\n\nSobald deine Kombo den Basiswert übersteigt, wird dein Paddel rot umrandet, um dich daran zu erinnern, dass es deine Kombo zerstört, wenn du es mit dem Ball berührst.",
"upgrades.sturdy_bricks.name": "Stabile Ziegel",
"upgrades.sturdy_bricks.tooltip": "+{{lvl}} Ziegel HP, +{{percent}}% Münzen, die beim Zerbrechen erzeugt werden",
"upgrades.sturdy_bricks.verbose_description": "Jede Stufe dieses Vorteils fügt allen Steinen einen HP hinzu. Du kannst die HP-Zahl mit der Perk \"Hellseher\" sehen. Du kannst den Schaden der Kugeln erhöhen, indem du den \"Piercing\"-Perk bekommst. Jede Stufe des Vorteils erhöht den Spawn von Münzen um 50 %.",
"upgrades.superhot.name": "SUPER HEISS",
"upgrades.superhot.tooltip": "Die Zeit bewegt sich, wenn sich das Paddel bewegt.",
"upgrades.superhot.verbose_description": "SUPERHEISS SUPERHEISS SUPERHEISS SUPERHEISS",
"upgrades.telekinesis.help_plural": "Stärkere Wirkung auf den Ball",
"upgrades.telekinesis.name": "Telekinese",
"upgrades.telekinesis.tooltip": "Der Puck kontrolliert die Flugbahn des Balls",
"upgrades.telekinesis.tooltip": "Das Paddel steuert die Flugbahn des Balls",
"upgrades.telekinesis.verbose_description": "Du kontrollierst den Ball, während er nach oben fliegt.",
"upgrades.top_is_lava.name": "Der Himmel ist die Grenze",
"upgrades.top_is_lava.tooltip": "+{{lvl}} Combo pro Stein, wird zurückgesetzt, wenn die Spitze getroffen wird",
"upgrades.top_is_lava.verbose_description": "Jedes Mal, wenn du einen Stein zerbrichst, erhöht sich deine Kombo um eins. Allerdings wird deine Kombo zurückgesetzt, sobald dein Ball den oberen Rand des Bildschirms trifft.\n\nWenn deine Kombo über dem Minimum liegt, erscheint oben ein roter Balken, der dich daran erinnert, dass du es vermeiden solltest, ihn zu treffen.",
"upgrades.trampoline.name": "Trampolin",
"upgrades.trampoline.tooltip": "+{{lvl}} Combo pro Abprall des Pucks,-{{lvl}} Combo pro Abprall an einer beliebigen Grenze",
"upgrades.trampoline.tooltip": "+{{lvl}} Combo pro Paddelabprall, -{{lvl}} Combo pro Abprall an einer beliebigen Grenze",
"upgrades.trampoline.verbose_description": "Eine der seltenen Combo-Upgrades, die keine Rücksetzbedingung hinzufügen",
"upgrades.trickledown.name": "Abfluss",
"upgrades.trickledown.tooltip": "Die Münzen erscheinen oben auf dem Bildschirm.",
"upgrades.trickledown.verbose_description": "Es könnte Ihnen helfen, ein paar Münzen beiseite zu legen. ",
"upgrades.unbounded.help_no_ceiling": "+{{lvl}} Combo pro Stein, keine weiteren Seiten oder Decken",
"upgrades.unbounded.name": "Unbegrenzt",
"upgrades.unbounded.tooltip": "+{{lvl}} Combo pro Stein, keine Seiten mehr, um den Ball im Spiel zu halten, Gefahr",
"upgrades.unbounded.verbose_description": "Ich hoffe, du hast einen Weg gefunden, deinen Ball auf dem Bildschirm zu halten. Es gibt keine Strafen mehr, wenn du die Seiten triffst. Auf Stufe 2+ verschwindet auch die Decke. Dies ist ein fortgeschrittener Vorteil.",
"upgrades.transparency.name": "Transparenz",
"upgrades.transparency.tooltip": "Je höher der Ball auf dem Bildschirm ist, desto transparenter wird er. Je transparenter sie ist, desto mehr Münzen produziert sie.",
"upgrades.transparency.verbose_description": "Je höher die Stufe, desto eher wird der Ball transparent und desto höher ist der Punktebonus.",
"upgrades.trickledown.name": "Trickle down economics",
"upgrades.trickledown.tooltip": "Die Münzen erscheinen am oberen Rand des Bildschirms.",
"upgrades.trickledown.verbose_description": "Es könnte Ihnen helfen, ein paar Münzen zur Seite zu legen.",
"upgrades.unbounded.name": "Polsterung",
"upgrades.unbounded.tooltip": "Fügt links und rechts des Levels Platz hinzu, aber dein Paddel kann nicht so weit gehen.",
"upgrades.unbounded.verbose_description": "Ein weiteres Upgrade könnte Ihnen helfen, die Reichweite Ihres Paddels zu vergrößern.",
"upgrades.viscosity.name": "Viskosität",
"upgrades.viscosity.tooltip": "Langsamerer Münzfall",
"upgrades.viscosity.verbose_description": "Münzen beschleunigen normalerweise durch Schwerkraft und Explosionen auf ziemlich hohe Geschwindigkeiten.\n\nMit dieser Fähigkeit werden sie ständig langsamer, als ob sie sich in einer Art zähflüssiger Flüssigkeit befänden.\n\nDas macht es einfacher, sie zu fangen, und lässt sich gut mit Perks kombinieren, die die Bewegung der Münze beeinflussen.",
"upgrades.wind.help_plural": "Stärkere Windkraft",
"upgrades.wind.name": "WIND",
"upgrades.wind.tooltip": "Puckstellung erzeugt Wind",
"upgrades.wind.verbose_description": "Der Wind hängt von der Position des Pucks ab: links weht links, rechts weht rechts. Wirkt sich sowohl auf Bälle als auch auf Münzen aus.",
"upgrades.wind.tooltip": "Paddelstellung erzeugt Wind",
"upgrades.wind.verbose_description": "Der Wind hängt von der Paddelposition ab: links bläst links, rechts bläst rechts. Wirkt sich sowohl auf Bälle als auch auf Münzen aus.",
"upgrades.yoyo.name": "Yo-Yo",
"upgrades.yoyo.tooltip": "Ball fällt in Richtung Puck",
"upgrades.yoyo.tooltip": "Ball fällt in Richtung Paddel",
"upgrades.yoyo.verbose_description": "Es ist das Gegenteil von Telekinese, den Ball zu kontrollieren, während er wieder nach unten fällt.",
"upgrades.zen.name": "Zen",
"upgrades.zen.tooltip": "+{{lvl}} Combo pro Ziegel, wird bei einer Explosion zurückgesetzt",

View file

@ -3,6 +3,29 @@
"confirmRestart.text": "You're about to start a new game. Are you sure you want to continue?",
"confirmRestart.title": "Start a new game?",
"confirmRestart.yes": "Restart game",
"editor.editing.bigger": "Increase level size",
"editor.editing.color": "Pick a color in the color list (max 5 per level)",
"editor.editing.copy": "Copy level code",
"editor.editing.copy_help": "Paste it in the #levels channel in our discord",
"editor.editing.credit": "Credits and source",
"editor.editing.credit_prompt": "Enter the source url or explanation of your level.",
"editor.editing.delete": "Delete level",
"editor.editing.down": "Move down all the bricks",
"editor.editing.help": "Then click a tile to color it.",
"editor.editing.left": "Move all bricks to the left",
"editor.editing.play": "Play this level",
"editor.editing.rename": "Level name",
"editor.editing.rename_prompt": "Please enter a new name for the level",
"editor.editing.right": "Move all bricks to the right",
"editor.editing.smaller": "Decrease level size",
"editor.editing.title": "Editing level : {{name}}",
"editor.editing.up": "Move up all the bricks",
"editor.help": "Create custom levels and share them for inclusion in the game.",
"editor.import": "Import a level",
"editor.import_instruction": "Paste a level code to import it in your level list",
"editor.locked": "Reach a total score of {{min}} to unlock",
"editor.new_level": "New level",
"editor.title": "Level Editor",
"gameOver.creative": "This run will not be recorded. ",
"gameOver.cumulative_total": "Your total cumulative score went from {{startTs}} to {{endTs}}.",
"gameOver.lost.summary": "You dropped the ball after catching {{score}} coins.",
@ -139,6 +162,8 @@
"score_panel.title": "{{score}} points at level {{level}}/{{max}} ",
"score_panel.upcoming_levels": "Upcoming levels :",
"score_panel.upgrades_picked": "Upgrades picked in this game run : ",
"settings.autoplay": "Auto play",
"settings.autoplay_help": "Start a session with random upgrades and a computer controlled paddle",
"settings.basic": "Basic graphics",
"settings.basic_help": "Better performance.",
"settings.colorful_coins": "Colorful coins",
@ -163,12 +188,14 @@
"settings.load_save_file_help": "Select a save file on your device",
"settings.max_coins": " {{max}} coins on screen maximum",
"settings.max_coins_help": "Cosmetic only, no effect on score",
"settings.max_particles": " {{max}} particles maximum",
"settings.max_particles_help": "Limits the number of particles show on screen for visual effect. ",
"settings.mobile": "Mobile mode",
"settings.mobile_help": "Leaves space under the paddle.",
"settings.pointer_lock": "Mouse pointer lock",
"settings.pointer_lock_help": "Locks and hides the mouse cursor.",
"settings.precise_lighting": "Precise lighting",
"settings.precise_lighting_help": "Use a smaller grid for background light effect",
"settings.probabilistic_lighting": "Persistence of vision",
"settings.probabilistic_lighting_help": "Improve performance when there are more than 150 coins by reusing some of the light of the previous frame",
"settings.record": "Record gameplay videos",
"settings.record_download": "Download video ({{size}} MB)",
"settings.record_help": "Get a video of each level.",
@ -187,8 +214,12 @@
"settings.show_fps_help": "Monitor the app's performance",
"settings.show_stats": "Show real time stats",
"settings.show_stats_help": "Coins, time, bounces, misses",
"settings.smooth_lighting": "Smooth lighting",
"settings.smooth_lighting_help": "Blur the background light effects to make them look less square. Increases lag.",
"settings.sounds": "Game sounds",
"settings.sounds_help": "Can slow down some phones.",
"settings.sounds_help": "Beeps, bloops and brrrr",
"settings.stress_test": "Stress test",
"settings.stress_test_help": "Start a bot controlled game with a very high number of coins, to test the performance limits of your device.",
"starting_perks.checked": "When you start a new game, one of those perks will be given to you. Click a perk to exclude it. ",
"starting_perks.help": "Choose possible starting upgrades",
"starting_perks.random": "All benefits have been removed, the choice will be random.",
@ -390,7 +421,7 @@
"upgrades.trampoline.tooltip": "+{{lvl}} combo per paddle bounce,-{{lvl}} combo per bounce on any border",
"upgrades.trampoline.verbose_description": "One of the rare combo upgrades that don't add a reset condition",
"upgrades.transparency.name": "Transparency",
"upgrades.transparency.tooltip": "The higher the ball is on the screen, the more transparent it becomes. The more transparent it is, the more coins it produces.",
"upgrades.transparency.tooltip": "The higher the ball is on the screen, the more transparent it becomes, and the more coins it produces (+{{percent}} % at full transparency).",
"upgrades.transparency.verbose_description": "Higher levels make the ball transparent sooner and increase the point bonus.",
"upgrades.trickledown.name": "Trickle down economics",
"upgrades.trickledown.tooltip": "The coins appear at the top of the screen.",

View file

@ -3,6 +3,29 @@
"confirmRestart.text": "Estás a punto de empezar un nuevo partido: ¿es esto realmente lo que querías?",
"confirmRestart.title": "¿Empezar una nueva partida?",
"confirmRestart.yes": "Empezar una nueva partida",
"editor.editing.bigger": "Aumentar el tamaño del nivel",
"editor.editing.color": "Elige un color de la lista de colores (máximo 5 por nivel)",
"editor.editing.copy": "Copiar código de nivel",
"editor.editing.copy_help": "Pégalo en el canal #levels en nuestro discord",
"editor.editing.credit": "Créditos y fuente",
"editor.editing.credit_prompt": "Introduce la URL de origen o la explicación de tu nivel.",
"editor.editing.delete": "Eliminar nivel",
"editor.editing.down": "Baja todos los ladrillos",
"editor.editing.help": "Luego haz clic en un mosaico para colorearlo.",
"editor.editing.left": "Mueve todos los ladrillos hacia la izquierda",
"editor.editing.play": "Juega este nivel",
"editor.editing.rename": "Nombre del nivel",
"editor.editing.rename_prompt": "Por favor, introduzca un nuevo nombre para el nivel",
"editor.editing.right": "Mueve todos los ladrillos hacia la derecha",
"editor.editing.smaller": "Disminuir el tamaño del nivel",
"editor.editing.title": "Nivel de edición: {{name}}",
"editor.editing.up": "Mueve todos los ladrillos hacia arriba",
"editor.help": "Crea niveles personalizados y compártelos para incluirlos en el juego.",
"editor.import": "Importar un nivel",
"editor.import_instruction": "Pegue un código de nivel para importarlo en su lista de niveles",
"editor.locked": "Alcanza una puntuación total de {{min}} para desbloquear",
"editor.new_level": "Nuevo nivel",
"editor.title": "Editor de niveles",
"gameOver.creative": "Esta parte de la prueba no se grabará.",
"gameOver.cumulative_total": "Su puntuación total acumulada ha pasado de {{startTs}} a {{endTs}}.",
"gameOver.lost.summary": "Se te ha caído la bola después de coger {{score}} monedas.",
@ -139,6 +162,8 @@
"score_panel.title": "{{score}} puntos en {{level}}/{{max}} nivel",
"score_panel.upcoming_levels": "Niveles de partido :",
"score_panel.upgrades_picked": "Mejoras elegidas durante el juego :",
"settings.autoplay": "",
"settings.autoplay_help": "",
"settings.basic": "Gráficos básicos",
"settings.basic_help": "Mejor rendimiento.",
"settings.colorful_coins": "Monedas de colores",
@ -163,12 +188,14 @@
"settings.load_save_file_help": "Seleccione un archivo guardado en su dispositivo",
"settings.max_coins": " {{max}} monedas en pantalla máximo",
"settings.max_coins_help": "Solo cosmético, sin efecto en la puntuación.",
"settings.max_particles": " {{max}} partículas máximo",
"settings.max_particles_help": "Limita la cantidad de partículas que se muestran en la pantalla para lograr un efecto visual.",
"settings.mobile": "Modo móvil",
"settings.mobile_help": "Deja espacio debajo de la paleta.",
"settings.pointer_lock": "Bloqueo del puntero del ratón",
"settings.pointer_lock_help": "Bloquea y oculta el cursor del mouse.",
"settings.precise_lighting": "",
"settings.precise_lighting_help": "",
"settings.probabilistic_lighting": "",
"settings.probabilistic_lighting_help": "",
"settings.record": "Grabar vídeos de juego",
"settings.record_download": "Descargar vídeo ({{size}} MB)",
"settings.record_help": "Obtenga un vídeo de cada nivel.",
@ -187,8 +214,12 @@
"settings.show_fps_help": "Supervisar el rendimiento de la aplicación",
"settings.show_stats": "Mostrar estadísticas en tiempo real",
"settings.show_stats_help": "Monedas, tiempo, rebotes, fallos.",
"settings.smooth_lighting": "",
"settings.smooth_lighting_help": "",
"settings.sounds": "Sonidos del juego",
"settings.sounds_help": "Puede ralentizar algunos teléfonos.",
"settings.sounds_help": "Pitidos, bloops y brrrr",
"settings.stress_test": "",
"settings.stress_test_help": "",
"starting_perks.checked": "Al empezar una partida nueva, recibirás una de esas ventajas. Haz clic en una ventaja para excluirla.",
"starting_perks.help": "Elija posibles actualizaciones iniciales",
"starting_perks.random": "Se han eliminado todos los beneficios, la elección será aleatoria.",
@ -395,7 +426,7 @@
"upgrades.trickledown.name": "Escorrentía",
"upgrades.trickledown.tooltip": "Las monedas aparecen en la parte superior de la pantalla.",
"upgrades.trickledown.verbose_description": "Podría ayudarte a ahorrar algunas monedas. ",
"upgrades.unbounded.name": "Liberado, entregado",
"upgrades.unbounded.name": "Espacio libre",
"upgrades.unbounded.tooltip": "+{{lvl}} combo por ladrillo, más lados para mantener la pelota en juego, peligro",
"upgrades.unbounded.verbose_description": "Espero que hayas encontrado la forma de mantener tu bola en la pantalla. En el nivel 2+, el techo también desaparecerá. Se trata de una ventaja avanzada.",
"upgrades.viscosity.name": "Fluido viscoso",

View file

@ -3,6 +3,29 @@
"confirmRestart.text": "Vous êtes sur le point de commencer une nouvelle partie, est-ce vraiment ce que vous vouliez ?",
"confirmRestart.title": "Démarrer une nouvelle partie?",
"confirmRestart.yes": "Commencer une nouvelle partie",
"editor.editing.bigger": "Augmenter la taille du niveau",
"editor.editing.color": "Choisissez une couleur dans la liste des couleurs (max 5 par niveau)",
"editor.editing.copy": "Copier le code du niveau",
"editor.editing.copy_help": "Collez-le dans le canal #levels de notre discord",
"editor.editing.credit": "Crédits et source",
"editor.editing.credit_prompt": "Entrez l'url source ou l'explication de votre niveau.",
"editor.editing.delete": "Supprimer le niveau",
"editor.editing.down": "Déplacez toutes les briques vers le bas",
"editor.editing.help": "Cliquez ensuite sur une tuile pour la colorier.",
"editor.editing.left": "Déplacer toutes les briques vers la gauche",
"editor.editing.play": "Jouez à ce niveau",
"editor.editing.rename": "Nom du niveau",
"editor.editing.rename_prompt": "Veuillez saisir un nouveau nom pour le niveau",
"editor.editing.right": "Déplacer toutes les briques vers la droite",
"editor.editing.smaller": "Diminuer la taille du niveau",
"editor.editing.title": "Niveau d'édition : {{name}}",
"editor.editing.up": "Déplacez toutes les briques",
"editor.help": "Créez des niveaux personnalisés et partagez-les pour les inclure dans le jeu.",
"editor.import": "Importer un niveau",
"editor.import_instruction": "Collez un code de niveau pour l'importer dans votre liste de niveaux",
"editor.locked": "Atteignez un score total de {{min}} pour débloquer",
"editor.new_level": "Nouveau niveau",
"editor.title": "Éditeur de niveau",
"gameOver.creative": "Cette partie de test ne sera pas enregistrée.",
"gameOver.cumulative_total": "Votre score total cumulé est passé de {{startTs}} à {{endTs}}.",
"gameOver.lost.summary": "Vous avez fait tomber la balle après avoir attrapé {{score}} pièces.",
@ -139,6 +162,8 @@
"score_panel.title": "{{score}} points au niveau {{level}}/{{max}} ",
"score_panel.upcoming_levels": "Niveaux de la parties : ",
"score_panel.upgrades_picked": "Améliorations choisies pendant la partie :",
"settings.autoplay": "",
"settings.autoplay_help": "",
"settings.basic": "Graphismes simplifiés",
"settings.basic_help": "Meilleures performances.",
"settings.colorful_coins": "Pièces colorées",
@ -163,12 +188,14 @@
"settings.load_save_file_help": "Depuis un fichier ",
"settings.max_coins": "{{max}} pièces affichées maximum",
"settings.max_coins_help": "Visuel uniquement, pas d'impact sur le score",
"settings.max_particles": " {{max}} particules maximum",
"settings.max_particles_help": "Limite le nombre de particules affichées à l'écran pour les effets visuels",
"settings.mobile": "Mode mobile",
"settings.mobile_help": "Laisse un espace sous la raquette.",
"settings.pointer_lock": "Verrouillage du pointeur",
"settings.pointer_lock_help": "Cache aussi le curseur de la souris.",
"settings.precise_lighting": "",
"settings.precise_lighting_help": "",
"settings.probabilistic_lighting": "",
"settings.probabilistic_lighting_help": "",
"settings.record": "Enregistrer des vidéos de jeu",
"settings.record_download": "Télécharger la vidéo ({{size}} MB)",
"settings.record_help": "Obtenez une vidéo de chaque niveau.",
@ -187,8 +214,12 @@
"settings.show_fps_help": "Surveiller la performance du jeu",
"settings.show_stats": "Statistiques en temps réel",
"settings.show_stats_help": "Pièces, temps, rebonds, ratés",
"settings.smooth_lighting": "",
"settings.smooth_lighting_help": "",
"settings.sounds": "Sons du jeu",
"settings.sounds_help": "Ralentis certains téléphones.",
"settings.sounds_help": "Bips, bloops et brrrr",
"settings.stress_test": "",
"settings.stress_test_help": "",
"starting_perks.checked": "Lorsque vous démarrez une nouvelle partie, l'un de ces avantages vous sera attribué. Cliquez sur un avantage pour l'exclure.",
"starting_perks.help": "Choisissez les avantages de départ",
"starting_perks.random": "Tous les avantages ont été retirés, le choix sera aléatoire.",
@ -390,7 +421,7 @@
"upgrades.trampoline.tooltip": "+{{lvl}} combo à chaque rebond d'une balle sur la raquette,-{{lvl}} combo à chaque rebond sur un des bords",
"upgrades.trampoline.verbose_description": "Une des rares améliorations à ne pas avoir de condition de remise à zéro",
"upgrades.transparency.name": "Camouflage",
"upgrades.transparency.tooltip": "Plus la balle est haut à l'écran, plus elle devient transparente. Plus elle est transparente, plus elle produit de pièces.",
"upgrades.transparency.tooltip": "Plus la balle est haut à l'écran, plus elle devient transparente et plus elle produit de pièces (+{{percent}} % à transparence maximum).",
"upgrades.transparency.verbose_description": "Les niveaux plus élevés rendent la balle transparente plus tôt et augmentent le bonus de points.",
"upgrades.trickledown.name": "Ruissellement",
"upgrades.trickledown.tooltip": "Les pièces apparaissent en haut de l'écran.",

View file

@ -3,7 +3,8 @@ import fr from "./fr.json";
import ar from "./ar.json";
import ru from "./ru.json";
import es from "./es.json";
// import de from "./de.json";
import tr from "./tr.json";
import de from "./de.json";
// import ko from "./ko.json";
// import ur from "./ur.json";
// import uz from "./uz.json";
@ -14,7 +15,6 @@ export const languages = [
text: "English",
value: "en",
strings: en,
levelName: "UK",
},
{
@ -46,13 +46,19 @@ export const languages = [
levelName: "Russia",
},
// {
// text: "Deutsch",
// value: "de",
// strings: de,
//
// levelName: "Germany",
// },
{
text: "Deutsch",
value: "de",
strings: de,
levelName: "Germany",
},
{
text: "Türkçe",
value: "tr",
strings: tr,
levelName: "Türkiye",
},
// {
// text: "汉语",
// value: "zh",

View file

@ -3,6 +3,29 @@
"confirmRestart.text": "Вы собираетесь начать новую игру. Вы уверены, что хотите продолжить?",
"confirmRestart.title": "Начать новую игру?",
"confirmRestart.yes": "Перезапустите игру",
"editor.editing.bigger": "Увеличить размер уровня",
"editor.editing.color": "Выберите цвет из списка цветов (максимум 5 на уровень)",
"editor.editing.copy": "Скопировать код уровня",
"editor.editing.copy_help": "Вставьте его в канал #levels в нашем Discord",
"editor.editing.credit": "Кредиты и источник",
"editor.editing.credit_prompt": "Введите исходный URL-адрес или пояснение вашего уровня.",
"editor.editing.delete": "Удалить уровень",
"editor.editing.down": "Сдвиньте все кирпичи вниз.",
"editor.editing.help": "Затем щелкните по плитке, чтобы раскрасить ее.",
"editor.editing.left": "Переместите все кирпичи влево.",
"editor.editing.play": "Пройти этот уровень",
"editor.editing.rename": "Название уровня",
"editor.editing.rename_prompt": "Введите новое название уровня.",
"editor.editing.right": "Переместите все кирпичи вправо.",
"editor.editing.smaller": "Уменьшить размер уровня",
"editor.editing.title": "Уровень редактирования: {{name}}",
"editor.editing.up": "Поднимите все кирпичи.",
"editor.help": "Создавайте собственные уровни и делитесь ими для включения в игру.",
"editor.import": "Импортировать уровень",
"editor.import_instruction": "Вставьте код уровня, чтобы импортировать его в список уровней.",
"editor.locked": "Наберите в общей сложности {{min}} очков, чтобы разблокировать",
"editor.new_level": "Новый уровень",
"editor.title": "Редактор уровней",
"gameOver.creative": "Этот забег не будет записываться.",
"gameOver.cumulative_total": "Ваш общий суммарный балл увеличился с {{startTs}} до {{endTs}}.",
"gameOver.lost.summary": "Вы уронили мяч, поймав {{score}} монет.",
@ -139,6 +162,8 @@
"score_panel.title": "{{score}} очков на уровне {{level}}/{{max}} ",
"score_panel.upcoming_levels": "Предстоящие уровни :",
"score_panel.upgrades_picked": "Обновления, выбранные в этой игре, запускаются :",
"settings.autoplay": "",
"settings.autoplay_help": "",
"settings.basic": "Базовая графика",
"settings.basic_help": "Улучшенная производительность.",
"settings.colorful_coins": "Разноцветные монеты",
@ -163,12 +188,14 @@
"settings.load_save_file_help": "Выберите файл сохранения на вашем устройстве",
"settings.max_coins": " {{max}} монет на экране максимум",
"settings.max_coins_help": "Только косметика, не влияет на результат",
"settings.max_particles": " {{max}} частиц максимум",
"settings.max_particles_help": "Ограничивает количество частиц, отображаемых на экране для визуального эффекта.",
"settings.mobile": "Мобильный режим",
"settings.mobile_help": "Оставляет место под лопаткой.",
"settings.pointer_lock": "Блокировка указателя мыши",
"settings.pointer_lock_help": "Фиксирует и скрывает курсор мыши.",
"settings.precise_lighting": "",
"settings.precise_lighting_help": "",
"settings.probabilistic_lighting": "",
"settings.probabilistic_lighting_help": "",
"settings.record": "Запись видеороликов игрового процесса",
"settings.record_download": "Скачать видео ({{size}} МБ)",
"settings.record_help": "Получите видеозапись каждого уровня.",
@ -187,8 +214,12 @@
"settings.show_fps_help": "Контролируйте работу приложения",
"settings.show_stats": "Показывайте статистику в реальном времени",
"settings.show_stats_help": "Монеты, время, отскоки, промахи",
"settings.smooth_lighting": "",
"settings.smooth_lighting_help": "",
"settings.sounds": "Звуки игры",
"settings.sounds_help": "Может замедлять работу некоторых телефонов.",
"settings.sounds_help": "Бипы, блепы и брррр",
"settings.stress_test": "",
"settings.stress_test_help": "",
"starting_perks.checked": "Когда вы начнете новую игру, вам будет дано одно из этих преимуществ. Щелкните по перку, чтобы исключить его.",
"starting_perks.help": "Выберите возможные стартовые апгрейды",
"starting_perks.random": "Все преимущества были убраны, выбор будет случайным.",

445
src/i18n/tr.json Normal file
View file

@ -0,0 +1,445 @@
{
"confirmRestart.no": "İptal etmek",
"confirmRestart.text": "Yeni bir oyuna başlamak üzeresiniz. Devam etmek istediğinizden emin misiniz?",
"confirmRestart.title": "Yeni bir oyuna mı başlasam?",
"confirmRestart.yes": "Oyunu yeniden başlat",
"editor.editing.bigger": "Seviye boyutunu artır",
"editor.editing.color": "Renk listesinden bir renk seçin (seviye başına en fazla 5)",
"editor.editing.copy": "Kopyalama seviyesi kodu",
"editor.editing.copy_help": "Bunu Discord'umuzdaki #levels kanalına yapıştırın",
"editor.editing.credit": "Krediler ve kaynak",
"editor.editing.credit_prompt": "Seviyenizin kaynak URL'sini veya açıklamasını girin.",
"editor.editing.delete": "Seviyeyi Sil",
"editor.editing.down": "Tüm tuğlaları aşağı doğru hareket ettirin",
"editor.editing.help": "Daha sonra renklendirmek istediğiniz kutucuğa tıklayın.",
"editor.editing.left": "Tüm tuğlaları sola taşı",
"editor.editing.play": "Bu seviyeyi oyna",
"editor.editing.rename": "Seviye Adı",
"editor.editing.rename_prompt": "Lütfen seviye için yeni bir ad girin",
"editor.editing.right": "Tüm tuğlaları sağa taşı",
"editor.editing.smaller": "Seviye boyutunu azalt",
"editor.editing.title": "Düzenleme düzeyi : {{name}}",
"editor.editing.up": "Tüm tuğlaları yukarı taşı",
"editor.help": "Özel seviyeler yaratın ve bunları oyuna dahil etmek için paylaşın.",
"editor.import": "Bir seviyeyi içe aktar",
"editor.import_instruction": "Seviye listenize aktarmak için bir seviye kodunu yapıştırın",
"editor.locked": "Kilidi açmak için toplam {{min}} puanına ulaşın",
"editor.new_level": "Yeni seviye",
"editor.title": "Seviye Editörü",
"gameOver.creative": "Bu koşu kaydedilmeyecek.",
"gameOver.cumulative_total": "Toplam kümülatif puanınız {{startTs}} 'dan {{endTs}}'e çıktı.",
"gameOver.lost.summary": " {{score}} jeton yakaladıktan sonra topu düşürdün.",
"gameOver.lost.title": "Oyun bitti",
"gameOver.stats.balls_lost": "Kaybedilen toplar",
"gameOver.stats.bricks_broken": "Tuğlalar kırıldı",
"gameOver.stats.bricks_per_minute": "Dakikada kırılan tuğla sayısı",
"gameOver.stats.catch_rate": "Yakalama oranı",
"gameOver.stats.combo_avg": "Ortalama kombo",
"gameOver.stats.combo_max": "Maksimum kombo",
"gameOver.stats.duration_per_level": "Seviye başına süre",
"gameOver.stats.hit_rate": "İsabet oranı",
"gameOver.stats.intro": "",
"gameOver.stats.level_reached": "Seviyeye ulaşıldı",
"gameOver.stats.total_score": "Toplam Puan",
"gameOver.stats.upgrades_applied": "Uygulanan yükseltmeler",
"gameOver.stats_intro": "Aşağıda {{count}} en iyi oyunlarınızla karşılaştırıldığında oyun istatistiklerinizi bulabilirsiniz.",
"gameOver.unlocked_perk": "Yükseltme kilidi açıldı",
"gameOver.unlocked_perk_plural": "Az önce {{count}} avantajın kilidini açtınız",
"gameOver.win.summary": "Bu oyun bitti. {{score}} jeton sakladın.",
"gameOver.win.title": "Bu oyunu tamamladın",
"help.content": "## Hedef\n\n7 seviye boyunca mümkün olduğunca çok jeton topla. \nTuğlaları kırdığında jetonlar belirir.\nPuanını artırmak için küreğinle topla.\nPuanın ekranın sağ üst köşesinde gösterilir.\nTopu düşürme yoksa oyun biter.\n\nTüm tuğlaları yok ettikten sonra bir yükseltme seçebilirsin.\n\n## Yükseltmeler \n\nSeçtiğin yükseltmeler koşunun sonuna kadar geçerli olur. \nBazıları daha güçlü etki için birden fazla kez seçilebilir.\nBazıları nişan almaya yardımcı olur veya oyunu başka şekillerde kolaylaştırır. \nBazıları yalnızca birleştirildiğinde işe yarar.\n\nHer oyunun başında her zaman bir yükseltme alırsın. \nSimgesi ilk seviyenin tuğlaları olarak hizmet eder. \nAyarlardan başlangıç yükseltmelerini seçebilirsin.\n\nBirçok yükseltme kombonu etkiler. \n\n## Kombo\n\n\"Kombon\", bir tuğla kırıldığında ortaya çıkan jeton sayısıdır. \nKüreğinizde görüntülenir, örneğin x4 her tuğlanın 4 jeton üreteceği anlamına gelir. \nKomboyu artıran çoğu yükseltme, onu sıfırlamak için bir koşul da ekler. \nTop herhangi bir tuğlaya çarpmadan küreğe geri dönerse kombo da sıfırlanır. \nBu olduğunda bir \"ıskalama\" mesajı gösterilir. \n\nHer seferinde bir tuğlaya doğru nişan almaya çalışın. \n\n## Nişan Alma\n\nSadece küreğin üzerindeki top konumu nasıl sekeceğine karar verir. \nTop küreğin tam ortasına çarparsa, dikey olarak geri sekecektir. \nBir tarafa daha fazla vurursanız, daha fazla açıya sahip olacaktır. \nKürek hızı ve gelen açı, zıpladıktan sonra topun yönü üzerinde hiçbir etkiye sahip değildir. \n\nNişan almaya yardımcı olan birçok yükseltme açılabilir. \n\n## Kilit Açmalar\n\nBreakout 71'i ilk kez oynarken, çoğu yükseltme ve seviye kilitlenir. \nYükseltmeler, sadece oynayarak ve birçok jeton yakalayarak açılır. \nİlk seviyeler yüksek bir puana ulaşarak açılır.\nDaha sonraki seviyeler hangi avantajları seçebileceğinize dair bir koşul ekler.\n\nHer seviyeden sonra birden fazla yükseltme aldığınızda yüksek puanlara ulaşmak çok daha kolaydır.\n\n## Tekrar atışlar ve ücretsiz yükseltmeler\n\nİyi oynadığınızda seçebileceğiniz ekstra bir yükseltme elde edersiniz: \n\n- Seviyeyi {{levelTimeGood}} saniyenin altında tamamlayın\n- Kenarlara veya tepeye {{wallBouncedGood}} kereden az vurun\n- Paraların {{catchRateGood}}%'sini yakalayın\n- Tuğlaları {{missesGood}} kereden az ıskalayın\n\nDaha da iyi yaparsanız yükseltmeleri atlamanıza izin veren bir tekrar atış da elde edersiniz: \n\n- Seviyeyi {{levelTimeBest}} saniyenin altında tamamlayın\n- Kenarlara veya tepeye {{wallBouncedBest}} kereden az vurun\n- Paraların {{catchRateBest}}%'sini yakalayın\n- Tuğlaları {{missesBest}} kereden az ıskalayın\n\nAyarlardaki bir seçenek bu istatistikleri görüntülemenizi sağlar",
"help.help": "Oyun hakkında daha fazla bilgi edinin",
"help.levels": "Seviyeler",
"help.title": "Yardım",
"help.upgrades": "## Yükseltmeler",
"history.columns.score": "Gol",
"history.columns.started": "Tarih",
"history.help": "En iyi {{count}} oyununuzu görün.",
"history.locked": "Kilidi açmak için en az on oyun oynayın",
"history.title": "Koşu geçmişi",
"lab.help": "İstediğiniz herhangi bir yapıyı deneyin",
"lab.instructions": "Aşağıdan yükseltmeleri seçin, ardından oynayacağınız seviyeyi seçin.",
"lab.menu_entry": "Yaratıcı mod",
"lab.reset": "Hepsini 0'a sıfırla",
"lab.select_level": "Oynamak için bir seviye seçin",
"lab.unlocks_at": "Toplam puan {{score}}olduğunda açılır",
"level_up.after_buttons": " {{level}}/{{max}}seviyesini yeni bitirdiniz.",
"level_up.before_buttons": " {{time}} saniyede {{levelSpawnedCoins}} üzerinden {{score}} jeton {{catchGain}} yakaladın {{timeGain}}.\n {{levelMisses}} kez {{missesGain}} ıskaladın ve {{levelWallBounces}} kez duvarlara veya tavana çarptın{{wallHitsGain}}.\n{{compliment}}",
"level_up.compliment_advice": "Tüm paraları toplamaya çalışın, tuğlaları asla kaçırmayın, duvarlara/tavana çarpmayın veya ek yükseltmeler kazanmak için 30 saniyenin altındaki seviyeyi temizlemeyin.",
"level_up.compliment_good": "Tebrikler !",
"level_up.compliment_perfect": "Çok etkileyici, böyle devam edin!",
"level_up.pick_upgrade_title": "Bir yükseltme seçin",
"level_up.plus_one_upgrade": "(+1 yükseltme)",
"level_up.plus_one_upgrade_and_reroll": "(+1 yükseltme ve +1 yeniden atma)",
"level_up.reroll": "Tekrar at ({{count}})",
"level_up.reroll_help": "Yeni seçenekler sunun",
"level_up.upgrade_perk_to_level": "Seviye {{level}}",
"main_menu.basic": "",
"main_menu.basic_help": "",
"main_menu.colorful_coins": "",
"main_menu.colorful_coins_help": "",
"main_menu.comboIncreaseTexts": "",
"main_menu.comboIncreaseTexts_help": "",
"main_menu.contrast": "",
"main_menu.contrast_help": "",
"main_menu.credit_levels": "",
"main_menu.donate": " {{hours}} saat oynadınız",
"main_menu.donate_help": "Bağış yapmaya ne dersiniz? Ayarlardan bu hatırlatıcıyı gizleyebilirsiniz.",
"main_menu.donation_reminder": "",
"main_menu.donation_reminder_help": "",
"main_menu.download_save_file": "",
"main_menu.download_save_file_help": "",
"main_menu.extra_bright": "",
"main_menu.extra_bright_help": "",
"main_menu.fullscreen": "",
"main_menu.fullscreen_help": "",
"main_menu.help_content": "",
"main_menu.help_help": "",
"main_menu.help_title": "",
"main_menu.help_upgrades": "",
"main_menu.high_score": "En yüksek puan : {{score}}",
"main_menu.kid": "",
"main_menu.kid_help": "",
"main_menu.language": "",
"main_menu.language_help": "",
"main_menu.load_save_file": "",
"main_menu.load_save_file_help": "",
"main_menu.max_coins": "",
"main_menu.max_coins_help": "",
"main_menu.max_particles": "",
"main_menu.max_particles_help": "",
"main_menu.mobile": "",
"main_menu.mobile_help": "",
"main_menu.normal": "Yeni Oyun",
"main_menu.normal_help": "Rastgele bir başlangıç avantajıyla 7 seviye oynayın",
"main_menu.pointer_lock": "",
"main_menu.pointer_lock_help": "",
"main_menu.record": "",
"main_menu.record_download": "",
"main_menu.record_help": "",
"main_menu.red_miss": "",
"main_menu.red_miss_help": "",
"main_menu.reset": "",
"main_menu.reset_cancel": "",
"main_menu.reset_confirm": "",
"main_menu.reset_help": "",
"main_menu.reset_instruction": "",
"main_menu.save_file_error": "",
"main_menu.save_file_loaded": "",
"main_menu.save_file_loaded_help": "",
"main_menu.save_file_loaded_ok": "",
"main_menu.settings_help": "Oyun tarzını ihtiyaçlarınıza ve zevkinize göre uyarlayın",
"main_menu.settings_title": "Ayarlar",
"main_menu.show_fps": "",
"main_menu.show_fps_help": "",
"main_menu.show_stats": "",
"main_menu.show_stats_help": "",
"main_menu.sounds": "",
"main_menu.sounds_help": "",
"main_menu.starting_perks": "",
"main_menu.starting_perks_checked": "",
"main_menu.starting_perks_full_random": "",
"main_menu.starting_perks_help": "",
"main_menu.starting_perks_unchecked": "",
"main_menu.title": "Kopuş 71",
"main_menu.unlocks": "Kilidi açılmış içerik",
"main_menu.unlocks_help": "Kilidini açtığınız avantajları ve seviyeleri deneyin",
"play.close_modale_window_tooltip": "Kapalı",
"play.current_lvl": "Seviye {{level}}/{{max}}",
"play.menu_label": "Menü",
"play.menu_tooltip": "Ana menüyü aç",
"play.missed_ball": "Kayıp",
"play.mobile_press_to_play": "Oynamak için buraya basın ve basılı tutun",
"play.score_tooltip": "Puanınızı, yükseltmelerinizi ve daha fazlasını görün",
"play.stats.coins_catch_rate": "Madeni para yakalama oranı",
"play.stats.levelMisses": "Hiçbir şeye isabet ettiremediğiniz şutlar",
"play.stats.levelTime": "Seviye zamanı",
"play.stats.levelWallBounces": "Duvar sıçramaları",
"score_panel.close_to_unlock": "Sonraki seviyenin kilidini aç:",
"score_panel.get_upgrades_to_unlock": " {{missingUpgrades}} alın ve \"{{level}}\" seviyesinin kilidini açmak için {{points}} puan daha kazanın",
"score_panel.rerolls_count": " {{rerolls}} yeniden atma biriktirdiniz",
"score_panel.score_to_unlock": "\"{{level}}\" seviyesini açmak için {{points}} puan daha kazanın",
"score_panel.title": " {{level}}/{{max}} seviyesinde{{score}} puan",
"score_panel.upcoming_levels": "Yaklaşan seviyeler :",
"score_panel.upgrades_picked": "Bu oyun çalışmasında seçilen yükseltmeler:",
"settings.autoplay": "",
"settings.autoplay_help": "",
"settings.basic": "Temel grafikler",
"settings.basic_help": "Daha iyi performans.",
"settings.colorful_coins": "Renkli madeni paralar",
"settings.colorful_coins_help": "Madeni paralar her zaman tuğlanın renginde ortaya çıkar",
"settings.comboIncreaseTexts": "Altında +X göster",
"settings.comboIncreaseTexts_help": "Kombo arttığında",
"settings.contrast": "Yüksek Kontrast",
"settings.contrast_help": "Daha renkli ve koyu görüntü oluşturma",
"settings.donation_reminder": "Bana bağış yapmayı hatırlat",
"settings.donation_reminder_help": "Oynanan süreyi ve bağış bağlantısını ana menüde görün",
"settings.download_save_file": "Puan ve istatistikleri indir",
"settings.download_save_file_help": "Bir kayıt dosyası al",
"settings.extra_bright": "Ekstra parlak",
"settings.extra_bright_help": "Madeni paraların ve tuğlaların etrafındaki hale boyutunu büyütür.",
"settings.fullscreen": "Tam ekran",
"settings.fullscreen_help": "Oyun başlamadan önce tam ekran olmaya çalışacak",
"settings.kid": "Çocuk modu",
"settings.kid_help": "Gelecek oyunlara \"daha yavaş top\" ile başlayın.",
"settings.language": "Dil",
"settings.language_help": "Oyunun dilini seçin",
"settings.load_save_file": "Kayıt dosyasını yükle",
"settings.load_save_file_help": "Cihazınızda bir kayıt dosyası seçin",
"settings.max_coins": "Ekranda maksimum {{max}} jeton var",
"settings.max_coins_help": "Sadece kozmetik, puan üzerinde etkisi yok",
"settings.mobile": "Mobil mod",
"settings.mobile_help": "Kürek altında boşluk bırakır.",
"settings.pointer_lock": "Fare işaretçisi kilidi",
"settings.pointer_lock_help": "Fare imlecini kilitler ve gizler.",
"settings.precise_lighting": "",
"settings.precise_lighting_help": "",
"settings.probabilistic_lighting": "",
"settings.probabilistic_lighting_help": "",
"settings.record": "Oyun videolarını kaydedin",
"settings.record_download": "Videoyu indir ({{size}} MB)",
"settings.record_help": "Her seviyenin videosunu edinin.",
"settings.red_miss": "Uyarıyı kaçırdım",
"settings.red_miss_help": "Vuruş yapmadan aşağı inen topların etrafında kırmızı parçacıklar göster.",
"settings.reset": "Oyunu Sıfırla",
"settings.reset_cancel": "HAYIR",
"settings.reset_confirm": "Evet",
"settings.reset_help": "En yüksek skoru, oyun süresini ve istatistikleri sil",
"settings.reset_instruction": "Oyunda kaydettiğiniz tüm ilerlemeyi kaybedeceksiniz, emin misiniz?",
"settings.save_file_error": "Kayıt dosyası yüklenirken hata oluştu",
"settings.save_file_loaded": "Yüklenen dosya kaydedildi",
"settings.save_file_loaded_help": "Uygulama şimdi kaydınızı uygulamak için yeniden yüklenecek",
"settings.save_file_loaded_ok": "TAMAM",
"settings.show_fps": "FPS Sayacı",
"settings.show_fps_help": "Uygulamanın performansını izleyin",
"settings.show_stats": "Gerçek zamanlı istatistikleri göster",
"settings.show_stats_help": "Paralar, zaman, sekmeler, ıskalar",
"settings.smooth_lighting": "",
"settings.smooth_lighting_help": "",
"settings.sounds": "Oyun sesleri",
"settings.sounds_help": "Bipler, blooplar ve brrrr",
"settings.stress_test": "",
"settings.stress_test_help": "",
"starting_perks.checked": "Yeni bir oyuna başladığınızda, bu avantajlardan biri size verilecektir. Bir avantajı hariç tutmak için tıklayın.",
"starting_perks.help": "Olası başlangıç yükseltmelerini seçin",
"starting_perks.random": "Tüm avantajlar kaldırıldı, seçim rastgele olacak.",
"starting_perks.title": "Başlangıç avantajları",
"starting_perks.unchecked": "Aşağıdaki avantajlar başlangıç avantajı olarak sunulmamaktadır, ancak havuza eklemek için tıklayabilirsiniz.",
"unlocks.greyed_out_help": "Grileştirilmiş yükseltmeler toplam puanınızı artırarak açılabilir. Toplam puan, oyunda her puan aldığınızda artar.",
"unlocks.intro": "Toplam puanınız {{ts}}. Aşağıda oyunun sunduğu tüm yükseltmeler ve seviyeler bulunmaktadır. Aşağıdaki bir yükseltmeye veya seviyeye tıklayarak onunla bir test oyunu başlatın.",
"unlocks.just_unlocked": "Seviye kilidi açıldı",
"unlocks.just_unlocked_plural": "Az önce {{count}} seviyenin kilidini açtınız",
"unlocks.level": "<h2> {{out_of}}seviyeden {{unlocked}} tanesinin kilidini açtın</h2>\n<p>İşte tüm oyun seviyeleri, denemek için birine tıkla.</p> ",
"unlocks.level_description": " {{bricks}} tuğla, {{colors}} renk ve {{bombs}} bombadan oluşan {{size}}x{{size}} büyüklüğünde bir seviye.",
"unlocks.minScore": "Kilidi açmak için bir koşuda ${{minScore}} 'a ulaşın.",
"unlocks.minScoreWithPerks": " {{required}} ile ancak {{forbidden}} olmadan bir koşuda ${{minScore}} 'a ulaşın.",
"unlocks.minTotalScore": "Toplam ${{score}}biriktirin",
"unlocks.reached": "En iyi skorunuz {{reached}}idi.",
"unlocks.title_upgrades": " {{out_of}}yükseltmeden {{unlocked}} tanesinin kilidini açtınız",
"upgrades.addiction.name": "Bağımlılık",
"upgrades.addiction.tooltip": "+{{lvl}} kombo / tuğla, kombo bir tuğlayı kırdıktan sonra {{delay}}saniye içinde sıfırlanır.",
"upgrades.addiction.verbose_description": "Geri sayım yalnızca her seviyenin ilk tuğlası kırıldıktan sonra başlar. Tüm tuğlalar yok edildiğinde durur.",
"upgrades.asceticism.name": "Zühd",
"upgrades.asceticism.tooltip": "+{{combo}} kombo / tuğla, - {{combo}} jeton yakalamada",
"upgrades.asceticism.verbose_description": "Kombonuz tırmanırken paraları bir yere saklamanız gerekecek.",
"upgrades.ball_attract_ball.help_plural": "Daha güçlü çekim kuvveti",
"upgrades.ball_attract_ball.name": "Yer çekimi",
"upgrades.ball_attract_ball.tooltip": "Toplar toplarını çeker",
"upgrades.ball_attract_ball.verbose_description": "\"Oyun alanının genişliğinin 3/4'ünden\" daha uzakta olan toplar birbirini çekmeye başlayacaktır. \n\nÇekim kuvveti, birbirlerinden en uzakta olduklarında daha güçlüdür. \n\nGökkuşağı parçacıkları çekim kuvvetini sembolize etmek için uçacaktır. Bu avantaj yalnızca birden fazla topunuz varsa sunulur.",
"upgrades.ball_attracts_coins.name": "Toplar madeni paraları çeker",
"upgrades.ball_attracts_coins.tooltip": "Madeni paralar en yakın topu takip eder ve daha yavaş düşer",
"upgrades.ball_attracts_coins.verbose_description": "\"Leke\" ve \"hayalet paralar\" ile birleştirdiğinizde, bu, paralarla topların \"etrafını boyamak\" için kullanılabilir. Ayrıca, para mıknatısı yerine de kullanılabilir.",
"upgrades.ball_repulse_ball.help_plural": "Daha güçlü itme kuvveti",
"upgrades.ball_repulse_ball.name": "Kişisel alan",
"upgrades.ball_repulse_ball.tooltip": "Toplar topları iter",
"upgrades.ball_repulse_ball.verbose_description": "Çeyrek ekran genişliğinden daha az uzaklıktaki toplar birbirini itmeye başlayacaktır. Birbirlerine yakınlarsa itme kuvveti daha güçlüdür. Parçacıklar bu kuvvetin uygulandığını sembolize etmek için dışarı fırlayacaktır. Bu avantaj yalnızca birden fazla topunuz varsa sunulur.",
"upgrades.base_combo.name": "Güçlü temeller",
"upgrades.base_combo.tooltip": "Kombo 1 yerine {{coins}} 'dan başlar.",
"upgrades.base_combo.verbose_description": "Kombonuz normalde seviyenin başında 1'den başlar ve hiçbir şeye çarpmadan zıpladığınızda 1'e sıfırlanır. Bu avantajla, kombo 3 puan daha yüksekte başlar, bu yüzden her zaman tuğla başına en az 4 jeton alırsınız. Kombonuz her sıfırlandığında, 1 yerine 4'e geri döner. Topunuz, kombosunun birden yüksek olduğunu belirtmek için biraz parıldayacaktır.",
"upgrades.bigger_explosions.name": "Kaboom",
"upgrades.bigger_explosions.tooltip": "Daha büyük patlamalar",
"upgrades.bigger_explosions.verbose_description": "Varsayılan patlama 3x3 kareyi temizler, bununla 5x5 kare olur ve paralara gelen darbe de önemli ölçüde daha güçlüdür. Ekran her patlamadan sonra yanıp söner (temel mod hariç)",
"upgrades.bigger_puck.name": "Daha büyük kürek",
"upgrades.bigger_puck.tooltip": "Kolayca daha fazla para yakalayın.",
"upgrades.bigger_puck.verbose_description": "Daha büyük bir kürek, topu asla ıskalamamanızı ve daha fazla jeton yakalamanızı kolaylaştırır, ayrıca topun sekme açısını hassas bir şekilde ayarlamanızı sağlar (topun açısı yalnızca küreğe çarptığı yere bağlıdır).",
"upgrades.bricks_attract_ball.name": "Tuğlalar topları çeker",
"upgrades.bricks_attract_ball.tooltip": "Top çarpacağı ilk {{count}} tuğlaya doğru gider.",
"upgrades.bricks_attract_ball.verbose_description": "Etkisi daha yüksek seviyelerde daha güçlüdür. Etki durmadan önce vurabilecek tuğla sayısı da daha yüksektir. Etki, top diske çarptığında yeniden silahlanır.",
"upgrades.bricks_attract_coins.name": "Tuğlalar madeni paraları çeker",
"upgrades.bricks_attract_coins.tooltip": "Onların orada kalmalarına yardımcı olur",
"upgrades.bricks_attract_coins.verbose_description": "",
"upgrades.clairvoyant.name": "Durugörü sahibi",
"upgrades.clairvoyant.tooltip": "Yaklaşan seviyeleri, tuğla HP'sini ve top yönünü görün",
"upgrades.clairvoyant.verbose_description": "Doğru yükseltmeleri seçmenize ve sağlam tuğlalarla neler olup bittiğini anlamanıza yardımcı olur. Seviye 2 ve 3, şüpheli fayda hakkında ek bilgi getirir (döngü modunda erişilebilir)",
"upgrades.coin_magnet.help_plural": "Madeni paralar üzerinde daha güçlü etki",
"upgrades.coin_magnet.name": "Madeni para mıknatısı",
"upgrades.coin_magnet.tooltip": "Kürek paraları çekiyor",
"upgrades.coin_magnet.verbose_description": "Madeni paraları küreğe yönlendirir. Madeni para zaten küreğe yakınsa etkisi daha güçlüdür.",
"upgrades.compound_interest.name": "Bileşik faiz",
"upgrades.compound_interest.tooltip": "+{{lvl}} kombo her kırılan tuğla için, jeton kaybedildiğinde sıfırlanır",
"upgrades.compound_interest.verbose_description": "Her tuğlayı kırdığınızda kombonuz bir artacak ve kırdığınız her tuğlayla birlikte daha fazla jeton ortaya çıkacaktır. \nAncak, bu jetonların her birini küreğinizle yakaladığınızdan emin olun, çünkü kaybedilen jeton kombonuzu sıfırlayacaktır.\nKombonuz minimumun üzerine çıktığında, oyun alanının alt kısmında jetonların oraya gitmemesi gerektiğini hatırlatan kırmızı bir çizgi olacaktır.",
"upgrades.concave_puck.name": "İçbükey kürek",
"upgrades.concave_puck.tooltip": "Dikey nişan alma hassasiyetini artırır",
"upgrades.concave_puck.verbose_description": "Toplar düz bir şekilde yukarı doğru çıkarak başlar ve daha az açıyla zıplar.",
"upgrades.corner_shot.name": "Köşe vuruşu",
"upgrades.corner_shot.tooltip": "Küreğinizin ekranın sınırlarıyla örtüşmesini sağlar",
"upgrades.corner_shot.verbose_description": "Köşelerde nişan almaya yardımcı olur. Daha ileri seviyeler daha uzağa gitmenizi sağlar.",
"upgrades.etherealcoins.name": "Uzayda Madeni Paralar",
"upgrades.etherealcoins.tooltip": "Madeni paralar artık yer çekiminden etkilenmiyor",
"upgrades.etherealcoins.verbose_description": "Paralar birkaç kez zıpladıktan sonra bile hızlarını koruyacak ve artık yer çekiminden etkilenmeyecekler.",
"upgrades.extra_levels.name": "5 dakika daha",
"upgrades.extra_levels.tooltip": "7 yerine {{count}} seviye oyna",
"upgrades.extra_levels.verbose_description": "Varsayılan oyun en fazla 7 seviye sürebilir, ardından oyun biter. \n\nBu avantajın her seviyesi bir seviye daha yukarı çıkmanızı sağlar. Son seviyeler genellikle en fazla puanı aldığınız seviyelerdir, bu yüzden fark dramatik olabilir.",
"upgrades.extra_life.help_plural": "(Son) topunuz kaybolmak yerine ({{lvl}} kez) dipte sekecektir.",
"upgrades.extra_life.name": "Ekstra Hayat",
"upgrades.extra_life.tooltip": "Top, kaybedilmeden önce alt çizgide bir kez sekecektir.",
"upgrades.extra_life.verbose_description": "Normalde bir topunuz vardır ve oyun onu bıraktığınız anda biter.\n\nBu yetenek, ekranın altına bir kez topu kurtaracak ve bu süreçte kırılacak beyaz bir çubuk ekler. \n\nEkranın altında bir top her zıpladığında bu yeteneğin bir seviyesini kaybedersiniz.",
"upgrades.forgiving.name": "Bağışlayıcı",
"upgrades.forgiving.tooltip": "Molaları kaçırmak, komboyu bir kerede azaltmak yerine kademeli olarak azaltır.",
"upgrades.forgiving.verbose_description": "Her seviyede ilk ıskalama bedava, sonra kombonun %10'u, sonra %20'si...",
"upgrades.fountain_toss.name": "Çeşme atışı",
"upgrades.fountain_toss.tooltip": "Birkaç jetonu kaçırdığınızda bir miktar kombo kazanın.",
"upgrades.fountain_toss.verbose_description": "Bir jetonu kaçırdığınızda ve kombonuz {{max}}altındaysa, kombonuzun bir artma olasılığı {{lvl}}/ kombodur.",
"upgrades.ghost_coins.name": "Hayalet paralar",
"upgrades.ghost_coins.tooltip": "Madeni paralar yavaşça tuğlaların arasından geçiyor",
"upgrades.ghost_coins.verbose_description": "Bu bir hata değil, bir özellik! Madeni paralar tuğlaların içinden yavaşça uçar. Daha yüksek seviyeler daha hızlı hareket etmelerini sağlar.",
"upgrades.helium.name": "Helyum",
"upgrades.helium.tooltip": "Yerçekimi küreğin solunda ve sağında tersine döndü",
"upgrades.helium.verbose_description": "Bu, paraları etkileyecek ve siz onları almaya hazır olana kadar paraların havada asılı kalmasına neden olacaktır.",
"upgrades.hot_start.name": "Sıcak başlangıç",
"upgrades.hot_start.tooltip": " {{start}}kombosundan başla, saniyede{{loss}} kombo",
"upgrades.hot_start.verbose_description": "Her seviyenin başında kombonuz +30 puanla başlayacak, ancak daha sonra her saniye bir puan düşecek. Etki diğer avantajlarla birikiyor.",
"upgrades.hypnosis.name": "Hipnoz",
"upgrades.hypnosis.tooltip": "Bir tuğlanın rengi değiştiğinde, o parayı en yakın topa ışınlayın ve tuğlayı lekeleme yeteneğini yeniden yükleyin.",
"upgrades.hypnosis.verbose_description": "",
"upgrades.implosions.name": "İç patlamalar",
"upgrades.implosions.tooltip": "Patlamalar paraları dışarı atmak yerine içeri çekiyor",
"upgrades.implosions.verbose_description": "Patlama kuvveti diğer şekilde uygulanır. Daha ileri seviyeler \"daha büyük patlama\" olarak işlev görür.",
"upgrades.instant_upgrade.name": "Anında yükseltme",
"upgrades.instant_upgrade.tooltip": "Şimdi +2 yükseltme, oyun sonuna kadar -1 seçim.",
"upgrades.instant_upgrade.verbose_description": "Hemen iki yükseltme seçin, böylece birini ücretsiz, diğerini de bu avantajı elde etmek için kullanılanı geri ödeyin. Yükseltmeleri seçmek için her sonraki menüde seçebileceğiniz daha az seçenek olacak.",
"upgrades.left_is_lava.name": "Sol taraftan kaçının",
"upgrades.left_is_lava.tooltip": "+ Kırılan tuğla başına{{lvl}} kombo. Top ekranın sol tarafına çarparsa kombo sıfırlanır",
"upgrades.left_is_lava.verbose_description": "Bir tuğlayı kırdığınızda, kombonuz bir artacak, böylece kırdığınız sonraki tüm tuğlalardan bir jeton daha alacaksınız.\n\nAncak, kombonuz topunuz sol tarafa çarptığı anda sıfırlanacaktır. \n\nKombonuz yükseldiği anda, sol taraf kırmızıya dönerek onlara çarpmamanız gerektiğini hatırlatır.\n",
"upgrades.limitless.name": "Sınırsız",
"upgrades.limitless.tooltip": "Tüm yükseltmelerin maksimum seviyesini {{lvl}} kadar artırın",
"upgrades.limitless.verbose_description": "Bu yeteneği seçmek, onun kendi sınırını da bir arttırır ve tekrar seçmenize olanak tanır.",
"upgrades.metamorphosis.name": "Başkalaşım",
"upgrades.metamorphosis.tooltip": "Her madeni para, {{lvl}} tuğlayı kendi rengiyle lekeleyebilir",
"upgrades.metamorphosis.verbose_description": "Bu yetenekle, madeni paralar geldikleri tuğlanın renginde olacak ve dokundukları ilk tuğlayı aynı renge boyayacak. Madeni paralar onları kıran topun hızıyla ortaya çıkar, bu da \"boyamak\" istediğiniz tuğlalara doğru biraz nişan alabileceğiniz anlamına gelir. 1. seviyede, her madeni para \"harcanmadan\" ve içi boş görünmeden önce 1 tuğlayı boyayabilir.",
"upgrades.minefield.name": "Mayın tarlası",
"upgrades.minefield.tooltip": "Ekrandaki bomba tuğlası başına +{{lvl}} kombo",
"upgrades.minefield.verbose_description": "Bir tuğla yerleştirildiğinde komboya +lvl ekler, tuğla yok edildiğinde -lvl ekler ve temel komboyu tuğla sayısıyla lvl çarpımı kadar yükseltir",
"upgrades.multiball.name": "Çoklu top",
"upgrades.multiball.tooltip": "Her seviyeye {{count}} topla başlayın.",
"upgrades.multiball.verbose_description": "Breakout 71'de topu düşürdüğünüz anda kaybedersiniz. \n\nBu avantajla iki top elde edersiniz ve bu yüzden birini kaybetmeyi göze alabilirsiniz. \n\nKaybedilen toplar bir sonraki seviyede geri gelir. \n\nBirden fazla topa sahip olmak bazı ek avantajları kullanılabilir hale getirir ve tabii ki seviyeyi daha hızlı temizler.",
"upgrades.nbricks.name": "Sıkı örneklem büyüklüğü",
"upgrades.nbricks.tooltip": "Kürek sıçraması başına tam olarak {{lvl}} tuğla vurun +{{lvl}} kombo için, aksi takdirde sıfırlanır",
"upgrades.nbricks.verbose_description": "Bu tuğlaları yok etmeniz gerekmiyor ancak onlara vurmanız gerekiyor. Patlamalarla yok edilen tuğlalar sayılmaz",
"upgrades.one_more_choice.name": "Ekstra seçenek",
"upgrades.one_more_choice.tooltip": "Daha fazla seviye atlama, listede {{lvl}} daha fazla seçenek sunacak",
"upgrades.one_more_choice.verbose_description": "Her yükseltme menüsü bir seçeneğe daha sahip olacak. Seçebileceğiniz yükseltme sayısını artırmaz.",
"upgrades.passive_income.name": "Pasif gelir",
"upgrades.passive_income.tooltip": "+{{lvl}} kombo / tuğla, kürek son {{time}}saniyede hareket etmediği sürece, bunun yerine sıfırlanır",
"upgrades.passive_income.verbose_description": "Bazı özellikler, topların hiçbir şey yapmanıza gerek kalmadan istediğinizi yapmasına yardımcı olabilir.",
"upgrades.picky_eater.name": "Seçici yiyen",
"upgrades.picky_eater.tooltip": "+{{lvl}} kombo her tuğla kırıldığında, top rengi değiştiğinde sıfırlanır",
"upgrades.picky_eater.verbose_description": "Topunuzla aynı renkte bir tuğlayı kırdığınızda, kombonuz bir artar. \nEğer farklı bir renkteyse, top yeni rengi alır, ancak kombo sıfırlanır, tabii topun renginde tuğla kalmamışsa. \nMinimumunuzdan daha yüksek bir kombo elde ettiğinizde, yanlış renkteki tuğlalar kırmızı bir kenarlık alır. \nBirden fazla topunuz varsa, bunlardan biri bir tuğlaya çarptığında hepsi renk değiştirir.",
"upgrades.pierce.name": "Delme",
"upgrades.pierce.tooltip": "Kürek zıplamasından sonra top {{count}} tuğlayı deldi",
"upgrades.pierce.verbose_description": "Top normalde bir şeye dokunduğu anda zıplar. Bu avantajla, 3 tuğla kırılıncaya kadar yörüngesine devam eder. \n\nBundan sonra, 4. tuğlada zıplar ve sayacı sıfırlamak için küreğe dokunmanız gerekir.",
"upgrades.pierce_color.name": "Renk delme",
"upgrades.pierce_color.tooltip": "+{{lvl}} topun rengindeki tuğlalara hasar",
"upgrades.pierce_color.verbose_description": "Bir top aynı renkteki bir tuğlaya çarptığında, engellenmeden geçecektir. \n\nFarklı renkteki bir tuğlaya ulaştığında, onu kıracak, rengini alacak ve sekecektir.\n\nSağlam tuğlalarınız varsa, top yine de aynı renkteki bir tuğladan sekebilir.",
"upgrades.puck_repulse_ball.help_plural": "Daha güçlü itme kuvveti",
"upgrades.puck_repulse_ball.name": "Yumuşak iniş",
"upgrades.puck_repulse_ball.tooltip": "Kürek topları iter",
"upgrades.puck_repulse_ball.verbose_description": "Bir top küreğe yaklaştığında yavaşlamaya başlayacak ve hatta küreğe değmeden bile zıplamaya başlayacaktır.",
"upgrades.rainbow.name": "Gökkuşağı",
"upgrades.rainbow.tooltip": "Paralar gökkuşağı renklerinde ortaya çıkar.",
"upgrades.rainbow.verbose_description": "Her seviye renkli jetonların oranını artırır. Renk seviye zamanına bağlıdır.",
"upgrades.reach.name": "Yukarıdan aşağıya",
"upgrades.reach.tooltip": "En alt sıradaki N tuğlasına dokunmak komboyu sıfırlar. Aksi takdirde, +N kombo",
"upgrades.reach.verbose_description": "Eğer sadece bir sıra tuğla varsa veya en alttaki tuğla sırası oyunun tüm genişliğini kaplıyorsa, bu avantaj hiçbir işe yaramaz. Aksi takdirde, bu en alttaki sırayı kırmak komboyu sıfırlarken, başka bir şeyi kırmak komboyu o en alttaki sırada bulunan tuğla sayısı kadar artırır. \n\nEn alttaki sıra kırmızıyla vurgulanacaktır.",
"upgrades.respawn.name": "Yeniden canlanma",
"upgrades.respawn.tooltip": " {{delay}}saniye sonra tuğlaların %{{percent}}'ı yeniden ortaya çıkar.",
"upgrades.respawn.verbose_description": "Bazı parçacık efektleri tuğlaların nerede belireceğini size bildirecektir.",
"upgrades.right_is_lava.name": "Sağ taraftan kaçının",
"upgrades.right_is_lava.tooltip": "Tuğla başına +{{lvl}} kombo. Top ekranın sağ tarafına çarparsa kombo sıfırlanır",
"upgrades.right_is_lava.verbose_description": "Bir tuğlayı kırdığınızda, kombonuz bir artacak, böylece kırdığınız sonraki tüm tuğlalardan bir jeton daha alacaksınız.\n\nAncak, topunuz sağ tarafa çarptığı anda kombonuz sıfırlanacaktır.\n\nKombonuz yükseldiği anda, onlara çarpmaktan kaçınmanız gerektiğini hatırlatmak için sağ taraf kırmızı olur.",
"upgrades.sacrifice.help_l1": "Bir canı kaybetmek tüm tuğlaları temizler",
"upgrades.sacrifice.help_over": "Komboya {{lvl}}can eklendiğinde tüm tuğlalar temizlenir",
"upgrades.sacrifice.name": "Kurban etmek",
"upgrades.sacrifice.verbose_description": "Bu, komboyu oldukça yükseğe çıkarabilir.",
"upgrades.sapper.help_plural": "İlk kırılan {{lvl}} tuğla bombaya dönüşür.",
"upgrades.sapper.name": "Kazmacı",
"upgrades.sapper.tooltip": "İlk kırılan tuğla bombaya dönüşür.",
"upgrades.sapper.verbose_description": "Sadece ortadan kaybolmak yerine, kırdığınız ilk tuğla bir bomba tuğlasıyla değiştirilecektir. Kürek üzerinde topu sektirmek, efekti yeniden etkinleştirir. Bu yeteneği seviyelendirmek, daha fazla bomba yerleştirmenize olanak tanır.",
"upgrades.shocks.name": "Şoklar",
"upgrades.shocks.tooltip": "Patlayıcı top çarpışmaları",
"upgrades.shocks.verbose_description": "İki top çarpıştığında hızları değişir, bir patlama meydana gelir ve onları ayırmak için ek hız kazanırlar.",
"upgrades.shunt.name": "Şant",
"upgrades.shunt.tooltip": "Kombonuzun {{percent}}%'sini seviyeler arasında tutun",
"upgrades.shunt.verbose_description": "Eğer sıcak başlatma özelliğiniz de varsa, sıcak başlatma özelliği mevcut kombinasyona eklenir",
"upgrades.side_flip.name": "Sağ elini kullanan",
"upgrades.side_flip.tooltip": "Sağdan kırılan tuğla başına +{{lvl}} kombo, aksi takdirde -{{loss}} ",
"upgrades.side_flip.verbose_description": "Bir kombo kazanmak için tuğlanın sağ tarafına vurun, ancak sol tarafına vurmaktan kaçının çünkü bu 2 komboyu kaldırır. Üstten ve alttan vurmanın bir etkisi yoktur.",
"upgrades.side_kick.name": "Solak",
"upgrades.side_kick.tooltip": "Soldan kırılan tuğla başına +{{lvl}} kombo, aksi takdirde -{{loss}} ",
"upgrades.side_kick.verbose_description": "Tuğlanın sol tarafına vurarak bir kombo elde edin, ancak sağ tarafına vurmaktan kaçının çünkü bu 2 komboyu kaldırır. Üstten ve alttan vurmanın bir etkisi yoktur.",
"upgrades.skip_last.help_plural": "Son {{lvl}} tuğla patlayacak.",
"upgrades.skip_last.name": "Kolay Temizlik",
"upgrades.skip_last.tooltip": "Son tuğla patlayacak.",
"upgrades.skip_last.verbose_description": "Bir sonraki seviyeye geçmek için tüm tuğlaları kırmanız gerekir. Ancak son tuğlaları elde etmek zor olabilir. \n\nBir seviyeyi erken bitirmek, yükseltme sırasında ekstra seçenekler sunar. Tuğlaları asla kaçırmamak da çok faydalıdır. \n\nYani son tuğlaları kırmakta zorlanıyorsanız, bu yeteneği birkaç kez elde etmek yardımcı olabilir.",
"upgrades.slow_down.name": "Daha yavaş top",
"upgrades.slow_down.tooltip": "Top daha yavaş hareket ediyor",
"upgrades.slow_down.verbose_description": "Top nispeten yavaş başlar, ancak oyununuzun her seviyesinde biraz daha hızlı başlayacaktır. \n\nAyrıca bir seviyede çok zaman geçirirseniz hızlanacaktır. \n\nBu avantaj onu daha yönetilebilir hale getirir. \n\nMenüde çocuk modunu etkinleştirerek her seferinde başlangıçta alabilirsiniz.",
"upgrades.smaller_puck.help_plural": "Daha küçük kürek ve daha yüksek taban kombinasyonu",
"upgrades.smaller_puck.name": "Daha küçük kürek",
"upgrades.smaller_puck.tooltip": "Ayrıca +5 temel kombo verir",
"upgrades.smaller_puck.verbose_description": "Bu, küreği daha küçük yapar, bu da teoride bazı köşe vuruşlarını kolaylaştırır, ancak gerçekte sadece zorluğu artırır.\n\nBu yüzden, bunu seçtikten sonra kıracağınız tüm tuğlalar için tuğla başına +5 jetonluk güzel bir bonus da alırsınız.",
"upgrades.soft_reset.name": "Yumuşak sıfırlama",
"upgrades.soft_reset.tooltip": "Kombo sıfırlamaları % {{percent}}tutar",
"upgrades.soft_reset.verbose_description": "Bir kombo sıfırlamanın etkisini sınırlayın.",
"upgrades.streak_shots.name": "Vuruş serisi",
"upgrades.streak_shots.tooltip": "Kürek çekmeden önce çok sayıda tuğla kırarsanız daha fazla para kazanırsınız.",
"upgrades.streak_shots.verbose_description": "Her tuğlayı kırdığınızda, kombonuz bir artar. \n\nAncak, top küreğinize değdiği anda kombo varsayılan değerine sıfırlanır.\n\nKombonuz temel değerin üzerine çıktığında, küreğinizin topla dokunduğunuzda kombonuzu yok edeceğini size hatırlatmak için kırmızı bir kenarı olacaktır.",
"upgrades.sturdy_bricks.name": "Sağlam tuğlalar",
"upgrades.sturdy_bricks.tooltip": "+{{lvl}} tuğla HP, kırıldığında ortaya çıkan +{{percent}}% para",
"upgrades.sturdy_bricks.verbose_description": "Bu yeteneğin her seviyesi tüm tuğlalara bir HP ekler. HP sayısını \"clairvoyant\" yeteneğiyle görebilirsiniz. \"Piercing\" yeteneğini alarak top hasarını artırabilirsiniz. Yeteneğin her seviyesi +50% jeton spawn'ı ekler.",
"upgrades.superhot.name": "SÜPER SICAK",
"upgrades.superhot.tooltip": "Kürek hareket ettikçe zaman da hareket eder.",
"upgrades.superhot.verbose_description": "SÜPER SICAK SÜPER SICAK SÜPER SICAK SÜPER SICAK",
"upgrades.telekinesis.help_plural": "Top üzerinde daha güçlü etki",
"upgrades.telekinesis.name": "Telekinezi",
"upgrades.telekinesis.tooltip": "Kürek topun yörüngesini kontrol eder",
"upgrades.telekinesis.verbose_description": "Top yukarı doğru giderken onu kontrol ediyorsun.",
"upgrades.top_is_lava.name": "Sınır gökyüzüdür",
"upgrades.top_is_lava.tooltip": "Tuğla başına +{{lvl}} kombo, tepeye vurulduğunda sıfırlanır",
"upgrades.top_is_lava.verbose_description": "Bir tuğlayı kırdığınızda, kombonuz bir artar. Ancak, topunuz ekranın tepesine çarptığı anda kombonuz sıfırlanır. \n\nKombonuz minimumun üzerinde olduğunda, ona çarpmamanız gerektiğini hatırlatmak için üstte kırmızı bir çubuk belirir.",
"upgrades.trampoline.name": "Trambolin",
"upgrades.trampoline.tooltip": "+ kürek sıçraması başına{{lvl}} kombo,- herhangi bir sınırda sıçrama başına{{lvl}} kombo",
"upgrades.trampoline.verbose_description": "Sıfırlama koşulu eklemeyen nadir kombo yükseltmelerinden biri",
"upgrades.transparency.name": "Şeffaflık",
"upgrades.transparency.tooltip": "Top ekranda ne kadar yüksekteyse o kadar şeffaf olur. Ne kadar şeffafsa o kadar çok jeton üretir.",
"upgrades.transparency.verbose_description": "Daha yüksek seviyeler topun daha çabuk şeffaf olmasını sağlar ve puan bonusunu artırır.",
"upgrades.trickledown.name": "Sızma ekonomisi",
"upgrades.trickledown.tooltip": "Paralar ekranın üst kısmında görünür.",
"upgrades.trickledown.verbose_description": "Kenara biraz para ayırmanıza yardımcı olabilir.",
"upgrades.unbounded.name": "Dolgu",
"upgrades.unbounded.tooltip": "Seviyenin sağına ve soluna boşluk ekler, ancak küreğiniz o kadar uzağa gidemez.",
"upgrades.unbounded.verbose_description": "Bir diğer geliştirme ise küreğinizin erişim mesafesini uzatmanıza yardımcı olabilir.",
"upgrades.viscosity.name": "Viskozite",
"upgrades.viscosity.tooltip": "Daha yavaş para düşüşü",
"upgrades.viscosity.verbose_description": "Madeni paralar normalde yer çekimi ve patlamalarla oldukça yüksek hızlara ulaşır. \n\nBu yetenek, sanki bir tür viskoz sıvının içindeymiş gibi sürekli olarak yavaşlamalarını sağlar. \n\nBu, onları yakalamayı kolaylaştırır ve madeni paranın hareketini etkileyen yeteneklerle güzel bir şekilde birleşir.",
"upgrades.wind.help_plural": "Daha güçlü rüzgar kuvveti",
"upgrades.wind.name": "Rüzgâr",
"upgrades.wind.tooltip": "Kürek pozisyonu rüzgar yaratır",
"upgrades.wind.verbose_description": "Rüzgar küreğin pozisyonuna bağlıdır: sol sola eser, sağ sağa eser. Hem topları hem de paraları etkiler.",
"upgrades.yoyo.name": "Yo-yo",
"upgrades.yoyo.tooltip": "Top küreğe doğru düşer",
"upgrades.yoyo.verbose_description": "Telekinezinin tam tersi, topun aşağı düşerken kontrol edilmesi.",
"upgrades.zen.name": "Zen",
"upgrades.zen.tooltip": "Tuğla başına +{{lvl}} kombo, patlama olduğunda sıfırlanır",
"upgrades.zen.verbose_description": "Sonuçta bu şiddet içermeyen bir oyun."
}

View file

@ -23,6 +23,7 @@
<body>
<button id="menu"><span id="menuLabel">menu</span></button>
<button id="score"></button>
<div id="stats" style="display: none"></div>
<canvas id="game"></canvas>
<div id="popup">

303
src/levelEditor.ts Normal file
View file

@ -0,0 +1,303 @@
import { icons, transformRawLevel } from "./loadGameData";
import { t } from "./i18n/i18n";
import {
commitSettingsChangesToLocalStorage,
getSettingValue,
getTotalScore,
setSettingValue,
} from "./settings";
import { asyncAlert } from "./asyncAlert";
import { Palette, RawLevel } from "./types";
import { levelIconHTML } from "./levelIcon";
import _palette from "./data/palette.json";
import { restart } from "./game";
import { describeLevel } from "./game_utils";
const palette = _palette as Palette;
const MAX_LEVEL_SIZE = 21;
const MIN_LEVEL_SIZE = 2;
export function levelEditorMenuEntry() {
const min = 10000;
const disabled = getTotalScore() < min;
return {
icon: icons["icon:editor"],
text: t("editor.title"),
disabled,
help: disabled ? t("editor.locked", { min }) : t("editor.help"),
async value() {
openLevelEditorLevelsList().then();
},
};
}
async function openLevelEditorLevelsList() {
const rawList = getSettingValue("custom_levels", []) as RawLevel[];
const customLevels = rawList.map(transformRawLevel);
let choice = await asyncAlert({
title: t("editor.title"),
content: [
...customLevels.map((l, li) => ({
text: l.name,
icon: levelIconHTML(l.bricks, l.size, l.color),
value() {
editRawLevelList(li);
},
help: l.credit || describeLevel(l),
})),
{
text: t("editor.new_level"),
icon: icons["icon:editor"],
value() {
rawList.push({
color: "",
size: 6,
bricks: "____________________________________",
name: "custom level" + (rawList.length + 1),
credit: "",
});
setSettingValue("custom_levels", rawList);
editRawLevelList(rawList.length - 1);
},
},
{
text: t("editor.import"),
help: t("editor.import_instruction"),
value() {
const code = prompt(t("editor.import_instruction"))?.trim();
if (code) {
let [name, credit] = code.match(/\[([^\]]+)]/gi);
let bricks = code
.split(name)[1]
.split(credit)[0]
.replace(/\s/gi, "");
name = name.slice(1, -1);
credit = credit.slice(1, -1);
name ||= "Imported on " + new Date().toISOString().slice(0, 10);
credit ||= "";
const size = Math.sqrt(bricks.length);
if (
Math.floor(size) === size &&
size >= MIN_LEVEL_SIZE &&
size <= MAX_LEVEL_SIZE
) {
rawList.push({
color: automaticBackgroundColor(bricks.split("")),
size,
bricks,
name,
credit,
});
setSettingValue("custom_levels", rawList);
}
}
openLevelEditorLevelsList();
},
},
],
});
if (typeof choice == "function") choice();
}
export async function editRawLevelList(nth: number, color = "W") {
let rawList = getSettingValue("custom_levels", []) as RawLevel[];
const level = rawList[nth];
const bricks = level.bricks.split("");
let grid = "";
for (let y = 0; y < level.size; y++) {
grid += '<div style="background: ' + (level.color || "black") + ';">';
for (let x = 0; x < level.size; x++) {
const c = bricks[y * level.size + x];
grid += `<span data-resolve-to="paint_brick:${x}:${y}" style="background: ${palette[c]}">${c == "B" ? "💣" : ""}</span>`;
}
grid += "</div>";
}
const levelColors = new Set(bricks);
levelColors.delete("_");
levelColors.delete("B");
let colorList =
'<div class="palette">' +
Object.entries(palette)
.filter(([key, value]) => key !== "_")
.filter(
([key, value]) =>
levelColors.size < 5 || levelColors.has(key) || key === "B",
)
.map(
([key, value]) =>
`<span data-resolve-to="set_color:${key}" data-selected="${key == color}" style="background: ${value}">${key == "B" ? "💣" : ""}</span>`,
)
.join("") +
"</div>";
const clicked = await asyncAlert<string | null>({
title: t("editor.editing.title", { name: level.name }),
content: [
t("editor.editing.color"),
colorList,
t("editor.editing.help"),
`<div class="gridEdit" style="--grid-size:${level.size};">${grid}</div>`,
{
icon: icons["icon:new_run"],
text: t("editor.editing.play"),
value: "play",
},
{
text: t("editor.editing.rename"),
value: "rename",
help: level.name,
},
{
text: t("editor.editing.credit"),
value: "credit",
help: level.credit,
},
{
text: t("editor.editing.delete"),
value: "delete",
},
{
text: t("editor.editing.copy"),
value: "copy",
help: t("editor.editing.copy_help"),
disabled:
!level.name ||
!level.credit ||
bricks.filter((b) => b !== "_").length < 6,
},
{
text: t("editor.editing.bigger"),
value: "size:+1",
disabled: level.size >= MAX_LEVEL_SIZE,
},
{
text: t("editor.editing.smaller"),
value: "size:-1",
disabled: level.size <= MIN_LEVEL_SIZE,
},
{
text: t("editor.editing.left"),
value: "move:-1:0",
},
{
text: t("editor.editing.right"),
value: "move:1:0",
},
{
text: t("editor.editing.up"),
value: "move:0:-1",
},
{
text: t("editor.editing.down"),
value: "move:0:1",
},
],
});
if (!clicked) return;
if (typeof clicked === "string") {
const [action, a, b] = clicked.split(":");
if (action == "paint_brick") {
const x = parseInt(a),
y = parseInt(b);
bricks[y * level.size + x] =
bricks[y * level.size + x] === color ? "_" : color;
level.bricks = bricks.join("");
}
if (action == "set_color") {
color = a;
}
if (action == "size") {
const newSize = level.size + parseInt(a);
const newBricks = [];
for (let y = 0; y < newSize; y++) {
for (let x = 0; x < newSize; x++) {
newBricks.push(
(x < level.size && y < level.size && bricks[y * level.size + x]) ||
"_",
);
}
}
level.size = newSize;
level.bricks = newBricks.join("");
}
if (action == "move") {
const dx = parseInt(a),
dy = parseInt(b);
const newBricks = [];
for (let y = 0; y < level.size; y++) {
for (let x = 0; x < level.size; x++) {
const tx = x - dx;
const ty = y - dy;
if (tx < 0 || tx >= level.size || ty < 0 || ty >= level.size) {
newBricks.push("_");
} else {
newBricks.push(bricks[ty * level.size + tx]);
}
}
}
level.bricks = newBricks.join("");
}
if (action === "play") {
restart({
level: transformRawLevel(level),
isEditorTrialRun: nth,
perks: {
base_combo: 7,
},
});
return;
}
if (action === "copy") {
let text = "```\n[" + level.name?.replace(/\[|\]/gi, " ") + "]";
bricks.forEach((b, bi) => {
if (!(bi % level.size)) text += "\n";
text += b;
});
text +=
"\n[" +
(level.credit?.replace(/\[|\]/gi, " ") || "Missing credits!") +
"]\n```";
navigator.clipboard.writeText(text);
// return
}
if (action === "rename") {
const name = prompt(t("editor.editing.rename_prompt"), level.name);
if (name) {
level.name = name;
}
}
if (action === "credit") {
const credit = prompt(
t("editor.editing.credit_prompt"),
level.credit || "",
);
if (credit !== "null") {
level.credit = credit || "";
}
}
if (action === "delete") {
rawList = rawList.filter((l, li) => li !== nth);
setSettingValue("custom_levels", rawList);
openLevelEditorLevelsList();
return;
}
}
level.color = automaticBackgroundColor(bricks);
setSettingValue("custom_levels", rawList);
editRawLevelList(nth, color);
}
function automaticBackgroundColor(bricks: string[]) {
return bricks.filter((b) => b === "g").length >
bricks.filter((b) => b !== "_").length * 0.05
? "#115988"
: "";
}

View file

@ -28,7 +28,7 @@ describe("json data checks", () => {
.filter((l) => {
const uniqueBricks = l.bricks
.split("")
.filter((b) => b !== "_" && b !== "black")
.filter((b) => b !== "_" && b !== "B")
.filter((a, b, c) => c.indexOf(a) === b);
return uniqueBricks.length > 5 && !l.name.startsWith("icon:");
})

View file

@ -13,28 +13,29 @@ const rawLevelsList = _rawLevelsList as RawLevel[];
export const appVersion = _appVersion as string;
export const icons = {} as { [k: string]: string };
export const allLevelsAndIcons = rawLevelsList
.map((level, i) => {
const bricks = level.bricks
.split("")
.map((c) => palette[c])
.slice(0, level.size * level.size);
const bricksCount = bricks.filter((i) => i).length;
const icon = levelIconHTML(bricks, level.size, level.color);
icons[level.name] = icon;
return {
...level,
bricks,
bricksCount,
icon,
color: level.color || "#000000",
svg: getLevelBackground(level),
};
})
.map((l, li) => ({
...l,
sortKey: ((Math.random() + 3) / 3.5) * l.bricksCount,
})) as Level[];
export function transformRawLevel(level: RawLevel) {
const bricks = level.bricks
.split("")
.map((c) => palette[c])
.slice(0, level.size * level.size);
const bricksCount = bricks.filter((i) => i).length;
const icon = levelIconHTML(bricks, level.size, level.color);
icons[level.name] = icon;
return {
...level,
bricks,
bricksCount,
icon,
color: level.color || "#000000",
svg: getLevelBackground(level),
sortKey: ((Math.random() + 3) / 3.5) * bricksCount,
};
}
export const allLevelsAndIcons = rawLevelsList.map(
transformRawLevel,
) as Level[];
export const allLevels = allLevelsAndIcons.filter(
(l) => !l.name.startsWith("icon:"),

View file

@ -4,6 +4,7 @@ import _appVersion from "./data/version.json";
import { generateSaveFileContent } from "./generateSaveFileContent";
import { getLevelUnlockCondition, reasonLevelIsLocked } from "./game_utils";
import { allLevels } from "./loadGameData";
import { toast } from "./toast";
// The page will be reloaded if any migrations were run
let migrationsRun = 0;
@ -15,6 +16,7 @@ function migrate(name: string, cb: () => void) {
localStorage.setItem(name, "" + Date.now());
migrationsRun++;
} catch (e) {
toast((e as Error).message);
console.warn("Migration " + name + " failed : ", e);
}
}
@ -80,16 +82,28 @@ migrate("remove_long_and_creative_mode_data", () => {
localStorage.setItem("breakout_71_runs_history", JSON.stringify(cleaned));
});
migrate("compact_runs_data", () => {
migrate("compact_runs_data_again", () => {
let runsHistory = JSON.parse(
localStorage.getItem("breakout_71_runs_history") || "[]",
) as RunHistoryItem[];
runsHistory = runsHistory.filter((r) => {
if (!r.perks) return false;
if ("mode" in r) {
if (r.mode !== "short") {
return false;
}
delete r.mode;
}
return true;
});
runsHistory.forEach((r) => {
r.runTime = Math.round(r.runTime);
for (let key in r.perks) {
if (r.perks && !r.perks[key]) {
delete r.perks[key];
if (r.perks) {
for (let key in r.perks) {
if (!r.perks[key]) {
delete r.perks[key];
}
}
}
if ("best_level_score" in r) {

View file

@ -32,6 +32,9 @@ export function monitorLevelsUnlocks(gameState: GameState) {
// Already unlocked
if (unlocked.has(name)) return;
// Score not reached yet
if (gameState.score < minScore) return;
if (!minScore) return;
if (gameState.score < minScore) return;
// We are missing a required perk
if (required.find((id) => !gameState.perks[id])) return;

View file

@ -28,12 +28,12 @@ export function getRunLevels(
(l, li) =>
unlockedBefore.has(l.name) || !reasonLevelIsLocked(li, history, false),
);
const firstLevel = allLevelsAndIcons.filter(
(l) => l.name == (params?.level || "icon:" + randomGift),
);
const firstLevel = params?.level
? [params.level]
: allLevelsAndIcons.filter((l) => l.name == "icon:" + randomGift);
const restInRandomOrder = unlocked
.filter((l) => l.name !== params?.level)
.filter((l) => l.name !== params?.level?.name)
.filter((l) => l.name !== params?.levelToAvoid)
.sort(() => Math.random() - 0.5);
@ -65,6 +65,7 @@ export function newGameState(params: RunParams): GameState {
const runLevels = getRunLevels(params, randomGift);
const gameState: GameState = {
startParams: params,
runLevels,
level: runLevels[0],
currentLevel: 0,
@ -141,8 +142,7 @@ export function newGameState(params: RunParams): GameState {
creative:
params?.computer_controlled ||
sumOfValues(params.perks) > 1 ||
(params.level && !params.level.startsWith("icon:")),
computer_controlled: params?.computer_controlled || false,
(params.level && !params.level.name.startsWith("icon:")),
};
resetBalls(gameState);

View file

@ -30,6 +30,21 @@ export const options = {
name: t("settings.extra_bright"),
help: t("settings.extra_bright_help"),
},
smooth_lighting: {
default: true,
name: t("settings.smooth_lighting"),
help: t("settings.smooth_lighting_help"),
},
precise_lighting: {
default: true,
name: t("settings.precise_lighting"),
help: t("settings.precise_lighting_help"),
},
probabilistic_lighting: {
default: false,
name: t("settings.probabilistic_lighting"),
help: t("settings.probabilistic_lighting_help"),
},
contrast: {
default: false,
name: t("settings.contrast"),

View file

@ -1,3 +1,5 @@
import { getSettingValue } from "./settings";
export function clamp(value: number, min: number, max: number) {
return Math.max(min, Math.min(value, max));
}
@ -8,9 +10,8 @@ export function comboKeepingRate(level: number) {
export function hoursSpentPlaying() {
try {
const timePlayed =
localStorage.getItem("breakout_71_total_play_time") || "0";
return Math.floor(parseFloat(timePlayed) / 1000 / 60 / 60);
const timePlayed = getSettingValue("breakout_71_total_play_time", 0);
return Math.floor(timePlayed / 1000 / 60 / 60);
} catch (e) {
return 0;
}

View file

@ -1,9 +1,10 @@
import { gameCanvas } from "./render";
import { max_levels } from "./game_utils";
import { isInWebView, max_levels } from "./game_utils";
import { getAudioRecordingTrack } from "./sounds";
import { t } from "./i18n/i18n";
import { GameState } from "./types";
import { isOptionOn } from "./options";
import { toast } from "./toast";
let mediaRecorder: MediaRecorder | null,
captureStream: MediaStream,
@ -88,6 +89,7 @@ export function startRecordingGame(gameState: GameState) {
const recordedChunks: Blob[] = [];
const instance = new MediaRecorder(captureStream, {
// Required for less mushy result
videoBitsPerSecond: 3500000,
});
mediaRecorder = instance;
@ -107,6 +109,7 @@ export function startRecordingGame(gameState: GameState) {
) {
await new Promise((r) => setTimeout(r, 200));
}
const video = document.createElement("video");
video.autoplay = true;
video.controls = false;
@ -124,12 +127,12 @@ export function startRecordingGame(gameState: GameState) {
const a = document.createElement("a");
a.download = captureFileName("webm");
a.target = "_blank";
if (window.location.href.endsWith("index.html?isInWebView=true")) {
if (isInWebView) {
a.href = await blobToBase64(blob);
} else {
a.href = video.src;
}
a.textContent = t("settings.record_download", {
size: (blob.size / 1000000).toFixed(2),
});
@ -144,8 +147,10 @@ function blobToBase64(blob: Blob): Promise<string> {
reader.onload = function () {
resolve(reader.result);
};
reader.onerror = function (e) {
reader.onerror = function () {
const e = reader.error;
console.error(e);
toast(e?.message || "Failed to convert the video to a data url");
reject(new Error("Failed to readAsDataURL of the video "));
};

View file

@ -4,6 +4,8 @@ import {
brickCenterX,
brickCenterY,
currentLevelInfo,
getCoinRenderColor,
getCornerOffset,
isMovingWhilePassiveIncome,
isPickyEatingPossible,
max_levels,
@ -11,9 +13,9 @@ import {
telekinesisEffectRate,
yoyoEffectRate,
} from "./game_utils";
import { Coin, colorString, GameState } from "./types";
import { colorString, GameState } from "./types";
import { t } from "./i18n/i18n";
import { gameState, lastMeasuredFPS } from "./game";
import { gameState, lastMeasuredFPS, startWork } from "./game";
import { isOptionOn } from "./options";
import {
catchRateBest,
@ -25,6 +27,7 @@ import {
wallBouncedBest,
wallBouncedGood,
} from "./pure_functions";
import { getCurrentMaxCoins } from "./settings";
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
export const ctx = gameCanvas.getContext("2d", {
@ -48,9 +51,14 @@ const haloCanvasCtx = haloCanvas.getContext("2d", {
alpha: false,
}) as CanvasRenderingContext2D;
export const haloScale = 16;
export function getHaloScale() {
return 16 * (isOptionOn("precise_lighting") ? 1 : 2);
}
let framesCounter = 0;
export function render(gameState: GameState) {
framesCounter++;
startWork("render:init");
const level = currentLevelInfo(gameState);
const hasCombo = gameState.combo > baseCombo(gameState);
@ -70,14 +78,13 @@ export function render(gameState: GameState) {
? (gameState.levelSpawnedCoins - gameState.levelLostCoins) /
gameState.levelSpawnedCoins
: 1;
startWork("render:scoreDisplay");
scoreDisplay.innerHTML =
(isOptionOn("show_fps")
(isOptionOn("show_fps") || gameState.startParams.computer_controlled
? `
<span class="${(Math.abs(lastMeasuredFPS - 60) < 2 && " ") || (Math.abs(lastMeasuredFPS - 60) < 10 && "good") || "bad"}">
<span class="${(Math.abs(lastMeasuredFPS - 60) < 2 && " ") || (Math.abs(lastMeasuredFPS - 60) < 10 && "good") || "bad"}">
${lastMeasuredFPS} FPS
</span><span> / </span>
`
: "") +
(isOptionOn("show_stats")
@ -99,23 +106,33 @@ export function render(gameState: GameState) {
`<span class="score" data-tooltip="${t("play.score_tooltip")}">$${gameState.score}</span>`;
scoreDisplay.className =
(gameState.computer_controlled && "hidden") ||
(gameState.startParams.computer_controlled && "computer_controlled") ||
(gameState.lastScoreIncrease > gameState.levelTime - 500 && "active") ||
"";
// Clear
if (!isOptionOn("basic") && level.svg && level.color === "#000000") {
const skipN =
isOptionOn("probabilistic_lighting") && liveCount(gameState.coins) > 150
? 3
: 0;
const shouldSkip = (index) =>
skipN ? (framesCounter + index) % (skipN + 1) !== 0 : false;
const haloScale = getHaloScale();
startWork("render:halo:clear");
haloCanvasCtx.globalCompositeOperation = "source-over";
haloCanvasCtx.globalAlpha = 0.99;
haloCanvasCtx.globalAlpha = skipN ? 0.1 : 0.99;
haloCanvasCtx.fillStyle = level.color;
haloCanvasCtx.fillRect(0, 0, width / haloScale, height / haloScale);
const brightness = isOptionOn("extra_bright") ? 3 : 1;
haloCanvasCtx.globalCompositeOperation = "lighten";
haloCanvasCtx.globalAlpha =
0.1 + (0.5 * 10) / (liveCount(gameState.coins) + 10);
forEachLiveOne(gameState.coins, (coin) => {
startWork("render:halo:coins");
forEachLiveOne(gameState.coins, (coin, index) => {
if (shouldSkip(index)) return;
const color = getCoinRenderColor(gameState, coin);
drawFuzzyBall(
haloCanvasCtx,
@ -126,7 +143,9 @@ export function render(gameState: GameState) {
);
});
gameState.balls.forEach((ball) => {
startWork("render:halo:balls");
gameState.balls.forEach((ball, index) => {
if (shouldSkip(index)) return;
haloCanvasCtx.globalAlpha = 0.3 * (1 - ballTransparency(ball, gameState));
drawFuzzyBall(
haloCanvasCtx,
@ -136,9 +155,12 @@ export function render(gameState: GameState) {
ball.y / haloScale,
);
});
startWork("render:halo:bricks");
haloCanvasCtx.globalAlpha = 0.05;
gameState.bricks.forEach((color, index) => {
if (!color) return;
if (shouldSkip(index)) return;
const x = brickCenterX(gameState, index),
y = brickCenterY(gameState, index);
drawFuzzyBall(
@ -151,8 +173,10 @@ export function render(gameState: GameState) {
);
});
startWork("render:halo:particles");
haloCanvasCtx.globalCompositeOperation = "screen";
forEachLiveOne(gameState.particles, (flash) => {
forEachLiveOne(gameState.particles, (flash, index) => {
if (shouldSkip(index)) return;
const { x, y, time, color, size, duration } = flash;
const elapsed = gameState.levelTime - time;
haloCanvasCtx.globalAlpha =
@ -166,14 +190,16 @@ export function render(gameState: GameState) {
);
});
startWork("render:halo:scale_up");
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";
ctx.imageSmoothingEnabled = isOptionOn("smooth_lighting") || false;
ctx.drawImage(haloCanvas, 0, 0, width, height);
ctx.imageSmoothingEnabled = false;
startWork("render:halo:pattern");
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "multiply";
if (level.svg && background.width && background.complete) {
@ -225,6 +251,7 @@ export function render(gameState: GameState) {
ctx.fillRect(0, 0, width, height);
}
} else {
startWork("render:halo-basic");
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = level.color || "#000";
@ -237,6 +264,7 @@ export function render(gameState: GameState) {
});
}
startWork("render:explosionshake");
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5;
@ -249,7 +277,7 @@ export function render(gameState: GameState) {
Math.sin(Date.now() + 36) * amplitude,
);
}
startWork("render:coins");
// Coins
ctx.globalAlpha = 1;
forEachLiveOne(gameState.coins, (coin) => {
@ -272,6 +300,7 @@ export function render(gameState: GameState) {
coin.a,
);
});
startWork("render:ball shade");
// Black shadow around balls
if (!isOptionOn("basic")) {
ctx.globalCompositeOperation = "source-over";
@ -289,10 +318,11 @@ export function render(gameState: GameState) {
);
});
}
startWork("render:bricks");
ctx.globalCompositeOperation = "source-over";
renderAllBricks();
startWork("render:lights");
ctx.globalCompositeOperation = "screen";
forEachLiveOne(gameState.lights, (flash) => {
const { x, y, time, color, size, duration } = flash;
@ -309,6 +339,7 @@ export function render(gameState: GameState) {
);
});
startWork("render:texts");
ctx.globalCompositeOperation = "screen";
forEachLiveOne(gameState.texts, (flash) => {
const { x, y, time, color, size, duration } = flash;
@ -318,6 +349,7 @@ export function render(gameState: GameState) {
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
});
startWork("render:particles");
forEachLiveOne(gameState.particles, (particle) => {
const { x, y, time, color, size, duration } = particle;
const elapsed = gameState.levelTime - time;
@ -325,6 +357,8 @@ export function render(gameState: GameState) {
ctx.globalCompositeOperation = "screen";
drawBall(ctx, color, size, x, y);
});
startWork("render:extra_life");
if (gameState.perks.extra_life) {
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
@ -339,9 +373,9 @@ export function render(gameState: GameState) {
}
}
startWork("render:balls");
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
gameState.balls.forEach((ball) => {
const drawingColor = gameState.ballsColor;
const ballAlpha = 1 - ballTransparency(ball, gameState);
@ -390,10 +424,10 @@ export function render(gameState: GameState) {
ctx.stroke();
}
});
// The puck
startWork("render:puck");
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
drawPuck(
ctx,
gameState.puckColor,
@ -404,22 +438,18 @@ export function render(gameState: GameState) {
gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1,
);
startWork("render:combotext");
if (gameState.combo > 1) {
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
const comboText = "x " + gameState.combo;
const comboTextWidth = (comboText.length * gameState.puckHeight) / 1.8;
const totalWidth = comboTextWidth + gameState.coinSize * 2;
const left = gameState.puckPosition - totalWidth / 2;
ctx.globalAlpha = gameState.combo > baseCombo(gameState) ? 1 : 0.3;
if (totalWidth < gameState.puckWidth) {
drawCoin(
ctx,
"#ffd300",
gameState.coinSize,
left + gameState.coinSize / 2,
gameState.gameZoneHeight - gameState.puckHeight / 2,
"#ffd300",
0,
);
drawText(
ctx,
comboText,
@ -429,6 +459,17 @@ export function render(gameState: GameState) {
gameState.gameZoneHeight - gameState.puckHeight / 2,
true,
);
ctx.globalAlpha = 1;
drawCoin(
ctx,
"#ffd300",
gameState.coinSize,
left + gameState.coinSize / 2,
gameState.gameZoneHeight - gameState.puckHeight / 2,
"#ffd300",
0,
);
} else {
drawText(
ctx,
@ -443,8 +484,8 @@ export function render(gameState: GameState) {
);
}
}
startWork("render:borders");
// Borders
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
@ -513,42 +554,63 @@ export function render(gameState: GameState) {
1,
);
startWork("render:bottom_line");
ctx.globalAlpha = 1;
const corner = getCornerOffset(gameState);
drawStraightLine(
ctx,
gameState,
(hasCombo && gameState.perks.compound_interest && "#FF0000") ||
(isOptionOn("mobile-mode") && "#FFFFFF") ||
(isOptionOn("mobile-mode") && "#666666") ||
(corner && "#666666") ||
"",
gameState.offsetXRoundedDown,
gameState.gameZoneHeight,
width - gameState.offsetXRoundedDown,
gameState.gameZoneHeight,
gameState.offsetXRoundedDown - corner,
gameState.gameZoneHeight - 1,
width - gameState.offsetXRoundedDown + corner,
gameState.gameZoneHeight - 1,
1,
);
startWork("render:contrast");
if (
!isOptionOn("basic") &&
isOptionOn("contrast") &&
level.svg &&
level.color === "#000000"
) {
ctx.imageSmoothingEnabled = true;
// haloCanvasCtx.globalCompositeOperation = 'multiply';
// haloCanvasCtx.fillRect(0,0,haloCanvas.width,haloCanvas.height)
haloCanvasCtx.fillStyle = "#FFFFFF";
haloCanvasCtx.globalAlpha = 0.25;
haloCanvasCtx.globalCompositeOperation = "screen";
haloCanvasCtx.fillRect(0, 0, haloCanvas.width, haloCanvas.height);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "overlay";
ctx.imageSmoothingEnabled = isOptionOn("smooth_lighting") || false;
if (isOptionOn("probabilistic_lighting")) {
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "soft-light";
} else {
haloCanvasCtx.fillStyle = "#FFFFFF";
haloCanvasCtx.globalAlpha = 0.25;
haloCanvasCtx.globalCompositeOperation = "screen";
haloCanvasCtx.fillRect(0, 0, haloCanvas.width, haloCanvas.height);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "overlay";
}
ctx.drawImage(haloCanvas, 0, 0, width, height);
ctx.imageSmoothingEnabled = false;
}
startWork("render:text_under_puck");
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
if (isOptionOn("mobile-mode") && gameState.startParams.computer_controlled) {
drawText(
ctx,
"breakout.lecaro.me?autoplay",
gameState.puckColor,
gameState.puckHeight,
gameState.canvasWidth / 2,
gameState.gameZoneHeight +
(gameState.canvasHeight - gameState.gameZoneHeight) / 2,
);
}
if (isOptionOn("mobile-mode") && !gameState.running) {
drawText(
ctx,
@ -561,6 +623,10 @@ export function render(gameState: GameState) {
);
}
startWork("render:askForWakeLock");
askForWakeLock(gameState);
startWork("render:resetTransform");
if (shaked) {
ctx.resetTransform();
}
@ -569,22 +635,25 @@ export function render(gameState: GameState) {
function drawStraightLine(
ctx: CanvasRenderingContext2D,
gameState: GameState,
mode: "#FFFFFF" | "" | "#FF0000",
mode: "#FFFFFF" | "" | "#FF0000" | string,
x1,
y1,
x2,
y2,
alpha = 1,
) {
x1 = Math.round(x1);
y1 = Math.round(y1);
x2 = Math.round(x2);
y2 = Math.round(y2);
ctx.globalAlpha = alpha;
if (!mode) return;
ctx.strokeStyle = mode;
if (mode == "#FF0000") {
ctx.strokeStyle = "#FF0000";
ctx.lineDashOffset = getDashOffset(gameState);
ctx.lineWidth = 2;
ctx.setLineDash(redBorderDash);
} else {
ctx.strokeStyle = "#FFFFFF";
ctx.lineWidth = 1;
}
ctx.beginPath();
@ -901,6 +970,7 @@ export function drawFuzzyBall(
x: number,
y: number,
) {
width = Math.max(width, 2);
const key = "fuzzy-circle" + color + "_" + width;
if (!color?.startsWith("#")) debugger;
@ -1101,13 +1171,26 @@ export function getDashOffset(gameState: GameState) {
return Math.floor(((gameState.levelTime % 500) / 500) * 10) % 10;
}
function getCoinRenderColor(gameState: GameState, coin: Coin) {
let wakeLock = null,
wakeLockPending = false;
function askForWakeLock(gameState: GameState) {
if (
gameState.perks.metamorphosis ||
isOptionOn("colorful_coins") ||
gameState.perks.hypnosis ||
gameState.perks.rainbow
)
return coin.color;
return "#ffd300";
gameState.startParams.computer_controlled &&
!wakeLock &&
!wakeLockPending
) {
wakeLockPending = true;
try {
navigator.wakeLock.request("screen").then((lock) => {
wakeLock = lock;
wakeLockPending = false;
lock.addEventListener("release", () => {
// the wake lock has been released
wakeLock = null;
});
});
} catch (e) {
console.warn("askForWakeLock error", e);
}
}
}

View file

@ -1,44 +1,84 @@
// Settings
let cachedSettings: { [key: string]: unknown } = {};
import { toast } from "./toast";
export function getSettingValue<T>(key: string, defaultValue: T) {
if (typeof cachedSettings[key] == "undefined") {
let cachedSettings: { [key: string]: unknown } = {};
let warnedUserAboutLSIssue = false;
try {
for (let key in localStorage) {
try {
const ls = localStorage.getItem(key);
if (ls) cachedSettings[key] = JSON.parse(ls) as T;
cachedSettings[key] = JSON.parse(localStorage.getItem(key) || "null");
} catch (e) {
if (!warnedUserAboutLSIssue) {
warnedUserAboutLSIssue = true;
toast(`Storage issue : ${(e as Error)?.message}`);
}
console.warn(e);
}
}
} catch (e) {
console.warn(e);
}
export function getSettingValue<T>(key: string, defaultValue: T) {
return (cachedSettings[key] as T) ?? defaultValue;
}
// We avoid using localstorage synchronously for perf reasons
let needsSaving: Set<string> = new Set();
export function setSettingValue<T>(key: string, value: T) {
needsSaving.add(key);
cachedSettings[key] = value;
}
export function commitSettingsChangesToLocalStorage() {
try {
localStorage.setItem(key, JSON.stringify(value));
for (let key of needsSaving) {
localStorage.setItem(key, JSON.stringify(cachedSettings[key]));
}
needsSaving.clear();
} catch (e) {
if (!warnedUserAboutLSIssue) {
warnedUserAboutLSIssue = true;
toast(`Storage issue : ${(e as Error)?.message}`);
}
console.warn(e);
}
}
setInterval(() => commitSettingsChangesToLocalStorage(), 500);
export function getTotalScore() {
return getSettingValue("breakout_71_total_score", 0);
}
export function getCurrentMaxCoins() {
return Math.pow(2, getSettingValue("max_coins", 1)) * 200;
return Math.pow(2, getSettingValue("max_coins", 2)) * 200;
}
export function getCurrentMaxParticles() {
return Math.pow(2, getSettingValue("max_particles", 1)) * 200;
return getCurrentMaxCoins();
}
export function cycleMaxCoins() {
setSettingValue("max_coins", (getSettingValue("max_coins", 1) + 1) % 6);
setSettingValue("max_coins", (getSettingValue("max_coins", 2) + 1) % 7);
}
export function cycleMaxParticles() {
setSettingValue(
"max_particles",
(getSettingValue("max_particles", 1) + 1) % 6,
);
let asked = false;
export async function askForPersistentStorage() {
if (asked) return;
asked = true;
if (!navigator.storage) return;
if (!navigator.storage.persist) return;
if (!navigator.storage.persisted) return;
if (await navigator.storage.persisted()) {
return;
}
const persistent = await navigator.storage.persist();
if (!persistent) {
console.warn("No storage granted");
}
}

View file

@ -16,7 +16,8 @@ export function playPendingSounds(gameState: GameState) {
};
if (ex.vol) {
sounds[soundName](
Math.min(2, ex.vol),
// In stress test, dim the sounds but play them
Math.min(1, ex.vol),
pixelsToPan(gameState, ex.x),
gameState.combo,
);
@ -25,13 +26,21 @@ export function playPendingSounds(gameState: GameState) {
}
}
export const sounds = {
wallBeep: (vol: number, pan: number, combo: number) => {
wallBeep: (volume: number, pan: number) => {
if (!isOptionOn("sound")) return;
createSingleBounceSound(800, pan, vol);
createSingleBounceSound(800, pan, volume);
},
plouf: (volume: number, pan: number) => {
if (!isOptionOn("sound")) return;
createSingleBounceSound(500, pan, volume * 0.5);
// createWaterDropSound(800, pan, volume*0.2, 0.2,'triangle')
},
comboIncreaseMaybe: (volume: number, pan: number, combo: number) => {
if (!isOptionOn("sound")) return;
let delta = 0;
if (!isNaN(lastComboPlayed)) {
if (lastComboPlayed < combo) delta = 1;

View file

@ -1,17 +1,15 @@
let onScreen = 0;
export function toast(html) {
const div = document.createElement("div");
div.classList = "toast";
let div = document.createElement("div");
div.classList = "hidden toast";
document.body.appendChild(div);
let timeout: NodeJS.Timeout | undefined;
export function toast(html: string) {
div.classList = "toast visible";
div.innerHTML = html;
const lasts = 1500 + onScreen * 200;
div.style.animationDuration = lasts + "ms";
div.style.top = 40 + onScreen * 50 + "px";
document.body.appendChild(div);
onScreen++;
setTimeout(() => {
div.remove();
onScreen--;
}, lasts);
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
timeout = undefined;
div.classList = "hidden toast";
}, 1500);
}

9
src/types.d.ts vendored
View file

@ -7,7 +7,6 @@ export type RawLevel = {
name: string;
size: number;
bricks: string;
svg: number | null;
color: string;
credit?: string;
};
@ -277,19 +276,23 @@ export type GameState = {
explode: { vol: number; x: number };
lifeLost: { vol: number; x: number };
coinCatch: { vol: number; x: number };
plouf: { vol: number; x: number };
colorChange: { vol: number; x: number };
};
rerolls: number;
creative: boolean;
computer_controlled: boolean;
startParams: RunParams;
};
export type RunParams = {
level?: string;
level?: Level;
levelToAvoid?: string;
perkToAvoid?: PerkId;
perks?: Partial<PerksMap>;
computer_controlled?: boolean;
isEditorTrialRun?: number;
isCreativeRun?: boolean;
stress?: boolean;
};
export type OptionDef = {
default: boolean;

View file

@ -766,7 +766,8 @@ export const rawUpgrades = [
id: "transparency",
max: 3,
name: t("upgrades.transparency.name"),
help: (lvl: number) => t("upgrades.transparency.tooltip", { lvl }),
help: (lvl: number) =>
t("upgrades.transparency.tooltip", { lvl, percent: lvl * 50 }),
fullHelp: t("upgrades.transparency.verbose_description"),
},
{
@ -818,7 +819,7 @@ export const rawUpgrades = [
threshold: 215000,
gift: false,
id: "bricks_attract_ball",
max: 3,
max: 1,
name: t("upgrades.bricks_attract_ball.name"),
help: (lvl: number) =>
t("upgrades.bricks_attract_ball.tooltip", { count: lvl * 3 }),