McCarthyism/front/pages/index.tsx
2021-05-09 17:27:19 -05:00

365 lines
10 KiB
TypeScript

import {Card, Suit} from 'bsx-core';
import {useEffect, useState} from 'react';
import io from 'socket.io-client';
import CreateRoomForm from '../components/CreateRoomForm';
import JoinRoomForm from '../components/JoinRoomForm';
import LoginForm from '../components/LoginForm';
interface GameState {
cards: Card[],
players: {username: string, numCards: number, stackSize: number, flipped: Card[], rank: number}[],
lastPlayed: number,
lastPlayedPlayer: string | null,
playerTurn: string
phase: number
}
const rankStrs = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
const suitChars = ['♣', '♦', '♥', '♠'];
const rules = `There are only 5 simple rules!
1. Each player will first be dealt the same number of cards.
2. At the beginning of each round, you must rearrange the order of your cards and place them face down in a stack.
3. During the round, players go around in a circle, claiming increasingly greater numbers. If it is your turn to claim a number, you must either claim a larger number than the previously claimed number or call BS. If you call BS, the previous player must flip over their claimed number of black (clubs or spades) cards from the tops of everyone's stacks.
4. If the previous player manages to flip over their claimed number of black cards, you must choose a card from your stack to give up. Otherwise, the previous player must choose one of their cards to give up.
5. If you give up all your cards, you lose! Last player remaining wins!
`;
function useForceUpdate(){
const [value, setValue] = useState(0);
return () => setValue(value => value + 1);
}
export default function Game() {
const forceUpdate = useForceUpdate();
const [socket, setSocket] = useState<SocketIOClient.Socket | null>(null);
const [connected, setConnected] = useState(false);
const [loggedIn, setLoggedIn] = useState(false);
const [username, setUsername] = useState<string | null>(null);
const [room, setRoom] = useState<string | null>(null);
const [roomUsers, setRoomUsers] = useState<string[]>([]);
const [roomHost, setRoomHost] = useState('');
const [gameState, setGameState] = useState<GameState | null>(null);
const [num, setNum] = useState(0);
useEffect(() => {
const socket = io(process.env.NEXT_PUBLIC_BACK_HOST!);
setSocket(socket);
socket.on('connect', () => setConnected(true));
socket.on('disconnect', () => {
setConnected(false);
setLoggedIn(false);
setRoom(null);
setGameState(null);
});
socket.on('joinRoom', (data: {name: string}) => setRoom(data.name));
socket.on('leaveRoom', () => {
setRoom(null);
setGameState(null);
});
socket.on('roomUpdate', (data: {users: string[], host: string}) => {
setRoomUsers(data.users);
setRoomHost(data.host);
});
socket.on('gameState', (data: GameState) => {
setGameState(data);
});
socket.on('endGame', () => setGameState(null));
return () => {socket.close()};
}, []);
if (!socket) return null;
if (!loggedIn) {
return (
<>
<h2>
Welcome to BSX!
</h2>
<div>
{rules}
</div>
<LoginForm
socket={socket}
finish={(s) => {
setLoggedIn(true);
setUsername(s);
}}
username={username}
/>
</>
);
}
if (!room) {
return (
<>
<p>Logged in as {username}</p>
<hr />
<JoinRoomForm socket={socket} />
<hr />
<CreateRoomForm socket={socket} />
</>
);
}
if (!gameState) {
return (
<>
<p>Room {room}</p>
<p>Users:</p>
<ul>
{roomUsers.map(user => <li key={user}>{user + (user === roomHost ? ' (Host)':'')}</li>)}
</ul>
<button onClick={() => socket.emit('leaveRoom')}>Leave</button>
{username === roomHost &&
<button
onClick={() => socket.emit('startGame')}
disabled={roomUsers.length < 2}
>
Start
</button>
}
</>
);
}
if (gameState.phase === 0) {
return (
<>
<button
onClick={() => {
document.getElementById('Rules')!.style.display = (document.getElementById('Rules')!.style.display === 'none' ? 'block' : 'none');
}}
>
Show/hide rules
</button>
<div id='Rules' style={{display: 'none'}}>
{rules}
</div>
<ul>
{gameState.players.map(p => (
<li key={p.username}>
{p.username + (p.rank ? ` (Rank ${p.rank})` : '') + (p.numCards ? ` (${p.numCards} cards)` : '')}
</li>
))}
</ul>
<div>
{gameState.playerTurn+` will go first this round!`}
</div>
{`Rearrange your card stack from top to bottom!`}
<div>
<p>Your cards stack:</p>
{gameState.cards.map((card, i) => (
<label key={card.rank+' '+card.suit}>
<div>
<button
onClick={() => {
const tmp = gameState.cards[i];
gameState.cards[i] = gameState.cards[i-1];
gameState.cards[i-1] = tmp;
forceUpdate();
}}
disabled={i === 0}
>
Move up
</button>
<span style={{color: (card.suit === Suit.Hearts || card.suit === Suit.Diamonds ? 'red' : 'black')}}>
{' '+rankStrs[card.rank]+' '+suitChars[card.suit]}
</span>
</div>
</label>
))}
<button
onClick={() => socket.emit('prepare', gameState.cards)}
//disabled={username !== gameState.playerTurn || !canPlay(gameState.lastPlayed, selectedCards)}
>
I'm ready!
</button>
</div>
</>
);
}
if (gameState.phase === 1) {
return (
<>
<button
onClick={() => {
document.getElementById('Rules')!.style.display = (document.getElementById('Rules')!.style.display === 'none' ? 'block' : 'none');
}}
>
Show/hide rules
</button>
<div id='Rules' style={{display: 'none'}}>
{rules}
</div>
<ul>
{gameState.players.map(p => (
<li key={p.username}>
{p.username + (p.rank ? ` (Rank ${p.rank})` : '') + (p.numCards ? ` (${p.numCards} cards)` : '')}
</li>
))}
</ul>
<div>
<p>Last played:</p>
{gameState.lastPlayed ? (
<>
{gameState.lastPlayed}
{` by ${gameState.lastPlayedPlayer}`}
</>
) : '(Nothing)'}
</div>
{`It's ${gameState.playerTurn}'s turn!`}
<div>
<input
type="text"
placeholder="Claim a number greater than the last claimed number..."
value={num}
onChange={(e) => setNum(+e.target.value)}
/>
<button
onClick={() => socket.emit('turn', num)}
disabled={username !== gameState.playerTurn || num <= gameState.lastPlayed}
>
Claim!
</button>
<button
onClick={() => socket.emit('turn', -1)}
disabled={username !== gameState.playerTurn || gameState.lastPlayed === 0}
>
BS!
</button>
</div>
</>
);
}
if (gameState.phase === 2) {
return (
<>
<button
onClick={() => {
document.getElementById('Rules')!.style.display = (document.getElementById('Rules')!.style.display === 'none' ? 'block' : 'none');
}}
>
Show/hide rules
</button>
<div id='Rules' style={{display: 'none'}}>
{rules}
</div>
<ul>
{gameState.players.map(p => (
<li key={p.username}>
{p.username + (p.rank ? ` (Rank ${p.rank})` : '') + (p.numCards ? ` (${p.numCards} cards)` : '')}
</li>
))}
</ul>
{`${gameState.playerTurn} has called BS! ${gameState.lastPlayedPlayer} must flip over ${gameState.lastPlayed} black cards!`}
<div>
<p>Stacks:</p>
{gameState.players.map((player, i) => (
<label key={player.username+': '+player.stackSize}>
<div>
<button
onClick={() => socket.emit('flip', i)}
disabled={username !== gameState.lastPlayedPlayer || player.stackSize === 0}
>
Flip!
</button>
{' '+player.username+': '+player.stackSize+' cards '}
{player.flipped.map((card, i) => (
<label key={card.rank+' '+card.suit}>
<span style={{color: (card.suit === Suit.Hearts || card.suit === Suit.Diamonds ? 'red' : 'black')}}>
{' '+rankStrs[card.rank]+' '+suitChars[card.suit]}
</span>
</label>
))}
</div>
</label>
))}
</div>
</>
);
}
if (gameState.phase === 3) {
return (
<>
<button
onClick={() => {
document.getElementById('Rules')!.style.display = (document.getElementById('Rules')!.style.display === 'none' ? 'block' : 'none');
}}
>
Show/hide rules
</button>
<div id='Rules' style={{display: 'none'}}>
{rules}
</div>
<ul>
{gameState.players.map(p => (
<li key={p.username}>
{p.username + (p.rank ? ` (Rank ${p.rank})` : '') + (p.numCards ? ` (${p.numCards} cards)` : '')}
</li>
))}
</ul>
{(gameState.lastPlayed > 0 ? `${gameState.lastPlayedPlayer}` : `${gameState.playerTurn}`) + ` lost! Now they must give up one of their cards!`}
<div>
<p>Stacks:</p>
{gameState.players.map((player, i) => (
<label key={player.username+': '+player.stackSize}>
<div>
<button
onClick={() => socket.emit('flip', i)}
disabled={true}
>
Flip!
</button>
{' '+player.username+': '+player.stackSize+' cards '}
{player.flipped.map((card, i) => (
<label key={card.rank+' '+card.suit}>
<span style={{color: (card.suit === Suit.Hearts || card.suit === Suit.Diamonds ? 'red' : 'black')}}>
{' '+rankStrs[card.rank]+' '+suitChars[card.suit]}
</span>
</label>
))}
</div>
</label>
))}
</div>
<div>
<p>Your cards:</p>
{gameState.cards.map((card, i) => (
<label key={card.rank+' '+card.suit}>
<div>
<button
onClick={() => socket.emit('giveup', i)}
disabled={username !== (gameState.lastPlayed > 0 ? gameState.lastPlayedPlayer : gameState.playerTurn)}
>
Give up this card!
</button>
<span style={{color: (card.suit === Suit.Hearts || card.suit === Suit.Diamonds ? 'red' : 'black')}}>
{' '+rankStrs[card.rank]+' '+suitChars[card.suit]}
</span>
</div>
</label>
))}
</div>
</>
);
}
return (
<>
<div>
<p>Something went wrong! :/</p>
</div>
</>
);
}