302 lines
7.8 KiB
Vue
302 lines
7.8 KiB
Vue
|
<script setup lang="ts">
|
||
|
import GameComponent from '@/components/GameComponent.vue'
|
||
|
import TileComponent from '@/components/TileComponent.vue'
|
||
|
import type { GameState, PlayerState } from '@/utilities/interfaces'
|
||
|
import { inject, onBeforeUnmount, ref } from 'vue'
|
||
|
import { onMounted } from 'vue'
|
||
|
|
||
|
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||
|
|
||
|
const socketService: any = inject('socket')
|
||
|
const { socket } = socketService
|
||
|
let gameState = ref<GameState | undefined>(undefined)
|
||
|
let playerState = ref<PlayerState | undefined>(undefined)
|
||
|
let data = ref('')
|
||
|
let responseField = ref('')
|
||
|
let statusField = ref('')
|
||
|
let sessionId = ref('')
|
||
|
let selectdAction: any = undefined
|
||
|
let canSelectTile = false
|
||
|
let canMakeMove = false
|
||
|
let tileSelected = ''
|
||
|
let moveType = ''
|
||
|
|
||
|
const options = [
|
||
|
{ value: 'createSession', default: '{"user": "arhuako"}' },
|
||
|
{ value: 'startSession', default: (id: string) => `{"sessionId": "${id}"}` },
|
||
|
{ value: 'joinSession', default: '{"user": "pepe", "sessionId": "arhuako"}' },
|
||
|
{ value: 'leaveSession', default: '{"user": "pepe", "sessionId": "arhuako"}' },
|
||
|
{ value: 'chat message', default: 'chat message' }
|
||
|
]
|
||
|
|
||
|
onMounted(async () => {
|
||
|
socket.on('gameState', (data: GameState, callback: any) => {
|
||
|
gameState.value = data
|
||
|
statusField.value = statusField.value + JSON.stringify(data, null, 2)
|
||
|
callback({
|
||
|
status: 'ok'
|
||
|
})
|
||
|
})
|
||
|
|
||
|
socket.on('playerState', (data: PlayerState, callback: any) => {
|
||
|
console.log('playerState :>> ', data)
|
||
|
playerState.value = data
|
||
|
statusField.value = statusField.value + JSON.stringify(data, null, 2)
|
||
|
callback({
|
||
|
status: 'ok'
|
||
|
})
|
||
|
})
|
||
|
|
||
|
socket.on('makeMove', async (data: any, callback: any) => {
|
||
|
statusField.value = statusField.value + JSON.stringify(data, null, 2)
|
||
|
canMakeMove = true
|
||
|
while (canMakeMove) {
|
||
|
await wait(500)
|
||
|
}
|
||
|
callback({
|
||
|
status: 'ok',
|
||
|
tileId: tileSelected,
|
||
|
moveType
|
||
|
})
|
||
|
})
|
||
|
|
||
|
socket.on('chooseTile', async (data: any, callback: any) => {
|
||
|
statusField.value = statusField.value + JSON.stringify(data, null, 2)
|
||
|
canSelectTile = true
|
||
|
while (canSelectTile) {
|
||
|
await wait(500)
|
||
|
}
|
||
|
callback({
|
||
|
status: 'ok',
|
||
|
tileId: tileSelected
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
|
||
|
function selectTile(id: string) {
|
||
|
if (canSelectTile) {
|
||
|
tileSelected = id
|
||
|
canSelectTile = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function selectTileToMove(id: string) {
|
||
|
if (canMakeMove) {
|
||
|
if (tileSelected === id) {
|
||
|
tileSelected = ''
|
||
|
return
|
||
|
}
|
||
|
tileSelected = id
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onTileClick(tileId: string) {
|
||
|
console.log('tileId :>> ', tileId)
|
||
|
selectTile(tileId)
|
||
|
}
|
||
|
|
||
|
function makeMove(type: string) {
|
||
|
if (canMakeMove) {
|
||
|
if (type === 'pass') {
|
||
|
tileSelected = ''
|
||
|
canMakeMove = false
|
||
|
} else if (tileSelected) {
|
||
|
moveType = type
|
||
|
canMakeMove = false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onBeforeUnmount(() => {
|
||
|
socketService.disconnect()
|
||
|
})
|
||
|
|
||
|
function actionSelected() {
|
||
|
console.log('event :>> ', selectdAction)
|
||
|
|
||
|
if (selectdAction.value === 'createSession') {
|
||
|
responseField.value = ''
|
||
|
} else if (selectdAction.value === 'startSession') {
|
||
|
data.value = selectdAction.default(sessionId.value)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
data.value = selectdAction.default
|
||
|
}
|
||
|
|
||
|
const getMessage = (msg: string) => {
|
||
|
if (msg.startsWith('{') && msg.endsWith('}')) return JSON.parse(msg)
|
||
|
return msg
|
||
|
}
|
||
|
|
||
|
async function sendMessage() {
|
||
|
if (selectdAction && data.value.trim() !== '') {
|
||
|
console.log('socketService :>> ', socketService)
|
||
|
|
||
|
const response = await socketService.sendMessageWithAck(
|
||
|
selectdAction.value,
|
||
|
getMessage(data.value.trim())
|
||
|
)
|
||
|
console.log('response :>> ', response)
|
||
|
handleResponse(response)
|
||
|
}
|
||
|
// socketService.emit('message', data.value)
|
||
|
}
|
||
|
|
||
|
function handleResponse(response: any) {
|
||
|
if (selectdAction.value === 'createSession') {
|
||
|
sessionId.value = response.sessionId
|
||
|
}
|
||
|
|
||
|
data.value = ''
|
||
|
const responseStr = JSON.stringify(response, null, 2)
|
||
|
responseField.value = !responseField.value
|
||
|
? responseStr
|
||
|
: responseField.value + '\n---\n ' + responseStr
|
||
|
selectdAction = undefined
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<div class="grid">
|
||
|
<div>
|
||
|
<GameComponent :gameState="gameState" :playerState="playerState" @tileClick="onTileClick" />
|
||
|
<p>SessionID: {{ sessionId }}</p>
|
||
|
<ul id="messages"></ul>
|
||
|
<form id="form" action="">
|
||
|
<p>
|
||
|
<select v-model="selectdAction" id="event" autocomplete="off" @change="actionSelected">
|
||
|
<option value="">Select event</option>
|
||
|
<option :key="option.value" v-for="option in options" :value="option">
|
||
|
{{ option.value }}
|
||
|
</option>
|
||
|
</select>
|
||
|
</p>
|
||
|
<!-- <p><input id="room" autocomplete="off" /></p> -->
|
||
|
<p>
|
||
|
<textarea v-model="data" id="message" autocomplete="off" placeholder="Data"></textarea>
|
||
|
</p>
|
||
|
<p><button @click.prevent.stop="sendMessage">Send</button></p>
|
||
|
<div class="grid">
|
||
|
<div>
|
||
|
<textarea
|
||
|
:value="responseField"
|
||
|
id="response"
|
||
|
autocomplete="off"
|
||
|
placeholder="Response"
|
||
|
></textarea>
|
||
|
</div>
|
||
|
<div>
|
||
|
<textarea
|
||
|
:value="statusField"
|
||
|
id="status"
|
||
|
autocomplete="off"
|
||
|
placeholder="Game status"
|
||
|
></textarea>
|
||
|
</div>
|
||
|
</div>
|
||
|
</form>
|
||
|
</div>
|
||
|
<div>
|
||
|
<!-- <div style="display: flex; gap: 4px">
|
||
|
<TileComponent :revealed="false" :top="6" :bottom="0" />
|
||
|
<TileComponent :revealed="true" :top="6" :bottom="0" />
|
||
|
</div> -->
|
||
|
<h4>Board</h4>
|
||
|
<!-- Board -->
|
||
|
<div class="board-container">
|
||
|
<div v-if="gameState?.tileSelectionPhase">
|
||
|
<div class="tiles-container">
|
||
|
<TileComponent
|
||
|
:key="tile.id"
|
||
|
v-for="tile in gameState.boneyard"
|
||
|
@tile-clicked="selectTile(tile.id)"
|
||
|
/>
|
||
|
</div>
|
||
|
<div v-if="canSelectTile">Please select a tile.</div>
|
||
|
<div v-else>Wait your turn.</div>
|
||
|
</div>
|
||
|
<div v-if="!gameState?.tileSelectionPhase">
|
||
|
<div class="tiles-container">
|
||
|
<TileComponent
|
||
|
:class="{ 'selected-tile': tileSelected === tile.id }"
|
||
|
:revealed="true"
|
||
|
:key="tile.id"
|
||
|
v-for="tile in gameState?.board"
|
||
|
:top="tile.pips[0]"
|
||
|
:bottom="tile.pips[1]"
|
||
|
/>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<!-- End board-->
|
||
|
|
||
|
<div class="hand-container">
|
||
|
<h4>
|
||
|
Your Hand <button class="control" @click="makeMove('left')">Left</button
|
||
|
><button class="control" @click="makeMove('right')">Right</button
|
||
|
><button class="control" @click="makeMove('pass')">Pass</button>
|
||
|
</h4>
|
||
|
<div class="tiles-container">
|
||
|
<TileComponent
|
||
|
:revealed="true"
|
||
|
:key="tile.id"
|
||
|
v-for="tile in playerState?.hand ?? []"
|
||
|
@tile-clicked="selectTileToMove(tile.id)"
|
||
|
:top="tile.pips[0]"
|
||
|
:bottom="tile.pips[1]"
|
||
|
/>
|
||
|
</div>
|
||
|
</div>
|
||
|
<!-- End hand-->
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<style scoped lang="scss">
|
||
|
* {
|
||
|
box-sizing: border-box;
|
||
|
}
|
||
|
|
||
|
.control {
|
||
|
font-size: 60%;
|
||
|
padding: 8px;
|
||
|
margin-right: 4px;
|
||
|
}
|
||
|
|
||
|
textarea {
|
||
|
width: 100%;
|
||
|
height: 150px;
|
||
|
resize: none;
|
||
|
font-size: 70%;
|
||
|
}
|
||
|
#response,
|
||
|
#status {
|
||
|
height: 400px;
|
||
|
}
|
||
|
|
||
|
.tiles-container {
|
||
|
display: flex;
|
||
|
gap: 4px;
|
||
|
flex-flow: row wrap;
|
||
|
align-content: flex-start;
|
||
|
}
|
||
|
|
||
|
.board-container {
|
||
|
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);
|
||
|
}
|
||
|
</style>
|