Merge branch 'rewrite'

This commit is contained in:
Jeena Paradies 2011-02-22 00:22:10 +01:00
commit 183e628e8d
37 changed files with 823 additions and 420 deletions

View file

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

View file

@ -90,4 +90,4 @@ TicTacToeClient.prototype.updateBoard = function(gameBoardData) {
this.spots[k++].innerHTML = t;
}
}
}
}

View file

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 187 B

Before After
Before After

View 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]))

View 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

View 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()

View file

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

Binary file not shown.

View file

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

View file

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

Binary file not shown.

View file

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

View file

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

View file

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

View file

@ -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"], " ")).

View file

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

View file

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

View file

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

View file

@ -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().'

View 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
View 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).

View file

@ -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]).