11import { Config } from "../configuration/Config" ;
22import { Execution , Game , Player , UnitType } from "../game/Game" ;
3- import { GameMap , TileRef } from "../game/GameMap" ;
3+ import { TileRef } from "../game/GameMap" ;
44import { calculateBoundingBox , getMode , inscribed , simpleHash } from "../Util" ;
55
66interface ClusterTraversalState {
@@ -139,17 +139,23 @@ export class PlayerExecution implements Execution {
139139 private surroundedBySamePlayer ( cluster : Set < TileRef > ) : false | Player {
140140 const enemies = new Set < number > ( ) ;
141141 for ( const tile of cluster ) {
142- if (
143- this . mg . isOceanShore ( tile ) ||
144- this . mg . isOnEdgeOfMap ( tile ) ||
145- this . mg . neighbors ( tile ) . some ( ( n ) => ! this . mg ?. hasOwner ( n ) )
146- ) {
142+ let hasUnownedNeighbor = false ;
143+ if ( this . mg . isOceanShore ( tile ) || this . mg . isOnEdgeOfMap ( tile ) ) {
144+ return false ;
145+ }
146+ this . mg . forEachNeighbor ( tile , ( n ) => {
147+ if ( ! this . mg . hasOwner ( n ) ) {
148+ hasUnownedNeighbor = true ;
149+ return ;
150+ }
151+ const ownerId = this . mg . ownerID ( n ) ;
152+ if ( ownerId !== this . player . smallID ( ) ) {
153+ enemies . add ( ownerId ) ;
154+ }
155+ } ) ;
156+ if ( hasUnownedNeighbor ) {
147157 return false ;
148158 }
149- this . mg
150- . neighbors ( tile )
151- . filter ( ( n ) => this . mg ?. ownerID ( n ) !== this . player ?. smallID ( ) )
152- . forEach ( ( p ) => this . mg && enemies . add ( this . mg . ownerID ( p ) ) ) ;
153159 if ( enemies . size !== 1 ) {
154160 return false ;
155161 }
@@ -172,14 +178,12 @@ export class PlayerExecution implements Execution {
172178 if ( this . mg . isShore ( tr ) || this . mg . isOnEdgeOfMap ( tr ) ) {
173179 return false ;
174180 }
175- this . mg
176- . neighbors ( tr )
177- . filter (
178- ( n ) =>
179- this . mg ?. owner ( n ) . isPlayer ( ) &&
180- this . mg ?. ownerID ( n ) !== this . player ?. smallID ( ) ,
181- )
182- . forEach ( ( n ) => enemyTiles . add ( n ) ) ;
181+ this . mg . forEachNeighbor ( tr , ( n ) => {
182+ const owner = this . mg . owner ( n ) ;
183+ if ( owner . isPlayer ( ) && this . mg . ownerID ( n ) !== this . player . smallID ( ) ) {
184+ enemyTiles . add ( n ) ;
185+ }
186+ } ) ;
183187 }
184188 if ( enemyTiles . size === 0 ) {
185189 return false ;
@@ -210,9 +214,13 @@ export class PlayerExecution implements Execution {
210214 return ;
211215 }
212216
213- const filter = ( _ : GameMap , t : TileRef ) : boolean =>
214- this . mg ?. ownerID ( t ) === this . player ?. smallID ( ) ;
215- const tiles = this . mg . bfs ( firstTile , filter ) ;
217+ const tiles = this . floodFillWithGen (
218+ this . bumpGeneration ( ) ,
219+ this . traversalState ( ) . visited ,
220+ [ firstTile ] ,
221+ ( tile , cb ) => this . mg . forEachNeighbor ( tile , cb ) ,
222+ ( tile ) => this . mg . ownerID ( tile ) === this . player . smallID ( ) ,
223+ ) ;
216224
217225 if ( this . player . numTilesOwned ( ) === tiles . size ) {
218226 this . mg . conquerPlayer ( capturing , this . player ) ;
@@ -226,7 +234,7 @@ export class PlayerExecution implements Execution {
226234 private getCapturingPlayer ( cluster : Set < TileRef > ) : Player | null {
227235 const neighbors = new Map < Player , number > ( ) ;
228236 for ( const t of cluster ) {
229- for ( const neighbor of this . mg . neighbors ( t ) ) {
237+ this . mg . forEachNeighbor ( t , ( neighbor ) => {
230238 const owner = this . mg . owner ( neighbor ) ;
231239 if (
232240 owner . isPlayer ( ) &&
@@ -235,7 +243,7 @@ export class PlayerExecution implements Execution {
235243 ) {
236244 neighbors . set ( owner , ( neighbors . get ( owner ) ?? 0 ) + 1 ) ;
237245 }
238- }
246+ } ) ;
239247 }
240248
241249 // If there are no enemies, return null
@@ -269,9 +277,40 @@ export class PlayerExecution implements Execution {
269277 const borderTiles = this . player . borderTiles ( ) ;
270278 if ( borderTiles . size === 0 ) return [ ] ;
271279
272- const totalTiles = this . mg . width ( ) * this . mg . height ( ) ;
280+ const state = this . traversalState ( ) ;
281+ const currentGen = this . bumpGeneration ( ) ;
282+ const visited = state . visited ;
283+
284+ const clusters : Set < TileRef > [ ] = [ ] ;
285+
286+ for ( const startTile of borderTiles ) {
287+ if ( visited [ startTile ] === currentGen ) continue ;
288+
289+ const cluster = this . floodFillWithGen (
290+ currentGen ,
291+ visited ,
292+ [ startTile ] ,
293+ ( tile , cb ) => this . mg . forEachNeighborWithDiag ( tile , cb ) ,
294+ ( tile ) => borderTiles . has ( tile ) ,
295+ ) ;
296+ clusters . push ( cluster ) ;
297+ }
298+ return clusters ;
299+ }
300+
301+ owner ( ) : Player {
302+ if ( this . player === null ) {
303+ throw new Error ( "Not initialized" ) ;
304+ }
305+ return this . player ;
306+ }
273307
274- // Retrieve or initialize traversal state for this specific Game instance.
308+ isActive ( ) : boolean {
309+ return this . active ;
310+ }
311+
312+ private traversalState ( ) : ClusterTraversalState {
313+ const totalTiles = this . mg . width ( ) * this . mg . height ( ) ;
275314 let state = traversalStates . get ( this . mg ) ;
276315 if ( ! state || state . visited . length < totalTiles ) {
277316 state = {
@@ -280,52 +319,52 @@ export class PlayerExecution implements Execution {
280319 } ;
281320 traversalStates . set ( this . mg , state ) ;
282321 }
322+ return state ;
323+ }
283324
284- // Generational clear: bump generation instead of filling the array.
325+ private bumpGeneration ( ) : number {
326+ const state = this . traversalState ( ) ;
285327 state . gen ++ ;
286328 if ( state . gen === 0xffffffff ) {
287- // Extremely rare wrap-around; reset the buffer.
288329 state . visited . fill ( 0 ) ;
289330 state . gen = 1 ;
290331 }
332+ return state . gen ;
333+ }
291334
292- const currentGen = state . gen ;
293- const visited = state . visited ;
294-
295- const clusters : Set < TileRef > [ ] = [ ] ;
335+ private floodFillWithGen (
336+ currentGen : number ,
337+ visited : Uint32Array ,
338+ startTiles : TileRef [ ] ,
339+ neighborFn : ( tile : TileRef , callback : ( neighbor : TileRef ) => void ) => void ,
340+ includeFn : ( tile : TileRef ) => boolean ,
341+ ) : Set < TileRef > {
342+ const result = new Set < TileRef > ( ) ;
296343 const stack : TileRef [ ] = [ ] ;
297344
298- for ( const startTile of borderTiles ) {
299- if ( visited [ startTile ] === currentGen ) continue ;
300-
301- const currentCluster = new Set < TileRef > ( ) ;
302- stack . push ( startTile ) ;
303- visited [ startTile ] = currentGen ;
304-
305- while ( stack . length > 0 ) {
306- const tile = stack . pop ( ) ! ;
307- currentCluster . add ( tile ) ;
308-
309- this . mg . forEachNeighborWithDiag ( tile , ( neighbor ) => {
310- if ( borderTiles . has ( neighbor ) && visited [ neighbor ] !== currentGen ) {
311- stack . push ( neighbor ) ;
312- visited [ neighbor ] = currentGen ;
313- }
314- } ) ;
315- }
316- clusters . push ( currentCluster ) ;
345+ for ( const start of startTiles ) {
346+ if ( visited [ start ] === currentGen ) continue ;
347+ if ( ! includeFn ( start ) ) continue ;
348+ visited [ start ] = currentGen ;
349+ result . add ( start ) ;
350+ stack . push ( start ) ;
317351 }
318- return clusters ;
319- }
320352
321- owner ( ) : Player {
322- if ( this . player === null ) {
323- throw new Error ( "Not initialized" ) ;
353+ while ( stack . length > 0 ) {
354+ const tile = stack . pop ( ) ! ;
355+ neighborFn ( tile , ( neighbor ) => {
356+ if ( visited [ neighbor ] === currentGen ) {
357+ return ;
358+ }
359+ if ( ! includeFn ( neighbor ) ) {
360+ return ;
361+ }
362+ visited [ neighbor ] = currentGen ;
363+ result . add ( neighbor ) ;
364+ stack . push ( neighbor ) ;
365+ } ) ;
324366 }
325- return this . player ;
326- }
327367
328- isActive ( ) : boolean {
329- return this . active ;
368+ return result ;
330369 }
331370}
0 commit comments