Skip to content

Commit

Permalink
Enables dealer to manage player scores
Browse files Browse the repository at this point in the history
  • Loading branch information
davidpounds committed Mar 17, 2021
1 parent edaf4e0 commit 9c7f149
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 51 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ The first user to connect is given the role of dealer, subsequent users are allo

* Force play in a clockwise direction.
* Enable the dealer to specify which player should play first in the next round.
* Enable the dealer to keep track of player scores.
* Force following suit.

## Available Scripts
Expand Down
11 changes: 11 additions & 0 deletions server/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@ const serverStore = {
players: getNewPlayers(CONFIG.MAX_PLAYERS),
deck: shuffleDeck(getResetDeck()),
currentUser: null,
scores: [],
};

export const resetScores = () => {
serverStore.scores = serverStore.players.map(player => ({
playerId: player,
score: 0,
}));
};

resetScores();

export const resetShuffleAndDeal = (resetPlayers = false) => {
const shuffledResetDeck = shuffleDeck(getResetDeck());
const { players } = serverStore;
const numberOfPlayers = players.length;
if (resetPlayers) {
serverStore.players = getNewPlayers(CONFIG.MAX_PLAYERS);
resetScores();
}
if (numberOfPlayers === CONFIG.MAX_PLAYERS) {
const numberOfCards = Math.floor(CONFIG.CARDS_IN_DECK / numberOfPlayers);
Expand Down
18 changes: 15 additions & 3 deletions server/websocketevents.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as ACTIONS from '../src/store/actiontypes.js';
import CONFIG from '../src/data/config.js';
import User from './User.class.js';
import { updateClientState } from './index.js';
import { resetShuffleAndDeal } from './store.js';
import { resetShuffleAndDeal, resetScores } from './store.js';
import { getFullCardName } from '../src/data/PlayingCard.class.js';

export const connectionHandler = (ws, req, serverStore, connectedClientCount) => {
Expand Down Expand Up @@ -71,6 +71,8 @@ const messageHandler = (ws, serverStore) => rawMessage => {
case ACTIONS.SERVER_DEALLOCATE_USER_TO_PLAYER:
deallocateUserToPlayer(serverStore, data);
break;
case ACTIONS.SERVER_UPDATE_PLAYER_SCORE:
updatePlayerScore(serverStore, data);
default:
break;
}
Expand Down Expand Up @@ -144,9 +146,19 @@ const allocateUserToPlayer = (serverStore, data) => {

const deallocateUserToPlayer = (serverStore, data) => {
const { user } = data;
const { players, users } = serverStore;
const { players } = serverStore;
const isPlayer = players.includes(user.playerId);
const message = isPlayer ? `${user.name} is no longer a player` : null;
serverStore.users.find(u => u.id === user.id).playerId = null;
updateClientState(message);
};
};

const updatePlayerScore = (serverStore, data) => {
const { playerId, score, resetAll = false } = data;
if (resetAll === true) {
resetScores();
} else {
serverStore.scores.find(score => score.playerId === playerId).score = score;
}
updateClientState();
};
22 changes: 19 additions & 3 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,27 @@ body {
}

.app {
width: calc(var(--card-width) * var(--hand-width-multiplier) * 2);
--half-card-width: calc(var(--card-width) / 2);
--in-play-width: calc(var(--card-width) * var(--hand-width-multiplier));
--connected-users-width: calc(var(--card-height) * 3);
/* width: calc(var(--in-play-width) * 2); */
max-width: 100vw;
display: grid;
grid-template-columns: var(--card-height) calc(var(--card-width) / 2) calc(var(--card-width) * var(--hand-width-multiplier)) calc(var(--card-width) / 2) var(--card-height);
grid-template-rows: var(--card-height) calc(var(--card-width) / 2) calc(var(--card-width) * var(--hand-width-multiplier)) calc(var(--card-width) / 2) var(--card-height);
grid-template-columns:
var(--card-height)
var(--half-card-width)
var(--in-play-width)
var(--half-card-width)
var(--card-height)
var(--half-card-width)
var(--connected-users-width);

grid-template-rows:
var(--card-height)
var(--half-card-width)
var(--in-play-width)
var(--half-card-width)
var(--card-height);
}

.cardlist {
Expand Down
72 changes: 57 additions & 15 deletions src/components/ConnectedUsers.css
Original file line number Diff line number Diff line change
@@ -1,31 +1,73 @@
.connected-users {
grid-column: -3 / -1;
grid-column: -2 / -1;
grid-row: 1 / -1;
transform: translateX(calc(var(--card-height) * 1.5));
color: #fff;
}

.connected-users ul {
margin: 0;
padding: 0;
.connected-users .reset-scores {
text-align: right;
}

.connected-users ul li {
margin: 0 0 0.5rem 1rem;
.connected-users .reset-scores .form-control {
cursor: pointer;
}

.connected-users .btn-icon svg {
width: 0.8rem;
height: 0.8rem;
.connected-users table {
border-collapse: collapse;
width: 100%;
}

.connected-users td,
.connected-users th {
padding: 0.5rem;
text-align: left;
}

.connected-users tr:nth-of-type(odd) td {
background-color: rgba(255, 255, 255, 0.05);
}

.connected-users tr:nth-of-type(even) td {
background-color: rgba(255, 255, 255, 0.1);
}

.connected-users .role {
width: 20%;
}

.connected-users .score {
text-align: right;
width: 15%;
}

.connected-users .btn-icon.add {
color: #fff;
transform: rotate(45deg);
.connected-users .form-control {
font-size: 1rem;
border: none;
background-color: rgba(255, 255, 255, 0.2);
color: inherit;
padding: 0.2rem;
}

.connected-users .btn-icon.remove {
color: #fc5554;
.connected-users .score input[type="number"] {
text-align: right;
-moz-appearance: textfield;
}

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

.connected-users select option {
background-color: rgba(255, 255, 255, 0.2);
color: #666;
}

.connected-users .btn-icon svg {
--icon-size: 0.8rem;
width: var(--icon-size);
height: var(--icon-size);
}

@media (max-width: 719px), (max-height: 719px) {
Expand Down
141 changes: 112 additions & 29 deletions src/components/ConnectedUsers.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import './ConnectedUsers.css';
import UserName from './UserName.jsx';
import * as ACTIONS from '../store/actiontypes.js';
Expand All @@ -12,7 +13,7 @@ import {

const ConnectedUsers = props => {
const { store, sendToServer } = props;
const { players = [], users = [], currentUser = null } = store;
const { players = [], users = [], scores = [], currentUser = null } = store;
const isDealer = currentUser?.isDealer ?? false;
const assignedPlayers = getAllocatedPlayers(players, users);
const unassignedPlayers = getUnallocatedPlayers(players, users);
Expand All @@ -27,36 +28,118 @@ const ConnectedUsers = props => {
}
};

const resetScores = () => {
sendToServer(ACTIONS.SERVER_UPDATE_PLAYER_SCORE, { resetAll: true });
};

return <div className="connected-users">
<ul>
{users.map(user => {
const playerIdForUser = getPlayerIdForUserId(players, users, user.id) ?? '';
const isPlayer = !isDealer && playerIdForUser !== null;
const isCurrentUser = user.id === currentUser.id;
const showButtons = !user.isDealer && isDealer;
const availablePlayers = uniqueArray([playerIdForUser, ...unassignedPlayers]).filter(ap => !!ap).sort((a, b) => {
const ai = getPlayerIndex(players, a);
const bi = getPlayerIndex(players, b);
return ai - bi;
});

return <li key={user.id}>
<UserName name={user.name} editable={isCurrentUser && !isDealer} sendToServer={sendToServer} />
{showButtons && !isPlayer && !playersFull && (
<select onChange={assignPlayerHandler(user)} value={playerIdForUser}>
<option value="">Spectator</option>
{availablePlayers.map(player => (
<option
value={player}
key={player}
>Player {getPlayerIndex(players, player) + 1}</option>
))}
</select>
)}
</li>;
})}
</ul>
<div className="reset-scores">
<button className="form-control" onClick={resetScores}>Reset scores</button>
</div>
<table>
<thead>
<tr>
<th>Name</th>
{isDealer && <th>Role</th>}
<th>Score</th>
{isDealer && <th>Add</th>}
</tr>
</thead>
<tbody>
{users.map(user => <UserRow
players={players}
scores={scores}
users={users}
user={user}
isDealer={isDealer}
currentUser={currentUser}
unassignedPlayers={unassignedPlayers}
sendToServer={sendToServer}
playersFull={playersFull}
changeHandler={assignPlayerHandler(user)}
/>)}
</tbody>
</table>
</div>;
}

const UserRow = props => {
const { players, users, user, isDealer, currentUser, unassignedPlayers, sendToServer, playersFull, scores, changeHandler } = props;
const playerIdForUser = getPlayerIdForUserId(players, users, user.id) ?? '';
const playerScore = scores.find(score => score.playerId === playerIdForUser)?.score ?? 0;
const isPlayer = !isDealer && playerIdForUser !== null;
const isCurrentUser = user.id === currentUser?.id;
const showButtons = !user.isDealer && isDealer;
const availablePlayers = uniqueArray([playerIdForUser, ...unassignedPlayers]).filter(ap => !!ap).sort((a, b) => {
const ai = getPlayerIndex(players, a);
const bi = getPlayerIndex(players, b);
return ai - bi;
});
const [valueToAdd, setValueToAdd] = useState(0);
const valueToAddChangeHandler = e => {
const value = Number.parseInt(e.target.value, 10);
if (!Number.isNaN(value)) {
setValueToAdd(value);
}
};

const updateScoreHandler = () => {
const value = Number.parseInt(valueToAdd, 10);
if (!Number.isNaN(value)) {
sendToServer(ACTIONS.SERVER_UPDATE_PLAYER_SCORE, { playerId: playerIdForUser, score: playerScore + value });
}
setValueToAdd(0);
};

return <tr key={user.id}>
<td>
<UserName name={user.name} editable={isCurrentUser && !isDealer} sendToServer={sendToServer} />
</td>
{isDealer && (
<td className="role">
{showButtons && !isPlayer && !playersFull && (
<PlayerPicker availablePlayers={availablePlayers} players={players} playerId={playerIdForUser} onChange={changeHandler} />
)}
</td>
)}
<td className="score">
{!user.isDealer && playerIdForUser && <>{playerScore}</>}
</td>
{isDealer && (
<td className="score">
{!user.isDealer && playerIdForUser && (
<input
className="form-control"
type="number"
value="0"
min="0"
max="99"
step="1"
maxlength="2"
size="2"
aria-label="Score to add"
value={valueToAdd}
onChange={valueToAddChangeHandler}
onBlur={updateScoreHandler}
/>
)}
</td>
)}
</tr>;

};

const PlayerPicker = props => {
const { availablePlayers, players, onChange, playerId } = props;
return <select className="form-control" onChange={onChange} value={playerId}>
<option value="">Spectator</option>
{availablePlayers.map(player => (
<option
value={player}
key={player}
>Player {getPlayerIndex(players, player) + 1}</option>
))}
</select>;
};

export default ConnectedUsers;
1 change: 1 addition & 0 deletions src/store/actiontypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const SERVER_ADD_CARDS_TO_PLAYED = 'SERVER_ADD_CARDS_TO_PLAYED';
export const SERVER_CHANGE_USER_NAME = 'SERVER_CHANGE_USER_NAME';
export const SERVER_ALLOCATE_USER_TO_PLAYER = 'SERVER_ALLOCATE_USER_TO_PLAYER';
export const SERVER_DEALLOCATE_USER_TO_PLAYER = 'SERVER_DEALLOCATE_USER_TO_PLAYER';
export const SERVER_UPDATE_PLAYER_SCORE = 'SERVER_UPDATE_PLAYER_SCORE';

export const CLIENT_UPDATE_STORE = 'CLIENT_UPDATE_STORE';
export const CLIENT_TOAST_MESSAGE = 'CLIENT_TOAST_MESSAGE';
Expand Down

0 comments on commit 9c7f149

Please sign in to comment.