mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:30:43 +00:00
update win condtion: 80% non fallout tiles
This commit is contained in:
@@ -3,7 +3,6 @@ import { NameLayer } from "./layers/NameLayer";
|
||||
import { TerrainLayer } from "./layers/TerrainLayer";
|
||||
import { TerritoryLayer } from "./layers/TerritoryLayer";
|
||||
import { ClientID } from "../../core/Schemas";
|
||||
import { UILayer } from "./layers/UILayer";
|
||||
import { EventBus } from "../../core/EventBus";
|
||||
import { TransformHandler } from "./TransformHandler";
|
||||
import { Layer } from "./layers/Layer";
|
||||
@@ -20,6 +19,8 @@ import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
|
||||
import { consolex } from "../../core/Consolex";
|
||||
import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler";
|
||||
import { GameView } from "../../core/game/GameView";
|
||||
import { WinModal } from "./layers/WinModal";
|
||||
import { SpawnTimer } from "./layers/SpawnTimer";
|
||||
|
||||
|
||||
export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventBus: EventBus, clientID: ClientID): GameRenderer {
|
||||
@@ -75,18 +76,27 @@ export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventB
|
||||
playerInfo.game = game
|
||||
|
||||
|
||||
const winModel = document.querySelector('win-modal') as WinModal
|
||||
if (!(playerInfo instanceof WinModal)) {
|
||||
console.error('win modal not found')
|
||||
}
|
||||
winModel.clientID = clientID
|
||||
winModel.game = game
|
||||
|
||||
|
||||
const layers: Layer[] = [
|
||||
new TerrainLayer(game),
|
||||
new TerritoryLayer(game, eventBus),
|
||||
new StructureLayer(game, eventBus),
|
||||
new UnitLayer(game, eventBus, clientID),
|
||||
new NameLayer(game, game.config().theme(), transformHandler, clientID),
|
||||
new UILayer(eventBus, game, clientID, transformHandler),
|
||||
eventsDisplay,
|
||||
new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable, buildMenu, uiState),
|
||||
new SpawnTimer(game, transformHandler),
|
||||
leaderboard,
|
||||
controlPanel,
|
||||
playerInfo
|
||||
playerInfo,
|
||||
winModel
|
||||
]
|
||||
|
||||
return new GameRenderer(game, eventBus, canvas, transformHandler, uiState, layers)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { GameView } from '../../../core/game/GameView';
|
||||
import { TransformHandler } from '../TransformHandler';
|
||||
import { Layer } from './Layer';
|
||||
|
||||
export class SpawnTimer implements Layer {
|
||||
|
||||
constructor(private game: GameView, private transformHandler: TransformHandler) { }
|
||||
|
||||
init() {
|
||||
}
|
||||
tick() {
|
||||
}
|
||||
shouldTransform(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
renderLayer(context: CanvasRenderingContext2D) {
|
||||
if (!this.game.inSpawnPhase()) {
|
||||
return
|
||||
}
|
||||
|
||||
const barHeight = 15;
|
||||
const barBackgroundWidth = this.transformHandler.width();
|
||||
|
||||
const ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns()
|
||||
|
||||
// Draw bar background
|
||||
context.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
||||
context.fillRect(0, 0, barBackgroundWidth, barHeight);
|
||||
|
||||
context.fillStyle = 'rgba(0, 128, 255, 0.7)';
|
||||
context.fillRect(0, 0, barBackgroundWidth * ratio, barHeight);
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { WinEvent } from "../../../core/execution/WinCheckExecution";
|
||||
import { Player } from "../../../core/game/Game";
|
||||
import { ClientID } from "../../../core/Schemas";
|
||||
import { Layer } from "./Layer";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { consolex } from "../../../core/Consolex";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
|
||||
interface MenuOption {
|
||||
label: string;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
export class UILayer implements Layer {
|
||||
private exitButton: HTMLButtonElement;
|
||||
private winModal: HTMLElement | null = null;
|
||||
|
||||
private customMenu = document.getElementById('customMenu');
|
||||
|
||||
|
||||
constructor(
|
||||
private eventBus: EventBus,
|
||||
private game: GameView,
|
||||
private clientID: ClientID,
|
||||
private transformHandler: TransformHandler
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
renderLayer(context: CanvasRenderingContext2D) {
|
||||
if (!this.game.inSpawnPhase()) {
|
||||
return
|
||||
}
|
||||
|
||||
const barHeight = 15;
|
||||
const barBackgroundWidth = this.transformHandler.width();
|
||||
|
||||
const ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns()
|
||||
|
||||
// Draw bar background
|
||||
context.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
||||
context.fillRect(0, 0, barBackgroundWidth, barHeight);
|
||||
|
||||
context.fillStyle = 'rgba(0, 128, 255, 0.7)';
|
||||
context.fillRect(0, 0, barBackgroundWidth * ratio, barHeight);
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
tick() {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.createWinModal()
|
||||
this.initRightClickMenu()
|
||||
this.eventBus.on(WinEvent, (e) => this.onWinEvent(e))
|
||||
}
|
||||
|
||||
initRightClickMenu() {
|
||||
if (!this.customMenu) {
|
||||
consolex.error('Custom menu not found');
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
this.customMenu!.style.display = 'none';
|
||||
});
|
||||
|
||||
const menuItems = this.customMenu.querySelectorAll('li');
|
||||
menuItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
alert(`You clicked: ${item.textContent}`);
|
||||
this.customMenu!.style.display = 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createWinModal() {
|
||||
consolex.log("Creating win modal");
|
||||
this.winModal = document.createElement('div');
|
||||
this.winModal.style.cssText = `
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border: 2px solid black;
|
||||
border-radius: 10px;
|
||||
z-index: 2000;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
`;
|
||||
|
||||
const content = document.createElement('div');
|
||||
|
||||
const title = document.createElement('h2');
|
||||
title.textContent = 'Game Over';
|
||||
title.id = 'winTitle';
|
||||
title.style.marginTop = '0';
|
||||
|
||||
const message = document.createElement('p');
|
||||
message.id = 'winMessage';
|
||||
|
||||
const buttonContainer = document.createElement('div');
|
||||
buttonContainer.style.display = 'flex';
|
||||
buttonContainer.style.justifyContent = 'space-between';
|
||||
buttonContainer.style.marginTop = '20px';
|
||||
|
||||
const exitButton = document.createElement('button');
|
||||
exitButton.textContent = 'Exit Game';
|
||||
exitButton.onclick = () => this.exitGame();
|
||||
this.styleButton(exitButton);
|
||||
|
||||
const continueButton = document.createElement('button');
|
||||
continueButton.textContent = 'Keep Playing';
|
||||
continueButton.onclick = () => this.closeWinModal();
|
||||
this.styleButton(continueButton);
|
||||
|
||||
buttonContainer.appendChild(exitButton);
|
||||
buttonContainer.appendChild(continueButton);
|
||||
|
||||
content.appendChild(title);
|
||||
content.appendChild(message);
|
||||
content.appendChild(buttonContainer);
|
||||
|
||||
this.winModal.appendChild(content);
|
||||
document.body.appendChild(this.winModal);
|
||||
|
||||
consolex.log("Win modal appended to body");
|
||||
}
|
||||
|
||||
styleButton(button: HTMLButtonElement) {
|
||||
button.style.cssText = `
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background-color: #4A90E2;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s;
|
||||
`;
|
||||
button.onmouseover = () => button.style.backgroundColor = '#3A7BCE';
|
||||
button.onmouseout = () => button.style.backgroundColor = '#4A90E2';
|
||||
}
|
||||
|
||||
|
||||
onWinEvent(event: WinEvent) {
|
||||
consolex.log(`${event.winner.name()} won the game!!}`)
|
||||
this.showWinModal(event.winner)
|
||||
}
|
||||
|
||||
showWinModal(winner: Player) {
|
||||
if (this.winModal) {
|
||||
const message = this.winModal.querySelector('#winMessage');
|
||||
if (message) {
|
||||
message.textContent = `${winner.name()} won the game!`;
|
||||
}
|
||||
const title = this.winModal.querySelector('#winTitle')
|
||||
if (winner.clientID() == this.clientID) {
|
||||
title.textContent = 'You Won!!!'
|
||||
} else {
|
||||
title.textContent = 'You Lost!!!'
|
||||
}
|
||||
this.winModal.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
closeWinModal() {
|
||||
if (this.winModal) {
|
||||
this.winModal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
exitGame() {
|
||||
this.closeWinModal();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { Player } from '../../../core/game/Game';
|
||||
import { ClientID } from '../../../core/Schemas';
|
||||
import { GameView, PlayerView } from '../../../core/game/GameView';
|
||||
import { Layer } from './Layer';
|
||||
import { GameUpdateType } from '../../../core/game/GameUpdates';
|
||||
|
||||
@customElement('win-modal')
|
||||
export class WinModal extends LitElement implements Layer {
|
||||
public clientID: ClientID
|
||||
public game: GameView
|
||||
private winner: PlayerView
|
||||
|
||||
@state()
|
||||
isVisible = false
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(30, 30, 30, 0.7);
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(5px);
|
||||
color: white;
|
||||
width: 300px;
|
||||
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.modal.visible {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -48%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 20px 0;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 150, 255, 0.6);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: rgba(0, 150, 255, 0.8);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.modal {
|
||||
width: 90%;
|
||||
max-width: 300px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
if (!this.winner) return null;
|
||||
const isWinner = this.winner.clientID() === this.clientID;
|
||||
const title = isWinner ? 'You Won!!!' : 'You Lost!!!';
|
||||
const message = `${this.winner.name()} won the game!`;
|
||||
|
||||
return html`
|
||||
<div class="modal ${this.isVisible ? 'visible' : ''}">
|
||||
<h2>${title}</h2>
|
||||
<p>${message}</p>
|
||||
<div class="button-container">
|
||||
<button @click=${this._handleExit}>Exit Game</button>
|
||||
<button @click=${this._handleContinue}>Keep Playing</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
show(winner: PlayerView) {
|
||||
this.winner = winner;
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.isVisible = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _handleExit() {
|
||||
this.hide();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
private _handleContinue() {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
init() { }
|
||||
|
||||
tick() {
|
||||
this.game.updatesSinceLastTick()[GameUpdateType.WinUpdate]
|
||||
.forEach(wu => this.show(this.game.playerBySmallID(wu.winnerID) as PlayerView))
|
||||
}
|
||||
|
||||
renderLayer(context: CanvasRenderingContext2D) {
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
+24
-15
@@ -117,19 +117,28 @@
|
||||
<build-menu></build-menu>
|
||||
<player-info-overlay></player-info-overlay>
|
||||
|
||||
<script>
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
document.body.style.visibility = "visible";
|
||||
document.body.style.opacity = 1;
|
||||
});
|
||||
</script>
|
||||
<single-player-modal></single-player-modal>
|
||||
<host-lobby-modal></host-lobby-modal>
|
||||
<join-private-lobby-modal></join-private-lobby-modal>
|
||||
<emoji-table></emoji-table>
|
||||
<leader-board></leader-board>
|
||||
<control-panel></control-panel>
|
||||
<events-display></events-display>
|
||||
<build-menu></build-menu>
|
||||
<player-info-overlay></player-info-overlay>
|
||||
<win-modal></win-modal>
|
||||
|
||||
<!-- Cloudflare Web Analytics -->
|
||||
<script
|
||||
defer
|
||||
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||
data-cf-beacon='{"token": "03d93e6fefb349c28ee69b408fa25a13"}'
|
||||
></script>
|
||||
<!-- End Cloudflare Web Analytics -->
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
document.body.style.visibility = 'visible';
|
||||
document.body.style.opacity = 1;
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Cloudflare Web Analytics -->
|
||||
<script defer src='https://static.cloudflareinsights.com/beacon.min.js'
|
||||
data-cf-beacon='{"token": "03d93e6fefb349c28ee69b408fa25a13"}'></script>
|
||||
<!-- End Cloudflare Web Analytics -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -145,7 +145,6 @@ export class GameRunner {
|
||||
),
|
||||
alliances: player.alliances().map(a => a.other(player).smallID())
|
||||
};
|
||||
console.log(`got relations: ${JSON.stringify(rel)}`)
|
||||
return rel
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ export class DefaultConfig implements Config {
|
||||
return 600 * 10
|
||||
}
|
||||
percentageTilesOwnedToWin(): number {
|
||||
return 95
|
||||
return 80
|
||||
}
|
||||
boatMaxNumber(): number {
|
||||
return 3
|
||||
|
||||
@@ -30,6 +30,10 @@ export class DevConfig extends DefaultConfig {
|
||||
return info
|
||||
}
|
||||
|
||||
// percentageTilesOwnedToWin(): number {
|
||||
// return 1
|
||||
// }
|
||||
|
||||
// populationIncreaseRate(player: Player): number {
|
||||
// return this.maxPopulation(player)
|
||||
// }
|
||||
@@ -38,15 +42,15 @@ export class DevConfig extends DefaultConfig {
|
||||
|
||||
// tradeShipSpawnRate(): number { // return 10
|
||||
// }
|
||||
// boatMaxDistance(): number {
|
||||
// return 5000
|
||||
// }
|
||||
boatMaxDistance(): number {
|
||||
return 5000
|
||||
}
|
||||
|
||||
// numBots(): number {
|
||||
// return 0
|
||||
// }
|
||||
// spawnNPCs(): boolean {
|
||||
// return false
|
||||
// }
|
||||
numBots(): number {
|
||||
return 0
|
||||
}
|
||||
spawnNPCs(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,8 +27,13 @@ export class WinCheckExecution implements Execution {
|
||||
return
|
||||
}
|
||||
const max = sorted[0]
|
||||
if (max.numTilesOwned() / this.mg.map().numLandTiles() * 100 > this.mg.config().percentageTilesOwnedToWin()) {
|
||||
const numTilesWithoutFallout = this.mg.numLandTiles() - this.mg.numTilesWithFallout()
|
||||
if (this.mg.ticks() % 10 == 0) {
|
||||
console.log(`player: ${max.name()} owns ${max.numTilesOwned()} tiles, ${numTilesWithoutFallout}`)
|
||||
}
|
||||
if (max.numTilesOwned() / numTilesWithoutFallout * 100 > this.mg.config().percentageTilesOwnedToWin()) {
|
||||
this.mg.setWinner(max)
|
||||
console.log(`${max.name()} has won the game`)
|
||||
this.active = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +325,8 @@ export interface Game extends GameMap {
|
||||
|
||||
// Nations
|
||||
nations(): Nation[]
|
||||
|
||||
numTilesWithFallout(): number
|
||||
}
|
||||
|
||||
export interface PlayerActions {
|
||||
|
||||
@@ -43,6 +43,8 @@ export class GameImpl implements Game {
|
||||
|
||||
private updates: GameUpdates = createGameUpdatesMap()
|
||||
|
||||
private _numTilesWithFallout = 0
|
||||
|
||||
constructor(
|
||||
private _map: GameMap,
|
||||
private miniGameMap: GameMap,
|
||||
@@ -59,6 +61,11 @@ export class GameImpl implements Game {
|
||||
n.strength
|
||||
))
|
||||
}
|
||||
|
||||
numTilesWithFallout(): number {
|
||||
return this._numTilesWithFallout
|
||||
}
|
||||
|
||||
owner(ref: TileRef): Player | TerraNullius {
|
||||
return this.playerBySmallID(this.ownerID(ref))
|
||||
}
|
||||
@@ -79,7 +86,6 @@ export class GameImpl implements Game {
|
||||
(this.updates[update.type] as any[]).push(update);
|
||||
}
|
||||
|
||||
|
||||
nextUnitID(): number {
|
||||
const old = this._nextUnitID
|
||||
this._nextUnitID++
|
||||
@@ -90,6 +96,10 @@ export class GameImpl implements Game {
|
||||
if (value && this.hasOwner(tile)) {
|
||||
throw Error(`cannot set fallout, tile ${tile} has owner`)
|
||||
}
|
||||
if (this._map.hasFallout(tile)) {
|
||||
return
|
||||
}
|
||||
this._numTilesWithFallout++
|
||||
this._map.setFallout(tile, value)
|
||||
this.addUpdate({
|
||||
type: GameUpdateType.Tile,
|
||||
@@ -331,7 +341,10 @@ export class GameImpl implements Game {
|
||||
owner._tiles.add(tile)
|
||||
owner._lastTileChange = this._ticks
|
||||
this.updateBorders(tile)
|
||||
this._map.setFallout(tile, false)
|
||||
if (this._map.hasFallout(tile)) {
|
||||
this._numTilesWithFallout--
|
||||
this._map.setFallout(tile, false)
|
||||
}
|
||||
this.addUpdate({
|
||||
type: GameUpdateType.Tile,
|
||||
update: this.toTileUpdate(tile)
|
||||
|
||||
Reference in New Issue
Block a user