%% %% %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(bonk). -export([run/0, run/1,bonk_dir/0,start/0]). -record(colors, {miss, x, bomb, face}). -record(scores, {points, level, bombs, hits, showed, bonus}). start() -> spawn(bonk, run, []). run() -> run(color). run([ColorMode]) -> % This is for the start script... run(ColorMode); run(ColorMode) when atom(ColorMode) -> GS = gs:start(), SoundPid=spawn_link(bonk_sound,start,[]), {H,M,S}=time(), random:seed(H*13,M*7,S*3), {SqrPids, Bmps, Colors} = create_board(GS, ColorMode), {ScoreL,_File} = get_highscore(), display_highscore(ScoreL), put(colormode, ColorMode), SoundPid ! music, sleep(6500), gs:config(aboutButton, [{enable,true}]), gs:config(newButton, [{enable,true}]), gs:config(quitButton, [{enable,true}]), idle(SoundPid, SqrPids, Bmps, Colors). %% 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 %% %% Note the silly slash that is added. The rest of the code uses %% append to contruct file names and assumes that the directory ends %% in slash. If filename:join was used and the problem is gone. -define(EbinFromGsPriv,"../contribs/bonk"). bonk_dir() -> GsPrivDir = code:priv_dir(gs), filename:join(GsPrivDir,?EbinFromGsPriv) ++ "/". idle(SoundPid, SqrPids, Bmps, Colors) -> receive {gs, newButton, click, _Data, _Args} -> init(SoundPid, SqrPids, Bmps, Colors); {gs, aboutButton, click, _Data, _Args} -> display_about(), idle(SoundPid, SqrPids, Bmps, Colors); {gs, quitButton, click, _Data, _Args} -> SoundPid ! quit, send_to_all(SqrPids, quit); _Other -> %%io:format("Got ~w in idle~n", [_Other]), idle(SoundPid, SqrPids, Bmps, Colors) end. init(SoundPid, SqrPids, Bmps, Colors) -> clear_board(Bmps), SoundPid ! start, gs:config(newButton, [{enable,false}]), gs:config(endButton, [{enable,true}]), gs:config(aboutButton, [{enable,false}]), Scores = #scores{points=0, level=1, bombs=0, hits=0, showed=0, bonus=10}, clear_scores(Scores), flush(), send_to_all(SqrPids, start), game(SoundPid, SqrPids, Bmps, Colors, Scores). game(SoundPid, SqrPids, Bmps, Colors, Scores) -> receive {gs, _Square, buttonpress, SqrPid, [1 | _Rest]} when pid(SqrPid) -> SqrPid ! bonk, game(SoundPid, SqrPids, Bmps, Colors, Scores); {gs, _Id, buttonpress, _Data, [Butt | _Rest]} when Butt =/= 1 -> NewScores = bomb(SoundPid, SqrPids, Scores), game(SoundPid, SqrPids, Bmps, Colors, NewScores); {show, Square, Rect} -> NewScores = show_face(Square, Rect, Colors, Scores), game(SoundPid, SqrPids, Bmps, Colors, NewScores); {hide, Square, Rect} -> NewScores = hide_face(Square, Rect, Colors, Scores), game(SoundPid, SqrPids, Bmps, Colors, NewScores); {missed, Square, Rect} -> case miss_face(SoundPid, Square, Rect, Colors, Scores) of {continue, NewScores} -> game(SoundPid, SqrPids, Bmps, Colors, NewScores); {game_over, NewScores} -> game_over(SoundPid, SqrPids, Bmps, Colors, NewScores) end; {bonked, Square, Rect} -> NewScores = bonked(SoundPid, SqrPids, Square, Rect, Scores, Colors), game(SoundPid, SqrPids, Bmps, Colors, NewScores); {bombed, Square, Rect} -> NewScores = bombed(SoundPid, SqrPids, Square, Rect, Scores, Colors), game(SoundPid, SqrPids, Bmps, Colors, NewScores); {gs, endButton, click, _Data, _Args} -> game_over(SoundPid, SqrPids, Bmps, Colors, Scores); {gs, quitButton, click, _Data, _Args} -> quit(SoundPid, SqrPids, Bmps, Colors, Scores); _Other -> game(SoundPid, SqrPids, Bmps, Colors, Scores) end. game_over(SoundPid, SqrPids, Bmps, Colors, Scores) -> SoundPid ! game_over, send_to_all(SqrPids, stop), flush(), sleep(2000), update_scorelist(SoundPid, Scores), gs:config(newButton, [{enable,true}]), gs:config(endButton, [{enable,false}]), gs:config(aboutButton, [{enable,true}]), idle(SoundPid, SqrPids, Bmps, Colors). quit(SoundPid, SqrPids, _Bmps, _Colors, _Scores) -> SoundPid ! quit, send_to_all(SqrPids, quit), true. bomb(SoundPid, SqrPids, Scores) -> case Scores#scores.bombs of Bombs when Bombs > 0 -> send_to_all(SqrPids, bomb), SoundPid ! bomb, gs:config(bombOut,[{text,integer_to_list(Bombs-1)}]), Scores#scores{bombs=Bombs-1}; _Other -> Scores end. show_face(Square, Rect, Colors, Scores) -> Showed = Scores#scores.showed, if Showed == Scores#scores.level+1 -> Square ! sleep, Scores; true -> FaceColors = Colors#colors.face, FaceColor = lists:nth(random:uniform(length(FaceColors)), FaceColors), gs:config(Rect, [{bitmap,lists:append(bonk_dir(),"bitmaps/bonkface")},{fg, FaceColor}]), Scores#scores{showed=Showed+1} end. hide_face(_Square, Rect, _Colors, Scores) -> Showed = Scores#scores.showed, gs:config(Rect, [{bitmap,lists:append(bonk_dir(),"bitmaps/bonktom")}]), Scores#scores{showed=Showed-1}. miss_face(SoundPid, _Square, Rect, Colors, Scores) -> SoundPid ! missed, gs:config(Rect, [{bitmap,lists:append(bonk_dir(),"bitmaps/bonkmiss")}, {fg, Colors#colors.miss}]), Bonus = Scores#scores.bonus, if Bonus > 1 -> gs:config(bonusOut, [{text,integer_to_list(Bonus-1)}]), {continue, Scores#scores{bonus=Bonus-1}}; true -> gs:config(bonusOut, [{text,"0"}]), {game_over, Scores} end. bonked(SoundPid, SqrPids, _Square, Rect, Scores, Colors) -> gs:config(Rect, [{bitmap,lists:append(bonk_dir(),"bitmaps/bonkx")}, {fg, Colors#colors.x}]), SoundPid ! bonk, update_score(SoundPid, SqrPids, Scores). bombed(SoundPid, SqrPids, _Square, Rect, Scores, Colors) -> gs:config(Rect, [{bitmap,lists:append(bonk_dir(),"bitmaps/bonkbomb")}, {fg, Colors#colors.bomb}]), update_score(SoundPid, SqrPids, Scores). update_score(SoundPid, SqrPids, Scores) -> Points = Scores#scores.points, Level = Scores#scores.level, NewPoints = Points+Level, gs:config(scoreOut,[{text,integer_to_list(NewPoints)}]), case Scores#scores.hits of 24 -> SoundPid ! new_level, NewLevel = Level+1, NewBombs = Scores#scores.bombs+1, send_to_all(SqrPids, {new_level, NewLevel}), gs:config(levelOut,[{text,integer_to_list(NewLevel)}]), gs:config(bombOut,[{text,integer_to_list(NewBombs)}]), Scores#scores{points=NewPoints, level=NewLevel, hits=0, bombs=NewBombs}; Hits -> Scores#scores{points=NewPoints, hits=Hits+1} end. send_to_all([], _Msg) -> true; send_to_all([Pid|Rest],Msg) when pid(Pid) -> Pid ! Msg, send_to_all(Rest,Msg); send_to_all([_Else|Rest],Msg) -> send_to_all(Rest,Msg). create_board(GS, ColorMode) -> Colors = case ColorMode of bw -> #colors{miss=white, x=white, bomb=white, face=[white]}; _Color -> #colors{miss=red, x=green, bomb=white, face=[lightblue, orange, magenta, peachpuff, pink]} end, BGCol = if ColorMode==bw -> black; true -> black end, % background color TextCol = if ColorMode==bw -> white; true -> pink end, % status texts NrCol = if ColorMode==bw -> white; true -> purple end, % status figures LogoCol = if ColorMode==bw -> white; true -> green end, % bonk logo BLineCol = if ColorMode==bw -> white; true -> grey end, % button line SLineCol = if ColorMode==bw -> white; true -> red end, % status line BTextCol = if ColorMode==bw -> white; true -> orange end, % button text HiHeadCol = if ColorMode==bw -> white; true -> red end, % high score label HiCol = if ColorMode==bw -> white; true -> cyan end, % high scores SquareCol = if ColorMode==bw -> white; true -> yellow end, % game squares ErlFgCol = if ColorMode==bw -> white; true -> red end, % ErlBGCol = if ColorMode==bw -> black; true -> white end, % ErlTxtCol = if ColorMode==bw -> white; true -> black end, % Width = 550, % width of bonk window Height = 550, % Height of bonk window BX = 0, % x-pos for first button DBX = 100, % space between buttons BY = 0, % y-pos for buttons BLineY = 30, % y-pos of button line LogoX = (Width-320) div 2, % x-pos of bonk logo (logo is 320 pix wide) LogoY = BLineY+2, % y-pos of bonk logo ErlLogoX = LogoX + 200, % x-pos of Erlang e ErlLogoY = LogoY + 10, % y-pos of Erlang e SLineY = Height-22, % status line position TextWidth = 50, % text width of status items SX = 2, % x-pos for first status item DSX = TextWidth+94, % pixels between status items SY = SLineY+2, % y-pos status items HiWidth = 100, % width of high score field _HiHeight = 180, % height of the same HiX = Width-HiWidth, % high score text position HiY = BLineY+10, DHY = 20, % space between title & scores SquareSize = 76, % size of each game square SquareSpace = 1, % space between game squares SquareX = 40, SquareY = 65, gs:create(window, bonkWin, GS, [{width, Width}, {height, Height}, {bg, BGCol}, {title, "Bonk the game"}, {iconname, "Bonk!"}, {map, false}]), gs:create(canvas, bonkCanvas, bonkWin, [{width, Width}, {height, Height}, {bg, BGCol}]), gs:create(image, bonkCanvas, [{bitmap,lists:append(bonk_dir(), "bitmaps/bonklogo")}, {coords, [{LogoX, LogoY}]}, {fg, LogoCol}, {bg, BGCol}]), gs:create(image, bonkCanvas, [{bitmap,lists:append(bonk_dir(), "bitmaps/erl-e")}, {coords, [{ErlLogoX, ErlLogoY}]}, {fg, ErlFgCol}, {bg, ErlBGCol}]), gs:create(image, bonkCanvas, [{bitmap,lists:append(bonk_dir(), "bitmaps/erl-text")}, {coords, [{ErlLogoX, ErlLogoY}]}, {fg, ErlTxtCol}]), gs:create(line, bLine, bonkCanvas, [{coords, [{0,BLineY}, {Width,BLineY}]}, {fg, BLineCol}, {width, 2}]), gs:create(line, bLine, bonkCanvas, [{coords, [{0,SLineY}, {Width, SLineY}]}, {fg, SLineCol}, {width, 2}]), gs:create(text, scoreText, bonkCanvas, [{coords, [{SX, SY}]}, {fg, TextCol}, {text, "Score:"}]), gs:create(text, scoreOut, bonkCanvas, [{coords, [{SX+TextWidth, SY}]}, {fg, NrCol}, {width, DSX-TextWidth}, {text, ""}]), gs:create(text, bombText, bonkCanvas, [{coords, [{SX+DSX, SY}]}, {fg, TextCol}, {text, "Bombs:"}]), gs:create(text, bombOut, bonkCanvas, [{coords, [{SX+DSX+TextWidth, SY}]}, {fg, NrCol}, {width, DSX-TextWidth}, {text, ""}]), gs:create(text, bonusText, bonkCanvas, [{coords, [{SX+2*DSX, SY}]}, {fg, TextCol}, {text, "Bonus:"}]), gs:create(text, bonusOut, bonkCanvas, [{coords, [{SX+2*DSX+TextWidth, SY}]}, {fg, NrCol}, {width, DSX-TextWidth}, {text, ""}]), gs:create(text, levelText,bonkCanvas, [{coords, [{SX+3*DSX, SY}]}, {fg, TextCol}, {text, "Level:"}]), gs:create(text, levelOut, bonkCanvas, [{coords, [{SX+3*DSX+TextWidth, SY}]}, {fg, NrCol}, {width, DSX-TextWidth}, {text, ""}]), gs:create(text, hiScoreText, bonkCanvas, [{coords, [{HiX, HiY}]}, {fg, HiHeadCol}, {text, "High Scores"}]), gs:create(text, hiScoreOut, bonkCanvas, [{coords, [{HiX, HiY+DHY}]}, {fg, HiCol}, {justify, left}, {width, HiWidth}]), gs:create(button, newButton,bonkWin, [{x, BX},{y, BY}, {enable,false}, {label, {text, "New Game"}}, {click, true}, {fg, BTextCol}, {bg, BGCol}, {relief, flat}, {activefg, BTextCol}, {activebg, BGCol}, {align, center}]), gs:create(button, endButton,bonkWin, [{x, BX+DBX},{y, BY}, {enable,false}, {label, {text, "End Game"}}, {click, true}, {fg, BTextCol}, {bg, BGCol}, {relief, flat}, {activefg, BTextCol}, {activebg, BGCol}, {align, center}]), gs:create(button, aboutButton,bonkWin, [{x, BX+2*DBX},{y, BY}, {enable,false}, {label, {text, "About"}}, {click, true}, {fg, BTextCol}, {bg, BGCol}, {relief, flat}, {activefg, BTextCol}, {activebg, BGCol}, {align, center}]), gs:create(button, quitButton, bonkWin, [{x, BX+3*DBX},{y, BY}, {enable,false}, {label, {text, "Quit"}}, {click, true}, {fg, BTextCol}, {bg, BGCol}, {relief, flat}, {activefg, BTextCol}, {activebg, BGCol}, {align, center}]), {SqrPids, Bmps} = create_squares(SquareX, SquareY, SquareSize, SquareCol, SquareSpace), gs:config(bonkWin, [{map, true}]), {SqrPids, Bmps, Colors}. create_squares(X, Y, Size, Color, Spc) -> create_squares(X, Y, Size, Color, Spc, 1, 1, [], []). create_squares(_X, _Y, _Size, _Color, _Spc, 4, 5, Pids, Bmps) -> {Pids, Bmps}; create_squares(X, Y, Size, Color, Spc, Row, 5, Pids, Bmps) -> create_squares(X, Y, Size, Color, Spc, Row+1, 1, Pids, Bmps); create_squares(X, Y, Size, Color, Spc, Row, Col, Pids, Bmps) -> Xpos = X+Col*Size+(Col-1)*Spc, Ypos = Y+Row*Size+(Row-1)*Spc, gs:create(rectangle, bonkCanvas, [{coords, [{Xpos,Ypos},{Xpos+Size, Ypos+Size}]}, {bw, 2},{fg, Color},{buttonpress,true}]), Bmp = gs:create(image, bonkCanvas, [{coords, [{Xpos+1, Ypos+1}]}, {bitmap,lists:append(bonk_dir(), "bitmaps/bonktom")}, {buttonpress, true},{fg, Color}]), Pid = bonk_square:start(Bmp), gs:config(Bmp, [{data, Pid}]), create_squares(X, Y, Size, Color, Spc, Row, Col+1, [Pid|Pids], [Bmp|Bmps]). clear_board([]) -> true; clear_board([Square | Rest]) -> gs:config(Square, [{bitmap,lists:append(bonk_dir(), "bitmaps/bonktom")}]), clear_board(Rest). %% Prints the list on the screen. %% The list is on the form [[Score,Name],[Score,Name]..]. display_highscore(ScoreList) -> display_highscore("",ScoreList,0). display_highscore(Scores,[],_N) -> gs:config(hiScoreOut,[{text,Scores}]); display_highscore(Scores,_ScoreList,10) -> % This is max number of items. display_highscore(Scores,[], 10); display_highscore(Scores,[[Score,Name]|Rest],N) -> NewScores = lists:append(Scores,lists:append(lists:append(Score, [32 | Name]), [10])), display_highscore(NewScores,Rest,N+1). %% Reads the highscorelist from the file "bonk.score". %% The list should be an sorted erlang-list. get_highscore() -> case file:consult("bonk.score") of {ok,[Score_list]} -> {Score_list,"./bonk.score"}; {error,_} -> {[],"./bonk.score"} end. %% Prints out the highscorelist and places the new score in the %% list if it is high enough. update_scorelist(SoundPid, Scores) -> case Scores#scores.points of 0 -> true; Score -> {ScoreL,FileName} = get_highscore(), New_scorelist=update_scorelist_2(ScoreL, Score, 0, SoundPid), display_highscore(New_scorelist), case file:open(FileName, write) of {error,_} -> true; {ok,FD} -> io:format(FD,"~w~s~n",[New_scorelist,"."]), file:close(FD) end end. update_scorelist_2([], Score, N, _SoundPid) when N < 10 -> [[integer_to_list(Score),getuser()]]; update_scorelist_2(_, _, N, _SoundPid) when N >= 10 -> []; update_scorelist_2([[Sc, Name] | Rest], Score, N, SoundPid) -> case list_to_integer(Sc) of Sc_int when Sc_int < Score -> if N == 0 -> SoundPid ! best_score; true -> SoundPid ! high_score end, lists:append([[integer_to_list(Score),getuser()]], update_scorelist_3([[Sc,Name]|Rest],N+1)); _Other -> lists:append([[Sc,Name]],update_scorelist_2(Rest, Score, N+1, SoundPid)) end. update_scorelist_3([],_) -> []; update_scorelist_3(_,N) when N >= 10 -> []; update_scorelist_3([Item|Rest],N) -> lists:append([Item],update_scorelist_3(Rest,N+1)). getuser() -> case os:type() of {unix,_} -> lists:delete(10,os:cmd("whoami")); _ -> "Unknown" end. %% Prints out the initial values of scores, bonus, level and bombs. clear_scores(Scores) -> Score = integer_to_list(Scores#scores.points), Bombs = integer_to_list(Scores#scores.bombs), Bonus = integer_to_list(Scores#scores.bonus), Level = integer_to_list(Scores#scores.level), gs:config(scoreOut,{text,Score}), gs:config(bombOut,{text,Bombs}), gs:config(bonusOut,{text,Bonus}), gs:config(levelOut,{text,Level}). %% Removes everything that is present in the message-que. flush() -> receive _X -> flush() after 0 -> true end. sleep(X) -> receive after X -> true end. %% Opens a window and shows the contents of the file: "bonk.txt". %% The window will be removed before the function ends. display_about() -> {BGColor,TextColor,Bfg} = case get(colormode) of bw -> {black, white, white}; _Color -> {black, peachpuff1, orange} end, Wid = 500, Hei = 635, GS = gs:start(), gs:create(window, aboutWin, GS, [{width, Wid}, {height,Hei}, {map, false}, {bg, BGColor}, {title, "About Bonk!"}]), gs:create(canvas, aboutCan, aboutWin, [{width, Wid},{height, Hei}, {bg, black}]), gs:create(button, okButton, aboutWin, [{x, Wid div 2 - 50},{y, Hei - 40}, {label,{text, "Ok"}}, {click, true}, {fg, Bfg}, {bg, BGColor}, {relief, flat}, {activefg, Bfg}, {activebg, BGColor}]), gs:create(text, aboutText, aboutCan, [{width, Wid-30}, {coords, [{15, 0}]}, {fg, TextColor}, {justify, center}]), case file:open(lists:append(bonk_dir(),"bonk.txt"), read) of {ok, Fd} -> write_text(Fd, "", io:get_line(Fd, "")), file:close(Fd); {error, _Reason} -> gs:config(aboutText, {text, "Error: could not read the about file"}) end, gs:config(aboutWin, [{map,true}]), receive {gs, okButton, click, _, _} -> gs:destroy(aboutWin) end. write_text(_Fd, Text, eof) -> gs:config(aboutText, {text, Text}); write_text(Fd, Text, More) -> write_text(Fd, lists:append(Text, More), io:get_line(Fd, "")).