mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-13 06:24:46 +00:00
created right click handler, refacter renderers
This commit is contained in:
@@ -6,64 +6,44 @@ import {NameRenderer} from "./NameRenderer";
|
||||
import {TerrainRenderer} from "./TerrainRenderer";
|
||||
import {TerritoryRenderer} from "./TerritoryRenderer";
|
||||
import {ClientID} from "../../core/Schemas";
|
||||
import {renderTroops} from "./Utils";
|
||||
import {createCanvas, renderTroops} from "./Utils";
|
||||
import {UIRenderer} from "./UIRenderer";
|
||||
import {EventBus} from "../../core/EventBus";
|
||||
import {TransformHandler} from "./TransformHandler";
|
||||
import {Layer} from "./Layer";
|
||||
|
||||
|
||||
export function createRenderer(game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer {
|
||||
const canvas = createCanvas()
|
||||
const transformHandler = new TransformHandler(game, eventBus, canvas.getBoundingClientRect())
|
||||
|
||||
const layers: Layer[] = [
|
||||
new TerrainRenderer(game),
|
||||
new TerritoryRenderer(game, eventBus),
|
||||
new NameRenderer(game, game.config().theme()),
|
||||
new UIRenderer(eventBus, game, game.config().theme(), clientID)
|
||||
]
|
||||
|
||||
return new GameRenderer(game, eventBus, canvas, transformHandler, layers)
|
||||
}
|
||||
|
||||
|
||||
export class GameRenderer {
|
||||
private territoryCanvas: HTMLCanvasElement
|
||||
private canvas: HTMLCanvasElement
|
||||
|
||||
private territoryContext: CanvasRenderingContext2D
|
||||
|
||||
private scale: number = 1.8
|
||||
private offsetX: number = -350
|
||||
private offsetY: number = -200
|
||||
|
||||
private context: CanvasRenderingContext2D
|
||||
|
||||
private nameRenderer: NameRenderer;
|
||||
private territoryRenderer: TerritoryRenderer;
|
||||
private uiRenderer: UIRenderer;
|
||||
|
||||
private theme: Theme
|
||||
|
||||
|
||||
constructor(private eventBus: EventBus, private gs: Game, private clientID: ClientID, private terrainRenderer: TerrainRenderer) {
|
||||
this.theme = gs.config().theme()
|
||||
this.nameRenderer = new NameRenderer(gs, this.theme)
|
||||
this.territoryRenderer = new TerritoryRenderer(gs)
|
||||
this.uiRenderer = new UIRenderer(eventBus, gs, this.theme, clientID)
|
||||
constructor(private game: Game, private eventBus: EventBus, private canvas: HTMLCanvasElement, public transformHandler: TransformHandler, private layers: Layer[]) {
|
||||
this.context = canvas.getContext("2d")
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.context = this.canvas.getContext('2d');
|
||||
|
||||
// Set canvas style to fill the screen
|
||||
this.canvas.style.position = 'fixed';
|
||||
this.canvas.style.left = '0';
|
||||
this.canvas.style.top = '0';
|
||||
this.canvas.style.width = '100%';
|
||||
this.canvas.style.height = '100%';
|
||||
this.canvas.style.touchAction = 'none';
|
||||
|
||||
this.nameRenderer.initialize()
|
||||
this.terrainRenderer.init()
|
||||
this.territoryRenderer.init()
|
||||
this.uiRenderer.init()
|
||||
|
||||
this.layers.forEach(l => l.init())
|
||||
|
||||
document.body.appendChild(this.canvas);
|
||||
window.addEventListener('resize', () => this.resizeCanvas());
|
||||
this.resizeCanvas();
|
||||
|
||||
|
||||
this.territoryCanvas = document.createElement('canvas')
|
||||
this.territoryCanvas.width = this.gs.width();
|
||||
this.territoryCanvas.height = this.gs.height();
|
||||
this.territoryContext = this.territoryCanvas.getContext('2d')
|
||||
this.territoryContext.globalAlpha = 0.4;
|
||||
this.transformHandler = new TransformHandler(this.game, this.eventBus, this.canvas.getBoundingClientRect())
|
||||
|
||||
requestAnimationFrame(() => this.renderGame());
|
||||
}
|
||||
@@ -76,55 +56,43 @@ export class GameRenderer {
|
||||
|
||||
renderGame() {
|
||||
// Set background
|
||||
this.context.fillStyle = this.theme.backgroundColor().toHex();
|
||||
this.context.fillStyle = this.game.config().theme().backgroundColor().toHex();
|
||||
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Save the current context state
|
||||
this.context.save();
|
||||
|
||||
this.transformHandler.handleTransform(this.context)
|
||||
|
||||
// Disable image smoothing for pixelated effect
|
||||
if (this.scale > 3) {
|
||||
this.context.imageSmoothingEnabled = false;
|
||||
} else {
|
||||
this.context.imageSmoothingEnabled = true;
|
||||
}
|
||||
|
||||
// Apply zoom and pan
|
||||
this.context.setTransform(
|
||||
this.scale,
|
||||
0,
|
||||
0,
|
||||
this.scale,
|
||||
this.gs.width() / 2 - this.offsetX * this.scale,
|
||||
this.gs.height() / 2 - this.offsetY * this.scale
|
||||
);
|
||||
|
||||
this.terrainRenderer.draw(this.context)
|
||||
this.territoryRenderer.draw(this.context)
|
||||
|
||||
const [upperLeft, bottomRight] = this.boundingRect()
|
||||
this.nameRenderer.render(this.context, this.scale, upperLeft, bottomRight)
|
||||
this.layers.forEach(l => {
|
||||
if (l.shouldTransform()) {
|
||||
l.render(this.context, this.transformHandler)
|
||||
}
|
||||
})
|
||||
|
||||
this.context.restore()
|
||||
|
||||
this.layers.forEach(l => {
|
||||
if (!l.shouldTransform()) {
|
||||
l.render(this.context, this.transformHandler)
|
||||
}
|
||||
})
|
||||
|
||||
this.renderSpawnBar()
|
||||
this.uiRenderer.render(this.context)
|
||||
|
||||
requestAnimationFrame(() => this.renderGame());
|
||||
}
|
||||
|
||||
|
||||
// TODO: move to UIRenderer
|
||||
renderSpawnBar() {
|
||||
if (!this.gs.inSpawnPhase()) {
|
||||
if (!this.game.inSpawnPhase()) {
|
||||
return
|
||||
}
|
||||
|
||||
const barHeight = 15;
|
||||
const barBackgroundWidth = this.canvas.width;
|
||||
|
||||
const ratio = this.gs.ticks() / this.gs.config().numSpawnPhaseTurns()
|
||||
const ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns()
|
||||
|
||||
// Draw bar background
|
||||
this.context.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
||||
@@ -135,19 +103,7 @@ export class GameRenderer {
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.nameRenderer.tick()
|
||||
}
|
||||
|
||||
tileUpdate(event: TileEvent) {
|
||||
this.territoryRenderer.tileUpdate(event)
|
||||
// this.tileToRenderQueue.push({tileEvent: event, lastUpdate: this.gs.ticks() + this.random.nextFloat(0, .5)})
|
||||
}
|
||||
|
||||
playerEvent(event: PlayerEvent) {
|
||||
}
|
||||
|
||||
boatEvent(event: BoatEvent) {
|
||||
this.territoryRenderer.boatEvent(event)
|
||||
this.layers.forEach(l => l.tick())
|
||||
}
|
||||
|
||||
resize(width: number, height: number): void {
|
||||
@@ -155,77 +111,4 @@ export class GameRenderer {
|
||||
this.canvas.height = Math.ceil(height / window.devicePixelRatio);
|
||||
}
|
||||
|
||||
paintCell(cell: Cell, color: Colord) {
|
||||
color = color.alpha(10) // Assign the result back to color
|
||||
this.territoryContext.fillStyle = color.toHslString()
|
||||
this.territoryContext.fillRect(cell.x, cell.y, 1, 1);
|
||||
}
|
||||
|
||||
clearCell(cell: Cell) {
|
||||
this.territoryContext.clearRect(cell.x, cell.y, 1, 1);
|
||||
}
|
||||
|
||||
onZoom(event: ZoomEvent) {
|
||||
const oldScale = this.scale;
|
||||
const zoomFactor = 1 + event.delta / 600;
|
||||
this.scale /= zoomFactor;
|
||||
|
||||
// Clamp the scale to prevent extreme zooming
|
||||
this.scale = Math.max(0.5, Math.min(20, this.scale));
|
||||
|
||||
const canvasRect = this.canvas.getBoundingClientRect();
|
||||
const canvasX = event.x - canvasRect.left;
|
||||
const canvasY = event.y - canvasRect.top;
|
||||
|
||||
// Calculate the world point we want to zoom towards
|
||||
const zoomPointX = (canvasX - this.gs.width() / 2) / oldScale + this.offsetX;
|
||||
const zoomPointY = (canvasY - this.gs.height() / 2) / oldScale + this.offsetY;
|
||||
|
||||
// Adjust the offset
|
||||
this.offsetX = zoomPointX - (canvasX - this.gs.width() / 2) / this.scale;
|
||||
this.offsetY = zoomPointY - (canvasY - this.gs.height() / 2) / this.scale;
|
||||
}
|
||||
|
||||
onMove(event: DragEvent) {
|
||||
this.offsetX -= event.deltaX / this.scale;
|
||||
this.offsetY -= event.deltaY / this.scale;
|
||||
}
|
||||
|
||||
|
||||
screenToWorldCoordinates(screenX: number, screenY: number): Cell {
|
||||
|
||||
const canvasRect = this.canvas.getBoundingClientRect();
|
||||
const canvasX = screenX - canvasRect.left;
|
||||
const canvasY = screenY - canvasRect.top;
|
||||
|
||||
// Calculate the world point we want to zoom towards
|
||||
const centerX = (canvasX - this.gs.width() / 2) / this.scale + this.offsetX;
|
||||
const centerY = (canvasY - this.gs.height() / 2) / this.scale + this.offsetY;
|
||||
|
||||
const gameX = centerX + this.gs.width() / 2
|
||||
const gameY = centerY + this.gs.height() / 2
|
||||
|
||||
return new Cell(Math.floor(gameX), Math.floor(gameY));
|
||||
}
|
||||
|
||||
boundingRect(): [Cell, Cell] {
|
||||
|
||||
// Calculate the world point we want to zoom towards
|
||||
const LeftX = (- this.gs.width() / 2) / this.scale + this.offsetX;
|
||||
const TopY = (- this.gs.height() / 2) / this.scale + this.offsetY;
|
||||
|
||||
const gameLeftX = LeftX + this.gs.width() / 2
|
||||
const gameTopY = TopY + this.gs.height() / 2
|
||||
|
||||
|
||||
// Calculate the world point we want to zoom towards
|
||||
const rightX = (screen.width - this.gs.width() / 2) / this.scale + this.offsetX;
|
||||
const rightY = (screen.height - this.gs.height() / 2) / this.scale + this.offsetY;
|
||||
|
||||
const gameRightX = rightX + this.gs.width() / 2
|
||||
const gameBottomY = rightY + this.gs.height() / 2
|
||||
|
||||
return [new Cell(Math.floor(gameLeftX), Math.floor(gameTopY)), new Cell(Math.floor(gameRightX), Math.floor(gameBottomY))]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import {TransformHandler} from "./TransformHandler"
|
||||
|
||||
export interface Layer {
|
||||
init()
|
||||
tick()
|
||||
render(context: CanvasRenderingContext2D, transformHandler: TransformHandler)
|
||||
shouldTransform(): boolean
|
||||
}
|
||||
@@ -2,7 +2,9 @@ import {Cell, Game, Player, PlayerType} from "../../core/Game"
|
||||
import {PseudoRandom} from "../../core/PseudoRandom"
|
||||
import {calculateBoundingBox} from "../../core/Util"
|
||||
import {Theme} from "../../core/configuration/Config"
|
||||
import {Layer} from "./Layer"
|
||||
import {placeName} from "./NameBoxCalculator"
|
||||
import {TransformHandler} from "./TransformHandler"
|
||||
import {renderTroops} from "./Utils"
|
||||
|
||||
class RenderInfo {
|
||||
@@ -17,7 +19,7 @@ class RenderInfo {
|
||||
) { }
|
||||
}
|
||||
|
||||
export class NameRenderer {
|
||||
export class NameRenderer implements Layer {
|
||||
|
||||
private lastChecked = 0
|
||||
private refreshRate = 1000
|
||||
@@ -32,9 +34,12 @@ export class NameRenderer {
|
||||
constructor(private game: Game, private theme: Theme) {
|
||||
|
||||
}
|
||||
shouldTransform(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
public initialize() {
|
||||
public init() {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.context = this.canvas.getContext('2d');
|
||||
|
||||
@@ -45,6 +50,7 @@ export class NameRenderer {
|
||||
this.canvas.height = this.game.height();
|
||||
}
|
||||
|
||||
// TODO: remove tick, move this to render
|
||||
public tick() {
|
||||
const now = Date.now()
|
||||
if (now - this.lastChecked > this.refreshRate) {
|
||||
@@ -74,11 +80,12 @@ export class NameRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
public render(mainContex: CanvasRenderingContext2D, scale: number, uppperLeft: Cell, bottomRight: Cell) {
|
||||
public render(mainContex: CanvasRenderingContext2D, transformHandler: TransformHandler) {
|
||||
const [upperLeft, bottomRight] = transformHandler.screenBoundingRect()
|
||||
for (const render of this.renders) {
|
||||
render.isVisible = this.isVisible(render, uppperLeft, bottomRight)
|
||||
if (render.player.isAlive() && render.isVisible && render.fontSize * scale > 10) {
|
||||
this.renderPlayerInfo(render, mainContex, scale, uppperLeft, bottomRight)
|
||||
render.isVisible = this.isVisible(render, upperLeft, bottomRight)
|
||||
if (render.player.isAlive() && render.isVisible && render.fontSize * transformHandler.scale > 10) {
|
||||
this.renderPlayerInfo(render, mainContex, transformHandler.scale, upperLeft, bottomRight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import {inherits} from "util"
|
||||
import {Game} from "../../core/Game";
|
||||
import {throws} from "assert";
|
||||
import {Layer} from "./Layer";
|
||||
import {TransformHandler} from "./TransformHandler";
|
||||
|
||||
export class TerrainRenderer {
|
||||
export class TerrainRenderer implements Layer {
|
||||
private canvas: HTMLCanvasElement
|
||||
private context: CanvasRenderingContext2D
|
||||
private imageData: ImageData
|
||||
|
||||
|
||||
constructor(private game: Game) { }
|
||||
shouldTransform(): boolean {
|
||||
return true
|
||||
}
|
||||
tick() {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.canvas = document.createElement('canvas');
|
||||
@@ -34,7 +41,7 @@ export class TerrainRenderer {
|
||||
})
|
||||
}
|
||||
|
||||
draw(context: CanvasRenderingContext2D) {
|
||||
render(context: CanvasRenderingContext2D, transformHandler: TransformHandler) {
|
||||
context.drawImage(
|
||||
this.canvas,
|
||||
-this.game.width() / 2,
|
||||
|
||||
@@ -4,8 +4,11 @@ import {PseudoRandom} from "../../core/PseudoRandom";
|
||||
import {Colord} from "colord";
|
||||
import {bfs, dist} from "../../core/Util";
|
||||
import {Theme} from "../../core/configuration/Config";
|
||||
import {Layer} from "./Layer";
|
||||
import {TransformHandler} from "./TransformHandler";
|
||||
import {EventBus} from "../../core/EventBus";
|
||||
|
||||
export class TerritoryRenderer {
|
||||
export class TerritoryRenderer implements Layer {
|
||||
private canvas: HTMLCanvasElement
|
||||
private context: CanvasRenderingContext2D
|
||||
private imageData: ImageData
|
||||
@@ -17,8 +20,15 @@ export class TerritoryRenderer {
|
||||
private boatToTrail = new Map<Boat, Set<Tile>>()
|
||||
|
||||
|
||||
constructor(private game: Game) {
|
||||
constructor(private game: Game, eventBus: EventBus) {
|
||||
this.theme = game.config().theme()
|
||||
eventBus.on(TileEvent, e => this.tileUpdate(e))
|
||||
eventBus.on(BoatEvent, e => this.boatEvent(e))
|
||||
}
|
||||
shouldTransform(): boolean {
|
||||
return true
|
||||
}
|
||||
tick() {
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -40,7 +50,7 @@ export class TerritoryRenderer {
|
||||
})
|
||||
}
|
||||
|
||||
draw(context: CanvasRenderingContext2D) {
|
||||
render(context: CanvasRenderingContext2D, transformHandler: TransformHandler) {
|
||||
this.renderTerritory()
|
||||
this.context.putImageData(this.imageData, 0, 0);
|
||||
context.drawImage(
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import {EventBus} from "../../core/EventBus"
|
||||
import {Cell, Game} from "../../core/Game";
|
||||
import {ZoomEvent, DragEvent} from "../InputHandler";
|
||||
|
||||
export class TransformHandler {
|
||||
public scale: number = 1.8
|
||||
private offsetX: number = -350
|
||||
private offsetY: number = -200
|
||||
|
||||
constructor(private game: Game, private eventBus: EventBus, private boundingRect: DOMRect) {
|
||||
this.eventBus.on(ZoomEvent, (e) => this.onZoom(e))
|
||||
this.eventBus.on(DragEvent, (e) => this.onMove(e))
|
||||
}
|
||||
|
||||
handleTransform(context: CanvasRenderingContext2D) {
|
||||
// Disable image smoothing for pixelated effect
|
||||
if (this.scale > 3) {
|
||||
context.imageSmoothingEnabled = false;
|
||||
} else {
|
||||
context.imageSmoothingEnabled = true;
|
||||
}
|
||||
|
||||
// Apply zoom and pan
|
||||
context.setTransform(
|
||||
this.scale,
|
||||
0,
|
||||
0,
|
||||
this.scale,
|
||||
this.game.width() / 2 - this.offsetX * this.scale,
|
||||
this.game.height() / 2 - this.offsetY * this.scale
|
||||
);
|
||||
}
|
||||
|
||||
screenToWorldCoordinates(screenX: number, screenY: number): Cell {
|
||||
const canvasRect = this.boundingRect;
|
||||
const canvasX = screenX - canvasRect.left;
|
||||
const canvasY = screenY - canvasRect.top;
|
||||
|
||||
// Calculate the world point we want to zoom towards
|
||||
const centerX = (canvasX - this.game.width() / 2) / this.scale + this.offsetX;
|
||||
const centerY = (canvasY - this.game.height() / 2) / this.scale + this.offsetY;
|
||||
|
||||
const gameX = centerX + this.game.width() / 2
|
||||
const gameY = centerY + this.game.height() / 2
|
||||
|
||||
return new Cell(Math.floor(gameX), Math.floor(gameY));
|
||||
}
|
||||
|
||||
screenBoundingRect(): [Cell, Cell] {
|
||||
|
||||
// Calculate the world point we want to zoom towards
|
||||
const LeftX = (- this.game.width() / 2) / this.scale + this.offsetX;
|
||||
const TopY = (- this.game.height() / 2) / this.scale + this.offsetY;
|
||||
|
||||
const gameLeftX = LeftX + this.game.width() / 2
|
||||
const gameTopY = TopY + this.game.height() / 2
|
||||
|
||||
|
||||
// Calculate the world point we want to zoom towards
|
||||
const rightX = (screen.width - this.game.width() / 2) / this.scale + this.offsetX;
|
||||
const rightY = (screen.height - this.game.height() / 2) / this.scale + this.offsetY;
|
||||
|
||||
const gameRightX = rightX + this.game.width() / 2
|
||||
const gameBottomY = rightY + this.game.height() / 2
|
||||
|
||||
return [new Cell(Math.floor(gameLeftX), Math.floor(gameTopY)), new Cell(Math.floor(gameRightX), Math.floor(gameBottomY))]
|
||||
}
|
||||
|
||||
onZoom(event: ZoomEvent) {
|
||||
const oldScale = this.scale;
|
||||
const zoomFactor = 1 + event.delta / 600;
|
||||
this.scale /= zoomFactor;
|
||||
|
||||
// Clamp the scale to prevent extreme zooming
|
||||
this.scale = Math.max(0.5, Math.min(20, this.scale));
|
||||
|
||||
const canvasRect = this.boundingRect
|
||||
const canvasX = event.x - canvasRect.left;
|
||||
const canvasY = event.y - canvasRect.top;
|
||||
|
||||
// Calculate the world point we want to zoom towards
|
||||
const zoomPointX = (canvasX - this.game.width() / 2) / oldScale + this.offsetX;
|
||||
const zoomPointY = (canvasY - this.game.height() / 2) / oldScale + this.offsetY;
|
||||
|
||||
// Adjust the offset
|
||||
this.offsetX = zoomPointX - (canvasX - this.game.width() / 2) / this.scale;
|
||||
this.offsetY = zoomPointY - (canvasY - this.game.height() / 2) / this.scale;
|
||||
}
|
||||
|
||||
onMove(event: DragEvent) {
|
||||
this.offsetX -= event.deltaX / this.scale;
|
||||
this.offsetY -= event.deltaY / this.scale;
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,61 @@ import {Game, Player} from "../../core/Game";
|
||||
import {ClientID} from "../../core/Schemas";
|
||||
import {renderTroops} from "./Utils";
|
||||
import winModalHtml from '../WinModal.html';
|
||||
import {RightClickEvent} from "../InputHandler";
|
||||
import {Layer} from "./Layer";
|
||||
import {TransformHandler} from "./TransformHandler";
|
||||
|
||||
export class UIRenderer {
|
||||
|
||||
interface MenuOption {
|
||||
label: string;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
export class UIRenderer implements Layer {
|
||||
private exitButton: HTMLButtonElement;
|
||||
private winModal: HTMLElement | null = null;
|
||||
|
||||
private customMenu = document.getElementById('customMenu');
|
||||
|
||||
|
||||
constructor(private eventBus: EventBus, private game: Game, private theme: Theme, private clientID: ClientID) {
|
||||
|
||||
}
|
||||
render(context: CanvasRenderingContext2D, transformHandler: TransformHandler) {
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
tick() {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.createExitButton()
|
||||
this.createWinModal()
|
||||
this.initRightClickMenu()
|
||||
this.eventBus.on(WinEvent, (e) => this.onWinEvent(e))
|
||||
this.eventBus.on(RightClickEvent, (e) => this.onRightClick(e))
|
||||
}
|
||||
|
||||
initRightClickMenu() {
|
||||
if (!this.customMenu) {
|
||||
console.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() {
|
||||
@@ -124,9 +165,6 @@ export class UIRenderer {
|
||||
document.body.appendChild(this.exitButton);
|
||||
}
|
||||
|
||||
render(context: CanvasRenderingContext2D) {
|
||||
}
|
||||
|
||||
|
||||
onWinEvent(event: WinEvent) {
|
||||
console.log(`${event.winner.name()} won the game!!}`)
|
||||
@@ -165,4 +203,37 @@ export class UIRenderer {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
private onRightClick(e: RightClickEvent) {
|
||||
this.customMenu!.style.display = 'block';
|
||||
this.customMenu!.style.left = `${e.x}px`;
|
||||
this.customMenu!.style.top = `${e.y}px`;
|
||||
}
|
||||
|
||||
private populateMenu(options: MenuOption[]) {
|
||||
if (!this.customMenu) return;
|
||||
|
||||
// Clear existing menu items
|
||||
this.customMenu.innerHTML = '';
|
||||
|
||||
// Create new menu items
|
||||
const ul = document.createElement('ul');
|
||||
options.forEach(option => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = option.label;
|
||||
li.onclick = () => {
|
||||
option.action();
|
||||
this.hideMenu();
|
||||
};
|
||||
ul.appendChild(li);
|
||||
});
|
||||
|
||||
this.customMenu.appendChild(ul);
|
||||
}
|
||||
|
||||
private hideMenu() {
|
||||
if (this.customMenu) {
|
||||
this.customMenu.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,4 +14,18 @@ export function renderTroops(troops: number): string {
|
||||
troopsStr = String(Math.floor(troops))
|
||||
}
|
||||
return troopsStr
|
||||
}
|
||||
|
||||
export function createCanvas(): HTMLCanvasElement {
|
||||
const canvas = document.createElement('canvas');
|
||||
|
||||
// Set canvas style to fill the screen
|
||||
canvas.style.position = 'fixed';
|
||||
canvas.style.left = '0';
|
||||
canvas.style.top = '0';
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
canvas.style.touchAction = 'none';
|
||||
|
||||
return canvas
|
||||
}
|
||||
Reference in New Issue
Block a user