2024-07-06 20:28:48 +02:00

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)
})
}
}