Compare commits
No commits in common. "e9306a2502d0754f31455551002c8744199d4385" and "c2025e0ca26bcf600ad43dc427bdddc78a08c44a" have entirely different histories.
e9306a2502
...
c2025e0ca2
@ -12,7 +12,6 @@ A fully original Bomberman-style browser game built from scratch.
|
|||||||
- Startup menu with mode selection (`Single Player` / `Multiplayer`) and music toggle
|
- Startup menu with mode selection (`Single Player` / `Multiplayer`) and music toggle
|
||||||
- Multiplayer name setting from menu (`Name: ...`) with local persistence
|
- Multiplayer name setting from menu (`Name: ...`) with local persistence
|
||||||
- Multiplayer lobby with host-controlled start (can start with 1-4 players)
|
- Multiplayer lobby with host-controlled start (can start with 1-4 players)
|
||||||
- Lobby shows when a match is currently running; waiting players auto-join the next round
|
|
||||||
- After each multiplayer round, host can start the next round directly (no lobby required)
|
- After each multiplayer round, host can start the next round directly (no lobby required)
|
||||||
- Host-authoritative multiplayer sync via WebSocket backend
|
- Host-authoritative multiplayer sync via WebSocket backend
|
||||||
|
|
||||||
|
|||||||
12
server.js
12
server.js
@ -211,18 +211,6 @@ function handleMessage(client, msg) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.type === "lobby_next_round") {
|
|
||||||
if (lobby.phase !== "game") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (client.id !== lobby.hostId) {
|
|
||||||
send(client.ws, { type: "error", message: "Only the host can start the next round." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
startMatch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.type === "player_input") {
|
if (msg.type === "player_input") {
|
||||||
if (lobby.phase !== "game") {
|
if (lobby.phase !== "game") {
|
||||||
return;
|
return;
|
||||||
|
|||||||
203
src/game.js
203
src/game.js
@ -86,10 +86,6 @@ const state = {
|
|||||||
lobbyMenu: {
|
lobbyMenu: {
|
||||||
selectedIndex: 0,
|
selectedIndex: 0,
|
||||||
},
|
},
|
||||||
notification: {
|
|
||||||
text: "",
|
|
||||||
expiresAt: 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const images = {};
|
const images = {};
|
||||||
@ -127,12 +123,10 @@ const network = {
|
|||||||
inputStateAtClient: {
|
inputStateAtClient: {
|
||||||
dir: null,
|
dir: null,
|
||||||
bomb: false,
|
bomb: false,
|
||||||
bombCell: null,
|
|
||||||
},
|
},
|
||||||
lastInputSentAt: 0,
|
lastInputSentAt: 0,
|
||||||
activeRoster: [],
|
activeRoster: [],
|
||||||
lastPingAt: 0,
|
lastPingAt: 0,
|
||||||
lobbyPhase: "lobby",
|
|
||||||
hasReceivedSnapshot: false,
|
hasReceivedSnapshot: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -209,14 +203,6 @@ function now() {
|
|||||||
return performance.now() / 1000;
|
return performance.now() / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMatchNotification(text, duration = 3) {
|
|
||||||
if (typeof text !== "string" || !text.trim()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.notification.text = text.trim();
|
|
||||||
state.notification.expiresAt = now() + duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadImage(path) {
|
function loadImage(path) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
@ -445,8 +431,19 @@ function restartMultiplayerRoundAsHost() {
|
|||||||
if (state.mode !== "multiplayer" || state.screen !== "game" || !network.isHost) {
|
if (state.mode !== "multiplayer" || state.screen !== "game" || !network.isHost) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendSocketMessage("lobby_next_round");
|
const roster = state.players
|
||||||
state.message = "Starting next round...";
|
.filter((player) => typeof player.ownerId === "string" && player.ownerId)
|
||||||
|
.map((player) => ({
|
||||||
|
id: player.ownerId,
|
||||||
|
name: player.name,
|
||||||
|
}));
|
||||||
|
if (roster.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
network.activeRoster = roster.slice(0, MAX_PLAYERS);
|
||||||
|
resetRoundMultiplayer(network.activeRoster);
|
||||||
|
network.lastSnapshotSentAt = 0;
|
||||||
|
broadcastSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
function activateMenuSelection() {
|
function activateMenuSelection() {
|
||||||
@ -508,11 +505,11 @@ function getMainMenuItems() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getLobbyMenuItems() {
|
function getLobbyMenuItems() {
|
||||||
const canStart = network.lobbyPhase === "lobby" && network.isHost && network.lobbyPlayers.length >= 1;
|
const canStart = network.isHost && network.lobbyPlayers.length >= 1;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "start",
|
id: "start",
|
||||||
label: canStart ? "Start Match" : network.lobbyPhase === "game" ? "Game In Progress" : "Waiting For Host",
|
label: canStart ? "Start Match" : "Waiting For Host",
|
||||||
disabled: !canStart,
|
disabled: !canStart,
|
||||||
},
|
},
|
||||||
{ id: "music", label: `Music: ${audio && audio.isMusicEnabled() ? "On" : "Off"}` },
|
{ id: "music", label: `Music: ${audio && audio.isMusicEnabled() ? "On" : "Off"}` },
|
||||||
@ -612,7 +609,6 @@ function ensureSocketConnection() {
|
|||||||
network.isHost = false;
|
network.isHost = false;
|
||||||
network.hostId = null;
|
network.hostId = null;
|
||||||
network.lobbyPlayers = [];
|
network.lobbyPlayers = [];
|
||||||
network.lobbyPhase = "lobby";
|
|
||||||
if (state.screen === "lobby") {
|
if (state.screen === "lobby") {
|
||||||
network.inLobby = true;
|
network.inLobby = true;
|
||||||
state.message = "Disconnected. Reconnecting...";
|
state.message = "Disconnected. Reconnecting...";
|
||||||
@ -667,30 +663,9 @@ function handleSocketMessage(raw) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.type === "lobby_state") {
|
if (msg.type === "lobby_state") {
|
||||||
const previousLobbyPlayers = network.lobbyPlayers.slice();
|
|
||||||
const nextLobbyPlayers = normalizeLobbyPlayers(msg.players || []);
|
|
||||||
|
|
||||||
network.hostId = msg.hostId || null;
|
network.hostId = msg.hostId || null;
|
||||||
network.isHost = network.hostId === network.clientId;
|
network.isHost = network.hostId === network.clientId;
|
||||||
network.lobbyPhase = msg.phase === "game" ? "game" : "lobby";
|
network.lobbyPlayers = normalizeLobbyPlayers(msg.players || []);
|
||||||
network.lobbyPlayers = nextLobbyPlayers;
|
|
||||||
|
|
||||||
if (state.mode === "multiplayer" && state.screen === "game" && msg.phase === "game") {
|
|
||||||
const activeMatchIds = new Set(network.activeRoster.map((player) => player.id));
|
|
||||||
const previousIds = new Set(previousLobbyPlayers.map((player) => player.id));
|
|
||||||
const newlyWaiting = nextLobbyPlayers.filter(
|
|
||||||
(player) => !previousIds.has(player.id) && !activeMatchIds.has(player.id),
|
|
||||||
);
|
|
||||||
if (newlyWaiting.length === 1) {
|
|
||||||
showMatchNotification(newlyWaiting[0].name + " is waiting in lobby.");
|
|
||||||
} else if (newlyWaiting.length > 1) {
|
|
||||||
showMatchNotification(String(newlyWaiting.length) + " players are waiting in lobby.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.screen === "lobby" && network.lobbyPhase === "game") {
|
|
||||||
state.message = "Match running. You will join next round.";
|
|
||||||
}
|
|
||||||
if (state.mode === "multiplayer" && state.screen === "game" && msg.phase === "lobby") {
|
if (state.mode === "multiplayer" && state.screen === "game" && msg.phase === "lobby") {
|
||||||
state.screen = "lobby";
|
state.screen = "lobby";
|
||||||
state.status = "idle";
|
state.status = "idle";
|
||||||
@ -703,7 +678,6 @@ function handleSocketMessage(raw) {
|
|||||||
if (msg.type === "lobby_start") {
|
if (msg.type === "lobby_start") {
|
||||||
network.hostId = msg.hostId;
|
network.hostId = msg.hostId;
|
||||||
network.isHost = msg.hostId === network.clientId;
|
network.isHost = msg.hostId === network.clientId;
|
||||||
network.lobbyPhase = "game";
|
|
||||||
network.activeRoster = normalizeLobbyPlayers(msg.roster || []);
|
network.activeRoster = normalizeLobbyPlayers(msg.roster || []);
|
||||||
startMultiplayerMatch(network.activeRoster);
|
startMultiplayerMatch(network.activeRoster);
|
||||||
return;
|
return;
|
||||||
@ -713,7 +687,7 @@ function handleSocketMessage(raw) {
|
|||||||
if (!network.isHost || state.mode !== "multiplayer" || state.screen !== "game") {
|
if (!network.isHost || state.mode !== "multiplayer" || state.screen !== "game") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
network.remoteInputs.set(msg.playerId, msg.input || { dir: null, bomb: false, bombCell: null });
|
network.remoteInputs.set(msg.playerId, msg.input || { dir: null, bomb: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,7 +710,6 @@ function startSinglePlayerFromMenu() {
|
|||||||
function startMultiplayerMatch(roster) {
|
function startMultiplayerMatch(roster) {
|
||||||
network.activeRoster = roster.slice(0, MAX_PLAYERS);
|
network.activeRoster = roster.slice(0, MAX_PLAYERS);
|
||||||
network.inLobby = false;
|
network.inLobby = false;
|
||||||
network.lobbyPhase = "game";
|
|
||||||
network.lastSnapshotAt = now();
|
network.lastSnapshotAt = now();
|
||||||
network.lastInputSentAt = 0;
|
network.lastInputSentAt = 0;
|
||||||
network.hasReceivedSnapshot = network.isHost;
|
network.hasReceivedSnapshot = network.isHost;
|
||||||
@ -759,7 +732,6 @@ function enterLobbyScreen() {
|
|||||||
network.inLobby = true;
|
network.inLobby = true;
|
||||||
network.isHost = false;
|
network.isHost = false;
|
||||||
network.hostId = null;
|
network.hostId = null;
|
||||||
network.lobbyPhase = "lobby";
|
|
||||||
network.lobbyPlayers = [];
|
network.lobbyPlayers = [];
|
||||||
network.remoteInputs.clear();
|
network.remoteInputs.clear();
|
||||||
network.remoteBombPrev.clear();
|
network.remoteBombPrev.clear();
|
||||||
@ -773,14 +745,11 @@ function leaveLobbyInternals() {
|
|||||||
network.inLobby = false;
|
network.inLobby = false;
|
||||||
network.isHost = false;
|
network.isHost = false;
|
||||||
network.hostId = null;
|
network.hostId = null;
|
||||||
network.lobbyPhase = "lobby";
|
|
||||||
network.lobbyPlayers = [];
|
network.lobbyPlayers = [];
|
||||||
network.remoteInputs.clear();
|
network.remoteInputs.clear();
|
||||||
network.remoteBombPrev.clear();
|
network.remoteBombPrev.clear();
|
||||||
network.activeRoster = [];
|
network.activeRoster = [];
|
||||||
state.notification.text = "";
|
network.inputStateAtClient = { dir: null, bomb: false };
|
||||||
state.notification.expiresAt = 0;
|
|
||||||
network.inputStateAtClient = { dir: null, bomb: false, bombCell: null };
|
|
||||||
network.lastInputSentAt = 0;
|
network.lastInputSentAt = 0;
|
||||||
network.hasReceivedSnapshot = false;
|
network.hasReceivedSnapshot = false;
|
||||||
}
|
}
|
||||||
@ -797,7 +766,7 @@ function leaveMultiplayerToMainMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hostStartMatchFromLobby() {
|
function hostStartMatchFromLobby() {
|
||||||
if (!network.isHost || network.lobbyPhase !== "lobby" || network.lobbyPlayers.length < 1) {
|
if (!network.isHost || network.lobbyPlayers.length < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendSocketMessage("lobby_start");
|
sendSocketMessage("lobby_start");
|
||||||
@ -976,94 +945,22 @@ function updateLobbyNetwork(dt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameBombCell(a, b) {
|
|
||||||
if (!a && !b) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!a || !b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return a.x === b.x && a.y === b.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveBombDropCell(player, preferredCell = null) {
|
|
||||||
const candidates = [];
|
|
||||||
if (player.moveProgress < 1) {
|
|
||||||
candidates.push({ x: player.moveFromX, y: player.moveFromY });
|
|
||||||
if (player.moveToX !== player.moveFromX || player.moveToY !== player.moveFromY) {
|
|
||||||
candidates.push({ x: player.moveToX, y: player.moveToY });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
candidates.push({ x: player.x, y: player.y });
|
|
||||||
}
|
|
||||||
|
|
||||||
const canPlaceAt = (x, y) => {
|
|
||||||
if (!inBounds(x, y)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const tile = state.map[y]?.[x];
|
|
||||||
if (!tile || tile.type !== "floor") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !getBombAt(x, y);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCandidateCell = (cell) =>
|
|
||||||
candidates.some((candidate) => candidate.x === cell.x && candidate.y === cell.y);
|
|
||||||
|
|
||||||
if (preferredCell && Number.isInteger(preferredCell.x) && Number.isInteger(preferredCell.y)) {
|
|
||||||
if (isCandidateCell(preferredCell) && canPlaceAt(preferredCell.x, preferredCell.y)) {
|
|
||||||
return { x: preferredCell.x, y: preferredCell.y };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const primary =
|
|
||||||
player.moveProgress < 1 && player.moveProgress >= 0.5
|
|
||||||
? { x: player.moveToX, y: player.moveToY }
|
|
||||||
: candidates[0];
|
|
||||||
|
|
||||||
if (primary && canPlaceAt(primary.x, primary.y)) {
|
|
||||||
return { x: primary.x, y: primary.y };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
if (canPlaceAt(candidate.x, candidate.y)) {
|
|
||||||
return { x: candidate.x, y: candidate.y };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendLocalMultiplayerInput(force = false) {
|
function sendLocalMultiplayerInput(force = false) {
|
||||||
if (state.mode !== "multiplayer" || state.screen !== "game" || network.isHost) {
|
if (state.mode !== "multiplayer" || state.screen !== "game" || network.isHost) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const t = now();
|
const t = now();
|
||||||
const localPlayer = state.players.find((player) => player.ownerId === network.clientId) || null;
|
|
||||||
const nextInput = {
|
const nextInput = {
|
||||||
dir: currentHumanDirection(),
|
dir: currentHumanDirection(),
|
||||||
bomb: inputState.bombHeld,
|
bomb: inputState.bombHeld,
|
||||||
bombCell: null,
|
|
||||||
};
|
};
|
||||||
if (nextInput.bomb && localPlayer) {
|
|
||||||
nextInput.bombCell = resolveBombDropCell(localPlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
const changed =
|
const changed =
|
||||||
nextInput.dir !== network.inputStateAtClient.dir ||
|
nextInput.dir !== network.inputStateAtClient.dir || nextInput.bomb !== network.inputStateAtClient.bomb;
|
||||||
nextInput.bomb !== network.inputStateAtClient.bomb ||
|
|
||||||
!sameBombCell(nextInput.bombCell, network.inputStateAtClient.bombCell);
|
|
||||||
const dueHeartbeat = t - network.lastInputSentAt > 0.12;
|
const dueHeartbeat = t - network.lastInputSentAt > 0.12;
|
||||||
if (!changed && !force && !dueHeartbeat) {
|
if (!changed && !force && !dueHeartbeat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
network.inputStateAtClient = nextInput;
|
||||||
network.inputStateAtClient = {
|
|
||||||
dir: nextInput.dir,
|
|
||||||
bomb: nextInput.bomb,
|
|
||||||
bombCell: nextInput.bombCell ? { ...nextInput.bombCell } : null,
|
|
||||||
};
|
|
||||||
network.lastInputSentAt = t;
|
network.lastInputSentAt = t;
|
||||||
sendSocketMessage("player_input", { input: nextInput, ts: t });
|
sendSocketMessage("player_input", { input: nextInput, ts: t });
|
||||||
}
|
}
|
||||||
@ -1105,20 +1002,18 @@ function queueDetonation(bombId) {
|
|||||||
state.pendingDetonations.push(bombId);
|
state.pendingDetonations.push(bombId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dropBomb(player, preferredCell = null) {
|
function dropBomb(player) {
|
||||||
if (!player.alive || player.bombsPlaced >= player.bombCapacity) {
|
if (!player.alive || player.bombsPlaced >= player.bombCapacity) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (getBombAt(player.x, player.y)) {
|
||||||
const dropCell = resolveBombDropCell(player, preferredCell);
|
|
||||||
if (!dropCell) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bomb = {
|
const bomb = {
|
||||||
id: state.nextBombId,
|
id: state.nextBombId,
|
||||||
x: dropCell.x,
|
x: player.x,
|
||||||
y: dropCell.y,
|
y: player.y,
|
||||||
timer: CONFIG.bombFuse,
|
timer: CONFIG.bombFuse,
|
||||||
range: player.flameRange,
|
range: player.flameRange,
|
||||||
ownerId: player.id,
|
ownerId: player.id,
|
||||||
@ -1404,7 +1299,7 @@ function occupiedTiles(player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getNetworkInput(ownerId) {
|
function getNetworkInput(ownerId) {
|
||||||
return network.remoteInputs.get(ownerId) || { dir: null, bomb: false, bombCell: null };
|
return network.remoteInputs.get(ownerId) || { dir: null, bomb: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDesiredDirection(player) {
|
function getDesiredDirection(player) {
|
||||||
@ -1446,9 +1341,8 @@ function updatePlayerMovement(player, dt) {
|
|||||||
player.renderY = player.y;
|
player.renderY = player.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
const occupied = new Set(occupiedTiles(player));
|
|
||||||
for (const bomb of state.bombs) {
|
for (const bomb of state.bombs) {
|
||||||
if (bomb.passThrough.has(player.id) && !occupied.has(tileKey(bomb.x, bomb.y))) {
|
if (bomb.passThrough.has(player.id) && (player.x !== bomb.x || player.y !== bomb.y)) {
|
||||||
bomb.passThrough.delete(player.id);
|
bomb.passThrough.delete(player.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1661,7 +1555,7 @@ function updateGame(dt) {
|
|||||||
const remote = getNetworkInput(player.ownerId);
|
const remote = getNetworkInput(player.ownerId);
|
||||||
const prevBomb = network.remoteBombPrev.get(player.ownerId) || false;
|
const prevBomb = network.remoteBombPrev.get(player.ownerId) || false;
|
||||||
if (remote.bomb && !prevBomb) {
|
if (remote.bomb && !prevBomb) {
|
||||||
dropBomb(player, remote.bombCell || null);
|
dropBomb(player);
|
||||||
}
|
}
|
||||||
network.remoteBombPrev.set(player.ownerId, remote.bomb);
|
network.remoteBombPrev.set(player.ownerId, remote.bomb);
|
||||||
}
|
}
|
||||||
@ -1943,11 +1837,7 @@ function drawLobby() {
|
|||||||
ctx.fillText("Multiplayer Lobby", canvas.width / 2, panelY + 48);
|
ctx.fillText("Multiplayer Lobby", canvas.width / 2, panelY + 48);
|
||||||
ctx.fillStyle = "#b6ddff";
|
ctx.fillStyle = "#b6ddff";
|
||||||
ctx.font = "bold 14px Trebuchet MS";
|
ctx.font = "bold 14px Trebuchet MS";
|
||||||
const lobbyHint =
|
ctx.fillText("Host can start with any number of players (1-4).", canvas.width / 2, panelY + 74);
|
||||||
network.lobbyPhase === "game"
|
|
||||||
? "Match currently running. You will join next round."
|
|
||||||
: "Host can start with any number of players (1-4).";
|
|
||||||
ctx.fillText(lobbyHint, canvas.width / 2, panelY + 74);
|
|
||||||
|
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
ctx.font = "bold 17px Trebuchet MS";
|
ctx.font = "bold 17px Trebuchet MS";
|
||||||
@ -1976,40 +1866,6 @@ function drawLobby() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawMatchNotification() {
|
|
||||||
if (state.screen !== "game" || !state.notification.text) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const remaining = state.notification.expiresAt - now();
|
|
||||||
if (remaining <= 0) {
|
|
||||||
state.notification.text = "";
|
|
||||||
state.notification.expiresAt = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fade = clamp(remaining < 0.35 ? remaining / 0.35 : 1, 0, 1);
|
|
||||||
const panelWidth = Math.min(canvas.width - 36, 520);
|
|
||||||
const panelHeight = 34;
|
|
||||||
const panelX = (canvas.width - panelWidth) / 2;
|
|
||||||
const panelY = 14;
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.globalAlpha = 0.96 * fade;
|
|
||||||
ctx.fillStyle = "#071421db";
|
|
||||||
ctx.strokeStyle = "#8acfff99";
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.fillRect(panelX, panelY, panelWidth, panelHeight);
|
|
||||||
ctx.strokeRect(panelX, panelY, panelWidth, panelHeight);
|
|
||||||
|
|
||||||
ctx.fillStyle = "#e7f4ff";
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
ctx.font = "bold 16px Trebuchet MS";
|
|
||||||
ctx.fillText(state.notification.text, canvas.width / 2, panelY + panelHeight / 2 + 1);
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawFrame() {
|
function drawFrame() {
|
||||||
if (state.screen === "mainMenu") {
|
if (state.screen === "mainMenu") {
|
||||||
drawMainMenu();
|
drawMainMenu();
|
||||||
@ -2027,7 +1883,6 @@ function drawFrame() {
|
|||||||
drawBombs();
|
drawBombs();
|
||||||
drawExplosions();
|
drawExplosions();
|
||||||
drawPlayers();
|
drawPlayers();
|
||||||
drawMatchNotification();
|
|
||||||
drawMenu();
|
drawMenu();
|
||||||
|
|
||||||
ctx.strokeStyle = "#86cef91f";
|
ctx.strokeStyle = "#86cef91f";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user