match page (initial) i18n
This commit is contained in:
parent
c3ba84a815
commit
8f2c492278
@ -38,6 +38,7 @@ export interface MatchSessionDto {
|
||||
matchWinner: PlayerDto | null
|
||||
matchInProgress: boolean
|
||||
playersReady: number
|
||||
gameSummaries: GameSummary[]
|
||||
}
|
||||
|
||||
export interface GameDto {
|
||||
@ -115,4 +116,11 @@ export interface GameSummary {
|
||||
winner: PlayerDto
|
||||
score: { id: string; name: string; score: number }[]
|
||||
players?: PlayerDto[]
|
||||
board: TileDto[]
|
||||
boneyard: TileDto[]
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
waitMillisToShowSummary: number
|
||||
activeHandStrokeColor: number
|
||||
}
|
||||
|
@ -1,17 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import type { GameDto, PlayerDto } from '@/common/interfaces'
|
||||
import { onMounted, onUnmounted, ref, inject, toRaw } from 'vue'
|
||||
import { onMounted, onUnmounted, ref, inject } from 'vue'
|
||||
import { Game } from '@/game/Game'
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { useEventBusStore } from '@/stores/eventBus'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useGameOptionsStore } from '@/stores/gameOptions'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
let sessionId: string
|
||||
const socketService: any = inject('socket')
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const gameOptionsStore = useGameOptionsStore()
|
||||
const eventBus = useEventBusStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { playerState, sessionState } = storeToRefs(gameStore)
|
||||
const { updateGameState } = gameStore
|
||||
const { gameOptions } = storeToRefs(gameOptionsStore)
|
||||
@ -68,12 +72,18 @@ const game = new Game(
|
||||
// )
|
||||
|
||||
onMounted(async () => {
|
||||
sessionId = route.params.id as string
|
||||
if (appEl.value === null) return
|
||||
const canvas = await game.setup()
|
||||
appEl.value.appendChild(canvas)
|
||||
await game.preload()
|
||||
await game.start(sessionState?.value?.players)
|
||||
|
||||
game.on('game:finish-click', () => {
|
||||
game.destroy()
|
||||
router.push({ name: 'match', params: { id: sessionId } })
|
||||
})
|
||||
|
||||
eventBus.subscribe('server:game-finished', (data) => {
|
||||
game.gameFinished(data)
|
||||
})
|
||||
@ -88,7 +98,7 @@ onMounted(async () => {
|
||||
|
||||
eventBus.subscribe('server:hand-dealt', (data: { player: PlayerDto; gameState: GameDto }) => {
|
||||
game.hand.update(data.player)
|
||||
game.updateOtherHands(data.gameState)
|
||||
game.updateOtherHands(data.gameState.players)
|
||||
})
|
||||
|
||||
eventBus.subscribe('server:next-turn', (gameState: GameDto) => {
|
||||
@ -99,25 +109,6 @@ onMounted(async () => {
|
||||
eventBus.subscribe('server:match-finished', (data) => {
|
||||
game.matchFinished(data)
|
||||
})
|
||||
|
||||
// eventBus.subscribe('server:game-state', (data) => {
|
||||
// console.log('server:game-state :>> ', data)
|
||||
// game.serverGameState(data)
|
||||
// })
|
||||
|
||||
// mockMove(game, [6, 6], 'left')
|
||||
// mockMove(game, [6, 4], 'left')
|
||||
// mockMove(game, [4, 4], 'left')
|
||||
// mockMove(game, [4, 0], 'left')
|
||||
// mockMove(game, [0, 0], 'left')
|
||||
// mockMove(game, [2, 0], 'left')
|
||||
// mockMove(game, [2, 2], 'left')
|
||||
// mockMove(game, [6, 5], 'right')
|
||||
// mockMove(game, [5, 5], 'right')
|
||||
// mockMove(game, [5, 1], 'right')
|
||||
// mockMove(game, [3, 1], 'right')
|
||||
// mockMove(game, [3, 0], 'right')
|
||||
// mockMove(game, [5, 0], 'right')
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
@ -1,41 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve successfully created a project with
|
||||
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 2.6rem;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,88 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import WelcomeItem from './WelcomeItem.vue'
|
||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||
import ToolingIcon from './icons/IconTooling.vue'
|
||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||
import CommunityIcon from './icons/IconCommunity.vue'
|
||||
import SupportIcon from './icons/IconSupport.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
This project is served and bundled with
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
|
||||
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
|
||||
>Cypress Component Testing</a
|
||||
>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in <code>README.md</code>.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||
you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
|
||||
Discord server, or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also subscribe to
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
|
||||
the official
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
twitter account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||
us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
</template>
|
@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,8 +1,8 @@
|
||||
import { Application, Assets, Container, EventEmitter, Sprite, Text, Ticker } from 'pixi.js'
|
||||
import { Application, Container, EventEmitter, 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, createCrosshair, isTilePair } from '@/common/helpers'
|
||||
import { DIRECTIONS, createContainer, isTilePair } from '@/common/helpers'
|
||||
import { createText } from '@/game/utilities/fonts'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
import { GlowFilter } from 'pixi-filters'
|
||||
@ -85,19 +85,12 @@ export class Board extends EventEmitter {
|
||||
visible: false,
|
||||
})
|
||||
|
||||
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),
|
||||
})
|
||||
// createCrosshair(this.tilesContainer, 0xff0000, {
|
||||
// width: this.width,
|
||||
// height: this.height,
|
||||
// x: this.scaleX(0),
|
||||
// y: this.scaleY(0),
|
||||
// })
|
||||
|
||||
this.textContainer = createContainer({
|
||||
width: this.width,
|
||||
@ -105,7 +98,7 @@ export class Board extends EventEmitter {
|
||||
parent: this.container,
|
||||
})
|
||||
|
||||
this.showText(t('starting_game'))
|
||||
this.showText(t('game.starting_game'))
|
||||
}
|
||||
|
||||
private calculateScale() {
|
||||
@ -144,11 +137,11 @@ export class Board extends EventEmitter {
|
||||
}
|
||||
|
||||
async setPlayerTurn(player: PlayerDto) {
|
||||
this.showText('Your turn!')
|
||||
this.showText(t('game.your-turn'))
|
||||
}
|
||||
|
||||
async setServerPlayerTurn(currentPlayer: PlayerDto) {
|
||||
this.showText(`${currentPlayer.name}'s turn!\n Please wait...`)
|
||||
this.showText(t('game.player-turn', [currentPlayer.name]))
|
||||
}
|
||||
|
||||
async playerMove(move: any, playerId: string) {
|
||||
@ -599,8 +592,7 @@ export class Board extends EventEmitter {
|
||||
return [canPlayNorth, canPlayEast, canPlaySouth, canPlayWest]
|
||||
}
|
||||
|
||||
gameFinished(data: any) {
|
||||
const { lastGame, gameState } = data
|
||||
clean() {
|
||||
this.tiles = []
|
||||
this.boneyard = []
|
||||
this.movements = []
|
||||
@ -614,10 +606,13 @@ export class Board extends EventEmitter {
|
||||
this.firstTile = undefined
|
||||
this.tilesContainer.removeChildren()
|
||||
this.interactionContainer.removeChildren()
|
||||
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'}`)
|
||||
gameFinished() {
|
||||
this.showText(t('game.round-finished'))
|
||||
}
|
||||
|
||||
matchFinished() {
|
||||
this.showText(t('game.match-finished'))
|
||||
}
|
||||
}
|
||||
|
8
src/game/Config.ts
Normal file
8
src/game/Config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { Config } from '@/common/interfaces'
|
||||
|
||||
const config: Config = {
|
||||
waitMillisToShowSummary: 1000,
|
||||
activeHandStrokeColor: 0xb39c4d,
|
||||
}
|
||||
|
||||
export default config
|
@ -1,4 +1,4 @@
|
||||
import { Application, Assets, Container, Sprite } from 'pixi.js'
|
||||
import { Application, Assets, Container, EventEmitter, Sprite } from 'pixi.js'
|
||||
import { Board } from '@/game/Board'
|
||||
import { assets } from '@/game/utilities/assets'
|
||||
import { Tile } from '@/game/Tile'
|
||||
@ -9,6 +9,7 @@ import { wait } from '@/common/helpers'
|
||||
import { Actions } from 'pixi-actions'
|
||||
import { OtherHand } from './OtherHand'
|
||||
import { GameSummayView } from './GameSummayView'
|
||||
import Config from './Config'
|
||||
|
||||
interface GameOptions {
|
||||
boardScale: number
|
||||
@ -18,7 +19,7 @@ interface GameOptions {
|
||||
background: string
|
||||
}
|
||||
|
||||
export class Game {
|
||||
export class Game extends EventEmitter {
|
||||
public board!: Board
|
||||
public hand!: Hand
|
||||
private app: Application = new Application()
|
||||
@ -27,6 +28,7 @@ export class Game {
|
||||
private otherHands: OtherHand[] = []
|
||||
private backgroundLayer: Container = new Container()
|
||||
private gameSummaryView!: GameSummayView
|
||||
private players: PlayerDto[] = []
|
||||
|
||||
constructor(
|
||||
private options: GameOptions = {
|
||||
@ -39,7 +41,9 @@ export class Game {
|
||||
private socketService: SocketIoClientService,
|
||||
private playerId: string,
|
||||
private sessionId: string,
|
||||
) {}
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async setup(): Promise<HTMLCanvasElement> {
|
||||
const width = this.options.width || 1200
|
||||
@ -59,7 +63,8 @@ export class Game {
|
||||
new OtherHand(this.app, 'top'),
|
||||
new OtherHand(this.app, 'right'),
|
||||
]
|
||||
this.initOtherHands(players)
|
||||
this.initPlayers(players)
|
||||
this.players = players
|
||||
this.gameSummaryView = new GameSummayView(this.app)
|
||||
this.hand.scale = this.options.handScale
|
||||
this.board.scale = this.options.boardScale
|
||||
@ -81,23 +86,23 @@ export class Game {
|
||||
this.backgroundLayer.addChild(background)
|
||||
}
|
||||
|
||||
initOtherHands(players: PlayerDto[]) {
|
||||
initPlayers(players: PlayerDto[]) {
|
||||
const myIndex = players.findIndex((player) => player.id === this.playerId)
|
||||
const copy = [...players]
|
||||
const cut = copy.splice(myIndex)
|
||||
cut.shift()
|
||||
const player = cut.shift()
|
||||
const final = cut.concat(copy)
|
||||
|
||||
for (let i = 0; i < final.length; i++) {
|
||||
const hand = this.otherHands[i]
|
||||
hand.setPlayer(final[i])
|
||||
}
|
||||
this.hand.setPlayer(player)
|
||||
|
||||
this.board.otherPlayerHands = this.otherHands
|
||||
}
|
||||
|
||||
updateOtherHands(gameState: GameDto) {
|
||||
const players = gameState.players
|
||||
updateOtherHands(players: PlayerDto[]) {
|
||||
players.forEach((player) => {
|
||||
const hand = this.otherHands.find((hand) => hand.player?.id === player.id)
|
||||
if (hand) {
|
||||
@ -115,6 +120,7 @@ export class Game {
|
||||
|
||||
setPlayersInactive() {
|
||||
this.otherHands.forEach((hand) => hand.setActive(false))
|
||||
this.hand.setActive(false)
|
||||
}
|
||||
|
||||
async preload() {
|
||||
@ -148,7 +154,12 @@ export class Game {
|
||||
await this.board.updateBoard(move, undefined)
|
||||
})
|
||||
|
||||
this.gameSummaryView.on('finishClick', (data) => {
|
||||
this.emit('game:finish-click', data)
|
||||
})
|
||||
|
||||
this.gameSummaryView.on('nextClick', (data) => {
|
||||
this.board.clean()
|
||||
this.updateScoreboard(data.sessionState)
|
||||
this.socketService.sendMessage('client:set-client-ready-for-next-game', {
|
||||
userId: this.playerId,
|
||||
@ -161,11 +172,13 @@ export class Game {
|
||||
|
||||
private updateScoreboard(sessionState: MatchSessionDto) {
|
||||
const scoreboard = sessionState.scoreboard
|
||||
this.otherHands.forEach((hand) => {
|
||||
const player: PlayerDto | undefined = hand.player
|
||||
const myScore = scoreboard.find((d) => d.id === this.playerId)?.score || 0
|
||||
this.hand.setScore(myScore)
|
||||
this.otherHands.forEach((otherHand) => {
|
||||
const player: PlayerDto | undefined = otherHand.player
|
||||
const name: string = player?.name || ''
|
||||
const score = scoreboard.find((d) => d.name === name)?.score || 0
|
||||
hand.setScore(score)
|
||||
otherHand.setScore(score)
|
||||
})
|
||||
}
|
||||
|
||||
@ -198,6 +211,7 @@ export class Game {
|
||||
const currentPlayer = state?.currentPlayer!
|
||||
this.setPlayersInactive()
|
||||
if (currentPlayer.id === this.playerId) {
|
||||
this.hand.setActive(true)
|
||||
this.hand.prepareForMove(this.board.count === 0, this.board.freeEnds)
|
||||
this.board.setPlayerTurn(currentPlayer)
|
||||
} else {
|
||||
@ -248,15 +262,19 @@ export class Game {
|
||||
})
|
||||
}
|
||||
|
||||
gameFinished(data: any) {
|
||||
async gameFinished(data: any) {
|
||||
await wait(Config.waitMillisToShowSummary)
|
||||
this.updateOtherHands(data.lastGame.players)
|
||||
this.hand.gameFinished()
|
||||
this.board.gameFinished(data)
|
||||
this.board.gameFinished()
|
||||
this.setPlayersInactive()
|
||||
this.gameSummaryView.setGameSummary(data, 'round')
|
||||
}
|
||||
|
||||
matchFinished(data: any) {
|
||||
// this.hand.matchFinished()
|
||||
this.board.matchFinished(data)
|
||||
async matchFinished(data: any) {
|
||||
await wait(Config.waitMillisToShowSummary)
|
||||
this.updateOtherHands(data.lastGame.players)
|
||||
this.board.matchFinished()
|
||||
this.gameSummaryView.setGameSummary(data, 'match')
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,6 @@ export class GameSummayView extends EventEmitter {
|
||||
height: this.height,
|
||||
parent: this.container,
|
||||
})
|
||||
console.log('GameSummaryView created!')
|
||||
|
||||
this.container.visible = false
|
||||
}
|
||||
|
||||
@ -67,7 +65,7 @@ export class GameSummayView extends EventEmitter {
|
||||
|
||||
if (this.gameSummary.isBlocked) {
|
||||
line += 30
|
||||
this.container.addChild(
|
||||
this.layer.addChild(
|
||||
createText({
|
||||
text: '(Blocked)',
|
||||
x: this.width / 2,
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { Application, Container, EventEmitter, Sprite, Texture, Ticker } from 'pixi.js'
|
||||
import { Application, Container, EventEmitter, Graphics, Sprite, Texture, Ticker } from 'pixi.js'
|
||||
import { Tile } from '@/game/Tile'
|
||||
import type { PlayerDto, TileDto } from '@/common/interfaces'
|
||||
import { GlowFilter } from 'pixi-filters'
|
||||
import { Scale, type ScaleFunction } from './utilities/scale'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
import { createButton, createContainer } from '@/common/helpers'
|
||||
import { Action, Actions } from 'pixi-actions'
|
||||
import { createText, playerNameText, whiteStyle } from './utilities/fonts'
|
||||
import Config from '@/game/Config'
|
||||
|
||||
export class Hand extends EventEmitter {
|
||||
tiles: Tile[] = []
|
||||
container: Container = new Container()
|
||||
buttonPass: Container = new Container()
|
||||
scoreLayer: Container = new Container()
|
||||
activeLayer: Container = new Container()
|
||||
height: number
|
||||
width: number
|
||||
ticker: Ticker
|
||||
@ -27,18 +30,22 @@ export class Hand extends EventEmitter {
|
||||
tilesLayer!: Container
|
||||
interactionsLayer!: Container
|
||||
score: number = 0
|
||||
active: boolean = false
|
||||
private player!: PlayerDto
|
||||
|
||||
constructor(app: Application) {
|
||||
super()
|
||||
app.stage.addChild(this.container)
|
||||
this.ticker = app.ticker
|
||||
this.height = 130 * this.scale
|
||||
this.width = app.canvas.width
|
||||
this.width = 800 // app.canvas.width
|
||||
this.container.y = app.canvas.height - this.height
|
||||
this.container.x = app.canvas.width / 2 - this.width / 2
|
||||
this.container.width = this.width
|
||||
this.container.height = this.height
|
||||
this.calculateScale()
|
||||
this.initLayers()
|
||||
this.render()
|
||||
}
|
||||
|
||||
initLayers() {
|
||||
@ -58,12 +65,12 @@ export class Hand extends EventEmitter {
|
||||
y: 0,
|
||||
parent: this.container,
|
||||
})
|
||||
this.container.addChild(this.scoreLayer)
|
||||
this.container.addChild(this.activeLayer)
|
||||
}
|
||||
|
||||
gameFinished() {
|
||||
this.logger.debug('gameFinished')
|
||||
this.tiles = []
|
||||
|
||||
this.initialized = false
|
||||
}
|
||||
|
||||
@ -205,6 +212,22 @@ export class Hand extends EventEmitter {
|
||||
this.render()
|
||||
}
|
||||
|
||||
setPlayer(player: PlayerDto | undefined) {
|
||||
if (!player) return
|
||||
this.player = player
|
||||
this.render()
|
||||
}
|
||||
|
||||
setScore(score: number) {
|
||||
this.score = score
|
||||
this.render()
|
||||
}
|
||||
|
||||
setActive(active: boolean) {
|
||||
this.active = active
|
||||
this.render()
|
||||
}
|
||||
|
||||
private createTiles(playerState: PlayerDto) {
|
||||
return playerState.hand.map((tile: TileDto) => {
|
||||
const newTile: Tile = new Tile(tile.id, this.ticker, tile.pips, this.scale, tile.playerId)
|
||||
@ -244,11 +267,38 @@ export class Hand extends EventEmitter {
|
||||
}
|
||||
|
||||
renderScore() {
|
||||
//this.scoreLayer.removeChildren()
|
||||
this.scoreLayer.removeChildren()
|
||||
const name = createText({
|
||||
text: this.player?.name ?? '-',
|
||||
x: 100,
|
||||
y: 50,
|
||||
style: playerNameText,
|
||||
})
|
||||
const text = createText({
|
||||
text: `${this.score}`,
|
||||
x: 100,
|
||||
// x: this.width - 5,
|
||||
y: 80,
|
||||
style: whiteStyle(36, 'bold'),
|
||||
})
|
||||
text.anchor.set(1, 0.5)
|
||||
this.scoreLayer.addChild(name)
|
||||
this.scoreLayer.addChild(text)
|
||||
}
|
||||
|
||||
renderActive() {
|
||||
this.activeLayer.removeChildren()
|
||||
if (this.active) {
|
||||
const rectangle = new Graphics()
|
||||
.roundRect(0, 0, this.width, this.height - 1, 5)
|
||||
.stroke(Config.activeHandStrokeColor)
|
||||
this.activeLayer.addChild(rectangle)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
this.renderTiles()
|
||||
this.renderScore()
|
||||
this.renderActive()
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ 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, scoreText } from './utilities/fonts'
|
||||
import { createText, playerNameText, whiteStyle } from './utilities/fonts'
|
||||
import Config from '@/game/Config'
|
||||
|
||||
export class OtherHand {
|
||||
tilesInitialNumber: number = 7
|
||||
@ -62,10 +63,11 @@ export class OtherHand {
|
||||
|
||||
setScore(score: number) {
|
||||
this.score = score
|
||||
this.render()
|
||||
}
|
||||
|
||||
setHand(tiles: TileDto[]) {
|
||||
this.hand = tiles.map((tile) => new Tile(tile.id, this.app.ticker, undefined, this.scale))
|
||||
this.hand = tiles.map((tile) => new Tile(tile.id, this.app.ticker, tile.pips, this.scale))
|
||||
this.render()
|
||||
}
|
||||
|
||||
@ -80,7 +82,7 @@ export class OtherHand {
|
||||
text: `${this.score}`,
|
||||
x: this.width - 5,
|
||||
y: 50,
|
||||
style: scoreText,
|
||||
style: whiteStyle(36, 'bold'),
|
||||
})
|
||||
text.anchor.set(1, 0.5)
|
||||
this.scoreLayer.addChild(text)
|
||||
@ -90,7 +92,7 @@ export class OtherHand {
|
||||
this.tilesLayer.removeChildren()
|
||||
const x = -9
|
||||
this.hand.forEach((tile, index) => {
|
||||
tile.setPosition(this.scaleX(x + index * 2), this.height / 2)
|
||||
tile.setPosition(this.scaleX(x + index * 2), this.height / 2 + 10)
|
||||
this.tilesLayer.addChild(tile.getSprite())
|
||||
})
|
||||
}
|
||||
@ -98,7 +100,9 @@ export class OtherHand {
|
||||
private renderActive() {
|
||||
this.interactionsLayer.removeChildren()
|
||||
if (this.active) {
|
||||
const rectangle = new Graphics().roundRect(0, 0, this.width, this.height, 5).stroke(0xffff00)
|
||||
const rectangle = new Graphics()
|
||||
.roundRect(0, 0, this.width, this.height, 5)
|
||||
.stroke(Config.activeHandStrokeColor)
|
||||
this.interactionsLayer.addChild(rectangle)
|
||||
}
|
||||
}
|
||||
|
48
src/i18n/en.json
Normal file
48
src/i18n/en.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"match-page": "Match Page",
|
||||
"login": "Login",
|
||||
"username": "Username",
|
||||
"username-placeholder": "Username",
|
||||
"password": "Password",
|
||||
"password-placeholder": "Password",
|
||||
"login-button": "Login",
|
||||
"invalid-username-or-password": "Invalid username or password",
|
||||
"winner": "Winner",
|
||||
"points-to-win": "Points to win",
|
||||
"final-scoreboard": "Final Scoreboard",
|
||||
"round-index-1": "Round {0}",
|
||||
"scoreboard": "Scoreboard",
|
||||
"available-sessions": "Available Sessions",
|
||||
"no-sessions-available": "No sessions available",
|
||||
"id-session-_id": "ID: {0}",
|
||||
"players-session-players-length": "Players: {0}",
|
||||
"copy": "Copy",
|
||||
"seed-session-seed": "Seed: {0}",
|
||||
"status-session-status": "Status: {0}",
|
||||
"delete": "Delete",
|
||||
"join": "Join",
|
||||
"welcome-to-the-user-username-s-home-page": "Welcome to the {0}'s Home Page",
|
||||
"name": "Name",
|
||||
"session-name-placeholder": "Session Name",
|
||||
"seed": "Seed",
|
||||
"seed-placeholder": "Type the session seed here!",
|
||||
"background-color": "Background color",
|
||||
"green-fabric": "Green Fabric",
|
||||
"gray-fabric": "Gray Fabric",
|
||||
"blue-fabric": "Blue Fabric",
|
||||
"yellow-fabric": "Yellow Fabric",
|
||||
"red-fabric": "Red Fabric",
|
||||
"crossed-game-teamed": "Crossed game ({0})",
|
||||
"create-match-session": "Create Match Session",
|
||||
"ready": "Ready",
|
||||
"unready": "Unready",
|
||||
"start": "Start",
|
||||
"cancel": "Cancel",
|
||||
"game": {
|
||||
"match-finished": "Match finished",
|
||||
"round-finished": "Round finished",
|
||||
"starting_game": "Starting game...",
|
||||
"your-turn": "Your turn!",
|
||||
"player-turn": "{0}'s turn!"
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export const en = {
|
||||
starting_game: 'Starting game...',
|
||||
}
|
48
src/i18n/es.json
Normal file
48
src/i18n/es.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"match-page": "Página de partido",
|
||||
"login": "Acceso",
|
||||
"username": "Nombre de usuario",
|
||||
"username-placeholder": "Nombre de usuario",
|
||||
"login-button": "Acceso",
|
||||
"password": "Contraseña",
|
||||
"password-placeholder": "Contraseña",
|
||||
"invalid-username-or-password": "usuario o contraseña invalido",
|
||||
"points-to-win": "Puntos para ganar",
|
||||
"round-index-1": "Ronda {0}",
|
||||
"scoreboard": "Marcador",
|
||||
"winner": "Ganador",
|
||||
"final-scoreboard": "Puntaje Final",
|
||||
"id-session-_id": "ID: {0}",
|
||||
"seed-placeholder": "¡Escriba la semilla de la sesión aquí!",
|
||||
"session-name-placeholder": "Nombre de la sesión",
|
||||
"status-session-status": "Estado: {0}",
|
||||
"unready": "No preparado",
|
||||
"welcome-to-the-user-username-s-home-page": "Bienvenido a la página de inicio de {0}",
|
||||
"available-sessions": "Sesiones disponibles",
|
||||
"background-color": "Color de fondo",
|
||||
"blue-fabric": "Fabrica azul",
|
||||
"cancel": "Cancelar",
|
||||
"copy": "Copiar",
|
||||
"game": {
|
||||
"starting_game": "Iniciando la partida...",
|
||||
"match-finished": "Partido terminado",
|
||||
"player-turn": "¡Es el turno de {0}!",
|
||||
"round-finished": "Ronda terminada",
|
||||
"your-turn": "¡Tu turno!"
|
||||
},
|
||||
"create-match-session": "Crear sesión de partido",
|
||||
"crossed-game-teamed": "Juego cruzado ({0})",
|
||||
"delete": "Borrar",
|
||||
"gray-fabric": "Tela gris",
|
||||
"green-fabric": "Tela verde",
|
||||
"join": "Unirse",
|
||||
"name": "Nombre",
|
||||
"no-sessions-available": "No hay sesiones disponibles",
|
||||
"players-session-players-length": "Jugadores: {0}",
|
||||
"ready": "Listo",
|
||||
"red-fabric": "Tela roja",
|
||||
"seed": "Semilla",
|
||||
"seed-session-seed": "Semilla: {0}",
|
||||
"start": "Comenzar",
|
||||
"yellow-fabric": "Tela amarilla"
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export const es = {
|
||||
starting_game: 'Iniciando la partida...',
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { en } from './en'
|
||||
import { es } from './es'
|
||||
import en from './en.json'
|
||||
import es from './es.json'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'es',
|
||||
messages: {
|
||||
en,
|
||||
@ -10,9 +11,11 @@ const i18n = createI18n({
|
||||
},
|
||||
})
|
||||
|
||||
const translate = (key: string) => {
|
||||
return i18n.global.t(key)
|
||||
}
|
||||
// const translate = (key: string, context: any, plural: number = 1) => {
|
||||
// return i18n.global.t(key, plural)
|
||||
// }
|
||||
|
||||
export default i18n
|
||||
export { translate, translate as t }
|
||||
|
||||
const { t } = i18n.global
|
||||
export { t, t as $t }
|
||||
|
@ -14,9 +14,9 @@ const router = createRouter({
|
||||
{
|
||||
path: '',
|
||||
component: LandingView,
|
||||
name: 'landing'
|
||||
}
|
||||
]
|
||||
name: 'landing',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
@ -27,10 +27,10 @@ const router = createRouter({
|
||||
name: 'home',
|
||||
component: HomeView,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
@ -45,13 +45,13 @@ const router = createRouter({
|
||||
name: 'match',
|
||||
component: () => import('@/views/MatchView.vue'),
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/game',
|
||||
path: '/game:id',
|
||||
component: AuthenticatedLayout,
|
||||
children: [
|
||||
{
|
||||
@ -59,12 +59,12 @@ const router = createRouter({
|
||||
name: 'game',
|
||||
component: () => import('@/views/GameView.vue'),
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
|
@ -4,11 +4,11 @@ import { ServiceBase } from './ServiceBase'
|
||||
export class GameService extends ServiceBase {
|
||||
private networkService = new NetworkService()
|
||||
|
||||
async createMatchSession(sessionName: string, seed: string) {
|
||||
async createMatchSession(sessionName: string, seed: string, options: any) {
|
||||
const response = await this.networkService.post({
|
||||
uri: '/game/match',
|
||||
body: { sessionName, seed },
|
||||
auth: true
|
||||
body: { sessionName, seed, options },
|
||||
auth: true,
|
||||
})
|
||||
const { sessionId } = response
|
||||
return sessionId
|
||||
@ -17,7 +17,7 @@ export class GameService extends ServiceBase {
|
||||
async cancelMatchSession(sessionId: string) {
|
||||
const response = await this.networkService.delete({
|
||||
uri: `/game/match/${sessionId}`,
|
||||
auth: true
|
||||
auth: true,
|
||||
})
|
||||
return response
|
||||
}
|
||||
@ -25,7 +25,7 @@ export class GameService extends ServiceBase {
|
||||
async joinMatchSession(sessionId: string) {
|
||||
const response = await this.networkService.put({
|
||||
uri: `/game/match/${sessionId}`,
|
||||
auth: true
|
||||
auth: true,
|
||||
})
|
||||
return response
|
||||
}
|
||||
@ -33,7 +33,7 @@ export class GameService extends ServiceBase {
|
||||
async listMatchSessions() {
|
||||
const response = await this.networkService.get({
|
||||
uri: '/game/match',
|
||||
auth: true
|
||||
auth: true,
|
||||
})
|
||||
return response
|
||||
}
|
||||
@ -41,7 +41,7 @@ export class GameService extends ServiceBase {
|
||||
async getMatchSession(sessionId: string) {
|
||||
const response = await this.networkService.get({
|
||||
uri: `/game/match/${sessionId}`,
|
||||
auth: true
|
||||
auth: true,
|
||||
})
|
||||
return response
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createColors } from 'colorette'
|
||||
import { createColors, gray, redBright, yellow } from 'colorette'
|
||||
import dayjs from 'dayjs'
|
||||
import pino, { type BaseLogger } from 'pino'
|
||||
import { isProxy, toRaw } from 'vue'
|
||||
@ -11,7 +11,7 @@ export class LoggingService {
|
||||
constructor() {
|
||||
this._logger = pino({
|
||||
browser: {
|
||||
asObject: true,
|
||||
asObject: false,
|
||||
transmit: {
|
||||
level: import.meta.env.VITE_LOG_LEVEL || 'error',
|
||||
send: (level, logEvent) => {
|
||||
@ -31,14 +31,14 @@ export class LoggingService {
|
||||
if (messages.length > 0) {
|
||||
console.log(
|
||||
`${logStr.join(' ')}:`,
|
||||
...messages.filter((m) => m !== undefined && m !== null)
|
||||
...messages.filter((m) => m !== undefined && m !== null),
|
||||
)
|
||||
} else {
|
||||
console.log(logStr.join(' '))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -46,7 +46,10 @@ export class LoggingService {
|
||||
return {
|
||||
info: green,
|
||||
debug: blue,
|
||||
error: red
|
||||
error: red,
|
||||
trace: gray,
|
||||
warn: yellow,
|
||||
fatal: redBright,
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,11 +58,11 @@ export class LoggingService {
|
||||
}
|
||||
|
||||
info(message: string, data?: any) {
|
||||
this._logger.info(this._getMessageWidthObject(message, data))
|
||||
this._logger.info(message, data)
|
||||
}
|
||||
|
||||
warn(message: string, data?: any) {
|
||||
this._logger.warn(this._getMessageWidthObject(message, data))
|
||||
this._logger.warn(message, data)
|
||||
}
|
||||
|
||||
error(error: any, message?: string) {
|
||||
@ -67,11 +70,11 @@ export class LoggingService {
|
||||
}
|
||||
|
||||
fatal(message: string, data?: any) {
|
||||
this._logger.fatal(this._getMessageWidthObject(message, data))
|
||||
this._logger.fatal(message, data)
|
||||
}
|
||||
|
||||
trace(message: string, data?: any) {
|
||||
this._logger.trace(this._getMessageWidthObject(message, data))
|
||||
this._logger.trace(message, data)
|
||||
}
|
||||
|
||||
object(message: any) {
|
||||
|
@ -28,9 +28,9 @@ export class SocketIoClientService extends ServiceBase {
|
||||
})
|
||||
this.socket.on('connect', () => {
|
||||
if (this.socket && this.socket.recovered) {
|
||||
console.log('socket recovered succesfully')
|
||||
this.logger.debug('SOCKET: socket recovered succesfully')
|
||||
} else {
|
||||
console.log('socket connected')
|
||||
this.logger.debug('SOCKET: socket connected')
|
||||
}
|
||||
this.isConnected = true
|
||||
this.addEvents()
|
||||
@ -42,21 +42,20 @@ export class SocketIoClientService extends ServiceBase {
|
||||
addEvents(): void {
|
||||
this.socket.on('disconnect', () => {
|
||||
this.isConnected = false
|
||||
console.log('Disconnected from server')
|
||||
console.log('SOCKET: Disconnected from server')
|
||||
})
|
||||
|
||||
this.socket.on('reconnect', () => {
|
||||
this.isConnected = true
|
||||
console.log('Reconnected to server')
|
||||
this.logger.debug('SOCKET: Reconnected to server')
|
||||
})
|
||||
|
||||
this.socket.on('reconnect_error', () => {
|
||||
this.isConnected = false
|
||||
console.log('Failed to reconnect to server')
|
||||
this.logger.debug('SOCKET: Failed to reconnect to server')
|
||||
})
|
||||
|
||||
this.socket.on('ping', () => {
|
||||
console.log('Ping received from server')
|
||||
this.socket.emit('pong') // Send pong response
|
||||
})
|
||||
|
||||
@ -73,7 +72,7 @@ export class SocketIoClientService extends ServiceBase {
|
||||
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)
|
||||
this.logger.trace(`SOCKET: Received event: ${event}`, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -81,24 +80,24 @@ export class SocketIoClientService extends ServiceBase {
|
||||
sendMessage(event: string, data: any): void {
|
||||
if (this.isConnected) {
|
||||
this.socket?.emit('client:event', { event, data })
|
||||
console.log('sendMessage :>> ', event, data)
|
||||
this.logger.trace(`SOCKET: sendMessage :>> ${event}`, data)
|
||||
} else {
|
||||
console.log('Not connected to server')
|
||||
this.logger.trace('Not connected to server')
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessageWithAck(event: string, data: any): Promise<any> {
|
||||
if (this.isConnected) {
|
||||
console.log('sendMessageWithAck :>> ', event, data)
|
||||
this.logger.trace(`SOCKET: sendMessageWithAck :>> ${event}}`, data)
|
||||
return await this.socket?.emitWithAck('client:event-with-ack', { event, data })
|
||||
} else {
|
||||
console.log('Not connected to server')
|
||||
this.logger.trace('SOCKET: Not connected to server')
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.socket?.disconnect()
|
||||
this.isConnected = false
|
||||
console.log('Disconnected from server')
|
||||
this.logger.debug('SOCKET: Disconnected from server')
|
||||
}
|
||||
}
|
||||
|
@ -12,16 +12,16 @@ const ioOpts = {
|
||||
reconnectionDelay: 1000, // Time between each attempt (in ms)
|
||||
reconnectionDelayMax: 5000, // Maximum time between attempts (in ms)
|
||||
randomizationFactor: 0.5, // Randomization factor for the delay
|
||||
timeout: 20000 // Connection timeout (in ms)
|
||||
timeout: 20000, // Connection timeout (in ms)
|
||||
}
|
||||
// const socket = URL === undefined ? io(ioOpts) : io(URL, ioOpts)
|
||||
const socket = io('http://localhost:3000', ioOpts)
|
||||
|
||||
socket.on('connect', () => {
|
||||
if (socket.recovered) {
|
||||
console.log('socket recovered succesfully')
|
||||
console.log('SOCKET: socket recovered succesfully')
|
||||
} else {
|
||||
console.log('socket connected')
|
||||
console.log('SOCKET: socket connected')
|
||||
}
|
||||
|
||||
// setTimeout(() => {
|
||||
@ -42,5 +42,5 @@ export default {
|
||||
},
|
||||
disconnect() {
|
||||
socket.disconnect()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -28,9 +28,11 @@ const socketService: any = inject('socket')
|
||||
const gameService: GameService = inject<GameService>('game') as GameService
|
||||
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
|
||||
|
||||
const { sessionState, isSessionStarted, playerState, amIHost } = storeToRefs(gameStore)
|
||||
const { sessionState, isSessionStarted, playerState, amIHost, readyForStart } =
|
||||
storeToRefs(gameStore)
|
||||
const { user } = storeToRefs(auth)
|
||||
const { gameOptions } = storeToRefs(gameOptionsStore)
|
||||
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
|
||||
|
||||
// function setPlayerReady() {
|
||||
// logger.debug('Starting game')
|
||||
@ -57,11 +59,60 @@ async function createMatch() {
|
||||
logger.debug('Creating match')
|
||||
await socketService.connect()
|
||||
gameOptions.value = { background: background.value }
|
||||
const id = await gameService.createMatchSession(sessionName.value, seed.value)
|
||||
const sessionOptions = {
|
||||
pointsToWin: pointsToWin.value,
|
||||
}
|
||||
const id = await gameService.createMatchSession(sessionName.value, seed.value, sessionOptions)
|
||||
logger.debug('Match created successfully')
|
||||
router.push({ name: 'match', params: { id } })
|
||||
// router.push({ name: 'match', params: { id } })
|
||||
}
|
||||
|
||||
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')
|
||||
if (sessionState?.value?.id) {
|
||||
await gameService.cancelMatchSession(sessionState?.value?.id)
|
||||
updateSessionState(undefined)
|
||||
updatePlayerState(undefined)
|
||||
updateGameState(undefined)
|
||||
|
||||
logger.debug('Match cancelled successfully')
|
||||
router.push({ name: 'home' })
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.subscribe('server:match-starting', (data) => {
|
||||
const session = data.sessionState as MatchSessionDto
|
||||
updateSessionState(session)
|
||||
router.push({ name: 'game', params: { id: session.id } })
|
||||
})
|
||||
|
||||
async function joinMatch(id: string) {
|
||||
if (id) {
|
||||
await socketService.connect()
|
||||
@ -80,7 +131,7 @@ async function deleteMatch(id: string) {
|
||||
async function loadData() {
|
||||
const listResponse = await gameService.listMatchSessions()
|
||||
matchSessions.value = listResponse.data
|
||||
sessionName.value = `Test #${listResponse.pagination.total + 1}`
|
||||
sessionName.value = `Test #${Date.now()}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@ -99,41 +150,47 @@ function copy(sessionSeed: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="block home">
|
||||
<div class="container home">
|
||||
<section class="section">
|
||||
<h1 class="title is-2">Welcome to the {{ user.username }}'s Home Page</h1>
|
||||
<div class="block">
|
||||
<h1 class="title is-2">
|
||||
{{ $t('welcome-to-the-user-username-s-home-page', [user.username]) }}
|
||||
</h1>
|
||||
<div class="block" v-if="!isSessionStarted">
|
||||
<div class="field">
|
||||
<label class="label">Name</label>
|
||||
<label class="label">{{ $t('name') }}</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input" v-model="sessionName" placeholder="Session Name" />
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
v-model="sessionName"
|
||||
placeholder="$t('session-name-placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Seed</label>
|
||||
<label class="label">{{ $t('seed') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
style="margin-bottom: 0"
|
||||
v-model="seed"
|
||||
placeholder="Type the session seed here!"
|
||||
placeholder="$t('seed-placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="cell">
|
||||
<div class="field">
|
||||
<label for="background" class="label">Background color</label>
|
||||
<label for="background" class="label">{{ $t('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>
|
||||
<option value="green">{{ $t('green-fabric') }}</option>
|
||||
<option value="gray">{{ $t('gray-fabric') }}</option>
|
||||
<option value="blue">{{ $t('blue-fabric') }}</option>
|
||||
<option value="yellow">{{ $t('yellow-fabric') }}</option>
|
||||
<option value="red">{{ $t('red-fabric') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -142,14 +199,14 @@ function copy(sessionSeed: string) {
|
||||
<div class="control">
|
||||
<label for="teamed" class="checkbox">
|
||||
<input v-model="teamed" name="teamed" type="checkbox" />
|
||||
Crossed game ({{ teamed }})
|
||||
{{ $t('crossed-game-teamed', [teamed]) }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<div class="field">
|
||||
<label for="pointsToWin" class="label">Points to win</label>
|
||||
<label for="pointsToWin" class="label">{{ $t('points-to-win') }}</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select v-model="pointsToWin" name="pointsToWin">
|
||||
@ -164,37 +221,58 @@ function copy(sessionSeed: string) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button class="button is-primary" @click.once="createMatch">
|
||||
{{ $t('create-match-session') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block" v-if="!isSessionStarted"></div>
|
||||
<div class="buttons" v-if="isSessionStarted">
|
||||
<button class="button" @click="setPlayerReady">
|
||||
<span v-if="!readyForStart">{{ $t('ready') }}</span
|
||||
><span v-else>{{ $t('unready') }}</span>
|
||||
</button>
|
||||
<button class="button" @click="startMatch" v-if="amIHost && readyForStart">
|
||||
<span>{{ $t('start') }}</span>
|
||||
</button>
|
||||
|
||||
<button class="button is-primary" @click.once="createMatch">Create Match Session</button>
|
||||
<button class="button" @click="cancelMatch">
|
||||
<span>{{ $t('cancel') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section available-sessions">
|
||||
<h2 class="title is-4">Available Sessions</h2>
|
||||
<h2 class="title is-4">{{ $t('available-sessions') }}</h2>
|
||||
<div class="block">
|
||||
<div v-if="matchSessions.length === 0">
|
||||
<p>No sessions available</p>
|
||||
<p>{{ $t('no-sessions-available') }}</p>
|
||||
</div>
|
||||
<div v-else class="grid is-col-min-12">
|
||||
<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>ID: {{ session._id }}</p>
|
||||
<p>Players: {{ session.players.length }}</p>
|
||||
<p>{{ $t('id-session-_id', [session._id]) }}</p>
|
||||
<p>{{ $t('players-session-players-length', [session.players.length]) }}</p>
|
||||
<p>
|
||||
Seed: {{ session.seed }}
|
||||
<button class="button is-small" @click="() => copy(session.seed)">Copy</button>
|
||||
{{ $t('seed-session-seed', [session.seed]) }}
|
||||
<button class="button is-small" @click="() => copy(session.seed)">
|
||||
{{ $t('copy') }}
|
||||
</button>
|
||||
</p>
|
||||
<p>Status: {{ session.status }}</p>
|
||||
<p>{{ $t('status-session-status', [session.status]) }}</p>
|
||||
<div class="buttons is-centered mt-4"></div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<p class="card-footer-item">
|
||||
<a href="#" @click.once.prevent="() => joinMatch(session._id)"> Join </a>
|
||||
<a href="#" @click.once.prevent="() => joinMatch(session._id)">
|
||||
{{ $t('join') }}
|
||||
</a>
|
||||
</p>
|
||||
<p class="card-footer-item">
|
||||
<a href="#" @click.once.prevent="() => deleteMatch(session._id)"> Delete </a>
|
||||
<a href="#" @click.once.prevent="() => deleteMatch(session._id)">
|
||||
{{ $t('delete') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,10 +2,12 @@
|
||||
import { AuthenticationService } from '@/services/AuthenticationService'
|
||||
import { inject, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const router = useRouter()
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const { t } = useI18n()
|
||||
|
||||
const authService = inject<AuthenticationService>('auth')
|
||||
|
||||
@ -14,7 +16,7 @@ async function login() {
|
||||
await authService?.login(username.value, password.value)
|
||||
router.push({ name: 'home' })
|
||||
} catch (error) {
|
||||
alert('Invalid username or password')
|
||||
alert(t('invalid-username-or-password'))
|
||||
}
|
||||
// if (username.value === 'admin' && password.value === 'password') {
|
||||
// localStorage.setItem('token', 'true')
|
||||
@ -27,24 +29,34 @@ async function login() {
|
||||
|
||||
<template>
|
||||
<div class="login">
|
||||
<h1>Login</h1>
|
||||
<h1 class="title">{{ $t('login') }}</h1>
|
||||
<form class="form" @submit.prevent="login">
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<label class="label">{{ $t('username') }}</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="username" placeholder="Username" />
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="username"
|
||||
:placeholder="t('username-placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<label class="label">{{ $t('password') }}</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" v-model="password" placeholder="Password" />
|
||||
<input
|
||||
class="input"
|
||||
type="password"
|
||||
v-model="password"
|
||||
:placeholder="t('password-placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-primary" type="submit">Login</button>
|
||||
<button class="button is-primary" type="submit">{{ $t('login-button') }}</button>
|
||||
</div>
|
||||
<!-- <div class="control">
|
||||
<button class="button">Cancel</button>
|
||||
|
@ -2,88 +2,28 @@
|
||||
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 { useGameOptionsStore } from '@/stores/gameOptions'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { inject, onBeforeMount, ref } from 'vue'
|
||||
import { inject, onBeforeMount, ref, toRaw } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const eventBus = useEventBusStore()
|
||||
const gameOptionsStore = useGameOptionsStore()
|
||||
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
|
||||
const { gameOptions } = storeToRefs(gameOptionsStore)
|
||||
|
||||
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)
|
||||
matchSession.value = (await gameService.getMatchSession(sessionId)) as MatchSessionDto
|
||||
console.log('matchSession.value :>> ', toRaw(matchSession.value))
|
||||
}
|
||||
|
||||
eventBus.subscribe('window-before-unload', async () => {
|
||||
logger.debug('Window before unload')
|
||||
await cancelMatch()
|
||||
})
|
||||
|
||||
eventBus.subscribe('server:match-starting', (data) => {
|
||||
const session = data.sessionState as MatchSessionDto
|
||||
updateSessionState(session)
|
||||
logger.debug('Match starting')
|
||||
router.push({ name: 'game' })
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
sessionId = route.params.id as string
|
||||
console.log('sessionId :>> ', sessionId)
|
||||
loadData()
|
||||
if (sessionId) {
|
||||
setInterval(loadData, 5000)
|
||||
// setInterval(loadData, 5000)
|
||||
} else {
|
||||
router.push({ name: 'home' })
|
||||
}
|
||||
@ -91,27 +31,50 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="title is-2">Match Page {{ isSessionStarted }}</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 class="container">
|
||||
<h1 class="title is-1">{{ $t('match-page') }}</h1>
|
||||
<h2 class="title is-3">{{ matchSession?.name }}</h2>
|
||||
<div class="block mt-6">
|
||||
<p class="mb-4">
|
||||
<span class="title is-5">{{ $t('winner') }}</span>
|
||||
<span class="is-size-5 ml-4">{{ matchSession?.matchWinner?.name }}</span>
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
<span class="title is-5">{{ $t('points-to-win') }}</span>
|
||||
<span class="is-size-5 ml-4">{{ matchSession?.pointsToWin }}</span>
|
||||
</p>
|
||||
<h3 class="title is-5">{{ $t('final-scoreboard') }}</h3>
|
||||
<div v-bind:key="$index" v-for="(score, $index) in matchSession?.scoreboard">
|
||||
<p class="">
|
||||
<span class="title is-5">{{ score.name }}</span>
|
||||
<span class="is-size-5 ml-4">{{ score.score }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</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 class="grid">
|
||||
<div
|
||||
class="cell"
|
||||
v-bind:key="$index"
|
||||
v-for="(summary, $index) in matchSession?.gameSummaries"
|
||||
>
|
||||
<div class="block mt-6">
|
||||
<h3 class="title is-5">{{ $t('round-index-1', [$index + 1]) }}</h3>
|
||||
<p class="mb-4">
|
||||
<span class="title is-5">{{ $t('winner') }}</span>
|
||||
<span class="is-size-5 ml-4">{{ summary.winner?.name }}</span>
|
||||
</p>
|
||||
<h4 class="title is-6">{{ $t('scoreboard') }}</h4>
|
||||
<div v-bind:key="$index" v-for="(gameScore, $index) in summary.score">
|
||||
<p class="">
|
||||
<span class="title is-5">{{ gameScore.name }}</span>
|
||||
<span class="is-size-5 ml-4">{{ gameScore.score }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<!-- <div>{{ matchSession }}</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
Loading…
x
Reference in New Issue
Block a user