fakehumans send boats, improved map

This commit is contained in:
evanpelle
2024-09-08 20:21:39 -07:00
parent 8331047a9b
commit a7ad8790aa
19 changed files with 437 additions and 55 deletions
+4 -2
View File
@@ -97,10 +97,12 @@
* more random names for game id & client id DONE 9/5/2024
* BUG: attacks speed up DONE 9/6/2024
* rebalance income DONE 9/7/2024
* Make fake humans
* Make more colors
* BUG: fix tile per turn pacing issues
--- v3 Release
* Make fake humans
* BUG: when clicking on enemy sometimes boat goes all the way around
* names dissappear too much (maybe screen size)
* directed expansion
@@ -111,6 +113,7 @@
* UI: event box
* make random waves on ocean, dark spots
* Load terrain dataImage in background
* BUG: half encircle Antoartica causes capture
--- v4 Release
@@ -119,4 +122,3 @@
* BUG: when sending boat to TerraNullius, only takes one tile
* REFACTOR: give terranullius an ID, game.player() returns terranullius
* REFACTOR: ocean is considered TerraNullius ?
File diff suppressed because one or more lines are too long
+266
View File
@@ -0,0 +1,266 @@
Carja
Reich3rd
DEATH
Ruby Rouse
Femboy
Syria
UnitedKingdom
kingdom
British Empire
I am better
fullsender
inlandmuse
Enes32
Emir
Zephyr
Crimson_Tide
OBLIVION
Sapphire_Siren
Enby_Warrior
Atlantis
SovietUnion
empire
Aztec_Empire
You_Shall_Not_Pass
madlad
coastaldreamer
Nox42
Pharaoh
GoldenHorde
Valhalla
QuantumQuasar
Ronin
DawnBreaker
NeonNightmare
Byzantium
CHAOS
Jade_Dragon
Comrade
Sparta
HolyRomanEmpire
republic
Inca_Sun
Get_Rekt
trickster
urbannomad
Loki69
Sultan
IronCurtain
Nirvana
CosmicDust
Samurai
Dusk_Reaper
PlasmaPhantom
Macedonia
ANARCHY
Onyx_Oracle
Regent
Troy
OttomanEmpire
federation
Maya_Moon
Unstoppable
memester
desertwalker
Thor23
Caliph
SilkRoad
Elysium
StardustCrusader
Ninja
Twilight_Harbinger
LunarSpecter
Carthage
HAVOC
Amber_Assassin
Monarch
Camelot
MongolHorde
dominion
Olmec_Jaguar
Invincible
madscientist
mountaindweller
Odin88
Shogun
Avalon
Utopia
NebulaNomad
Ronin
Shadow_Weaver
AstralAvenger
Pompeii
MAYHEM
Emerald_Enigma
Overlord
Asgard
VikingRealm
principality
Phoenician_Trader
memelord
jungleexplorer
Zeus55
Rajah
Shangri_La
Arcadia
GalacticEmissary
Shinobi
Void_Walker
EclipseHarbinger
SPQR
Liam
Eros
Aztec
Kaiser2nd
VOID
Scarlet Shadow
Tomboy
Egypt
RussianFederation
empire
Roman Empire
U cant win
risktaker
coastaldrift
Murat55
Sultan
Breeze
Sparta
CHAOS
Emerald Knight
Enby
Persia
OttomanEmpire
republic
Mongol Horde
Im unstoppable
nightowl
urbanlegend
Fatih78
Vizier
Tempest
Maya
ANARCHY
Obsidian Raven
Androgynous
Babylon
ByzantineEmpire
dominion
Austro Hungarian
You will lose
daydreamer
ruralnomad
Osman41
Pasha
Cyclone
Inca
HAVOC
Crimson Tide
Nonbinary
Carthage
HolyRomanEmpire
nation
Viking Raiders
Git gud
madlad
forestdweller
Orhan63
Bey
Tsunami
Olmec
OBLIVION
Azure Falcon
Genderfluid
Sumeria
GoldenHorde
sovereignty
Zulu Kingdom
Bow to me
trickster
desertroamer
Selim90
Khan
Tornado
Hittite
MAYHEM
Violet Viper
Queer
Assyria
MongolEmpire
realm
Phoenician Traders
L2P noob
memester
mountaintrekker
Mehmet27
Aga
Hurricane
Minoan
ABYSS
Indigo Serpent
Genderqueer
Akkad
TimuridEmpire
dynasty
Hanseatic League
EZ clap
edgelord
cavewanderer
Yunus84
Dey
Sandstorm
Mayan
ENIGMA
Amber Wraith
Agender
Parthia
SassanidEmpire
confederacy
Venetian Republic
Get rekt
madcap
islandexplorer
Kemal52
Raja
Grizzly
inlandmuse
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Anon
Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 KiB

