Merge branch 'daniel-server' of https://github.com/OSU-CS290-Sp18/final-project-group-4 into daniel-server

This commit is contained in:
clairecahill 2018-06-12 19:56:59 -07:00
commit 1af80d185c
14 changed files with 510 additions and 7 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.js linguist-vendored
*.css linguist-vendored
*.scss linguist-vendored=false

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
/lib/
/bin/
/.shards/
game_saves.db
.sass-cache*

BIN
Go Normal file

Binary file not shown.

19
public/css/main.css vendored
View File

@ -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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
public/images/clouds.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

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
View 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
View File

@ -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

View File

@ -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
View 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>