This commit is contained in:
pb-coding 2023-09-24 00:00:10 +02:00
parent 93f207cf12
commit 85c508d02d
10 changed files with 218 additions and 193 deletions

View file

@ -1,25 +1,16 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { socket } from "./socket"; import { socket } from "./socket";
import { Object3D, Mesh } from "three";
import { Canvas } from "@react-three/fiber"; import GameCanvas from "./components/GameCanvas";
import { OrbitControls, useGLTF, PerspectiveCamera } from "@react-three/drei"; import { Footer } from "./components/Footer";
import { ConnectionState } from "./components/ConnectionState";
import { ConnectionManager } from "./components/ConnectionManager";
import { JoinSession } from "./components/JoinSession"; import { JoinSession } from "./components/JoinSession";
import { Events } from "./components/Events";
import { Game } from "./types/gameTypes"; import { Game } from "./types/gameTypes";
import Action from "./components/Action";
import CardStack from "./components/CardStackOld";
import DepositCards from "./components/DepositCardsOld";
import CardCache from "./components/CardCacheOld";
import { extractCurrentPlayer } from "./helpers"; import { extractCurrentPlayer } from "./helpers";
import PlayArea from "./components/PlayArea"; import MessageDisplay from "./components/MessageDisplay";
import Text from "./global/Text";
import Button from "./global/Button";
export default function App() { export default function App() {
const [isConnected, setIsConnected] = useState(socket.connected); const [isConnected, setIsConnected] = useState(socket.connected);
const [messageEvents, setMessageEvents] = useState<string[]>([]); // const [messageEvents, setMessageEvents] = useState<string[]>([]);
const [session, setSession] = useState(""); const [session, setSession] = useState("");
const [clientsInRoom, setClientsInRoom] = useState(0); const [clientsInRoom, setClientsInRoom] = useState(0);
const [gameData, setGameData] = useState<Game | null>(null); const [gameData, setGameData] = useState<Game | null>(null);
@ -28,20 +19,7 @@ export default function App() {
const showStartGameButton = session !== "" && clientsInRoom >= 2; const showStartGameButton = session !== "" && clientsInRoom >= 2;
const showNextGameButton = gameData?.phase === "new round"; const showNextGameButton = gameData?.phase === "new round";
const playersData = extractCurrentPlayer(gameData); const currentPlayerData = extractCurrentPlayer(gameData);
function startGame() {
socket.emit("new-game", { sessionId: session });
}
function nextGame() {
socket.emit("next-round", { sessionId: session });
}
function clickCard(cardPosition: number) {
console.log("Clicked card", cardPosition);
socket.emit("click-card", cardPosition);
}
function setTempMessage(message: string) { function setTempMessage(message: string) {
setMessageDisplay(message); setMessageDisplay(message);
@ -65,7 +43,7 @@ export default function App() {
function onMessageEvent(message: string) { function onMessageEvent(message: string) {
setTempMessage(message); setTempMessage(message);
setMessageEvents((previous) => [...previous, message]); // setMessageEvents((previous) => [...previous, message]);
} }
function onGameUpdate(gameData: Game) { function onGameUpdate(gameData: Game) {
@ -87,130 +65,27 @@ export default function App() {
}; };
}, []); }, []);
// Three-Fiber
const tableModel = useGLTF("/models/table.glb");
const heightProportion = 1.25;
return ( return (
<div className="bg-gray-700"> <div className="bg-gray-900 w-screen h-screen">
<div {!gameData && (
style={{ <JoinSession
width: window.innerWidth, clientsInRoom={clientsInRoom}
height: window.innerHeight / heightProportion, session={session}
}} setSession={setSession}
> showStartGameButton={showStartGameButton}
<Canvas> />
<PerspectiveCamera )}
makeDefault <GameCanvas session={session} gameData={gameData} />
manual <MessageDisplay message={messageDispaly} />
fov={75} {session !== "" && (
aspect={window.innerWidth / (window.innerHeight / heightProportion)} <Footer
near={0.1} isConnected={isConnected}
far={1000} session={session}
position={[0, 45, 10]} clientsInRoom={clientsInRoom}
// lookAt={() => new Vector3(0, 0, 0)} currentPlayerData={currentPlayerData}
/> showNextGameButton={showNextGameButton}
<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>
{messageDispaly && messageDispaly !== "" && <p>{messageDispaly}</p>}
<JoinSession session={session} setSession={setSession} />
<Text>Player ID: {playersData?.id}</Text>
<Text>Player Name: {playersData?.name}</Text>
<Text>Player Round Points: {playersData?.roundPoints}</Text>
<Text>Player Total Points: {playersData?.totalPoints}</Text>
<ConnectionState
isConnected={isConnected}
session={session}
clientsInRoom={clientsInRoom}
/>
<ConnectionManager />
{showStartGameButton && <Button onClick={startGame}>Start Game</Button>}
{showNextGameButton && <Button onClick={nextGame}>Next Game</Button>}
<div className="bg-white">
<p> ############### OLD INTERFACE ############### </p>
{gameData &&
gameData.players.map((playerData, index) => (
<div key={index}>
<p>Player ID: {playerData.id}</p>
<p>Player Name: {playerData.name}</p>
<p>Player Round Points: {playerData.roundPoints}</p>
<p>Player Total Points: {playerData.totalPoints}</p>
<table>
<thead>
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
<th>Column 4</th>
</tr>
</thead>
<tbody>
{Array(Math.ceil(playerData.cards.length / 4))
.fill(null)
.map((_, rowIndex) => (
<tr key={rowIndex}>
{Array(4)
.fill(null)
.map((_, colIndex) => {
const card =
playerData.cards[rowIndex * 4 + colIndex];
return (
<td key={colIndex}>
{card && (
<Action
data={playerData}
action={() =>
clickCard(rowIndex * 4 + colIndex)
}
>
{card.value}
</Action>
)}
</td>
);
})}
</tr>
))}
</tbody>
</table>
<br />
<CardStack gameData={gameData} playerData={playerData} />
<DepositCards gameData={gameData} playerData={playerData} />
<CardCache playersData={playersData} />
<br />
</div>
))}
<br />
<br />
<Events events={messageEvents} />
</div>
</div> </div>
); );
} }

View file

@ -1,27 +0,0 @@
import { FC } from "react";
import { socket } from "../socket";
import Text from "../global/Text";
type ConnectionStateProps = {
isConnected: boolean;
session: string;
clientsInRoom: number;
};
export const ConnectionState: FC<ConnectionStateProps> = ({
isConnected,
session,
clientsInRoom,
}) => {
return (
<div>
<Text>
State: {"" + isConnected} <br />
Session: {session} <br />
Players: {clientsInRoom} <br />
SocketId: {socket.id}
</Text>
</div>
);
};

40
src/components/Footer.tsx Normal file
View file

@ -0,0 +1,40 @@
import { FC } from "react";
import { socket } from "../socket";
import Text from "../global/Text";
import Button from "../global/Button";
import { Player } from "../types/gameTypes";
type Footer = {
isConnected: boolean;
session: string;
clientsInRoom: number;
currentPlayerData: Player | undefined;
showNextGameButton: boolean;
};
export const Footer: FC<Footer> = ({
isConnected,
session,
clientsInRoom,
currentPlayerData,
showNextGameButton,
}) => {
function nextGame() {
socket.emit("next-round", { sessionId: session });
}
return (
<div>
<Text>Player Name: {currentPlayerData?.name}</Text>
<Text>Round Points: {currentPlayerData?.roundPoints}</Text>
<Text>Total Points: {currentPlayerData?.totalPoints}</Text>
<br />
<Text>State: {"" + isConnected}</Text>
<Text>SocketId: {socket.id}</Text>
<Text>Session: {session}</Text>
<Text>Players: {clientsInRoom}</Text>
{showNextGameButton && <Button onClick={nextGame}>Next Game</Button>}
</div>
);
};

View file

@ -0,0 +1,69 @@
import { FC } from "react";
import { Object3D, Mesh } from "three";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, useGLTF, PerspectiveCamera } from "@react-three/drei";
import PlayArea from "../components/PlayArea";
import { Game } from "../types/gameTypes";
type GameCanvasProps = {
session: string;
gameData: Game | null;
};
const GameCanvas: FC<GameCanvasProps> = ({ gameData }) => {
const tableModel = useGLTF("/models/table.glb");
const heightProportion = 1.4;
if (!gameData) return null;
return (
<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, 43, 10]}
// lookAt={() => new Vector3(0, 20, 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, 2]}
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>
);
};
export default GameCanvas;

