Merge branch 'matt-server'
This commit is contained in:
commit
164cf6a73a
7
.editorconfig
Normal file
7
.editorconfig
Normal file
|
@ -0,0 +1,7 @@
|
|||
[*.cr]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/docs/
|
||||
/lib/
|
||||
/bin/
|
||||
/.shards/
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "external/GoUI"]
|
||||
path = external/GoUI
|
||||
url = https://dev.danilafe.com/CS-290/GoUI.git
|
BIN
.sass-cache/2da5b7a9ed68f2839637c88aab8550dd9bb593bb/main.scssc
Normal file
BIN
.sass-cache/2da5b7a9ed68f2839637c88aab8550dd9bb593bb/main.scssc
Normal file
Binary file not shown.
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
|||
language: crystal
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Danila Fedorin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
4
Makefile
Normal file
4
Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
generate-elm :
|
||||
cd external/GoUI && elm make Go.elm --output ../../public/js/Go.js
|
||||
generate-css :
|
||||
scss public/scss/main.scss > public/css/main.css
|
41
README.md
41
README.md
|
@ -1 +1,40 @@
|
|||
# Final Project for Group 4
|
||||
# Go
|
||||
|
||||
This is an implementation of a game of GO in web application form.
|
||||
|
||||
## Installation
|
||||
|
||||
TODO: Write installation instructions for Windows
|
||||
___
|
||||
TODO: Write installation instructions for Mac
|
||||
___
|
||||
Arch Linux:
|
||||
1. Install dependencies: `pacman -S crystal shards ruby-sass`
|
||||
2. Install Shards: `shards install`
|
||||
3. TODO: Add elm compilation
|
||||
4. Compile SASS Style: `make generate-css`
|
||||
5. Compile Crystal app: `crystal build --release src/Go.cr`
|
||||
6. Run with `./Go`
|
||||
___
|
||||
|
||||
## Usage
|
||||
|
||||
While crystal app is running, go to localhost with port of 3000 in the browser.
|
||||
|
||||
## Development
|
||||
|
||||
TODO: Write development instructions here
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it ( https://github.com/[your-github-name]/Go/fork )
|
||||
2. Create your feature branch (git checkout -b my-new-feature)
|
||||
3. Commit your changes (git commit -am 'Add some feature')
|
||||
4. Push to the branch (git push origin my-new-feature)
|
||||
5. Create a new Pull Request
|
||||
|
||||
## Contributors
|
||||
|
||||
- [[your-github-name]](https://github.com/[your-github-name]) Danila Fedorin - creator, maintainer
|
||||
|
||||
- [sessionm21](https://github.com/sessionm21) Matthew Sessions - hax0r, maintainer
|
||||
|
|
1
external/GoUI
vendored
Submodule
1
external/GoUI
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit b68222dab7a766ede897ba9bdc68f262e2194e3d
|
BIN
game_saves.db
Normal file
BIN
game_saves.db
Normal file
Binary file not shown.
115
public/css/main.css
Normal file
115
public/css/main.css
Normal file
|
@ -0,0 +1,115 @@
|
|||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: "Indie Flower", serif;
|
||||
text-align: center; }
|
||||
|
||||
h1 {
|
||||
font-size: 5em;
|
||||
margin: 0px; }
|
||||
|
||||
body {
|
||||
font-family: "Raleway", sans-serif;
|
||||
margin: 0px;
|
||||
background-color: #f4f4f4; }
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 750px;
|
||||
margin: auto; }
|
||||
|
||||
.board {
|
||||
background-color: tomato;
|
||||
padding: 20px;
|
||||
max-width: 500px;
|
||||
margin: auto;
|
||||
border-radius: 10px; }
|
||||
|
||||
.black-player .board-cell:hover .overlay {
|
||||
background-color: black; }
|
||||
|
||||
.white-player .board-cell:hover .overlay {
|
||||
background-color: white; }
|
||||
|
||||
.board-cell {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 5.5555555556%; }
|
||||
.board-cell .overlay {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
border-radius: 50%;
|
||||
transition: background-color .25s; }
|
||||
.board-cell.small {
|
||||
padding: 5.5555555556%; }
|
||||
.board-cell.medium {
|
||||
padding: 3.8461538462%; }
|
||||
.board-cell.large {
|
||||
padding: 2.6315789474%; }
|
||||
|
||||
.black-cell .overlay {
|
||||
background-color: black; }
|
||||
|
||||
.white-cell .overlay {
|
||||
background-color: white; }
|
||||
|
||||
.split-wrapper {
|
||||
display: flex;
|
||||
width: 100%; }
|
||||
@media screen and (max-width: 640px) {
|
||||
.split-wrapper {
|
||||
flex-direction: column; } }
|
||||
|
||||
.split-item {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box; }
|
||||
|
||||
.split-wrapper form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px; }
|
||||
.split-wrapper form input {
|
||||
margin-top: 20px;
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
max-width: 300px; }
|
||||
.split-wrapper form input[type="radio"] {
|
||||
opacity: 0;
|
||||
width: 0px;
|
||||
height: 0px; }
|
||||
.split-wrapper form input[type="radio"]:checked ~ label {
|
||||
color: tomato;
|
||||
transition: color .25s; }
|
||||
.split-wrapper form input[type="submit"] {
|
||||
padding: 10px;
|
||||
background-color: tomato;
|
||||
color: white; }
|
||||
.split-wrapper form input[type="submit"]:focus, .split-wrapper form input[type="submit"]:hover {
|
||||
background-color: inherit;
|
||||
color: tomato;
|
||||
transition: background-color .25s, color .25s; }
|
||||
.split-wrapper form input[type="text"] {
|
||||
background-color: inherit;
|
||||
border-bottom: solid tomato;
|
||||
border-width: 2px;
|
||||
height: 3em;
|
||||
box-sizing: border-box;
|
||||
display: block; }
|
||||
.split-wrapper form input[type="text"]:focus {
|
||||
border-width: 3px;
|
||||
transition: background-color .25s, border-width .25s; }
|
||||
.split-wrapper form .radio-parent {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
max-width: 300px; }
|
||||
.split-wrapper form .radio-wrapper {
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
text-align: center; }
|
9744
public/js/Go.js
Normal file
9744
public/js/Go.js
Normal file
File diff suppressed because it is too large
Load Diff
169
public/scss/main.scss
Normal file
169
public/scss/main.scss
Normal file
|
@ -0,0 +1,169 @@
|
|||
$background-grey: #f4f4f4;
|
||||
|
||||
@mixin board-cell($size) {
|
||||
padding: 100%/$size/2;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: "Indie Flower", serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 5em;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Raleway", sans-serif;
|
||||
margin: 0px;
|
||||
background-color: $background-grey;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 750px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.board {
|
||||
background-color: tomato;
|
||||
padding: 20px;
|
||||
max-width: 500px;
|
||||
margin: auto;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.black-player {
|
||||
.board-cell:hover .overlay {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
.white-player {
|
||||
.board-cell:hover .overlay {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.board-cell {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
@include board-cell(9);
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
border-radius: 50%;
|
||||
transition: background-color .25s;
|
||||
}
|
||||
|
||||
&.small {
|
||||
@include board-cell(9);
|
||||
}
|
||||
|
||||
&.medium {
|
||||
@include board-cell(13);
|
||||
}
|
||||
|
||||
&.large {
|
||||
@include board-cell(19);
|
||||
}
|
||||
}
|
||||
|
||||
.black-cell {
|
||||
.overlay {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
.white-cell {
|
||||
.overlay {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.split-wrapper {
|
||||
display: flex;
|
||||
@media screen and (max-width: 640px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.split-item {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.split-wrapper form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
|
||||
input {
|
||||
margin-top: 20px;
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
opacity: 0;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
|
||||
&:checked ~ label {
|
||||
color: tomato;
|
||||
transition: color .25s;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
padding: 10px;
|
||||
background-color: tomato;
|
||||
color: white;
|
||||
|
||||
&:focus, &:hover {
|
||||
background-color: inherit;
|
||||
color: tomato;
|
||||
transition: background-color .25s, color .25s;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
background-color: inherit;
|
||||
border-bottom: solid tomato;
|
||||
border-width: 2px;
|
||||
|
||||
&:focus {
|
||||
border-width: 3px;
|
||||
transition: background-color .25s, border-width .25s;
|
||||
}
|
||||
|
||||
height: 3em;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.radio-parent {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.radio-wrapper {
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
59
public/svg/black.svg
Normal file
59
public/svg/black.svg
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?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"
|
||||
height="100"
|
||||
width="100"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="black.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<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="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1159"
|
||||
inkscape:window-height="719"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.36"
|
||||
inkscape:cx="50"
|
||||
inkscape:cy="50"
|
||||
inkscape:window-x="307"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4" />
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
stroke="black"
|
||||
stroke-width="3"
|
||||
fill="red"
|
||||
id="circle2"
|
||||
style="fill:#000000" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
58
public/svg/circle.svg
Normal file
58
public/svg/circle.svg
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?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"
|
||||
height="100"
|
||||
width="100"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="circle.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<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="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1159"
|
||||
inkscape:window-height="719"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.36"
|
||||
inkscape:cx="50"
|
||||
inkscape:cy="50"
|
||||
inkscape:window-x="307"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4" />
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
stroke="black"
|
||||
stroke-width="3"
|
||||
fill="red"
|
||||
id="circle2"
|
||||
style="fill:#000000" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
64
public/svg/cross.svg
Normal file
64
public/svg/cross.svg
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?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"
|
||||
height="100"
|
||||
width="100"
|
||||
version="1.1"
|
||||
id="svg14"
|
||||
sodipodi:docname="crossB.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata20">
|
||||
<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="defs18" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1028"
|
||||
id="namedview16"
|
||||
showgrid="false"
|
||||
inkscape:zoom="8.27"
|
||||
inkscape:cx="50"
|
||||
inkscape:cy="50"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg14" />
|
||||
<line
|
||||
x1="50"
|
||||
y1="0"
|
||||
x2="50"
|
||||
y2="100"
|
||||
style="stroke:rgb(0,0,0);stroke-width:2"
|
||||
id="line10" />
|
||||
<line
|
||||
x1="0"
|
||||
y1="50"
|
||||
x2="100"
|
||||
y2="50"
|
||||
style="stroke:rgb(0,0,0);stroke-width:2"
|
||||
id="line12" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
59
public/svg/white.svg
Normal file
59
public/svg/white.svg
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?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"
|
||||
height="100"
|
||||
width="100"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="white.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<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="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1159"
|
||||
inkscape:window-height="719"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.36"
|
||||
inkscape:cx="50"
|
||||
inkscape:cy="50"
|
||||
inkscape:window-x="307"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4" />
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
stroke="black"
|
||||
stroke-width="3"
|
||||
fill="red"
|
||||
id="circle2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
22
shard.lock
Normal file
22
shard.lock
Normal file
|
@ -0,0 +1,22 @@
|
|||
version: 1.0
|
||||
shards:
|
||||
db:
|
||||
github: crystal-lang/crystal-db
|
||||
version: 0.5.0
|
||||
|
||||
kemal:
|
||||
github: kemalcr/kemal
|
||||
version: 0.22.0
|
||||
|
||||
kilt:
|
||||
github: jeromegn/kilt
|
||||
version: 0.4.0
|
||||
|
||||
radix:
|
||||
github: luislavena/radix
|
||||
version: 0.3.8
|
||||
|
||||
sqlite3:
|
||||
github: crystal-lang/crystal-sqlite3
|
||||
version: 0.9.0
|
||||
|
21
shard.yml
Normal file
21
shard.yml
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: Go
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- Danila Fedorin <danila.fedorin@gmail.com>
|
||||
|
||||
targets:
|
||||
Go:
|
||||
main: src/Go.cr
|
||||
|
||||
dependencies:
|
||||
kemal:
|
||||
github: kemalcr/kemal
|
||||
db:
|
||||
github: crystal-lang/crystal-db
|
||||
sqlite3:
|
||||
github: crystal-lang/crystal-sqlite3
|
||||
|
||||
crystal: 0.24.2
|
||||
|
||||
license: MIT
|
9
spec/Go_spec.cr
Normal file
9
spec/Go_spec.cr
Normal file
|
@ -0,0 +1,9 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe Go do
|
||||
# TODO: Write tests
|
||||
|
||||
it "works" do
|
||||
false.should eq(true)
|
||||
end
|
||||
end
|
2
spec/spec_helper.cr
Normal file
2
spec/spec_helper.cr
Normal file
|
@ -0,0 +1,2 @@
|
|||
require "spec"
|
||||
require "../src/Go"
|
199
src/Go.cr
Normal file
199
src/Go.cr
Normal file
|
@ -0,0 +1,199 @@
|
|||
require "./Go/*"
|
||||
require "kemal"
|
||||
require "json"
|
||||
|
||||
require "db"
|
||||
require "sqlite3"
|
||||
|
||||
URL = "localhost"
|
||||
PORT = "3000"
|
||||
GAME_CACHE = {} of String => Go::Game
|
||||
GAME_SAVE = "./game_saves.db"
|
||||
|
||||
def save_game(db, gameid, game)
|
||||
# Function: save_game
|
||||
# Parameters: db(String)[Unused] gameid(String) game(Go::Game)
|
||||
turn, size, white_pass, black_pass, board = game.encode
|
||||
DB.open "sqlite3:./#{GAME_SAVE}" do |db|
|
||||
# Create table if one does not exist, gameid is UNIQUE => No duplicates
|
||||
db.exec "create table if not exists game_saves (gameid string, turn integer, size integer, white_pass string, black_pass string, board string, UNIQUE(gameid) )"
|
||||
# If duplicate => replace values, else => make new row for gameid
|
||||
db.exec "insert or replace into game_saves values (?, ?, ?, ?, ?, ?)", gameid, turn.value, size, white_pass, black_pass, board
|
||||
end
|
||||
end
|
||||
|
||||
def query_game(db, gameid) : Go::Game?
|
||||
# Function: query_game
|
||||
# Parameters: db(String)[Unused] gameid(String)
|
||||
turn = 0
|
||||
size = Go::Size::Small
|
||||
white_pass = ""
|
||||
black_pass = ""
|
||||
board = ""
|
||||
begin
|
||||
DB.open "sqlite3:./#{GAME_SAVE}" do |db|
|
||||
# Query whole row where the gameid is found
|
||||
db.query "SELECT * FROM game_saves WHERE gameid = ?", gameid do |rs|
|
||||
rs.each do
|
||||
id = rs.read(String) # Reduntant
|
||||
turn = rs.read(Int32)
|
||||
size = rs.read(Int32)
|
||||
white_pass = rs.read(String)
|
||||
black_pass = rs.read(String)
|
||||
board = rs.read(String)
|
||||
end
|
||||
end
|
||||
end
|
||||
# New Go::Game object
|
||||
game = Go::Game.new()
|
||||
game.size = Go::Size.from_value(size)
|
||||
game.white_pass = white_pass
|
||||
game.black_pass = black_pass
|
||||
game.turn = Go::Color.from_value(turn)
|
||||
# Parses game board string
|
||||
counter = 0
|
||||
# For each character in the board String
|
||||
board.each_char do |char|
|
||||
x = counter % 9
|
||||
y = counter / 9
|
||||
coord = {x.to_i8, y.to_i8}
|
||||
if(char == 'B')
|
||||
game.board[coord] = Go::Color::Black
|
||||
elsif(char == 'W')
|
||||
game.board[coord] = Go::Color::White
|
||||
end
|
||||
counter += 1
|
||||
end
|
||||
rescue
|
||||
# Catch bad query
|
||||
puts "DB query Failed"
|
||||
return nil
|
||||
end
|
||||
# Finished Go::Game object to return
|
||||
return game
|
||||
end
|
||||
|
||||
def lookup_game(db, cache, id) : Go::Game?
|
||||
# Function: lookup_game
|
||||
# Parameters: db(String)[Unused] cache({(String), (Go::Game)}) id(String)
|
||||
if game = cache[id]?
|
||||
return game
|
||||
else
|
||||
loaded_game = query_game(db, id)
|
||||
# Need to convert id to string for some reason
|
||||
cache[id.to_s] = loaded_game if loaded_game
|
||||
return loaded_game
|
||||
end
|
||||
end
|
||||
|
||||
def create_game(db, cache, game, id)
|
||||
# Function: create_game
|
||||
# Parameters: db(String)[Unused] cache({(String), (Go::Game)}) game(Go::Game) id(String)
|
||||
cache[id] = game
|
||||
end
|
||||
|
||||
def handle_message(id, game, socket, message)
|
||||
# Function: handle_message
|
||||
# Parameters: id(String) game(Go::Game) socket() message()
|
||||
|
||||
split_command = message.split(" ")
|
||||
command = split_command[0]
|
||||
if command == "place"
|
||||
x = split_command[1].to_i8
|
||||
y = split_command[2].to_i8
|
||||
color = split_command[3] == "Black" ? Go::Color::Black : Go::Color::White
|
||||
|
||||
game.update(x, y, color)
|
||||
game.sockets.each { |socket| socket.send game.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
get "/" do |env|
|
||||
render "src/Go/views/index.ecr", "src/Go/views/base.ecr"
|
||||
end
|
||||
|
||||
get "/save" do |env|
|
||||
GAME_CACHE.each do |game_hash|
|
||||
gameid, game = game_hash
|
||||
save_game("none", gameid, game)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
post "/game" do |env|
|
||||
game_id = env.params.body["id"]?
|
||||
game_password = env.params.body["password"]?
|
||||
if game_id == nil || game_password == nil
|
||||
render_404
|
||||
elsif game = lookup_game(nil, GAME_CACHE, game_id)
|
||||
id = game_id
|
||||
size = game.size.value
|
||||
black = nil
|
||||
|
||||
if game_password == game.black_pass
|
||||
black = true
|
||||
elsif game_password == game.white_pass
|
||||
black = false
|
||||
end
|
||||
|
||||
black.try { |black| render "src/Go/views/game.ecr", "src/Go/views/base.ecr"} || render_404
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
post "/create" do |env|
|
||||
game_id = env.params.body["id"]?
|
||||
user_password = env.params.body["your-password"]?
|
||||
other_password = env.params.body["their-password"]?
|
||||
color = env.params.body["color"]?
|
||||
|
||||
color_e = nil
|
||||
if color == "black"
|
||||
color_e = Go::Color::Black
|
||||
elsif color == "white"
|
||||
color_e = Go::Color::White
|
||||
end
|
||||
|
||||
if game_id == nil || user_password == nil || other_password == nil || color == nil || color_e == nil
|
||||
render_404
|
||||
elsif game = lookup_game(nil, GAME_CACHE, game_id)
|
||||
render_404
|
||||
else
|
||||
color_e = color_e.as(Go::Color)
|
||||
user_password = user_password.as(String)
|
||||
other_password = other_password.as(String)
|
||||
if color_e == Go::Color::Black
|
||||
white_pass, black_pass = other_password, user_password
|
||||
else
|
||||
white_pass, black_pass = user_password, other_password
|
||||
end
|
||||
game = Go::Game.new(Go::Size::Small, black_pass, white_pass)
|
||||
create_game(nil, GAME_CACHE, game, game_id.as(String))
|
||||
|
||||
id = game_id
|
||||
size = game.size.value
|
||||
black = color_e == Go::Color::Black
|
||||
render "src/Go/views/game.ecr", "src/Go/views/base.ecr"
|
||||
end
|
||||
end
|
||||
|
||||
ws "/game/:id" do |socket, env|
|
||||
game_id = env.params.url["id"]
|
||||
if game = lookup_game(nil, GAME_CACHE, game_id)
|
||||
socket.send game.to_json
|
||||
game.sockets << socket
|
||||
|
||||
socket.on_message do |message|
|
||||
game.try { |game| handle_message(game_id, game, socket, message) }
|
||||
end
|
||||
|
||||
socket.on_close do
|
||||
game.try { |game| game.sockets.delete socket }
|
||||
end
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
Kemal.run
|
147
src/Go/Game.cr
Normal file
147
src/Go/Game.cr
Normal file
|
@ -0,0 +1,147 @@
|
|||
module Go
|
||||
enum Color
|
||||
Black
|
||||
White
|
||||
end
|
||||
|
||||
enum Size
|
||||
Small = 9,
|
||||
Medium = 13,
|
||||
Large = 19
|
||||
end
|
||||
|
||||
alias Board = Hash(Tuple(Int8, Int8), Color)
|
||||
|
||||
class Game
|
||||
property size : Size
|
||||
property black_pass : String
|
||||
property white_pass : String
|
||||
property board : Board
|
||||
property turn : Color
|
||||
property sockets : Array(HTTP::WebSocket)
|
||||
|
||||
def initialize()
|
||||
@size = Size::Small
|
||||
@white_pass = ""
|
||||
@black_pass = ""
|
||||
@board = Board.new
|
||||
@turn = Color::Black
|
||||
@sockets = [] of HTTP::WebSocket
|
||||
end
|
||||
def initialize(size : Size, @black_pass, @white_pass)
|
||||
@size = size
|
||||
@board = Board.new
|
||||
@turn = Color::Black
|
||||
@sockets = [] of HTTP::WebSocket
|
||||
end
|
||||
|
||||
private def cell_json(index, color, json)
|
||||
json.object do
|
||||
json.field "index" do
|
||||
json.object do
|
||||
json.field "x", index[0]
|
||||
json.field "y", index[1]
|
||||
end
|
||||
end
|
||||
json.field "color", color.to_s
|
||||
end
|
||||
end
|
||||
|
||||
private def board_json(board, json)
|
||||
json.array do
|
||||
board.each do |key, value|
|
||||
cell_json(key, value, json)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_json
|
||||
JSON.build do |json|
|
||||
json.object do
|
||||
json.field "turn", @turn.to_s
|
||||
json.field "board" { board_json(@board, json) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private def count_neighbors(x, y, color, visited)
|
||||
coord = {x, y}
|
||||
if visited.includes?(coord) || (x < 0 || x >= @size.value || y < 0 || y >= @size.value)
|
||||
return 0
|
||||
else
|
||||
visited.push(coord)
|
||||
case @board[coord]?
|
||||
when color
|
||||
return count_neighbors(x - 1, y, color, visited) +
|
||||
count_neighbors(x + 1, y, color, visited) +
|
||||
count_neighbors(x, y - 1, color, visited) +
|
||||
count_neighbors(x, y + 1, color, visited)
|
||||
when nil
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
private def remove_color(x, y, color)
|
||||
coord = {x, y}
|
||||
if !(x < 0 || x >= @size.value || y < 0 || y >= @size.value) && @board[coord]? == color
|
||||
@board.delete(coord)
|
||||
remove_color(x - 1, y, color)
|
||||
remove_color(x + 1, y, color)
|
||||
remove_color(x, y - 1, color)
|
||||
remove_color(x, y + 1, color)
|
||||
end
|
||||
end
|
||||
|
||||
private def try_remove_branch(x, y, color)
|
||||
coord = {x, y}
|
||||
if @board[coord]? == color
|
||||
neighbor_count = count_neighbors(x, y, color, [] of Tuple(Int8, Int8))
|
||||
if neighbor_count == 0
|
||||
remove_color(x, y, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def invert(color)
|
||||
color == Color::Black ? Color::White : Color::Black
|
||||
end
|
||||
|
||||
def update(x, y, color)
|
||||
coord = {x, y}
|
||||
if @turn == color
|
||||
@board[coord] = color
|
||||
new_color = invert(color)
|
||||
try_remove_branch(x - 1, y, new_color)
|
||||
try_remove_branch(x + 1, y, new_color)
|
||||
try_remove_branch(x, y - 1, new_color)
|
||||
try_remove_branch(x, y + 1, new_color)
|
||||
try_remove_branch(x, y, color)
|
||||
@turn = new_color
|
||||
end
|
||||
end
|
||||
|
||||
private def color_char(color)
|
||||
color == Color::Black ? 'B' : 'W'
|
||||
end
|
||||
|
||||
private def board_string(board)
|
||||
String.build do |str|
|
||||
(0...@size.value).each do |x|
|
||||
(0...@size.value).each do |y|
|
||||
color = @board[{x.to_i8, y.to_i8}]?
|
||||
char = color ? color_char(color) : 'E'
|
||||
str << char
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def encode
|
||||
{ @turn, @size.value, @white_pass.to_s, @black_pass.to_s, board_string(@board) }
|
||||
end
|
||||
end
|
||||
end
|
3
src/Go/version.cr
Normal file
3
src/Go/version.cr
Normal file
|
@ -0,0 +1,3 @@
|
|||
module Go
|
||||
VERSION = "0.1.0"
|
||||
end
|
13
src/Go/views/base.ecr
Normal file
13
src/Go/views/base.ecr
Normal file
|
@ -0,0 +1,13 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Indie+Flower|Raleway" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content-wrapper">
|
||||
<h1>Go</h1>
|
||||
<%= content %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
11
src/Go/views/game.ecr
Normal file
11
src/Go/views/game.ecr
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div id="elm-root" class="<%= black ? "black" : "white" %>-player"></div>
|
||||
<script src="/js/Go.js"></script>
|
||||
<script>
|
||||
var node = document.getElementById('elm-root')
|
||||
var app = Elm.Main.embed(node, {
|
||||
'black' : <%= black %>,
|
||||
'url' : "<%= "ws://" + URL + ":" + PORT %>",
|
||||
'id' : "<%= id %>",
|
||||
'size' : <%= size %>
|
||||
})
|
||||
</script>
|
29
src/Go/views/index.ecr
Normal file
29
src/Go/views/index.ecr
Normal file
|
@ -0,0 +1,29 @@
|
|||
<div class="split-wrapper">
|
||||
<div class="split-item">
|
||||
<h2>Create Game</h2>
|
||||
<form autocomplete="off" id="create-form" action="/create" method="post">
|
||||
<input required placeholder="Session Name" type="text" name="id"></input>
|
||||
<input required placeholder="Your Password" type="text" name="your-password"></input>
|
||||
<input required placeholder="Their Password" type="text" name="their-password"></input>
|
||||
<div class="radio-parent">
|
||||
<div class="radio-wrapper">
|
||||
<input type="radio" name="color" id="radio-black" value="black" checked></input>
|
||||
<label for="radio-black">Black</label>
|
||||
</div>
|
||||
<div class="radio-wrapper">
|
||||
<input type="radio" name="color" id="radio-white" value="white"></input>
|
||||
<label for="radio-white">White</label>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" value="Create Game"></input>
|
||||
</form>
|
||||
</div>
|
||||
<div class="split-item">
|
||||
<h2>Join Game</h2>
|
||||
<form autocomplete="off" id="join-form" action="/game" method="post">
|
||||
<input required placeholder="Session Name" type="text" name="id"></input>
|
||||
<input required placeholder="Your Password" type="text" name="password"></input>
|
||||
<input type="submit" value="Join Game"></input>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user