var websocket = new Websocket(SERVER); websocket .connect() .done(function(){ var myGame = new MyGame(websocket); });
self.restart = function(evt){ websocket.send(JSON.stringify({ action:'start' })); };
self.move = function(direction){ // 0: up, 1: right, 2:down, 3: left if(!toMove){ return false; } if(direction === 0){ direction = 'up'; }else if(direction === 1){ direction = 'right'; }else if(direction === 2){ direction = 'down'; }else if(direction === 3){ direction = 'left'; } websocket.send(JSON.stringify({ action:'move', value: direction })); };
self.wsHandler = function(evt){ var game = JSON.parse(evt.data); if(game.grid){ var grid = {cells: []}; game.grid.forEach(function (column, y) { var row = []; column.forEach(function (cell, x) { if(cell){ if(cell.mergedFrom){ cell.mergedFrom.forEach(function(tile){ tile['x'] = x; tile['y'] = y; }); } row.push({ value: cell.value, x: x, y: y, previousPosition: cell.previousPosition, mergedFrom: cell.mergedFrom }); } }); grid.cells.push(row); }); var scores = game.scores, bestScore = 0; if(scores && scores.length>0){ bestScore = scores[0].score; while (scoresEl.firstChild) { scoresEl.removeChild(scoresEl.firstChild); } scores.forEach(function(score){ var div = document.createElement('Div'); var name = document.createElement('Div'); var scoreEl = document.createElement('Div'); div.setAttribute("class", 'score'); name.setAttribute("class", 'name'); scoreEl.setAttribute("class", 'score'); name.appendChild(document.createTextNode(score.name)); scoreEl.appendChild(document.createTextNode(score.score)); div.appendChild(name); div.appendChild(scoreEl); scoresEl.appendChild(div); }); } actuator.actuate(grid, { score: game.score, bestScore: bestScore, score: game.score, won: game.won, over: game.over, keepPlaying: game.keepPlaying }); } //playername actuator if(game.user){ if(playername.value !== playername){ playername.value = game.user.name; } } };
% The next option is required so we can use lager. {erl_opts, [{parse_transform, lager_transform}]}. {lib_dirs,["deps"]}. % Our dependencies. {deps, [ {'lager', ".*", { git, "git://github.com/basho/lager.git", "master"} }, {'cowboy', ".*", { git, "git://github.com/extend/cowboy.git", "master"} }, {'mochiweb', ".*", { git, "git://github.com/mochi/mochiweb.git", "master"} }, {'sqlite3', ".*", { git, "git://github.com/alexeyr/erlang-sqlite3.git", "master"} } ]}.
# rebar gd # rebar co
# rebar compile skip_deps=true; erl -pa ebin deps/*/ebin -eval 'starter:start().' -noshell -detached
-module(starter). -export([start/0]). start() -> application:start(ranch), application:start(crypto), application:start(cowlib), application:start(cowboy), application:start(inets), application:start(mochiweb), application:start(erl2048).
{application, erl2048, [ {description, "2048 game server."}, {vsn, "1"}, {modules, []}, {registered, [erl2048_sup]}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {erl2048_app, []}}, {env, []} ]}.
%% Feel free to use, reuse and abuse the code in this file. %% @private -module(erl2048_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}.
%% Feel free to use, reuse and abuse the code in this file. %% @private -module(erl2048_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", cowboy_static, {file, "../client/index.html"}}, {"/websocket", ws_handler, []}, {"/static/[...]", cowboy_static, {dir, "../client/static"}} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [{env, [{dispatch, Dispatch}]}]), {ok, _} = db:start_link(), erl2048_sup:start_link(). stop(_State) -> {ok, _} = db:stop(), ok.
-module(db). -export([start_link/0,stop/0]). -export([insert/2, select/0, createUser/1, changeName/2]). start_link() -> {ok, PID} = sqlite3:open(db, [{file, "db.sqlite3"}]), Tables = sqlite3:list_tables(db), case lists:member("scores", Tables) of false -> sqlite3:create_table(db, scores, [{id, integer, [{primary_key, [asc, autoincrement]}]}, {userid, integer}, {score, integer}]) end, case lists:member("users", Tables) of false -> sqlite3:create_table(db, users, [{id, integer, [{primary_key, [asc, autoincrement]}]}, {name, text}]) end, {ok, PID}. stop() -> sqlite3:close(db). select() -> Ret = sqlite3:sql_exec(db, "select users.name, scores.score from scores LEFT JOIN users ON (users.id = scores.userid) ORDER BY score desc;"), [{columns,_},{rows,Rows}] = Ret, formatScores(Rows). insert(Score, Player) -> [{columns,_},{rows,Rows}] = sqlite3:sql_exec(db, "SELECT score FROM scores WHERE userid = ?", [{1,Player}]), DBScore = if length(Rows) > 0 -> element(1,hd(Rows)); true -> 0 end, if Score > DBScore -> sqlite3:delete(db, scores, {userid, Player}), sqlite3:write(db, scores, [{userid, Player}, {score, Score}]), sqlite3:sql_exec(db, "DELETE FROM scores WHERE id IN (SELECT id FROM scores ORDER BY score desc LIMIT 1 OFFSET 10)"); true -> undefined end. formatScores([]) -> []; formatScores([{Name, Score} | Rows]) -> [{struct, [{name, Name},{score, Score}]} | formatScores(Rows)]. createUser(UserName) -> sqlite3:write(db, users, [{name, UserName}]). changeName(Id, NewName) -> sqlite3:update(db, users, {id, Id}, [{name, NewName}]).
-module(ws_handler). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3]). -export([websocket_handle/3]). -export([websocket_info/3]). -export([websocket_terminate/3]). init({tcp, http}, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, _Opts) -> State = {struct, [ { user, { struct, [{id, null},{name, <<"Player">>}] } } ]}, {ok, Req, State}. websocket_handle({text, Msg}, Req, State) -> Message = mochijson2:decode(Msg, [{format, proplist}]), Action = binary_to_list(proplists:get_value(<<"action">>, Message)), {NewState, Response} = case Action of "start" -> TmpState = game:init(State), {TmpState, TmpState}; "move" -> TmpState = game:move(list_to_atom(binary_to_list(proplists:get_value(<<"value">>, Message))), State), {TmpState, TmpState}; "newName" -> NewName = proplists:get_value(<<"value">>, Message), JsonData = element(2, State), User = proplists:get_value(user, JsonData), {struct,UserJsonData} = User, Id = proplists:get_value(id, UserJsonData), db:changeName(Id, NewName), TmpState = {struct, [ { user, { struct, [ { name, NewName },{ id, Id } ] } } | proplists:delete(user, JsonData) ]}, { TmpState, {struct, [{ user, { struct, [ { name, NewName },{ id, Id } ] } }]} }; _Else -> State end, {reply, {text, mochijson2:encode(Response)}, Req, NewState}; websocket_handle(_Data, Req, State) -> {ok, Req, State}. websocket_info({send, Msg}, Req, State) -> {reply, {text, Msg}, Req, State}; websocket_info(_Info, Req, State) -> {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok.
{struct, [ {key1, Value1}, {key2, Value2}, .... ]}
-module(tile). -export([init/1, init/0, prepare/2]). prepare(null, _) -> null; prepare(Tile, { X, Y }) -> { struct, [ {value, proplists:get_value(value, element(2, Tile))}, {mergedFrom, null}, {previousPosition, {struct, [{ x, X - 1},{ y, Y - 1 }]}} ] }. init(Value) -> { struct, [ {value, Value}, {mergedFrom, null}, {previousPosition, null} ] }. init() -> init(2).
-module(grid). -export([ build/0, cellsAvailable/1, randomAvailableCell/1, insertTile/3, availableCells/1, cellContent/2, removeTile/2, moveTile/3, size/0, withinBounds/1, cellAvailable/2 ]). -define(SIZE, 4). size() -> ?SIZE. build() -> [[null || _ <- lists:seq(1, ?SIZE)] || _ <- lists:seq(1, ?SIZE)]. availableCells(Grid) -> lists:append( setY( availableCells(Grid, 1) ) ). availableCells([Grid | Tail ], N) when is_list(Grid) -> [{availableCells(Grid, 1), N} | availableCells(Tail, N +1)]; availableCells([Grid | Tail ], N) -> case Grid =:= null of true -> [ N | availableCells(Tail, N +1)]; false -> availableCells(Tail, N +1) end; availableCells([], _) -> []. setY([{Cell, Y}|Tail]) -> [ setY(Cell, Y) | setY(Tail)]; setY([]) -> []. setY([Head | Tail], Y) -> [ {Head, Y} | setY(Tail, Y)]; setY([], _) -> []. cellsAvailable(Grid) -> length(availableCells(Grid)) > 0. randomAvailableCell(Grid) -> Cells = availableCells(Grid), lists:nth(random:uniform(length(Cells)) ,Cells). insertTile({X, Y}, Tile, Grid) -> Row = lists:nth(Y,Grid), lists:sublist(Grid,Y - 1) ++ [ lists:sublist(Row,X - 1) ++ [Tile] ++ lists:nthtail(X,Row)] ++ lists:nthtail(Y,Grid). cellContent({ X, Y }, Grid) -> case withinBounds({ X, Y }) of true -> lists:nth(X,lists:nth(Y,Grid)); false -> null end. removeTile({ X, Y }, Grid) -> insertTile({X, Y}, null, Grid). moveTile(Cell, Cell, Grid) -> Grid; moveTile(Cell, Next, Grid) -> insertTile(Next, grid:cellContent(Cell, Grid), removeTile(Cell, Grid)). withinBounds({X, Y}) when (X > 0), (X =< ?SIZE), (Y > 0), (Y =< ?SIZE) -> true; withinBounds(_) -> false. cellAvailable(Cell, Grid) -> case grid:withinBounds(Cell) of true -> cellContent(Cell, Grid) =:= null; false -> false end.
-module(game). -export([init/1, move/2]). init(State) -> StateUser = proplists:get_value(user, element(2, State)), StateUserJsonData = element(2, StateUser), User = case proplists:get_value(id, StateUserJsonData) of null -> Name = proplists:get_value(name, StateUserJsonData), {rowid, Id} = db:createUser(Name), { struct, [{name, Name},{id, Id}]}; _Else -> StateUser end, { struct, [ {grid ,addStartTiles(grid:build())}, {user , User}, {score,0}, {scores, db:select()}, {won, false}, {over, false}, {keepPlaying, false} ] }. addStartTiles(Grid, 0) -> Grid; addStartTiles(Grid, N) -> NewGrid = addRandomTile(Grid), addStartTiles(NewGrid, N - 1). addStartTiles(Grid) -> addStartTiles(Grid, 2). addRandomTile(Grid) -> random:seed(now()), case grid:cellsAvailable(Grid) of true -> case random:uniform(10) < 9 of true -> Tile = tile:init(); false -> Tile = tile:init(grid:size()) end, grid:insertTile(grid:randomAvailableCell(Grid), Tile, Grid); false -> Grid end. getVector(left) -> { -1, 0 }; getVector(up) -> { 0, -1 }; getVector(right) -> { 1, 0 }; getVector(down) -> { 0, 1 }. buildTraversals() -> Traver = lists:seq(1, grid:size()), { Traver, Traver }. buildTraversals({ 1 , _ }) -> { T1, T2} = buildTraversals(), { lists:reverse(T1), T2 }; buildTraversals({ _ , 1 }) -> { T1, T2} = buildTraversals(), { T1, lists:reverse(T2) }; buildTraversals({ _ , _ }) -> buildTraversals(). prepareTiles( [{_Key, _Value} | _Tail ] ) -> JsonData = [{_Key, _Value} | _Tail ], [{ grid, prepareTiles(proplists:get_value(grid, JsonData)) } | proplists:delete(grid, JsonData) ]; prepareTiles( Grid ) -> prepareTiles( Grid, 1). prepareTiles([], _) -> []; prepareTiles([Row | Tail], Y) -> [ prepareTileY(Row, 1, Y) | prepareTiles(Tail, Y + 1)]. prepareTileY([], _, _) -> []; prepareTileY([Cell | Tail], X, Y) -> [prepareTileX(Cell, X, Y) | prepareTileY(Tail, X + 1, Y) ]. prepareTileX(Tile, X, Y) -> tile:prepare(Tile, {X, Y}). process_travesals_y([], _, _, JsonData) -> JsonData; process_travesals_y(_, [], _, JsonData) -> JsonData; process_travesals_y([ Y | Tail ], TraversalsX, Vector, JsonData) -> process_travesals_y( Tail, TraversalsX, Vector, process_travesals_y( Y, TraversalsX, Vector, JsonData) ); process_travesals_y(Y, [ X | Tail ], Vector, JsonData) -> process_travesals_y(Y, Tail, Vector, process_travesals_y( Y, X, Vector, JsonData )); process_travesals_y( Y, X, Vector, JsonData ) -> moveTile({ X, Y }, Vector, JsonData). findFarthestPosition({X, Y}, {VecX, VecY}, Grid) -> Next = { X + VecX, Y + VecY }, case grid:cellAvailable(Next, Grid) of true -> findFarthestPosition(Next, {VecX, VecY}, Grid); false -> { {X, Y}, Next % Used to check if a merge is required } end. moveTile(Cell, Vector, JsonData) -> Grid = proplists:get_value(grid, JsonData), Tile = grid:cellContent(Cell, Grid), case Tile =:= null of true -> JsonData; false -> { Farthest, Next } = findFarthestPosition(Cell, Vector, Grid), {struct, CurrJsonData} = Tile, CurrValue = proplists:get_value(value, CurrJsonData), NextTile = if Next =:= null -> null; true -> grid:cellContent(Next, Grid) end, {NextValue, NextMerged} = if NextTile =:= null -> {null, null}; true -> NextJsonData = element(2, NextTile), {proplists:get_value(value, NextJsonData), proplists:get_value(mergedFrom, NextJsonData)} end, if CurrValue =:= NextValue, NextMerged =:= null -> MergedValue = CurrValue * 2, Merged = { struct, [ {value, MergedValue}, {mergedFrom, [Tile,NextTile]}, {previousPosition, null} ] }, NewGrid = grid:insertTile(Next, Merged, grid:removeTile(Cell, Grid)), % Update the score Score = proplists:get_value(score, JsonData) + MergedValue, % The mighty 2048 tile Won = if MergedValue =:= 2048 -> true; true -> false end, Removed = proplists:delete(score, proplists:delete(won, proplists:delete(grid, JsonData))), [ {grid,NewGrid}, {won,Won}, {score,Score} | Removed ]; true -> [ { grid, grid:moveTile(Cell, Farthest, proplists:get_value(grid, JsonData)) } | proplists:delete(grid, JsonData) ] end end. move(left, State) -> move(getVector(left), State); move(right, State) -> move(getVector(right), State); move(up, State) -> move(getVector(up), State); move(down, State) -> move(getVector(down), State); move(Vector, State) -> {struct, JsonData} = State, case proplists:get_value(over, JsonData) or ( proplists:get_value(won, JsonData) and (not proplists:get_value(keepPlaying, JsonData)) ) of true -> State; _Else -> PreparedJsonData = updateBestScore(prepareTiles(JsonData)), { TraversalsX, TraversalsY } = buildTraversals(Vector), NewJsonData = process_travesals_y( TraversalsY, TraversalsX, Vector, PreparedJsonData ), NewGrid = proplists:get_value(grid, NewJsonData), Grid = proplists:get_value(grid, PreparedJsonData), if NewGrid =/= Grid -> %If changed - add new tile {struct, UserJsonData} = proplists:get_value(user, NewJsonData), NewScore = proplists:get_value(score, NewJsonData), Score = proplists:get_value(score, PreparedJsonData), case NewScore > Score of true -> db:insert( proplists:get_value(score, NewJsonData), proplists:get_value(id, UserJsonData) ); _Else -> undefined end, Over = case movesAvailable(NewGrid) of true -> false; fale -> true % Game over! end, Removed = proplists:delete(grid, proplists:delete(over, NewJsonData)), {struct,[{ grid, addRandomTile(NewGrid) }, { over, Over } | Removed ]}; true -> %return state otherwise {struct,PreparedJsonData} end end. movesAvailable(_) -> true. updateBestScore(JsonData) -> [{ scores, db:select() } | proplists:delete(scores, JsonData) ].
init(State) -> StateUser = proplists:get_value(user, element(2, State)), StateUserJsonData = element(2, StateUser), User = case proplists:get_value(id, StateUserJsonData) of null -> Name = proplists:get_value(name, StateUserJsonData), {rowid, Id} = db:createUser(Name), { struct, [{name, Name},{id, Id}]}; _Else -> StateUser end, { struct, [ {grid ,addStartTiles(grid:build())}, {user , User}, {score,0}, {scores, db:select()}, {won, false}, {over, false}, {keepPlaying, false} ] }.
move(left, State) -> move(getVector(left), State); move(right, State) -> move(getVector(right), State); move(up, State) -> move(getVector(up), State); move(down, State) -> move(getVector(down), State); move(Vector, State) -> {struct, JsonData} = State, case proplists:get_value(over, JsonData) or ( proplists:get_value(won, JsonData) and (not proplists:get_value(keepPlaying, JsonData)) ) of true -> State; _Else -> PreparedJsonData = updateBestScore(prepareTiles(JsonData)), { TraversalsX, TraversalsY } = buildTraversals(Vector), NewJsonData = process_travesals_y( TraversalsY, TraversalsX, Vector, PreparedJsonData ), NewGrid = proplists:get_value(grid, NewJsonData), Grid = proplists:get_value(grid, PreparedJsonData), if NewGrid =/= Grid -> %If changed - add new tile {struct, UserJsonData} = proplists:get_value(user, NewJsonData), NewScore = proplists:get_value(score, NewJsonData), Score = proplists:get_value(score, PreparedJsonData), case NewScore > Score of true -> db:insert( proplists:get_value(score, NewJsonData), proplists:get_value(id, UserJsonData) ); _Else -> undefined end, Over = case movesAvailable(NewGrid) of true -> false; fale -> true % Game over! end, Removed = proplists:delete(grid, proplists:delete(over, NewJsonData)), {struct,[{ grid, addRandomTile(NewGrid) }, { over, Over } | Removed ]}; true -> %return state otherwise {struct,PreparedJsonData} end end.
moveTile(Cell, Vector, JsonData) -> Grid = proplists:get_value(grid, JsonData), Tile = grid:cellContent(Cell, Grid), case Tile =:= null of true -> JsonData; false -> { Farthest, Next } = findFarthestPosition(Cell, Vector, Grid), {struct, CurrJsonData} = Tile, CurrValue = proplists:get_value(value, CurrJsonData), NextTile = if Next =:= null -> null; true -> grid:cellContent(Next, Grid) end, {NextValue, NextMerged} = if NextTile =:= null -> {null, null}; true -> NextJsonData = element(2, NextTile), {proplists:get_value(value, NextJsonData), proplists:get_value(mergedFrom, NextJsonData)} end, if CurrValue =:= NextValue, NextMerged =:= null -> MergedValue = CurrValue * 2, Merged = { struct, [ {value, MergedValue}, {mergedFrom, [Tile,NextTile]}, {previousPosition, null} ] }, NewGrid = grid:insertTile(Next, Merged, grid:removeTile(Cell, Grid)), % Update the score Score = proplists:get_value(score, JsonData) + MergedValue, % The mighty 2048 tile Won = if MergedValue =:= 2048 -> true; true -> false end, Removed = proplists:delete(score, proplists:delete(won, proplists:delete(grid, JsonData))), [ {grid,NewGrid}, {won,Won}, {score,Score} | Removed ]; true -> [ { grid, grid:moveTile(Cell, Farthest, proplists:get_value(grid, JsonData)) } | proplists:delete(grid, JsonData) ] end end.
Source: https://habr.com/ru/post/216517/
All Articles