- 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
135 lines
4.6 KiB
JavaScript
135 lines
4.6 KiB
JavaScript
/* ============================================================
|
|
TAB SHOOTER — Popup (Overlay) Controller
|
|
Nutzt die geteilte engine.js.
|
|
============================================================ */
|
|
|
|
import { TabShooterGame, loadTabs, clamp } from "./engine.js";
|
|
|
|
const CANVAS_W = 420;
|
|
const CANVAS_H = 600;
|
|
|
|
// ---------- 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";
|
|
|
|
// Merke dir den Tab, von dem aus das Popup geöffnet wurde (den aktiven Tab
|
|
// im zuletzt fokussierten Fenster). Wird dieser Tab geschlossen, reißt Chrome
|
|
// auch das Popup mit sich -> Spiel beendet sich ohne Vorwarnung. Wir schließen
|
|
// ihn daher per excludeTabId von der Zielliste aus.
|
|
let openerTabId = null;
|
|
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
|
if (tabs && tabs[0]) openerTabId = tabs[0].id;
|
|
});
|
|
|
|
// ---------- 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() {
|
|
// Den Opener-Tab (aktuellen Tab, der das Popup trägt) nie als Ziel zulassen,
|
|
// da sein Schließen das Popup und damit das Spiel sofort beenden würde.
|
|
const tabs = await loadTabs(selectedScope, openerTabId);
|
|
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");
|
|
});
|
|
|
|
// ---------- FULLSCREEN BUTTON ----------
|
|
document.getElementById("fullscreen-btn").addEventListener("click", async () => {
|
|
// Open the fullscreen game in a new tab. The popup will close automatically.
|
|
await chrome.tabs.create({ url: chrome.runtime.getURL("game.html") });
|
|
window.close();
|
|
});
|
|
|
|
// ---------- INIT ----------
|
|
loadHighscore();
|