Implemented two new VP adding cards
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
parent
e3f07e3502
commit
14115400a3
@ -25,9 +25,11 @@ const Hand = ({buildActive, setBuildActive, setCardActive}) => {
|
|||||||
const [longestRoad, setLongestRoad] = useState(undefined);
|
const [longestRoad, setLongestRoad] = useState(undefined);
|
||||||
const [largestArmy, setLargestArmy] = useState(undefined);
|
const [largestArmy, setLargestArmy] = useState(undefined);
|
||||||
const [development, setDevelopment] = useState([]);
|
const [development, setDevelopment] = useState([]);
|
||||||
|
const [mostPorts, setMostPorts] = useState(undefined);
|
||||||
|
const [mostDeveloped, setMostDeveloped] = useState(undefined);
|
||||||
|
|
||||||
const fields = useMemo(() => [
|
const fields = useMemo(() => [
|
||||||
'private', 'turn', 'color', 'longestRoad', 'largestArmy'
|
'private', 'turn', 'color', 'longestRoad', 'largestArmy', 'mostPorts', 'mostDeveloped'
|
||||||
], []);
|
], []);
|
||||||
const onWsMessage = (event) => {
|
const onWsMessage = (event) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
@ -49,6 +51,14 @@ const Hand = ({buildActive, setBuildActive, setCardActive}) => {
|
|||||||
if ('largestArmy' in data.update && largestArmy !== data.update.largestArmy) {
|
if ('largestArmy' in data.update && largestArmy !== data.update.largestArmy) {
|
||||||
setLargestArmy(data.update.largestArmy);
|
setLargestArmy(data.update.largestArmy);
|
||||||
}
|
}
|
||||||
|
if ('mostDeveloped' in data.update
|
||||||
|
&& data.update.mostDeveloped !== mostDeveloped) {
|
||||||
|
setMostDeveloped(data.update.mostDeveloped);
|
||||||
|
}
|
||||||
|
if ('mostPorts' in data.update
|
||||||
|
&& data.update.mostPorts !== mostPorts) {
|
||||||
|
setMostPorts(data.update.mostPorts);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -121,6 +131,16 @@ const Hand = ({buildActive, setBuildActive, setCardActive}) => {
|
|||||||
<div className="CardGroup">
|
<div className="CardGroup">
|
||||||
{ development }
|
{ development }
|
||||||
</div>
|
</div>
|
||||||
|
{mostDeveloped && mostDeveloped === color &&
|
||||||
|
<Placard
|
||||||
|
type='most-developed'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{mostPorts && mostPorts === color &&
|
||||||
|
<Placard
|
||||||
|
type='port-of-call'
|
||||||
|
/>
|
||||||
|
}
|
||||||
{ longestRoad && longestRoad === color &&
|
{ longestRoad && longestRoad === color &&
|
||||||
<Placard
|
<Placard
|
||||||
type='longest-road'
|
type='longest-road'
|
||||||
|
@ -23,9 +23,11 @@ const Volcano = ({ ws, rules, field, disabled }) => {
|
|||||||
console.log(`house-rules - ${field} - `, rules[field]);
|
console.log(`house-rules - ${field} - `, rules[field]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGold(rules[field].gold);
|
if (field in rules) {
|
||||||
setNumber(rules[field].number);
|
setGold('gold' in rules[field] ? rules[field].gold : false);
|
||||||
}, [rules, field]);
|
setNumber('number' in rules[field] ? rules[field].number : init);
|
||||||
|
}
|
||||||
|
}, [rules, field, init]);
|
||||||
|
|
||||||
const toggleGold = () => {
|
const toggleGold = () => {
|
||||||
rules[field].gold = !gold;
|
rules[field].gold = !gold;
|
||||||
@ -209,11 +211,25 @@ const HouseRules = ({ houseRulesActive, setHouseRulesActive }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/* https://icebreaker.com/games/catan-1/feature/catan-house-rules */
|
/* https://icebreaker.com/games/catan-1/feature/catan-house-rules */
|
||||||
setRuleElements([{
|
setRuleElements([{
|
||||||
title: `Why you play so slow?`,
|
title: `Why you play so slowf`,
|
||||||
key: `slowest-turn`,
|
key: `slowest-turn`,
|
||||||
description: `The player with the longest turn so far receives -2VP.`,
|
description: `The player with the longest turn idle time (longer than 2 minutes) so far loses 2VP.`,
|
||||||
element: <Placard type="longest-turn"/>,
|
element: <Placard type="longest-turn"/>,
|
||||||
implemented: false
|
implemented: false
|
||||||
|
}, {
|
||||||
|
title: `You are so developed`,
|
||||||
|
key: `most-developed`,
|
||||||
|
description:
|
||||||
|
`The player with the most development cards (more than 4) receives 2VP.`,
|
||||||
|
element: <Placard type="most-developed" />,
|
||||||
|
implemented: true
|
||||||
|
}, {
|
||||||
|
title: `Another round of port`,
|
||||||
|
key: `port-of-call`,
|
||||||
|
description:
|
||||||
|
`The player with the most harbor ports (more than 2) receives 2VP.`,
|
||||||
|
element: <Placard type="port-of-call" />,
|
||||||
|
implemented: true
|
||||||
}, {
|
}, {
|
||||||
title: `More victory points`,
|
title: `More victory points`,
|
||||||
key: `victory-points`,
|
key: `victory-points`,
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
width: 16rem;
|
||||||
background-color: #44444480;
|
background-color: #44444480;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import { Placard } from './Placard.js';
|
|||||||
import { GlobalContext } from './GlobalContext.js';
|
import { GlobalContext } from './GlobalContext.js';
|
||||||
|
|
||||||
const Player = ({ player, onClick, reverse, color,
|
const Player = ({ player, onClick, reverse, color,
|
||||||
largestArmy, isSelf, longestRoad }) => {
|
largestArmy, isSelf, longestRoad, mostPorts, mostDeveloped }) => {
|
||||||
if (!player) {
|
if (!player) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@ -34,6 +34,22 @@ const Player = ({ player, onClick, reverse, color,
|
|||||||
disabled/><b>{player.points}</b></>;
|
disabled/><b>{player.points}</b></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mostPortsPlacard = mostPorts && mostPorts === color ?
|
||||||
|
<Placard
|
||||||
|
disabled
|
||||||
|
active={false}
|
||||||
|
type='port-of-call'
|
||||||
|
count={player.ports}
|
||||||
|
/> : undefined;
|
||||||
|
|
||||||
|
const mostDevelopedPlacard = mostDeveloped && mostDeveloped === color ?
|
||||||
|
<Placard
|
||||||
|
disabled
|
||||||
|
active={false}
|
||||||
|
type='most-developed'
|
||||||
|
count={player.developmentCards}
|
||||||
|
/> : undefined;
|
||||||
|
|
||||||
const longestRoadPlacard = longestRoad && longestRoad === color ?
|
const longestRoadPlacard = longestRoad && longestRoad === color ?
|
||||||
<Placard
|
<Placard
|
||||||
disabled
|
disabled
|
||||||
@ -61,9 +77,11 @@ const Player = ({ player, onClick, reverse, color,
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className="Points">{points}</div>
|
<div className="Points">{points}</div>
|
||||||
{ (largestArmy || longestRoad || armyCards || resourceCards || developmentCards) && <>
|
{ (largestArmy || longestRoad || armyCards || resourceCards || developmentCards || mostPorts || mostDeveloped) && <>
|
||||||
<div className="Has">
|
<div className="Has">
|
||||||
{ !reverse && <>
|
{ !reverse && <>
|
||||||
|
{ mostDevelopedPlacard }
|
||||||
|
{ mostPortsPlacard }
|
||||||
{ largestArmyPlacard }
|
{ largestArmyPlacard }
|
||||||
{ longestRoadPlacard }
|
{ longestRoadPlacard }
|
||||||
{ !largestArmyPlacard && armyCards }
|
{ !largestArmyPlacard && armyCards }
|
||||||
@ -76,6 +94,8 @@ const Player = ({ player, onClick, reverse, color,
|
|||||||
{ !largestArmyPlacard && armyCards }
|
{ !largestArmyPlacard && armyCards }
|
||||||
{ longestRoadPlacard }
|
{ longestRoadPlacard }
|
||||||
{ largestArmyPlacard }
|
{ largestArmyPlacard }
|
||||||
|
{ mostPortsPlacard }
|
||||||
|
{ mostDevelopedPlacard }
|
||||||
</> }
|
</> }
|
||||||
</div>
|
</div>
|
||||||
</> }
|
</> }
|
||||||
@ -92,8 +112,10 @@ const PlayersStatus = ({ active }) => {
|
|||||||
const [color, setColor] = useState(undefined);
|
const [color, setColor] = useState(undefined);
|
||||||
const [largestArmy, setLargestArmy] = useState(undefined);
|
const [largestArmy, setLargestArmy] = useState(undefined);
|
||||||
const [longestRoad, setLongestRoad] = useState(undefined);
|
const [longestRoad, setLongestRoad] = useState(undefined);
|
||||||
|
const [mostPorts, setMostPorts] = useState(undefined);
|
||||||
|
const [mostDeveloped, setMostDeveloped] = useState(undefined);
|
||||||
const fields = useMemo(() => [
|
const fields = useMemo(() => [
|
||||||
'players', 'color', 'longestRoad', 'largestArmy'
|
'players', 'color', 'longestRoad', 'largestArmy', 'mostPorts', 'mostDeveloped'
|
||||||
], []);
|
], []);
|
||||||
const onWsMessage = (event) => {
|
const onWsMessage = (event) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
@ -106,12 +128,22 @@ const PlayersStatus = ({ active }) => {
|
|||||||
if ('color' in data.update && data.update.color !== color) {
|
if ('color' in data.update && data.update.color !== color) {
|
||||||
setColor(data.update.color);
|
setColor(data.update.color);
|
||||||
}
|
}
|
||||||
if ('longestRoad' in data.update && data.update.longestRoad !== longestRoad) {
|
if ('longestRoad' in data.update
|
||||||
|
&& data.update.longestRoad !== longestRoad) {
|
||||||
setLongestRoad(data.update.longestRoad);
|
setLongestRoad(data.update.longestRoad);
|
||||||
}
|
}
|
||||||
if ('largestArmy' in data.update && data.update.largestArmy !== largestArmy) {
|
if ('largestArmy' in data.update
|
||||||
|
&& data.update.largestArmy !== largestArmy) {
|
||||||
setLargestArmy(data.update.largestArmy);
|
setLargestArmy(data.update.largestArmy);
|
||||||
}
|
}
|
||||||
|
if ('mostDeveloped' in data.update
|
||||||
|
&& data.update.mostDeveloped !== mostDeveloped) {
|
||||||
|
setMostDeveloped(data.update.mostDeveloped);
|
||||||
|
}
|
||||||
|
if ('mostPorts' in data.update
|
||||||
|
&& data.update.mostPorts !== mostPorts) {
|
||||||
|
setMostPorts(data.update.mostPorts);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -149,6 +181,8 @@ const PlayersStatus = ({ active }) => {
|
|||||||
reverse
|
reverse
|
||||||
largestArmy={largestArmy}
|
largestArmy={largestArmy}
|
||||||
longestRoad={longestRoad}
|
longestRoad={longestRoad}
|
||||||
|
mostPorts={mostPorts}
|
||||||
|
mostDeveloped={mostDeveloped}
|
||||||
isSelf={active}
|
isSelf={active}
|
||||||
key={`PlayerStatus-${color}`}
|
key={`PlayerStatus-${color}`}
|
||||||
color={color}/>;
|
color={color}/>;
|
||||||
@ -160,6 +194,8 @@ const PlayersStatus = ({ active }) => {
|
|||||||
player={players[key]}
|
player={players[key]}
|
||||||
largestArmy={largestArmy}
|
largestArmy={largestArmy}
|
||||||
longestRoad={longestRoad}
|
longestRoad={longestRoad}
|
||||||
|
mostPorts={mostPorts}
|
||||||
|
mostDeveloped={mostDeveloped}
|
||||||
key={`PlayerStatus-${key}}`}
|
key={`PlayerStatus-${key}}`}
|
||||||
color={key}/>;
|
color={key}/>;
|
||||||
});
|
});
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
|
|
||||||
.Loading {
|
|
||||||
position: absolute;
|
|
||||||
top: 1em;
|
|
||||||
right: 31em;
|
|
||||||
width: 3em !important;
|
|
||||||
height: 3em !important;
|
|
||||||
z-index: 10010;
|
|
||||||
}
|
|
||||||
|
|
||||||
.NoNetwork {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10000;
|
|
||||||
display: flex;
|
|
||||||
top: 1em;
|
|
||||||
right: 31em;
|
|
||||||
width: 3em;
|
|
||||||
height: 3em;
|
|
||||||
background-image: url("./assets/no-network.png");
|
|
||||||
background-size: contain;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Roberta .Pip-Shape:hover,
|
|
||||||
.Roberta .Pip-Shape {
|
|
||||||
background-image:url("./assets/woman-robber.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
.BottomBar {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 30rem;
|
|
||||||
justify-items: space-between;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-end;
|
|
||||||
height: 10.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.Display {
|
|
||||||
display: inline-block;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.Placard:hover {
|
|
||||||
filter: brightness(105%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.Development:hover {
|
|
||||||
filter: brightness(150%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.Development.Selected {
|
|
||||||
filter: brightness(150%);
|
|
||||||
top: -1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.Action {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex: 1 0;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
background-color: rgba(16, 16, 16, 0.25);
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 0.25em;
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Error {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
top: calc(50vh - 1.5em);
|
|
||||||
left: calc(0vw + 1em);
|
|
||||||
right: calc(30vw + 2em);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: yellow;
|
|
||||||
padding: 1em;
|
|
||||||
z-index: 10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Message {
|
|
||||||
display: inline-block;
|
|
||||||
flex: 1 0;
|
|
||||||
justify-content: left;
|
|
||||||
text-align: left;
|
|
||||||
/* font-size: 12pt;*/
|
|
||||||
padding: 0.5em;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Message .PlayerColor {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Message div {
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.PlayerName {
|
|
||||||
padding: 0.5em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.PlayerName > .nameInput {
|
|
||||||
margin-right: 1em;
|
|
||||||
flex: 1;
|
|
||||||
max-width: 30em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.PlayerName > Button {
|
|
||||||
background: lightblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Statistics > div:nth-child(2) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Statistics div:nth-child(2) div {
|
|
||||||
padding: 0.25em 0.5em;
|
|
||||||
}
|
|
@ -1,816 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import "./Table.css";
|
|
||||||
import history from "./history.js";
|
|
||||||
import Paper from '@material-ui/core/Paper';
|
|
||||||
import Button from '@material-ui/core/Button';
|
|
||||||
import TextField from '@material-ui/core/TextField';
|
|
||||||
import List from '@material-ui/core/List';
|
|
||||||
import Board from './Board.js';
|
|
||||||
import Trade from './Trade.js';
|
|
||||||
import PlayerColor from './PlayerColor.js';
|
|
||||||
import Dice from './Dice.js';
|
|
||||||
import Resource from './Resource.js';
|
|
||||||
import ViewCard from './ViewCard.js';
|
|
||||||
import Winner from './Winner.js';
|
|
||||||
import ChooseCard from './ChooseCard.js';
|
|
||||||
import Chat from './Chat.js';
|
|
||||||
import { CircularProgress } from "@material-ui/core";
|
|
||||||
import 'moment-timezone';
|
|
||||||
import Activities from './Activities.js';
|
|
||||||
import Placard from './Placard.js';
|
|
||||||
import PlayersStatus from './PlayersStatus.js';
|
|
||||||
import { MediaAgent, MediaControl, MediaContext } from './MediaControl.js';
|
|
||||||
import { base, assetsPath, getPlayerName, gamesPath } from './Common.js';
|
|
||||||
|
|
||||||
/* Start of withRouter polyfill */
|
|
||||||
// https://reactrouter.com/docs/en/v6/faq#what-happened-to-withrouter-i-need-it
|
|
||||||
import {
|
|
||||||
useLocation,
|
|
||||||
useNavigate,
|
|
||||||
useParams
|
|
||||||
} from "react-router-dom";
|
|
||||||
|
|
||||||
function withRouter(Component) {
|
|
||||||
function ComponentWithRouterProp(props) {
|
|
||||||
let location = useLocation();
|
|
||||||
let navigate = useNavigate();
|
|
||||||
let params = useParams();
|
|
||||||
return (
|
|
||||||
<Component
|
|
||||||
{...props}
|
|
||||||
router={{ location, navigate, params }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ComponentWithRouterProp;
|
|
||||||
}
|
|
||||||
/* end of withRouter polyfill */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const StartButton = ({ table, game }) => {
|
|
||||||
const startClick = (event) => {
|
|
||||||
table.setGameState("game-order");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button disabled={!game.color || game.active < 2} onClick={startClick}>Start game</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* This needs to take in a mechanism to declare the
|
|
||||||
* player's active item in the game */
|
|
||||||
const Players = ({ table, game }) => {
|
|
||||||
const toggleSelected = (key) => {
|
|
||||||
table.setSelected(game.color === key ? "" : key);
|
|
||||||
}
|
|
||||||
|
|
||||||
const players = [];
|
|
||||||
|
|
||||||
if (!game.id) {
|
|
||||||
return (<></>);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let color in game.players) {
|
|
||||||
const item = game.players[color], inLobby = game.state === 'lobby';
|
|
||||||
if (!inLobby && item.status === 'Not active') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let name = getPlayerName(game.sessions, color),
|
|
||||||
selectable = game.state === 'lobby' && (item.status === 'Not active' || game.color === color);
|
|
||||||
players.push((
|
|
||||||
<div
|
|
||||||
data-selectable={selectable}
|
|
||||||
data-selected={game.color === color}
|
|
||||||
className="PlayerEntry"
|
|
||||||
onClick={() => { inLobby && selectable && toggleSelected(color) }}
|
|
||||||
key={`player-${color}`}>
|
|
||||||
<PlayerColor color={color}/>{name ? name : 'Available' }
|
|
||||||
{ name && <MediaContext.Provider value={game.peers}>
|
|
||||||
<MediaControl peer={name} isSelf={game.color === color}/>
|
|
||||||
</MediaContext.Provider> }
|
|
||||||
{ !name && <div></div> }
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper className="Players">
|
|
||||||
<List className="PlayerSelector">
|
|
||||||
{ players }
|
|
||||||
</List>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("TODO: Convert this to a function component!!!!");
|
|
||||||
|
|
||||||
class Table extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
message: "",
|
|
||||||
error: "",
|
|
||||||
signature: "",
|
|
||||||
buildActive: false,
|
|
||||||
cardActive: undefined,
|
|
||||||
loading: 0,
|
|
||||||
noNetwork: false,
|
|
||||||
ws: undefined,
|
|
||||||
peers: {}
|
|
||||||
};
|
|
||||||
this.componentDidMount = this.componentDidMount.bind(this);
|
|
||||||
this.throwDice = this.throwDice.bind(this);
|
|
||||||
this.rollDice = this.rollDice.bind(this);
|
|
||||||
this.setGameState = this.setGameState.bind(this);
|
|
||||||
this.shuffleTable = this.shuffleTable.bind(this);
|
|
||||||
this.startTrading = this.startTrading.bind(this);
|
|
||||||
this.offerTrade = this.offerTrade.bind(this);
|
|
||||||
this.acceptTrade = this.acceptTrade.bind(this);
|
|
||||||
this.rejectTrade = this.rejectTrade.bind(this);
|
|
||||||
this.cancelTrading = this.cancelTrading.bind(this);
|
|
||||||
this.discard = this.discard.bind(this);
|
|
||||||
this.passTurn = this.passTurn.bind(this);
|
|
||||||
this.updateGame = this.updateGame.bind(this);
|
|
||||||
this.setPlayerName = this.setPlayerName.bind(this);
|
|
||||||
this.setSelected = this.setSelected.bind(this);
|
|
||||||
this.updateMessage = this.updateMessage.bind(this);
|
|
||||||
this.sendAction = this.sendAction.bind(this);
|
|
||||||
this.buildClicked = this.buildClicked.bind(this);
|
|
||||||
this.closeCard = this.closeCard.bind(this);
|
|
||||||
this.playCard = this.playCard.bind(this);
|
|
||||||
this.selectResources = this.selectResources.bind(this);
|
|
||||||
this.buildItem = this.buildItem.bind(this);
|
|
||||||
|
|
||||||
this.loadTimer = null;
|
|
||||||
this.peers = {};
|
|
||||||
this.id = (props.router && props.router.params.id) ? props.router.params.id : 0;
|
|
||||||
this.setPeers = this.setPeers.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPeers(update) {
|
|
||||||
for (let key in this.peers) {
|
|
||||||
if (!(key in update)) {
|
|
||||||
delete this.peers[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({ peers: Object.assign({}, this.peers, update)});
|
|
||||||
}
|
|
||||||
|
|
||||||
closeCard() {
|
|
||||||
this.setState({cardActive: undefined});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendAction(action, value, extra) {
|
|
||||||
if (this.loadTimer) {
|
|
||||||
window.clearTimeout(this.loadTimer);
|
|
||||||
this.loadTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ loading: this.state.loading + 1 });
|
|
||||||
|
|
||||||
return window.fetch(`${base}/api/v1/games/${this.state.id}/${action}/${value}`, {
|
|
||||||
method: "PUT",
|
|
||||||
cache: 'no-cache',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: extra ? JSON.stringify(extra) : undefined
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.status >= 400) {
|
|
||||||
throw new Error(`Unable to perform ${action}!`);
|
|
||||||
}
|
|
||||||
return res.json();
|
|
||||||
}).then((game) => {
|
|
||||||
const error = (game.status !== 'success') ? game.status : undefined;
|
|
||||||
this.updateGame(game);
|
|
||||||
this.updateMessage();
|
|
||||||
this.setError(error);
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
this.setError(error.message);
|
|
||||||
}).then(() => {
|
|
||||||
this.setState({ loading: this.state.loading - 1 });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelected(key) {
|
|
||||||
return this.sendAction('player-selected', key);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendChat(message) {
|
|
||||||
return this.sendAction('chat', undefined, {message: message});
|
|
||||||
}
|
|
||||||
|
|
||||||
selectResources(cards) {
|
|
||||||
return this.sendAction('select-resources', undefined, cards);
|
|
||||||
}
|
|
||||||
|
|
||||||
playCard(card) {
|
|
||||||
this.setState({ cardActive: undefined });
|
|
||||||
return this.sendAction('play-card', undefined, card);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlayerName(name) {
|
|
||||||
return this.sendAction('player-name', name)
|
|
||||||
.then(() => {
|
|
||||||
this.setState({ pickName: false });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shuffleTable() {
|
|
||||||
return this.sendAction('shuffle')
|
|
||||||
.then(() => {
|
|
||||||
this.setError("Table shuffled!");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
startTrading() {
|
|
||||||
return this.sendAction('trade');
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelTrading() {
|
|
||||||
return this.sendAction('trade', 'cancel');
|
|
||||||
}
|
|
||||||
|
|
||||||
offerTrade(trade) {
|
|
||||||
return this.sendAction('trade', 'offer', trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptTrade(trade) {
|
|
||||||
return this.sendAction('trade', 'accept', trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelTrade(trade) {
|
|
||||||
return this.sendAction('trade', 'cancel', trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
rejectTrade(trade) {
|
|
||||||
return this.sendAction('trade', 'reject', trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
discard(resources) {
|
|
||||||
return this.sendAction('discard', undefined, resources);
|
|
||||||
}
|
|
||||||
|
|
||||||
passTurn() {
|
|
||||||
return this.sendAction('pass');
|
|
||||||
};
|
|
||||||
|
|
||||||
rollDice() {
|
|
||||||
return this.sendAction('roll');
|
|
||||||
}
|
|
||||||
|
|
||||||
setError(error) {
|
|
||||||
if (!error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.errorTimeout) {
|
|
||||||
clearTimeout(this.errorTimeout);
|
|
||||||
}
|
|
||||||
setTimeout(() => { this.setState({error: undefined}) }, 3000);
|
|
||||||
if (this.state.error !== error) {
|
|
||||||
this.setState({ error });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setGameState(state) {
|
|
||||||
if (this.loadTimer) {
|
|
||||||
window.clearTimeout(this.loadTimer);
|
|
||||||
this.loadTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ loading: this.state.loading + 1 });
|
|
||||||
return window.fetch(`${base}/api/v1/games/${this.state.id}/state/${state}`, {
|
|
||||||
method: "PUT",
|
|
||||||
cache: 'no-cache',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.status >= 400) {
|
|
||||||
console.log(res);
|
|
||||||
throw new Error(`Unable to set state to ${state}`);
|
|
||||||
}
|
|
||||||
return res.json();
|
|
||||||
}).then((game) => {
|
|
||||||
console.log (`Game state set to ${game.state}!`);
|
|
||||||
this.updateGame(game);
|
|
||||||
this.updateMessage();
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
this.setError(error.message);
|
|
||||||
}).then(() => {
|
|
||||||
this.setState({ loading: this.state.loading - 1 });
|
|
||||||
return this.state.state;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buildClicked(event) {
|
|
||||||
console.log("Build clicked");
|
|
||||||
this.setState({ buildActive: this.state.buildActive ? false : true });
|
|
||||||
};
|
|
||||||
|
|
||||||
placeRobber(robber) {
|
|
||||||
return this.sendAction('place-robber', robber);
|
|
||||||
};
|
|
||||||
|
|
||||||
buyDevelopment() {
|
|
||||||
return this.sendAction('buy-development');
|
|
||||||
}
|
|
||||||
|
|
||||||
buySettlement() {
|
|
||||||
return this.sendAction('buy-settlement');
|
|
||||||
}
|
|
||||||
|
|
||||||
placeSettlement(settlement) {
|
|
||||||
return this.sendAction('place-settlement', settlement);
|
|
||||||
}
|
|
||||||
|
|
||||||
buyCity() {
|
|
||||||
return this.sendAction('buy-city');
|
|
||||||
}
|
|
||||||
placeCity(city) {
|
|
||||||
return this.sendAction('place-city', city);
|
|
||||||
}
|
|
||||||
|
|
||||||
buyRoad() {
|
|
||||||
return this.sendAction('buy-road');
|
|
||||||
}
|
|
||||||
placeRoad(road) {
|
|
||||||
return this.sendAction('place-road', road);
|
|
||||||
}
|
|
||||||
|
|
||||||
stealResource(color) {
|
|
||||||
return this.sendAction('steal-resource', color);
|
|
||||||
}
|
|
||||||
|
|
||||||
throwDice() {
|
|
||||||
return this.rollDice();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGame(game) {
|
|
||||||
if (this.state.signature !== game.signature) {
|
|
||||||
this.setState( { signature: game.signature });
|
|
||||||
}
|
|
||||||
console.log("Update Game", game);
|
|
||||||
|
|
||||||
/* Only update fields that are changing */
|
|
||||||
for (let key in game) {
|
|
||||||
if (game[key] === this.state[key]) {
|
|
||||||
delete game[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(`Updating: `, { ...game });
|
|
||||||
this.setState( { ...game } );
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMessage() {
|
|
||||||
const player = (this.state.id && this.state.color) ? this.state.players[this.state.color] : undefined,
|
|
||||||
name = this.state ? this.state.name : "";
|
|
||||||
|
|
||||||
let message = <></>;
|
|
||||||
if (this.state.pickName || !name) {
|
|
||||||
message = <>{message}Enter the name you would like to be known by, then press <b>ENTER</b> or select <b>SET</b>.</>;
|
|
||||||
} else {
|
|
||||||
switch (this.state.state) {
|
|
||||||
case 'lobby':
|
|
||||||
message = <>{message}You are in the lobby as <b>{name}</b>.</>;
|
|
||||||
if (!this.state.color) {
|
|
||||||
message = <>{message}You select one of the <b>Available</b> colors below.</>;
|
|
||||||
} else {
|
|
||||||
message = <>{message}You have selected <PlayerColor color={this.state.color}/>.</>;
|
|
||||||
}
|
|
||||||
message = <>{message}You can chat with other players below.</>;
|
|
||||||
if (this.state.active < 2) {
|
|
||||||
message = <>{message}Once there are two or more players, you can select <StartButton table={this} game={this.state}/>.</>;
|
|
||||||
} else {
|
|
||||||
message = <>{message}There are enough players to start the game. Select <StartButton table={this} game={this.state}/> when ready.</>;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'game-order':
|
|
||||||
if (!player) {
|
|
||||||
message = <>{message}You are an observer in this game as <b>{name}</b>.</>;
|
|
||||||
message = <>{message}You can chat with other players below as <b>{this.state.name}</b>, but cannot play unless players go back to the Lobby.</>;
|
|
||||||
} else {
|
|
||||||
if (!player.order) {
|
|
||||||
message = <>{message}You need to roll for game order. Click <b>Roll Dice</b> below.</>;
|
|
||||||
} else {
|
|
||||||
message = <>{message}You rolled <Dice pips={player.order}/> for game order. Waiting for all players to roll.</>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'initial-placement':
|
|
||||||
message = <>{message}It is time for all the players to place their initial two settlements, with one road connected to each settlement.</>;
|
|
||||||
break;
|
|
||||||
case 'active':
|
|
||||||
if (!player) {
|
|
||||||
message = <>{message}This game is no longer in the lobby.<br/><b>TODO: Override game state to allow Lobby mode while in-game</b></>;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case null:
|
|
||||||
case undefined:
|
|
||||||
case '':
|
|
||||||
message = <>{message}The game is in a wonky state. Sorry :(</>;
|
|
||||||
break;
|
|
||||||
case 'normal':
|
|
||||||
if (this.state.turn) {
|
|
||||||
if (this.state.turn.roll === 7) {
|
|
||||||
message = <>{message}Robber was rolled!</>;
|
|
||||||
let move = true;
|
|
||||||
for (let color in this.state.players) {
|
|
||||||
let name = '';
|
|
||||||
for (let i = 0; i < this.state.sessions.length; i++) {
|
|
||||||
if (this.state.sessions[i].color === color) {
|
|
||||||
name = this.state.sessions[i].name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const discard = this.state.players[color].mustDiscard;
|
|
||||||
if (discard) {
|
|
||||||
move = false;
|
|
||||||
message = <>{message}<PlayerColor color={color}/> {name} needs to discard {discard} resources.</>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (move && (this.state.turn && !this.state.turn.placedRobber)) {
|
|
||||||
message = <>{message}<PlayerColor color={this.state.turn.color}/> {this.state.turn.name} needs to move the robber.</>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message = <>It is <PlayerColor color={this.state.turn.color}/> {this.state.turn.name}'s turn.</>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
message = <>{message}Game state is: {this.state.state}</>;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({ message: message });
|
|
||||||
}
|
|
||||||
|
|
||||||
resetKeepAlive(isDead) {
|
|
||||||
if (isDead) {
|
|
||||||
console.log(`Short circuiting keep-alive`);
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.close();
|
|
||||||
delete this.ws;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// console.log(`${this.game.name} Resetting keep-alive. Last ping: ${(Date.now() - this.lastPing) / 1000}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.keepAlive) {
|
|
||||||
clearTimeout(this.keepAlive);
|
|
||||||
this.keepAlive = 0;
|
|
||||||
} else {
|
|
||||||
console.log(`No keep-alive active`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.keepAlive = setTimeout(() => {
|
|
||||||
console.log(`${this.state.name} No ping after 10 seconds. Last ping: ${(Date.now() - this.lastPing) / 1000}`);
|
|
||||||
this.setState({ noNetwork: true });
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.close();
|
|
||||||
delete this.ws;
|
|
||||||
}
|
|
||||||
this.connectWebSocket();
|
|
||||||
}, isDead ? 3000 : 10000);
|
|
||||||
|
|
||||||
if (this.state.noNetwork !== false && !isDead) {
|
|
||||||
this.setState({ noNetwork: false });
|
|
||||||
} else if (this.state.noNetwork !== true && isDead) {
|
|
||||||
this.setState({ noNetwork: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connectWebSocket() {
|
|
||||||
if (!this.state.id) {
|
|
||||||
console.log(`Cannot initiate websocket connection while no game is set.`);
|
|
||||||
this.resetKeepAlive(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.ws) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let loc = window.location, new_uri;
|
|
||||||
if (loc.protocol === "https:") {
|
|
||||||
new_uri = "wss";
|
|
||||||
} else {
|
|
||||||
new_uri = "ws";
|
|
||||||
}
|
|
||||||
new_uri = `${new_uri}://${loc.host}${base}/api/v1/games/ws/${this.state.id}/`;
|
|
||||||
console.log(`Attempting WebSocket connection to ${new_uri}`);
|
|
||||||
|
|
||||||
this.ws = new WebSocket(new_uri);
|
|
||||||
this.setState({ ws: this.ws });
|
|
||||||
this.lastPing = this.state.timestamp;
|
|
||||||
|
|
||||||
this.ws.addEventListener('message', (event) => {
|
|
||||||
this.resetKeepAlive();
|
|
||||||
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = JSON.parse(event.data);
|
|
||||||
} catch (error) {
|
|
||||||
this.setError(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let update;
|
|
||||||
switch (data.type) {
|
|
||||||
case 'game-update':
|
|
||||||
console.log(`Game update received`);
|
|
||||||
update = data.update;
|
|
||||||
const error = (update.status !== 'success') ? update.status : undefined;
|
|
||||||
this.updateGame(update);
|
|
||||||
this.updateMessage();
|
|
||||||
this.setError(error);
|
|
||||||
break;
|
|
||||||
case 'ping':
|
|
||||||
this.lastPing = data.ping;
|
|
||||||
this.ws.send(JSON.stringify({ type: 'pong', timestamp: data.ping }));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ws.addEventListener('error', (event) => {
|
|
||||||
this.setState({ error: event.message });
|
|
||||||
console.error(`${this.state.name} WebSocket error: ${(Date.now() - this.state.lastPing) / 1000}`);
|
|
||||||
this.resetKeepAlive(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ws.addEventListener('close', (event) => {
|
|
||||||
console.log(`${this.state.name} WebSocket close: ${(Date.now() - this.state.lastPing) / 1000}`);
|
|
||||||
this.setState({ error: event.message });
|
|
||||||
this.resetKeepAlive(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ws.addEventListener('open', (event) => {
|
|
||||||
console.log(`${this.state.name} WebSocket open: Sending game-update request: ${(Date.now() - this.lastPing) / 1000}`);
|
|
||||||
this.ws.send(JSON.stringify({ type: 'game-update' }));
|
|
||||||
this.resetKeepAlive();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.start = new Date();
|
|
||||||
|
|
||||||
console.log(`Mounted: ${base}`);
|
|
||||||
|
|
||||||
const params = {};
|
|
||||||
if (this.id) {
|
|
||||||
console.log(`Loading game: ${this.id}`);
|
|
||||||
params.url = `${base}/api/v1/games/${this.id}`;
|
|
||||||
params.method = "GET"
|
|
||||||
} else {
|
|
||||||
console.log("Requesting new game.");
|
|
||||||
params.url = `${base}/api/v1/games/`;
|
|
||||||
params.method = "POST";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ loading: this.state.loading + 1 });
|
|
||||||
window.fetch(params.url, {
|
|
||||||
method: params.method,
|
|
||||||
cache: 'no-cache',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
// body: JSON.stringify(data) // body data type must match "Content-Type" header
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.status < 400) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
let error;
|
|
||||||
if (!this.id) {
|
|
||||||
error = `Unable to create new game.`;
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
error = `Unable to find game ${this.id}. Starting new game.`
|
|
||||||
console.log(error);
|
|
||||||
this.setError(error);
|
|
||||||
|
|
||||||
params.url = `${base}/api/v1/games/${this.id}`;
|
|
||||||
params.method = "POST";
|
|
||||||
|
|
||||||
return window.fetch(params.url, {
|
|
||||||
method: params.method,
|
|
||||||
cache: 'no-cache',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).then((res) => {
|
|
||||||
return res.json();
|
|
||||||
}).then((game) => {
|
|
||||||
if (!this.id) {
|
|
||||||
history.push(`${gamesPath}/${game.id}`);
|
|
||||||
}
|
|
||||||
this.id = game.id;
|
|
||||||
|
|
||||||
this.updateGame(game);
|
|
||||||
|
|
||||||
/* Connect to the WebSocket (after the game is setup) */
|
|
||||||
this.connectWebSocket();
|
|
||||||
|
|
||||||
this.updateMessage();
|
|
||||||
|
|
||||||
this.setState({ error: "" });
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
this.setError(error.message);
|
|
||||||
}).then(() => {
|
|
||||||
this.setState({ loading: this.state.loading - 1 });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buildItem(item) {
|
|
||||||
return this.sendAction(`buy-${item}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.close();
|
|
||||||
this.ws = null;
|
|
||||||
}
|
|
||||||
if (this.loadTimer) {
|
|
||||||
clearTimeout(this.loadTimer);
|
|
||||||
this.loadTimer = 0;
|
|
||||||
}
|
|
||||||
if (this.keepAlive) {
|
|
||||||
clearTimeout(this.keepAlive);
|
|
||||||
this.keepAlive = 0;
|
|
||||||
}
|
|
||||||
if (this.updateSizeTimer) {
|
|
||||||
clearTimeout(this.updateSizeTimer);
|
|
||||||
this.updateSizeTimer = 0;
|
|
||||||
}
|
|
||||||
if (this.errorTimeout) {
|
|
||||||
clearTimeout(this.errorTimeout);
|
|
||||||
this.errorTimeout = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cardClicked(card) {
|
|
||||||
this.setState({cardActive: card });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const game = this.state,
|
|
||||||
player = game ? game.player : undefined,
|
|
||||||
isTurn = (game && game.turn && game.turn.color === game.color) ? true : false,
|
|
||||||
showMessage = (game && (game.state === 'lobby' || !game.name));
|
|
||||||
|
|
||||||
let color;
|
|
||||||
switch (game ? game.color : undefined) {
|
|
||||||
case "O": color = "orange"; break;
|
|
||||||
case "R": color = "red"; break;
|
|
||||||
case "B": color = "blue"; break;
|
|
||||||
default: case "W": color = "white"; break;
|
|
||||||
}
|
|
||||||
let development;
|
|
||||||
if (player) {
|
|
||||||
let stacks = {};
|
|
||||||
game.player.development.forEach(card =>
|
|
||||||
(card.type in stacks)
|
|
||||||
? stacks[card.type].push(card)
|
|
||||||
: stacks[card.type] = [card]);
|
|
||||||
|
|
||||||
development = [];
|
|
||||||
for (let type in stacks) {
|
|
||||||
const cards = stacks[type]
|
|
||||||
.sort((A, B) => {
|
|
||||||
if (A.played) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (B.played) {
|
|
||||||
return +1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}).map(card => <Development
|
|
||||||
onClick={() => this.cardClicked(card)}
|
|
||||||
card={card}
|
|
||||||
table={this}
|
|
||||||
key={`${type}-${card.card}`}
|
|
||||||
type={`${type}-${card.card}`}/>);
|
|
||||||
development.push(<div key={type} className="Stack">{ cards }</div>);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
development = <>/</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.state.id) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (<>
|
|
||||||
<MediaContext.Provider value={{peers: this.peers, setPeers: this.setPeers}}>
|
|
||||||
<MediaAgent ws={this.state.ws} name={this.state.name}/>
|
|
||||||
</MediaContext.Provider>
|
|
||||||
<div className="Table">
|
|
||||||
{ this.state.loading > 0 && <CircularProgress className='Loading'/> }
|
|
||||||
|
|
||||||
{ this.state.noNetwork && <div className='NoNetwork'/> }
|
|
||||||
|
|
||||||
<Activities table={this.state}/>
|
|
||||||
|
|
||||||
<div style={{display: "inline-flex", flex: 1, flexDirection: "column"}}>
|
|
||||||
<Board table={this} game={this.state}/>
|
|
||||||
{ player !== undefined && <>
|
|
||||||
<PlayersStatus table={this} game={this.state}/>
|
|
||||||
<PlayersStatus active={true}
|
|
||||||
onClick={this.buildItem}
|
|
||||||
table={this}
|
|
||||||
game={this.state}
|
|
||||||
color={this.state.color}/>
|
|
||||||
<div className="BottomBar">
|
|
||||||
<div className="Hand">
|
|
||||||
<Resource type="wood" count={player.wood}/>
|
|
||||||
<Resource type="wheat" count={player.wheat}/>
|
|
||||||
<Resource type="stone" count={player.stone}/>
|
|
||||||
<Resource type="brick" count={player.brick}/>
|
|
||||||
<Resource type="sheep" count={player.sheep}/>
|
|
||||||
</div>
|
|
||||||
<div className="Hand">
|
|
||||||
{ development }
|
|
||||||
</div>
|
|
||||||
{ game.longestRoad && game.longestRoad === game.color &&
|
|
||||||
<Placard
|
|
||||||
active={false}
|
|
||||||
type='longest-road'
|
|
||||||
table={this}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{ game.largestArmy && game.largestArmy === game.color &&
|
|
||||||
<Placard
|
|
||||||
active={false}
|
|
||||||
type='largest-army'
|
|
||||||
table={this}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<Placard
|
|
||||||
active={this.state.buildActive}
|
|
||||||
disabled={!game || !game.turn || !game.turn.roll}
|
|
||||||
table={this} type={`${color}`}/>
|
|
||||||
</div>
|
|
||||||
</> }
|
|
||||||
{ player === undefined && <div className="BottomBar"></div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{ game && <div className={'Game ' + game.state}>
|
|
||||||
{ showMessage && <Paper className="Message">{ this.state.message }</Paper> }
|
|
||||||
{(this.state.pickName || !game.name) && <PlayerName table={this} game={this.state}/> }
|
|
||||||
{(!this.state.pickName && game.name) && <>
|
|
||||||
<Players table={this} game={this.state}/>
|
|
||||||
<Chat table={this} game={this.state}/>
|
|
||||||
<Action table={this} game={this.state}/>
|
|
||||||
</> }
|
|
||||||
</div> }
|
|
||||||
|
|
||||||
{ game && game.state === 'winner' &&
|
|
||||||
<Winner table={this} color={game.winner}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ this.state.cardActive &&
|
|
||||||
<ViewCard table={this} card={this.state.cardActive}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ game && game.state === 'game-order' &&
|
|
||||||
<GameOrder table={this}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ game && game.state === 'normal' &&
|
|
||||||
game.turn.actions && game.turn.actions.indexOf('trade') !== -1 &&
|
|
||||||
<Trade table={this}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ game
|
|
||||||
&& isTurn
|
|
||||||
&& game.turn.actions
|
|
||||||
&& game.turn.actions.indexOf('select-resources') !== -1 &&
|
|
||||||
<ChooseCard table={this} type={game.turn.active}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ game && game.state === 'normal' &&
|
|
||||||
game.turn &&
|
|
||||||
isTurn &&
|
|
||||||
game.turn.actions && game.turn.actions.indexOf('steal-resource') !== -1 &&
|
|
||||||
<SelectPlayer table={this} game={this.state} players={game.turn.limits.players}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ this.state.error && <Paper onClick={() => this.setState({ error: undefined })} className="Error"><div>{this.state.error}</div></Paper> }
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default withRouter(props => <Table {...props}/>);
|
|
@ -487,6 +487,8 @@ const processRoll = (game, session, dice) => {
|
|||||||
addChatMessage(game, null, `House rule 'Volcanoes have minerals' activated. Players must select which resources to receive from the Volcano!`);
|
addChatMessage(game, null, `House rule 'Volcanoes have minerals' activated. Players must select which resources to receive from the Volcano!`);
|
||||||
game.turn.active = 'volcano';
|
game.turn.active = 'volcano';
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
delete game.turn.select;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -590,7 +592,9 @@ const newPlayer = (color) => {
|
|||||||
color: color,
|
color: color,
|
||||||
name: "",
|
name: "",
|
||||||
totalTime: 0,
|
totalTime: 0,
|
||||||
turnStart: 0
|
turnStart: 0,
|
||||||
|
ports: 0,
|
||||||
|
developmentCards: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2276,9 +2280,11 @@ const buyDevelopment = (game, session) => {
|
|||||||
if (game.state !== 'normal') {
|
if (game.state !== 'normal') {
|
||||||
return `You cannot purchase a development card unless the game is active (${game.state}).`;
|
return `You cannot purchase a development card unless the game is active (${game.state}).`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.color !== game.turn.color) {
|
if (session.color !== game.turn.color) {
|
||||||
return `It is not your turn! It is ${game.turn.name}'s turn.`;
|
return `It is not your turn! It is ${game.turn.name}'s turn.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!game.turn.roll) {
|
if (!game.turn.roll) {
|
||||||
return `You cannot build until you have rolled.`;
|
return `You cannot build until you have rolled.`;
|
||||||
}
|
}
|
||||||
@ -2306,6 +2312,7 @@ const buyDevelopment = (game, session) => {
|
|||||||
player.wheat--;
|
player.wheat--;
|
||||||
player.sheep--;
|
player.sheep--;
|
||||||
player.resources = 0;
|
player.resources = 0;
|
||||||
|
player.developmentCards++;
|
||||||
[ 'wheat', 'brick', 'sheep', 'stone', 'wood' ].forEach(resource => {
|
[ 'wheat', 'brick', 'sheep', 'stone', 'wood' ].forEach(resource => {
|
||||||
player.resources += player[resource];
|
player.resources += player[resource];
|
||||||
});
|
});
|
||||||
@ -2314,6 +2321,19 @@ const buyDevelopment = (game, session) => {
|
|||||||
card.turn = game.turns;
|
card.turn = game.turns;
|
||||||
player.development.push(card);
|
player.development.push(card);
|
||||||
|
|
||||||
|
if (isRuleEnabled(game, 'most-developed')) {
|
||||||
|
if (player.development.length >= 5
|
||||||
|
&& (!game.mostDeveloped
|
||||||
|
|| player.developmentCards
|
||||||
|
> game.players[game.mostDeveloped].developmentCards)) {
|
||||||
|
if (game.mostDeveloped !== session.color) {
|
||||||
|
game.mostDeveloped = session.color;
|
||||||
|
game.mostDevelopmentCards = player.developmentCards;
|
||||||
|
addChatMessage(game, session, `${session.name} now has the most development cards (${player.developmentCards})!`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendUpdateToPlayer(game, session, {
|
sendUpdateToPlayer(game, session, {
|
||||||
private: session.player
|
private: session.player
|
||||||
});
|
});
|
||||||
@ -2321,6 +2341,7 @@ const buyDevelopment = (game, session) => {
|
|||||||
sendUpdateToPlayers(game, {
|
sendUpdateToPlayers(game, {
|
||||||
chat: game.chat,
|
chat: game.chat,
|
||||||
activities: game.activities,
|
activities: game.activities,
|
||||||
|
mostDeveloped: game.mostDeveloped,
|
||||||
players: getFilteredPlayers(game)
|
players: getFilteredPlayers(game)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2461,7 +2482,10 @@ const placeSettlement = (game, session, index) => {
|
|||||||
return `You have requested to place a settlement illegally!`;
|
return `You have requested to place a settlement illegally!`;
|
||||||
}
|
}
|
||||||
/* If this is not a valid road in the turn limits, discard it */
|
/* If this is not a valid road in the turn limits, discard it */
|
||||||
if (game.turn && game.turn.limits && game.turn.limits.corners && game.turn.limits.corners.indexOf(index) === -1) {
|
if (game.turn
|
||||||
|
&& game.turn.limits
|
||||||
|
&& game.turn.limits.corners
|
||||||
|
&& game.turn.limits.corners.indexOf(index) === -1) {
|
||||||
return `You tried to cheat! You should not try to break the rules.`;
|
return `You tried to cheat! You should not try to break the rules.`;
|
||||||
}
|
}
|
||||||
const corner = game.placements.corners[index];
|
const corner = game.placements.corners[index];
|
||||||
@ -2518,6 +2542,21 @@ const placeSettlement = (game, session, index) => {
|
|||||||
if (player.banks.indexOf(type) === -1) {
|
if (player.banks.indexOf(type) === -1) {
|
||||||
player.banks.push(type);
|
player.banks.push(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
player.ports++;
|
||||||
|
|
||||||
|
if (isRuleEnabled(game, 'port-of-call')) {
|
||||||
|
console.log(`Checking port-of-call`, player.ports, game.mostPorts);
|
||||||
|
if (player.ports >= 3
|
||||||
|
&& (!game.mostPorts
|
||||||
|
|| player.ports > game.mostPortCount)) {
|
||||||
|
if (game.mostPorts !== session.color) {
|
||||||
|
game.mostPorts = session.color;
|
||||||
|
game.mostPortCount = player.ports;
|
||||||
|
addChatMessage(game, session, `${session.name} now has the most ports (${player.ports})!`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
game.turn.actions = [];
|
game.turn.actions = [];
|
||||||
@ -2550,6 +2589,7 @@ const placeSettlement = (game, session, index) => {
|
|||||||
if (player.banks.indexOf(type) === -1) {
|
if (player.banks.indexOf(type) === -1) {
|
||||||
player.banks.push(type);
|
player.banks.push(type);
|
||||||
}
|
}
|
||||||
|
player.ports++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
player.settlements--;
|
player.settlements--;
|
||||||
@ -2570,6 +2610,7 @@ const placeSettlement = (game, session, index) => {
|
|||||||
sendUpdateToPlayers(game, {
|
sendUpdateToPlayers(game, {
|
||||||
placements: game.placements,
|
placements: game.placements,
|
||||||
activities: game.activities,
|
activities: game.activities,
|
||||||
|
mostPorts: game.mostPorts,
|
||||||
turn: game.turn,
|
turn: game.turn,
|
||||||
chat: game.chat
|
chat: game.chat
|
||||||
});
|
});
|
||||||
@ -2812,6 +2853,16 @@ const setRules = (game, session, rules) => {
|
|||||||
`${getName(session)} has ${rules[rule].enabled ? 'en' : 'dis'}abled the Twelve and Two are Synonyms house rule.`);
|
`${getName(session)} has ${rules[rule].enabled ? 'en' : 'dis'}abled the Twelve and Two are Synonyms house rule.`);
|
||||||
game.rules[rule] = rules[rule];
|
game.rules[rule] = rules[rule];
|
||||||
break;
|
break;
|
||||||
|
case 'most-developed':
|
||||||
|
addChatMessage(game, null,
|
||||||
|
`${getName(session)} has ${rules[rule].enabled ? 'en' : 'dis'}abled the Most Developed house rule.`);
|
||||||
|
game.rules[rule] = rules[rule];
|
||||||
|
break;
|
||||||
|
case 'port-of-call':
|
||||||
|
addChatMessage(game, null,
|
||||||
|
`${getName(session)} has ${rules[rule].enabled ? 'en' : 'dis'}abled the Another Round of Port house rule.`);
|
||||||
|
game.rules[rule] = rules[rule];
|
||||||
|
break;
|
||||||
case 'tiles-start-facing-down':
|
case 'tiles-start-facing-down':
|
||||||
addChatMessage(game, null,
|
addChatMessage(game, null,
|
||||||
`${getName(session)} has ${rules[rule].enabled ? 'en' : 'dis'}abled the Tiles Start Facing Down house rule.`);
|
`${getName(session)} has ${rules[rule].enabled ? 'en' : 'dis'}abled the Tiles Start Facing Down house rule.`);
|
||||||
@ -3636,6 +3687,12 @@ const calculatePoints = (game, update) => {
|
|||||||
if (key === game.largestArmy) {
|
if (key === game.largestArmy) {
|
||||||
player.points += 2;
|
player.points += 2;
|
||||||
}
|
}
|
||||||
|
if (key === game.mostPorts) {
|
||||||
|
player.points += 2;
|
||||||
|
}
|
||||||
|
if (key === game.mostDeveloped) {
|
||||||
|
player.points += 2;
|
||||||
|
}
|
||||||
player.points += MAX_SETTLEMENTS - player.settlements;
|
player.points += MAX_SETTLEMENTS - player.settlements;
|
||||||
player.points += 2 * (MAX_CITIES - player.cities);
|
player.points += 2 * (MAX_CITIES - player.cities);
|
||||||
|
|
||||||
@ -4460,6 +4517,10 @@ const resetGame = (game) => {
|
|||||||
longestRoadLength: 0,
|
longestRoadLength: 0,
|
||||||
largestArmy: '',
|
largestArmy: '',
|
||||||
largestArmySize: 0,
|
largestArmySize: 0,
|
||||||
|
mostDeveloped: '',
|
||||||
|
mostDevelopmentCards: 0,
|
||||||
|
mostPorts: '',
|
||||||
|
mostPortCount: 0,
|
||||||
winner: undefined,
|
winner: undefined,
|
||||||
active: 0
|
active: 0
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user