Using sqlite database, mt proctor animation.

This commit is contained in:
2020-04-09 22:52:44 -07:00
parent b34a66f697
commit 6ff6387fef
31 changed files with 1525 additions and 280 deletions

View File

@@ -34,9 +34,9 @@ class Chrome extends React.Component {
render() {
return (
<div className='flex-fullcenter'>
<div className='background-heading'><h1>Alpha Centauri Farming</h1></div>
{this.props.children}
<Tractor spikes={this.props.spikes} className={this.props.tractorClass} />
<div className='background-heading'><h1>Alpha Centauri Farming</h1></div>
{this.props.children}
<Tractor spikes={this.props.spikes} className={this.props.tractorClass} />
</div>
);
}
@@ -49,31 +49,43 @@ class App extends React.Component {
case SCREENS.intro:
view = (<Chrome spikes={true} tractorClass='intro'><Welcome /></Chrome>);
break;
case SCREENS.start:
view = (<Chrome><CreateOrJoin /></Chrome>);
break;
case SCREENS.newGame:
view = (<Chrome>
case SCREENS.start:
view = (<Chrome><CreateOrJoin signOut={this.props.logout} /></Chrome>);
break;
case SCREENS.newGame:
view = (<Chrome>
<div className='view-container'>
<NewGame colors={['green', 'red', 'blue', 'yellow', 'black']}
button={'Start'}
title={'New Game'}
type={'new-game'}
showGameName={true} />
<NewGame colors={['green', 'red', 'blue', 'yellow', 'black']}
button={'Start'}
title={'New Game'}
type={'new-game'}
showGameName={true}
createAccount={this.props.createAccount}
login={this.props.login}
errors={this.props.errors}
/>
</div>
</Chrome>);
break;
case SCREENS.joinGame:
view = (
<Chrome>
<div className='view-container'>
<JoinGame createAccount={this.props.createAccount}
login={this.props.login}
errors={this.props.errors}
/>
</div>
</Chrome>);
break;
case SCREENS.joinGame:
view = (<Chrome><div className='view-container'><JoinGame /></div></Chrome>);
break;
case SCREENS.play:
view = (<Board />);
break;
}
return (
<Fragment>
{view}
<div id={messagePanelId}><MessagePanel /></div>
{view}
<div id={messagePanelId}><MessagePanel /></div>
</Fragment>
);
}

View File

@@ -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
// <https://www.gnu.org/licenses/>.
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 (
<GroupBox title="Create Account">
<form onSubmit={this.onSubmit}>
<Row>
<Col width="12">
<label>
Username
<input onChange={this.onChange} required name="username" type="text" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
<label>
Email (optional)
<input onChange={this.onChange} name="email" type="email" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
<label>
Password
<input onChange={this.onChange} required name="password" type="password" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
<label>
Confirm Password
<input onChange={this.onChange} required name="confirmPassword" type="password" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
{!this.state.loading ? (
<>
{this.props.errors.map((err, i) => (
<p key={i}>
Error: {err}
</p>
))}
<Button type="submit">Create Account</Button>
</>
) : (
<span>Creating Account...</span>
)}
</Col>
</Row>
</form>
</GroupBox>
);
}
}

View File

@@ -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 (
<Fragment>
<Button size='large' className='shadow' onClick={this.props.showNewGame}>
New Game
</Button>
{this.props.start.start.games.length > 0 ? (
<Button size='large' className='shadow' onClick={this.props.showJoinGame}>
Join Game
</Button>
) : (<Fragment />)}
<Button size='large' className='shadow' onClick={this.props.showNewGame}>
New Game
</Button>
{(this.props.start.start.games.length > 0) || (this.props.start.start.openGames.length > 0) ? (
<Button size='large' className='shadow' onClick={this.props.showJoinGame}>
Join Game
</Button>
) : (<Fragment />)}
{this.props.start.start.user ? (
<Button size='large' className='shadow sign-out-button' onClick={this.signOut}>
Sign Out
</Button>
) : (<></>)}
</Fragment>
);
}

View File

