9 Commits

16 changed files with 616 additions and 293 deletions

View File

@@ -37,14 +37,14 @@ install:
interactive:
cd dist/ && csi -include-path $(assets) -include-path ../src/server -s farm.scm
src/server/farm: src/server/farm.scm
src/server/farm: src/server/farm.scm src/server/db.scm
cd src/server/ && csc -include-path ../../$(assets) -O3 farm.scm
farm:
make src/server/farm
runprod:
cd dist/ && chmod +x farm && ./farm
cd dist/ && chmod +x farm && ./farm -:a50
upload:
rsync -rtvz dist/ $(SERVER):~/farm

View File

@@ -47,7 +47,7 @@
"20 Cows on Peridier Ridge"))))
(define *ff-text*
'(((p (img (@ (src "53c3a93b0867eee67b9b9f6ebc4c1f4a.gif") (style "float: left;"))) "Natural Disaster--The Solar Winds break through the atmosphere. You are luckily shielded by Mt Proctor. Your hay survives and jumps in price. " (b "COLLECT $500 per Hay acre") ". To see if they escaped, other players must roll. Odd: escaped, Even: hit. " (b "Wind hit players must clean up all acres at $100 per acre.")))
'(((p (img (@ (src "./assets/img/volcano2.53c3a93b0867eee67b9b9f6ebc4c1f4a.gif") (style "float: left;"))) "Natural Disaster--The Solar Winds break through the atmosphere. You are luckily shielded by Mt Proctor. Your hay survives and jumps in price. " (b "COLLECT $500 per Hay acre") ". To see if they escaped, other players must roll. Odd: escaped, Even: hit. " (b "Wind hit players must clean up all acres at $100 per acre.")))
((p "Planetary Disaster Fund comes through." (p (b "COLLECT $100 per Grain acre."))))
((p "Another high wind spring and your wheat didn't get sprayed. Weeds take over and cut your harvest in half. Hold this card through Wheat Harvest for this year."))
((p "Kept back some of your cows and Proxima B steak goes viral.") (p (b "COLLECT $2,000 if you have cows.")))

45
assets/img/cake.svg Normal file
View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 80.125 79.983627"
xml:space="preserve"
id="svg4786"
sodipodi:docname="cake.svg"
width="80.125"
height="79.983627"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata4792"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs4790" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1375"
id="namedview4788"
showgrid="false"
inkscape:zoom="9.424"
inkscape:cx="40.23"
inkscape:cy="27.714"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4786" /><path
d="M 79.125,21.572625 H 62.26 v -7.523 c 2.469,-0.565 4.319,-2.775 4.319,-5.4129999 0,-2.758 -4.007,-7.411 -4.811,-8.31599999 -0.379,-0.428 -1.116,-0.427 -1.496,0 -0.803,0.90499999 -4.81,5.55899999 -4.81,8.31599999 0,2.8059999 2.092,5.1269999 4.798,5.4989999 v 7.437 H 48.247 v -7.523 c 2.469,-0.565 4.319,-2.775 4.319,-5.4129999 0,-2.758 -4.007,-7.411 -4.811,-8.31599999 -0.38,-0.428 -1.116,-0.427 -1.496,0 -0.803,0.90499999 -4.81,5.55899999 -4.81,8.31599999 0,2.8059999 2.092,5.1269999 4.798,5.4989999 v 7.437 H 34.233 v -7.523 c 2.469,-0.565 4.319,-2.775 4.319,-5.4129999 0,-2.758 -4.007,-7.411 -4.811,-8.31599999 -0.379,-0.428 -1.116,-0.427 -1.496,0 -0.803,0.90499999 -4.81,5.55899999 -4.81,8.31599999 0,2.8059999 2.092,5.1269999 4.798,5.4989999 v 7.437 H 20.219 v -7.523 c 2.469,-0.565 4.319,-2.775 4.319,-5.4129999 0,-2.758 -4.007,-7.411 -4.811,-8.31599999 -0.38,-0.428 -1.116,-0.427 -1.496,0 -0.803,0.90499999 -4.81,5.55899999 -4.81,8.31599999 0,2.8059999 2.092,5.1269999 4.798,5.4989999 v 7.437 H 1.129 c -0.552,0 -1,0.447 -1,1 v 8.891 c 0,0.062 0.006,0.123 0.017,0.182 0.062,3.042 1.68,5.705 4.084,7.237 v 26.175 H 3.543 c -1.954,0 -3.543,1.59 -3.543,3.543 v 2.023 c 0,1.683 1.181,3.089 2.757,3.449 0.007,0.027 0.002,0.054 0.012,0.081 l 1.717,4.771 c 0.254,0.702 1.133,1.059 2.613,1.059 h 65.803 c 1.48,0 2.359,-0.356 2.613,-1.06 l 1.717,-4.771 c 0.01,-0.027 0.005,-0.054 0.012,-0.081 1.575,-0.36 2.756,-1.766 2.756,-3.449 v -2.023 c 0,-1.953 -1.589,-3.543 -3.543,-3.543 H 75.77 v -26.013 c 2.601,-1.53 4.355,-4.351 4.355,-7.581 v -8.891 c 0,-0.552 -0.448,-0.999 -1,-0.999 z M 57.462,8.6356251 c 0,-1.337 1.953,-4.153 3.558,-6.11 1.668,2.035 3.559,4.833 3.559,6.11 0,1.9619999 -1.596,3.5579999 -3.559,3.5579999 -1.961,0 -3.558,-1.596 -3.558,-3.5579999 z m -14.013,0 c 0,-1.337 1.953,-4.153 3.558,-6.11 1.668,2.035 3.559,4.833 3.559,6.11 0,1.9619999 -1.596,3.5579999 -3.559,3.5579999 -1.962,0 -3.558,-1.596 -3.558,-3.5579999 z m -14.014,0 c 0,-1.337 1.953,-4.153 3.558,-6.11 1.668,2.035 3.559,4.833 3.559,6.11 0,1.9619999 -1.596,3.5579999 -3.559,3.5579999 -1.962,0 -3.558,-1.596 -3.558,-3.5579999 z m -14.014,0 c 0,-1.337 1.953,-4.153 3.558,-6.11 1.668,2.035 3.559,4.833 3.559,6.11 0,1.9619999 -1.596,3.5579999 -3.559,3.5579999 -1.961,0 -3.558,-1.596 -3.558,-3.5579999 z M 72.901,77.983625 H 7.099 c -0.38,0 -0.672,-0.038 -0.854,-0.077 l -1.346,-3.739 h 70.202 l -1.346,3.739 c -0.181,0.039 -0.474,0.077 -0.854,0.077 z m 5.099,-9.384 v 2.023 c 0,0.852 -0.692,1.544 -1.543,1.544 H 3.543 c -0.851,0 -1.543,-0.692 -1.543,-1.544 v -2.023 c 0,-0.851 0.692,-1.543 1.543,-1.543 h 72.914 c 0.851,0 1.543,0.692 1.543,1.543 z m -4.23,-3.543 H 6.23 v -25.229 c 0.855,0.278 1.765,0.433 2.712,0.433 3.383,0 6.327,-1.919 7.798,-4.727 1.472,2.808 4.415,4.727 7.798,4.727 3.383,0 6.327,-1.919 7.798,-4.727 1.472,2.808 4.415,4.727 7.798,4.727 3.383,0 6.327,-1.919 7.798,-4.727 1.472,2.808 4.415,4.727 7.798,4.727 3.383,0 6.326,-1.919 7.798,-4.727 1.472,2.808 4.415,4.727 7.798,4.727 0.848,0 1.666,-0.127 2.442,-0.352 v 25.148 z m 4.355,-33.594 c 0,3.748 -3.049,6.798 -6.798,6.798 -3.749,0 -6.798,-3.05 -6.798,-6.798 0,-0.553 -0.448,-1 -1,-1 -0.552,0 -1,0.447 -1,1 0,3.748 -3.049,6.798 -6.798,6.798 -3.749,0 -6.798,-3.05 -6.798,-6.798 0,-0.553 -0.448,-1 -1,-1 -0.552,0 -1,0.447 -1,1 0,3.748 -3.05,6.798 -6.798,6.798 -3.748,0 -6.798,-3.05 -6.798,-6.798 0,-0.553 -0.448,-1 -1,-1 -0.552,0 -1,0.447 -1,1 0,3.748 -3.05,6.798 -6.798,6.798 -3.748,0 -6.798,-3.05 -6.798,-6.798 0,-0.553 -0.448,-1 -1,-1 -0.552,0 -1,0.447 -1,1 0,3.748 -3.05,6.798 -6.798,6.798 -3.748,0 -6.798,-3.05 -6.798,-6.798 0,-0.059 -0.005,-0.116 -0.015,-0.172 v -7.719 h 75.996 v 7.891 z"
id="path4780"
inkscape:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

53
package-lock.json generated
View File

@@ -2212,8 +2212,7 @@
"array-uniq": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
"dev": true
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
},
"array-unique": {
"version": "0.3.2",
@@ -2667,8 +2666,7 @@
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
},
"bignumber.js": {
"version": "2.4.0",
@@ -3787,6 +3785,16 @@
"integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=",
"dev": true
},
"css-url-relative-plugin": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-url-relative-plugin/-/css-url-relative-plugin-1.0.0.tgz",
"integrity": "sha1-T4FVU2I2Tw8ZG9HFKLwnsKdqFGw=",
"requires": {
"loader-utils": "^1.1.0",
"parse-import": "^2.0.0",
"webpack-sources": "^1.1.0"
}
},
"css-what": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
@@ -4189,8 +4197,7 @@
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
"dev": true
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
},
"end-of-stream": {
"version": "1.4.4",
@@ -5486,6 +5493,15 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"get-imports": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/get-imports/-/get-imports-1.0.0.tgz",
"integrity": "sha1-R8C07piTUWQsVJdxk79Pyqv1N48=",
"requires": {
"array-uniq": "^1.0.1",
"import-regex": "^1.1.0"
}
},
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
@@ -6018,6 +6034,11 @@
"resolve-cwd": "^2.0.0"
}
},
"import-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/import-regex/-/import-regex-1.1.0.tgz",
"integrity": "sha1-pVxS5McFx2XKIQ6SQqBrvMiqf2Y="
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -6500,7 +6521,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
@@ -6619,7 +6639,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
"integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^2.0.0",
@@ -6918,8 +6937,7 @@
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"minipass": {
"version": "3.1.1",
@@ -7681,6 +7699,14 @@
"integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==",
"dev": true
},
"parse-import": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parse-import/-/parse-import-2.0.0.tgz",
"integrity": "sha1-KyR0Aw4AirmNt2xLy/TbWucwb18=",
"requires": {
"get-imports": "^1.0.0"
}
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@@ -9694,8 +9720,7 @@
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
"dev": true
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
},
"source-map": {
"version": "0.5.7",
@@ -10797,7 +10822,6 @@
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
"dev": true,
"requires": {
"source-list-map": "^2.0.0",
"source-map": "~0.6.1"
@@ -10806,8 +10830,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},

View File

@@ -46,6 +46,7 @@
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/react-fontawesome": "^0.1.8",
"cookies-js": "^1.2.3",
"css-url-relative-plugin": "^1.0.0",
"mobx": "^5.15.3",
"mobx-react": "^6.1.4",
"react": "^16.12.0",

View File

@@ -22,6 +22,7 @@ import CowImg from './../../../assets/img/cow.svg'
import HayImg from './../../../assets/img/hay.svg'
import WheatImg from './../../../assets/img/wheat.svg'
import TractorImg from './../../../assets/img/tractor-icon.svg'
import CakeImg from './../../../assets/img/cake.svg'
import TractorFullImg from './../../../assets/img/tractor-with-spikes.svg'
import HarvesterImg from './../../../assets/img/harvester.svg'
import VolcanoImg from './../../../assets/img/volcano2.gif'
@@ -46,21 +47,22 @@ import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer,
setMovingSkip } from './actions.js'
import { buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit,
buyUncleBert, skip, endAiTurn, startGame } from './interface.js'
buyUncleBert, skip, endAiTurn, startGame, readyToStart,
leaveGame } 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) {
@@ -288,6 +290,12 @@ class PlayerResources extends React.Component {
}
}
/* {' '}
* <ResourceUnit img={CakeImg} h='240' s='100' label='Birthday'
* amount={player.assets.birthday ? player.assets.birthday : 0}>
* {player.assets.birthday ? player.assets.birthday : 0}
* </ResourceUnit> */
// http://stackoverflow.com/questions/149055
function formatMoney(n) {
return n.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, '$1,').slice(0, -2); }
@@ -343,6 +351,7 @@ class PlayerSummary extends React.Component {
<PlayerTurnContainer player={player}
game={this.props.game}
ui={this.props.ui}
hideForLarge={this.props.hideForLarge}
screen={this.props.screen}
showScreen={this.props.showScreen}/>
</GroupBox>
@@ -421,7 +430,9 @@ class PlayerTurnContainer extends React.Component {
<br />
</>
) : (<></>)}
{view}
<div className={this.props.hideForLarge ? 'hide-for-large' : ''}>
{view}
</div>
</div>
</Col>
</Row>
@@ -885,7 +896,7 @@ class Misc extends React.Component {
<img src={VolcanoImg} /> Copyright <a href="https://thenounproject.com/Maludk/">Laymik</a> with modifications by Thomas Hintz - <CCBY />
</li>
<li>
<img src={CornImg} /> <img src={FruitImg} /> Copyright <a href='https://madexmade.com/'>Made</a> - <CCBY />
<img src={CornImg} /> <img src={FruitImg} /> <img src={CakeImg} /> Copyright <a href='https://madexmade.com/'>Made</a> - <CCBY />
</li>
<li>
<img src={CowImg} /> Copyright <a href='https://thenounproject.com/rivercon/'>rivercon</a> - <CCBY />
@@ -1488,6 +1499,24 @@ class Moving extends React.Component {
}
class Action extends React.Component {
state = {
bertChoice: 'nothing'
}
setBertChoice = (e) => {
this.setState({ bertChoice: e.target.value });
}
bertSubmit = (e) => {
e.preventDefault();
if (this.state.bertChoice === 'accept') {
buyUncleBert();
this.props.showNextAction();
} else if (this.state.bertChoice === 'deny') {
this.props.showNextAction();
}
}
render() {
let view, buttons;
const { currentPlayer } = this.props;
@@ -1512,44 +1541,54 @@ class Action extends React.Component {
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break;
case 'farmers-fate':
view = (
<div className='game-card'>
<GroupBox title={fateCard}>
<div className='card'
dangerouslySetInnerHTML={{ __html: this.props.ui.actionValue }} />
</GroupBox>
</div>
);
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break;
case 'ff-uncle-bert':
const ffButtons = (
<Fragment>
{this.props.player.cash >= 10000 ? (
<Button onClick={() => { buyUncleBert(); this.props.showNextAction(); }}>
Yes, take over for $10,000!
</Button>
) : (<Fragment />)}
<Button onClick={() => this.props.showNextAction()}>
No, continue on
</Button>
</Fragment>
);
view = (
<div className='game-card'>
<GroupBox title={fateCard}>
<div className='card'
dangerouslySetInnerHTML={{ __html: this.props.ui.actionValue }} />
</GroupBox>
</div>
);
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break;
case 'ff-uncle-bert':
const { cash } = this.props.player;
const ffButtons = (
<>
{cash < 10000 ? (
<Button onClick={() => this.props.showScreen(SCREENS.loans)}>Raise ${formatMoney(10000 - cash)} Now</Button>
) : (<></>)}
<form onSubmit={this.bertSubmit}>
<label>
<input type="radio" name="bert" value="accept" onClick={this.setBertChoice} />
Yes, take over for $10,000!
</label>
<label>
<input type="radio" name="bert" value="deny" onClick={this.setBertChoice} />
No, continue on
</label>
<Button type="submit" disabled={this.state.bertChoice === 'nothing' || this.state.bertChoice === 'accept' && cash < 10000}>Submit</Button>
</form>
</>
);
view = (
<GroupBox title={`Uncle Bert's inheritance`}>
<div className='center'>
<p>
{currentPlayer.cash >= 10000 ?
`You have enough cash to take over Uncle Bert's farm!` :
`You must raise another $` +
formatMoney(10000 - currentPlayer.cash) +
` to be able to take over Uncle Berts farm!`
}
(
<>
You must raise another $
{formatMoney(10000 - currentPlayer.cash) + ' '}
to be able to take over Uncle Berts farm!
</>
)}
</p>
<p>
<div>
{(this.props.player.name === this.props.game.currentPlayer) ?
ffButtons : (<Fragment />)}
</p>
</div>
</div>
</GroupBox>
);
@@ -1772,6 +1811,11 @@ class AlertOverlay extends React.Component {
this.hide(e);
this.props.handler();
}
cancelButtonClick = e => {
e.preventDefault();
this.props.cancelHandler();
}
// <label><input type='checkbox' onClick={this.hidePermanent} /> {`Don't show again`}</label>
render() {
@@ -1785,7 +1829,17 @@ class AlertOverlay extends React.Component {
<div className='alert-overlay-contents'>
{this.props.children}
<br />
<Button onClick={this.buttonClick}>{this.props.buttonText}</Button>
<div>
<Button onClick={this.buttonClick} disabled={!!this.props.disabled}>{this.props.buttonText}</Button>
{this.props.cancelButtonText ? (
<>
{' '}
<Button onClick={this.cancelButtonClick} disabled={!!this.props.cancelDisabled}>
{this.props.cancelButtonText}
</Button>
</>
): (<></>)}
</div>
{!this.props.preventHiding ? (<a onClick={this.hide}>close</a>) : (<></>)}
</div>
</div>
@@ -1878,9 +1932,47 @@ class Info extends React.Component {
}
}
class StartGame extends React.Component {
render() {
const { auditThreshold, downPayment, loanInterest, maxDebt, startingOtbs, startingCash, startingDebt } = this.props.game.settings;
const { name } = this.props.game;
return (
<>
<h1>Lobby</h1>
<p>
<b>Game</b>: {name}
</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>
<h4>Game Settings</h4>
<ul>
<li><b>Audit Threshold</b>: ${formatMoney(auditThreshold)}</li>
<li><b>Max Debt</b>: ${formatMoney(maxDebt)}</li>
<li><b>Loan Interest</b>: {loanInterest * 100}%</li>
<li><b>Required Down Payment</b>: ${formatMoney(downPayment)}</li>
<li><b>Starting {itemCardShort}</b>: {startingOtbs}</li>
<li><b>Starting Cash</b>: ${formatMoney(startingCash)}</li>
<li><b>Starting Debt</b>: ${formatMoney(startingDebt)}</li>
</ul>
<label>
<input type="checkbox" onChange={this.props.toggleReady} />
Ready to start
</label>
</>
);
}
}
const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms',
cards: 'cards', trade: 'trade', loans: 'loans',
action: 'action', info: 'info' };
action: 'action', info: 'info', error: 'error' };
class BoardApp extends React.Component {
iconToScreen = { user: SCREENS.summary, 'window-restore': SCREENS.cards,
@@ -1895,7 +1987,8 @@ class BoardApp extends React.Component {
screen: SCREENS.summary,
card: props.ui.card,
timerId: false,
currentPlayer: this.props.player
currentPlayer: this.props.player,
readyToStart: false
};
this.myRef = React.createRef();
this.actionRef = React.createRef();
@@ -1943,6 +2036,10 @@ class BoardApp extends React.Component {
.find(p => p.player.name === this.props.game.currentPlayer).player;
this.setState({ currentPlayer });
}
if (!prevProps.ui.exn && this.props.ui.exn) {
this.setState({ screen: SCREENS.error });
}
}
componentDidMount() {
@@ -1996,6 +2093,11 @@ class BoardApp extends React.Component {
}
}
startGameToggleReady = () => {
this.setState(state => { return { readyToStart: !state.readyToStart }; });
readyToStart();
}
render() {
let alertOverlay;
const alert = this.props.ui.unhandledAlert;
@@ -2037,21 +2139,16 @@ class BoardApp extends React.Component {
alertHandled={this.props.alertHandled}
buttonText='Start Game'
hideHandler={() => 'nothing'}
cancelButtonText='Leave Game'
cancelHandler={leaveGame}
cancelDisabled={this.state.readyToStart}
preventHiding={true}
disabled={!this.props.game.readyToStart}
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>
<StartGame player={this.props.player}
game={this.props.game}
toggleReady={this.startGameToggleReady}
/>
</AlertOverlay>
);
} else if (alert && alert.type === ALERTS.proposedTrade) {
@@ -2165,6 +2262,7 @@ class BoardApp extends React.Component {
<PlayerSummary player={this.props.player}
ui={this.props.ui}
screen={this.state.screen}
hideForLarge={true}
game={this.props.game} showScreen={this.showScreen} />
</div>
<div className={this.tabClass(SCREENS.action)}>
@@ -2207,6 +2305,11 @@ class BoardApp extends React.Component {
<div className={this.tabClass(SCREENS.info)}>
<Info harvestTable={this.props.ui.harvestTable} game={this.props.game} />
</div>
<div className={this.tabClass(SCREENS.error)}>
<h3>Error</h3>
<p>A server error occured.</p>
<p><a href="/?reload-game=true">Reload game</a></p>
</div>
</div>
<div className='static-tab-container show-for-large'>
<div className='tab show'>

View File

@@ -39,3 +39,4 @@ export const AUTO_SKIP = 'auto-skip'
export const MESSAGE = 'message'
export const SET_HARVEST_TABLE = 'set-harvest-table'
export const SET_MOVING_SKIP = 'set-moving-skip'
export const SERVER_ERROR = 'server-error'

View File

@@ -21,13 +21,14 @@ import { UPDATE_GAME, UPDATE_PLAYER, GAME_STATE, SET_SELECTED_CARD, SET_CARDS,
MP_MOUSE, SET_MP_DIMS, MARK_ACTION_CHANGE_HANDLED, SET_NEXT_ACTION,
MOVE_PLAYER, NEXT_UI_ACTION, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED,
AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE, SET_CARD_ERROR,
SET_MOVING_SKIP } from './actionTypes.js'
SET_MOVING_SKIP, SERVER_ERROR } from './actionTypes.js'
export { updateGame, updatePlayer, gameState, setSelectedCard, setCards,
spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace,
mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction,
markActionChangeHandled, nextUIActionSilent, alert, alertHandled,
autoSkip, message, setHarvestTable, setCardError, setMovingSkip }
autoSkip, message, setHarvestTable, setCardError, setMovingSkip,
serverError }
function updateGame(update) {
return { type: UPDATE_GAME,
@@ -135,3 +136,7 @@ function setHarvestTable(table) {
function setMovingSkip(skip) {
return { type: SET_MOVING_SKIP, skip };
}
function serverError(exn) {
return { type: SERVER_ERROR, exn };
}

View File

@@ -25,13 +25,13 @@ import { updateGame, updatePlayer, gameState, setSelectedCard, setCards,
movePlayer, setOldMessages, markActionChangeHandled,
mpMouse, rolled, setNextAction, nextUIAction, nextUIActionSilent, alert,
autoSkip, message, alertHandled, setHarvestTable,
setCardError, setMovingSkip } from './actions.js'
setCardError, setMovingSkip, serverError } from './actions.js'
import { itemCard, fateCard } from 'game.js'
export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, handleMessage,
nextAction, buyUncleBert, actionsFinished, skip, endAiTurn,
startGame }
startGame, readyToStart, leaveGame }
let store;
let movingTimer = 0;
@@ -42,9 +42,13 @@ function handleMessage(evt) {
if (data.event === 'error') {
console.log('error:' + data.exn);
store.dispatch(serverError(data.exn));
return;
}
batch(() => {
if (data.event === 'left-game') {
window.location.href = window.location.pathname;
}
if (data.game.state === GAME_STATES.preGame) {
store.dispatch(alert(ALERTS.preGame, '', 'pre-game'));
}
@@ -79,7 +83,7 @@ function handleMessage(evt) {
store.dispatch(movePlayer(data.player.space, -1, data.player.color));
store.dispatch(setHarvestTable(data.harvestTable));
}
// new player(s) added to game, put them on the board
// player(s) added or removed from game, put them on the board
if (data.game.otherPlayers.length !== store.getState().farm.game.otherPlayers.length) {
const otherPlayers = store.getState().farm.game.otherPlayers;
const newPlayers = data.game.otherPlayers.filter(
@@ -198,6 +202,14 @@ function startGame() {
sendCommand({ type: 'start-game' });
}
function readyToStart() {
sendCommand({ type: 'ready-to-start' });
}
function leaveGame() {
sendCommand({ type: 'leave-game' });
}
// TODO share with Board.jsx
// http://stackoverflow.com/questions/149055
function formatMoney(n) {

View File

@@ -22,7 +22,7 @@ import { UPDATE_GAME, UPDATE_PLAYER, GAME_STATE, SET_SELECTED_CARD, SET_CARDS,
SET_MP_DIMS, MOVE_PLAYER, SET_NEXT_ACTION, NEXT_UI_ACTION,
MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED,
AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE, SET_CARD_ERROR,
SET_MOVING_SKIP } from './actionTypes.js'
SET_MOVING_SKIP, SERVER_ERROR } from './actionTypes.js'
import { GAME_STATES } from '../../constants.js'
import { spaceContent, corners } from 'game.js'
@@ -88,7 +88,7 @@ const initialState = {
debt: 5000,
spaces,
state: GAME_STATES.turnEnded,
assets: { hay: 10, grain: 10, fruit: 0, cows: 0, harvester: 0, tractor: 0 },
assets: { hay: 10, grain: 10, fruit: 0, cows: 0, harvester: 0, tractor: 0, birthday: 0 },
color: '',
name: '',
ridges: { ridge1: 0, ridge2: 0, ridge3: 0, ridge4: 0 },
@@ -105,10 +105,15 @@ const initialState = {
state: GAME_STATES.preTurn,
turn: 0,
oldMessages: [],
name: '',
settings: { downPayment: 0.2,
loanInterest: 0.2,
maxDebt: 50000,
auditThreshold: 250000 }
auditThreshold: 250000,
startingOtbs: 2,
startingCash: 5000,
startingDebt: 5000 },
readyToStart: false
},
ui: { card: { type: 'no-card', contents: '', total: 0 },
cards: [],
@@ -124,7 +129,8 @@ const initialState = {
autoSkip: false,
playerSpaces: {},
movingSkip: false,
harvestTable: false },
harvestTable: false,
exn: false },
spaces: spaces,
space: null,
// message panel dimenions
@@ -228,6 +234,8 @@ export default function(state = initialState, action) {
return { ...state, ui: { ...state.ui, harvestTable: action.table }};
case SET_MOVING_SKIP:
return { ...state, ui: { ...state.ui, movingSkip: action.skip }};
case SERVER_ERROR:
return { ...state, ui: { ...state.ui, exn: action.exn }};
default:
return state;
}

View File

@@ -25,6 +25,8 @@ import LoginOrCreateAccount from '../login-or-create-account/LoginOrCreateAccoun
import { startOrJoinGame } from '../start/actions.js'
import { start } from '../app/actions.js'
import { itemCardShort } from 'game.js'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowCircleLeft, faCog } from '@fortawesome/free-solid-svg-icons'
@@ -63,6 +65,7 @@ class NewGame extends React.Component {
auditThreshold: 250000,
startingCash: 5000,
startingDebt: 5000,
startingOtbs: 2,
trade: true,
showLogin: false
};
@@ -71,10 +74,10 @@ class NewGame extends React.Component {
handleInputChange = e => {
const target = e.target,
value = target.type === 'checkbox' && target.name !== 'trade'
? target.name : target.value,
? target.name : target.value,
name = target.type === 'checkbox' && target.name !== 'trade'
? 'checkedColor' : target.name;
? 'checkedColor' : target.name;
this.setState({
[name]: value
@@ -98,122 +101,129 @@ class NewGame extends React.Component {
render() {
let titleBar = !this.props.hideBack ? (
<Fragment>
<a onClick={this.handleBack}>
<Fragment>
<a onClick={this.handleBack}>
<FontAwesomeIcon icon={faArrowCircleLeft} />
</a>
{this.props.title}
</Fragment>
) : this.props.title,
</a>
{this.props.title}
</Fragment>
) : this.props.title,
colors = this.props.colors.map(c => (
<label key={c} className={'player player-selectable player-' + c + (this.state.checkedColor === c ? ' player-selected' : '')}>
<input type='checkbox'
checked={this.state.checkedColor === c}
onChange={this.handleInputChange}
name={c} />
<input type='checkbox'
checked={this.state.checkedColor === c}
onChange={this.handleInputChange}
name={c} />
</label>
)
),
),
gameName = this.props.showGameName && (
<Row>
<Col width='12'>
<label>Game Name
<input type='text' name='gameName' value={this.state.gameName}
required
onChange={this.handleInputChange} />
</label>
</Col>
<Col width='12'>
<label>Game Name
<input type='text' name='gameName' value={this.state.gameName}
required
onChange={this.handleInputChange} />
</label>
</Col>
</Row>
),
settingsClass = this.state.showSettings ? '' : 'hidden',
mainScreenClass = !this.state.showSettings ? '' : 'hidden';
return (
<GroupBox title={titleBar}>
{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}
/>
</>
)}
{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} />
<InputRow label={'Number of Starting ' + itemCardShort}
name='startingOtbs'
min={0}
max={8}
step={1}
value={this.state.startingOtbs}
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>
);
}

View File

@@ -78,6 +78,7 @@ const unsubscribeNewOrJoinGame = store.subscribe(() => {
});
let autostart = new URL(window.location.href).searchParams.get('autostart');
let rejoin = new URL(window.location.href).searchParams.get('reload-game');
function handleMessage(evt) {
const data = JSON.parse(evt.data),
@@ -86,6 +87,8 @@ function handleMessage(evt) {
if (data.event === 'error') {
console.log('error:' + data.exn);
} else if (data.event === 'new-game-started') {
history.replaceState({}, document.title, '/');
console.log('clearing auto-reload');
initialize(store, Ws.sendCommand);
Ws.setMainOnMessage(handleMessageFarm);
Ws.openSecondary('push-web-socket');
@@ -111,6 +114,10 @@ function handleMessage(evt) {
gameName: data.games.games[0].name }));
}
}
if (rejoin) {
store.dispatch(startOrJoinGame({ type: 'join-as-existing',
gameId: false }));
}
}
}

View File

@@ -13,7 +13,7 @@
(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 games(id INTEGER PRIMARY KEY, status TEXT, object TEXT, updated INTEGER);"))
(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);"))))
@@ -70,14 +70,14 @@
(define (db-add-game status object)
(with-db (db)
(exec (sql db "insert into games(status, object) values (?, ?);")
status (alist->string object))
(exec (sql db "insert into games(status, object, updated) values (?, ?, ?);")
status (alist->string object) (current-seconds))
(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))))
(exec (sql db "replace into games(id, status, object, updated) values (?, ?, ?, ?);")
id status (alist->string object) (current-seconds))))
(define (db-fetch-game id)
(string->alist
@@ -91,7 +91,7 @@
string->alist
(with-db (db)
(query fetch-column
(sql db "select object from games where status=?;")
(sql db "select object from games where status=? order by updated desc;")
"pre-game"))))
(define (db-fetch-game-row id)
@@ -127,8 +127,13 @@
(exec (sql db "insert into user_games(user_id, game_id) values (?, ?);")
user-id game-id)))
(define (db-remove-user-game user-id game-id)
(with-db (db)
(exec (sql db "delete from user_games where user_id=? and game_id=?;")
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)))
(sql db "select game_id from user_games join games on user_games.game_id=games.id where user_games.user_id=? and not games.status=? order by updated desc;")
user-id "finished")))

