adding tauri

This commit is contained in:
Jose Conde
2024-07-18 23:52:33 +02:00
parent 54bd7f3840
commit a8d6129d3e
45 changed files with 4598 additions and 255 deletions

View File

@ -15,3 +15,9 @@
--bulma-danger-s: 74%;
--bulma-danger-l: 37%;
}
.tabs li.is-disabled {
pointer-events: none;
cursor: default;
opacity: 0.3;
}

View File

@ -1 +1,6 @@
@import './base.css';
html {
font-size: 16px;
overflow: auto;
}

View File

@ -28,7 +28,7 @@ export interface MatchSessionDto {
seed: string
waitingForPlayers: boolean
mode: string
pointsToWin: number
options: MatchSessionOptions
sessionInProgress: boolean
status: string
maxPlayers: number
@ -100,15 +100,24 @@ export interface AnimationOptions {
height?: number
}
export interface GameOptions {
boardScale?: number
handScale?: number
width?: number
height?: number
background?: string
teamed?: boolean
pointsToWin?: number
export interface ScreenOptions {
boardScale: number
handScale: number
width: number
height: number
}
export interface MatchSessionOptions {
screen?: ScreenOptions
seed?: string
background: string
teamed: boolean
winTarget: number
winType: 'points' | 'rounds'
sessionName: string
numPlayers: 1 | 2 | 3 | 4
}
export interface GameSummary {
gameId: string
isBlocked: boolean

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { GameDto, PlayerDto } from '@/common/interfaces'
import type { GameDto, MatchSessionOptions, PlayerDto } from '@/common/interfaces'
import { onMounted, onUnmounted, ref, inject } from 'vue'
import { Game } from '@/game/Game'
import { useGameStore } from '@/stores/game'
@ -20,8 +20,9 @@ const { playerState, sessionState } = storeToRefs(gameStore)
const { updateGameState } = gameStore
const { gameOptions } = storeToRefs(gameOptionsStore)
const minScreenWidth = 800
const minScreenHeight = 700
const minScreenWidth = 1280
const minScreenHeight = 72
0
let screenWidth = window.innerWidth - 10
let screenHeight = window.innerHeight - 10
@ -29,48 +30,34 @@ 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
const minSide = Math.min(screenWidth, screenHeight)
const boardScale = minSide > 1440 ? 1 : minSide > 1080 ? 0.8 : minSide > 720 ? 0.7 : 0.5
let appEl = ref<HTMLElement | null>(null)
const defaultOptions: MatchSessionOptions = {
background: gameOptions.value?.background || 'green',
teamed: false,
winType: 'points',
winTarget: 100,
seed: '',
sessionName: `Test #${Date.now()}`,
numPlayers: 1,
}
const gameOptionsValue: MatchSessionOptions = {
...defaultOptions,
...sessionState.value?.options,
...{ screen: { width: screenWidth, height: screenHeight, boardScale: boardScale, handScale: 1 } },
}
const game = new Game(
{
width: screenWidth,
height: screenHeight,
boardScale,
handScale: 1,
background: gameOptions.value?.background || 'green',
},
gameOptionsValue,
socketService,
playerState.value?.id || '',
sessionState.value?.id || '',
)
// watch(
// () => gameStore.gameState,
// async (value: GameDto | undefined) => {
// if (value === undefined) return
// await game.board?.setState(value)
// }
// )
// watch(
// () => gameStore.sessionState,
// (value: MatchSessionDto | undefined) => {
// if (value === undefined) return
// // logger.debug('gameSessionState-------------------------------------- :>> ', value)
// }
// )
// watch(
// () => gameStore.playerState,
// (value: PlayerDto | undefined) => {
// if (value === undefined) return
// game.hand.update(value as PlayerDto)
// }
// )
onMounted(async () => {
sessionId = route.params.id as string
if (appEl.value === null) return

View File

@ -0,0 +1,211 @@
<script setup lang="ts">
import type { MatchSessionOptions } from '@/common/interfaces'
import { ref } from 'vue'
const emit = defineEmits(['createMatch'])
let options = ref<MatchSessionOptions>({
background: 'green',
teamed: false,
winType: 'points',
winTarget: 100,
seed: '',
sessionName: `Test #${Date.now()}`,
numPlayers: 1,
})
function createMatch() {
emit('createMatch', options.value)
}
</script>
<template>
<div>
<div class="grid">
<div class="cell">
<div class="field">
<label class="label">{{ $t('mode') }}</label>
<div class="control">
<div class="buttons has-addons">
<button
class="button"
:class="{ 'is-primary is-selected': options.numPlayers === 1 }"
@click="
() => {
console.log('options :>> ', options)
options.numPlayers = 1
}
"
>
{{ $t('singleplayer') }}
</button>
<button
class="button"
:class="{ 'is-primary is-selected': options.numPlayers > 1 }"
@click="
() => {
console.log('options :>> ', options)
options.numPlayers = 2
}
"
>
{{ $t('multiplayer') }}
</button>
</div>
</div>
</div>
</div>
<div class="cell">
<div class="field" v-if="options.numPlayers > 1">
<label class="label">{{ $t('players-number') }}</label>
<div class="control">
<div class="buttons has-addons">
<button
class="button is-primary"
@click="options.numPlayers--"
:disabled="options.numPlayers <= 2"
>
-
</button>
<button class="button is-primary" disabled>{{ options.numPlayers }}</button>
<button
class="button is-primary"
@click="options.numPlayers++"
:disabled="options.numPlayers >= 4"
>
+
</button>
</div>
</div>
</div>
</div>
<div class="cell">
<div class="field" v-if="options.numPlayers > 1">
<div class="control">
<label for="teamed" class="checkbox">
<input v-model="options.teamed" name="teamed" type="checkbox" />
{{ $t('crossed-game-teamed') }}
</label>
</div>
</div>
</div>
</div>
<div class="grid">
<div class="cell">
<div class="field">
<label class="label">{{ $t('session-name') }}</label>
<div class="control">
<input
type="text"
class="input"
v-model="options.sessionName"
placeholder="$t('session-name-placeholder')"
/>
</div>
</div>
</div>
<!---->
<div class="cell">
<div class="field">
<label class="label">{{ $t('seed') }}</label>
<div class="control">
<input
type="text"
class="input"
style="margin-bottom: 0"
v-model="options.seed"
:placeholder="$t('seed-placeholder')"
/>
</div>
</div>
</div>
<!---->
</div>
<div class="grid">
<div class="cell">
<div class="field">
<label for="background" class="label">{{ $t('background-color') }}</label>
<div class="control">
<div class="select">
<select v-model="options.background" name="background">
<option value="wood-1">{{ $t('wood-1') }}</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>
</div>
</div>
<div class="cell">
<div class="field">
<label for="winTarget" class="label">{{ $t('win-type') }}</label>
<div class="control">
<div class="buttons has-addons">
<button
class="button"
:class="{ 'is-primary is-selected': options.winType === 'points' }"
@click="
() => {
options.winType = 'points'
options.winTarget = 100
}
"
>
{{ $t('points') }}
</button>
<button
class="button"
:class="{ 'is-primary is-selected': options.winType === 'rounds' }"
@click="
() => {
options.winType = 'rounds'
options.winTarget = 2
}
"
>
{{ $t('rounds') }}
</button>
</div>
</div>
</div>
</div>
<div class="cell">
<div class="field">
<label for="winTarget" class="label">{{ $t('points-to-win') }}</label>
<div class="control">
<div class="select" v-if="options.winType === 'points'">
<select v-model="options.winTarget" name="winTarget">
<option value="20">{{ $t('n-points', [20]) }}</option>
<option value="50">{{ $t('n-points', [50]) }}</option>
<option value="80">{{ $t('n-points', [80]) }}</option>
<option value="100">{{ $t('n-points', [100]) }}</option>
<option value="150">{{ $t('n-points', [150]) }}</option>
<option value="200">{{ $t('n-points', [200]) }}</option>
</select>
</div>
<div class="select" v-if="options.winType === 'rounds'">
<select v-model="options.winTarget" name="winTarget">
<option value="1">{{ $t('n-of-m-rounds', [1, 1]) }}</option>
<option value="2">{{ $t('n-of-m-rounds', [2, 3]) }}</option>
<option value="3">{{ $t('n-of-m-rounds', [3, 5]) }}</option>
<option value="4">{{ $t('n-of-m-rounds', [4, 7]) }}</option>
<option value="5">{{ $t('n-of-m-rounds', [5, 9]) }}</option>
<option value="6">{{ $t('n-of-m-rounds', [6, 11]) }}</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="buttons mt-6">
<button class="button is-primary" @click.prevent="createMatch">
{{ $t('create-match-session') }}
</button>
</div>
</div>
</template>
<style scoped></style>

View File

@ -1,9 +1,16 @@
import { Application, Assets, Container, EventEmitter, Sprite } from 'pixi.js'
import { Application, Assets, Container, EventEmitter, TilingSprite } from 'pixi.js'
import { Board } from '@/game/Board'
import { assets } from '@/game/utilities/assets'
import { Tile } from '@/game/Tile'
import { Hand } from '@/game/Hand'
import type { GameDto, MatchSessionDto, Movement, PlayerDto, TileDto } from '@/common/interfaces'
import type {
GameDto,
MatchSessionDto,
MatchSessionOptions,
Movement,
PlayerDto,
TileDto,
} from '@/common/interfaces'
import type { SocketIoClientService } from '@/services/SocketIoClientService'
import { wait } from '@/common/helpers'
import { Actions } from 'pixi-actions'
@ -31,23 +38,24 @@ export class Game extends EventEmitter {
private players: PlayerDto[] = []
constructor(
private options: GameOptions = {
boardScale: 1,
handScale: 1,
width: 1200,
height: 800,
background: 'bg-green',
},
private options: MatchSessionOptions,
private socketService: SocketIoClientService,
private playerId: string,
private sessionId: string,
) {
super()
this.options.screen = {
width: 1280,
height: 720,
handScale: 1,
boardScale: 0.7,
...this.options.screen,
}
}
async setup(): Promise<HTMLCanvasElement> {
const width = this.options.width || 1200
const height = this.options.height || 800
const width = this.options.screen?.width || 1280
const height = this.options.screen?.height || 720
await this.app.init({ width, height })
this.app.ticker.add((tick) => Actions.tick(tick.deltaTime / 60))
@ -65,9 +73,9 @@ export class Game extends EventEmitter {
]
this.initPlayers(players)
this.players = players
this.gameSummaryView = new GameSummayView(this.app)
this.hand.scale = this.options.handScale
this.board.scale = this.options.boardScale
this.gameSummaryView = new GameSummayView(this.app, this.options)
this.hand.scale = this.options.screen?.handScale || 1
this.board.scale = this.options.screen?.boardScale || 0.7
this.setBoardEvents()
this.setHandEvents()
this.initEventBus()
@ -80,7 +88,7 @@ export class Game extends EventEmitter {
iniialStuff(app: Application) {
app.stage.addChild(this.backgroundLayer)
const background = new Sprite(Assets.get(`bg-${this.options.background}`))
const background = new TilingSprite(Assets.get(`bg-${this.options.background}`))
this.backgroundLayer.addChild(background)
}

View File

@ -1,5 +1,5 @@
import { createButton, createContainer } from '@/common/helpers'
import type { GameSummary, MatchSessionDto } from '@/common/interfaces'
import type { GameSummary, MatchSessionDto, MatchSessionOptions } from '@/common/interfaces'
import { EventEmitter, type Application, type Container } from 'pixi.js'
import { createText, whiteStyle, yellowStyle } from './utilities/fonts'
@ -12,7 +12,10 @@ export class GameSummayView extends EventEmitter {
matchState!: MatchSessionDto
type: 'round' | 'match' = 'round'
constructor(app: Application) {
constructor(
app: Application,
private options: MatchSessionOptions,
) {
super()
this.width = 500
this.height = 400

View File

@ -28,10 +28,11 @@ import tile6_4 from '@/assets/images/tiles/6-4.png'
import tile6_5 from '@/assets/images/tiles/6-5.png'
import tile6_6 from '@/assets/images/tiles/6-6.png'
import bgWood_1 from '@/assets/images/backgrounds/wood-1.jpg'
import bg_1 from '@/assets/images/backgrounds/bg-1.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 bg_blue from '@/assets/images/backgrounds/bg-blue.png'
import bg_gray from '@/assets/images/backgrounds/bg-1.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'
@ -69,10 +70,11 @@ export const assets = [
{ alias: 'tile-6_5', src: tile6_5 },
{ alias: 'tile-6_6', src: tile6_6 },
{ alias: 'bg-wood-1', src: bgWood_1 },
{ alias: 'bg-gray', src: bg_1 },
{ alias: 'bg-green', src: bg_green },
{ alias: 'bg-red', src: bg_red },
{ alias: 'bg-yellow', src: bg_yellow },
{ alias: 'bg-blue', src: bg_blue },
{ alias: 'bg-gray', src: bg_gray },
{ alias: 'snd-move-1', src: snd_move_1 },
{ alias: 'snd-move-2', src: snd_move_2 },
{ alias: 'snd-move-3', src: snd_move_3 },

View File

@ -8,7 +8,7 @@
"login-button": "Login",
"invalid-username-or-password": "Invalid username or password",
"winner": "Winner",
"points-to-win": "Points to win",
"points-to-win": "Win Target",
"final-scoreboard": "Final Scoreboard",
"round-index-1": "Round {0}",
"scoreboard": "Scoreboard",
@ -26,13 +26,13 @@
"session-name-placeholder": "Session Name",
"seed": "Seed",
"seed-placeholder": "Type the session seed here!",
"background-color": "Background color",
"background-color": "Background",
"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})",
"crossed-game-teamed": "Crossed game",
"create-match-session": "Create Match Session",
"ready": "Ready",
"unready": "Unready",
@ -46,5 +46,18 @@
"player-turn": "{0}'s turn!"
},
"back": "Back",
"session-name": "Session Name"
"session-name": "Session Name",
"mode": "Mode",
"singleplayer": "Singleplayer",
"multiplayer": "Multiplayer",
"players-number": "Players Number",
"wood-1": "Wood",
"win-type": "Win unit",
"points": "Points",
"rounds": "Rounds",
"n-points": "{value} Points",
"n-of-m-rounds": "{0} of {1} Rounds",
"create-session": "Create Session",
"join-a-multiplayer-session": "Join a Multiplayer Session",
"tournaments": "Tournaments"
}

View File

@ -7,7 +7,7 @@
"password": "Contraseña",
"password-placeholder": "Contraseña",
"invalid-username-or-password": "usuario o contraseña invalido",
"points-to-win": "Puntos para ganar",
"points-to-win": "Objetivo para ganar",
"round-index-1": "Ronda {0}",
"scoreboard": "Marcador",
"winner": "Ganador",
@ -19,7 +19,7 @@
"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",
"background-color": "Fondo",
"blue-fabric": "Tela azul",
"cancel": "Cancelar",
"copy": "Copiar",
@ -31,7 +31,7 @@
"your-turn": "¡Tu turno!"
},
"create-match-session": "Crear sesión de partido",
"crossed-game-teamed": "Juego cruzado ({0})",
"crossed-game-teamed": "Juego cruzado",
"delete": "Borrar",
"gray-fabric": "Tela gris",
"green-fabric": "Tela verde",
@ -46,5 +46,18 @@
"start": "Comenzar",
"yellow-fabric": "Tela amarilla",
"back": "Volver",
"session-name": "Nombre de la sesión"
"session-name": "Nombre de la sesión",
"mode": "Modo",
"singleplayer": "Un jugador",
"multiplayer": "Multijugador",
"players-number": "Número de jugadores",
"wood-1": "Madera",
"win-type": "Unidad de puntaje",
"points": "Puntos",
"rounds": "Rondas",
"n-points": "{0} puntos",
"n-of-m-rounds": "{0} de {1} rondas",
"create-session": "Crear sesión",
"join-a-multiplayer-session": "Únete a una sesión multijugador",
"tournaments": "Torneos"
}

View File

@ -2,20 +2,20 @@ import { createI18n } from 'vue-i18n'
import en from './en.json'
import es from './es.json'
const browserLang = 'pt' //avigator.language.split('-')[0]
const i18n = createI18n({
legacy: false,
locale: 'es',
locale: browserLang,
fallbackLocale: 'en',
missingWarn: false,
fallbackWarn: false,
messages: {
en,
es,
},
})
// const translate = (key: string, context: any, plural: number = 1) => {
// return i18n.global.t(key, plural)
// }
export default i18n
const { t } = i18n.global
export { t, t as $t }

View File

@ -1,13 +1,14 @@
import { NetworkService } from './NetworkService'
import { ServiceBase } from './ServiceBase'
import type { MatchSessionOptions } from '@/common/interfaces'
export class GameService extends ServiceBase {
private networkService = new NetworkService()
async createMatchSession(sessionName: string, seed: string, options: any) {
async createMatchSession(options: MatchSessionOptions) {
const response = await this.networkService.post({
uri: '/game/match',
body: { sessionName, seed, options },
body: { options },
auth: true,
})
const { sessionId } = response

View File

@ -1,9 +1,9 @@
import type { GameOptions } from '@/common/interfaces'
import type { MatchSessionOptions } from '@/common/interfaces'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useGameOptionsStore = defineStore('gameOptions', () => {
const gameOptions = ref<GameOptions>()
const gameOptions = ref<MatchSessionOptions>()
return { gameOptions }
})

View File

@ -1,23 +1,23 @@
<script setup lang="ts">
import { inject, onMounted, onUnmounted, ref } from 'vue'
import { computed, inject, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useGameStore } from '@/stores/game'
import { storeToRefs } from 'pinia'
import type { LoggingService } from '@/services/LoggingService'
import type { GameService } from '@/services/GameService'
import type { MatchSessionDto } from '@/common/interfaces'
import type { MatchSessionOptions, MatchSessionDto } from '@/common/interfaces'
import { useEventBusStore } from '@/stores/eventBus'
import { useAuthStore } from '@/stores/auth'
import { copyToclipboard } from '@/common/helpers'
import { useGameOptionsStore } from '@/stores/gameOptions'
import MatchConfiguration from '@/components/MatchConfiguration.vue'
import { useI18n } from 'vue-i18n'
let teamedWith = ref<string | undefined>(undefined)
let background = ref<string>('green')
let teamed = ref<boolean>(false)
let pointsToWin = ref<number>(100)
let seed = ref<string>('')
let sessionName = ref(`Test #${Date.now()}`)
let matchSessions = ref<MatchSessionDto[]>([])
let dataInterval: any
let loadingSessions = ref<boolean>(false)
const router = useRouter()
const gameStore = useGameStore()
@ -33,36 +33,18 @@ const { sessionState, isSessionStarted, playerState, amIHost, readyForStart } =
const { user } = storeToRefs(auth)
const { gameOptions } = storeToRefs(gameOptionsStore)
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
// function setPlayerReady() {
// logger.debug('Starting game')
// if (!sessionState.value) {
// logger.error('No session found')
// return
// }
// if (!playerState.value) {
// logger.error('No player found')
// return
// }
// socketService.sendMessage('client:set-player-ready', {
// userId: playerState.value.id,
// sessionId: sessionState.value.id
// })
// }
const { t } = useI18n()
const eventBus = useEventBusStore()
eventBus.subscribe('window-before-unload', () => {
logger.debug('Window before unload')
})
async function createMatch() {
async function createMatch(options: MatchSessionOptions) {
logger.debug('Creating match')
await socketService.connect()
gameOptions.value = { background: background.value }
const sessionOptions = {
pointsToWin: pointsToWin.value,
}
const id = await gameService.createMatchSession(sessionName.value, seed.value, sessionOptions)
gameOptions.value = options
await gameService.createMatchSession(options)
logger.debug('Match created successfully')
// router.push({ name: 'match', params: { id } })
}
@ -90,6 +72,7 @@ async function startMatch() {
await socketService.sendMessageWithAck('client:start-session', {
sessionId: sessionId,
playerId: playerId,
teamedWith: teamedWith.value,
})
}
}
@ -97,13 +80,16 @@ async function startMatch() {
async function cancelMatch() {
logger.debug('Cancelling match')
if (sessionState?.value?.id) {
await gameService.cancelMatchSession(sessionState?.value?.id)
if (amIHost.value) {
await gameService.cancelMatchSession(sessionState?.value?.id)
} else {
//TODO: await gameService.leaveMatchSession(sessionState?.value?.id)
}
updateSessionState(undefined)
updatePlayerState(undefined)
updateGameState(undefined)
logger.debug('Match cancelled successfully')
router.push({ name: 'home' })
teamedWith.value = undefined
}
}
@ -127,14 +113,28 @@ async function deleteMatch(id: string) {
}
}
const playersToTeamUpWith = computed(() => {
return (
sessionState?.value?.players.filter((player) => player.id !== sessionState?.value?.creator) ||
[]
)
})
const canStart = computed(() => {
const players = sessionState?.value?.players || []
const options = gameOptions.value
const allReady = (players.length || 0) > 0 && players.every((player) => player.ready)
return (!options?.teamed && allReady) || (options?.teamed && !!teamedWith.value && allReady)
})
async function loadData() {
const listResponse = await gameService.listMatchSessions()
matchSessions.value = listResponse.data
}
onMounted(() => {
loadData()
dataInterval = setInterval(loadData, 5000)
// loadData()
// dataInterval = setInterval(loadData, 5000)
})
onUnmounted(() => {
@ -142,9 +142,34 @@ onUnmounted(() => {
})
function copy(sessionSeed: string) {
seed.value = sessionSeed
copyToclipboard(sessionSeed)
}
let tabs = ref<any[]>([
{ label: t('create-session'), id: 'create-tab', active: true, disabled: false },
{ label: t('join-a-multiplayer-session'), id: 'join-tab', active: false, disabled: false },
{ label: t('tournaments'), id: 'torunaments-tab', active: false, disabled: true },
])
const selectedTab = computed(() => tabs.value.find((t) => t.active)?.id)
const isCreateTab = computed(() => selectedTab.value === 'create-tab')
const isJoinTab = computed(() => selectedTab.value === 'join-tab')
const isTournamentTab = computed(() => selectedTab.value === 'torunaments-tab')
async function tabClick(tab: any) {
tabs.value.forEach((t) => (t.active = t === tab))
if (tab.id === 'join-tab') {
loadingSessions.value = true
await loadData()
dataInterval = setInterval(loadData, 5000)
} else {
clearInterval(dataInterval)
}
}
function onCreateMatch(options: MatchSessionOptions) {
console.log('Creating match', options)
createMatch(options)
}
</script>
<template>
@ -153,139 +178,110 @@ function copy(sessionSeed: string) {
<h1 class="title is-2">
{{ $t('welcome-to-the-user-username-s-home-page', [user.username]) }}
</h1>
<!-- Tabs -->
<div class="block" v-if="!isSessionStarted">
<div class="field">
<label class="label">{{ $t('session-name') }}</label>
<div class="control">
<input
type="text"
class="input"
v-model="sessionName"
placeholder="$t('session-name-placeholder')"
/>
</div>
<div class="tabs is-centered">
<ul>
<li
v-bind:key="tab.label"
v-for="tab in tabs"
:class="{ 'is-active': tab.active, 'is-disabled': tab.disabled }"
>
<a @click="() => tabClick(tab)">{{ tab.label }}</a>
</li>
</ul>
</div>
<div class="field">
<label class="label">{{ $t('seed') }}</label>
<div class="control">
<input
type="text"
class="input"
style="margin-bottom: 0"
v-model="seed"
:placeholder="$t('seed-placeholder')"
/>
</div>
</div>
<div class="grid">
<div class="cell">
<div class="field">
<label for="background" class="label">{{ $t('background-color') }}</label>
<div class="control">
<div class="select">
<select v-model="background" name="background">
<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>
<!-- Tabs End -->
<!-- Match Configuration -->
<section class="section" v-if="isCreateTab">
<MatchConfiguration @create-match="onCreateMatch" />
</section>
<!-- Match Configuration End -->
<!-- Join a Multiplayer Session -->
<section class="section available-sessions" v-if="isJoinTab">
<div class="block">
<div v-if="matchSessions.length === 0">
<p>{{ $t('no-sessions-available') }}</p>
</div>
<div class="field">
<div class="control">
<label for="teamed" class="checkbox">
<input v-model="teamed" name="teamed" type="checkbox" />
{{ $t('crossed-game-teamed', [teamed]) }}
</label>
</div>
</div>
</div>
<div class="cell">
<div class="field">
<label for="pointsToWin" class="label">{{ $t('points-to-win') }}</label>
<div class="control">
<div class="select">
<select v-model="pointsToWin" name="pointsToWin">
<option value="50">50</option>
<option value="80">80</option>
<option value="100">100</option>
<option value="150">150</option>
<option value="200">200</option>
</select>
</div>
</div>
</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">
<h2 class="title is-4">{{ sessionState?.name }}</h2>
<h6 class="title is-size-5">Players</h6>
<div v-for="player in sessionState?.players" :key="player.id">
<p>{{ player.name }}</p>
<p>{{ player.ready ? 'Ready' : 'Not ready' }}</p>
</div>
<div class="buttons mt-6">
<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" @click="cancelMatch">
<span>{{ $t('cancel') }}</span>
</button>
</div>
</div>
</section>
<section class="section available-sessions" v-if="!isSessionStarted">
<h2 class="title is-4">{{ $t('available-sessions') }}</h2>
<div class="block">
<div v-if="matchSessions.length === 0">
<p>{{ $t('no-sessions-available') }}</p>
</div>
<div v-else class="fixed-grid has-3-cols">
<div class="grid">
<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>{{ $t('id-session-_id', [session._id]) }}</p>
<p>{{ $t('players-session-players-length', [session.players.length]) }}</p>
<p>
{{ $t('seed-session-seed', [session.seed]) }}
<button class="button is-small is-ghost" @click="() => copy(session.seed)">
{{ $t('copy') }}
</button>
</p>
<p>{{ $t('status-session-status', [session.status]) }}</p>
<div class="buttons is-centered mt-6">
<button
class="button is-primary"
@click.once.prevent="() => joinMatch(session._id)"
>
<span>{{ $t('join') }}</span>
</button>
<button
class="button is-text"
@click.once.prevent="() => deleteMatch(session._id)"
>
<span>{{ $t('delete') }}</span>
</button>
<div v-else class="fixed-grid has-3-cols">
<div class="grid">
<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>{{ $t('id-session-_id', [session._id]) }}</p>
<p>{{ $t('players-session-players-length', [session.players.length]) }}</p>
<p>
{{ $t('seed-session-seed', [session.seed]) }}
<button class="button is-small is-ghost" @click="() => copy(session.seed)">
{{ $t('copy') }}
</button>
</p>
<p>{{ $t('status-session-status', [session.status]) }}</p>
<div class="buttons is-centered mt-6">
<button
class="button is-primary"
@click.once.prevent="() => joinMatch(session._id)"
>
<span>{{ $t('join') }}</span>
</button>
<button
class="button is-text"
@click.once.prevent="() => deleteMatch(session._id)"
>
<span>{{ $t('delete') }}</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Join a Multiplayer Session End -->
<!-- Tournaments -->
<section class="section" v-if="isTournamentTab"></section>
<!-- Tournaments End -->
</div>
<div class="block" v-if="isSessionStarted">
<h2 class="title is-4">{{ sessionState?.name }}</h2>
<h6 class="title is-size-5">Players</h6>
<div v-for="player in sessionState?.players" :key="player.id">
<p>{{ player.name }} ({{ player.ready ? 'Ready' : 'Not ready' }})</p>
</div>
<div class="mt-4" v-if="amIHost && gameOptions?.teamed && playersToTeamUpWith.length > 0">
<div class="field">
<label for="background" class="label">Team up with</label>
<div class="control">
<div class="select">
<select v-model="teamedWith" name="teamedWidth">
<option v-for="player in playersToTeamUpWith" :key="player.id" :value="player.id">
{{ player.name }}
</option>
</select>
</div>
</div>
</div>
</div>
<div class="buttons mt-6">
<button class="button is-dark" @click="setPlayerReady">
<span v-if="!readyForStart">{{ $t('ready') }}</span
><span v-else>{{ $t('unready') }}</span>
</button>
<button
class="button is-success"
:disabled="!canStart"
@click="startMatch"
v-if="amIHost"
>
<span>{{ $t('start') }}</span>
</button>
<button class="button is-danger" @click="cancelMatch">
<span>{{ $t('cancel') }}</span>
</button>
</div>
</div>
</section>

View File

@ -39,9 +39,13 @@ onBeforeMount(() => {
<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('win-type') }}</span>
<span class="is-size-5 ml-4">{{ matchSession?.options.winType }}</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>
<span class="is-size-5 ml-4">{{ matchSession?.options.winTarget }}</span>
</p>
<h3 class="title is-5">{{ $t('final-scoreboard') }}</h3>
<div v-bind:key="$index" v-for="(score, $index) in matchSession?.scoreboard">