View file

@ -4,11 +4,18 @@ import { socket } from "../socket";
import Button from "../global/Button"; import Button from "../global/Button";
type JoinSessionProps = { type JoinSessionProps = {
clientsInRoom: number;
session: string; session: string;
setSession: Dispatch<SetStateAction<string>>; setSession: Dispatch<SetStateAction<string>>;
showStartGameButton: boolean;
}; };
export const JoinSession: FC<JoinSessionProps> = ({ session, setSession }) => { export const JoinSession: FC<JoinSessionProps> = ({
clientsInRoom,
session,
setSession,
showStartGameButton,
}) => {
const [sessionField, setSessionField] = useState(""); const [sessionField, setSessionField] = useState("");
function joinSession(event: React.FormEvent<HTMLFormElement>) { function joinSession(event: React.FormEvent<HTMLFormElement>) {
@ -18,15 +25,59 @@ export const JoinSession: FC<JoinSessionProps> = ({ session, setSession }) => {
setSessionField(""); setSessionField("");
} }
if (session !== "") { function startGame() {
return null; socket.emit("new-game", { sessionId: session });
} }
return ( const isActiveSession = session !== "";
<form onSubmit={joinSession}>
<input onChange={(e) => setSessionField(e.target.value)} />
<Button>Join</Button> return (
</form> <section>
<div
className="w-full h-full absolute top-0 left-0 z-0"
style={{
backgroundImage: "linear-gradient(to bottom, #233876, #111827)",
}}
>
<div className="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 z-10 relative">
<h1 className="mb-4 text-4xl font-extrabold tracking-tight leading-none md:text-5xl lg:text-6xl text-white">
Skyjo
</h1>
<p className="mb-8 text-lg font-normal lg:text-xl sm:px-16 lg:px-48 text-gray-200">
Play Skyjo online with your friends!
</p>
<div className="p-4">
{!isActiveSession && (
<form onSubmit={joinSession}>
<label
htmlFor="first_name"
className="block mb-2 text-sm font-medium text-white"
>
Join Skyjo Session
</label>
<div className="flex space-x-1 items-center">
<input
className="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
placeholder="Session name"
required
onChange={(e) => setSessionField(e.target.value)}
/>
<Button>Join</Button>
</div>
</form>
)}
</div>
<p className="text-gray-200 text-3xl my-2">Session: {session}</p>
<p className="text-gray-200 text-3xl my-4">
Players: {clientsInRoom}
</p>
<br />
{showStartGameButton && (
<Button onClick={startGame}>Start Game</Button>
)}
</div>
</div>
</section>
); );
}; };

View file

@ -0,0 +1,19 @@
import { FC } from "react";
type MessageDisplayProps = {
message: string;
};
const MessageDisplay: FC<MessageDisplayProps> = ({ message }) => {
if (!message || message == "") return null;
return (
<div className="absolute top-0 left-0 w-full h-full flex justify-center items-center">
<div className="bg-gray-800 p-4 rounded-lg">
<p className="text-white text-xl opacity-100">{message}</p>
</div>
</div>
);
};
export default MessageDisplay;

View file

@ -4,7 +4,6 @@ import PlayerCards from "../components/PlayerCards";
import { Game } from "../types/gameTypes"; import { Game } from "../types/gameTypes";
import CardStackStaple from "./CardStackStaple"; import CardStackStaple from "./CardStackStaple";
import DiscardPile from "./DiscardPile"; import DiscardPile from "./DiscardPile";
import CardCache from "./CardCache";
type PlayAreaProps = { type PlayAreaProps = {
gameData: Game | null; gameData: Game | null;
@ -18,7 +17,6 @@ const PlayArea: FC<PlayAreaProps> = ({ gameData }) => {
<PlayerCards playersData={gameData.players} /> <PlayerCards playersData={gameData.players} />
<CardStackStaple cardStackData={gameData.cardStack} /> <CardStackStaple cardStackData={gameData.cardStack} />
<DiscardPile discardPileData={gameData.discardPile} /> <DiscardPile discardPileData={gameData.discardPile} />
<CardCache playersData={gameData.players} />
</> </>
); );
}; };

