From 5f8c6994aa87388a03d4e59158d2fb7747a00814 Mon Sep 17 00:00:00 2001 From: DTieman Date: Fri, 19 Apr 2024 22:12:31 +0200 Subject: [PATCH 1/7] Added the ability to choose your cardtype when playing a jack or the newly added joker --- .dockerignore | 2 ++ .gitignore | 1 + src/layout/components/Card.tsx | 16 ++++++++++++---- src/layout/components/Game.tsx | 19 ++++++++++++++----- src/layout/pages/Room.tsx | 23 +++++++++++++---------- 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/.dockerignore b/.dockerignore index 6f692d5..e01e7e7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ .env.example +.env .idea README.md +.gitea \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2a1832d..96f1b63 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ yarn-debug.log* yarn-error.log* .idea +.env \ No newline at end of file diff --git a/src/layout/components/Card.tsx b/src/layout/components/Card.tsx index 1de719d..28001ef 100644 --- a/src/layout/components/Card.tsx +++ b/src/layout/components/Card.tsx @@ -11,9 +11,17 @@ const Card: FunctionComponent = ({cardString, handleClick, isHidden, isCl const cardType = cardString.split(' ')[0].toLowerCase(); const cardValue = cardString.split(' ')[1].toLowerCase(); - const cardSource = isHidden ? - require(`../../assets/cards/back.png`) : - require(`../../assets/cards/${cardType}_${cardValue}.png`); + const cardSource = (): string => { + if (isHidden) return require("../../assets/cards/back.png"); + if (cardType === "JOKER") { + if (cardValue === "RED") { + return require("../../assets/cards/joker_red.png") + } + return require("../../assets/cards/joker_black.png") + } + return require(`../../assets/cards/${cardType}_${cardValue}.png`); + } + const cardName = isHidden ? 'back' : `${cardType} ${cardValue}`; const handleCardClick = () => { @@ -24,7 +32,7 @@ const Card: FunctionComponent = ({cardString, handleClick, isHidden, isCl return (
- {cardName} + {cardName}
) } diff --git a/src/layout/components/Game.tsx b/src/layout/components/Game.tsx index 7822622..50bc1c2 100644 --- a/src/layout/components/Game.tsx +++ b/src/layout/components/Game.tsx @@ -1,9 +1,11 @@ import React, {FunctionComponent} from "react"; import Deck from "./Deck"; import Hand from "./Hand"; +import {GHButton} from "./Button"; -interface GameState { +export interface GameState { PlayerName: string; + CurrentState: string; Hand: string[]; CurrentCard: string; CurrentPlayer: string; @@ -14,9 +16,12 @@ interface Props { gameState: GameState handleCardSend: (cardString: string) => void; handleDraw: () => void; + handleChoice: (choice: string) => void; } -const Game:FunctionComponent = ({gameState, handleCardSend, handleDraw}) => { +const CHOICES = ['SPADES', 'HEARTS', 'DIAMONDS', 'CLUBS']; + +const Game: FunctionComponent = ({gameState, handleCardSend, handleDraw, handleChoice}) => { return (
{ @@ -27,13 +32,17 @@ const Game:FunctionComponent = ({gameState, handleCardSend, handleDraw}) gameState.Hand && } + { + gameState.CurrentState === 'CHOOSE' && + CHOICES.map(choice => handleChoice(choice)}>{choice}) + }
    { - gameState.Players && - gameState.Players.map((player, index) => { + gameState?.Players && + gameState.Players.map((player) => { const isCurrentPlayer = player === gameState.CurrentPlayer; const isMe = player === gameState.PlayerName; - return
  • + return
  • {player} {isMe && '(You)'}
  • }) diff --git a/src/layout/pages/Room.tsx b/src/layout/pages/Room.tsx index 6c9f3cd..7ccb713 100644 --- a/src/layout/pages/Room.tsx +++ b/src/layout/pages/Room.tsx @@ -3,15 +3,7 @@ import React from "react"; import {useNavigate, useParams} from "react-router"; import {GHButton} from "../components/Button"; import useTitle from "../../utils/hooks/TitleHook"; -import Game from "../components/Game"; - -interface GameState { - PlayerName: string; - Hand: string[]; - CurrentCard: string; - CurrentPlayer: string; - Players: string[]; -} +import Game, {GameState} from "../components/Game"; interface ChatMessage { PlayerName: string; @@ -35,6 +27,7 @@ const Room = () => { const [gameState, setGameState] = React.useState({ PlayerName: '', + CurrentState: '', Hand: [], CurrentCard: '', CurrentPlayer: '', @@ -78,6 +71,16 @@ const Room = () => { }) } + const handleChoice = (choice: string) => { + handleSend({ + Type: "GAME", + Payload: JSON.stringify({ + Action: "CHOOSE", + Data: choice + }) + }); + } + const handleDraw = () => { handleSend({ Type: "GAME", @@ -99,7 +102,7 @@ const Room = () => {

    Room {roomId}

    Leave Room - + setChatInput(e.target.value)} />
      -- 2.49.1 From e78d0d46f7e1125815c6c6613e970285ecbcfca4 Mon Sep 17 00:00:00 2001 From: DTieman Date: Fri, 19 Apr 2024 23:28:29 +0200 Subject: [PATCH 2/7] Slightly better chat (send with enter) --- src/layout/components/Button.tsx | 13 +++++-------- src/layout/pages/Room.tsx | 14 +++++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/layout/components/Button.tsx b/src/layout/components/Button.tsx index f8fc6e8..c5e3529 100644 --- a/src/layout/components/Button.tsx +++ b/src/layout/components/Button.tsx @@ -1,13 +1,10 @@ -import React, {FunctionComponent} from "react"; +import React, {ButtonHTMLAttributes, DetailedHTMLProps, FunctionComponent} from "react"; -interface Props { - className?: string - onClick?: () => void - children?: React.ReactNode -} +export const GHButton: FunctionComponent, HTMLButtonElement>> = (props) => { + + const {className, ...rest} = props; -export const GHButton: FunctionComponent = ({className, onClick, children}) => { return ( - {children} + ); } \ No newline at end of file diff --git a/src/layout/pages/Room.tsx b/src/layout/pages/Room.tsx index 7ccb713..920434c 100644 --- a/src/layout/pages/Room.tsx +++ b/src/layout/pages/Room.tsx @@ -1,5 +1,5 @@ import useWebSocket from "react-use-websocket"; -import React from "react"; +import React, {FormEvent} from "react"; import {useNavigate, useParams} from "react-router"; import {GHButton} from "../components/Button"; import useTitle from "../../utils/hooks/TitleHook"; @@ -91,11 +91,13 @@ const Room = () => { }); } - const handleChat = (message: string) => { + const handleChat = (e: FormEvent) => { + e.preventDefault(); handleSend({ Type: "CHAT", - Payload: message + Payload: chatInput }); + setChatInput(''); } return ( @@ -103,8 +105,10 @@ const Room = () => {

      Room {roomId}

      Leave Room - setChatInput(e.target.value)} /> - +
      + setChatInput(e.target.value)} /> + Send +
        {chatMessages.map((message, index) => (
      • {message.PlayerName}: {message.Message}
      • -- 2.49.1 From d547db430c92bc4365069e4a8af3dadb224032ea Mon Sep 17 00:00:00 2001 From: DTieman Date: Sat, 20 Apr 2024 00:23:18 +0200 Subject: [PATCH 3/7] Better layout --- src/layout/components/Button.tsx | 15 ++++++++++++++- src/layout/components/Deck.tsx | 2 +- src/layout/pages/MainLobby.tsx | 9 +++++---- src/layout/pages/Rooms.tsx | 4 ++-- src/styles/App.scss | 18 +++++++++++++++--- src/styles/layout/components/button.scss | 7 +++++++ src/styles/layout/pages/mainlobby.scss | 1 - src/styles/utils/pointer.scss | 7 ++++++- 8 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/layout/components/Button.tsx b/src/layout/components/Button.tsx index c5e3529..370533f 100644 --- a/src/layout/components/Button.tsx +++ b/src/layout/components/Button.tsx @@ -1,10 +1,23 @@ import React, {ButtonHTMLAttributes, DetailedHTMLProps, FunctionComponent} from "react"; -export const GHButton: FunctionComponent, HTMLButtonElement>> = (props) => { +interface ButtonProps extends DetailedHTMLProps, HTMLButtonElement> { + +} + +export const GHButton: FunctionComponent = (props) => { const {className, ...rest} = props; return ( ); +} + +export const NoButton: FunctionComponent = (props) => { + + const {className, ...rest} = props; + + return ( + + ); } \ No newline at end of file diff --git a/src/layout/components/Deck.tsx b/src/layout/components/Deck.tsx index c43221e..a7419ac 100644 --- a/src/layout/components/Deck.tsx +++ b/src/layout/components/Deck.tsx @@ -20,7 +20,7 @@ const Deck: FunctionComponent = ({currentCard, actionOnClick}) => {

    - +
); diff --git a/src/layout/pages/MainLobby.tsx b/src/layout/pages/MainLobby.tsx index 37e8454..5dad41e 100644 --- a/src/layout/pages/MainLobby.tsx +++ b/src/layout/pages/MainLobby.tsx @@ -2,6 +2,7 @@ import React from "react"; import useTitle from "../../utils/hooks/TitleHook"; import Card from "../components/Card"; import {useNavigate} from "react-router"; +import {NoButton} from "../components/Button"; const ROOM_URL = `${process.env.REACT_APP_API_URL}/room`; @@ -23,14 +24,14 @@ const MainLobby = () => {

Mau-Mau

-
+

Host Game

-
-
navigateTo('/rooms')}> + + navigateTo('/rooms')}>

Join Game

-
+
); diff --git a/src/layout/pages/Rooms.tsx b/src/layout/pages/Rooms.tsx index 394a661..b5bdbc3 100644 --- a/src/layout/pages/Rooms.tsx +++ b/src/layout/pages/Rooms.tsx @@ -19,11 +19,11 @@ const Rooms = () => { return (
-

Rooms

+

Rooms

    { rooms.map((room, index) => { - return
  • navigateTo(`/room/${room}`)}>{room}
  • }) } diff --git a/src/styles/App.scss b/src/styles/App.scss index 9bd1017..2a810b9 100644 --- a/src/styles/App.scss +++ b/src/styles/App.scss @@ -7,9 +7,21 @@ src: local('Mau'), url(../assets/fonts/OrientalCatsLight.otf) format('opentype'); } -.app { - width: 100vw; - height: 100vh; +* { + box-sizing: border-box; +} + +body { + margin: 0; + height: 100dvh; + width: 100dvw; + overflow: hidden; +} + +#root, .app { + @extend .dark; + padding: 1rem; + height: 100%; } .mau { diff --git a/src/styles/layout/components/button.scss b/src/styles/layout/components/button.scss index d60cc65..d3fc8c8 100644 --- a/src/styles/layout/components/button.scss +++ b/src/styles/layout/components/button.scss @@ -53,4 +53,11 @@ ::-webkit-details-marker { display: none; } +} + +.no-button { + background-color: transparent; + border: none; + color: inherit; + cursor: pointer; } \ No newline at end of file diff --git a/src/styles/layout/pages/mainlobby.scss b/src/styles/layout/pages/mainlobby.scss index 4effd35..1a3cff9 100644 --- a/src/styles/layout/pages/mainlobby.scss +++ b/src/styles/layout/pages/mainlobby.scss @@ -1,5 +1,4 @@ .main-lobby { - width: 100vw; height: 75vh; display: flex; flex-direction: column; diff --git a/src/styles/utils/pointer.scss b/src/styles/utils/pointer.scss index 57cca58..0a90b81 100644 --- a/src/styles/utils/pointer.scss +++ b/src/styles/utils/pointer.scss @@ -1,4 +1,9 @@ -.clickable { +.mouse-pointer { cursor: pointer; user-select: none; +} + +.mouse-default { + cursor: default; + user-select: none; } \ No newline at end of file -- 2.49.1 From d907a564833d128193f1d472057ef21aedbf1f9a Mon Sep 17 00:00:00 2001 From: DTieman Date: Sat, 20 Apr 2024 23:44:23 +0200 Subject: [PATCH 4/7] Custom names and time in chat --- src/layout/components/Game.tsx | 17 +++++++++-------- src/layout/pages/MainLobby.tsx | 11 +++++++++++ src/layout/pages/Room.tsx | 25 ++++++++++++++++++------- src/styles/layout/pages/mainlobby.scss | 8 +++++++- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/layout/components/Game.tsx b/src/layout/components/Game.tsx index 50bc1c2..cde4e88 100644 --- a/src/layout/components/Game.tsx +++ b/src/layout/components/Game.tsx @@ -2,14 +2,15 @@ import React, {FunctionComponent} from "react"; import Deck from "./Deck"; import Hand from "./Hand"; import {GHButton} from "./Button"; +import {Player} from "../pages/Room"; export interface GameState { - PlayerName: string; + Me: Player; CurrentState: string; Hand: string[]; CurrentCard: string; - CurrentPlayer: string; - Players: string[]; + CurrentPlayer: Player; + Players: Player[]; } interface Props { @@ -38,12 +39,12 @@ const Game: FunctionComponent = ({gameState, handleCardSend, handleDraw, }
      { - gameState?.Players && + gameState?.Me?.Id && gameState.Players.map((player) => { - const isCurrentPlayer = player === gameState.CurrentPlayer; - const isMe = player === gameState.PlayerName; - return
    • - {player} {isMe && '(You)'} + const isCurrentPlayer = player.Id === gameState.CurrentPlayer.Id; + const isMe = player.Id === gameState.Me.Id; + return
    • + {player.Name} {isMe && '(You)'}
    • }) } diff --git a/src/layout/pages/MainLobby.tsx b/src/layout/pages/MainLobby.tsx index 5dad41e..006e532 100644 --- a/src/layout/pages/MainLobby.tsx +++ b/src/layout/pages/MainLobby.tsx @@ -11,6 +11,7 @@ const MainLobby = () => { useTitle('Mau-Mau Lobby'); const navigateTo = useNavigate(); + const playerName = localStorage.getItem('playerName') ?? ""; const handleCreateRoom = () => { fetch(ROOM_URL, { @@ -20,9 +21,19 @@ const MainLobby = () => { }); } + const changePlayerName = (name: string) => { + if (!name) return; + localStorage.setItem('playerName', name); + } + return (

      Mau-Mau

      +
      + + changePlayerName(e.target.value)}/> +

      Host Game

      diff --git a/src/layout/pages/Room.tsx b/src/layout/pages/Room.tsx index 920434c..b20875e 100644 --- a/src/layout/pages/Room.tsx +++ b/src/layout/pages/Room.tsx @@ -6,10 +6,16 @@ import useTitle from "../../utils/hooks/TitleHook"; import Game, {GameState} from "../components/Game"; interface ChatMessage { - PlayerName: string; + Time: Date; + Sender: string; Message: string; } +export interface Player { + Name: string; + Id: string; +} + interface SocketMessage { Type: string; Payload: any; @@ -23,14 +29,15 @@ const Room = () => { const {roomId} = useParams(); - const WS_URL = `${process.env.REACT_APP_WEBSOCKET_URL}/room/${roomId}`; + const playerName = localStorage.getItem('playerName') || Math.random().toString(36).substring(7); + const WS_URL = `${process.env.REACT_APP_WEBSOCKET_URL}/room/${roomId}/${playerName}`; const [gameState, setGameState] = React.useState({ - PlayerName: '', + Me: {Name: playerName, Id: ''}, CurrentState: '', Hand: [], CurrentCard: '', - CurrentPlayer: '', + CurrentPlayer: {Name: "", Id: ''}, Players: [] }); const [chatMessages, setChatMessages] = React.useState([]); @@ -44,7 +51,11 @@ const Room = () => { const data = JSON.parse(event.data); const payload = JSON.parse(data.Payload); if (data.Type === 'GAME') setGameState(payload); - if (data.Type === 'CHAT') setChatMessages(prev => [...prev, payload]); + if (data.Type === 'CHAT') setChatMessages(prev => [...prev, { + Time: new Date(payload.Time), + Sender: payload.Sender, + Message: payload.Message + }]); } }); @@ -110,8 +121,8 @@ const Room = () => { Send
        - {chatMessages.map((message, index) => ( -
      • {message.PlayerName}: {message.Message}
      • + {chatMessages.map((message) => ( +
      • ({message.Time.toLocaleTimeString()}) {message.Sender}: {message.Message}
      • ))}
      diff --git a/src/styles/layout/pages/mainlobby.scss b/src/styles/layout/pages/mainlobby.scss index 1a3cff9..3688271 100644 --- a/src/styles/layout/pages/mainlobby.scss +++ b/src/styles/layout/pages/mainlobby.scss @@ -5,6 +5,12 @@ justify-content: space-evenly; align-items: center; + &__name { + display: flex; + flex-direction: column; + font-size: 1.5rem; + } + &__container { display: flex; gap: 2rem; @@ -18,6 +24,6 @@ } &__title { - font-size: 5rem; + font-size: 7.5rem; } } \ No newline at end of file -- 2.49.1 From 4959e197bb632d9701b4345decb4146a77baf93c Mon Sep 17 00:00:00 2001 From: DTieman Date: Sun, 21 Apr 2024 16:28:55 +0200 Subject: [PATCH 5/7] Mostly design update + - Chat cleaning - Player hand size visibility --- src/layout/components/Chat.tsx | 31 ++++ src/layout/components/Game.tsx | 58 +++++--- src/layout/components/Lobby.tsx | 9 ++ src/layout/pages/Room.tsx | 133 +++++++++--------- src/styles/App.scss | 3 +- src/styles/layout/components/_components.scss | 2 + src/styles/layout/components/chat.scss | 28 ++++ src/styles/layout/components/game.scss | 25 ++++ src/styles/layout/components/hand.scss | 5 +- src/styles/layout/pages/_pages.scss | 3 +- src/styles/layout/pages/room.scss | 12 ++ src/styles/utils/_utils.scss | 3 +- src/styles/utils/theme.scss | 8 -- 13 files changed, 222 insertions(+), 98 deletions(-) create mode 100644 src/layout/components/Chat.tsx create mode 100644 src/layout/components/Lobby.tsx create mode 100644 src/styles/layout/components/chat.scss create mode 100644 src/styles/layout/components/game.scss create mode 100644 src/styles/layout/pages/room.scss delete mode 100644 src/styles/utils/theme.scss diff --git a/src/layout/components/Chat.tsx b/src/layout/components/Chat.tsx new file mode 100644 index 0000000..bc5182c --- /dev/null +++ b/src/layout/components/Chat.tsx @@ -0,0 +1,31 @@ +import {GHButton} from "./Button"; +import React, {FormEvent, FunctionComponent, RefObject} from "react"; + +interface Props{ + chatRef: RefObject; + handleSend: (message: string) => void; +} + +const Chat: FunctionComponent = ({chatRef, handleSend}) => { + + const handleChat = (form: FormEvent) => { + form.preventDefault(); + const data = new FormData(form.currentTarget); + const chatInput = data.get("chat-input") as string; + if(!chatInput) return; + handleSend(chatInput); + form.currentTarget.reset(); + } + + return ( + <> +
        +
        + + Send +
        + + ) +} + +export default Chat; \ No newline at end of file diff --git a/src/layout/components/Game.tsx b/src/layout/components/Game.tsx index cde4e88..206dcb1 100644 --- a/src/layout/components/Game.tsx +++ b/src/layout/components/Game.tsx @@ -2,7 +2,7 @@ import React, {FunctionComponent} from "react"; import Deck from "./Deck"; import Hand from "./Hand"; import {GHButton} from "./Button"; -import {Player} from "../pages/Room"; +import {GameAction, Player} from "../pages/Room"; export interface GameState { Me: Player; @@ -15,16 +15,52 @@ export interface GameState { interface Props { gameState: GameState - handleCardSend: (cardString: string) => void; - handleDraw: () => void; - handleChoice: (choice: string) => void; + handleGameAction: (action: GameAction) => void; } const CHOICES = ['SPADES', 'HEARTS', 'DIAMONDS', 'CLUBS']; -const Game: FunctionComponent = ({gameState, handleCardSend, handleDraw, handleChoice}) => { +const Game: FunctionComponent = ({gameState, handleGameAction}) => { + + const handleChoice = (choice: string) => { + handleGameAction({Action: 'CHOOSE', Data: choice}); + } + + const handleDraw = () => { + handleGameAction({Action: 'DRAW', Data: ""}); + } + + const handleCardSend = (cardString: string) => { + handleGameAction({Action: "PLAYCARD", Data: JSON.stringify({ + CardType: cardString.split(' ')[0], + CardValue: cardString.split(' ')[1] + })}) + } + return (
        +
          + { + gameState?.Me?.Id && + gameState.Players.map((player) => { + const isCurrentPlayer = player.Id === gameState.CurrentPlayer.Id; + const isMe = player.Id === gameState.Me.Id; + + const style = + { + fontWeight: isMe ? 'bold' : 'normal', + outline: isCurrentPlayer ? '2px solid red' : 'none' + } + + return
        1. +
          +

          {player.Name} {isMe && '(You)'}

          +

          {player.CardsLeft} left

          +
          +
        2. + }) + } +
        { gameState.CurrentCard && @@ -37,18 +73,6 @@ const Game: FunctionComponent = ({gameState, handleCardSend, handleDraw, gameState.CurrentState === 'CHOOSE' && CHOICES.map(choice => handleChoice(choice)}>{choice}) } -
          - { - gameState?.Me?.Id && - gameState.Players.map((player) => { - const isCurrentPlayer = player.Id === gameState.CurrentPlayer.Id; - const isMe = player.Id === gameState.Me.Id; - return
        • - {player.Name} {isMe && '(You)'} -
        • - }) - } -
        ) } diff --git a/src/layout/components/Lobby.tsx b/src/layout/components/Lobby.tsx new file mode 100644 index 0000000..e35cb13 --- /dev/null +++ b/src/layout/components/Lobby.tsx @@ -0,0 +1,9 @@ +const Lobby = () => { + return ( +
        +

        Lobby

        +
        + ); +} + +export default Lobby; diff --git a/src/layout/pages/Room.tsx b/src/layout/pages/Room.tsx index b20875e..bb72bdc 100644 --- a/src/layout/pages/Room.tsx +++ b/src/layout/pages/Room.tsx @@ -1,19 +1,16 @@ import useWebSocket from "react-use-websocket"; -import React, {FormEvent} from "react"; +import React, {useEffect} from "react"; import {useNavigate, useParams} from "react-router"; import {GHButton} from "../components/Button"; import useTitle from "../../utils/hooks/TitleHook"; import Game, {GameState} from "../components/Game"; - -interface ChatMessage { - Time: Date; - Sender: string; - Message: string; -} +import Lobby from "../components/Lobby"; +import Chat from "../components/Chat"; export interface Player { Name: string; Id: string; + CardsLeft: number; } interface SocketMessage { @@ -21,6 +18,11 @@ interface SocketMessage { Payload: any; } +export interface GameAction { + Action: string; + Data: string; +} + const Room = () => { useTitle('Mau!'); @@ -33,15 +35,31 @@ const Room = () => { const WS_URL = `${process.env.REACT_APP_WEBSOCKET_URL}/room/${roomId}/${playerName}`; const [gameState, setGameState] = React.useState({ - Me: {Name: playerName, Id: ''}, + Me: {Name: playerName, Id: '', CardsLeft: 0}, CurrentState: '', Hand: [], CurrentCard: '', - CurrentPlayer: {Name: "", Id: ''}, + CurrentPlayer: {Name: "", Id: '', CardsLeft: 0}, Players: [] }); - const [chatMessages, setChatMessages] = React.useState([]); - const [chatInput, setChatInput] = React.useState(''); + + const [chatScroll, setChatScroll] = React.useState(true); + const chatRef = React.useRef(null); + + const addChatMessage = (message: {Sender: string, Message: string}) => { + if(!chatRef.current) return; + + const { scrollTop, scrollHeight, clientHeight } = chatRef.current; + + const newChatElement = document.createElement('li'); + newChatElement.innerHTML = `${message.Sender}: ${message.Message}`; + chatRef.current.appendChild(newChatElement); + + const isNearBottom = scrollTop + clientHeight + newChatElement.scrollHeight * 2 >= scrollHeight; + if (chatScroll && isNearBottom) { + newChatElement.scrollIntoView({behavior: 'smooth'}); + } + } const websocket = useWebSocket(WS_URL, { onOpen: () => { @@ -51,17 +69,11 @@ const Room = () => { const data = JSON.parse(event.data); const payload = JSON.parse(data.Payload); if (data.Type === 'GAME') setGameState(payload); - if (data.Type === 'CHAT') setChatMessages(prev => [...prev, { - Time: new Date(payload.Time), - Sender: payload.Sender, - Message: payload.Message - }]); + if (data.Type === 'CHAT') addChatMessage({Sender: payload.Sender, Message: payload.Message}) } }); const handleLeaveRoom = () => { - const socket = websocket.getWebSocket(); - if (socket) socket.close(); navigateTo('/'); } @@ -69,62 +81,51 @@ const Room = () => { websocket.sendMessage(JSON.stringify(message)); } - const handleCardSend = (card: string) => { - handleSend({ - Type: "GAME", - Payload: JSON.stringify({ - Action: "PLAYCARD", - Data: JSON.stringify({ - CardType: card.split(' ')[0], - CardValue: card.split(' ')[1] - }) - }) - }) + const handleGameAction = (action: GameAction) => { + handleSend({Type: "GAME", Payload: JSON.stringify(action)}); } - const handleChoice = (choice: string) => { - handleSend({ - Type: "GAME", - Payload: JSON.stringify({ - Action: "CHOOSE", - Data: choice - }) - }); - } - - const handleDraw = () => { - handleSend({ - Type: "GAME", - Payload: JSON.stringify({ - Action: "DRAW", - Data: "" - }) - }); - } - - const handleChat = (e: FormEvent) => { - e.preventDefault(); + const handleChatMessage = (chatMessage: string) => { handleSend({ Type: "CHAT", - Payload: chatInput + Payload: chatMessage }); - setChatInput(''); } + const isLobby = false; + const isGame = true; + + useEffect(() => { + return () => { + const socket = websocket.getWebSocket(); + if (socket) socket.close(); + } + // this is a cleanup function and should not include any dependencies that would cause it to run more than once + // eslint-disable-next-line + }, []); + return ( -
        -

        Room {roomId}

        - Leave Room - -
        - setChatInput(e.target.value)} /> - Send -
        -
          - {chatMessages.map((message) => ( -
        • ({message.Time.toLocaleTimeString()}) {message.Sender}: {message.Message}
        • - ))} -
        +
        + +
        + { + isLobby && + + } + { + isGame && + + } +
        ) } diff --git a/src/styles/App.scss b/src/styles/App.scss index 2a810b9..91b4b2e 100644 --- a/src/styles/App.scss +++ b/src/styles/App.scss @@ -19,7 +19,8 @@ body { } #root, .app { - @extend .dark; + background-color: #131313; + color: #ffffff; padding: 1rem; height: 100%; } diff --git a/src/styles/layout/components/_components.scss b/src/styles/layout/components/_components.scss index b053990..55c56f4 100644 --- a/src/styles/layout/components/_components.scss +++ b/src/styles/layout/components/_components.scss @@ -1,4 +1,6 @@ @import "button"; @import "card"; +@import "chat"; @import "hand"; +@import "game"; @import "deck"; \ No newline at end of file diff --git a/src/styles/layout/components/chat.scss b/src/styles/layout/components/chat.scss new file mode 100644 index 0000000..732b436 --- /dev/null +++ b/src/styles/layout/components/chat.scss @@ -0,0 +1,28 @@ +.chat { + + &__list { + overflow-y: hidden; + word-wrap: break-word; + list-style-type: none; + padding: 0; + margin: 1rem 0; + + &:hover { + overflow-y: scroll; + } + } + + &-form { + display: flex; + flex-direction: column; + + &__input { + background-color: #2e2e2e; + border: none; + color: #fff; + font-size: 1rem; + padding: 0.5rem; + border-radius: 0.5rem; + } + } +} \ No newline at end of file diff --git a/src/styles/layout/components/game.scss b/src/styles/layout/components/game.scss new file mode 100644 index 0000000..3737ee5 --- /dev/null +++ b/src/styles/layout/components/game.scss @@ -0,0 +1,25 @@ +.game { + height: 100%; + display: grid; + grid-template-rows: 1fr 1fr 1fr; + place-items: center; + + &__players { + list-style-type: none; + padding: 0; + margin: 0; + display: flex; + justify-content: space-around; + flex-direction: row; + width: 100%; + + &__info { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + } + } +} \ No newline at end of file diff --git a/src/styles/layout/components/hand.scss b/src/styles/layout/components/hand.scss index 6eed4cf..1651805 100644 --- a/src/styles/layout/components/hand.scss +++ b/src/styles/layout/components/hand.scss @@ -1,8 +1,7 @@ .hand { display: flex; - flex-direction: row; - //justify-content: center; - flex-flow: nowrap !important; + max-width: 100%; + flex-flow: row nowrap !important; overflow-x: scroll; padding: 0.5rem; gap: 0.5rem; diff --git a/src/styles/layout/pages/_pages.scss b/src/styles/layout/pages/_pages.scss index 00335b4..ebfd0f7 100644 --- a/src/styles/layout/pages/_pages.scss +++ b/src/styles/layout/pages/_pages.scss @@ -1 +1,2 @@ -@import "mainlobby"; \ No newline at end of file +@import "mainlobby"; +@import "room"; \ No newline at end of file diff --git a/src/styles/layout/pages/room.scss b/src/styles/layout/pages/room.scss new file mode 100644 index 0000000..5c11822 --- /dev/null +++ b/src/styles/layout/pages/room.scss @@ -0,0 +1,12 @@ +.room { + display: grid; + grid-template-columns: auto 1fr; + height: 100%; + + &-aside { + display: grid; + min-width: 20%; + grid-template-rows: auto minmax(0, 1fr) auto; + overflow: hidden auto; + } +} \ No newline at end of file diff --git a/src/styles/utils/_utils.scss b/src/styles/utils/_utils.scss index 647d032..04972e8 100644 --- a/src/styles/utils/_utils.scss +++ b/src/styles/utils/_utils.scss @@ -1,2 +1 @@ -@import "pointer"; -@import "theme"; \ No newline at end of file +@import "pointer"; \ No newline at end of file diff --git a/src/styles/utils/theme.scss b/src/styles/utils/theme.scss deleted file mode 100644 index 8895a7e..0000000 --- a/src/styles/utils/theme.scss +++ /dev/null @@ -1,8 +0,0 @@ -.dark { - background-color: #333; - color: #fff; -} -.light { - background-color: #fff; - color: #333; -} \ No newline at end of file -- 2.49.1 From d5df884ae80790dd5b71ebc2c84386411ef3c0f7 Mon Sep 17 00:00:00 2001 From: DTieman Date: Mon, 22 Apr 2024 21:52:12 +0200 Subject: [PATCH 6/7] Better lobby system --- src/layout/components/Lobby.tsx | 22 ++++++++++++++- src/layout/pages/Room.tsx | 48 ++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/layout/components/Lobby.tsx b/src/layout/components/Lobby.tsx index e35cb13..3dfd788 100644 --- a/src/layout/components/Lobby.tsx +++ b/src/layout/components/Lobby.tsx @@ -1,7 +1,27 @@ -const Lobby = () => { +import {FunctionComponent} from "react"; +import {GHButton} from "./Button"; + +interface Props { + winner: string | undefined; + handleLobbyAction: (action: string) => void; +} + +const Lobby: FunctionComponent = ({winner, handleLobbyAction}) => { + + const onStartClick = () => { + console.log('Start Game'); + handleLobbyAction("START"); + } + + // @ts-ignore return (

        Lobby

        + { + winner && +

        {winner} has won the game!

        + } + Start Game
        ); } diff --git a/src/layout/pages/Room.tsx b/src/layout/pages/Room.tsx index bb72bdc..3806e83 100644 --- a/src/layout/pages/Room.tsx +++ b/src/layout/pages/Room.tsx @@ -34,14 +34,9 @@ const Room = () => { const playerName = localStorage.getItem('playerName') || Math.random().toString(36).substring(7); const WS_URL = `${process.env.REACT_APP_WEBSOCKET_URL}/room/${roomId}/${playerName}`; - const [gameState, setGameState] = React.useState({ - Me: {Name: playerName, Id: '', CardsLeft: 0}, - CurrentState: '', - Hand: [], - CurrentCard: '', - CurrentPlayer: {Name: "", Id: '', CardsLeft: 0}, - Players: [] - }); + const [gameState, setGameState] = React.useState(undefined); + const [lobbyState, setLobbyState] = React.useState(""); + const [winner, setWinner] = React.useState(undefined); const [chatScroll, setChatScroll] = React.useState(true); const chatRef = React.useRef(null); @@ -68,8 +63,24 @@ const Room = () => { onMessage: (event) => { const data = JSON.parse(event.data); const payload = JSON.parse(data.Payload); - if (data.Type === 'GAME') setGameState(payload); - if (data.Type === 'CHAT') addChatMessage({Sender: payload.Sender, Message: payload.Message}) + switch (data.Type) { + case 'LOBBY': + setLobbyState(data.Type); + break; + case 'GAME': + setLobbyState(data.Type); + setGameState(payload); + break; + case 'CHAT': + addChatMessage(payload); + break; + case "END": + setWinner(payload.Name) + setGameState(undefined); + break; + default: + console.log('Unknown message type:', data.Type); + } } }); @@ -85,6 +96,10 @@ const Room = () => { handleSend({Type: "GAME", Payload: JSON.stringify(action)}); } + const handleLobbyAction = (action: string) => { + handleSend({Type: "LOBBY", Payload: JSON.stringify(action)}); + } + const handleChatMessage = (chatMessage: string) => { handleSend({ Type: "CHAT", @@ -92,9 +107,6 @@ const Room = () => { }); } - const isLobby = false; - const isGame = true; - useEffect(() => { return () => { const socket = websocket.getWebSocket(); @@ -118,12 +130,10 @@ const Room = () => {
        { - isLobby && - - } - { - isGame && - + gameState ? + + : + }
        -- 2.49.1 From b738263726d97fb10d39c60e1d33538909fb79ee Mon Sep 17 00:00:00 2001 From: DTieman Date: Mon, 22 Apr 2024 22:52:24 +0200 Subject: [PATCH 7/7] Fixed eslint --- src/layout/pages/Room.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/layout/pages/Room.tsx b/src/layout/pages/Room.tsx index 3806e83..7b4a7f3 100644 --- a/src/layout/pages/Room.tsx +++ b/src/layout/pages/Room.tsx @@ -31,27 +31,26 @@ const Room = () => { const {roomId} = useParams(); - const playerName = localStorage.getItem('playerName') || Math.random().toString(36).substring(7); + const playerName = localStorage.getItem('playerName') ?? Math.random().toString(36).substring(7); const WS_URL = `${process.env.REACT_APP_WEBSOCKET_URL}/room/${roomId}/${playerName}`; const [gameState, setGameState] = React.useState(undefined); const [lobbyState, setLobbyState] = React.useState(""); const [winner, setWinner] = React.useState(undefined); - const [chatScroll, setChatScroll] = React.useState(true); const chatRef = React.useRef(null); - const addChatMessage = (message: {Sender: string, Message: string}) => { - if(!chatRef.current) return; + const addChatMessage = (message: { Sender: string, Message: string }) => { + if (!chatRef.current) return; - const { scrollTop, scrollHeight, clientHeight } = chatRef.current; + const {scrollTop, scrollHeight, clientHeight} = chatRef.current; const newChatElement = document.createElement('li'); newChatElement.innerHTML = `${message.Sender}: ${message.Message}`; chatRef.current.appendChild(newChatElement); const isNearBottom = scrollTop + clientHeight + newChatElement.scrollHeight * 2 >= scrollHeight; - if (chatScroll && isNearBottom) { + if (isNearBottom) { newChatElement.scrollIntoView({behavior: 'smooth'}); } } @@ -132,7 +131,7 @@ const Room = () => { { gameState ? - : + : lobbyState === 'LOBBY' && } -- 2.49.1