Jose Conde 498a8253fd v0.2.0
2024-07-25 16:29:14 +02:00

379 lines
11 KiB
TypeScript

import { Application, Assets, Container, EventEmitter, TilingSprite } from 'pixi.js'
import { Board } from '@/game/Board'
import { assets } from '@/game/utilities/assets'
import { Tile } from '@/game/Tile'
import { Hand } from '@/game/Hand'
import type {
GameDto,
MatchSessionDto,
MatchSessionOptions,
Movement,
PlayerDto,
TileDto,
} from '@/common/interfaces'
import type { SocketIoClientService } from '@/services/SocketIoClientService'
import { wait } from '@/common/helpers'
import { Actions } from 'pixi-actions'
import { OtherHand } from './OtherHand'
import { GameSummayView } from './GameSummayView'
import Config from './Config'
import { createText, grayStyle } from './utilities/fonts'
import { t } from '@/i18n'
import { DIRECTION_INDEXES, DIRECTIONS } from '@/common/constants'
export class Game extends EventEmitter {
public board!: Board
public hand!: Hand
private app: Application = new Application()
private selectedTile: TileDto | undefined
private currentMove: Movement | undefined
private otherHands: OtherHand[] = []
private backgroundLayer: Container = new Container()
private gameSummaryView!: GameSummayView
private players: PlayerDto[] = []
constructor(
private options: MatchSessionOptions,
private socketService: SocketIoClientService | undefined,
private playerId: string,
private sessionId: string,
) {
super()
this.options.screen = {
width: 1280,
height: 720,
handScale: 1,
boardScale: 0.7,
...this.options.screen,
}
}
get stage() {
return this.app.stage
}
async setup(): Promise<HTMLCanvasElement> {
const width = this.options.screen?.width || 1280
const height = this.options.screen?.height || 720
await this.app.init({ width, height })
this.app.ticker.add((tick) => Actions.tick(tick.deltaTime / 60))
return this.app.canvas
}
async start(players: PlayerDto[] = []) {
this.iniialStuff(this.app)
this.board = new Board(this.app, this.options)
this.hand = new Hand(this.app, this.options)
this.otherHands = [
new OtherHand(this.app, 'left'),
new OtherHand(this.app, 'top'),
new OtherHand(this.app, 'right'),
]
this.initPlayers(players)
this.players = players
this.gameSummaryView = new GameSummayView(this.app, this.options)
this.hand.scale = this.options.screen?.handScale || 1
this.board.scale = this.options.screen?.boardScale || 0.7
this.setBoardEvents()
this.setHandEvents()
this.initEventBus()
await wait(500)
this.socketService &&
this.socketService.sendMessage('client:set-client-ready', {
sessionId: this.sessionId,
userId: this.playerId,
})
}
iniialStuff(app: Application) {
app.stage.addChild(this.backgroundLayer)
const background = new TilingSprite({
texture: Assets.get(`bg-${this.options.background}`),
width: app.canvas.width,
height: app.canvas.height,
})
this.backgroundLayer.addChild(background)
const actor = this.options.teamed ? t('team') : t('player')
const type =
this.options.winType === 'points'
? t('n-points', this.options.winTarget)
: t('n-rounds', this.options.winTarget)
const helptext = t('first-actor-to-win-this-options-wintarget-this-options-wintype', [
actor.toLowerCase(),
type.toLowerCase(),
])
this.backgroundLayer.addChild(
createText({
text: `${helptext}`,
x: this.app.canvas.width / 2,
y: 120,
style: grayStyle(14, 'lighter', false),
}),
)
}
initPlayers(players: PlayerDto[]) {
const myIndex = players.findIndex((player) => player.id === this.playerId)
const copy = [...players]
const cut = copy.splice(myIndex)
const player = cut.shift()
const final = cut.concat(copy)
for (let i = 0; i < final.length; i++) {
const hand = this.otherHands[i]
hand.setPlayer(final[i])
}
this.hand.setPlayer(player)
this.board.otherPlayerHands = this.otherHands
}
updateOtherHands(players: PlayerDto[]) {
players.forEach((player) => {
const hand = this.otherHands.find((hand) => hand.player?.id === player.id)
if (hand) {
hand.setHand(player.hand)
}
})
}
highLightPlayer(player: PlayerDto) {
const hand = this.otherHands.find((hand) => hand.player?.id === player.id)
if (hand) {
hand.setActive(true)
}
}
setPlayersInactive() {
this.otherHands.forEach((hand) => hand.setActive(false))
this.hand.setActive(false)
}
async preload() {
await Assets.load(assets)
}
destroy() {
this.removeBoardEvents()
this.removeHandEvents()
this.app.destroy()
}
private initEventBus() {}
private setHandEvents() {
this.hand.on('hand-updated', (tiles: Tile[]) => {
this.board.setPlayerHand(tiles)
})
this.hand.on('game:tile-click', (tile: TileDto) => this.highlightMoves(tile))
this.hand.on('game:timer-timeout', this.handleHandPlayerTimeout.bind(this))
this.hand.on('game:button-pass-click', this.sendPassEvent.bind(this))
this.gameSummaryView.on('finishClick', (data) => {
this.emit('game:finish-click', data)
})
this.gameSummaryView.on('nextClick', (data) => {
this.board.clean()
this.updateScoreboard(data.sessionState)
this.socketService &&
this.socketService.sendMessage('client:set-client-ready-for-next-game', {
userId: this.playerId,
sessionId: this.sessionId,
})
})
this.hand.on('hand-initialized', () => {})
}
private handleHandPlayerTimeout(tile: TileDto | null) {
if (tile === null) {
this.sendPassEvent()
this.hand.disablePassButton()
return
}
const freeEnds = this.board.freeEnds
console.log('freeEnds :>> ', freeEnds, this.playerId)
const move: Movement = {
id: '',
tile,
type: 'right',
playerId: this.playerId,
}
if (freeEnds !== undefined) {
const side = tile.pips?.includes(freeEnds[0]) ? 'left' : 'right'
const direction = side === 'left' ? this.board.leftDirection : this.board.rightDirection
let dirIndex = DIRECTION_INDEXES[direction]
const validMoves = this.board.nextTileValidMoves(tile, side)
const validPoints = this.board.nextTileValidPoints(tile, side, validMoves)
let safeCount = 4
while (safeCount > 0 && !validMoves[dirIndex % 4]) {
dirIndex += 1
safeCount -= 1
}
console.log('validMoves :>> ', validMoves)
console.log('validPoints :>> ', validPoints)
const validPoint = validPoints[dirIndex % 4]
if (validPoint !== undefined) {
move.x = validPoint[0]
move.y = validPoint[1]
}
move.direction = DIRECTIONS[dirIndex % 4]
move.type = side
}
this.currentMove = move
this.board.updateBoard(move, this.hand.tileMoved(tile))
}
private async sendPassEvent() {
const move: Movement = {
id: '',
type: 'pass',
playerId: this.playerId,
}
this.socketService &&
this.socketService.sendMessage('client:player-move', {
sessionId: this.sessionId,
move: move,
})
await this.board.updateBoard(move, undefined)
}
private updateScoreboard(sessionState: MatchSessionDto) {
const scoreboard = sessionState.scoreboard
const myScore = scoreboard.find((d) => d.id === this.playerId)?.score || 0
this.hand.setScore(myScore)
this.otherHands.forEach((otherHand) => {
const player: PlayerDto | undefined = otherHand.player
const name: string = player?.name || ''
const score = scoreboard.find((d) => d.name === name)?.score || 0
otherHand.setScore(score)
})
}
highlightMoves(tile: TileDto) {
this.selectedTile = tile
if (tile !== undefined) {
this.board.setMovesForTile(this.getMoves(tile), tile)
} else {
this.board.cleanInteractions()
}
}
getMoves(tile: any): [boolean, boolean] {
if (tile === undefined) return [false, false]
if (this.board.count === 0) return [false, true]
const validEnds: [boolean, boolean] = [false, false]
const freeEnds = this.board.freeEnds
if (freeEnds !== undefined) {
if (tile.pips != undefined) {
validEnds[0] = tile.pips.includes(freeEnds[0])
validEnds[1] = tile.pips.includes(freeEnds[1])
}
}
return validEnds
}
async setNextPlayer(state: GameDto) {
const currentPlayer = state?.currentPlayer!
this.setPlayersInactive()
if (currentPlayer.id === this.playerId) {
this.hand.setActive(true)
this.hand.prepareForMove(this.board.count === 0, this.board.freeEnds)
this.board.setPlayerTurn(currentPlayer)
} else {
this.board.setServerPlayerTurn(currentPlayer)
this.highLightPlayer(currentPlayer)
}
}
private setBoardEvents() {
this.board.on('game:board-left-action-click', async (data) => {
if (this.selectedTile === undefined) return
const move: Movement = {
tile: this.selectedTile,
type: 'left',
playerId: this.playerId,
...data,
}
this.currentMove = move
const tile = this.hand.tileMoved(this.selectedTile)
await this.board.updateBoard({ ...move, tile: this.selectedTile }, tile)
})
this.board.on('game:board-right-action-click', async (data) => {
if (this.selectedTile === undefined) return
const move: Movement = {
tile: this.selectedTile,
type: 'right',
playerId: this.playerId,
...data,
}
this.currentMove = move
const tile = this.hand.tileMoved(this.selectedTile)
await this.board.updateBoard({ ...move, tile: this.selectedTile }, tile)
})
this.board.on('game:tile-animation-ended', async (tile) => {
if (tile !== null && tile !== undefined && tile.playerId === this.playerId) {
this.socketService &&
this.socketService.sendMessage('client:player-move', {
sessionId: this.sessionId,
move: this.currentMove,
})
} else {
this.socketService &&
this.socketService.sendMessage('client:animation-ended', {
sessionId: this.sessionId,
userId: this.playerId,
})
}
})
}
async gameFinished(data: any) {
await wait(Config.waitMillisToShowSummary)
this.updateOtherHands(data.lastGame.players)
this.hand.gameFinished()
this.board.gameFinished()
this.setPlayersInactive()
this.gameSummaryView.setGameSummary(data, 'round')
}
async matchFinished(data: any) {
await wait(Config.waitMillisToShowSummary)
this.updateOtherHands(data.lastGame.players)
this.board.matchFinished()
this.gameSummaryView.setGameSummary(data, 'match')
}
serverPlayerMove(data: any, playerId: string) {
this.board.playerMove(data, playerId)
if (!(data.move === undefined || data.move === null)) {
const otherHand = this.otherHands.find((hand) => hand.player?.id === data.move.playerId)
if (otherHand) {
otherHand.update(data.move)
}
} else {
// ShowPassed
}
}
private removeBoardEvents() {
this.board.off('game:board-left-action-click')
this.board.off('game:board-right-action-click')
}
private removeHandEvents() {
this.hand.off('game:tile-click')
this.hand.off('game:button-pass-click')
}
}