Go one on one with another player!
@@ -226,7 +273,7 @@ onMounted(async () => {
-
@@ -249,7 +296,7 @@ onMounted(async () => {
-
diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js
index df46c27..a043f63 100644
--- a/frontend/src/router/index.js
+++ b/frontend/src/router/index.js
@@ -9,6 +9,7 @@ import TestDealing from '@/pages/TestDealing.vue'
import TestAllAnimations from '@/pages/TestAllAnimations.vue'
import TestGameBoard from '@/pages/TestGameBoard.vue'
import SinglePlayerGamePage from '@/pages/game/SinglePlayerGamePage.vue'
+import MultiplayerGamePage from '@/pages/game/MultiplayerGamePage.vue'
import RegisterPage from '@/pages/register/RegisterPage.vue'
import CoinsPurchasePage from '@/pages/purchase/CoinPurchasePage.vue'
@@ -44,6 +45,12 @@ const router = createRouter({
props: { gameType: 9 },
meta: { requiresAuth: true },
},
+ {
+ path: '/game/multiplayer/:id',
+ name: 'multiplayer-game',
+ component: MultiplayerGamePage,
+ meta: { requiresAuth: true }
+ },
{
path: '/user',
component: UserPage,
diff --git a/frontend/src/stores/socket.js b/frontend/src/stores/socket.js
index b762d59..e46923d 100644
--- a/frontend/src/stores/socket.js
+++ b/frontend/src/stores/socket.js
@@ -1,24 +1,138 @@
import { defineStore } from 'pinia'
-import { inject } from 'vue'
+import { ref } from 'vue'
+import { io } from 'socket.io-client'
+import { useAuthStore } from './auth'
-export const useSocketStore = defineStore('socket', () => {
- const socket = inject('socket')
+export const useSocketStore = defineStore('websocket', () => {
+ const authStore = useAuthStore()
- const handleConnection = () => {
- try {
- socket.on('connect', () => {
- console.log(`[Socket] Connected -- ${socket.id}`)
- })
- socket.on('disconnect', () => {
- console.log(`[Socket] Disconnected -- ${socket.id}`)
- })
- } catch (error) {
- console.error('[Socket] Connection error:', error)
+ const socket = ref(null)
+ const connected = ref(false)
+ const gameState = ref(null)
+ const lastError = ref(null)
+
+ const WS_URL = 'http://localhost:3000'
+
+ const connect = () => {
+ if (socket.value?.connected) return
+
+ const token = authStore.getToken()
+ if (!token) {
+ console.error('No token available for WebSocket connection')
+ return
+ }
+
+ socket.value = io(WS_URL, {
+ auth: {
+ token: `Bearer ${token}`
+ },
+ reconnection: true,
+ reconnectionAttempts: 5,
+ reconnectionDelay: 1000
+ })
+
+ // Connection events
+ socket.value.on('connect', () => {
+ connected.value = true
+ console.log('[WebSocket] Connected:', socket.value.id)
+ })
+
+ socket.value.on('disconnect', (reason) => {
+ connected.value = false
+ console.log('[WebSocket] Disconnected:', reason)
+ })
+
+ socket.value.on('connect_error', (error) => {
+ console.error('[WebSocket] Connection error:', error.message)
+ lastError.value = error.message
+ })
+
+ // Heartbeat
+ socket.value.on('heartbeat', () => {
+ socket.value.emit('heartbeat_ack')
+ })
+
+ // Game events
+ socket.value.on('game-state', (state) => {
+ gameState.value = state
+ console.log('[Game] State updated:', state)
+ })
+
+ socket.value.on('game-error', (data) => {
+ lastError.value = data.message
+ console.error('[Game] Error:', data.message)
+ })
+
+ socket.value.on('error', (data) => {
+ lastError.value = data.message
+ console.error('[Socket] Error:', data.message)
+ })
+ }
+
+ const disconnect = () => {
+ if (socket.value) {
+ socket.value.disconnect()
+ socket.value = null
+ connected.value = false
+ gameState.value = null
+ lastError.value = null
+ }
+ }
+
+ const joinGame = (gameId) => {
+ if (!socket.value?.connected) {
+ console.error('[Game] Socket not connected')
+ return
+ }
+ console.log('[Game] Joining game:', gameId)
+ socket.value.emit('join-game', { gameId })
+ }
+
+ const playCard = (gameId, cardId) => {
+ if (!socket.value?.connected) {
+ console.error('[Game] Socket not connected')
+ return
+ }
+ console.log('[Game] Playing card:', cardId)
+ socket.value.emit('play-card', { gameId, cardId })
+ }
+
+ const onTrickEnd = (callback) => {
+ if (socket.value) {
+ socket.value.on('trick-end', callback)
+ }
+ }
+
+ const onGameOver = (callback) => {
+ if (socket.value) {
+ socket.value.on('game-over', callback)
+ }
+ }
+
+ const offTrickEnd = () => {
+ if (socket.value) {
+ socket.value.off('trick-end')
+ }
+ }
+
+ const offGameOver = () => {
+ if (socket.value) {
+ socket.value.off('game-over')
}
}
return {
- handleConnection,
+ socket,
+ connected,
+ gameState,
+ lastError,
+ connect,
+ disconnect,
+ joinGame,
+ playCard,
+ onTrickEnd,
+ onGameOver,
+ offTrickEnd,
+ offGameOver
}
-
})
diff --git a/websockets/events/connection.js b/websockets/events/connection.js
index db4fe03..87edfec 100644
--- a/websockets/events/connection.js
+++ b/websockets/events/connection.js
@@ -1,4 +1,4 @@
-import { addUser, removeUser } from "../state/connection";
+import { addUser, removeUser } from "../state/connection.js";
const HEARTBEAT_INTERVAL = 30000; // 30 seconds
const HEARTBEAT_TIMEOUT = 10000; // 10 seconds
diff --git a/websockets/state/connection.js b/websockets/state/connection.js
index 574712a..3ae391b 100644
--- a/websockets/state/connection.js
+++ b/websockets/state/connection.js
@@ -14,4 +14,4 @@ export const removeUser = (socketId) => {
export const getUserCount = () => {
return users.size;
-};
\ No newline at end of file
+};