247 lines
6.3 KiB
TypeScript
247 lines
6.3 KiB
TypeScript
import {
|
|
Application,
|
|
Container,
|
|
EventEmitter,
|
|
Graphics,
|
|
Sprite,
|
|
Text,
|
|
Texture,
|
|
Ticker
|
|
} from 'pixi.js'
|
|
import { Tile } from '@/game/Tile'
|
|
import type { Dimension, PlayerDto, TileDto } from '@/common/interfaces'
|
|
import { GlowFilter } from 'pixi-filters'
|
|
import { Scale, type ScaleFunction } from './utilities/scale'
|
|
import { LoggingService } from '@/services/LoggingService'
|
|
|
|
export class Hand extends EventEmitter {
|
|
tiles: Tile[] = []
|
|
container: Container = new Container()
|
|
buttonPass: Container = new Container()
|
|
buttonNext: Container = new Container()
|
|
height: number
|
|
width: number
|
|
ticker: Ticker
|
|
lastTimeClicked: number = 0
|
|
doubleClickThreshold: number = 300
|
|
initialized: boolean = false
|
|
_canMove: boolean = false
|
|
scale: number = 1
|
|
scaleY!: ScaleFunction
|
|
scaleX!: ScaleFunction
|
|
grain: number = 25
|
|
logger: LoggingService = new LoggingService()
|
|
|
|
constructor(app: Application) {
|
|
super()
|
|
app.stage.addChild(this.container)
|
|
this.ticker = app.ticker
|
|
this.height = 130
|
|
this.width = app.canvas.width
|
|
this.container.y = app.canvas.height - this.height
|
|
this.container.width = this.width
|
|
this.container.height = this.height
|
|
this.calculateScale()
|
|
this.addBg()
|
|
}
|
|
|
|
gameFinished() {
|
|
this.logger.debug('gameFinished')
|
|
this.tiles = []
|
|
this.container.removeChildren()
|
|
this.initialized = false
|
|
this.buttonNext = this.createButton(
|
|
'NEXT',
|
|
{ x: this.width / 2 - 25, y: this.height / 2, width: 50, height: 20 },
|
|
'nextClick'
|
|
)
|
|
}
|
|
|
|
get canMove() {
|
|
return this._canMove
|
|
}
|
|
|
|
private calculateScale() {
|
|
const scaleXSteps = Math.floor(this.width / (this.grain * this.scale)) / 2
|
|
const scaleYSteps = Math.floor(this.height / (this.grain * this.scale)) / 2
|
|
this.scaleX = Scale([-scaleXSteps, scaleXSteps], [0, this.width])
|
|
this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
|
|
}
|
|
|
|
set canMove(value: boolean) {
|
|
this._canMove = value
|
|
if (value) {
|
|
this.createPassButton()
|
|
} else {
|
|
this.container.removeChild(this.buttonPass)
|
|
}
|
|
|
|
this.tiles.forEach((tile) => {
|
|
tile.interactive = value
|
|
})
|
|
}
|
|
|
|
initialize(playerState: PlayerDto) {
|
|
this.tiles = this.createTiles(playerState)
|
|
this.emit('handUpdated', this.tiles)
|
|
this.initialized = this.tiles.length > 0
|
|
this.renderTiles()
|
|
}
|
|
|
|
private addBg() {
|
|
const bg = new Sprite(Texture.WHITE)
|
|
bg.alpha = 0.08
|
|
bg.width = this.width
|
|
bg.height = this.height
|
|
this.container.addChild(bg)
|
|
}
|
|
|
|
private onTileClick(tile: Tile) {
|
|
// if (Date.now() - this.lastTimeClicked < this.doubleClickThreshold) {
|
|
// this.emit('tileDoubleClick', { id: tile.id })
|
|
// return
|
|
// }
|
|
|
|
const selected = this.tiles.find((t) => t.selected)
|
|
if (selected) {
|
|
this.deselectTile(selected)
|
|
if (selected.id === tile.id) {
|
|
this.emit('tileClick')
|
|
return
|
|
}
|
|
}
|
|
|
|
tile.selected = true
|
|
tile.alpha = 1
|
|
|
|
tile.animateTo(tile.x, tile.y - 10)
|
|
this.emit('tileClick', tile.toPlain())
|
|
}
|
|
|
|
private deselectTile(selected: Tile) {
|
|
selected.animateTo(selected.x, selected.y + 10)
|
|
selected.selected = false
|
|
selected.alpha = 0.7
|
|
}
|
|
|
|
public tileMoved(tileDto: TileDto) {
|
|
const tile = this.tiles.find((t) => t.id === tileDto.id)
|
|
|
|
if (!tile) return
|
|
|
|
tile.interactive = false
|
|
tile.clearFilters()
|
|
tile.off('pointerdown')
|
|
tile.off('pointerover')
|
|
tile.off('pointerout')
|
|
}
|
|
|
|
private createButton(
|
|
textStr: string,
|
|
dimension: Dimension,
|
|
action: string | Function
|
|
): Container {
|
|
const { x, y, width, height } = dimension
|
|
const rectangle = new Graphics().roundRect(x, y, width + 4, height + 4, 5).fill(0xffff00)
|
|
const text = new Text({
|
|
text: textStr,
|
|
style: {
|
|
fontFamily: 'Arial',
|
|
fontSize: 12,
|
|
fontWeight: 'bold',
|
|
fill: 0x121212,
|
|
align: 'center'
|
|
}
|
|
})
|
|
text.anchor = 0.5
|
|
const container = new Container()
|
|
container.addChild(rectangle)
|
|
container.addChild(text)
|
|
|
|
text.y = y + height / 2
|
|
text.x = x + width / 2
|
|
|
|
container.eventMode = 'static'
|
|
container.cursor = 'pointer'
|
|
rectangle.alpha = 0.7
|
|
text.alpha = 0.7
|
|
container.on('pointerdown', () => {
|
|
action instanceof Function ? action() : this.emit(action)
|
|
})
|
|
container.on('pointerover', () => {
|
|
rectangle.alpha = 1
|
|
text.alpha = 1
|
|
})
|
|
|
|
container.on('pointerout', () => {
|
|
rectangle.alpha = 0.7
|
|
text.alpha = 0.7
|
|
})
|
|
|
|
this.container.addChild(container)
|
|
return container
|
|
}
|
|
|
|
private createPassButton() {
|
|
const lastTile = this.tiles[this.tiles.length - 1]
|
|
const x = lastTile ? lastTile.x + lastTile.width : this.scaleX(0)
|
|
this.buttonPass = this.createButton(
|
|
'PASS',
|
|
{ x, y: this.height / 2, width: 50, height: 20 },
|
|
'passClick'
|
|
)
|
|
}
|
|
|
|
update(playerState: PlayerDto) {
|
|
if (!this.initialized) {
|
|
this.initialize(playerState)
|
|
return
|
|
}
|
|
const missing: Tile | undefined = this.tiles.find(
|
|
(tile: Tile) => !playerState.hand.find((t) => t.id === tile.id)
|
|
)
|
|
if (missing) {
|
|
this.container.removeChild(missing.getSprite())
|
|
this.tiles = this.tiles.filter((tile) => tile.id !== missing.id)
|
|
this.emit('handUpdated', this.tiles)
|
|
}
|
|
this.renderTiles()
|
|
}
|
|
|
|
private createTiles(playerState: PlayerDto) {
|
|
return playerState.hand.map((tile) => {
|
|
const newTile: Tile = new Tile(tile.id, this.ticker, tile.pips, this.scale)
|
|
newTile.alpha = 0.7
|
|
newTile.anchor = 0.5
|
|
newTile.addTo(this.container)
|
|
newTile.on('pointerdown', () => this.onTileClick(newTile))
|
|
newTile.on('pointerover', () => {
|
|
newTile.alpha = 1
|
|
newTile.setFilters([
|
|
new GlowFilter({
|
|
distance: 10,
|
|
outerStrength: 2,
|
|
innerStrength: 1,
|
|
color: 0xffffff,
|
|
quality: 0.5
|
|
})
|
|
])
|
|
})
|
|
newTile.on('pointerout', () => {
|
|
if (!newTile.selected) {
|
|
newTile.alpha = 0.7
|
|
newTile.getSprite().filters = []
|
|
}
|
|
})
|
|
return newTile
|
|
})
|
|
}
|
|
|
|
renderTiles() {
|
|
const deltaX = this.width / 2 - (this.tiles.length * 50 - 5) / 2
|
|
this.tiles.forEach((tile, i) => {
|
|
tile.setPosition(deltaX + tile.width / 2 + i * (tile.width + 5), tile.height / 2 + 20)
|
|
})
|
|
}
|
|
}
|