wip
This commit is contained in:
parent
f5d58c1541
commit
1e549de4fb
15 changed files with 479 additions and 32 deletions
79
src/App.tsx
79
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 (
|
||||
<div className="App">
|
||||
<ThreeScene gameData={gameData} />
|
||||
<div
|
||||
style={{
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight / heightProportion,
|
||||
}}
|
||||
>
|
||||
<Canvas>
|
||||
<PerspectiveCamera
|
||||
makeDefault
|
||||
manual
|
||||
fov={75}
|
||||
aspect={window.innerWidth / (window.innerHeight / heightProportion)}
|
||||
near={0.1}
|
||||
far={1000}
|
||||
position={[0, 45, 10]}
|
||||
// lookAt={() => new Vector3(0, 0, 0)}
|
||||
/>
|
||||
<ambientLight color={0xa3a3a3} intensity={0.1} />
|
||||
<directionalLight
|
||||
color={0xffffff}
|
||||
position={[0, 50, 20]}
|
||||
castShadow
|
||||
shadow-mapSize={[1024, 1024]}
|
||||
/>
|
||||
<mesh>
|
||||
<boxGeometry args={[2, 2, 2]} />
|
||||
<meshStandardMaterial color={0x00ff00} />
|
||||
</mesh>
|
||||
<primitive
|
||||
object={tableModel.scene}
|
||||
position={[0, 1.8, 0]}
|
||||
scale={[2, 2, 2]}
|
||||
traverse={(node: Object3D) => {
|
||||
if (node instanceof Mesh) {
|
||||
// node.castShadow = true;
|
||||
node.receiveShadow = true;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<gridHelper args={[100, 100]} />
|
||||
<axesHelper args={[5]} />
|
||||
<OrbitControls />
|
||||
<PlayArea gameData={gameData} />
|
||||
</Canvas>
|
||||
</div>
|
||||
<ConnectionState
|
||||
isConnected={isConnected}
|
||||
session={session}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,42 @@
|
|||
import { FC } from "react";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { Vector3, Object3D } from "three";
|
||||
import { socket } from "../socket";
|
||||
|
||||
import { createCard } from "../objects/cards";
|
||||
import { Player } from "../types/gameTypes";
|
||||
|
||||
type CardCacheProps = {
|
||||
playersData: Player | undefined;
|
||||
playersData: Player[];
|
||||
};
|
||||
|
||||
const CardCache: FC<CardCacheProps> = ({ playersData }) => {
|
||||
if (!playersData?.cardCache) {
|
||||
return null;
|
||||
}
|
||||
const [cardCacheCard, setCardCacheCard] = useState<Object3D | null>(null);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Card Cache: {playersData.cardCache.value}</p>
|
||||
</div>
|
||||
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 <primitive object={cardCacheCard} />;
|
||||
};
|
||||
|
||||
export default CardCache;
|
||||
|
|
|
|||
21
src/components/CardCacheOld.tsx
Normal file
21
src/components/CardCacheOld.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { FC } from "react";
|
||||
|
||||
import { Player } from "../types/gameTypes";
|
||||
|
||||
type CardCacheProps = {
|
||||
playersData: Player | undefined;
|
||||
};
|
||||
|
||||
const CardCache: FC<CardCacheProps> = ({ playersData }) => {
|
||||
if (!playersData?.cardCache) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Card Cache: {playersData.cardCache.value}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardCache;
|
||||
27
src/components/CardStackCard.tsx
Normal file
27
src/components/CardStackCard.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { FC, useState, useEffect } from "react";
|
||||
import { Object3D, Object3DEventMap } from "three";
|
||||
import { socket } from "../socket";
|
||||
|
||||
type CardStackCardProps = {
|
||||
card: Object3D<Object3DEventMap>;
|
||||
isUppermostCard: boolean;
|
||||
};
|
||||
|
||||
const CardStackCard: FC<CardStackCardProps> = ({ card, isUppermostCard }) => {
|
||||
const [cardObject, setCardObject] =
|
||||
useState<Object3D<Object3DEventMap>>(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 <primitive object={cardObject} onClick={() => clickCard()} />;
|
||||
};
|
||||
|
||||
export default CardStackCard;
|
||||
45
src/components/CardStackStaple.tsx
Normal file
45
src/components/CardStackStaple.tsx
Normal file
|
|
@ -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<CardStackProps> = ({ cardStackData }) => {
|
||||
const [cardStack, setCardStack] = useState<Object3D[]>([]);
|
||||
|
||||
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) => (
|
||||
<CardStackCard
|
||||
key={index}
|
||||
card={card}
|
||||
isUppermostCard={checkIfUppermostCard(index)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardStackStaple;
|
||||
48
src/components/DiscardPile.tsx
Normal file
48
src/components/DiscardPile.tsx
Normal file
|
|
@ -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<DiscardPileProps> = ({ discardPileData }) => {
|
||||
const [discardPile, setDiscardPile] = useState<Object3D[]>([]);
|
||||
|
||||
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) => (
|
||||
<DiscardPileCard
|
||||
key={index}
|
||||
card={card}
|
||||
isUppermostCard={checkIfUppermostCard(index)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiscardPile;
|
||||
30
src/components/DiscardPileCard.tsx
Normal file
30
src/components/DiscardPileCard.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { FC, useState, useEffect } from "react";
|
||||
import { Object3D, Object3DEventMap } from "three";
|
||||
import { socket } from "../socket";
|
||||
|
||||
type DiscardPileCardProps = {
|
||||
card: Object3D<Object3DEventMap>;
|
||||
isUppermostCard: boolean;
|
||||
};
|
||||
|
||||
const DiscardPileCard: FC<DiscardPileCardProps> = ({
|
||||
card,
|
||||
isUppermostCard,
|
||||
}) => {
|
||||
const [cardObject, setCardObject] =
|
||||
useState<Object3D<Object3DEventMap>>(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 <primitive object={cardObject} onClick={() => clickCard()} />;
|
||||
};
|
||||
|
||||
export default DiscardPileCard;
|
||||
26
src/components/PlayArea.tsx
Normal file
26
src/components/PlayArea.tsx
Normal file
|
|
@ -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<PlayAreaProps> = ({ gameData }) => {
|
||||
if (!gameData) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PlayerCards playersData={gameData.players} />
|
||||
<CardStackStaple cardStackData={gameData.cardStack} />
|
||||
<DiscardPile discardPileData={gameData.discardPile} />
|
||||
<CardCache playersData={gameData.players} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayArea;
|
||||
69
src/components/PlayerCard.tsx
Normal file
69
src/components/PlayerCard.tsx
Normal file
|
|
@ -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<Object3DEventMap>;
|
||||
index: number;
|
||||
isCurrentPlayer: boolean;
|
||||
playerWithCards: PlayerWithVisualCards;
|
||||
};
|
||||
|
||||
const PlayerCard: FC<PlayerCardProps> = ({
|
||||
card,
|
||||
index,
|
||||
isCurrentPlayer,
|
||||
playerWithCards,
|
||||
}) => {
|
||||
const isCardRevealed = playerWithCards.player.knownCardPositions[index];
|
||||
const [cardObject, setCardObject] =
|
||||
useState<Object3D<Object3DEventMap>>(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<number>(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 <primitive object={cardObject} onClick={() => clickCard()} />;
|
||||
};
|
||||
|
||||
export default PlayerCard;
|
||||
73
src/components/PlayerCards.tsx
Normal file
73
src/components/PlayerCards.tsx
Normal file
|
|
@ -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<PlayerCardsProps> = ({ 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) => (
|
||||
<PlayerCard
|
||||
key={index}
|
||||
card={card}
|
||||
playerWithCards={playerWithCards}
|
||||
index={index}
|
||||
isCurrentPlayer={playerIndex === 0}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayerCards;
|
||||
7
src/helpers.ts
Normal file
7
src/helpers.ts
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue