This commit is contained in:
Jose Conde
2024-07-16 02:14:50 +02:00
parent 3755f2857a
commit 5392dce264
31 changed files with 883 additions and 183 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
VITE_LOG_LEVEL= 'error' VITE_LOG_LEVEL= 'trace'
VITE_API_URL= 'http://localhost:3000/api' VITE_API_URL= 'http://localhost:3000/api'
+4 -4
View File
@@ -3,13 +3,13 @@ require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = { module.exports = {
root: true, root: true,
'extends': [ extends: [
'plugin:vue/vue3-essential', 'plugin:vue/vue3-essential',
'eslint:recommended', 'eslint:recommended',
'@vue/eslint-config-typescript', '@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting' '@vue/eslint-config-prettier/skip-formatting',
], ],
parserOptions: { parserOptions: {
ecmaVersion: 'latest' ecmaVersion: 'latest',
} },
} }
+1 -1
View File
@@ -4,5 +4,5 @@
"tabWidth": 2, "tabWidth": 2,
"singleQuote": true, "singleQuote": true,
"printWidth": 100, "printWidth": 100,
"trailingComma": "none" "trailingComma": "all"
} }
+9
View File
@@ -8,6 +8,7 @@
"name": "domino-client", "name": "domino-client",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@pixi/sound": "^6.0.0",
"bulma": "^1.0.1", "bulma": "^1.0.1",
"colorette": "^2.0.20", "colorette": "^2.0.20",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
@@ -664,6 +665,14 @@
"resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz",
"integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==" "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA=="
}, },
"node_modules/@pixi/sound": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@pixi/sound/-/sound-6.0.0.tgz",
"integrity": "sha512-fEaCs2JmyYT1qqouFS3DydSccI35dyYD0pKK2hEbIGVDKUTvl224x0p4qme2YU9l465WRtM7gspLzP5fFf1mxQ==",
"peerDependencies": {
"pixi.js": "^8.0.0"
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+1
View File
@@ -14,6 +14,7 @@
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@pixi/sound": "^6.0.0",
"bulma": "^1.0.1", "bulma": "^1.0.1",
"colorette": "^2.0.20", "colorette": "^2.0.20",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15 -12
View File
@@ -17,8 +17,13 @@ export function getColorBackground(container: Container, colorName: string, alph
export function createContainer(options: ContainerOptions) { export function createContainer(options: ContainerOptions) {
const opts = { ...DEFAULT_CONTAINER_OPTIONS, ...options } const opts = { ...DEFAULT_CONTAINER_OPTIONS, ...options }
const container = new Container() const container = new Container()
container.x = opts.x
container.y = opts.y
if (opts.parent) {
opts.parent.addChild(container)
}
const rect = new Graphics().rect(opts.x, opts.y, opts.width, opts.height) const rect = new Graphics().rect(0, 0, opts.width, opts.height)
if (opts.color) { if (opts.color) {
rect.fill(opts.color) rect.fill(opts.color)
@@ -29,19 +34,17 @@ export function createContainer(options: ContainerOptions) {
rect.visible = opts.visible rect.visible = opts.visible
container.addChild(rect) container.addChild(rect)
if (opts.parent) {
opts.parent.addChild(container)
}
return container return container
} }
export function createButton( export function createButton(options: {
textStr: string, text: string
dimension: Dimension, dimension: Dimension
action: Function, action: Function
parent?: Container parent?: Container
): Container { }): Container {
const { x, y, width, height } = dimension const { text: textStr, dimension, action, parent } = options
const { x, y, width, height = 25 } = dimension
const rectangle = new Graphics().roundRect(x, y, width + 4, height + 4, 5).fill(0xffff00) const rectangle = new Graphics().roundRect(x, y, width + 4, height + 4, 5).fill(0xffff00)
const text = new Text({ const text = new Text({
text: textStr, text: textStr,
@@ -50,8 +53,8 @@ export function createButton(
fontSize: 12, fontSize: 12,
fontWeight: 'bold', fontWeight: 'bold',
fill: 0x121212, fill: 0x121212,
align: 'center' align: 'center',
} },
}) })
text.anchor = 0.5 text.anchor = 0.5
const container = new Container() const container = new Container()
+18 -1
View File
@@ -34,7 +34,7 @@ export interface MatchSessionDto {
maxPlayers: number maxPlayers: number
numPlayers: number numPlayers: number
waitingSeconds: number waitingSeconds: number
scoreboard: Map<string, number> scoreboard: { id: string; name: string; score: number }[]
matchWinner: PlayerDto | null matchWinner: PlayerDto | null
matchInProgress: boolean matchInProgress: boolean
playersReady: number playersReady: number
@@ -98,3 +98,20 @@ export interface AnimationOptions {
width?: number width?: number
height?: number height?: number
} }
export interface GameOptions {
boardScale?: number
handScale?: number
width?: number
height?: number
background?: string
teamed?: boolean
}
export interface GameSummary {
gameId: string
isBlocked: boolean
isTied: boolean
winner: PlayerDto
score: { id: string; name: string; score: number }[]
players?: PlayerDto[]
}
+269
View File
@@ -0,0 +1,269 @@
export const summaryMock = {
lastGame: {
gameId: '6cd2b32c-185e-4869-a3f9-7a1a718a00f7',
isBlocked: true,
isTied: false,
winner: {
id: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
name: 'Bob (AI)',
score: 14,
hand: [
{
id: '69c6e996-7dd4-4565-a49c-96b7ba35db7d',
flipped: false,
revealed: true,
playerId: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
},
],
teamedWith: null,
ready: true,
},
score: [
{
id: '668977662eb15e2ef3bdac3f',
name: 'arhuako',
score: 0,
},
{
id: '3494d8b9-6b15-49e5-8fec-15e2d6539f71',
name: 'Alice (AI)',
score: 0,
},
{
id: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
name: 'Bob (AI)',
score: 14,
},
{
id: '2eda2a20-a97a-4310-a431-3aaef916e602',
name: 'Charlie (AI)',
score: 0,
},
],
players: [
{
id: '668977662eb15e2ef3bdac3f',
name: 'arhuako',
score: 0,
hand: [
{
id: '93d523a2-ae4d-4b51-a7a7-2e740be2da14',
flipped: false,
revealed: true,
playerId: '668977662eb15e2ef3bdac3f',
},
],
teamedWith: null,
ready: true,
},
{
id: '3494d8b9-6b15-49e5-8fec-15e2d6539f71',
name: 'Alice (AI)',
score: 0,
hand: [
{
id: 'cf9a04f9-3d8e-4feb-afa6-34a8a61ec014',
flipped: false,
revealed: true,
playerId: '3494d8b9-6b15-49e5-8fec-15e2d6539f71',
},
],
teamedWith: null,
ready: true,
},
{
id: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
name: 'Bob (AI)',
score: 14,
hand: [
{
id: '69c6e996-7dd4-4565-a49c-96b7ba35db7d',
flipped: false,
revealed: true,
playerId: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
},
],
teamedWith: null,
ready: true,
},
{
id: '2eda2a20-a97a-4310-a431-3aaef916e602',
name: 'Charlie (AI)',
score: 0,
hand: [
{
id: '22d0a60c-b9e9-43f6-804b-6899221de04d',
flipped: false,
revealed: true,
playerId: '2eda2a20-a97a-4310-a431-3aaef916e602',
},
],
teamedWith: null,
ready: true,
},
],
},
sessionState: {
id: '669524c8b78825b8f9ede908',
name: 'Test #26',
creator: '668977662eb15e2ef3bdac3f',
players: [
{
id: '668977662eb15e2ef3bdac3f',
name: 'arhuako',
score: 0,
hand: [],
teamedWith: null,
ready: false,
},
{
id: '3494d8b9-6b15-49e5-8fec-15e2d6539f71',
name: 'Alice (AI)',
score: 0,
hand: [],
teamedWith: null,
ready: false,
},
{
id: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
name: 'Bob (AI)',
score: 0,
hand: [],
teamedWith: null,
ready: false,
},
{
id: '2eda2a20-a97a-4310-a431-3aaef916e602',
name: 'Charlie (AI)',
score: 0,
hand: [],
teamedWith: null,
ready: false,
},
],
playersReady: 0,
sessionInProgress: true,
maxPlayers: 4,
numPlayers: 4,
waitingForPlayers: false,
waitingSeconds: 0,
seed: '1721050312717-840b4s07gb8-8d980dd8',
mode: 'classic',
pointsToWin: 50,
status: 'in progress',
scoreboard: [
['arhuako', 0],
['Alice (AI)', 0],
['Bob (AI)', 14],
['Charlie (AI)', 0],
],
matchWinner: null,
matchInProgress: true,
gameSummaries: [
{
gameId: '6cd2b32c-185e-4869-a3f9-7a1a718a00f7',
isBlocked: true,
isTied: false,
winner: {
id: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
name: 'Bob (AI)',
score: 14,
hand: [
{
id: '69c6e996-7dd4-4565-a49c-96b7ba35db7d',
flipped: false,
revealed: true,
playerId: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
},
],
teamedWith: null,
ready: true,
},
score: [
{
id: '668977662eb15e2ef3bdac3f',
name: 'arhuako',
score: 0,
},
{
id: '3494d8b9-6b15-49e5-8fec-15e2d6539f71',
name: 'Alice (AI)',
score: 0,
},
{
id: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
name: 'Bob (AI)',
score: 14,
},
{
id: '2eda2a20-a97a-4310-a431-3aaef916e602',
name: 'Charlie (AI)',
score: 0,
},
],
players: [
{
id: '668977662eb15e2ef3bdac3f',
name: 'arhuako',
score: 0,
hand: [
{
id: '93d523a2-ae4d-4b51-a7a7-2e740be2da14',
flipped: false,
revealed: true,
playerId: '668977662eb15e2ef3bdac3f',
},
],
teamedWith: null,
ready: true,
},
{
id: '3494d8b9-6b15-49e5-8fec-15e2d6539f71',
name: 'Alice (AI)',
score: 0,
hand: [
{
id: 'cf9a04f9-3d8e-4feb-afa6-34a8a61ec014',
flipped: false,
revealed: true,
playerId: '3494d8b9-6b15-49e5-8fec-15e2d6539f71',
},
],
teamedWith: null,
ready: true,
},
{
id: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
name: 'Bob (AI)',
score: 14,
hand: [
{
id: '69c6e996-7dd4-4565-a49c-96b7ba35db7d',
flipped: false,
revealed: true,
playerId: '23ed7e37-84e2-4e4e-b833-e97aef351f18',
},
],
teamedWith: null,
ready: true,
},
{
id: '2eda2a20-a97a-4310-a431-3aaef916e602',
name: 'Charlie (AI)',
score: 0,
hand: [
{
id: '22d0a60c-b9e9-43f6-804b-6899221de04d',
flipped: false,
revealed: true,
playerId: '2eda2a20-a97a-4310-a431-3aaef916e602',
},
],
teamedWith: null,
ready: true,
},
],
},
],
},
}
+6 -2
View File
@@ -5,13 +5,16 @@ import { Game } from '@/game/Game'
import { useGameStore } from '@/stores/game' import { useGameStore } from '@/stores/game'
import { useEventBusStore } from '@/stores/eventBus' import { useEventBusStore } from '@/stores/eventBus'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useGameOptionsStore } from '@/stores/gameOptions'
const socketService: any = inject('socket') const socketService: any = inject('socket')
const gameStore = useGameStore() const gameStore = useGameStore()
const gameOptionsStore = useGameOptionsStore()
const eventBus = useEventBusStore() const eventBus = useEventBusStore()
const { playerState, sessionState } = storeToRefs(gameStore) const { playerState, sessionState } = storeToRefs(gameStore)
const { updateGameState } = gameStore const { updateGameState } = gameStore
const { gameOptions } = storeToRefs(gameOptionsStore)
const minScreenWidth = 800 const minScreenWidth = 800
const minScreenHeight = 700 const minScreenHeight = 700
@@ -31,11 +34,12 @@ const game = new Game(
width: screenWidth, width: screenWidth,
height: screenHeight, height: screenHeight,
boardScale, boardScale,
handScale: 1 handScale: 1,
background: gameOptions.value?.background || 'green',
}, },
socketService, socketService,
playerState.value?.id || '', playerState.value?.id || '',
sessionState.value?.id || '' sessionState.value?.id || '',
) )
// watch( // watch(
+34 -23
View File
@@ -8,6 +8,7 @@ import { LoggingService } from '@/services/LoggingService'
import { GlowFilter } from 'pixi-filters' import { GlowFilter } from 'pixi-filters'
import { ORIENTATION_ANGLES } from '@/common/constants' import { ORIENTATION_ANGLES } from '@/common/constants'
import type { OtherHand } from './OtherHand' import type { OtherHand } from './OtherHand'
import { sound } from '@pixi/sound'
export class Board extends EventEmitter { export class Board extends EventEmitter {
private _scale: number = 1 private _scale: number = 1
@@ -46,63 +47,61 @@ export class Board extends EventEmitter {
constructor(app: Application) { constructor(app: Application) {
super() super()
this.ticker = app.ticker this.ticker = app.ticker
this.height = app.canvas.height - 130 this.height = app.canvas.height - 130 - 130
this.width = app.canvas.width this.width = app.canvas.width
this.scaleX = Scale([0, this.width], [0, this.width]) this.scaleX = Scale([0, this.width], [0, this.width])
this.scaleY = Scale([0, this.height], [0, this.height]) this.scaleY = Scale([0, this.height], [0, this.height])
this.calculateScale() this.calculateScale()
this.container = createContainer({ this.container = createContainer({
y: 130,
width: this.width, width: this.width,
height: this.height, height: this.height,
parent: app.stage parent: app.stage,
}) })
const background = new Sprite(Assets.get('bg-green')) // this.container.y = 130
// background.width = this.width
// background.height = this.height
this.container.addChild(background)
this.initialContainer = createContainer({ this.initialContainer = createContainer({
width: this.width, width: this.width,
height: this.height, height: this.height,
// color: 0x1e2f23, // color: 0x1e2f23,
visible: false, visible: false,
parent: this.container parent: this.container,
}) })
this.tilesContainer = createContainer({ this.tilesContainer = createContainer({
width: this.width, width: this.width,
height: this.height, height: this.height,
// color: 0x1e2f23, // color: 0x1e2f23,
parent: this.container parent: this.container,
}) })
this.interactionContainer = createContainer({ this.interactionContainer = createContainer({
width: this.width, width: this.width,
height: this.height, height: this.height,
parent: this.container, parent: this.container,
visible: false visible: false,
}) })
createCrosshair(this.tilesContainer, 0xff0000, { createCrosshair(this.tilesContainer, 0xff0000, {
width: this.width, width: this.width,
height: this.height, height: this.height,
x: this.scaleX(0), x: this.scaleX(0),
y: this.scaleY(0) y: this.scaleY(0),
}) })
createCrosshair(this.interactionContainer, 0xffff00, { createCrosshair(this.interactionContainer, 0xffff00, {
width: this.width, width: this.width,
height: this.height, height: this.height,
x: this.scaleX(0), x: this.scaleX(0),
y: this.scaleY(0) y: this.scaleY(0),
}) })
this.textContainer = createContainer({ this.textContainer = createContainer({
width: this.width, width: this.width,
height: this.height, height: this.height,
parent: this.container parent: this.container,
}) })
this.showText('Starting game...') this.showText('Starting game...')
@@ -134,7 +133,13 @@ export class Board extends EventEmitter {
showText(text: string) { showText(text: string) {
this.textContainer.removeChildren() this.textContainer.removeChildren()
this.textContainer.addChild(createText(text, this.scaleX(0), 100)) this.textContainer.addChild(
createText({
text,
x: this.scaleX(0),
y: -10,
}),
)
} }
async setPlayerTurn(player: PlayerDto) { async setPlayerTurn(player: PlayerDto) {
@@ -169,7 +174,6 @@ export class Board extends EventEmitter {
} }
async 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'
@@ -234,11 +238,18 @@ export class Board extends EventEmitter {
tile.addTo(this.tilesContainer) tile.addTo(this.tilesContainer)
tile.reScale(this.scale) tile.reScale(this.scale)
this.tiles.push(tile) this.tiles.push(tile)
const moveSound = this.getRandomClickSound()
await this.animateTile(tile, x, y, orientation, move) await this.animateTile(tile, x, y, orientation, move)
sound.play(moveSound)
this.emit('game:tile-animation-ended', tile.toPlain()) this.emit('game:tile-animation-ended', tile.toPlain())
} }
getRandomClickSound() {
const sounds = ['snd-move-1', 'snd-move-2', 'snd-move-3', 'snd-move-4']
const index = Math.floor(Math.random() * sounds.length)
return sounds[index]
}
async animateTile(tile: Tile, x: number, y: number, orientation: string, move: Movement) { async animateTile(tile: Tile, x: number, y: number, orientation: string, move: Movement) {
const targetX = this.scaleX(x) const targetX = this.scaleX(x)
const targetY = this.scaleY(y) const targetY = this.scaleY(y)
@@ -246,13 +257,13 @@ export class Board extends EventEmitter {
x: targetX, x: targetX,
y: targetY, y: targetY,
rotation: ORIENTATION_ANGLES[orientation], rotation: ORIENTATION_ANGLES[orientation],
duration: 20 duration: 20,
} }
const tempAlpha = tile.alpha const tempAlpha = tile.alpha
tile.alpha = 0 tile.alpha = 0
const clonedTile = tile.clone() const clonedTile = tile.clone()
clonedTile.addTo(this.tilesContainer) clonedTile.addTo(this.tilesContainer)
const pos = this.getAnimationInitialPoosition(move) const pos = this.getAnimationInitialPosition(move)
clonedTile.setPosition(this.scaleX(pos.x), this.scaleY(pos.y)) clonedTile.setPosition(this.scaleX(pos.x), this.scaleY(pos.y))
await clonedTile.animateTo(animation) await clonedTile.animateTo(animation)
clonedTile.removeFromParent() clonedTile.removeFromParent()
@@ -261,7 +272,7 @@ export class Board extends EventEmitter {
tile.alpha = tempAlpha tile.alpha = tempAlpha
} }
getAnimationInitialPoosition(move: Movement): { x: number; y: number } { getAnimationInitialPosition(move: Movement): { x: number; y: number } {
const otherHand = this.otherPlayerHands.find((h) => h.player?.id === move.playerId) const otherHand = this.otherPlayerHands.find((h) => h.player?.id === move.playerId)
if (otherHand === undefined) { if (otherHand === undefined) {
return { x: 0, y: this.scaleY.inverse(this.height + 50) } return { x: 0, y: this.scaleY.inverse(this.height + 50) }
@@ -271,9 +282,9 @@ export class Board extends EventEmitter {
case 'left': case 'left':
return { x: this.scaleX.inverse(100), y: this.scaleY.inverse(100) } return { x: this.scaleX.inverse(100), y: this.scaleY.inverse(100) }
case 'right': case 'right':
return { x: 0, y: this.scaleY.inverse(100) }
case 'top':
return { x: this.scaleX.inverse(this.width - 100), y: this.scaleY.inverse(20) } return { x: this.scaleX.inverse(this.width - 100), y: this.scaleY.inverse(20) }
case 'top':
return { x: 0, y: this.scaleY.inverse(100) }
} }
} }
@@ -365,7 +376,7 @@ export class Board extends EventEmitter {
nextTileValidPoints( nextTileValidPoints(
tile: TileDto, tile: TileDto,
side: string, side: string,
validMoves: boolean[] validMoves: boolean[],
): ([number, number] | undefined)[] { ): ([number, number] | undefined)[] {
const isLeft = side === 'left' const isLeft = side === 'left'
const end = isLeft ? this.leftTile : this.rightTile const end = isLeft ? this.leftTile : this.rightTile
@@ -450,8 +461,8 @@ export class Board extends EventEmitter {
outerStrength: 1, outerStrength: 1,
innerStrength: 0, innerStrength: 0,
color: 0xffffff, color: 0xffffff,
quality: 0.5 quality: 0.5,
}) }),
]) ])
dot.setOrientation(direction ?? 'north') dot.setOrientation(direction ?? 'north')
// const dot = new Dot(this.ticker, this.scale) // const dot = new Dot(this.ticker, this.scale)
+58 -15
View File
@@ -1,13 +1,30 @@
import { Application, Assets } from 'pixi.js' import { Application, Assets, Container, Sprite } from 'pixi.js'
import { Board } from '@/game/Board' 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 { GameDto, Movement, PlayerDto, TileDto } from '@/common/interfaces' import type {
GameDto,
GameSummary,
MatchSessionDto,
Movement,
PlayerDto,
TileDto,
} from '@/common/interfaces'
import type { SocketIoClientService } from '@/services/SocketIoClientService' import type { SocketIoClientService } from '@/services/SocketIoClientService'
import { wait } from '@/common/helpers' import { wait } from '@/common/helpers'
import { Actions } from 'pixi-actions' import { Actions } from 'pixi-actions'
import { OtherHand } from './OtherHand' import { OtherHand } from './OtherHand'
import { GameSummayView } from './GameSummayView'
import { summaryMock } from '@/common/summarymock'
interface GameOptions {
boardScale: number
handScale: number
width: number
height: number
background: string
}
export class Game { export class Game {
public board!: Board public board!: Board
@@ -16,17 +33,20 @@ export class Game {
private selectedTile: TileDto | undefined private selectedTile: TileDto | undefined
private currentMove: Movement | undefined private currentMove: Movement | undefined
private otherHands: OtherHand[] = [] private otherHands: OtherHand[] = []
private backgroundLayer: Container = new Container()
private gameSummaryView!: GameSummayView
constructor( constructor(
private options: { boardScale: number; handScale: number; width: number; height: number } = { private options: GameOptions = {
boardScale: 1, boardScale: 1,
handScale: 1, handScale: 1,
width: 1200, width: 1200,
height: 800 height: 800,
background: 'bg-green',
}, },
private socketService: SocketIoClientService, private socketService: SocketIoClientService,
private playerId: string, private playerId: string,
private sessionId: string private sessionId: string,
) {} ) {}
async setup(): Promise<HTMLCanvasElement> { async setup(): Promise<HTMLCanvasElement> {
@@ -39,14 +59,16 @@ export class Game {
} }
async start(players: PlayerDto[] = []) { async start(players: PlayerDto[] = []) {
this.iniialStuff(this.app)
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.otherHands = [ this.otherHands = [
new OtherHand(this.app, 'left'), new OtherHand(this.app, 'left'),
new OtherHand(this.app, 'top'), new OtherHand(this.app, 'top'),
new OtherHand(this.app, 'right') new OtherHand(this.app, 'right'),
] ]
this.initOtherHands(players) this.initOtherHands(players)
this.gameSummaryView = new GameSummayView(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()
@@ -55,10 +77,18 @@ export class Game {
wait(3000) wait(3000)
this.socketService.sendMessage('client:set-client-ready', { this.socketService.sendMessage('client:set-client-ready', {
sessionId: this.sessionId, sessionId: this.sessionId,
userId: this.playerId userId: this.playerId,
}) })
} }
iniialStuff(app: Application) {
app.stage.addChild(this.backgroundLayer)
const background = new Sprite(Assets.get(`bg-${this.options.background}`))
background.width = this.app.canvas.width
background.height = this.app.canvas.height
this.backgroundLayer.addChild(background)
}
initOtherHands(players: PlayerDto[]) { initOtherHands(players: PlayerDto[]) {
const myIndex = players.findIndex((player) => player.id === this.playerId) const myIndex = players.findIndex((player) => player.id === this.playerId)
const copy = [...players] const copy = [...players]
@@ -107,25 +137,36 @@ export class Game {
const move: Movement = { const move: Movement = {
id: '', id: '',
type: 'pass', type: 'pass',
playerId: this.playerId playerId: this.playerId,
} }
this.socketService.sendMessage('client:player-move', { this.socketService.sendMessage('client:player-move', {
sessionId: this.sessionId, sessionId: this.sessionId,
move: move move: move,
}) })
await this.board.updateBoard(move, undefined) await this.board.updateBoard(move, undefined)
}) })
this.hand.on('nextClick', () => { this.gameSummaryView.on('nextClick', (data) => {
this.updateScoreboard(data.sessionState)
this.socketService.sendMessage('client:set-client-ready-for-next-game', { this.socketService.sendMessage('client:set-client-ready-for-next-game', {
userId: this.playerId, userId: this.playerId,
sessionId: this.sessionId sessionId: this.sessionId,
}) })
}) })
this.hand.on('hand-initialized', () => {}) this.hand.on('hand-initialized', () => {})
} }
private updateScoreboard(sessionState: MatchSessionDto) {
const scoreboard = sessionState.scoreboard
this.otherHands.forEach((hand) => {
const player: PlayerDto | undefined = hand.player
const name: string = player?.name || ''
const score = scoreboard.find((d) => d.name === name)?.score || 0
hand.setScore(score)
})
}
highlightMoves(tile: TileDto) { highlightMoves(tile: TileDto) {
this.selectedTile = tile this.selectedTile = tile
if (tile !== undefined) { if (tile !== undefined) {
@@ -168,7 +209,7 @@ export class Game {
tile: this.selectedTile, tile: this.selectedTile,
type: 'left', type: 'left',
playerId: this.playerId, playerId: this.playerId,
...data ...data,
} }
this.currentMove = move this.currentMove = move
const tile = this.hand.tileMoved(this.selectedTile) const tile = this.hand.tileMoved(this.selectedTile)
@@ -181,7 +222,7 @@ export class Game {
tile: this.selectedTile, tile: this.selectedTile,
type: 'right', type: 'right',
playerId: this.playerId, playerId: this.playerId,
...data ...data,
} }
this.currentMove = move this.currentMove = move
const tile = this.hand.tileMoved(this.selectedTile) const tile = this.hand.tileMoved(this.selectedTile)
@@ -192,12 +233,12 @@ export class Game {
if (tile !== null && tile !== undefined && tile.playerId === this.playerId) { if (tile !== null && tile !== undefined && tile.playerId === this.playerId) {
this.socketService.sendMessage('client:player-move', { this.socketService.sendMessage('client:player-move', {
sessionId: this.sessionId, sessionId: this.sessionId,
move: this.currentMove move: this.currentMove,
}) })
} else { } else {
this.socketService.sendMessage('client:animation-ended', { this.socketService.sendMessage('client:animation-ended', {
sessionId: this.sessionId, sessionId: this.sessionId,
userId: this.playerId userId: this.playerId,
}) })
} }
}) })
@@ -206,11 +247,13 @@ export class Game {
gameFinished(data: any) { gameFinished(data: any) {
this.hand.gameFinished() this.hand.gameFinished()
this.board.gameFinished(data) this.board.gameFinished(data)
this.gameSummaryView.setGameSummary(data, 'round')
} }
matchFinished(data: any) { matchFinished(data: any) {
// this.hand.matchFinished() // this.hand.matchFinished()
this.board.matchFinished(data) this.board.matchFinished(data)
this.gameSummaryView.setGameSummary(data, 'match')
} }
serverPlayerMove(data: any, playerId: string) { serverPlayerMove(data: any, playerId: string) {
+168
View File
@@ -0,0 +1,168 @@
import { createButton, createContainer } from '@/common/helpers'
import type { GameSummary, MatchSessionDto } from '@/common/interfaces'
import { EventEmitter, type Application, type Container } from 'pixi.js'
import { createText, whiteStyle, yellowStyle } from './utilities/fonts'
export class GameSummayView extends EventEmitter {
public width: number
public height: number
container!: Container
layer!: Container
gameSummary!: GameSummary
matchState!: MatchSessionDto
type: 'round' | 'match' = 'round'
constructor(app: Application) {
super()
this.width = 500
this.height = 400
this.container = createContainer({
width: this.width,
height: this.height,
x: app.canvas.width / 2 - this.width / 2,
y: app.canvas.height / 2 - this.height / 2,
parent: app.stage,
alpha: 0.7,
color: 0x121212,
})
this.layer = createContainer({
width: this.width,
height: this.height,
parent: this.container,
})
console.log('GameSummaryView created!')
this.container.visible = false
}
setGameSummary(data: any, type: 'round' | 'match') {
this.type = type
this.matchState = data.sessionState
this.gameSummary = data.lastGame
this.render()
this.container.visible = true
}
renderTitle(y: number = 20, title: string): number {
const text = createText({
text: title,
x: this.width / 2,
y,
style: yellowStyle(24),
})
this.layer.addChild(text)
return y + 24
}
renderWinner(y: number): number {
let line = y + 12
this.layer.addChild(
createText({
text: `Winner: ${this.gameSummary.winner.name}`,
x: this.width / 2,
y: line,
style: whiteStyle(20),
}),
)
if (this.gameSummary.isBlocked) {
line += 30
this.container.addChild(
createText({
text: '(Blocked)',
x: this.width / 2,
y: line,
style: whiteStyle(),
}),
)
}
line += 30
this.layer.addChild(
createText({
text: `Points this round: ${this.gameSummary.winner.score}`,
x: this.width / 2,
y: line,
style: whiteStyle(20),
}),
)
return line + 16
}
renderScores(y: number): number {
const scores = this.matchState.scoreboard
// this.type === 'round'
// ? this.gameSummary.score
// : this.matchState.scoreboard.map((d) => ({ name: d[0], score: d[1] }))
let line = y + 30
scores.forEach((score: any) => {
line = line + 30
this.layer.addChild(
createText({
text: `${score.name}:`,
x: 130,
y: line,
style: whiteStyle(18),
align: 'left',
}),
)
this.layer.addChild(
createText({
text: `${score.score}`,
x: 330,
y: line,
style: whiteStyle(18),
align: 'right',
}),
)
})
return line
}
renderButtons() {
if (this.type === 'round') {
this.layer.addChild(
createButton({
text: 'Next',
dimension: {
x: this.width / 2 - 25,
y: this.height - 50,
width: 60,
height: 25,
},
action: () => {
this.emit('nextClick', { sessionState: this.matchState })
this.container.visible = false
},
parent: this.layer,
}),
)
} else {
this.layer.addChild(
createButton({
text: 'Finish',
dimension: {
x: this.width / 2 - 25,
y: this.height - 50,
width: 60,
height: 25,
},
action: () => {
this.emit('finishClick', this.gameSummary)
this.container.visible = false
},
parent: this.layer,
}),
)
}
}
render() {
const title: string = this.type === 'round' ? 'Round Summary' : 'Match Finished!'
this.layer.removeChildren()
let y = this.renderTitle(30, title.toUpperCase())
y = this.renderWinner(y)
this.renderScores(y)
this.renderButtons()
}
}
+26 -26
View File
@@ -11,7 +11,6 @@ export class Hand extends EventEmitter {
tiles: Tile[] = [] tiles: Tile[] = []
container: Container = new Container() container: Container = new Container()
buttonPass: Container = new Container() buttonPass: Container = new Container()
buttonNext: Container = new Container()
height: number height: number
width: number width: number
ticker: Ticker ticker: Ticker
@@ -27,6 +26,7 @@ export class Hand extends EventEmitter {
availableTiles: Tile[] = [] availableTiles: Tile[] = []
tilesLayer!: Container tilesLayer!: Container
interactionsLayer!: Container interactionsLayer!: Container
score: number = 0
constructor(app: Application) { constructor(app: Application) {
super() super()
@@ -49,14 +49,14 @@ export class Hand extends EventEmitter {
height: this.height, height: this.height,
x: 0, x: 0,
y: 0, y: 0,
parent: this.container parent: this.container,
}) })
this.interactionsLayer = createContainer({ this.interactionsLayer = createContainer({
width: this.width, width: this.width,
height: this.height, height: this.height,
x: 0, x: 0,
y: 0, y: 0,
parent: this.container parent: this.container,
}) })
} }
@@ -65,16 +65,6 @@ export class Hand extends EventEmitter {
this.tiles = [] this.tiles = []
this.initialized = false this.initialized = false
this.buttonNext = createButton(
'NEXT',
{ x: this.width / 2 - 25, y: this.height / 2, width: 50, height: 20 },
() => {
this.tilesLayer.removeChildren()
this.interactionsLayer.removeChild(this.buttonNext)
this.emit('nextClick')
},
this.interactionsLayer
)
} }
get canMove() { get canMove() {
@@ -98,7 +88,7 @@ export class Hand extends EventEmitter {
this.availableTiles.forEach((tile) => { this.availableTiles.forEach((tile) => {
tile.animateTo({ tile.animateTo({
x: tile.x, x: tile.x,
y: tile.y - 10 y: tile.y - 10,
}) })
tile.interactive = true tile.interactive = true
}) })
@@ -108,7 +98,7 @@ export class Hand extends EventEmitter {
this.availableTiles.forEach((tile) => { this.availableTiles.forEach((tile) => {
tile.animateTo({ tile.animateTo({
x: tile.x, x: tile.x,
y: tile.y + 10 y: tile.y + 10,
}) })
tile.setPosition(tile.x, tile.y + 10) tile.setPosition(tile.x, tile.y + 10)
tile.interactive = false tile.interactive = false
@@ -128,7 +118,7 @@ export class Hand extends EventEmitter {
initialize(playerState: PlayerDto) { initialize(playerState: PlayerDto) {
this.tiles = this.createTiles(playerState) this.tiles = this.createTiles(playerState)
this.initialized = this.tiles.length > 0 this.initialized = this.tiles.length > 0
this.renderTiles() this.render()
this.emit('hand-updated', this.tiles) this.emit('hand-updated', this.tiles)
} }
@@ -187,31 +177,32 @@ export class Hand extends EventEmitter {
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 = createButton( this.buttonPass = createButton({
'PASS', text: 'PASS',
{ x, y: this.height / 2, width: 50, height: 20 }, dimension: { x, y: this.height / 2, width: 50, height: 20 },
() => { action: () => {
this.interactionsLayer.removeChild(this.buttonPass) this.interactionsLayer.removeChild(this.buttonPass)
this.emit('game:button-pass-click') this.emit('game:button-pass-click')
}, },
this.interactionsLayer parent: this.interactionsLayer,
) })
} }
update(playerState: PlayerDto) { update(playerState: PlayerDto) {
this.tilesLayer.removeChildren()
if (!this.initialized) { if (!this.initialized) {
this.initialize(playerState) this.initialize(playerState)
return return
} }
const missing: Tile | undefined = this.tiles.find( const missing: Tile | undefined = this.tiles.find(
(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.tilesLayer.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('hand-updated', this.tiles) this.emit('hand-updated', this.tiles)
} }
this.renderTiles() this.render()
} }
private createTiles(playerState: PlayerDto) { private createTiles(playerState: PlayerDto) {
@@ -230,8 +221,8 @@ export class Hand extends EventEmitter {
outerStrength: 2, outerStrength: 2,
innerStrength: 1, innerStrength: 1,
color: 0xffffff, color: 0xffffff,
quality: 0.5 quality: 0.5,
}) }),
]) ])
}) })
newTile.on('pointerout', () => { newTile.on('pointerout', () => {
@@ -251,4 +242,13 @@ export class Hand extends EventEmitter {
tile.setPosition(deltaX + tile.width / 2 + i * (tile.width + 5), tile.height / 2 + 20) tile.setPosition(deltaX + tile.width / 2 + i * (tile.width + 5), tile.height / 2 + 20)
}) })
} }
renderScore() {
//this.scoreLayer.removeChildren()
}
render() {
this.renderTiles()
this.renderScore()
}
} }
+39 -8
View File
@@ -4,7 +4,7 @@ import { Scale, type ScaleFunction } from './utilities/scale'
import { Tile } from './Tile' import { Tile } from './Tile'
import type { Movement, PlayerDto, TileDto } from '@/common/interfaces' import type { Movement, PlayerDto, TileDto } from '@/common/interfaces'
import { createContainer } from '@/common/helpers' import { createContainer } from '@/common/helpers'
import { createText, playerNameText } from './utilities/fonts' import { createText, playerNameText, scoreText } from './utilities/fonts'
export class OtherHand { export class OtherHand {
tilesInitialNumber: number = 7 tilesInitialNumber: number = 7
@@ -22,10 +22,12 @@ export class OtherHand {
logger: LoggingService = new LoggingService() logger: LoggingService = new LoggingService()
tilesLayer!: Container tilesLayer!: Container
interactionsLayer!: Container interactionsLayer!: Container
scoreLayer: Container = new Container()
score: number = 0
constructor( constructor(
private app: Application, private app: Application,
public position: 'left' | 'right' | 'top' = 'left' public position: 'left' | 'right' | 'top' = 'left',
) { ) {
this.height = 100 this.height = 100
this.width = 300 this.width = 300
@@ -37,11 +39,23 @@ export class OtherHand {
this.container.y = y this.container.y = y
this.calculateScale() this.calculateScale()
this.initLayers() this.initLayers()
this.render()
} }
setPlayer(player: PlayerDto) { setPlayer(player: PlayerDto) {
this.player = player this.player = player
this.container.addChild(createText(`${player.name}`, this.width / 2, 12, playerNameText)) this.container.addChild(
createText({
text: `${player.name}`,
x: this.width / 2,
y: 12,
style: playerNameText,
}),
)
}
setScore(score: number) {
this.score = score
} }
setHand(tiles: TileDto[]) { setHand(tiles: TileDto[]) {
@@ -54,7 +68,19 @@ export class OtherHand {
this.render() this.render()
} }
private render() { private renderScore() {
this.scoreLayer.removeChildren()
const text = createText({
text: `${this.score}`,
x: this.width - 5,
y: 50,
style: scoreText,
})
text.anchor.set(1, 0.5)
this.scoreLayer.addChild(text)
}
private renderTiles() {
this.tilesLayer.removeChildren() this.tilesLayer.removeChildren()
const x = -9 const x = -9
this.hand.forEach((tile, index) => { this.hand.forEach((tile, index) => {
@@ -63,6 +89,11 @@ export class OtherHand {
}) })
} }
private render() {
this.renderTiles()
this.renderScore()
}
private addBg() { private addBg() {
const bg = new Sprite(Texture.WHITE) const bg = new Sprite(Texture.WHITE)
bg.alpha = 0.08 bg.alpha = 0.08
@@ -96,17 +127,17 @@ export class OtherHand {
height: this.height, height: this.height,
x: 0, x: 0,
y: 0, y: 0,
parent: this.container parent: this.container,
}) })
this.interactionsLayer = createContainer({ this.interactionsLayer = createContainer({
width: this.width, width: this.width,
height: this.height, height: this.height,
x: 0, x: 0,
y: 0, y: 0,
parent: this.container parent: this.container,
}) })
this.container.addChild(this.tilesLayer) // this.container.addChild(this.tilesLayer)
this.container.addChild(this.interactionsLayer) this.container.addChild(this.scoreLayer)
} }
private calculateScale() { private calculateScale() {
+16 -4
View File
@@ -27,10 +27,16 @@ import tile6_3 from '@/assets/images/tiles/6-3.png'
import tile6_4 from '@/assets/images/tiles/6-4.png' import tile6_4 from '@/assets/images/tiles/6-4.png'
import tile6_5 from '@/assets/images/tiles/6-5.png' import tile6_5 from '@/assets/images/tiles/6-5.png'
import tile6_6 from '@/assets/images/tiles/6-6.png' import tile6_6 from '@/assets/images/tiles/6-6.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' import bg_green from '@/assets/images/backgrounds/bg-green.png'
import bg_red from '@/assets/images/backgrounds/bg-red.png'
import bg_yellow from '@/assets/images/backgrounds/bg-yellow.png'
import snd_move_1 from '@/assets/sounds/move-1.mp3'
import snd_move_2 from '@/assets/sounds/move-2.mp3'
import snd_move_3 from '@/assets/sounds/move-3.mp3'
import snd_move_4 from '@/assets/sounds/move-4.mp3'
import snd_intro from '@/assets/sounds/intro.mp3'
export const assets = [ export const assets = [
{ alias: 'tile-back', src: tileBack }, { alias: 'tile-back', src: tileBack },
@@ -62,8 +68,14 @@ export const assets = [
{ alias: 'tile-6_4', src: tile6_4 }, { alias: 'tile-6_4', src: tile6_4 },
{ alias: 'tile-6_5', src: tile6_5 }, { alias: 'tile-6_5', src: tile6_5 },
{ alias: 'tile-6_6', src: tile6_6 }, { alias: 'tile-6_6', src: tile6_6 },
{ 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-gray', src: bg_1 },
{ alias: 'bg-green', src: bg_green } { alias: 'bg-green', src: bg_green },
{ alias: 'bg-red', src: bg_red },
{ alias: 'bg-yellow', src: bg_yellow },
{ alias: 'snd-move-1', src: snd_move_1 },
{ alias: 'snd-move-2', src: snd_move_2 },
{ alias: 'snd-move-3', src: snd_move_3 },
{ alias: 'snd-move-4', src: snd_move_4 },
{ alias: 'snd-intro', src: snd_intro },
] ]
+106 -6
View File
@@ -1,19 +1,27 @@
import { Text, TextStyle } from 'pixi.js' import {
Container,
Text,
TextStyle,
type TextStyleAlign,
type TextStyleFontStyle,
type TextStyleFontWeight,
type TextStyleOptions,
} from 'pixi.js'
export const dropShadowStyle = { export const dropShadowStyle = {
alpha: 0.5, alpha: 0.5,
angle: 0.3, angle: 0.3,
blur: 5, blur: 5,
distance: 4 distance: 4,
} }
export const mainText = new TextStyle({ export const mainText = new TextStyle({
dropShadow: dropShadowStyle, dropShadow: dropShadowStyle,
fill: '#b71a1a', fill: '#aaaaaa',
fontFamily: 'Arial, Helvetica, sans-serif', fontFamily: 'Arial, Helvetica, sans-serif',
fontWeight: 'bold', fontWeight: 'bold',
letterSpacing: 1, letterSpacing: 1,
stroke: '#658f56' stroke: '#565656',
}) })
export const playerNameText = new TextStyle({ export const playerNameText = new TextStyle({
@@ -23,12 +31,104 @@ export const playerNameText = new TextStyle({
letterSpacing: 1, letterSpacing: 1,
stroke: '#565656', stroke: '#565656',
fontSize: 15, fontSize: 15,
fontWeight: 'bold' fontWeight: 'bold',
}) })
export function createText(str: string, x: number, y: number, style: TextStyle = mainText) { export const summaryTitle = new TextStyle({
dropShadow: dropShadowStyle,
fill: '#a2a2a2',
fontFamily: 'Arial, Helvetica, sans-serif',
letterSpacing: 1,
stroke: '#565656',
fontSize: 15,
fontWeight: 'bold',
})
export const scoreText = new TextStyle({
dropShadow: dropShadowStyle,
fill: '#aaaaaa',
fontFamily: 'Arial, Helvetica, sans-serif',
letterSpacing: 1,
stroke: '#565656',
fontSize: 32,
fontWeight: 'bold',
})
function getStyle(styleOptions: TextStyleOptions = {}) {
const {
fill = 0xa2a2a2,
stroke = 0x565656,
fontSize = 15,
fontFamily = 'Arial, Helvetica, sans-serif',
fontWeight = 'normal',
fontStyle = 'normal',
dropShadow,
letterSpacing = 1,
} = styleOptions
const style = new TextStyle({
fill,
fontFamily,
letterSpacing,
stroke,
fontSize,
fontStyle,
fontWeight: fontWeight as any,
dropShadow: dropShadow ? dropShadowStyle : undefined,
})
return style
}
export const whiteStyle = (
fontSize: number = 15,
fontWeight: TextStyleFontWeight = 'normal',
dropShadow: boolean = true,
) =>
getStyle({
fontSize,
fontWeight,
dropShadow,
})
export const yellowStyle = (
fontSize: number = 15,
fontWeight: TextStyleFontWeight = 'normal',
dropShadow: boolean = true,
) =>
getStyle({
fill: 0xffff00,
fontSize,
fontWeight,
dropShadow,
})
interface TextOptions {
text: string
x: number
y: number
style?: TextStyle
container?: Container
align?: 'left' | 'center' | 'right'
fontStyle?: TextStyleFontStyle
}
export function createText(textOptions: TextOptions) {
const defaultOptions = { style: whiteStyle(), align: 'center' }
const { text: str, x, y, style, container, align } = { ...defaultOptions, ...textOptions }
const text = new Text({ text: str, style }) const text = new Text({ text: str, style })
switch (align) {
case 'center':
text.anchor.set(0.5, 0.5) text.anchor.set(0.5, 0.5)
break
case 'left':
text.anchor.set(0, 0.5)
break
case 'right':
text.anchor.set(1, 0.5)
break
}
if (container) container.addChild(text)
text.x = x text.x = x
text.y = y text.y = y
return text return text
+9 -10
View File
@@ -23,8 +23,8 @@ export class SocketIoClientService extends ServiceBase {
} }
this.socket = io(this.url, { this.socket = io(this.url, {
auth: { auth: {
token: jwt.value token: jwt.value,
} },
}) })
this.socket.on('connect', () => { this.socket.on('connect', () => {
if (this.socket && this.socket.recovered) { if (this.socket && this.socket.recovered) {
@@ -62,14 +62,6 @@ export class SocketIoClientService extends ServiceBase {
// Custom events // Custom events
// this.socket.on('makeMove', async (data: any, callback: any) => {
// callback(await this.gameEventManager.handleCanMakeMoveEvent(data))
// })
// this.socket.on('chooseTile', async (data: any, callback: any) => {
// callback(await this.gameEventManager.handleCanSelectTileEvent())
// })
this.socket.on('server:game-event', (data: any) => { this.socket.on('server:game-event', (data: any) => {
this.gameEventManager.handleGameEvent(data) this.gameEventManager.handleGameEvent(data)
}) })
@@ -77,6 +69,13 @@ export class SocketIoClientService extends ServiceBase {
this.socket.on('server:game-event-ack', async (data: any, callback: any) => { this.socket.on('server:game-event-ack', async (data: any, callback: any) => {
await this.gameEventManager.handleGameEventAck(data, callback) await this.gameEventManager.handleGameEventAck(data, callback)
}) })
this.socket.onAny((eventName, eventData) => {
if (eventName === 'server:game-event' || eventName === 'server:game-event-ack') {
const { event, data } = eventData
this.logger.debug(`Received event: ${event}`, data)
}
})
} }
sendMessage(event: string, data: any): void { sendMessage(event: string, data: any): void {
+9
View File
@@ -0,0 +1,9 @@
import type { GameOptions } from '@/common/interfaces'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useGameOptionsStore = defineStore('gameOptions', () => {
const gameOptions = ref<GameOptions>()
return { gameOptions }
})
+11 -12
View File
@@ -40,20 +40,16 @@ function copySeed() {
<template> <template>
<div class="block"> <div class="block">
<section class="block info"> <section class="block info">
<p> <p>Running: {{ sessionState?.sessionInProgress }}</p>
Running: {{ sessionState?.sessionInProgress }} Seed: {{ sessionState?.seed }} <p>Seed: {{ sessionState?.seed }}</p>
<button @click="copySeed">Copy!</button>
</p>
<p> <p>
FreeEnds: {{ gameState?.boardFreeEnds }} - Current Player:{{ FreeEnds: {{ gameState?.boardFreeEnds }} - Current Player:{{
gameState?.currentPlayer?.name gameState?.currentPlayer?.name
}} }}
- Score: {{ sessionState?.scoreboard }}
</p>
<p v-if="sessionState?.id">
SessionID: {{ sessionState.id }} PlayerID: {{ playerState?.id }} - canMakeMove
{{ canMakeMove }}
</p> </p>
<p>Score: {{ sessionState?.scoreboard }}</p>
<p v-if="sessionState?.id">SessionID: {{ sessionState.id }}</p>
<p>PlayerID: {{ playerState?.id }}</p>
</section> </section>
<section class="block"> <section class="block">
<div class="game-container"> <div class="game-container">
@@ -107,9 +103,12 @@ function copySeed() {
justify-content: center; justify-content: center;
} }
.info { .info {
position: absolute; color: white;
top: 0; opacity: 0.1;
left: 0; position: fixed;
top: 200px;
left: 10px;
z-index: 20; z-index: 20;
pointer-events: none;
} }
</style> </style>
+62 -40
View File
@@ -9,25 +9,27 @@ import type { MatchSessionDto } from '@/common/interfaces'
import { useEventBusStore } from '@/stores/eventBus' import { useEventBusStore } from '@/stores/eventBus'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { copyToclipboard } from '@/common/helpers' import { copyToclipboard } from '@/common/helpers'
import { useGameOptionsStore } from '@/stores/gameOptions'
let background = ref<string>('green')
let teamed = ref<boolean>(false)
let seed = ref<string>('') let seed = ref<string>('')
let sessionName = ref('Test Value') let sessionName = ref('Test Value')
let sessionId = ref('')
let matchSessions = ref<MatchSessionDto[]>([]) let matchSessions = ref<MatchSessionDto[]>([])
let dataInterval: any let dataInterval: any
const router = useRouter() const router = useRouter()
const gameStore = useGameStore() const gameStore = useGameStore()
const auth = useAuthStore() const auth = useAuthStore()
const gameOptionsStore = useGameOptionsStore()
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
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
const { readyForStart, sessionState, isSessionStarted, playerState, amIHost } = const { sessionState, isSessionStarted, playerState, amIHost } = storeToRefs(gameStore)
storeToRefs(gameStore)
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
const { user } = storeToRefs(auth) const { user } = storeToRefs(auth)
const { gameOptions } = storeToRefs(gameOptionsStore)
// function setPlayerReady() { // function setPlayerReady() {
// logger.debug('Starting game') // logger.debug('Starting game')
@@ -53,26 +55,12 @@ eventBus.subscribe('window-before-unload', () => {
async function createMatch() { async function createMatch() {
logger.debug('Creating match') logger.debug('Creating match')
await socketService.connect() await socketService.connect()
gameOptions.value = { background: background.value }
const id = await gameService.createMatchSession(sessionName.value, seed.value) const id = await gameService.createMatchSession(sessionName.value, seed.value)
logger.debug('Match created successfully') logger.debug('Match created successfully')
router.push({ name: 'match', params: { id } }) router.push({ name: 'match', params: { id } })
} }
async function cancelMatch() {
logger.debug('Cancelling match')
await gameService.cancelMatchSession(sessionId.value)
await socketService.disconnect()
sessionId.value = ''
seed.value = ''
sessionName.value = ''
updateSessionState(undefined)
updatePlayerState(undefined)
updateGameState(undefined)
logger.debug('Match cancelled successfully')
loadData()
}
async function joinMatch(id: string) { async function joinMatch(id: string) {
if (id) { if (id) {
await socketService.connect() await socketService.connect()
@@ -90,19 +78,16 @@ async function deleteMatch(id: string) {
async function loadData() { async function loadData() {
const listResponse = await gameService.listMatchSessions() const listResponse = await gameService.listMatchSessions()
console.log('listResponse :>> ', listResponse)
matchSessions.value = listResponse.data matchSessions.value = listResponse.data
sessionName.value = `Test #${listResponse.pagination.total + 1}` sessionName.value = `Test #${listResponse.pagination.total + 1}`
} }
onMounted(() => { onMounted(() => {
logger.debug('Home view mounted')
loadData() loadData()
dataInterval = setInterval(loadData, 5000) dataInterval = setInterval(loadData, 5000)
}) })
onUnmounted(() => { onUnmounted(() => {
logger.debug('Home view unmounted')
clearInterval(dataInterval) clearInterval(dataInterval)
}) })
@@ -117,32 +102,58 @@ function copy(sessionSeed: string) {
<section class="section"> <section class="section">
<h1 class="title is-2">Welcome to the {{ user.username }}'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> <div class="field">
<p>{{ sessionState || 'No session' }}</p> <label class="label">Name</label>
<p>{{ playerState || 'No player state' }}</p> <div class="control">
<p>Session started: {{ isSessionStarted }}</p> <input type="text" class="input" v-model="sessionName" placeholder="Session Name" />
<p>Host: {{ amIHost }}</p>
</div> </div>
<div class="block" v-if="!isSessionStarted"> </div>
<div class="grid"> <div class="field">
<div class="cell"> <label class="label">Seed</label>
<div class="control">
<input <input
type="text"
class="input" class="input"
style="margin-bottom: 0" style="margin-bottom: 0"
v-model="sessionName" v-model="seed"
placeholder="Session Name" placeholder="Type the session seed here!"
/> />
</div> </div>
</div>
<div class="grid">
<div class="cell"> <div class="cell">
<input class="input" style="margin-bottom: 0" v-model="seed" placeholder="Seed" /> <div class="field">
<label for="background" class="label">Background color</label>
<div class="control">
<div class="select">
<select v-model="background" name="background">
<option value="green">Green Fabric</option>
<option value="gray">Gray Fabric</option>
<option value="blue">Blue Fabric</option>
<option value="yellow">Yellow Fabric</option>
<option value="red">Red Fabric</option>
</select>
</div> </div>
</div> </div>
</div> </div>
<button class="button" @click="createMatch" v-if="!isSessionStarted"> <div class="field">
Create Match Session <div class="control">
</button> <label for="teamed" class="checkbox">
<input v-model="teamed" name="teamed" type="checkbox" />
Crossed game ({{ teamed }})
</label>
</div>
</div>
</div>
<div class="cell"></div>
</div>
</div>
<div class="block" v-if="!isSessionStarted"></div>
<button class="button is-primary" @click.once="createMatch">Create Match Session</button>
</section> </section>
<section class="section available-sessions" v-if="!isSessionStarted"> <section class="section available-sessions">
<h2 class="title is-4">Available Sessions</h2> <h2 class="title is-4">Available Sessions</h2>
<div class="block"> <div class="block">
<div v-if="matchSessions.length === 0"> <div v-if="matchSessions.length === 0">
@@ -150,16 +161,27 @@ function copy(sessionSeed: string) {
</div> </div>
<div v-else class="grid is-col-min-12"> <div v-else class="grid is-col-min-12">
<div class="cell" v-for="session in matchSessions" :key="session.id"> <div class="cell" v-for="session in matchSessions" :key="session.id">
<div class="card">
<div class="card-content">
<p class="title is-6">{{ session.name }}</p> <p class="title is-6">{{ session.name }}</p>
<p>ID: {{ session._id }}</p> <p>ID: {{ session._id }}</p>
<p>Players: {{ session.players.length }}</p> <p>Players: {{ session.players.length }}</p>
<p> <p>
Seed: {{ session.seed }} Seed: {{ session.seed }}
<button @click="() => copy(session.seed)">Copy</button> <button class="button is-small" @click="() => copy(session.seed)">Copy</button>
</p> </p>
<p>Status: {{ session.status }}</p> <p>Status: {{ session.status }}</p>
<button class="button" @click="() => joinMatch(session._id)">Join</button> <div class="buttons is-centered mt-4"></div>
<button class="button" @click="() => deleteMatch(session._id)">Delete</button> </div>
<div class="card-footer">
<p class="card-footer-item">
<a href="#" @click.once.prevent="() => joinMatch(session._id)"> Join </a>
</p>
<p class="card-footer-item">
<a href="#" @click.once.prevent="() => deleteMatch(session._id)"> Delete </a>
</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
+6 -3
View File
@@ -4,6 +4,7 @@ import type { GameService } from '@/services/GameService'
import type { LoggingService } from '@/services/LoggingService' import type { LoggingService } from '@/services/LoggingService'
import { useEventBusStore } from '@/stores/eventBus' import { useEventBusStore } from '@/stores/eventBus'
import { useGameStore } from '@/stores/game' import { useGameStore } from '@/stores/game'
import { useGameOptionsStore } from '@/stores/gameOptions'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { inject, onBeforeMount, ref } from 'vue' import { inject, onBeforeMount, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
@@ -12,6 +13,7 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const gameStore = useGameStore() const gameStore = useGameStore()
const eventBus = useEventBusStore() const eventBus = useEventBusStore()
const gameOptionsStore = useGameOptionsStore()
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
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
@@ -22,6 +24,7 @@ let matchSession = ref<MatchSessionDto | undefined>(undefined)
const { readyForStart, sessionState, isSessionStarted, playerState, amIHost } = const { readyForStart, sessionState, isSessionStarted, playerState, amIHost } =
storeToRefs(gameStore) storeToRefs(gameStore)
const { updateSessionState, updatePlayerState, updateGameState } = gameStore const { updateSessionState, updatePlayerState, updateGameState } = gameStore
const { gameOptions } = storeToRefs(gameOptionsStore)
async function setPlayerReady() { async function setPlayerReady() {
logger.debug('Starting game') logger.debug('Starting game')
@@ -35,7 +38,7 @@ async function setPlayerReady() {
} }
await socketService.sendMessage('client:set-player-ready', { await socketService.sendMessage('client:set-player-ready', {
userId: playerState.value.id, userId: playerState.value.id,
sessionId: sessionState.value.id sessionId: sessionState.value.id,
}) })
} }
@@ -45,7 +48,7 @@ async function startMatch() {
if (sessionId) { if (sessionId) {
await socketService.sendMessageWithAck('client:start-session', { await socketService.sendMessageWithAck('client:start-session', {
sessionId: sessionId, sessionId: sessionId,
playerId: playerId playerId: playerId,
}) })
} }
} }
@@ -89,7 +92,7 @@ onBeforeMount(() => {
<template> <template>
<div> <div>
<h1 class="title is-2">Match Page</h1> <h1 class="title is-2">Match Page {{ isSessionStarted }}</h1>
<div class="block" v-if="matchSession"> <div class="block" v-if="matchSession">
<p>Session ID: {{ matchSession._id }}</p> <p>Session ID: {{ matchSession._id }}</p>
<p>Session Name: {{ matchSession.name }}</p> <p>Session Name: {{ matchSession.name }}</p>