379 lines
11 KiB
TypeScript
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')
|
|
}
|
|
}
|