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
-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;
}