- Add new fullscreen game mode playable in a dedicated browser tab - Extract shared game logic into `engine.js` to be used by both popup and fullscreen modes - Implement self-protection to prevent closing the game tab or popup tab - Update documentation and bump version to 1.1.0
127 lines
4.2 KiB
JavaScript
127 lines
4.2 KiB
JavaScript
/* ============================================================
|
||
TAB SHOOTER — Vollbild-Controller (game.html)
|
||
Läuft als eigener Tab. Dieser Tab wird über seine ID
|
||
von der Zielliste ausgeschlossen -> er lässt sich NICHT abschießen.
|
||
============================================================ */
|
||
|
||
import { TabShooterGame, loadTabs } from "./engine.js";
|
||
|
||
// ---------- DOM ----------
|
||
const canvas = document.getElementById("game-canvas");
|
||
const screens = {
|
||
start: document.getElementById("start-screen"),
|
||
game: document.getElementById("game-screen"),
|
||
gameover: document.getElementById("gameover-screen"),
|
||
empty: document.getElementById("empty-screen"),
|
||
};
|
||
const els = {
|
||
hsValue: document.getElementById("hs-value"),
|
||
hudScore: document.getElementById("hud-score"),
|
||
hudLives: document.getElementById("hud-lives"),
|
||
hudTabs: document.getElementById("hud-tabs"),
|
||
hudPowerups: document.getElementById("hud-powerups"),
|
||
resultTabs: document.getElementById("result-tabs"),
|
||
resultScore: document.getElementById("result-score"),
|
||
resultHighscore: document.getElementById("result-highscore"),
|
||
newRecord: document.getElementById("new-record"),
|
||
};
|
||
|
||
// ---------- STATE ----------
|
||
let game = null;
|
||
let highscore = 0;
|
||
let selectedScope = "current";
|
||
let myTabId = null;
|
||
|
||
// Eigene Tab-ID herausfinden – damit wir sie aus der Zielliste streichen können.
|
||
(async () => {
|
||
try {
|
||
const [tab] = await chrome.tabs.getCurrent();
|
||
if (tab) myTabId = tab.id;
|
||
} catch (_) {
|
||
// getCurrent() schlägt fehlt, wenn wir gar kein Tab sind (sollte nicht passieren)
|
||
}
|
||
})();
|
||
|
||
// ---------- SCREEN HELPERS ----------
|
||
function showScreen(name) {
|
||
Object.values(screens).forEach((s) => s.classList.remove("visible"));
|
||
screens[name].classList.add("visible");
|
||
}
|
||
|
||
// ---------- HIGHSCORE ----------
|
||
async function loadHighscore() {
|
||
const data = await chrome.storage.local.get("highscore");
|
||
highscore = data.highscore || 0;
|
||
els.hsValue.textContent = highscore;
|
||
}
|
||
async function saveHighscore(value) {
|
||
await chrome.storage.local.set({ highscore: value });
|
||
highscore = value;
|
||
}
|
||
|
||
// ---------- HUD CALLBACKS ----------
|
||
const hudCallbacks = {
|
||
onScore: (s) => (els.hudScore.textContent = s),
|
||
onLives: (l) => (els.hudLives.textContent = "❤️".repeat(Math.max(0, l))),
|
||
onTabs: (t) => (els.hudTabs.textContent = t),
|
||
onPowerups: (g) => {
|
||
let html = "";
|
||
if (g.shield > 0) html += "🛡️";
|
||
if (g.tripleShot > 0) html += "🔫";
|
||
if (g.rapidFire > 0) html += "⚡";
|
||
els.hudPowerups.innerHTML = html;
|
||
},
|
||
onGameOver: ({ score }) => {
|
||
const isNewRecord = score > highscore;
|
||
if (isNewRecord) saveHighscore(score);
|
||
els.resultTabs.textContent = game.tabsDestroyed;
|
||
els.resultScore.textContent = score;
|
||
els.resultHighscore.textContent = Math.max(highscore, score);
|
||
els.newRecord.classList.toggle("hidden", !isNewRecord);
|
||
showScreen("gameover");
|
||
},
|
||
};
|
||
|
||
// ---------- GAME START ----------
|
||
async function startNewGame() {
|
||
// WICHTIG: myTabId wird ausgeschlossen, damit dieser Tab kein Ziel ist.
|
||
const tabs = await loadTabs(selectedScope, myTabId);
|
||
if (tabs.length === 0) {
|
||
showScreen("empty");
|
||
return;
|
||
}
|
||
showScreen("game");
|
||
els.hudScore.textContent = "0";
|
||
els.hudLives.textContent = "❤️❤️❤️";
|
||
els.hudTabs.textContent = tabs.length;
|
||
els.hudPowerups.innerHTML = "";
|
||
|
||
game = new TabShooterGame(canvas, {}, hudCallbacks);
|
||
await game.init(tabs);
|
||
game.start();
|
||
}
|
||
|
||
// ---------- EVENT WIRING ----------
|
||
document.querySelectorAll(".scope-btn").forEach((btn) => {
|
||
btn.addEventListener("click", () => {
|
||
document.querySelectorAll(".scope-btn").forEach((b) => b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
selectedScope = btn.dataset.scope;
|
||
});
|
||
});
|
||
|
||
document.getElementById("start-btn").addEventListener("click", startNewGame);
|
||
document.getElementById("replay-btn").addEventListener("click", startNewGame);
|
||
|
||
document.getElementById("menu-btn").addEventListener("click", () => {
|
||
if (game) game.stop();
|
||
loadHighscore();
|
||
showScreen("start");
|
||
});
|
||
document.getElementById("empty-menu-btn").addEventListener("click", () => {
|
||
loadHighscore();
|
||
showScreen("start");
|
||
});
|
||
|
||
// ---------- INIT ----------
|
||
loadHighscore();
|