GoServer/src/Go.cr

272 lines
7.9 KiB
Crystal
Raw Normal View History

require "./Go/*"
2018-05-25 12:43:30 -07:00
require "kemal"
require "json"
2018-06-04 11:28:39 -07:00
require "db"
require "sqlite3"
2018-05-25 12:43:30 -07:00
URL = "localhost"
2018-05-26 00:33:12 -07:00
PORT = "3000"
GAME_CACHE = {} of String => Go::Game
2018-06-06 18:33:01 -07:00
GAME_SAVE = "./game_saves.db"
2018-06-08 14:08:47 -07:00
GAME_DB = DB.open "sqlite3:./#{GAME_SAVE}"
2018-06-04 11:28:39 -07:00
2018-06-08 14:20:50 -07:00
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
2018-06-04 11:28:39 -07:00
def save_game(db, gameid, game)
2018-06-06 23:02:25 -07:00
# Function: save_game
2018-06-08 14:08:47 -07:00
# Parameters: db(Sqlite) gameid(String) game(Go::Game)
2018-06-06 23:02:25 -07:00
# Returns: None
2018-06-05 10:27:07 -07:00
turn, size, white_pass, black_pass, board = game.encode
2018-06-08 14:08:47 -07:00
# 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
2018-06-04 11:28:39 -07:00
end
2018-06-06 23:02:25 -07:00
def save_all(cache)
# Function: save_all
# Parameters: cache({(String),(Go::Game)})
# Returns: None
cache.each do |game_hash|
2018-06-06 19:18:15 -07:00
gameid, game = game_hash
2018-06-08 14:08:47 -07:00
save_game(GAME_DB, gameid, game)
2018-06-06 19:18:15 -07:00
end
end
2018-06-04 11:28:39 -07:00
def query_game(db, gameid) : Go::Game?
2018-06-06 23:02:25 -07:00
# Function: query_game
2018-06-08 14:08:47 -07:00
# Parameters: db(Sqlite) gameid(String)
2018-06-06 23:02:25 -07:00
# Returns: (Go::Game) for a given gameid
2018-06-08 14:08:47 -07:00
turn = 0
size = 9
2018-06-05 10:27:07 -07:00
white_pass = ""
black_pass = ""
board = ""
2018-06-06 18:33:01 -07:00
begin
2018-06-08 14:08:47 -07:00
# 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)
2018-06-04 11:28:39 -07:00
end
end
2018-06-08 14:08:47 -07:00
if ( board == "" )
return nil
end
2018-06-06 18:33:01 -07:00
# 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|
2018-06-06 21:41:07 -07:00
x = counter / 9
y = counter % 9
2018-06-06 18:33:01 -07:00
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
2018-06-08 14:08:47 -07:00
def game_cleaner(db, cache)
2018-06-06 23:02:25 -07:00
# Function: game_cleaner
2018-06-08 14:08:47 -07:00
# Parameters: db(Sqlite) cache({(String),(Go::Game)})
2018-06-06 23:02:25 -07:00
# 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()
2018-06-08 14:08:47 -07:00
# 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
2018-06-06 23:02:25 -07:00
end
end
2018-06-08 14:08:47 -07:00
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
2018-06-06 23:02:25 -07:00
sleep 2.hour
end
end
end
def lookup_game(db, cache, id) : Go::Game?
2018-06-06 23:02:25 -07:00
# Function: lookup_game
2018-06-08 14:08:47 -07:00
# Parameters: db(Sqlite) cache({(String), (Go::Game)}) id(String)
2018-06-06 23:02:25 -07:00
# 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)
2018-06-06 18:33:01 -07:00
# Need to convert id to string for some reason
cache[id.to_s] = loaded_game if loaded_game
return loaded_game
end
end
2018-05-25 23:36:52 -07:00
def create_game(db, cache, game, id)
2018-06-06 23:02:25 -07:00
# Function: create_game
2018-06-08 14:08:47 -07:00
# Parameters: db(Sqlite) cache({(String), (Go::Game)}) game(Go::Game) id(String)
2018-06-06 23:02:25 -07:00
# Returns: None
2018-05-25 23:36:52 -07:00
cache[id] = game
end
def handle_message(id, game, socket, message)
2018-06-06 23:02:25 -07:00
# 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"
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)
2018-05-26 13:21:53 -07:00
game.sockets.each { |socket| socket.send game.to_json }
2018-06-06 21:41:07 -07:00
# If saving game on move
2018-06-08 14:08:47 -07:00
save_game(GAME_DB, id, game)
end
end
2018-05-25 12:43:30 -07:00
2018-06-12 16:28:29 -07:00
get "/about" do |env|
render "src/Go/views/about.ecr"
end
get "/rules" do |env|
render "src/Go/views/rules.ecr"
end
2018-05-25 12:43:30 -07:00
get "/" do |env|
render "src/Go/views/index.ecr", "src/Go/views/base.ecr"
end
2018-05-25 23:36:52 -07:00
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
2018-06-08 14:08:47 -07:00
elsif game = lookup_game(GAME_DB, GAME_CACHE, game_id)
id = game_id
size = game.size.value
black = nil
2018-05-26 13:21:53 -07:00
if game_password == game.black_pass
black = true
2018-05-26 13:21:53 -07:00
elsif game_password == game.white_pass
black = false
end
2018-05-25 15:35:55 -07:00
black.try { |black| render "src/Go/views/game.ecr", "src/Go/views/base.ecr"} || render_404
else
render_404
end
end
2018-05-25 23:36:52 -07:00
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
2018-06-08 14:08:47 -07:00
elsif game = lookup_game(GAME_DB, GAME_CACHE, game_id)
2018-05-25 23:36:52 -07:00
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))
2018-05-25 23:41:10 -07:00
id = game_id
size = game.size.value
black = color_e == Go::Color::Black
render "src/Go/views/game.ecr", "src/Go/views/base.ecr"
2018-05-25 23:36:52 -07:00
end
end
ws "/game/:id" do |socket, env|
game_id = env.params.url["id"]
2018-06-08 14:08:47 -07:00
if game = lookup_game(GAME_DB, GAME_CACHE, game_id)
2018-05-26 13:21:53 -07:00
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
2018-05-25 12:43:30 -07:00
end
2018-06-06 21:41:07 -07:00
# For timed-autosave
# spawn do
# loop do
# sleep 10.minute
2018-06-06 23:02:25 -07:00
# save_all(GAME_CACHE)
2018-06-06 21:41:07 -07:00
# end
# end
2018-06-08 14:20:50 -07:00
# If table does not exist, build it
make_table(GAME_DB)
# Remove games that are older than 24hrs
2018-06-08 14:08:47 -07:00
game_cleaner(GAME_DB, GAME_CACHE)
2018-06-06 21:41:07 -07:00
Kemal.run
2018-06-08 14:20:50 -07:00
2018-06-06 21:41:07 -07:00
# If exit is disabled in kemal.stop
2018-06-06 23:02:25 -07:00
# For save on close
# at_exit do
# save_all(GAME_CACHE)
2018-06-12 16:28:29 -07:00
# end