View file

@ -21,7 +21,7 @@ const PlayerCards: FC<PlayerCardsProps> = ({ playersData }) => {
const currentPlayerWithCards: PlayerWithVisualCards[] = []; const currentPlayerWithCards: PlayerWithVisualCards[] = [];
let nonCurrentPlayerIndex = 1; let nonCurrentPlayerIndex = 1;
playersData.forEach((player) => { playersData.forEach((player) => {
const positionOffset = 12; const positionOffset = 14;
const playerWithCards: PlayerWithVisualCards = { const playerWithCards: PlayerWithVisualCards = {
player, player,
cards: [], cards: [],
@ -68,7 +68,7 @@ const PlayerCards: FC<PlayerCardsProps> = ({ playersData }) => {
<CardCache <CardCache
playerData={playerWithCards.player} playerData={playerWithCards.player}
// current player is always at index 0 // current player is always at index 0
position={new Vector3(9, 20, 4 - playerIndex * 12)} position={new Vector3(9, 20, 6 - playerIndex * 12)}
/> />
</> </>
))} ))}

View file

@ -7,7 +7,7 @@ type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
const Button: FC<ButtonProps> = ({ children, ...rest }) => { const Button: FC<ButtonProps> = ({ children, ...rest }) => {
return ( return (
<button <button
className="text-white focus:ring-4 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-blue-800" className="text-white focus:ring-4 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-blue-800"
{...rest} {...rest}
> >
{children} {children}

View file

@ -5,7 +5,7 @@ type TextNormalProps = {
}; };
const Text: FC<TextNormalProps> = ({ children }) => { const Text: FC<TextNormalProps> = ({ children }) => {
return <p className="text-white">{children}</p>; return <p className="mb-2 text-sm font-medium text-white">{children}</p>;
}; };
export default Text; export default Text;