diff --git a/src/App.tsx b/src/App.tsx
index 3cb566e..22a0bfa 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,15 +1,19 @@
import { useState, useEffect } from "react";
import { socket } from "./socket";
-import ThreeScene from "./components/ThreeScene";
+import { Object3D, Mesh } from "three";
+import { Canvas } from "@react-three/fiber";
+import { OrbitControls, useGLTF, PerspectiveCamera } from "@react-three/drei";
import { ConnectionState } from "./components/ConnectionState";
import { ConnectionManager } from "./components/ConnectionManager";
import { JoinSession } from "./components/JoinSession";
import { Events } from "./components/Events";
import { Game } from "./types/gameTypes";
import Action from "./components/Action";
-import CardStack from "./components/CardStack";
-import DepositCards from "./components/DepositCards";
-import CardCache from "./components/CardCache";
+import CardStack from "./components/CardStackOld";
+import DepositCards from "./components/DepositCardsOld";
+import CardCache from "./components/CardCacheOld";
+import { extractCurrentPlayer } from "./helpers";
+import PlayArea from "./components/PlayArea";
export default function App() {
const [isConnected, setIsConnected] = useState(socket.connected);
@@ -22,7 +26,7 @@ export default function App() {
const showStartGameButton = session !== "" && clientsInRoom >= 2;
const showNextGameButton = gameData?.phase === "new round";
- const playersData = extractMyData(gameData);
+ const playersData = extractCurrentPlayer(gameData);
function startGame() {
socket.emit("new-game", { sessionId: session });
@@ -37,11 +41,6 @@ export default function App() {
socket.emit("click-card", cardPosition);
}
- function extractMyData(gameData: Game | null) {
- if (!gameData) return undefined;
- return gameData.players.find((player) => player.socketId === socket.id);
- }
-
function setTempMessage(message: string) {
setMessageDisplay(message);
setTimeout(() => {
@@ -49,16 +48,6 @@ export default function App() {
}, 3000);
}
- useEffect(() => {
- if (gameData?.phase === "game-over") {
- setTempMessage("Game Over");
- } else if (gameData?.phase === "game-started") {
- setTempMessage("Game Started");
- } else if (gameData?.phase === "waiting-for-players") {
- setTempMessage("Waiting for players");
- }
- }, [gameData]);
-
useEffect(() => {
function onConnect() {
setIsConnected(true);
@@ -96,9 +85,57 @@ export default function App() {
};
}, []);
+ // Three-Fiber
+ const tableModel = useGLTF("/models/table.glb");
+ const heightProportion = 1.25;
+
return (
-
+
+
+
= ({ playersData }) => {
- if (!playersData?.cardCache) {
- return null;
- }
+ const [cardCacheCard, setCardCacheCard] = useState(null);
- return (
-
-
Card Cache: {playersData.cardCache.value}
-
- );
+ const updateCardCache = (playerData: Player) => {
+ if (playerData.cardCache == null) {
+ setCardCacheCard(null);
+ return;
+ }
+ const showFaceUp = true;
+ const card = createCard(
+ playerData.cardCache,
+ new Vector3(9, 20, 4),
+ showFaceUp
+ );
+ setCardCacheCard(card);
+ };
+
+ useEffect(() => {
+ const playerData = playersData.find(
+ (player) => player.socketId === socket.id
+ );
+ if (!playerData) return;
+ updateCardCache(playerData);
+ }, [playersData]);
+
+ if (!cardCacheCard) return null;
+
+ return ;
};
export default CardCache;
diff --git a/src/components/CardCacheOld.tsx b/src/components/CardCacheOld.tsx
new file mode 100644
index 0000000..f1f068d
--- /dev/null
+++ b/src/components/CardCacheOld.tsx
@@ -0,0 +1,21 @@
+import { FC } from "react";
+
+import { Player } from "../types/gameTypes";
+
+type CardCacheProps = {
+ playersData: Player | undefined;
+};
+
+const CardCache: FC = ({ playersData }) => {
+ if (!playersData?.cardCache) {
+ return null;
+ }
+
+ return (
+
+
Card Cache: {playersData.cardCache.value}
+
+ );
+};
+
+export default CardCache;
diff --git a/src/components/CardStackCard.tsx b/src/components/CardStackCard.tsx
new file mode 100644
index 0000000..26c7f02
--- /dev/null
+++ b/src/components/CardStackCard.tsx
@@ -0,0 +1,27 @@
+import { FC, useState, useEffect } from "react";
+import { Object3D, Object3DEventMap } from "three";
+import { socket } from "../socket";
+
+type CardStackCardProps = {
+ card: Object3D;
+ isUppermostCard: boolean;
+};
+
+const CardStackCard: FC = ({ card, isUppermostCard }) => {
+ const [cardObject, setCardObject] =
+ useState>(card);
+
+ useEffect(() => {
+ if (cardObject.name === card.name) return;
+ setCardObject(card);
+ }, [card, cardObject]);
+
+ const clickCard = () => {
+ if (!isUppermostCard) return;
+ console.log("Draw card");
+ socket.emit("draw-from-card-stack", "draw card");
+ };
+ return clickCard()} />;
+};
+
+export default CardStackCard;
diff --git a/src/components/CardStack.tsx b/src/components/CardStackOld.tsx
similarity index 100%
rename from src/components/CardStack.tsx
rename to src/components/CardStackOld.tsx
diff --git a/src/components/CardStackStaple.tsx b/src/components/CardStackStaple.tsx
new file mode 100644
index 0000000..03f91f3
--- /dev/null
+++ b/src/components/CardStackStaple.tsx
@@ -0,0 +1,45 @@
+import { FC, useEffect, useState } from "react";
+import { Vector3, Object3D } from "three";
+import { CardStack } from "../types/gameTypes";
+import { createCardStaple } from "../objects/cards";
+import CardStackCard from "./CardStackCard";
+
+type CardStackProps = {
+ cardStackData: CardStack | null;
+};
+
+const CardStackStaple: FC = ({ cardStackData }) => {
+ const [cardStack, setCardStack] = useState([]);
+
+ const updateCardStack = (cardStackData: CardStack) => {
+ const stack = createCardStaple(
+ cardStackData.cards,
+ new Vector3(-1.2, 20, 0)
+ );
+ setCardStack(stack);
+ };
+
+ const checkIfUppermostCard = (index: number) => {
+ return index === cardStack.length - 1;
+ };
+
+ useEffect(() => {
+ if (!cardStackData) return;
+ updateCardStack(cardStackData);
+ }, [cardStackData]);
+ if (!cardStackData) return null;
+
+ return (
+ <>
+ {cardStack.map((card, index) => (
+
+ ))}
+ >
+ );
+};
+
+export default CardStackStaple;
diff --git a/src/components/DepositCards.tsx b/src/components/DepositCardsOld.tsx
similarity index 100%
rename from src/components/DepositCards.tsx
rename to src/components/DepositCardsOld.tsx
diff --git a/src/components/DiscardPile.tsx b/src/components/DiscardPile.tsx
new file mode 100644
index 0000000..5c9bb5d
--- /dev/null
+++ b/src/components/DiscardPile.tsx
@@ -0,0 +1,48 @@
+import { FC, useState, useEffect } from "react";
+import { Vector3, Object3D } from "three";
+
+import { Card } from "../types/gameTypes";
+import { createCardStaple } from "../objects/cards";
+import DiscardPileCard from "./DiscardPileCard";
+
+type DiscardPileProps = {
+ discardPileData: Card[] | null;
+};
+
+const DiscardPile: FC = ({ discardPileData }) => {
+ const [discardPile, setDiscardPile] = useState([]);
+
+ const updateDiscardPile = (discardPileData: Card[]) => {
+ const showFaceUp = true;
+ const pile = createCardStaple(
+ discardPileData,
+ new Vector3(1.2, 20, 0),
+ showFaceUp
+ );
+ setDiscardPile(pile);
+ };
+
+ const checkIfUppermostCard = (index: number) => {
+ return index === discardPile.length - 1;
+ };
+
+ useEffect(() => {
+ if (!discardPileData) return;
+ updateDiscardPile(discardPileData);
+ }, [discardPileData]);
+
+ if (!discardPile) return null;
+ return (
+ <>
+ {discardPile.map((card, index) => (
+
+ ))}
+ >
+ );
+};
+
+export default DiscardPile;
diff --git a/src/components/DiscardPileCard.tsx b/src/components/DiscardPileCard.tsx
new file mode 100644
index 0000000..9eaa1c1
--- /dev/null
+++ b/src/components/DiscardPileCard.tsx
@@ -0,0 +1,30 @@
+import { FC, useState, useEffect } from "react";
+import { Object3D, Object3DEventMap } from "three";
+import { socket } from "../socket";
+
+type DiscardPileCardProps = {
+ card: Object3D;
+ isUppermostCard: boolean;
+};
+
+const DiscardPileCard: FC = ({
+ card,
+ isUppermostCard,
+}) => {
+ const [cardObject, setCardObject] =
+ useState>(card);
+
+ useEffect(() => {
+ if (cardObject.name === card.name) return;
+ setCardObject(card);
+ }, [card, cardObject]);
+
+ const clickCard = () => {
+ if (!isUppermostCard) return;
+ console.log("Draw card");
+ socket.emit("draw-from-card-stack", "draw card");
+ };
+ return clickCard()} />;
+};
+
+export default DiscardPileCard;
diff --git a/src/components/PlayArea.tsx b/src/components/PlayArea.tsx
new file mode 100644
index 0000000..984baba
--- /dev/null
+++ b/src/components/PlayArea.tsx
@@ -0,0 +1,26 @@
+import { FC } from "react";
+
+import PlayerCards from "../components/PlayerCards";
+import { Game } from "../types/gameTypes";
+import CardStackStaple from "./CardStackStaple";
+import DiscardPile from "./DiscardPile";
+import CardCache from "./CardCache";
+
+type PlayAreaProps = {
+ gameData: Game | null;
+};
+
+const PlayArea: FC = ({ gameData }) => {
+ if (!gameData) return null;
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default PlayArea;
diff --git a/src/components/PlayerCard.tsx b/src/components/PlayerCard.tsx
new file mode 100644
index 0000000..852af43
--- /dev/null
+++ b/src/components/PlayerCard.tsx
@@ -0,0 +1,69 @@
+import { FC, useState, useEffect } from "react";
+import { Object3D, Object3DEventMap } from "three";
+// import { useFrame } from "@react-three/fiber";
+import { socket } from "../socket";
+import { PlayerWithVisualCards } from "../types/gameTypes";
+
+type PlayerCardProps = {
+ card: Object3D;
+ index: number;
+ isCurrentPlayer: boolean;
+ playerWithCards: PlayerWithVisualCards;
+};
+
+const PlayerCard: FC = ({
+ card,
+ index,
+ isCurrentPlayer,
+ playerWithCards,
+}) => {
+ const isCardRevealed = playerWithCards.player.knownCardPositions[index];
+ const [cardObject, setCardObject] =
+ useState>(card);
+
+ useEffect(() => {
+ if (cardObject.name === card.name) return;
+ setCardObject(card);
+ }, [card, cardObject]);
+
+ // TODO: Fix stuttering card rotation
+ /*const [rotationGoal, setRotationGoal] = useState(0);
+ const rotationSpeed = 0.05; // Adjust for faster/slower flip
+ const currentRotation = useRef(0);
+
+ useFrame(() => {
+ if (currentRotation.current < rotationGoal) {
+ card.rotation.x += rotationSpeed;
+ currentRotation.current += rotationSpeed;
+ if (currentRotation.current >= rotationGoal) {
+ card.rotation.x = rotationGoal;
+ }
+ } else if (currentRotation.current > rotationGoal) {
+ card.rotation.x -= rotationSpeed;
+ currentRotation.current -= rotationSpeed;
+ if (currentRotation.current <= rotationGoal) {
+ card.rotation.x = rotationGoal;
+ }
+ }
+ });*/
+
+ if (isCardRevealed) {
+ card.rotation.x = Math.PI;
+ }
+
+ const clickCard = () => {
+ if (!isCurrentPlayer) return;
+ console.log("Clicked on one of my cards");
+ socket.emit("click-card", index, (response: string) => {
+ console.log("Response:", response);
+ /*if (rotationGoal === 0) {
+ setRotationGoal(Math.PI);
+ } else {
+ setRotationGoal(0);
+ }*/
+ });
+ };
+ return clickCard()} />;
+};
+
+export default PlayerCard;
diff --git a/src/components/PlayerCards.tsx b/src/components/PlayerCards.tsx
new file mode 100644
index 0000000..39b9692
--- /dev/null
+++ b/src/components/PlayerCards.tsx
@@ -0,0 +1,73 @@
+import { FC, useState, useEffect } from "react";
+import { socket } from "../socket";
+import { Vector3 } from "three";
+
+import { createPlayerCards } from "../objects/cards";
+import PlayerCard from "./PlayerCard";
+import { PlayerWithVisualCards, Player } from "../types/gameTypes";
+
+type PlayerCardsProps = {
+ playersData: Player[];
+};
+
+const PlayerCards: FC = ({ playersData }) => {
+ const [playersWithCards, setPlayersWithCards] = useState<
+ PlayerWithVisualCards[]
+ >([]);
+
+ const updatePlayerCards = (playersData: Player[]) => {
+ const playersWithCards: PlayerWithVisualCards[] = [];
+ const currentPlayerWithCards: PlayerWithVisualCards[] = [];
+ let nonCurrentPlayerIndex = 1;
+ playersData.forEach((player) => {
+ const positionOffset = 12;
+ const playerWithCards: PlayerWithVisualCards = {
+ player,
+ cards: [],
+ };
+ if (player.socketId === socket.id) {
+ playerWithCards.cards = createPlayerCards(
+ player.cards,
+ new Vector3(0, 20, positionOffset)
+ );
+ currentPlayerWithCards.push(playerWithCards);
+ } else {
+ const playerOffset = positionOffset + nonCurrentPlayerIndex * -20;
+ playerWithCards.cards = createPlayerCards(
+ player.cards,
+ new Vector3(0, 20, playerOffset)
+ );
+ playersWithCards.push(playerWithCards);
+ nonCurrentPlayerIndex++;
+ }
+ });
+ setPlayersWithCards([...currentPlayerWithCards, ...playersWithCards]);
+ };
+
+ useEffect(() => {
+ if (!playersData) return;
+ updatePlayerCards(playersData);
+ }, [playersData]);
+
+ if (!playersWithCards) return null;
+
+ return (
+ <>
+ {playersWithCards.map((playerWithCards, playerIndex) => (
+ <>
+ {playerWithCards.cards.map((card, index) => (
+
+ ))}
+ >
+ ))}
+ >
+ );
+};
+
+export default PlayerCards;
diff --git a/src/helpers.ts b/src/helpers.ts
new file mode 100644
index 0000000..1aae00f
--- /dev/null
+++ b/src/helpers.ts
@@ -0,0 +1,7 @@
+import { Game } from "./types/gameTypes";
+import { socket } from "./socket";
+
+export function extractCurrentPlayer(gameData: Game | null) {
+ if (!gameData) return undefined;
+ return gameData.players.find((player) => player.socketId === socket.id);
+}
diff --git a/src/objects/cards.ts b/src/objects/cards.ts
index 517e0b5..3bbe5b0 100644
--- a/src/objects/cards.ts
+++ b/src/objects/cards.ts
@@ -77,7 +77,11 @@ const getCardTexture = (value: number | string) => {
return cardTexture;
};
-export const createCard = (cardData: Card, position: Vector3) => {
+export const createCard = (
+ cardData: Card,
+ position: Vector3,
+ faceUp: boolean = false
+) => {
const cardMaterial = [
new MeshBasicMaterial(),
new MeshBasicMaterial(),
@@ -89,6 +93,12 @@ export const createCard = (cardData: Card, position: Vector3) => {
const card = new Mesh(cardGeometry, cardMaterial);
card.name = cardData.name;
card.position.copy(position);
+
+ if (faceUp) {
+ card.rotation.x = Math.PI;
+ console.log("faceUp");
+ }
+
return card;
};
@@ -117,3 +127,29 @@ export const createPlayerCards = (
});
return playerCards;
};
+
+export const createCardStaple = (
+ cards: Card[],
+ positionReference: Vector3,
+ faceUp: boolean = false
+) => {
+ const cardStackCards: Mesh<
+ BoxGeometry,
+ MeshBasicMaterial[],
+ Object3DEventMap
+ >[] = [];
+ cards.forEach((card: Card, index: number) => {
+ const cardPositionX = positionReference.x;
+ const cardPositionY = positionReference.y + index * 0.01;
+ const cardPositionZ = positionReference.z;
+
+ const cardPosition = new Vector3(
+ cardPositionX,
+ cardPositionY,
+ cardPositionZ
+ );
+ const cardStackCard = createCard(card, cardPosition, faceUp);
+ cardStackCards.push(cardStackCard);
+ });
+ return cardStackCards;
+};
diff --git a/src/types/gameTypes.ts b/src/types/gameTypes.ts
index 93811b0..1ced9bf 100644
--- a/src/types/gameTypes.ts
+++ b/src/types/gameTypes.ts
@@ -1,3 +1,5 @@
+import { Object3D } from "three";
+
export type Player = {
id: number;
socketId: string;
@@ -12,6 +14,11 @@ export type Player = {
closedRound: boolean;
};
+export type PlayerWithVisualCards = {
+ player: Player;
+ cards: Object3D[];
+};
+
export type CardValue =
| -2
| -1