mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 14:10:45 +00:00
Merge branch 'main' of github.com:openfrontio/OpenFrontIO
This commit is contained in:
+148
-129
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+145
-128
File diff suppressed because one or more lines are too long
+55
-52
File diff suppressed because one or more lines are too long
+24
-24
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1122
-1154
File diff suppressed because one or more lines are too long
+544
-551
File diff suppressed because one or more lines are too long
+379
-393
File diff suppressed because one or more lines are too long
+156
-159
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10
-2
@@ -232,7 +232,15 @@ export class HelpModal extends LitElement {
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Space</td>
|
||||
<td>Alternate view</td>
|
||||
<td>Alternate view (terrain/countries)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Shift + left click</td>
|
||||
<td>Attack (when left click is set to open menu)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ctrl + left click</td>
|
||||
<td>Open build menu</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C</td>
|
||||
@@ -306,7 +314,7 @@ export class HelpModal extends LitElement {
|
||||
<li class="mb-4">Pause/Unpause the game - Only available in single player mode.</li>
|
||||
<li class="mb-4">Timer - Time passed since the start of the game.</li>
|
||||
<li class="mb-4">Exit button.</li>
|
||||
<li class="mb-4">Settings - Open the settings menu. Inside you can toggle the Alternate View, Dark Mode, and Emojis.</li>
|
||||
<li class="mb-4">Settings - Open the settings menu. Inside you can toggle the Alternate View, Dark Mode, Emojis and action on left click.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EventBus, GameEvent } from "../core/EventBus";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
|
||||
export class MouseUpEvent implements GameEvent {
|
||||
constructor(
|
||||
@@ -87,6 +88,8 @@ export class InputHandler {
|
||||
private readonly PAN_SPEED = 5;
|
||||
private readonly ZOOM_SPEED = 10;
|
||||
|
||||
private userSettings: UserSettings = new UserSettings();
|
||||
|
||||
constructor(
|
||||
private canvas: HTMLCanvasElement,
|
||||
private eventBus: EventBus,
|
||||
@@ -276,10 +279,12 @@ export class InputHandler {
|
||||
if (dist < 10) {
|
||||
if (event.pointerType == "touch") {
|
||||
event.preventDefault();
|
||||
console.log("firing context menu event");
|
||||
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY));
|
||||
} else {
|
||||
}
|
||||
|
||||
if (!this.userSettings.leftClickOpensMenu() || event.shiftKey) {
|
||||
this.eventBus.emit(new MouseUpEvent(event.x, event.y));
|
||||
} else {
|
||||
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const button = ({
|
||||
children,
|
||||
}) => html`
|
||||
<button
|
||||
class="flex items-center justify-center p-1
|
||||
class="flex items-center justify-center p-1
|
||||
bg-opacity-70 bg-gray-700 text-opacity-90 text-white
|
||||
border-none rounded cursor-pointer
|
||||
hover:bg-opacity-60 hover:bg-gray-600
|
||||
@@ -96,6 +96,10 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
this.eventBus.emit(new RefreshGraphicsEvent());
|
||||
}
|
||||
|
||||
private onToggleLeftClickOpensMenu() {
|
||||
this.userSettings.toggleLeftClickOpenMenu();
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log("init called from OptionsMenu");
|
||||
this.showPauseButton =
|
||||
@@ -137,8 +141,8 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
children: this.isPaused ? "▶️" : "⏸",
|
||||
})}
|
||||
<div
|
||||
class="w-14 h-8 lg:w-20 lg:h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
||||
class="w-14 h-8 lg:w-20 lg:h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
||||
rounded text-sm lg:text-xl"
|
||||
>
|
||||
${this.timer}
|
||||
@@ -177,6 +181,15 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
title: "Dark Mode",
|
||||
children: "🌙: " + (this.userSettings.darkMode() ? "On" : "Off"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleLeftClickOpensMenu,
|
||||
title: "Left click",
|
||||
children:
|
||||
"🖱️: " +
|
||||
(this.userSettings.leftClickOpensMenu()
|
||||
? "Opens menu"
|
||||
: "Attack"),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -4,7 +4,12 @@ import { EventBus } from "../../../core/EventBus";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { Layer } from "./Layer";
|
||||
import { MouseUpEvent } from "../../InputHandler";
|
||||
import { AllPlayers, Player, PlayerActions } from "../../../core/game/Game";
|
||||
import {
|
||||
AllPlayers,
|
||||
Player,
|
||||
PlayerActions,
|
||||
UnitType,
|
||||
} from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
import { renderNumber, renderTroops } from "../../Utils";
|
||||
import targetIcon from "../../../../resources/images/TargetIconWhite.svg";
|
||||
@@ -128,6 +133,24 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
getTotalNukesSent(): number {
|
||||
const stats = this.actions.interaction?.stats;
|
||||
if (!stats) {
|
||||
return 0;
|
||||
}
|
||||
let sum = 0;
|
||||
const nukes = stats.sentNukes[this.g.myPlayer().id()];
|
||||
if (!nukes) {
|
||||
return 0;
|
||||
}
|
||||
for (const nukeType in nukes) {
|
||||
if (nukeType != UnitType.MIRVWarhead) {
|
||||
sum += nukes[nukeType];
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
@@ -165,7 +188,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
<!-- Close button -->
|
||||
<button
|
||||
@click=${this.handleClose}
|
||||
class="absolute -top-2 -right-2 w-6 h-6 flex items-center justify-center
|
||||
class="absolute -top-2 -right-2 w-6 h-6 flex items-center justify-center
|
||||
bg-red-500 hover:bg-red-600 text-white rounded-full
|
||||
text-sm font-bold transition-colors"
|
||||
>
|
||||
@@ -177,7 +200,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
<div class="flex items-center gap-1 lg:gap-2">
|
||||
<div
|
||||
class="px-4 h-8 lg:h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
||||
rounded text-sm lg:text-xl w-full"
|
||||
>
|
||||
${other?.name()}
|
||||
@@ -212,6 +235,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Embargo -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-white text-opacity-80 text-sm px-2">
|
||||
Embargo against you
|
||||
@@ -221,12 +245,22 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-white text-opacity-80 text-sm px-2">
|
||||
Nukes sent by them to you
|
||||
</div>
|
||||
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
|
||||
${this.getTotalNukesSent()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex justify-center gap-2">
|
||||
${canTarget
|
||||
? html`<button
|
||||
@click=${(e) => this.handleTargetClick(e, other)}
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
text-white rounded-lg transition-colors"
|
||||
>
|
||||
@@ -237,8 +271,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
? html`<button
|
||||
@click=${(e) =>
|
||||
this.handleBreakAllianceClick(e, myPlayer, other)}
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
text-white rounded-lg transition-colors"
|
||||
>
|
||||
<img
|
||||
@@ -252,8 +286,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
? html`<button
|
||||
@click=${(e) =>
|
||||
this.handleAllianceClick(e, myPlayer, other)}
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
text-white rounded-lg transition-colors"
|
||||
>
|
||||
<img src=${allianceIcon} alt="Alliance" class="w-6 h-6" />
|
||||
@@ -262,8 +296,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
${canDonate
|
||||
? html`<button
|
||||
@click=${(e) => this.handleDonateClick(e, myPlayer, other)}
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
text-white rounded-lg transition-colors"
|
||||
>
|
||||
<img src=${donateIcon} alt="Donate" class="w-6 h-6" />
|
||||
@@ -272,8 +306,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
${canSendEmoji
|
||||
? html`<button
|
||||
@click=${(e) => this.handleEmojiClick(e, myPlayer, other)}
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
text-white rounded-lg transition-colors"
|
||||
>
|
||||
<img src=${emojiIcon} alt="Emoji" class="w-6 h-6" />
|
||||
|
||||
@@ -168,6 +168,7 @@ export class GameRunner {
|
||||
canBreakAlliance: player.isAlliedWith(other),
|
||||
canDonate: player.canDonate(other),
|
||||
canEmbargo: !player.hasEmbargoAgainst(other),
|
||||
stats: this.game.stats().getPlayerStats(other.id()),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,14 @@ export class MirvExecution implements Execution {
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, true);
|
||||
this.player = mg.player(this.senderID);
|
||||
this.targetPlayer = this.mg.owner(this.dst);
|
||||
|
||||
this.mg
|
||||
.stats()
|
||||
.increaseNukeCount(
|
||||
this.player.id(),
|
||||
this.targetPlayer.id(),
|
||||
UnitType.MIRV,
|
||||
);
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
UnitType,
|
||||
TerraNullius,
|
||||
MessageType,
|
||||
NukeType,
|
||||
} from "../game/Game";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { consolex } from "../Consolex";
|
||||
@@ -22,10 +23,7 @@ export class NukeExecution implements Execution {
|
||||
private random: PseudoRandom;
|
||||
|
||||
constructor(
|
||||
private type:
|
||||
| UnitType.AtomBomb
|
||||
| UnitType.HydrogenBomb
|
||||
| UnitType.MIRVWarhead,
|
||||
private type: NukeType,
|
||||
private senderID: PlayerID,
|
||||
private dst: TileRef,
|
||||
private src?: TileRef,
|
||||
@@ -74,6 +72,14 @@ export class NukeExecution implements Execution {
|
||||
target.id(),
|
||||
);
|
||||
}
|
||||
|
||||
this.mg
|
||||
.stats()
|
||||
.increaseNukeCount(
|
||||
this.senderID,
|
||||
target.id(),
|
||||
this.nuke.type() as NukeType,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.waitTicks > 0) {
|
||||
@@ -157,6 +163,7 @@ export class NukeExecution implements Execution {
|
||||
const prev = attacked.get(mp);
|
||||
attacked.set(mp, prev + 1);
|
||||
}
|
||||
|
||||
if (this.mg.isLand(tile)) {
|
||||
this.mg.setFallout(tile, true);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PlayerUpdate,
|
||||
UnitUpdate,
|
||||
} from "./GameUpdates";
|
||||
import { PlayerStats, Stats } from "./Stats";
|
||||
|
||||
export type PlayerID = string;
|
||||
export type Tick = number;
|
||||
@@ -79,6 +80,11 @@ export enum UnitType {
|
||||
MIRVWarhead = "MIRV Warhead",
|
||||
Construction = "Construction",
|
||||
}
|
||||
export type NukeType =
|
||||
| UnitType.AtomBomb
|
||||
| UnitType.HydrogenBomb
|
||||
| UnitType.MIRVWarhead
|
||||
| UnitType.MIRV;
|
||||
|
||||
export enum Relation {
|
||||
Hostile = 0,
|
||||
@@ -379,6 +385,8 @@ export interface Game extends GameMap {
|
||||
nations(): Nation[];
|
||||
|
||||
numTilesWithFallout(): number;
|
||||
// Optional as it's not initialized before the end of spawn phase
|
||||
stats(): Stats;
|
||||
}
|
||||
|
||||
export interface PlayerActions {
|
||||
@@ -408,6 +416,7 @@ export interface PlayerInteraction {
|
||||
canTarget: boolean;
|
||||
canDonate: boolean;
|
||||
canEmbargo: boolean;
|
||||
stats: PlayerStats;
|
||||
}
|
||||
|
||||
export interface EmojiMessage {
|
||||
|
||||
@@ -30,7 +30,8 @@ import { UnitImpl } from "./UnitImpl";
|
||||
import { consolex } from "../Consolex";
|
||||
import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap";
|
||||
import { DefenseGrid } from "./DefensePostGrid";
|
||||
import { simpleHash } from "../Util";
|
||||
import { StatsImpl } from "./StatsImpl";
|
||||
import { Stats } from "./Stats";
|
||||
|
||||
export function createGame(
|
||||
gameMap: GameMap,
|
||||
@@ -67,6 +68,9 @@ export class GameImpl implements Game {
|
||||
private updates: GameUpdates = createGameUpdatesMap();
|
||||
private defenseGrid: DefenseGrid;
|
||||
|
||||
// Not initialized until the game has finished spawning
|
||||
private _stats: StatsImpl = new StatsImpl();
|
||||
|
||||
constructor(
|
||||
private _map: GameMap,
|
||||
private miniGameMap: GameMap,
|
||||
@@ -639,6 +643,9 @@ export class GameImpl implements Game {
|
||||
numTilesWithFallout(): number {
|
||||
return this._map.numTilesWithFallout();
|
||||
}
|
||||
stats(): Stats {
|
||||
return this._stats;
|
||||
}
|
||||
}
|
||||
|
||||
// Or a more dynamic approach that will catch new enum values:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NukeType, PlayerID } from "./Game";
|
||||
|
||||
export interface PlayerStats {
|
||||
sentNukes: {
|
||||
// target
|
||||
[key: PlayerID]: {
|
||||
[key in NukeType]: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
increaseNukeCount(sender: PlayerID, target: PlayerID, type: NukeType): void;
|
||||
getPlayerStats(player: PlayerID): PlayerStats;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { NukeType, Player, PlayerID, UnitType } from "./Game";
|
||||
import { PlayerStats, Stats } from "./Stats";
|
||||
|
||||
interface StatsInternalData {
|
||||
// player
|
||||
[key: PlayerID]: PlayerStats;
|
||||
}
|
||||
|
||||
export class StatsImpl implements Stats {
|
||||
data: StatsInternalData = {};
|
||||
|
||||
_createUserData(sender: PlayerID, target: PlayerID): void {
|
||||
if (!this.data[sender]) {
|
||||
this.data[sender] = { sentNukes: {} };
|
||||
}
|
||||
if (!this.data[sender].sentNukes[target]) {
|
||||
this.data[sender].sentNukes[target] = {
|
||||
[UnitType.MIRV]: 0,
|
||||
[UnitType.MIRVWarhead]: 0,
|
||||
[UnitType.AtomBomb]: 0,
|
||||
[UnitType.HydrogenBomb]: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
increaseNukeCount(sender: PlayerID, target: PlayerID, type: NukeType): void {
|
||||
this._createUserData(sender, target);
|
||||
this.data[sender].sentNukes[target][type]++;
|
||||
}
|
||||
|
||||
getPlayerStats(player: PlayerID): PlayerStats {
|
||||
return this.data[player];
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,14 @@ export class UserSettings {
|
||||
return this.get("settings.darkMode", false);
|
||||
}
|
||||
|
||||
leftClickOpensMenu() {
|
||||
return this.get("settings.leftClickOpensMenu", false);
|
||||
}
|
||||
|
||||
toggleLeftClickOpenMenu() {
|
||||
this.set("settings.leftClickOpensMenu", !this.leftClickOpensMenu());
|
||||
}
|
||||
|
||||
toggleEmojis() {
|
||||
this.set("settings.emojis", !this.emojis());
|
||||
}
|
||||
|
||||
@@ -7,7 +7,18 @@ import { fileURLToPath } from "url";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const mapName = "Africa";
|
||||
const maps = [
|
||||
"Africa",
|
||||
"Asia",
|
||||
"WorldMap",
|
||||
"BlackSea",
|
||||
"Europe",
|
||||
"Mars",
|
||||
"Mena",
|
||||
"Oceania",
|
||||
"NorthAmerica",
|
||||
];
|
||||
const min_island_size = 30;
|
||||
|
||||
interface Coord {
|
||||
x: number;
|
||||
@@ -26,7 +37,7 @@ class Terrain {
|
||||
constructor(public type: TerrainType) {}
|
||||
}
|
||||
|
||||
export async function loadTerrainMap(): Promise<void> {
|
||||
async function loadTerrainMap(mapName: string): Promise<void> {
|
||||
const imagePath = path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
@@ -39,8 +50,8 @@ export async function loadTerrainMap(): Promise<void> {
|
||||
const readStream = createReadStream(imagePath);
|
||||
const img = await decodePNGFromStream(readStream);
|
||||
|
||||
console.log("Image loaded successfully");
|
||||
console.log("Image dimensions:", img.width, "x", img.height);
|
||||
console.log(`${mapName}: Image loaded successfully`);
|
||||
console.log(`${mapName}: `, "Image dimensions:", img.width, "x", img.height);
|
||||
|
||||
const terrain: Terrain[][] = Array(img.width)
|
||||
.fill(null)
|
||||
@@ -67,7 +78,8 @@ export async function loadTerrainMap(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
removeSmallLakes(terrain);
|
||||
removeSmallIslands(terrain);
|
||||
removeSmallLakes(mapName, terrain);
|
||||
const shorelineWaters = processShore(terrain);
|
||||
processDistToLand(shorelineWaters, terrain);
|
||||
processOcean(terrain);
|
||||
@@ -79,7 +91,7 @@ export async function loadTerrainMap(): Promise<void> {
|
||||
"maps",
|
||||
mapName + ".bin",
|
||||
);
|
||||
fs.writeFile(outputPath, packTerrain(terrain));
|
||||
fs.writeFile(outputPath, packTerrain(mapName, terrain));
|
||||
|
||||
const miniTerrain = await createMiniMap(terrain);
|
||||
const miniOutputPath = path.join(
|
||||
@@ -90,7 +102,11 @@ export async function loadTerrainMap(): Promise<void> {
|
||||
"maps",
|
||||
mapName + "Mini.bin",
|
||||
);
|
||||
fs.writeFile(miniOutputPath, packTerrain(miniTerrain));
|
||||
fs.writeFile(miniOutputPath, packTerrain(mapName, miniTerrain));
|
||||
}
|
||||
|
||||
export async function loadTerrainMaps() {
|
||||
await Promise.all(maps.map((map) => loadTerrainMap(map)));
|
||||
}
|
||||
|
||||
export async function createMiniMap(tm: Terrain[][]): Promise<Terrain[][]> {
|
||||
@@ -192,7 +208,7 @@ function neighbors(x: number, y: number, map: Terrain[][]): Terrain[] {
|
||||
return ns;
|
||||
}
|
||||
|
||||
function packTerrain(map: Terrain[][]): Uint8Array {
|
||||
function packTerrain(mapName: string, map: Terrain[][]): Uint8Array {
|
||||
const width = map.length;
|
||||
const height = map[0].length;
|
||||
const packedData = new Uint8Array(4 + width * height);
|
||||
@@ -229,7 +245,7 @@ function packTerrain(map: Terrain[][]): Uint8Array {
|
||||
packedData[4 + y * width + x] = packedByte;
|
||||
}
|
||||
}
|
||||
logBinaryAsBits(packedData);
|
||||
logBinaryAsBits(mapName, packedData);
|
||||
return packedData;
|
||||
}
|
||||
|
||||
@@ -278,8 +294,70 @@ function processOcean(map: Terrain[][]) {
|
||||
}
|
||||
}
|
||||
|
||||
function removeSmallLakes(map: Terrain[][]) {
|
||||
console.log(`removing lakes ${map.length}, ${map[0].length}`);
|
||||
function getIsland(
|
||||
map: Terrain[][],
|
||||
x: number,
|
||||
y: number,
|
||||
visited: Set<string>,
|
||||
) {
|
||||
let island = [];
|
||||
let next = [[x, y]];
|
||||
while (next.length) {
|
||||
const [x, y] = next.pop();
|
||||
const key = `${x},${y}`;
|
||||
if (
|
||||
x < 0 ||
|
||||
x >= map.length ||
|
||||
y < 0 ||
|
||||
y >= map[0].length ||
|
||||
x < 0 ||
|
||||
x >= map.length ||
|
||||
visited.has(key)
|
||||
)
|
||||
continue;
|
||||
|
||||
if (map[x][y].type == TerrainType.Land) {
|
||||
next.push([x + 1, y]);
|
||||
next.push([x - 1, y]);
|
||||
next.push([x, y + 1]);
|
||||
next.push([x, y - 1]);
|
||||
}
|
||||
|
||||
island.push([x, y]);
|
||||
visited.add(key);
|
||||
}
|
||||
|
||||
return island;
|
||||
}
|
||||
|
||||
function removeSmallIslands(map: Terrain[][]) {
|
||||
const visited = new Set<string>();
|
||||
|
||||
for (let x = 0; x < map.length; x++) {
|
||||
for (let y = 0; y < map[0].length; y++) {
|
||||
if (map[x][y].type == TerrainType.Land) {
|
||||
const key = `${x},${y}`;
|
||||
|
||||
// PERF: If getIsland already visited that coordinates then it's
|
||||
// useless to go over it again.
|
||||
if (visited.has(key)) continue;
|
||||
|
||||
const island = getIsland(map, x, y, visited);
|
||||
if (island.length < min_island_size) {
|
||||
island.forEach((pos) => {
|
||||
const x = pos[0];
|
||||
const y = pos[1];
|
||||
map[x][y].type = TerrainType.Water;
|
||||
map[x][y].ocean = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeSmallLakes(mapName: string, map: Terrain[][]) {
|
||||
console.log(`${mapName}: removing lakes ${map.length}, ${map[0].length}`);
|
||||
|
||||
for (let x = 0; x < map.length; x++) {
|
||||
for (let y = 0; y < map[0].length; y++) {
|
||||
@@ -300,11 +378,15 @@ function removeSmallLakes(map: Terrain[][]) {
|
||||
}
|
||||
}
|
||||
|
||||
function logBinaryAsBits(data: Uint8Array, length: number = 8) {
|
||||
function logBinaryAsBits(
|
||||
mapName: string,
|
||||
data: Uint8Array,
|
||||
length: number = 8,
|
||||
) {
|
||||
const bits = Array.from(data.slice(0, length))
|
||||
.map((b) => b.toString(2).padStart(8, "0"))
|
||||
.join(" ");
|
||||
console.log("Binary data (bits):", bits);
|
||||
console.log(`${mapName}: Binary data (bits):`, bits);
|
||||
}
|
||||
|
||||
await loadTerrainMap();
|
||||
await loadTerrainMaps();
|
||||
|
||||
Reference in New Issue
Block a user