mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-16 23:42:15 -07:00
feature: added viewing support for multiple players, added custom context menu setup
This commit is contained in:
parent
d18b4d93cc
commit
ec631f3c2a
6 changed files with 1372 additions and 20 deletions
|
|
@ -1,23 +1,64 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="game">
|
<div class="game">
|
||||||
<!-- GRID SYSTEM, 2 columns -->
|
<!-- GRID SYSTEM, 2 columns -->
|
||||||
<div class="play-area-container">
|
<div class="play-area-container" @contextmenu.prevent="openContextMenu($event)">
|
||||||
<!-- the left side of the screen, the playmats -->
|
<!-- the left side of the screen, the playmats -->
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; width: 100%; height: 100%">
|
<div style="display: grid; grid-template-columns: 1fr 1fr; width: 100%; height: 100%">
|
||||||
<!-- FOR THE OTHER PLAYER -->
|
<!-- FOR THE OTHER PLAYERS -->
|
||||||
<div class="play-view-area">
|
<div
|
||||||
THIS AREA IS FOR THE OTHER PLAYER, WEBSOCKETS TO TRANSMIT ARRAY OF CARDS AND THEIR
|
v-for="i in numberOfPlayers"
|
||||||
POSITIONS
|
:key="i"
|
||||||
</div>
|
:class="[
|
||||||
<div class="play-view-area">
|
'play-view-area',
|
||||||
THIS AREA IS FOR THE OTHER PLAYER, WEBSOCKETS TO TRANSMIT ARRAY OF CARDS AND THEIR
|
i <= Math.ceil(numberOfPlayers / 2) ? 'flipped' : '',
|
||||||
POSITIONS
|
'bg-red',
|
||||||
</div>
|
]"
|
||||||
<div class="play-view-area">
|
>
|
||||||
THIS AREA IS FOR THE OTHER PLAYER, WEBSOCKETS TO TRANSMIT ARRAY OF CARDS AND THEIR
|
<!-- find the total number of players, divide by 2, that is how many draw areas should be flipped, the rest normal, rounded up -->
|
||||||
POSITIONS
|
<div
|
||||||
|
class="draw-view-area bg-blue"
|
||||||
|
style="height: 20%; position: absolute; bottom: 0; width: 100%; border-radius: 12px"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<p class="text-center" style="color: white; font-weight: bold; font-size: 24px">
|
||||||
|
Player {{ i }}'s Area
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<div v-for="(stack, index) in cardStacksOpponent" :key="index">
|
||||||
|
<div
|
||||||
|
v-for="(card, i) in stack"
|
||||||
|
:key="card.id"
|
||||||
|
class="card"
|
||||||
|
:style="cardStyle(card, i)"
|
||||||
|
@mouseover="startPeekTimer(card)"
|
||||||
|
@mouseleave="cancelPeekTimer(card)"
|
||||||
|
>
|
||||||
|
{{ card.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="play-view-area flipped">
|
||||||
|
<div
|
||||||
|
class="draw-view-area bg-yellow"
|
||||||
|
style="height: 20%; position: absolute; bottom: 0; width: 100%; border-radius: 12px"
|
||||||
|
></div>
|
||||||
|
<div v-for="(stack, index) in cardStacksOpponent" :key="index">
|
||||||
|
<div
|
||||||
|
v-for="(card, i) in stack"
|
||||||
|
:key="card.id"
|
||||||
|
class="card"
|
||||||
|
:style="cardStyle(card, i)"
|
||||||
|
@dblclick="toggleTap(card)"
|
||||||
|
@mouseover="startPeekTimer(card)"
|
||||||
|
@mouseleave="cancelPeekTimer(card)"
|
||||||
|
>
|
||||||
|
{{ card.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
<!-- FOR OUR PLAYER -->
|
<!-- FOR OUR PLAYER -->
|
||||||
<div class="play-area">
|
<div class="play-area">
|
||||||
<div class="playAreaStyles" ref="playArea">
|
<div class="playAreaStyles" ref="playArea">
|
||||||
|
|
@ -87,11 +128,25 @@
|
||||||
<button @click="clearBoard">Clear</button>
|
<button @click="clearBoard">Clear</button>
|
||||||
<button @click="connect()">CONNECT TO SOCKET</button>
|
<button @click="connect()">CONNECT TO SOCKET</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom Context Menu -->
|
||||||
|
<div
|
||||||
|
v-if="contextMenu.visible"
|
||||||
|
:style="{ top: `${contextMenu.y - 20}px`, left: `${contextMenu.x - 20}px` }"
|
||||||
|
class="context-menu"
|
||||||
|
@click.self="contextMenu.visible = false"
|
||||||
|
@mouseleave="closeContextMenu"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<!-- <li @click="action1">Action 1</li>
|
||||||
|
<li @click="action2">Action 2</li> -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, reactive } from 'vue';
|
||||||
import type { CSSProperties } from 'vue';
|
import type { CSSProperties } from 'vue';
|
||||||
import socket from '../socket.js'; // Import your socket connection module
|
import socket from '../socket.js'; // Import your socket connection module
|
||||||
|
|
||||||
|
|
@ -105,12 +160,25 @@ interface Card {
|
||||||
peek?: boolean; // optional: if the card is currently being peeked (hovered)
|
peek?: boolean; // optional: if the card is currently being peeked (hovered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Player {
|
||||||
|
username: string; // Player's username
|
||||||
|
cards: Card[]; // Array of cards owned by the player
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextMenuState {
|
||||||
|
visible: boolean;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
item?: unknown; // Replace `unknown` with your actual item type
|
||||||
|
}
|
||||||
|
|
||||||
// Constants defining card size in pixels
|
// Constants defining card size in pixels
|
||||||
const CARD_WIDTH = 60;
|
const CARD_WIDTH = 60;
|
||||||
const CARD_HEIGHT = 90;
|
const CARD_HEIGHT = 90;
|
||||||
|
|
||||||
// Reactive state holding all cards
|
// Reactive state holding all cards
|
||||||
const cards = ref<Card[]>([]);
|
const cards = ref<Card[]>([]);
|
||||||
|
const everyCard = ref<Player[]>([]); // This will hold all cards, including those not currently displayed
|
||||||
|
|
||||||
// Currently dragged card (or null if none)
|
// Currently dragged card (or null if none)
|
||||||
const dragging = ref<Card | null>(null);
|
const dragging = ref<Card | null>(null);
|
||||||
|
|
@ -128,10 +196,24 @@ const drawArea = ref<HTMLElement | null>(null);
|
||||||
const hoverTimers = new Map<number, number>();
|
const hoverTimers = new Map<number, number>();
|
||||||
|
|
||||||
const username = ref('Player1'); // Replace with actual username logic
|
const username = ref('Player1'); // Replace with actual username logic
|
||||||
|
const numberOfPlayers = ref(3); // Replace with actual number of other players logic
|
||||||
|
|
||||||
|
// get the cards from updateCards from the server
|
||||||
|
socket.on('updateCards', (data) => {
|
||||||
|
console.log('Received updated cards from server:', data.player.cards);
|
||||||
|
// set returned data to everyCard, (each Object will have its own username, and cards) which will then be iterated over to show the cards in the red spaces
|
||||||
|
everyCard.value = data.player.cards.map((card: Card) => ({
|
||||||
|
username: data.player.username,
|
||||||
|
cards: [card],
|
||||||
|
}));
|
||||||
|
|
||||||
|
// log the received cards
|
||||||
|
console.log('Every card:', everyCard.value);
|
||||||
|
});
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
socket.connect();
|
console.log('Connecting to WebSocket server...');
|
||||||
socket.emit('join', username.value, 'game-room');
|
|
||||||
|
socket.emit('join', username.value, 'game-room');
|
||||||
// Here you would typically initialize your WebSocket connection
|
// Here you would typically initialize your WebSocket connection
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -145,7 +227,22 @@ const emitCardsToServer = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//send this players cards to the websocket server so it can store them
|
const contextMenu = reactive<ContextMenuState>({
|
||||||
|
visible: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const openContextMenu = (event: MouseEvent , item?: unknown) => {
|
||||||
|
contextMenu.x = event.clientX;
|
||||||
|
contextMenu.y = event.clientY;
|
||||||
|
contextMenu.item = item;
|
||||||
|
contextMenu.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeContextMenu = () => {
|
||||||
|
contextMenu.visible = false;
|
||||||
|
};
|
||||||
|
|
||||||
// Computed array of cards currently peeked (hovered), should be limited to only one card at a time
|
// Computed array of cards currently peeked (hovered), should be limited to only one card at a time
|
||||||
const peekedCards = computed(() => {
|
const peekedCards = computed(() => {
|
||||||
|
|
@ -299,6 +396,9 @@ const dragMove = (e: MouseEvent) => {
|
||||||
c.x += dx;
|
c.x += dx;
|
||||||
c.y += dy;
|
c.y += dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emit updated cards to server
|
||||||
|
emitCardsToServer();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -340,6 +440,9 @@ const stopDrag = () => {
|
||||||
// Remove event listeners
|
// Remove event listeners
|
||||||
window.removeEventListener('mousemove', dragMove);
|
window.removeEventListener('mousemove', dragMove);
|
||||||
window.removeEventListener('mouseup', stopDrag);
|
window.removeEventListener('mouseup', stopDrag);
|
||||||
|
|
||||||
|
//emit updated cards to server
|
||||||
|
emitCardsToServer();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -359,6 +462,9 @@ const toggleTap = (card: Card) => {
|
||||||
for (const c of sameStack) {
|
for (const c of sameStack) {
|
||||||
c.tapped = shouldTap;
|
c.tapped = shouldTap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emit all tapped cards to server
|
||||||
|
emitCardsToServer();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -377,6 +483,22 @@ const cardStacks = computed(() => {
|
||||||
return Object.values(groups);
|
return Object.values(groups);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// need to create stacking logic for everyCard, so that it can be displayed in the red areas
|
||||||
|
// This will be used to display cards in the opponent's areas
|
||||||
|
const cardStacksOpponent = computed(() => {
|
||||||
|
const groups: Record<string, Card[]> = {};
|
||||||
|
for (const player of everyCard.value) {
|
||||||
|
for (const card of player.cards) {
|
||||||
|
// Use x,y as unique key for group
|
||||||
|
const key = `${card.x},${card.y}`;
|
||||||
|
if (!groups[key]) groups[key] = [];
|
||||||
|
groups[key].push(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return all groups as under a single array called cardStacks
|
||||||
|
return Object.values(groups);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates CSS styles for each card based on its position and index in stack.
|
* Generates CSS styles for each card based on its position and index in stack.
|
||||||
* - Cards are absolutely positioned based on their x,y grid coordinates.
|
* - Cards are absolutely positioned based on their x,y grid coordinates.
|
||||||
|
|
@ -544,4 +666,19 @@ const cardStyle = (card: Card, index: number): CSSProperties => ({
|
||||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flipped {
|
||||||
|
transform: scaleY(-1) scaleX(-1); /* Flip the card */
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu {
|
||||||
|
position: absolute;
|
||||||
|
height: 200px;
|
||||||
|
width: 150px;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,16 @@ export const state = reactive({
|
||||||
const URL = "http://localhost:3000";
|
const URL = "http://localhost:3000";
|
||||||
const socket = io(URL);
|
const socket = io(URL);
|
||||||
|
|
||||||
export default socket;
|
|
||||||
|
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
state.connected = true;
|
state.connected = true;
|
||||||
console.log("Connected to socket server");
|
console.log("Connected to socket server");
|
||||||
})
|
})
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
state.connected = false;
|
||||||
|
console.log("Disconnected from socket server");
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export default socket;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
33
ws_server/.gitignore
vendored
Normal file
33
ws_server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
.DS_Store
|
||||||
|
.thumbs.db
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Quasar core related directories
|
||||||
|
.quasar
|
||||||
|
/dist
|
||||||
|
/quasar.config.*.temporary.compiled*
|
||||||
|
|
||||||
|
# Cordova related directories and files
|
||||||
|
/src-cordova/node_modules
|
||||||
|
/src-cordova/platforms
|
||||||
|
/src-cordova/plugins
|
||||||
|
/src-cordova/www
|
||||||
|
|
||||||
|
# Capacitor related directories and files
|
||||||
|
/src-capacitor/www
|
||||||
|
/src-capacitor/node_modules
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
|
||||||
|
# local .env files
|
||||||
|
.env.local*
|
||||||
42
ws_server/app.js
Normal file
42
ws_server/app.js
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import express from "express";
|
||||||
|
import { createServer } from "node:http";
|
||||||
|
import { Server } from "socket.io";
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const server = createServer(app);
|
||||||
|
const io = new Server(server, {
|
||||||
|
cors: {
|
||||||
|
origin: "http://localhost:9000",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
res.send("<h1>Hello world</h1>");
|
||||||
|
});
|
||||||
|
|
||||||
|
io.on("connection", (socket) => {
|
||||||
|
console.log("a user connected");
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
console.log("user disconnected");
|
||||||
|
});
|
||||||
|
socket.on("join", (username, room) => {
|
||||||
|
socket.join(room);
|
||||||
|
// socket.to(room).emit("updateCards")
|
||||||
|
console.log(`${username} joined room: ${room}`);
|
||||||
|
});
|
||||||
|
socket.on("sync-cards", ({ room, username, cards }) => {
|
||||||
|
console.log(`syncing cards for room: ${room}`);
|
||||||
|
console.log("the cards: ", username, cards);
|
||||||
|
io.to(room).emit("updateCards", {
|
||||||
|
player: {
|
||||||
|
username: username,
|
||||||
|
cards: cards,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(`sent updated cards for room: ${room}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(3000, () => {
|
||||||
|
console.log("server running at http://localhost:3000");
|
||||||
|
});
|
||||||
1128
ws_server/package-lock.json
generated
Normal file
1128
ws_server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
6
ws_server/package.json
Normal file
6
ws_server/package.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"socket.io": "^4.8.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue