update win condtion: 80% non fallout tiles

This commit is contained in:
Evan
2025-01-24 08:41:19 -08:00
parent d009ef925c
commit 7a2f4210ed
11 changed files with 275 additions and 216 deletions
+13 -3
View File
@@ -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)
+34
View File
@@ -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);
}
}
-184
View File
@@ -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();
}
}
+167
View File
@@ -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
View File
@@ -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>
-1
View File
@@ -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
}
+1 -1
View File
@@ -171,7 +171,7 @@ export class DefaultConfig implements Config {
return 600 * 10
}
percentageTilesOwnedToWin(): number {
return 95
return 80
}
boatMaxNumber(): number {
return 3
+13 -9
View File
@@ -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
}
}
+6 -1
View File
@@ -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
}
}
+2
View File
@@ -325,6 +325,8 @@ export interface Game extends GameMap {
// Nations
nations(): Nation[]
numTilesWithFallout(): number
}
export interface PlayerActions {
+15 -2
View File
@@ -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)