mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:10:42 +00:00
improved algorithm for calculating player name location
This commit is contained in:
@@ -31,14 +31,14 @@
|
||||
* BUG: boats freeze game on path calculation DONE 8/18/2024
|
||||
* use analyitics DONE 8/18/2024
|
||||
* Use Overpass font DONE 8/18/2024
|
||||
* BUG: invert zoom
|
||||
* BUG: invert zoom DONE 8/19/2024
|
||||
* better algorithm for name render placement
|
||||
* show how many players in each lobby
|
||||
* make boats larger
|
||||
* make coasts look better
|
||||
* have boats not get close to shore
|
||||
* BUG: boat doesn't work if on lake on other player not on lake
|
||||
* Allow boats to attack TerraNullius
|
||||
* make coasts look better
|
||||
* add shader to dim border
|
||||
* remove player.info()
|
||||
* fix enemy islands when attacking
|
||||
|
||||
@@ -169,7 +169,7 @@ export class GameRenderer {
|
||||
this.scale /= zoomFactor;
|
||||
|
||||
// Clamp the scale to prevent extreme zooming
|
||||
this.scale = Math.max(0.1, Math.min(10, this.scale));
|
||||
this.scale = Math.max(0.1, Math.min(15, this.scale));
|
||||
|
||||
const canvasRect = this.canvas.getBoundingClientRect();
|
||||
const canvasX = event.x - canvasRect.left;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {Game, Player, Tile, Cell} from '../core/Game';
|
||||
import {Game, Player, Tile, Cell, TerrainTypes} from '../../core/Game';
|
||||
import {within} from '../../core/Util';
|
||||
|
||||
export interface Point {
|
||||
x: number;
|
||||
@@ -12,14 +13,23 @@ export interface Rectangle {
|
||||
height: number;
|
||||
}
|
||||
|
||||
|
||||
export function placeName(game: Game, player: Player): [position: Cell, fontSize: number] {
|
||||
const boundingBox = calculateBoundingBox(player);
|
||||
const grid = createGrid(game, player, boundingBox);
|
||||
|
||||
const rawScalingFactor = (boundingBox.max.x - boundingBox.min.x) / 50
|
||||
const scalingFactor = within(Math.floor(rawScalingFactor), 1, 100)
|
||||
|
||||
const grid = createGrid(game, player, boundingBox, scalingFactor);
|
||||
const largestRectangle = findLargestInscribedRectangle(grid);
|
||||
largestRectangle.x = largestRectangle.x * scalingFactor
|
||||
largestRectangle.y = largestRectangle.y * scalingFactor
|
||||
largestRectangle.width = largestRectangle.width * scalingFactor
|
||||
largestRectangle.height = largestRectangle.height * scalingFactor
|
||||
|
||||
const center = new Cell(
|
||||
largestRectangle.x + largestRectangle.width / 2,
|
||||
largestRectangle.y + largestRectangle.height / 2,
|
||||
Math.floor(largestRectangle.x + largestRectangle.width / 2 + boundingBox.min.x),
|
||||
Math.floor(largestRectangle.y + largestRectangle.height / 2 + boundingBox.min.y),
|
||||
)
|
||||
|
||||
const fontSize = calculateFontSize(largestRectangle, player.info().name);
|
||||
@@ -27,7 +37,7 @@ export function placeName(game: Game, player: Player): [position: Cell, fontSize
|
||||
return [center, fontSize]
|
||||
}
|
||||
|
||||
export function calculateBoundingBox(player: Player): {min: Point; max: Point} {
|
||||
export function calculateBoundingBox(player: Player): {min: Cell; max: Cell} {
|
||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||
|
||||
player.borderTiles().forEach((tile: Tile) => {
|
||||
@@ -38,20 +48,33 @@ export function calculateBoundingBox(player: Player): {min: Point; max: Point} {
|
||||
maxY = Math.max(maxY, cell.y);
|
||||
});
|
||||
|
||||
return {min: {x: minX, y: minY}, max: {x: maxX, y: maxY}};
|
||||
return {min: new Cell(minX, minY), max: new Cell(maxX, maxY)}
|
||||
}
|
||||
|
||||
export function createGrid(game: Game, player: Player, boundingBox: {min: Point; max: Point}): boolean[][] {
|
||||
const width = boundingBox.max.x - boundingBox.min.x + 1;
|
||||
const height = boundingBox.max.y - boundingBox.min.y + 1;
|
||||
export function createGrid(game: Game, player: Player, boundingBox: {min: Point; max: Point}, scalingFactor: number): boolean[][] {
|
||||
const scaledBoundingBox: {min: Point; max: Point} = {
|
||||
min: {
|
||||
x: Math.floor(boundingBox.min.x / scalingFactor),
|
||||
y: Math.floor(boundingBox.min.y / scalingFactor)
|
||||
},
|
||||
max: {
|
||||
x: Math.floor(boundingBox.max.x / scalingFactor),
|
||||
y: Math.floor(boundingBox.max.y / scalingFactor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const width = scaledBoundingBox.max.x - scaledBoundingBox.min.x + 1;
|
||||
const height = scaledBoundingBox.max.y - scaledBoundingBox.min.y + 1;
|
||||
const grid: boolean[][] = Array(width).fill(null).map(() => Array(height).fill(false));
|
||||
|
||||
for (let y = boundingBox.min.y; y <= boundingBox.max.y; y++) {
|
||||
for (let x = boundingBox.min.x; x <= boundingBox.max.x; x++) {
|
||||
const cell = new Cell(x, y);
|
||||
|
||||
for (let x = scaledBoundingBox.min.x; x <= scaledBoundingBox.max.x; x++) {
|
||||
for (let y = scaledBoundingBox.min.y; y <= scaledBoundingBox.max.y; y++) {
|
||||
const cell = new Cell(x * scalingFactor, y * scalingFactor);
|
||||
if (game.isOnMap(cell)) {
|
||||
const tile = game.tile(cell);
|
||||
grid[x - boundingBox.min.x][y - boundingBox.min.y] = tile.owner() === player;
|
||||
grid[x - scaledBoundingBox.min.x][y - scaledBoundingBox.min.y] = tile.owner() === player; // TODO: okay if lake
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,9 +91,9 @@ export function findLargestInscribedRectangle(grid: boolean[][]): Rectangle {
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
if (grid[col][row]) {
|
||||
heights[row]++;
|
||||
heights[col]++;
|
||||
} else {
|
||||
heights[row] = 0;
|
||||
heights[col] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +143,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 aspectRatio = name.length; // Assuming width:height ratio of 2:1 for each character
|
||||
const widthConstrained = rectangle.width / name.length;
|
||||
const heightConstrained = rectangle.height / 2;
|
||||
const heightConstrained = rectangle.height / name.length;
|
||||
return Math.min(widthConstrained, heightConstrained);
|
||||
}
|
||||
@@ -2,10 +2,18 @@ import PriorityQueue from "priority-queue-typescript"
|
||||
import {Cell, Game, Player} from "../../core/Game"
|
||||
import {PseudoRandom} from "../../core/PseudoRandom"
|
||||
import {Theme} from "../../core/configuration/Config"
|
||||
import {calculateBoundingBox} from "../NameBoxCalculator"
|
||||
import {calculateBoundingBox, placeName} from "./NameBoxCalculator"
|
||||
|
||||
class RenderInfo {
|
||||
constructor(public player: Player, public lastRendered: number, public location: Cell, public fontSize: number) { }
|
||||
public isVisible = true
|
||||
constructor(
|
||||
public player: Player,
|
||||
public lastRenderCalc: number,
|
||||
public lastBoundingCalculated: number,
|
||||
public boundingBox: {min: Cell, max: Cell},
|
||||
public location: Cell,
|
||||
public fontSize: number
|
||||
) { }
|
||||
}
|
||||
|
||||
export class NameRenderer {
|
||||
@@ -17,7 +25,7 @@ export class NameRenderer {
|
||||
private renderInfo: Map<Player, RenderInfo> = new Map()
|
||||
private context: CanvasRenderingContext2D
|
||||
private canvas: HTMLCanvasElement
|
||||
private toRender: PriorityQueue<RenderInfo> = new PriorityQueue<RenderInfo>(1000, (a: RenderInfo, b: RenderInfo) => a.lastRendered - b.lastRendered);
|
||||
private renders: RenderInfo[] = []
|
||||
private seenPlayers: Set<Player> = new Set()
|
||||
|
||||
constructor(private game: Game, private theme: Theme) {
|
||||
@@ -36,63 +44,76 @@ export class NameRenderer {
|
||||
this.canvas.height = this.game.height();
|
||||
}
|
||||
|
||||
public tick() {
|
||||
const now = Date.now()
|
||||
if (now - this.lastChecked > this.refreshRate) {
|
||||
this.lastChecked = now
|
||||
this.renders = this.renders.filter(r => r.player.isAlive())
|
||||
for (const player of this.game.players()) {
|
||||
if (player.isAlive()) {
|
||||
if (!this.seenPlayers.has(player)) {
|
||||
this.seenPlayers.add(player)
|
||||
this.renders.push(new RenderInfo(player, 0, 0, null, null, 0))
|
||||
}
|
||||
} else {
|
||||
this.seenPlayers.delete(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const render of this.renders) {
|
||||
const now = Date.now()
|
||||
if (now - render.lastBoundingCalculated > this.refreshRate) {
|
||||
render.boundingBox = calculateBoundingBox(render.player);
|
||||
render.lastBoundingCalculated = now
|
||||
}
|
||||
if (render.isVisible && now - render.lastRenderCalc > this.refreshRate) {
|
||||
this.calculateRenderInfo(render)
|
||||
render.lastRenderCalc = now + this.rand.nextInt(-50, 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public render(mainContex: CanvasRenderingContext2D, scale: number, uppperLeft: Cell, bottomRight: Cell) {
|
||||
for (const render of this.toRender) {
|
||||
if (render.player.isAlive()) {
|
||||
for (const render of this.renders) {
|
||||
render.isVisible = this.isVisible(render, uppperLeft, bottomRight)
|
||||
if (render.player.isAlive() && render.isVisible && render.fontSize * scale > 10) {
|
||||
this.renderPlayerInfo(render, mainContex, scale, uppperLeft, bottomRight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public tick() {
|
||||
const now = Date.now()
|
||||
if (now - this.lastChecked > this.refreshRate) {
|
||||
this.lastChecked = now
|
||||
for (const player of this.game.players()) {
|
||||
if (!this.seenPlayers.has(player)) {
|
||||
this.toRender.add(new RenderInfo(player, 0, null, null))
|
||||
this.seenPlayers.add(player)
|
||||
}
|
||||
isVisible(render: RenderInfo, min: Cell, max: Cell): boolean {
|
||||
const ratio = (max.x - min.x) / Math.max(20, (render.boundingBox.max.x - render.boundingBox.min.x))
|
||||
if (render.player.info().isBot) {
|
||||
if (ratio > 15) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (ratio > 30) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
while (!this.toRender.empty() && now - this.toRender.peek().lastRendered > this.refreshRate) {
|
||||
const renderInfo = this.toRender.poll()
|
||||
this.calculateRenderInfo(renderInfo)
|
||||
renderInfo.lastRendered = now + this.rand.nextInt(-50, 50)
|
||||
this.toRender.add(renderInfo)
|
||||
if (render.boundingBox.max.x < min.x || render.boundingBox.max.y < min.y || render.boundingBox.min.x > max.x || render.boundingBox.max.y > max.y) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
calculateRenderInfo(render: RenderInfo): boolean {
|
||||
|
||||
let wasUpdated = false
|
||||
|
||||
render.lastRendered = Date.now() + this.rand.nextInt(0, 100)
|
||||
wasUpdated = true
|
||||
|
||||
const box = calculateBoundingBox(render.player)
|
||||
const centerX = box.min.x + ((box.max.x - box.min.x) / 2)
|
||||
const centerY = box.min.y + ((box.max.y - box.min.y) / 2)
|
||||
render.location = new Cell(centerX, centerY)
|
||||
render.fontSize = Math.max(Math.min(box.max.x - box.min.x, box.max.y - box.min.y) / render.player.info().name.length / 2, 1.5)
|
||||
return wasUpdated
|
||||
calculateRenderInfo(render: RenderInfo) {
|
||||
if (render.player.numTilesOwned() == 0) {
|
||||
render.fontSize = 0
|
||||
return
|
||||
}
|
||||
render.lastRenderCalc = Date.now() + this.rand.nextInt(0, 100)
|
||||
const [cell, size] = placeName(this.game, render.player)
|
||||
render.location = cell
|
||||
render.fontSize = Math.max(1, Math.floor(size))
|
||||
}
|
||||
|
||||
renderPlayerInfo(render: RenderInfo, context: CanvasRenderingContext2D, scale: number, uppperLeft: Cell, bottomRight: Cell) {
|
||||
if (render.fontSize * scale < 10) {
|
||||
return
|
||||
}
|
||||
|
||||
const nameCenterX = Math.floor(render.location.x - this.game.width() / 2)
|
||||
const nameCenterY = Math.floor(render.location.y - this.game.height() / 2)
|
||||
|
||||
if (render.location.x < uppperLeft.x || render.location.x > bottomRight.x || render.location.y < uppperLeft.y || render.location.y > bottomRight.y) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
context.textRendering = "optimizeSpeed";
|
||||
|
||||
context.font = `${render.fontSize}px ${this.theme.font()}`;
|
||||
|
||||
@@ -15,12 +15,15 @@ export const devConfig = new class extends DefaultConfig {
|
||||
player(): PlayerConfig {
|
||||
return devPlayerConfig
|
||||
}
|
||||
numBots(): number {
|
||||
return 500
|
||||
}
|
||||
}
|
||||
|
||||
export const devPlayerConfig = new class extends DefaultPlayerConfig {
|
||||
startTroops(playerInfo: PlayerInfo): number {
|
||||
if (playerInfo.isBot) {
|
||||
return 10
|
||||
return 5000
|
||||
}
|
||||
return 5000
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ import {defaultConfig} from '../core/configuration/DefaultConfig';
|
||||
import {GamePhase} from './GameServer';
|
||||
import {getConfig} from '../core/configuration/Config';
|
||||
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
@@ -19,7 +17,6 @@ const app = express();
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocketServer({server});
|
||||
|
||||
|
||||
// Serve static files from the 'out' directory
|
||||
app.use(express.static(path.join(__dirname, '../../out')));
|
||||
app.use(express.json())
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ fi
|
||||
|
||||
# Set the instance name based on the environment
|
||||
if [[ "$ENV" == "dev" ]]; then
|
||||
INSTANCE_NAME="openfrontio-dev"
|
||||
INSTANCE_NAME="openfrontio-dev-instance"
|
||||
echo "[DEV] Deploying to openfront.dev"
|
||||
else
|
||||
INSTANCE_NAME="openfrontio-instance"
|
||||
|
||||
Reference in New Issue
Block a user