Compare commits
4 Commits
6287968404
...
9d60519f5c
Author | SHA1 | Date |
---|---|---|
Danila Fedorin | 9d60519f5c | |
Danila Fedorin | e7b1144301 | |
Danila Fedorin | 75a386539e | |
Danila Fedorin | f01c891cec |
2
Makefile
2
Makefile
|
@ -1,2 +1,4 @@
|
||||||
generate-elm :
|
generate-elm :
|
||||||
cd external/GoUI && elm make Go.elm --output ../../public/js/Go.js
|
cd external/GoUI && elm make Go.elm --output ../../public/js/Go.js
|
||||||
|
generate-css :
|
||||||
|
scss public/scss/main.scss > public/css/main.css
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0d31433b96e1f7e656243a44eaed97b01f8f7d80
|
Subproject commit 54d1229c9f12ee0edf65774a3335bff70ff78a84
|
|
@ -0,0 +1,55 @@
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: "Indie Flower", serif; }
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 5em;
|
||||||
|
margin: 0px;
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Raleway", sans-serif;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: #f4f4f4; }
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
max-width: 750px;
|
||||||
|
margin: auto; }
|
||||||
|
|
||||||
|
.board {
|
||||||
|
background-color: tomato;
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: auto;
|
||||||
|
border-radius: 10px; }
|
||||||
|
|
||||||
|
.black-player .overlay:hover {
|
||||||
|
background-color: black; }
|
||||||
|
|
||||||
|
.white-player .overlay:hover {
|
||||||
|
background-color: white; }
|
||||||
|
|
||||||
|
.board-cell {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
padding: 5.55556%; }
|
||||||
|
.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.55556%; }
|
||||||
|
.board-cell.medium {
|
||||||
|
padding: 3.84615%; }
|
||||||
|
.board-cell.large {
|
||||||
|
padding: 2.63158%; }
|
||||||
|
|
||||||
|
.black-cell .overlay {
|
||||||
|
background-color: black; }
|
||||||
|
|
||||||
|
.white-cell .overlay {
|
||||||
|
background-color: white; }
|
|
@ -9504,7 +9504,18 @@ var _user$project$Go_View$renderIndex = function (_p0) {
|
||||||
_1: {ctor: '[]'}
|
_1: {ctor: '[]'}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ctor: '[]'});
|
{
|
||||||
|
ctor: '::',
|
||||||
|
_0: A2(
|
||||||
|
_elm_lang$html$Html$div,
|
||||||
|
{
|
||||||
|
ctor: '::',
|
||||||
|
_0: _elm_lang$html$Html_Attributes$class('overlay'),
|
||||||
|
_1: {ctor: '[]'}
|
||||||
|
},
|
||||||
|
{ctor: '[]'}),
|
||||||
|
_1: {ctor: '[]'}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
var _user$project$Go_View$renderBoard = F2(
|
var _user$project$Go_View$renderBoard = F2(
|
||||||
function (size, board) {
|
function (size, board) {
|
||||||
|
@ -9550,10 +9561,7 @@ var _user$project$Go_Ws$wsUrl = function (m) {
|
||||||
return A2(
|
return A2(
|
||||||
_elm_lang$core$Basics_ops['++'],
|
_elm_lang$core$Basics_ops['++'],
|
||||||
m.sessionUrl,
|
m.sessionUrl,
|
||||||
A2(
|
A2(_elm_lang$core$Basics_ops['++'], '/game/', m.sessionId));
|
||||||
_elm_lang$core$Basics_ops['++'],
|
|
||||||
'/game/',
|
|
||||||
_elm_lang$core$Basics$toString(m.sessionId)));
|
|
||||||
};
|
};
|
||||||
var _user$project$Go_Ws$sendMove = F2(
|
var _user$project$Go_Ws$sendMove = F2(
|
||||||
function (m, c) {
|
function (m, c) {
|
||||||
|
@ -9655,7 +9663,7 @@ var _user$project$Main$initDummy = {
|
||||||
_user$project$Go_Types$Model,
|
_user$project$Go_Types$Model,
|
||||||
_user$project$Go_Types$Black,
|
_user$project$Go_Types$Black,
|
||||||
'ws://localhost:3000',
|
'ws://localhost:3000',
|
||||||
1,
|
'debug',
|
||||||
9,
|
9,
|
||||||
_elm_lang$core$Maybe$Nothing,
|
_elm_lang$core$Maybe$Nothing,
|
||||||
_elm_lang$core$Maybe$Nothing,
|
_elm_lang$core$Maybe$Nothing,
|
||||||
|
@ -9698,7 +9706,7 @@ var _user$project$Main$main = _elm_lang$html$Html$programWithFlags(
|
||||||
},
|
},
|
||||||
A2(_elm_lang$core$Json_Decode$field, 'size', _elm_lang$core$Json_Decode$int));
|
A2(_elm_lang$core$Json_Decode$field, 'size', _elm_lang$core$Json_Decode$int));
|
||||||
},
|
},
|
||||||
A2(_elm_lang$core$Json_Decode$field, 'id', _elm_lang$core$Json_Decode$int));
|
A2(_elm_lang$core$Json_Decode$field, 'id', _elm_lang$core$Json_Decode$string));
|
||||||
},
|
},
|
||||||
A2(_elm_lang$core$Json_Decode$field, 'black', _elm_lang$core$Json_Decode$bool)));
|
A2(_elm_lang$core$Json_Decode$field, 'black', _elm_lang$core$Json_Decode$bool)));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
$background-grey: #f4f4f4;
|
||||||
|
|
||||||
|
@mixin board-cell($size) {
|
||||||
|
padding: 100%/$size/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: "Indie Flower", serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 5em;
|
||||||
|
margin: 0px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Raleway", sans-serif;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: $background-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
max-width: 750px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board {
|
||||||
|
background-color: tomato;
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: auto;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.black-player .overlay {
|
||||||
|
&:hover {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-player .overlay {
|
||||||
|
&:hover {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-cell {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
@include board-cell(9);
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
top: 10%;
|
||||||
|
left: 10%;
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
@include board-cell(9);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
@include board-cell(13);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
@include board-cell(19);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.black-cell {
|
||||||
|
.overlay {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-cell {
|
||||||
|
.overlay {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
119
src/Go.cr
119
src/Go.cr
|
@ -2,81 +2,30 @@ require "./Go/*"
|
||||||
require "kemal"
|
require "kemal"
|
||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
enum Color
|
|
||||||
Black
|
|
||||||
White
|
|
||||||
end
|
|
||||||
|
|
||||||
enum Size
|
|
||||||
Small = 9,
|
|
||||||
Medium = 13,
|
|
||||||
Large = 19
|
|
||||||
end
|
|
||||||
|
|
||||||
alias Board = Hash(Tuple(Int8, Int8), Color)
|
|
||||||
|
|
||||||
|
|
||||||
def cell_json(index, color, json)
|
|
||||||
json.object do
|
|
||||||
json.field "index" do
|
|
||||||
json.object do
|
|
||||||
json.field "x", index[0]
|
|
||||||
json.field "y", index[1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
json.field "color", color.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def board_json(board, json)
|
|
||||||
json.array do
|
|
||||||
board.each do |key, value|
|
|
||||||
cell_json(key, value, json)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Game
|
|
||||||
property size : Size
|
|
||||||
property board : Board
|
|
||||||
property turn : Color
|
|
||||||
property sockets : Array(HTTP::WebSocket)
|
|
||||||
|
|
||||||
def initialize(size : Size)
|
|
||||||
@size = size
|
|
||||||
@board = Board.new
|
|
||||||
@turn = Color::Black
|
|
||||||
@sockets = [] of HTTP::WebSocket
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_string
|
|
||||||
JSON.build do |json|
|
|
||||||
json.object do
|
|
||||||
json.field "turn", @turn.to_s
|
|
||||||
json.field "board" { board_json(@board, json) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(x, y, color)
|
|
||||||
@board[{x, y}] = color
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
URL = "localhost"
|
URL = "localhost"
|
||||||
GAME_CACHE = {} of Int64 => Game
|
GAME_CACHE = {} of String => Go::Game
|
||||||
|
|
||||||
def lookup_game(cache, id) : Game?
|
def query_game(db, id) : Go::Game?
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lookup_game(db, cache, id) : Go::Game?
|
||||||
|
if game = cache[id]?
|
||||||
|
return game
|
||||||
|
else
|
||||||
|
loaded_game = query_game(db, id)
|
||||||
|
cache[id] = loaded_game if loaded_game
|
||||||
|
return loaded_game
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_message(id, game, socket, message)
|
def handle_message(id, game, socket, message)
|
||||||
split_command = message.split(" ")
|
split_command = message.split(" ")
|
||||||
command = split_command[0]
|
command = split_command[0]
|
||||||
if command == "place"
|
if command == "place"
|
||||||
x = split_command[1].to_i8
|
x = split_command[1].to_i8
|
||||||
y = split_command[2].to_i8
|
y = split_command[2].to_i8
|
||||||
color = split_command[3] == "Black" ? Color::Black : Color::White
|
color = split_command[3] == "Black" ? Go::Color::Black : Go::Color::White
|
||||||
|
|
||||||
game.update(x, y, color)
|
game.update(x, y, color)
|
||||||
game.sockets.each { |socket| socket.send game.to_string }
|
game.sockets.each { |socket| socket.send game.to_string }
|
||||||
|
@ -88,41 +37,43 @@ get "/" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/game/:id" do |env|
|
get "/game/:id" do |env|
|
||||||
if game_id = env.params.url["id"].to_i64?
|
game_id = env.params.url["id"]
|
||||||
if game = (GAME_CACHE[game_id]? || lookup_game(GAME_CACHE, game_id))
|
game_password = env.params.query["password"]?
|
||||||
|
if game = lookup_game(nil, GAME_CACHE, game_id)
|
||||||
|
id = game_id
|
||||||
|
size = game.size.value
|
||||||
|
black = nil
|
||||||
|
|
||||||
|
if game_password == game.blackPass
|
||||||
black = true
|
black = true
|
||||||
id = game_id
|
elsif game_password == game.whitePass
|
||||||
size = game.size.value
|
black = false
|
||||||
render "src/Go/views/game.ecr"
|
|
||||||
else
|
|
||||||
render_404
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
black.try { |black| render "src/Go/views/game.ecr", "src/Go/views/base.ecr"} || render_404
|
||||||
else
|
else
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ws "/game/:id" do |socket, env|
|
ws "/game/:id" do |socket, env|
|
||||||
if game_id = env.params.url["id"].to_i64?
|
game_id = env.params.url["id"]
|
||||||
if game = (GAME_CACHE[game_id]? || lookup_game(GAME_CACHE, game_id))
|
if game = lookup_game(nil, GAME_CACHE, game_id)
|
||||||
socket.send game.to_string
|
socket.send game.to_string
|
||||||
game.sockets << socket
|
game.sockets << socket
|
||||||
|
|
||||||
socket.on_message do |message|
|
socket.on_message do |message|
|
||||||
game.try { |game| handle_message(game_id, game, socket, message) }
|
game.try { |game| handle_message(game_id, game, socket, message) }
|
||||||
end
|
end
|
||||||
|
|
||||||
socket.on_close do
|
socket.on_close do
|
||||||
game.try { |game| game.sockets.delete socket }
|
game.try { |game| game.sockets.delete socket }
|
||||||
end
|
|
||||||
else
|
|
||||||
render_404
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
GAME_CACHE[1_i64] = Game.new(Size::Small)
|
GAME_CACHE["debug"] = Go::Game.new(Go::Size::Small, "black", "white")
|
||||||
|
|
||||||
Kemal.run
|
Kemal.run
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
module Go
|
||||||
|
enum Color
|
||||||
|
Black
|
||||||
|
White
|
||||||
|
end
|
||||||
|
|
||||||
|
enum Size
|
||||||
|
Small = 9,
|
||||||
|
Medium = 13,
|
||||||
|
Large = 19
|
||||||
|
end
|
||||||
|
|
||||||
|
alias Board = Hash(Tuple(Int8, Int8), Color)
|
||||||
|
|
||||||
|
class Game
|
||||||
|
property size : Size
|
||||||
|
property blackPass : String
|
||||||
|
property whitePass : String
|
||||||
|
property board : Board
|
||||||
|
property turn : Color
|
||||||
|
property sockets : Array(HTTP::WebSocket)
|
||||||
|
|
||||||
|
def initialize(size : Size, @blackPass, @whitePass)
|
||||||
|
@size = size
|
||||||
|
@board = Board.new
|
||||||
|
@turn = Color::Black
|
||||||
|
@sockets = [] of HTTP::WebSocket
|
||||||
|
end
|
||||||
|
|
||||||
|
private def cell_json(index, color, json)
|
||||||
|
json.object do
|
||||||
|
json.field "index" do
|
||||||
|
json.object do
|
||||||
|
json.field "x", index[0]
|
||||||
|
json.field "y", index[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
json.field "color", color.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def board_json(board, json)
|
||||||
|
json.array do
|
||||||
|
board.each do |key, value|
|
||||||
|
cell_json(key, value, json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_string
|
||||||
|
JSON.build do |json|
|
||||||
|
json.object do
|
||||||
|
json.field "turn", @turn.to_s
|
||||||
|
json.field "board" { board_json(@board, json) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(x, y, color)
|
||||||
|
@board[{x, y}] = color
|
||||||
|
@turn = @turn == Color::Black ? Color::White : Color::Black
|
||||||
|
end
|
||||||
|
|
||||||
|
private def color_char(color)
|
||||||
|
color == Color::Black ? 'B' : 'W'
|
||||||
|
end
|
||||||
|
|
||||||
|
private def board_string(board)
|
||||||
|
String.build do |str|
|
||||||
|
(0...@size.value).each do |x|
|
||||||
|
(0...@size.value).each do |y|
|
||||||
|
color = @board[{x.to_i8, y.to_i8}]?
|
||||||
|
char = color ? color_char(color) : 'E'
|
||||||
|
str << char
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode
|
||||||
|
{ @turn.to_s, @size.value, board_string(@board) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,12 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Indie+Flower|Raleway" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<h1>Go</h1>
|
||||||
|
<%= content %>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,30 +1,11 @@
|
||||||
<html>
|
<div id="elm-root" class="<%= black ? "black" : "white" %>-player"></div>
|
||||||
<head>
|
<script src="/js/Go.js"></script>
|
||||||
<style>
|
<script>
|
||||||
.board-cell {
|
var node = document.getElementById('elm-root')
|
||||||
display: inline-block;
|
var app = Elm.Main.embed(node, {
|
||||||
background-color: #eaeaea;
|
'black' : <%= black %>,
|
||||||
padding: 20px;
|
'url' : "<%= "ws://" + URL + ":" + Kemal.config.port.to_s %>",
|
||||||
}
|
'id' : "<%= id %>",
|
||||||
.black-cell {
|
'size' : <%= size %>
|
||||||
background-color: black;
|
})
|
||||||
}
|
</script>
|
||||||
.board {
|
|
||||||
max-width: 360px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="elm-root"></div>
|
|
||||||
<script src="/js/Go.js"></script>
|
|
||||||
<script>
|
|
||||||
var node = document.getElementById('elm-root')
|
|
||||||
var app = Elm.Main.embed(node, {
|
|
||||||
'black' : <%= black %>,
|
|
||||||
'url' : "<%= "ws://" + URL + ":" + Kemal.config.port.to_s %>",
|
|
||||||
'id' : <%= id %>,
|
|
||||||
'size' : <%= size %>
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
Loading…
Reference in New Issue