reworked event communication
This commit is contained in:
		
							
								
								
									
										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'}`) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										118
									
								
								src/game/Game.ts
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								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.selectedTile = tile |  | ||||||
|       if (tile !== undefined) { |  | ||||||
|         this.board.setMovesForTile(this.getMoves(tile), tile) |  | ||||||
|       } else { |  | ||||||
|         this.board.cleanInteractions() |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     this.hand.on('passClick', () => { |     this.hand.on('game:button-pass-click', async () => { | ||||||
|       const move: Movement = { |       const move: Movement = { | ||||||
|         id: '', |         id: '', | ||||||
|         type: 'pass', |         type: 'pass', | ||||||
|         playerId: this.playerId |         playerId: this.playerId | ||||||
|       } |       } | ||||||
|       this.emit('move', move) |       this.socketService.sendMessage('client:player-move', { | ||||||
|       this.board.updateBoard(move) |         sessionId: this.sessionId, | ||||||
|  |         move: move | ||||||
|  |       }) | ||||||
|  |       await this.board.updateBoard(move) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     this.hand.on('nextClick', async () => { |     this.hand.on('nextClick', () => { | ||||||
|       await this.socketService.sendMessageWithAck('playerReady', { |       this.socketService.sendMessage('client:set-player-ready', { | ||||||
|         userId: this.playerId, |         userId: this.playerId, | ||||||
|         sessionId: this.sessionId |         sessionId: this.sessionId | ||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     this.hand.on('hand-initialized', () => {}) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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] { |   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) => { | ||||||
|     this.tiles.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() | ||||||
|  |       } | ||||||
|       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> { | ||||||
|     const initialX = this.sprite.x |     return new Promise((resolve) => { | ||||||
|     const initialY = this.sprite.y |       const { | ||||||
|  |         x: targetX, | ||||||
|  |         y: targetY, | ||||||
|  |         rotation: targetRotation, | ||||||
|  |         duration = 10, | ||||||
|  |         width: targetWidth, | ||||||
|  |         height: targetHeight | ||||||
|  |       } = options | ||||||
|  |  | ||||||
|     const deltaX = x - this.sprite.x |       const initialX = this.sprite.x | ||||||
|     const deltaY = y - this.sprite.y |       const initialY = this.sprite.y | ||||||
|     let elapsed: number = 0 |       const initialRotation = this.sprite.rotation | ||||||
|     const duration: number = 10 |       const initialWidth = this.sprite.width | ||||||
|  |       const initialHeight = this.sprite.height | ||||||
|  |  | ||||||
|     const tick: any = (delta: any) => { |       const deltaX = targetX ? targetX - this.sprite.x : null | ||||||
|       elapsed += delta.deltaTime |       const deltaY = targetY ? targetY - this.sprite.y : null | ||||||
|       const progress = Math.min(elapsed / duration, 1) |       const deltaRotation = targetRotation ? targetRotation - this.sprite.rotation : null | ||||||
|  |       const deltaWidth = targetWidth ? targetWidth - this.sprite.width : null | ||||||
|  |       const deltaHeight = targetHeight ? targetHeight - this.sprite.height : null | ||||||
|  |  | ||||||
|       this.sprite.x = initialX + deltaX * progress |       let elapsed: number = 0 | ||||||
|       this.sprite.y = initialY + deltaY * progress |  | ||||||
|  |  | ||||||
|       if (progress === 1) { |       const tick: any = (delta: any) => { | ||||||
|         this.ticker?.remove(tick) |         elapsed += delta.deltaTime | ||||||
|  |         const progress = Math.min(elapsed / duration, 1) | ||||||
|  |  | ||||||
|  |         // Linear interpolation | ||||||
|  |         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) { | ||||||
|  |           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,87 +1,68 @@ | |||||||
| 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) { |     switch (event) { | ||||||
|       case 'session-created': |       case 'update-match-session-state': | ||||||
|         this.updateSessionState(data) |         this.updateSessionState(data) | ||||||
|         break |         break | ||||||
|       case 'game-finished': |       case 'update-game-state': | ||||||
|  |         this.updateGameState(data) | ||||||
|  |         break | ||||||
|  |       case 'update-player-state': | ||||||
|  |         this.updatePlayerState(data) | ||||||
|  |         break | ||||||
|  |       case 'server:player-turn': | ||||||
|  |         this.onCanMakeMoveEvent(data) | ||||||
|  |         break | ||||||
|       default: |       default: | ||||||
|         this.eventBus.publish(event, data) |         this.eventBus.publish(event, data) | ||||||
|         break |         break | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   handleGameEventAck(gameEvent: SocketEvent) { |   handleGameEventAck(gameEvent: SocketEvent, callback: any) { | ||||||
|     const { event, data } = gameEvent |     const { event, data } = gameEvent | ||||||
|     try { |     switch (event) { | ||||||
|       switch (event) { |       // case 'ask-client-for-move': | ||||||
|         case 'update-match-session-state': |       //   return this.handleCanMakeMoveEvent(data, callback) | ||||||
|           this.updateSessionState(data) |       default: | ||||||
|           break |         this.eventBus.publish(event, data) | ||||||
|         case 'update-game-state': |         break | ||||||
|           this.updateGameState(data) |  | ||||||
|           break |  | ||||||
|         case 'update-player-state': |  | ||||||
|           this.updatePlayerState(data) |  | ||||||
|           break |  | ||||||
|         case 'ask-client-for-move': |  | ||||||
|           return this.handleCanMakeMoveEvent(data) |  | ||||||
|         default: |  | ||||||
|           this.eventBus.publish(event, data) |  | ||||||
|           break |  | ||||||
|       } |  | ||||||
|       return { status: 'ok' } |  | ||||||
|     } catch (error) { |  | ||||||
|       return { status: 'error', error } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   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 | ||||||
|   }) | //   }) | ||||||
|   readyForStart.value = true | // } | ||||||
| } |  | ||||||
|  | const eventBus = useEventBusStore() | ||||||
|  | eventBus.subscribe('window-before-unload', () => { | ||||||
|  |   logger.debug('Window before unload') | ||||||
|  | }) | ||||||
|  |  | ||||||
| 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> | ||||||
		Reference in New Issue
	
	Block a user