9 Commits

16 changed files with 616 additions and 293 deletions

View File

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

View File

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

View File

@@ -46,6 +46,7 @@
"@fortawesome/free-solid-svg-icons": "^5.12.0", "@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/react-fontawesome": "^0.1.8", "@fortawesome/react-fontawesome": "^0.1.8",
"cookies-js": "^1.2.3", "cookies-js": "^1.2.3",
"css-url-relative-plugin": "^1.0.0",
"mobx": "^5.15.3", "mobx": "^5.15.3",
"mobx-react": "^6.1.4", "mobx-react": "^6.1.4",
"react": "^16.12.0", "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 HayImg from './../../../assets/img/hay.svg'
import WheatImg from './../../../assets/img/wheat.svg' import WheatImg from './../../../assets/img/wheat.svg'
import TractorImg from './../../../assets/img/tractor-icon.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 TractorFullImg from './../../../assets/img/tractor-with-spikes.svg'
import HarvesterImg from './../../../assets/img/harvester.svg' import HarvesterImg from './../../../assets/img/harvester.svg'
import VolcanoImg from './../../../assets/img/volcano2.gif' import VolcanoImg from './../../../assets/img/volcano2.gif'
@@ -46,7 +47,8 @@ import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer,
setMovingSkip } from './actions.js' setMovingSkip } from './actions.js'
import { buy, roll, endTurn, loan, trade, submitTradeAccept, import { buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, submitTradeDeny, submitTradeCancel, audit,
buyUncleBert, skip, endAiTurn, startGame } from './interface.js' buyUncleBert, skip, endAiTurn, startGame, readyToStart,
leaveGame } from './interface.js'
function netWorth(player) { function netWorth(player) {
return ((player.assets.hay + player.assets.grain) * 2000) + return ((player.assets.hay + player.assets.grain) * 2000) +
@@ -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 // http://stackoverflow.com/questions/149055
function formatMoney(n) { function formatMoney(n) {
return n.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, '$1,').slice(0, -2); } 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} <PlayerTurnContainer player={player}
game={this.props.game} game={this.props.game}
ui={this.props.ui} ui={this.props.ui}
hideForLarge={this.props.hideForLarge}
screen={this.props.screen} screen={this.props.screen}
showScreen={this.props.showScreen}/> showScreen={this.props.showScreen}/>
</GroupBox> </GroupBox>
@@ -421,8 +430,10 @@ class PlayerTurnContainer extends React.Component {
<br /> <br />
</> </>
) : (<></>)} ) : (<></>)}
<div className={this.props.hideForLarge ? 'hide-for-large' : ''}>
{view} {view}
</div> </div>
</div>
</Col> </Col>
</Row> </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 /> <img src={VolcanoImg} /> Copyright <a href="https://thenounproject.com/Maludk/">Laymik</a> with modifications by Thomas Hintz - <CCBY />
</li> </li>
<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>
<li> <li>
<img src={CowImg} /> Copyright <a href='https://thenounproject.com/rivercon/'>rivercon</a> - <CCBY /> <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 { 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() { render() {
let view, buttons; let view, buttons;
const { currentPlayer } = this.props; const { currentPlayer } = this.props;
@@ -1523,17 +1552,24 @@ class Action extends React.Component {
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>); buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break; break;
case 'ff-uncle-bert': case 'ff-uncle-bert':
const { cash } = this.props.player;
const ffButtons = ( const ffButtons = (
<Fragment> <>
{this.props.player.cash >= 10000 ? ( {cash < 10000 ? (
<Button onClick={() => { buyUncleBert(); this.props.showNextAction(); }}> <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! Yes, take over for $10,000!
</Button> </label>
) : (<Fragment />)} <label>
<Button onClick={() => this.props.showNextAction()}> <input type="radio" name="bert" value="deny" onClick={this.setBertChoice} />
No, continue on No, continue on
</Button> </label>
</Fragment> <Button type="submit" disabled={this.state.bertChoice === 'nothing' || this.state.bertChoice === 'accept' && cash < 10000}>Submit</Button>
</form>
</>
); );
view = ( view = (
<GroupBox title={`Uncle Bert's inheritance`}> <GroupBox title={`Uncle Bert's inheritance`}>
@@ -1541,15 +1577,18 @@ class Action extends React.Component {
<p> <p>
{currentPlayer.cash >= 10000 ? {currentPlayer.cash >= 10000 ?
`You have enough cash to take over Uncle Bert's farm!` : `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>
<p> <div>
{(this.props.player.name === this.props.game.currentPlayer) ? {(this.props.player.name === this.props.game.currentPlayer) ?
ffButtons : (<Fragment />)} ffButtons : (<Fragment />)}
</p> </div>
</div> </div>
</GroupBox> </GroupBox>
); );
@@ -1772,6 +1811,11 @@ class AlertOverlay extends React.Component {
this.hide(e); this.hide(e);
this.props.handler(); this.props.handler();
} }
cancelButtonClick = e => {
e.preventDefault();
this.props.cancelHandler();
}
// <label><input type='checkbox' onClick={this.hidePermanent} /> {`Don't show again`}</label> // <label><input type='checkbox' onClick={this.hidePermanent} /> {`Don't show again`}</label>
render() { render() {
@@ -1785,7 +1829,17 @@ class AlertOverlay extends React.Component {
<div className='alert-overlay-contents'> <div className='alert-overlay-contents'>
{this.props.children} {this.props.children}
<br /> <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>) : (<></>)} {!this.props.preventHiding ? (<a onClick={this.hide}>close</a>) : (<></>)}
</div> </div>
</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', const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms',
cards: 'cards', trade: 'trade', loans: 'loans', cards: 'cards', trade: 'trade', loans: 'loans',
action: 'action', info: 'info' }; action: 'action', info: 'info', error: 'error' };
class BoardApp extends React.Component { class BoardApp extends React.Component {
iconToScreen = { user: SCREENS.summary, 'window-restore': SCREENS.cards, iconToScreen = { user: SCREENS.summary, 'window-restore': SCREENS.cards,
@@ -1895,7 +1987,8 @@ class BoardApp extends React.Component {
screen: SCREENS.summary, screen: SCREENS.summary,
card: props.ui.card, card: props.ui.card,
timerId: false, timerId: false,
currentPlayer: this.props.player currentPlayer: this.props.player,
readyToStart: false
}; };
this.myRef = React.createRef(); this.myRef = React.createRef();
this.actionRef = 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; .find(p => p.player.name === this.props.game.currentPlayer).player;
this.setState({ currentPlayer }); this.setState({ currentPlayer });
} }
if (!prevProps.ui.exn && this.props.ui.exn) {
this.setState({ screen: SCREENS.error });
}
} }
componentDidMount() { componentDidMount() {
@@ -1996,6 +2093,11 @@ class BoardApp extends React.Component {
} }
} }
startGameToggleReady = () => {
this.setState(state => { return { readyToStart: !state.readyToStart }; });
readyToStart();
}
render() { render() {
let alertOverlay; let alertOverlay;
const alert = this.props.ui.unhandledAlert; const alert = this.props.ui.unhandledAlert;
@@ -2037,21 +2139,16 @@ class BoardApp extends React.Component {
alertHandled={this.props.alertHandled} alertHandled={this.props.alertHandled}
buttonText='Start Game' buttonText='Start Game'
hideHandler={() => 'nothing'} hideHandler={() => 'nothing'}
cancelButtonText='Leave Game'
cancelHandler={leaveGame}
cancelDisabled={this.state.readyToStart}
preventHiding={true} preventHiding={true}
disabled={!this.props.game.readyToStart}
handler={startGame}> handler={startGame}>
<Fragment> <StartGame player={this.props.player}
<h1>Pre Game</h1> game={this.props.game}
<p>When all players have joined click 'Start Game'!</p> toggleReady={this.startGameToggleReady}
<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> </AlertOverlay>
); );
} else if (alert && alert.type === ALERTS.proposedTrade) { } else if (alert && alert.type === ALERTS.proposedTrade) {
@@ -2165,6 +2262,7 @@ class BoardApp extends React.Component {
<PlayerSummary player={this.props.player} <PlayerSummary player={this.props.player}
ui={this.props.ui} ui={this.props.ui}
screen={this.state.screen} screen={this.state.screen}
hideForLarge={true}
game={this.props.game} showScreen={this.showScreen} /> game={this.props.game} showScreen={this.showScreen} />
</div> </div>
<div className={this.tabClass(SCREENS.action)}> <div className={this.tabClass(SCREENS.action)}>
@@ -2207,6 +2305,11 @@ class BoardApp extends React.Component {
<div className={this.tabClass(SCREENS.info)}> <div className={this.tabClass(SCREENS.info)}>
<Info harvestTable={this.props.ui.harvestTable} game={this.props.game} /> <Info harvestTable={this.props.ui.harvestTable} game={this.props.game} />
</div> </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>
<div className='static-tab-container show-for-large'> <div className='static-tab-container show-for-large'>
<div className='tab show'> <div className='tab show'>

View File

@@ -39,3 +39,4 @@ export const AUTO_SKIP = 'auto-skip'
export const MESSAGE = 'message' export const MESSAGE = 'message'
export const SET_HARVEST_TABLE = 'set-harvest-table' export const SET_HARVEST_TABLE = 'set-harvest-table'
export const SET_MOVING_SKIP = 'set-moving-skip' 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, MP_MOUSE, SET_MP_DIMS, MARK_ACTION_CHANGE_HANDLED, SET_NEXT_ACTION,
MOVE_PLAYER, NEXT_UI_ACTION, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED, MOVE_PLAYER, NEXT_UI_ACTION, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED,
AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE, SET_CARD_ERROR, 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, export { updateGame, updatePlayer, gameState, setSelectedCard, setCards,
spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace, spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace,
mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction, mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction,
markActionChangeHandled, nextUIActionSilent, alert, alertHandled, markActionChangeHandled, nextUIActionSilent, alert, alertHandled,
autoSkip, message, setHarvestTable, setCardError, setMovingSkip } autoSkip, message, setHarvestTable, setCardError, setMovingSkip,
serverError }
function updateGame(update) { function updateGame(update) {
return { type: UPDATE_GAME, return { type: UPDATE_GAME,
@@ -135,3 +136,7 @@ function setHarvestTable(table) {
function setMovingSkip(skip) { function setMovingSkip(skip) {
return { type: SET_MOVING_SKIP, 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, movePlayer, setOldMessages, markActionChangeHandled,
mpMouse, rolled, setNextAction, nextUIAction, nextUIActionSilent, alert, mpMouse, rolled, setNextAction, nextUIAction, nextUIActionSilent, alert,
autoSkip, message, alertHandled, setHarvestTable, autoSkip, message, alertHandled, setHarvestTable,
setCardError, setMovingSkip } from './actions.js' setCardError, setMovingSkip, serverError } from './actions.js'
import { itemCard, fateCard } from 'game.js' import { itemCard, fateCard } from 'game.js'
export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept, export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, handleMessage, submitTradeDeny, submitTradeCancel, audit, handleMessage,
nextAction, buyUncleBert, actionsFinished, skip, endAiTurn, nextAction, buyUncleBert, actionsFinished, skip, endAiTurn,
startGame } startGame, readyToStart, leaveGame }
let store; let store;
let movingTimer = 0; let movingTimer = 0;
@@ -42,9 +42,13 @@ function handleMessage(evt) {
if (data.event === 'error') { if (data.event === 'error') {
console.log('error:' + data.exn); console.log('error:' + data.exn);
store.dispatch(serverError(data.exn));
return; return;
} }
batch(() => { batch(() => {
if (data.event === 'left-game') {
window.location.href = window.location.pathname;
}
if (data.game.state === GAME_STATES.preGame) { if (data.game.state === GAME_STATES.preGame) {
store.dispatch(alert(ALERTS.preGame, '', 'pre-game')); 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(movePlayer(data.player.space, -1, data.player.color));
store.dispatch(setHarvestTable(data.harvestTable)); 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) { if (data.game.otherPlayers.length !== store.getState().farm.game.otherPlayers.length) {
const otherPlayers = store.getState().farm.game.otherPlayers; const otherPlayers = store.getState().farm.game.otherPlayers;
const newPlayers = data.game.otherPlayers.filter( const newPlayers = data.game.otherPlayers.filter(
@@ -198,6 +202,14 @@ function startGame() {
sendCommand({ type: 'start-game' }); sendCommand({ type: 'start-game' });
} }
function readyToStart() {
sendCommand({ type: 'ready-to-start' });
}
function leaveGame() {
sendCommand({ type: 'leave-game' });
}
// TODO share with Board.jsx // TODO share with Board.jsx
// http://stackoverflow.com/questions/149055 // http://stackoverflow.com/questions/149055
function formatMoney(n) { 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, SET_MP_DIMS, MOVE_PLAYER, SET_NEXT_ACTION, NEXT_UI_ACTION,
MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED, MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED,
AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE, SET_CARD_ERROR, 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 { GAME_STATES } from '../../constants.js'
import { spaceContent, corners } from 'game.js' import { spaceContent, corners } from 'game.js'
@@ -88,7 +88,7 @@ const initialState = {
debt: 5000, debt: 5000,
spaces, spaces,
state: GAME_STATES.turnEnded, 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: '', color: '',
name: '', name: '',
ridges: { ridge1: 0, ridge2: 0, ridge3: 0, ridge4: 0 }, ridges: { ridge1: 0, ridge2: 0, ridge3: 0, ridge4: 0 },
@@ -105,10 +105,15 @@ const initialState = {
state: GAME_STATES.preTurn, state: GAME_STATES.preTurn,
turn: 0, turn: 0,
oldMessages: [], oldMessages: [],
name: '',
settings: { downPayment: 0.2, settings: { downPayment: 0.2,
loanInterest: 0.2, loanInterest: 0.2,
maxDebt: 50000, maxDebt: 50000,
auditThreshold: 250000 } auditThreshold: 250000,
startingOtbs: 2,
startingCash: 5000,
startingDebt: 5000 },
readyToStart: false
}, },
ui: { card: { type: 'no-card', contents: '', total: 0 }, ui: { card: { type: 'no-card', contents: '', total: 0 },
cards: [], cards: [],
@@ -124,7 +129,8 @@ const initialState = {
autoSkip: false, autoSkip: false,
playerSpaces: {}, playerSpaces: {},
movingSkip: false, movingSkip: false,
harvestTable: false }, harvestTable: false,
exn: false },
spaces: spaces, spaces: spaces,
space: null, space: null,
// message panel dimenions // message panel dimenions
@@ -228,6 +234,8 @@ export default function(state = initialState, action) {
return { ...state, ui: { ...state.ui, harvestTable: action.table }}; return { ...state, ui: { ...state.ui, harvestTable: action.table }};
case SET_MOVING_SKIP: case SET_MOVING_SKIP:
return { ...state, ui: { ...state.ui, movingSkip: action.skip }}; return { ...state, ui: { ...state.ui, movingSkip: action.skip }};
case SERVER_ERROR:
return { ...state, ui: { ...state.ui, exn: action.exn }};
default: default:
return state; return state;
} }

View File

@@ -25,6 +25,8 @@ import LoginOrCreateAccount from '../login-or-create-account/LoginOrCreateAccoun
import { startOrJoinGame } from '../start/actions.js' import { startOrJoinGame } from '../start/actions.js'
import { start } from '../app/actions.js' import { start } from '../app/actions.js'
import { itemCardShort } from 'game.js'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowCircleLeft, faCog } from '@fortawesome/free-solid-svg-icons' import { faArrowCircleLeft, faCog } from '@fortawesome/free-solid-svg-icons'
@@ -63,6 +65,7 @@ class NewGame extends React.Component {
auditThreshold: 250000, auditThreshold: 250000,
startingCash: 5000, startingCash: 5000,
startingDebt: 5000, startingDebt: 5000,
startingOtbs: 2,
trade: true, trade: true,
showLogin: false showLogin: false
}; };
@@ -180,6 +183,13 @@ class NewGame extends React.Component {
step={1000} step={1000}
value={this.state.startingDebt} value={this.state.startingDebt}
onChange={this.handleInputChange} /> 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> <Row>
<Col width='12'> <Col width='12'>
<label> <label>

View File

@@ -78,6 +78,7 @@ const unsubscribeNewOrJoinGame = store.subscribe(() => {
}); });
let autostart = new URL(window.location.href).searchParams.get('autostart'); let autostart = new URL(window.location.href).searchParams.get('autostart');
let rejoin = new URL(window.location.href).searchParams.get('reload-game');
function handleMessage(evt) { function handleMessage(evt) {
const data = JSON.parse(evt.data), const data = JSON.parse(evt.data),
@@ -86,6 +87,8 @@ function handleMessage(evt) {
if (data.event === 'error') { if (data.event === 'error') {
console.log('error:' + data.exn); console.log('error:' + data.exn);
} else if (data.event === 'new-game-started') { } else if (data.event === 'new-game-started') {
history.replaceState({}, document.title, '/');
console.log('clearing auto-reload');
initialize(store, Ws.sendCommand); initialize(store, Ws.sendCommand);
Ws.setMainOnMessage(handleMessageFarm); Ws.setMainOnMessage(handleMessageFarm);
Ws.openSecondary('push-web-socket'); Ws.openSecondary('push-web-socket');
@@ -111,6 +114,10 @@ function handleMessage(evt) {
gameName: data.games.games[0].name })); 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) (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 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 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 players(id INTEGER PRIMARY KEY, object TEXT);"))
(exec (sql db "create table user_games(user_id INTEGER, game_id INTEGER);")))) (exec (sql db "create table user_games(user_id INTEGER, game_id INTEGER);"))))
@@ -70,14 +70,14 @@
(define (db-add-game status object) (define (db-add-game status object)
(with-db (db) (with-db (db)
(exec (sql db "insert into games(status, object) values (?, ?);") (exec (sql db "insert into games(status, object, updated) values (?, ?, ?);")
status (alist->string object)) status (alist->string object) (current-seconds))
(last-insert-rowid db))) (last-insert-rowid db)))
(define (db-update-game id status object) (define (db-update-game id status object)
(with-db (db) (with-db (db)
(exec (sql db "replace into games(id, status, object) values (?, ?, ?);") (exec (sql db "replace into games(id, status, object, updated) values (?, ?, ?, ?);")
id status (alist->string object)))) id status (alist->string object) (current-seconds))))
(define (db-fetch-game id) (define (db-fetch-game id)
(string->alist (string->alist
@@ -91,7 +91,7 @@
string->alist string->alist
(with-db (db) (with-db (db)
(query fetch-column (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")))) "pre-game"))))
(define (db-fetch-game-row id) (define (db-fetch-game-row id)
@@ -127,8 +127,13 @@
(exec (sql db "insert into user_games(user_id, game_id) values (?, ?);") (exec (sql db "insert into user_games(user_id, game_id) values (?, ?);")
user-id game-id))) 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) (define (db-fetch-user-games user-id)
(with-db (db) (with-db (db)
(query fetch-column (query fetch-column
(sql db "select game_id from user_games where 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))) user-id "finished")))

View File

@@ -18,7 +18,7 @@
;;; <https://www.gnu.org/licenses/>. ;;; <https://www.gnu.org/licenses/>.
(import chicken scheme srfi-1 data-structures) (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 srfi-18 medea numbers spiffy spiffy-cookies
intarweb pll sxml-transforms websockets miscmacros intarweb pll sxml-transforms websockets miscmacros
mailbox) mailbox)
@@ -106,7 +106,8 @@
(finished initform: #f accessor: player-finished) (finished initform: #f accessor: player-finished)
(assets initform: (assets initform:
'((hay . 10) (grain . 10) (fruit . 0) (cows . 0) '((hay . 10) (grain . 10) (fruit . 0) (cows . 0)
(harvester . 0) (tractor . 0)) (harvester . 0) (tractor . 0)
(birthday . 0))
accessor: player-assets) accessor: player-assets)
(ridges initform: (ridges initform:
'((ridge1 . 0) (ridge2 . 0) (ridge3 . 0) (ridge4 . 0)) '((ridge1 . 0) (ridge2 . 0) (ridge3 . 0) (ridge4 . 0))
@@ -126,7 +127,8 @@
(mutex initform: (make-mutex 'player) accessor: player-mutex) (mutex initform: (make-mutex 'player) accessor: player-mutex)
(harvesting initform: #f accessor: player-harvesting) (harvesting initform: #f accessor: player-harvesting)
(hay-doubled initform: #f accessor: player-hay-doubled) (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>) (define-class <ai> (<player>)
((processing-turn initform: #f accessor: ai-processing-turn))) ((processing-turn initform: #f accessor: ai-processing-turn)))
@@ -155,7 +157,8 @@
(audit-threshold . 250000) (audit-threshold . 250000)
(starting-cash . 5000) (starting-cash . 5000)
(starting-debt . 5000) (starting-debt . 5000)
(trade . #t)) (trade . #t)
(starting-otbs . 2))
accessor: game-settings) accessor: game-settings)
(mutex initform: (make-mutex 'game) accessor: game-mutex))) (mutex initform: (make-mutex 'game) accessor: game-mutex)))
@@ -168,8 +171,8 @@
(mutex initform: (make-mutex 'app) accessor: app-mutex))) (mutex initform: (make-mutex 'app) accessor: app-mutex)))
(define (player->sexp player) (define (player->sexp player)
`((cash . ,(player-cash player)) `((cash . ,(inexact->exact (round (player-cash player))))
(debt . ,(player-debt player)) (debt . ,(inexact->exact (round (player-debt player))))
(space . ,(player-space player)) (space . ,(player-space player))
(previous-space . ,(player-previous-space player)) (previous-space . ,(player-previous-space player))
(state . ,(player-state player)) (state . ,(player-state player))
@@ -246,12 +249,54 @@
'games (map sexp->game (alist-ref 'games x)) 'games (map sexp->game (alist-ref 'games x))
'last-game-id (alist-ref 'last-game-id x))) 'last-game-id (alist-ref 'last-game-id x)))
(define (save-app) (define (validate-game g)
(with-output-to-file "/home/tjhintz/app.scm" (assert (instance-of? g <game>))
(lambda () (assert (number? (game-id g)))
(write (app->sexp *app*))))) (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) (define (save-game game)
(validate-game game)
(db-update-game (game-id game) (symbol->string (game-state game)) (db-update-game (game-id game) (symbol->string (game-state game))
(game->sexp game))) (game->sexp game)))
@@ -292,7 +337,9 @@
(set-cookie! (session-cookie-name) sid)))) (set-cookie! (session-cookie-name) sid))))
(session-lifetime (* 60 60 24 7 4)) (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 (handle-not-found
(let ((old-handler (handle-not-found))) (let ((old-handler (handle-not-found)))
@@ -518,11 +565,19 @@
#f)) #f))
(state . ,(symbol->string (game-state g))) (state . ,(symbol->string (game-state g)))
(turn . ,(game-turn g)) (turn . ,(game-turn g))
(name . ,(game-name g))
(settings . ((downPayment . ,(game-setting 'down-payment g)) (settings . ((downPayment . ,(game-setting 'down-payment g))
(loanInterest . ,(game-setting 'loan-interest g)) (loanInterest . ,(game-setting 'loan-interest g))
(maxDebt . ,(game-setting 'max-debt g)) (maxDebt . ,(game-setting 'max-debt g))
(auditThreshold . ,(game-setting 'audit-threshold 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) (define (buy-crop crop unnormalized-crop amount cash-value player game)
(let ((total-cost (* amount (alist-ref unnormalized-crop (let ((total-cost (* amount (alist-ref unnormalized-crop
@@ -576,14 +631,21 @@
(lambda (p1 p2) (lambda (p1 p2)
(> (player-net-worth p1) (> (player-net-worth p1)
(player-net-worth p2)))))) (player-net-worth p2))))))
(bonus (max (farming-round ;; (bonus (max (farming-round
(* (- (player-net-worth richest) ;; (inexact->exact
(player-net-worth player)) ;; (round
0.2)) ;; (* (- (player-net-worth richest)
2500))) ;; (+ (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) (safe-set! (player-cash player)
;; (+ (player-cash player) 5000) (+ (player-cash player) 5000)
(+ (player-cash player) bonus)) ;; (+ (player-cash player) bonus)
)
(safe-set! (player-display-cash player) (player-cash player)) (safe-set! (player-display-cash player) (player-cash player))
(safe-set! (game-actions game) (safe-set! (game-actions game)
(cons `((?action . info) (cons `((?action . info)
@@ -826,8 +888,8 @@
#f))) #f)))
(define (call-audit game player) (define (call-audit game player)
(if (game-called-audit game) (if (not (game-called-audit game))
(begin (safe-set! (game-called-audit game) player)))) (safe-set! (game-called-audit game) player)))
(define (player-net-worth player) (define (player-net-worth player)
(+ (* (+ (player-asset 'hay player) (player-asset 'grain player)) 2000) (+ (* (+ (player-asset 'hay player) (player-asset 'grain player)) 2000)
@@ -941,6 +1003,17 @@
rolls)) rolls))
(_make-rolls n 1 (list (next-roll -1)))) (_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) (define (process-message player game type msg)
(when player (when player
(safe-set! (player-last-cash player) (player-cash player))) (safe-set! (player-last-cash player) (player-cash player)))
@@ -1252,10 +1325,8 @@
(begin (advance-turn game player) (begin (advance-turn game player)
(handle-exceptions (handle-exceptions
exn exn
(begin (print-call-chain) (begin (log-error exn)
(print exn) (log-msg "error saving app"))
(print-error-message exn)
(print "error saving app"))
(save-game game)) (save-game game))
(if (eq? (game-state game) 'finished) (if (eq? (game-state game) 'finished)
(do-end-of-game game) (do-end-of-game game)
@@ -1285,6 +1356,10 @@
0)) 0))
(starting-debt . ,(->i (alist-ref 'startingDebt msg) (starting-debt . ,(->i (alist-ref 'startingDebt msg)
0)) 0))
(starting-otbs . ,(min (max (->number (alist-ref 'startingOtbs msg)
2)
0)
8))
(trade . ,(or (alist-ref 'trade msg) #t))))) (trade . ,(or (alist-ref 'trade msg) #t)))))
(player (add-player-to-game game (player (add-player-to-game game
color color
@@ -1300,7 +1375,7 @@
(session-set! (sid) 'game-id (game-id game))) (session-set! (sid) 'game-id (game-id game)))
(*game* game) (*game* game)
(*player* player) (*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) ;; (set-startup-otbs game ai-player 2)
;; (thread-start! (make-ai-push-receiver game ai-player)) ;; (thread-start! (make-ai-push-receiver game ai-player))
(create-start-response "new-game-started"))) (create-start-response "new-game-started")))
@@ -1322,11 +1397,12 @@
(db-add-user-game (alist-ref 'id user) (game-id game)) (db-add-user-game (alist-ref 'id user) (game-id game))
(*game* game) (*game* game)
(*player* player) (*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") (message-players! game player '() type: "update")
(create-start-response "new-game-started"))) (create-start-response "new-game-started")))
((string=? type "join-as-existing") ((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)) (user-id (session-ref (sid) 'user-id))
(game (find-game id)) (game (find-game id))
(player (find (lambda (p) (equal? (player-user-id p) user-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) 'game-id #f)
(session-set! (sid) 'user-id #f) (session-set! (sid) 'user-id #f)
(create-start-response "start-init")) (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") ((string=? type "start-game")
(safe-set! (game-state (*game*)) 'pre-turn) (safe-set! (game-state (*game*)) 'pre-turn)
(db-update-game (game-id (*game*)) (symbol->string (game-state (*game*))) (db-update-game (game-id (*game*)) (symbol->string (game-state (*game*)))
@@ -1439,19 +1550,19 @@
exn exn
(send-message (send-message
(json->string (json->string
`((exn . ,(with-output-to-string `((exn . ,(begin (log-error exn)
(conc "Server error: " (with-output-to-string
(lambda () (lambda ()
(print-call-chain) (print-error-message exn))))))
(print-error-message exn))))
(event . "error")))) (event . "error"))))
(send-message (send-message
(json->string (json->string
(handle-exceptions (handle-exceptions
exn exn
`((exn . ,(with-output-to-string `((exn . ,(begin (log-error exn)
(conc "Server error: " (with-output-to-string
(lambda () (lambda ()
(print-call-chain) (print-error-message exn))))))
(print-error-message exn))))
(event . "error")) (event . "error"))
(session-game) (session-game)
(let* ((game (*game*)) (let* ((game (*game*))
@@ -1491,18 +1602,18 @@
exn exn
(send-message (send-message
(json->string (json->string
`((exn . ,(with-output-to-string `((exn . ,(begin (log-error exn)
(conc "Server error: " (with-output-to-string
(lambda () (lambda ()
(print-call-chain) (print-error-message exn)))))))))
(print-error-message exn)))))))
(send-message (send-message
(json->string (json->string
(handle-exceptions (handle-exceptions
exn exn
`((exn . ,(with-output-to-string `((exn . ,(begin (log-error exn)
(conc "Server error: " (with-output-to-string
(lambda () (lambda ()
(print-call-chain) (print-error-message exn))))))
(print-error-message exn))))
(event . "error")) (event . "error"))
(create-ws-response (*player*) (create-ws-response (*player*)
(alist-ref 'type msg) (alist-ref 'type msg)
@@ -1939,7 +2050,7 @@
(define (gains amount) (lambda (n) (+ n amount))) (define (gains amount) (lambda (n) (+ n amount)))
(define (player-crop-rule player crop) (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) (define (aug4-action player)
(when (not (already-harvested? 'wheat player)) (when (not (already-harvested? 'wheat player))
@@ -1969,6 +2080,7 @@
((apr2 add-rule ?p ,(make-player-year-rule ((apr2 add-rule ?p ,(make-player-year-rule
10 '((?p corn harvest-mult 2) (?p grain))))) 10 '((?p corn harvest-mult 2) (?p grain)))))
((apr2 player-action ?p ,(lambda (p) (safe-set! (player-corn-doubled p) #t)))) ((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))) ((apr3 money ?p ,(pays 500)))
((apr4 money ?p ,(pays 1000))) ((apr4 money ?p ,(pays 1000)))
((may1 money ?p ,(gains 500))) ((may1 money ?p ,(gains 500)))
@@ -2045,7 +2157,8 @@
,@(player-crop-rule player 'hay) ,@(player-crop-rule player 'hay)
,@(player-crop-rule player 'grain) ,@(player-crop-rule player 'grain)
,@(player-crop-rule player 'tractor) ,@(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))))) `((,(list-ref *months* space) ?action tom ?value)))))
(if a (if a
(begin (set! res (cons a res)) (loop (amb+))) (begin (set! res (cons a res)) (loop (amb+)))
@@ -2140,7 +2253,10 @@
(let ((value (alist-ref '?value action))) (let ((value (alist-ref '?value action)))
(if (procedure? value) (if (procedure? value)
(value player) (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) ((eq? a 'harvest-mult)
(safe-set! (player-harvest-mult player) (safe-set! (player-harvest-mult player)
(* (player-harvest-mult player) (alist-ref '?value action)))) (* (player-harvest-mult player) (alist-ref '?value action))))
@@ -2238,6 +2354,15 @@
(define (gp i) (define (gp i)
(list-ref (game-players (first-game)) 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 (cond-expand
(geiser (geiser
'()) '())
@@ -2256,49 +2381,3 @@
;; you can get $50 from operating expense ;; you can get $50 from operating expense
;; mark spaces ;; 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; } color: white; }
.alert-overlay-contents { .alert-overlay-contents {
max-height: 90vh;
overflow: auto;
background: $light-color; background: $light-color;
padding: 2rem; padding: 2rem;
display: flex; display: flex;

View File

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