mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-29 08:19:13 -04:00
Build 29095000
This commit is contained in:
parent
096f7d4abd
commit
4c324d211c
27 changed files with 500 additions and 1350 deletions
|
@ -30,6 +30,7 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades !
|
||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
|
- hardcoded the levels unlock conditions so that they wouldn't change at each update
|
||||||
- hide any tooltip on page scroll
|
- hide any tooltip on page scroll
|
||||||
- added a "display level code" button in editor
|
- added a "display level code" button in editor
|
||||||
- passive income : paddle transparent for a much shorter time
|
- passive income : paddle transparent for a much shorter time
|
||||||
|
@ -573,6 +574,7 @@ Here are a few interesting games in the breakout genre :
|
||||||
- Wizorb : https://store.steampowered.com/app/207420/Wizorb/
|
- Wizorb : https://store.steampowered.com/app/207420/Wizorb/
|
||||||
- Ricochet infinity : https://www.myabandonware.com/game/ricochet-infinity-dxm
|
- Ricochet infinity : https://www.myabandonware.com/game/ricochet-infinity-dxm
|
||||||
- Whackerball : https://store.steampowered.com/app/2192170/Whackerball/
|
- Whackerball : https://store.steampowered.com/app/2192170/Whackerball/
|
||||||
|
- Arkanoid Archive lists many, many more https://www.youtube.com/@ArkanoidGame
|
||||||
|
|
||||||
# PC game suggestions
|
# PC game suggestions
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ android {
|
||||||
applicationId = "me.lecaro.breakout"
|
applicationId = "me.lecaro.breakout"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 29092809
|
versionCode = 29095000
|
||||||
versionName = "29092809"
|
versionName = "29095000"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
|
|
File diff suppressed because one or more lines are too long
22
dist/index.html
vendored
22
dist/index.html
vendored
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
||||||
// The version of the cache.
|
// The version of the cache.
|
||||||
const VERSION = "29092809";
|
const VERSION = "29095000";
|
||||||
|
|
||||||
// The name of the cache
|
// The name of the cache
|
||||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
const CACHE_NAME = `breakout-71-${VERSION}`;
|
||||||
|
|
|
@ -9,15 +9,11 @@ import {
|
||||||
restart,
|
restart,
|
||||||
} from "./game";
|
} from "./game";
|
||||||
import { asyncAlert, requiredAsyncAlert } from "./asyncAlert";
|
import { asyncAlert, requiredAsyncAlert } from "./asyncAlert";
|
||||||
import {
|
import { describeLevel, highScoreText, sumOfValues } from "./game_utils";
|
||||||
describeLevel,
|
|
||||||
highScoreText,
|
|
||||||
sumOfValues,
|
|
||||||
} from "./game_utils";
|
|
||||||
import { getHistory } from "./gameOver";
|
import { getHistory } from "./gameOver";
|
||||||
import { noCreative } from "./upgrades";
|
import { noCreative } from "./upgrades";
|
||||||
import { levelIconHTML } from "./levelIcon";
|
import { levelIconHTML } from "./levelIcon";
|
||||||
import {reasonLevelIsLocked} from "./get_level_unlock_condition";
|
import { reasonLevelIsLocked } from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export function creativeMode(gameState: GameState) {
|
export function creativeMode(gameState: GameState) {
|
||||||
return {
|
return {
|
||||||
|
@ -46,7 +42,8 @@ export async function openCreativeModePerksPicker() {
|
||||||
while (true) {
|
while (true) {
|
||||||
const levelOptions = [
|
const levelOptions = [
|
||||||
...allLevels.map((l, li) => {
|
...allLevels.map((l, li) => {
|
||||||
const problem = reasonLevelIsLocked(li, l.name,getHistory(), true)?.text || "";
|
const problem =
|
||||||
|
reasonLevelIsLocked(li, l.name, getHistory(), true)?.text || "";
|
||||||
return {
|
return {
|
||||||
icon: icons[l.name],
|
icon: icons[l.name],
|
||||||
text: l.name,
|
text: l.name,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,38 +1,43 @@
|
||||||
import conditions from "./unlockConditions.json"
|
import conditions from "./unlockConditions.json";
|
||||||
import levels from "./levels.json"
|
import levels from "./levels.json";
|
||||||
import {rawUpgrades} from "../upgrades";
|
import { rawUpgrades } from "../upgrades";
|
||||||
import {getLevelUnlockCondition} from "../get_level_unlock_condition";
|
import { getLevelUnlockCondition } from "../get_level_unlock_condition";
|
||||||
import {UnlockCondition} from "../types";
|
import { UnlockCondition } from "../types";
|
||||||
|
|
||||||
describe("conditions", () => {
|
describe("conditions", () => {
|
||||||
it("defines conditions for existing levels only", () => {
|
it("defines conditions for existing levels only", () => {
|
||||||
const conditionForMissingLevel=Object.keys(conditions).filter(levelName=>!levels.find(l=>l.name===levelName))
|
const conditionForMissingLevel = Object.keys(conditions).filter(
|
||||||
expect(conditionForMissingLevel).toEqual([]);
|
(levelName) => !levels.find((l) => l.name === levelName),
|
||||||
});
|
);
|
||||||
it("defines conditions with existing upgrades only", () => {
|
expect(conditionForMissingLevel).toEqual([]);
|
||||||
|
});
|
||||||
const existingIds :Set<string>= new Set(rawUpgrades.map(u=>u.id));
|
it("defines conditions with existing upgrades only", () => {
|
||||||
const missing:Set<string>=new Set();
|
const existingIds: Set<string> = new Set(rawUpgrades.map((u) => u.id));
|
||||||
Object.values(conditions).forEach(({required,forbidden})=>{
|
const missing: Set<string> = new Set();
|
||||||
[...required,...forbidden].forEach(id=> {
|
Object.values(conditions).forEach(({ required, forbidden }) => {
|
||||||
if(!existingIds.has(id))
|
[...required, ...forbidden].forEach((id) => {
|
||||||
missing.add(id)
|
if (!existingIds.has(id)) missing.add(id);
|
||||||
})
|
});
|
||||||
})
|
|
||||||
|
|
||||||
expect([...missing]).toEqual([]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("defines conditions for all levels", () => {
|
expect([...missing]).toEqual([]);
|
||||||
const toAdd : Record<string,UnlockCondition>= {}
|
});
|
||||||
levels.filter(l=>!l.name.startsWith('icon:')).forEach((l,li)=> {
|
|
||||||
if(l.name in conditions) return
|
|
||||||
toAdd[l.name]= getLevelUnlockCondition(li, l.name)
|
|
||||||
})
|
|
||||||
if(Object.keys(toAdd).length){
|
|
||||||
console.debug('Missing hardcoded conditons\n\n'+ JSON.stringify(toAdd).slice(1,-1)+'\n\n')
|
|
||||||
}
|
|
||||||
expect(Object.keys(toAdd)).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
it("defines conditions for all levels", () => {
|
||||||
|
const toAdd: Record<string, UnlockCondition> = {};
|
||||||
|
levels
|
||||||
|
.filter((l) => !l.name.startsWith("icon:"))
|
||||||
|
.forEach((l, li) => {
|
||||||
|
if (l.name in conditions) return;
|
||||||
|
toAdd[l.name] = getLevelUnlockCondition(li, l.name);
|
||||||
|
});
|
||||||
|
if (Object.keys(toAdd).length) {
|
||||||
|
console.debug(
|
||||||
|
"Missing hardcoded conditons\n\n" +
|
||||||
|
JSON.stringify(toAdd).slice(1, -1) +
|
||||||
|
"\n\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expect(Object.keys(toAdd)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
"29092809"
|
"29095000"
|
||||||
|
|
|
@ -450,14 +450,12 @@ h2.histogram-title strong {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
&.desktop{
|
&.desktop {
|
||||||
|
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
|
|
||||||
}
|
}
|
||||||
&.mobile{
|
&.mobile {
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
left:2.5vw;
|
left: 2.5vw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ import { getNearestUnlockHTML, openScorePanel } from "./openScorePanel";
|
||||||
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
|
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
|
||||||
import { levelEditorMenuEntry } from "./levelEditor";
|
import { levelEditorMenuEntry } from "./levelEditor";
|
||||||
import { categories } from "./upgrades";
|
import { categories } from "./upgrades";
|
||||||
import {reasonLevelIsLocked} from "./get_level_unlock_condition";
|
import { reasonLevelIsLocked } from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export async function play() {
|
export async function play() {
|
||||||
if (await applyFullScreenChoice()) return;
|
if (await applyFullScreenChoice()) return;
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {
|
||||||
currentLevelInfo,
|
currentLevelInfo,
|
||||||
describeLevel,
|
describeLevel,
|
||||||
pickedUpgradesHTMl,
|
pickedUpgradesHTMl,
|
||||||
|
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
import {
|
import {
|
||||||
askForPersistentStorage,
|
askForPersistentStorage,
|
||||||
|
@ -18,7 +17,10 @@ import { stopRecording } from "./recording";
|
||||||
import { asyncAlert } from "./asyncAlert";
|
import { asyncAlert } from "./asyncAlert";
|
||||||
import { editRawLevelList } from "./levelEditor";
|
import { editRawLevelList } from "./levelEditor";
|
||||||
import { openCreativeModePerksPicker } from "./creative";
|
import { openCreativeModePerksPicker } from "./creative";
|
||||||
import {isLevelLocked, reasonLevelIsLocked} from "./get_level_unlock_condition";
|
import {
|
||||||
|
isLevelLocked,
|
||||||
|
reasonLevelIsLocked,
|
||||||
|
} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export function addToTotalPlayTime(ms: number) {
|
export function addToTotalPlayTime(ms: number) {
|
||||||
setSettingValue(
|
setSettingValue(
|
||||||
|
@ -140,7 +142,7 @@ export function getHistograms(gameState: GameState) {
|
||||||
.map((l, li) => ({
|
.map((l, li) => ({
|
||||||
li,
|
li,
|
||||||
l,
|
l,
|
||||||
r: reasonLevelIsLocked(li,l.name, runsHistory, false)?.text,
|
r: reasonLevelIsLocked(li, l.name, runsHistory, false)?.text,
|
||||||
}))
|
}))
|
||||||
.filter((l) => l.r);
|
.filter((l) => l.r);
|
||||||
|
|
||||||
|
@ -160,7 +162,7 @@ export function getHistograms(gameState: GameState) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const unlocked = locked.filter(
|
const unlocked = locked.filter(
|
||||||
({ li ,l}) => !isLevelLocked(li, l.name, runsHistory),
|
({ li, l }) => !isLevelLocked(li, l.name, runsHistory),
|
||||||
);
|
);
|
||||||
if (unlocked.length) {
|
if (unlocked.length) {
|
||||||
unlockedLevels = `
|
unlockedLevels = `
|
||||||
|
|
|
@ -1265,7 +1265,6 @@ export function gameStateTick(
|
||||||
// If you dont have buoy, we directly declare the coin "lost" to make it clear
|
// If you dont have buoy, we directly declare the coin "lost" to make it clear
|
||||||
resetCombo(gameState, coin.x, coin.y);
|
resetCombo(gameState, coin.x, coin.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -1659,7 +1658,7 @@ export function ballTick(gameState: GameState, ball: Ball, frames: number) {
|
||||||
speedLimitDampener += 3;
|
speedLimitDampener += 3;
|
||||||
|
|
||||||
ball.vx +=
|
ball.vx +=
|
||||||
(gameState.puckPosition > ball.x ? 1 :-1) *
|
(gameState.puckPosition > ball.x ? 1 : -1) *
|
||||||
frames *
|
frames *
|
||||||
yoyoEffectRate(gameState, ball);
|
yoyoEffectRate(gameState, ball);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {Ball, Coin, GameState, Level, PerkId, PerksMap,} from "./types";
|
import { Ball, Coin, GameState, Level, PerkId, PerksMap } from "./types";
|
||||||
import {icons, upgrades} from "./loadGameData";
|
import { icons, upgrades } from "./loadGameData";
|
||||||
import {t} from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
import {clamp} from "./pure_functions";
|
import { clamp } from "./pure_functions";
|
||||||
import {getSettingValue, getTotalScore} from "./settings";
|
import { getSettingValue, getTotalScore } from "./settings";
|
||||||
import {isOptionOn} from "./options";
|
import { isOptionOn } from "./options";
|
||||||
|
|
||||||
export function describeLevel(level: Level) {
|
export function describeLevel(level: Level) {
|
||||||
let bricks = 0,
|
let bricks = 0,
|
||||||
|
@ -192,10 +192,13 @@ export function telekinesisEffectRate(gameState: GameState, ball: Ball) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function yoyoEffectRate(gameState: GameState, ball: Ball) {
|
export function yoyoEffectRate(gameState: GameState, ball: Ball) {
|
||||||
if(ball.vy < 0) return 0
|
if (ball.vy < 0) return 0;
|
||||||
if(!gameState.perks.yoyo) return 0
|
if (!gameState.perks.yoyo) return 0;
|
||||||
return Math.abs(gameState.puckPosition - ball.x)/gameState.gameZoneWidth * gameState.perks.yoyo/2
|
return (
|
||||||
|
((Math.abs(gameState.puckPosition - ball.x) / gameState.gameZoneWidth) *
|
||||||
|
gameState.perks.yoyo) /
|
||||||
|
2
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findLast<T>(
|
export function findLast<T>(
|
||||||
|
|
|
@ -1,113 +1,123 @@
|
||||||
import {PerkId, RunHistoryItem, UnlockCondition} from "./types";
|
import { PerkId, RunHistoryItem, UnlockCondition } from "./types";
|
||||||
import {upgrades} from "./loadGameData";
|
import { upgrades } from "./loadGameData";
|
||||||
import {hashCode} from "./getLevelBackground";
|
import { hashCode } from "./getLevelBackground";
|
||||||
import {t} from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
|
|
||||||
import _hardCodedCondition from './data/unlockConditions.json'
|
import _hardCodedCondition from "./data/unlockConditions.json";
|
||||||
|
|
||||||
const hardCodedCondition = _hardCodedCondition as Record<string, UnlockCondition>
|
const hardCodedCondition = _hardCodedCondition as Record<
|
||||||
|
string,
|
||||||
|
UnlockCondition
|
||||||
|
>;
|
||||||
|
|
||||||
let excluded: Set<PerkId>;
|
let excluded: Set<PerkId>;
|
||||||
|
|
||||||
function isExcluded(id: PerkId) {
|
function isExcluded(id: PerkId) {
|
||||||
if (!excluded) {
|
if (!excluded) {
|
||||||
excluded = new Set([
|
excluded = new Set([
|
||||||
"extra_levels",
|
"extra_levels",
|
||||||
"extra_life",
|
"extra_life",
|
||||||
"one_more_choice",
|
"one_more_choice",
|
||||||
"shunt",
|
"shunt",
|
||||||
"slow_down",
|
"slow_down",
|
||||||
]);
|
]);
|
||||||
// Avoid excluding a perk that's needed for the required one
|
// Avoid excluding a perk that's needed for the required one
|
||||||
upgrades.forEach((u) => {
|
upgrades.forEach((u) => {
|
||||||
if (u.requires) excluded.add(u.requires);
|
if (u.requires) excluded.add(u.requires);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return excluded.has(id);
|
return excluded.has(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLevelUnlockCondition(levelIndex: number, levelName:string):UnlockCondition {
|
export function getLevelUnlockCondition(
|
||||||
|
levelIndex: number,
|
||||||
|
levelName: string,
|
||||||
|
): UnlockCondition {
|
||||||
|
if (hardCodedCondition[levelName]) return hardCodedCondition[levelName];
|
||||||
|
const result: UnlockCondition = {
|
||||||
|
required: [],
|
||||||
|
forbidden: [],
|
||||||
|
minScore: Math.max(-1000 + 100 * levelIndex, 0),
|
||||||
|
};
|
||||||
|
|
||||||
if(hardCodedCondition[levelName]) return hardCodedCondition[levelName]
|
if (levelIndex > 20) {
|
||||||
const result :UnlockCondition = {
|
const possibletargets = [...upgrades]
|
||||||
required:[],
|
.slice(0, Math.floor(levelIndex / 2))
|
||||||
forbidden:[],
|
.filter((u) => !isExcluded(u.id))
|
||||||
minScore : Math.max(-1000 + 100 * levelIndex, 0)
|
.sort((a, b) => hashCode(levelIndex + a.id) - hashCode(levelIndex + b.id))
|
||||||
}
|
.map((u) => u.id);
|
||||||
|
|
||||||
if (levelIndex > 20) {
|
const length = Math.min(3, Math.ceil(levelIndex / 30));
|
||||||
const possibletargets = [...upgrades]
|
result.required = possibletargets.slice(0, length);
|
||||||
.slice(0, Math.floor(levelIndex / 2))
|
result.forbidden = possibletargets.slice(length, length + length);
|
||||||
.filter((u) => !isExcluded(u.id))
|
}
|
||||||
.sort(
|
return result;
|
||||||
(a, b) => hashCode(levelIndex + a.id) - hashCode(levelIndex + b.id),
|
|
||||||
).map(u => u.id);
|
|
||||||
|
|
||||||
const length = Math.min(3, Math.ceil(levelIndex / 30));
|
|
||||||
result.required = possibletargets.slice(0, length);
|
|
||||||
result.forbidden = possibletargets.slice(length, length + length);
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBestScoreMatching(
|
export function getBestScoreMatching(
|
||||||
history: RunHistoryItem[],
|
history: RunHistoryItem[],
|
||||||
required: PerkId[] = [],
|
required: PerkId[] = [],
|
||||||
forbidden: PerkId[] = [],
|
forbidden: PerkId[] = [],
|
||||||
) {
|
) {
|
||||||
return Math.max(
|
return Math.max(
|
||||||
0,
|
0,
|
||||||
...history
|
...history
|
||||||
.filter(
|
.filter(
|
||||||
(r) =>
|
(r) =>
|
||||||
!required.find((id) => !r?.perks?.[id]) &&
|
!required.find((id) => !r?.perks?.[id]) &&
|
||||||
!forbidden.find((id) => r?.perks?.[id]),
|
!forbidden.find((id) => r?.perks?.[id]),
|
||||||
)
|
)
|
||||||
.map((r) => r.score),
|
.map((r) => r.score),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLevelLocked(
|
export function isLevelLocked(
|
||||||
levelIndex: number,
|
levelIndex: number,
|
||||||
levelName:string,
|
levelName: string,
|
||||||
history: RunHistoryItem[]){
|
history: RunHistoryItem[],
|
||||||
const {required, forbidden, minScore} = getLevelUnlockCondition(levelIndex, levelName);
|
) {
|
||||||
return getBestScoreMatching(history, required, forbidden) < minScore
|
const { required, forbidden, minScore } = getLevelUnlockCondition(
|
||||||
|
levelIndex,
|
||||||
|
levelName,
|
||||||
|
);
|
||||||
|
return getBestScoreMatching(history, required, forbidden) < minScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reasonLevelIsLocked(
|
export function reasonLevelIsLocked(
|
||||||
levelIndex: number,levelName:string,
|
levelIndex: number,
|
||||||
history: RunHistoryItem[],
|
levelName: string,
|
||||||
mentionBestScore: boolean,
|
history: RunHistoryItem[],
|
||||||
|
mentionBestScore: boolean,
|
||||||
): null | { reached: number; minScore: number; text: string } {
|
): null | { reached: number; minScore: number; text: string } {
|
||||||
|
const { required, forbidden, minScore } = getLevelUnlockCondition(
|
||||||
const {required, forbidden, minScore} = getLevelUnlockCondition(levelIndex, levelName);
|
levelIndex,
|
||||||
const reached = getBestScoreMatching(history, required, forbidden);
|
levelName,
|
||||||
let reachedText =
|
);
|
||||||
reached && mentionBestScore ? t("unlocks.reached", {reached}) : "";
|
const reached = getBestScoreMatching(history, required, forbidden);
|
||||||
if (reached >= minScore) {
|
let reachedText =
|
||||||
return null;
|
reached && mentionBestScore ? t("unlocks.reached", { reached }) : "";
|
||||||
} else if (!required.length && !forbidden.length) {
|
if (reached >= minScore) {
|
||||||
return {
|
return null;
|
||||||
reached,
|
} else if (!required.length && !forbidden.length) {
|
||||||
minScore,
|
return {
|
||||||
text: t("unlocks.minScore", {minScore}) + reachedText,
|
reached,
|
||||||
};
|
minScore,
|
||||||
} else {
|
text: t("unlocks.minScore", { minScore }) + reachedText,
|
||||||
return {
|
};
|
||||||
reached,
|
} else {
|
||||||
minScore,
|
return {
|
||||||
text:
|
reached,
|
||||||
t("unlocks.minScoreWithPerks", {
|
minScore,
|
||||||
minScore,
|
text:
|
||||||
required: required.map((u) => upgradeName(u)).join(", "),
|
t("unlocks.minScoreWithPerks", {
|
||||||
forbidden: forbidden.map((u) => upgradeName(u)).join(", "),
|
minScore,
|
||||||
}) + reachedText,
|
required: required.map((u) => upgradeName(u)).join(", "),
|
||||||
};
|
forbidden: forbidden.map((u) => upgradeName(u)).join(", "),
|
||||||
}
|
}) + reachedText,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function upgradeName(id:PerkId){
|
export function upgradeName(id: PerkId) {
|
||||||
return upgrades.find(u=>u.id==id)!.name
|
return upgrades.find((u) => u.id == id)!.name;
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ import {
|
||||||
MAX_LEVEL_SIZE,
|
MAX_LEVEL_SIZE,
|
||||||
MIN_LEVEL_SIZE,
|
MIN_LEVEL_SIZE,
|
||||||
} from "./pure_functions";
|
} from "./pure_functions";
|
||||||
import {toast} from "./toast";
|
import { toast } from "./toast";
|
||||||
|
|
||||||
const palette = _palette as Palette;
|
const palette = _palette as Palette;
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ export async function editRawLevelList(nth: number, color = "W") {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (action === "copy" || action ==='show_code') {
|
if (action === "copy" || action === "show_code") {
|
||||||
let text =
|
let text =
|
||||||
"```\n[" +
|
"```\n[" +
|
||||||
(level.name || "unnamed level")?.replace(/\[|\]/gi, " ") +
|
(level.name || "unnamed level")?.replace(/\[|\]/gi, " ") +
|
||||||
|
@ -249,21 +249,23 @@ export async function editRawLevelList(nth: number, color = "W") {
|
||||||
"]\n```";
|
"]\n```";
|
||||||
|
|
||||||
if (action === "copy") {
|
if (action === "copy") {
|
||||||
try{
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
toast(t('editor.editing.copied'))
|
toast(t("editor.editing.copied"));
|
||||||
}catch (e){
|
} catch (e) {
|
||||||
if('message' in e) {
|
if ("message" in e) {
|
||||||
toast(e.message)
|
toast(e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
await asyncAlert({
|
await asyncAlert({
|
||||||
title:t('editor.editing.show_code'),
|
title: t("editor.editing.show_code"),
|
||||||
content:[`
|
content: [
|
||||||
|
`
|
||||||
<pre>${text}</pre>
|
<pre>${text}</pre>
|
||||||
`]
|
`,
|
||||||
})
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// return
|
// return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Palette, RawLevel } from "../types";
|
import { Palette, RawLevel } from "../types";
|
||||||
import _palette from "../data/palette.json";
|
import _palette from "../data/palette.json";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
|
@ -4,7 +4,10 @@ import _appVersion from "./data/version.json";
|
||||||
import { generateSaveFileContent } from "./generateSaveFileContent";
|
import { generateSaveFileContent } from "./generateSaveFileContent";
|
||||||
import { allLevels } from "./loadGameData";
|
import { allLevels } from "./loadGameData";
|
||||||
import { toast } from "./toast";
|
import { toast } from "./toast";
|
||||||
import {isLevelLocked, reasonLevelIsLocked} from "./get_level_unlock_condition";
|
import {
|
||||||
|
isLevelLocked,
|
||||||
|
reasonLevelIsLocked,
|
||||||
|
} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
// The page will be reloaded if any migrations were run
|
// The page will be reloaded if any migrations were run
|
||||||
let migrationsRun = 0;
|
let migrationsRun = 0;
|
||||||
|
@ -128,7 +131,7 @@ migrate("set_breakout_71_unlocked_levels" + _appVersion, () => {
|
||||||
) as string[];
|
) as string[];
|
||||||
|
|
||||||
allLevels
|
allLevels
|
||||||
.filter((l, li) => !isLevelLocked(li,l.name, runsHistory))
|
.filter((l, li) => !isLevelLocked(li, l.name, runsHistory))
|
||||||
.forEach((l) => {
|
.forEach((l) => {
|
||||||
if (!breakout_71_unlocked_levels.includes(l.name)) {
|
if (!breakout_71_unlocked_levels.includes(l.name)) {
|
||||||
breakout_71_unlocked_levels.push(l.name);
|
breakout_71_unlocked_levels.push(l.name);
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import {GameState, PerkId} from "./types";
|
import { GameState, PerkId } from "./types";
|
||||||
import { getSettingValue, setSettingValue } from "./settings";
|
import { getSettingValue, setSettingValue } from "./settings";
|
||||||
import { allLevels, icons } from "./loadGameData";
|
import { allLevels, icons } from "./loadGameData";
|
||||||
|
|
||||||
import { t } from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
import { toast } from "./toast";
|
import { toast } from "./toast";
|
||||||
import { schedulGameSound } from "./gameStateMutators";
|
import { schedulGameSound } from "./gameStateMutators";
|
||||||
import {getLevelUnlockCondition} from "./get_level_unlock_condition";
|
import { getLevelUnlockCondition } from "./get_level_unlock_condition";
|
||||||
|
|
||||||
let list: {
|
let list: {
|
||||||
minScore: number;
|
minScore: number;
|
||||||
forbidden: PerkId[];
|
forbidden: PerkId[];
|
||||||
required: PerkId[];
|
required: PerkId[];
|
||||||
}[];
|
}[];
|
||||||
let unlocked : Set<string> |null = null
|
let unlocked: Set<string> | null = null;
|
||||||
|
|
||||||
export function monitorLevelsUnlocks(gameState: GameState) {
|
export function monitorLevelsUnlocks(gameState: GameState) {
|
||||||
if(!unlocked){
|
if (!unlocked) {
|
||||||
unlocked = new Set(
|
unlocked = new Set(
|
||||||
getSettingValue("breakout_71_unlocked_levels", []) as string[],
|
getSettingValue("breakout_71_unlocked_levels", []) as string[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,10 @@ import { isOptionOn } from "./options";
|
||||||
import { getHistory } from "./gameOver";
|
import { getHistory } from "./gameOver";
|
||||||
import { getSettingValue, getTotalScore } from "./settings";
|
import { getSettingValue, getTotalScore } from "./settings";
|
||||||
import { isBlackListedForStart, isStartingPerk } from "./startingPerks";
|
import { isBlackListedForStart, isStartingPerk } from "./startingPerks";
|
||||||
import {isLevelLocked, reasonLevelIsLocked} from "./get_level_unlock_condition";
|
import {
|
||||||
|
isLevelLocked,
|
||||||
|
reasonLevelIsLocked,
|
||||||
|
} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export function getRunLevels(
|
export function getRunLevels(
|
||||||
params: RunParams,
|
params: RunParams,
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { GameState } from "./types";
|
import { GameState } from "./types";
|
||||||
import { asyncAlert } from "./asyncAlert";
|
import { asyncAlert } from "./asyncAlert";
|
||||||
import { t } from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
import {
|
import { levelsListHTMl, max_levels, pickedUpgradesHTMl } from "./game_utils";
|
||||||
levelsListHTMl,
|
|
||||||
max_levels,
|
|
||||||
pickedUpgradesHTMl,
|
|
||||||
} from "./game_utils";
|
|
||||||
import { getCreativeModeWarning, getHistory } from "./gameOver";
|
import { getCreativeModeWarning, getHistory } from "./gameOver";
|
||||||
import { pause } from "./game";
|
import { pause } from "./game";
|
||||||
import { allLevels, icons } from "./loadGameData";
|
import { allLevels, icons } from "./loadGameData";
|
||||||
import { firstWhere } from "./pure_functions";
|
import { firstWhere } from "./pure_functions";
|
||||||
import { getSettingValue, getTotalScore } from "./settings";
|
import { getSettingValue, getTotalScore } from "./settings";
|
||||||
import {getLevelUnlockCondition, reasonLevelIsLocked, upgradeName} from "./get_level_unlock_condition";
|
import {
|
||||||
|
getLevelUnlockCondition,
|
||||||
|
reasonLevelIsLocked,
|
||||||
|
upgradeName,
|
||||||
|
} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export async function openScorePanel(gameState: GameState) {
|
export async function openScorePanel(gameState: GameState) {
|
||||||
pause(true);
|
pause(true);
|
||||||
|
@ -41,10 +41,13 @@ export function getNearestUnlockHTML(gameState: GameState) {
|
||||||
const unlocked = new Set(getSettingValue("breakout_71_unlocked_levels", []));
|
const unlocked = new Set(getSettingValue("breakout_71_unlocked_levels", []));
|
||||||
const firstUnlockable = firstWhere(allLevels, (l, li) => {
|
const firstUnlockable = firstWhere(allLevels, (l, li) => {
|
||||||
if (unlocked.has(l.name)) return;
|
if (unlocked.has(l.name)) return;
|
||||||
const reason = reasonLevelIsLocked(li, l.name, getHistory(), false);
|
const reason = reasonLevelIsLocked(li, l.name, getHistory(), false);
|
||||||
if (!reason) return;
|
if (!reason) return;
|
||||||
|
|
||||||
const { minScore, forbidden, required } = getLevelUnlockCondition(li, l.name);
|
const { minScore, forbidden, required } = getLevelUnlockCondition(
|
||||||
|
li,
|
||||||
|
l.name,
|
||||||
|
);
|
||||||
const missing = required.filter((id) => !gameState?.perks?.[id]);
|
const missing = required.filter((id) => !gameState?.perks?.[id]);
|
||||||
// we can't have a forbidden perk
|
// we can't have a forbidden perk
|
||||||
if (forbidden.find((id) => gameState?.perks?.[id])) {
|
if (forbidden.find((id) => gameState?.perks?.[id])) {
|
||||||
|
@ -69,7 +72,9 @@ export function getNearestUnlockHTML(gameState: GameState) {
|
||||||
|
|
||||||
if (!firstUnlockable) return "";
|
if (!firstUnlockable) return "";
|
||||||
let missingPoints = Math.max(0, firstUnlockable.minScore - gameState.score);
|
let missingPoints = Math.max(0, firstUnlockable.minScore - gameState.score);
|
||||||
let missingUpgrades = firstUnlockable.missing.map((id) => upgradeName(id)).join(", ");
|
let missingUpgrades = firstUnlockable.missing
|
||||||
|
.map((id) => upgradeName(id))
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
const title =
|
const title =
|
||||||
(missingUpgrades &&
|
(missingUpgrades &&
|
||||||
|
|
|
@ -12,8 +12,10 @@ export const options = {
|
||||||
help: t("settings.sounds_help"),
|
help: t("settings.sounds_help"),
|
||||||
},
|
},
|
||||||
"mobile-mode": {
|
"mobile-mode": {
|
||||||
default: window.innerHeight > window.innerWidth ||('ontouchstart' in window) ||
|
default:
|
||||||
(navigator.maxTouchPoints > 0) ,
|
window.innerHeight > window.innerWidth ||
|
||||||
|
"ontouchstart" in window ||
|
||||||
|
navigator.maxTouchPoints > 0,
|
||||||
name: t("settings.mobile"),
|
name: t("settings.mobile"),
|
||||||
help: t("settings.mobile_help"),
|
help: t("settings.mobile_help"),
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,8 @@ import { isOptionOn } from "./options";
|
||||||
import {
|
import {
|
||||||
ballTransparency,
|
ballTransparency,
|
||||||
catchRateBest,
|
catchRateBest,
|
||||||
catchRateGood, clamp,
|
catchRateGood,
|
||||||
|
clamp,
|
||||||
coinsBoostedCombo,
|
coinsBoostedCombo,
|
||||||
levelTimeBest,
|
levelTimeBest,
|
||||||
levelTimeGood,
|
levelTimeGood,
|
||||||
|
@ -401,11 +402,14 @@ export function render(gameState: GameState) {
|
||||||
) {
|
) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(gameState.puckPosition, gameState.gameZoneHeight);
|
ctx.moveTo(gameState.puckPosition, gameState.gameZoneHeight);
|
||||||
ctx.globalAlpha =clamp(
|
ctx.globalAlpha = clamp(
|
||||||
Math.max(
|
Math.max(
|
||||||
telekinesisEffectRate(gameState, ball),
|
telekinesisEffectRate(gameState, ball),
|
||||||
yoyoEffectRate(gameState, ball),
|
yoyoEffectRate(gameState, ball),
|
||||||
) * ballAlpha,0,1);
|
) * ballAlpha,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
ctx.strokeStyle = gameState.puckColor;
|
ctx.strokeStyle = gameState.puckColor;
|
||||||
ctx.bezierCurveTo(
|
ctx.bezierCurveTo(
|
||||||
gameState.puckPosition,
|
gameState.puckPosition,
|
||||||
|
|
|
@ -14,9 +14,9 @@ export function hideAnyTooltip() {
|
||||||
const tooltip = document.getElementById("tooltip") as HTMLDivElement;
|
const tooltip = document.getElementById("tooltip") as HTMLDivElement;
|
||||||
|
|
||||||
function setupMobileTooltips(tooltip: HTMLDivElement) {
|
function setupMobileTooltips(tooltip: HTMLDivElement) {
|
||||||
tooltip.className='mobile'
|
tooltip.className = "mobile";
|
||||||
function openTooltip(e: Event) {
|
function openTooltip(e: Event) {
|
||||||
hideAnyTooltip()
|
hideAnyTooltip();
|
||||||
const hovering = e.target as HTMLElement;
|
const hovering = e.target as HTMLElement;
|
||||||
if (!hovering?.hasAttribute("data-help-content")) {
|
if (!hovering?.hasAttribute("data-help-content")) {
|
||||||
return;
|
return;
|
||||||
|
@ -25,9 +25,8 @@ function setupMobileTooltips(tooltip: HTMLDivElement) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
tooltip.innerHTML = hovering.getAttribute("data-help-content") || "";
|
tooltip.innerHTML = hovering.getAttribute("data-help-content") || "";
|
||||||
tooltip.style.display = "";
|
tooltip.style.display = "";
|
||||||
const { top } = hovering.getBoundingClientRect();
|
const { top } = hovering.getBoundingClientRect();
|
||||||
tooltip.style.transform = `translate(0,${top}px) translate(0,-100%)`;
|
tooltip.style.transform = `translate(0,${top}px) translate(0,-100%)`;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.addEventListener("touchstart", openTooltip, true);
|
document.body.addEventListener("touchstart", openTooltip, true);
|
||||||
|
@ -62,7 +61,7 @@ function setupMobileTooltips(tooltip: HTMLDivElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupDesktopTooltips(tooltip: HTMLDivElement) {
|
function setupDesktopTooltips(tooltip: HTMLDivElement) {
|
||||||
tooltip.className='desktop'
|
tooltip.className = "desktop";
|
||||||
function updateTooltipPosition(e: { clientX: number; clientY: number }) {
|
function updateTooltipPosition(e: { clientX: number; clientY: number }) {
|
||||||
tooltip.style.transform = `translate(${e.clientX}px,${e.clientY}px) translate(${e.clientX > window.innerWidth / 2 ? "-100%" : "0"},${e.clientY > (window.innerHeight * 2) / 3 ? "-100%" : "20px"})`;
|
tooltip.style.transform = `translate(${e.clientX}px,${e.clientY}px) translate(${e.clientX > window.innerWidth / 2 ? "-100%" : "0"},${e.clientY > (window.innerHeight * 2) / 3 ? "-100%" : "20px"})`;
|
||||||
}
|
}
|
||||||
|
|
19
src/types.d.ts
vendored
19
src/types.d.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
import {rawUpgrades} from "./upgrades";
|
import { rawUpgrades } from "./upgrades";
|
||||||
import {options} from "./options";
|
import { options } from "./options";
|
||||||
|
|
||||||
export type colorString = string;
|
export type colorString = string;
|
||||||
|
|
||||||
|
@ -301,14 +301,9 @@ export type OptionDef = {
|
||||||
help: string;
|
help: string;
|
||||||
};
|
};
|
||||||
export type OptionId = keyof typeof options;
|
export type OptionId = keyof typeof options;
|
||||||
export type UpgradeLike = {
|
|
||||||
id: PerkId;
|
|
||||||
name: string;
|
|
||||||
requires: string;
|
|
||||||
threshold: number;
|
|
||||||
};
|
|
||||||
export type UnlockCondition = {
|
export type UnlockCondition = {
|
||||||
required: PerkId[];
|
required: PerkId[];
|
||||||
forbidden: PerkId[];
|
forbidden: PerkId[];
|
||||||
minScore: number;
|
minScore: number;
|
||||||
}
|
};
|
||||||
|
|
|
@ -926,10 +926,10 @@ export const rawUpgrades = [
|
||||||
max: 4,
|
max: 4,
|
||||||
name: t("upgrades.passive_income.name"),
|
name: t("upgrades.passive_income.name"),
|
||||||
help: (lvl: number) =>
|
help: (lvl: number) =>
|
||||||
t("upgrades.passive_income.tooltip", { time: lvl * 0.10-0.05, lvl }),
|
t("upgrades.passive_income.tooltip", { time: lvl * 0.1 - 0.05, lvl }),
|
||||||
fullHelp: (lvl: number) =>
|
fullHelp: (lvl: number) =>
|
||||||
t("upgrades.passive_income.verbose_description", {
|
t("upgrades.passive_income.verbose_description", {
|
||||||
time: lvl * 0.10-0.05,
|
time: lvl * 0.1 - 0.05,
|
||||||
lvl,
|
lvl,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue