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"}, {vsn, "0.1.0"},
{modules, [ {modules, [
ggs_app, ggs_app,
ggs_sup ggs_sup,
ggs_dispatcher
]}, ]},
{registered, [ggs_sup]}, {registered, [ggs_sup]},
{applications, [kernel, stdlib]}, {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; this.spots[k++].innerHTML = t;
} }
} }
} }

View file

@ -1,12 +1,6 @@
- background image - background
- subimages for game_area:s
- subimages for game markers (X or 0)
- rectangle collision on game_area:s
- redraw background then all game_area:s - 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 array of squares with nr_of_squares elements
board contains x, y, width and height board contains x, y, width and height
board contains a frame 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.image import load
from pygame.rect import Rect from pygame.rect import Rect
from pygame import Surface from pygame import Surface
from data import greatest_sequence
#inherits Board. #inherits Board.
#Used for displaying the board on the screen and interact with it #Used for displaying the board on the screen and interact with it
@ -47,4 +48,14 @@ class TicTacToeBoard(Board):
game_rectangle.state = 'o' game_rectangle.state = 'o'
self.players_turn = (self.players_turn + 1) % 2 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). -module(gamedb).
-import(mnesia). -import(mnesia).
-export([init/0,insert_player/1,example_player/0,read_player/1,test_player/0]). -export([init/0,insert_player/1,example_player/0,read_player/1,test_player/0]).
-include("gamedb.hrl"). -include("gamedb.hrl").
%%-----------------------------------------------------
%% Creation
%%-----------------------------------------------------
init() -> init() ->
mnesia:create_table(player, [{attributes, record_info(fields, player)}]). mnesia:create_table(player, [{attributes, record_info(fields, player)}]).
%%-----------------------------------------------------
%% Test
%%-----------------------------------------------------
test_player() -> test_player() ->
insert_player(example_player()), insert_player(example_player()),
read_player(0001). read_player(0001).
example_player() ->
#player{id = 0001,
name = "Tux"}.
%%-----------------------------------------------------
%% Insertions
%%-----------------------------------------------------
insert_player(Player) -> insert_player(Player) ->
Fun = fun() -> Fun = fun() ->
mnesia:write(Player) mnesia:write(Player)
end, end,
mnesia:transaction(Fun). mnesia:transaction(Fun).
example_player() ->
#player{id = 0001,
name = "Tux"}.
%%-----------------------------------------------------
%% Querries
%%-----------------------------------------------------
read_player(Player_Key) -> read_player(Player_Key) ->
Fun = fun() -> Fun = fun() ->
[P] = mnesia:read(player, Player_Key), [P] = mnesia:read(player, Player_Key),

View file

@ -11,7 +11,7 @@ s.connect((HOST, PORT))
print "Saying hello to server" print "Saying hello to server"
s.send( s.send(
"Command: hello\n\ "Server-Command: hello\n\
Content-Type: text\n\ Content-Type: text\n\
Content-Length: 0\n\ Content-Length: 0\n\
\n\ \n\
@ -27,7 +27,7 @@ print "Data: ", ' '.join(data.split(" ")[1:])
print "Defining a function called myFun" print "Defining a function called myFun"
s.send( s.send(
"Token: %s\n\ "Token: %s\n\
Command: define\n\ Server-Command: define\n\
Content-Type: text\n\ Content-Type: text\n\
Content-Length: 49\n\ Content-Length: 49\n\
\n\ \n\
@ -42,7 +42,7 @@ print "Data: ", ' '.join(data.split(" ")[1:])
print "Calling myFun" print "Calling myFun"
s.send( s.send(
"Token: %s\n\ "Token: %s\n\
Command: call\n\ Server-Command: call\n\
Content-Type: text\n\ Content-Type: text\n\
Content-Length: 6\n\ Content-Length: 6\n\
\n\ \n\
@ -64,7 +64,7 @@ s.connect((HOST, PORT))
print "Calling myFun" print "Calling myFun"
s.send( s.send(
"Token: %s\n\ "Token: %s\n\
Command: call\n\ Server-Command: call\n\
Content-Type: text\n\ Content-Type: text\n\
Content-Length: 6\n\ Content-Length: 6\n\
\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). -behaviour(supervisor).
%% API %% API
-export([start/1, start_link/1]). -export([start_link/1]).
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
start(Port) ->
[FirstArg] = Port,
{IntPort, _} = string:to_integer(FirstArg),
start_link(IntPort).
start_link(Port) -> start_link(Port) ->
supervisor:start_link({local, ?SERVER}, ?MODULE, [Port]). supervisor:start_link({local, ?SERVER}, ?MODULE, [Port]).
init([Port]) -> init([Port]) ->
Server = {ggs_server_sup, Dispatcher = {ggs_dispatcher,
{ggs_server_sup, start_link, [Port]}, {ggs_dispatcher, start_link, [Port]},
permanent, permanent,
2000, 2000,
worker, 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 RestartStrategy = { one_for_one, % Restart only crashing child
10, % Allow ten crashes per.. 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 #!/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]).