Merge branch 'rewrite'
This commit is contained in:
commit
183e628e8d
37 changed files with 823 additions and 420 deletions
|
@ -3,7 +3,8 @@
|
|||
{vsn, "0.1.0"},
|
||||
{modules, [
|
||||
ggs_app,
|
||||
ggs_sup
|
||||
ggs_sup,
|
||||
ggs_dispatcher
|
||||
]},
|
||||
{registered, [ggs_sup]},
|
||||
{applications, [kernel, stdlib]},
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 5350ed21606606dbee5ecb07e974f2abb9106270
|
||||
Subproject commit 2f2785fafb0da6db75810eb6fa97d09c58257588
|
|
@ -1,12 +1,6 @@
|
|||
- background image
|
||||
- subimages for game_area:s
|
||||
- subimages for game markers (X or 0)
|
||||
- rectangle collision on game_area:s
|
||||
- background
|
||||
- redraw background then all game_area:s
|
||||
- board_state: a hashtable where key is game_area_index
|
||||
and value is either'x' 'o' or ' '
|
||||
|
||||
board contains nr_of_squares = 9
|
||||
board contains array of squares with nr_of_squares elements
|
||||
board contains x, y, width and height
|
||||
board contains a frame
|
||||
|
|
15
games/tic-tac-toe/data.py
Normal file
15
games/tic-tac-toe/data.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
def greatest_sequence(match, pattern):
|
||||
m = match
|
||||
p = pattern
|
||||
size = 0
|
||||
max_size = 0
|
||||
|
||||
for p in pattern:
|
||||
if m == p:
|
||||
size += 1
|
||||
else:
|
||||
if size > max_size:
|
||||
max_size = size
|
||||
size = 0
|
||||
|
||||
return max_size
|
BIN
games/tic-tac-toe/data.pyc
Normal file
BIN
games/tic-tac-toe/data.pyc
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 187 B |
15
games/tic-tac-toe/player.py
Normal file
15
games/tic-tac-toe/player.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from pygame.mouse import get_pos
|
||||
from point import Point
|
||||
|
||||
class Player(object):
|
||||
def __init__(self, id, shape, board):
|
||||
self.shape = shape
|
||||
self.board = board
|
||||
self.id = id
|
||||
|
||||
|
||||
def turn(self):
|
||||
#Ask mouse for position
|
||||
board.make_turn(Point(get_pos()[0],get_pos()[1]))
|
||||
|
||||
|
23
games/tic-tac-toe/server.py
Normal file
23
games/tic-tac-toe/server.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
#server.py
|
||||
import json
|
||||
from socket import socket, AF_INET, SOCK_STREAM
|
||||
|
||||
|
||||
class server(object):
|
||||
def __init__(self, port=None):
|
||||
self.port = port
|
||||
self.world = GGS.init()
|
||||
self.socket = socket(AF_INET, SOCK_STREAM)
|
||||
self.socket.connect(("www.???.com", 80))
|
||||
|
||||
def turn(self, id, index):
|
||||
rows = sqrt(board.nr_of_rectangles)
|
||||
x = int(index / rows)
|
||||
y = int(index % rows)
|
||||
|
||||
json.dumps({"x": x, "y": y}
|
||||
world.callCommand("tictactoe", "set", json.dumps({"x": x, "y": y}))
|
||||
|
||||
sent = 0
|
||||
length = len(
|
||||
while sent
|
14
games/tic-tac-toe/test_data.py
Normal file
14
games/tic-tac-toe/test_data.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
import unittest
|
||||
import data
|
||||
|
||||
|
||||
class TestData(unittest.TestCase):
|
||||
def setUp(self):
|
||||
array = [0,1,1,1,3,4,5,2,2,3,3,3,3,3,33,4,2,2]
|
||||
|
||||
self.assertTrue(data.greatest_sequence(array, 3) == 5)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
|
@ -4,6 +4,7 @@ from point import Point
|
|||
from pygame.image import load
|
||||
from pygame.rect import Rect
|
||||
from pygame import Surface
|
||||
from data import greatest_sequence
|
||||
|
||||
#inherits Board.
|
||||
#Used for displaying the board on the screen and interact with it
|
||||
|
@ -47,4 +48,14 @@ class TicTacToeBoard(Board):
|
|||
game_rectangle.state = 'o'
|
||||
self.players_turn = (self.players_turn + 1) % 2
|
||||
|
||||
"""
|
||||
def turn(self, mouse_point):
|
||||
if player.id != players_turn:
|
||||
print "Other players turn"
|
||||
|
||||
else:
|
||||
for game_rectangle in self.game_rectangles:
|
||||
if (mouse_point.inside(game_rectangle) and
|
||||
game_rectangle.state == ' '):
|
||||
server.turn(player.id, game_rectangle.index)
|
||||
"""
|
||||
|
|
Binary file not shown.
BIN
mnesia/.gamedb.erl.swp
Normal file
BIN
mnesia/.gamedb.erl.swp
Normal file
Binary file not shown.
|
@ -1,26 +1,45 @@
|
|||
%Test Mnesia
|
||||
%%%%----------------------------------------------------
|
||||
%%% @author Mattias Pettersson <mattiaspgames@gmail.com>
|
||||
%%% @copyright 2011 Mattias Pettersson
|
||||
%%% @doc Database for runtime game variable storage.
|
||||
%%% @end
|
||||
|
||||
Test Mnesia
|
||||
-module(gamedb).
|
||||
-import(mnesia).
|
||||
-export([init/0,insert_player/1,example_player/0,read_player/1,test_player/0]).
|
||||
-include("gamedb.hrl").
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% Creation
|
||||
%%-----------------------------------------------------
|
||||
init() ->
|
||||
mnesia:create_table(player, [{attributes, record_info(fields, player)}]).
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% Test
|
||||
%%-----------------------------------------------------
|
||||
test_player() ->
|
||||
insert_player(example_player()),
|
||||
read_player(0001).
|
||||
|
||||
example_player() ->
|
||||
#player{id = 0001,
|
||||
name = "Tux"}.
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% Insertions
|
||||
%%-----------------------------------------------------
|
||||
insert_player(Player) ->
|
||||
Fun = fun() ->
|
||||
mnesia:write(Player)
|
||||
end,
|
||||
mnesia:transaction(Fun).
|
||||
|
||||
example_player() ->
|
||||
#player{id = 0001,
|
||||
name = "Tux"}.
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% Querries
|
||||
%%-----------------------------------------------------
|
||||
read_player(Player_Key) ->
|
||||
Fun = fun() ->
|
||||
[P] = mnesia:read(player, Player_Key),
|
|
@ -11,7 +11,7 @@ s.connect((HOST, PORT))
|
|||
|
||||
print "Saying hello to server"
|
||||
s.send(
|
||||
"Command: hello\n\
|
||||
"Server-Command: hello\n\
|
||||
Content-Type: text\n\
|
||||
Content-Length: 0\n\
|
||||
\n\
|
||||
|
@ -27,7 +27,7 @@ print "Data: ", ' '.join(data.split(" ")[1:])
|
|||
print "Defining a function called myFun"
|
||||
s.send(
|
||||
"Token: %s\n\
|
||||
Command: define\n\
|
||||
Server-Command: define\n\
|
||||
Content-Type: text\n\
|
||||
Content-Length: 49\n\
|
||||
\n\
|
||||
|
@ -42,7 +42,7 @@ print "Data: ", ' '.join(data.split(" ")[1:])
|
|||
print "Calling myFun"
|
||||
s.send(
|
||||
"Token: %s\n\
|
||||
Command: call\n\
|
||||
Server-Command: call\n\
|
||||
Content-Type: text\n\
|
||||
Content-Length: 6\n\
|
||||
\n\
|
||||
|
@ -64,7 +64,7 @@ s.connect((HOST, PORT))
|
|||
print "Calling myFun"
|
||||
s.send(
|
||||
"Token: %s\n\
|
||||
Command: call\n\
|
||||
Server-Command: call\n\
|
||||
Content-Type: text\n\
|
||||
Content-Length: 6\n\
|
||||
\n\
|
||||
|
|
BIN
src/.ggs_connection.erl.swp
Normal file
BIN
src/.ggs_connection.erl.swp
Normal file
Binary file not shown.
|
@ -1,41 +0,0 @@
|
|||
-module(ggs_backup).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0 ]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-record(state, {port, lsock, client_vm_map = []}).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
init([]) ->
|
||||
{ok, #state{port = -1, lsock = -1, client_vm_map = -1}, 0}.
|
||||
|
||||
handle_call(get_backup, _From, State) ->
|
||||
BackedUpState = case State of
|
||||
#state{port = -1, lsock = -1, client_vm_map = -1} ->
|
||||
not_initialized;
|
||||
Other ->
|
||||
Other
|
||||
end,
|
||||
{reply, {backup_state, BackedUpState}, State}.
|
||||
|
||||
handle_cast({set_backup, NewState}, _State) ->
|
||||
{noreply, NewState}.
|
||||
|
||||
handle_info(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
100
src/ggs_coordinator.erl
Normal file
100
src/ggs_coordinator.erl
Normal file
|
@ -0,0 +1,100 @@
|
|||
-module(ggs_coordinator).
|
||||
|
||||
%% API Exports
|
||||
-export([start_link/0, stop/1, join_table/1, create_table/1, join_lobby/0,
|
||||
respawn_player/2, respawn_table/1, remove_player/2]).
|
||||
|
||||
%% gen_server callback exports
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
|
||||
code_change/3]).
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-record(co_state,
|
||||
{players = [], % List of all player processes
|
||||
player_table_map = [], % Players <-> Table map
|
||||
table_state_map = [],
|
||||
tables = []}). % Table <-> Table state map
|
||||
|
||||
%% @doc This module act as "the man in the middle".
|
||||
%% Creates the starting connection between table and players.
|
||||
|
||||
%% @doc Starts the coordinator process.
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
%% @doc Terminates the coordinator process.
|
||||
stop(Reason) ->
|
||||
gen_server:cast(ggs_coordinator, {stop, Reason}).
|
||||
|
||||
%% @doc Joins table with specified token, returns {error, no_such_table}
|
||||
%% if the specified table token does not exist
|
||||
join_table(Token) ->
|
||||
gen_server:call(ggs_coordinator, {join_table, Token}).
|
||||
|
||||
%% @doc Create a new table, return {error, Reason} or {ok, TableToken}
|
||||
create_table(Params) ->
|
||||
gen_server:call(ggs_coordinator, {create_table, Params}).
|
||||
|
||||
%% @doc This is the first function run by a newly created players.
|
||||
%% Generates a unique token that we use to identify the player.
|
||||
join_lobby() ->
|
||||
gen_server:call(ggs_coordinator, join_lobby).
|
||||
|
||||
%% @doc Act as a supervisor to player and respawns player when it gets bad data.
|
||||
respawn_player(_Player, _Socket) ->
|
||||
ggs_logger:not_implemented().
|
||||
|
||||
%% @doc Act as a supervisor to table and respawns table when it gets bad data.
|
||||
respawn_table(_Token) ->
|
||||
ggs_logger:not_implemented().
|
||||
|
||||
%% @doc Removes a player from coordinator.
|
||||
remove_player(_From, _Player) ->
|
||||
ggs_logger:not_implemented().
|
||||
|
||||
%% gen_server callbacks
|
||||
|
||||
init([]) ->
|
||||
{ok, #co_state{}}.
|
||||
|
||||
handle_call(join_lobby, _From, State) ->
|
||||
Token = helpers:get_new_token(),
|
||||
{reply, {ok, Token}, State};
|
||||
|
||||
handle_call({join_table, Table}, From, State) ->
|
||||
{FromPlayer, _Ref} = From,
|
||||
Tables = State#co_state.tables,
|
||||
case lists:keyfind(Table, 1, Tables) of
|
||||
{TableID, TablePID} ->
|
||||
ggs_table:add_player(TablePID, FromPlayer),
|
||||
{reply, {ok, TablePID}, State};
|
||||
false ->
|
||||
{reply, {error, no_such_table}, State}
|
||||
end;
|
||||
|
||||
handle_call({create_table, {force, TableID}}, From, State) ->
|
||||
TableIDMap = State#co_state.player_table_map,
|
||||
Tables = State#co_state.tables,
|
||||
NewTableProc = ggs_table:start_link(),
|
||||
{reply, {ok, TableID}, State#co_state{
|
||||
player_table_map = [{From, TableID} | TableIDMap],
|
||||
tables = [{TableID, NewTableProc} | Tables]
|
||||
}};
|
||||
|
||||
handle_call(_Message, _From, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_cast({stop, Reason}, State) ->
|
||||
{stop, normal, state};
|
||||
|
||||
handle_cast(_Message, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Message, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(normal, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
68
src/ggs_dispatcher.erl
Normal file
68
src/ggs_dispatcher.erl
Normal file
|
@ -0,0 +1,68 @@
|
|||
-module(ggs_dispatcher).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API Exports
|
||||
-export([start_link/1, stop/1]).
|
||||
|
||||
%% gen_server callback exports
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
|
||||
code_change/3]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
|
||||
%% @doc This module is the entry-point for clients connecting to GGS. This is
|
||||
%% the module responsible for:
|
||||
%% * Greeting a connecting client, and associating a socket for it
|
||||
%% * Spawning a ggs_player for the connecting client, passing the socket
|
||||
|
||||
%% @doc Starts a new dispatcher with the specified port. Registers this
|
||||
%% dispatcher under the name "ggs_dispatcher". The pid of the dispatcher
|
||||
%% is returned.
|
||||
%% @spec start_link(Port) -> Pid
|
||||
%% Port = Integer
|
||||
%% Pid = #<Pid>
|
||||
start_link(Port) ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []).
|
||||
|
||||
%% @doc Stops the dispatcher with the specified reason.
|
||||
%% @spec stop(Reason) -> ok.
|
||||
%% Reason = String
|
||||
stop(_Reason) -> ggs_logger:not_implemented().
|
||||
|
||||
%% gen_server callbacks
|
||||
|
||||
%% @doc Initiate the dispatcher. This is called from gen_server
|
||||
init([Port]) ->
|
||||
{ok, LSock} = gen_tcp:listen(Port, [{active, true},
|
||||
{reuseaddr, true}]),
|
||||
{ok, LSock, 0}.
|
||||
|
||||
handle_call(_Message, _From, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_cast(_Message, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({tcp, _Socket, RawData}, State) ->
|
||||
io:format("Got connect request!~n"),
|
||||
{noreply, State};
|
||||
|
||||
handle_info({tcp_closed, Socket}, State) ->
|
||||
gen_tcp:close(Socket),
|
||||
{stop, "Client closed socket", State};
|
||||
|
||||
%% @doc This is our function for accepting connections. When a client connects,
|
||||
%% it will immediately time out due to timing settings set in init and here,
|
||||
%% and when it does, we accept the connection.
|
||||
handle_info(timeout, LSock) ->
|
||||
{ok, Sock} = gen_tcp:accept(LSock),
|
||||
spawn(ggs_player, start_link, [Sock]),
|
||||
{noreply, LSock, 0}.
|
||||
|
||||
terminate(normal, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
130
src/ggs_gamevm.erl
Normal file
130
src/ggs_gamevm.erl
Normal file
|
@ -0,0 +1,130 @@
|
|||
%% @doc This module is responsible for running the game VM:s. You can issue
|
||||
%% commands to a vm using this module.
|
||||
|
||||
-module(ggs_gamevm).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-record(state, { port, table } ).
|
||||
|
||||
%% API
|
||||
-export([start_link/1, define/2, user_command/4, stop/1, call_js/2]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
|
||||
%% ----------------------------------------------------------------------
|
||||
% API implementation
|
||||
|
||||
%% @doc Create a new VM process. The process ID is returned and can be used
|
||||
%% with for example the define method of this module.
|
||||
start_link(Table) ->
|
||||
erlang_js:start(), %% @TODO: should only be done once
|
||||
{ok, Pid} = gen_server:start_link(?MODULE, [Table], []),
|
||||
Pid.
|
||||
|
||||
%% @doc Define some new code on the specified VM, returns the atom ok.
|
||||
define(GameVM, SourceCode) ->
|
||||
gen_server:cast(GameVM, {define, SourceCode}).
|
||||
|
||||
%% @doc Execute a user command on the specified VM. This function is
|
||||
%% asynchronous, and returns ok.
|
||||
%% @spec user_command(GameVM, User, Command, Args) -> ok
|
||||
%% GameVM = process IS of VM
|
||||
%% Player = the player running the command
|
||||
%% Command = a game command to run
|
||||
%% Args = arguments for the Command parameter
|
||||
user_command(GameVM, Player, Command, Args) ->
|
||||
gen_server:cast(GameVM, {user_command, Player, Command, Args}).
|
||||
|
||||
%% @private
|
||||
% only for tests
|
||||
call_js(GameVM, SourceCode) ->
|
||||
gen_server:call(GameVM, {eval, SourceCode}).
|
||||
|
||||
% @doc stops the gamevm process
|
||||
stop(GameVM) ->
|
||||
gen_server:cast(GameVM, stop).
|
||||
|
||||
|
||||
%% ----------------------------------------------------------------------
|
||||
|
||||
%% @private
|
||||
init([Table]) ->
|
||||
process_flag(trap_exit, true),
|
||||
{ok, Port} = js_driver:new(),
|
||||
%% @TODO: add here default JS API instead
|
||||
{ok, #state { port = Port, table = Table }}.
|
||||
|
||||
%% private
|
||||
% only needed for the tests
|
||||
handle_call({eval, SourceCode}, _From, #state { port = Port } = State) ->
|
||||
{ok, Ret} = js:eval(Port, list_to_binary(SourceCode)),
|
||||
{reply, Ret, State}.
|
||||
|
||||
%% @private
|
||||
handle_cast({define, SourceCode}, #state { port = Port } = State) ->
|
||||
ok = js:define(Port, list_to_binary(SourceCode)),
|
||||
{noreply, State};
|
||||
handle_cast({user_command, Player, Command, Args}, #state { port = Port } = State) ->
|
||||
Arguments = string:concat("'", string:concat(
|
||||
string:join([js_escape(Player), js_escape(Command), js_escape(Args)], "','"), "'")),
|
||||
Js = list_to_binary(string:concat(string:concat("userCommand(", Arguments), ");")),
|
||||
js_driver:define_js(Port, Js),
|
||||
{noreply, State};
|
||||
handle_cast(stop, State) ->
|
||||
{stop, normal, State};
|
||||
handle_cast(Msg, S) ->
|
||||
error_logger:error_report([unknown_msg, Msg]),
|
||||
{noreply, S}.
|
||||
|
||||
%% @private
|
||||
handle_info(Msg, S) ->
|
||||
error_logger:error_report([unknown_msg, Msg]),
|
||||
{noreply, S}.
|
||||
|
||||
%% @private
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%% @private
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
js_escape(S) ->
|
||||
lists:flatmap(fun($\') -> [$\\, $\']; (X) -> [X] end, S).
|
||||
|
||||
%% ----------------------------------------------------------------------
|
||||
% Tests
|
||||
|
||||
start_link_test() ->
|
||||
erlang_js:start(), %% @TODO: should only be done once
|
||||
GameVM = start_link(test_table),
|
||||
?assertNot(GameVM =:= undefined).
|
||||
|
||||
define_test() ->
|
||||
GameVM = start_link(test_table),
|
||||
define(GameVM, "function hello(test) { return test; }"),
|
||||
?assertMatch(<<"jeena">>, gen_server:call(GameVM, {eval, "hello('jeena')"})).
|
||||
|
||||
stop_test() ->
|
||||
GameVM = start_link(test_table),
|
||||
ok = stop(GameVM).
|
||||
|
||||
user_command_test() ->
|
||||
GameVM = start_link(test_table),
|
||||
define(GameVM, "var t = '';\nfunction userCommand(user, command, args) { t = user + command + args; }\n"),
|
||||
user_command(GameVM, "'jeena", "thecommand", "theargs'"),
|
||||
?assertMatch(<<"'jeenathecommandtheargs'">>, gen_server:call(GameVM, {eval, "t;"})).
|
||||
|
||||
js_erlang_test() ->
|
||||
GameVM = start_link(test_table),
|
||||
define(GameVM, "var t = '';\nfunction userCommand(user, command, args) { t = callErlang('erlang time') + ''; }\n"),
|
||||
user_command(GameVM, "", "", ""),
|
||||
{A, B, C} = erlang:time(),
|
||||
T = "{" ++ integer_to_list(A) ++ ", " ++ integer_to_list(B) ++ ", " ++ integer_to_list(C) ++ "}",
|
||||
?assertMatch(T, binary_to_list(gen_server:call(GameVM, {eval, "t;"}))).
|
||||
|
41
src/ggs_gamevm_e.erl
Normal file
41
src/ggs_gamevm_e.erl
Normal file
|
@ -0,0 +1,41 @@
|
|||
-module(ggs_gamevm_e).
|
||||
-export([start_link/1, define/2, user_command/4]).
|
||||
%% @doc This module is responsible for running the game VM:s. You can issue
|
||||
%% commands to a vm using this module.
|
||||
|
||||
%% @doc Create a new VM process. The process ID is returned and can be used
|
||||
%% with for example the define method of this module.
|
||||
start_link(Table) ->
|
||||
PortPid = spawn( fun() ->
|
||||
loop(Table)
|
||||
end ),
|
||||
PortPid.
|
||||
|
||||
%% @doc Define some new code on the specified VM, returns the atom ok.
|
||||
define(GameVM, SourceCode) ->
|
||||
GameVM ! {define,SourceCode},
|
||||
ok.
|
||||
|
||||
%% @doc Execute a user command on the specified VM. This function is
|
||||
%% asynchronous, and returns ok.
|
||||
%% @spec user_command(GameVM, User, Command, Args) -> ok
|
||||
%% GameVM = process IS of VM
|
||||
%% Player = the player running the command
|
||||
%% Command = a game command to run
|
||||
%% Args = arguments for the Command parameter
|
||||
user_command(GameVM, Player, Command, Args) ->
|
||||
Ref = make_ref(),
|
||||
GameVM ! {user_command, Player, Command, Args, self(), Ref},
|
||||
ok.
|
||||
|
||||
%% Helper functions
|
||||
|
||||
loop(Table) ->
|
||||
receive
|
||||
{define, SourceCode} ->
|
||||
loop(Table);
|
||||
{user_command, _User, Command, _Args, _From, _Ref} ->
|
||||
io:format("GameVM received a message~n"),
|
||||
ggs_table:notify_all_players(Table, Command),
|
||||
loop(Table)
|
||||
end.
|
8
src/ggs_logger.erl
Normal file
8
src/ggs_logger.erl
Normal file
|
@ -0,0 +1,8 @@
|
|||
-module(ggs_logger).
|
||||
-export([not_implemented/0, log/2]).
|
||||
|
||||
not_implemented() ->
|
||||
exit(not_implemented).
|
||||
|
||||
log(Format, Args) ->
|
||||
error_logger:info_msg(Format, Args).
|
|
@ -1,68 +0,0 @@
|
|||
-module(ggs_mnesia_controller_server).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0,
|
||||
stop/0
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%%====================================================
|
||||
%%% API
|
||||
%%%====================================================
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% @doc Starts the server
|
||||
%% @end
|
||||
%%-----------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% @doc Stops the server.
|
||||
%% @spec stop() -> ok
|
||||
%% @end
|
||||
%%-----------------------------------------------------
|
||||
stop() ->
|
||||
gen_server:cast(?SERVER, stop).
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%-----------------------------------------------------
|
||||
|
||||
init([]) ->
|
||||
mnesia:create_schema([node()]),
|
||||
mnesia:start(),
|
||||
{ok, {}, 0}.
|
||||
|
||||
handle_cast(a, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
% Request a value from the Mnesia database
|
||||
handle_call({getValue, _Key},_From,State) ->
|
||||
{reply,value_of_key_requested_goes_here, State};
|
||||
|
||||
% Set a value in the Mnesia database
|
||||
handle_call({setValue, _Key, Value},_From,State) ->
|
||||
{reply,value_set_or_updated, State}.
|
||||
|
||||
handle_info(timeout, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% Internal functions
|
||||
%%-----------------------------------------------------
|
66
src/ggs_player.erl
Normal file
66
src/ggs_player.erl
Normal file
|
@ -0,0 +1,66 @@
|
|||
-module(ggs_player).
|
||||
-export([start_link/1, notify/3, get_token/1, stop/2]).
|
||||
-record(pl_state,
|
||||
{token, % Player's token
|
||||
socket, % Player's socket
|
||||
table}). % Player's table
|
||||
|
||||
%% @doc This module handles communication between a player and GGS. This module is
|
||||
%%responsible for:
|
||||
%% * The storage of the player socket, player token and a table token.
|
||||
%% * Ability to fetch a player token.
|
||||
%% * Forwarding messages from players to the game
|
||||
%% * Remove a player from GGS
|
||||
|
||||
%% @doc Spawns a process representing the player in GGS. Takes the player socket as
|
||||
%% an argument for storage and later usage. Creates a unique player token
|
||||
%% identifying the player.
|
||||
%% @spec start_link(Socket::socket()) -> {ok, Pid} | {error, Reason}
|
||||
start_link(Socket) ->
|
||||
% The socket is in 'active' mode, and that means we are pushed any data
|
||||
% that arrives on it, we do not need to recv() manually. Since the socket
|
||||
% was opened in our parent process, we need to change the owner of it to
|
||||
% us, otherwise these messages end up in our parent.
|
||||
erlang:port_connect(Socket, self()),
|
||||
{ok, Token} = ggs_coordinator:join_lobby(),
|
||||
TableStatus = ggs_coordinator:join_table(1337),
|
||||
case TableStatus of
|
||||
{ok, Table} ->
|
||||
loop(#pl_state{socket = Socket, token = Token, table = Table});
|
||||
{error, no_such_table} ->
|
||||
ggs_coordinator:create_table({force, 1337}),
|
||||
{ok, Table} = ggs_coordinator:join_table(1337),
|
||||
loop(#pl_state{socket = Socket, token = Token, table = Table})
|
||||
end.
|
||||
|
||||
%% @doc Handles incoming messages from the GGS and forwards them through the player
|
||||
%% socket to the player.
|
||||
%% @spec notify(Player::Pid(), From::Pid(),
|
||||
%% {Command::String(), Message::string()}) -> ok
|
||||
notify(Player, From, Message) ->
|
||||
Player ! {notify, From, Message}.
|
||||
|
||||
%% @doc Get the player token uniquely representing the player.
|
||||
%% @spec get_token() -> string()
|
||||
get_token(_Player) ->
|
||||
ggs_logger:not_implemented().
|
||||
|
||||
%% @doc Properly terminates the player process. The player token will be lost
|
||||
%% together with the table token. It should also close the player socket and the
|
||||
%% process should return in the end.
|
||||
%% @spec stop(Table::pid()) -> Reason::string()
|
||||
stop(_Player,_Table) ->
|
||||
ggs_logger:not_implemented().
|
||||
|
||||
%% Internals
|
||||
|
||||
loop(#pl_state{token = Token, socket = Socket, table = Table} = State) ->
|
||||
receive
|
||||
{tcp, Socket, Data} -> % Just echo for now..
|
||||
io:format("Notifying table..~n"),
|
||||
ggs_table:notify_game(Table, Token, Data),
|
||||
loop(State);
|
||||
{notify, From, Message} ->
|
||||
gen_tcp:send(Socket, Message),
|
||||
loop(State)
|
||||
end.
|
|
@ -1,39 +0,0 @@
|
|||
-module(ggs_protocol).
|
||||
-export([parse/1]).
|
||||
|
||||
parse(Data) ->
|
||||
Message =string:tokens(Data, "\n"),
|
||||
% Turn "A: B" pairs into "{A, B}" tuples, for searching.
|
||||
MsgKV = lists:map((fun(Str) ->
|
||||
list_to_tuple(string:tokens(Str, ": ")) end
|
||||
), Message),
|
||||
% Hacky way to build a tuple, filter out not_found later on
|
||||
Processed = {
|
||||
case lists:keysearch("Command", 1, MsgKV) of
|
||||
{value,{_, "define"}} ->
|
||||
define;
|
||||
{value,{_, "call"}} ->
|
||||
call;
|
||||
{value,{_, "hello"}} ->
|
||||
hello;
|
||||
false ->
|
||||
not_found
|
||||
end,
|
||||
case lists:keysearch("Token", 1, MsgKV) of
|
||||
{value,{_, Value}} ->
|
||||
Value;
|
||||
false ->
|
||||
not_found
|
||||
end,
|
||||
case lists:keysearch("Content-Length", 1, MsgKV) of
|
||||
{value,{_, Value}} ->
|
||||
{Length, _} = string:to_integer(Value),
|
||||
[_|Cont] = re:split(Data, "\n\n",[{return,list}]),
|
||||
Content = string:join(Cont, "\n\n"),
|
||||
Payload = string:substr(Content,1,Length),
|
||||
Payload;
|
||||
false ->
|
||||
not_found
|
||||
end
|
||||
},
|
||||
gen_server:cast(ggs_server, Processed).
|
|
@ -1,156 +0,0 @@
|
|||
%%%----------------------------------------------------
|
||||
%%% @author Jonatan Pålsson <Jonatan.p@gmail.com>
|
||||
%%% @copyright 2010 Jonatan Pålsson
|
||||
%%% @doc RPC over TCP server
|
||||
%%% @end
|
||||
%%%----------------------------------------------------
|
||||
|
||||
-module(ggs_server).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/1,
|
||||
start_link/0,
|
||||
stop/0
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(DEFAULT_PORT, 1055).
|
||||
|
||||
-record(state, {port, lsock, client_vm_map = []}).
|
||||
|
||||
%%%====================================================
|
||||
%%% API
|
||||
%%%====================================================
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% @doc Starts the server
|
||||
%% @end
|
||||
%%-----------------------------------------------------
|
||||
start_link(Port) ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []).
|
||||
|
||||
start_link() ->
|
||||
start_link(?DEFAULT_PORT).
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% @doc Stops the server.
|
||||
%% @spec stop() -> ok
|
||||
%% @end
|
||||
%%-----------------------------------------------------
|
||||
stop() ->
|
||||
gen_server:cast(?SERVER, stop).
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%-----------------------------------------------------
|
||||
|
||||
init([Port]) ->
|
||||
case gen_server:call(ggs_backup, get_backup) of
|
||||
{backup_state, not_initialized} ->
|
||||
{ok, LSock} = gen_tcp:listen(Port, [{active, true},
|
||||
{reuseaddr, true}]),
|
||||
{ok, #state{port = Port, lsock = LSock}, 0};
|
||||
{backup_state, State} ->
|
||||
{ok, LSock} = gen_tcp:listen(Port, [{active, true},
|
||||
{reuseaddr, true}]),
|
||||
{ok, State#state{lsock = LSock}, 0}
|
||||
end.
|
||||
|
||||
handle_call({backup_state, OldState}, _From, State) ->
|
||||
io:format("Received old state from backup~n"),
|
||||
{noreply, OldState}.
|
||||
|
||||
|
||||
handle_info({tcp, Socket, RawData}, State) ->
|
||||
ggs_protocol:parse(RawData),
|
||||
{noreply, State#state{lsock = Socket}};
|
||||
|
||||
handle_info({tcp_closed, Socket}, State) ->
|
||||
gen_tcp:close(Socket),
|
||||
{stop, "Client closed socket", State};
|
||||
|
||||
handle_info(timeout, #state{lsock = LSock} = State) ->
|
||||
{ok, _Sock} = gen_tcp:accept(LSock),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(Other, State) ->
|
||||
erlang:display(Other).
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%-----------------------------------------------------
|
||||
%% Internal functions
|
||||
%%-----------------------------------------------------
|
||||
handle_cast(stop, State) ->
|
||||
{stop, normal, State};
|
||||
|
||||
% Handle javascript defines
|
||||
handle_cast({define, Token, Payload}, State) ->
|
||||
JSVM = getJSVM(Token, State),
|
||||
%js_runner:define(JSVM, Payload),
|
||||
JSVM!{define,self(),Payload},
|
||||
send(State#state.lsock, Token, "Okay, defined that for you!"),
|
||||
{noreply, State};
|
||||
|
||||
% Handle javascript calls
|
||||
handle_cast({call, Token, Payload}, State) ->
|
||||
io:format("test1~n"),
|
||||
io:format("Got call request: ~p~n", [Payload]),
|
||||
io:format("test2~n"),
|
||||
JSVM = getJSVM(Token, State),
|
||||
JSVM!{get_port, self()},
|
||||
receive
|
||||
{ok, Port} -> erlang:display(erlang:port_info(Port)),
|
||||
io:format("test1~n")
|
||||
end,
|
||||
%erlang:display(erlang:port_info(Port)),
|
||||
%{ok, Ret} = js_runner:call(JSVM, Payload, []),
|
||||
JSVM!{call, self(), Payload, []},
|
||||
receive
|
||||
{ok, Ret} ->
|
||||
send(State#state.lsock, Token, "JS says:", binary_to_list(Ret)),
|
||||
{noreply, State}
|
||||
end;
|
||||
% Set the new state to the reference generated, and JSVM associated
|
||||
handle_cast({hello, _, _}, State) ->
|
||||
JSVM = js_runner:boot(),
|
||||
Client = getRef(),
|
||||
send(State#state.lsock, Client, "This is your refID"),
|
||||
OldMap = State#state.client_vm_map,
|
||||
JSVM!{get_port, self()},
|
||||
receive
|
||||
{ok, Port} -> NewState = State#state{client_vm_map = OldMap ++ [{Client, Port}]},
|
||||
gen_server:cast(ggs_backup, {set_backup, NewState}),
|
||||
{noreply, NewState}
|
||||
end.
|
||||
%%-----------------------------------------------------
|
||||
%% Helpers
|
||||
%%-----------------------------------------------------
|
||||
getRef() ->
|
||||
%{A1,A2,A3} = now(),
|
||||
%#random:seed(A1, A2, A3),
|
||||
%random:uniform(1000).
|
||||
string:strip(os:cmd("uuidgen"), right, $\n ).
|
||||
|
||||
getJSVM(RefID, State) ->
|
||||
VMs = State#state.client_vm_map,
|
||||
erlang:display(RefID),
|
||||
erlang:display(VMs),
|
||||
{value, {_,VM}} = lists:keysearch(RefID, 1, VMs),
|
||||
VM.
|
||||
|
||||
send(Socket, RefID, String) ->
|
||||
gen_tcp:send(Socket, string:join([RefID,String,"\n"], " ")).
|
||||
|
||||
send(Socket, RefID, String1, String2) ->
|
||||
gen_tcp:send(Socket, string:join([RefID,String1, String2,"\n"], " ")).
|
|
@ -1,48 +0,0 @@
|
|||
-module(ggs_server_sup).
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start/1, start_link/1]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
start(Port) ->
|
||||
[FirstArg] = Port,
|
||||
{IntPort, _} = string:to_integer(FirstArg),
|
||||
start_link(IntPort).
|
||||
|
||||
start_link(Port) ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, [Port]).
|
||||
|
||||
init([Port]) ->
|
||||
GGSServer = {ggs_server,
|
||||
{ggs_server, start_link, [Port]},
|
||||
permanent,
|
||||
2000,
|
||||
worker,
|
||||
[ggs_server]
|
||||
},
|
||||
Backup = {ggs_backup,
|
||||
{ggs_backup, start_link, []},
|
||||
permanent,
|
||||
2000,
|
||||
worker,
|
||||
[ggs_backup]
|
||||
},
|
||||
MnesiaServer = {ggs_mnesia_controller_server,
|
||||
{ggs_mnesia_controller_server, start_link, []},
|
||||
permanent,
|
||||
2000,
|
||||
worker,
|
||||
[ggs_mnesia_controller_server]
|
||||
},
|
||||
Children = [MnesiaServer, Backup, GGSServer],
|
||||
|
||||
RestartStrategy = { one_for_one, % Restart only crashing child
|
||||
10, % Allow ten crashes per..
|
||||
1 % 1 second, then crash supervisor.
|
||||
},
|
||||
{ok, {RestartStrategy, Children}}.
|
||||
|
|
@ -2,29 +2,31 @@
|
|||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start/1, start_link/1]).
|
||||
-export([start_link/1]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
start(Port) ->
|
||||
[FirstArg] = Port,
|
||||
{IntPort, _} = string:to_integer(FirstArg),
|
||||
start_link(IntPort).
|
||||
|
||||
start_link(Port) ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, [Port]).
|
||||
|
||||
init([Port]) ->
|
||||
Server = {ggs_server_sup,
|
||||
{ggs_server_sup, start_link, [Port]},
|
||||
Dispatcher = {ggs_dispatcher,
|
||||
{ggs_dispatcher, start_link, [Port]},
|
||||
permanent,
|
||||
2000,
|
||||
worker,
|
||||
[ggs_server_sup]
|
||||
[ggs_dispatcher]
|
||||
},
|
||||
Children = [Server],
|
||||
Coordinator = {ggs_coordinator,
|
||||
{ggs_coordinator, start_link, []},
|
||||
permanent,
|
||||
2000,
|
||||
worker,
|
||||
[ggs_coordinator]
|
||||
},
|
||||
Children = [Dispatcher, Coordinator],
|
||||
|
||||
RestartStrategy = { one_for_one, % Restart only crashing child
|
||||
10, % Allow ten crashes per..
|
||||
|
|
199
src/ggs_table.erl
Normal file
199
src/ggs_table.erl
Normal file
|
@ -0,0 +1,199 @@
|
|||
%% @doc This module represents a table with players
|
||||
|
||||
-module(ggs_table).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3, notify_all_players/2, notify_game/3,
|
||||
add_player/2]).
|
||||
|
||||
-record(state, { players, game_vm } ).
|
||||
|
||||
%% API
|
||||
-export([start_link/0,
|
||||
add_player/2,
|
||||
remove_player/2,
|
||||
stop/1,
|
||||
notify/3]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%% ----------------------------------------------------------------------
|
||||
% API implementation
|
||||
|
||||
% @doc returns a new table
|
||||
start_link() ->
|
||||
{ok, Pid} = gen_server:start_link(?MODULE, [], []),
|
||||
|
||||
%% @private
|
||||
call(Pid, Msg) ->
|
||||
gen_server:call(Pid, Msg, infinity).
|
||||
|
||||
% @doc adds a player to a table
|
||||
add_player(Table, Player) ->
|
||||
call(Table, {add_player, Player}).
|
||||
|
||||
% @doc removes player form a table
|
||||
remove_player(Table, Player) ->
|
||||
call(Table, {remove_player, Player}).
|
||||
|
||||
% @doc stops the table process
|
||||
stop(Table) ->
|
||||
gen_server:cast(Table, stop).
|
||||
|
||||
% @doc notifies the table with a message from a player
|
||||
notify(Table, Player, Message) ->
|
||||
gen_server:cast(Table, {notify, Player, Message}).
|
||||
|
||||
notify_all_players(Table, Message) ->
|
||||
gen_server:cast(Table, {notify_all_players, Message}).
|
||||
|
||||
notify_game(Table, From, Message) ->
|
||||
io:format("Notify game called on"),
|
||||
erlang:display(Table),
|
||||
io:format("~n"),
|
||||
gen_server:cast(Table, {notify_game, Message, From}).
|
||||
|
||||
%% ----------------------------------------------------------------------
|
||||
|
||||
%% @private
|
||||
init([]) ->
|
||||
GameVM = ggs_gamevm_e:start_link(self()), %% @TODO: Temporary erlang gamevm
|
||||
{ok, #state {
|
||||
game_vm = GameVM,
|
||||
players = [] }}.
|
||||
|
||||
%% @private
|
||||
handle_call({add_player, Player}, _From, #state { players = Players } = State) ->
|
||||
{reply, ok, State#state { players = [Player | Players] }};
|
||||
|
||||
handle_call({remove_player, Player}, _From, #state { players = Players } = State) ->
|
||||
{reply, ok, State#state { players = Players -- [Player] }};
|
||||
|
||||
handle_call(get_player_list, _From, #state { players = Players } = State) ->
|
||||
{reply, {ok, Players}, State};
|
||||
|
||||
handle_call(Msg, _From, State) ->
|
||||
error_logger:error_report([unknown_msg, Msg]),
|
||||
{reply, ok, State}.
|
||||
|
||||
%% @private
|
||||
handle_cast({notify, Player, Message}, #state { game_vm = GameVM } = State) ->
|
||||
case Message of
|
||||
{server, define, Args} ->
|
||||
ggs_gamevm_e:define(GameVM, Args);
|
||||
{game, Command, Args} ->
|
||||
ggs_gamevm_e:user_command(GameVM, Player, Command, Args)
|
||||
end,
|
||||
{noreply, State};
|
||||
|
||||
handle_cast({notify_game, Message, From}, #state { game_vm = GameVM } = State) ->
|
||||
io:format("notify_game message received~n"),
|
||||
ggs_gamevm_e:user_command(GameVM, From, Message, ""),
|
||||
{noreply, State};
|
||||
|
||||
handle_cast({notify_all_players, Message}, #state{players = Players} = State) ->
|
||||
io:format("Notifying all players... ~p~n", [Players]),
|
||||
lists:foreach(fun(P) ->
|
||||
io:format("Notifying ~p~n", [P]),
|
||||
ggs_player:notify(P, "Server", Message)
|
||||
end, Players),
|
||||
{noreply, State};
|
||||
|
||||
handle_cast(stop, State) ->
|
||||
{stop, normal, State};
|
||||
handle_cast(Msg, S) ->
|
||||
error_logger:error_report([unknown_msg, Msg]),
|
||||
{noreply, S}.
|
||||
|
||||
%% @private
|
||||
handle_info(Msg, S) ->
|
||||
error_logger:error_report([unknown_msg, Msg]),
|
||||
{noreply, S}.
|
||||
|
||||
%% @private
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%% @private
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% @TODO: Please put these tests in a separate file. We can't compile this file if
|
||||
%% they contain errors from switching vms
|
||||
%% ----------------------------------------------------------------------
|
||||
% Tests
|
||||
|
||||
%<<<<<<< HEAD
|
||||
%start_link_test() ->
|
||||
% Table = start_link(),
|
||||
% ?assertNot(Table =:= undefined).
|
||||
|
||||
%add_player_test() ->
|
||||
% Table = start_link(),
|
||||
% Player = test_player,
|
||||
% add_player(Table, Player),
|
||||
% {ok, [Player]} = gen_server:call(Table, get_player_list).
|
||||
|
||||
%remove_player_test() ->
|
||||
% Table = start_link(),
|
||||
% Player = test_player,
|
||||
% Player2 = test_player2,
|
||||
% add_player(Table, Player),
|
||||
% {ok, [Player]} = gen_server:call(Table, get_player_list),
|
||||
% add_player(Table, Player2),
|
||||
% {ok, [Player2, Player]} = gen_server:call(Table, get_player_list),
|
||||
% remove_player(Table, Player),
|
||||
% {ok, [Player2]} = gen_server:call(Table, get_player_list),
|
||||
% remove_player(Table, Player2),
|
||||
% {ok, []} = gen_server:call(Table, get_player_list).
|
||||
%
|
||||
%stop_test() ->
|
||||
% Table = start_link(),
|
||||
% ok = stop(Table).
|
||||
|
||||
% @private
|
||||
%notify_test() ->
|
||||
% Table = start_link(),
|
||||
% Player = test_player,
|
||||
% Message = {server, define, "function helloWorld(x) { }"},
|
||||
% ok = notify(Table, Player, Message).
|
||||
%=======
|
||||
%%start_link_test() ->
|
||||
% Table = start_link("123", none),
|
||||
% ?assertNot(Table =:= undefined).
|
||||
%
|
||||
%add_player_test() ->
|
||||
% Table = start_link("123", none),
|
||||
% Player = test_player,
|
||||
% add_player(Table, Player),
|
||||
% {ok, [Player]} = gen_server:call(Table, get_player_list).
|
||||
|
||||
%remove_player_test() ->
|
||||
% Table = start_link("123", none),
|
||||
% Player = test_player,
|
||||
% Player2 = test_player2,
|
||||
% add_player(Table, Player),
|
||||
% {ok, [Player]} = gen_server:call(Table, get_player_list),
|
||||
% add_player(Table, Player2),
|
||||
% {ok, [Player2, Player]} = gen_server:call(Table, get_player_list),
|
||||
% remove_player(Table, Player),
|
||||
% {ok, [Player2]} = gen_server:call(Table, get_player_list),
|
||||
% remove_player(Table, Player2),
|
||||
% {ok, []} = gen_server:call(Table, get_player_list).
|
||||
%
|
||||
%stop_test() ->
|
||||
% Table = start_link("123", none),
|
||||
% ok = stop(Table).
|
||||
%
|
||||
%% @private
|
||||
%notify_test() ->
|
||||
% Table = start_link("123", none),
|
||||
% Player = test_player,
|
||||
% Message = {server, define, "function helloWorld(x) { }"},
|
||||
% ok = notify(Table, Player, Message).
|
||||
%>>>>>>> jonte_rewrite
|
||||
%Message2 = {game, "helloWorld", "test"},
|
||||
%ok = notify(Table, Player, Message2).
|
||||
|
8
src/helpers.erl
Normal file
8
src/helpers.erl
Normal file
|
@ -0,0 +1,8 @@
|
|||
-module(helpers).
|
||||
-export([not_implemented/0, get_new_token/0]).
|
||||
|
||||
not_implemented() ->
|
||||
exit("Not implemented").
|
||||
|
||||
get_new_token() ->
|
||||
string:strip(os:cmd("uuidgen"), right, $\n ).
|
|
@ -1,33 +0,0 @@
|
|||
-module(js_runner).
|
||||
-export([boot/0]).
|
||||
|
||||
%Mattias
|
||||
boot() ->
|
||||
erlang_js:start(),
|
||||
{ok, Port} = js_driver:new(),
|
||||
PortPid = spawn(fun() -> port_process(Port) end ),
|
||||
PortPid.
|
||||
|
||||
|
||||
port_process(Port) ->
|
||||
receive
|
||||
{get_port, From} ->
|
||||
From!{ok,Port},
|
||||
port_process(Port);
|
||||
{define, From, Data} ->
|
||||
ok = js:define(From, list_to_binary(Data)),
|
||||
From!{ok},
|
||||
port_process(Port);
|
||||
{call, From, Func, Params} ->
|
||||
{ok,Ret} = js:call(From, list_to_binary(Func), Params), %Port unsure
|
||||
From!{ok,Ret},
|
||||
port_process(Port)
|
||||
end.
|
||||
|
||||
%These two babies will be ambigiuous
|
||||
%define(Port, Data) ->
|
||||
% port_pid!{define,self(),Port,Data}.
|
||||
|
||||
|
||||
%call(Port, Func, Params) ->
|
||||
% port_pid!{call, self(), Port, Func, Params}.
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
erl -boot start_sasl -pa ebin_test -pa erlang_js/ebin/ -pa erlv8/ebin -pa ebin -pa src -eval 'ggs_protocol_test:test_parse().'
|
||||
erl -boot start_sasl -pa ebin_test -pa erlang_js/ebin/ -pa ebin -pa src -eval 'ggs_coordinator_test:test().'
|
||||
|
|
53
tests/ggs_coordinator_test.erl
Normal file
53
tests/ggs_coordinator_test.erl
Normal file
|
@ -0,0 +1,53 @@
|
|||
-module(ggs_coordinator_test).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
coordinator_test_() ->
|
||||
{foreach,
|
||||
fun() ->
|
||||
{ok, _Coord} = ggs_coordinator:start_link(),
|
||||
timer:sleep(100)
|
||||
end,
|
||||
fun(_X) ->
|
||||
ggs_coordinator:stop("End of test"),
|
||||
timer:sleep(100)
|
||||
end,
|
||||
[
|
||||
fun test_start_link/0,
|
||||
fun test_stop/0,
|
||||
fun test_join_bad_table/0,
|
||||
fun test_join_lobby/0
|
||||
]
|
||||
}.
|
||||
|
||||
test_start_link() ->
|
||||
% Check process info
|
||||
PInfo = whereis(ggs_coordinator),
|
||||
?assert((PInfo /= undefined)). % Did the server start?
|
||||
|
||||
test_stop() ->
|
||||
ok = ggs_coordinator:stop(""), % Extra cleaning
|
||||
timer:sleep(100),
|
||||
% Did it stop?
|
||||
?assert((whereis(ggs_coordinator)) == undefined).
|
||||
|
||||
test_join_bad_table() ->
|
||||
Response = ggs_coordinator:join_table("Nonexistant table"),
|
||||
?assert(Response == {error, no_such_table}).
|
||||
|
||||
test_join_lobby() ->
|
||||
{Response, _} = ggs_coordinator:join_lobby(),
|
||||
?assert(Response /= error).
|
||||
|
||||
%% 'Manual' tests
|
||||
|
||||
create_table_test() ->
|
||||
{ok, _Coord} = ggs_coordinator:start_link(),
|
||||
timer:sleep(100),
|
||||
% Forcibly create a table. This functionality should be disabled
|
||||
% in the production system, but is pretty nice for testing.
|
||||
Response = ggs_coordinator:create_table({force, 1337}),
|
||||
?assert(Response == {ok, 1337}).
|
||||
|
||||
join_good_table_test() ->
|
||||
Response = ggs_coordinator:join_table(1337),
|
||||
?assert(Response == {ok, 1337}).
|
27
tests/ggs_player_test.erl
Normal file
27
tests/ggs_player_test.erl
Normal file
|
@ -0,0 +1,27 @@
|
|||
-include_lib("eunit/include/eunit.hrl").
|
||||
-import(ggs_player).
|
||||
|
||||
%% @doc start_link should always return ok for any valid socket. A valid socket
|
||||
%% should always return {ok, Pid} and {error, Reason} otherwise.
|
||||
start_link_test() ->
|
||||
ggs_logger:not_implemented().
|
||||
|
||||
%% @doc Given that start_link returned {ok, Player}. Notify shall always return ok and
|
||||
%% deliver a specified message through the socket.
|
||||
notify_test() ->
|
||||
Player = start_link("bad arg"),
|
||||
Message = {"something", ""},
|
||||
Ret = ggs_player:notify(Player, self(), Message)
|
||||
?assertNot(ok =:= Ret).
|
||||
|
||||
%% @doc Given that start_link returned {ok, Player}. get_token shall always return a valid
|
||||
%% player token. a valid token should be unique.
|
||||
get_token_test() ->
|
||||
ggs_logger:not_implemented().
|
||||
|
||||
%% @doc Given that start_link returned {ok, Pid}. There shouldn't be possible to
|
||||
%% execute this function with the same Player and Table arguments twice.
|
||||
stop_test() ->
|
||||
Player = start_link(something),
|
||||
Table = test,
|
||||
ok = stop(Player, Table).
|
|
@ -1,6 +0,0 @@
|
|||
-module(ggs_protocol_test).
|
||||
-export([test_parse/0]).
|
||||
|
||||
test_parse() ->
|
||||
Ret = ggs_protocol:parse("<> __define JavaScript"),
|
||||
io:format("~p~n", [Ret]).
|
Reference in a new issue