can select map private game

This commit is contained in:
evanpelle
2024-10-12 20:54:18 -07:00
parent 3a2cd4e326
commit 5da1c2f620
7 changed files with 135 additions and 90 deletions
+22 -10
View File
@@ -1,39 +1,52 @@
import {Executor} from "../core/execution/ExecutionManager";
import {Cell, MutableGame, PlayerEvent, PlayerID, MutablePlayer, TileEvent, Player, Game, BoatEvent, Tile, PlayerType} from "../core/game/Game";
import {Cell, MutableGame, PlayerEvent, PlayerID, MutablePlayer, TileEvent, Player, Game, BoatEvent, Tile, PlayerType, GameMap} from "../core/game/Game";
import {createGame} from "../core/game/GameImpl";
import {EventBus} from "../core/EventBus";
import {Config} from "../core/configuration/Config";
import {Config, getConfig} from "../core/configuration/Config";
import {createRenderer, GameRenderer} from "./graphics/GameRenderer";
import {InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent} from "./InputHandler"
import {ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, ClientMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ServerSyncMessage, Turn} from "../core/Schemas";
import {TerrainMap} from "../core/game/TerrainMapLoader";
import {loadTerrainMap, TerrainMap} from "../core/game/TerrainMapLoader";
import {and, bfs, dist, manhattanDist} from "../core/Util";
import {TerrainLayer} from "./graphics/layers/TerrainLayer";
import {WinCheckExecution} from "../core/execution/WinCheckExecution";
import {SendAttackIntentEvent, SendSpawnIntentEvent, Transport} from "./Transport";
import {createCanvas} from "./graphics/Utils";
import {DisplayMessageEvent, MessageType} from "./graphics/layers/EventsDisplay";
import {v4 as uuidv4} from 'uuid';
export function createClientGame(isLocal: boolean, playerName: () => string, clientID: ClientID, playerID: PlayerID, ip: string | null, gameID: GameID, config: Config, terrainMap: TerrainMap): ClientGame {
export interface GameConfig {
isLocal: boolean
playerName: () => string
gameID: GameID
ip: string | null
map: GameMap
}
export async function createClientGame(gameConfig: GameConfig): Promise<ClientGame> {
let eventBus = new EventBus()
const config = getConfig()
const clientID = uuidv4()
const playerID = uuidv4()
const terrainMap = await loadTerrainMap(gameConfig.map)
let game = createGame(terrainMap, eventBus, config)
const canvas = createCanvas()
let gameRenderer = createRenderer(canvas, game, eventBus, clientID)
const transport = new Transport(isLocal, eventBus, gameID, clientID, playerID, config, playerName)
const transport = new Transport(gameConfig.isLocal, eventBus, gameConfig.gameID, clientID, playerID, config, gameConfig.playerName)
return new ClientGame(
clientID,
ip,
gameID,
gameConfig.ip,
eventBus,
game,
gameRenderer,
new InputHandler(canvas, eventBus),
new Executor(game, gameID),
new Executor(game, gameConfig.gameID),
transport,
)
}
@@ -52,7 +65,6 @@ export class ClientGame {
constructor(
private id: ClientID,
private clientIP: string | null,
private gameID: GameID,
private eventBus: EventBus,
private gs: Game,
private renderer: GameRenderer,
+10 -22
View File
@@ -1,10 +1,6 @@
import {Config, getConfig} from "../core/configuration/Config";
import {GameID, Lobby, ServerMessage, ServerMessageSchema} from "../core/Schemas";
import {loadTerrainMap, TerrainMap} from "../core/game/TerrainMapLoader";
import {ClientGame, createClientGame} from "./ClientGame";
import backgroundImage from '../../resources/images/TerrainMapFrontPage.png';
import favicon from '../../resources/images/Favicon.png';
import {v4 as uuidv4} from 'uuid';
import './PublicLobby';
import './UsernameInput';
@@ -13,19 +9,17 @@ import './UsernameInput';
import './styles.css';
import {UsernameInput} from "./UsernameInput";
import {SinglePlayerModal} from "./SinglePlayerModal";
import {GameMap} from "../core/game/Game";
const usernameKey: string = 'username';
class Client {
private terrainMap: Promise<TerrainMap>
private game: ClientGame
private ip: Promise<string | null> = null
private config: Config
private usernameInput: UsernameInput | null = null;
@@ -38,9 +32,7 @@ class Client {
console.warn('Username input element not found');
}
this.config = getConfig()
setFavicon()
this.terrainMap = loadTerrainMap()
this.ip = getClientIP()
document.addEventListener('join-lobby', this.handleJoinLobby.bind(this));
document.addEventListener('leave-lobby', this.handleLeaveLobby.bind(this));
@@ -61,23 +53,19 @@ class Client {
private async handleJoinLobby(event: CustomEvent) {
const lobby = event.detail.lobby
console.log(`joining lobby ${lobby.id}`)
const [terrainMap, clientIP] = await Promise.all([
this.terrainMap,
this.ip
]);
const clientIP = await this.ip
console.log(`got ip ${clientIP}`)
if (this.game != null) {
this.game.stop()
}
this.game = createClientGame(
event.detail.singlePlayer,
(): string => {return this.usernameInput.getCurrentUsername()},
uuidv4(),
uuidv4(),
clientIP,
lobby.id,
this.config,
terrainMap
this.game = await createClientGame(
{
isLocal: event.detail.singlePlayer,
playerName: (): string => this.usernameInput.getCurrentUsername(),
gameID: lobby.id,
ip: clientIP,
map: event.detail.map,
}
);
this.game.join();
const g = this.game;
+6 -1
View File
@@ -1,6 +1,7 @@
import {LitElement, html, css} from 'lit';
import {customElement, state} from 'lit/decorators.js';
import {Lobby} from "../core/Schemas";
import {GameMap} from '../core/game/Game';
@customElement('public-lobby')
export class PublicLobby extends LitElement {
@@ -108,7 +109,11 @@ export class PublicLobby extends LitElement {
if (this.currLobby == null) {
this.currLobby = lobby
this.dispatchEvent(new CustomEvent('join-lobby', {
detail: {lobby: lobby, singlePlayer: false},
detail: {
lobby: lobby,
singlePlayer: false,
map: GameMap.World,
},
bubbles: true,
composed: true
}));
+75 -50
View File
@@ -1,64 +1,72 @@
import {LitElement, html, css} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {GameMap} from '../core/game/Game';
@customElement('single-player-modal')
export class SinglePlayerModal extends LitElement {
@state() private isModalOpen = false;
@state() private selectedMap: GameMap = GameMap.World;
static styles = css`
.modal-overlay {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-overlay {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: white;
margin: 15% auto;
padding: 20px;
border-radius: 8px;
width: 80%;
max-width: 500px;
text-align: center; /* Center the content inside the modal */
}
.modal-content {
background-color: white;
margin: 15% auto;
padding: 20px;
border-radius: 8px;
width: 80%;
max-width: 500px;
text-align: center;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #007bff; /* Changed to blue */
color: white;
border: none;
border-radius: 4px;
transition: background-color 0.3s;
display: inline-block; /* Ensures the button takes only necessary width */
margin-top: 20px; /* Adds some space above the button */
}
button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
transition: background-color 0.3s;
display: inline-block;
margin-top: 20px;
}
button:hover {
background-color: #0056b3; /* Darker blue for hover state */
}
button:hover {
background-color: #0056b3;
}
select {
padding: 8px;
font-size: 16px;
margin-top: 10px;
width: 200px;
}
`;
render() {
@@ -67,6 +75,18 @@ button:hover {
<div class="modal-content">
<span class="close" @click=${this.close}>&times;</span>
<h2>Start Single Player Game</h2>
<div>
<label for="map-select">Map: </label>
<select id="map-select" @change=${this.handleMapChange}>
${Object.entries(GameMap)
.filter(([key]) => isNaN(Number(key)))
.map(([key, value]) => html`
<option value=${value} ?selected=${this.selectedMap === value}>
${key}
</option>
`)}
</select>
</div>
<button @click=${this.startGame}>Start Game</button>
</div>
</div>
@@ -81,14 +101,19 @@ button:hover {
this.isModalOpen = false;
}
private handleMapChange(e: Event) {
this.selectedMap = Number((e.target as HTMLSelectElement).value) as GameMap;
}
private startGame() {
console.log('Starting single player game...');
console.log(`Starting single player game with map: ${GameMap[this.selectedMap]}`);
this.dispatchEvent(new CustomEvent('join-lobby', {
detail: {
singlePlayer: true,
lobby: {
id: "LOCAL",
}
},
map: this.selectedMap,
},
bubbles: true,
composed: true
+5
View File
@@ -11,6 +11,11 @@ export type Tick = number
export const AllPlayers = "AllPlayers" as const;
export enum GameMap {
World,
Europe
}
export class Nation {
constructor(
public readonly name: string,
+16 -6
View File
@@ -1,6 +1,13 @@
import {Cell, TerrainType} from './Game';
import binAsString from "!!binary-loader!../../../resources/maps/Europe.bin";
import worldMapInfo from "../../../resources/maps/Europe.json"
import {Cell, GameMap, TerrainType} from './Game';
import europeBin from "!!binary-loader!../../../resources/maps/Europe.bin";
import europeInfo from "../../../resources/maps/Europe.json"
import worldBin from "!!binary-loader!../../../resources/maps/WorldMap.bin";
import worldInfo from "../../../resources/maps/WorldMap.json"
const maps = new Map()
.set(GameMap.World, {bin: worldBin, info: worldInfo})
.set(GameMap.Europe, {bin: europeBin, info: europeInfo});
export interface NationMap {
name: string;
@@ -44,10 +51,13 @@ export class Terrain {
constructor(public type: TerrainType) { }
}
export async function loadTerrainMap(): Promise<TerrainMap> {
export async function loadTerrainMap(map: GameMap): Promise<TerrainMap> {
const mapData = maps.get(map)
// Simulate an asynchronous file load
const fileData = await new Promise<string>((resolve) => {
setTimeout(() => resolve(binAsString), 100);
setTimeout(() => resolve(mapData.bin), 100);
});
console.log(`Loaded data length: ${fileData.length} bytes`);
@@ -103,7 +113,7 @@ export async function loadTerrainMap(): Promise<TerrainMap> {
}
}
return new TerrainMap(terrain, numLand, worldMapInfo);
return new TerrainMap(terrain, numLand, mapData.info);
}
function logBinaryAsAscii(data: string, length: number = 8) {