mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-22 19:26:41 +00:00
can select map private game
This commit is contained in:
@@ -164,7 +164,7 @@
|
|||||||
* Make fake humans spawn by their country DONE 10/9/2024
|
* Make fake humans spawn by their country DONE 10/9/2024
|
||||||
* UI: leader board DONE 10/12/2024
|
* UI: leader board DONE 10/12/2024
|
||||||
* single player mode DONE 10/12/2024
|
* single player mode DONE 10/12/2024
|
||||||
* single player select map
|
* single player select map DONE 10/12/2024
|
||||||
* implement private game
|
* implement private game
|
||||||
* private game can select map
|
* private game can select map
|
||||||
* optimize sendBoat function
|
* optimize sendBoat function
|
||||||
|
|||||||
+22
-10
@@ -1,39 +1,52 @@
|
|||||||
import {Executor} from "../core/execution/ExecutionManager";
|
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 {createGame} from "../core/game/GameImpl";
|
||||||
import {EventBus} from "../core/EventBus";
|
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 {createRenderer, GameRenderer} from "./graphics/GameRenderer";
|
||||||
import {InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent} from "./InputHandler"
|
import {InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent} from "./InputHandler"
|
||||||
import {ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, ClientMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ServerSyncMessage, Turn} from "../core/Schemas";
|
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 {and, bfs, dist, manhattanDist} from "../core/Util";
|
||||||
import {TerrainLayer} from "./graphics/layers/TerrainLayer";
|
|
||||||
import {WinCheckExecution} from "../core/execution/WinCheckExecution";
|
import {WinCheckExecution} from "../core/execution/WinCheckExecution";
|
||||||
import {SendAttackIntentEvent, SendSpawnIntentEvent, Transport} from "./Transport";
|
import {SendAttackIntentEvent, SendSpawnIntentEvent, Transport} from "./Transport";
|
||||||
import {createCanvas} from "./graphics/Utils";
|
import {createCanvas} from "./graphics/Utils";
|
||||||
import {DisplayMessageEvent, MessageType} from "./graphics/layers/EventsDisplay";
|
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()
|
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)
|
let game = createGame(terrainMap, eventBus, config)
|
||||||
const canvas = createCanvas()
|
const canvas = createCanvas()
|
||||||
let gameRenderer = createRenderer(canvas, game, eventBus, clientID)
|
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(
|
return new ClientGame(
|
||||||
clientID,
|
clientID,
|
||||||
ip,
|
gameConfig.ip,
|
||||||
gameID,
|
|
||||||
eventBus,
|
eventBus,
|
||||||
game,
|
game,
|
||||||
gameRenderer,
|
gameRenderer,
|
||||||
new InputHandler(canvas, eventBus),
|
new InputHandler(canvas, eventBus),
|
||||||
new Executor(game, gameID),
|
new Executor(game, gameConfig.gameID),
|
||||||
transport,
|
transport,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -52,7 +65,6 @@ export class ClientGame {
|
|||||||
constructor(
|
constructor(
|
||||||
private id: ClientID,
|
private id: ClientID,
|
||||||
private clientIP: string | null,
|
private clientIP: string | null,
|
||||||
private gameID: GameID,
|
|
||||||
private eventBus: EventBus,
|
private eventBus: EventBus,
|
||||||
private gs: Game,
|
private gs: Game,
|
||||||
private renderer: GameRenderer,
|
private renderer: GameRenderer,
|
||||||
|
|||||||
+10
-22
@@ -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 {ClientGame, createClientGame} from "./ClientGame";
|
||||||
import backgroundImage from '../../resources/images/TerrainMapFrontPage.png';
|
import backgroundImage from '../../resources/images/TerrainMapFrontPage.png';
|
||||||
import favicon from '../../resources/images/Favicon.png';
|
import favicon from '../../resources/images/Favicon.png';
|
||||||
import {v4 as uuidv4} from 'uuid';
|
|
||||||
|
|
||||||
import './PublicLobby';
|
import './PublicLobby';
|
||||||
import './UsernameInput';
|
import './UsernameInput';
|
||||||
@@ -13,19 +9,17 @@ import './UsernameInput';
|
|||||||
import './styles.css';
|
import './styles.css';
|
||||||
import {UsernameInput} from "./UsernameInput";
|
import {UsernameInput} from "./UsernameInput";
|
||||||
import {SinglePlayerModal} from "./SinglePlayerModal";
|
import {SinglePlayerModal} from "./SinglePlayerModal";
|
||||||
|
import {GameMap} from "../core/game/Game";
|
||||||
|
|
||||||
|
|
||||||
const usernameKey: string = 'username';
|
const usernameKey: string = 'username';
|
||||||
|
|
||||||
|
|
||||||
class Client {
|
class Client {
|
||||||
private terrainMap: Promise<TerrainMap>
|
|
||||||
private game: ClientGame
|
private game: ClientGame
|
||||||
|
|
||||||
private ip: Promise<string | null> = null
|
private ip: Promise<string | null> = null
|
||||||
|
|
||||||
private config: Config
|
|
||||||
|
|
||||||
private usernameInput: UsernameInput | null = null;
|
private usernameInput: UsernameInput | null = null;
|
||||||
|
|
||||||
|
|
||||||
@@ -38,9 +32,7 @@ class Client {
|
|||||||
console.warn('Username input element not found');
|
console.warn('Username input element not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.config = getConfig()
|
|
||||||
setFavicon()
|
setFavicon()
|
||||||
this.terrainMap = loadTerrainMap()
|
|
||||||
this.ip = getClientIP()
|
this.ip = getClientIP()
|
||||||
document.addEventListener('join-lobby', this.handleJoinLobby.bind(this));
|
document.addEventListener('join-lobby', this.handleJoinLobby.bind(this));
|
||||||
document.addEventListener('leave-lobby', this.handleLeaveLobby.bind(this));
|
document.addEventListener('leave-lobby', this.handleLeaveLobby.bind(this));
|
||||||
@@ -61,23 +53,19 @@ class Client {
|
|||||||
private async handleJoinLobby(event: CustomEvent) {
|
private async handleJoinLobby(event: CustomEvent) {
|
||||||
const lobby = event.detail.lobby
|
const lobby = event.detail.lobby
|
||||||
console.log(`joining lobby ${lobby.id}`)
|
console.log(`joining lobby ${lobby.id}`)
|
||||||
const [terrainMap, clientIP] = await Promise.all([
|
const clientIP = await this.ip
|
||||||
this.terrainMap,
|
|
||||||
this.ip
|
|
||||||
]);
|
|
||||||
console.log(`got ip ${clientIP}`)
|
console.log(`got ip ${clientIP}`)
|
||||||
if (this.game != null) {
|
if (this.game != null) {
|
||||||
this.game.stop()
|
this.game.stop()
|
||||||
}
|
}
|
||||||
this.game = createClientGame(
|
this.game = await createClientGame(
|
||||||
event.detail.singlePlayer,
|
{
|
||||||
(): string => {return this.usernameInput.getCurrentUsername()},
|
isLocal: event.detail.singlePlayer,
|
||||||
uuidv4(),
|
playerName: (): string => this.usernameInput.getCurrentUsername(),
|
||||||
uuidv4(),
|
gameID: lobby.id,
|
||||||
clientIP,
|
ip: clientIP,
|
||||||
lobby.id,
|
map: event.detail.map,
|
||||||
this.config,
|
}
|
||||||
terrainMap
|
|
||||||
);
|
);
|
||||||
this.game.join();
|
this.game.join();
|
||||||
const g = this.game;
|
const g = this.game;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {LitElement, html, css} from 'lit';
|
import {LitElement, html, css} from 'lit';
|
||||||
import {customElement, state} from 'lit/decorators.js';
|
import {customElement, state} from 'lit/decorators.js';
|
||||||
import {Lobby} from "../core/Schemas";
|
import {Lobby} from "../core/Schemas";
|
||||||
|
import {GameMap} from '../core/game/Game';
|
||||||
|
|
||||||
@customElement('public-lobby')
|
@customElement('public-lobby')
|
||||||
export class PublicLobby extends LitElement {
|
export class PublicLobby extends LitElement {
|
||||||
@@ -108,7 +109,11 @@ export class PublicLobby extends LitElement {
|
|||||||
if (this.currLobby == null) {
|
if (this.currLobby == null) {
|
||||||
this.currLobby = lobby
|
this.currLobby = lobby
|
||||||
this.dispatchEvent(new CustomEvent('join-lobby', {
|
this.dispatchEvent(new CustomEvent('join-lobby', {
|
||||||
detail: {lobby: lobby, singlePlayer: false},
|
detail: {
|
||||||
|
lobby: lobby,
|
||||||
|
singlePlayer: false,
|
||||||
|
map: GameMap.World,
|
||||||
|
},
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
composed: true
|
composed: true
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,64 +1,72 @@
|
|||||||
import {LitElement, html, css} from 'lit';
|
import {LitElement, html, css} from 'lit';
|
||||||
import {customElement, property, state} from 'lit/decorators.js';
|
import {customElement, property, state} from 'lit/decorators.js';
|
||||||
|
import {GameMap} from '../core/game/Game';
|
||||||
|
|
||||||
@customElement('single-player-modal')
|
@customElement('single-player-modal')
|
||||||
export class SinglePlayerModal extends LitElement {
|
export class SinglePlayerModal extends LitElement {
|
||||||
@state() private isModalOpen = false;
|
@state() private isModalOpen = false;
|
||||||
|
@state() private selectedMap: GameMap = GameMap.World;
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin: 15% auto;
|
margin: 15% auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
text-align: center; /* Center the content inside the modal */
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close:hover,
|
.close:hover,
|
||||||
.close:focus {
|
.close:focus {
|
||||||
color: black;
|
color: black;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #007bff; /* Changed to blue */
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
display: inline-block; /* Ensures the button takes only necessary width */
|
display: inline-block;
|
||||||
margin-top: 20px; /* Adds some space above the button */
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #0056b3; /* Darker blue for hover state */
|
background-color: #0056b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -67,6 +75,18 @@ button:hover {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<span class="close" @click=${this.close}>×</span>
|
<span class="close" @click=${this.close}>×</span>
|
||||||
<h2>Start Single Player Game</h2>
|
<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>
|
<button @click=${this.startGame}>Start Game</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,14 +101,19 @@ button:hover {
|
|||||||
this.isModalOpen = false;
|
this.isModalOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleMapChange(e: Event) {
|
||||||
|
this.selectedMap = Number((e.target as HTMLSelectElement).value) as GameMap;
|
||||||
|
}
|
||||||
|
|
||||||
private startGame() {
|
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', {
|
this.dispatchEvent(new CustomEvent('join-lobby', {
|
||||||
detail: {
|
detail: {
|
||||||
singlePlayer: true,
|
singlePlayer: true,
|
||||||
lobby: {
|
lobby: {
|
||||||
id: "LOCAL",
|
id: "LOCAL",
|
||||||
}
|
},
|
||||||
|
map: this.selectedMap,
|
||||||
},
|
},
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
composed: true
|
composed: true
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ export type Tick = number
|
|||||||
|
|
||||||
export const AllPlayers = "AllPlayers" as const;
|
export const AllPlayers = "AllPlayers" as const;
|
||||||
|
|
||||||
|
export enum GameMap {
|
||||||
|
World,
|
||||||
|
Europe
|
||||||
|
}
|
||||||
|
|
||||||
export class Nation {
|
export class Nation {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import {Cell, TerrainType} from './Game';
|
import {Cell, GameMap, TerrainType} from './Game';
|
||||||
import binAsString from "!!binary-loader!../../../resources/maps/Europe.bin";
|
import europeBin from "!!binary-loader!../../../resources/maps/Europe.bin";
|
||||||
import worldMapInfo from "../../../resources/maps/Europe.json"
|
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 {
|
export interface NationMap {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -44,10 +51,13 @@ export class Terrain {
|
|||||||
constructor(public type: TerrainType) { }
|
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
|
// Simulate an asynchronous file load
|
||||||
const fileData = await new Promise<string>((resolve) => {
|
const fileData = await new Promise<string>((resolve) => {
|
||||||
setTimeout(() => resolve(binAsString), 100);
|
setTimeout(() => resolve(mapData.bin), 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Loaded data length: ${fileData.length} bytes`);
|
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) {
|
function logBinaryAsAscii(data: string, length: number = 8) {
|
||||||
|
|||||||
Reference in New Issue
Block a user