create EventsDisplay file

This commit is contained in:
evanpelle
2024-09-20 13:14:46 -07:00
parent 828675f087
commit a0353066c9
11 changed files with 279 additions and 31 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ import {TerrainMap} from "../core/game/TerrainMapLoader";
import {and, bfs, dist, manhattanDist} from "../core/Util";
import {TerrainLayer} from "./graphics/layers/TerrainLayer";
import {WinCheckExecution} from "../core/execution/WinCheckExecution";
import {SendAllianceRequestEvent} from "./graphics/layers/UILayer";
import {SendAllianceRequestUIEvent} from "./graphics/layers/UILayer";
@@ -122,7 +122,7 @@ export class ClientGame {
this.eventBus.on(PlayerEvent, (e) => this.playerEvent(e))
this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e))
this.eventBus.on(SendAllianceRequestEvent, (e) => this.onSendAllianceRequest(e))
this.eventBus.on(SendAllianceRequestUIEvent, (e) => this.onSendAllianceRequest(e))
this.renderer.initialize()
this.input.initialize()
@@ -271,7 +271,7 @@ export class ClientGame {
}
}
private onSendAllianceRequest(event: SendAllianceRequestEvent) {
private onSendAllianceRequest(event: SendAllianceRequestUIEvent) {
this.sendIntent({
type: "allianceRequest",
clientID: this.id,
+3 -1
View File
@@ -8,6 +8,7 @@ import {UILayer} from "./layers/UILayer";
import {EventBus} from "../../core/EventBus";
import {TransformHandler} from "./TransformHandler";
import {Layer} from "./layers/Layer";
import {EventsDisplay} from "./layers/EventsDisplay";
export function createRenderer(game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer {
@@ -18,7 +19,8 @@ export function createRenderer(game: Game, eventBus: EventBus, clientID: ClientI
new TerrainLayer(game),
new TerritoryLayer(game, eventBus),
new NameLayer(game, game.config().theme(), transformHandler, clientID),
new UILayer(eventBus, game, game.config().theme(), clientID, transformHandler)
new UILayer(eventBus, game, clientID, transformHandler),
new EventsDisplay(eventBus, game, clientID)
]
return new GameRenderer(game, eventBus, canvas, transformHandler, layers)
+167
View File
@@ -0,0 +1,167 @@
import {nullable} from "zod";
import {EventBus} from "../../../core/EventBus";
import {AllianceRequestEvent, AllianceRequestReplyEvent, Game} from "../../../core/game/Game";
import {ClientID} from "../../../core/Schemas";
import {Layer} from "./Layer";
interface Event {
description: string;
buttons?: {
text: string
className: string
action: () => void
}[];
highlight?: boolean;
}
export class EventsDisplay implements Layer {
private container: HTMLElement;
private events: Event[];
private tableContainer: HTMLDivElement;
constructor(private eventBus: EventBus, private game: Game, private clientID: ClientID) {
const element = document.getElementById("app");
element.style.zIndex = "1000"
if (!element) throw new Error(`Container element with id app not found`);
this.container = element;
this.events = [];
this.createTableContainer()
}
init() {
this.eventBus.on(AllianceRequestEvent, a => this.onAllianceRequestEvent(a))
this.eventBus.on(AllianceRequestReplyEvent, a => this.onAllianceRequestReplyEvent(a))
this.renderTable()
}
tick() {
}
private createTableContainer() {
this.tableContainer = document.createElement('div');
this.tableContainer.id = 'table-container';
this.tableContainer.style.position = 'fixed';
this.tableContainer.style.bottom = '0px'; // Distance from bottom
this.tableContainer.style.right = '0px'; // Distance from right
this.tableContainer.style.zIndex = '1000';
this.tableContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.0)';
this.tableContainer.style.padding = '20px';
this.tableContainer.style.boxShadow = '0 0 10px rgba(0,0,0,0.0)';
document.body.appendChild(this.tableContainer);
this.tableContainer.style.minWidth = '400px'; // Set minimum width
}
shouldTransform(): boolean {
return false
}
onAllianceRequestEvent(event: AllianceRequestEvent): void {
const myPlayer = this.game.playerByClientID(this.clientID)
if (myPlayer == null) {
return
}
if (event.allianceRequest.recipient() != myPlayer) {
return
}
this.addEvent({
description: `${event.allianceRequest.requestor().name()} requests an alliance!`,
buttons: [
{
text: "Accept",
className: "btn",
action: () => alert('accepted'),
},
{
text: "Reject",
className: "btn btn-info",
action: () => alert('rejected'),
}
],
highlight: true
});
this.renderTable()
}
onAllianceRequestReplyEvent(event: AllianceRequestReplyEvent) {
const myPlayer = this.game.playerByClientID(this.clientID)
if (myPlayer == null) {
return
}
if (event.allianceRequest.requestor() != myPlayer) {
return
}
this.addEvent({
description: `${event.allianceRequest.recipient().name()} ${event.accepted ? "accepted" : "rejected"} your alliance request`,
highlight: true
});
this.renderTable()
}
addEvent(event: Event): void {
this.events.push(event);
}
removeEvent(index: number): void {
this.events.splice(index, 1);
}
updateEvent(index: number, event: Event): void {
this.events[index] = event;
}
render(): void { }
renderTable(): void {
let tableHtml = `
<table class="events-table">
<tbody>
`;
this.events.forEach((event, eventIndex) => {
tableHtml += `
<tr${event.highlight ? ' style="background-color: rgba(255, 255, 0, 0.1);"' : ''}>
<td>
${event.description}
${event.buttons ? '<br>' + event.buttons.map((btn, btnIndex) =>
`<button class="${btn.className}" data-event-index="${eventIndex}" data-button-index="${btnIndex}">${btn.text}</button>`
).join('') : ''}
</td>
</tr>
`;
});
tableHtml += `
</tbody>
</table>
`;
this.tableContainer.innerHTML = tableHtml;
// Add event listeners to buttons
this.tableContainer.querySelectorAll('button').forEach(button => {
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation(); // Prevent the event from reaching the canvas
const target = e.target as HTMLElement;
const eventIndex = parseInt(target.getAttribute('data-event-index') || '');
const buttonIndex = parseInt(target.getAttribute('data-button-index') || '');
if (!isNaN(eventIndex) && !isNaN(buttonIndex)) {
const event = this.events[eventIndex];
const buttonAction = event.buttons?.[buttonIndex]?.action;
if (buttonAction) {
buttonAction();
// Optionally, you might want to remove the event after the action is performed
this.removeEvent(eventIndex);
this.renderTable(); // Re-render the table if you remove the event
}
}
});
});
}
}
+8 -17
View File
@@ -3,13 +3,11 @@ import {EventBus, GameEvent} from "../../../core/EventBus";
import {WinEvent} from "../../../core/execution/WinCheckExecution";
import {AllianceRequest, AllianceRequestReplyEvent, Game, Player} from "../../../core/game/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 SendAllianceRequestEvent implements GameEvent {
export class SendAllianceRequestUIEvent implements GameEvent {
constructor(
public readonly requestor: Player,
public readonly recipient: Player
@@ -28,7 +26,12 @@ export class UILayer implements Layer {
private customMenu = document.getElementById('customMenu');
constructor(private eventBus: EventBus, private game: Game, private theme: Theme, private clientID: ClientID, private transformHandler: TransformHandler) {
constructor(
private eventBus: EventBus,
private game: Game,
private clientID: ClientID,
private transformHandler: TransformHandler
) {
}
@@ -63,7 +66,6 @@ export class UILayer implements Layer {
this.initRightClickMenu()
this.eventBus.on(WinEvent, (e) => this.onWinEvent(e))
this.eventBus.on(RightClickEvent, (e) => this.onRightClick(e))
this.eventBus.on(AllianceRequestReplyEvent, (e) => this.onAllianceRequestReplyEvent(e))
}
initRightClickMenu() {
@@ -193,17 +195,6 @@ export class UILayer implements Layer {
this.showWinModal(event.winner)
}
onAllianceRequestReplyEvent(event: AllianceRequestReplyEvent) {
if (event.allianceRequest.requestor().clientID() == this.clientID) {
const recipient = event.allianceRequest.recipient().name()
if (event.accepted) {
alert(`${recipient} accepted your alliance request`)
} else {
alert(`${recipient} rejected your alliance request`)
}
}
}
showWinModal(winner: Player) {
if (this.winModal) {
const message = this.winModal.querySelector('#winMessage');
@@ -265,7 +256,7 @@ export class UILayer implements Layer {
label: "Request Alliance",
action: (): void => {
this.eventBus.emit(
new SendAllianceRequestEvent(myPlayer, owner)
new SendAllianceRequestUIEvent(myPlayer, owner)
)
},
}
+1 -1
View File
@@ -46,7 +46,7 @@
</ul>
</div>
<div id="app"></div>
<style>
body {
+67
View File
@@ -231,4 +231,71 @@ h3 {
#customMenu ul li:hover {
background-color: #f1f1f1;
}
#table-container {
background-color: rgba(0, 0, 0, 0.7);
}
/* Events Table Styles */
.events-table {
width: 100%;
border-collapse: collapse;
background-color: rgba(0, 0, 0, 0.5);
color: white;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
.events-table th,
.events-table td {
padding: 15px;
text-align: left;
border-bottom: 1px solid rgba(255, 255, 255, 0.0);
z-index: 1000;
}
.events-table th {
background-color: rgba(0, 0, 0, 0.0);
font-size: 1.2em;
text-transform: uppercase;
}
.events-table tr:hover {
background-color: rgba(255, 255, 255, 0.0);
}
.btn {
display: inline-block;
padding: 8px 16px;
margin: 5px 10px 5px 0;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #45a049;
}
.btn-info {
background-color: #2196F3;
}
.btn-info:hover {
background-color: #0b7dda;
}
@media (max-width: 600px) {
.events-table th,
.events-table td {
padding: 10px;
}
.btn {
display: block;
margin: 5px 0;
}
}
+3 -3
View File
@@ -6,13 +6,13 @@ export const devConfig = new class extends DefaultConfig {
return 80
}
numSpawnPhaseTurns(): number {
return 40
return 80
}
gameCreationRate(): number {
return 2 * 1000
return 10 * 1000
}
lobbyLifetime(): number {
return 2 * 1000
return 10 * 1000
}
turnIntervalMs(): number {
return 100
@@ -3,8 +3,8 @@ import {AllianceRequest, Execution, MutableGame, MutablePlayer, Player, PlayerID
export class AllianceRequestExecution implements Execution {
private active = true
private mg: MutableGame = null
private requestor: Player;
private recipient: Player
private requestor: MutablePlayer;
private recipient: MutablePlayer
constructor(private requestorID: PlayerID, private recipientID: PlayerID) { }
@@ -18,7 +18,7 @@ export class AllianceRequestExecution implements Execution {
if (this.requestor.alliedWith(this.recipient)) {
console.warn('already allied')
} else {
this.mg.createAllianceRequest(this.requestor, this.recipient)
this.requestor.createAllianceRequest(this.recipient)
}
this.active = false
}
+7 -2
View File
@@ -157,12 +157,14 @@ export interface MutablePlayer extends Player {
outgoingAllianceRequests(): MutableAllianceRequest[]
alliances(): MutableAlliance[]
breakAllianceWith(other: Player): void
createAllianceRequest(recipient: Player): MutableAllianceRequest
addBoat(troops: number, tile: Tile, target: Player | TerraNullius): MutableBoat
}
export interface Game {
// Throws exception is player not found
player(id: PlayerID): Player
playerByClientID(id: ClientID): Player | null
hasPlayer(id: PlayerID): boolean
players(): Player[]
tile(cell: Cell): Tile
@@ -183,11 +185,10 @@ export interface Game {
export interface MutableGame extends Game {
player(id: PlayerID): MutablePlayer
playerByClientID(id: ClientID): MutablePlayer | null
players(): MutablePlayer[]
addPlayer(playerInfo: PlayerInfo, troops: number): MutablePlayer
executions(): Execution[]
// todo move to player.
createAllianceRequest(requestor: Player, recipient: Player): MutableAllianceRequest
}
export class TileEvent implements GameEvent {
@@ -202,6 +203,10 @@ export class BoatEvent implements GameEvent {
constructor(public readonly boat: Boat, public oldTile: Tile) { }
}
export class AllianceRequestEvent implements GameEvent {
constructor(public readonly allianceRequest: AllianceRequest) { }
}
export class AllianceRequestReplyEvent implements GameEvent {
constructor(public readonly allianceRequest: AllianceRequest, public readonly accepted: boolean) { }
}
+13 -1
View File
@@ -1,13 +1,14 @@
import {info} from "console";
import {Config} from "../configuration/Config";
import {EventBus} from "../EventBus";
import {Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerraNullius, Tile, TileEvent, Boat, BoatEvent, PlayerType, MutableAllianceRequest, AllianceRequestReplyEvent} from "./Game";
import {Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerraNullius, Tile, TileEvent, Boat, BoatEvent, PlayerType, MutableAllianceRequest, AllianceRequestReplyEvent, AllianceRequestEvent} from "./Game";
import {TerrainMap} from "./TerrainMapLoader";
import {PlayerImpl} from "./PlayerImpl";
import {TerraNulliusImpl} from "./TerraNulliusImpl";
import {TileImpl} from "./TileImpl";
import {AllianceRequestImpl} from "./AllianceRequestImpl";
import {AllianceImpl} from "./AllianceImpl";
import {ClientID} from "../Schemas";
export function createGame(terrainMap: TerrainMap, eventBus: EventBus, config: Config): Game {
return new GameImpl(terrainMap, eventBus, config)
@@ -50,6 +51,7 @@ export class GameImpl implements MutableGame {
createAllianceRequest(requestor: Player, recipient: Player): MutableAllianceRequest {
const ar = new AllianceRequestImpl(requestor, recipient, this._ticks, this)
this.allianceRequests.push(ar)
this.eventBus.emit(new AllianceRequestEvent(ar))
return ar
}
@@ -192,6 +194,16 @@ export class GameImpl implements MutableGame {
return this._players.get(id)
}
playerByClientID(id: ClientID): MutablePlayer | null {
for (const [pID, player] of this._players) {
if (player.clientID() == id) {
return player
}
}
return null
}
tile(cell: Cell): Tile {
this.assertIsOnMap(cell)
return this.map[cell.x][cell.y]
+4
View File
@@ -150,6 +150,10 @@ export class PlayerImpl implements MutablePlayer {
this.gs.breakAlliance(this, other)
}
createAllianceRequest(recipient: Player): MutableAllianceRequest {
return this.gs.createAllianceRequest(this, recipient)
}
hash(): number {
return simpleHash(this.id()) * (this.troops() + this.numTilesOwned());
}