improved algorithm for calculating player name location

This commit is contained in:
evanpelle
2024-08-19 13:07:15 -07:00
parent fe67cc8492
commit 7d48dd1cba
7 changed files with 111 additions and 68 deletions
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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);
}
+64 -43
View File
@@ -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()}`;
+4 -1
View File
@@ -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
}
-3
View File
@@ -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
View File
@@ -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"