abstract class Layer { public readonly element : SVGGElement ; public readonly tileWidth : number ; public readonly tileHeight : number ; public readonly mapWidth : number ; public readonly mapHeight : number ; constructor ( element , tileWidth , tileHeight , mapWidth , mapHeight ) { this . element = element ; this . mapWidth = mapWidth ; this . mapHeight = mapHeight ; this . tileWidth = tileWidth ; this . tileHeight = tileHeight ; } } class CursorLayer extends Layer { private readonly cursor : SVGRectElement ; private anchors : Array < AnchorTile > ; private cursorX : number = 0 ; private cursorY : number = 0 ; public readonly histories : Array < PathfindingHistory > ; public showDetailDescription : ( tile : SolutionTile ) => any ; constructor ( element , cursor , tileWidth , tileHeight , mapWidth , mapHeight ) { super ( element , tileWidth , tileHeight , mapWidth , mapHeight ) ; this . anchors = new Array < AnchorTile > ( ) ; this . histories = new Array < PathfindingHistory > ( ) ; this . cursor = cursor ; this . element . parentElement . addEventListener ( "mousemove" , e => this . onLayerMouseMove ( e ) ) ; this . element . parentElement . addEventListener ( "mouseleave" , e => this . onLayerMouseLeave ( e ) ) ; this . element . parentElement . addEventListener ( "mouseenter" , e => this . onLayerMouseEnter ( e ) ) ; } private onLayerMouseMove ( event ) { var rect = this . element . parentElement . getBoundingClientRect ( ) ; var mouseX = Math . floor ( ( event . clientX - rect . left ) / this . tileWidth ) ; var mouseY = Math . floor ( ( event . clientY - rect . top ) / this . tileHeight ) ; var changed = false ; if ( this . cursorX != mouseX ) { this . cursor . x . baseVal . value = mouseX * this . tileWidth ; this . cursorX = mouseX ; changed = true ; } if ( this . cursorY != mouseY ) { this . cursor . y . baseVal . value = mouseY * this . tileHeight ; this . cursorY = mouseY ; changed = true ; } if ( changed ) { this . cursor . getElementsByClassName ( "cursor-x" ) [ 0 ] . textContent = `${ this . cursorX }` ; this . cursor . getElementsByClassName ( "cursor-y" ) [ 0 ] . textContent = `${ this . cursorY }` ; } } private onLayerMouseLeave ( event ) { this . cursor . style . visibility = "hidden" ; } private onLayerMouseEnter ( event ) { this . cursor . style . visibility = "inherit" ; } public togglePath ( index ) { var history = this . histories [ index ] ; if ( history == null ) return ; history . isVisible = ! history . isVisible ; if ( history . isVisible ) { var begin = 0.3 ; var tileElement = null as SVGElement ; var callback = this . showDetailDescription ; for ( let step of history . path ) { tileElement = step . visualize ( this . tileWidth , this . tileHeight ) ; step . updateAnimation ( begin ) ; if ( callback != null ) { tileElement . onmousemove = function ( ev ) { callback ( step ) ; } ; } this . element . appendChild ( tileElement ) ; begin += 0.1 ; } for ( let tile of history . unvisited ) { var filtered = history . path . filter ( p => p . x === tile . x && p . y === tile . y ) ; if ( filtered . length > 0 ) { tile . levels . forEach ( level => filtered [ 0 ] . updateLevels ( level ) ) ; } else { tileElement = tile . visualize ( this . tileWidth , this . tileHeight ) ; if ( callback != null ) { tileElement . onmousemove = function ( ev ) { callback ( tile ) ; } ; } this . element . appendChild ( tileElement ) ; } } } else { history . path . forEach ( p => p . remove ( ) ) ; history . unvisited . forEach ( d => d . remove ( ) ) ; } return history . isVisible ; } public placeTile ( x , y , color ) { if ( x < 0 || y < 0 || x >= this . mapWidth || y >= this . mapHeight ) { return ; } let filtered = this . anchors . filter ( a => a . x === x && a . y === y ) ; if ( filtered . length > 0 ) { filtered [ 0 ] . updateColor ( color ) ; } else { var anchor = new AnchorTile ( x , y , color ) ; this . element . appendChild ( anchor . visualize ( this . tileWidth , this . tileHeight ) ) ; this . anchors . push ( anchor ) ; } } public removeTile ( x , y ) { if ( x < 0 || y < 0 || x >= this . mapWidth || y >= this . mapHeight ) { return ; } this . anchors . filter ( a => a . x === x && a . y === y ) . forEach ( a => a . remove ( ) ) ; this . anchors = this . anchors . filter ( a => ! a . isRemoved ( ) ) ; } public clearAnchors ( ) { for ( let anchor of this . anchors ) { anchor . remove ( ) ; } this . anchors = new Array < AnchorTile > ( ) ; } public clearTiles ( ) { for ( let history of this . histories ) { history . path . forEach ( path => path . remove ( ) ) ; history . unvisited . forEach ( detail => detail . remove ( ) ) ; } } } class ForegroundLayer extends Layer { private readonly assetIds : ReadonlyArray < string > ; public obstacle : number ; public isPathfindingOnly : boolean ; public objectPracingPredicate : ( i : number , j : number , obstacle : number ) => boolean ; public pathPlacingCallback : ( i : number , j : number ) => boolean ; constructor ( sourceLayer , element , assetIds ) { super ( element , sourceLayer . tileWidth , sourceLayer . tileHeight , sourceLayer . mapWidth , sourceLayer . mapHeight ) ; this . element . parentElement . addEventListener ( "mouseup" , e => this . onSourceLayerMouseUp ( e ) ) ; this . element . parentElement . addEventListener ( "contextmenu" , e => e . preventDefault ( ) ) ; this . assetIds = assetIds ; this . obstacle = 1 ; this . isPathfindingOnly = false ; for ( let obj of document . querySelectorAll ( "#obstacle-list > svg" ) ) { obj . addEventListener ( "click" , e => this . onChangeObstacle ( e ) ) ; } } private onSourceLayerMouseUp ( event ) { var rect = this . element . parentElement . getBoundingClientRect ( ) ; var i = Math . floor ( ( event . clientX - rect . left ) / this . tileWidth ) ; var j = Math . floor ( ( event . clientY - rect . top ) / this . tileHeight ) ; if ( this . isPathfindingOnly ) { if ( this . pathPlacingCallback != null ) { this . pathPlacingCallback ( i , j ) ; } return ; } switch ( event . button ) { case 0 : if ( this . objectPracingPredicate != null && this . objectPracingPredicate ( i , j , this . obstacle ) ) { this . placeObject ( i , j ) ; } else { this . removeObject ( i , j ) ; } break ; case 2 : if ( this . pathPlacingCallback != null ) { this . pathPlacingCallback ( i , j ) ; } break ; } } private onChangeObstacle ( event ) { this . obstacle = parseInt ( event . srcElement . getAttribute ( "data-obstacle-value" ) ) ; } public placePath ( path , assetIdSelector ) { for ( let step of path ) { var id = `step-x-${ step . x }-y-${ step . y }` ; var existing = this . element . querySelector ( "#" + id ) ; var assetId = assetIdSelector ( step ) ; if ( existing != null ) { existing . remove ( ) ; } if ( assetId != null && assetId . length > 0 ) { this . placeImage ( step . x , step . y , "path-" + assetId ) . id = id ; } } } public removePath ( path , assetIdSelector ) { for ( let step of path ) { var id = this . removeStep ( step . x , step . y ) ; var assetId = assetIdSelector ( step ) ; if ( assetId != null && assetId . length > 0 ) { this . placeImage ( step . x , step . y , "path-" + assetId ) . id = id ; } } } public removeStep ( x , y ) { var id = `step-x-${ x }-y-${ y }` ; if ( x < 0 || y < 0 || x >= this . mapWidth || y >= this . mapHeight ) { return id ; } var existing = this . element . querySelector ( "#" + id ) ; if ( existing != null ) { existing . remove ( ) ; } return id ; } public placeStep ( x , y , assertId ) { if ( x < 0 || y < 0 || x >= this . mapWidth || y >= this . mapHeight ) { return ; } this . placeImage ( x , y , assertId ) ; } private placeImage ( x , y , assertId ) { var img = document . createElementNS ( "http://www.w3.org/2000/svg" , "use" ) ; img . x . baseVal . value = x * this . tileWidth ; img . y . baseVal . value = y * this . tileHeight ; img . setAttributeNS ( "http://www.w3.org/1999/xlink" , "xlink:href" , "#" + assertId ) ; img . classList . add ( `image-x-${ x }-y-${ y }` ) ; this . element . appendChild ( img ) ; return img ; } public placeObject ( x , y ) { if ( x < 0 || y < 0 || x >= this . mapWidth || y >= this . mapHeight ) { return ; } var id = `object-x-${ x }-y-${ y }` ; var existing = this . element . querySelector ( "#" + id ) ; if ( existing != null ) { existing . remove ( ) ; } this . removeStep ( x , y ) ; this . placeImage ( x , y , this . assetIds [ this . obstacle - 1 ] ) . id = id ; } public removeObject ( x , y ) { if ( x < 0 || y < 0 || x >= this . mapWidth || y >= this . mapHeight ) { return ; } var id = `object-x-${ x }-y-${ y }` ; var existing = this . element . querySelector ( "#" + id ) ; if ( existing != null ) { existing . remove ( ) ; } } public clearMap ( ) { for ( let img of this . element . querySelectorAll ( "use" ) ) { img . remove ( ) ; } } }