diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 1ba1ff910..2297bd0f9 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -3,7 +3,6 @@ import { NameLayer } from "./layers/NameLayer"; import { TerrainLayer } from "./layers/TerrainLayer"; import { TerritoryLayer } from "./layers/TerritoryLayer"; import { ClientID } from "../../core/Schemas"; -import { UILayer } from "./layers/UILayer"; import { EventBus } from "../../core/EventBus"; import { TransformHandler } from "./TransformHandler"; import { Layer } from "./layers/Layer"; @@ -20,6 +19,8 @@ import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay"; import { consolex } from "../../core/Consolex"; import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler"; import { GameView } from "../../core/game/GameView"; +import { WinModal } from "./layers/WinModal"; +import { SpawnTimer } from "./layers/SpawnTimer"; export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventBus: EventBus, clientID: ClientID): GameRenderer { @@ -75,18 +76,27 @@ export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventB playerInfo.game = game + const winModel = document.querySelector('win-modal') as WinModal + if (!(playerInfo instanceof WinModal)) { + console.error('win modal not found') + } + winModel.clientID = clientID + winModel.game = game + + const layers: Layer[] = [ new TerrainLayer(game), new TerritoryLayer(game, eventBus), new StructureLayer(game, eventBus), new UnitLayer(game, eventBus, clientID), new NameLayer(game, game.config().theme(), transformHandler, clientID), - new UILayer(eventBus, game, clientID, transformHandler), eventsDisplay, new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable, buildMenu, uiState), + new SpawnTimer(game, transformHandler), leaderboard, controlPanel, - playerInfo + playerInfo, + winModel ] return new GameRenderer(game, eventBus, canvas, transformHandler, uiState, layers) diff --git a/src/client/graphics/layers/SpawnTimer.ts b/src/client/graphics/layers/SpawnTimer.ts new file mode 100644 index 000000000..05a1ad451 --- /dev/null +++ b/src/client/graphics/layers/SpawnTimer.ts @@ -0,0 +1,34 @@ +import { GameView } from '../../../core/game/GameView'; +import { TransformHandler } from '../TransformHandler'; +import { Layer } from './Layer'; + +export class SpawnTimer implements Layer { + + constructor(private game: GameView, private transformHandler: TransformHandler) { } + + init() { + } + tick() { + } + shouldTransform(): boolean { + return false + } + + renderLayer(context: CanvasRenderingContext2D) { + if (!this.game.inSpawnPhase()) { + return + } + + const barHeight = 15; + const barBackgroundWidth = this.transformHandler.width(); + + const ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns() + + // Draw bar background + context.fillStyle = 'rgba(0, 0, 0, 0.5)'; + context.fillRect(0, 0, barBackgroundWidth, barHeight); + + context.fillStyle = 'rgba(0, 128, 255, 0.7)'; + context.fillRect(0, 0, barBackgroundWidth * ratio, barHeight); + } +} diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts deleted file mode 100644 index 04592cca9..000000000 --- a/src/client/graphics/layers/UILayer.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { EventBus } from "../../../core/EventBus"; -import { WinEvent } from "../../../core/execution/WinCheckExecution"; -import { Player } from "../../../core/game/Game"; -import { ClientID } from "../../../core/Schemas"; -import { Layer } from "./Layer"; -import { TransformHandler } from "../TransformHandler"; -import { consolex } from "../../../core/Consolex"; -import { GameView } from "../../../core/game/GameView"; - -interface MenuOption { - label: string; - action: () => void; -} - -export class UILayer implements Layer { - private exitButton: HTMLButtonElement; - private winModal: HTMLElement | null = null; - - private customMenu = document.getElementById('customMenu'); - - - constructor( - private eventBus: EventBus, - private game: GameView, - private clientID: ClientID, - private transformHandler: TransformHandler - ) { - - } - - renderLayer(context: CanvasRenderingContext2D) { - if (!this.game.inSpawnPhase()) { - return - } - - const barHeight = 15; - const barBackgroundWidth = this.transformHandler.width(); - - const ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns() - - // Draw bar background - context.fillStyle = 'rgba(0, 0, 0, 0.5)'; - context.fillRect(0, 0, barBackgroundWidth, barHeight); - - context.fillStyle = 'rgba(0, 128, 255, 0.7)'; - context.fillRect(0, 0, barBackgroundWidth * ratio, barHeight); - } - - shouldTransform(): boolean { - return false - } - - tick() { - } - - init() { - this.createWinModal() - this.initRightClickMenu() - this.eventBus.on(WinEvent, (e) => this.onWinEvent(e)) - } - - initRightClickMenu() { - if (!this.customMenu) { - consolex.error('Custom menu not found'); - return; - } - - document.addEventListener('click', () => { - this.customMenu!.style.display = 'none'; - }); - - const menuItems = this.customMenu.querySelectorAll('li'); - menuItems.forEach(item => { - item.addEventListener('click', () => { - alert(`You clicked: ${item.textContent}`); - this.customMenu!.style.display = 'none'; - }); - }); - } - - createWinModal() { - consolex.log("Creating win modal"); - this.winModal = document.createElement('div'); - this.winModal.style.cssText = ` - display: none; - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: white; - padding: 20px; - border: 2px solid black; - border-radius: 10px; - z-index: 2000; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - `; - - const content = document.createElement('div'); - - const title = document.createElement('h2'); - title.textContent = 'Game Over'; - title.id = 'winTitle'; - title.style.marginTop = '0'; - - const message = document.createElement('p'); - message.id = 'winMessage'; - - const buttonContainer = document.createElement('div'); - buttonContainer.style.display = 'flex'; - buttonContainer.style.justifyContent = 'space-between'; - buttonContainer.style.marginTop = '20px'; - - const exitButton = document.createElement('button'); - exitButton.textContent = 'Exit Game'; - exitButton.onclick = () => this.exitGame(); - this.styleButton(exitButton); - - const continueButton = document.createElement('button'); - continueButton.textContent = 'Keep Playing'; - continueButton.onclick = () => this.closeWinModal(); - this.styleButton(continueButton); - - buttonContainer.appendChild(exitButton); - buttonContainer.appendChild(continueButton); - - content.appendChild(title); - content.appendChild(message); - content.appendChild(buttonContainer); - - this.winModal.appendChild(content); - document.body.appendChild(this.winModal); - - consolex.log("Win modal appended to body"); - } - - styleButton(button: HTMLButtonElement) { - button.style.cssText = ` - padding: 10px 20px; - font-size: 16px; - cursor: pointer; - background-color: #4A90E2; - color: white; - border: none; - border-radius: 5px; - transition: background-color 0.3s; - `; - button.onmouseover = () => button.style.backgroundColor = '#3A7BCE'; - button.onmouseout = () => button.style.backgroundColor = '#4A90E2'; - } - - - onWinEvent(event: WinEvent) { - consolex.log(`${event.winner.name()} won the game!!}`) - this.showWinModal(event.winner) - } - - showWinModal(winner: Player) { - if (this.winModal) { - const message = this.winModal.querySelector('#winMessage'); - if (message) { - message.textContent = `${winner.name()} won the game!`; - } - const title = this.winModal.querySelector('#winTitle') - if (winner.clientID() == this.clientID) { - title.textContent = 'You Won!!!' - } else { - title.textContent = 'You Lost!!!' - } - this.winModal.style.display = 'block'; - } - } - - closeWinModal() { - if (this.winModal) { - this.winModal.style.display = 'none'; - } - } - - exitGame() { - this.closeWinModal(); - window.location.reload(); - } - -} \ No newline at end of file diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts new file mode 100644 index 000000000..04a2e9f8e --- /dev/null +++ b/src/client/graphics/layers/WinModal.ts @@ -0,0 +1,167 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { Player } from '../../../core/game/Game'; +import { ClientID } from '../../../core/Schemas'; +import { GameView, PlayerView } from '../../../core/game/GameView'; +import { Layer } from './Layer'; +import { GameUpdateType } from '../../../core/game/GameUpdates'; + +@customElement('win-modal') +export class WinModal extends LitElement implements Layer { + public clientID: ClientID + public game: GameView + private winner: PlayerView + + @state() + isVisible = false + + static styles = css` + :host { + display: block; + } + + .modal { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: rgba(30, 30, 30, 0.7); + padding: 25px; + border-radius: 10px; + z-index: 9999; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + backdrop-filter: blur(5px); + color: white; + width: 300px; + transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out; + } + + .modal.visible { + display: block; + animation: fadeIn 0.3s ease-out; + } + + @keyframes fadeIn { + from { + opacity: 0; + transform: translate(-50%, -48%); + } + to { + opacity: 1; + transform: translate(-50%, -50%); + } + } + + h2 { + margin: 0 0 15px 0; + font-size: 24px; + text-align: center; + color: white; + } + + p { + margin: 0 0 20px 0; + text-align: center; + background-color: rgba(0, 0, 0, 0.3); + padding: 10px; + border-radius: 5px; + } + + .button-container { + display: flex; + justify-content: space-between; + gap: 10px; + } + + button { + flex: 1; + padding: 12px; + font-size: 16px; + cursor: pointer; + background: rgba(0, 150, 255, 0.6); + color: white; + border: none; + border-radius: 5px; + transition: background-color 0.2s ease, transform 0.1s ease; + } + + button:hover { + background: rgba(0, 150, 255, 0.8); + transform: translateY(-1px); + } + + button:active { + transform: translateY(1px); + } + + @media (max-width: 768px) { + .modal { + width: 90%; + max-width: 300px; + padding: 20px; + } + + h2 { + font-size: 20px; + } + + button { + padding: 10px; + font-size: 14px; + } + } + `; + + render() { + if (!this.winner) return null; + const isWinner = this.winner.clientID() === this.clientID; + const title = isWinner ? 'You Won!!!' : 'You Lost!!!'; + const message = `${this.winner.name()} won the game!`; + + return html` +
${message}
+ +