diff --git a/Makefile b/Makefile
index d1960cc..5662624 100644
--- a/Makefile
+++ b/Makefile
@@ -35,7 +35,7 @@ install:
npm install
interactive:
- cd dist/ && csi -include-path $(assets) -s farm.scm
+ cd dist/ && csi -include-path $(assets) -include-path ../src/server -s farm.scm
src/server/farm: src/server/farm.scm
cd src/server/ && csc -include-path ../../$(assets) -O3 farm.scm
diff --git a/assets/game/acf/game.scm b/assets/game/acf/game.scm
index d9b0d93..b5f3a51 100644
--- a/assets/game/acf/game.scm
+++ b/assets/game/acf/game.scm
@@ -47,7 +47,7 @@
"20 Cows on Peridier Ridge"))))
(define *ff-text*
- '(((p "Natural Disaster--The Solar Winds break through the atmosphere. You are luckily shielded by Mt Proctor. Your hay survives and jumps in price. " (b "COLLECT $500 per Hay acre") ". To see if they escaped, other players must roll. Odd: escaped, Even: hit. " (b "Wind hit players must clean up all acres at $100 per acre.")))
+ '(((p (img (@ (src "53c3a93b0867eee67b9b9f6ebc4c1f4a.gif") (style "float: left;"))) "Natural Disaster--The Solar Winds break through the atmosphere. You are luckily shielded by Mt Proctor. Your hay survives and jumps in price. " (b "COLLECT $500 per Hay acre") ". To see if they escaped, other players must roll. Odd: escaped, Even: hit. " (b "Wind hit players must clean up all acres at $100 per acre.")))
((p "Planetary Disaster Fund comes through." (p (b "COLLECT $100 per Grain acre."))))
((p "Another high wind spring and your wheat didn't get sprayed. Weeds take over and cut your harvest in half. Hold this card through Wheat Harvest for this year."))
((p "Kept back some of your cows and Proxima B steak goes viral.") (p (b "COLLECT $2,000 if you have cows.")))
diff --git a/assets/img/volcano.gif b/assets/img/volcano.gif
new file mode 100644
index 0000000..ab87cea
Binary files /dev/null and b/assets/img/volcano.gif differ
diff --git a/assets/img/volcano.svg b/assets/img/volcano.svg
new file mode 100644
index 0000000..6e02850
--- /dev/null
+++ b/assets/img/volcano.svg
@@ -0,0 +1,73 @@
+
+image/svg+xml
\ No newline at end of file
diff --git a/assets/img/volcano0.svg b/assets/img/volcano0.svg
new file mode 100644
index 0000000..178957b
--- /dev/null
+++ b/assets/img/volcano0.svg
@@ -0,0 +1,80 @@
+
+image/svg+xml
\ No newline at end of file
diff --git a/assets/img/volcano1.svg b/assets/img/volcano1.svg
new file mode 100644
index 0000000..79576dc
--- /dev/null
+++ b/assets/img/volcano1.svg
@@ -0,0 +1,82 @@
+
+image/svg+xml
\ No newline at end of file
diff --git a/assets/img/volcano2.gif b/assets/img/volcano2.gif
new file mode 100644
index 0000000..74f6c6f
Binary files /dev/null and b/assets/img/volcano2.gif differ
diff --git a/assets/img/volcano2.svg b/assets/img/volcano2.svg
new file mode 100644
index 0000000..67a34f1
--- /dev/null
+++ b/assets/img/volcano2.svg
@@ -0,0 +1,82 @@
+
+image/svg+xml
\ No newline at end of file
diff --git a/assets/img/volcano3.svg b/assets/img/volcano3.svg
new file mode 100644
index 0000000..da0f934
--- /dev/null
+++ b/assets/img/volcano3.svg
@@ -0,0 +1,82 @@
+
+image/svg+xml
\ No newline at end of file
diff --git a/assets/img/volcano4.svg b/assets/img/volcano4.svg
new file mode 100644
index 0000000..e73349e
--- /dev/null
+++ b/assets/img/volcano4.svg
@@ -0,0 +1,82 @@
+
+image/svg+xml
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 8baf848..de9aa6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "webpack-test",
+ "name": "farm",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
@@ -3462,6 +3462,11 @@
"safe-buffer": "~5.1.1"
}
},
+ "cookies-js": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/cookies-js/-/cookies-js-1.2.3.tgz",
+ "integrity": "sha1-AzFQSefFK+4/cxhqaRZ+qw3bLTE="
+ },
"copy-concurrently": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
diff --git a/package.json b/package.json
index 57fc53e..d5bd698 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/react-fontawesome": "^0.1.8",
+ "cookies-js": "^1.2.3",
"mobx": "^5.15.3",
"mobx-react": "^6.1.4",
"react": "^16.12.0",
diff --git a/src/components/app/App.jsx b/src/components/app/App.jsx
index 9fae531..4002d23 100644
--- a/src/components/app/App.jsx
+++ b/src/components/app/App.jsx
@@ -34,9 +34,9 @@ class Chrome extends React.Component {
render() {
return (
-
Alpha Centauri Farming
- {this.props.children}
-
+
Alpha Centauri Farming
+ {this.props.children}
+
);
}
@@ -49,31 +49,43 @@ class App extends React.Component {
case SCREENS.intro:
view = ( );
break;
- case SCREENS.start:
- view = ( );
- break;
- case SCREENS.newGame:
- view = (
+ case SCREENS.start:
+ view = ( );
+ break;
+ case SCREENS.newGame:
+ view = (
-
+
+ );
+ break;
+ case SCREENS.joinGame:
+ view = (
+
+
+
+
);
break;
- case SCREENS.joinGame:
- view = (
);
- break;
case SCREENS.play:
view = ( );
break;
}
return (
- {view}
-
+ {view}
+
);
}
diff --git a/src/components/create-account/CreateAccount.jsx b/src/components/create-account/CreateAccount.jsx
new file mode 100644
index 0000000..c48a5a1
--- /dev/null
+++ b/src/components/create-account/CreateAccount.jsx
@@ -0,0 +1,108 @@
+// Copyright 2020 Thomas Hintz
+//
+// This file is part of the Alpha Centauri Farming project.
+//
+// The Alpha Centauri Farming project is free software: you can
+// redistribute it and/or modify it under the terms of the GNU General
+// Public License as published by the Free Software Foundation, either
+// version 3 of the License, or (at your option) any later version.
+//
+// The Alpha Centauri Farming project is distributed in the hope that
+// it will be useful, but WITHOUT ANY WARRANTY; without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with the Alpha Centauri Farming project. If not, see
+// .
+
+import React from 'react'
+import { GroupBox, Row, Col, Button } from '../widgets.jsx'
+
+export default class CreateAccount extends React.Component {
+ state = {
+ username: '',
+ email: '',
+ password: '',
+ confirmPassword: '',
+ loading: false
+ }
+ onChange = (e) => {
+ const target = e.target,
+ value = target.value,
+ name = target.name;
+
+ this.setState({
+ [name]: value
+ });
+ }
+
+ onSubmit = (e) => {
+ e.preventDefault();
+ this.setState({ loading: true });
+ this.props.createAccount(this.state);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.state.loading && prevProps.errors !== this.props.errors) {
+ this.setState({ loading: false });
+ }
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
diff --git a/src/components/create-or-join/CreateOrJoin.jsx b/src/components/create-or-join/CreateOrJoin.jsx
index d8d3fa6..7a6facf 100644
--- a/src/components/create-or-join/CreateOrJoin.jsx
+++ b/src/components/create-or-join/CreateOrJoin.jsx
@@ -19,21 +19,34 @@
import React, { Fragment } from 'react'
import { connect } from 'react-redux'
+import Cookies from 'cookies-js'
+
import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import { showNewGame, showJoinGame } from '../app/actions.js'
class CreateOrJoin extends React.Component {
+ signOut = (e) => {
+ e.preventDefault();
+ this.props.signOut();
+ Cookies.expire('awful-cookie');
+ }
+
render() {
return (
-
- New Game
-
- {this.props.start.start.games.length > 0 ? (
-
- Join Game
-
- ) : ( )}
+
+ New Game
+
+ {(this.props.start.start.games.length > 0) || (this.props.start.start.openGames.length > 0) ? (
+
+ Join Game
+
+ ) : ( )}
+ {this.props.start.start.user ? (
+
+ Sign Out
+
+ ) : (<>>)}
);
}
diff --git a/src/components/farm/Board.jsx b/src/components/farm/Board.jsx
index bf5e34a..0856e8d 100644
--- a/src/components/farm/Board.jsx
+++ b/src/components/farm/Board.jsx
@@ -24,6 +24,7 @@ import WheatImg from './../../../assets/img/wheat.svg'
import TractorImg from './../../../assets/img/tractor-icon.svg'
import TractorFullImg from './../../../assets/img/tractor-with-spikes.svg'
import HarvesterImg from './../../../assets/img/harvester.svg'
+import VolcanoImg from './../../../assets/img/volcano2.gif'
import React, { Fragment } from 'react'
import ReactDOM from 'react-dom'
@@ -45,21 +46,21 @@ import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer,
setMovingSkip } from './actions.js'
import { buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit,
- buyUncleBert, skip, endAiTurn } from './interface.js'
+ buyUncleBert, skip, endAiTurn, startGame } from './interface.js'
function netWorth(player) {
return ((player.assets.hay + player.assets.grain) * 2000) +
- (player.assets.fruit * 5000) +
- (player.assets.cows * 500) +
- ((player.assets.harvester + player.assets.tractor) * 10000) +
+ (player.assets.fruit * 5000) +
+ (player.assets.cows * 500) +
+ ((player.assets.harvester + player.assets.tractor) * 10000) +
player.displayCash - player.debt;
}
function assetsValue(player) {
return ((player.assets.hay + player.assets.grain) * 2000) +
- (player.assets.fruit * 5000) +
- (player.assets.cows * 500) +
- ((player.assets.harvester + player.assets.tractor) * 10000);
+ (player.assets.fruit * 5000) +
+ (player.assets.cows * 500) +
+ ((player.assets.harvester + player.assets.tractor) * 10000);
}
function getElementValue(id) {
@@ -464,20 +465,20 @@ class FarmsContainer extends React.Component {
return (
Ridges :
- {ridgeNames.map((ridge, idx) => (
-
- {ridge}
- {'(' + ((idx + 2) * 10) + ' cows)'}
-
- ))}
-
- {this.props.otherPlayers
- .map(p => (
-
-
{'\u00A0'}
-
{p.player.name}
-
-
))}
+ {ridgeNames.map((ridge, idx) => (
+
+ {ridge}
+ {'(' + ((idx + 2) * 10) + ' cows)'}
+
+ ))}
+
+ {this.props.otherPlayers
+ .map(p => (
+
+
{'\u00A0'}
+
{p.player.name}
+
+
))}
);
}
@@ -521,11 +522,11 @@ const makeDefaultToTrade = () => {
class TradeContainer2 extends React.Component {
resources = [{ img: HayImg,
- h: '120',
- s: '100',
- label: 'acres of Hay',
- key: 'hay',
- amount: 10
+ h: '120',
+ s: '100',
+ label: 'acres of Hay',
+ key: 'hay',
+ amount: 10
}, {
img: WheatImg,
h: '41',
@@ -859,7 +860,7 @@ class CCBY extends React.Component {
render() {
return (
- License Creative Commons CCBY
+ License Creative Commons CCBY
);
}
@@ -880,6 +881,9 @@ class Misc extends React.Component {
Copyright Nick Roach with modifications by Thomas Hintz - License GPL
+
+ Copyright Laymik -
+
Copyright Made -
@@ -1090,6 +1094,7 @@ class Die extends React.Component {
trigger = 0;
decayFactorPct = 0.4;
showScreenTimerId = false;
+ rollIndex = 0;
constructor(props) {
super(props);
@@ -1107,12 +1112,16 @@ class Die extends React.Component {
}
roll() {
- let roll = random(6);
- while (roll === this.lastRoll) {
- roll = random(6);
+ if (!this.props.rolls) {
+ let roll = random(6);
+ while (roll === this.lastRoll) {
+ roll = random(6);
+ }
+ this.lastRoll = roll;
+ return roll;
+ } else {
+ return this.props.rolls[++this.rollIndex];
}
- this.lastRoll = roll;
- return roll;
}
tick = () => {
@@ -1272,6 +1281,7 @@ class Rolling extends React.Component {
return (
this.nextView('income')}
skip={this.props.player.name === this.props.game.currentPlayer}
autoSkip={this.props.autoSkip === 'die'}
@@ -1569,6 +1580,7 @@ class Action extends React.Component {
break;
case 'harvest':
view = (
-
-
-
+ {!this.props.preventHiding ? (
+
+
+
+ ) : (<>>)}
{this.props.children}
{this.props.buttonText}
-
close
+ {!this.props.preventHiding ? (
close ) : (<>>)}
);
@@ -2015,6 +2030,30 @@ class BoardApp extends React.Component {
);
+ } else if (alert && alert.type === ALERTS.preGame) {
+ alertOverlay = (
+ 'nothing'}
+ preventHiding={true}
+ handler={startGame}>
+
+ Pre Game
+ When all players have joined click 'Start Game'!
+ Players
+
+ {this.props.player.name}
+ {this.props.game.otherPlayers.map((p, i) => (
+
+ {p.player.name}
+
+ ))}
+
+
+
+ );
} else if (alert && alert.type === ALERTS.proposedTrade) {
alertOverlay = (
{
+ if (data.game.state === GAME_STATES.preGame) {
+ store.dispatch(alert(ALERTS.preGame, '', 'pre-game'));
+ }
+ if (store.getState().farm.game.state === GAME_STATES.preGame &&
+ data.game.state === GAME_STATES.preTurn) {
+ store.dispatch(alertHandled('pre-game'));
+ }
if (data.player.state === GAME_STATES.preTurn &&
data.game.otherPlayers.length > 0 &&
store.getState().farm.player.state !== GAME_STATES.preTurn) {
@@ -186,6 +194,10 @@ function skip(component) {
sendCommand({ type: 'skip', component });
}
+function startGame() {
+ sendCommand({ type: 'start-game' });
+}
+
// TODO share with Board.jsx
// http://stackoverflow.com/questions/149055
function formatMoney(n) {
diff --git a/src/components/join-game/JoinGame.jsx b/src/components/join-game/JoinGame.jsx
index 463b529..91398ec 100644
--- a/src/components/join-game/JoinGame.jsx
+++ b/src/components/join-game/JoinGame.jsx
@@ -23,6 +23,7 @@ import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import { startOrJoinGame } from '../start/actions.js'
import { start } from '../app/actions.js'
+import LoginOrCreateAccount from '../login-or-create-account/LoginOrCreateAccount.jsx';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons'
@@ -36,14 +37,15 @@ class JoinGame extends React.Component {
super(props);
this.state = {
screen: JoinGameScreens.list,
- game: null
+ game: null,
+ showSignIn: false
};
}
handleClickGame = game => {
this.setState({ screen: JoinGameScreens.details,
game: game
- });
+ });
}
handleBack = e => {
@@ -54,59 +56,82 @@ class JoinGame extends React.Component {
}
}
- handleJoinAsExisting = e => {
+ handleJoinAsExisting = (e, id) => {
e.preventDefault();
this.props.startOrJoinGame({ type: 'join-as-existing',
- playerName: e.target.text,
- gameId: this.state.game.id,
- gameName: this.state.game.name });
+ gameId: id });
+ }
+
+ showSignIn = (e) => {
+ e.preventDefault();
+ this.setState(state => { return { showSignIn: !state.showSignIn }; });
}
render() {
return (
-
-
-
- Join Game
+
+
+
+ Join Game
)}>
-
+
- {this.state.screen === JoinGameScreens.list ?
- (
+ {this.state.screen === JoinGameScreens.list ?
+ (<>
+ My Games
+ {(!this.props.user && !this.state.showSignIn) ? (
+ Sign In to see your games
+ ) : (<>>)}
+ {(!this.props.user && this.state.showSignIn) ? (
+ <>
+
+
+
+ >
+ ) : (<>>)}
+ )
- : (
-
- Game: {this.state.game.name}
- Join as existing player:
-
- {this.state.game.players.map((p, i) =>
- (
-
- {p}
-
- ))}
-
-
- )}
-
-
+ .map((g, i) =>
+ (
+ this.handleJoinAsExisting(e, g.id)}>{g.name}
+ ))}
+
+ Open Games
+
+ >
+ )
+ : (
+
+ Game: {this.state.game.name}
+
+ )}
+
+
);
}
diff --git a/src/components/login-or-create-account/LoginOrCreateAccount.jsx b/src/components/login-or-create-account/LoginOrCreateAccount.jsx
new file mode 100644
index 0000000..37b6f78
--- /dev/null
+++ b/src/components/login-or-create-account/LoginOrCreateAccount.jsx
@@ -0,0 +1,50 @@
+// Copyright 2020 Thomas Hintz
+//
+// This file is part of the Alpha Centauri Farming project.
+//
+// The Alpha Centauri Farming project is free software: you can
+// redistribute it and/or modify it under the terms of the GNU General
+// Public License as published by the Free Software Foundation, either
+// version 3 of the License, or (at your option) any later version.
+//
+// The Alpha Centauri Farming project is distributed in the hope that
+// it will be useful, but WITHOUT ANY WARRANTY; without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with the Alpha Centauri Farming project. If not, see
+// .
+
+import React from 'react'
+import { GroupBox, Row, Col, Button } from '../widgets.jsx'
+
+import CreateAccount from '../create-account/CreateAccount.jsx';
+import Login from '../login/Login.jsx';
+
+export default class LoginOrCreateAccount extends React.Component {
+ state = {
+ showLogin: !!this.props.showLogin
+ }
+
+ toggleLogin = (e) => {
+ e.preventDefault();
+ this.setState(state => { return { showLogin: !state.showLogin }; });
+ }
+
+ render() {
+ return (
+ <>
+ {this.state.showLogin ? (
+
+ ) : (
+
+ )}
+
+ >
+ );
+ }
+}
diff --git a/src/components/login/Login.jsx b/src/components/login/Login.jsx
new file mode 100644
index 0000000..f8873b3
--- /dev/null
+++ b/src/components/login/Login.jsx
@@ -0,0 +1,90 @@
+// Copyright 2020 Thomas Hintz
+//
+// This file is part of the Alpha Centauri Farming project.
+//
+// The Alpha Centauri Farming project is free software: you can
+// redistribute it and/or modify it under the terms of the GNU General
+// Public License as published by the Free Software Foundation, either
+// version 3 of the License, or (at your option) any later version.
+//
+// The Alpha Centauri Farming project is distributed in the hope that
+// it will be useful, but WITHOUT ANY WARRANTY; without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with the Alpha Centauri Farming project. If not, see
+// .
+
+import React from 'react'
+import { GroupBox, Row, Col, Button } from '../widgets.jsx'
+
+export default class Login extends React.Component {
+ state = {
+ username: '',
+ password: '',
+ loading: false
+ }
+ onChange = (e) => {
+ const target = e.target,
+ value = target.value,
+ name = target.name;
+
+ this.setState({
+ [name]: value
+ });
+ }
+
+ onSubmit = (e) => {
+ e.preventDefault();
+ this.setState({ loading: true });
+ this.props.login(this.state);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.state.loading && prevProps.errors !== this.props.errors) {
+ this.setState({ loading: false });
+ }
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
diff --git a/src/components/new-game/NewGame.jsx b/src/components/new-game/NewGame.jsx
index 7de2a38..5bf4f5c 100644
--- a/src/components/new-game/NewGame.jsx
+++ b/src/components/new-game/NewGame.jsx
@@ -20,6 +20,7 @@ import React, { Fragment } from 'react'
import { connect } from 'react-redux'
import { GroupBox, Row, Col, Button } from '../widgets.jsx'
+import LoginOrCreateAccount from '../login-or-create-account/LoginOrCreateAccount.jsx';
import { startOrJoinGame } from '../start/actions.js'
import { start } from '../app/actions.js'
@@ -53,7 +54,6 @@ class NewGame extends React.Component {
super(props);
this.state = {
showSettings: false,
- playerName: '',
checkedColor: props.colors[0],
gameId: typeof props.gameId === 'undefined' ? -1 : props.gameId,
gameName: props.gameName || '',
@@ -63,7 +63,8 @@ class NewGame extends React.Component {
auditThreshold: 250000,
startingCash: 5000,
startingDebt: 5000,
- trade: true
+ trade: true,
+ showLogin: false
};
}
@@ -96,8 +97,7 @@ class NewGame extends React.Component {
}
render() {
- let playerNameInput,
- titleBar = !this.props.hideBack ? (
+ let titleBar = !this.props.hideBack ? (
@@ -129,97 +129,97 @@ class NewGame extends React.Component {
mainScreenClass = !this.state.showSettings ? '' : 'hidden';
return (
-
+ {this.props.user ? (
+
+ ) : (
+ <>
+ Sign in or create account to continue
+
+ >
+ )}
);
}
}
export default connect(
- null,
+ state => state.start.start,
{ startOrJoinGame, start }
)(NewGame)
diff --git a/src/components/start/actionTypes.js b/src/components/start/actionTypes.js
index 4a7e716..02a220e 100644
--- a/src/components/start/actionTypes.js
+++ b/src/components/start/actionTypes.js
@@ -17,4 +17,7 @@
// .
export const SET_START_GAMES = 'set-start-games';
+export const SET_OPEN_GAMES = 'set-open-games';
+export const SET_USER = 'set-user';
+export const SET_ERRORS = 'set-errors';
export const START_OR_JOIN_GAME = 'start-or-join-game';
diff --git a/src/components/start/actions.js b/src/components/start/actions.js
index a2b64ac..1d10f68 100644
--- a/src/components/start/actions.js
+++ b/src/components/start/actions.js
@@ -16,16 +16,32 @@
// along with the Alpha Centauri Farming project. If not, see
// .
-import { SET_START_GAMES, START_OR_JOIN_GAME } from './actionTypes.js'
+import { SET_START_GAMES, START_OR_JOIN_GAME, SET_USER, SET_OPEN_GAMES,
+ SET_ERRORS } from './actionTypes.js'
-export { setStartGames, startOrJoinGame }
+export { setStartGames, startOrJoinGame, setUser, setOpenGames, setErrors }
function setStartGames(games) {
return { type: SET_START_GAMES,
games };
}
+function setOpenGames(games) {
+ return { type: SET_OPEN_GAMES,
+ games };
+}
+
+function setUser(user) {
+ return { type: SET_USER,
+ user };
+}
+
function startOrJoinGame(msg) {
return { type: START_OR_JOIN_GAME,
msg };
}
+
+function setErrors(errors) {
+ return { type: SET_ERRORS,
+ errors };
+}
diff --git a/src/components/start/reducers.js b/src/components/start/reducers.js
index 86c9fc9..ee6f9fc 100644
--- a/src/components/start/reducers.js
+++ b/src/components/start/reducers.js
@@ -16,11 +16,15 @@
// along with the Alpha Centauri Farming project. If not, see
// .
-import { SET_START_GAMES, START_OR_JOIN_GAME } from './actionTypes.js'
+import { SET_START_GAMES, START_OR_JOIN_GAME, SET_USER, SET_OPEN_GAMES,
+ SET_ERRORS } from './actionTypes.js'
import { SCREENS } from '../../constants.js'
const initialState = {
- start: { games: [] },
+ start: { games: [],
+ openGames: [],
+ errors: [],
+ user: false },
msg: null
};
@@ -28,6 +32,12 @@ export default function(state = initialState, action) {
switch (action.type) {
case SET_START_GAMES:
return { ...state, start: { ...state.start, games: action.games }};
+ case SET_OPEN_GAMES:
+ return { ...state, start: { ...state.start, openGames: action.games }};
+ case SET_USER:
+ return { ...state, start: { ...state.start, user: action.user }};
+ case SET_ERRORS:
+ return { ...state, start: { ...state.start, errors: action.errors }};
case START_OR_JOIN_GAME:
return { ...state, msg: action.msg };
default:
diff --git a/src/constants.js b/src/constants.js
index 94d6d30..10ab120 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -26,7 +26,8 @@ export const SCREENS = {
export const GAME_STATES = { preTurn: 'pre-turn',
midTurn: 'mid-turn',
- turnEnded: 'turn-ended' };
+ turnEnded: 'turn-ended',
+ preGame: 'pre-game' };
export const rootId = 'initial-element';
export const messagePanelId = 'message-panel';
export const ALERTS = { beginTurn: 'begin-turn',
@@ -34,4 +35,5 @@ export const ALERTS = { beginTurn: 'begin-turn',
endOfGame: 'end-of-game',
auditCalled: 'audit-called',
raiseMoney: 'raise-money',
- proposedTrade: 'proposed-trade' }
+ proposedTrade: 'proposed-trade',
+ preGame: 'pre-game' }
diff --git a/src/main.jsx b/src/main.jsx
index e344880..bdda299 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -28,7 +28,8 @@ import { rootId } from './constants.js'
import App from './components/app/App.jsx'
import { initialize, handleMessage as handleMessageFarm } from './components/farm/interface.js'
-import { setStartGames, startOrJoinGame } from './components/start/actions.js'
+import { setStartGames, startOrJoinGame, setUser, setOpenGames,
+ setErrors } from './components/start/actions.js'
import { play } from './components/app/actions.js'
import { createStore } from 'redux'
import rootReducer from './rootReducers.js'
@@ -46,9 +47,24 @@ document.body.appendChild(makeDiv(rootId));
window.store = store;
+const createAccount = (msg) => {
+ Ws.sendCommand({ type: 'create-account', ...msg});
+}
+
+const login = (msg) => {
+ Ws.sendCommand({ type: 'login', ...msg});
+}
+
+const logout = () => {
+ Ws.sendCommand({ type: 'logout'});
+}
+
ReactDOM.render(
-
+
,
document.getElementById(rootId)
);
@@ -78,6 +94,9 @@ function handleMessage(evt) {
store.dispatch(play());
} else if (data.event === 'start-init') {
store.dispatch(setStartGames(data.games.games));
+ store.dispatch(setOpenGames(data.openGames.games));
+ store.dispatch(setUser(data.user));
+ store.dispatch(setErrors(data.errors));
if (autostart) {
if (data.games.games.length === 0) {
store.dispatch(startOrJoinGame({ type: 'new-game',
diff --git a/src/server/db.scm b/src/server/db.scm
new file mode 100644
index 0000000..f461447
--- /dev/null
+++ b/src/server/db.scm
@@ -0,0 +1,134 @@
+(use sql-de-lite crypt)
+
+(define *db* "/home/tjhintz/db")
+
+(define-syntax with-db
+ (syntax-rules ()
+ ((_ (var) body ...)
+ (call-with-database *db*
+ (lambda (var)
+ body ...)))))
+
+(define (create-tables)
+ (with-db (db)
+ (exec (sql db "create table users(id INTEGER PRIMARY KEY, username TEXT, email TEXT, password TEXT, salt TEXT);"))
+ (exec (sql db "create table sessions(bindings TEXT, session_id TEXT PRIMARY KEY);"))
+ (exec (sql db "create table games(id INTEGER PRIMARY KEY, status TEXT, object TEXT);"))
+ (exec (sql db "create table players(id INTEGER PRIMARY KEY, object TEXT);"))
+ (exec (sql db "create table user_games(user_id INTEGER, game_id INTEGER);"))))
+
+(define (db-session-set! sid bindings)
+ (with-db (db)
+ (exec (sql db "insert or replace into sessions(bindings, session_id) values (?, ?);")
+ (with-output-to-string (lambda () (write bindings)))
+ sid)))
+
+(define (db-session-ref sid)
+ (with-input-from-string
+ (or (alist-ref
+ 'bindings
+ (with-db (db)
+ (query fetch-alist
+ (sql db "select * from sessions where session_id=?;")
+ sid)))
+ "#f")
+ read))
+
+(define (add-user username email password)
+ (let ((salt (crypt-gensalt)))
+ (with-db (db)
+ (exec (sql db "insert into users(username, password, salt, email) values(?, ?, ?, ?);")
+ username (crypt password salt) salt email)
+ (last-insert-rowid db))))
+
+(define (fetch-user username)
+ (with-db (db)
+ (query fetch-alist
+ (sql db "select * from users where username=?;")
+ username)))
+
+(define (fetch-user-by-id id)
+ (with-db (db)
+ (query fetch-alist
+ (sql db "select * from users where id=?;")
+ id)))
+
+(define (valid-password? username password)
+ (and-let* ((user (fetch-user username))
+ (_ (if (null? user)
+ (begin (crypt password "$2a$12$OW1wyLclJvq.PIxgoHCjdu")
+ #f)
+ #t)))
+ (string=? (crypt password (alist-ref 'salt user))
+ (alist-ref 'password user))))
+
+(define (alist->string alist)
+ (with-output-to-string (lambda () (write alist))))
+
+(define (string->alist s)
+ (with-input-from-string s read))
+
+(define (db-add-game status object)
+ (with-db (db)
+ (exec (sql db "insert into games(status, object) values (?, ?);")
+ status (alist->string object))
+ (last-insert-rowid db)))
+
+(define (db-update-game id status object)
+ (with-db (db)
+ (exec (sql db "replace into games(id, status, object) values (?, ?, ?);")
+ id status (alist->string object))))
+
+(define (db-fetch-game id)
+ (string->alist
+ (with-db (db)
+ (query fetch-value
+ (sql db "select object from games where id=?;")
+ id))))
+
+(define (db-fetch-open-games)
+ (map
+ string->alist
+ (with-db (db)
+ (query fetch-column
+ (sql db "select object from games where status=?;")
+ "pre-game"))))
+
+(define (db-fetch-game-row id)
+ (let ((res
+ (with-db (db)
+ (query fetch-alist
+ (sql db "select * from games where id=?;")
+ id))))
+ `((id . ,(alist-ref 'id res))
+ (status . ,(alist-ref 'status res))
+ (object . ,(string->alist (alist-ref 'object res))))))
+
+(define (db-add-player object)
+ (with-db (db)
+ (exec (sql db "insert into players(object) values (?);")
+ (alist->string object))
+ (last-insert-rowid db)))
+
+(define (db-update-player id object)
+ (with-db (db)
+ (exec (sql db "replace into players(id, object) values (?, ?);")
+ id (alist->string object))))
+
+(define (db-fetch-player id)
+ (string->alist
+ (with-db (db)
+ (query fetch-value
+ (sql db "select object from players where id=?;")
+ id))))
+
+(define (db-add-user-game user-id game-id)
+ (with-db (db)
+ (exec (sql db "insert into user_games(user_id, game_id) values (?, ?);")
+ user-id game-id)))
+
+(define (db-fetch-user-games user-id)
+ (with-db (db)
+ (query fetch-column
+ (sql db "select game_id from user_games where user_id=?;")
+ user-id)))
diff --git a/src/server/farm.scm b/src/server/farm.scm
index 42e0f3f..4977601 100644
--- a/src/server/farm.scm
+++ b/src/server/farm.scm
@@ -29,6 +29,33 @@
(else
(include "game")))
+(include "db.scm")
+
+(session-storage-initialize
+ (lambda ()
+ 'no-op))
+
+(session-storage-set!
+ (lambda (sid session-item)
+ (db-session-set! sid (session-item-bindings session-item))))
+
+(define (expiration)
+ (+ (current-milliseconds)
+ (inexact->exact (floor (* (session-lifetime) 1000)))))
+
+(session-storage-ref
+ (lambda (sid)
+ (let ((data (db-session-ref sid)))
+ (if data
+ (make-session-item (expiration) (remote-address) data #f)
+ (error "session not found")))
+ ;; (make-session-item (+ (current-milliseconds) 100000000) (remote-address) `((user-id . ,(db-session-ref sid))) #f)
+ ))
+
+(session-storage-delete!
+ (lambda (sid)
+ (error "session storage delete not handled")))
+
(root-path "./")
(define (neq? a b) (not (eq? a b)))
@@ -55,6 +82,7 @@
(SRV:send-reply (pre-post-order* sxml rules)))))))
(define *game* (make-parameter #f))
+(define *player* (make-parameter #f))
(define-syntax safe-set!
(ir-macro-transformer
@@ -90,6 +118,7 @@
(next-year-rules initform: '() accessor: player-next-year-rules)
(color initform: #f accessor: player-color)
(name initform: "PLAYER X" accessor: player-name)
+ (user-id initform: #f accessor: player-user-id)
(trade initform: '() accessor: player-trade)
(last-updated initform: 0 accessor: player-last-updated)
(last-cash initform: 5000 accessor: player-last-cash)
@@ -114,7 +143,7 @@
(colors initform: '() accessor: game-colors)
(last-updated initform: 0 accessor: game-last-updated)
(called-audit initform: #f accessor: game-called-audit)
- (state initform: 'playing accessor: game-state)
+ (state initform: 'pre-game accessor: game-state)
(name initform: "game" accessor: game-name)
(turn initform: 1 accessor: game-turn)
(current-player initform: #f accessor: game-current-player)
@@ -154,6 +183,7 @@
(next-year-rules . ,(player-next-year-rules player))
(color . ,(player-color player))
(name . ,(player-name player))
+ (user-id . ,(player-user-id player))
(trade . ())
(last-updated . 0)
(last-cash . ,(player-cash player))
@@ -196,11 +226,11 @@
*operating-expense-cards*)))
'called-audit (if (alist-ref 'called-audit x)
(find (lambda (p)
- (string=? (player-name p) (alist-ref 'called-audit x)))
+ (equal? (player-name p) (alist-ref 'called-audit x)))
players)
#f)
'current-player (find (lambda (p)
- (string=? (player-name p) (alist-ref 'current-player x)))
+ (equal? (player-name p) (alist-ref 'current-player x)))
players)
(fold (lambda (k r) (cons k (cons (alist-ref k x) r)))
'()
@@ -221,6 +251,10 @@
(lambda ()
(write (app->sexp *app*)))))
+(define (save-game game)
+ (db-update-game (game-id game) (symbol->string (game-state game))
+ (game->sexp game)))
+
(define (load-app)
(with-input-from-file "/home/tjhintz/app.scm"
(lambda ()
@@ -236,7 +270,7 @@
(fold (lambda (k r) (cons k (cons (alist-ref k x) r)))
'()
'(cash debt space previous-space state assets ridges
- harvest-mult otbs
+ harvest-mult otbs user-id
year-rules next-year-rules hay-doubled corn-doubled
color name trade last-updated last-cash))))
@@ -310,13 +344,14 @@
(safe-set! (game-colors game) (cdr (game-colors game)))
color))
-(define (add-player-to-game game color name)
+(define (add-player-to-game game color name user-id)
(let ((player (make
'cash (game-setting 'starting-cash game)
'display-cash (game-setting 'starting-cash game)
'debt (game-setting 'starting-debt game)
'color color
'name name
+ 'user-id user-id
'state (if (= (length (game-players game)) 0)
'pre-turn 'turn-ended))))
(safe-set! (game-players game) (append (game-players game) (list player)))
@@ -441,6 +476,7 @@
(player-otbs p))))
(color . ,(symbol->string (player-color p)))
(name . ,(player-name p))
+ (user-id . ,(player-user-id p))
(trade . ,(player-trade p))
(lastCash . ,(player-last-cash p))
(hayDoubled . ,(player-hay-doubled p))
@@ -459,6 +495,7 @@
(player-otbs p))))
(color . ,(symbol->string (player-color p)))
(name . ,(player-name p))
+ (user-id . ,(player-user-id p))
(trade . ,(player-trade p))
(lastCash . ,(player-last-cash p))
(hayDoubled . ,(player-hay-doubled p))
@@ -535,13 +572,23 @@
(define (finish-year player #!optional (collect-wages #t))
(let ((game (*game*)))
(when collect-wages
- (safe-set! (player-cash player)
- (+ (player-cash player) 5000))
- (safe-set! (player-display-cash player) (player-cash player))
- (safe-set! (game-actions game)
- (cons '((?action . info)
- (?value . "You earned $5,000 from your city job!"))
- (game-actions game))))
+ (let* ((richest (car (sort (game-players game)
+ (lambda (p1 p2)
+ (> (player-net-worth p1)
+ (player-net-worth p2))))))
+ (bonus (max (farming-round
+ (* (- (player-net-worth richest)
+ (player-net-worth player))
+ 0.2))
+ 2500)))
+ (safe-set! (player-cash player)
+ ;; (+ (player-cash player) 5000)
+ (+ (player-cash player) bonus))
+ (safe-set! (player-display-cash player) (player-cash player))
+ (safe-set! (game-actions game)
+ (cons `((?action . info)
+ (?value . ,(conc "You earned $" bonus " from your city job!")))
+ (game-actions game)))))
(when (game-called-audit game)
(safe-set! (game-actions game)
(append (game-actions game)
@@ -810,7 +857,7 @@
(player->list player)
(game->list (*game*) player)))
-(define (create-start-response event)
+(define (create-start-response event #!key (errors '()))
`((event . ,event)
(games . ((games . ,(list->vector
(map (lambda (game)
@@ -820,7 +867,24 @@
(map symbol->string (game-colors game))))
(players . ,(list->vector
(map player-name (game-players game))))))
- (app-games *app*))))))))
+ (map (lambda (gid)
+ (sexp->game (db-fetch-game gid)))
+ (db-fetch-user-games (session-ref (sid) 'user-id -1))))))))
+ (openGames . ((games . ,(list->vector
+ (map (lambda (game)
+ `((name . ,(game-name game))
+ (id . ,(game-id game))
+ (colors . ,(list->vector
+ (map symbol->string (game-colors game))))
+ (players . ,(list->vector
+ (map player-name (game-players game))))))
+ (map sexp->game (db-fetch-open-games)))))))
+ (user . ,(let ((id (session-ref (sid) 'user-id #f)))
+ (if (and id (not (equal? id -1)))
+ id
+ #f)))
+ (errors . ,(list->vector errors))))
+
(define (message-players! game player message #!key (type "action"))
(for-each (lambda (p)
(when (not (eq? p player))
@@ -855,13 +919,35 @@
(safe-set! (player-display-cash player) (player-cash player)))
(game-players game))))
+(define (find-game id)
+ (let ((game-in-memory (find (lambda (g) (= (game-id g) id))
+ (app-games *app*))))
+ (if game-in-memory
+ game-in-memory
+ (let ((db-game (sexp->game (db-fetch-game id))))
+ (push! db-game (app-games *app*))
+ db-game))))
+
+(define (next-roll last-roll)
+ (let ((roll (+ (random 6) 1)))
+ (if (= roll last-roll)
+ (next-roll last-roll)
+ roll)))
+
+(define (make-rolls n)
+ (define (_make-rolls n i rolls)
+ (if (<= i n)
+ (_make-rolls n (+ i 1) (cons (next-roll (car rolls)) rolls))
+ rolls))
+ (_make-rolls n 1 (list (next-roll -1))))
+
(define (process-message player game type msg)
- (when game
- (safe-set! (game-messages game) '())
+ (when player
(safe-set! (player-last-cash player) (player-cash player)))
(print "message type: " type)
(cond ((string=? type "roll")
- (let ((num (+ (random 6) 1)))
+ (let ((num (+ (random 6) 1))
+ (rolls (make-rolls 22)))
(when *next-roll* (set! num *next-roll*))
(safe-set! (player-previous-space player)
(player-space player))
@@ -876,7 +962,8 @@
(finish-year player))
(safe-set! (player-harvest-mult player) 1)
(let ((resp `((from . ,(player-previous-space player))
- (to . ,(player-space player)))))
+ (to . ,(player-space player))
+ (rolls . ,(list->vector rolls)))))
(safe-set! (game-actions game)
(append (game-actions game)
`(((?action . move) (?value . ,resp))
@@ -1169,7 +1256,7 @@
(print exn)
(print-error-message exn)
(print "error saving app"))
- (save-app))
+ (save-game game))
(if (eq? (game-state game) 'finished)
(do-end-of-game game)
(message-players! game player '() type: "update"))
@@ -1180,6 +1267,7 @@
(create-start-response "start-init"))
((string=? type "new-game")
(let* ((color (string->symbol (alist-ref 'checkedColor msg)))
+ (user (fetch-user-by-id (session-ref (sid) 'user-id)))
(game (make 'colors (filter (cut neq? <> color)
'(green red blue yellow black))
'name (alist-ref 'gameName msg)
@@ -1200,49 +1288,81 @@
(trade . ,(or (alist-ref 'trade msg) #t)))))
(player (add-player-to-game game
color
- (alist-ref 'playerName msg)))
+ (alist-ref 'username user)
+ (alist-ref 'id user)))
;; (ai-player (add-ai-to-game game 'red "AI Player 1"))
)
(push! game (app-games *app*))
- (session-set! (sid) 'player player)
- (session-set! (sid) 'game game)
+ (let ((gid (db-add-game "pre-game" (game->sexp game))))
+ (safe-set! (game-id game) gid)
+ (db-update-game gid "pre-game" (game->sexp game))
+ (db-add-user-game (alist-ref 'id user) (game-id game))
+ (session-set! (sid) 'game-id (game-id game)))
(*game* game)
+ (*player* player)
(set-startup-otbs game player 2)
;; (set-startup-otbs game ai-player 2)
;; (thread-start! (make-ai-push-receiver game ai-player))
(create-start-response "new-game-started")))
((string=? type "join-game")
- (let* ((name (alist-ref 'gameName msg))
+ (let* ((user (fetch-user-by-id (session-ref (sid) 'user-id)))
+ (name (alist-ref 'username user))
(id (alist-ref 'gameId msg))
- (game (find (lambda (g) (= (game-id g) id))
- (app-games *app*)))
+ (game (find-game id))
(color-raw (string->symbol (alist-ref 'checkedColor msg)))
(color (if (not (member color-raw (game-colors game)))
(car (game-colors game))
color-raw))
(player (add-player-to-game game
color
- (alist-ref 'playerName msg))))
+ (alist-ref 'username user)
+ (alist-ref 'id user))))
(safe-set! (game-colors game) (filter (cut neq? <> color) (game-colors game)))
- (session-set! (sid) 'player player)
- (session-set! (sid) 'game game)
+ (session-set! (sid) 'game-id (game-id game))
+ (db-add-user-game (alist-ref 'id user) (game-id game))
(*game* game)
+ (*player* player)
(set-startup-otbs game player 2)
(message-players! game player '() type: "update")
(create-start-response "new-game-started")))
((string=? type "join-as-existing")
- (let* ((name (alist-ref 'gameName msg))
- (pname (alist-ref 'playerName msg))
- (id (alist-ref 'gameId msg))
- (game (find (lambda (g) (= (game-id g) id))
- (app-games *app*)))
- (player (find (lambda (p) (string=? (player-name p) pname))
+ (let* ((id (alist-ref 'gameId msg))
+ (user-id (session-ref (sid) 'user-id))
+ (game (find-game id))
+ (player (find (lambda (p) (equal? (player-user-id p) user-id))
(game-players game))))
- (session-set! (sid) 'player player)
- (session-set! (sid) 'game game)
(*game* game)
+ (*player* player)
(create-start-response "new-game-started")))
- ))
+ ((string=? type "create-account")
+ (let ((username (alist-ref 'username msg))
+ (email (alist-ref 'email msg))
+ (password (alist-ref 'password msg))
+ (confirm-password (alist-ref 'confirmPassword msg)))
+ (if (string=? password confirm-password)
+ (if (null? (fetch-user username))
+ (let ((id (add-user username email password)))
+ (session-set! (sid) 'user-id id)
+ (create-start-response "start-init"))
+ (create-start-response "start-init" errors: '("Account already exists")))
+ (create-start-response "start-init" errors: '("Passwords don't match")))))
+ ((string=? type "login")
+ (let ((username (alist-ref 'username msg))
+ (password (alist-ref 'password msg)))
+ (if (valid-password? username password)
+ (begin (session-set! (sid) 'user-id (alist-ref 'id (fetch-user username)))
+ (create-start-response "start-init"))
+ (create-start-response "start-init" errors: '("Invalid password or account doesn't exist")))))
+ ((string=? type "logout")
+ (session-set! (sid) 'game-id #f)
+ (session-set! (sid) 'user-id #f)
+ (create-start-response "start-init"))
+ ((string=? type "start-game")
+ (safe-set! (game-state (*game*)) 'pre-turn)
+ (db-update-game (game-id (*game*)) (symbol->string (game-state (*game*)))
+ (game->sexp (*game*)))
+ (message-players! (*game*) (*player*) '() type: "update")
+ (create-ws-response (*player*) "update" '()))))
(define (process-ai-push-message player game msg)
(print (player-name player))
@@ -1297,6 +1417,18 @@
(process-ai-push-message player game msg)
(loop (mailbox-receive! (player-mailbox player))))))
+(define (session-game)
+ (let ((user-id (session-ref (sid) 'user-id)))
+ (if (and (not (*game*)) (session-ref (sid) 'game-id #f))
+ (let ((possible-game (find-game (session-ref (sid) 'game-id))))
+ (when possible-game
+ (*game* possible-game)
+ (*player* (find (lambda (p)
+ (equal? (player-user-id p) user-id))
+ (game-players (*game*))))
+ (*game*)))
+ (and (*game*)))))
+
(define (websocket-page)
(sid (read-cookie (session-cookie-name)))
;; TODO some kind of error handling if (sid) #f
@@ -1321,16 +1453,28 @@
(print-call-chain)
(print-error-message exn))))
(event . "error"))
- (let* ((game (session-ref (sid) 'game #f))
- (player (session-ref (sid) 'player #f))
- (res (process-message player
+ (session-game)
+ (let* ((game (*game*))
+ (res (process-message (*player*)
game
(alist-ref 'type msg)
msg)))
(when game
(safe-set! (game-last-updated game) (+ (game-last-updated game) 1))
- (safe-set! (player-last-updated player) (game-last-updated game)))
- res)))))
+ (when (*player*)
+ (safe-set! (player-last-updated (*player*)) (game-last-updated game))))
+ res)
+ ;; (let* ((game (session-ref (sid) 'game #f))
+ ;; (player (session-ref (sid) 'player #f))
+ ;; (res (process-message player
+ ;; game
+ ;; (alist-ref 'type msg)
+ ;; msg)))
+ ;; (when game
+ ;; (safe-set! (game-last-updated game) (+ (game-last-updated game) 1))
+ ;; (safe-set! (player-last-updated player) (game-last-updated game)))
+ ;; res)
+ ))))
(loop (read-json (receive-message)))))))
(define (push-websocket-page)
@@ -1338,39 +1482,33 @@
;; TODO some kind of error handling if (sid) #f
(with-concurrent-websocket
(lambda ()
- (let ((game (session-ref (sid) 'game))
- (player (session-ref (sid) 'player)))
- (*game* game)
- (let loop ((msg (mailbox-receive! (player-mailbox player))))
- (print msg)
- (when (not game)
- (set! game (session-ref (sid) 'game)))
- (when (not player)
- (set! player (session-ref (sid) 'player)))
- ;; when (< (player-last-updated player)
- ;; (game-last-updated game))
- (handle-exceptions
- exn
- (send-message
- (json->string
+ (session-game)
+ (let loop ((msg (mailbox-receive! (player-mailbox (*player*)))))
+ (session-game)
+ ;; when (< (player-last-updated player)
+ ;; (game-last-updated game))
+ (handle-exceptions
+ exn
+ (send-message
+ (json->string
+ `((exn . ,(with-output-to-string
+ (lambda ()
+ (print-call-chain)
+ (print-error-message exn)))))))
+ (send-message
+ (json->string
+ (handle-exceptions
+ exn
`((exn . ,(with-output-to-string
(lambda ()
(print-call-chain)
- (print-error-message exn)))))))
- (send-message
- (json->string
- (handle-exceptions
- exn
- `((exn . ,(with-output-to-string
- (lambda ()
- (print-call-chain)
- (print-error-message exn))))
- (event . "error"))
- (create-ws-response player
- (alist-ref 'type msg)
- (alist-ref 'value msg))
- ))))
- (loop (mailbox-receive! (player-mailbox player))))))))
+ (print-error-message exn))))
+ (event . "error"))
+ (create-ws-response (*player*)
+ (alist-ref 'type msg)
+ (alist-ref 'value msg))
+ ))))
+ (loop (mailbox-receive! (player-mailbox (*player*))))))))
(define (otb-spec->otb-cards spec id)
`((contents . ,(sxml->html* (list-ref spec 5)))
@@ -2034,6 +2172,7 @@
(game-players game)))))
((alist-ref 'action operating-expense) player)
`((rolled . ,rolled)
+ (rolls . ,(list->vector (make-rolls 22)))
(income . ,income)
(harvestMult . ,harvest-mult)
(operatingExpense . ,(alist-ref 'contents operating-expense))
diff --git a/src/style.scss b/src/style.scss
index b9b12b1..df63c66 100644
--- a/src/style.scss
+++ b/src/style.scss
@@ -141,6 +141,8 @@ $tab-margin: 0.3rem;
align-items: center; }
.view-container {
+ max-height: 90vh;
+ overflow: auto;
border: 0.3rem solid $dark-color;
background: linear-gradient(180deg, $light-color 0%, $orange-color 100%);
padding: 1rem;
@@ -919,3 +921,75 @@ $intro-time: 6s;
.pad-right {
padding-right: 0.3rem; }
+
+.sign-out-button {
+ position: absolute;
+ bottom: 1rem; }
+
+/* -------- MENU ------- */
+/* Position and sizing of burger button */
+.bm-burger-button {
+ position: fixed;
+ width: 36px;
+ height: 30px;
+ left: 36px;
+ top: 36px;
+}
+
+/* Color/shape of burger icon bars */
+.bm-burger-bars {
+ background: #373a47;
+}
+
+/* Color/shape of burger icon bars on hover*/
+.bm-burger-bars-hover {
+ background: #a90000;
+}
+
+/* Position and sizing of clickable cross button */
+.bm-cross-button {
+ height: 24px;
+ width: 24px;
+}
+
+/* Color/shape of close button cross */
+.bm-cross {
+ background: #bdc3c7;
+}
+
+/*
+Sidebar wrapper styles
+Note: Beware of modifying this element as it can break the animations - you should not need to touch it in most cases
+*/
+.bm-menu-wrap {
+ position: fixed;
+ height: 100%;
+}
+
+/* General sidebar styles */
+.bm-menu {
+ background: #373a47;
+ padding: 2.5em 1.5em 0;
+ font-size: 1.15em;
+}
+
+/* Morph shape necessary with bubble or elastic */
+.bm-morph-shape {
+ fill: #373a47;
+}
+
+/* Wrapper for item list */
+.bm-item-list {
+ color: #b8b7ad;
+ padding: 0.8em;
+}
+
+/* Individual item */
+.bm-item {
+ display: inline-block;
+}
+
+/* Styling of overlay */
+.bm-overlay {
+ background: rgba(0, 0, 0, 0.3);
+}
diff --git a/src/websocket.js b/src/websocket.js
index 2a8bb83..4894503 100644
--- a/src/websocket.js
+++ b/src/websocket.js
@@ -22,7 +22,9 @@ export { sendCommand, setMainOnMessage, setSecondaryOnMessage,
class WebSocketConnection {
location = '';
+ fullPath = '';
reopen = true;
+ onmessageProc = () => 'nothing'
ws = null;
constructor(location) {
@@ -35,6 +37,7 @@ class WebSocketConnection {
uri = "ws:";
}
uri += "//" + loc.host;
+ this.fullPath = uri + '/websocket/' + location;
this.ws = new WebSocket(uri + '/websocket/' + location);
}
@@ -47,8 +50,17 @@ class WebSocketConnection {
this.ws.send(JSON.stringify(cmd));
}
+ reestablish = () => {
+ this.ws = new WebSocket(this.fullPath);
+ this.ws.onmessage = this.onmessageProc;
+ }
+
onmessage(proc) {
+ this.onmessageProc = proc;
this.ws.onmessage = proc;
+ // this.ws.onclose = () => {
+ // setTimeout(this.reestablish, 500);
+ // }
}
onopen(proc) {
diff --git a/webpack.common.js b/webpack.common.js
index 33fc4b8..fa9c6c0 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -72,7 +72,7 @@ module.exports = {
}
},
{
- test: /\.(woff|woff2|eot|ttf|otf|svg|png)$/,
+ test: /\.(woff|woff2|eot|ttf|otf|svg|png|gif)$/,
use: [
'file-loader',
],