Compare commits
38 Commits
7190ea2ded
...
7d548c390a
Author | SHA1 | Date | |
---|---|---|---|
7d548c390a | |||
|
ba4f60c0a8 | ||
|
2ed23ee32b | ||
55a39fe1c7 | |||
|
23b8471301 | ||
d8ae468f39 | |||
b7bdbbdc12 | |||
|
1af80d185c | ||
733c32748e | |||
bc395e5e7e | |||
b844a83db1 | |||
|
7eab8eb71e | ||
|
7e17bba627 | ||
|
913dea2fea | ||
|
59bc24553d | ||
|
3af22f9464 | ||
|
fc42ac7651 | ||
|
b41035fac7 | ||
|
2c8bc7aff9 | ||
|
f04f44e593 | ||
|
fe61d3dc73 | ||
|
dac5a64a45 | ||
|
c0a1548463 | ||
|
b4a31e709c | ||
|
48d5309f63 | ||
|
24b262901f | ||
|
4b226210e8 | ||
|
6bae399d59 | ||
|
0a103b7b73 | ||
|
164cf6a73a | ||
|
12c137ef4f | ||
|
9c3665ea5c | ||
|
2ec51a45ea | ||
|
65be4f3c4c | ||
|
5ee0839451 | ||
|
05974b845c | ||
|
0939a6590e | ||
|
de0fbfed94 |
3
.gitattributes
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
*.js linguist-vendored
|
||||||
|
*.css linguist-vendored
|
||||||
|
*.scss linguist-vendored=false
|
2
.gitignore
vendored
|
@ -2,3 +2,5 @@
|
||||||
/lib/
|
/lib/
|
||||||
/bin/
|
/bin/
|
||||||
/.shards/
|
/.shards/
|
||||||
|
game_saves.db
|
||||||
|
.sass-cache*
|
65
public/css/main.css
vendored
|
@ -1,20 +1,71 @@
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
font-family: "Indie Flower", serif;
|
font-family: "Comfortaa", serif;
|
||||||
text-align: center; }
|
text-align: center; }
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 5em;
|
font-family: "Bangers", serif;
|
||||||
margin: 0px; }
|
font-size: 7em;
|
||||||
|
color: darkred;
|
||||||
|
display: inline;
|
||||||
|
margin: 0px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background: white;
|
||||||
|
padding: 2px 17px 2px 8px;
|
||||||
|
border-radius: 15px; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Raleway", sans-serif;
|
font-family: "Comfortaa", sans-serif;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
background-color: #f4f4f4; }
|
background-color: #f4f4f4; }
|
||||||
|
|
||||||
|
.rules-body {
|
||||||
|
background-image: url(/images/grid.jpg); }
|
||||||
|
|
||||||
|
.rules-title {
|
||||||
|
font-family: "Bangers", serif;
|
||||||
|
font-size: 66px;
|
||||||
|
color: darkred;
|
||||||
|
display: inline;
|
||||||
|
border-radius: 20px;
|
||||||
|
line-height: 115px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: white;
|
||||||
|
margin-left: 35%; }
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
max-width: 750px;
|
max-width: 750px;
|
||||||
margin: auto; }
|
margin: auto; }
|
||||||
|
|
||||||
|
ul.rules-list {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
padding: 26px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin: 0px 25px; }
|
||||||
|
|
||||||
|
li.rule {
|
||||||
|
padding: 3px;
|
||||||
|
font-size: 18px; }
|
||||||
|
|
||||||
|
ul.game-instructions {
|
||||||
|
display: flex;
|
||||||
|
padding: 0px; }
|
||||||
|
|
||||||
|
li#welcome {
|
||||||
|
padding: 0px 10px 0px 5px;
|
||||||
|
border-left: solid tomato;
|
||||||
|
text-align: center;
|
||||||
|
list-style: none; }
|
||||||
|
li#welcome:last-child {
|
||||||
|
border-right: solid tomato; }
|
||||||
|
|
||||||
|
div.top-bar {
|
||||||
|
background-image: url(/images/grid.jpg);
|
||||||
|
line-height: 135px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 40px; }
|
||||||
|
|
||||||
.board {
|
.board {
|
||||||
background-color: tomato;
|
background-color: tomato;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
@ -63,7 +114,11 @@ body {
|
||||||
|
|
||||||
.split-item {
|
.split-item {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
box-sizing: border-box; }
|
box-sizing: border-box;
|
||||||
|
border: solid tomato 1px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background-color: white;
|
||||||
|
margin: 0px 10px; }
|
||||||
|
|
||||||
.split-wrapper form {
|
.split-wrapper form {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
95
public/css/style.css
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
body {
|
||||||
|
font-family: Roboto;
|
||||||
|
background-image: url(/images/clouds.jpg);
|
||||||
|
background-color: #F5F7FA;
|
||||||
|
background-size: cover;
|
||||||
|
color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color:white;
|
||||||
|
box-shadow: 2px 2px 3px rgba(0,0,0,.13) ,1px 2px 2px rgba(0,0,0,.1) , -1px -2px 2px rgba(0,0,0,.05);
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
max-width: 750px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-container{
|
||||||
|
display:flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.person-photo img{
|
||||||
|
padding-top:10px;
|
||||||
|
height:30%;
|
||||||
|
width:30%;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border-radius:50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-grow:1;
|
||||||
|
flex-basis:50%;
|
||||||
|
|
||||||
|
background-color:white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toplevel{
|
||||||
|
position:relative;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size:30px;
|
||||||
|
color:#546076;
|
||||||
|
font-weight:100;
|
||||||
|
text-align:center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Mission{
|
||||||
|
font-size:40px;
|
||||||
|
font-weight:100;
|
||||||
|
padding-top:16px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Person-Name{
|
||||||
|
font-size:20px;
|
||||||
|
font-weight:600;
|
||||||
|
padding-top:16px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
.member-text {
|
||||||
|
position:center;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
font:Roboto;
|
||||||
|
font-weight:400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mission-text {
|
||||||
|
position:center;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
width:50%;
|
||||||
|
width:800px;
|
||||||
|
font:Roboto;
|
||||||
|
font-weight:400;
|
||||||
|
text-align:center;
|
||||||
|
padding-top:1%;
|
||||||
|
}
|
BIN
public/images/claire.jpg
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
public/images/clouds.jpg
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
public/images/danila.jpg
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
public/images/go-example.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
public/images/grid.jpg
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
public/images/matthew.jpg
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
public/images/michael.jpg
Normal file
After Width: | Height: | Size: 9.4 KiB |
|
@ -5,26 +5,87 @@ $background-grey: #f4f4f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
font-family: "Indie Flower", serif;
|
font-family: "Comfortaa", serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 5em;
|
font-family: "Bangers", serif;
|
||||||
|
font-size: 7em;
|
||||||
|
color:darkred;
|
||||||
|
display:inline;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background: white;
|
||||||
|
padding: 2px 17px 2px 8px;
|
||||||
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Raleway", sans-serif;
|
font-family: "Comfortaa", sans-serif;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
background-color: $background-grey;
|
background-color: $background-grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rules-body {
|
||||||
|
background-image: url(/images/grid.jpg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-title {
|
||||||
|
font-family: "Bangers", serif;
|
||||||
|
font-size: 66px;
|
||||||
|
color: darkred;
|
||||||
|
display: inline;
|
||||||
|
border-radius: 20px;
|
||||||
|
line-height: 115px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: white;
|
||||||
|
margin-left: 35%;
|
||||||
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
max-width: 750px;
|
max-width: 750px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.rules-list {
|
||||||
|
background-color: $background-grey;
|
||||||
|
padding: 26px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin: 0px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.rule {
|
||||||
|
padding: 3px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.game-instructions {
|
||||||
|
display: flex;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
li#welcome {
|
||||||
|
padding: 0px 10px 0px 5px;
|
||||||
|
border-left: solid tomato;
|
||||||
|
text-align: center;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: solid tomato;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.top-bar {
|
||||||
|
background-image: url(/images/grid.jpg);
|
||||||
|
line-height: 135px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
.board {
|
.board {
|
||||||
background-color: tomato;
|
background-color: tomato;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
@ -97,6 +158,10 @@ body {
|
||||||
.split-item {
|
.split-item {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
border: solid tomato 1px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background-color: white;
|
||||||
|
margin: 0px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-wrapper form {
|
.split-wrapper form {
|
||||||
|
|
163
src/Go.cr
|
@ -2,29 +2,152 @@ require "./Go/*"
|
||||||
require "kemal"
|
require "kemal"
|
||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
|
require "db"
|
||||||
|
require "sqlite3"
|
||||||
|
|
||||||
URL = "localhost"
|
URL = "localhost"
|
||||||
PORT = "3000"
|
PORT = "3000"
|
||||||
GAME_CACHE = {} of String => Go::Game
|
GAME_CACHE = {} of String => Go::Game
|
||||||
|
GAME_SAVE = "./game_saves.db"
|
||||||
|
GAME_DB = DB.open "sqlite3:./#{GAME_SAVE}"
|
||||||
|
|
||||||
def query_game(db, id) : Go::Game?
|
def make_table(db)
|
||||||
return nil
|
# Function: make_table
|
||||||
|
# Parameters: db(Sqlite)
|
||||||
|
# Returns: None
|
||||||
|
db.exec "create table if not exists game_saves (
|
||||||
|
gameid string, turn integer, size integer, white_pass string,
|
||||||
|
black_pass string, time string, board string, UNIQUE(gameid) )"
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_game(db, gameid, game)
|
||||||
|
# Function: save_game
|
||||||
|
# Parameters: db(Sqlite) gameid(String) game(Go::Game)
|
||||||
|
# Returns: None
|
||||||
|
turn, size, white_pass, black_pass, board = game.encode
|
||||||
|
# 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, Time.now.to_json, board
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_all(cache)
|
||||||
|
# Function: save_all
|
||||||
|
# Parameters: cache({(String),(Go::Game)})
|
||||||
|
# Returns: None
|
||||||
|
cache.each do |game_hash|
|
||||||
|
gameid, game = game_hash
|
||||||
|
save_game(GAME_DB, gameid, game)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def query_game(db, gameid) : Go::Game?
|
||||||
|
# Function: query_game
|
||||||
|
# Parameters: db(Sqlite) gameid(String)
|
||||||
|
# Returns: (Go::Game) for a given gameid
|
||||||
|
turn = 0
|
||||||
|
size = 9
|
||||||
|
white_pass = ""
|
||||||
|
black_pass = ""
|
||||||
|
board = ""
|
||||||
|
begin
|
||||||
|
# Query whole row where the gameid is found
|
||||||
|
db.query "SELECT turn,size,white_pass,black_pass,board FROM game_saves WHERE gameid = ?", gameid do |rs|
|
||||||
|
rs.each do
|
||||||
|
turn = rs.read(Int32)
|
||||||
|
size = rs.read(Int32)
|
||||||
|
white_pass = rs.read(String)
|
||||||
|
black_pass = rs.read(String)
|
||||||
|
board = rs.read(String)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ( board == "" )
|
||||||
|
return nil
|
||||||
|
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
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
# Finished Go::Game object to return
|
||||||
|
return game
|
||||||
|
end
|
||||||
|
|
||||||
|
def game_cleaner(db, cache)
|
||||||
|
# Function: game_cleaner
|
||||||
|
# Parameters: db(Sqlite) cache({(String),(Go::Game)})
|
||||||
|
# Returns: None
|
||||||
|
# Description: Cleans the database and memory of games older than 24 hours, every 2 hours
|
||||||
|
spawn do
|
||||||
|
loop do
|
||||||
|
gameid = ""
|
||||||
|
ntime = Time.now()
|
||||||
|
# Time span, for the subtraction of two time objects
|
||||||
|
tspan = Time::Span.new(0,0,0)
|
||||||
|
db.query "SELECT time, gameid FROM game_saves" do |rs|
|
||||||
|
rs.each do
|
||||||
|
stime = Time.from_json(rs.read(String))
|
||||||
|
gameid = rs.read(String)
|
||||||
|
tspan = ntime - stime
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if( tspan.hours > 24 || tspan.days > 0 )
|
||||||
|
# Delete game from database
|
||||||
|
db.exec("DELETE FROM game_saves WHERE gameid = ?", gameid)
|
||||||
|
# Delete game from memory
|
||||||
|
cache.delete(gameid)
|
||||||
|
puts "Game: #{gameid} deleted due to inactivity"
|
||||||
|
end
|
||||||
|
sleep 2.hour
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def lookup_game(db, cache, id) : Go::Game?
|
def lookup_game(db, cache, id) : Go::Game?
|
||||||
|
# Function: lookup_game
|
||||||
|
# Parameters: db(Sqlite) cache({(String), (Go::Game)}) id(String)
|
||||||
|
# Returns: None
|
||||||
|
# Description: Loads game data from memory, then attempts load from database
|
||||||
if game = cache[id]?
|
if game = cache[id]?
|
||||||
return game
|
return game
|
||||||
else
|
else
|
||||||
loaded_game = query_game(db, id)
|
loaded_game = query_game(db, id)
|
||||||
cache[id] = loaded_game if loaded_game
|
# Need to convert id to string for some reason
|
||||||
|
cache[id.to_s] = loaded_game if loaded_game
|
||||||
return loaded_game
|
return loaded_game
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_game(db, cache, game, id)
|
def create_game(db, cache, game, id)
|
||||||
|
# Function: create_game
|
||||||
|
# Parameters: db(Sqlite) cache({(String), (Go::Game)}) game(Go::Game) id(String)
|
||||||
|
# Returns: None
|
||||||
cache[id] = game
|
cache[id] = game
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_message(id, game, socket, message)
|
def handle_message(id, game, socket, message)
|
||||||
|
# Function: handle_message
|
||||||
|
# Parameters: id(String) game(Go::Game) socket(WebSocket) message(String)
|
||||||
|
# Returns: None
|
||||||
|
# Description: Handle placement messages from the WebSocket
|
||||||
split_command = message.split(" ")
|
split_command = message.split(" ")
|
||||||
command = split_command[0]
|
command = split_command[0]
|
||||||
if command == "place"
|
if command == "place"
|
||||||
|
@ -34,9 +157,19 @@ def handle_message(id, game, socket, message)
|
||||||
|
|
||||||
game.update(x, y, color)
|
game.update(x, y, color)
|
||||||
game.sockets.each { |socket| socket.send game.to_json }
|
game.sockets.each { |socket| socket.send game.to_json }
|
||||||
|
# If saving game on move
|
||||||
|
save_game(GAME_DB, id, game)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get "/about" do |env|
|
||||||
|
render "src/Go/views/about.ecr"
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/rules" do |env|
|
||||||
|
render "src/Go/views/rules.ecr"
|
||||||
|
end
|
||||||
|
|
||||||
get "/" do |env|
|
get "/" do |env|
|
||||||
render "src/Go/views/index.ecr", "src/Go/views/base.ecr"
|
render "src/Go/views/index.ecr", "src/Go/views/base.ecr"
|
||||||
end
|
end
|
||||||
|
@ -46,7 +179,7 @@ post "/game" do |env|
|
||||||
game_password = env.params.body["password"]?
|
game_password = env.params.body["password"]?
|
||||||
if game_id == nil || game_password == nil
|
if game_id == nil || game_password == nil
|
||||||
render_404
|
render_404
|
||||||
elsif game = lookup_game(nil, GAME_CACHE, game_id)
|
elsif game = lookup_game(GAME_DB, GAME_CACHE, game_id)
|
||||||
id = game_id
|
id = game_id
|
||||||
size = game.size.value
|
size = game.size.value
|
||||||
black = nil
|
black = nil
|
||||||
|
@ -78,7 +211,7 @@ post "/create" do |env|
|
||||||
|
|
||||||
if game_id == nil || user_password == nil || other_password == nil || color == nil || color_e == nil
|
if game_id == nil || user_password == nil || other_password == nil || color == nil || color_e == nil
|
||||||
render_404
|
render_404
|
||||||
elsif game = lookup_game(nil, GAME_CACHE, game_id)
|
elsif game = lookup_game(GAME_DB, GAME_CACHE, game_id)
|
||||||
render_404
|
render_404
|
||||||
else
|
else
|
||||||
color_e = color_e.as(Go::Color)
|
color_e = color_e.as(Go::Color)
|
||||||
|
@ -101,7 +234,7 @@ end
|
||||||
|
|
||||||
ws "/game/:id" do |socket, env|
|
ws "/game/:id" do |socket, env|
|
||||||
game_id = env.params.url["id"]
|
game_id = env.params.url["id"]
|
||||||
if game = lookup_game(nil, GAME_CACHE, game_id)
|
if game = lookup_game(GAME_DB, GAME_CACHE, game_id)
|
||||||
socket.send game.to_json
|
socket.send game.to_json
|
||||||
game.sockets << socket
|
game.sockets << socket
|
||||||
|
|
||||||
|
@ -117,4 +250,22 @@ ws "/game/:id" do |socket, env|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# For timed-autosave
|
||||||
|
# spawn do
|
||||||
|
# loop do
|
||||||
|
# sleep 10.minute
|
||||||
|
# save_all(GAME_CACHE)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# If table does not exist, build it
|
||||||
|
make_table(GAME_DB)
|
||||||
|
# Remove games that are older than 24hrs
|
||||||
|
game_cleaner(GAME_DB, GAME_CACHE)
|
||||||
Kemal.run
|
Kemal.run
|
||||||
|
|
||||||
|
# If exit is disabled in kemal.stop
|
||||||
|
# For save on close
|
||||||
|
# at_exit do
|
||||||
|
# save_all(GAME_CACHE)
|
||||||
|
# end
|
||||||
|
|
|
@ -20,6 +20,14 @@ module Go
|
||||||
property turn : Color
|
property turn : Color
|
||||||
property sockets : Array(HTTP::WebSocket)
|
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)
|
def initialize(size : Size, @black_pass, @white_pass)
|
||||||
@size = size
|
@size = size
|
||||||
@board = Board.new
|
@board = Board.new
|
||||||
|
@ -133,7 +141,7 @@ module Go
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode
|
def encode
|
||||||
{ @turn.to_s, @size.value, board_string(@board) }
|
{ @turn, @size.value, @white_pass.to_s, @black_pass.to_s, board_string(@board) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
72
src/Go/views/about.ecr
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>About Us</title>
|
||||||
|
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Roboto:100,400,700" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/style.css" media="screen">
|
||||||
|
<script src="https://use.fontawesome.com/releases/v5.0.9/js/all.js" defer></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h2>Hello!</h2>
|
||||||
|
<p>
|
||||||
|
Welcome to our Go website. We are Group 4 from CS 290. Our mission is very ambitious - to get an A in CS 290, Web Development. Despite the immense difficulty of this task, we did our best to bring you this website, so that you may enjoy playing the game of Go with your friends. After Google's AlphaGo's victory against the best human Go player, there's not really a point to playing against a computer - you will lose.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="member-container card">
|
||||||
|
<h2>Group Members</h2>
|
||||||
|
<div class="toplevel">
|
||||||
|
<article class="member">
|
||||||
|
<div class="member-content">
|
||||||
|
<div class="person-photo" alt"">
|
||||||
|
<img src="/images/claire.jpg" alt="daa">
|
||||||
|
</div>
|
||||||
|
<h2 class="Person-Name" >Claire Cahill</h2>
|
||||||
|
<p class="member-text">
|
||||||
|
Claire created a page explaining the rules of Go, so that new players can learn how to play the game instead of clicking around randomly.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="member">
|
||||||
|
<div class="member-content">
|
||||||
|
<div class="person-photo" alt"">
|
||||||
|
<img src="/images/danila.jpg" alt="daa">
|
||||||
|
</div>
|
||||||
|
<h2 class="Person-Name" >Danila Fedorin</h2>
|
||||||
|
<p class="member-text">
|
||||||
|
Danila created the front-end and a basic back-end for the Go game. He used unconventional languages, namely Elm, a functional programming language designed for web applications, and Crystal, a compiled sibiling language of Ruby.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="member">
|
||||||
|
<div class="member-content">
|
||||||
|
<div class="person-photo" alt"">
|
||||||
|
<img src="/images/michael.jpg" alt="daa">
|
||||||
|
</div>
|
||||||
|
<h2 class="Person-Name" >Michael Huang</h2>
|
||||||
|
<p class="member-text">
|
||||||
|
Michael created this about page!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="member">
|
||||||
|
<div class="member-content">
|
||||||
|
<div class="person-photo" alt"">
|
||||||
|
<img src="/images/matthew.jpg" alt="daa">
|
||||||
|
</div>
|
||||||
|
<h2 class="Person-Name" >Matthew Sessions</h2>
|
||||||
|
<p class="member-text">
|
||||||
|
Matt adapted the Crystal back-end to work with SQLite. This meant loading and saving Go games into the database instead of an in-memory cache, thereby allowing games to persist after server restarts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -3,10 +3,11 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="/css/main.css">
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Indie+Flower|Raleway" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Indie+Flower|Raleway" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Bangers" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="top-bar"><h1 class="title">GO</h1></div>
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<h1>Go</h1>
|
|
||||||
<%= content %>
|
<%= content %>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,29 +1,37 @@
|
||||||
|
<ul class="game-instructions">
|
||||||
|
<li id="welcome">Welcome to our simulation of Go! <a href="/about">Meet the creators.</a></li>
|
||||||
|
<li id="welcome">Defeat your opponent by earning more territory.</li>
|
||||||
|
<li id="welcome">"Capture" dots of the opposite color by surrounding them.</li>
|
||||||
|
<li id="welcome">Create a game by filling out the fields below, giving the next player a password in "Their password."</li>
|
||||||
|
<li id="welcome">Join a game by entering the corresponding session name and password.</li>
|
||||||
|
<li id="welcome">Additional rules are found <a href="/rules">here.</a></li>
|
||||||
|
</ul>
|
||||||
<div class="split-wrapper">
|
<div class="split-wrapper">
|
||||||
<div class="split-item">
|
<div class="split-item">
|
||||||
<h2>Create Game</h2>
|
<h2>Create Game</h2>
|
||||||
<form autocomplete="off" id="create-form" action="/create" method="post">
|
<form autocomplete="off" id="create-form" action="/create" method="post">
|
||||||
<input required placeholder="Session Name" type="text" name="id"></input>
|
<input required placeholder="Session Name" type="text" name="id"></input>
|
||||||
<input required placeholder="Your Password" type="text" name="your-password"></input>
|
<input required placeholder="Your Password" type="text" name="your-password"></input>
|
||||||
<input required placeholder="Their Password" type="text" name="their-password"></input>
|
<input required placeholder="Their Password" type="text" name="their-password"></input>
|
||||||
<div class="radio-parent">
|
<div class="radio-parent">
|
||||||
<div class="radio-wrapper">
|
<div class="radio-wrapper">
|
||||||
<input type="radio" name="color" id="radio-black" value="black" checked></input>
|
<input type="radio" name="color" id="radio-black" value="black" checked></input>
|
||||||
<label for="radio-black">Black</label>
|
<label for="radio-black">Black</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="radio-wrapper">
|
<div class="radio-wrapper">
|
||||||
<input type="radio" name="color" id="radio-white" value="white"></input>
|
<input type="radio" name="color" id="radio-white" value="white"></input>
|
||||||
<label for="radio-white">White</label>
|
<label for="radio-white">White</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" value="Create Game"></input>
|
<input type="submit" value="Create Game"></input>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="split-item">
|
<div class="split-item">
|
||||||
<h2>Join Game</h2>
|
<h2>Join Game</h2>
|
||||||
<form autocomplete="off" id="join-form" action="/game" method="post">
|
<form autocomplete="off" id="join-form" action="/game" method="post">
|
||||||
<input required placeholder="Session Name" type="text" name="id"></input>
|
<input required placeholder="Session Name" type="text" name="id"></input>
|
||||||
<input required placeholder="Your Password" type="text" name="password"></input>
|
<input required placeholder="Your Password" type="text" name="password"></input>
|
||||||
<input type="submit" value="Join Game"></input>
|
<input type="submit" value="Join Game"></input>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
24
src/Go/views/rules.ecr
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/css/main.css"> </link>
|
||||||
|
</head>
|
||||||
|
<body class="rules-body">
|
||||||
|
<h2 class="rules-title">Rules of Go</h2>
|
||||||
|
<ul class="rules-list">
|
||||||
|
<li class="rule">The main object of the game is to cover more surface area of the board with your color (either black or white). Dots (sometimes referred to as “stones”) are placed at the intersections of lines.</li>
|
||||||
|
<li class="rule">Once a dot is placed, it cannot be removed. However, it can be “captured” by the other player if he/she completely surrounds a group of dots.</li>
|
||||||
|
<li class="rule">Every dot captured by a player earns him/her a point.</li>
|
||||||
|
<li class="rule">Points are also earned for the dots of that player’s color remaining on the board at the end of the game.</li>
|
||||||
|
<li class="rule">The player with the most points at the end of the game is the winner.</li>
|
||||||
|
<li class="rule">Dots are placed one at a time. Black plays first.</li>
|
||||||
|
<img src="/images/go-example.jpg">
|
||||||
|
<li class="rule">This diagram, (taken from <a href=https://www.britgo.org/intro/intro2.html>britgo</a>) shows the black player capturing a white stone at a (location a shows an example of a black “eye”). The black player has 15 points of territory - 10 in the bottom right corner and 5 at the top of the board. They also earn a point for capturing a white dot at location a, so their total is 16 points. The white player has 17 points - 11 along the left side and 6 in the top right corner. Therefore, the white player would win by 1 point.</li>
|
||||||
|
<li class="rule">Each player has an endless amount of dots to place.</li>
|
||||||
|
<li class="rule">A player can choose to pass on a turn. This usually occurs when the player cannot place any more dots, if they are able to place it in their own “eye” or the “eye” of the other player, causing it to be captured. The game ends if both players to pass their turn.</li>
|
||||||
|
<li class="rule">Strings of dots can only be formed when dots are horizontally or vertically adjacent (diagonals don’t count).</li>
|
||||||
|
<li class="rule">A player may not self-capture. A self-capture would occur if a dot was placed inside a group of the opposite color.</li>
|
||||||
|
<li class="rule">For additional rules, check out <a href=https://www.britgo.org/intro/intro2.html>this page.</a></li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|