View File

@@ -18,7 +18,7 @@
;;; <https://www.gnu.org/licenses/>.
(import chicken scheme srfi-1 data-structures)
(use http-session srfi-69 coops uri-common
(use http-session srfi-69 coops coops-utils uri-common
srfi-18 medea numbers spiffy spiffy-cookies
intarweb pll sxml-transforms websockets miscmacros
mailbox)
@@ -106,7 +106,8 @@
(finished initform: #f accessor: player-finished)
(assets initform:
'((hay . 10) (grain . 10) (fruit . 0) (cows . 0)
(harvester . 0) (tractor . 0))
(harvester . 0) (tractor . 0)
(birthday . 0))
accessor: player-assets)
(ridges initform:
'((ridge1 . 0) (ridge2 . 0) (ridge3 . 0) (ridge4 . 0))
@@ -126,7 +127,8 @@
(mutex initform: (make-mutex 'player) accessor: player-mutex)
(harvesting initform: #f accessor: player-harvesting)
(hay-doubled initform: #f accessor: player-hay-doubled)
(corn-doubled initform: #f accessor: player-corn-doubled)))
(corn-doubled initform: #f accessor: player-corn-doubled)
(ready-to-start initform: #f accessor: player-ready-to-start)))
(define-class <ai> (<player>)
((processing-turn initform: #f accessor: ai-processing-turn)))
@@ -155,7 +157,8 @@
(audit-threshold . 250000)
(starting-cash . 5000)
(starting-debt . 5000)
(trade . #t))
(trade . #t)
(starting-otbs . 2))
accessor: game-settings)
(mutex initform: (make-mutex 'game) accessor: game-mutex)))
@@ -168,8 +171,8 @@
(mutex initform: (make-mutex 'app) accessor: app-mutex)))
(define (player->sexp player)
`((cash . ,(player-cash player))
(debt . ,(player-debt player))
`((cash . ,(inexact->exact (round (player-cash player))))
(debt . ,(inexact->exact (round (player-debt player))))
(space . ,(player-space player))
(previous-space . ,(player-previous-space player))
(state . ,(player-state player))
@@ -246,12 +249,54 @@
'games (map sexp->game (alist-ref 'games x))
'last-game-id (alist-ref 'last-game-id x)))
(define (save-app)
(with-output-to-file "/home/tjhintz/app.scm"
(lambda ()
(write (app->sexp *app*)))))
(define (validate-game g)
(assert (instance-of? g <game>))
(assert (number? (game-id g)))
(assert (list? (game-players g)))
(for-each (lambda (p)
(assert (instance-of? p <player>))
(assert (number? (player-cash p)))
(assert (number? (player-display-cash p)))
(assert (= (player-cash p) (player-display-cash p)))
(assert (number? (player-debt p)))
(assert (number? (player-space p)))
(assert (number? (player-previous-space p)))
(assert (symbol? (player-state p)))
(assert (member (player-state p) '(turn-ended pre-turn mid-turn)))
(assert (boolean? (player-finished p)))
(assert (list? (player-assets p))) ;; TODO test assets
(assert (list? (player-ridges p)))
(assert (number? (player-harvest-mult p)))
(assert (list? (player-otbs p)))
(assert (list? (player-farmers-fates p)))
(assert (list? (player-year-rules p)))
(assert (list? (player-next-year-rules p)))
(assert (symbol? (player-color p)))
(assert (string? (player-name p)))
(assert (number? (player-user-id p)))
(assert (list? (player-trade p)))
(assert (number? (player-last-cash p)))
(assert (boolean? (player-harvesting p)))
(assert (boolean? (player-hay-doubled p)))
(assert (boolean? (player-corn-doubled p))))
(game-players g))
(assert (list? (game-otbs g)))
(assert (list? (game-used-otbs g)))
(assert (list? (game-farmers-fates g)))
(assert (list? (game-operating-expenses g)))
(assert (number? (game-operating-expense-index g)))
(assert (list? (game-colors g)))
(assert (or (instance-of? (game-called-audit g) <player>)
(boolean? (game-called-audit g))))
(assert (symbol? (game-state g))) ;; TODO test all symbols
(assert (string? (game-name g)))
(assert (number? (game-turn g)))
(assert (or (instance-of? (game-current-player g) <player>)
(boolean? (game-current-player g))))
(assert (list? (game-settings g))))
(define (save-game game)
(validate-game game)
(db-update-game (game-id game) (symbol->string (game-state game))
(game->sexp game)))
@@ -292,7 +337,9 @@
(set-cookie! (session-cookie-name) sid))))
(session-lifetime (* 60 60 24 7 4))
(access-log (current-output-port))
;; (access-log (current-output-port))
(access-log "access.log")
(error-log "error.log")
(handle-not-found
(let ((old-handler (handle-not-found)))
@@ -518,11 +565,19 @@
#f))
(state . ,(symbol->string (game-state g)))
(turn . ,(game-turn g))
(name . ,(game-name g))
(settings . ((downPayment . ,(game-setting 'down-payment g))
(loanInterest . ,(game-setting 'loan-interest g))
(maxDebt . ,(game-setting 'max-debt g))
(auditThreshold . ,(game-setting 'audit-threshold g))
(trade . ,(game-setting 'trade g))))))))
(startingOtbs . ,(game-setting 'starting-otbs g))
(startingCash . ,(game-setting 'starting-cash g))
(startingDebt . ,(game-setting 'starting-debt g))
(trade . ,(game-setting 'trade g))))
(readyToStart . ,(fold (lambda (p r)
(and (player-ready-to-start p) r))
#t
(game-players g)))))))
(define (buy-crop crop unnormalized-crop amount cash-value player game)
(let ((total-cost (* amount (alist-ref unnormalized-crop
@@ -576,14 +631,21 @@
(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)))
;; (bonus (max (farming-round
;; (inexact->exact
;; (round
;; (* (- (player-net-worth richest)
;; (+ (player-net-worth player)
;; ;; don't give a bonus for emergency debt
;; (max 0 (- (player-debt player) (game-setting 'max-debt game)))))
;; 0.2))))
;; 2500))
(bonus 5000)
)
(safe-set! (player-cash player)
;; (+ (player-cash player) 5000)
(+ (player-cash player) bonus))
(+ (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)
@@ -826,8 +888,8 @@
#f)))
(define (call-audit game player)
(if (game-called-audit game)
(begin (safe-set! (game-called-audit game) player))))
(if (not (game-called-audit game))
(safe-set! (game-called-audit game) player)))
(define (player-net-worth player)
(+ (* (+ (player-asset 'hay player) (player-asset 'grain player)) 2000)
@@ -941,6 +1003,17 @@
rolls))
(_make-rolls n 1 (list (next-roll -1))))
(define (log-error exn)
(with-output-to-file (error-log)
(lambda ()
(print-call-chain)
(print exn)
(print-error-message exn))
append:))
(define (log-msg msg)
(log-to (error-log) "~A" msg))
(define (process-message player game type msg)
(when player
(safe-set! (player-last-cash player) (player-cash player)))
@@ -1252,10 +1325,8 @@
(begin (advance-turn game player)
(handle-exceptions
exn
(begin (print-call-chain)
(print exn)
(print-error-message exn)
(print "error saving app"))
(begin (log-error exn)
(log-msg "error saving app"))
(save-game game))
(if (eq? (game-state game) 'finished)
(do-end-of-game game)
@@ -1285,6 +1356,10 @@
0))
(starting-debt . ,(->i (alist-ref 'startingDebt msg)
0))
(starting-otbs . ,(min (max (->number (alist-ref 'startingOtbs msg)
2)
0)
8))
(trade . ,(or (alist-ref 'trade msg) #t)))))
(player (add-player-to-game game
color
@@ -1300,7 +1375,7 @@
(session-set! (sid) 'game-id (game-id game)))
(*game* game)
(*player* player)
(set-startup-otbs game player 2)
(set-startup-otbs game player (alist-ref 'starting-otbs (game-settings game)))
;; (set-startup-otbs game ai-player 2)
;; (thread-start! (make-ai-push-receiver game ai-player))
(create-start-response "new-game-started")))
@@ -1322,11 +1397,12 @@
(db-add-user-game (alist-ref 'id user) (game-id game))
(*game* game)
(*player* player)
(set-startup-otbs game player 2)
(set-startup-otbs game player (alist-ref 'starting-otbs (game-settings game)))
(message-players! game player '() type: "update")
(create-start-response "new-game-started")))
((string=? type "join-as-existing")
(let* ((id (alist-ref 'gameId msg))
(let* ((id (or (alist-ref 'gameId msg)
(session-ref (sid) 'game-id)))
(user-id (session-ref (sid) 'user-id))
(game (find-game id))
(player (find (lambda (p) (equal? (player-user-id p) user-id))
@@ -1357,6 +1433,41 @@
(session-set! (sid) 'game-id #f)
(session-set! (sid) 'user-id #f)
(create-start-response "start-init"))
((string=? type "ready-to-start")
(safe-set! (player-ready-to-start (*player*)) (not (player-ready-to-start (*player*))))
(message-players! (*game*) (*player*) '() type: "update")
(create-ws-response (*player*) "update" '()))
((string=? type "kick-player")
(let ((kicked-player (find (lambda (p)
(equal? (player-name p) (alist-ref 'name msg)))
(game-players (*game*)))))
(safe-set! (game-colors (*game*))
(cons (player-color kicked-player) (game-colors (*game*))))
(safe-set! (game-otbs (*game*))
(append (game-otbs (*game*))
(player-otbs kicked-player)))
(safe-set! (game-players (*game*))
(filter (lambda (p)
(eq? p kicked-player))
(game-players (*game*))))
(db-remove-user-game (player-user-id kicked-player) (game-id (*game*))))
(message-players! (*game*) (*player*) '() type: "update")
(create-ws-response (*player*) "update" '()))
((string=? type "leave-game")
(safe-set! (game-colors (*game*))
(cons (player-color (*player*)) (game-colors (*game*))))
(safe-set! (game-otbs (*game*))
(append (game-otbs (*game*))
(player-otbs (*player*))))
(safe-set! (game-players (*game*))
(filter (lambda (p)
(not (eq? p (*player*))))
(game-players (*game*))))
(when (not (null? (game-players (*game*))))
(safe-set! (game-current-player (*game*)) (car (game-players (*game*)))))
(db-remove-user-game (player-user-id (*player*)) (game-id (*game*)))
(message-players! (*game*) (*player*) '() type: "left-game")
(create-ws-response (*player*) "left-game" '()))
((string=? type "start-game")
(safe-set! (game-state (*game*)) 'pre-turn)
(db-update-game (game-id (*game*)) (symbol->string (game-state (*game*)))
@@ -1439,19 +1550,19 @@
exn
(send-message
(json->string
`((exn . ,(with-output-to-string
(lambda ()
(print-call-chain)
(print-error-message exn))))
`((exn . ,(begin (log-error exn)
(conc "Server error: " (with-output-to-string
(lambda ()
(print-error-message exn))))))
(event . "error"))))
(send-message
(json->string
(handle-exceptions
exn
`((exn . ,(with-output-to-string
(lambda ()
(print-call-chain)
(print-error-message exn))))
`((exn . ,(begin (log-error exn)
(conc "Server error: " (with-output-to-string
(lambda ()
(print-error-message exn))))))
(event . "error"))
(session-game)
(let* ((game (*game*))
@@ -1491,18 +1602,18 @@
exn
(send-message
(json->string
`((exn . ,(with-output-to-string
(lambda ()
(print-call-chain)
(print-error-message exn)))))))
`((exn . ,(begin (log-error exn)
(conc "Server error: " (with-output-to-string
(lambda ()
(print-error-message exn)))))))))
(send-message
(json->string
(handle-exceptions
exn
`((exn . ,(with-output-to-string
(lambda ()
(print-call-chain)
(print-error-message exn))))
`((exn . ,(begin (log-error exn)
(conc "Server error: " (with-output-to-string
(lambda ()
(print-error-message exn))))))
(event . "error"))
(create-ws-response (*player*)
(alist-ref 'type msg)
@@ -1939,7 +2050,7 @@
(define (gains amount) (lambda (n) (+ n amount)))
(define (player-crop-rule player crop)
(if (> (alist-ref crop (player-assets player)) 0) `(((tom ,crop))) '()))
(if (> (alist-ref crop (player-assets player) eqv? 0) 0) `(((tom ,crop))) '()))
(define (aug4-action player)
(when (not (already-harvested? 'wheat player))
@@ -1969,6 +2080,7 @@
((apr2 add-rule ?p ,(make-player-year-rule
10 '((?p corn harvest-mult 2) (?p grain)))))
((apr2 player-action ?p ,(lambda (p) (safe-set! (player-corn-doubled p) #t))))
((apr2 money ?p ,(gains 5000)) (?p birthday))
((apr3 money ?p ,(pays 500)))
((apr4 money ?p ,(pays 1000)))
((may1 money ?p ,(gains 500)))
@@ -2045,7 +2157,8 @@
,@(player-crop-rule player 'hay)
,@(player-crop-rule player 'grain)
,@(player-crop-rule player 'tractor)
,@(player-crop-rule player 'harvester))
,@(player-crop-rule player 'harvester)
,@(player-crop-rule player 'birthday))
`((,(list-ref *months* space) ?action tom ?value)))))
(if a
(begin (set! res (cons a res)) (loop (amb+)))
@@ -2140,7 +2253,10 @@
(let ((value (alist-ref '?value action)))
(if (procedure? value)
(value player)
(apply (alist-ref (car value) *action-map*) player (cdr value)))))
(let ((action-proc (alist-ref (car value) *action-map*)))
(if (procedure? action-proc)
(apply action-proc player (cdr value))
(print (conc "unknown action value: " value)))))))
((eq? a 'harvest-mult)
(safe-set! (player-harvest-mult player)
(* (player-harvest-mult player) (alist-ref '?value action))))
@@ -2238,6 +2354,15 @@
(define (gp i)
(list-ref (game-players (first-game)) i))
(define (gn name)
(find (lambda (p) (equal? (player-name p) name))
(game-players (first-game))))
(define (gnb name)
(let ((player (gn name)))
(safe-set! (player-assets player)
(alist-update 'birthday 1 (player-assets player)))))
(cond-expand
(geiser
'())
@@ -2256,49 +2381,3 @@
;; you can get $50 from operating expense
;; mark spaces
;; error:
;; Call history:
;; farm.scm:714: mailbox#mailbox-send!
;; type-errors.scm:119: make-error-type-message
;; type-errors.scm:104: make-bad-argument-message
;; type-errors.scm:106: make-type-name-message
;; type-errors.scm:290: ->string
;; type-errors.scm:291: conc
;; type-errors.scm:103: string-append
;; type-errors.scm:119: signal-type-error
;; farm.scm:1125: k7172
;; farm.scm:1125: g7176
;; farm.scm:1127: with-output-to-string
;; farm.scm:1129: print-call-chain <--
;; Error: (mailbox-send!) bad argument type - not a mailbox: ()
;; error:
;; Call history:
;; farm.scm:129: alist-ref
;; farm.scm:1125: k7172
;; farm.scm:1125: g7176
;; farm.scm:1127: with-output-to-string
;; farm.scm:1129: print-call-chain <--
;; Error: (assv) bad argument type: ridge-cows
;; proposed trade to wrong player
;; accidentally clicking no for uncle bert
;; farmers fate 2nd week of january
;; error:
;; Call history:
;; farm.scm:129: alist-ref
;; farm.scm:1213: k7426
;; farm.scm:1213: g7430
;; farm.scm:1215: with-output-to-string
;; farm.scm:1217: print-call-chain <--
;; Error: (assv) bad argument type: #<coops instance of `<game>'>
;; when getting trade the name is wrong

View File

@@ -798,6 +798,8 @@ $trade-margin: 3rem;
color: white; }
.alert-overlay-contents {
max-height: 90vh;
overflow: auto;
background: $light-color;
padding: 2rem;
display: flex;

View File

@@ -24,15 +24,28 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const webpack = require("webpack");
const autoprefixer = require("autoprefixer");
const CssUrlRelativePlugin = require('css-url-relative-plugin')
module.exports = {
entry: {
app: './src/main.jsx',
},
output: {
filename: '[name].bundle.js',
filename: './assets/[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
@@ -42,8 +55,8 @@ module.exports = {
}),
new FaviconsWebpackPlugin('./assets/img/tractor.svg'),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
filename: './assets/[name].[contenthash].css',
chunkFilename: './assets/[id].[contenthash].css',
}),
new CopyPlugin([
{ from: './src/server/farm.scm', to: './[name].[ext]' },
@@ -57,6 +70,7 @@ module.exports = {
]
}
}),
new CssUrlRelativePlugin()
],
module: {
rules: [
@@ -71,11 +85,19 @@ module.exports = {
}
}
},
// {
// test: /mars-texture.png$/,
// loader: 'file-loader',
// options: {
// name: './assets/img/[name].[contenthash].[ext]',
// },
// },
{
test: /\.(woff|woff2|eot|ttf|otf|svg|png|gif)$/,
use: [
'file-loader',
],
loader: 'file-loader',
options: {
name: './assets/img/[name].[contenthash].[ext]',
},
},
{
test: /\.s[ac]ss$/i,