Skip to content

Commit f9d365f

Browse files
Merge branch 'master' into schema-colors
2 parents 6e719a5 + b7aaecf commit f9d365f

File tree

23 files changed

+400
-116
lines changed

23 files changed

+400
-116
lines changed

client/src/client-config.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const DEFAULT_CONFIG = {
2525
showAllRobotRadii: false,
2626
showTimelineMarkers: true,
2727
showHealthBars: true,
28+
showPaintBars: false,
2829
showPaintMarkers: true,
2930
showMapXY: true,
3031
enableFancyPaint: true,
@@ -52,6 +53,7 @@ const configDescription: Record<keyof ClientConfig, string> = {
5253
showAllRobotRadii: 'Show all robot view and attack radii',
5354
showTimelineMarkers: 'Show user-generated markers on the timeline',
5455
showHealthBars: 'Show health bars below all robots',
56+
showPaintBars: 'Show paint bars below all robots',
5557
showPaintMarkers: 'Show paint markers created using mark()',
5658
showMapXY: 'Show X,Y when hovering a tile',
5759
enableFancyPaint: 'Enable fancy paint rendering',

client/src/components/controls-bar/controls-bar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export const ControlsBar: React.FC = () => {
167167
icon={<ControlIcons.SkipForwardsIcon />}
168168
tooltip={'Increase Speed'}
169169
onClick={() => GameRunner.multiplyUpdatesPerSecond(2)}
170-
disabled={Math.abs(targetUPS) >= 64}
170+
disabled={Math.abs(targetUPS) >= 128}
171171
/>
172172
<ControlsBarButton
173173
icon={<ControlIcons.PlaybackStopIcon />}

client/src/components/sidebar/map-editor/MapGenerator.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import Match from '../../../playback/Match'
44
import { CurrentMap, StaticMap } from '../../../playback/Map'
55
import Round from '../../../playback/Round'
66
import Bodies from '../../../playback/Bodies'
7-
import { BATTLECODE_YEAR, DIRECTIONS } from '../../../constants'
7+
import { BATTLECODE_YEAR, DIRECTIONS, TEAM_COLOR_NAMES } from '../../../constants'
88
import { nativeAPI } from '../runner/native-api-wrapper'
99
import { Vector } from '../../../playback/Vector'
10+
import { RobotType } from 'battlecode-schema/js/battlecode/schema'
1011

1112
export function loadFileAsMap(file: File): Promise<Game> {
1213
return new Promise((resolve, reject) => {
@@ -54,8 +55,6 @@ function verifyMap(map: CurrentMap, bodies: Bodies): string {
5455

5556
// Validate map elements
5657
let numWalls = 0
57-
let numPaintTowers = 0
58-
let numMoneyTowers = 0
5958
const mapSize = map.width * map.height
6059
for (let i = 0; i < mapSize; i++) {
6160
const pos = map.indexToLocation(i)
@@ -104,8 +103,6 @@ function verifyMap(map: CurrentMap, bodies: Bodies): string {
104103
}
105104
}
106105

107-
numPaintTowers += body && body.robotType === schema.RobotType.PAINT_TOWER ? 1 : 0
108-
numMoneyTowers += body && body.robotType === schema.RobotType.MONEY_TOWER ? 1 : 0
109106
numWalls += wall
110107
}
111108

@@ -117,24 +114,61 @@ function verifyMap(map: CurrentMap, bodies: Bodies): string {
117114
}
118115

119116
// Validate initial bodies
120-
if (numPaintTowers !== 2) {
121-
return `Expected exactly 2 paint towers, found ${numPaintTowers}`
122-
}
123-
if (numMoneyTowers !== 2) {
124-
return `Expected exactly 2 money towers, found ${numMoneyTowers}`
125-
}
117+
const numPaintTowers = [0, 0]
118+
const numMoneyTowers = [0, 0]
126119
for (const body of bodies.bodies.values()) {
127-
// Check distance to nearby ruins
120+
// Check distance to nearby ruins, towers, and walls
121+
122+
if (body.robotType === RobotType.PAINT_TOWER) {
123+
numPaintTowers[body.team.id - 1]++
124+
} else if (body.robotType === RobotType.MONEY_TOWER) {
125+
numMoneyTowers[body.team.id - 1]++
126+
} else {
127+
return `Tower at (${body.pos.x}, ${body.pos.y}) has invalid type!`
128+
}
128129

129130
for (const checkRuin of map.staticMap.ruins) {
130-
if (squareIntersects(checkRuin, body.pos, 2)) {
131+
if (squareIntersects(checkRuin, body.pos, 4)) {
131132
return (
132133
`Tower at (${body.pos.x}, ${body.pos.y}) is too close to ruin ` +
133134
`at (${checkRuin.x}, ${checkRuin.y}), must be ` +
134-
`>= 3 away`
135+
`>= 5 away`
136+
)
137+
}
138+
}
139+
140+
for (const checkBody of bodies.bodies.values()) {
141+
if (checkBody === body) continue
142+
if (squareIntersects(checkBody.pos, body.pos, 4)) {
143+
return (
144+
`Tower at (${body.pos.x}, ${body.pos.y}) is too close to ruin ` +
145+
`at (${checkBody.pos.x}, ${checkBody.pos.y}), must be ` +
146+
`>= 5 away`
135147
)
136148
}
137149
}
150+
151+
const wall = map.staticMap.walls.findIndex(
152+
(v, i) => !!v && squareIntersects(map.indexToLocation(i), body.pos, 2)
153+
)
154+
if (wall !== -1) {
155+
const pos = map.indexToLocation(wall)
156+
return (
157+
`Tower at (${body.pos.x}, ${body.pos.y}) is too close to wall ` +
158+
`at (${pos.x}, ${pos.y}), must be ` +
159+
`>= 3 away`
160+
)
161+
}
162+
}
163+
164+
for (const teamIdx of [0, 1]) {
165+
if (numPaintTowers[teamIdx] !== 1) {
166+
return `Expected exactly 1 ${TEAM_COLOR_NAMES[teamIdx]} paint tower, found ${numPaintTowers[teamIdx]}`
167+
}
168+
169+
if (numMoneyTowers[teamIdx] !== 1) {
170+
return `Expected exactly 1 ${TEAM_COLOR_NAMES[teamIdx]} money tower, found ${numMoneyTowers[teamIdx]}`
171+
}
138172
}
139173

140174
return ''

client/src/components/sidebar/map-editor/map-editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export const MapEditorPage: React.FC<Props> = (props) => {
160160
GameRunner.setMatch(editGame.current.currentMatch)
161161

162162
const round = editGame.current.currentMatch!.currentRound
163-
const brushes = round.map.getEditorBrushes().concat(round.bodies.getEditorBrushes(round.map.staticMap))
163+
const brushes = round.map.getEditorBrushes(round).concat(round.bodies.getEditorBrushes(round))
164164
brushes[0].open = true
165165
setBrushes(brushes)
166166
setCleared(round.bodies.isEmpty() && round.map.isEmpty())

client/src/constants.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import {Colors} from './colors'
2-
3-
export const CLIENT_VERSION = '1.0.0'
1+
export const CLIENT_VERSION = '1.3.0'
42
export const SPEC_VERSION = '1'
53
export const BATTLECODE_YEAR: number = 2025
64
export const MAP_SIZE_RANGE = {

client/src/playback/Bodies.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ export default class Bodies {
188188
return this.bodies.size === 0
189189
}
190190

191-
getEditorBrushes(map: StaticMap): MapEditorBrush[] {
192-
return [new TowerBrush(this, map)]
191+
getEditorBrushes(round: Round): MapEditorBrush[] {
192+
return [new TowerBrush(round)]
193193
}
194194

195195
toInitialBodyTable(builder: flatbuffers.Builder): number {
@@ -275,6 +275,9 @@ export class Body {
275275
if (focused || config.showHealthBars) {
276276
this.drawHealthBar(match, overlayCtx)
277277
}
278+
if (focused || config.showPaintBars) {
279+
this.drawPaintBar(match, overlayCtx, focused || config.showHealthBars)
280+
}
278281
}
279282
}
280283

@@ -382,6 +385,15 @@ export class Body {
382385
const squares2 = this.getAllLocationsWithinRadiusSquared(match, pos, this.metadata.visionRadiusSquared())
383386
this.drawEdges(match, ctx, lightly, squares2)
384387

388+
// Currently vision/message radius are always the same
389+
/*
390+
ctx.beginPath()
391+
ctx.strokeStyle = 'brown'
392+
ctx.lineWidth = 0.1
393+
const squares3 = this.getAllLocationsWithinRadiusSquared(match, pos, this.metadata.messageRadiusSquared())
394+
this.drawEdges(match, ctx, lightly, squares3)
395+
*/
396+
385397
ctx.globalAlpha = 1
386398
}
387399

@@ -427,6 +439,21 @@ export class Body {
427439
ctx.fillRect(hpBarX, hpBarY, hpBarWidth * (this.hp / this.maxHp), hpBarHeight)
428440
}
429441

442+
private drawPaintBar(match: Match, ctx: CanvasRenderingContext2D, healthVisible: boolean): void {
443+
const dimension = match.currentRound.map.staticMap.dimension
444+
const interpCoords = this.getInterpolatedCoords(match)
445+
const renderCoords = renderUtils.getRenderCoords(interpCoords.x, interpCoords.y, dimension)
446+
const paintBarWidth = 0.8
447+
const paintBarHeight = 0.1
448+
const paintBarYOffset = 0.4 + (healthVisible ? 0.11 : 0)
449+
const paintBarX = renderCoords.x + 0.5 - paintBarWidth / 2
450+
const paintBarY = renderCoords.y + 0.5 + paintBarYOffset
451+
ctx.fillStyle = 'rgba(0,0,0,.3)'
452+
ctx.fillRect(paintBarX, paintBarY, paintBarWidth, paintBarHeight)
453+
ctx.fillStyle = '#c515ed'
454+
ctx.fillRect(paintBarX, paintBarY, paintBarWidth * (this.paint / this.maxPaint), paintBarHeight)
455+
}
456+
430457
protected drawLevel(match: Match, ctx: CanvasRenderingContext2D) {
431458
if (this.level <= 1) return
432459

client/src/playback/Brushes.ts

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Bodies from './Bodies'
99
import { CurrentMap, StaticMap } from './Map'
1010
import { Vector } from './Vector'
1111
import { Team } from './Game'
12+
import Round from './Round'
1213

1314
const applyInRadius = (
1415
map: CurrentMap | StaticMap,
@@ -40,7 +41,35 @@ const squareIntersects = (check: Vector, center: Vector, radius: number) => {
4041
)
4142
}
4243

44+
const checkValidRuinPlacement = (check: Vector, map: StaticMap, bodies: Bodies) => {
45+
// Check if ruin is too close to the border
46+
if (check.x <= 1 || check.x >= map.width - 2 || check.y <= 1 || check.y >= map.height - 2) {
47+
return false
48+
}
49+
50+
// Check if this is a valid ruin location
51+
const idx = map.locationToIndex(check.x, check.y)
52+
const ruin = map.ruins.findIndex((l) => squareIntersects(l, check, 4))
53+
const wall = map.walls.findIndex((v, i) => !!v && squareIntersects(map.indexToLocation(i), check, 2))
54+
const paint = map.initialPaint[idx]
55+
56+
let tower = undefined
57+
for (const b of bodies.bodies.values()) {
58+
if (squareIntersects(check, b.pos, 4)) {
59+
tower = b
60+
break
61+
}
62+
}
63+
64+
if (tower || ruin !== -1 || wall !== -1 || paint) {
65+
return false
66+
}
67+
68+
return true
69+
}
70+
4371
export class WallsBrush extends SymmetricMapEditorBrush<StaticMap> {
72+
private readonly bodies: Bodies
4473
public readonly name = 'Walls'
4574
public readonly fields = {
4675
shouldAdd: {
@@ -54,8 +83,9 @@ export class WallsBrush extends SymmetricMapEditorBrush<StaticMap> {
5483
}
5584
}
5685

57-
constructor(map: StaticMap) {
58-
super(map)
86+
constructor(round: Round) {
87+
super(round.map.staticMap)
88+
this.bodies = round.bodies
5989
}
6090

6191
public symmetricApply(x: number, y: number, fields: Record<string, MapEditorBrushField>) {
@@ -64,7 +94,17 @@ export class WallsBrush extends SymmetricMapEditorBrush<StaticMap> {
6494
const pos = this.map.indexToLocation(idx)
6595
const ruin = this.map.ruins.findIndex((l) => squareIntersects(l, pos, 2))
6696
const paint = this.map.initialPaint[idx]
67-
if (ruin !== -1 || paint) return true
97+
98+
let tower = undefined
99+
for (const b of this.bodies.bodies.values()) {
100+
if (squareIntersects(pos, b.pos, 2)) {
101+
tower = b
102+
break
103+
}
104+
}
105+
106+
if (tower || ruin !== -1 || paint) return true
107+
68108
this.map.walls[idx] = 1
69109
}
70110

@@ -94,6 +134,7 @@ export class WallsBrush extends SymmetricMapEditorBrush<StaticMap> {
94134
}
95135

96136
export class RuinsBrush extends SymmetricMapEditorBrush<StaticMap> {
137+
private readonly bodies: Bodies
97138
public readonly name = 'Ruins'
98139
public readonly fields = {
99140
shouldAdd: {
@@ -102,26 +143,14 @@ export class RuinsBrush extends SymmetricMapEditorBrush<StaticMap> {
102143
}
103144
}
104145

105-
constructor(map: StaticMap) {
106-
super(map)
146+
constructor(round: Round) {
147+
super(round.map.staticMap)
148+
this.bodies = round.bodies
107149
}
108150

109151
public symmetricApply(x: number, y: number, fields: Record<string, MapEditorBrushField>) {
110152
const add = (x: number, y: number) => {
111-
// Check if ruin is too close to the border
112-
if (x <= 1 || x >= this.map.width - 2 || y <= 1 || y >= this.map.height - 2) {
113-
return true
114-
}
115-
116-
// Check if this is a valid ruin location
117-
const pos = { x, y }
118-
const idx = this.map.locationToIndex(x, y)
119-
const ruin = this.map.ruins.findIndex((l) => squareIntersects(l, pos, 4))
120-
const wall = this.map.walls.findIndex(
121-
(v, i) => !!v && squareIntersects(this.map.indexToLocation(i), pos, 2)
122-
)
123-
const paint = this.map.initialPaint[idx]
124-
if (ruin !== -1 || wall !== -1 || paint) {
153+
if (!checkValidRuinPlacement({ x, y }, this.map, this.bodies)) {
125154
return true
126155
}
127156

@@ -145,6 +174,7 @@ export class RuinsBrush extends SymmetricMapEditorBrush<StaticMap> {
145174
}
146175

147176
export class PaintBrush extends SymmetricMapEditorBrush<CurrentMap> {
177+
private readonly bodies: Bodies
148178
public readonly name = 'Paint'
149179
public readonly fields = {
150180
shouldAdd: {
@@ -171,8 +201,9 @@ export class PaintBrush extends SymmetricMapEditorBrush<CurrentMap> {
171201
}
172202
}
173203

174-
constructor(map: CurrentMap) {
175-
super(map)
204+
constructor(round: Round) {
205+
super(round.map)
206+
this.bodies = round.bodies
176207
}
177208

178209
public symmetricApply(x: number, y: number, fields: Record<string, MapEditorBrushField>, robotOne: boolean) {
@@ -217,6 +248,7 @@ export class PaintBrush extends SymmetricMapEditorBrush<CurrentMap> {
217248
}
218249

219250
export class TowerBrush extends SymmetricMapEditorBrush<StaticMap> {
251+
private readonly bodies: Bodies
220252
public readonly name = 'Towers'
221253
public readonly fields = {
222254
isTower: {
@@ -238,26 +270,20 @@ export class TowerBrush extends SymmetricMapEditorBrush<StaticMap> {
238270
}
239271
}
240272

241-
constructor(
242-
private readonly bodies: Bodies,
243-
map: StaticMap
244-
) {
245-
super(map)
273+
constructor(round: Round) {
274+
super(round.map.staticMap)
275+
this.bodies = round.bodies
246276
}
247277

248278
public symmetricApply(x: number, y: number, fields: Record<string, MapEditorBrushField>, robotOne: boolean) {
249279
const towerType: schema.RobotType = fields.towerType.value
250280
const isTower: boolean = fields.isTower.value
251281

252282
const add = (x: number, y: number, team: Team) => {
253-
// Check if this is a valid tower location
254283
const pos = { x, y }
255-
const idx = this.map.locationToIndex(x, y)
256-
const body = this.bodies.getBodyAtLocation(x, y)
257-
const wall = this.map.walls[idx]
258-
const ruin = this.map.ruins.findIndex((l) => squareIntersects(l, pos, 2))
259-
260-
if (body || wall || ruin !== -1) return null
284+
if (!checkValidRuinPlacement(pos, this.map, this.bodies)) {
285+
return null
286+
}
261287

262288
const id = this.bodies.getNextID()
263289
this.bodies.spawnBodyFromValues(id, towerType, team, pos)

0 commit comments

Comments
 (0)