Using sqlite database, mt proctor animation.

logins
Thomas Hintz 5 years ago
parent b34a66f697
commit 6ff6387fef

@ -35,7 +35,7 @@ install:
npm install npm install
interactive: interactive:
cd dist/ && csi -include-path $(assets) -s farm.scm cd dist/ && csi -include-path $(assets) -include-path ../src/server -s farm.scm
src/server/farm: src/server/farm.scm src/server/farm: src/server/farm.scm
cd src/server/ && csc -include-path ../../$(assets) -O3 farm.scm cd src/server/ && csc -include-path ../../$(assets) -O3 farm.scm

@ -47,7 +47,7 @@
"20 Cows on Peridier Ridge")))) "20 Cows on Peridier Ridge"))))
(define *ff-text* (define *ff-text*
'(((p "Natural Disaster--The Solar Winds break through the atmosphere. You are luckily shielded by Mt Proctor. Your hay survives and jumps in price. " (b "COLLECT $500 per Hay acre") ". To see if they escaped, other players must roll. Odd: escaped, Even: hit. " (b "Wind hit players must clean up all acres at $100 per acre."))) '(((p (img (@ (src "53c3a93b0867eee67b9b9f6ebc4c1f4a.gif") (style "float: left;"))) "Natural Disaster--The Solar Winds break through the atmosphere. You are luckily shielded by Mt Proctor. Your hay survives and jumps in price. " (b "COLLECT $500 per Hay acre") ". To see if they escaped, other players must roll. Odd: escaped, Even: hit. " (b "Wind hit players must clean up all acres at $100 per acre.")))
((p "Planetary Disaster Fund comes through." (p (b "COLLECT $100 per Grain acre.")))) ((p "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.")))

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

@ -0,0 +1,73 @@
<?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 83.521004 90"
enable-background="new 0 0 100 100"
xml:space="preserve"
id="svg4827"
sodipodi:docname="volcano.svg"
width="83.521004"
height="90"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata4833"><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="defs4831" /><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="namedview4829"
showgrid="false"
inkscape:zoom="16.931965"
inkscape:cx="34.840436"
inkscape:cy="58.282217"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="background"
style="display:inline"><path
d="m 22.724,90 h 3.742 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 h -3.742 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 z"
id="path4848"
inkscape:connector-curvature="0" /><path
d="m 31.709,48.246 c -0.5,0 -0.92,0.373 -0.991,0.868 C 26.776,76.3 7.844,86.928 5.8,88 H 1 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 17.541 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 H 9.625 C 15.767,83.846 29.057,72.489 32.57,50.247 H 46.913 C 50.775,72.775 66.307,83.893 73.711,88 H 63.602 C 55.695,83.379 50.214,73.507 50.159,73.407 c -0.267,-0.485 -0.875,-0.662 -1.357,-0.396 -0.484,0.266 -0.662,0.873 -0.396,1.357 0.21,0.383 4.67,8.425 11.601,13.632 H 33.441 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 49.08 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.553 -0.447,-1 -1,-1 H 78.222 C 76.161,87.159 53.146,77.158 48.752,49.104 c -0.077,-0.49 -0.493,-0.858 -0.99,-0.858"
id="path4821"
inkscape:connector-curvature="0" /></g><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="smoke"
style="display:inline"><path
d="m 29.271,25.283 c 0.179,0.522 -0.101,1.091 -0.623,1.27 -0.107,0.037 -0.216,0.054 -0.323,0.054 -0.416,0 -0.804,-0.262 -0.946,-0.677 -0.976,-2.858 0.208,-6.044 2.816,-7.576 2.525,-1.483 5.727,-1.065 7.784,1.015 0.389,0.393 0.385,1.026 -0.008,1.414 -0.393,0.389 -1.026,0.385 -1.414,-0.008 -1.414,-1.43 -3.614,-1.716 -5.349,-0.697 -1.793,1.053 -2.607,3.242 -1.937,5.205 z"
id="path4856"
inkscape:connector-curvature="0" /><path
d="m 59.002,9.435 c 4.279,0.188 8.044,3.055 9.37,7.136 0.137,0.422 0.529,0.691 0.951,0.691 0.102,0 0.207,-0.016 0.309,-0.049 0.525,-0.171 0.813,-0.735 0.642,-1.26 C 68.691,11.083 64.197,7.661 59.09,7.437 58.553,7.435 58.071,7.84 58.047,8.392 58.023,8.944 58.45,9.41 59.002,9.435 Z"
id="path4854"
inkscape:connector-curvature="0" /><path
d="m 15.571,32.367 h 0.85 c 0.47,2.492 2.663,4.383 5.29,4.383 h 5.372 c 2.044,0 3.708,1.663 3.708,3.708 v 4.135 h 2 v -4.135 c 0,-3.147 -2.561,-5.708 -5.708,-5.708 h -5.372 c -1.865,0 -3.383,-1.518 -3.383,-3.383 0,-0.552 -0.448,-1 -1,-1 h -1.757 c -3.271,0 -5.933,-2.662 -5.933,-5.933 0,-3.271 2.662,-5.933 5.933,-5.933 h 3.212 c 0.552,0 1,-0.448 1,-1 v -1.073 c 0,-3.677 2.992,-6.668 6.668,-6.668 0.552,0 1,-0.448 1,-1 V 8.377 C 27.452,4.861 30.313,2 33.829,2 c 3.516,0 6.377,2.861 6.377,6.377 V 8.76 c 0,0.552 0.448,1 1,1 h 3.454 c 2.576,0 4.671,2.095 4.671,4.67 0,0.552 0.448,1 1,1 h 3.058 c 5.435,0 9.856,4.421 9.856,9.855 0,0.552 0.448,1 1,1 h 2.791 c 1.905,0 3.455,1.55 3.455,3.456 0,1.906 -1.55,3.455 -3.455,3.455 H 52.499 c -3.147,0 -5.708,2.561 -5.708,5.708 v 6.034 h 2 v -6.034 c 0,-2.044 1.664,-3.708 3.708,-3.708 h 14.536 c 3.008,0 5.455,-2.447 5.455,-5.455 0,-3.008 -2.447,-5.456 -5.455,-5.456 H 65.202 C 64.693,18.214 59.589,13.43 53.388,13.43 H 51.255 C 50.771,10.225 47.997,7.76 44.659,7.76 H 42.183 C 41.866,3.428 38.24,0 33.829,0 29.398,0 25.76,3.457 25.471,7.815 21.152,8.304 17.784,11.98 17.784,16.428 v 0.073 h -2.213 c -4.375,0 -7.933,3.559 -7.933,7.933 0,4.374 3.559,7.933 7.933,7.933 z"
id="path4852"
inkscape:connector-curvature="0" /><path
d="M 43.531,44.77 V 30.819 c 0,-0.552 -0.448,-1 -1,-1 v 0 c -0.552,0 -1,0.448 -1,1 V 44.77 Z"
id="path4850"
inkscape:connector-curvature="0" /><path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 36.754563,48.297963 V 38.85399"
id="path4844"
inkscape:connector-curvature="0" /></g></svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

@ -0,0 +1,80 @@
<?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 83.521004 90"
enable-background="new 0 0 100 100"
xml:space="preserve"
id="svg4827"
sodipodi:docname="volcano0.svg"
width="83.521004"
height="90"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/tjhintz/tmp/volcano/volcano0.png"
inkscape:export-xdpi="106.654"
inkscape:export-ydpi="106.654"><metadata
id="metadata4833"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs4831" /><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="namedview4829"
showgrid="false"
inkscape:zoom="11.972707"
inkscape:cx="3.3491555"
inkscape:cy="44.177135"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="background"
style="display:inline"><path
d="m 22.724,90 h 3.742 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 h -3.742 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 z"
id="path4848"
inkscape:connector-curvature="0" /><path
d="m 31.709,48.246 c -0.5,0 -0.92,0.373 -0.991,0.868 C 26.776,76.3 7.844,86.928 5.8,88 H 1 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 17.541 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 H 9.625 C 15.767,83.846 29.057,72.489 32.57,50.247 H 46.913 C 50.775,72.775 66.307,83.893 73.711,88 H 63.602 C 55.695,83.379 50.214,73.507 50.159,73.407 c -0.267,-0.485 -0.875,-0.662 -1.357,-0.396 -0.484,0.266 -0.662,0.873 -0.396,1.357 0.21,0.383 4.67,8.425 11.601,13.632 H 33.441 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 49.08 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.553 -0.447,-1 -1,-1 H 78.222 C 76.161,87.159 53.146,77.158 48.752,49.104 c -0.077,-0.49 -0.493,-0.858 -0.99,-0.858"
id="path4821"
inkscape:connector-curvature="0" /></g><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="smoke"
style="display:inline"><path
d="m 38.275981,46.275652 c 0.0179,0.0522 -0.0101,0.1091 -0.0623,0.127 -0.0107,0.0037 -0.0216,0.0054 -0.0323,0.0054 -0.0416,0 -0.0804,-0.0262 -0.0946,-0.0677 -0.0976,-0.2858 0.0208,-0.6044 0.2816,-0.7576 0.2525,-0.1483 0.5727,-0.1065 0.7784,0.1015 0.0389,0.0393 0.0385,0.1026 -8e-4,0.1414 -0.0393,0.0389 -0.1026,0.0385 -0.1414,-8e-4 -0.1414,-0.143 -0.3614,-0.1716 -0.5349,-0.0697 -0.1793,0.1053 -0.2607,0.3242 -0.1937,0.5205 z"
id="path4856"
inkscape:connector-curvature="0"
style="stroke-width:0.1" /><path
d="m 41.249081,44.690852 c 0.4279,0.0188 0.8044,0.3055 0.937,0.7136 0.0137,0.0422 0.0529,0.0691 0.0951,0.0691 0.0102,0 0.0207,-0.0016 0.0309,-0.0049 0.0525,-0.0171 0.0813,-0.0735 0.0642,-0.126 -0.1583,-0.487 -0.6077,-0.8292 -1.1184,-0.8516 -0.0537,-2e-4 -0.1019,0.0403 -0.1043,0.0955 -0.0024,0.0552 0.0403,0.1018 0.0955,0.1043 z"
id="path4854"
inkscape:connector-curvature="0"
style="stroke-width:0.1" /><path
d="m 36.905981,46.984052 h 0.085 c 0.047,0.2492 0.2663,0.4383 0.529,0.4383 h 0.5372 c 0.2044,0 0.3708,0.1663 0.3708,0.3708 v 0.4135 h 0.2 v -0.4135 c 0,-0.3147 -0.2561,-0.5708 -0.5708,-0.5708 h -0.5372 c -0.1865,0 -0.3383,-0.1518 -0.3383,-0.3383 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 h -0.1757 c -0.3271,0 -0.5933,-0.2662 -0.5933,-0.5933 0,-0.3271 0.2662,-0.5933 0.5933,-0.5933 h 0.3212 c 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.1073 c 0,-0.3677 0.2992,-0.6668 0.6668,-0.6668 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.0383 c 1e-4,-0.3516 0.2862,-0.6377 0.6378,-0.6377 0.3516,0 0.6377,0.2861 0.6377,0.6377 v 0.0383 c 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3454 c 0.2576,0 0.4671,0.2095 0.4671,0.467 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3058 c 0.5435,0 0.9856,0.4421 0.9856,0.9855 0,0.0552 0.0448,0.1 0.1,0.1 h 0.2791 c 0.1905,0 0.3455,0.155 0.3455,0.3456 0,0.1906 -0.155,0.3455 -0.3455,0.3455 h -1.4537 c -0.3147,0 -0.5708,0.2561 -0.5708,0.5708 v 0.6034 h 0.2 v -0.6034 c 0,-0.2044 0.1664,-0.3708 0.3708,-0.3708 h 1.4536 c 0.3008,0 0.5455,-0.2447 0.5455,-0.5455 0,-0.3008 -0.2447,-0.5456 -0.5455,-0.5456 h -0.1833 c -0.0509,-0.6071 -0.5613,-1.0855 -1.1814,-1.0855 h -0.2133 c -0.0484,-0.3205 -0.3258,-0.567 -0.6596,-0.567 h -0.2476 c -0.0317,-0.4332 -0.3943,-0.776 -0.8354,-0.776 -0.4431,0 -0.8069,0.3457 -0.8358,0.7815 -0.4319,0.0489 -0.7687,0.4165 -0.7687,0.8613 v 0.0073 h -0.2213 c -0.4375,0 -0.7933,0.3559 -0.7933,0.7933 0,0.4374 0.3559,0.7933 0.7933,0.7933 z"
id="path4852"
inkscape:connector-curvature="0"
style="stroke-width:0.1" /><path
d="m 39.701981,48.224352 v -1.3951 c 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 v 0 c -0.0552,0 -0.1,0.0448 -0.1,0.1 v 1.3951 z"
id="path4850"
inkscape:connector-curvature="0"
style="stroke-width:0.1" /><path
style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 39.024337,48.577149 V 47.632751"
id="path4844"
inkscape:connector-curvature="0" /></g></svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

@ -0,0 +1,82 @@
<?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 83.521004 90"
enable-background="new 0 0 100 100"
xml:space="preserve"
id="svg4827"
sodipodi:docname="volcano1.svg"
width="83.521004"
height="90"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/tjhintz/tmp/volcano/volcano1.png"
inkscape:export-xdpi="106.654"
inkscape:export-ydpi="106.654"><metadata
id="metadata4833"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs4831" /><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="namedview4829"
showgrid="false"
inkscape:zoom="16"
inkscape:cx="19.364384"
inkscape:cy="35.768813"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="background"
style="display:inline"><path
d="m 22.724,90 h 3.742 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 h -3.742 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 z"
id="path4848"
inkscape:connector-curvature="0" /><path
d="m 31.709,48.246 c -0.5,0 -0.92,0.373 -0.991,0.868 C 26.776,76.3 7.844,86.928 5.8,88 H 1 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 17.541 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 H 9.625 C 15.767,83.846 29.057,72.489 32.57,50.247 H 46.913 C 50.775,72.775 66.307,83.893 73.711,88 H 63.602 C 55.695,83.379 50.214,73.507 50.159,73.407 c -0.267,-0.485 -0.875,-0.662 -1.357,-0.396 -0.484,0.266 -0.662,0.873 -0.396,1.357 0.21,0.383 4.67,8.425 11.601,13.632 H 33.441 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 49.08 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.553 -0.447,-1 -1,-1 H 78.222 C 76.161,87.159 53.146,77.158 48.752,49.104 c -0.077,-0.49 -0.493,-0.858 -0.99,-0.858"
id="path4821"
inkscape:connector-curvature="0" /></g><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="smoke"
style="display:inline"><g
id="g4911"
transform="matrix(2,0,0,2,-39.355281,-48.923119)"><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4856"
d="m 38.275981,46.275652 c 0.0179,0.0522 -0.0101,0.1091 -0.0623,0.127 -0.0107,0.0037 -0.0216,0.0054 -0.0323,0.0054 -0.0416,0 -0.0804,-0.0262 -0.0946,-0.0677 -0.0976,-0.2858 0.0208,-0.6044 0.2816,-0.7576 0.2525,-0.1483 0.5727,-0.1065 0.7784,0.1015 0.0389,0.0393 0.0385,0.1026 -8e-4,0.1414 -0.0393,0.0389 -0.1026,0.0385 -0.1414,-8e-4 -0.1414,-0.143 -0.3614,-0.1716 -0.5349,-0.0697 -0.1793,0.1053 -0.2607,0.3242 -0.1937,0.5205 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4854"
d="m 41.249081,44.690852 c 0.4279,0.0188 0.8044,0.3055 0.937,0.7136 0.0137,0.0422 0.0529,0.0691 0.0951,0.0691 0.0102,0 0.0207,-0.0016 0.0309,-0.0049 0.0525,-0.0171 0.0813,-0.0735 0.0642,-0.126 -0.1583,-0.487 -0.6077,-0.8292 -1.1184,-0.8516 -0.0537,-2e-4 -0.1019,0.0403 -0.1043,0.0955 -0.0024,0.0552 0.0403,0.1018 0.0955,0.1043 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4852"
d="m 36.905981,46.984052 h 0.085 c 0.047,0.2492 0.2663,0.4383 0.529,0.4383 h 0.5372 c 0.2044,0 0.3708,0.1663 0.3708,0.3708 v 0.4135 h 0.2 v -0.4135 c 0,-0.3147 -0.2561,-0.5708 -0.5708,-0.5708 h -0.5372 c -0.1865,0 -0.3383,-0.1518 -0.3383,-0.3383 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 h -0.1757 c -0.3271,0 -0.5933,-0.2662 -0.5933,-0.5933 0,-0.3271 0.2662,-0.5933 0.5933,-0.5933 h 0.3212 c 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.1073 c 0,-0.3677 0.2992,-0.6668 0.6668,-0.6668 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.0383 c 1e-4,-0.3516 0.2862,-0.6377 0.6378,-0.6377 0.3516,0 0.6377,0.2861 0.6377,0.6377 v 0.0383 c 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3454 c 0.2576,0 0.4671,0.2095 0.4671,0.467 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3058 c 0.5435,0 0.9856,0.4421 0.9856,0.9855 0,0.0552 0.0448,0.1 0.1,0.1 h 0.2791 c 0.1905,0 0.3455,0.155 0.3455,0.3456 0,0.1906 -0.155,0.3455 -0.3455,0.3455 h -1.4537 c -0.3147,0 -0.5708,0.2561 -0.5708,0.5708 v 0.6034 h 0.2 v -0.6034 c 0,-0.2044 0.1664,-0.3708 0.3708,-0.3708 h 1.4536 c 0.3008,0 0.5455,-0.2447 0.5455,-0.5455 0,-0.3008 -0.2447,-0.5456 -0.5455,-0.5456 h -0.1833 c -0.0509,-0.6071 -0.5613,-1.0855 -1.1814,-1.0855 h -0.2133 c -0.0484,-0.3205 -0.3258,-0.567 -0.6596,-0.567 h -0.2476 c -0.0317,-0.4332 -0.3943,-0.776 -0.8354,-0.776 -0.4431,0 -0.8069,0.3457 -0.8358,0.7815 -0.4319,0.0489 -0.7687,0.4165 -0.7687,0.8613 v 0.0073 h -0.2213 c -0.4375,0 -0.7933,0.3559 -0.7933,0.7933 0,0.4374 0.3559,0.7933 0.7933,0.7933 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4850"
d="m 39.701981,48.224352 v -1.3951 c 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 v 0 c -0.0552,0 -0.1,0.0448 -0.1,0.1 v 1.3951 z" /><path
inkscape:connector-curvature="0"
id="path4844"
d="M 39.024337,48.577149 V 47.632751"
style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1,82 @@
<?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 83.521004 90"
enable-background="new 0 0 100 100"
xml:space="preserve"
id="svg4827"
sodipodi:docname="volcano2.svg"
width="83.521004"
height="90"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/tjhintz/tmp/volcano/volcano2.png"
inkscape:export-xdpi="106.654"
inkscape:export-ydpi="106.654"><metadata
id="metadata4833"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs4831" /><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="namedview4829"
showgrid="false"
inkscape:zoom="11.313709"
inkscape:cx="-8.2897213"
inkscape:cy="46.522973"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="background"
style="display:inline"><path
d="m 22.724,90 h 3.742 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 h -3.742 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 z"
id="path4848"
inkscape:connector-curvature="0" /><path
d="m 31.709,48.246 c -0.5,0 -0.92,0.373 -0.991,0.868 C 26.776,76.3 7.844,86.928 5.8,88 H 1 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 17.541 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 H 9.625 C 15.767,83.846 29.057,72.489 32.57,50.247 H 46.913 C 50.775,72.775 66.307,83.893 73.711,88 H 63.602 C 55.695,83.379 50.214,73.507 50.159,73.407 c -0.267,-0.485 -0.875,-0.662 -1.357,-0.396 -0.484,0.266 -0.662,0.873 -0.396,1.357 0.21,0.383 4.67,8.425 11.601,13.632 H 33.441 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 49.08 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.553 -0.447,-1 -1,-1 H 78.222 C 76.161,87.159 53.146,77.158 48.752,49.104 c -0.077,-0.49 -0.493,-0.858 -0.99,-0.858"
id="path4821"
inkscape:connector-curvature="0" /></g><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="smoke"
style="display:inline"><g
id="g4911"
transform="matrix(4,0,0,4,-118.06584,-146.02873)"><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4856"
d="m 38.275981,46.275652 c 0.0179,0.0522 -0.0101,0.1091 -0.0623,0.127 -0.0107,0.0037 -0.0216,0.0054 -0.0323,0.0054 -0.0416,0 -0.0804,-0.0262 -0.0946,-0.0677 -0.0976,-0.2858 0.0208,-0.6044 0.2816,-0.7576 0.2525,-0.1483 0.5727,-0.1065 0.7784,0.1015 0.0389,0.0393 0.0385,0.1026 -8e-4,0.1414 -0.0393,0.0389 -0.1026,0.0385 -0.1414,-8e-4 -0.1414,-0.143 -0.3614,-0.1716 -0.5349,-0.0697 -0.1793,0.1053 -0.2607,0.3242 -0.1937,0.5205 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4854"
d="m 41.249081,44.690852 c 0.4279,0.0188 0.8044,0.3055 0.937,0.7136 0.0137,0.0422 0.0529,0.0691 0.0951,0.0691 0.0102,0 0.0207,-0.0016 0.0309,-0.0049 0.0525,-0.0171 0.0813,-0.0735 0.0642,-0.126 -0.1583,-0.487 -0.6077,-0.8292 -1.1184,-0.8516 -0.0537,-2e-4 -0.1019,0.0403 -0.1043,0.0955 -0.0024,0.0552 0.0403,0.1018 0.0955,0.1043 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4852"
d="m 36.905981,46.984052 h 0.085 c 0.047,0.2492 0.2663,0.4383 0.529,0.4383 h 0.5372 c 0.2044,0 0.3708,0.1663 0.3708,0.3708 v 0.4135 h 0.2 v -0.4135 c 0,-0.3147 -0.2561,-0.5708 -0.5708,-0.5708 h -0.5372 c -0.1865,0 -0.3383,-0.1518 -0.3383,-0.3383 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 h -0.1757 c -0.3271,0 -0.5933,-0.2662 -0.5933,-0.5933 0,-0.3271 0.2662,-0.5933 0.5933,-0.5933 h 0.3212 c 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.1073 c 0,-0.3677 0.2992,-0.6668 0.6668,-0.6668 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.0383 c 1e-4,-0.3516 0.2862,-0.6377 0.6378,-0.6377 0.3516,0 0.6377,0.2861 0.6377,0.6377 v 0.0383 c 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3454 c 0.2576,0 0.4671,0.2095 0.4671,0.467 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3058 c 0.5435,0 0.9856,0.4421 0.9856,0.9855 0,0.0552 0.0448,0.1 0.1,0.1 h 0.2791 c 0.1905,0 0.3455,0.155 0.3455,0.3456 0,0.1906 -0.155,0.3455 -0.3455,0.3455 h -1.4537 c -0.3147,0 -0.5708,0.2561 -0.5708,0.5708 v 0.6034 h 0.2 v -0.6034 c 0,-0.2044 0.1664,-0.3708 0.3708,-0.3708 h 1.4536 c 0.3008,0 0.5455,-0.2447 0.5455,-0.5455 0,-0.3008 -0.2447,-0.5456 -0.5455,-0.5456 h -0.1833 c -0.0509,-0.6071 -0.5613,-1.0855 -1.1814,-1.0855 h -0.2133 c -0.0484,-0.3205 -0.3258,-0.567 -0.6596,-0.567 h -0.2476 c -0.0317,-0.4332 -0.3943,-0.776 -0.8354,-0.776 -0.4431,0 -0.8069,0.3457 -0.8358,0.7815 -0.4319,0.0489 -0.7687,0.4165 -0.7687,0.8613 v 0.0073 h -0.2213 c -0.4375,0 -0.7933,0.3559 -0.7933,0.7933 0,0.4374 0.3559,0.7933 0.7933,0.7933 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4850"
d="m 39.701981,48.224352 v -1.3951 c 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 v 0 c -0.0552,0 -0.1,0.0448 -0.1,0.1 v 1.3951 z" /><path
inkscape:connector-curvature="0"
id="path4844"
d="M 39.024337,48.577149 V 47.632751"
style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

@ -0,0 +1,82 @@
<?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 83.521004 90"
enable-background="new 0 0 100 100"
xml:space="preserve"
id="svg4827"
sodipodi:docname="volcano3.svg"
width="83.521004"
height="90"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/tjhintz/tmp/volcano/volcano3.png"
inkscape:export-xdpi="106.66666"
inkscape:export-ydpi="106.66666"><metadata
id="metadata4833"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs4831" /><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="namedview4829"
showgrid="false"
inkscape:zoom="8"
inkscape:cx="-43.021575"
inkscape:cy="60.241354"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="background"
style="display:inline"><path
d="m 22.724,90 h 3.742 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 h -3.742 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 z"
id="path4848"
inkscape:connector-curvature="0" /><path
d="m 31.709,48.246 c -0.5,0 -0.92,0.373 -0.991,0.868 C 26.776,76.3 7.844,86.928 5.8,88 H 1 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 17.541 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 H 9.625 C 15.767,83.846 29.057,72.489 32.57,50.247 H 46.913 C 50.775,72.775 66.307,83.893 73.711,88 H 63.602 C 55.695,83.379 50.214,73.507 50.159,73.407 c -0.267,-0.485 -0.875,-0.662 -1.357,-0.396 -0.484,0.266 -0.662,0.873 -0.396,1.357 0.21,0.383 4.67,8.425 11.601,13.632 H 33.441 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 49.08 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.553 -0.447,-1 -1,-1 H 78.222 C 76.161,87.159 53.146,77.158 48.752,49.104 c -0.077,-0.49 -0.493,-0.858 -0.99,-0.858"
id="path4821"
inkscape:connector-curvature="0" /></g><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="smoke"
style="display:inline"><g
id="g4911"
transform="matrix(8,0,0,8,-275.48696,-340.41183)"><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4856"
d="m 38.275981,46.275652 c 0.0179,0.0522 -0.0101,0.1091 -0.0623,0.127 -0.0107,0.0037 -0.0216,0.0054 -0.0323,0.0054 -0.0416,0 -0.0804,-0.0262 -0.0946,-0.0677 -0.0976,-0.2858 0.0208,-0.6044 0.2816,-0.7576 0.2525,-0.1483 0.5727,-0.1065 0.7784,0.1015 0.0389,0.0393 0.0385,0.1026 -8e-4,0.1414 -0.0393,0.0389 -0.1026,0.0385 -0.1414,-8e-4 -0.1414,-0.143 -0.3614,-0.1716 -0.5349,-0.0697 -0.1793,0.1053 -0.2607,0.3242 -0.1937,0.5205 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4854"
d="m 41.249081,44.690852 c 0.4279,0.0188 0.8044,0.3055 0.937,0.7136 0.0137,0.0422 0.0529,0.0691 0.0951,0.0691 0.0102,0 0.0207,-0.0016 0.0309,-0.0049 0.0525,-0.0171 0.0813,-0.0735 0.0642,-0.126 -0.1583,-0.487 -0.6077,-0.8292 -1.1184,-0.8516 -0.0537,-2e-4 -0.1019,0.0403 -0.1043,0.0955 -0.0024,0.0552 0.0403,0.1018 0.0955,0.1043 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4852"
d="m 36.905981,46.984052 h 0.085 c 0.047,0.2492 0.2663,0.4383 0.529,0.4383 h 0.5372 c 0.2044,0 0.3708,0.1663 0.3708,0.3708 v 0.4135 h 0.2 v -0.4135 c 0,-0.3147 -0.2561,-0.5708 -0.5708,-0.5708 h -0.5372 c -0.1865,0 -0.3383,-0.1518 -0.3383,-0.3383 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 h -0.1757 c -0.3271,0 -0.5933,-0.2662 -0.5933,-0.5933 0,-0.3271 0.2662,-0.5933 0.5933,-0.5933 h 0.3212 c 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.1073 c 0,-0.3677 0.2992,-0.6668 0.6668,-0.6668 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.0383 c 1e-4,-0.3516 0.2862,-0.6377 0.6378,-0.6377 0.3516,0 0.6377,0.2861 0.6377,0.6377 v 0.0383 c 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3454 c 0.2576,0 0.4671,0.2095 0.4671,0.467 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3058 c 0.5435,0 0.9856,0.4421 0.9856,0.9855 0,0.0552 0.0448,0.1 0.1,0.1 h 0.2791 c 0.1905,0 0.3455,0.155 0.3455,0.3456 0,0.1906 -0.155,0.3455 -0.3455,0.3455 h -1.4537 c -0.3147,0 -0.5708,0.2561 -0.5708,0.5708 v 0.6034 h 0.2 v -0.6034 c 0,-0.2044 0.1664,-0.3708 0.3708,-0.3708 h 1.4536 c 0.3008,0 0.5455,-0.2447 0.5455,-0.5455 0,-0.3008 -0.2447,-0.5456 -0.5455,-0.5456 h -0.1833 c -0.0509,-0.6071 -0.5613,-1.0855 -1.1814,-1.0855 h -0.2133 c -0.0484,-0.3205 -0.3258,-0.567 -0.6596,-0.567 h -0.2476 c -0.0317,-0.4332 -0.3943,-0.776 -0.8354,-0.776 -0.4431,0 -0.8069,0.3457 -0.8358,0.7815 -0.4319,0.0489 -0.7687,0.4165 -0.7687,0.8613 v 0.0073 h -0.2213 c -0.4375,0 -0.7933,0.3559 -0.7933,0.7933 0,0.4374 0.3559,0.7933 0.7933,0.7933 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4850"
d="m 39.701981,48.224352 v -1.3951 c 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 v 0 c -0.0552,0 -0.1,0.0448 -0.1,0.1 v 1.3951 z" /><path
inkscape:connector-curvature="0"
id="path4844"
d="M 39.024337,48.577149 V 47.632751"
style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

@ -0,0 +1,82 @@
<?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 83.521004 90"
enable-background="new 0 0 100 100"
xml:space="preserve"
id="svg4827"
sodipodi:docname="volcano4.svg"
width="83.521004"
height="90"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/tjhintz/tmp/volcano/volcano4.png"
inkscape:export-xdpi="106.66666"
inkscape:export-ydpi="106.66666"><metadata
id="metadata4833"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs4831" /><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="namedview4829"
showgrid="false"
inkscape:zoom="8"
inkscape:cx="-14.5812"
inkscape:cy="44.273261"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="background"
style="display:inline"><path
d="m 22.724,90 h 3.742 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 h -3.742 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 z"
id="path4848"
inkscape:connector-curvature="0" /><path
d="m 31.709,48.246 c -0.5,0 -0.92,0.373 -0.991,0.868 C 26.776,76.3 7.844,86.928 5.8,88 H 1 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 17.541 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.552 -0.448,-1 -1,-1 H 9.625 C 15.767,83.846 29.057,72.489 32.57,50.247 H 46.913 C 50.775,72.775 66.307,83.893 73.711,88 H 63.602 C 55.695,83.379 50.214,73.507 50.159,73.407 c -0.267,-0.485 -0.875,-0.662 -1.357,-0.396 -0.484,0.266 -0.662,0.873 -0.396,1.357 0.21,0.383 4.67,8.425 11.601,13.632 H 33.441 c -0.552,0 -1,0.448 -1,1 v 0 c 0,0.552 0.448,1 1,1 h 49.08 c 0.552,0 1,-0.448 1,-1 v 0 c 0,-0.553 -0.447,-1 -1,-1 H 78.222 C 76.161,87.159 53.146,77.158 48.752,49.104 c -0.077,-0.49 -0.493,-0.858 -0.99,-0.858"
id="path4821"
inkscape:connector-curvature="0" /></g><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="smoke"
style="display:inline"><g
id="g4911"
transform="matrix(9.9676375,0,0,9.9676375,-351.91862,-436.06862)"><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4856"
d="m 38.275981,46.275652 c 0.0179,0.0522 -0.0101,0.1091 -0.0623,0.127 -0.0107,0.0037 -0.0216,0.0054 -0.0323,0.0054 -0.0416,0 -0.0804,-0.0262 -0.0946,-0.0677 -0.0976,-0.2858 0.0208,-0.6044 0.2816,-0.7576 0.2525,-0.1483 0.5727,-0.1065 0.7784,0.1015 0.0389,0.0393 0.0385,0.1026 -8e-4,0.1414 -0.0393,0.0389 -0.1026,0.0385 -0.1414,-8e-4 -0.1414,-0.143 -0.3614,-0.1716 -0.5349,-0.0697 -0.1793,0.1053 -0.2607,0.3242 -0.1937,0.5205 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4854"
d="m 41.249081,44.690852 c 0.4279,0.0188 0.8044,0.3055 0.937,0.7136 0.0137,0.0422 0.0529,0.0691 0.0951,0.0691 0.0102,0 0.0207,-0.0016 0.0309,-0.0049 0.0525,-0.0171 0.0813,-0.0735 0.0642,-0.126 -0.1583,-0.487 -0.6077,-0.8292 -1.1184,-0.8516 -0.0537,-2e-4 -0.1019,0.0403 -0.1043,0.0955 -0.0024,0.0552 0.0403,0.1018 0.0955,0.1043 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4852"
d="m 36.905981,46.984052 h 0.085 c 0.047,0.2492 0.2663,0.4383 0.529,0.4383 h 0.5372 c 0.2044,0 0.3708,0.1663 0.3708,0.3708 v 0.4135 h 0.2 v -0.4135 c 0,-0.3147 -0.2561,-0.5708 -0.5708,-0.5708 h -0.5372 c -0.1865,0 -0.3383,-0.1518 -0.3383,-0.3383 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 h -0.1757 c -0.3271,0 -0.5933,-0.2662 -0.5933,-0.5933 0,-0.3271 0.2662,-0.5933 0.5933,-0.5933 h 0.3212 c 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.1073 c 0,-0.3677 0.2992,-0.6668 0.6668,-0.6668 0.0552,0 0.1,-0.0448 0.1,-0.1 v -0.0383 c 1e-4,-0.3516 0.2862,-0.6377 0.6378,-0.6377 0.3516,0 0.6377,0.2861 0.6377,0.6377 v 0.0383 c 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3454 c 0.2576,0 0.4671,0.2095 0.4671,0.467 0,0.0552 0.0448,0.1 0.1,0.1 h 0.3058 c 0.5435,0 0.9856,0.4421 0.9856,0.9855 0,0.0552 0.0448,0.1 0.1,0.1 h 0.2791 c 0.1905,0 0.3455,0.155 0.3455,0.3456 0,0.1906 -0.155,0.3455 -0.3455,0.3455 h -1.4537 c -0.3147,0 -0.5708,0.2561 -0.5708,0.5708 v 0.6034 h 0.2 v -0.6034 c 0,-0.2044 0.1664,-0.3708 0.3708,-0.3708 h 1.4536 c 0.3008,0 0.5455,-0.2447 0.5455,-0.5455 0,-0.3008 -0.2447,-0.5456 -0.5455,-0.5456 h -0.1833 c -0.0509,-0.6071 -0.5613,-1.0855 -1.1814,-1.0855 h -0.2133 c -0.0484,-0.3205 -0.3258,-0.567 -0.6596,-0.567 h -0.2476 c -0.0317,-0.4332 -0.3943,-0.776 -0.8354,-0.776 -0.4431,0 -0.8069,0.3457 -0.8358,0.7815 -0.4319,0.0489 -0.7687,0.4165 -0.7687,0.8613 v 0.0073 h -0.2213 c -0.4375,0 -0.7933,0.3559 -0.7933,0.7933 0,0.4374 0.3559,0.7933 0.7933,0.7933 z" /><path
style="stroke-width:0.1"
inkscape:connector-curvature="0"
id="path4850"
d="m 39.701981,48.224352 v -1.3951 c 0,-0.0552 -0.0448,-0.1 -0.1,-0.1 v 0 c -0.0552,0 -0.1,0.0448 -0.1,0.1 v 1.3951 z" /><path
inkscape:connector-curvature="0"
id="path4844"
d="M 39.024337,48.577149 V 47.632751"
style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

7
package-lock.json generated

@ -1,5 +1,5 @@
{ {
"name": "webpack-test", "name": "farm",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
@ -3462,6 +3462,11 @@
"safe-buffer": "~5.1.1" "safe-buffer": "~5.1.1"
} }
}, },
"cookies-js": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/cookies-js/-/cookies-js-1.2.3.tgz",
"integrity": "sha1-AzFQSefFK+4/cxhqaRZ+qw3bLTE="
},
"copy-concurrently": { "copy-concurrently": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",

@ -45,6 +45,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.26", "@fortawesome/fontawesome-svg-core": "^1.2.26",
"@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",
"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",

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

@ -0,0 +1,108 @@
// Copyright 2020 Thomas Hintz
//
// This file is part of the Alpha Centauri Farming project.
//
// The Alpha Centauri Farming project is free software: you can
// redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// The Alpha Centauri Farming project is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Alpha Centauri Farming project. If not, see
// <https://www.gnu.org/licenses/>.
import React from 'react'
import { GroupBox, Row, Col, Button } from '../widgets.jsx'
export default class CreateAccount extends React.Component {
state = {
username: '',
email: '',
password: '',
confirmPassword: '',
loading: false
}
onChange = (e) => {
const target = e.target,
value = target.value,
name = target.name;
this.setState({
[name]: value
});
}
onSubmit = (e) => {
e.preventDefault();
this.setState({ loading: true });
this.props.createAccount(this.state);
}
componentDidUpdate(prevProps) {
if (this.state.loading && prevProps.errors !== this.props.errors) {
this.setState({ loading: false });
}
}
render() {
return (
<GroupBox title="Create Account">
<form onSubmit={this.onSubmit}>
<Row>
<Col width="12">
<label>
Username
<input onChange={this.onChange} required name="username" type="text" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
<label>
Email (optional)
<input onChange={this.onChange} name="email" type="email" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
<label>
Password
<input onChange={this.onChange} required name="password" type="password" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
<label>
Confirm Password
<input onChange={this.onChange} required name="confirmPassword" type="password" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
{!this.state.loading ? (
<>
{this.props.errors.map((err, i) => (
<p key={i}>
Error: {err}
</p>
))}
<Button type="submit">Create Account</Button>
</>
) : (
<span>Creating Account...</span>
)}
</Col>
</Row>
</form>
</GroupBox>
);
}
}

@ -19,21 +19,34 @@
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import Cookies from 'cookies-js'
import { GroupBox, Row, Col, Button } from '../widgets.jsx' import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import { showNewGame, showJoinGame } from '../app/actions.js' import { showNewGame, showJoinGame } from '../app/actions.js'
class CreateOrJoin extends React.Component { class CreateOrJoin extends React.Component {
signOut = (e) => {
e.preventDefault();
this.props.signOut();
Cookies.expire('awful-cookie');
}
render() { render() {
return ( return (
<Fragment> <Fragment>
<Button size='large' className='shadow' onClick={this.props.showNewGame}> <Button size='large' className='shadow' onClick={this.props.showNewGame}>
New Game New Game
</Button> </Button>
{this.props.start.start.games.length > 0 ? ( {(this.props.start.start.games.length > 0) || (this.props.start.start.openGames.length > 0) ? (
<Button size='large' className='shadow' onClick={this.props.showJoinGame}> <Button size='large' className='shadow' onClick={this.props.showJoinGame}>
Join Game Join Game
</Button> </Button>
) : (<Fragment />)} ) : (<Fragment />)}
{this.props.start.start.user ? (
<Button size='large' className='shadow sign-out-button' onClick={this.signOut}>
Sign Out
</Button>
) : (<></>)}
</Fragment> </Fragment>
); );
} }

@ -24,6 +24,7 @@ import WheatImg from './../../../assets/img/wheat.svg'
import TractorImg from './../../../assets/img/tractor-icon.svg' import TractorImg from './../../../assets/img/tractor-icon.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 React, { Fragment } from 'react' import React, { Fragment } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
@ -45,21 +46,21 @@ 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 } from './interface.js' buyUncleBert, skip, endAiTurn, startGame } 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) +
(player.assets.fruit * 5000) + (player.assets.fruit * 5000) +
(player.assets.cows * 500) + (player.assets.cows * 500) +
((player.assets.harvester + player.assets.tractor) * 10000) + ((player.assets.harvester + player.assets.tractor) * 10000) +
player.displayCash - player.debt; player.displayCash - player.debt;
} }
function assetsValue(player) { function assetsValue(player) {
return ((player.assets.hay + player.assets.grain) * 2000) + return ((player.assets.hay + player.assets.grain) * 2000) +
(player.assets.fruit * 5000) + (player.assets.fruit * 5000) +
(player.assets.cows * 500) + (player.assets.cows * 500) +
((player.assets.harvester + player.assets.tractor) * 10000); ((player.assets.harvester + player.assets.tractor) * 10000);
} }
function getElementValue(id) { function getElementValue(id) {
@ -464,20 +465,20 @@ class FarmsContainer extends React.Component {
return ( return (
<GroupBox title='Farms'> <GroupBox title='Farms'>
<b>Ridges</b>: <b>Ridges</b>:
{ridgeNames.map((ridge, idx) => ( {ridgeNames.map((ridge, idx) => (
<div key={idx} className={"farms-ridge player-" + ridges['ridge' + (idx + 1)]}> <div key={idx} className={"farms-ridge player-" + ridges['ridge' + (idx + 1)]}>
<span>{ridge}</span> <span>{ridge}</span>
<span className='num-cows'>{'(' + ((idx + 2) * 10) + ' cows)'}</span> <span className='num-cows'>{'(' + ((idx + 2) * 10) + ' cows)'}</span>
</div> </div>
))} ))}
<br /> <br />
{this.props.otherPlayers {this.props.otherPlayers
.map(p => ( .map(p => (
<div key={p.player.name}> <div key={p.player.name}>
<PlayerColorIcon color={p.player.color} />{'\u00A0'} <PlayerColorIcon color={p.player.color} />{'\u00A0'}
<b>{p.player.name}</b> <PlayerResources player={p.player} /> <b>{p.player.name}</b> <PlayerResources player={p.player} />
<br /> <br /> <br /> <br />
</div>))} </div>))}
</GroupBox> </GroupBox>
); );
} }
@ -521,11 +522,11 @@ const makeDefaultToTrade = () => {
class TradeContainer2 extends React.Component { class TradeContainer2 extends React.Component {
resources = [{ img: HayImg, resources = [{ img: HayImg,
h: '120', h: '120',
s: '100', s: '100',
label: 'acres of Hay', label: 'acres of Hay',
key: 'hay', key: 'hay',
amount: 10 amount: 10
}, { }, {
img: WheatImg, img: WheatImg,
h: '41', h: '41',
@ -859,7 +860,7 @@ class CCBY extends React.Component {
render() { render() {
return ( return (
<Fragment> <Fragment>
License Creative Commons <a href='https://creativecommons.org/licenses/by/3.0/us/legalcode'>CCBY</a> License Creative Commons <a href={`https://creativecommons.org/licenses/by/${this.props.version ? this.props.version : 3}.0/us/legalcode`}>CCBY</a>
</Fragment> </Fragment>
); );
} }
@ -880,6 +881,9 @@ class Misc extends React.Component {
<li> <li>
<img src={TractorFullImg} /> <img src={TractorImg} /> Copyright Nick Roach with modifications by Thomas Hintz - License <a href='GPL http://www.gnu.org/copyleft/gpl.html'>GPL</a> <img src={TractorFullImg} /> <img src={TractorImg} /> Copyright Nick Roach with modifications by Thomas Hintz - License <a href='GPL http://www.gnu.org/copyleft/gpl.html'>GPL</a>
</li> </li>
<li>
<img src={VolcanoImg} /> Copyright <a href="https://thenounproject.com/Maludk/">Laymik</a> - <CCBY />
</li>
<li> <li>
<img src={CornImg} /> <img src={FruitImg} /> Copyright <a href='https://madexmade.com/'>Made</a> - <CCBY /> <img src={CornImg} /> <img src={FruitImg} /> Copyright <a href='https://madexmade.com/'>Made</a> - <CCBY />
</li> </li>
@ -1090,6 +1094,7 @@ class Die extends React.Component {
trigger = 0; trigger = 0;
decayFactorPct = 0.4; decayFactorPct = 0.4;
showScreenTimerId = false; showScreenTimerId = false;
rollIndex = 0;
constructor(props) { constructor(props) {
super(props); super(props);
@ -1107,12 +1112,16 @@ class Die extends React.Component {
} }
roll() { roll() {
let roll = random(6); if (!this.props.rolls) {
while (roll === this.lastRoll) { let roll = random(6);
roll = random(6); while (roll === this.lastRoll) {
roll = random(6);
}
this.lastRoll = roll;
return roll;
} else {
return this.props.rolls[++this.rollIndex];
} }
this.lastRoll = roll;
return roll;
} }
tick = () => { tick = () => {
@ -1272,6 +1281,7 @@ class Rolling extends React.Component {
return ( return (
<GroupBox title={this.props.name + ' is rolling!'}> <GroupBox title={this.props.name + ' is rolling!'}>
<Die decay={true} num={this.props.num} ms={2000} roll={true} <Die decay={true} num={this.props.num} ms={2000} roll={true}
rolls={this.props.rolls}
showScreen={this.props.showScreen} showScreen={this.props.showScreen}
skip={this.props.skip} skip={this.props.skip}
autoSkip={this.props.autoSkip} autoSkip={this.props.autoSkip}
@ -1354,6 +1364,7 @@ class Harvest extends React.Component {
break; break;
case 'roll': case 'roll':
view = (<Die decay={true} num={this.props.rolled} ms={2000} roll={true} view = (<Die decay={true} num={this.props.rolled} ms={2000} roll={true}
rolls={this.props.rolls}
showScreen={() => this.nextView('income')} showScreen={() => this.nextView('income')}
skip={this.props.player.name === this.props.game.currentPlayer} skip={this.props.player.name === this.props.game.currentPlayer}
autoSkip={this.props.autoSkip === 'die'} autoSkip={this.props.autoSkip === 'die'}
@ -1569,6 +1580,7 @@ class Action extends React.Component {
break; break;
case 'harvest': case 'harvest':
view = (<Harvest rolled={this.props.ui.actionValue.rolled} view = (<Harvest rolled={this.props.ui.actionValue.rolled}
rolls={this.props.ui.actionValue.rolls}
player={this.props.player} player={this.props.player}
currentPlayer={currentPlayer} currentPlayer={currentPlayer}
harvestMult={this.props.ui.actionValue.harvestMult} harvestMult={this.props.ui.actionValue.harvestMult}
@ -1635,6 +1647,7 @@ class Action extends React.Component {
(this.props.ui.actionValue.to < this.props.ui.actionValue.from ? (this.props.ui.actionValue.to < this.props.ui.actionValue.from ?
this.props.ui.actionValue.from - 49 : this.props.ui.actionValue.from); this.props.ui.actionValue.from - 49 : this.props.ui.actionValue.from);
view = (<Rolling num={roll} view = (<Rolling num={roll}
rolls={this.props.ui.actionValue.rolls}
name={this.props.game.currentPlayer} name={this.props.game.currentPlayer}
showScreen={(this.props.player.name === this.props.game.currentPlayer || showScreen={(this.props.player.name === this.props.game.currentPlayer ||
currentPlayer.ai) currentPlayer.ai)
@ -1764,14 +1777,16 @@ class AlertOverlay extends React.Component {
render() { render() {
return ( return (
<div className={'alert-overlay' + (this.state.visible ? '' : ' hidden') }> <div className={'alert-overlay' + (this.state.visible ? '' : ' hidden') }>
<div onClick={this.hide} className='alert-overlay-hide'> {!this.props.preventHiding ? (
<FontAwesomeIcon icon={faTimes} /> <div onClick={this.hide} className='alert-overlay-hide'>
</div> <FontAwesomeIcon icon={faTimes} />
</div>
) : (<></>)}
<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> <Button onClick={this.buttonClick}>{this.props.buttonText}</Button>
<a onClick={this.hide}>close</a> {!this.props.preventHiding ? (<a onClick={this.hide}>close</a>) : (<></>)}
</div> </div>
</div> </div>
); );
@ -2015,6 +2030,30 @@ class BoardApp extends React.Component {
</Fragment> </Fragment>
</AlertOverlay> </AlertOverlay>
); );
} else if (alert && alert.type === ALERTS.preGame) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Start Game'
hideHandler={() => 'nothing'}
preventHiding={true}
handler={startGame}>
<Fragment>
<h1>Pre Game</h1>
<p>When all players have joined click 'Start Game'!</p>
<h3>Players</h3>
<ul>
<li>{this.props.player.name}</li>
{this.props.game.otherPlayers.map((p, i) => (
<li key={i}>
{p.player.name}
</li>
))}
</ul>
</Fragment>
</AlertOverlay>
);
} else if (alert && alert.type === ALERTS.proposedTrade) { } else if (alert && alert.type === ALERTS.proposedTrade) {
alertOverlay = ( alertOverlay = (
<AlertOverlay visible={true} <AlertOverlay visible={true}

@ -30,7 +30,8 @@ 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 }
let store; let store;
let movingTimer = 0; let movingTimer = 0;
@ -44,6 +45,13 @@ function handleMessage(evt) {
return; return;
} }
batch(() => { batch(() => {
if (data.game.state === GAME_STATES.preGame) {
store.dispatch(alert(ALERTS.preGame, '', 'pre-game'));
}
if (store.getState().farm.game.state === GAME_STATES.preGame &&
data.game.state === GAME_STATES.preTurn) {
store.dispatch(alertHandled('pre-game'));
}
if (data.player.state === GAME_STATES.preTurn && if (data.player.state === GAME_STATES.preTurn &&
data.game.otherPlayers.length > 0 && data.game.otherPlayers.length > 0 &&
store.getState().farm.player.state !== GAME_STATES.preTurn) { store.getState().farm.player.state !== GAME_STATES.preTurn) {
@ -186,6 +194,10 @@ function skip(component) {
sendCommand({ type: 'skip', component }); sendCommand({ type: 'skip', component });
} }
function startGame() {
sendCommand({ type: 'start-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) {

@ -23,6 +23,7 @@ import { GroupBox, Row, Col, Button } from '../widgets.jsx'
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 LoginOrCreateAccount from '../login-or-create-account/LoginOrCreateAccount.jsx';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons' import { faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons'
@ -36,14 +37,15 @@ class JoinGame extends React.Component {
super(props); super(props);
this.state = { this.state = {
screen: JoinGameScreens.list, screen: JoinGameScreens.list,
game: null game: null,
showSignIn: false
}; };
} }
handleClickGame = game => { handleClickGame = game => {
this.setState({ screen: JoinGameScreens.details, this.setState({ screen: JoinGameScreens.details,
game: game game: game
}); });
} }
handleBack = e => { handleBack = e => {
@ -54,59 +56,82 @@ class JoinGame extends React.Component {
} }
} }
handleJoinAsExisting = e => { handleJoinAsExisting = (e, id) => {
e.preventDefault(); e.preventDefault();
this.props.startOrJoinGame({ type: 'join-as-existing', this.props.startOrJoinGame({ type: 'join-as-existing',
playerName: e.target.text, gameId: id });
gameId: this.state.game.id, }
gameName: this.state.game.name });
showSignIn = (e) => {
e.preventDefault();
this.setState(state => { return { showSignIn: !state.showSignIn }; });
} }
render() { render() {
return ( return (
<GroupBox title={( <GroupBox title={(
<Fragment> <Fragment>
<a href="#" onClick={this.handleBack}> <a href="#" onClick={this.handleBack}>
<FontAwesomeIcon icon={faArrowCircleLeft} /> <FontAwesomeIcon icon={faArrowCircleLeft} />
</a> </a>
Join Game Join Game
</Fragment> </Fragment>
)}> )}>
<Row> <Row>
<Col width='12'> <Col width='12'>
{this.state.screen === JoinGameScreens.list ? {this.state.screen === JoinGameScreens.list ?
(<ul> (<>
<h3>My Games</h3>
{(!this.props.user && !this.state.showSignIn) ? (
<a onClick={this.showSignIn}>Sign In to see your games</a>
) : (<></>)}
{(!this.props.user && this.state.showSignIn) ? (
<>
<hr />
<LoginOrCreateAccount login={this.props.login}
createAccount={this.props.createAccount}
errors={this.props.errors}
showLogin={true}
/>
<hr />
</>
) : (<></>)}
<ul>
{this.props.games {this.props.games
.map((g, i) => .map((g, i) =>
(<li key={i}> (<li key={i}>
<a onClick={e => { <a onClick={(e) => this.handleJoinAsExisting(e, g.id)}>{g.name}</a>
e.preventDefault(); </li>))}
this.handleClickGame(g); }}>{g.name}</a> </ul>
</li>))} <h3>Open Games</h3>
</ul>) <ul>
: ( {this.props.openGames
<Fragment> .map((g, i) =>
<h3><b>Game:</b> {this.state.game.name}</h3> (<li key={i}>
<h4>Join as existing player:</h4> <a onClick={e => {
<ul> e.preventDefault();
{this.state.game.players.map((p, i) => this.handleClickGame(g); }}>{g.name}</a>
(<li key={i}> </li>))}
<a onClick={this.handleJoinAsExisting}> </ul>
{p} </>
</a> )
</li>))} : (
</ul> <Fragment>
<NewGame colors={this.state.game.colors} <h3><b>Game:</b> {this.state.game.name}</h3>
button={'Join'} <NewGame colors={this.state.game.colors}
showGameName={false} button={'Join'}
gameName={this.state.game.name} showGameName={false}
gameId={this.state.game.id} gameName={this.state.game.name}
type={'join-game'} gameId={this.state.game.id}
hideBack={true} type={'join-game'}
title={'Join as New Player'} /> hideBack={true}
</Fragment>)} createAccount={this.props.createAccount}
</Col> login={this.props.login}
</Row> errors={this.props.errors}
title={'Join as New Player'} />
</Fragment>)}
</Col>
</Row>
</GroupBox> </GroupBox>
); );
} }

@ -0,0 +1,50 @@
// Copyright 2020 Thomas Hintz
//
// This file is part of the Alpha Centauri Farming project.
//
// The Alpha Centauri Farming project is free software: you can
// redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// The Alpha Centauri Farming project is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Alpha Centauri Farming project. If not, see
// <https://www.gnu.org/licenses/>.
import React from 'react'
import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import CreateAccount from '../create-account/CreateAccount.jsx';
import Login from '../login/Login.jsx';
export default class LoginOrCreateAccount extends React.Component {
state = {
showLogin: !!this.props.showLogin
}
toggleLogin = (e) => {
e.preventDefault();
this.setState(state => { return { showLogin: !state.showLogin }; });
}
render() {
return (
<>
{this.state.showLogin ? (
<Login errors={this.props.errors} login={this.props.login} />
) : (
<CreateAccount errors={this.props.errors}
createAccount={this.props.createAccount} />
)}
<div className="center">
<a onClick={this.toggleLogin}>{this.state.showLogin ? 'Create Account' : 'Login'}</a>
</div>
</>
);
}
}

@ -0,0 +1,90 @@
// Copyright 2020 Thomas Hintz
//
// This file is part of the Alpha Centauri Farming project.
//
// The Alpha Centauri Farming project is free software: you can
// redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// The Alpha Centauri Farming project is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Alpha Centauri Farming project. If not, see
// <https://www.gnu.org/licenses/>.
import React from 'react'
import { GroupBox, Row, Col, Button } from '../widgets.jsx'
export default class Login extends React.Component {
state = {
username: '',
password: '',
loading: false
}
onChange = (e) => {
const target = e.target,
value = target.value,
name = target.name;
this.setState({
[name]: value
});
}
onSubmit = (e) => {
e.preventDefault();
this.setState({ loading: true });
this.props.login(this.state);
}
componentDidUpdate(prevProps) {
if (this.state.loading && prevProps.errors !== this.props.errors) {
this.setState({ loading: false });
}
}
render() {
return (
<GroupBox title="Login">
<form onSubmit={this.onSubmit}>
<Row>
<Col width="12">
<label>
Username
<input onChange={this.onChange} required name="username" type="text" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
<label>
Password
<input onChange={this.onChange} required name="password" type="password" />
</label>
</Col>
</Row>
<Row>
<Col width="12">
{!this.state.loading ? (
<>
{this.props.errors.map((err, i) => (
<p key={i}>
Error: {err}
</p>
))}
<Button type="submit">Login</Button>
</>
) : (
<span>Logging in...</span>
)}
</Col>
</Row>
</form>
</GroupBox>
);
}
}

@ -20,6 +20,7 @@ import React, { Fragment } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { GroupBox, Row, Col, Button } from '../widgets.jsx' import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import LoginOrCreateAccount from '../login-or-create-account/LoginOrCreateAccount.jsx';
import { startOrJoinGame } from '../start/actions.js' import { startOrJoinGame } from '../start/actions.js'
import { start } from '../app/actions.js' import { start } from '../app/actions.js'
@ -53,7 +54,6 @@ class NewGame extends React.Component {
super(props); super(props);
this.state = { this.state = {
showSettings: false, showSettings: false,
playerName: '',
checkedColor: props.colors[0], checkedColor: props.colors[0],
gameId: typeof props.gameId === 'undefined' ? -1 : props.gameId, gameId: typeof props.gameId === 'undefined' ? -1 : props.gameId,
gameName: props.gameName || '', gameName: props.gameName || '',
@ -63,7 +63,8 @@ class NewGame extends React.Component {
auditThreshold: 250000, auditThreshold: 250000,
startingCash: 5000, startingCash: 5000,
startingDebt: 5000, startingDebt: 5000,
trade: true trade: true,
showLogin: false
}; };
} }
@ -96,8 +97,7 @@ class NewGame extends React.Component {
} }
render() { render() {
let playerNameInput, let titleBar = !this.props.hideBack ? (
titleBar = !this.props.hideBack ? (
<Fragment> <Fragment>
<a onClick={this.handleBack}> <a onClick={this.handleBack}>
<FontAwesomeIcon icon={faArrowCircleLeft} /> <FontAwesomeIcon icon={faArrowCircleLeft} />
@ -129,97 +129,97 @@ class NewGame extends React.Component {
mainScreenClass = !this.state.showSettings ? '' : 'hidden'; mainScreenClass = !this.state.showSettings ? '' : 'hidden';
return ( return (
<GroupBox title={titleBar}> <GroupBox title={titleBar}>
<form onSubmit={this.handleSubmit}> {this.props.user ? (
<div className={mainScreenClass}> <form onSubmit={this.handleSubmit}>
<Row> <div className={mainScreenClass}>
<Col width='12'> <Row>
<label>Your Name <Col width='12'>
<input type='text' name='playerName' <label>Your Color</label>
required {colors}
value={this.state.playerName} <br /><br />
onChange={this.handleInputChange} /> </Col>
</label> </Row>
</Col> {gameName}
</Row> </div>
<Row> <div className={settingsClass}>
<Col width='12'> <InputRow label='Down Payment'
<label>Your Color</label> name='downPayment'
{colors} min={0}
<br /><br /> max={1}
</Col> step={0.1}
</Row> value={this.state.downPayment}
{gameName} onChange={this.handleInputChange} />
</div> <InputRow label='Loan Interest'
<div className={settingsClass}> name='loanInterest'
<InputRow label='Down Payment' min={0}
name='downPayment' max={1}
min={0} step={0.1}
max={1} value={this.state.loanInterest}
step={0.1} onChange={this.handleInputChange} />
value={this.state.downPayment} <InputRow label='Maximum Debt'
onChange={this.handleInputChange} /> name='maxDebt'
<InputRow label='Loan Interest' min={0}
name='loanInterest' step={5000}
min={0} value={this.state.maxDebt}
max={1} onChange={this.handleInputChange} />
step={0.1} <InputRow label='Audit Threshold'
value={this.state.loanInterest} name='auditThreshold'
onChange={this.handleInputChange} /> min={0}
<InputRow label='Maximum Debt' step={25000}
name='maxDebt' value={this.state.auditThreshold}
min={0} onChange={this.handleInputChange} />
step={5000} <InputRow label='Starting Cash'
value={this.state.maxDebt} name='startingCash'
onChange={this.handleInputChange} /> min={0}
<InputRow label='Audit Threshold' step={1000}
name='auditThreshold' value={this.state.startingCash}
min={0} onChange={this.handleInputChange} />
step={25000} <InputRow label='Starting Debt'
value={this.state.auditThreshold} name='startingDebt'
onChange={this.handleInputChange} /> min={0}
<InputRow label='Starting Cash' step={1000}
name='startingCash' value={this.state.startingDebt}
min={0} onChange={this.handleInputChange} />
step={1000} <Row>
value={this.state.startingCash} <Col width='12'>
onChange={this.handleInputChange} /> <label>
<InputRow label='Starting Debt' <input type='checkbox'
name='startingDebt' checked={this.state.trade}
min={0} onChange={this.handleInputChange}
step={1000} name='trade' />
value={this.state.startingDebt} Enable Trading
onChange={this.handleInputChange} /> </label>
<Row> </Col>
<Col width='12'> </Row>
<label> </div>
<input type='checkbox' <Row>
checked={this.state.trade} <Col width='12'>
onChange={this.handleInputChange} <div className='new-game-submit-container'>
name='trade' /> <Button type='submit'>{this.props.button} Game</Button>
Enable Trading {this.props.showGameName ? (
</label> <a onClick={this.toggleSettings}>
</Col> <FontAwesomeIcon icon={faCog} size='lg' />
</Row> </a>
</div> ) : (<Fragment />)}
<Row> </div>
<Col width='12'> </Col>
<div className='new-game-submit-container'> </Row>
<Button type='submit'>{this.props.button} Game</Button> </form>
{this.props.showGameName ? ( ) : (
<a onClick={this.toggleSettings}> <>
<FontAwesomeIcon icon={faCog} size='lg' /> <span>Sign in or create account to continue</span>
</a> <LoginOrCreateAccount login={this.props.login}
) : (<Fragment />)} createAccount={this.props.createAccount}
</div> errors={this.props.errors}
</Col> />
</Row> </>
</form> )}
</GroupBox> </GroupBox>
); );
} }
} }
export default connect( export default connect(
null, state => state.start.start,
{ startOrJoinGame, start } { startOrJoinGame, start }
)(NewGame) )(NewGame)

@ -17,4 +17,7 @@
// <https://www.gnu.org/licenses/>. // <https://www.gnu.org/licenses/>.
export const SET_START_GAMES = 'set-start-games'; export const SET_START_GAMES = 'set-start-games';
export const SET_OPEN_GAMES = 'set-open-games';
export const SET_USER = 'set-user';
export const SET_ERRORS = 'set-errors';
export const START_OR_JOIN_GAME = 'start-or-join-game'; export const START_OR_JOIN_GAME = 'start-or-join-game';

@ -16,16 +16,32 @@
// along with the Alpha Centauri Farming project. If not, see // along with the Alpha Centauri Farming project. If not, see
// <https://www.gnu.org/licenses/>. // <https://www.gnu.org/licenses/>.
import { SET_START_GAMES, START_OR_JOIN_GAME } from './actionTypes.js' import { SET_START_GAMES, START_OR_JOIN_GAME, SET_USER, SET_OPEN_GAMES,
SET_ERRORS } from './actionTypes.js'
export { setStartGames, startOrJoinGame } export { setStartGames, startOrJoinGame, setUser, setOpenGames, setErrors }
function setStartGames(games) { function setStartGames(games) {
return { type: SET_START_GAMES, return { type: SET_START_GAMES,
games }; games };
} }
function setOpenGames(games) {
return { type: SET_OPEN_GAMES,
games };
}
function setUser(user) {
return { type: SET_USER,
user };
}
function startOrJoinGame(msg) { function startOrJoinGame(msg) {
return { type: START_OR_JOIN_GAME, return { type: START_OR_JOIN_GAME,
msg }; msg };
} }
function setErrors(errors) {
return { type: SET_ERRORS,
errors };
}

@ -16,11 +16,15 @@
// along with the Alpha Centauri Farming project. If not, see // along with the Alpha Centauri Farming project. If not, see
// <https://www.gnu.org/licenses/>. // <https://www.gnu.org/licenses/>.
import { SET_START_GAMES, START_OR_JOIN_GAME } from './actionTypes.js' import { SET_START_GAMES, START_OR_JOIN_GAME, SET_USER, SET_OPEN_GAMES,
SET_ERRORS } from './actionTypes.js'
import { SCREENS } from '../../constants.js' import { SCREENS } from '../../constants.js'
const initialState = { const initialState = {
start: { games: [] }, start: { games: [],
openGames: [],
errors: [],
user: false },
msg: null msg: null
}; };
@ -28,6 +32,12 @@ export default function(state = initialState, action) {
switch (action.type) { switch (action.type) {
case SET_START_GAMES: case SET_START_GAMES:
return { ...state, start: { ...state.start, games: action.games }}; return { ...state, start: { ...state.start, games: action.games }};
case SET_OPEN_GAMES:
return { ...state, start: { ...state.start, openGames: action.games }};
case SET_USER:
return { ...state, start: { ...state.start, user: action.user }};
case SET_ERRORS:
return { ...state, start: { ...state.start, errors: action.errors }};
case START_OR_JOIN_GAME: case START_OR_JOIN_GAME:
return { ...state, msg: action.msg }; return { ...state, msg: action.msg };
default: default:

@ -26,7 +26,8 @@ export const SCREENS = {
export const GAME_STATES = { preTurn: 'pre-turn', export const GAME_STATES = { preTurn: 'pre-turn',
midTurn: 'mid-turn', midTurn: 'mid-turn',
turnEnded: 'turn-ended' }; turnEnded: 'turn-ended',
preGame: 'pre-game' };
export const rootId = 'initial-element'; export const rootId = 'initial-element';
export const messagePanelId = 'message-panel'; export const messagePanelId = 'message-panel';
export const ALERTS = { beginTurn: 'begin-turn', export const ALERTS = { beginTurn: 'begin-turn',
@ -34,4 +35,5 @@ export const ALERTS = { beginTurn: 'begin-turn',
endOfGame: 'end-of-game', endOfGame: 'end-of-game',
auditCalled: 'audit-called', auditCalled: 'audit-called',
raiseMoney: 'raise-money', raiseMoney: 'raise-money',
proposedTrade: 'proposed-trade' } proposedTrade: 'proposed-trade',
preGame: 'pre-game' }

@ -28,7 +28,8 @@ import { rootId } from './constants.js'
import App from './components/app/App.jsx' import App from './components/app/App.jsx'
import { initialize, handleMessage as handleMessageFarm } from './components/farm/interface.js' import { initialize, handleMessage as handleMessageFarm } from './components/farm/interface.js'
import { setStartGames, startOrJoinGame } from './components/start/actions.js' import { setStartGames, startOrJoinGame, setUser, setOpenGames,
setErrors } from './components/start/actions.js'
import { play } from './components/app/actions.js' import { play } from './components/app/actions.js'
import { createStore } from 'redux' import { createStore } from 'redux'
import rootReducer from './rootReducers.js' import rootReducer from './rootReducers.js'
@ -46,9 +47,24 @@ document.body.appendChild(makeDiv(rootId));
window.store = store; window.store = store;
const createAccount = (msg) => {
Ws.sendCommand({ type: 'create-account', ...msg});
}
const login = (msg) => {
Ws.sendCommand({ type: 'login', ...msg});
}
const logout = () => {
Ws.sendCommand({ type: 'logout'});
}
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<App /> <App createAccount={createAccount}
login={login}
logout={logout}
/>
</Provider>, </Provider>,
document.getElementById(rootId) document.getElementById(rootId)
); );
@ -78,6 +94,9 @@ function handleMessage(evt) {
store.dispatch(play()); store.dispatch(play());
} else if (data.event === 'start-init') { } else if (data.event === 'start-init') {
store.dispatch(setStartGames(data.games.games)); store.dispatch(setStartGames(data.games.games));
store.dispatch(setOpenGames(data.openGames.games));
store.dispatch(setUser(data.user));
store.dispatch(setErrors(data.errors));
if (autostart) { if (autostart) {
if (data.games.games.length === 0) { if (data.games.games.length === 0) {
store.dispatch(startOrJoinGame({ type: 'new-game', store.dispatch(startOrJoinGame({ type: 'new-game',

@ -0,0 +1,134 @@
(use sql-de-lite crypt)
(define *db* "/home/tjhintz/db")
(define-syntax with-db
(syntax-rules ()
((_ (var) body ...)
(call-with-database *db*
(lambda (var)
body ...)))))
(define (create-tables)
(with-db (db)
(exec (sql db "create table users(id INTEGER PRIMARY KEY, username TEXT, email TEXT, password TEXT, salt TEXT);"))
(exec (sql db "create table sessions(bindings TEXT, session_id TEXT PRIMARY KEY);"))
(exec (sql db "create table games(id INTEGER PRIMARY KEY, status TEXT, object TEXT);"))
(exec (sql db "create table players(id INTEGER PRIMARY KEY, object TEXT);"))
(exec (sql db "create table user_games(user_id INTEGER, game_id INTEGER);"))))
(define (db-session-set! sid bindings)
(with-db (db)
(exec (sql db "insert or replace into sessions(bindings, session_id) values (?, ?);")
(with-output-to-string (lambda () (write bindings)))
sid)))
(define (db-session-ref sid)
(with-input-from-string
(or (alist-ref
'bindings
(with-db (db)
(query fetch-alist
(sql db "select * from sessions where session_id=?;")
sid)))
"#f")
read))
(define (add-user username email password)
(let ((salt (crypt-gensalt)))
(with-db (db)
(exec (sql db "insert into users(username, password, salt, email) values(?, ?, ?, ?);")
username (crypt password salt) salt email)
(last-insert-rowid db))))
(define (fetch-user username)
(with-db (db)
(query fetch-alist
(sql db "select * from users where username=?;")
username)))
(define (fetch-user-by-id id)
(with-db (db)
(query fetch-alist
(sql db "select * from users where id=?;")
id)))
(define (valid-password? username password)
(and-let* ((user (fetch-user username))
(_ (if (null? user)
(begin (crypt password "$2a$12$OW1wyLclJvq.PIxgoHCjdu")
#f)
#t)))
(string=? (crypt password (alist-ref 'salt user))
(alist-ref 'password user))))
(define (alist->string alist)
(with-output-to-string (lambda () (write alist))))
(define (string->alist s)
(with-input-from-string s read))
(define (db-add-game status object)
(with-db (db)
(exec (sql db "insert into games(status, object) values (?, ?);")
status (alist->string object))
(last-insert-rowid db)))
(define (db-update-game id status object)
(with-db (db)
(exec (sql db "replace into games(id, status, object) values (?, ?, ?);")
id status (alist->string object))))
(define (db-fetch-game id)
(string->alist
(with-db (db)
(query fetch-value
(sql db "select object from games where id=?;")
id))))
(define (db-fetch-open-games)
(map
string->alist
(with-db (db)
(query fetch-column
(sql db "select object from games where status=?;")
"pre-game"))))
(define (db-fetch-game-row id)
(let ((res
(with-db (db)
(query fetch-alist
(sql db "select * from games where id=?;")
id))))
`((id . ,(alist-ref 'id res))
(status . ,(alist-ref 'status res))
(object . ,(string->alist (alist-ref 'object res))))))
(define (db-add-player object)
(with-db (db)
(exec (sql db "insert into players(object) values (?);")
(alist->string object))
(last-insert-rowid db)))
(define (db-update-player id object)
(with-db (db)
(exec (sql db "replace into players(id, object) values (?, ?);")
id (alist->string object))))
(define (db-fetch-player id)
(string->alist
(with-db (db)
(query fetch-value
(sql db "select object from players where id=?;")
id))))
(define (db-add-user-game user-id game-id)
(with-db (db)
(exec (sql db "insert into user_games(user_id, game_id) values (?, ?);")
user-id game-id)))
(define (db-fetch-user-games user-id)
(with-db (db)
(query fetch-column
(sql db "select game_id from user_games where user_id=?;")
user-id)))

@ -29,6 +29,33 @@
(else (else
(include "game"))) (include "game")))
(include "db.scm")
(session-storage-initialize
(lambda ()
'no-op))
(session-storage-set!
(lambda (sid session-item)
(db-session-set! sid (session-item-bindings session-item))))
(define (expiration)
(+ (current-milliseconds)
(inexact->exact (floor (* (session-lifetime) 1000)))))
(session-storage-ref
(lambda (sid)
(let ((data (db-session-ref sid)))
(if data
(make-session-item (expiration) (remote-address) data #f)
(error "session not found")))
;; (make-session-item (+ (current-milliseconds) 100000000) (remote-address) `((user-id . ,(db-session-ref sid))) #f)
))
(session-storage-delete!
(lambda (sid)
(error "session storage delete not handled")))
(root-path "./") (root-path "./")
(define (neq? a b) (not (eq? a b))) (define (neq? a b) (not (eq? a b)))
@ -55,6 +82,7 @@
(SRV:send-reply (pre-post-order* sxml rules))))))) (SRV:send-reply (pre-post-order* sxml rules)))))))
(define *game* (make-parameter #f)) (define *game* (make-parameter #f))
(define *player* (make-parameter #f))
(define-syntax safe-set! (define-syntax safe-set!
(ir-macro-transformer (ir-macro-transformer
@ -90,6 +118,7 @@
(next-year-rules initform: '() accessor: player-next-year-rules) (next-year-rules initform: '() accessor: player-next-year-rules)
(color initform: #f accessor: player-color) (color initform: #f accessor: player-color)
(name initform: "PLAYER X" accessor: player-name) (name initform: "PLAYER X" accessor: player-name)
(user-id initform: #f accessor: player-user-id)
(trade initform: '() accessor: player-trade) (trade initform: '() accessor: player-trade)
(last-updated initform: 0 accessor: player-last-updated) (last-updated initform: 0 accessor: player-last-updated)
(last-cash initform: 5000 accessor: player-last-cash) (last-cash initform: 5000 accessor: player-last-cash)
@ -114,7 +143,7 @@
(colors initform: '() accessor: game-colors) (colors initform: '() accessor: game-colors)
(last-updated initform: 0 accessor: game-last-updated) (last-updated initform: 0 accessor: game-last-updated)
(called-audit initform: #f accessor: game-called-audit) (called-audit initform: #f accessor: game-called-audit)
(state initform: 'playing accessor: game-state) (state initform: 'pre-game accessor: game-state)
(name initform: "game" accessor: game-name) (name initform: "game" accessor: game-name)
(turn initform: 1 accessor: game-turn) (turn initform: 1 accessor: game-turn)
(current-player initform: #f accessor: game-current-player) (current-player initform: #f accessor: game-current-player)
@ -154,6 +183,7 @@
(next-year-rules . ,(player-next-year-rules player)) (next-year-rules . ,(player-next-year-rules player))
(color . ,(player-color player)) (color . ,(player-color player))
(name . ,(player-name player)) (name . ,(player-name player))
(user-id . ,(player-user-id player))
(trade . ()) (trade . ())
(last-updated . 0) (last-updated . 0)
(last-cash . ,(player-cash player)) (last-cash . ,(player-cash player))
@ -196,11 +226,11 @@
*operating-expense-cards*))) *operating-expense-cards*)))
'called-audit (if (alist-ref 'called-audit x) 'called-audit (if (alist-ref 'called-audit x)
(find (lambda (p) (find (lambda (p)
(string=? (player-name p) (alist-ref 'called-audit x))) (equal? (player-name p) (alist-ref 'called-audit x)))
players) players)
#f) #f)
'current-player (find (lambda (p) 'current-player (find (lambda (p)
(string=? (player-name p) (alist-ref 'current-player x))) (equal? (player-name p) (alist-ref 'current-player x)))
players) players)
(fold (lambda (k r) (cons k (cons (alist-ref k x) r))) (fold (lambda (k r) (cons k (cons (alist-ref k x) r)))
'() '()
@ -221,6 +251,10 @@
(lambda () (lambda ()
(write (app->sexp *app*))))) (write (app->sexp *app*)))))
(define (save-game game)
(db-update-game (game-id game) (symbol->string (game-state game))
(game->sexp game)))
(define (load-app) (define (load-app)
(with-input-from-file "/home/tjhintz/app.scm" (with-input-from-file "/home/tjhintz/app.scm"
(lambda () (lambda ()
@ -236,7 +270,7 @@
(fold (lambda (k r) (cons k (cons (alist-ref k x) r))) (fold (lambda (k r) (cons k (cons (alist-ref k x) r)))
'() '()
'(cash debt space previous-space state assets ridges '(cash debt space previous-space state assets ridges
harvest-mult otbs harvest-mult otbs user-id
year-rules next-year-rules hay-doubled corn-doubled year-rules next-year-rules hay-doubled corn-doubled
color name trade last-updated last-cash)))) color name trade last-updated last-cash))))
@ -310,13 +344,14 @@
(safe-set! (game-colors game) (cdr (game-colors game))) (safe-set! (game-colors game) (cdr (game-colors game)))
color)) color))
(define (add-player-to-game game color name) (define (add-player-to-game game color name user-id)
(let ((player (make <player> (let ((player (make <player>
'cash (game-setting 'starting-cash game) 'cash (game-setting 'starting-cash game)
'display-cash (game-setting 'starting-cash game) 'display-cash (game-setting 'starting-cash game)
'debt (game-setting 'starting-debt game) 'debt (game-setting 'starting-debt game)
'color color 'color color
'name name 'name name
'user-id user-id
'state (if (= (length (game-players game)) 0) 'state (if (= (length (game-players game)) 0)
'pre-turn 'turn-ended)))) 'pre-turn 'turn-ended))))
(safe-set! (game-players game) (append (game-players game) (list player))) (safe-set! (game-players game) (append (game-players game) (list player)))
@ -441,6 +476,7 @@
(player-otbs p)))) (player-otbs p))))
(color . ,(symbol->string (player-color p))) (color . ,(symbol->string (player-color p)))
(name . ,(player-name p)) (name . ,(player-name p))
(user-id . ,(player-user-id p))
(trade . ,(player-trade p)) (trade . ,(player-trade p))
(lastCash . ,(player-last-cash p)) (lastCash . ,(player-last-cash p))
(hayDoubled . ,(player-hay-doubled p)) (hayDoubled . ,(player-hay-doubled p))
@ -459,6 +495,7 @@
(player-otbs p)))) (player-otbs p))))
(color . ,(symbol->string (player-color p))) (color . ,(symbol->string (player-color p)))
(name . ,(player-name p)) (name . ,(player-name p))
(user-id . ,(player-user-id p))
(trade . ,(player-trade p)) (trade . ,(player-trade p))
(lastCash . ,(player-last-cash p)) (lastCash . ,(player-last-cash p))
(hayDoubled . ,(player-hay-doubled p)) (hayDoubled . ,(player-hay-doubled p))
@ -535,13 +572,23 @@
(define (finish-year player #!optional (collect-wages #t)) (define (finish-year player #!optional (collect-wages #t))
(let ((game (*game*))) (let ((game (*game*)))
(when collect-wages (when collect-wages
(safe-set! (player-cash player) (let* ((richest (car (sort (game-players game)
(+ (player-cash player) 5000)) (lambda (p1 p2)
(safe-set! (player-display-cash player) (player-cash player)) (> (player-net-worth p1)
(safe-set! (game-actions game) (player-net-worth p2))))))
(cons '((?action . info) (bonus (max (farming-round
(?value . "You earned $5,000 from your city job!")) (* (- (player-net-worth richest)
(game-actions game)))) (player-net-worth player))
0.2))
2500)))
(safe-set! (player-cash player)
;; (+ (player-cash player) 5000)
(+ (player-cash player) bonus))
(safe-set! (player-display-cash player) (player-cash player))
(safe-set! (game-actions game)
(cons `((?action . info)
(?value . ,(conc "You earned $" bonus " from your city job!")))
(game-actions game)))))
(when (game-called-audit game) (when (game-called-audit game)
(safe-set! (game-actions game) (safe-set! (game-actions game)
(append (game-actions game) (append (game-actions game)
@ -810,7 +857,7 @@
(player->list player) (player->list player)
(game->list (*game*) player))) (game->list (*game*) player)))
(define (create-start-response event) (define (create-start-response event #!key (errors '()))
`((event . ,event) `((event . ,event)
(games . ((games . ,(list->vector (games . ((games . ,(list->vector
(map (lambda (game) (map (lambda (game)
@ -820,7 +867,24 @@
(map symbol->string (game-colors game)))) (map symbol->string (game-colors game))))
(players . ,(list->vector (players . ,(list->vector
(map player-name (game-players game)))))) (map player-name (game-players game))))))
(app-games *app*)))))))) (map (lambda (gid)
(sexp->game (db-fetch-game gid)))
(db-fetch-user-games (session-ref (sid) 'user-id -1))))))))
(openGames . ((games . ,(list->vector
(map (lambda (game)
`((name . ,(game-name game))
(id . ,(game-id game))
(colors . ,(list->vector
(map symbol->string (game-colors game))))
(players . ,(list->vector
(map player-name (game-players game))))))
(map sexp->game (db-fetch-open-games)))))))
(user . ,(let ((id (session-ref (sid) 'user-id #f)))
(if (and id (not (equal? id -1)))
id
#f)))
(errors . ,(list->vector errors))))
(define (message-players! game player message #!key (type "action")) (define (message-players! game player message #!key (type "action"))
(for-each (lambda (p) (for-each (lambda (p)
(when (not (eq? p player)) (when (not (eq? p player))
@ -855,13 +919,35 @@
(safe-set! (player-display-cash player) (player-cash player))) (safe-set! (player-display-cash player) (player-cash player)))
(game-players game)))) (game-players game))))
(define (find-game id)
(let ((game-in-memory (find (lambda (g) (= (game-id g) id))
(app-games *app*))))
(if game-in-memory
game-in-memory
(let ((db-game (sexp->game (db-fetch-game id))))
(push! db-game (app-games *app*))
db-game))))
(define (next-roll last-roll)
(let ((roll (+ (random 6) 1)))
(if (= roll last-roll)
(next-roll last-roll)
roll)))
(define (make-rolls n)
(define (_make-rolls n i rolls)
(if (<= i n)
(_make-rolls n (+ i 1) (cons (next-roll (car rolls)) rolls))
rolls))
(_make-rolls n 1 (list (next-roll -1))))
(define (process-message player game type msg) (define (process-message player game type msg)
(when game (when player
(safe-set! (game-messages game) '())
(safe-set! (player-last-cash player) (player-cash player))) (safe-set! (player-last-cash player) (player-cash player)))
(print "message type: " type) (print "message type: " type)
(cond ((string=? type "roll") (cond ((string=? type "roll")
(let ((num (+ (random 6) 1))) (let ((num (+ (random 6) 1))
(rolls (make-rolls 22)))
(when *next-roll* (set! num *next-roll*)) (when *next-roll* (set! num *next-roll*))
(safe-set! (player-previous-space player) (safe-set! (player-previous-space player)
(player-space player)) (player-space player))
@ -876,7 +962,8 @@
(finish-year player)) (finish-year player))
(safe-set! (player-harvest-mult player) 1) (safe-set! (player-harvest-mult player) 1)
(let ((resp `((from . ,(player-previous-space player)) (let ((resp `((from . ,(player-previous-space player))
(to . ,(player-space player))))) (to . ,(player-space player))
(rolls . ,(list->vector rolls)))))
(safe-set! (game-actions game) (safe-set! (game-actions game)
(append (game-actions game) (append (game-actions game)
`(((?action . move) (?value . ,resp)) `(((?action . move) (?value . ,resp))
@ -1169,7 +1256,7 @@
(print exn) (print exn)
(print-error-message exn) (print-error-message exn)
(print "error saving app")) (print "error saving app"))
(save-app)) (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)
(message-players! game player '() type: "update")) (message-players! game player '() type: "update"))
@ -1180,6 +1267,7 @@
(create-start-response "start-init")) (create-start-response "start-init"))
((string=? type "new-game") ((string=? type "new-game")
(let* ((color (string->symbol (alist-ref 'checkedColor msg))) (let* ((color (string->symbol (alist-ref 'checkedColor msg)))
(user (fetch-user-by-id (session-ref (sid) 'user-id)))
(game (make <game> 'colors (filter (cut neq? <> color) (game (make <game> 'colors (filter (cut neq? <> color)
'(green red blue yellow black)) '(green red blue yellow black))
'name (alist-ref 'gameName msg) 'name (alist-ref 'gameName msg)
@ -1200,49 +1288,81 @@
(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
(alist-ref 'playerName msg))) (alist-ref 'username user)
(alist-ref 'id user)))
;; (ai-player (add-ai-to-game game 'red "AI Player 1")) ;; (ai-player (add-ai-to-game game 'red "AI Player 1"))
) )
(push! game (app-games *app*)) (push! game (app-games *app*))
(session-set! (sid) 'player player) (let ((gid (db-add-game "pre-game" (game->sexp game))))
(session-set! (sid) 'game game) (safe-set! (game-id game) gid)
(db-update-game gid "pre-game" (game->sexp game))
(db-add-user-game (alist-ref 'id user) (game-id game))
(session-set! (sid) 'game-id (game-id game)))
(*game* game) (*game* game)
(*player* player)
(set-startup-otbs game player 2) (set-startup-otbs game player 2)
;; (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")))
((string=? type "join-game") ((string=? type "join-game")
(let* ((name (alist-ref 'gameName msg)) (let* ((user (fetch-user-by-id (session-ref (sid) 'user-id)))
(name (alist-ref 'username user))
(id (alist-ref 'gameId msg)) (id (alist-ref 'gameId msg))
(game (find (lambda (g) (= (game-id g) id)) (game (find-game id))
(app-games *app*)))
(color-raw (string->symbol (alist-ref 'checkedColor msg))) (color-raw (string->symbol (alist-ref 'checkedColor msg)))
(color (if (not (member color-raw (game-colors game))) (color (if (not (member color-raw (game-colors game)))
(car (game-colors game)) (car (game-colors game))
color-raw)) color-raw))
(player (add-player-to-game game (player (add-player-to-game game
color color
(alist-ref 'playerName msg)))) (alist-ref 'username user)
(alist-ref 'id user))))
(safe-set! (game-colors game) (filter (cut neq? <> color) (game-colors game))) (safe-set! (game-colors game) (filter (cut neq? <> color) (game-colors game)))
(session-set! (sid) 'player player) (session-set! (sid) 'game-id (game-id game))
(session-set! (sid) 'game game) (db-add-user-game (alist-ref 'id user) (game-id game))
(*game* game) (*game* game)
(*player* player)
(set-startup-otbs game player 2) (set-startup-otbs game player 2)
(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* ((name (alist-ref 'gameName msg)) (let* ((id (alist-ref 'gameId msg))
(pname (alist-ref 'playerName msg)) (user-id (session-ref (sid) 'user-id))
(id (alist-ref 'gameId msg)) (game (find-game id))
(game (find (lambda (g) (= (game-id g) id)) (player (find (lambda (p) (equal? (player-user-id p) user-id))
(app-games *app*)))
(player (find (lambda (p) (string=? (player-name p) pname))
(game-players game)))) (game-players game))))
(session-set! (sid) 'player player)
(session-set! (sid) 'game game)
(*game* game) (*game* game)
(*player* player)
(create-start-response "new-game-started"))) (create-start-response "new-game-started")))
)) ((string=? type "create-account")
(let ((username (alist-ref 'username msg))
(email (alist-ref 'email msg))
(password (alist-ref 'password msg))
(confirm-password (alist-ref 'confirmPassword msg)))
(if (string=? password confirm-password)
(if (null? (fetch-user username))
(let ((id (add-user username email password)))
(session-set! (sid) 'user-id id)
(create-start-response "start-init"))
(create-start-response "start-init" errors: '("Account already exists")))
(create-start-response "start-init" errors: '("Passwords don't match")))))
((string=? type "login")
(let ((username (alist-ref 'username msg))
(password (alist-ref 'password msg)))
(if (valid-password? username password)
(begin (session-set! (sid) 'user-id (alist-ref 'id (fetch-user username)))
(create-start-response "start-init"))
(create-start-response "start-init" errors: '("Invalid password or account doesn't exist")))))
((string=? type "logout")
(session-set! (sid) 'game-id #f)
(session-set! (sid) 'user-id #f)
(create-start-response "start-init"))
((string=? type "start-game")
(safe-set! (game-state (*game*)) 'pre-turn)
(db-update-game (game-id (*game*)) (symbol->string (game-state (*game*)))
(game->sexp (*game*)))
(message-players! (*game*) (*player*) '() type: "update")
(create-ws-response (*player*) "update" '()))))
(define (process-ai-push-message player game msg) (define (process-ai-push-message player game msg)
(print (player-name player)) (print (player-name player))
@ -1297,6 +1417,18 @@
(process-ai-push-message player game msg) (process-ai-push-message player game msg)
(loop (mailbox-receive! (player-mailbox player)))))) (loop (mailbox-receive! (player-mailbox player))))))
(define (session-game)
(let ((user-id (session-ref (sid) 'user-id)))
(if (and (not (*game*)) (session-ref (sid) 'game-id #f))
(let ((possible-game (find-game (session-ref (sid) 'game-id))))
(when possible-game
(*game* possible-game)
(*player* (find (lambda (p)
(equal? (player-user-id p) user-id))
(game-players (*game*))))
(*game*)))
(and (*game*)))))
(define (websocket-page) (define (websocket-page)
(sid (read-cookie (session-cookie-name))) (sid (read-cookie (session-cookie-name)))
;; TODO some kind of error handling if (sid) #f ;; TODO some kind of error handling if (sid) #f
@ -1321,16 +1453,28 @@
(print-call-chain) (print-call-chain)
(print-error-message exn)))) (print-error-message exn))))
(event . "error")) (event . "error"))
(let* ((game (session-ref (sid) 'game #f)) (session-game)
(player (session-ref (sid) 'player #f)) (let* ((game (*game*))
(res (process-message player (res (process-message (*player*)
game game
(alist-ref 'type msg) (alist-ref 'type msg)
msg))) msg)))
(when game (when game
(safe-set! (game-last-updated game) (+ (game-last-updated game) 1)) (safe-set! (game-last-updated game) (+ (game-last-updated game) 1))
(safe-set! (player-last-updated player) (game-last-updated game))) (when (*player*)
res))))) (safe-set! (player-last-updated (*player*)) (game-last-updated game))))
res)
;; (let* ((game (session-ref (sid) 'game #f))
;; (player (session-ref (sid) 'player #f))
;; (res (process-message player
;; game
;; (alist-ref 'type msg)
;; msg)))
;; (when game
;; (safe-set! (game-last-updated game) (+ (game-last-updated game) 1))
;; (safe-set! (player-last-updated player) (game-last-updated game)))
;; res)
))))
(loop (read-json (receive-message))))))) (loop (read-json (receive-message)))))))
(define (push-websocket-page) (define (push-websocket-page)
@ -1338,39 +1482,33 @@
;; TODO some kind of error handling if (sid) #f ;; TODO some kind of error handling if (sid) #f
(with-concurrent-websocket (with-concurrent-websocket
(lambda () (lambda ()
(let ((game (session-ref (sid) 'game)) (session-game)
(player (session-ref (sid) 'player))) (let loop ((msg (mailbox-receive! (player-mailbox (*player*)))))
(*game* game) (session-game)
(let loop ((msg (mailbox-receive! (player-mailbox player)))) ;; when (< (player-last-updated player)
(print msg) ;; (game-last-updated game))
(when (not game) (handle-exceptions
(set! game (session-ref (sid) 'game))) exn
(when (not player) (send-message
(set! player (session-ref (sid) 'player))) (json->string
;; when (< (player-last-updated player) `((exn . ,(with-output-to-string
;; (game-last-updated game)) (lambda ()
(handle-exceptions (print-call-chain)
exn (print-error-message exn)))))))
(send-message (send-message
(json->string (json->string
(handle-exceptions
exn
`((exn . ,(with-output-to-string `((exn . ,(with-output-to-string
(lambda () (lambda ()
(print-call-chain) (print-call-chain)
(print-error-message exn))))))) (print-error-message exn))))
(send-message (event . "error"))
(json->string (create-ws-response (*player*)
(handle-exceptions (alist-ref 'type msg)
exn (alist-ref 'value msg))
`((exn . ,(with-output-to-string ))))
(lambda () (loop (mailbox-receive! (player-mailbox (*player*))))))))
(print-call-chain)
(print-error-message exn))))
(event . "error"))
(create-ws-response player
(alist-ref 'type msg)
(alist-ref 'value msg))
))))
(loop (mailbox-receive! (player-mailbox player))))))))
(define (otb-spec->otb-cards spec id) (define (otb-spec->otb-cards spec id)
`((contents . ,(sxml->html* (list-ref spec 5))) `((contents . ,(sxml->html* (list-ref spec 5)))
@ -2034,6 +2172,7 @@
(game-players game))))) (game-players game)))))
((alist-ref 'action operating-expense) player) ((alist-ref 'action operating-expense) player)
`((rolled . ,rolled) `((rolled . ,rolled)
(rolls . ,(list->vector (make-rolls 22)))
(income . ,income) (income . ,income)
(harvestMult . ,harvest-mult) (harvestMult . ,harvest-mult)
(operatingExpense . ,(alist-ref 'contents operating-expense)) (operatingExpense . ,(alist-ref 'contents operating-expense))

@ -141,6 +141,8 @@ $tab-margin: 0.3rem;
align-items: center; } align-items: center; }
.view-container { .view-container {
max-height: 90vh;
overflow: auto;
border: 0.3rem solid $dark-color; border: 0.3rem solid $dark-color;
background: linear-gradient(180deg, $light-color 0%, $orange-color 100%); background: linear-gradient(180deg, $light-color 0%, $orange-color 100%);
padding: 1rem; padding: 1rem;
@ -919,3 +921,75 @@ $intro-time: 6s;
.pad-right { .pad-right {
padding-right: 0.3rem; } padding-right: 0.3rem; }
.sign-out-button {
position: absolute;
bottom: 1rem; }
/* -------- MENU ------- */
/* Position and sizing of burger button */
.bm-burger-button {
position: fixed;
width: 36px;
height: 30px;
left: 36px;
top: 36px;
}
/* Color/shape of burger icon bars */
.bm-burger-bars {
background: #373a47;
}
/* Color/shape of burger icon bars on hover*/
.bm-burger-bars-hover {
background: #a90000;
}
/* Position and sizing of clickable cross button */
.bm-cross-button {
height: 24px;
width: 24px;
}
/* Color/shape of close button cross */
.bm-cross {
background: #bdc3c7;
}
/*
Sidebar wrapper styles
Note: Beware of modifying this element as it can break the animations - you should not need to touch it in most cases
*/
.bm-menu-wrap {
position: fixed;
height: 100%;
}
/* General sidebar styles */
.bm-menu {
background: #373a47;
padding: 2.5em 1.5em 0;
font-size: 1.15em;
}
/* Morph shape necessary with bubble or elastic */
.bm-morph-shape {
fill: #373a47;
}
/* Wrapper for item list */
.bm-item-list {
color: #b8b7ad;
padding: 0.8em;
}
/* Individual item */
.bm-item {
display: inline-block;
}
/* Styling of overlay */
.bm-overlay {
background: rgba(0, 0, 0, 0.3);
}

@ -22,7 +22,9 @@ export { sendCommand, setMainOnMessage, setSecondaryOnMessage,
class WebSocketConnection { class WebSocketConnection {
location = ''; location = '';
fullPath = '';
reopen = true; reopen = true;
onmessageProc = () => 'nothing'
ws = null; ws = null;
constructor(location) { constructor(location) {
@ -35,6 +37,7 @@ class WebSocketConnection {
uri = "ws:"; uri = "ws:";
} }
uri += "//" + loc.host; uri += "//" + loc.host;
this.fullPath = uri + '/websocket/' + location;
this.ws = new WebSocket(uri + '/websocket/' + location); this.ws = new WebSocket(uri + '/websocket/' + location);
} }
@ -47,8 +50,17 @@ class WebSocketConnection {
this.ws.send(JSON.stringify(cmd)); this.ws.send(JSON.stringify(cmd));
} }
reestablish = () => {
this.ws = new WebSocket(this.fullPath);
this.ws.onmessage = this.onmessageProc;
}
onmessage(proc) { onmessage(proc) {
this.onmessageProc = proc;
this.ws.onmessage = proc; this.ws.onmessage = proc;
// this.ws.onclose = () => {
// setTimeout(this.reestablish, 500);
// }
} }
onopen(proc) { onopen(proc) {

@ -72,7 +72,7 @@ module.exports = {
} }
}, },
{ {
test: /\.(woff|woff2|eot|ttf|otf|svg|png)$/, test: /\.(woff|woff2|eot|ttf|otf|svg|png|gif)$/,
use: [ use: [
'file-loader', 'file-loader',
], ],

Loading…
Cancel
Save