Merge branch 'daniel-server' of https://github.com/OSU-CS290-Sp18/final-project-group-4 into daniel-server
This commit is contained in:
commit
1af80d185c
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.js linguist-vendored
|
||||
*.css linguist-vendored
|
||||
*.scss linguist-vendored=false
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,3 +2,5 @@
|
|||
/lib/
|
||||
/bin/
|
||||
/.shards/
|
||||
game_saves.db
|
||||
.sass-cache*
|
19
public/css/main.css
vendored
19
public/css/main.css
vendored
|
@ -88,6 +88,7 @@ div.top-bar {
|
|||
.board-cell {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
<<<<<<< HEAD
|
||||
padding: 5.5555555556%;
|
||||
}
|
||||
.board-cell .overlay {
|
||||
|
@ -109,6 +110,24 @@ div.top-bar {
|
|||
.board-cell.large {
|
||||
padding: 2.6315789474%;
|
||||
}
|
||||
=======
|
||||
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%; }
|
||||
>>>>>>> 733c32748eb734f126d45e84e3609b04d4f1f023
|
||||
|
||||
.black-cell .overlay {
|
||||
background-color: black;
|
||||
|
|
94
public/css/style.css
vendored
Normal file
94
public/css/style.css
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
body {
|
||||
font-family: Roboto;
|
||||
background-image: url(/images/clouds.jpg);
|
||||
background-color: #F5F7FA;
|
||||
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
BIN
public/images/claire.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
BIN
public/images/clouds.jpg
Normal file
BIN
public/images/clouds.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 330 KiB |
BIN
public/images/danila.jpg
Normal file
BIN
public/images/danila.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
BIN
public/images/matthew.jpg
Normal file
BIN
public/images/matthew.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
BIN
public/images/michael.jpg
Normal file
BIN
public/images/michael.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
158
public/scss/css/main.css
vendored
Normal file
158
public/scss/css/main.css
vendored
Normal file
|
@ -0,0 +1,158 @@
|
|||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: "Comfortaa", serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 5em;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Comfortaa", sans-serif;
|
||||
margin: 0px;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 750px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul.game-instructions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
div.top-bar {
|
||||
background-image: url(../../src/Go/views/grid.jpg);
|
||||
line-height: 135px;
|
||||
}
|
||||
|
||||
.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 0.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 0.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 0.25s, color 0.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 0.25s, border-width 0.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;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=css/main.css.map */
|
159
src/Go.cr
159
src/Go.cr
|
@ -2,29 +2,152 @@ 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"
|
||||
GAME_DB = DB.open "sqlite3:./#{GAME_SAVE}"
|
||||
|
||||
def query_game(db, id) : Go::Game?
|
||||
return nil
|
||||
def make_table(db)
|
||||
# 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
|
||||
|
||||
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]?
|
||||
return game
|
||||
else
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
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(" ")
|
||||
command = split_command[0]
|
||||
if command == "place"
|
||||
|
@ -34,9 +157,15 @@ def handle_message(id, game, socket, message)
|
|||
|
||||
game.update(x, y, color)
|
||||
game.sockets.each { |socket| socket.send game.to_json }
|
||||
# If saving game on move
|
||||
save_game(GAME_DB, id, game)
|
||||
end
|
||||
end
|
||||
|
||||
get "/about" do |env|
|
||||
render "src/Go/views/about.ecr"
|
||||
end
|
||||
|
||||
get "/" do |env|
|
||||
render "src/Go/views/index.ecr", "src/Go/views/base.ecr"
|
||||
end
|
||||
|
@ -46,7 +175,7 @@ post "/game" do |env|
|
|||
game_password = env.params.body["password"]?
|
||||
if game_id == nil || game_password == nil
|
||||
render_404
|
||||
elsif game = lookup_game(nil, GAME_CACHE, game_id)
|
||||
elsif game = lookup_game(GAME_DB, GAME_CACHE, game_id)
|
||||
id = game_id
|
||||
size = game.size.value
|
||||
black = nil
|
||||
|
@ -78,7 +207,7 @@ post "/create" do |env|
|
|||
|
||||
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)
|
||||
elsif game = lookup_game(GAME_DB, GAME_CACHE, game_id)
|
||||
render_404
|
||||
else
|
||||
color_e = color_e.as(Go::Color)
|
||||
|
@ -101,7 +230,7 @@ end
|
|||
|
||||
ws "/game/:id" do |socket, env|
|
||||
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
|
||||
game.sockets << socket
|
||||
|
||||
|
@ -117,4 +246,22 @@ ws "/game/:id" do |socket, env|
|
|||
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
|
||||
|
||||
# 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 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
|
||||
|
@ -133,7 +141,7 @@ module Go
|
|||
end
|
||||
|
||||
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
|
||||
|
|
72
src/Go/views/about.ecr
Normal file
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>
|
Loading…
Reference in New Issue
Block a user