-module(esoko). -define(VERSION, '2002.1104'). -vsn(?VERSION). -author('cpressey@catseye.mb.ca'). -copyright('Copyright (c)2002 Cat`s Eye Technologies. All rights reserved.'). % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions % are met: % % Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in % the documentation and/or other materials provided with the % distribution. % % Neither the name of Cat's Eye Technologies nor the names of its % contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND % CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, % INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE % DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, % OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, % PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, % OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON % ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, % OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY % OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. -export([start/0]). %%% BEGIN esoko.erl %%% %% @spec load_screen(file, filename()) -> ok | {error, Reason} %% @doc Loads a screen from a disk file. load_screen(file, Name) -> case file:open(filename:join( [code:priv_dir(esoko), "screens", "screen." ++ Name]), [read]) of {ok, Io} -> erlang:put(score_target, 0), read_screen(Io, 0, 0), file:close(Io), ok; Else -> Else end. read_screen(File, X, Y) -> case io:get_line(File, '') of eof -> read_screen_tail(X, Y); L -> load_screen_row(string:left(L, 20), X, Y), read_screen(File, X, Y+1) end. read_screen_tail(X, Y) when Y < 20 -> load_screen_row(string:left("", 20), X, Y), read_screen_tail(X, Y+1); read_screen_tail(X, Y) -> ok. load_screen_row([], X, Y) -> nil; load_screen_row([10 | T], X, Y) -> load_screen_row([32 | T], X, Y); load_screen_row([H | T], X, Y) -> erlang:put({X, Y}, H), case H of $$ -> erlang:put(score_target, erlang:get(score_target) + 1), load_screen_row(T, X+1, Y); $@ -> erlang:put(init_position, {X, Y}), erlang:put({X, Y}, $ ), load_screen_row(T, X+1, Y); $+ -> erlang:put(init_position, {X, Y}), erlang:put({X, Y}, $. ), load_screen_row(T, X+1, Y); N -> load_screen_row(T, X+1, Y) end. % Main interface to esoko, which should be called upon starting. start() -> erlang:put(score_target, 1), erlang:put(init_position, {1,1}), load_screen(file, "1"), Screen = gs:start(), Width = 20, Height = 16, {InitX, InitY} = erlang:get(init_position), Window = gs:create(window, Screen, [{title, "Erlang Sokoban starring Rusty the Cat!"}, {width, Width * 32},{height, Height * 32 + 32},{keypress,true}]), gs:create(entry, level_entry, Window, [{text, "1"}, {justify, right}, {x, 0}, {y, 0}, {width, 128}]), gs:create(button, load_button, Window, [{label, {text, "Load"}}, {x, 128}, {y, 0}, {width, 128}]), gs:create(label, esoko_label, Window, [{label, {text, "esoko"}}, {font, {helvetica, bold, 21}}, {justify, center}, {x, 256}, {y, 0}, {width, 128}]), gs:create(button, about_button, Window, [{label, {text, "About..."}}, {x, 512-128}, {y, 0}, {width, 128}]), gs:create(button, quit_button, Window, [{label, {text, "Quit"}}, {x, 512}, {y, 0}, {width, 128}]), gs:create(window, about_dialogue, Screen, [{title, "About esoko..."}, {width, 320}, {height, 280}]), gs:create(label, about_label, about_dialogue, [{label, {text, "Erlang Sokoban " ++ atom_to_list(?VERSION) ++ "\n" ++ "starring Rusty the Cat\n\n(c)2001 Cat's Eye Technologies\n" ++ "http://www.catseye.mb.ca/\n\n" ++ "Original Sokoban game by Hiroyuki Imabayashi\n" ++ "Screens borrowed from xsokoban\n" ++ "http://xsokoban.lcs.mit.edu/xsokoban.html\n\n" ++ "Erlang (c)Ericsson Utvecklings AB\n" ++ "http://www.erlang.se/\n\n" ++ "Rusty the Cat (c)Chris Williams\n" ++ "http://www.ccma.ca/inthumor/\n\n"}}, {font, {screen, 12}}, {justify, center}, {x, 0}, {y, 10}, {width, 320}, {height, 240}]), gs:create(button, close_about_button, about_dialogue, [{label, {text, "OK"}}, {x, 80}, {y, 240}, {width, 160}]), Canvas = gs:create(canvas, Window, [{x,0},{y,32},{width, Width * 32},{height, Height * 32},{bg,black}]), Cells = create_grid(Canvas, Width-1, Height-1), Player = gs:create(image, Canvas, [{coords,[{InitX * 32,InitY * 32}]}, {load_gif, image_file("player")}]), gs:config(Window, {map,true}), loop(Window, Canvas, Cells, Player, InitX, InitY). image_file(Image) -> filename:join([code:priv_dir(esoko), "images", Image ++ ".gif"]). % Create initial images on the canvas. create_grid(Canvas, X, Y) when Y < 0 -> {}; create_grid(Canvas, X, Y) -> erlang:append_element( create_grid(Canvas, X, Y-1), create_grid_row(Canvas, X, Y)). create_grid_row(Canvas, X, Y) when X < 0 -> {}; create_grid_row(Canvas, X, Y) -> erlang:append_element( create_grid_row(Canvas, X-1, Y), create_grid_cell(Canvas, X, Y)). create_grid_cell(Canvas, X, Y) -> case erlang:get({X,Y}) of $# -> gs:create(image, Canvas, [{coords,[{X * 32,Y * 32}]},{load_gif,image_file("wall")}]); $ -> gs:create(image, Canvas, [{coords,[{X * 32,Y * 32}]},{load_gif,image_file("floor")}]); $. -> gs:create(image, Canvas, [{coords,[{X * 32,Y * 32}]},{load_gif,image_file("target")}]); $$ -> gs:create(image, Canvas, [{coords,[{X * 32,Y * 32}]},{load_gif,image_file("crate")}]); $* -> gs:create(image, Canvas, [{coords,[{X * 32,Y * 32}]},{load_gif,image_file("prize")}]); N -> gs:create(image, Canvas, [{coords,[{X * 32,Y * 32}]},{load_gif,image_file("floor")}]) end. % For loading the next level after current level is solved. load_new_screen(Level, Window, Cells, Player) -> gs:config(Window, {cursor, busy}), Result = load_screen(file, Level), mod_grid(Cells, 20-1, 16-1), {InitX, InitY} = erlang:get(init_position), gs:config(Player, [{coords,[{InitX * 32,InitY * 32}]}]), gs:config(Window, {cursor, arrow}), case Result of error -> gs:config(Window, beep), {InitX, InitY}; _ -> {InitX, InitY} end. mod_grid(Cells, X, Y) when Y < 0 -> {}; mod_grid(Cells, X, Y) -> erlang:append_element( mod_grid(Cells, X, Y-1), mod_grid_row(Cells, X, Y)). mod_grid_row(Cells, X, Y) when X < 0 -> {}; mod_grid_row(Cells, X, Y) -> erlang:append_element( mod_grid_row(Cells, X-1, Y), mod_grid_cell(Cells, X, Y)). mod_grid_cell(Cells, X, Y) -> Cell = element(X+1, element(Y+1, Cells)), case erlang:get({X,Y}) of $# -> gs:config(Cell, [{load_gif,image_file("wall")}]); $ -> gs:config(Cell, [{load_gif,image_file("floor")}]); $. -> gs:config(Cell, [{load_gif,image_file("target")}]); $$ -> gs:config(Cell, [{load_gif,image_file("crate")}]); $* -> gs:config(Cell, [{load_gif,image_file("prize")}]); N -> io:fwrite("I don't understand ~w.~n", [N]) end. % Routine to move the player, push blocks, modify score, etc. move(Cells, X, Y, DX, DY) -> case erlang:get({X + DX, Y + DY}) of $# -> {X, Y}; $$ -> case erlang:get({X + DX*2, Y + DY*2}) of $ -> erlang:put({X + DX*2, Y + DY*2}, $$), erlang:put({X + DX, Y + DY}, $ ), gs:config(element(X+DX*2+1, element(Y+DY*2+1, Cells)), {load_gif,image_file("crate")}), gs:config(element(X+DX+1, element(Y+DY+1, Cells)), {load_gif,image_file("floor")}), {X + DX, Y + DY}; $. -> erlang:put({X + DX*2, Y + DY*2}, $*), erlang:put({X + DX, Y + DY}, $ ), gs:config(element(X+DX*2+1, element(Y+DY*2+1, Cells)), {load_gif,image_file("prize")}), gs:config(element(X+DX+1, element(Y+DY+1, Cells)), {load_gif,image_file("floor")}), erlang:put(score_target, erlang:get(score_target) - 1), {X + DX, Y + DY}; N -> {X, Y} end; $* -> case erlang:get({X + DX*2, Y + DY*2}) of $ -> erlang:put({X + DX*2, Y + DY*2}, $$), erlang:put({X + DX, Y + DY}, $.), gs:config(element(X+DX*2+1, element(Y+DY*2+1, Cells)), {load_gif,image_file("crate")}), gs:config(element(X+DX+1, element(Y+DY+1, Cells)), {load_gif,image_file("target")}), erlang:put(score_target, erlang:get(score_target) + 1), {X + DX, Y + DY}; $. -> erlang:put({X + DX*2, Y + DY*2}, $*), erlang:put({X + DX, Y + DY}, $.), gs:config(element(X+DX*2+1, element(Y+DY*2+1, Cells)), {load_gif,image_file("prize")}), gs:config(element(X+DX+1, element(Y+DY+1, Cells)), {load_gif,image_file("target")}), {X + DX, Y + DY}; N -> {X, Y} end; N -> {X + DX, Y + DY} end. % Handle advancing to the next level after current level is solved. next_level(Window, Cells, Player) -> Level = gs:read(level_entry, text), NewLevel = erlang:integer_to_list(erlang:list_to_integer(Level) + 1), gs:config(level_entry, {text, NewLevel}), {X2, Y2} = load_new_screen(NewLevel, Window, Cells, Player), {X2, Y2}. % Main user-interface loop. loop(Window, Canvas, Cells, Player, X, Y) -> gs:config(Player, {coords, [{X*32, Y*32}]}), case erlang:get(score_target) of 0 -> {X3, Y3} = next_level(Window, Cells, Player), loop(Window, Canvas, Cells, Player, X3, Y3); N -> receive {gs, Window, destroy, Data, Args} -> gs:stop(); {gs, quit_button, click, Data, Args} -> self() ! {gs, Window, destroy, Data, Args}, loop(Window, Canvas, Cells, Player, X, Y); {gs, about_button, click, Data, Args} -> gs:config(about_dialogue, {map,true}), loop(Window, Canvas, Cells, Player, X, Y); {gs, close_about_button, click, Data, Args} -> gs:config(about_dialogue, {map,false}), loop(Window, Canvas, Cells, Player, X, Y); {gs, load_button, click, Data, Args} -> Level = gs:read(level_entry, text), {X2, Y2} = load_new_screen(Level, Window, Cells, Player), loop(Window, Canvas, Cells, Player, X2, Y2); {gs, Window, keypress, Data, ['Down' | RArgs]} -> {X2, Y2} = move(Cells, X, Y, 0, 1), loop(Window, Canvas, Cells, Player, X2, Y2); {gs, Window, keypress, Data, ['Up' | RArgs]} -> {X2, Y2} = move(Cells, X, Y, 0, -1), loop(Window, Canvas, Cells, Player, X2, Y2); {gs, Window, keypress, Data, ['Left' | RArgs]} -> {X2, Y2} = move(Cells, X, Y, -1, 0), loop(Window, Canvas, Cells, Player, X2, Y2); {gs, Window, keypress, Data, ['Right' | RArgs]} -> {X2, Y2} = move(Cells, X, Y, 1, 0), loop(Window, Canvas, Cells, Player, X2, Y2); N -> io:format("Unrecognized message: ~p~n",[N]), loop(Window, Canvas, Cells, Player, X, Y) end end. %%% END of esoko.erl %%%