@@ -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 (
<GroupBox title='Farms'>
<b>Ridges</b>:
{ridgeNames.map((ridge, idx) => (
<div key={idx} className={"farms-ridge player-" + ridges['ridge' + (idx + 1)]}>
<span>{ridge}</span>
<span className='num-cows'>{'(' + ((idx + 2) * 10) + ' cows)'}</span>
</div>
))}
<br />
{this.props.otherPlayers
.map(p => (
<div key={p.player.name}>
<PlayerColorIcon color={p.player.color} />{'\u00A0'}
<b>{p.player.name}</b> <PlayerResources player={p.player} />
<br /> <br />
</div>))}
{ridgeNames.map((ridge, idx) => (
<div key={idx} className={"farms-ridge player-" + ridges['ridge' + (idx + 1)]}>
<span>{ridge}</span>
<span className='num-cows'>{'(' + ((idx + 2) * 10) + ' cows)'}</span>
</div>
))}
<br />
{this.props.otherPlayers
.map(p => (
<div key={p.player.name}>
<PlayerColorIcon color={p.player.color} />{'\u00A0'}
<b>{p.player.name}</b> <PlayerResources player={p.player} />
<br /> <br />
</div>))}
</GroupBox>
);
}
@@ -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 (
<Fragment>
License Creative Commons <a href='https://creativecommons.org/licenses/by/3.0/us/legalcode'>CCBY</a>
License Creative Commons <a href={`https://creativecommons.org/licenses/by/${this.props.version ? this.props.version : 3}.0/us/legalcode`}>CCBY</a>
</Fragment>
);
}
@@ -880,6 +881,9 @@ class Misc extends React.Component {
<li>
<img src={TractorFullImg} /> <img src={TractorImg} /> Copyright Nick Roach with modifications by Thomas Hintz - License <a href='GPL http://www.gnu.org/copyleft/gpl.html'>GPL</a>
</li>
<li>
<img src={VolcanoImg} /> Copyright <a href="https://thenounproject.com/Maludk/">Laymik</a> - <CCBY />
</li>
<li>
<img src={CornImg} /> <img src={FruitImg} /> Copyright <a href='https://madexmade.com/'>Made</a> - <CCBY />
</li>
@@ -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 (
<GroupBox title={this.props.name + ' is rolling!'}>
<Die decay={true} num={this.props.num} ms={2000} roll={true}
rolls={this.props.rolls}
showScreen={this.props.showScreen}
skip={this.props.skip}
autoSkip={this.props.autoSkip}
@@ -1354,6 +1364,7 @@ class Harvest extends React.Component {
break;
case 'roll':
view = (<Die decay={true} num={this.props.rolled} ms={2000} roll={true}
rolls={this.props.rolls}
showScreen={() => 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 = (<Harvest rolled={this.props.ui.actionValue.rolled}
rolls={this.props.ui.actionValue.rolls}
player={this.props.player}
currentPlayer={currentPlayer}
harvestMult={this.props.ui.actionValue.harvestMult}
@@ -1635,6 +1647,7 @@ class Action extends React.Component {
(this.props.ui.actionValue.to < this.props.ui.actionValue.from ?
this.props.ui.actionValue.from - 49 : this.props.ui.actionValue.from);
view = (<Rolling num={roll}
rolls={this.props.ui.actionValue.rolls}
name={this.props.game.currentPlayer}
showScreen={(this.props.player.name === this.props.game.currentPlayer ||
currentPlayer.ai)
@@ -1764,14 +1777,16 @@ class AlertOverlay extends React.Component {
render() {
return (
<div className={'alert-overlay' + (this.state.visible ? '' : ' hidden') }>
<div onClick={this.hide} className='alert-overlay-hide'>
<FontAwesomeIcon icon={faTimes} />
</div>
{!this.props.preventHiding ? (
<div onClick={this.hide} className='alert-overlay-hide'>
<FontAwesomeIcon icon={faTimes} />
</div>
) : (<></>)}
<div className='alert-overlay-contents'>
{this.props.children}
<br />
<Button onClick={this.buttonClick}>{this.props.buttonText}</Button>
<a onClick={this.hide}>close</a>
{!this.props.preventHiding ? (<a onClick={this.hide}>close</a>) : (<></>)}
</div>
</div>
);
@@ -2015,6 +2030,30 @@ class BoardApp extends React.Component {
</Fragment>
</AlertOverlay>
);
} else if (alert && alert.type === ALERTS.preGame) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Start Game'
hideHandler={() => 'nothing'}
preventHiding={true}
handler={startGame}>
<Fragment>
<h1>Pre Game</h1>
<p>When all players have joined click 'Start Game'!</p>
<h3>Players</h3>
<ul>
<li>{this.props.player.name}</li>
{this.props.game.otherPlayers.map((p, i) => (
<li key={i}>
{p.player.name}
</li>
))}
</ul>
</Fragment>
</AlertOverlay>
);
} else if (alert && alert.type === ALERTS.proposedTrade) {
alertOverlay = (
<AlertOverlay visible={true}

View File

@@ -30,7 +30,8 @@ import { itemCard, fateCard } from 'game.js'
export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, handleMessage,
nextAction, buyUncleBert, actionsFinished, skip, endAiTurn }
nextAction, buyUncleBert, actionsFinished, skip, endAiTurn,
startGame }
let store;
let movingTimer = 0;
@@ -44,6 +45,13 @@ function handleMessage(evt) {
return;
}
batch(() => {
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) {

View File

@@ -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 (
<GroupBox title={(
<Fragment>
<a href="#" onClick={this.handleBack}>
<FontAwesomeIcon icon={faArrowCircleLeft} />
</a>
Join Game
<a href="#" onClick={this.handleBack}>
<FontAwesomeIcon icon={faArrowCircleLeft} />
</a>
Join Game
</Fragment>
)}>
<Row>
<Row>
<Col width='12'>
{this.state.screen === JoinGameScreens.list ?
(<ul>
{this.state.screen === JoinGameScreens.list ?
(<>
<h3>My Games</h3>
{(!this.props.user && !this.state.showSignIn) ? (
<a onClick={this.showSignIn}>Sign In to see your games</a>
) : (<></>)}
{(!this.props.user && this.state.showSignIn) ? (
<>
<hr />
<LoginOrCreateAccount login={this.props.login}
createAccount={this.props.createAccount}
errors={this.props.errors}
showLogin={true}
/>
<hr />
</>
) : (<></>)}
<ul>
{this.props.games
.map((g, i) =>
(<li key={i}>
<a onClick={e => {
e.preventDefault();
this.handleClickGame(g); }}>{g.name}</a>
</li>))}
</ul>)
: (
<Fragment>
<h3><b>Game:</b> {this.state.game.name}</h3>
<h4>Join as existing player:</h4>
<ul>
{this.state.game.players.map((p, i) =>
(<li key={i}>
<a onClick={this.handleJoinAsExisting}>
{p}
</a>
</li>))}
</ul>
<NewGame colors={this.state.game.colors}
button={'Join'}
showGameName={false}
gameName={this.state.game.name}
gameId={this.state.game.id}
type={'join-game'}
hideBack={true}
title={'Join as New Player'} />
</Fragment>)}
</Col>
</Row>
.map((g, i) =>
(<li key={i}>
<a onClick={(e) => this.handleJoinAsExisting(e, g.id)}>{g.name}</a>
</li>))}
</ul>
<h3>Open Games</h3>
<ul>
{this.props.openGames
.map((g, i) =>
(<li key={i}>
<a onClick={e => {
e.preventDefault();
this.handleClickGame(g); }}>{g.name}</a>
</li>))}
</ul>
</>
)
: (
<Fragment>
<h3><b>Game:</b> {this.state.game.name}</h3>
<NewGame colors={this.state.game.colors}
button={'Join'}
showGameName={false}
gameName={this.state.game.name}
gameId={this.state.game.id}
type={'join-game'}
hideBack={true}
createAccount={this.props.createAccount}
login={this.props.login}
errors={this.props.errors}
title={'Join as New Player'} />
</Fragment>)}
</Col>
</Row>
</GroupBox>
);
}

View File

@@ -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
// <https://www.gnu.org/licenses/>.
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 ? (
<Login errors={this.props.errors} login={this.props.login} />
) : (
<CreateAccount errors={this.props.errors}
createAccount={this.props.createAccount} />
)}
<div className="center">
<a onClick={this.toggleLogin}>{this.state.showLogin ? 'Create Account' : 'Login'}</a>
</div>
</>
);
}
}

View File

@@ -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
// <https://www.gnu.org/licenses/>.
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 (
<GroupBox title="Login">
<form onSubmit={this.onSubmit}>
<Row>
<Col width="12">
<label>
Username
<input onChange={this.onChange} required name="username" type="text" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
<label>
Password
<input onChange={this.onChange} required name="password" type="password" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
{!this.state.loading ? (
<>
{this.props.errors.map((err, i) => (
<p key={i}>
Error: {err}
</p>
))}
<Button type="submit">Login</Button>
</>
) : (
<span>Logging in...</span>
)}
</Col>
</Row>
</form>
</GroupBox>
);
}
}

View File

@@ -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 ? (
<Fragment>
<a onClick={this.handleBack}>
<FontAwesomeIcon icon={faArrowCircleLeft} />
@@ -129,97 +129,97 @@ class NewGame extends React.Component {
mainScreenClass = !this.state.showSettings ? '' : 'hidden';
return (
<GroupBox title={titleBar}>
<form onSubmit={this.handleSubmit}>
<div className={mainScreenClass}>
<Row>
<Col width='12'>
<label>Your Name
<input type='text' name='playerName'
required
value={this.state.playerName}
onChange={this.handleInputChange} />
</label>
</Col>
</Row>
<Row>
<Col width='12'>
<label>Your Color</label>
{colors}
<br /><br />
</Col>
</Row>
{gameName}
</div>
<div className={settingsClass}>
<InputRow label='Down Payment'
name='downPayment'
min={0}
max={1}
step={0.1}
value={this.state.downPayment}
onChange={this.handleInputChange} />
<InputRow label='Loan Interest'
name='loanInterest'
min={0}
max={1}
step={0.1}
value={this.state.loanInterest}
onChange={this.handleInputChange} />
<InputRow label='Maximum Debt'
name='maxDebt'
min={0}
step={5000}
value={this.state.maxDebt}
onChange={this.handleInputChange} />
<InputRow label='Audit Threshold'
name='auditThreshold'
min={0}
step={25000}
value={this.state.auditThreshold}
onChange={this.handleInputChange} />
<InputRow label='Starting Cash'
name='startingCash'
min={0}
step={1000}
value={this.state.startingCash}
onChange={this.handleInputChange} />
<InputRow label='Starting Debt'
name='startingDebt'
min={0}
step={1000}
value={this.state.startingDebt}
onChange={this.handleInputChange} />
<Row>
<Col width='12'>
<label>
<input type='checkbox'
checked={this.state.trade}
onChange={this.handleInputChange}
name='trade' />
Enable Trading
</label>
</Col>
</Row>
</div>
<Row>
<Col width='12'>
<div className='new-game-submit-container'>
<Button type='submit'>{this.props.button} Game</Button>
{this.props.showGameName ? (
<a onClick={this.toggleSettings}>
<FontAwesomeIcon icon={faCog} size='lg' />
</a>
) : (<Fragment />)}
</div>
</Col>
</Row>
</form>
{this.props.user ? (
<form onSubmit={this.handleSubmit}>
<div className={mainScreenClass}>
<Row>
<Col width='12'>
<label>Your Color</label>
{colors}
<br /><br />
</Col>
</Row>
{gameName}
</div>
<div className={settingsClass}>
<InputRow label='Down Payment'
name='downPayment'
min={0}
max={1}
step={0.1}
value={this.state.downPayment}
onChange={this.handleInputChange} />
<InputRow label='Loan Interest'
name='loanInterest'
min={0}
max={1}
step={0.1}
value={this.state.loanInterest}
onChange={this.handleInputChange} />
<InputRow label='Maximum Debt'
name='maxDebt'
min={0}
step={5000}
value={this.state.maxDebt}
onChange={this.handleInputChange} />
<InputRow label='Audit Threshold'
name='auditThreshold'
min={0}
step={25000}
value={this.state.auditThreshold}
onChange={this.handleInputChange} />
<InputRow label='Starting Cash'
name='startingCash'
min={0}
step={1000}
value={this.state.startingCash}
onChange={this.handleInputChange} />
<InputRow label='Starting Debt'
name='startingDebt'
min={0}
step={1000}
value={this.state.startingDebt}
onChange={this.handleInputChange} />
<Row>
<Col width='12'>
<label>
<input type='checkbox'
checked={this.state.trade}
onChange={this.handleInputChange}
name='trade' />
Enable Trading
</label>
</Col>
</Row>
</div>
<Row>
<Col width='12'>
<div className='new-game-submit-container'>
<Button type='submit'>{this.props.button} Game</Button>
{this.props.showGameName ? (
<a onClick={this.toggleSettings}>
<FontAwesomeIcon icon={faCog} size='lg' />
</a>
) : (<Fragment />)}
</div>
</Col>
</Row>
</form>
) : (
<>
<span>Sign in or create account to continue</span>
<LoginOrCreateAccount login={this.props.login}
createAccount={this.props.createAccount}
errors={this.props.errors}
/>
</>
)}
</GroupBox>
);
}
}
export default connect(
null,
state => state.start.start,
{ startOrJoinGame, start }
)(NewGame)

View File

@@ -17,4 +17,7 @@
// <https://www.gnu.org/licenses/>.
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';

View File

@@ -16,16 +16,32 @@
// along with the Alpha Centauri Farming project. If not, see
// <https://www.gnu.org/licenses/>.
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 };
}

View File

@@ -16,11 +16,15 @@
// along with the Alpha Centauri Farming project. If not, see
// <https://www.gnu.org/licenses/>.
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:

View File

@@ -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' }

View File

@@ -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(
<Provider store={store}>
<App />
<App createAccount={createAccount}
login={login}
logout={logout}
/>
</Provider>,
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',

134
src/server/db.scm Normal file
View File

@@ -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)))

View File

@@ -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 <player>
'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 <game> '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))

View File

@@ -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);
}

View File

@@ -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) {