working flow
This commit is contained in:
parent
1b058db6c0
commit
3755f2857a
@ -1,6 +1,7 @@
|
||||
import { Graphics, Container, Text } from 'pixi.js'
|
||||
import type { ContainerOptions, Dimension, TileDto } from './interfaces'
|
||||
import { DEFAULT_CONTAINER_OPTIONS } from './constants'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
|
||||
export function getColorBackground(container: Container, colorName: string, alpha: number = 0.5) {
|
||||
const graphics = new Graphics()
|
||||
@ -22,6 +23,10 @@ export function createContainer(options: ContainerOptions) {
|
||||
if (opts.color) {
|
||||
rect.fill(opts.color)
|
||||
}
|
||||
if (opts.alpha) {
|
||||
rect.alpha = opts.alpha
|
||||
}
|
||||
|
||||
rect.visible = opts.visible
|
||||
container.addChild(rect)
|
||||
if (opts.parent) {
|
||||
@ -78,6 +83,15 @@ export function createButton(
|
||||
return container
|
||||
}
|
||||
|
||||
export function createCrosshair(container: Container, color: number = 0xff0000, d: Dimension) {
|
||||
const verticalLine = new Graphics().moveTo(d.x, 0).lineTo(d.x, d.height).stroke(color)
|
||||
const horizontalLine = new Graphics().moveTo(0, d.y).lineTo(d.width, d.y).stroke(color)
|
||||
verticalLine.alpha = 0.2
|
||||
horizontalLine.alpha = 0.2
|
||||
container.addChild(verticalLine)
|
||||
container.addChild(horizontalLine)
|
||||
}
|
||||
|
||||
export async function wait(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
@ -91,3 +105,8 @@ export function isTilePair(tile: TileDto): boolean {
|
||||
export function isTileVertical(tile: TileDto): boolean {
|
||||
return tile.orientation === 'north' || tile.orientation === 'south'
|
||||
}
|
||||
|
||||
export function copyToclipboard(value: string) {
|
||||
const { toClipboard } = useClipboard()
|
||||
toClipboard(value)
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ export interface ContainerOptions {
|
||||
color?: number
|
||||
visible?: boolean
|
||||
parent?: Container
|
||||
alpha?: number
|
||||
}
|
||||
|
||||
export interface Dimension {
|
||||
|
@ -1,31 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import type { MatchSessionDto, GameDto, PlayerDto } from '@/common/interfaces'
|
||||
import { onMounted, onUnmounted, ref, watch, inject } from 'vue'
|
||||
import type { GameDto, PlayerDto } from '@/common/interfaces'
|
||||
import { onMounted, onUnmounted, ref, inject, toRaw } from 'vue'
|
||||
import { Game } from '@/game/Game'
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { useEventBusStore } from '@/stores/eventBus'
|
||||
import type { LoggingService } from '@/services/LoggingService'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
|
||||
const emit = defineEmits(['move'])
|
||||
const socketService: any = inject('socket')
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const eventBus = useEventBusStore()
|
||||
const { playerState, sessionState, canMakeMove } = storeToRefs(gameStore)
|
||||
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
|
||||
const { playerState, sessionState } = storeToRefs(gameStore)
|
||||
const { updateGameState } = gameStore
|
||||
|
||||
const minScreenWidth = 800
|
||||
const minScreenHeight = 700
|
||||
|
||||
let screenWidth = window.innerWidth - 10
|
||||
let screenHeight = window.innerHeight - 10
|
||||
|
||||
if (screenWidth < minScreenWidth) screenWidth = minScreenWidth
|
||||
if (screenHeight < minScreenHeight) screenHeight = minScreenHeight
|
||||
|
||||
const boardScale = screenWidth > 1200 ? 0.8 : screenWidth > 1200 ? 0.7 : 0.6
|
||||
|
||||
let appEl = ref<HTMLElement | null>(null)
|
||||
|
||||
const game = new Game(
|
||||
{
|
||||
width: 1200,
|
||||
height: 650,
|
||||
boardScale: 0.7,
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
boardScale,
|
||||
handScale: 1
|
||||
},
|
||||
emit,
|
||||
socketService,
|
||||
playerState.value?.id || '',
|
||||
sessionState.value?.id || ''
|
||||
@ -61,36 +68,31 @@ onMounted(async () => {
|
||||
const canvas = await game.setup()
|
||||
appEl.value.appendChild(canvas)
|
||||
await game.preload()
|
||||
await game.start()
|
||||
await game.start(sessionState?.value?.players)
|
||||
|
||||
eventBus.subscribe('server:game-finished', (data) => {
|
||||
console.log('server:game-finished :>> ', data)
|
||||
game.gameFinished(data)
|
||||
})
|
||||
|
||||
eventBus.subscribe('server:server-player-move', (data) => {
|
||||
console.log('server:player-move :>> ', data)
|
||||
game.serverPlayerMove(data, playerState.value?.id ?? '')
|
||||
})
|
||||
|
||||
eventBus.subscribe('game:player-turn-started', (data: any) => {
|
||||
console.log('game:player-turn-started :>> ', data)
|
||||
// game.setCanMakeMove(true)
|
||||
})
|
||||
|
||||
eventBus.subscribe('server:hand-dealt', (playerState: PlayerDto) => {
|
||||
console.log('server:hand-dealt :>> ', playerState)
|
||||
game.hand.update(playerState)
|
||||
eventBus.subscribe('server:hand-dealt', (data: { player: PlayerDto; gameState: GameDto }) => {
|
||||
game.hand.update(data.player)
|
||||
game.updateOtherHands(data.gameState)
|
||||
})
|
||||
|
||||
eventBus.subscribe('server:next-turn', (gameState: GameDto) => {
|
||||
console.log('server:next-turn :>> ', gameState)
|
||||
updateGameState(gameState)
|
||||
game.setNextPlayer(gameState)
|
||||
})
|
||||
|
||||
eventBus.subscribe('server:match-finished', (data) => {
|
||||
console.log('server:match-finished :>> ', data)
|
||||
game.matchFinished(data)
|
||||
})
|
||||
|
||||
@ -115,7 +117,7 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
game.destroy()
|
||||
//game.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -1,27 +1,18 @@
|
||||
import {
|
||||
Application,
|
||||
Assets,
|
||||
Container,
|
||||
EventEmitter,
|
||||
Graphics,
|
||||
Sprite,
|
||||
Text,
|
||||
Ticker
|
||||
} from 'pixi.js'
|
||||
import { Application, Assets, Container, EventEmitter, Sprite, Text, Ticker } from 'pixi.js'
|
||||
import { Scale, type ScaleFunction } from '@/game/utilities/scale'
|
||||
import type { AnimationOptions, Movement, PlayerDto, TileDto } from '@/common/interfaces'
|
||||
import { Tile } from '@/game/Tile'
|
||||
import { DIRECTIONS, createContainer, isTilePair } from '@/common/helpers'
|
||||
import { DIRECTIONS, createContainer, createCrosshair, isTilePair } from '@/common/helpers'
|
||||
import { createText } from '@/game/utilities/fonts'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
import { inject } from 'vue'
|
||||
import { GlowFilter } from 'pixi-filters'
|
||||
import { ORIENTATION_ANGLES } from '@/common/constants'
|
||||
import type { OtherHand } from './OtherHand'
|
||||
|
||||
export class Board extends EventEmitter {
|
||||
private _scale: number = 1
|
||||
private _canMove: boolean = false
|
||||
private logger = inject<LoggingService>('logger')!
|
||||
private logger: LoggingService = new LoggingService()
|
||||
|
||||
ticker: Ticker
|
||||
height: number
|
||||
@ -50,6 +41,7 @@ export class Board extends EventEmitter {
|
||||
playerHand: Tile[] = []
|
||||
firstTile?: Tile
|
||||
currentPlayer!: PlayerDto
|
||||
otherPlayerHands: OtherHand[] = []
|
||||
|
||||
constructor(app: Application) {
|
||||
super()
|
||||
@ -93,18 +85,19 @@ export class Board extends EventEmitter {
|
||||
visible: false
|
||||
})
|
||||
|
||||
const verticalLine = new Graphics()
|
||||
.moveTo(this.scaleX(0), 0)
|
||||
.lineTo(this.scaleX(0), this.height)
|
||||
.stroke(0xff0000)
|
||||
const horizontalLine = new Graphics()
|
||||
.moveTo(0, this.scaleY(0))
|
||||
.lineTo(this.width, this.scaleY(0))
|
||||
.stroke(0xff0000)
|
||||
verticalLine.alpha = 0.2
|
||||
horizontalLine.alpha = 0.2
|
||||
this.tilesContainer.addChild(verticalLine)
|
||||
this.tilesContainer.addChild(horizontalLine)
|
||||
createCrosshair(this.tilesContainer, 0xff0000, {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
x: this.scaleX(0),
|
||||
y: this.scaleY(0)
|
||||
})
|
||||
|
||||
createCrosshair(this.interactionContainer, 0xffff00, {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
x: this.scaleX(0),
|
||||
y: this.scaleY(0)
|
||||
})
|
||||
|
||||
this.textContainer = createContainer({
|
||||
width: this.width,
|
||||
@ -135,15 +128,6 @@ export class Board extends EventEmitter {
|
||||
this.calculateScale()
|
||||
}
|
||||
|
||||
get canMove() {
|
||||
return this._canMove
|
||||
}
|
||||
|
||||
set canMove(value: boolean) {
|
||||
this._canMove = value
|
||||
this.updateCanMoveText()
|
||||
}
|
||||
|
||||
setPlayerHand(tiles: Tile[]) {
|
||||
this.playerHand = tiles
|
||||
}
|
||||
@ -153,12 +137,8 @@ export class Board extends EventEmitter {
|
||||
this.textContainer.addChild(createText(text, this.scaleX(0), 100))
|
||||
}
|
||||
|
||||
private updateCanMoveText() {
|
||||
if (this.canMove) {
|
||||
async setPlayerTurn(player: PlayerDto) {
|
||||
this.showText('Your turn!')
|
||||
} else {
|
||||
this.showText('Waiting for players')
|
||||
}
|
||||
}
|
||||
|
||||
async setServerPlayerTurn(currentPlayer: PlayerDto) {
|
||||
@ -168,6 +148,9 @@ export class Board extends EventEmitter {
|
||||
async playerMove(move: any, playerId: string) {
|
||||
const { move: lastMove } = move
|
||||
if (lastMove === null) {
|
||||
setTimeout(() => {
|
||||
this.emit('game:tile-animation-ended')
|
||||
}, 500)
|
||||
return
|
||||
}
|
||||
if (
|
||||
@ -252,21 +235,46 @@ export class Board extends EventEmitter {
|
||||
tile.reScale(this.scale)
|
||||
this.tiles.push(tile)
|
||||
|
||||
await this.animateTile(tile, x, y, orientation, move)
|
||||
this.emit('game:tile-animation-ended', tile.toPlain())
|
||||
}
|
||||
|
||||
async animateTile(tile: Tile, x: number, y: number, orientation: string, move: Movement) {
|
||||
const targetX = this.scaleX(x)
|
||||
const targetY = this.scaleY(y)
|
||||
const animation: AnimationOptions = {
|
||||
x: this.scaleX(x),
|
||||
y: this.scaleY(y),
|
||||
x: targetX,
|
||||
y: targetY,
|
||||
rotation: ORIENTATION_ANGLES[orientation],
|
||||
duration: 20
|
||||
}
|
||||
|
||||
tile.setPosition(this.scaleX(x), this.scaleY(y))
|
||||
const tempAlpha = tile.alpha
|
||||
tile.alpha = 0
|
||||
const clonedTile = tile.clone()
|
||||
clonedTile.addTo(this.tilesContainer)
|
||||
const pos = this.getAnimationInitialPoosition(move)
|
||||
clonedTile.setPosition(this.scaleX(pos.x), this.scaleY(pos.y))
|
||||
await clonedTile.animateTo(animation)
|
||||
clonedTile.removeFromParent()
|
||||
tile.setOrientation(orientation)
|
||||
tile.setPosition(targetX, targetY)
|
||||
tile.alpha = tempAlpha
|
||||
}
|
||||
|
||||
// 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())
|
||||
getAnimationInitialPoosition(move: Movement): { x: number; y: number } {
|
||||
const otherHand = this.otherPlayerHands.find((h) => h.player?.id === move.playerId)
|
||||
if (otherHand === undefined) {
|
||||
return { x: 0, y: this.scaleY.inverse(this.height + 50) }
|
||||
}
|
||||
const position = otherHand.position
|
||||
switch (position) {
|
||||
case 'left':
|
||||
return { x: this.scaleX.inverse(100), y: this.scaleY.inverse(100) }
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
getPlayedTile(id: string): Tile | undefined {
|
||||
@ -315,16 +323,14 @@ export class Board extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
async updateBoard(move: Movement) {
|
||||
async updateBoard(move: Movement, tile: Tile | undefined) {
|
||||
try {
|
||||
const { tile: tileDto } = move
|
||||
const tile = this.getTileInHand(tileDto?.id ?? '')
|
||||
|
||||
// const { tileDto: tileDto } = move
|
||||
// const tile = this.getTileInHand(tileDto?.id ?? '')
|
||||
this.movements.push(move)
|
||||
if (tile === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.movements.push(move)
|
||||
await this.addTile(tile, move)
|
||||
this.setFreeEnd(move)
|
||||
} catch (error) {
|
||||
|
@ -5,17 +5,17 @@ import { Tile } from '@/game/Tile'
|
||||
import { Hand } from '@/game/Hand'
|
||||
import type { GameDto, Movement, PlayerDto, TileDto } from '@/common/interfaces'
|
||||
import type { SocketIoClientService } from '@/services/SocketIoClientService'
|
||||
import { useEventBusStore } from '@/stores/eventBus'
|
||||
import { wait } from '@/common/helpers'
|
||||
import { Actions } from 'pixi-actions'
|
||||
import { OtherHand } from './OtherHand'
|
||||
|
||||
export class Game {
|
||||
public board!: Board
|
||||
public hand!: Hand
|
||||
private app: Application = new Application()
|
||||
private selectedTile: TileDto | undefined
|
||||
private eventBus: any = useEventBusStore()
|
||||
private currentMove: Movement | undefined
|
||||
private otherHands: OtherHand[] = []
|
||||
|
||||
constructor(
|
||||
private options: { boardScale: number; handScale: number; width: number; height: number } = {
|
||||
@ -24,24 +24,29 @@ export class Game {
|
||||
width: 1200,
|
||||
height: 800
|
||||
},
|
||||
private emit: any,
|
||||
private socketService: SocketIoClientService,
|
||||
private playerId: string,
|
||||
private sessionId: string
|
||||
) {}
|
||||
|
||||
async setup(): Promise<HTMLCanvasElement> {
|
||||
const width = 1200
|
||||
const height = 800
|
||||
const width = this.options.width || 1200
|
||||
const height = this.options.height || 800
|
||||
|
||||
await this.app.init({ width, height })
|
||||
this.app.ticker.add((tick) => Actions.tick(tick.deltaTime / 60))
|
||||
return this.app.canvas
|
||||
}
|
||||
|
||||
async start() {
|
||||
async start(players: PlayerDto[] = []) {
|
||||
this.board = new Board(this.app)
|
||||
this.hand = new Hand(this.app)
|
||||
this.otherHands = [
|
||||
new OtherHand(this.app, 'left'),
|
||||
new OtherHand(this.app, 'top'),
|
||||
new OtherHand(this.app, 'right')
|
||||
]
|
||||
this.initOtherHands(players)
|
||||
this.hand.scale = this.options.handScale
|
||||
this.board.scale = this.options.boardScale
|
||||
this.setBoardEvents()
|
||||
@ -54,6 +59,32 @@ export class Game {
|
||||
})
|
||||
}
|
||||
|
||||
initOtherHands(players: PlayerDto[]) {
|
||||
const myIndex = players.findIndex((player) => player.id === this.playerId)
|
||||
const copy = [...players]
|
||||
const cut = copy.splice(myIndex)
|
||||
cut.shift()
|
||||
const final = cut.concat(copy)
|
||||
|
||||
for (let i = 0; i < final.length; i++) {
|
||||
const hand = this.otherHands[i]
|
||||
hand.setPlayer(final[i])
|
||||
}
|
||||
|
||||
this.board.otherPlayerHands = this.otherHands
|
||||
}
|
||||
|
||||
updateOtherHands(gameState: GameDto) {
|
||||
const players = gameState.players
|
||||
|
||||
players.forEach((player) => {
|
||||
const hand = this.otherHands.find((hand) => hand.player?.id === player.id)
|
||||
if (hand) {
|
||||
hand.setHand(player.hand)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async preload() {
|
||||
await Assets.load(assets)
|
||||
}
|
||||
@ -82,11 +113,11 @@ export class Game {
|
||||
sessionId: this.sessionId,
|
||||
move: move
|
||||
})
|
||||
await this.board.updateBoard(move)
|
||||
await this.board.updateBoard(move, undefined)
|
||||
})
|
||||
|
||||
this.hand.on('nextClick', () => {
|
||||
this.socketService.sendMessage('client:set-player-ready', {
|
||||
this.socketService.sendMessage('client:set-client-ready-for-next-game', {
|
||||
userId: this.playerId,
|
||||
sessionId: this.sessionId
|
||||
})
|
||||
@ -120,24 +151,18 @@ export class Game {
|
||||
return validEnds
|
||||
}
|
||||
|
||||
public setCanMakeMove(value: boolean) {
|
||||
this.hand.setCanMove(value, this.board.count === 0, this.board.freeEnds)
|
||||
this.board.canMove = value
|
||||
}
|
||||
|
||||
async setNextPlayer(state: GameDto) {
|
||||
const currentPlayer = state?.currentPlayer!
|
||||
if (currentPlayer.id !== this.playerId) {
|
||||
this.setCanMakeMove(false)
|
||||
this.board.setServerPlayerTurn(currentPlayer)
|
||||
if (currentPlayer.id === this.playerId) {
|
||||
this.hand.prepareForMove(this.board.count === 0, this.board.freeEnds)
|
||||
this.board.setPlayerTurn(currentPlayer)
|
||||
} else {
|
||||
this.setCanMakeMove(true)
|
||||
this.board.setServerPlayerTurn(currentPlayer)
|
||||
}
|
||||
}
|
||||
|
||||
private setBoardEvents() {
|
||||
this.board.on('game:board-left-action-click', async (data) => {
|
||||
console.log('left data :>> ', data)
|
||||
if (this.selectedTile === undefined) return
|
||||
const move: Movement = {
|
||||
tile: this.selectedTile,
|
||||
@ -146,12 +171,11 @@ export class Game {
|
||||
...data
|
||||
}
|
||||
this.currentMove = move
|
||||
this.hand.tileMoved(this.selectedTile)
|
||||
await this.board.updateBoard({ ...move, tile: this.selectedTile })
|
||||
const tile = this.hand.tileMoved(this.selectedTile)
|
||||
await this.board.updateBoard({ ...move, tile: this.selectedTile }, tile)
|
||||
})
|
||||
|
||||
this.board.on('game:board-right-action-click', async (data) => {
|
||||
console.log('right data :>> ', data)
|
||||
if (this.selectedTile === undefined) return
|
||||
const move: Movement = {
|
||||
tile: this.selectedTile,
|
||||
@ -160,31 +184,25 @@ export class Game {
|
||||
...data
|
||||
}
|
||||
this.currentMove = move
|
||||
this.hand.tileMoved(this.selectedTile)
|
||||
await this.board.updateBoard({ ...move, tile: this.selectedTile })
|
||||
const tile = this.hand.tileMoved(this.selectedTile)
|
||||
await this.board.updateBoard({ ...move, tile: this.selectedTile }, tile)
|
||||
})
|
||||
|
||||
this.board.on('game:tile-animation-ended', async (tile) => {
|
||||
console.log('animation ended', tile)
|
||||
if (tile.playerId === this.playerId) {
|
||||
if (tile !== null && tile !== undefined && tile.playerId === this.playerId) {
|
||||
this.socketService.sendMessage('client:player-move', {
|
||||
sessionId: this.sessionId,
|
||||
move: this.currentMove
|
||||
})
|
||||
} else {
|
||||
this.socketService.sendMessage('client:animation-ended', {
|
||||
sessionId: this.sessionId,
|
||||
userId: this.playerId
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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.board.gameFinished(data)
|
||||
@ -197,6 +215,13 @@ export class Game {
|
||||
|
||||
serverPlayerMove(data: any, playerId: string) {
|
||||
this.board.playerMove(data, playerId)
|
||||
|
||||
if (!(data.move === undefined || data.move === null)) {
|
||||
const otherHand = this.otherHands.find((hand) => hand.player?.id === data.move.playerId)
|
||||
if (otherHand) {
|
||||
otherHand.update(data.move)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeBoardEvents() {
|
||||
|
@ -88,30 +88,31 @@ export class Hand extends EventEmitter {
|
||||
this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
|
||||
}
|
||||
|
||||
setCanMove(value: boolean, isFirstMove: boolean, freeEnds?: [number, number]) {
|
||||
console.log('this.tiles :>> ', this.tiles.length)
|
||||
this.availableTiles =
|
||||
!value || isFirstMove
|
||||
prepareForMove(isFirstMove: boolean, freeEnds?: [number, number]) {
|
||||
this.availableTiles = 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) {
|
||||
if (this.availableTiles.length === 0) {
|
||||
this.createPassButton()
|
||||
} else {
|
||||
this.interactionsLayer.removeChild(this.buttonPass)
|
||||
}
|
||||
this.availableTiles.forEach((tile) => {
|
||||
if (value) {
|
||||
tile.animateTo({
|
||||
x: tile.x,
|
||||
y: tile.y - 10
|
||||
})
|
||||
// const action: Action = Actions.moveTo(tile.getSprite(), tile.x, tile.y - 10, 1,).play()
|
||||
}
|
||||
tile.interactive = value
|
||||
tile.interactive = true
|
||||
})
|
||||
}
|
||||
|
||||
afterMove() {
|
||||
this.availableTiles.forEach((tile) => {
|
||||
tile.animateTo({
|
||||
x: tile.x,
|
||||
y: tile.y + 10
|
||||
})
|
||||
tile.setPosition(tile.x, tile.y + 10)
|
||||
tile.interactive = false
|
||||
})
|
||||
this._canMove = value
|
||||
}
|
||||
|
||||
hasMoves(tile: TileDto, freeEnds?: [number, number]): boolean {
|
||||
@ -166,20 +167,12 @@ export class Hand extends EventEmitter {
|
||||
selected.alpha = 0.7
|
||||
}
|
||||
|
||||
public tileMoved(tileDto: TileDto) {
|
||||
public tileMoved(tileDto: TileDto): Tile | undefined {
|
||||
const tile = this.tiles.find((t) => t.id === tileDto.id)
|
||||
|
||||
if (!tile) return
|
||||
|
||||
this.availableTiles
|
||||
.filter((t) => t.id !== tileDto.id)
|
||||
.forEach((t) => {
|
||||
t.animateTo({
|
||||
x: t.x,
|
||||
y: t.y + 10
|
||||
})
|
||||
})
|
||||
|
||||
this.afterMove()
|
||||
this.tiles = this.tiles.filter((t) => t.id !== tileDto.id)
|
||||
|
||||
tile.interactive = false
|
||||
@ -187,6 +180,8 @@ export class Hand extends EventEmitter {
|
||||
tile.off('pointerdown')
|
||||
tile.off('pointerover')
|
||||
tile.off('pointerout')
|
||||
this.tilesLayer.removeChild(tile.getSprite())
|
||||
return tile
|
||||
}
|
||||
|
||||
private createPassButton() {
|
||||
@ -195,7 +190,10 @@ export class Hand extends EventEmitter {
|
||||
this.buttonPass = createButton(
|
||||
'PASS',
|
||||
{ x, y: this.height / 2, width: 50, height: 20 },
|
||||
() => this.emit('game:button-pass-click'),
|
||||
() => {
|
||||
this.interactionsLayer.removeChild(this.buttonPass)
|
||||
this.emit('game:button-pass-click')
|
||||
},
|
||||
this.interactionsLayer
|
||||
)
|
||||
}
|
||||
|
118
src/game/OtherHand.ts
Normal file
118
src/game/OtherHand.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
import { Application, Container, Sprite, Texture } from 'pixi.js'
|
||||
import { Scale, type ScaleFunction } from './utilities/scale'
|
||||
import { Tile } from './Tile'
|
||||
import type { Movement, PlayerDto, TileDto } from '@/common/interfaces'
|
||||
import { createContainer } from '@/common/helpers'
|
||||
import { createText, playerNameText } from './utilities/fonts'
|
||||
|
||||
export class OtherHand {
|
||||
tilesInitialNumber: number = 7
|
||||
player?: PlayerDto
|
||||
hand: Tile[] = []
|
||||
container: Container = new Container()
|
||||
height: number
|
||||
width: number
|
||||
scale: number = 0.5
|
||||
scaleY!: ScaleFunction
|
||||
scaleX!: ScaleFunction
|
||||
x: number = 0
|
||||
y: number = 0
|
||||
grain: number = 25
|
||||
logger: LoggingService = new LoggingService()
|
||||
tilesLayer!: Container
|
||||
interactionsLayer!: Container
|
||||
|
||||
constructor(
|
||||
private app: Application,
|
||||
public position: 'left' | 'right' | 'top' = 'left'
|
||||
) {
|
||||
this.height = 100
|
||||
this.width = 300
|
||||
app.stage.addChild(this.container)
|
||||
const { x, y } = this.getPosition()
|
||||
this.container.width = this.width
|
||||
this.container.height = this.height
|
||||
this.container.x = x
|
||||
this.container.y = y
|
||||
this.calculateScale()
|
||||
this.initLayers()
|
||||
}
|
||||
|
||||
setPlayer(player: PlayerDto) {
|
||||
this.player = player
|
||||
this.container.addChild(createText(`${player.name}`, this.width / 2, 12, playerNameText))
|
||||
}
|
||||
|
||||
setHand(tiles: TileDto[]) {
|
||||
this.hand = tiles.map((tile) => new Tile(tile.id, this.app.ticker, undefined, this.scale))
|
||||
this.render()
|
||||
}
|
||||
|
||||
update(move: Movement) {
|
||||
this.hand = this.hand.filter((tile) => tile.id !== move?.tile?.id)
|
||||
this.render()
|
||||
}
|
||||
|
||||
private render() {
|
||||
this.tilesLayer.removeChildren()
|
||||
const x = -9
|
||||
this.hand.forEach((tile, index) => {
|
||||
tile.setPosition(this.scaleX(x + index * 2), this.height / 2)
|
||||
this.tilesLayer.addChild(tile.getSprite())
|
||||
})
|
||||
}
|
||||
|
||||
private addBg() {
|
||||
const bg = new Sprite(Texture.WHITE)
|
||||
bg.alpha = 0.08
|
||||
bg.width = this.width
|
||||
bg.height = this.height
|
||||
this.container.addChild(bg)
|
||||
}
|
||||
|
||||
private getPosition() {
|
||||
let x = 0
|
||||
let y = 0
|
||||
|
||||
if (this.position === 'left') {
|
||||
x = 0
|
||||
y = 30
|
||||
} else if (this.position === 'right') {
|
||||
x = this.app.canvas.width - this.width
|
||||
y = 30
|
||||
} else {
|
||||
x = (this.app.canvas.width - this.width) / 2
|
||||
y = 0
|
||||
}
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
private initLayers() {
|
||||
this.container.removeChildren()
|
||||
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
|
||||
})
|
||||
this.container.addChild(this.tilesLayer)
|
||||
this.container.addChild(this.interactionsLayer)
|
||||
}
|
||||
|
||||
private calculateScale() {
|
||||
const scaleXSteps = Math.floor(this.width / (this.grain * this.scale)) / 2
|
||||
const scaleYSteps = Math.floor(this.height / (this.grain * this.scale)) / 2
|
||||
this.scaleX = Scale([-scaleXSteps, scaleXSteps], [0, this.width])
|
||||
this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import type { AnimationOptions } from '@/common/interfaces'
|
||||
import { Sprite, Texture, Ticker } from 'pixi.js'
|
||||
import { Container, Sprite, Texture, Ticker } from 'pixi.js'
|
||||
import { Tile } from './Tile'
|
||||
|
||||
export abstract class SpriteBase {
|
||||
private _interactive: boolean = false
|
||||
protected sprite: Sprite = new Sprite()
|
||||
private container?: Container
|
||||
|
||||
constructor(
|
||||
protected ticker?: Ticker,
|
||||
@ -136,6 +137,11 @@ export abstract class SpriteBase {
|
||||
}
|
||||
|
||||
addTo(container: any) {
|
||||
this.container = container
|
||||
container.addChild(this.sprite)
|
||||
}
|
||||
|
||||
removeFromParent() {
|
||||
this.container?.removeChild(this.sprite)
|
||||
}
|
||||
}
|
||||
|
@ -86,4 +86,11 @@ export class Tile extends SpriteBase {
|
||||
}
|
||||
this.orientation = value
|
||||
}
|
||||
|
||||
clone(): Tile {
|
||||
const copy = new Tile(this.id, this.ticker, this.pips, this.scale, this.playerId)
|
||||
copy.selected = this.selected
|
||||
copy.orientation = this.orientation
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,16 @@ export const mainText = new TextStyle({
|
||||
stroke: '#658f56'
|
||||
})
|
||||
|
||||
export const playerNameText = new TextStyle({
|
||||
dropShadow: dropShadowStyle,
|
||||
fill: '#a2a2a2',
|
||||
fontFamily: 'Arial, Helvetica, sans-serif',
|
||||
letterSpacing: 1,
|
||||
stroke: '#565656',
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold'
|
||||
})
|
||||
|
||||
export function createText(str: string, x: number, y: number, style: TextStyle = mainText) {
|
||||
const text = new Text({ text: str, style })
|
||||
text.anchor.set(0.5, 0.5)
|
||||
|
@ -31,13 +31,15 @@ onBeforeUnmount(() => {
|
||||
})
|
||||
|
||||
function copySeed() {
|
||||
if (sessionState?.value?.seed) toClipboard(sessionState.value.seed)
|
||||
if (sessionState?.value?.seed) {
|
||||
toClipboard(sessionState.value.seed)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="block">
|
||||
<section class="block">
|
||||
<section class="block info">
|
||||
<p>
|
||||
Running: {{ sessionState?.sessionInProgress }} Seed: {{ sessionState?.seed }}
|
||||
<button @click="copySeed">Copy!</button>
|
||||
@ -99,56 +101,15 @@ function copySeed() {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.action-select {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.control {
|
||||
padding: 8px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 110px;
|
||||
resize: none;
|
||||
}
|
||||
#response,
|
||||
#status {
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.tiles-container {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-flow: row wrap;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.board-container {
|
||||
.info {
|
||||
position: absolute;
|
||||
height: 600px;
|
||||
padding: 10px;
|
||||
width: calc(50% - 50px);
|
||||
background-color: var(--pico-form-element-background-color);
|
||||
}
|
||||
|
||||
.hand-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
height: 200px;
|
||||
padding: 10px;
|
||||
width: calc(50% - 50px);
|
||||
background-color: var(--pico-form-element-background-color);
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
</style>
|
||||
|
@ -8,8 +8,9 @@ import type { GameService } from '@/services/GameService'
|
||||
import type { MatchSessionDto } from '@/common/interfaces'
|
||||
import { useEventBusStore } from '@/stores/eventBus'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { copyToclipboard } from '@/common/helpers'
|
||||
|
||||
let seed = ref('')
|
||||
let seed = ref<string>('')
|
||||
let sessionName = ref('Test Value')
|
||||
let sessionId = ref('')
|
||||
let matchSessions = ref<MatchSessionDto[]>([])
|
||||
@ -79,16 +80,19 @@ async function joinMatch(id: string) {
|
||||
router.push({ name: 'match', params: { id } })
|
||||
}
|
||||
}
|
||||
|
||||
async function startMatch() {
|
||||
if (sessionState.value && sessionState.value.id) {
|
||||
router.push({ name: 'game' })
|
||||
async function deleteMatch(id: string) {
|
||||
if (id) {
|
||||
await socketService.connect()
|
||||
await gameService.cancelMatchSession(id)
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
matchSessions.value = await gameService.listMatchSessions()
|
||||
sessionName.value = `Test #${matchSessions.value.length + 1}`
|
||||
const listResponse = await gameService.listMatchSessions()
|
||||
console.log('listResponse :>> ', listResponse)
|
||||
matchSessions.value = listResponse.data
|
||||
sessionName.value = `Test #${listResponse.pagination.total + 1}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@ -101,6 +105,11 @@ onUnmounted(() => {
|
||||
logger.debug('Home view unmounted')
|
||||
clearInterval(dataInterval)
|
||||
})
|
||||
|
||||
function copy(sessionSeed: string) {
|
||||
seed.value = sessionSeed
|
||||
copyToclipboard(sessionSeed)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -132,16 +141,6 @@ onUnmounted(() => {
|
||||
<button class="button" @click="createMatch" v-if="!isSessionStarted">
|
||||
Create Match Session
|
||||
</button>
|
||||
<!-- <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="readyForStart">
|
||||
<span>Start</span>
|
||||
</button>
|
||||
|
||||
<button class="button" @click="cancelMatch" v-if="isSessionStarted">
|
||||
<span>Cancel</span>
|
||||
</button> -->
|
||||
</section>
|
||||
<section class="section available-sessions" v-if="!isSessionStarted">
|
||||
<h2 class="title is-4">Available Sessions</h2>
|
||||
@ -149,12 +148,19 @@ onUnmounted(() => {
|
||||
<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 v-else class="grid is-col-min-12">
|
||||
<div class="cell" v-for="session in matchSessions" :key="session.id">
|
||||
<p class="title is-6">{{ session.name }}</p>
|
||||
<p>ID: {{ session._id }}</p>
|
||||
<p>Players: {{ session.players.length }}</p>
|
||||
<p>
|
||||
Seed: {{ session.seed }}
|
||||
<button @click="() => copy(session.seed)">Copy</button>
|
||||
</p>
|
||||
<p>Status: {{ session.status }}</p>
|
||||
<button class="button" @click="() => joinMatch(session._id)">Join</button>
|
||||
<button class="button" @click="() => deleteMatch(session._id)">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -70,7 +70,9 @@ eventBus.subscribe('window-before-unload', async () => {
|
||||
await cancelMatch()
|
||||
})
|
||||
|
||||
eventBus.subscribe('server:match-starting', () => {
|
||||
eventBus.subscribe('server:match-starting', (data) => {
|
||||
const session = data.sessionState as MatchSessionDto
|
||||
updateSessionState(session)
|
||||
logger.debug('Match starting')
|
||||
router.push({ name: 'game' })
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user