reworked event communication
This commit is contained in:
parent
d862f94f74
commit
1b058db6c0
2
.env
2
.env
@ -1,2 +1,2 @@
|
|||||||
VITE_LOG_LEVEL= 'debug'
|
VITE_LOG_LEVEL= 'error'
|
||||||
VITE_API_URL= 'http://localhost:3000/api'
|
VITE_API_URL= 'http://localhost:3000/api'
|
9
package-lock.json
generated
9
package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pino": "^9.2.0",
|
"pino": "^9.2.0",
|
||||||
|
"pixi-actions": "^1.1.11",
|
||||||
"pixi-filters": "^6.0.4",
|
"pixi-filters": "^6.0.4",
|
||||||
"pixi.js": "^8.2.1",
|
"pixi.js": "^8.2.1",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
@ -3809,6 +3810,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
|
||||||
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="
|
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/pixi-actions": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pixi-actions/-/pixi-actions-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-03gMHGZsMg0KrIuDMUUPHq0mnjBjDBXJsrJkhrQO0o1hzsJ1ejcn9zhHRp99oqHkyicLuVJu5iBmblMg0xEYBA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pixi.js": ">7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pixi-filters": {
|
"node_modules/pixi-filters": {
|
||||||
"version": "6.0.4",
|
"version": "6.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-6.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-6.0.4.tgz",
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pino": "^9.2.0",
|
"pino": "^9.2.0",
|
||||||
|
"pixi-actions": "^1.1.11",
|
||||||
"pixi-filters": "^6.0.4",
|
"pixi-filters": "^6.0.4",
|
||||||
"pixi.js": "^8.2.1",
|
"pixi.js": "^8.2.1",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
|
21
src/App.vue
21
src/App.vue
@ -1,10 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject } from 'vue'
|
import { inject, onMounted, onUnmounted } from 'vue'
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
import type { AuthenticationService } from './services/AuthenticationService'
|
import type { AuthenticationService } from './services/AuthenticationService'
|
||||||
|
import { useEventBusStore } from './stores/eventBus'
|
||||||
|
|
||||||
const auth: AuthenticationService = inject<AuthenticationService>('auth') as AuthenticationService
|
const auth: AuthenticationService = inject<AuthenticationService>('auth') as AuthenticationService
|
||||||
auth.fromStorage()
|
auth.fromStorage()
|
||||||
|
const eventBus = useEventBusStore()
|
||||||
|
|
||||||
|
const handleBeforeUnload = (evt: any) => {
|
||||||
|
// evt.preventDefault()
|
||||||
|
// const isGame = location.pathname === '/game' ? true : ''
|
||||||
|
// console.log('isGame :>> ', isGame)
|
||||||
|
// evt.returnValue = isGame
|
||||||
|
// eventBus.publish('window-before-unload')
|
||||||
|
// console.log('location.href :>> ', location.pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
BIN
src/assets/images/backgrounds/bg-green.png
Normal file
BIN
src/assets/images/backgrounds/bg-green.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 MiB |
@ -1,7 +1,13 @@
|
|||||||
export const defaultContainerOptions = {
|
export const DEFAULT_CONTAINER_OPTIONS = {
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
visible: true
|
visible: true
|
||||||
}
|
}
|
||||||
|
export const ORIENTATION_ANGLES: { [key: string]: number } = {
|
||||||
|
north: 0,
|
||||||
|
east: Math.PI / 2,
|
||||||
|
south: Math.PI,
|
||||||
|
west: (3 * Math.PI) / 2
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Graphics, Container } from 'pixi.js'
|
import { Graphics, Container, Text } from 'pixi.js'
|
||||||
import type { ContainerOptions, TileDto } from './interfaces'
|
import type { ContainerOptions, Dimension, TileDto } from './interfaces'
|
||||||
import { defaultContainerOptions } from './constants'
|
import { DEFAULT_CONTAINER_OPTIONS } from './constants'
|
||||||
|
|
||||||
export function getColorBackground(container: Container, colorName: string, alpha: number = 0.5) {
|
export function getColorBackground(container: Container, colorName: string, alpha: number = 0.5) {
|
||||||
const graphics = new Graphics()
|
const graphics = new Graphics()
|
||||||
@ -14,7 +14,7 @@ export function getColorBackground(container: Container, colorName: string, alph
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createContainer(options: ContainerOptions) {
|
export function createContainer(options: ContainerOptions) {
|
||||||
const opts = { ...defaultContainerOptions, ...options }
|
const opts = { ...DEFAULT_CONTAINER_OPTIONS, ...options }
|
||||||
const container = new Container()
|
const container = new Container()
|
||||||
|
|
||||||
const rect = new Graphics().rect(opts.x, opts.y, opts.width, opts.height)
|
const rect = new Graphics().rect(opts.x, opts.y, opts.width, opts.height)
|
||||||
@ -30,6 +30,54 @@ export function createContainer(options: ContainerOptions) {
|
|||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createButton(
|
||||||
|
textStr: string,
|
||||||
|
dimension: Dimension,
|
||||||
|
action: Function,
|
||||||
|
parent?: Container
|
||||||
|
): 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())
|
||||||
|
container.on('pointerover', () => {
|
||||||
|
rectangle.alpha = 1
|
||||||
|
text.alpha = 1
|
||||||
|
})
|
||||||
|
|
||||||
|
container.on('pointerout', () => {
|
||||||
|
rectangle.alpha = 0.7
|
||||||
|
text.alpha = 0.7
|
||||||
|
})
|
||||||
|
|
||||||
|
if (parent !== undefined) {
|
||||||
|
parent.addChild(container)
|
||||||
|
}
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
export async function wait(ms: number) {
|
export async function wait(ms: number) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,10 @@ export interface TileDto {
|
|||||||
y?: number
|
y?: number
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
|
playerId?: string
|
||||||
}
|
}
|
||||||
export interface MatchSessionState {
|
export interface MatchSessionDto {
|
||||||
|
_id: string
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
creator: string
|
creator: string
|
||||||
@ -38,7 +40,7 @@ export interface MatchSessionState {
|
|||||||
playersReady: number
|
playersReady: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameState {
|
export interface GameDto {
|
||||||
id: string
|
id: string
|
||||||
players: PlayerDto[]
|
players: PlayerDto[]
|
||||||
tilesInBoneyard: TileDto[]
|
tilesInBoneyard: TileDto[]
|
||||||
@ -85,3 +87,13 @@ export interface SocketEvent {
|
|||||||
event: string
|
event: string
|
||||||
data: any
|
data: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AnimationOptions {
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
rotation?: number
|
||||||
|
scale?: number
|
||||||
|
duration?: number
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MatchSessionState, GameState, PlayerDto } from '@/common/interfaces'
|
import type { MatchSessionDto, GameDto, PlayerDto } from '@/common/interfaces'
|
||||||
import { onMounted, onUnmounted, ref, watch, inject } from 'vue'
|
import { onMounted, onUnmounted, ref, watch, inject } from 'vue'
|
||||||
import { Game } from '@/game/Game'
|
import { Game } from '@/game/Game'
|
||||||
import { useGameStore } from '@/stores/game'
|
import { useGameStore } from '@/stores/game'
|
||||||
@ -13,7 +13,8 @@ const socketService: any = inject('socket')
|
|||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const eventBus = useEventBusStore()
|
const eventBus = useEventBusStore()
|
||||||
const { playerState, sessionState } = storeToRefs(gameStore)
|
const { playerState, sessionState, canMakeMove } = storeToRefs(gameStore)
|
||||||
|
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
|
||||||
|
|
||||||
let appEl = ref<HTMLElement | null>(null)
|
let appEl = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
@ -30,6 +31,31 @@ const game = new Game(
|
|||||||
sessionState.value?.id || ''
|
sessionState.value?.id || ''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// watch(
|
||||||
|
// () => gameStore.gameState,
|
||||||
|
// async (value: GameDto | undefined) => {
|
||||||
|
// if (value === undefined) return
|
||||||
|
// await game.board?.setState(value)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// watch(
|
||||||
|
// () => gameStore.sessionState,
|
||||||
|
// (value: MatchSessionDto | undefined) => {
|
||||||
|
// if (value === undefined) return
|
||||||
|
|
||||||
|
// // logger.debug('gameSessionState-------------------------------------- :>> ', value)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// watch(
|
||||||
|
// () => gameStore.playerState,
|
||||||
|
// (value: PlayerDto | undefined) => {
|
||||||
|
// if (value === undefined) return
|
||||||
|
// game.hand.update(value as PlayerDto)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (appEl.value === null) return
|
if (appEl.value === null) return
|
||||||
const canvas = await game.setup()
|
const canvas = await game.setup()
|
||||||
@ -37,41 +63,41 @@ onMounted(async () => {
|
|||||||
await game.preload()
|
await game.preload()
|
||||||
await game.start()
|
await game.start()
|
||||||
|
|
||||||
eventBus.subscribe('game-finished', () => {
|
eventBus.subscribe('server:game-finished', (data) => {
|
||||||
game.gameFinished()
|
console.log('server:game-finished :>> ', data)
|
||||||
|
game.gameFinished(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
eventBus.subscribe('server:server-player-move', (data) => {
|
||||||
() => gameStore.canMakeMove,
|
console.log('server:player-move :>> ', data)
|
||||||
(value: boolean) => {
|
game.serverPlayerMove(data, playerState.value?.id ?? '')
|
||||||
game.setCanMakeMove(value)
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
eventBus.subscribe('game:player-turn-started', (data: any) => {
|
||||||
() => gameStore.gameState,
|
console.log('game:player-turn-started :>> ', data)
|
||||||
(value: GameState | undefined) => {
|
// game.setCanMakeMove(true)
|
||||||
if (value === undefined) return
|
})
|
||||||
game.board?.setState(value, playerState.value?.id ?? '')
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
eventBus.subscribe('server:hand-dealt', (playerState: PlayerDto) => {
|
||||||
() => gameStore.sessionState,
|
console.log('server:hand-dealt :>> ', playerState)
|
||||||
(value: MatchSessionState | undefined) => {
|
game.hand.update(playerState)
|
||||||
if (value === undefined) return
|
})
|
||||||
|
|
||||||
// logger.debug('gameSessionState-------------------------------------- :>> ', value)
|
eventBus.subscribe('server:next-turn', (gameState: GameDto) => {
|
||||||
}
|
console.log('server:next-turn :>> ', gameState)
|
||||||
)
|
updateGameState(gameState)
|
||||||
|
game.setNextPlayer(gameState)
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
eventBus.subscribe('server:match-finished', (data) => {
|
||||||
() => gameStore.playerState,
|
console.log('server:match-finished :>> ', data)
|
||||||
(value: PlayerDto | undefined) => {
|
game.matchFinished(data)
|
||||||
if (value === undefined) return
|
})
|
||||||
game.hand.update(value as PlayerDto)
|
|
||||||
}
|
// eventBus.subscribe('server:game-state', (data) => {
|
||||||
)
|
// console.log('server:game-state :>> ', data)
|
||||||
|
// game.serverGameState(data)
|
||||||
|
// })
|
||||||
|
|
||||||
// mockMove(game, [6, 6], 'left')
|
// mockMove(game, [6, 6], 'left')
|
||||||
// mockMove(game, [6, 4], 'left')
|
// mockMove(game, [6, 4], 'left')
|
||||||
|
@ -9,13 +9,14 @@ import {
|
|||||||
Ticker
|
Ticker
|
||||||
} from 'pixi.js'
|
} from 'pixi.js'
|
||||||
import { Scale, type ScaleFunction } from '@/game/utilities/scale'
|
import { Scale, type ScaleFunction } from '@/game/utilities/scale'
|
||||||
import type { GameState, Movement, TileDto } from '@/common/interfaces'
|
import type { AnimationOptions, Movement, PlayerDto, TileDto } from '@/common/interfaces'
|
||||||
import { Tile } from '@/game/Tile'
|
import { Tile } from '@/game/Tile'
|
||||||
import { DIRECTIONS, createContainer, isTilePair, isTileVertical } from '@/common/helpers'
|
import { DIRECTIONS, createContainer, isTilePair } from '@/common/helpers'
|
||||||
import { createText } from '@/game/utilities/fonts'
|
import { createText } from '@/game/utilities/fonts'
|
||||||
import { LoggingService } from '@/services/LoggingService'
|
import { LoggingService } from '@/services/LoggingService'
|
||||||
import { inject } from 'vue'
|
import { inject } from 'vue'
|
||||||
import { GlowFilter } from 'pixi-filters'
|
import { GlowFilter } from 'pixi-filters'
|
||||||
|
import { ORIENTATION_ANGLES } from '@/common/constants'
|
||||||
|
|
||||||
export class Board extends EventEmitter {
|
export class Board extends EventEmitter {
|
||||||
private _scale: number = 1
|
private _scale: number = 1
|
||||||
@ -28,7 +29,6 @@ export class Board extends EventEmitter {
|
|||||||
grain: number = 25
|
grain: number = 25
|
||||||
scaleY: ScaleFunction
|
scaleY: ScaleFunction
|
||||||
scaleX: ScaleFunction
|
scaleX: ScaleFunction
|
||||||
state?: GameState
|
|
||||||
container!: Container
|
container!: Container
|
||||||
initialContainer!: Container
|
initialContainer!: Container
|
||||||
tilesContainer!: Container
|
tilesContainer!: Container
|
||||||
@ -49,6 +49,7 @@ export class Board extends EventEmitter {
|
|||||||
rightDirection: string = 'east'
|
rightDirection: string = 'east'
|
||||||
playerHand: Tile[] = []
|
playerHand: Tile[] = []
|
||||||
firstTile?: Tile
|
firstTile?: Tile
|
||||||
|
currentPlayer!: PlayerDto
|
||||||
|
|
||||||
constructor(app: Application) {
|
constructor(app: Application) {
|
||||||
super()
|
super()
|
||||||
@ -65,7 +66,7 @@ export class Board extends EventEmitter {
|
|||||||
parent: app.stage
|
parent: app.stage
|
||||||
})
|
})
|
||||||
|
|
||||||
const background = new Sprite(Assets.get('bg-1'))
|
const background = new Sprite(Assets.get('bg-green'))
|
||||||
// background.width = this.width
|
// background.width = this.width
|
||||||
// background.height = this.height
|
// background.height = this.height
|
||||||
this.container.addChild(background)
|
this.container.addChild(background)
|
||||||
@ -160,10 +161,12 @@ export class Board extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(state: GameState, playerId: string) {
|
async setServerPlayerTurn(currentPlayer: PlayerDto) {
|
||||||
this.state = state
|
this.showText(`${currentPlayer.name}'s turn!\n Please wait...`)
|
||||||
const { lastMove } = state
|
}
|
||||||
|
|
||||||
|
async playerMove(move: any, playerId: string) {
|
||||||
|
const { move: lastMove } = move
|
||||||
if (lastMove === null) {
|
if (lastMove === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -177,12 +180,13 @@ export class Board extends EventEmitter {
|
|||||||
this.nextTile = tile
|
this.nextTile = tile
|
||||||
lastMove.tile = tile.toPlain()
|
lastMove.tile = tile.toPlain()
|
||||||
this.movements.push(lastMove)
|
this.movements.push(lastMove)
|
||||||
this.addTile(tile, lastMove)
|
await this.addTile(tile, lastMove)
|
||||||
this.setFreeEnd(lastMove)
|
this.setFreeEnd(lastMove)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addTile(tile: Tile, move: Movement) {
|
async addTile(tile: Tile, move: Movement) {
|
||||||
|
console.log('adding tile', tile.pips)
|
||||||
let orientation = ''
|
let orientation = ''
|
||||||
let x: number =
|
let x: number =
|
||||||
move.type === 'left'
|
move.type === 'left'
|
||||||
@ -244,14 +248,25 @@ export class Board extends EventEmitter {
|
|||||||
availablePosition && ([x, y] = availablePosition)
|
availablePosition && ([x, y] = availablePosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const endTile = isLeft ? this.leftTile : this.rightTile
|
tile.addTo(this.tilesContainer)
|
||||||
const isEndVertical = endTile?.isVertical() ?? false
|
|
||||||
const isNextVertical = orientation === 'north' || orientation === 'south'
|
|
||||||
tile.setPosition(this.scaleX(x), this.scaleY(y))
|
|
||||||
tile.setOrientation(orientation)
|
|
||||||
tile.reScale(this.scale)
|
tile.reScale(this.scale)
|
||||||
this.tiles.push(tile)
|
this.tiles.push(tile)
|
||||||
tile.addTo(this.tilesContainer)
|
|
||||||
|
const animation: AnimationOptions = {
|
||||||
|
x: this.scaleX(x),
|
||||||
|
y: this.scaleY(y),
|
||||||
|
rotation: ORIENTATION_ANGLES[orientation],
|
||||||
|
duration: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.setPosition(this.scaleX(x), this.scaleY(y))
|
||||||
|
tile.setOrientation(orientation)
|
||||||
|
|
||||||
|
// tile.setPosition(this.scaleX(0), this.height + tile.height / 2)
|
||||||
|
// console.log('going to animate', tile.pips)
|
||||||
|
// await tile.animateTo(animation)
|
||||||
|
// console.log('animated', tile.pips)
|
||||||
|
this.emit('game:tile-animation-ended', tile.toPlain())
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlayedTile(id: string): Tile | undefined {
|
getPlayedTile(id: string): Tile | undefined {
|
||||||
@ -300,7 +315,7 @@ export class Board extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBoard(move: Movement) {
|
async updateBoard(move: Movement) {
|
||||||
try {
|
try {
|
||||||
const { tile: tileDto } = move
|
const { tile: tileDto } = move
|
||||||
const tile = this.getTileInHand(tileDto?.id ?? '')
|
const tile = this.getTileInHand(tileDto?.id ?? '')
|
||||||
@ -310,7 +325,7 @@ export class Board extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.movements.push(move)
|
this.movements.push(move)
|
||||||
this.addTile(tile, move)
|
await this.addTile(tile, move)
|
||||||
this.setFreeEnd(move)
|
this.setFreeEnd(move)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error, 'Error updating board')
|
this.logger.error(error, 'Error updating board')
|
||||||
@ -437,7 +452,7 @@ export class Board extends EventEmitter {
|
|||||||
dot.alpha = 0.1
|
dot.alpha = 0.1
|
||||||
dot.interactive = true
|
dot.interactive = true
|
||||||
dot.on('pointerdown', () => {
|
dot.on('pointerdown', () => {
|
||||||
this.emit(`${side}Click`, direction && { direction, x, y })
|
this.emit(`game:board-${side}-action-click`, direction && { direction, x, y })
|
||||||
this.cleanInteractions()
|
this.cleanInteractions()
|
||||||
})
|
})
|
||||||
dot.on('pointerover', () => {
|
dot.on('pointerover', () => {
|
||||||
@ -566,7 +581,8 @@ export class Board extends EventEmitter {
|
|||||||
return [canPlayNorth, canPlayEast, canPlaySouth, canPlayWest]
|
return [canPlayNorth, canPlayEast, canPlaySouth, canPlayWest]
|
||||||
}
|
}
|
||||||
|
|
||||||
gameFinished() {
|
gameFinished(data: any) {
|
||||||
|
const { lastGame, gameState } = data
|
||||||
this.tiles = []
|
this.tiles = []
|
||||||
this.boneyard = []
|
this.boneyard = []
|
||||||
this.movements = []
|
this.movements = []
|
||||||
@ -580,6 +596,10 @@ export class Board extends EventEmitter {
|
|||||||
this.firstTile = undefined
|
this.firstTile = undefined
|
||||||
this.tilesContainer.removeChildren()
|
this.tilesContainer.removeChildren()
|
||||||
this.interactionContainer.removeChildren()
|
this.interactionContainer.removeChildren()
|
||||||
this.showText('Game finished')
|
this.showText(`Game finished \n Winner ${lastGame?.winner?.name ?? 'No winner'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchFinished(data: any) {
|
||||||
|
// this.showText(`Game finished \n Winner ${lastGame?.winner?.name ?? 'No winner'}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
130
src/game/Game.ts
130
src/game/Game.ts
@ -3,14 +3,19 @@ import { Board } from '@/game/Board'
|
|||||||
import { assets } from '@/game/utilities/assets'
|
import { assets } from '@/game/utilities/assets'
|
||||||
import { Tile } from '@/game/Tile'
|
import { Tile } from '@/game/Tile'
|
||||||
import { Hand } from '@/game/Hand'
|
import { Hand } from '@/game/Hand'
|
||||||
import type { Movement, TileDto } from '@/common/interfaces'
|
import type { GameDto, Movement, PlayerDto, TileDto } from '@/common/interfaces'
|
||||||
import type { SocketIoClientService } from '@/services/SocketIoClientService'
|
import type { SocketIoClientService } from '@/services/SocketIoClientService'
|
||||||
|
import { useEventBusStore } from '@/stores/eventBus'
|
||||||
|
import { wait } from '@/common/helpers'
|
||||||
|
import { Actions } from 'pixi-actions'
|
||||||
|
|
||||||
export class Game {
|
export class Game {
|
||||||
public board!: Board
|
public board!: Board
|
||||||
public hand!: Hand
|
public hand!: Hand
|
||||||
private app: Application = new Application()
|
private app: Application = new Application()
|
||||||
private selectedTile: TileDto | undefined
|
private selectedTile: TileDto | undefined
|
||||||
|
private eventBus: any = useEventBusStore()
|
||||||
|
private currentMove: Movement | undefined
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private options: { boardScale: number; handScale: number; width: number; height: number } = {
|
private options: { boardScale: number; handScale: number; width: number; height: number } = {
|
||||||
@ -30,16 +35,23 @@ export class Game {
|
|||||||
const height = 800
|
const height = 800
|
||||||
|
|
||||||
await this.app.init({ width, height })
|
await this.app.init({ width, height })
|
||||||
|
this.app.ticker.add((tick) => Actions.tick(tick.deltaTime / 60))
|
||||||
return this.app.canvas
|
return this.app.canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
async start() {
|
||||||
this.board = new Board(this.app)
|
this.board = new Board(this.app)
|
||||||
this.hand = new Hand(this.app)
|
this.hand = new Hand(this.app)
|
||||||
this.hand.scale = this.options.handScale
|
this.hand.scale = this.options.handScale
|
||||||
this.board.scale = this.options.boardScale
|
this.board.scale = this.options.boardScale
|
||||||
this.setBoardEvents()
|
this.setBoardEvents()
|
||||||
this.setHandEvents()
|
this.setHandEvents()
|
||||||
|
this.initEventBus()
|
||||||
|
wait(3000)
|
||||||
|
this.socketService.sendMessage('client:set-client-ready', {
|
||||||
|
sessionId: this.sessionId,
|
||||||
|
userId: this.playerId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async preload() {
|
async preload() {
|
||||||
@ -52,35 +64,44 @@ export class Game {
|
|||||||
this.app.destroy()
|
this.app.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private initEventBus() {}
|
||||||
|
|
||||||
private setHandEvents() {
|
private setHandEvents() {
|
||||||
this.hand.on('handUpdated', (tiles: Tile[]) => {
|
this.hand.on('hand-updated', (tiles: Tile[]) => {
|
||||||
this.board.setPlayerHand(tiles)
|
this.board.setPlayerHand(tiles)
|
||||||
})
|
})
|
||||||
this.hand.on('tileClick', (tile: TileDto) => {
|
this.hand.on('game:tile-click', (tile: TileDto) => this.highlightMoves(tile))
|
||||||
|
|
||||||
|
this.hand.on('game:button-pass-click', async () => {
|
||||||
|
const move: Movement = {
|
||||||
|
id: '',
|
||||||
|
type: 'pass',
|
||||||
|
playerId: this.playerId
|
||||||
|
}
|
||||||
|
this.socketService.sendMessage('client:player-move', {
|
||||||
|
sessionId: this.sessionId,
|
||||||
|
move: move
|
||||||
|
})
|
||||||
|
await this.board.updateBoard(move)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hand.on('nextClick', () => {
|
||||||
|
this.socketService.sendMessage('client:set-player-ready', {
|
||||||
|
userId: this.playerId,
|
||||||
|
sessionId: this.sessionId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hand.on('hand-initialized', () => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightMoves(tile: TileDto) {
|
||||||
this.selectedTile = tile
|
this.selectedTile = tile
|
||||||
if (tile !== undefined) {
|
if (tile !== undefined) {
|
||||||
this.board.setMovesForTile(this.getMoves(tile), tile)
|
this.board.setMovesForTile(this.getMoves(tile), tile)
|
||||||
} else {
|
} else {
|
||||||
this.board.cleanInteractions()
|
this.board.cleanInteractions()
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
this.hand.on('passClick', () => {
|
|
||||||
const move: Movement = {
|
|
||||||
id: '',
|
|
||||||
type: 'pass',
|
|
||||||
playerId: this.playerId
|
|
||||||
}
|
|
||||||
this.emit('move', move)
|
|
||||||
this.board.updateBoard(move)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.hand.on('nextClick', async () => {
|
|
||||||
await this.socketService.sendMessageWithAck('playerReady', {
|
|
||||||
userId: this.playerId,
|
|
||||||
sessionId: this.sessionId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMoves(tile: any): [boolean, boolean] {
|
getMoves(tile: any): [boolean, boolean] {
|
||||||
@ -100,12 +121,22 @@ export class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setCanMakeMove(value: boolean) {
|
public setCanMakeMove(value: boolean) {
|
||||||
this.hand.canMove = value
|
this.hand.setCanMove(value, this.board.count === 0, this.board.freeEnds)
|
||||||
this.board.canMove = value
|
this.board.canMove = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setNextPlayer(state: GameDto) {
|
||||||
|
const currentPlayer = state?.currentPlayer!
|
||||||
|
if (currentPlayer.id !== this.playerId) {
|
||||||
|
this.setCanMakeMove(false)
|
||||||
|
this.board.setServerPlayerTurn(currentPlayer)
|
||||||
|
} else {
|
||||||
|
this.setCanMakeMove(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setBoardEvents() {
|
private setBoardEvents() {
|
||||||
this.board.on('leftClick', (data) => {
|
this.board.on('game:board-left-action-click', async (data) => {
|
||||||
console.log('left data :>> ', data)
|
console.log('left data :>> ', data)
|
||||||
if (this.selectedTile === undefined) return
|
if (this.selectedTile === undefined) return
|
||||||
const move: Movement = {
|
const move: Movement = {
|
||||||
@ -114,12 +145,12 @@ export class Game {
|
|||||||
playerId: this.playerId,
|
playerId: this.playerId,
|
||||||
...data
|
...data
|
||||||
}
|
}
|
||||||
this.emit('move', move)
|
this.currentMove = move
|
||||||
this.hand.tileMoved(this.selectedTile)
|
this.hand.tileMoved(this.selectedTile)
|
||||||
this.board.updateBoard({ ...move, tile: this.selectedTile })
|
await this.board.updateBoard({ ...move, tile: this.selectedTile })
|
||||||
})
|
})
|
||||||
|
|
||||||
this.board.on('rightClick', (data) => {
|
this.board.on('game:board-right-action-click', async (data) => {
|
||||||
console.log('right data :>> ', data)
|
console.log('right data :>> ', data)
|
||||||
if (this.selectedTile === undefined) return
|
if (this.selectedTile === undefined) return
|
||||||
const move: Movement = {
|
const move: Movement = {
|
||||||
@ -128,24 +159,53 @@ export class Game {
|
|||||||
playerId: this.playerId,
|
playerId: this.playerId,
|
||||||
...data
|
...data
|
||||||
}
|
}
|
||||||
this.emit('move', move)
|
this.currentMove = move
|
||||||
this.hand.tileMoved(this.selectedTile)
|
this.hand.tileMoved(this.selectedTile)
|
||||||
this.board.updateBoard({ ...move, tile: this.selectedTile })
|
await this.board.updateBoard({ ...move, tile: this.selectedTile })
|
||||||
|
})
|
||||||
|
|
||||||
|
this.board.on('game:tile-animation-ended', async (tile) => {
|
||||||
|
console.log('animation ended', tile)
|
||||||
|
if (tile.playerId === this.playerId) {
|
||||||
|
this.socketService.sendMessage('client:player-move', {
|
||||||
|
sessionId: this.sessionId,
|
||||||
|
move: this.currentMove
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
gameFinished() {
|
// sendMoveEvent(move: Movement) {
|
||||||
|
// this.board.on('game:tile-animation-ended', async (tile) => {
|
||||||
|
// this.eventBus.publish('game:tile-animation-ended', tile)
|
||||||
|
// // this.socketService.sendMessageWithAck('client:tile-animation-ended', {
|
||||||
|
// // sessionId: this.sessionId,
|
||||||
|
// // tile
|
||||||
|
// // })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
gameFinished(data: any) {
|
||||||
this.hand.gameFinished()
|
this.hand.gameFinished()
|
||||||
this.board.gameFinished()
|
this.board.gameFinished(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchFinished(data: any) {
|
||||||
|
// this.hand.matchFinished()
|
||||||
|
this.board.matchFinished(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverPlayerMove(data: any, playerId: string) {
|
||||||
|
this.board.playerMove(data, playerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeBoardEvents() {
|
private removeBoardEvents() {
|
||||||
this.board.off('leftClick')
|
this.board.off('game:board-left-action-click')
|
||||||
this.board.off('rightClick')
|
this.board.off('game:board-right-action-click')
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeHandEvents() {
|
private removeHandEvents() {
|
||||||
this.hand.off('tileClick')
|
this.hand.off('game:tile-click')
|
||||||
this.hand.off('passClick')
|
this.hand.off('game:button-pass-click')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
166
src/game/Hand.ts
166
src/game/Hand.ts
@ -1,18 +1,11 @@
|
|||||||
import {
|
import { Application, Container, EventEmitter, Sprite, Texture, Ticker } from 'pixi.js'
|
||||||
Application,
|
|
||||||
Container,
|
|
||||||
EventEmitter,
|
|
||||||
Graphics,
|
|
||||||
Sprite,
|
|
||||||
Text,
|
|
||||||
Texture,
|
|
||||||
Ticker
|
|
||||||
} from 'pixi.js'
|
|
||||||
import { Tile } from '@/game/Tile'
|
import { Tile } from '@/game/Tile'
|
||||||
import type { Dimension, PlayerDto, TileDto } from '@/common/interfaces'
|
import type { PlayerDto, TileDto } from '@/common/interfaces'
|
||||||
import { GlowFilter } from 'pixi-filters'
|
import { GlowFilter } from 'pixi-filters'
|
||||||
import { Scale, type ScaleFunction } from './utilities/scale'
|
import { Scale, type ScaleFunction } from './utilities/scale'
|
||||||
import { LoggingService } from '@/services/LoggingService'
|
import { LoggingService } from '@/services/LoggingService'
|
||||||
|
import { createButton, createContainer } from '@/common/helpers'
|
||||||
|
import { Action, Actions } from 'pixi-actions'
|
||||||
|
|
||||||
export class Hand extends EventEmitter {
|
export class Hand extends EventEmitter {
|
||||||
tiles: Tile[] = []
|
tiles: Tile[] = []
|
||||||
@ -31,18 +24,40 @@ export class Hand extends EventEmitter {
|
|||||||
scaleX!: ScaleFunction
|
scaleX!: ScaleFunction
|
||||||
grain: number = 25
|
grain: number = 25
|
||||||
logger: LoggingService = new LoggingService()
|
logger: LoggingService = new LoggingService()
|
||||||
|
availableTiles: Tile[] = []
|
||||||
|
tilesLayer!: Container
|
||||||
|
interactionsLayer!: Container
|
||||||
|
|
||||||
constructor(app: Application) {
|
constructor(app: Application) {
|
||||||
super()
|
super()
|
||||||
app.stage.addChild(this.container)
|
app.stage.addChild(this.container)
|
||||||
this.ticker = app.ticker
|
this.ticker = app.ticker
|
||||||
this.height = 130
|
this.height = 130 * this.scale
|
||||||
this.width = app.canvas.width
|
this.width = app.canvas.width
|
||||||
this.container.y = app.canvas.height - this.height
|
this.container.y = app.canvas.height - this.height
|
||||||
this.container.width = this.width
|
this.container.width = this.width
|
||||||
this.container.height = this.height
|
this.container.height = this.height
|
||||||
this.calculateScale()
|
this.calculateScale()
|
||||||
|
this.initLayers()
|
||||||
|
}
|
||||||
|
|
||||||
|
initLayers() {
|
||||||
|
this.container.removeChildren()
|
||||||
this.addBg()
|
this.addBg()
|
||||||
|
this.tilesLayer = createContainer({
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
parent: this.container
|
||||||
|
})
|
||||||
|
this.interactionsLayer = createContainer({
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
parent: this.container
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
gameFinished() {
|
gameFinished() {
|
||||||
@ -50,14 +65,15 @@ export class Hand extends EventEmitter {
|
|||||||
this.tiles = []
|
this.tiles = []
|
||||||
|
|
||||||
this.initialized = false
|
this.initialized = false
|
||||||
this.buttonNext = this.createButton(
|
this.buttonNext = createButton(
|
||||||
'NEXT',
|
'NEXT',
|
||||||
{ x: this.width / 2 - 25, y: this.height / 2, width: 50, height: 20 },
|
{ x: this.width / 2 - 25, y: this.height / 2, width: 50, height: 20 },
|
||||||
() => {
|
() => {
|
||||||
this.container.removeChildren()
|
this.tilesLayer.removeChildren()
|
||||||
this.container.removeChild(this.buttonNext)
|
this.interactionsLayer.removeChild(this.buttonNext)
|
||||||
this.emit('nextClick')
|
this.emit('nextClick')
|
||||||
}
|
},
|
||||||
|
this.interactionsLayer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,24 +88,47 @@ export class Hand extends EventEmitter {
|
|||||||
this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
|
this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
|
||||||
}
|
}
|
||||||
|
|
||||||
set canMove(value: boolean) {
|
setCanMove(value: boolean, isFirstMove: boolean, freeEnds?: [number, number]) {
|
||||||
this._canMove = value
|
console.log('this.tiles :>> ', this.tiles.length)
|
||||||
if (value) {
|
this.availableTiles =
|
||||||
|
!value || isFirstMove
|
||||||
|
? this.tiles
|
||||||
|
: this.tiles.filter((tile) => this.hasMoves(tile.toPlain(), freeEnds))
|
||||||
|
|
||||||
|
console.log('this.availableTiles :>> ', this.availableTiles.length)
|
||||||
|
if (value && this.availableTiles.length === 0) {
|
||||||
this.createPassButton()
|
this.createPassButton()
|
||||||
} else {
|
} else {
|
||||||
this.container.removeChild(this.buttonPass)
|
this.interactionsLayer.removeChild(this.buttonPass)
|
||||||
|
}
|
||||||
|
this.availableTiles.forEach((tile) => {
|
||||||
|
if (value) {
|
||||||
|
tile.animateTo({
|
||||||
|
x: tile.x,
|
||||||
|
y: tile.y - 10
|
||||||
|
})
|
||||||
|
// const action: Action = Actions.moveTo(tile.getSprite(), tile.x, tile.y - 10, 1,).play()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tiles.forEach((tile) => {
|
|
||||||
tile.interactive = value
|
tile.interactive = value
|
||||||
})
|
})
|
||||||
|
this._canMove = value
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMoves(tile: TileDto, freeEnds?: [number, number]): boolean {
|
||||||
|
if (tile === undefined || freeEnds === undefined) return false
|
||||||
|
|
||||||
|
let hasMoves: boolean = false
|
||||||
|
if (tile.pips != undefined) {
|
||||||
|
hasMoves = tile.pips.includes(freeEnds[0]) || tile.pips.includes(freeEnds[1])
|
||||||
|
}
|
||||||
|
return hasMoves
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(playerState: PlayerDto) {
|
initialize(playerState: PlayerDto) {
|
||||||
this.tiles = this.createTiles(playerState)
|
this.tiles = this.createTiles(playerState)
|
||||||
this.emit('handUpdated', this.tiles)
|
|
||||||
this.initialized = this.tiles.length > 0
|
this.initialized = this.tiles.length > 0
|
||||||
this.renderTiles()
|
this.renderTiles()
|
||||||
|
this.emit('hand-updated', this.tiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
private addBg() {
|
private addBg() {
|
||||||
@ -109,8 +148,9 @@ export class Hand extends EventEmitter {
|
|||||||
const selected = this.tiles.find((t) => t.selected)
|
const selected = this.tiles.find((t) => t.selected)
|
||||||
if (selected) {
|
if (selected) {
|
||||||
this.deselectTile(selected)
|
this.deselectTile(selected)
|
||||||
|
|
||||||
if (selected.id === tile.id) {
|
if (selected.id === tile.id) {
|
||||||
this.emit('tileClick')
|
this.emit('game:tile-click')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,12 +158,10 @@ export class Hand extends EventEmitter {
|
|||||||
tile.selected = true
|
tile.selected = true
|
||||||
tile.alpha = 1
|
tile.alpha = 1
|
||||||
|
|
||||||
tile.animateTo(tile.x, tile.y - 10)
|
this.emit('game:tile-click', tile.toPlain())
|
||||||
this.emit('tileClick', tile.toPlain())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private deselectTile(selected: Tile) {
|
private deselectTile(selected: Tile) {
|
||||||
selected.animateTo(selected.x, selected.y + 10)
|
|
||||||
selected.selected = false
|
selected.selected = false
|
||||||
selected.alpha = 0.7
|
selected.alpha = 0.7
|
||||||
}
|
}
|
||||||
@ -133,6 +171,17 @@ export class Hand extends EventEmitter {
|
|||||||
|
|
||||||
if (!tile) return
|
if (!tile) return
|
||||||
|
|
||||||
|
this.availableTiles
|
||||||
|
.filter((t) => t.id !== tileDto.id)
|
||||||
|
.forEach((t) => {
|
||||||
|
t.animateTo({
|
||||||
|
x: t.x,
|
||||||
|
y: t.y + 10
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.tiles = this.tiles.filter((t) => t.id !== tileDto.id)
|
||||||
|
|
||||||
tile.interactive = false
|
tile.interactive = false
|
||||||
tile.clearFilters()
|
tile.clearFilters()
|
||||||
tile.off('pointerdown')
|
tile.off('pointerdown')
|
||||||
@ -140,59 +189,14 @@ export class Hand extends EventEmitter {
|
|||||||
tile.off('pointerout')
|
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() {
|
private createPassButton() {
|
||||||
const lastTile = this.tiles[this.tiles.length - 1]
|
const lastTile = this.tiles[this.tiles.length - 1]
|
||||||
const x = lastTile ? lastTile.x + lastTile.width : this.scaleX(0)
|
const x = lastTile ? lastTile.x + lastTile.width : this.scaleX(0)
|
||||||
this.buttonPass = this.createButton(
|
this.buttonPass = createButton(
|
||||||
'PASS',
|
'PASS',
|
||||||
{ x, y: this.height / 2, width: 50, height: 20 },
|
{ x, y: this.height / 2, width: 50, height: 20 },
|
||||||
'passClick'
|
() => this.emit('game:button-pass-click'),
|
||||||
|
this.interactionsLayer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,21 +209,22 @@ export class Hand extends EventEmitter {
|
|||||||
(tile: Tile) => !playerState.hand.find((t) => t.id === tile.id)
|
(tile: Tile) => !playerState.hand.find((t) => t.id === tile.id)
|
||||||
)
|
)
|
||||||
if (missing) {
|
if (missing) {
|
||||||
this.container.removeChild(missing.getSprite())
|
this.tilesLayer.removeChild(missing.getSprite())
|
||||||
this.tiles = this.tiles.filter((tile) => tile.id !== missing.id)
|
this.tiles = this.tiles.filter((tile) => tile.id !== missing.id)
|
||||||
this.emit('handUpdated', this.tiles)
|
this.emit('hand-updated', this.tiles)
|
||||||
}
|
}
|
||||||
this.renderTiles()
|
this.renderTiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTiles(playerState: PlayerDto) {
|
private createTiles(playerState: PlayerDto) {
|
||||||
return playerState.hand.map((tile) => {
|
return playerState.hand.map((tile: TileDto) => {
|
||||||
const newTile: Tile = new Tile(tile.id, this.ticker, tile.pips, this.scale)
|
const newTile: Tile = new Tile(tile.id, this.ticker, tile.pips, this.scale, tile.playerId)
|
||||||
newTile.alpha = 0.7
|
newTile.alpha = 0.7
|
||||||
newTile.anchor = 0.5
|
newTile.anchor = 0.5
|
||||||
newTile.addTo(this.container)
|
newTile.addTo(this.tilesLayer)
|
||||||
newTile.on('pointerdown', () => this.onTileClick(newTile))
|
newTile.on('pointerdown', () => this.onTileClick(newTile))
|
||||||
newTile.on('pointerover', () => {
|
newTile.on('pointerover', () => {
|
||||||
|
this.emit('tileHover', newTile.toPlain())
|
||||||
newTile.alpha = 1
|
newTile.alpha = 1
|
||||||
newTile.setFilters([
|
newTile.setFilters([
|
||||||
new GlowFilter({
|
new GlowFilter({
|
||||||
@ -233,6 +238,7 @@ export class Hand extends EventEmitter {
|
|||||||
})
|
})
|
||||||
newTile.on('pointerout', () => {
|
newTile.on('pointerout', () => {
|
||||||
if (!newTile.selected) {
|
if (!newTile.selected) {
|
||||||
|
this.emit('tileHover')
|
||||||
newTile.alpha = 0.7
|
newTile.alpha = 0.7
|
||||||
newTile.getSprite().filters = []
|
newTile.getSprite().filters = []
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import type { AnimationOptions } from '@/common/interfaces'
|
||||||
import { Sprite, Texture, Ticker } from 'pixi.js'
|
import { Sprite, Texture, Ticker } from 'pixi.js'
|
||||||
|
import { Tile } from './Tile'
|
||||||
|
|
||||||
export abstract class SpriteBase {
|
export abstract class SpriteBase {
|
||||||
private _interactive: boolean = false
|
private _interactive: boolean = false
|
||||||
@ -81,28 +83,56 @@ export abstract class SpriteBase {
|
|||||||
this.sprite.filters = []
|
this.sprite.filters = []
|
||||||
}
|
}
|
||||||
|
|
||||||
animateTo(x: number, y: number) {
|
animateTo(options: AnimationOptions): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const {
|
||||||
|
x: targetX,
|
||||||
|
y: targetY,
|
||||||
|
rotation: targetRotation,
|
||||||
|
duration = 10,
|
||||||
|
width: targetWidth,
|
||||||
|
height: targetHeight
|
||||||
|
} = options
|
||||||
|
|
||||||
const initialX = this.sprite.x
|
const initialX = this.sprite.x
|
||||||
const initialY = this.sprite.y
|
const initialY = this.sprite.y
|
||||||
|
const initialRotation = this.sprite.rotation
|
||||||
|
const initialWidth = this.sprite.width
|
||||||
|
const initialHeight = this.sprite.height
|
||||||
|
|
||||||
|
const deltaX = targetX ? targetX - this.sprite.x : null
|
||||||
|
const deltaY = targetY ? targetY - this.sprite.y : null
|
||||||
|
const deltaRotation = targetRotation ? targetRotation - this.sprite.rotation : null
|
||||||
|
const deltaWidth = targetWidth ? targetWidth - this.sprite.width : null
|
||||||
|
const deltaHeight = targetHeight ? targetHeight - this.sprite.height : null
|
||||||
|
|
||||||
const deltaX = x - this.sprite.x
|
|
||||||
const deltaY = y - this.sprite.y
|
|
||||||
let elapsed: number = 0
|
let elapsed: number = 0
|
||||||
const duration: number = 10
|
|
||||||
|
|
||||||
const tick: any = (delta: any) => {
|
const tick: any = (delta: any) => {
|
||||||
elapsed += delta.deltaTime
|
elapsed += delta.deltaTime
|
||||||
const progress = Math.min(elapsed / duration, 1)
|
const progress = Math.min(elapsed / duration, 1)
|
||||||
|
|
||||||
this.sprite.x = initialX + deltaX * progress
|
// Linear interpolation
|
||||||
this.sprite.y = initialY + deltaY * progress
|
if (deltaX !== null) this.sprite.x = initialX + deltaX * progress
|
||||||
|
if (deltaY !== null) this.sprite.y = initialY + deltaY * progress
|
||||||
|
|
||||||
|
// Rotation interpolation
|
||||||
|
if (deltaRotation !== null)
|
||||||
|
this.sprite.rotation = initialRotation + deltaRotation * progress
|
||||||
|
|
||||||
|
// Scale interpolation
|
||||||
|
// this.sprite.width = initialWidth + deltaWidth * progress
|
||||||
|
// this.sprite.height = initialHeight + deltaHeight * progress
|
||||||
|
|
||||||
|
//
|
||||||
if (progress === 1) {
|
if (progress === 1) {
|
||||||
this.ticker?.remove(tick)
|
this.ticker?.remove(tick)
|
||||||
|
resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ticker?.add(tick)
|
this.ticker?.add(tick)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addTo(container: any) {
|
addTo(container: any) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Texture, Ticker } from 'pixi.js'
|
import { Texture, Ticker } from 'pixi.js'
|
||||||
import { SpriteBase } from '@/game/SpriteBase'
|
import { SpriteBase } from '@/game/SpriteBase'
|
||||||
|
import { ORIENTATION_ANGLES } from '@/common/constants'
|
||||||
|
|
||||||
export class Tile extends SpriteBase {
|
export class Tile extends SpriteBase {
|
||||||
selected: boolean = false
|
selected: boolean = false
|
||||||
@ -11,7 +12,8 @@ export class Tile extends SpriteBase {
|
|||||||
public id: string,
|
public id: string,
|
||||||
ticker?: Ticker,
|
ticker?: Ticker,
|
||||||
public pips?: [number, number],
|
public pips?: [number, number],
|
||||||
scale: number = 1
|
scale: number = 1,
|
||||||
|
public playerId?: string
|
||||||
) {
|
) {
|
||||||
super(ticker, scale)
|
super(ticker, scale)
|
||||||
this.id = id
|
this.id = id
|
||||||
@ -26,6 +28,7 @@ export class Tile extends SpriteBase {
|
|||||||
|
|
||||||
toPlain() {
|
toPlain() {
|
||||||
return {
|
return {
|
||||||
|
playerId: this.playerId,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
pips: this.pips,
|
pips: this.pips,
|
||||||
orientation: this.orientation,
|
orientation: this.orientation,
|
||||||
@ -69,16 +72,16 @@ export class Tile extends SpriteBase {
|
|||||||
setOrientation(value: string) {
|
setOrientation(value: string) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'north':
|
case 'north':
|
||||||
this.sprite.rotation = 0
|
this.sprite.rotation = ORIENTATION_ANGLES.north
|
||||||
break
|
break
|
||||||
case 'east':
|
case 'east':
|
||||||
this.sprite.rotation = Math.PI / 2
|
this.sprite.rotation = ORIENTATION_ANGLES.east
|
||||||
break
|
break
|
||||||
case 'south':
|
case 'south':
|
||||||
this.sprite.rotation = Math.PI
|
this.sprite.rotation = ORIENTATION_ANGLES.south
|
||||||
break
|
break
|
||||||
case 'west':
|
case 'west':
|
||||||
this.sprite.rotation = (3 * Math.PI) / 2
|
this.sprite.rotation = ORIENTATION_ANGLES.west
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.orientation = value
|
this.orientation = value
|
||||||
|
@ -30,6 +30,7 @@ import tile6_6 from '@/assets/images/tiles/6-6.png'
|
|||||||
import dot from '@/assets/images/circle.png'
|
import dot from '@/assets/images/circle.png'
|
||||||
import bgWood_1 from '@/assets/images/backgrounds/wood-1.jpg'
|
import bgWood_1 from '@/assets/images/backgrounds/wood-1.jpg'
|
||||||
import bg_1 from '@/assets/images/backgrounds/bg-1.png'
|
import bg_1 from '@/assets/images/backgrounds/bg-1.png'
|
||||||
|
import bg_green from '@/assets/images/backgrounds/bg-green.png'
|
||||||
|
|
||||||
export const assets = [
|
export const assets = [
|
||||||
{ alias: 'tile-back', src: tileBack },
|
{ alias: 'tile-back', src: tileBack },
|
||||||
@ -63,5 +64,6 @@ export const assets = [
|
|||||||
{ alias: 'tile-6_6', src: tile6_6 },
|
{ alias: 'tile-6_6', src: tile6_6 },
|
||||||
{ alias: 'dot', src: dot },
|
{ alias: 'dot', src: dot },
|
||||||
{ alias: 'bg-wood-1', src: bgWood_1 },
|
{ alias: 'bg-wood-1', src: bgWood_1 },
|
||||||
{ alias: 'bg-1', src: bg_1 }
|
{ alias: 'bg-1', src: bg_1 },
|
||||||
|
{ alias: 'bg-green', src: bg_green }
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Tile } from '../Tile'
|
import { Tile } from '../Tile'
|
||||||
import type { GameState, Movement } from '../../common/interfaces'
|
import type { GameDto, Movement } from '../../common/interfaces'
|
||||||
import type { Game } from '../Game'
|
import type { Game } from '../Game'
|
||||||
import { wait } from '../../common/helpers'
|
import { wait } from '../../common/helpers'
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export const playerState = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const gameState_0: GameState = {
|
export const gameState_0: GameDto = {
|
||||||
id: 'f043051e-6850-444f-857c-b889220fc187',
|
id: 'f043051e-6850-444f-857c-b889220fc187',
|
||||||
lastMove: {
|
lastMove: {
|
||||||
tile: {
|
tile: {
|
||||||
|
@ -1,29 +1,19 @@
|
|||||||
import { useGameStore } from '@/stores/game'
|
import { useGameStore } from '@/stores/game'
|
||||||
import { wait } from '@/common/helpers'
|
import { wait } from '@/common/helpers'
|
||||||
import type { MatchSessionState, SocketEvent } from '@/common/interfaces'
|
import type { MatchSessionDto, Movement, SocketEvent } from '@/common/interfaces'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useEventBusStore } from '@/stores/eventBus'
|
import { useEventBusStore } from '@/stores/eventBus'
|
||||||
|
import type { SocketIoClientService } from '@/services/SocketIoClientService'
|
||||||
|
|
||||||
export class SocketIoEventManager {
|
export class SocketIoEventManager {
|
||||||
gameStore: any = useGameStore()
|
gameStore: any = useGameStore()
|
||||||
eventBus = useEventBusStore()
|
eventBus = useEventBusStore()
|
||||||
|
callbacksMap: Map<string, any> = new Map()
|
||||||
|
|
||||||
|
constructor(private socketService: SocketIoClientService) {}
|
||||||
|
|
||||||
handleGameEvent(gameEvent: SocketEvent) {
|
handleGameEvent(gameEvent: SocketEvent) {
|
||||||
const { event, data } = gameEvent
|
const { event, data } = gameEvent
|
||||||
switch (event) {
|
|
||||||
case 'session-created':
|
|
||||||
this.updateSessionState(data)
|
|
||||||
break
|
|
||||||
case 'game-finished':
|
|
||||||
default:
|
|
||||||
this.eventBus.publish(event, data)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleGameEventAck(gameEvent: SocketEvent) {
|
|
||||||
const { event, data } = gameEvent
|
|
||||||
try {
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'update-match-session-state':
|
case 'update-match-session-state':
|
||||||
this.updateSessionState(data)
|
this.updateSessionState(data)
|
||||||
@ -34,54 +24,45 @@ export class SocketIoEventManager {
|
|||||||
case 'update-player-state':
|
case 'update-player-state':
|
||||||
this.updatePlayerState(data)
|
this.updatePlayerState(data)
|
||||||
break
|
break
|
||||||
case 'ask-client-for-move':
|
case 'server:player-turn':
|
||||||
return this.handleCanMakeMoveEvent(data)
|
this.onCanMakeMoveEvent(data)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
this.eventBus.publish(event, data)
|
this.eventBus.publish(event, data)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return { status: 'ok' }
|
}
|
||||||
} catch (error) {
|
|
||||||
return { status: 'error', error }
|
handleGameEventAck(gameEvent: SocketEvent, callback: any) {
|
||||||
|
const { event, data } = gameEvent
|
||||||
|
switch (event) {
|
||||||
|
// case 'ask-client-for-move':
|
||||||
|
// return this.handleCanMakeMoveEvent(data, callback)
|
||||||
|
default:
|
||||||
|
this.eventBus.publish(event, data)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateSessionState(data: MatchSessionState) {
|
private updateSessionState(data: MatchSessionDto) {
|
||||||
const { updateSessionState } = this.gameStore
|
const { updateSessionState } = this.gameStore
|
||||||
updateSessionState(data)
|
updateSessionState(data)
|
||||||
return {
|
|
||||||
status: 'ok'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateGameState(data: any) {
|
private updateGameState(data: any) {
|
||||||
const { updateGameState } = this.gameStore
|
const { updateGameState } = this.gameStore
|
||||||
updateGameState(data)
|
updateGameState(data)
|
||||||
return {
|
|
||||||
status: 'ok'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updatePlayerState(data: any) {
|
private updatePlayerState(data: any) {
|
||||||
const { updatePlayerState } = this.gameStore
|
const { updatePlayerState } = this.gameStore
|
||||||
updatePlayerState(data)
|
updatePlayerState(data)
|
||||||
return {
|
|
||||||
status: 'ok'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleCanMakeMoveEvent(data: any) {
|
private onCanMakeMoveEvent(data: any) {
|
||||||
const { canMakeMove, moveToMake } = storeToRefs(this.gameStore)
|
const { setIncomingFreeEnds } = this.gameStore
|
||||||
const { updateCanMakeMove, setIncomingFreeEnds } = this.gameStore
|
setIncomingFreeEnds(data.firstMove ? undefined : data.freeHands)
|
||||||
setIncomingFreeEnds(data.freeHands)
|
this.eventBus.publish('game:player-turn-started', data)
|
||||||
updateCanMakeMove(true)
|
|
||||||
while (canMakeMove.value) {
|
|
||||||
await wait(500)
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
status: 'ok',
|
|
||||||
...moveToMake.value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleCanSelectTileEvent() {
|
private async handleCanSelectTileEvent() {
|
||||||
|
@ -36,6 +36,20 @@ const router = createRouter({
|
|||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
// component: () => import('../views/AboutView.vue')
|
// component: () => import('../views/AboutView.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/match/:id',
|
||||||
|
component: AuthenticatedLayout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'match',
|
||||||
|
component: () => import('@/views/MatchView.vue'),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/game',
|
path: '/game',
|
||||||
component: AuthenticatedLayout,
|
component: AuthenticatedLayout,
|
||||||
|
@ -4,7 +4,7 @@ import { ServiceBase } from './ServiceBase'
|
|||||||
export class GameService extends ServiceBase {
|
export class GameService extends ServiceBase {
|
||||||
private networkService = new NetworkService()
|
private networkService = new NetworkService()
|
||||||
|
|
||||||
async createMatch(sessionName: string, seed: string) {
|
async createMatchSession(sessionName: string, seed: string) {
|
||||||
const response = await this.networkService.post({
|
const response = await this.networkService.post({
|
||||||
uri: '/game/match',
|
uri: '/game/match',
|
||||||
body: { sessionName, seed },
|
body: { sessionName, seed },
|
||||||
@ -13,4 +13,36 @@ export class GameService extends ServiceBase {
|
|||||||
const { sessionId } = response
|
const { sessionId } = response
|
||||||
return sessionId
|
return sessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async cancelMatchSession(sessionId: string) {
|
||||||
|
const response = await this.networkService.delete({
|
||||||
|
uri: `/game/match/${sessionId}`,
|
||||||
|
auth: true
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
async joinMatchSession(sessionId: string) {
|
||||||
|
const response = await this.networkService.put({
|
||||||
|
uri: `/game/match/${sessionId}`,
|
||||||
|
auth: true
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
async listMatchSessions() {
|
||||||
|
const response = await this.networkService.get({
|
||||||
|
uri: '/game/match',
|
||||||
|
auth: true
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMatchSession(sessionId: string) {
|
||||||
|
const response = await this.networkService.get({
|
||||||
|
uri: `/game/match/${sessionId}`,
|
||||||
|
auth: true
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,11 @@ export class NetworkService {
|
|||||||
return await this.request(options)
|
return await this.request(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async put(options: RequestOptions) {
|
||||||
|
options.method = 'PUT'
|
||||||
|
return await this.request(options)
|
||||||
|
}
|
||||||
|
|
||||||
async patch(options: RequestOptions) {
|
async patch(options: RequestOptions) {
|
||||||
options.method = 'PATCH'
|
options.method = 'PATCH'
|
||||||
return await this.request(options)
|
return await this.request(options)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import type { MatchSessionState, GameState, PlayerDto } from '@/common/interfaces'
|
|
||||||
import { io, Socket } from 'socket.io-client'
|
import { io, Socket } from 'socket.io-client'
|
||||||
import { SocketIoEventManager } from '@/managers/SocketIoEventManager'
|
import { SocketIoEventManager } from '@/managers/SocketIoEventManager'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
@ -8,10 +7,11 @@ import { ServiceBase } from './ServiceBase'
|
|||||||
export class SocketIoClientService extends ServiceBase {
|
export class SocketIoClientService extends ServiceBase {
|
||||||
private socket!: Socket
|
private socket!: Socket
|
||||||
private isConnected = false
|
private isConnected = false
|
||||||
private gameEventManager = new SocketIoEventManager()
|
private gameEventManager: SocketIoEventManager
|
||||||
|
|
||||||
constructor(private url: string) {
|
constructor(private url: string) {
|
||||||
super()
|
super()
|
||||||
|
this.gameEventManager = new SocketIoEventManager(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
@ -70,18 +70,19 @@ export class SocketIoClientService extends ServiceBase {
|
|||||||
// callback(await this.gameEventManager.handleCanSelectTileEvent())
|
// callback(await this.gameEventManager.handleCanSelectTileEvent())
|
||||||
// })
|
// })
|
||||||
|
|
||||||
this.socket.on('game-event', (data: any) => {
|
this.socket.on('server:game-event', (data: any) => {
|
||||||
this.gameEventManager.handleGameEvent(data)
|
this.gameEventManager.handleGameEvent(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.socket.on('game-event-ack', async (data: any, callback: any) => {
|
this.socket.on('server:game-event-ack', async (data: any, callback: any) => {
|
||||||
callback(await this.gameEventManager.handleGameEventAck(data))
|
await this.gameEventManager.handleGameEventAck(data, callback)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(event: string, data: any): void {
|
sendMessage(event: string, data: any): void {
|
||||||
if (this.isConnected) {
|
if (this.isConnected) {
|
||||||
this.socket?.emit(event, data)
|
this.socket?.emit('client:event', { event, data })
|
||||||
|
console.log('sendMessage :>> ', event, data)
|
||||||
} else {
|
} else {
|
||||||
console.log('Not connected to server')
|
console.log('Not connected to server')
|
||||||
}
|
}
|
||||||
@ -89,7 +90,8 @@ export class SocketIoClientService extends ServiceBase {
|
|||||||
|
|
||||||
async sendMessageWithAck(event: string, data: any): Promise<any> {
|
async sendMessageWithAck(event: string, data: any): Promise<any> {
|
||||||
if (this.isConnected) {
|
if (this.isConnected) {
|
||||||
return await this.socket?.emitWithAck(event, data)
|
console.log('sendMessageWithAck :>> ', event, data)
|
||||||
|
return await this.socket?.emitWithAck('client:event-with-ack', { event, data })
|
||||||
} else {
|
} else {
|
||||||
console.log('Not connected to server')
|
console.log('Not connected to server')
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type { MatchSessionState, GameState, Movement, PlayerDto } from '@/common/interfaces'
|
import type { MatchSessionDto, GameDto, Movement, PlayerDto } from '@/common/interfaces'
|
||||||
|
|
||||||
export const useGameStore = defineStore('game', () => {
|
export const useGameStore = defineStore('game', () => {
|
||||||
const sessionState = ref<MatchSessionState | undefined>(undefined)
|
const sessionState = ref<MatchSessionDto | undefined>(undefined)
|
||||||
const gameState = ref<GameState | undefined>(undefined)
|
const gameState = ref<GameDto | undefined>(undefined)
|
||||||
const playerState = ref<PlayerDto | undefined>(undefined)
|
const playerState = ref<PlayerDto | undefined>(undefined)
|
||||||
const canMakeMove = ref(false)
|
const canMakeMove = ref(false)
|
||||||
const canSelectTile = ref(false)
|
const canSelectTile = ref(false)
|
||||||
const gameFinished = ref(false)
|
const gameFinished = ref(false)
|
||||||
const readyForStart = ref(false)
|
|
||||||
const moveToMake = ref<Movement | undefined>(undefined)
|
const moveToMake = ref<Movement | undefined>(undefined)
|
||||||
const incomingFreeEnds = ref<[number, number] | undefined>(undefined)
|
const incomingFreeEnds = ref<[number, number] | undefined>(undefined)
|
||||||
const showReadyButton = ref(false)
|
const showReadyButton = ref(false)
|
||||||
@ -21,16 +20,17 @@ export const useGameStore = defineStore('game', () => {
|
|||||||
playerState.value !== undefined &&
|
playerState.value !== undefined &&
|
||||||
playerState.value.id === sessionState.value.creator
|
playerState.value.id === sessionState.value.creator
|
||||||
)
|
)
|
||||||
|
const readyForStart = computed(() => playerState.value !== undefined && playerState.value.ready)
|
||||||
|
|
||||||
function updateSessionState(newState: MatchSessionState) {
|
function updateSessionState(newState: MatchSessionDto | undefined) {
|
||||||
sessionState.value = newState
|
sessionState.value = newState
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGameState(newState: GameState) {
|
function updateGameState(newState: GameDto | undefined) {
|
||||||
gameState.value = newState
|
gameState.value = newState
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePlayerState(newState: PlayerDto) {
|
function updatePlayerState(newState: PlayerDto | undefined) {
|
||||||
playerState.value = newState
|
playerState.value = newState
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,10 +54,6 @@ export const useGameStore = defineStore('game', () => {
|
|||||||
showReadyButton.value = value
|
showReadyButton.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
function setReadyForStart(value: boolean) {
|
|
||||||
readyForStart.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateGameFinished(value: boolean) {
|
function updateGameFinished(value: boolean) {
|
||||||
gameFinished.value = value
|
gameFinished.value = value
|
||||||
}
|
}
|
||||||
@ -81,7 +77,6 @@ export const useGameStore = defineStore('game', () => {
|
|||||||
setIncomingFreeEnds,
|
setIncomingFreeEnds,
|
||||||
updateCanSelectTile,
|
updateCanSelectTile,
|
||||||
setShowReadyButton,
|
setShowReadyButton,
|
||||||
setReadyForStart,
|
|
||||||
updateGameFinished,
|
updateGameFinished,
|
||||||
isSessionStarted,
|
isSessionStarted,
|
||||||
amIHost
|
amIHost
|
||||||
|
@ -2,19 +2,17 @@
|
|||||||
import GameComponent from '@/components/GameComponent.vue'
|
import GameComponent from '@/components/GameComponent.vue'
|
||||||
import { useGameStore } from '@/stores/game'
|
import { useGameStore } from '@/stores/game'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { inject, onBeforeUnmount, ref } from 'vue'
|
import { onBeforeUnmount } from 'vue'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import useClipboard from 'vue-clipboard3'
|
import useClipboard from 'vue-clipboard3'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const socketService: any = inject('socket')
|
|
||||||
|
|
||||||
const { toClipboard } = useClipboard()
|
const { toClipboard } = useClipboard()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const { moveToMake, canMakeMove, sessionState, gameState, playerState } = storeToRefs(gameStore)
|
const { moveToMake, canMakeMove, sessionState, gameState, playerState } = storeToRefs(gameStore)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
startMatch()
|
// startMatch()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!playerState?.value) {
|
if (!playerState?.value) {
|
||||||
@ -25,25 +23,13 @@ if (!playerState?.value) {
|
|||||||
function makeMove(move: any) {
|
function makeMove(move: any) {
|
||||||
moveToMake.value = move
|
moveToMake.value = move
|
||||||
canMakeMove.value = false
|
canMakeMove.value = false
|
||||||
|
console.log('makemove :>> ', move)
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// socketService.disconnect()
|
// socketService.disconnect()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function startMatch() {
|
|
||||||
const sessionId = sessionState?.value?.id
|
|
||||||
const seed = sessionState?.value?.seed
|
|
||||||
const playerId = playerState?.value?.id
|
|
||||||
if (sessionId) {
|
|
||||||
await socketService.sendMessageWithAck('startSession', {
|
|
||||||
sessionId: sessionId,
|
|
||||||
playerId: playerId,
|
|
||||||
seed: seed?.trim()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copySeed() {
|
function copySeed() {
|
||||||
if (sessionState?.value?.seed) toClipboard(sessionState.value.seed)
|
if (sessionState?.value?.seed) toClipboard(sessionState.value.seed)
|
||||||
}
|
}
|
||||||
@ -63,7 +49,8 @@ function copySeed() {
|
|||||||
- Score: {{ sessionState?.scoreboard }}
|
- Score: {{ sessionState?.scoreboard }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="sessionState?.id">
|
<p v-if="sessionState?.id">
|
||||||
SessionID: {{ sessionState.id }} PlayerID: {{ playerState?.id }}
|
SessionID: {{ sessionState.id }} PlayerID: {{ playerState?.id }} - canMakeMove
|
||||||
|
{{ canMakeMove }}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section class="block">
|
<section class="block">
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject, ref } from 'vue'
|
import { inject, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useGameStore } from '@/stores/game'
|
import { useGameStore } from '@/stores/game'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { LoggingService } from '@/services/LoggingService'
|
import type { LoggingService } from '@/services/LoggingService'
|
||||||
import type { GameService } from '@/services/GameService'
|
import type { GameService } from '@/services/GameService'
|
||||||
|
import type { MatchSessionDto } from '@/common/interfaces'
|
||||||
|
import { useEventBusStore } from '@/stores/eventBus'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
let seed = ref('')
|
let seed = ref('')
|
||||||
let sessionName = ref('Test Value')
|
let sessionName = ref('Test Value')
|
||||||
let sessionId = ref('')
|
let sessionId = ref('')
|
||||||
|
let matchSessions = ref<MatchSessionDto[]>([])
|
||||||
|
let dataInterval: any
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const socketService: any = inject('socket')
|
const socketService: any = inject('socket')
|
||||||
const gameService: GameService = inject<GameService>('game') as GameService
|
const gameService: GameService = inject<GameService>('game') as GameService
|
||||||
@ -19,41 +25,58 @@ const logger: LoggingService = inject<LoggingService>('logger') as LoggingServic
|
|||||||
|
|
||||||
const { readyForStart, sessionState, isSessionStarted, playerState, amIHost } =
|
const { readyForStart, sessionState, isSessionStarted, playerState, amIHost } =
|
||||||
storeToRefs(gameStore)
|
storeToRefs(gameStore)
|
||||||
|
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
|
||||||
|
const { user } = storeToRefs(auth)
|
||||||
|
|
||||||
async function setPlayerReady() {
|
// function setPlayerReady() {
|
||||||
logger.debug('Starting game')
|
// logger.debug('Starting game')
|
||||||
if (!sessionState.value) {
|
// if (!sessionState.value) {
|
||||||
logger.error('No session found')
|
// logger.error('No session found')
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if (!playerState.value) {
|
// if (!playerState.value) {
|
||||||
logger.error('No player found')
|
// logger.error('No player found')
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
await socketService.sendMessageWithAck('playerReady', {
|
// socketService.sendMessage('client:set-player-ready', {
|
||||||
userId: playerState.value.id,
|
// userId: playerState.value.id,
|
||||||
sessionId: sessionState.value.id
|
// sessionId: sessionState.value.id
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
const eventBus = useEventBusStore()
|
||||||
|
eventBus.subscribe('window-before-unload', () => {
|
||||||
|
logger.debug('Window before unload')
|
||||||
})
|
})
|
||||||
readyForStart.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createMatch() {
|
async function createMatch() {
|
||||||
logger.debug('Creating match')
|
logger.debug('Creating match')
|
||||||
await socketService.connect()
|
await socketService.connect()
|
||||||
sessionId.value = await gameService.createMatch(sessionName.value, seed.value)
|
const id = await gameService.createMatchSession(sessionName.value, seed.value)
|
||||||
logger.debug('Match reated successfully')
|
logger.debug('Match created successfully')
|
||||||
|
router.push({ name: 'match', params: { id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function joinMatch() {
|
async function cancelMatch() {
|
||||||
const sessionId = sessionState?.value?.id
|
logger.debug('Cancelling match')
|
||||||
const playerId = playerState?.value?.id
|
await gameService.cancelMatchSession(sessionId.value)
|
||||||
if (sessionId && playerId) {
|
await socketService.disconnect()
|
||||||
await socketService.sendMessageWithAck('joinSession', {
|
sessionId.value = ''
|
||||||
user: 'pepe',
|
seed.value = ''
|
||||||
sessionId: sessionId
|
sessionName.value = ''
|
||||||
})
|
updateSessionState(undefined)
|
||||||
// sessionId.value = response.sessionId
|
updatePlayerState(undefined)
|
||||||
// playerId.value = response.playerId
|
updateGameState(undefined)
|
||||||
|
|
||||||
|
logger.debug('Match cancelled successfully')
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function joinMatch(id: string) {
|
||||||
|
if (id) {
|
||||||
|
await socketService.connect()
|
||||||
|
await gameService.joinMatchSession(id)
|
||||||
|
router.push({ name: 'match', params: { id } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,12 +85,28 @@ async function startMatch() {
|
|||||||
router.push({ name: 'game' })
|
router.push({ name: 'game' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
matchSessions.value = await gameService.listMatchSessions()
|
||||||
|
sessionName.value = `Test #${matchSessions.value.length + 1}`
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
logger.debug('Home view mounted')
|
||||||
|
loadData()
|
||||||
|
dataInterval = setInterval(loadData, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
logger.debug('Home view unmounted')
|
||||||
|
clearInterval(dataInterval)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="block home">
|
<div class="block home">
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<h1 class="title is-2">Welcome to the Player's Home Page</h1>
|
<h1 class="title is-2">Welcome to the {{ user.username }}'s Home Page</h1>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<p>This is a protected route.</p>
|
<p>This is a protected route.</p>
|
||||||
<p>{{ sessionState || 'No session' }}</p>
|
<p>{{ sessionState || 'No session' }}</p>
|
||||||
@ -93,17 +132,30 @@ async function startMatch() {
|
|||||||
<button class="button" @click="createMatch" v-if="!isSessionStarted">
|
<button class="button" @click="createMatch" v-if="!isSessionStarted">
|
||||||
Create Match Session
|
Create Match Session
|
||||||
</button>
|
</button>
|
||||||
<button class="button" @click="setPlayerReady" v-if="isSessionStarted">
|
<!-- <button class="button" @click="setPlayerReady" v-if="isSessionStarted">
|
||||||
<span v-if="!readyForStart">Ready</span><span v-else>Unready</span>
|
<span v-if="!readyForStart">Ready</span><span v-else>Unready</span>
|
||||||
</button>
|
</button> -->
|
||||||
<button class="button" @click="startMatch" v-if="readyForStart">
|
<!-- <button class="button" @click="startMatch" v-if="readyForStart">
|
||||||
<span>Start</span>
|
<span>Start</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button class="button" @click="cancelMatch" v-if="isSessionStarted">
|
||||||
|
<span>Cancel</span>
|
||||||
|
</button> -->
|
||||||
</section>
|
</section>
|
||||||
<section class="section available-sessions">
|
<section class="section available-sessions" v-if="!isSessionStarted">
|
||||||
<h2 class="title is-4">Available Sessions</h2>
|
<h2 class="title is-4">Available Sessions</h2>
|
||||||
<div class="bloc">
|
<div class="block">
|
||||||
<p>There are no available sessions at the moment.</p>
|
<div v-if="matchSessions.length === 0">
|
||||||
|
<p>No sessions available</p>
|
||||||
|
</div>
|
||||||
|
<div v-else v-for="session in matchSessions" :key="session.id">
|
||||||
|
<p>{{ session.name }}</p>
|
||||||
|
<p>{{ session }}</p>
|
||||||
|
<button class="button" @click="() => joinMatch(session._id)">
|
||||||
|
Join ({{ session._id }})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
114
src/views/MatchView.vue
Normal file
114
src/views/MatchView.vue
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { MatchSessionDto } from '@/common/interfaces'
|
||||||
|
import type { GameService } from '@/services/GameService'
|
||||||
|
import type { LoggingService } from '@/services/LoggingService'
|
||||||
|
import { useEventBusStore } from '@/stores/eventBus'
|
||||||
|
import { useGameStore } from '@/stores/game'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { inject, onBeforeMount, ref } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
const eventBus = useEventBusStore()
|
||||||
|
const socketService: any = inject('socket')
|
||||||
|
const gameService: GameService = inject<GameService>('game') as GameService
|
||||||
|
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
|
||||||
|
|
||||||
|
let sessionId: string
|
||||||
|
let matchSession = ref<MatchSessionDto | undefined>(undefined)
|
||||||
|
|
||||||
|
const { readyForStart, sessionState, isSessionStarted, playerState, amIHost } =
|
||||||
|
storeToRefs(gameStore)
|
||||||
|
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
|
||||||
|
|
||||||
|
async function setPlayerReady() {
|
||||||
|
logger.debug('Starting game')
|
||||||
|
if (!sessionState.value) {
|
||||||
|
logger.error('No session found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!playerState.value) {
|
||||||
|
logger.error('No player found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await socketService.sendMessage('client:set-player-ready', {
|
||||||
|
userId: playerState.value.id,
|
||||||
|
sessionId: sessionState.value.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startMatch() {
|
||||||
|
const sessionId = sessionState?.value?.id
|
||||||
|
const playerId = playerState?.value?.id
|
||||||
|
if (sessionId) {
|
||||||
|
await socketService.sendMessageWithAck('client:start-session', {
|
||||||
|
sessionId: sessionId,
|
||||||
|
playerId: playerId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cancelMatch() {
|
||||||
|
logger.debug('Cancelling match')
|
||||||
|
await gameService.cancelMatchSession(sessionId)
|
||||||
|
updateSessionState(undefined)
|
||||||
|
updatePlayerState(undefined)
|
||||||
|
updateGameState(undefined)
|
||||||
|
|
||||||
|
logger.debug('Match cancelled successfully')
|
||||||
|
router.push({ name: 'home' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
await gameService.getMatchSession(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.subscribe('window-before-unload', async () => {
|
||||||
|
logger.debug('Window before unload')
|
||||||
|
await cancelMatch()
|
||||||
|
})
|
||||||
|
|
||||||
|
eventBus.subscribe('server:match-starting', () => {
|
||||||
|
logger.debug('Match starting')
|
||||||
|
router.push({ name: 'game' })
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
sessionId = route.params.id as string
|
||||||
|
if (sessionId) {
|
||||||
|
setInterval(loadData, 5000)
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'home' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1 class="title is-2">Match Page</h1>
|
||||||
|
<div class="block" v-if="matchSession">
|
||||||
|
<p>Session ID: {{ matchSession._id }}</p>
|
||||||
|
<p>Session Name: {{ matchSession.name }}</p>
|
||||||
|
<p>Session started: {{ isSessionStarted }}</p>
|
||||||
|
<p>Host: {{ amIHost }}</p>
|
||||||
|
<p>{{ sessionState || 'No session' }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<p v-if="!amIHost && !readyForStart">Waiting for host to start session</p>
|
||||||
|
<button class="button" @click="setPlayerReady" v-if="isSessionStarted">
|
||||||
|
<span v-if="!readyForStart">Ready</span><span v-else>Unready</span>
|
||||||
|
</button>
|
||||||
|
<button class="button" @click="startMatch" v-if="amIHost && readyForStart">
|
||||||
|
<span>Start</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="button" @click="cancelMatch" v-if="isSessionStarted">
|
||||||
|
<span>Cancel</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
Loading…
x
Reference in New Issue
Block a user