Using sqlite database, mt proctor animation.
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
108
src/components/create-account/CreateAccount.jsx
Normal file
108
src/components/create-account/CreateAccount.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
90
src/components/login/Login.jsx
Normal file
90
src/components/login/Login.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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' }
|
||||
|
||||
23
src/main.jsx
23
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(
|
||||
<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
134
src/server/db.scm
Normal 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)))
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user