format all files with prettier

This commit is contained in:
Evan
2025-02-12 08:28:15 -08:00
parent a30079ac94
commit 40966ca3b9
51 changed files with 2492 additions and 2458 deletions
+15 -15
View File
@@ -64,13 +64,13 @@ export interface LobbyConfig {
export function joinLobby(
lobbyConfig: LobbyConfig,
onjoin: () => void
onjoin: () => void,
): () => void {
const eventBus = new EventBus();
initRemoteSender(eventBus);
consolex.log(
`joinging lobby: gameID: ${lobbyConfig.gameID}, clientID: ${lobbyConfig.clientID}, persistentID: ${lobbyConfig.persistentID}`
`joinging lobby: gameID: ${lobbyConfig.gameID}, clientID: ${lobbyConfig.clientID}, persistentID: ${lobbyConfig.persistentID}`,
);
const serverConfig = getServerConfig();
@@ -91,7 +91,7 @@ export function joinLobby(
lobbyConfig,
gameConfig,
eventBus,
serverConfig
serverConfig,
);
const onconnect = () => {
@@ -103,7 +103,7 @@ export function joinLobby(
consolex.log("lobby: game started");
onjoin();
createClientGame(lobbyConfig, message.config, eventBus, transport).then(
(r) => r.start()
(r) => r.start(),
);
}
};
@@ -118,7 +118,7 @@ export async function createClientGame(
lobbyConfig: LobbyConfig,
gameConfig: GameConfig,
eventBus: EventBus,
transport: Transport
transport: Transport,
): Promise<ClientGameRunner> {
const config = getConfig(gameConfig);
@@ -126,14 +126,14 @@ export async function createClientGame(
const worker = new WorkerClient(
lobbyConfig.gameID,
gameConfig,
lobbyConfig.clientID
lobbyConfig.clientID,
);
await worker.initialize();
const gameView = new GameView(
worker,
config,
gameMap.gameMap,
lobbyConfig.clientID
lobbyConfig.clientID,
);
consolex.log("going to init path finder");
@@ -143,11 +143,11 @@ export async function createClientGame(
canvas,
gameView,
eventBus,
lobbyConfig.clientID
lobbyConfig.clientID,
);
consolex.log(
`creating private game got difficulty: ${gameConfig.difficulty}`
`creating private game got difficulty: ${gameConfig.difficulty}`,
);
return new ClientGameRunner(
@@ -157,7 +157,7 @@ export async function createClientGame(
new InputHandler(canvas, eventBus),
transport,
worker,
gameView
gameView,
);
}
@@ -175,7 +175,7 @@ export class ClientGameRunner {
private input: InputHandler,
private transport: Transport,
private worker: WorkerClient,
private gameView: GameView
private gameView: GameView,
) {}
public start() {
@@ -223,7 +223,7 @@ export class ClientGameRunner {
}
if (this.turnsSeen != message.turn.turnNumber) {
consolex.error(
`got wrong turn have turns ${this.turnsSeen}, received turn ${message.turn.turnNumber}`
`got wrong turn have turns ${this.turnsSeen}, received turn ${message.turn.turnNumber}`,
);
} else {
this.worker.sendTurn(message.turn);
@@ -246,7 +246,7 @@ export class ClientGameRunner {
}
const cell = this.renderer.transformHandler.screenToWorldCoordinates(
event.x,
event.y
event.y,
);
if (!this.gameView.isValidCoord(cell.x, cell.y)) {
return;
@@ -276,8 +276,8 @@ export class ClientGameRunner {
this.eventBus.emit(
new SendAttackIntentEvent(
this.gameView.owner(tile).id(),
this.myPlayer.troops() * this.renderer.uiState.attackRatio
)
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
),
);
}
});
+5 -5
View File
@@ -355,7 +355,7 @@ export class HostLobbyModal extends LitElement {
.selected=${this.selectedMap === value}
></map-display>
</div>
`
`,
)}
</div>
</div>
@@ -381,7 +381,7 @@ export class HostLobbyModal extends LitElement {
${DifficultyDescription[key]}
</p>
</div>
`
`,
)}
</div>
</div>
@@ -443,7 +443,7 @@ export class HostLobbyModal extends LitElement {
<div class="players-list">
${this.players.map(
(player) => html`<span class="player-tag">${player}</span>`
(player) => html`<span class="player-tag">${player}</span>`,
)}
</div>
</div>
@@ -484,7 +484,7 @@ export class HostLobbyModal extends LitElement {
},
bubbles: true,
composed: true,
})
}),
);
});
this.isModalOpen = true;
@@ -545,7 +545,7 @@ export class HostLobbyModal extends LitElement {
private async startGame() {
consolex.log(
`Starting private game with map: ${GameMapType[this.selectedMap]}`
`Starting private game with map: ${GameMapType[this.selectedMap]}`,
);
this.close();
const response = await fetch(`/start_private_lobby/${this.lobbyId}`, {
+32 -11
View File
@@ -2,31 +2,46 @@ import { EventBus, GameEvent } from "../core/EventBus";
import { Game } from "../core/game/Game";
export class MouseUpEvent implements GameEvent {
constructor(public readonly x: number, public readonly y: number) {}
constructor(
public readonly x: number,
public readonly y: number,
) {}
}
export class MouseDownEvent implements GameEvent {
constructor(public readonly x: number, public readonly y: number) {}
constructor(
public readonly x: number,
public readonly y: number,
) {}
}
export class MouseMoveEvent implements GameEvent {
constructor(public readonly x: number, public readonly y: number) {}
constructor(
public readonly x: number,
public readonly y: number,
) {}
}
export class ContextMenuEvent implements GameEvent {
constructor(public readonly x: number, public readonly y: number) {}
constructor(
public readonly x: number,
public readonly y: number,
) {}
}
export class ZoomEvent implements GameEvent {
constructor(
public readonly x: number,
public readonly y: number,
public readonly delta: number
public readonly delta: number,
) {}
}
export class DragEvent implements GameEvent {
constructor(public readonly deltaX: number, public readonly deltaY: number) {}
constructor(
public readonly deltaX: number,
public readonly deltaY: number,
) {}
}
export class AlternateViewEvent implements GameEvent {
@@ -36,7 +51,10 @@ export class AlternateViewEvent implements GameEvent {
export class RefreshGraphicsEvent implements GameEvent {}
export class ShowBuildMenuEvent implements GameEvent {
constructor(public readonly x: number, public readonly y: number) {}
constructor(
public readonly x: number,
public readonly y: number,
) {}
}
export class InputHandler {
@@ -60,7 +78,10 @@ export class InputHandler {
private readonly PAN_SPEED = 5;
private readonly ZOOM_SPEED = 10;
constructor(private canvas: HTMLCanvasElement, private eventBus: EventBus) {}
constructor(
private canvas: HTMLCanvasElement,
private eventBus: EventBus,
) {}
initialize() {
this.canvas.addEventListener("pointerdown", (e) => this.onPointerDown(e));
@@ -105,12 +126,12 @@ export class InputHandler {
if (this.activeKeys.has("Minus")) {
this.eventBus.emit(
new ZoomEvent(screenCenterX, screenCenterY, this.ZOOM_SPEED)
new ZoomEvent(screenCenterX, screenCenterY, this.ZOOM_SPEED),
);
}
if (this.activeKeys.has("Equal")) {
this.eventBus.emit(
new ZoomEvent(screenCenterX, screenCenterY, -this.ZOOM_SPEED)
new ZoomEvent(screenCenterX, screenCenterY, -this.ZOOM_SPEED),
);
}
}, 1);
@@ -251,7 +272,7 @@ export class InputHandler {
if (Math.abs(pinchDelta) > 1) {
const zoomCenter = this.getPinchCenter();
this.eventBus.emit(
new ZoomEvent(zoomCenter.x, zoomCenter.y, -pinchDelta * 2)
new ZoomEvent(zoomCenter.x, zoomCenter.y, -pinchDelta * 2),
);
this.lastPinchDistance = currentPinchDistance;
}
+3 -3
View File
@@ -264,7 +264,7 @@ export class JoinPrivateLobbyModal extends LitElement {
<div class="players-list">
${this.players.map(
(player) =>
html`<span class="player-tag">${player}</span>`
html`<span class="player-tag">${player}</span>`,
)}
</div>
</div>`
@@ -302,7 +302,7 @@ export class JoinPrivateLobbyModal extends LitElement {
detail: { lobby: this.lobbyIdInput.value },
bubbles: true,
composed: true,
})
}),
);
}
@@ -340,7 +340,7 @@ export class JoinPrivateLobbyModal extends LitElement {
},
bubbles: true,
composed: true,
})
}),
);
this.playersInterval = setInterval(() => this.pollPlayers(), 1000);
} else {
+138 -149
View File
@@ -1,179 +1,168 @@
import { ClientGameRunner, joinLobby } from './ClientGameRunner';
import backgroundImage from '../../resources/images/EuropeBackground.svg';
import favicon from '../../resources/images/Favicon.svg';
import { ClientGameRunner, joinLobby } from "./ClientGameRunner";
import backgroundImage from "../../resources/images/EuropeBackground.svg";
import favicon from "../../resources/images/Favicon.svg";
import './PublicLobby';
import './UsernameInput';
import './styles.css';
import { UsernameInput } from './UsernameInput';
import { SinglePlayerModal } from './SinglePlayerModal';
import { HostLobbyModal as HostPrivateLobbyModal } from './HostLobbyModal';
import { JoinPrivateLobbyModal } from './JoinPrivateLobbyModal';
import { generateID } from '../core/Util';
import { generateCryptoRandomUUID } from './Utils';
import { consolex } from '../core/Consolex';
import './components/FlagInput';
import { FlagInput } from './components/FlagInput';
import "./PublicLobby";
import "./UsernameInput";
import "./styles.css";
import { UsernameInput } from "./UsernameInput";
import { SinglePlayerModal } from "./SinglePlayerModal";
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
import { generateID } from "../core/Util";
import { generateCryptoRandomUUID } from "./Utils";
import { consolex } from "../core/Consolex";
import "./components/FlagInput";
import { FlagInput } from "./components/FlagInput";
class Client {
private gameStop: () => void;
private gameStop: () => void;
private usernameInput: UsernameInput | null = null;
private flagInput: FlagInput | null = null;
private usernameInput: UsernameInput | null = null;
private flagInput: FlagInput | null = null;
private joinModal: JoinPrivateLobbyModal;
constructor() {}
private joinModal: JoinPrivateLobbyModal;
constructor() {}
initialize(): void {
this.flagInput = document.querySelector('flag-input') as FlagInput;
if (!this.flagInput) {
consolex.warn('Flag input element not found');
}
initialize(): void {
this.flagInput = document.querySelector("flag-input") as FlagInput;
if (!this.flagInput) {
consolex.warn("Flag input element not found");
}
this.usernameInput = document.querySelector(
'username-input'
) as UsernameInput;
if (!this.usernameInput) {
consolex.warn('Username input element not found');
}
window.addEventListener('beforeunload', (event) => {
consolex.log('Browser is closing');
if (this.gameStop != null) {
this.gameStop();
}
});
this.usernameInput = document.querySelector(
"username-input",
) as UsernameInput;
if (!this.usernameInput) {
consolex.warn("Username input element not found");
}
window.addEventListener("beforeunload", (event) => {
consolex.log("Browser is closing");
if (this.gameStop != null) {
this.gameStop();
}
});
setFavicon();
document.addEventListener(
'join-lobby',
this.handleJoinLobby.bind(this)
);
document.addEventListener(
'leave-lobby',
this.handleLeaveLobby.bind(this)
);
document.addEventListener(
'single-player',
this.handleSinglePlayer.bind(this)
);
setFavicon();
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
document.addEventListener(
"single-player",
this.handleSinglePlayer.bind(this),
);
const spModal = document.querySelector(
'single-player-modal'
) as SinglePlayerModal;
spModal instanceof SinglePlayerModal;
document
.getElementById('single-player')
.addEventListener('click', () => {
if (this.usernameInput.isValid()) {
spModal.open();
}
});
const spModal = document.querySelector(
"single-player-modal",
) as SinglePlayerModal;
spModal instanceof SinglePlayerModal;
document.getElementById("single-player").addEventListener("click", () => {
if (this.usernameInput.isValid()) {
spModal.open();
}
});
const hostModal = document.querySelector(
'host-lobby-modal'
) as HostPrivateLobbyModal;
hostModal instanceof HostPrivateLobbyModal;
document
.getElementById('host-lobby-button')
.addEventListener('click', () => {
if (this.usernameInput.isValid()) {
hostModal.open();
}
});
const hostModal = document.querySelector(
"host-lobby-modal",
) as HostPrivateLobbyModal;
hostModal instanceof HostPrivateLobbyModal;
document
.getElementById("host-lobby-button")
.addEventListener("click", () => {
if (this.usernameInput.isValid()) {
hostModal.open();
}
});
this.joinModal = document.querySelector(
'join-private-lobby-modal'
) as JoinPrivateLobbyModal;
this.joinModal instanceof JoinPrivateLobbyModal;
document
.getElementById('join-private-lobby-button')
.addEventListener('click', () => {
if (this.usernameInput.isValid()) {
this.joinModal.open();
}
});
}
this.joinModal = document.querySelector(
"join-private-lobby-modal",
) as JoinPrivateLobbyModal;
this.joinModal instanceof JoinPrivateLobbyModal;
document
.getElementById("join-private-lobby-button")
.addEventListener("click", () => {
if (this.usernameInput.isValid()) {
this.joinModal.open();
}
});
}
private async handleJoinLobby(event: CustomEvent) {
const lobby = event.detail.lobby;
consolex.log(`joining lobby ${lobby.id}`);
if (this.gameStop != null) {
consolex.log('joining lobby, stopping existing game');
this.gameStop();
}
this.gameStop = joinLobby(
{
gameType: event.detail.gameType,
flag: (): string => this.flagInput.getCurrentFlag(),
playerName: (): string =>
this.usernameInput.getCurrentUsername(),
gameID: lobby.id,
persistentID: getPersistentIDFromCookie(),
playerID: generateID(),
clientID: generateID(),
map: event.detail.map,
difficulty: event.detail.difficulty,
disableBots: event.detail.disableBots,
disableNPCs: event.detail.disableNPCs,
creativeMode: event.detail.creativeMode,
},
() => this.joinModal.close()
);
}
private async handleJoinLobby(event: CustomEvent) {
const lobby = event.detail.lobby;
consolex.log(`joining lobby ${lobby.id}`);
if (this.gameStop != null) {
consolex.log("joining lobby, stopping existing game");
this.gameStop();
}
this.gameStop = joinLobby(
{
gameType: event.detail.gameType,
flag: (): string => this.flagInput.getCurrentFlag(),
playerName: (): string => this.usernameInput.getCurrentUsername(),
gameID: lobby.id,
persistentID: getPersistentIDFromCookie(),
playerID: generateID(),
clientID: generateID(),
map: event.detail.map,
difficulty: event.detail.difficulty,
disableBots: event.detail.disableBots,
disableNPCs: event.detail.disableNPCs,
creativeMode: event.detail.creativeMode,
},
() => this.joinModal.close(),
);
}
private async handleLeaveLobby(event: CustomEvent) {
if (this.gameStop == null) {
return;
}
consolex.log('leaving lobby, cancelling game');
this.gameStop();
this.gameStop = null;
}
private async handleLeaveLobby(event: CustomEvent) {
if (this.gameStop == null) {
return;
}
consolex.log("leaving lobby, cancelling game");
this.gameStop();
this.gameStop = null;
}
private async handleSinglePlayer(event: CustomEvent) {
alert('coming soon');
}
private async handleSinglePlayer(event: CustomEvent) {
alert("coming soon");
}
}
// Initialize the client when the DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new Client().initialize();
document.addEventListener("DOMContentLoaded", () => {
new Client().initialize();
});
document.body.style.backgroundImage = `url(${backgroundImage})`;
function setFavicon(): void {
const link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = favicon;
document.head.appendChild(link);
const link = document.createElement("link");
link.type = "image/x-icon";
link.rel = "shortcut icon";
link.href = favicon;
document.head.appendChild(link);
}
// WARNING: DO NOT EXPOSE THIS ID
export function getPersistentIDFromCookie(): string {
const COOKIE_NAME = 'player_persistent_id';
const COOKIE_NAME = "player_persistent_id";
// Try to get existing cookie
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [cookieName, cookieValue] = cookie
.split('=')
.map((c) => c.trim());
if (cookieName === COOKIE_NAME) {
return cookieValue;
}
}
// Try to get existing cookie
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
const [cookieName, cookieValue] = cookie.split("=").map((c) => c.trim());
if (cookieName === COOKIE_NAME) {
return cookieValue;
}
}
// If no cookie exists, create new ID and set cookie
const newID = generateCryptoRandomUUID();
document.cookie = [
`${COOKIE_NAME}=${newID}`,
`max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years
'path=/',
'SameSite=Strict',
'Secure',
].join(';');
// If no cookie exists, create new ID and set cookie
const newID = generateCryptoRandomUUID();
document.cookie = [
`${COOKIE_NAME}=${newID}`,
`max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years
"path=/",
"SameSite=Strict",
"Secure",
].join(";");
return newID;
return newID;
}
+3 -3
View File
@@ -20,7 +20,7 @@ export class PublicLobby extends LitElement {
this.fetchAndUpdateLobbies();
this.lobbiesInterval = window.setInterval(
() => this.fetchAndUpdateLobbies(),
1000
1000,
);
}
@@ -111,7 +111,7 @@ export class PublicLobby extends LitElement {
},
bubbles: true,
composed: true,
})
}),
);
} else {
this.dispatchEvent(
@@ -119,7 +119,7 @@ export class PublicLobby extends LitElement {
detail: { lobby: this.currLobby },
bubbles: true,
composed: true,
})
}),
);
this.currLobby = null;
}
+4 -4
View File
@@ -251,7 +251,7 @@ export class SinglePlayerModal extends LitElement {
.selected=${this.selectedMap === value}
></map-display>
</div>
`
`,
)}
</div>
</div>
@@ -277,7 +277,7 @@ export class SinglePlayerModal extends LitElement {
${DifficultyDescription[key]}
</p>
</div>
`
`,
)}
</div>
</div>
@@ -363,7 +363,7 @@ export class SinglePlayerModal extends LitElement {
}
private startGame() {
consolex.log(
`Starting single player game with map: ${GameMapType[this.selectedMap]}`
`Starting single player game with map: ${GameMapType[this.selectedMap]}`,
);
this.dispatchEvent(
new CustomEvent("join-lobby", {
@@ -380,7 +380,7 @@ export class SinglePlayerModal extends LitElement {
},
bubbles: true,
composed: true,
})
}),
);
this.close();
}
+2 -2
View File
@@ -52,7 +52,7 @@ export function generateCryptoRandomUUID(): string {
(
c ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
).toString(16)
).toString(16),
);
}
@@ -64,6 +64,6 @@ export function generateCryptoRandomUUID(): string {
const r: number = (Math.random() * 16) | 0;
const v: number = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
},
);
}
+193 -207
View File
@@ -1,231 +1,217 @@
import { LitElement, html, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import Countries from '../data/countries.json';
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import Countries from "../data/countries.json";
const flagKey: string = 'flag';
const flagKey: string = "flag";
@customElement('flag-input')
@customElement("flag-input")
export class FlagInput extends LitElement {
@state() private flag: string = '';
@state() private search: string = '';
@state() private showModal: boolean = false;
@state() private flag: string = "";
@state() private search: string = "";
@state() private showModal: boolean = false;
static styles = css`
.hidden {
display: none;
}
static styles = css`
.hidden {
display: none;
}
.flag-container {
display: flex;
}
.flag-container {
display: flex;
}
.no-selected-flag {
position: absolute;
left: 8px;
top: 8px;
height: 50px;
border-radius: 0.75rem;
border: none;
background: none;
font-size: 1rem;
cursor: pointer;
}
.no-selected-flag {
position: absolute;
left: 8px;
top: 8px;
height: 50px;
border-radius: 0.75rem;
border: none;
background: none;
font-size: 1rem;
cursor: pointer;
}
.selected-flag {
width: 48px;
cursor: pointer;
position: absolute;
left: 24px;
top: 14px;
border: 1px solid black;
}
.selected-flag {
width: 48px;
cursor: pointer;
position: absolute;
left: 24px;
top: 14px;
border: 1px solid black;
}
.flag-modal {
display: flex;
flex-direction: column;
gap: 0.5rem;
position: absolute;
top: 60px;
left: 0;
width: 560px;
height: 500px;
max-height: 50vh;
background-color: rgb(35 35 35 / 0.8);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
padding: 10px;
border-radius: 8px;
}
.flag-modal {
display: flex;
flex-direction: column;
gap: 0.5rem;
position: absolute;
top: 60px;
left: 0;
width: 560px;
height: 500px;
max-height: 50vh;
background-color: rgb(35 35 35 / 0.8);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
padding: 10px;
border-radius: 8px;
}
.flag-search {
height: 2rem;
border-radius: 8px;
border: none;
text-align: center;
font-size: 1.3rem;
}
.flag-search {
height: 2rem;
border-radius: 8px;
border: none;
text-align: center;
font-size: 1.3rem;
}
.flag-dropdown {
overflow-y: auto;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
}
.flag-dropdown {
overflow-y: auto;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
}
.dropdown-item {
opacity: 0.7;
width: calc(100% / 4 - 15px);
text-align: center;
color: white;
cursor: pointer;
border: none;
background: none;
}
.dropdown-item {
opacity: 0.7;
width: calc(100% / 4 - 15px);
text-align: center;
color: white;
cursor: pointer;
border: none;
background: none;
}
.dropdown-item:hover {
opacity: 1;
}
.dropdown-item:hover {
opacity: 1;
}
.country-flag {
width: 100%;
height: auto;
}
.country-flag {
width: 100%;
height: auto;
}
@media (max-width: 768px) {
.flag-modal {
left: 0px;
width: calc(100% - 16px);
height: 50vh;
}
@media (max-width: 768px) {
.flag-modal {
left: 0px;
width: calc(100% - 16px);
height: 50vh;
}
.dropdown-item {
width: calc(100% / 3 - 15px);
}
}
`;
.dropdown-item {
width: calc(100% / 3 - 15px);
}
}
`;
private handleSearch(e: Event) {
this.search = String((e.target as HTMLInputElement).value);
}
private handleSearch(e: Event) {
this.search = String((e.target as HTMLInputElement).value);
}
private setFlag(flag: string) {
this.flag = flag;
this.showModal = false;
this.storeFlag(flag);
}
private setFlag(flag: string) {
this.flag = flag;
this.showModal = false;
this.storeFlag(flag);
}
public getCurrentFlag(): string {
return this.flag;
}
public getCurrentFlag(): string {
return this.flag;
}
private getStoredFlag(): string {
const storedFlag = localStorage.getItem(flagKey);
if (storedFlag) {
return storedFlag;
}
return '';
}
private getStoredFlag(): string {
const storedFlag = localStorage.getItem(flagKey);
if (storedFlag) {
return storedFlag;
}
return "";
}
private storeFlag(flag: string) {
if (flag) {
localStorage.setItem(flagKey, flag);
} else if (flag === '') {
localStorage.removeItem(flagKey);
}
}
private storeFlag(flag: string) {
if (flag) {
localStorage.setItem(flagKey, flag);
} else if (flag === "") {
localStorage.removeItem(flagKey);
}
}
private dispatchFlagEvent() {
this.dispatchEvent(
new CustomEvent('flag-change', {
detail: { flag: this.flag },
bubbles: true,
composed: true,
})
);
}
private dispatchFlagEvent() {
this.dispatchEvent(
new CustomEvent("flag-change", {
detail: { flag: this.flag },
bubbles: true,
composed: true,
}),
);
}
connectedCallback() {
super.connectedCallback();
this.flag = this.getStoredFlag();
this.dispatchFlagEvent();
}
connectedCallback() {
super.connectedCallback();
this.flag = this.getStoredFlag();
this.dispatchFlagEvent();
}
render() {
return html`
<div class="flag-container">
${this.flag === ''
? html` <button
class="no-selected-flag"
@click=${() => (this.showModal = true)}
>
Flags
</button>`
: html`<img
class="selected-flag"
src="flags/${this.flag}.svg"
@click=${() => (this.showModal = true)}
/>`}
${this.showModal
? html`
<div
class="flag-modal ${this.showModal
? ''
: 'hidden'}"
>
<input
class="flag-search"
type="text"
placeholder="Search..."
@change=${this.handleSearch}
@keyup=${this.handleSearch}
/>
<div class="flag-dropdown">
<!-- Show each flag as button -->
<button
@click=${() => this.setFlag('')}
class="dropdown-item"
>
<img
class="country-flag"
src="flags/xx.svg"
/>
<span class="country-name">None</span>
</button>
${Countries.filter(
(country) =>
country.name
.toLowerCase()
.includes(
this.search.toLowerCase()
) ||
country.code
.toLowerCase()
.includes(
this.search.toLowerCase()
)
).map(
(country) => html`
<button
@click=${() =>
this.setFlag(country.code)}
class="dropdown-item"
>
<img
class="country-flag"
src="flags/${country.code}.svg"
/>
<span class="country-name"
>${country.name}</span
>
</button>
`
)}
</div>
</div>
`
: ''}
</div>
`;
}
render() {
return html`
<div class="flag-container">
${this.flag === ""
? html` <button
class="no-selected-flag"
@click=${() => (this.showModal = true)}
>
Flags
</button>`
: html`<img
class="selected-flag"
src="flags/${this.flag}.svg"
@click=${() => (this.showModal = true)}
/>`}
${this.showModal
? html`
<div class="flag-modal ${this.showModal ? "" : "hidden"}">
<input
class="flag-search"
type="text"
placeholder="Search..."
@change=${this.handleSearch}
@keyup=${this.handleSearch}
/>
<div class="flag-dropdown">
<!-- Show each flag as button -->
<button
@click=${() => this.setFlag("")}
class="dropdown-item"
>
<img class="country-flag" src="flags/xx.svg" />
<span class="country-name">None</span>
</button>
${Countries.filter(
(country) =>
country.name
.toLowerCase()
.includes(this.search.toLowerCase()) ||
country.code
.toLowerCase()
.includes(this.search.toLowerCase()),
).map(
(country) => html`
<button
@click=${() => this.setFlag(country.code)}
class="dropdown-item"
>
<img
class="country-flag"
src="flags/${country.code}.svg"
/>
<span class="country-name">${country.name}</span>
</button>
`,
)}
</div>
</div>
`
: ""}
</div>
`;
}
}
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -29,7 +29,7 @@ export function createRenderer(
canvas: HTMLCanvasElement,
game: GameView,
eventBus: EventBus,
clientID: ClientID
clientID: ClientID,
): GameRenderer {
const transformHandler = new TransformHandler(game, eventBus, canvas);
@@ -65,7 +65,7 @@ export function createRenderer(
controlPanel.game = game;
const eventsDisplay = document.querySelector(
"events-display"
"events-display",
) as EventsDisplay;
if (!(eventsDisplay instanceof EventsDisplay)) {
consolex.error("events display not found");
@@ -75,7 +75,7 @@ export function createRenderer(
eventsDisplay.clientID = clientID;
const playerInfo = document.querySelector(
"player-info-overlay"
"player-info-overlay",
) as PlayerInfoOverlay;
if (!(playerInfo instanceof PlayerInfoOverlay)) {
consolex.error("player info overlay not found");
@@ -129,7 +129,7 @@ export function createRenderer(
buildMenu,
uiState,
playerInfo,
playerPanel
playerPanel,
),
new SpawnTimer(game, transformHandler),
leaderboard,
@@ -147,7 +147,7 @@ export function createRenderer(
canvas,
transformHandler,
uiState,
layers
layers,
);
}
@@ -160,7 +160,7 @@ export class GameRenderer {
private canvas: HTMLCanvasElement,
public transformHandler: TransformHandler,
public uiState: UIState,
private layers: Layer[]
private layers: Layer[],
) {
this.context = canvas.getContext("2d");
}
@@ -183,7 +183,7 @@ export class GameRenderer {
this.transformHandler = new TransformHandler(
this.game,
this.eventBus,
this.canvas
this.canvas,
);
requestAnimationFrame(() => this.renderGame());
@@ -229,7 +229,7 @@ export class GameRenderer {
const duration = performance.now() - start;
if (duration > 50) {
console.warn(
`tick ${this.game.ticks()} took ${duration}ms to render frame`
`tick ${this.game.ticks()} took ${duration}ms to render frame`,
);
}
}
+4 -4
View File
@@ -200,7 +200,7 @@ export class BuildMenu extends LitElement {
public onBuildSelected = (item: BuildItemDisplay) => {
this.eventBus.emit(
new BuildUnitIntentEvent(item.unitType, this.clickedCell)
new BuildUnitIntentEvent(item.unitType, this.clickedCell),
);
this.hideMenu();
};
@@ -235,7 +235,7 @@ export class BuildMenu extends LitElement {
? this.game
.unitInfo(item.unitType)
.cost(this.myPlayer)
: 0
: 0,
)}
<img
src=${goldCoinIcon}
@@ -246,10 +246,10 @@ export class BuildMenu extends LitElement {
/>
</span>
</button>
`
`,
)}
</div>
`
`,
)}
</div>
`;
+3 -3
View File
@@ -10,7 +10,7 @@ const emojiTable: string[][] = [
["😎", "❤️", "💰", "🤝", "🖕"],
["💥", "🆘", "🕊️", "➡️", "⬅️"],
["↙️", "↖️", "↗️", "⬆️", "↘️"],
["⬇️", "❓", "⏳", "☢️", "⚠️"]
["⬇️", "❓", "⏳", "☢️", "⚠️"],
];
@customElement("emoji-table")
@@ -110,10 +110,10 @@ export class EmojiTable extends LitElement {
>
${emoji}
</button>
`
`,
)}
</div>
`
`,
)}
</div>
`;
+15 -15
View File
@@ -150,10 +150,10 @@ export class EventsDisplay extends LitElement implements Layer {
}
const requestor = this.game.playerBySmallID(
update.requestorID
update.requestorID,
) as PlayerView;
const recipient = this.game.playerBySmallID(
update.recipientID
update.recipientID,
) as PlayerView;
this.addEvent({
@@ -164,7 +164,7 @@ export class EventsDisplay extends LitElement implements Layer {
className: "btn",
action: () =>
this.eventBus.emit(
new SendAllianceReplyIntentEvent(requestor, recipient, true)
new SendAllianceReplyIntentEvent(requestor, recipient, true),
),
},
{
@@ -172,7 +172,7 @@ export class EventsDisplay extends LitElement implements Layer {
className: "btn-info",
action: () =>
this.eventBus.emit(
new SendAllianceReplyIntentEvent(requestor, recipient, false)
new SendAllianceReplyIntentEvent(requestor, recipient, false),
),
},
],
@@ -181,7 +181,7 @@ export class EventsDisplay extends LitElement implements Layer {
createdAt: this.game.ticks(),
onDelete: () =>
this.eventBus.emit(
new SendAllianceReplyIntentEvent(requestor, recipient, false)
new SendAllianceReplyIntentEvent(requestor, recipient, false),
),
});
}
@@ -193,7 +193,7 @@ export class EventsDisplay extends LitElement implements Layer {
}
const recipient = this.game.playerBySmallID(
update.request.recipientID
update.request.recipientID,
) as PlayerView;
this.addEvent({
@@ -238,8 +238,8 @@ export class EventsDisplay extends LitElement implements Layer {
update.player1ID === myPlayer.smallID()
? update.player2ID
: update.player2ID === myPlayer.smallID()
? update.player1ID
: null;
? update.player1ID
: null;
const other = this.game.playerBySmallID(otherID) as PlayerView;
if (!other || !myPlayer.isAlive() || !other.isAlive()) return;
@@ -275,7 +275,7 @@ export class EventsDisplay extends LitElement implements Layer {
? AllPlayers
: this.game.playerBySmallID(update.emoji.recipientID);
const sender = this.game.playerBySmallID(
update.emoji.senderID
update.emoji.senderID,
) as PlayerView;
if (recipient == myPlayer) {
@@ -333,11 +333,11 @@ export class EventsDisplay extends LitElement implements Layer {
${renderTroops(attack.troops)}
${(
this.game.playerBySmallID(
attack.attackerID
attack.attackerID,
) as PlayerView
)?.name()}
</div>
`
`,
)}
</td>
</tr>
@@ -355,7 +355,7 @@ export class EventsDisplay extends LitElement implements Layer {
this.game.playerBySmallID(attack.targetID) as PlayerView
)?.name()}
</div>
`
`,
)}
</td>
</tr>
@@ -385,7 +385,7 @@ export class EventsDisplay extends LitElement implements Layer {
(event, index) => html`
<tr
class="border-b border-opacity-0 ${this.getMessageTypeClasses(
event.type
event.type,
)}"
>
<td class="lg:p-3 p-1 text-left">
@@ -410,14 +410,14 @@ export class EventsDisplay extends LitElement implements Layer {
>
${btn.text}
</button>
`
`,
)}
</div>
`
: ""}
</td>
</tr>
`
`,
)}
${this.renderAttacks()}
</tbody>
+2 -2
View File
@@ -80,7 +80,7 @@ export class Leaderboard extends LitElement implements Layer {
name: myPlayer.displayName(),
position: place,
score: formatPercentage(
myPlayer.numTilesOwned() / this.game.numLandTiles()
myPlayer.numTilesOwned() / this.game.numLandTiles(),
),
gold: renderNumber(myPlayer.gold()),
isMyPlayer: true,
@@ -205,7 +205,7 @@ export class Leaderboard extends LitElement implements Layer {
<td>${player.score}</td>
<td>${player.gold}</td>
</tr>
`
`,
)}
</tbody>
</table>
+27 -17
View File
@@ -26,7 +26,7 @@ class RenderInfo {
public lastRenderCalc: number,
public location: Cell,
public fontSize: number,
public element: HTMLElement
public element: HTMLElement,
) {}
}
@@ -50,7 +50,7 @@ export class NameLayer implements Layer {
private game: GameView,
private theme: Theme,
private transformHandler: TransformHandler,
private clientID: ClientID
private clientID: ClientID,
) {
this.traitorIconImage = new Image();
this.traitorIconImage.src = traitorIcon;
@@ -101,7 +101,13 @@ export class NameLayer implements Layer {
if (!this.seenPlayers.has(player)) {
this.seenPlayers.add(player);
this.renders.push(
new RenderInfo(player, 0, null, 0, this.createPlayerElement(player))
new RenderInfo(
player,
0,
null,
0,
this.createPlayerElement(player),
),
);
}
}
@@ -110,11 +116,11 @@ export class NameLayer implements Layer {
public renderLayer(mainContex: CanvasRenderingContext2D) {
const screenPosOld = this.transformHandler.worldToScreenCoordinates(
new Cell(0, 0)
new Cell(0, 0),
);
const screenPos = new Cell(
screenPosOld.x - window.innerWidth / 2,
screenPosOld.y - window.innerHeight / 2
screenPosOld.y - window.innerHeight / 2,
);
this.container.style.transform = `translate(${screenPos.x}px, ${screenPos.y}px) scale(${this.transformHandler.scale})`;
@@ -131,7 +137,7 @@ export class NameLayer implements Layer {
0,
0,
mainContex.canvas.width,
mainContex.canvas.height
mainContex.canvas.height,
);
}
@@ -205,7 +211,7 @@ export class NameLayer implements Layer {
const oldLocation = render.location;
render.location = new Cell(
render.player.nameLocation().x,
render.player.nameLocation().y
render.player.nameLocation().y,
);
// Calculate base size and scale
@@ -229,10 +235,10 @@ export class NameLayer implements Layer {
// Update text sizes
const nameDiv = render.element.querySelector(
".player-name"
".player-name",
) as HTMLDivElement;
const troopsDiv = render.element.querySelector(
".player-troops"
".player-troops",
) as HTMLDivElement;
nameDiv.style.fontSize = `${render.fontSize}px`;
troopsDiv.style.fontSize = `${render.fontSize}px`;
@@ -240,7 +246,7 @@ export class NameLayer implements Layer {
// Handle icons
const iconsDiv = render.element.querySelector(
".player-icons"
".player-icons",
) as HTMLDivElement;
const iconSize = Math.min(render.fontSize * 1.5, 48);
const myPlayer = this.getPlayer();
@@ -250,7 +256,7 @@ export class NameLayer implements Layer {
if (render.player === this.firstPlace) {
if (!existingCrown) {
iconsDiv.appendChild(
this.createIconElement(this.crownIconImage.src, iconSize, "crown")
this.createIconElement(this.crownIconImage.src, iconSize, "crown"),
);
}
} else if (existingCrown) {
@@ -262,7 +268,11 @@ export class NameLayer implements Layer {
if (render.player.isTraitor()) {
if (!existingTraitor) {
iconsDiv.appendChild(
this.createIconElement(this.traitorIconImage.src, iconSize, "traitor")
this.createIconElement(
this.traitorIconImage.src,
iconSize,
"traitor",
),
);
}
} else if (existingTraitor) {
@@ -277,8 +287,8 @@ export class NameLayer implements Layer {
this.createIconElement(
this.allianceIconImage.src,
iconSize,
"alliance"
)
"alliance",
),
);
}
} else if (existingAlliance) {
@@ -293,7 +303,7 @@ export class NameLayer implements Layer {
) {
if (!existingTarget) {
iconsDiv.appendChild(
this.createIconElement(this.targetIconImage.src, iconSize, "target")
this.createIconElement(this.targetIconImage.src, iconSize, "target"),
);
}
} else if (existingTarget) {
@@ -307,7 +317,7 @@ export class NameLayer implements Layer {
.filter(
(emoji) =>
emoji.recipientID == AllPlayers ||
emoji.recipientID == myPlayer?.smallID()
emoji.recipientID == myPlayer?.smallID(),
);
if (emojis.length > 0) {
@@ -340,7 +350,7 @@ export class NameLayer implements Layer {
private createIconElement(
src: string,
size: number,
id: string
id: string,
): HTMLImageElement {
const icon = document.createElement("img");
icon.src = src;
@@ -22,7 +22,7 @@ import { renderNumber, renderTroops } from "../../Utils";
function euclideanDistWorld(
coord: { x: number; y: number },
tileRef: TileRef,
game: GameView
game: GameView,
): number {
const x = game.x(tileRef);
const y = game.y(tileRef);
@@ -71,7 +71,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
init() {
this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) =>
this.onMouseEvent(e)
this.onMouseEvent(e),
);
this._isActive = true;
}
+2 -2
View File
@@ -53,7 +53,7 @@ export class PlayerPanel extends LitElement implements Layer {
private handleAllianceClick(
e: Event,
myPlayer: PlayerView,
other: PlayerView
other: PlayerView,
) {
e.stopPropagation();
this.eventBus.emit(new SendAllianceRequestIntentEvent(myPlayer, other));
@@ -63,7 +63,7 @@ export class PlayerPanel extends LitElement implements Layer {
private handleBreakAllianceClick(
e: Event,
myPlayer: PlayerView,
other: PlayerView
other: PlayerView,
) {
e.stopPropagation();
this.eventBus.emit(new SendBreakAllianceIntentEvent(myPlayer, other));
+11 -11
View File
@@ -99,7 +99,7 @@ export class RadialMenu implements Layer {
private buildMenu: BuildMenu,
private uiState: UIState,
private playerInfoOverlay: PlayerInfoOverlay,
private playerPanel: PlayerPanel
private playerPanel: PlayerPanel,
) {}
init() {
@@ -108,7 +108,7 @@ export class RadialMenu implements Layer {
this.eventBus.on(ShowBuildMenuEvent, (e) => {
const clickedCell = this.transformHandler.screenToWorldCoordinates(
e.x,
e.y
e.y,
);
if (clickedCell == null) {
return;
@@ -144,7 +144,7 @@ export class RadialMenu implements Layer {
.append("g")
.attr(
"transform",
`translate(${this.menuSize / 2},${this.menuSize / 2})`
`translate(${this.menuSize / 2},${this.menuSize / 2})`,
);
const pie = d3
@@ -169,7 +169,7 @@ export class RadialMenu implements Layer {
.append("path")
.attr("d", arc)
.attr("fill", (d) =>
d.data.disabled ? this.disabledColor : d.data.color
d.data.disabled ? this.disabledColor : d.data.color,
)
.attr("stroke", "#ffffff")
.attr("stroke-width", "2")
@@ -294,7 +294,7 @@ export class RadialMenu implements Layer {
this.clickedCell = this.transformHandler.screenToWorldCoordinates(
event.x,
event.y
event.y,
);
if (!this.g.isValidCoord(this.clickedCell.x, this.clickedCell.y)) {
return;
@@ -323,7 +323,7 @@ export class RadialMenu implements Layer {
private handlePlayerActions(
myPlayer: PlayerView,
actions: PlayerActions,
tile: TileRef
tile: TileRef,
) {
this.activateMenuElement(Slot.Build, "#ebe250", buildIcon, () => {
this.buildMenu.showMenu(myPlayer, this.clickedCell);
@@ -341,8 +341,8 @@ export class RadialMenu implements Layer {
new SendBoatAttackIntentEvent(
this.g.owner(tile).id(),
this.clickedCell,
this.uiState.attackRatio * myPlayer.troops()
)
this.uiState.attackRatio * myPlayer.troops(),
),
);
});
}
@@ -394,8 +394,8 @@ export class RadialMenu implements Layer {
this.eventBus.emit(
new SendAttackIntentEvent(
this.g.owner(clicked).id(),
this.uiState.attackRatio * myPlayer.troops()
)
this.uiState.attackRatio * myPlayer.troops(),
),
);
}
}
@@ -406,7 +406,7 @@ export class RadialMenu implements Layer {
slot: Slot,
color: string,
icon: string,
action: () => void
action: () => void,
) {
const menuItem = this.menuItems.get(slot);
menuItem.action = action;
+16 -13
View File
@@ -50,7 +50,10 @@ export class StructureLayer implements Layer {
},
};
constructor(private game: GameView, private eventBus: EventBus) {
constructor(
private game: GameView,
private eventBus: EventBus,
) {
this.theme = game.config().theme();
this.loadIconData();
}
@@ -72,11 +75,11 @@ export class StructureLayer implements Layer {
0,
0,
tempCanvas.width,
tempCanvas.height
tempCanvas.height,
);
this.unitIcons.set(unitType, iconData);
console.log(
`icond data width height: ${iconData.width}, ${iconData.height}`
`icond data width height: ${iconData.width}, ${iconData.height}`,
);
};
});
@@ -89,9 +92,9 @@ export class StructureLayer implements Layer {
tick() {
this.game
.updatesSinceLastTick()
[GameUpdateType.Unit].forEach((u) =>
this.handleUnitRendering(this.game.unit(u.id))
);
[
GameUpdateType.Unit
].forEach((u) => this.handleUnitRendering(this.game.unit(u.id)));
}
init() {
@@ -113,7 +116,7 @@ export class StructureLayer implements Layer {
-this.game.width() / 2,
-this.game.height() / 2,
this.game.width(),
this.game.height()
this.game.height(),
);
}
@@ -133,7 +136,7 @@ export class StructureLayer implements Layer {
// Clear previous rendering
for (const tile of this.game.bfs(
unit.tile(),
euclDistFN(unit.tile(), config.borderRadius)
euclDistFN(unit.tile(), config.borderRadius),
)) {
this.clearCell(new Cell(this.game.x(tile), this.game.y(tile)));
}
@@ -145,27 +148,27 @@ export class StructureLayer implements Layer {
// Draw border and territory
for (const tile of this.game.bfs(
unit.tile(),
euclDistFN(unit.tile(), config.borderRadius)
euclDistFN(unit.tile(), config.borderRadius),
)) {
this.paintCell(
new Cell(this.game.x(tile), this.game.y(tile)),
unit.type() == UnitType.Construction
? underConstructionColor
: this.theme.borderColor(unit.owner().info()),
255
255,
);
}
for (const tile of this.game.bfs(
unit.tile(),
euclDistFN(unit.tile(), config.territoryRadius)
euclDistFN(unit.tile(), config.territoryRadius),
)) {
this.paintCell(
new Cell(this.game.x(tile), this.game.y(tile)),
unit.type() == UnitType.Construction
? underConstructionColor
: this.theme.territoryColor(unit.owner().info()),
130
130,
);
}
@@ -181,7 +184,7 @@ export class StructureLayer implements Layer {
startY: number,
width: number,
height: number,
unit: UnitView
unit: UnitView,
) {
let color = this.theme.borderColor(unit.owner().info());
if (unit.type() == UnitType.Construction) {
+14 -11
View File
@@ -50,7 +50,10 @@ export class TerritoryLayer implements Layer {
private refreshRate = 50;
private lastRefresh = 0;
constructor(private game: GameView, private eventBus: EventBus) {
constructor(
private game: GameView,
private eventBus: EventBus,
) {
this.theme = game.config().theme();
}
@@ -67,7 +70,7 @@ export class TerritoryLayer implements Layer {
this.game
.bfs(
tile,
manhattanDistFN(tile, this.game.config().defensePostRange())
manhattanDistFN(tile, this.game.config().defensePostRange()),
)
.forEach((t) => {
if (
@@ -91,7 +94,7 @@ export class TerritoryLayer implements Layer {
0,
0,
this.game.width(),
this.game.height()
this.game.height(),
);
const humans = this.game
.playerViews()
@@ -111,7 +114,7 @@ export class TerritoryLayer implements Layer {
this.paintHighlightCell(
new Cell(this.game.x(tile), this.game.y(tile)),
this.theme.spawnHighlightColor(),
255
255,
);
}
}
@@ -138,7 +141,7 @@ export class TerritoryLayer implements Layer {
0,
0,
this.game.width(),
this.game.height()
this.game.height(),
);
this.initImageData();
this.canvas.width = this.game.width();
@@ -185,7 +188,7 @@ export class TerritoryLayer implements Layer {
-this.game.width() / 2,
-this.game.height() / 2,
this.game.width(),
this.game.height()
this.game.height(),
);
if (this.game.inSpawnPhase()) {
context.drawImage(
@@ -193,7 +196,7 @@ export class TerritoryLayer implements Layer {
-this.game.width() / 2,
-this.game.height() / 2,
this.game.width(),
this.game.height()
this.game.height(),
);
}
}
@@ -224,7 +227,7 @@ export class TerritoryLayer implements Layer {
this.game.x(tile),
this.game.y(tile),
this.theme.falloutColor(),
150
150,
);
return;
}
@@ -241,14 +244,14 @@ export class TerritoryLayer implements Layer {
this.game.x(tile),
this.game.y(tile),
this.theme.defendedBorderColor(owner.info()),
255
255,
);
} else {
this.paintCell(
this.game.x(tile),
this.game.y(tile),
this.theme.borderColor(owner.info()),
255
255,
);
}
} else {
@@ -256,7 +259,7 @@ export class TerritoryLayer implements Layer {
this.game.x(tile),
this.game.y(tile),
this.theme.territoryColor(owner.info()),
150
150,
);
}
}
+24 -24
View File
@@ -36,7 +36,7 @@ export class UnitLayer implements Layer {
constructor(
private game: GameView,
private eventBus: EventBus,
private clientID: ClientID
private clientID: ClientID,
) {
this.theme = game.config().theme();
}
@@ -65,7 +65,7 @@ export class UnitLayer implements Layer {
-this.game.width() / 2,
-this.game.height() / 2,
this.game.width(),
this.game.height()
this.game.height(),
);
}
@@ -131,7 +131,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
euclDistFN(unit.lastTile(), 6)
euclDistFN(unit.lastTile(), 6),
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -147,21 +147,21 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255
255,
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 4)
manhattanDistFN(unit.tile(), 4),
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255
255,
);
}
@@ -172,7 +172,7 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255
255,
);
}
}
@@ -198,14 +198,14 @@ export class UnitLayer implements Layer {
this.game.y(unit.tile()),
rel,
this.theme.borderColor(unit.owner().info()),
255
255,
);
this.paintCell(
this.game.x(unit.lastTile()),
this.game.y(unit.lastTile()),
rel,
this.theme.borderColor(unit.owner().info()),
255
255,
);
}
@@ -215,7 +215,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
euclDistFN(unit.lastTile(), 2)
euclDistFN(unit.lastTile(), 2),
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -228,7 +228,7 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255
255,
);
}
}
@@ -246,7 +246,7 @@ export class UnitLayer implements Layer {
this.game.y(unit.tile()),
rel,
this.theme.borderColor(unit.owner().info()),
255
255,
);
}
}
@@ -257,7 +257,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
euclDistFN(unit.lastTile(), 3)
euclDistFN(unit.lastTile(), 3),
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -266,28 +266,28 @@ export class UnitLayer implements Layer {
// Paint territory
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 2)
manhattanDistFN(unit.tile(), 2),
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255
255,
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 1)
manhattanDistFN(unit.tile(), 1),
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255
255,
);
}
}
@@ -305,7 +305,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
manhattanDistFN(unit.lastTile(), 3)
manhattanDistFN(unit.lastTile(), 3),
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -318,35 +318,35 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
150
150,
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 2)
manhattanDistFN(unit.tile(), 2),
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255
255,
);
}
// Paint territory
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 1)
manhattanDistFN(unit.tile(), 1),
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255
255,
);
}
} else {
@@ -362,7 +362,7 @@ export class UnitLayer implements Layer {
y: number,
relationship: Relationship,
color: Colord,
alpha: number
alpha: number,
) {
this.clearCell(x, y);
if (this.alternateView) {
+14 -10
View File
@@ -29,7 +29,7 @@ export async function createGameRunner(
gameID: string,
gameConfig: GameConfig,
clientID: ClientID,
callBack: (gu: GameUpdateViewData) => void
callBack: (gu: GameUpdateViewData) => void,
): Promise<GameRunner> {
const config = getConfig(gameConfig);
const gameMap = await loadGameMap(gameConfig.gameMap);
@@ -37,9 +37,13 @@ export async function createGameRunner(
gameMap.gameMap,
gameMap.miniGameMap,
gameMap.nationMap,
config
config,
);
const gr = new GameRunner(
game as Game,
new Executor(game, gameID, clientID),
callBack,
);
const gr = new GameRunner(game as Game, new Executor(game, gameID, clientID), callBack);
gr.init();
return gr;
}
@@ -54,13 +58,13 @@ export class GameRunner {
constructor(
public game: Game,
private execManager: Executor,
private callBack: (gu: GameUpdateViewData | ErrorUpdate) => void
private callBack: (gu: GameUpdateViewData | ErrorUpdate) => void,
) {}
init() {
if (this.game.config().spawnBots()) {
this.game.addExecution(
...this.execManager.spawnBots(this.game.config().numBots())
...this.execManager.spawnBots(this.game.config().numBots()),
);
}
if (this.game.config().spawnNPCs()) {
@@ -83,7 +87,7 @@ export class GameRunner {
this.isExecuting = true;
this.game.addExecution(
...this.execManager.createExecs(this.turns[this.currTurn])
...this.execManager.createExecs(this.turns[this.currTurn]),
);
this.currTurn++;
@@ -107,10 +111,10 @@ export class GameRunner {
.players()
.filter(
(p) =>
p.type() == PlayerType.Human || p.type() == PlayerType.FakeHuman
p.type() == PlayerType.Human || p.type() == PlayerType.FakeHuman,
)
.forEach(
(p) => (this.playerViewData[p.id()] = placeName(this.game, p))
(p) => (this.playerViewData[p.id()] = placeName(this.game, p)),
);
}
@@ -136,7 +140,7 @@ export class GameRunner {
public playerActions(
playerID: PlayerID,
x: number,
y: number
y: number,
): PlayerActions {
const player = this.game.player(playerID);
const tile = this.game.ref(x, y);
@@ -144,7 +148,7 @@ export class GameRunner {
canBoat: player.canBoat(tile),
canAttack: player.canAttack(tile),
buildableUnits: Object.values(UnitType).filter(
(ut) => player.canBuild(ut, tile) != false
(ut) => player.canBuild(ut, tile) != false,
),
canSendEmojiAllPlayers: player.canSendEmoji(AllPlayers),
} as PlayerActions;
+4 -4
View File
@@ -101,7 +101,9 @@ const SafeString = z
.string()
// Remove common dangerous characters and patterns
// The weird \u stuff is to allow emojis
.regex(/^[a-zA-Z0-9\s.,!?@#$%&*()-_+=\[\]{}|;:"'\/\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]]+$/)
.regex(
/^[a-zA-Z0-9\s.,!?@#$%&*()-_+=\[\]{}|;:"'\/\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]]+$/,
)
// Reasonable max length to prevent DOS
.max(1000);
@@ -111,7 +113,7 @@ const EmojiSchema = z.string().refine(
},
{
message: "Must contain at least one emoji character",
}
},
);
const ID = z
.string()
@@ -324,5 +326,3 @@ export const GameRecordSchema = z.object({
turns: z.array(TurnSchema),
winner: ID.nullable(),
});
+19 -19
View File
@@ -16,7 +16,7 @@ import { andFN, GameMap, manhattanDistFN, TileRef } from "./game/GameMap";
export function manhattanDistWrapped(
c1: Cell,
c2: Cell,
width: number
width: number,
): number {
// Calculate x distance
let dx = Math.abs(c1.x - c2.x);
@@ -36,7 +36,7 @@ export function within(value: number, min: number, max: number): number {
export function distSort(
gm: GameMap,
target: TileRef
target: TileRef,
): (a: TileRef, b: TileRef) => number {
return (a: TileRef, b: TileRef) => {
return gm.manhattanDist(a, target) - gm.manhattanDist(b, target);
@@ -45,7 +45,7 @@ export function distSort(
export function distSortUnit(
gm: GameMap,
target: Unit | TileRef
target: Unit | TileRef,
): (a: Unit, b: Unit) => number {
const targetRef = typeof target === "number" ? target : target.tile();
@@ -61,7 +61,7 @@ export function distSortUnit(
export function sourceDstOceanShore(
gm: Game,
src: Player,
tile: TileRef
tile: TileRef,
): [TileRef | null, TileRef | null] {
const dst = gm.owner(tile);
let srcTile = closestOceanShoreFromPlayer(gm, src, tile);
@@ -88,10 +88,10 @@ export function targetTransportTile(gm: Game, tile: TileRef): TileRef | null {
export function closestOceanShoreFromPlayer(
gm: GameMap,
player: Player,
target: TileRef
target: TileRef,
): TileRef | null {
const shoreTiles = Array.from(player.borderTiles()).filter((t) =>
gm.isOceanShore(t)
gm.isOceanShore(t),
);
if (shoreTiles.length == 0) {
return null;
@@ -101,12 +101,12 @@ export function closestOceanShoreFromPlayer(
const closestDistance = manhattanDistWrapped(
gm.cell(target),
gm.cell(closest),
gm.width()
gm.width(),
);
const currentDistance = manhattanDistWrapped(
gm.cell(target),
gm.cell(current),
gm.width()
gm.width(),
);
return currentDistance < closestDistance ? current : closest;
});
@@ -115,13 +115,13 @@ export function closestOceanShoreFromPlayer(
function closestOceanShoreTN(
gm: GameMap,
tile: TileRef,
searchDist: number
searchDist: number,
): TileRef {
const tn = Array.from(
gm.bfs(
tile,
andFN((_, t) => !gm.hasOwner(t), manhattanDistFN(tile, searchDist))
)
andFN((_, t) => !gm.hasOwner(t), manhattanDistFN(tile, searchDist)),
),
)
.filter((t) => gm.isOceanShore(t))
.sort((a, b) => gm.manhattanDist(tile, a) - gm.manhattanDist(tile, b));
@@ -143,7 +143,7 @@ export function simpleHash(str: string): number {
export function calculateBoundingBox(
gm: GameMap,
borderTiles: ReadonlySet<TileRef>
borderTiles: ReadonlySet<TileRef>,
): { min: Cell; max: Cell } {
let minX = Infinity,
minY = Infinity,
@@ -163,18 +163,18 @@ export function calculateBoundingBox(
export function calculateBoundingBoxCenter(
gm: GameMap,
borderTiles: ReadonlySet<TileRef>
borderTiles: ReadonlySet<TileRef>,
): Cell {
const { min, max } = calculateBoundingBox(gm, borderTiles);
return new Cell(
min.x + Math.floor((max.x - min.x) / 2),
min.y + Math.floor((max.y - min.y) / 2)
min.y + Math.floor((max.y - min.y) / 2),
);
}
export function inscribed(
outer: { min: Cell; max: Cell },
inner: { min: Cell; max: Cell }
inner: { min: Cell; max: Cell },
): boolean {
return (
outer.min.x <= inner.min.x &&
@@ -238,7 +238,7 @@ export function processName(name: string): string {
// Add CSS for the emoji images
const withEmojiStyles = styledHTML.replace(
/<img/g,
'<img style="height: 1.2em; width: 1.2em; vertical-align: -0.2em; margin: 0 0.05em 0 0.1em;"'
'<img style="height: 1.2em; width: 1.2em; vertical-align: -0.2em; margin: 0 0.05em 0 0.1em;"',
);
// Sanitize the final HTML, allowing styles and specific attributes
@@ -262,7 +262,7 @@ export function CreateGameRecord(
turns: Turn[],
start: number,
end: number,
winner: ClientID | null
winner: ClientID | null,
): GameRecord {
const record: GameRecord = {
id: id,
@@ -289,7 +289,7 @@ export function CreateGameRecord(
}
record.players = players;
record.durationSeconds = Math.floor(
(record.endTimestampMS - record.startTimestampMS) / 1000
(record.endTimestampMS - record.startTimestampMS) / 1000,
);
record.num_turns = turns.length;
record.winner = winner;
@@ -303,7 +303,7 @@ export function assertNever(x: never): never {
export function generateID(): GameID {
const nanoid = customAlphabet(
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
8
8,
);
return nanoid();
}
+2 -2
View File
@@ -82,14 +82,14 @@ export interface Config {
attckTroops: number,
attacker: Player,
defender: Player | TerraNullius,
numAdjacentTilesWithEnemy: number
numAdjacentTilesWithEnemy: number,
): number;
attackLogic(
gm: Game,
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
tileToConquer: TileRef
tileToConquer: TileRef,
): {
attackerTroopLoss: number;
defenderTroopLoss: number;
+398 -417
View File
@@ -1,450 +1,431 @@
import {
Difficulty,
Game,
GameType,
Gold,
Player,
PlayerInfo,
PlayerType,
TerrainType,
TerraNullius,
Tick,
UnitInfo,
UnitType,
} from '../game/Game';
import { GameMap, TileRef } from '../game/GameMap';
import { PlayerView } from '../game/GameView';
import { GameConfig } from '../Schemas';
import { assertNever, within } from '../Util';
import { Config, ServerConfig, Theme } from './Config';
import { pastelTheme } from './PastelTheme';
Difficulty,
Game,
GameType,
Gold,
Player,
PlayerInfo,
PlayerType,
TerrainType,
TerraNullius,
Tick,
UnitInfo,
UnitType,
} from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
import { PlayerView } from "../game/GameView";
import { GameConfig } from "../Schemas";
import { assertNever, within } from "../Util";
import { Config, ServerConfig, Theme } from "./Config";
import { pastelTheme } from "./PastelTheme";
export abstract class DefaultServerConfig implements ServerConfig {
turnIntervalMs(): number {
return 100;
}
gameCreationRate(): number {
return 1 * 60 * 1000;
}
lobbyLifetime(): number {
return 2 * 60 * 1000;
}
turnIntervalMs(): number {
return 100;
}
gameCreationRate(): number {
return 1 * 60 * 1000;
}
lobbyLifetime(): number {
return 2 * 60 * 1000;
}
}
export class DefaultConfig implements Config {
constructor(
private _serverConfig: ServerConfig,
private _gameConfig: GameConfig
) {}
spawnImmunityDuration(): Tick {
return 5 * 10;
}
constructor(
private _serverConfig: ServerConfig,
private _gameConfig: GameConfig,
) {}
spawnImmunityDuration(): Tick {
return 5 * 10;
}
gameConfig(): GameConfig {
return this._gameConfig;
}
gameConfig(): GameConfig {
return this._gameConfig;
}
serverConfig(): ServerConfig {
return this._serverConfig;
}
serverConfig(): ServerConfig {
return this._serverConfig;
}
difficultyModifier(difficulty: Difficulty): number {
switch (difficulty) {
case Difficulty.Easy:
return 1;
case Difficulty.Medium:
return 3;
case Difficulty.Hard:
return 9;
case Difficulty.Impossible:
return 18;
}
}
difficultyModifier(difficulty: Difficulty): number {
switch (difficulty) {
case Difficulty.Easy:
return 1;
case Difficulty.Medium:
return 3;
case Difficulty.Hard:
return 9;
case Difficulty.Impossible:
return 18;
}
}
cityPopulationIncrease(): number {
return 250_000;
}
cityPopulationIncrease(): number {
return 250_000;
}
falloutDefenseModifier(): number {
return 5;
}
falloutDefenseModifier(): number {
return 5;
}
defensePostRange(): number {
return 30;
}
defensePostDefenseBonus(): number {
return 5;
}
spawnNPCs(): boolean {
return !this._gameConfig.disableNPCs;
}
spawnBots(): boolean {
return !this._gameConfig.disableBots;
}
creativeMode(): boolean {
return this._gameConfig.creativeMode;
}
tradeShipGold(dist: number): Gold {
return 10000 + 100 * Math.pow(dist, 1.1);
}
tradeShipSpawnRate(): number {
return 500;
}
defensePostRange(): number {
return 30;
}
defensePostDefenseBonus(): number {
return 5;
}
spawnNPCs(): boolean {
return !this._gameConfig.disableNPCs;
}
spawnBots(): boolean {
return !this._gameConfig.disableBots;
}
creativeMode(): boolean {
return this._gameConfig.creativeMode;
}
tradeShipGold(dist: number): Gold {
return 10000 + 100 * Math.pow(dist, 1.1);
}
tradeShipSpawnRate(): number {
return 500;
}
unitInfo(type: UnitType): UnitInfo {
switch (type) {
case UnitType.TransportShip:
return {
cost: () => 0,
territoryBound: false,
};
case UnitType.Warship:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: (p.units(UnitType.Warship).length + 1) * 250_000,
territoryBound: false,
maxHealth: 1000,
};
case UnitType.Shell:
return {
cost: () => 0,
territoryBound: false,
damage: 250,
};
case UnitType.Port:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: Math.min(
1_000_000,
Math.pow(2, p.units(UnitType.Port).length) *
250_000
),
territoryBound: true,
constructionDuration: this.creativeMode() ? 0 : 2 * 10,
};
case UnitType.AtomBomb:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: 750_000,
territoryBound: false,
};
case UnitType.HydrogenBomb:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: 5_000_000,
territoryBound: false,
};
case UnitType.MIRV:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: 10_000_000,
territoryBound: false,
};
case UnitType.MIRVWarhead:
return {
cost: () => 0,
territoryBound: false,
};
case UnitType.TradeShip:
return {
cost: () => 0,
territoryBound: false,
};
case UnitType.MissileSilo:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: 1_000_000,
territoryBound: true,
constructionDuration: this.creativeMode() ? 0 : 10 * 10,
};
case UnitType.DefensePost:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: Math.min(
250_000,
(p.units(UnitType.DefensePost).length + 1) *
50_000
),
territoryBound: true,
constructionDuration: this.creativeMode() ? 0 : 5 * 10,
};
case UnitType.City:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: Math.min(
1_000_000,
Math.pow(2, p.units(UnitType.City).length) *
125_000
),
territoryBound: true,
constructionDuration: this.creativeMode() ? 0 : 2 * 10,
};
case UnitType.Construction:
return {
cost: () => 0,
territoryBound: true,
};
default:
assertNever(type);
}
}
defaultDonationAmount(sender: Player): number {
return Math.floor(sender.troops() / 3);
}
donateCooldown(): Tick {
return 10 * 10;
}
emojiMessageDuration(): Tick {
return 5 * 10;
}
emojiMessageCooldown(): Tick {
return 15 * 10;
}
targetDuration(): Tick {
return 10 * 10;
}
targetCooldown(): Tick {
return 15 * 10;
}
allianceRequestCooldown(): Tick {
return 30 * 10;
}
allianceDuration(): Tick {
return 600 * 10;
}
percentageTilesOwnedToWin(): number {
return 80;
}
boatMaxNumber(): number {
return 3;
}
boatMaxDistance(): number {
return 500;
}
numSpawnPhaseTurns(): number {
return this._gameConfig.gameType == GameType.Singleplayer ? 100 : 300;
}
numBots(): number {
return 400;
}
theme(): Theme {
return pastelTheme;
}
unitInfo(type: UnitType): UnitInfo {
switch (type) {
case UnitType.TransportShip:
return {
cost: () => 0,
territoryBound: false,
};
case UnitType.Warship:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: (p.units(UnitType.Warship).length + 1) * 250_000,
territoryBound: false,
maxHealth: 1000,
};
case UnitType.Shell:
return {
cost: () => 0,
territoryBound: false,
damage: 250,
};
case UnitType.Port:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: Math.min(
1_000_000,
Math.pow(2, p.units(UnitType.Port).length) * 250_000,
),
territoryBound: true,
constructionDuration: this.creativeMode() ? 0 : 2 * 10,
};
case UnitType.AtomBomb:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode() ? 0 : 750_000,
territoryBound: false,
};
case UnitType.HydrogenBomb:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode() ? 0 : 5_000_000,
territoryBound: false,
};
case UnitType.MIRV:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: 10_000_000,
territoryBound: false,
};
case UnitType.MIRVWarhead:
return {
cost: () => 0,
territoryBound: false,
};
case UnitType.TradeShip:
return {
cost: () => 0,
territoryBound: false,
};
case UnitType.MissileSilo:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode() ? 0 : 1_000_000,
territoryBound: true,
constructionDuration: this.creativeMode() ? 0 : 10 * 10,
};
case UnitType.DefensePost:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: Math.min(
250_000,
(p.units(UnitType.DefensePost).length + 1) * 50_000,
),
territoryBound: true,
constructionDuration: this.creativeMode() ? 0 : 5 * 10,
};
case UnitType.City:
return {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.creativeMode()
? 0
: Math.min(
1_000_000,
Math.pow(2, p.units(UnitType.City).length) * 125_000,
),
territoryBound: true,
constructionDuration: this.creativeMode() ? 0 : 2 * 10,
};
case UnitType.Construction:
return {
cost: () => 0,
territoryBound: true,
};
default:
assertNever(type);
}
}
defaultDonationAmount(sender: Player): number {
return Math.floor(sender.troops() / 3);
}
donateCooldown(): Tick {
return 10 * 10;
}
emojiMessageDuration(): Tick {
return 5 * 10;
}
emojiMessageCooldown(): Tick {
return 15 * 10;
}
targetDuration(): Tick {
return 10 * 10;
}
targetCooldown(): Tick {
return 15 * 10;
}
allianceRequestCooldown(): Tick {
return 30 * 10;
}
allianceDuration(): Tick {
return 600 * 10;
}
percentageTilesOwnedToWin(): number {
return 80;
}
boatMaxNumber(): number {
return 3;
}
boatMaxDistance(): number {
return 500;
}
numSpawnPhaseTurns(): number {
return this._gameConfig.gameType == GameType.Singleplayer ? 100 : 300;
}
numBots(): number {
return 400;
}
theme(): Theme {
return pastelTheme;
}
attackLogic(
gm: Game,
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
tileToConquer: TileRef
): {
attackerTroopLoss: number;
defenderTroopLoss: number;
tilesPerTickUsed: number;
} {
let mag = 0;
let speed = 0;
const type = gm.terrainType(tileToConquer);
switch (type) {
case TerrainType.Plains:
mag = 80;
speed = 15;
break;
case TerrainType.Highland:
mag = 100;
speed = 20;
break;
case TerrainType.Mountain:
mag = 120;
speed = 25;
break;
default:
throw new Error(`terrain type ${type} not supported`);
}
if (defender.isPlayer()) {
for (const dp of gm.nearbyDefensePosts(tileToConquer)) {
if (dp.owner() == defender) {
mag *= this.defensePostDefenseBonus();
speed *= this.defensePostDefenseBonus();
break;
}
}
}
attackLogic(
gm: Game,
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
tileToConquer: TileRef,
): {
attackerTroopLoss: number;
defenderTroopLoss: number;
tilesPerTickUsed: number;
} {
let mag = 0;
let speed = 0;
const type = gm.terrainType(tileToConquer);
switch (type) {
case TerrainType.Plains:
mag = 80;
speed = 15;
break;
case TerrainType.Highland:
mag = 100;
speed = 20;
break;
case TerrainType.Mountain:
mag = 120;
speed = 25;
break;
default:
throw new Error(`terrain type ${type} not supported`);
}
if (defender.isPlayer()) {
for (const dp of gm.nearbyDefensePosts(tileToConquer)) {
if (dp.owner() == defender) {
mag *= this.defensePostDefenseBonus();
speed *= this.defensePostDefenseBonus();
break;
}
}
}
if (gm.hasFallout(tileToConquer)) {
mag *= this.falloutDefenseModifier();
speed *= this.falloutDefenseModifier();
}
if (gm.hasFallout(tileToConquer)) {
mag *= this.falloutDefenseModifier();
speed *= this.falloutDefenseModifier();
}
if (attacker.isPlayer() && defender.isPlayer()) {
if (
attacker.type() == PlayerType.Human &&
defender.type() == PlayerType.Bot
) {
mag *= 0.8;
}
if (
attacker.type() == PlayerType.FakeHuman &&
defender.type() == PlayerType.Bot
) {
mag *= 0.8;
}
}
if (attacker.isPlayer() && defender.isPlayer()) {
if (
attacker.type() == PlayerType.Human &&
defender.type() == PlayerType.Bot
) {
mag *= 0.8;
}
if (
attacker.type() == PlayerType.FakeHuman &&
defender.type() == PlayerType.Bot
) {
mag *= 0.8;
}
}
if (defender.isPlayer()) {
return {
attackerTroopLoss:
within(defender.troops() / (2.5 * attackTroops), 0.1, 10) *
mag,
defenderTroopLoss: defender.troops() / defender.numTilesOwned(),
tilesPerTickUsed:
within(defender.troops() / (5 * attackTroops), 0.2, 1.5) *
speed,
};
} else {
return {
attackerTroopLoss:
attacker.type() == PlayerType.Bot ? mag / 10 : mag / 5,
defenderTroopLoss: 0,
tilesPerTickUsed: within(
(2000 * Math.max(10, speed)) / attackTroops,
5,
100
),
};
}
}
if (defender.isPlayer()) {
return {
attackerTroopLoss:
within(defender.troops() / (2.5 * attackTroops), 0.1, 10) * mag,
defenderTroopLoss: defender.troops() / defender.numTilesOwned(),
tilesPerTickUsed:
within(defender.troops() / (5 * attackTroops), 0.2, 1.5) * speed,
};
} else {
return {
attackerTroopLoss:
attacker.type() == PlayerType.Bot ? mag / 10 : mag / 5,
defenderTroopLoss: 0,
tilesPerTickUsed: within(
(2000 * Math.max(10, speed)) / attackTroops,
5,
100,
),
};
}
}
attackTilesPerTick(
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
numAdjacentTilesWithEnemy: number
): number {
if (defender.isPlayer()) {
return (
within(
((5 * attackTroops) / defender.troops()) * 2,
0.01,
0.5
) *
numAdjacentTilesWithEnemy *
3
);
} else {
return numAdjacentTilesWithEnemy * 2;
}
}
attackTilesPerTick(
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
numAdjacentTilesWithEnemy: number,
): number {
if (defender.isPlayer()) {
return (
within(((5 * attackTroops) / defender.troops()) * 2, 0.01, 0.5) *
numAdjacentTilesWithEnemy *
3
);
} else {
return numAdjacentTilesWithEnemy * 2;
}
}
boatAttackAmount(
attacker: Player,
defender: Player | TerraNullius
): number {
return Math.floor(attacker.troops() / 5);
}
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number {
return Math.floor(attacker.troops() / 5);
}
attackAmount(attacker: Player, defender: Player | TerraNullius) {
if (attacker.type() == PlayerType.Bot) {
return attacker.troops() / 20;
} else {
return attacker.troops() / 5;
}
}
attackAmount(attacker: Player, defender: Player | TerraNullius) {
if (attacker.type() == PlayerType.Bot) {
return attacker.troops() / 20;
} else {
return attacker.troops() / 5;
}
}
startManpower(playerInfo: PlayerInfo): number {
if (playerInfo.playerType == PlayerType.Bot) {
return 10_000;
}
if (playerInfo.playerType == PlayerType.FakeHuman) {
switch (this._gameConfig.difficulty) {
case Difficulty.Easy:
return 2_500 * (playerInfo?.nation?.strength ?? 1);
case Difficulty.Medium:
return 5_000 * (playerInfo?.nation?.strength ?? 1);
case Difficulty.Hard:
return 15_000 * (playerInfo?.nation?.strength ?? 1);
case Difficulty.Impossible:
return 20_000 * (playerInfo?.nation?.strength ?? 1);
}
}
return this.creativeMode() ? 1_000_000 : 25_000;
}
startManpower(playerInfo: PlayerInfo): number {
if (playerInfo.playerType == PlayerType.Bot) {
return 10_000;
}
if (playerInfo.playerType == PlayerType.FakeHuman) {
switch (this._gameConfig.difficulty) {
case Difficulty.Easy:
return 2_500 * (playerInfo?.nation?.strength ?? 1);
case Difficulty.Medium:
return 5_000 * (playerInfo?.nation?.strength ?? 1);
case Difficulty.Hard:
return 15_000 * (playerInfo?.nation?.strength ?? 1);
case Difficulty.Impossible:
return 20_000 * (playerInfo?.nation?.strength ?? 1);
}
}
return this.creativeMode() ? 1_000_000 : 25_000;
}
maxPopulation(player: Player | PlayerView): number {
let maxPop =
player.type() == PlayerType.Human && this.creativeMode()
? 999_999_999_999
: 2 * (Math.pow(player.numTilesOwned(), 0.6) * 1000 + 50000) +
player.units(UnitType.City).length *
this.cityPopulationIncrease();
maxPopulation(player: Player | PlayerView): number {
let maxPop =
player.type() == PlayerType.Human && this.creativeMode()
? 999_999_999_999
: 2 * (Math.pow(player.numTilesOwned(), 0.6) * 1000 + 50000) +
player.units(UnitType.City).length * this.cityPopulationIncrease();
if (player.type() == PlayerType.Bot) {
return maxPop / 2;
}
if (player.type() == PlayerType.Bot) {
return maxPop / 2;
}
if (player.type() == PlayerType.Human) {
return maxPop;
}
if (player.type() == PlayerType.Human) {
return maxPop;
}
switch (this._gameConfig.difficulty) {
case Difficulty.Easy:
return maxPop * 0.5;
case Difficulty.Medium:
return maxPop * 0.7;
case Difficulty.Hard:
return maxPop * 1;
case Difficulty.Impossible:
return maxPop * 1.5;
}
}
switch (this._gameConfig.difficulty) {
case Difficulty.Easy:
return maxPop * 0.5;
case Difficulty.Medium:
return maxPop * 0.7;
case Difficulty.Hard:
return maxPop * 1;
case Difficulty.Impossible:
return maxPop * 1.5;
}
}
populationIncreaseRate(player: Player): number {
let max = this.maxPopulation(player);
populationIncreaseRate(player: Player): number {
let max = this.maxPopulation(player);
let toAdd = 10 + Math.pow(player.population(), 0.73) / 4;
let toAdd = 10 + Math.pow(player.population(), 0.73) / 4;
const ratio = 1 - player.population() / max;
toAdd *= ratio;
const ratio = 1 - player.population() / max;
toAdd *= ratio;
if (player.type() == PlayerType.Bot) {
toAdd *= 0.7;
}
if (player.type() == PlayerType.Bot) {
toAdd *= 0.7;
}
return Math.min(player.population() + toAdd, max) - player.population();
}
return Math.min(player.population() + toAdd, max) - player.population();
}
goldAdditionRate(player: Player): number {
return Math.sqrt(player.workers() * player.numTilesOwned()) / 200;
}
goldAdditionRate(player: Player): number {
return Math.sqrt(player.workers() * player.numTilesOwned()) / 200;
}
troopAdjustmentRate(player: Player): number {
const maxDiff = this.maxPopulation(player) / 1000;
const target = player.population() * player.targetTroopRatio();
const diff = target - player.troops();
if (Math.abs(diff) < maxDiff) {
return diff;
}
const adjustment = maxDiff * Math.sign(diff);
// Can ramp down troops much faster
if (adjustment < 0) {
return adjustment * 5;
}
return adjustment;
}
troopAdjustmentRate(player: Player): number {
const maxDiff = this.maxPopulation(player) / 1000;
const target = player.population() * player.targetTroopRatio();
const diff = target - player.troops();
if (Math.abs(diff) < maxDiff) {
return diff;
}
const adjustment = maxDiff * Math.sign(diff);
// Can ramp down troops much faster
if (adjustment < 0) {
return adjustment * 5;
}
return adjustment;
}
}
+9 -9
View File
@@ -45,7 +45,7 @@ export class AttackExecution implements Execution {
private _ownerID: PlayerID,
private _targetID: PlayerID | null,
private sourceTile: TileRef | null,
private removeTroops: boolean = true
private removeTroops: boolean = true,
) {}
public targetID(): PlayerID {
@@ -95,7 +95,7 @@ export class AttackExecution implements Execution {
this.attack = this._owner.createAttack(
this.target,
this.startTroops,
this.sourceTile
this.sourceTile,
);
for (const incoming of this._owner.incomingAttacks()) {
@@ -174,7 +174,7 @@ export class AttackExecution implements Execution {
this.attack.troops(),
this._owner,
this.target,
this.border.size + this.random.nextInt(0, 5)
this.border.size + this.random.nextInt(0, 5),
);
// consolex.log(`num tiles per tick: ${numTilesPerTick}`)
// consolex.log(`num execs: ${this.mg.executions().length}`)
@@ -212,7 +212,7 @@ export class AttackExecution implements Execution {
this.attack.troops(),
this._owner,
this.target,
tileToConquer
tileToConquer,
);
numTilesPerTick -= tilesPerTickUsed;
this.attack.setTroops(this.attack.troops() - attackerTroopLoss);
@@ -253,8 +253,8 @@ export class AttackExecution implements Execution {
new TileContainer(
neighbor,
dist / 100 + this.random.nextInt(0, 2) - numOwnedByMe + mag,
this.mg.ticks()
)
this.mg.ticks(),
),
);
}
}
@@ -264,10 +264,10 @@ export class AttackExecution implements Execution {
const gold = this.target.gold();
this.mg.displayMessage(
`Conquered ${this.target.displayName()} received ${renderNumber(
gold
gold,
)} gold`,
MessageType.SUCCESS,
this._owner.id()
this._owner.id(),
);
this.target.removeGold(gold);
this._owner.addGold(gold);
@@ -306,6 +306,6 @@ class TileContainer {
constructor(
public readonly tile: TileRef,
public readonly priority: number,
public readonly tick: number
public readonly tick: number,
) {}
}
+2 -2
View File
@@ -104,8 +104,8 @@ export class BotExecution implements Execution {
this.bot.id(),
toAttack.isPlayer() ? toAttack.id() : null,
null,
null
)
null,
),
);
}
+3 -3
View File
@@ -31,7 +31,7 @@ export class ConstructionExecution implements Execution {
constructor(
private ownerId: PlayerID,
private tile: TileRef,
private constructionType: UnitType
private constructionType: UnitType,
) {}
init(mg: Game, ticks: number): void {
@@ -56,7 +56,7 @@ export class ConstructionExecution implements Execution {
this.construction = this.player.buildUnit(
UnitType.Construction,
0,
spawnTile
spawnTile,
);
this.cost = this.mg.unitInfo(this.constructionType).cost(this.player);
this.player.removeGold(this.cost);
@@ -88,7 +88,7 @@ export class ConstructionExecution implements Execution {
case UnitType.AtomBomb:
case UnitType.HydrogenBomb:
this.mg.addExecution(
new NukeExecution(this.constructionType, player.id(), this.tile)
new NukeExecution(this.constructionType, player.id(), this.tile),
);
break;
case UnitType.MIRV:
+4 -1
View File
@@ -16,7 +16,10 @@ export class DefensePostExecution implements Execution {
private post: Unit;
private active: boolean = true;
constructor(private ownerId: PlayerID, private tile: TileRef) {}
constructor(
private ownerId: PlayerID,
private tile: TileRef,
) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
+12 -12
View File
@@ -41,7 +41,7 @@ export class Executor {
constructor(
private mg: Game,
private gameID: GameID,
private clientID: ClientID
private clientID: ClientID,
) {
// Add one to avoid id collisions with bots.
this.random = new PseudoRandom(simpleHash(gameID) + 1);
@@ -58,7 +58,7 @@ export class Executor {
intent.troops,
intent.attackerID,
intent.targetID,
null
null,
);
}
case "spawn":
@@ -71,16 +71,16 @@ export class Executor {
: fixProfaneUsername(sanitize(intent.name)),
intent.playerType,
intent.clientID,
intent.playerID
intent.playerID,
),
this.mg.ref(intent.x, intent.y)
this.mg.ref(intent.x, intent.y),
);
case "boat":
return new TransportShipExecution(
intent.attackerID,
intent.targetID,
this.mg.ref(intent.x, intent.y),
intent.troops
intent.troops,
);
case "allianceRequest":
return new AllianceRequestExecution(intent.requestor, intent.recipient);
@@ -88,7 +88,7 @@ export class Executor {
return new AllianceRequestReplyExecution(
intent.requestor,
intent.recipient,
intent.accept
intent.accept,
);
case "breakAlliance":
return new BreakAllianceExecution(intent.requestor, intent.recipient);
@@ -98,13 +98,13 @@ export class Executor {
return new EmojiExecution(
intent.sender,
intent.recipient,
intent.emoji
intent.emoji,
);
case "donate":
return new DonateExecution(
intent.sender,
intent.recipient,
intent.troops
intent.troops,
);
case "troop_ratio":
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
@@ -112,7 +112,7 @@ export class Executor {
return new ConstructionExecution(
intent.player,
this.mg.ref(intent.x, intent.y),
intent.unit
intent.unit,
);
default:
throw new Error(`intent type ${intent} not found`);
@@ -137,9 +137,9 @@ export class Executor {
PlayerType.FakeHuman,
null,
this.random.nextID(),
nation
)
)
nation,
),
),
);
}
return execs;
+35 -32
View File
@@ -40,9 +40,12 @@ export class FakeHumanExecution implements Execution {
private lastEnemyUpdateTick: number = 0;
private lastEmojiSent = new Map<Player, Tick>();
constructor(gameID: GameID, private playerInfo: PlayerInfo) {
constructor(
gameID: GameID,
private playerInfo: PlayerInfo,
) {
this.random = new PseudoRandom(
simpleHash(playerInfo.id) + simpleHash(gameID)
simpleHash(playerInfo.id) + simpleHash(gameID),
);
}
@@ -99,7 +102,7 @@ export class FakeHumanExecution implements Execution {
const enemyborder = Array.from(this.player.borderTiles())
.flatMap((t) => this.mg.neighbors(t))
.filter(
(t) => this.mg.isLand(t) && this.mg.ownerID(t) != this.player.smallID()
(t) => this.mg.isLand(t) && this.mg.ownerID(t) != this.player.smallID(),
);
if (enemyborder.length == 0) {
@@ -114,7 +117,7 @@ export class FakeHumanExecution implements Execution {
}
const enemiesWithTN = enemyborder.map((t) =>
this.mg.playerBySmallID(this.mg.ownerID(t))
this.mg.playerBySmallID(this.mg.ownerID(t)),
);
if (enemiesWithTN.filter((o) => !o.isPlayer()).length > 0) {
this.sendAttack(this.mg.terraNullius());
@@ -194,7 +197,7 @@ export class FakeHumanExecution implements Execution {
this.lastEnemyUpdateTick = this.mg.ticks();
if (target.ally.type() == PlayerType.Human) {
this.mg.addExecution(
new EmojiExecution(this.player.id(), target.ally.id(), "👍")
new EmojiExecution(this.player.id(), target.ally.id(), "👍"),
);
}
}
@@ -215,8 +218,8 @@ export class FakeHumanExecution implements Execution {
new EmojiExecution(
this.player.id(),
this.enemy.id(),
this.random.randElement(["🤡", "😡"])
)
this.random.randElement(["🤡", "😡"]),
),
);
}
}
@@ -260,7 +263,7 @@ export class FakeHumanExecution implements Execution {
}
if (this.player.canBuild(UnitType.AtomBomb, tile)) {
this.mg.addExecution(
new NukeExecution(UnitType.AtomBomb, this.player.id(), tile)
new NukeExecution(UnitType.AtomBomb, this.player.id(), tile),
);
return;
}
@@ -271,9 +274,9 @@ export class FakeHumanExecution implements Execution {
const closest = closestTwoTiles(
this.mg,
Array.from(this.player.borderTiles()).filter((t) =>
this.mg.isOceanShore(t)
this.mg.isOceanShore(t),
),
Array.from(other.borderTiles()).filter((t) => this.mg.isOceanShore(t))
Array.from(other.borderTiles()).filter((t) => this.mg.isOceanShore(t)),
);
if (closest == null) {
return;
@@ -287,8 +290,8 @@ export class FakeHumanExecution implements Execution {
this.player.id(),
other.id(),
closest.y,
this.player.troops() / 5
)
this.player.troops() / 5,
),
);
}
}
@@ -297,12 +300,12 @@ export class FakeHumanExecution implements Execution {
const ports = this.player.units(UnitType.Port);
if (ports.length == 0 && this.player.gold() > this.cost(UnitType.Port)) {
const oceanTiles = Array.from(this.player.borderTiles()).filter((t) =>
this.mg.isOceanShore(t)
this.mg.isOceanShore(t),
);
if (oceanTiles.length > 0) {
const buildTile = this.random.randElement(oceanTiles);
this.mg.addExecution(
new ConstructionExecution(this.player.id(), buildTile, UnitType.Port)
new ConstructionExecution(this.player.id(), buildTile, UnitType.Port),
);
}
return;
@@ -310,7 +313,7 @@ export class FakeHumanExecution implements Execution {
this.maybeSpawnStructure(
UnitType.City,
2,
(t) => new ConstructionExecution(this.player.id(), t, UnitType.City)
(t) => new ConstructionExecution(this.player.id(), t, UnitType.City),
);
if (this.maybeSpawnWarship()) {
return;
@@ -319,14 +322,14 @@ export class FakeHumanExecution implements Execution {
UnitType.MissileSilo,
1,
(t) =>
new ConstructionExecution(this.player.id(), t, UnitType.MissileSilo)
new ConstructionExecution(this.player.id(), t, UnitType.MissileSilo),
);
}
private maybeSpawnStructure(
type: UnitType,
maxNum: number,
build: (tile: TileRef) => Execution
build: (tile: TileRef) => Execution,
) {
const units = this.player.units(type);
if (units.length >= maxNum) {
@@ -373,8 +376,8 @@ export class FakeHumanExecution implements Execution {
new ConstructionExecution(
this.player.id(),
targetTile,
UnitType.Warship
)
UnitType.Warship,
),
);
return true;
}
@@ -403,11 +406,11 @@ export class FakeHumanExecution implements Execution {
for (let attempts = 0; attempts < 50; attempts++) {
const randX = this.random.nextInt(
this.mg.x(portTile) - radius,
this.mg.x(portTile) + radius
this.mg.x(portTile) + radius,
);
const randY = this.random.nextInt(
this.mg.y(portTile) - radius,
this.mg.y(portTile) + radius
this.mg.y(portTile) + radius,
);
if (!this.mg.isValidCoord(randX, randY)) {
continue;
@@ -457,8 +460,8 @@ export class FakeHumanExecution implements Execution {
new AllianceRequestReplyExecution(
req.requestor().id(),
this.player.id(),
accept
)
accept,
),
);
}
@@ -469,7 +472,7 @@ export class FakeHumanExecution implements Execution {
if (oceanShore == null) {
oceanShore = Array.from(this.player.borderTiles()).filter((t) =>
this.mg.isOceanShore(t)
this.mg.isOceanShore(t),
);
}
if (oceanShore.length == 0) {
@@ -482,9 +485,9 @@ export class FakeHumanExecution implements Execution {
src,
andFN(
(gm, t) => gm.isOcean(t) || gm.isOceanShore(t),
manhattanDistFN(src, 200)
)
)
manhattanDistFN(src, 200),
),
),
).filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) != this.player);
if (otherShore.length == 0) {
@@ -508,8 +511,8 @@ export class FakeHumanExecution implements Execution {
this.player.id(),
this.mg.hasOwner(dst) ? this.mg.owner(dst).id() : null,
dst,
this.player.troops() / 5
)
this.player.troops() / 5,
),
);
return;
}
@@ -548,8 +551,8 @@ export class FakeHumanExecution implements Execution {
this.player.id(),
toAttack.isPlayer() ? toAttack.id() : null,
null,
null
)
null,
),
);
}
@@ -557,7 +560,7 @@ export class FakeHumanExecution implements Execution {
return (
this.mg.bfs(
tile,
andFN((gm, t) => gm.isLand(t), manhattanDistFN(tile, 10))
andFN((gm, t) => gm.isLand(t), manhattanDistFN(tile, 10)),
).size < 50
);
}
+12 -9
View File
@@ -37,7 +37,10 @@ export class MirvExecution implements Execution {
private separateDst: TileRef;
constructor(private senderID: PlayerID, private dst: TileRef) {}
constructor(
private senderID: PlayerID,
private dst: TileRef,
) {}
init(mg: Game, ticks: number): void {
this.random = new PseudoRandom(mg.ticks() + simpleHash(this.senderID));
@@ -57,7 +60,7 @@ export class MirvExecution implements Execution {
}
this.nuke = this.player.buildUnit(UnitType.MIRV, 0, spawn);
const x = Math.floor(
(this.mg.x(this.dst) + this.mg.x(this.mg.x(this.nuke.tile()))) / 2
(this.mg.x(this.dst) + this.mg.x(this.mg.x(this.nuke.tile()))) / 2,
);
const y = Math.max(0, this.mg.y(this.dst) - 500) + 50;
this.separateDst = this.mg.ref(x, y);
@@ -66,7 +69,7 @@ export class MirvExecution implements Execution {
for (let i = 0; i < 4; i++) {
const result = this.pathFinder.nextTile(
this.nuke.tile(),
this.separateDst
this.separateDst,
);
switch (result.type) {
case PathFindResultType.Completed:
@@ -81,7 +84,7 @@ export class MirvExecution implements Execution {
break;
case PathFindResultType.PathNotFound:
consolex.warn(
`nuke cannot find path from ${this.nuke.tile()} to ${this.dst}`
`nuke cannot find path from ${this.nuke.tile()} to ${this.dst}`,
);
this.active = false;
return;
@@ -103,7 +106,7 @@ export class MirvExecution implements Execution {
console.log(`dsts: ${dsts.length}`);
dsts.sort(
(a, b) =>
this.mg.manhattanDist(b, this.dst) - this.mg.manhattanDist(a, this.dst)
this.mg.manhattanDist(b, this.dst) - this.mg.manhattanDist(a, this.dst),
);
console.log(`got ${dsts.length} dsts!!`);
@@ -116,8 +119,8 @@ export class MirvExecution implements Execution {
this.nuke.tile(),
15 + Math.floor((i / this.warheadCount) * 5),
// this.random.nextInt(5, 9),
this.random.nextInt(0, 15)
)
this.random.nextInt(0, 15),
),
);
}
if (this.targetPlayer.isPlayer()) {
@@ -138,11 +141,11 @@ export class MirvExecution implements Execution {
tries++;
const x = this.random.nextInt(
this.mg.x(ref) - this.mirvRange,
this.mg.x(ref) + this.mirvRange
this.mg.x(ref) + this.mirvRange,
);
const y = this.random.nextInt(
this.mg.y(ref) - this.mirvRange,
this.mg.y(ref) + this.mirvRange
this.mg.y(ref) + this.mirvRange,
);
if (!this.mg.isValidCoord(x, y)) {
continue;
+3 -3
View File
@@ -29,7 +29,7 @@ export class NukeExecution implements Execution {
private dst: TileRef,
private src?: TileRef,
private speed: number = 4,
private waitTicks = 0
private waitTicks = 0,
) {}
init(mg: Game, ticks: number): void {
@@ -77,7 +77,7 @@ export class NukeExecution implements Execution {
let nextY = y;
const ratio = Math.floor(
1 + Math.abs(dstY - y) / (Math.abs(dstX - x) + 1)
1 + Math.abs(dstY - y) / (Math.abs(dstX - x) + 1),
);
if (this.random.chance(ratio) && x != dstX) {
@@ -123,7 +123,7 @@ export class NukeExecution implements Execution {
const ratio = Object.fromEntries(
this.mg
.players()
.map((p) => [p.id(), (p.troops() + p.workers()) / p.numTilesOwned()])
.map((p) => [p.id(), (p.troops() + p.workers()) / p.numTilesOwned()]),
);
const attacked = new Map<Player, number>();
for (const tile of toDestroy) {
+8 -5
View File
@@ -25,7 +25,10 @@ export class PortExecution implements Execution {
private portPaths = new Map<Unit, TileRef[]>();
private computingPaths = new Map<Unit, MiniAStar>();
constructor(private _owner: PlayerID, private tile: TileRef) {}
constructor(
private _owner: PlayerID,
private tile: TileRef,
) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
@@ -46,7 +49,7 @@ export class PortExecution implements Execution {
.filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
.sort(
(a, b) =>
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile)
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile),
);
if (spawns.length == 0) {
@@ -68,7 +71,7 @@ export class PortExecution implements Execution {
const alliedPortsSet = new Set(alliedPorts);
const allyConnections = new Set(
Array.from(this.portPaths.keys()).map((p) => p.owner())
Array.from(this.portPaths.keys()).map((p) => p.owner()),
);
allyConnections;
@@ -100,7 +103,7 @@ export class PortExecution implements Execution {
port.tile(),
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
10_000,
25
25,
);
this.computingPaths.set(port, pf);
}
@@ -123,7 +126,7 @@ export class PortExecution implements Execution {
if (path != null) {
const pf = PathFinder.Mini(this.mg, 10000, false);
this.mg.addExecution(
new TradeShipExecution(this.player().id(), this.port, port, pf, path)
new TradeShipExecution(this.player().id(), this.port, port, pf, path),
);
}
}
+9 -6
View File
@@ -37,7 +37,10 @@ export class WarshipExecution implements Execution {
private alreadySentShell = new Set<Unit>();
constructor(private playerID: PlayerID, private patrolCenterTile: TileRef) {}
constructor(
private playerID: PlayerID,
private patrolCenterTile: TileRef,
) {}
init(mg: Game, ticks: number): void {
this.pathfinder = PathFinder.Mini(mg, 5000, false);
@@ -68,7 +71,7 @@ export class WarshipExecution implements Execution {
const ships = this.mg
.units(UnitType.TransportShip, UnitType.Warship, UnitType.TradeShip)
.filter(
(u) => this.mg.manhattanDist(u.tile(), this.warship.tile()) < 130
(u) => this.mg.manhattanDist(u.tile(), this.warship.tile()) < 130,
)
.filter((u) => u.owner() != this.warship.owner())
.filter((u) => u != this.warship)
@@ -80,7 +83,7 @@ export class WarshipExecution implements Execution {
// Patrol unless we are hunting down a tradeship
const result = this.pathfinder.nextTile(
this.warship.tile(),
this.patrolTile
this.patrolTile,
);
switch (result.type) {
case PathFindResultType.Completed:
@@ -115,8 +118,8 @@ export class WarshipExecution implements Execution {
this.warship.tile(),
this.warship.owner(),
this.warship,
this.target
)
this.target,
),
);
if (!this.target.hasHealth()) {
// Don't send multiple shells to target that can be oneshotted
@@ -134,7 +137,7 @@ export class WarshipExecution implements Execution {
const result = this.pathfinder.nextTile(
this.warship.tile(),
this.target.tile(),
5
5,
);
switch (result.type) {
case PathFindResultType.Completed:
@@ -13,7 +13,10 @@ export class AllianceRequestExecution implements Execution {
private requestor: Player;
private recipient: Player;
constructor(private requestorID: PlayerID, private recipientID: PlayerID) {}
constructor(
private requestorID: PlayerID,
private recipientID: PlayerID,
) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
+1 -1
View File
@@ -9,7 +9,7 @@ export class AttackImpl implements Attack {
private _target: Player | TerraNullius,
private _attacker: Player,
private _troops: number,
private _sourceTile: TileRef | null
private _sourceTile: TileRef | null,
) {}
sourceTile(): TileRef | null {
+5 -2
View File
@@ -6,13 +6,16 @@ export class DefenseGrid {
private grid: Set<Unit | UnitView>[][];
private readonly cellSize = 100;
constructor(private gm: GameMap, private searchRange: number) {
constructor(
private gm: GameMap,
private searchRange: number,
) {
this.grid = Array(Math.ceil(gm.height() / this.cellSize))
.fill(null)
.map(() =>
Array(Math.ceil(gm.width() / this.cellSize))
.fill(null)
.map(() => new Set<Unit | UnitView>())
.map(() => new Set<Unit | UnitView>()),
);
}
+8 -5
View File
@@ -89,7 +89,7 @@ export class Nation {
public readonly flag: string,
public readonly name: string,
public readonly cell: Cell,
public readonly strength: number
public readonly strength: number,
) {}
}
@@ -98,7 +98,10 @@ export class Cell {
private strRepr: string;
constructor(public readonly x, public readonly y) {
constructor(
public readonly x,
public readonly y,
) {
this.strRepr = `Cell[${this.x},${this.y}]`;
}
@@ -176,7 +179,7 @@ export class PlayerInfo {
public readonly clientID: ClientID | null,
// TODO: make player id the small id
public readonly id: PlayerID,
public readonly nation?: Nation | null
public readonly nation?: Nation | null,
) {}
}
@@ -302,7 +305,7 @@ export interface Player {
createAttack(
target: Player | TerraNullius,
troops: number,
sourceTile: TileRef
sourceTile: TileRef,
): Attack;
outgoingAttacks(): Attack[];
incomingAttacks(): Attack[];
@@ -350,7 +353,7 @@ export interface Game extends GameMap {
displayMessage(
message: string,
type: MessageType,
playerID: PlayerID | null
playerID: PlayerID | null,
): void;
// Nations
+14 -14
View File
@@ -35,7 +35,7 @@ export function createGame(
gameMap: GameMap,
miniGameMap: GameMap,
nationMap: NationMap,
config: Config
config: Config,
): Game {
return new GameImpl(gameMap, miniGameMap, nationMap, config);
}
@@ -70,7 +70,7 @@ export class GameImpl implements Game {
private _map: GameMap,
private miniGameMap: GameMap,
nationMap: NationMap,
private _config: Config
private _config: Config,
) {
this._terraNullius = new TerraNulliusImpl();
this._width = _map.width();
@@ -81,12 +81,12 @@ export class GameImpl implements Game {
n.flag || "",
n.name,
new Cell(n.coordinates[0], n.coordinates[1]),
n.strength
)
n.strength,
),
);
this.defenseGrid = new DefenseGrid(
this._map,
this._config.defensePostRange()
this._config.defensePostRange(),
);
}
@@ -173,11 +173,11 @@ export class GameImpl implements Game {
this,
request.requestor() as PlayerImpl,
request.recipient() as PlayerImpl,
this._ticks
this._ticks,
);
this.alliances_.push(alliance);
(request.requestor() as PlayerImpl).pastOutgoingAllianceRequests.push(
request
request,
);
this.addUpdate({
type: GameUpdateType.AllianceRequestReply,
@@ -189,7 +189,7 @@ export class GameImpl implements Game {
rejectAllianceRequest(request: AllianceRequestImpl) {
this.allianceRequests = this.allianceRequests.filter((ar) => ar != request);
(request.requestor() as PlayerImpl).pastOutgoingAllianceRequests.push(
request
request,
);
this.addUpdate({
type: GameUpdateType.AllianceRequestReply,
@@ -296,7 +296,7 @@ export class GameImpl implements Game {
removeExecution(exec: Execution) {
this.execs = this.execs.filter((execution) => execution !== exec);
this.unInitExecs = this.unInitExecs.filter(
(execution) => execution !== exec
(execution) => execution !== exec,
);
}
@@ -448,7 +448,7 @@ export class GameImpl implements Game {
}
if (!breaker.isAlliedWith(other)) {
throw new Error(
`${breaker} not allied with ${other}, cannot break alliance`
`${breaker} not allied with ${other}, cannot break alliance`,
);
}
if (!other.isTraitor()) {
@@ -459,7 +459,7 @@ export class GameImpl implements Game {
const alliances = other.alliances().filter((a) => breakerSet.has(a));
if (alliances.length != 1) {
throw new Error(
`must have exactly one alliance, have ${alliances.length}`
`must have exactly one alliance, have ${alliances.length}`,
);
}
this.alliances_ = this.alliances_.filter((a) => a != alliances[0]);
@@ -478,7 +478,7 @@ export class GameImpl implements Game {
.filter((a) => p1Set.has(a));
if (alliances.length != 1) {
throw new Error(
`cannot expire alliance: must have exactly one alliance, have ${alliances.length}`
`cannot expire alliance: must have exactly one alliance, have ${alliances.length}`,
);
}
this.alliances_ = this.alliances_.filter((a) => a != alliances[0]);
@@ -506,7 +506,7 @@ export class GameImpl implements Game {
displayMessage(
message: string,
type: MessageType,
playerID: PlayerID | null
playerID: PlayerID | null,
): void {
let id = null;
if (playerID != null) {
@@ -614,7 +614,7 @@ export class GameImpl implements Game {
}
bfs(
tile: TileRef,
filter: (gm: GameMap, tile: TileRef) => boolean
filter: (gm: GameMap, tile: TileRef) => boolean,
): Set<TileRef> {
return this._map.bfs(tile, filter);
}
+1 -1
View File
@@ -80,7 +80,7 @@ export interface PlayerUpdate {
type: GameUpdateType.Player;
nameViewData?: NameViewData;
clientID: ClientID;
flag: string,
flag: string;
name: string;
displayName: string;
id: PlayerID;
+19 -10
View File
@@ -36,7 +36,10 @@ export class UnitView {
public _wasUpdated = true;
public lastPos: MapPos[] = [];
constructor(private gameView: GameView, private data: UnitUpdate) {
constructor(
private gameView: GameView,
private data: UnitUpdate,
) {
this.lastPos.push(data.pos);
}
@@ -95,14 +98,14 @@ export class PlayerView {
constructor(
private game: GameView,
public data: PlayerUpdate,
public nameData: NameViewData
public nameData: NameViewData,
) {}
async actions(tile: TileRef): Promise<PlayerActions> {
return this.game.worker.playerInteraction(
this.id(),
this.game.x(tile),
this.game.y(tile)
this.game.y(tile),
);
}
@@ -156,12 +159,12 @@ export class PlayerView {
}
allies(): PlayerView[] {
return this.data.allies.map(
(a) => this.game.playerBySmallID(a) as PlayerView
(a) => this.game.playerBySmallID(a) as PlayerView,
);
}
targets(): PlayerView[] {
return this.data.targets.map(
(id) => this.game.playerBySmallID(id) as PlayerView
(id) => this.game.playerBySmallID(id) as PlayerView,
);
}
gold(): Gold {
@@ -199,7 +202,13 @@ export class PlayerView {
return this.data.outgoingEmojis;
}
info(): PlayerInfo {
return new PlayerInfo(this.flag(), this.name(), this.type(), this.clientID(), this.id());
return new PlayerInfo(
this.flag(),
this.name(),
this.type(),
this.clientID(),
this.id(),
);
}
}
@@ -218,7 +227,7 @@ export class GameView implements GameMap {
public worker: WorkerClient,
private _config: Config,
private _map: GameMap,
private _myClientID: ClientID
private _myClientID: ClientID,
) {
this.lastUpdate = {
tick: 0,
@@ -250,7 +259,7 @@ export class GameView implements GameMap {
} else {
this._players.set(
pu.id,
new PlayerView(this, pu, gu.playerNameViewData[pu.id])
new PlayerView(this, pu, gu.playerNameViewData[pu.id]),
);
}
});
@@ -347,7 +356,7 @@ export class GameView implements GameMap {
return Array.from(this._units.values()).filter((u) => u.isActive());
}
return Array.from(this._units.values()).filter(
(u) => u.isActive() && types.includes(u.type())
(u) => u.isActive() && types.includes(u.type()),
);
}
unit(id: number): UnitView {
@@ -443,7 +452,7 @@ export class GameView implements GameMap {
}
bfs(
tile: TileRef,
filter: (gm: GameMap, tile: TileRef) => boolean
filter: (gm: GameMap, tile: TileRef) => boolean,
): Set<TileRef> {
return this._map.bfs(tile, filter);
}
+31 -28
View File
@@ -46,7 +46,10 @@ interface Target {
}
class Donation {
constructor(public readonly recipient: Player, public readonly tick: Tick) {}
constructor(
public readonly recipient: Player,
public readonly tick: Tick,
) {}
}
export class PlayerImpl implements Player {
@@ -85,7 +88,7 @@ export class PlayerImpl implements Player {
private mg: GameImpl,
private _smallID: number,
private readonly playerInfo: PlayerInfo,
startPopulation: number
startPopulation: number,
) {
this._flag = playerInfo.flag;
this._name = playerInfo.name;
@@ -125,7 +128,7 @@ export class PlayerImpl implements Player {
attackerID: a.attacker().smallID(),
targetID: a.target().smallID(),
troops: a.troops(),
} as AttackUpdate)
}) as AttackUpdate,
),
incomingAttacks: this._incomingAttacks.map(
(a) =>
@@ -133,7 +136,7 @@ export class PlayerImpl implements Player {
attackerID: a.attacker().smallID(),
targetID: a.target().smallID(),
troops: a.troops(),
} as AttackUpdate)
}) as AttackUpdate,
),
};
}
@@ -203,7 +206,7 @@ export class PlayerImpl implements Player {
const owner = this.mg.map().ownerID(neighbor);
if (owner != this.smallID()) {
ns.add(
this.mg.playerBySmallID(owner) as PlayerImpl | TerraNulliusImpl
this.mg.playerBySmallID(owner) as PlayerImpl | TerraNulliusImpl,
);
}
}
@@ -249,7 +252,7 @@ export class PlayerImpl implements Player {
alliances(): MutableAlliance[] {
return this.mg.alliances_.filter(
(a) => a.requestor() == this || a.recipient() == this
(a) => a.requestor() == this || a.recipient() == this,
);
}
@@ -269,7 +272,7 @@ export class PlayerImpl implements Player {
return null;
}
return this.alliances().find(
(a) => a.recipient() == other || a.requestor() == other
(a) => a.recipient() == other || a.requestor() == other,
);
}
@@ -398,7 +401,7 @@ export class PlayerImpl implements Player {
targets(): PlayerImpl[] {
return this.targets_
.filter(
(t) => this.mg.ticks() - t.tick < this.mg.config().targetDuration()
(t) => this.mg.ticks() - t.tick < this.mg.config().targetDuration(),
)
.map((t) => t.target as PlayerImpl);
}
@@ -430,7 +433,7 @@ export class PlayerImpl implements Player {
.filter(
(e) =>
this.mg.ticks() - e.createdAt <
this.mg.config().emojiMessageDuration()
this.mg.config().emojiMessageDuration(),
)
.sort((a, b) => b.createdAt - a.createdAt);
}
@@ -439,7 +442,7 @@ export class PlayerImpl implements Player {
const recipientID =
recipient == AllPlayers ? AllPlayers : recipient.smallID();
const prevMsgs = this.outgoingEmojis_.filter(
(msg) => msg.recipientID == recipientID
(msg) => msg.recipientID == recipientID,
);
for (const msg of prevMsgs) {
if (
@@ -475,12 +478,12 @@ export class PlayerImpl implements Player {
this.mg.displayMessage(
`Sent ${renderTroops(troops)} troops to ${recipient.name()}`,
MessageType.INFO,
this.id()
this.id(),
);
this.mg.displayMessage(
`Recieved ${renderTroops(troops)} troops from ${this.name()}`,
MessageType.SUCCESS,
recipient.id()
recipient.id(),
);
}
@@ -495,7 +498,7 @@ export class PlayerImpl implements Player {
removeGold(toRemove: Gold): void {
if (toRemove > this._gold) {
throw Error(
`Player ${this} does not enough gold (${toRemove} vs ${this._gold}))`
`Player ${this} does not enough gold (${toRemove} vs ${this._gold}))`,
);
}
this._gold -= toRemove;
@@ -521,7 +524,7 @@ export class PlayerImpl implements Player {
setTargetTroopRatio(target: number): void {
if (target < 0 || target > 1) {
throw new Error(
`invalid targetTroopRatio ${target} set on player ${PlayerImpl}`
`invalid targetTroopRatio ${target} set on player ${PlayerImpl}`,
);
}
this._targetTroopRatio = target;
@@ -553,7 +556,7 @@ export class PlayerImpl implements Player {
}
const prev = unit.owner();
(prev as PlayerImpl)._units = (prev as PlayerImpl)._units.filter(
(u) => u != unit
(u) => u != unit,
);
(unit as UnitImpl)._owner = this;
this._units.push(unit as UnitImpl);
@@ -561,12 +564,12 @@ export class PlayerImpl implements Player {
this.mg.displayMessage(
`${unit.type()} captured by ${this.displayName()}`,
MessageType.ERROR,
prev.id()
prev.id(),
);
this.mg.displayMessage(
`Captured ${unit.type()} from ${prev.displayName()}`,
MessageType.SUCCESS,
this.id()
this.id(),
);
}
@@ -578,7 +581,7 @@ export class PlayerImpl implements Player {
spawnTile,
troops,
this.mg.nextUnitID(),
this
this,
);
this._units.push(b);
this.removeGold(cost);
@@ -641,7 +644,7 @@ export class PlayerImpl implements Player {
.filter((t) => this.mg.owner(t) == this && this.mg.isOceanShore(t))
.sort(
(a, b) =>
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile)
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile),
);
if (spawns.length == 0) {
return false;
@@ -657,12 +660,12 @@ export class PlayerImpl implements Player {
.filter(
(u) =>
this.mg.manhattanDist(u.tile(), tile) <
this.mg.config().boatMaxDistance()
this.mg.config().boatMaxDistance(),
)
.sort(
(a, b) =>
this.mg.manhattanDist(a.tile(), tile) -
this.mg.manhattanDist(b.tile(), tile)
this.mg.manhattanDist(b.tile(), tile),
);
if (spawns.length == 0) {
return false;
@@ -690,7 +693,7 @@ export class PlayerImpl implements Player {
tradeShipSpawn(targetTile: TileRef): TileRef | false {
const spawns = this.units(UnitType.Port).filter(
(u) => u.tile() == targetTile
(u) => u.tile() == targetTile,
);
if (spawns.length == 0) {
return false;
@@ -721,7 +724,7 @@ export class PlayerImpl implements Player {
this.allRelationsSorted().map(({ player, relation }) => [
player.smallID(),
relation,
])
]),
),
alliances: this.alliances().map((a) => a.other(this).smallID()),
};
@@ -765,8 +768,8 @@ export class PlayerImpl implements Player {
tile,
andFN(
(gm, t) => gm.ownerID(t) == gm.ownerID(tile) && gm.isLand(t),
manhattanDistFN(tile, 25)
)
manhattanDistFN(tile, 25),
),
)) {
if (this.mg.isOceanShore(t)) {
nearOcean = true;
@@ -790,7 +793,7 @@ export class PlayerImpl implements Player {
createAttack(
target: Player | TerraNullius,
troops: number,
sourceTile: TileRef
sourceTile: TileRef,
): Attack {
const attack = new AttackImpl(target, this, troops, sourceTile);
this._outgoingAttacks.push(attack);
@@ -835,8 +838,8 @@ export class PlayerImpl implements Player {
tile,
andFN(
(gm, t) => !gm.hasOwner(t) && gm.isLand(t),
manhattanDistFN(tile, 200)
)
manhattanDistFN(tile, 200),
),
)) {
for (const n of this.mg.neighbors(t)) {
if (this.mg.owner(n) == this) {
+3 -3
View File
@@ -20,7 +20,7 @@ export class UnitImpl implements Unit {
private _tile: TileRef,
private _troops: number,
private _id: number,
public _owner: PlayerImpl
public _owner: PlayerImpl,
) {
// default to half health (or 1 is no health specified)
this._health = (this.mg.unitInfo(_type).maxHealth ?? 2) / 2;
@@ -89,7 +89,7 @@ export class UnitImpl implements Unit {
this.mg.displayMessage(
`Your ${this.type()} was captured by ${newOwner.displayName()}`,
MessageType.ERROR,
oldOwner.id()
oldOwner.id(),
);
}
@@ -111,7 +111,7 @@ export class UnitImpl implements Unit {
this.mg.displayMessage(
`Your ${this.type()} was destroyed`,
MessageType.ERROR,
this.owner().id()
this.owner().id(),
);
}
}
+2 -1
View File
@@ -61,7 +61,8 @@ export function validateUsername(username: string): {
if (!validPattern.test(username)) {
return {
isValid: false,
error: "Username can only contain letters, numbers, spaces, underscores, and [square brackets].",
error:
"Username can only contain letters, numbers, spaces, underscores, and [square brackets].",
};
}
+2 -2
View File
@@ -35,7 +35,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
message.gameID,
message.gameConfig,
message.clientID,
gameUpdate
gameUpdate,
).then((gr) => {
sendMessage({
type: "initialized",
@@ -72,7 +72,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
const actions = (await gameRunner).playerActions(
message.playerID,
message.x,
message.y
message.y,
);
sendMessage({
type: "player_actions_result",
+4 -4
View File
@@ -14,13 +14,13 @@ export class WorkerClient {
private isInitialized = false;
private messageHandlers: Map<string, (message: WorkerMessage) => void>;
private gameUpdateCallback?: (
update: GameUpdateViewData | ErrorUpdate
update: GameUpdateViewData | ErrorUpdate,
) => void;
constructor(
private gameID: GameID,
private gameConfig: GameConfig,
private clientID: ClientID
private clientID: ClientID,
) {
this.worker = new Worker(new URL("./Worker.worker.ts", import.meta.url));
this.messageHandlers = new Map();
@@ -28,7 +28,7 @@ export class WorkerClient {
// Set up global message handler
this.worker.addEventListener(
"message",
this.handleWorkerMessage.bind(this)
this.handleWorkerMessage.bind(this),
);
}
@@ -135,7 +135,7 @@ export class WorkerClient {
playerInteraction(
playerID: PlayerID,
x: number,
y: number
y: number,
): Promise<PlayerActions> {
return new Promise((resolve, reject) => {
if (!this.isInitialized) {
+3 -3
View File
@@ -52,7 +52,7 @@ export class GameManager {
disableBots: false,
disableNPCs: false,
creativeMode: false,
})
}),
);
return id;
}
@@ -60,7 +60,7 @@ export class GameManager {
hasActiveGame(gameID: GameID): boolean {
const game = this.games
.filter(
(g) => g.phase() == GamePhase.Lobby || g.phase() == GamePhase.Active
(g) => g.phase() == GamePhase.Lobby || g.phase() == GamePhase.Active,
)
.find((g) => g.id == gameID);
return game != null;
@@ -93,7 +93,7 @@ export class GameManager {
disableBots: false,
disableNPCs: false,
creativeMode: false,
})
}),
);
}
+4 -4
View File
@@ -134,7 +134,7 @@ wss.on("connection", (ws, req) => {
ws.on("message", (message: string) => {
try {
const clientMsg: ClientMessage = ClientMessageSchema.parse(
JSON.parse(message)
JSON.parse(message),
);
if (clientMsg.type == "join") {
const forwarded = req.headers["x-forwarded-for"];
@@ -147,7 +147,7 @@ wss.on("connection", (ws, req) => {
const { isValid, error } = validateUsername(clientMsg.username);
if (!isValid) {
console.log(
`game ${clientMsg.gameID}, client ${clientMsg.clientID} received invalid username, ${error}`
`game ${clientMsg.gameID}, client ${clientMsg.clientID} received invalid username, ${error}`,
);
return;
}
@@ -158,10 +158,10 @@ wss.on("connection", (ws, req) => {
clientMsg.persistentID,
ip,
clientMsg.username,
ws
ws,
),
clientMsg.gameID,
clientMsg.lastTurn
clientMsg.lastTurn,
);
}
if (clientMsg.type == "log") {