tab-shooter/popup.js
Janik Dietz 25fc6fb345
feat: add fullscreen mode and extract shared game engine
- 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
2026-08-04 08:02:53 +02:00

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();