Compare commits

..

No commits in common. "7d548c390af1bd5d4d474e331444cf90a968bd79" and "7190ea2dedf01a097de326f3645ed2bb23a5f918" have entirely different histories.

19 changed files with 43 additions and 527 deletions

3
.gitattributes vendored
View File

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

2
.gitignore vendored
View File

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

BIN
Go

Binary file not shown.

View File

@ -1,71 +1,20 @@
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: "Comfortaa", serif; font-family: "Indie Flower", serif;
text-align: center; } text-align: center; }
h1 { h1 {
font-family: "Bangers", serif; font-size: 5em;
font-size: 7em; margin: 0px; }
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: "Comfortaa", sans-serif; font-family: "Raleway", 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;
@ -114,11 +63,7 @@ div.top-bar {
.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;

View File

@ -1,95 +0,0 @@
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%;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -5,87 +5,26 @@ $background-grey: #f4f4f4;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: "Comfortaa", serif; font-family: "Indie Flower", serif;
text-align: center; text-align: center;
} }
h1 { h1 {
font-family: "Bangers", serif; font-size: 5em;
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: "Comfortaa", sans-serif; font-family: "Raleway", 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;
@ -158,10 +97,6 @@ div.top-bar {
.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 {

161
src/Go.cr
View File

@ -2,152 +2,29 @@ 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 make_table(db) def query_game(db, id) : Go::Game?
# 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 return nil
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
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? 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)
# Need to convert id to string for some reason cache[id] = loaded_game if loaded_game
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"
@ -157,19 +34,9 @@ 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
@ -179,7 +46,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(GAME_DB, GAME_CACHE, game_id) elsif game = lookup_game(nil, GAME_CACHE, game_id)
id = game_id id = game_id
size = game.size.value size = game.size.value
black = nil black = nil
@ -211,7 +78,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(GAME_DB, GAME_CACHE, game_id) elsif game = lookup_game(nil, GAME_CACHE, game_id)
render_404 render_404
else else
color_e = color_e.as(Go::Color) color_e = color_e.as(Go::Color)
@ -234,7 +101,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(GAME_DB, GAME_CACHE, game_id) if game = lookup_game(nil, GAME_CACHE, game_id)
socket.send game.to_json socket.send game.to_json
game.sockets << socket game.sockets << socket
@ -250,22 +117,4 @@ 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

View File

@ -20,14 +20,6 @@ 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
@ -141,7 +133,7 @@ module Go
end end
def encode def encode
{ @turn, @size.value, @white_pass.to_s, @black_pass.to_s, board_string(@board) } { @turn.to_s, @size.value, board_string(@board) }
end end
end end
end end

View File

@ -1,72 +0,0 @@
<!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>

View File

@ -3,11 +3,10 @@
<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>

View File

@ -1,11 +1,3 @@
<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>

View File

@ -1,24 +0,0 @@
<!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 players 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 dont 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>