diff options
Diffstat (limited to 'lib/gs/contribs/cols/cols.erl')
-rw-r--r-- | lib/gs/contribs/cols/cols.erl | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/lib/gs/contribs/cols/cols.erl b/lib/gs/contribs/cols/cols.erl new file mode 100644 index 0000000000..67b46d0dfb --- /dev/null +++ b/lib/gs/contribs/cols/cols.erl @@ -0,0 +1,618 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(cols). + +-export([start/0, init/0]). + +%% internal export. +-export([make_board_elem/3]). + +%%====================================================================== +%% Contents +%%===================== +%% 1. The actual program +%% 2. Graphics +%% 3. Data structures and stuff +%% 4. Lambdas +%%====================================================================== + + +-define(COLORS, {red,green,blue,grey,yellow,{66,153,130}}). +-define(HIGHFILE, "./cols.high"). +-define(HEIGHT, 17). +-define(LEFT, 50). +-define(SIZE, 15). +-define(VERSION, "v0.9"). +-define(WIDTH, 8). + +-record(state, {bit,board,nextbit,ticks, score=0}). +%%---------------------------------------------------------------------- +%% Consists of three boxes. +%%---------------------------------------------------------------------- +-record(bit, {x,y,topColor, middleColor, bottomColor, + top_gsobj,mid_gsobj,bot_gsobj}). + +%%====================================================================== +%% 1. The actual program +%%====================================================================== + +start() -> + spawn_link(cols,init,[]). + +init() -> + make_graphics(), + {A,B,C} = erlang:now(), + random:seed(A,B,C), + NextBit = make_bit(), + Board = make_screen_board(), + S = #state{bit=make_bit(), board=Board, ticks=update_timer(1), + score=make_score(), nextbit=new_bit_xy(NextBit, -2,5)}, + gs:config(win, [{map, true}]), + loop(S). + +make_graphics() -> + G = gs:start(), + H = ?HEIGHT*?SIZE, + W = ?WIDTH*?SIZE, + BotMargin = 100, + gs:create(window, win, G, [{destroy,true},{map, true},{title, "cols"}, + {height, H+BotMargin}, {width, W+?LEFT+10}, + {bg, grey},{keypress,true}]), + gs:create(canvas, can, win, [{bg, black}, + {height, H+BotMargin}, + {width, W+?LEFT+20}]), + gs:create(text, can, [{text, "Next"}, {coords, [{5, 45}]}, {fg, red}]), + gs:create(image, help, can, [{coords,[{5,7}]}, + {load_gif, dir() ++ "/help.gif"}, + {buttonpress,true}]), + draw_borders(). + +loop(State) -> + receive + Event -> loop(update(Event, State)) + end. + +%%---------------------------------------------------------------------- +%% How fast speed should be doubled +%%---------------------------------------------------------------------- +-define(DBL_TICKS, 300). + +update_timer(Ticks) -> + K = 0.001/?DBL_TICKS, + M = 1.001-K, + Q = K*Ticks+M, + Timeout = round(1/math:log(Q)), + timer:send_after(Timeout, self(), fall_timeout), + Ticks+1. + +add_score({ScoreObj, NScore}, DScore) -> + NScore2 = NScore + DScore, + gs:config(ScoreObj, [{text, io_lib:format("Score: ~w", [NScore2])}]), + {ScoreObj, NScore2}. + + +update({gs,_Obj,keypress,_Data, ['Left'|_]}, State) -> + #state{bit=Bit, board = Board} = State, + #bit{x=X,y=Y} = Bit, + if X > 0 -> + case is_board_empty(Board, X-1,Y) of + true -> + State#state{bit=new_bit_xy(Bit, X-1, Y)}; + false -> + State + end; + true -> State + end; + +update({gs,_Obj,keypress,_Data, ['Right'|_]}, State) -> + #state{bit=Bit, board = Board} = State, + #bit{x=X,y=Y} = Bit, + if X < ?WIDTH - 1 -> + case is_board_empty(Board, X+1, Y) of + true -> + State#state{bit=new_bit_xy(Bit, X+1, Y)}; + false -> + State + end; + true -> State + end; + +update({gs,_Obj,keypress,_Data, ['Up'|_]}, State) -> + State#state{bit=shift_bits(State#state.bit)}; + +update({gs,_Obj,keypress,_Data, [Key|_]}, State) -> + case drop_key(Key) of + true -> + #state{bit=Bit, board=Board, score=Score} = State, + #bit{x=X,y=Y} = Bit, + {NewX, NewY, NewScore} = drop(X,Y,Score,Board), + fasten_bit(State#state{bit=new_bit_xy(Bit,NewX, NewY), + score=NewScore}); + false -> State + end; + +update(fall_timeout, State) -> + #state{bit=Bit, board=Board, ticks = Ticks, score=Score} = State, + NewY = Bit#bit.y+1, + X = Bit#bit.x, + case is_fall_ok(Board, X, NewY) of + true -> + State#state{bit=new_bit_xy(Bit, X, NewY), + ticks=update_timer(Ticks), score=add_score(Score, 1)}; + false -> + S1 = fasten_bit(State), + S1#state{ticks=update_timer(Ticks)} + end; + +update({gs,_,destroy,_,_}, _State) -> + exit(normal); + +update({gs,help,buttonpress,_,_}, State) -> + show_help(), + State; + +update(OtherEvent, State) -> + ok=io:format("got other! ~w~n", [OtherEvent]), State. + +drop_key('Down') -> true; +drop_key(space) -> true; +drop_key(_) -> false. + +is_board_empty(Board, X, Y) -> + case {color_at(Board, X, Y), + color_at(Board, X, Y + 1), + color_at(Board, X, Y + 2)} of + {black, black, black} -> true; + _ -> false + end. + +%%---------------------------------------------------------------------- +%% Returns: NewState +%%---------------------------------------------------------------------- +fasten_bit(State) -> + #state{board=Board, bit=Bit, nextbit=NextBit, score=Score} = State, + #bit{x=X,y=Y,topColor=C1,middleColor=C2,bottomColor=C3} = Bit, + B1 = update_screen_element(Board, X, Y, C1), + B2 = update_screen_element(B1, X, Y+1, C2), + B3 = update_screen_element(B2, X, Y+2, C3), + destroy_bit(Bit), + #bit{topColor=NC1,middleColor=NC2,bottomColor=NC3} = NextBit, + {B4, ExtraScore} = erase_bits(B3, [{X,Y},{X,Y+1},{X,Y+2}], 0), + NewBit = make_bit(NC1,NC2,NC3), + case is_board_empty(B4, NewBit#bit.x, NewBit#bit.y) of + true -> + State#state{score=add_score(Score, ExtraScore), + bit=NewBit, nextbit=new_colors(NextBit),board=B4}; + false -> + {_GsObj,Score2}=State#state.score, + highscore:run(Score2,?HIGHFILE), + exit(normal) + end. + +%%---------------------------------------------------------------------- +%% Args: Check: list of {X,Y} to check. +%% Returns: {NewBoard, ExtraScore} +%%---------------------------------------------------------------------- +erase_bits(Board, Checks, ExtraScore) -> + ElemsToDelete = elems2del(Checks,Board,[]), + NDel = length(ElemsToDelete), + if + NDel > 0 -> + Board2 = delete_elems(Board, ElemsToDelete), + {NewBoard, NewCheck} = fall_down(Board2, ElemsToDelete), + if NDel > 3 -> + {B,ES}=erase_bits(NewBoard,NewCheck,ExtraScore+2*NDel), + {NewBoard2, NewCheck2} = bonus(B, NewCheck), + erase_bits(NewBoard2, NewCheck2, ES); + true -> + erase_bits(NewBoard, NewCheck, 2*NDel) + end; + true -> {Board, ExtraScore} + end. + +bonus(Board, Check) -> + Cols = collect_bottom_bits(0,Board), + NewBoard = randomize_columns(5, Board, Cols), + NewCheck = update_check(Check, Cols), + {NewBoard, NewCheck}. + +randomize_columns(0, Board, _) -> Board; +randomize_columns(N, Board, Cols) -> + NewBoard = randomize_columns(Cols,Board), + randomize_columns(N-1, NewBoard, Cols). + +randomize_columns([],Board) -> Board; +randomize_columns([X|Xs],Board) -> + flush(), + timer:sleep(50), + randomize_columns(Xs,update_screen_element(Board,X,?HEIGHT-1,rndColor())). + +%%---------------------------------------------------------------------- +%% Returns: NewBoard +%%---------------------------------------------------------------------- +delete_elems(Board, Elems2Del) -> + OrgObjs = org_objs(Elems2Del,Board), + visual_effect(?SIZE, OrgObjs), + NewBoard = update_board(Elems2Del, Board), + put_back(OrgObjs), + NewBoard. + +visual_effect(0,_OrgObjs) -> done; +visual_effect(Size,OrgObjs) -> + set_size(OrgObjs,Size), + flush(), + timer:sleep(20), + visual_effect(Size-1,OrgObjs). + +set_size([],_Size) -> done; +set_size([{GsObj,[{X1,Y1},{_X2,_Y2}]}|T],Size) -> + gs:config(GsObj, [{coords, [{X1,Y1},{X1+Size,Y1+Size}]}]), + set_size(T,Size). + +%%---------------------------------------------------------------------- +%% Note: Loop over columns where something is removed only. (efficiency) +%% Returns: {ReversedNewColumns (perhaps shorter), Checks} +%% cols:fall_column([a,b,black,black,c,f,black,d,black], 3, 15, [], []). +%% should return: {[a,b,c,f,d],[{3,11},{3,12},{3,13}]} +%%---------------------------------------------------------------------- +fall_column([], _X, _Y, ColumnAcc, ChecksAcc) -> + {ColumnAcc, ChecksAcc}; +fall_column([black|Colors], X, Y, ColumnAcc, ChecksAcc) -> + case find_box(Colors) of + false -> {ColumnAcc, ChecksAcc}; + NewColors when list(NewColors) -> + fall_one_step(NewColors, X, Y, ColumnAcc, ChecksAcc) + end; +fall_column([Color|Colors], X, Y, ColumnAcc, ChecksAcc) -> + fall_column(Colors, X, Y-1, [Color | ColumnAcc], ChecksAcc). + +find_box([]) -> false; +find_box([black|Colors]) -> + find_box(Colors); +find_box([Color|Colors]) -> [Color|Colors]. + +%%---------------------------------------------------------------------- +%% Enters: ([a,b, , ,c,d], 3, 8, Q) +%% Leaves: ([b,a|Q], [ , , ,c,d], 10, [{3,8},{4,9}]) +%%---------------------------------------------------------------------- +fall_one_step([], X, Y, ColumnAcc, Checks) -> + fall_column([], X, Y, ColumnAcc, Checks); +fall_one_step([black|Colors], X, Y, ColumnAcc, Checks) -> + fall_column([black|Colors], X, Y, ColumnAcc, Checks); +fall_one_step([Color|Colors], X, Y, ColumnAcc, Checks) -> + fall_one_step(Colors, X, Y-1, [Color|ColumnAcc],[{X,Y}|Checks]). + +%%---------------------------------------------------------------------- +%% Returns: {NewBoard, NewChecks} +%%---------------------------------------------------------------------- +fall_down(Board1, Elems2Del) -> + UpDatedCols = updated_cols(Elems2Del, []), + fall_column(UpDatedCols, Board1, []). + +fall_column([], NewBoard, NewChecks) -> {NewBoard, NewChecks}; +fall_column([X|Xs], BoardAcc, ChecksAcc) -> + OrgColumn = boardcolumn_to_tuple(BoardAcc, X), + Column = columntuple_to_list(OrgColumn), + {NewColumn, NewChecksAcc} = fall_column(Column, X,?HEIGHT-1,[],ChecksAcc), + NewBoardAcc = + set_board_column(BoardAcc,X,new_column_list(NewColumn,OrgColumn)), + fall_column(Xs,NewBoardAcc,NewChecksAcc). + +new_column_list(NewColumn, ColumnTuple) -> + Nempty = ?HEIGHT - length(NewColumn), + L = make_list(black, Nempty) ++ NewColumn, + new_column_list(L, 1, ColumnTuple). + +new_column_list([H|T], N, Tuple) -> + {GsObj, Color} = element(N, Tuple), + [update_screen_element({GsObj, Color},H) | new_column_list(T, N+1, Tuple)]; +new_column_list([], _, _) -> []. + + +%%---------------------------------------------------------------------- +%% Returns: a reversed list of colors. +%%---------------------------------------------------------------------- +columntuple_to_list(ColumnTuple) when tuple(ColumnTuple) -> + columntuple_to_list(tuple_to_list(ColumnTuple),[]). + +columntuple_to_list([],Acc) -> Acc; +columntuple_to_list([{_GsObj, Color}|T],Acc) -> + columntuple_to_list(T,[Color|Acc]). + +%%====================================================================== +%% 2. Graphics +%%====================================================================== + +make_bit() -> + make_bit(rndColor(),rndColor(),rndColor()). + +make_bit(Tc,Mc,Bc) -> + X = ?WIDTH div 2, + Y = 0, + #bit{x=X,y=Y,topColor= Tc, middleColor=Mc, bottomColor=Bc, + top_gsobj = make_box(X,Y,Tc), mid_gsobj=make_box(X,Y+1,Mc), + bot_gsobj=make_box(X,Y+2,Bc)}. + +new_colors(Bit) -> + #bit{top_gsobj=T,mid_gsobj=M,bot_gsobj=B} = Bit, + Tc = rndColor(), + Mc = rndColor(), + Bc = rndColor(), + gs:config(T, [{fill, Tc}]), + gs:config(M, [{fill, Mc}]), + gs:config(B, [{fill, Bc}]), + Bit#bit{topColor= Tc, middleColor=Mc, bottomColor=Bc}. + +new_bit_xy(Bit, NewX, NewY) -> + #bit{x=X,y=Y,top_gsobj=T,mid_gsobj=M,bot_gsobj=B} = Bit, + Dx = (NewX - X) * ?SIZE, + Dy = (NewY - Y) * ?SIZE, + gs:config(T, [{move, {Dx, Dy}}]), + gs:config(M, [{move, {Dx, Dy}}]), + gs:config(B, [{move, {Dx, Dy}}]), + Bit#bit{x=NewX, y=NewY}. + +destroy_bit(#bit{top_gsobj=T,mid_gsobj=M,bot_gsobj=B}) -> + gs:destroy(T), + gs:destroy(M), + gs:destroy(B). + +shift_bits(Bit) -> + #bit{topColor=C1,middleColor=C2,bottomColor=C3, + top_gsobj=T,mid_gsobj=M,bot_gsobj=B} = Bit, + gs:config(T, {fill,C2}), + gs:config(M, {fill,C3}), + gs:config(B, {fill,C1}), + Bit#bit{topColor=C2, middleColor=C3, bottomColor=C1}. + +rndColor() -> + Siz = size(?COLORS), + element(random:uniform(Siz), ?COLORS). + +make_score() -> + {gs:create(text, can, [{text, "Score: 0"}, {fg, red}, + {coords, [{5,?HEIGHT*?SIZE+10}]}]), 0}. + +make_screen_board() -> + xy_loop({cols,make_board_elem}, make_board(), ?WIDTH, ?HEIGHT). + +make_board_elem(X,Y,Board) -> + set_board_element(Board,X,Y,{make_box(X,Y,black),black}). + +flush() -> gs:read(can, bg). + +draw_borders() -> + BotY = ?HEIGHT*?SIZE, + RightX = ?LEFT + ?SIZE*?WIDTH, + LeftX = ?LEFT - 1, + gs:create(line,can,[{coords,[{LeftX,0},{LeftX,BotY}]},{fg,white}]), + gs:create(line,can,[{coords,[{LeftX,BotY},{RightX,BotY}]},{fg,white}]), + gs:create(line,can,[{coords,[{RightX,0},{RightX, BotY}]}, {fg,white}]). + +update_screen_element(ScrBoard, X, Y, Color) -> + case board_element(ScrBoard,X,Y) of + {_GsObj, Color} -> + ScrBoard; % don't have to update screen + {GsObj, _ScreenColor} -> + gs:config(GsObj, color_args(Color)), + set_board_element(ScrBoard, X, Y, {GsObj, Color}) + end. + +update_screen_element(ScrElem, Color) -> + case ScrElem of + {_GsObj, Color} -> + ScrElem; % don't have to update screen + {GsObj, _ScreenColor} -> + gs:config(GsObj, color_args(Color)), + {GsObj, Color} + end. + + +color_args(black) -> [{fg,black},{fill,black}]; +color_args(Color) -> [{fg,white},{fill,Color}]. + +%%====================================================================== +%% 3. Data structures and stuff +%%====================================================================== + +xy_loop(Fun, Acc, XMax, YMax) -> + xy_loop(Fun, Acc, 0, 0, XMax, YMax). + +xy_loop(_Fun, Acc, _X, YMax, _XMax, YMax) -> Acc; +xy_loop(Fun, Acc, XMax, Y, XMax, YMax) -> + xy_loop(Fun, Acc, 0, Y+1, XMax, YMax); +xy_loop(Fun, Acc, X, Y, XMax, YMax) -> + xy_loop(Fun, apply(Fun, [X, Y,Acc]), X+1,Y,XMax, YMax). + +%%---------------------------------------------------------------------- +%% Returns: a sorted list of {X,Y} to delete. +%% Pre: PrevDelElems is sorted. +%%---------------------------------------------------------------------- +erase_bits_at(Board, PrevDelElems, X,Y) -> + C = color_at(Board, X, Y), + erase_bits_at([vert, horiz, slash, backslash],X,Y,C,Board,PrevDelElems). + +erase_bits_at([], _X,_Y,_C,_Board, Elems2Del) -> Elems2Del; +erase_bits_at([Dir|Ds],X,Y,C,Board, Elems2DelAcc) -> + Dx = dx(Dir), + Dy = dy(Dir), + DelElems = lists:append(check_dir(Board, X-Dx,Y-Dy,-Dx,-Dy,C), + check_dir(Board, X,Y,Dx,Dy,C)), + N_in_a_row = length(DelElems), + if N_in_a_row >= 3 -> + erase_bits_at(Ds,X,Y,C,Board, + ordsets:union(lists:sort(DelElems),Elems2DelAcc)); + true -> erase_bits_at(Ds,X,Y,C,Board,Elems2DelAcc) + end. + +dx(vert) -> 0; +dx(horiz) -> 1; +dx(slash) -> 1; +dx(backslash) -> -1. + +dy(vert) -> -1; +dy(horiz) -> 0; +dy(slash) -> -1; +dy(backslash) -> -1. + + +%%---------------------------------------------------------------------- +%% Returns: list of {X,Y} to delete. +%%---------------------------------------------------------------------- +check_dir(Board, X, Y, Dx, Dy, Color) + when X >= 0, X < ?WIDTH, Y >= 0, Y < ?HEIGHT -> + case color_at(Board, X, Y) of + Color -> + [{X,Y} | check_dir(Board, X+Dx, Y+Dy, Dx, Dy, Color)]; + _OtherColor -> + [] + end; +check_dir(_Board, _X, _Y, _Dx, _Dy, _Color) -> []. + +make_box(X, Y, Color) -> + make_box(X, Y, 1, 1, Color). + +%%---------------------------------------------------------------------- +%% Returns: GsObj +%%---------------------------------------------------------------------- +make_box(X, Y, Height, Width, Color) -> + Opts = if Color == black -> [{fg, black}, {fill, black}]; + true -> [{fill, Color}, {fg, white}] end, + gs:create(rectangle, can, [{coords, [{?LEFT + X * ?SIZE, Y * ?SIZE}, + {?LEFT + X * ?SIZE + (?SIZE*Width)-1, + Y * ?SIZE + (?SIZE*Height)-1}]}|Opts]). + +is_fall_ok(_Board, _NewX, NewY) when NewY+2 >= ?HEIGHT -> false; +is_fall_ok(Board, NewX, NewY) -> + case color_at(Board, NewX, NewY+2) of + black -> + true; + _ -> false + end. + +color_at(Board, X, Y) -> + {_GsObj, Color} = board_element(Board, X, Y), + Color. + + +%%---------------------------------------------------------------------- +%% X:0..?WIDTH-1, Y:0..?HEIGHT +%%---------------------------------------------------------------------- +make_board() -> + list_to_tuple(make_list(make_column(), ?WIDTH)). + +board_element(Board, X, Y) -> + element(Y+1, element(X+1, Board)). + +set_board_element(Board, X, Y, NewValue) -> + Col = element(X+1, Board), + NewCol=setelement(Y+1,Col, NewValue), + setelement(X+1, Board, NewCol). + +make_column() -> + list_to_tuple(make_list(black, ?HEIGHT)). + +make_list(_Elem, 0) -> []; +make_list(Elem, N) -> [Elem|make_list(Elem,N-1)]. + +boardcolumn_to_tuple(Board, X) -> + element(X+1, Board). + +set_board_column(Board, X, NewCol) when length(NewCol) == ?HEIGHT -> + setelement(X+1, Board, list_to_tuple(NewCol)). + +show_help() -> + W = gs:create(window, win, [{title, "cols Help"}, {width, 300}, + {height,300}, {map, true}]), + gs:create(label, W, [{x,0},{y,0},{height, 200},{width,300},{justify,center}, + {label, {text, + "cols $Revision: 1.23 $" + "\nby\n" + "Klas Eriksson, [email protected]\n\n" + "Help: Use arrows and space keys.\n" + " Try to get 3 in-a-row.\n" + " More than 3 gives bonus."}}]), + B=gs:create(button, W, [{x,100},{y,250}, {label, {text, "Dismiss"}}]), + receive + {gs, B, click, _, _} -> ok + end, + gs:destroy(W). + +%%====================================================================== +%% 4. Lambdas +%%====================================================================== + +drop(X,Y,Score,Board) -> + case is_fall_ok(Board, X, Y+1) of + true -> drop(X,Y+1,add_score(Score, 1),Board); + false -> {X,Y, Score} + end. + +elems2del([], _Board,Elems2DelAcc) -> Elems2DelAcc; +elems2del([{X,Y}|Checks],Board,Elems2DelAcc) -> + NewElems2DelAcc = ordsets:union(erase_bits_at(Board,Elems2DelAcc,X,Y), + Elems2DelAcc), + elems2del(Checks,Board,NewElems2DelAcc). + +collect_bottom_bits(?WIDTH,_Board) -> []; +collect_bottom_bits(X,Board) -> + case color_at(Board, X, ?HEIGHT-1) of + black -> collect_bottom_bits(X+1,Board); + _AcolorHere -> [X|collect_bottom_bits(X+1,Board)] + end. + +update_check(_Check,[]) -> []; +update_check(Check,[X|Xs]) -> + case lists:member({X, ?HEIGHT-1}, Check) of + true -> update_check(Check,Xs); + false -> [{X, ?HEIGHT-1}|update_check(Check,Xs)] + end. + +org_objs([],_Board) -> []; +org_objs([{X,Y}|XYs],Board) -> + {GsObj, _Color} = board_element(Board, X, Y), + [{GsObj, lists:sort(gs:read(GsObj, coords))}|org_objs(XYs,Board)]. + +update_board([],Board) -> Board; +update_board([{X,Y}|XYs], Board) -> + update_board(XYs,update_screen_element(Board, X, Y, black)). + +put_back([]) -> done; +put_back([{GsObj, Coords}|Objs]) -> + gs:config(GsObj, [{coords, Coords}]), + put_back(Objs). + +updated_cols([], UpdColsAcc) -> UpdColsAcc; +updated_cols([{X,_Y}|XYs], UpdColsAcc) -> + case lists:member(X,UpdColsAcc) of + true -> updated_cols(XYs,UpdColsAcc); + false -> updated_cols(XYs,[X|UpdColsAcc]) + end. + +%% This is not an application so we don't have their way of knowing +%% a private data directory where the GIF files are located (this directory). +%% We can find GS and makes it relative from there /kgb + +-define(EbinFromGsPriv,"../contribs/ebin"). + +dir()-> + GsPrivDir = code:priv_dir(gs), + filename:join(GsPrivDir,?EbinFromGsPriv). |