Use vue, migrate emoji table to vue

This commit is contained in:
evanpelle
2024-10-11 17:47:35 -07:00
parent 0904125b69
commit d85370dc19
16 changed files with 234 additions and 207 deletions
+61 -8
View File
@@ -22,6 +22,7 @@
"googleapis": "^143.0.0",
"hammerjs": "^2.0.8",
"jimp": "^0.22.12",
"lit": "^3.2.1",
"msgpack5": "^6.0.2",
"node-addon-api": "^8.1.0",
"node-gyp": "^10.2.0",
@@ -43,7 +44,7 @@
"@types/d3": "^7.4.3",
"@types/jest": "^29.5.12",
"@types/mocha": "^10.0.7",
"@types/node": "^22.5.2",
"@types/node": "^22.7.5",
"@types/sinon": "^17.0.3",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.11",
@@ -69,7 +70,7 @@
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"typescript": "^5.5.4",
"typescript": "^5.6.3",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4"
@@ -3655,6 +3656,21 @@
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
"dev": true
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz",
"integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==",
"license": "BSD-3-Clause"
},
"node_modules/@lit/reactive-element": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.2.0"
}
},
"node_modules/@npmcli/agent": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz",
@@ -4428,9 +4444,9 @@
}
},
"node_modules/@types/node": {
"version": "22.5.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz",
"integrity": "sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==",
"version": "22.7.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
@@ -4542,6 +4558,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT"
},
"node_modules/@types/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
@@ -9990,6 +10012,37 @@
"dev": true,
"license": "MIT"
},
"node_modules/lit": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz",
"integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit/reactive-element": "^2.0.4",
"lit-element": "^4.1.0",
"lit-html": "^3.2.0"
}
},
"node_modules/lit-element": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz",
"integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.2.0",
"@lit/reactive-element": "^2.0.4",
"lit-html": "^3.2.0"
}
},
"node_modules/lit-html": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz",
"integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==",
"license": "BSD-3-Clause",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
},
"node_modules/load-bmfont": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz",
@@ -13470,9 +13523,9 @@
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
+3 -2
View File
@@ -20,7 +20,7 @@
"@types/d3": "^7.4.3",
"@types/jest": "^29.5.12",
"@types/mocha": "^10.0.7",
"@types/node": "^22.5.2",
"@types/node": "^22.7.5",
"@types/sinon": "^17.0.3",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.11",
@@ -46,7 +46,7 @@
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"typescript": "^5.5.4",
"typescript": "^5.6.3",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4"
@@ -68,6 +68,7 @@
"googleapis": "^143.0.0",
"hammerjs": "^2.0.8",
"jimp": "^0.22.12",
"lit": "^3.2.1",
"msgpack5": "^6.0.2",
"node-addon-api": "^8.1.0",
"node-gyp": "^10.2.0",
+9 -4
View File
@@ -8,19 +8,24 @@ import {EventBus} from "../../core/EventBus";
import {TransformHandler} from "./TransformHandler";
import {Layer} from "./layers/Layer";
import {EventsDisplay} from "./layers/EventsDisplay";
import {RadialMenu} from "./layers/RadialMenu";
import {RadialMenu} from "./layers/radial/RadialMenu";
import {EmojiTable} from "./layers/radial/EmojiTable";
export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer {
const transformHandler = new TransformHandler(game, eventBus, canvas)
const emojiTable = document.querySelector('emoji-table') as EmojiTable;
if (!emojiTable || !(emojiTable instanceof EmojiTable)) {
console.error('EmojiTable element not found in the DOM');
}
const layers: Layer[] = [
new TerrainLayer(game),
new TerritoryLayer(game, eventBus),
new NameLayer(game, game.config().theme(), transformHandler, clientID),
new UILayer(eventBus, game, clientID, transformHandler),
new EventsDisplay(eventBus, game, clientID),
new RadialMenu(eventBus, game, transformHandler, clientID),
new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable),
]
return new GameRenderer(game, eventBus, canvas, transformHandler, layers)
@@ -65,7 +70,7 @@ export class GameRenderer {
this.layers.forEach(l => {
if (l.shouldTransform()) {
l.render(this.context)
l.renderLayer(this.context)
}
})
@@ -73,7 +78,7 @@ export class GameRenderer {
this.layers.forEach(l => {
if (!l.shouldTransform()) {
l.render(this.context)
l.renderLayer(this.context)
}
})
+1 -1
View File
@@ -263,7 +263,7 @@ export class EventsDisplay implements Layer {
this.events[index] = event;
}
render(): void { }
renderLayer(): void { }
renderTable(): void {
if (this.events.length === 0) {
+1 -1
View File
@@ -3,6 +3,6 @@ import {TransformHandler} from "../TransformHandler"
export interface Layer {
init()
tick()
render(context: CanvasRenderingContext2D)
renderLayer(context: CanvasRenderingContext2D)
shouldTransform(): boolean
}
+1 -1
View File
@@ -100,7 +100,7 @@ export class NameLayer implements Layer {
}
}
public render(mainContex: CanvasRenderingContext2D) {
public renderLayer(mainContex: CanvasRenderingContext2D) {
const [upperLeft, bottomRight] = this.transformHandler.screenBoundingRect()
for (const render of this.renders) {
render.isVisible = this.isVisible(render, upperLeft, bottomRight)
+1 -1
View File
@@ -41,7 +41,7 @@ export class TerrainLayer implements Layer {
})
}
render(context: CanvasRenderingContext2D) {
renderLayer(context: CanvasRenderingContext2D) {
context.drawImage(
this.canvas,
-this.game.width() / 2,
+1 -1
View File
@@ -50,7 +50,7 @@ export class TerritoryLayer implements Layer {
})
}
render(context: CanvasRenderingContext2D) {
renderLayer(context: CanvasRenderingContext2D) {
this.renderTerritory()
this.context.putImageData(this.imageData, 0, 0);
context.drawImage(
+1 -1
View File
@@ -30,7 +30,7 @@ export class UILayer implements Layer {
}
render(context: CanvasRenderingContext2D) {
renderLayer(context: CanvasRenderingContext2D) {
if (!this.game.inSpawnPhase()) {
return
}
@@ -0,0 +1,107 @@
import {LitElement, html, css} from 'lit';
import {customElement, state} from 'lit/decorators.js';
const emojiTable: string[][] = [
["😀", "😱", "🤩", "🎯", "🥺"],
["🪦", "👏", "🥉", "🥈", "🥇"],
["🤙", "🥰", "😇", "😊", "🔥"],
["💪", "🥳", "💀", "😭", "🤦‍♂️"],
["😎", "👎", "👍", "🥱", "💔"],
["❤️", "💰", "🤝", "🛡️", "💥"],
["🆘", "🕊️", "➡️", "⬅️", "↙️"],
["↖️", "↗️", "⬆️", "↘️", "⬇️"]
];
@customElement('emoji-table')
export class EmojiTable extends LitElement {
static styles = css`
:host {
display: block;
}
.emoji-table {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
background-color: #1E1E1E;
padding: 15px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
max-width: 95vw;
max-height: 95vh;
overflow-y: auto;
}
.emoji-row {
display: flex;
justify-content: center;
width: 100%;
}
.emoji-button {
font-size: 60px;
width: 80px;
height: 80px;
border: 1px solid #333;
background-color: #2C2C2C;
color: white;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
margin: 8px;
}
.emoji-button:hover {
background-color: #3A3A3A;
transform: scale(1.1);
}
.emoji-button:active {
background-color: #4A4A4A;
transform: scale(0.95);
}
.hidden {
display: none !important;
}
`;
@state()
private _hidden = true;
public onEmojiClicked: (emoji: string) => void = () => { }
render() {
return html`
<div class="emoji-table ${this._hidden ? 'hidden' : ''}">
${emojiTable.map(row => html`
<div class="emoji-row">
${row.map(emoji => html`
<button class="emoji-button" @click=${() => this.onEmojiClicked(emoji)}>
${emoji}
</button>
`)}
</div>
`)}
</div>
`;
}
hideTable() {
this._hidden = true;
this.requestUpdate();
}
showTable() {
this._hidden = false;
this.requestUpdate();
}
get isVisible() {
return !this._hidden;
}
}
@@ -1,21 +1,21 @@
import {EventBus} from "../../../core/EventBus";
import {AllPlayers, Cell, Game, Player} from "../../../core/game/Game";
import {ClientID} from "../../../core/Schemas";
import {and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore} from "../../../core/Util";
import {ContextMenuEvent, MouseUpEvent} from "../../InputHandler";
import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../Transport";
import {TransformHandler} from "../TransformHandler";
import {Layer} from "./Layer";
import {EventBus} from "../../../../core/EventBus";
import {AllPlayers, Cell, Game, Player} from "../../../../core/game/Game";
import {ClientID} from "../../../../core/Schemas";
import {and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore} from "../../../../core/Util";
import {ContextMenuEvent, MouseUpEvent} from "../../../InputHandler";
import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../../Transport";
import {TransformHandler} from "../../TransformHandler";
import {Layer} from "../Layer";
import * as d3 from 'd3';
import traitorIcon from '../../../../resources/images/TraitorIconWhite.png';
import allianceIcon from '../../../../resources/images/AllianceIconWhite.png';
import boatIcon from '../../../../resources/images/BoatIconWhite.png';
import swordIcon from '../../../../resources/images/SwordIconWhite.png';
import targetIcon from '../../../../resources/images/TargetIconWhite.png';
import emojiIcon from '../../../../resources/images/EmojiIconWhite.png';
import disabledIcon from '../../../../resources/images/DisabledIcon.png';
import donateIcon from '../../../../resources/images/DonateIconWhite.png';
import {MessageType} from "./EventsDisplay";
import traitorIcon from '../../../../../resources/images/TraitorIconWhite.png';
import allianceIcon from '../../../../../resources/images/AllianceIconWhite.png';
import boatIcon from '../../../../../resources/images/BoatIconWhite.png';
import swordIcon from '../../../../../resources/images/SwordIconWhite.png';
import targetIcon from '../../../../../resources/images/TargetIconWhite.png';
import emojiIcon from '../../../../../resources/images/EmojiIconWhite.png';
import disabledIcon from '../../../../../resources/images/DisabledIcon.png';
import donateIcon from '../../../../../resources/images/DonateIconWhite.png';
import {EmojiTable} from "./EmojiTable";
enum Slot {
@@ -45,12 +45,12 @@ export class RadialMenu implements Layer {
private isCenterButtonEnabled = false
constructor(
private eventBus: EventBus,
private game: Game,
private transformHandler: TransformHandler,
private clientID: ClientID,
private emojiTable: EmojiTable
) { }
init() {
@@ -59,49 +59,6 @@ export class RadialMenu implements Layer {
this.createMenuElement();
}
private hideEmojiTable(): void {
const emojiTable: HTMLTableElement | null = document.getElementById('uniqueEmojiTable') as HTMLTableElement | null;
if (emojiTable instanceof HTMLTableElement) {
if (!emojiTable.classList.contains('hidden')) {
emojiTable.classList.add('hidden');
}
} else {
console.error('Emoji table not found');
}
}
private showEmojiTable(recipient: Player | typeof AllPlayers): void {
const emojiTable: HTMLTableElement | null = document.getElementById('uniqueEmojiTable') as HTMLTableElement | null;
if (emojiTable instanceof HTMLTableElement) {
emojiTable.classList.remove('hidden');
} else {
console.error('Emoji table not found');
}
this.setupEmojiButtons(recipient)
}
private setupEmojiButtons(recipient: Player | typeof AllPlayers) {
let emojiTable = document.getElementById('uniqueEmojiTable');
if (emojiTable) {
// Remove existing listeners
emojiTable.replaceWith(emojiTable.cloneNode(true));
emojiTable = document.getElementById('uniqueEmojiTable');
emojiTable.addEventListener('click', (event) => {
const emojiElement = event.target as HTMLElement;
if (emojiElement.classList.contains('emoji-button')) {
const emoji = emojiElement.textContent;
this.hideEmojiTable()
this.eventBus.emit(new SendEmojiIntentEvent(recipient, emoji))
}
});
} else {
console.error('Emoji table not found');
}
}
private createMenuElement() {
this.menuElement = d3.select(document.body)
.append('div')
@@ -227,7 +184,7 @@ export class RadialMenu implements Layer {
// Update logic if needed
}
render(context: CanvasRenderingContext2D) {
renderLayer(context: CanvasRenderingContext2D) {
// No need to render anything on the canvas
}
@@ -272,7 +229,11 @@ export class RadialMenu implements Layer {
const target = tile.owner() == myPlayer ? AllPlayers : (tile.owner() as Player)
if (myPlayer.canSendEmoji(target)) {
this.activateMenuElement(Slot.Emoji, "#ebe250", emojiIcon, () => {
this.showEmojiTable(target)
this.emojiTable.onEmojiClicked = (emoji: string) => {
this.emojiTable.hideTable()
this.eventBus.emit(new SendEmojiIntentEvent(target, emoji))
}
this.emojiTable.showTable()
})
}
}
@@ -387,7 +348,7 @@ export class RadialMenu implements Layer {
private onPointerUp(event: MouseUpEvent) {
this.hideRadialMenu()
this.hideEmojiTable()
this.emojiTable.hideTable()
}
private showRadialMenu(x: number, y: number) {
@@ -467,4 +428,4 @@ export class RadialMenu implements Layer {
.attr('fill', enabled ? 'white' : '#cccccc');
}, 25);
}
}
}
+1 -58
View File
@@ -79,64 +79,7 @@
<div id="app"></div>
<div id="radialMenu" class="radial-menu"></div>
<table id="uniqueEmojiTable" class="emoji-table hidden">
<tr>
<td><button class="emoji-button">😀</button></td>
<td><button class="emoji-button">😱</button></td>
<td><button class="emoji-button">🤩</button></td>
<td><button class="emoji-button">🎯</button></td>
<td><button class="emoji-button">🥺</button></td>
</tr>
<tr>
<td><button class="emoji-button">🪦</button></td>
<td><button class="emoji-button">👏</button></td>
<td><button class="emoji-button">🥉</button></td>
<td><button class="emoji-button">🥈</button></td>
<td><button class="emoji-button">🥇</button></td>
</tr>
<tr>
<td><button class="emoji-button">🤙</button></td>
<td><button class="emoji-button">🥰</button></td>
<td><button class="emoji-button">😇</button></td>
<td><button class="emoji-button">😊</button></td>
<td><button class="emoji-button">🔥</button></td>
</tr>
<tr>
<td><button class="emoji-button">💪</button></td>
<td><button class="emoji-button">🥳</button></td>
<td><button class="emoji-button">💀</button></td>
<td><button class="emoji-button">😭</button></td>
<td><button class="emoji-button">🤦‍♂️</button></td>
</tr>
<tr>
<td><button class="emoji-button">😎</button></td>
<td><button class="emoji-button">👎</button></td>
<td><button class="emoji-button">👍</button></td>
<td><button class="emoji-button">🥱</button></td>
<td><button class="emoji-button">💔</button></td>
</tr>
<tr>
<td><button class="emoji-button">❤️</button></td>
<td><button class="emoji-button">💰</button></td>
<td><button class="emoji-button">🤝</button></td>
<td><button class="emoji-button">🛡️</button></td>
<td><button class="emoji-button">💥</button></td>
</tr>
<tr>
<td><button class="emoji-button">🆘</button></td>
<td><button class="emoji-button">🕊️</button></td>
<td><button class="emoji-button">➡️</button></td>
<td><button class="emoji-button">⬅️</button></td>
<td><button class="emoji-button">↙️</button></td>
</tr>
<tr>
<td><button class="emoji-button">↖️</button></td>
<td><button class="emoji-button">↗️</button></td>
<td><button class="emoji-button">⬆️</button></td>
<td><button class="emoji-button">↘️</button></td>
<td><button class="emoji-button">⬇️</button></td>
</tr>
</table>
<emoji-table></emoji-table>
<style>
body {
+12
View File
@@ -0,0 +1,12 @@
import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('my-element')
export class MyElement extends LitElement {
@property()
name = 'World';
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
-57
View File
@@ -406,64 +406,7 @@ h3 {
}
/* EMOJI Table */
.emoji-table {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
background-color: #1E1E1E;
padding: 15px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
max-width: 95vw;
max-height: 95vh;
overflow-y: auto;
}
.emoji-row {
display: flex;
justify-content: center;
width: 100%;
}
.emoji-button {
font-size: 60px;
/* Increased font size for larger emojis */
width: 80px;
/* Increased width */
height: 80px;
/* Increased height */
border: 1px solid #333;
background-color: #2C2C2C;
color: white;
border-radius: 12px;
/* Slightly increased border radius */
cursor: pointer;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
margin: 8px;
/* Increased margin */
}
.emoji-button:hover {
background-color: #3A3A3A;
transform: scale(1.1);
}
.emoji-button:active {
background-color: #4A4A4A;
transform: scale(0.95);
}
.emoji-table.hidden {
display: none;
}
@media (max-width: 600px) {
.emoji-button {
+6 -6
View File
@@ -8,12 +8,12 @@ export const devConfig = new class extends DefaultConfig {
numSpawnPhaseTurns(): number {
return 40
}
// gameCreationRate(): number {
// return 2 * 1000
// }
// lobbyLifetime(): number {
// return 2 * 1000
// }
gameCreationRate(): number {
return 2 * 1000
}
lobbyLifetime(): number {
return 2 * 1000
}
turnIntervalMs(): number {
return 100
}
+3 -1
View File
@@ -6,7 +6,9 @@
"moduleResolution": "node",
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
"esModuleInterop": true,
"experimentalDecorators": true,
"useDefineForClassFields": false,
},
"include": [
"src/**/*",