After

Width:  |  Height:  |  Size: 823 KiB

-1
View File
@@ -80,7 +80,6 @@ class Client {
const nextGame = document.getElementById('next-game');
nextGame.textContent = `Next Game: ${nextLobby.id.substring(0, 3)}`
}
}
async fetchLobbies(): Promise<Lobby[]> {
+5 -3
View File
@@ -11,7 +11,6 @@ import {and, bfs, dist, manhattanDist} from "../core/Util";
import {TerrainRenderer} from "./graphics/TerrainRenderer";
export function createClientGame(name: string, clientID: ClientID, playerID: PlayerID, ip: string | null, gameID: GameID, config: Config, terrainMap: TerrainMap): ClientGame {
let eventBus = new EventBus()
let game = createGame(terrainMap, eventBus, config)
@@ -28,7 +27,7 @@ export function createClientGame(name: string, clientID: ClientID, playerID: Pla
game,
gameRenderer,
new InputHandler(eventBus),
new Executor(game)
new Executor(game, gameID)
)
}
@@ -118,6 +117,8 @@ export class ClientGame {
}
public start() {
console.log('version 3')
this.isActive = true
// TODO: make each class do this, or maybe have client intercept all requests?
//this.eventBus.on(TickEvent, (e) => this.tick(e))
@@ -131,7 +132,8 @@ export class ClientGame {
this.renderer.initialize()
this.input.initialize()
this.gs.addExecution(...this.executor.spawnBots(this.gs.config().numBots()))
this.gs.addExecution(...this.executor.fakeHumanExecutions(1))
console.log('!!! number fake humans 15')
this.gs.addExecution(...this.executor.fakeHumanExecutions(15))
this.intervalID = setInterval(() => this.tick(), 10);
}
+1 -1
View File
@@ -130,7 +130,7 @@ export function largestRectangleInHistogram(widths: number[]): Rectangle {
export function calculateFontSize(rectangle: Rectangle, name: string): number {
// This is a simplified calculation. You might want to adjust it based on your specific font and rendering system.
const widthConstrained = rectangle.width / name.length;
const widthConstrained = rectangle.width / name.length * 2;
const heightConstrained = rectangle.height / 3;
return Math.min(widthConstrained, heightConstrained);
}
+1 -1
View File
@@ -10,7 +10,7 @@
<body>
<div class="content">
<h1>OpenFront.io</h1>
<h2>(v0.3.5)</h2>
<h2>(v0.3.6)</h2>
<div id="username-container">
<input type="text" id="username" placeholder="Enter your username">
</div>
+12
View File
@@ -34,4 +34,16 @@ export class PseudoRandom {
nextID(): string {
return this.nextInt(0, 1000000).toString(36).padStart(5, '0');
}
randElement<T>(arr: T[]): T {
if (arr.length == 0) {
throw new Error('array must not be empty')
}
return arr[this.nextInt(0, arr.length)];
}
chance(odds: number): boolean {
return this.nextInt(0, odds) == 0
}
}
+1
View File
@@ -40,6 +40,7 @@ export interface Config {
tilesPerTickUsed: number
}
attackAmount(attacker: Player, defender: Player | TerraNullius): number
maxTroops(player: Player): number
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number
boatMaxDistance(): number
boatMaxNumber(): number
+20 -3
View File
@@ -46,6 +46,16 @@ export class DefaultConfig implements Config {
speed = 10
break
}
if (attacker.isPlayer() && defender.isPlayer()) {
if (attacker.type() == PlayerType.Bot && defender.type() != PlayerType.Bot) {
mag *= 1.2
}
if (attacker.type() != PlayerType.Bot && defender.type() == PlayerType.Bot) {
mag *= .8
}
}
if (defender.isPlayer()) {
return {
attackerTroopLoss: within(defender.troops() / attacker.troops() * mag, 1, 1000),
@@ -88,9 +98,13 @@ export class DefaultConfig implements Config {
return 10000
}
troopAdditionRate(player: Player): number {
maxTroops(player: Player): number {
let max = Math.sqrt(player.numTilesOwned()) * 3000 + 50000
max = Math.min(max, 1_000_000)
return Math.min(max, 1_000_000)
}
troopAdditionRate(player: Player): number {
let max = this.maxTroops(player)
let toAdd = 10 + (player.troops() + Math.sqrt(player.troops() * player.numTilesOwned())) / 100
@@ -98,8 +112,11 @@ export class DefaultConfig implements Config {
toAdd *= ratio
// console.log(`to add ${toAdd}`)
if (player.type() == PlayerType.FakeHuman) {
toAdd *= 1.2
}
if (player.type() == PlayerType.Bot) {
toAdd *= .7
toAdd *= .6
}
return Math.min(player.troops() + toAdd, max)
+1 -1
View File
@@ -16,7 +16,7 @@ export const devConfig = new class extends DefaultConfig {
}
numBots(): number {
return 0
return 400
}
// startTroops(playerInfo: PlayerInfo): number {
@@ -85,7 +85,6 @@ export class BoatAttackExecution implements Execution {
this.aStarPre.compute(5)
this.path = this.aStarPre.reconstructPath()
if (this.path != null) {
console.log(`got path ${this.path.map(t => t.cell().toString())}`)
this.boat = this.attacker.addBoat(this.troops, this.src, this.target)
} else {
console.log('got null path')
+8 -2
View File
@@ -1,4 +1,4 @@
import {Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerInfo, TerraNullius} from "../Game"
import {Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerInfo, PlayerType, TerraNullius} from "../Game"
import {PseudoRandom} from "../PseudoRandom"
import {simpleHash} from "../Util";
import {AttackExecution} from "./AttackExecution";
@@ -59,7 +59,13 @@ export class BotExecution implements Execution {
}
const toAttack = border[this.random.nextInt(0, border.length)]
this.sendAttack(toAttack.owner())
const owner = toAttack.owner()
if (owner.isPlayer() && (owner.type() == PlayerType.FakeHuman || owner.type() == PlayerType.Human)) {
if (this.random.nextInt(0, 1) == 1) {
return
}
}
this.sendAttack(owner)
}
sendAttack(toAttack: Player | TerraNullius) {
+11 -5
View File
@@ -1,5 +1,5 @@
import {Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, Tile, PlayerType} from "../Game";
import {AttackIntent, BoatAttackIntentSchema, Intent, Turn} from "../Schemas";
import {AttackIntent, BoatAttackIntentSchema, GameID, Intent, Turn} from "../Schemas";
import {AttackExecution} from "./AttackExecution";
import {SpawnExecution} from "./SpawnExecution";
import {BotSpawner} from "./BotSpawner";
@@ -7,14 +7,20 @@ import {BoatAttackExecution} from "./BoatAttackExecution";
import {PseudoRandom} from "../PseudoRandom";
import {UpdateNameExecution} from "./UpdateNameExecution";
import {FakeHumanExecution} from "./FakeHumanExecution";
import Usernames from '../../../resources/Usernames.txt'
import {simpleHash} from "../Util";
export class Executor {
private random = new PseudoRandom(999)
private usernames = Usernames.split('\n')
constructor(private gs: Game) {
// private random = new PseudoRandom(999)
private random: PseudoRandom = null
constructor(private gs: Game, gameID: GameID) {
this.random = new PseudoRandom(simpleHash(gameID))
}
createExecs(turn: Turn): Execution[] {
@@ -64,9 +70,9 @@ export class Executor {
for (let i = 0; i < numFakes; i++) {
execs.push(
new FakeHumanExecution(new PlayerInfo(
"fake_human" + i,
this.usernames[this.random.nextInt(0, this.usernames.length)],
PlayerType.FakeHuman,
this.random.nextID(),
null,
this.random.nextID()
))
)
+97 -32
View File
@@ -1,7 +1,8 @@
import {Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerInfo, TerraNullius, Tile} from "../Game"
import {Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerInfo, TerrainType, TerraNullius, Tile} from "../Game"
import {PseudoRandom} from "../PseudoRandom"
import {simpleHash} from "../Util";
import {and, bfs, dist, simpleHash} from "../Util";
import {AttackExecution} from "./AttackExecution";
import {BoatAttackExecution} from "./BoatAttackExecution";
import {SpawnExecution} from "./SpawnExecution";
export class FakeHumanExecution implements Execution {
@@ -11,11 +12,11 @@ export class FakeHumanExecution implements Execution {
private attackRate: number
private mg: MutableGame
private neighborsTerraNullius = true
private player: Player = null
constructor(private playerInfo: PlayerInfo) {
this.random = new PseudoRandom(simpleHash(playerInfo.id))
this.attackRate = this.random.nextInt(10, 50)
}
init(mg: MutableGame, ticks: number) {
@@ -25,59 +26,123 @@ export class FakeHumanExecution implements Execution {
tick(ticks: number) {
if (this.mg.inSpawnPhase()) {
if (ticks % 10 == 0) {
if (ticks % this.random.nextInt(5, 30) == 0) {
this.mg.addExecution(new SpawnExecution(
this.playerInfo,
this.randomLand().cell()
))
}
}
if (this.player == null) {
this.player = this.mg.players().find(p => p.id() == this.playerInfo.id)
if (this.player == null) {
//console.log(`player with id ${this.playerInfo.id} not found in FakeHumanExecution`)
return
}
}
if (ticks % this.attackRate != 0) {
if (this.player.troops() < this.mg.config().maxTroops(this.player) / 4) {
return
}
// if (this.neighborsTerraNullius) {
// for (const b of this.bot.borderTiles()) {
// for (const n of b.neighbors()) {
// if (n.owner() == this.mg.terraNullius() && n.isLand()) {
// this.sendAttack(this.mg.terraNullius())
// return
// }
// }
// }
// this.neighborsTerraNullius = false
// }
if (ticks % this.random.nextInt(10, 30) != 0) {
return
}
// const border = Array.from(this.bot.borderTiles()).flatMap(t => t.neighbors()).filter(t => t.hasOwner() && t.owner() != this.bot)
if (this.neighborsTerraNullius) {
for (const b of this.player.borderTiles()) {
for (const n of b.neighbors()) {
if (n.owner() == this.mg.terraNullius() && n.isLand()) {
this.sendAttack(this.mg.terraNullius())
return
}
}
}
this.neighborsTerraNullius = false
}
// if (border.length == 0) {
// return
// }
const enemyborder = Array.from(this.player.borderTiles()).flatMap(t => t.neighbors()).filter(t => t.hasOwner() && t.owner() != this.player)
if (enemyborder.length == 0 || this.random.chance(5)) {
this.sendBoat()
return
}
const enemies = enemyborder.map(t => t.owner()).filter(o => o.isPlayer()).map(o => o as Player).sort((a, b) => a.troops() - b.troops())
if (this.random.nextInt(0, 1) == 1) {
this.sendAttack(enemies[0])
} else {
this.sendAttack(enemies[this.random.nextInt(0, enemies.length)])
}
}
sendBoat(tries: number = 0) {
if (tries > 100) {
return
}
const oceanShore = Array.from(this.player.borderTiles()).filter(t => t.isOceanShore())
if (oceanShore.length == 0) {
return
}
const src = this.random.randElement(oceanShore)
const otherShore = Array.from(
bfs(
src,
and((t) => t.isOcean() || t.isOceanShore(), dist(src, 200))
)
).filter(t => t.isOceanShore() && t.owner() != this.player)
if (otherShore.length == 0) {
return
}
for (let i = 0; i < 100; i++) {
const dst = this.random.randElement(otherShore)
if (this.isSmallIsland(dst)) {
continue
}
this.mg.addExecution(new BoatAttackExecution(
this.player.id(),
dst.hasOwner() ? dst.owner().id() : null,
dst.cell(),
this.player.troops() / 5,
))
return
}
this.sendBoat(tries + 1)
// const toAttack = border[this.random.nextInt(0, border.length)]
// this.sendAttack(toAttack.owner())
}
randomLand(): Tile {
while (true) {
const cell = new Cell(this.random.nextInt(0, this.mg.width()), this.random.nextInt(0, this.mg.height()))
const tile = this.mg.tile(cell)
if (tile.isLand()) {
if (tile.isLand() && !tile.hasOwner()) {
if (tile.terrain() == TerrainType.Mountain && this.random.chance(2)) {
continue
}
return tile
}
}
}
// sendAttack(toAttack: Player | TerraNullius) {
// this.mg.addExecution(new AttackExecution(
// this.bot.troops() / 20,
// this.bot.id(),
// toAttack.isPlayer() ? toAttack.id() : null,
// null,
// null
// ))
// }
sendAttack(toAttack: Player | TerraNullius) {
this.mg.addExecution(new AttackExecution(
this.player.troops() / 5,
this.player.id(),
toAttack.isPlayer() ? toAttack.id() : null,
null,
null
))
}
isSmallIsland(tile: Tile): boolean {
return bfs(tile, and((t) => t.isLand(), dist(tile, 50))).size < 50
}
owner(): MutablePlayer {
return null
+1 -2
View File
@@ -97,7 +97,6 @@ export class PlayerExecution implements Execution {
}
private removeCluster(cluster: Set<Tile>) {
console.log('removing cluster!')
const arr = Array.from(cluster)
const mode = getMode(arr.flatMap(t => t.neighbors()).filter(t => t.hasOwner() && t.owner() != this.player).map(t => t.owner().id()))
if (!this.mg.hasPlayer(mode)) {
@@ -155,6 +154,6 @@ export class PlayerExecution implements Execution {
}
isActive(): boolean {
return this.player.isAlive()
return this.player == null || this.player.isAlive()
}
}
+4
View File
@@ -19,3 +19,7 @@ declare module '*.bin' {
const value: string;
export default value;
}
declare module '*.txt' {
const value: string;
export default value;
}
+4
View File
@@ -21,6 +21,10 @@ export default (env, argv) => {
test: /\.bin$/,
use: 'raw-loader'
},
{
test: /\.txt$/,
use: 'raw-loader'
},
{
test: /\.ts$/,
use: 'ts-loader',