%% File : gsmines.erl %%% Author : %%% Purpose : Ehhh ... %%% Created : 3 Nov 1998 by -module(gsmines). -author('klacke@JB'). -import(lists, [flatten/1,map/2, member/2,foldl/3]). -define(WSZ, 300). %% win size -define(BSZ, 9). %% board size -export([start/0,init/0]). %% One of these for each square on the board -record(sq, { cc, %% coords mine = false, %% true | false text = nil, %% possible text blank = nil, %% possible blank ball = nil, %% possible ball cross = nil}).%% possible cross -record(board, { count = 0, %% number of filled squares arr = make_board()}). %% the array start() -> spawn(gsmines, init, []). init() -> I=gs:start(), Win=gs:create(window, I, [{width, ?WSZ}, {height, ?WSZ + 35}, {title,"Gsmines"}, {map, true}]), Bar = gs:create(menubar, Win, []), Fmb = gs:create(menubutton, Bar, [{label,{text,"Game"}}]), Fmnu = gs:create(menu, Fmb, []), gs:create(menuitem, Fmnu, [{label,{text, "Start"}}]), gs:create(menuitem, Fmnu, [{data, {difficulty, 1}}, {label,{text, "Easy"}}]), gs:create(menuitem, Fmnu, [{data, {difficulty, 2}}, {label,{text, "Medium"}}]), gs:create(menuitem, Fmnu, [{data, {difficulty, 3}}, {label,{text, "Hard"}}]), gs:create(menuitem, Fmnu, [{label,{text, "Explode"}}]), gs:create(menuitem, Fmnu, [{label,{text, "Exit"}}]), gs:create(canvas, can1,Win, [{x,0},{y, 35}, {width,?WSZ},{height,?WSZ}]), gs:config(can1, [{buttonpress,true}]), draw_lines(0), put(difficulty, 2), loop(#board{}, idle). make_board() -> {X,Y,Z} = now(), random:seed(X,Y,Z), make_board(?BSZ, []). make_board(I, Lines) when I < 0 -> list_to_tuple(Lines); make_board(Lno, Lines) -> make_board(Lno-1, [make_line(Lno, ?BSZ, []) | Lines]). make_line(_, I, Ack) when I < 0 -> list_to_tuple(Ack); make_line(Lno, I, Ack) -> R = random:uniform(10), Sq = #sq{cc = {Lno, I}, mine = (R =< get(difficulty))}, make_line(Lno, I-1, [Sq|Ack]). get_sq({X, Y}, Board) -> Line = element(X+1, Board#board.arr), element(Y+1, Line). set_sq(Sq, Board) -> {X,Y} = Sq#sq.cc, Arr = Board#board.arr, Line = element(X+1, Arr), Line2 = setelement(Y+1, Line, Sq), A2 = setelement(X+1, Arr, Line2), Board#board{arr = A2}. draw_lines(X) when X > ?WSZ -> ok; draw_lines(I) -> gs:create(line, can1, [{coords, [{I, 0}, {I, ?WSZ}]}]), gs:create(line, can1, [{coords, [{0, I}, {?WSZ, I}]}]), draw_lines(I+30). incr(Board) -> Board#board{count = 1 + Board#board.count}. decr(Board) -> Board#board{count = Board#board.count - 1}. %% Set a red ball an return new board draw_ball(Board, {X, Y}, Colour) -> Sq = get_sq({X, Y}, Board), Red = gs:create(oval, can1, [ {coords, [{(X * 30) + 5, (Y * 30) + 5}, {(X * 30) + 25, (Y * 30) + 25}]}, {fill, Colour}]), incr(set_sq(Sq#sq{ball = Red}, Board)). %% Draw a cross and return a new board draw_cross(Board, Coord) -> {X0, Y0} = Coord, Coords = map(fun({X, Y}) -> {(30*X0) + X, 30*Y0 + Y} end, [{12,2}, {18,2}, {18, 12}, {28,12}, {28,18}, {18,18}, {18, 28}, {12, 28}, {12, 18}, {2, 18}, {2,12}, {12, 12}]), P = gs:create(polygon, can1, [{coords, Coords}, {fill, black}]), Sq = get_sq(Coord, Board), Sq2 = Sq#sq{cross = P}, incr(set_sq(Sq2, Board)). draw_blank(Coord, Board) -> {X, Y} = Coord, Coords = [{X*30, Y*30}, {(X+1) * 30, Y*30}, {(X+1) * 30, (Y+1) * 30}, {X*30, (Y+1) * 30}], P = gs:create(polygon, can1, [{coords, Coords}, {fill, green}]), Sq = get_sq(Coord, Board), Sq2 = Sq#sq{blank = P}, incr(set_sq(Sq2, Board)). %% handle right button and return a new board cross(Board, Coord) -> Sq = get_sq(Coord, Board), case has_graphic_object(Sq) of cross -> decr(destroy(Sq, Board)); false -> draw_cross(Board, Coord); _ -> Board end. %% destro graphics obj in Sq and return new Sq destroy(Sq) -> if Sq#sq.text /= nil -> gs:destroy(Sq#sq.text), Sq#sq{text = nil}; Sq#sq.ball /= nil -> gs:destroy(Sq#sq.ball), Sq#sq{ball = nil}; Sq#sq.cross /= nil -> gs:destroy(Sq#sq.cross), Sq#sq{cross = nil}; Sq#sq.blank /= nil -> gs:destroy(Sq#sq.blank), Sq#sq{blank = nil}; true -> Sq end. %% If Sq has a graphics object, destroy it and %% and return new board destroy(Sq, Board) -> Sq2 = destroy(Sq), set_sq(Sq2, Board). has_graphic_object(Sq) -> if Sq#sq.text /= nil -> text; Sq#sq.ball /= nil -> ball; Sq#sq.cross /= nil -> cross; Sq#sq.blank /= nil -> blank; true -> false end. %% handle button 1 and return a new board down(Board, Coord) -> Sq = get_sq(Coord, Board), if Sq#sq.mine == true -> explode(Board, red); true -> case has_graphic_object(Sq) of false -> draw_number(Coord, Board); _ -> Board end end. draw_number(Coord, Board) -> Ns = neighbours(Coord), Num = count_mines(Board, Ns), print_number(get_sq(Coord, Board), Board, Num). %% special case, fill greens print_number(Sq, Board, 0) -> {Blanks, Borders} = get_blanks(Sq, Board), B2 = foldl(fun(N, Ack) -> draw_number(N, Ack) end, Board, Borders), foldl(fun(N, Ack) -> draw_blank(N, Ack) end, B2, Blanks); print_number(Sq, Board, Num) -> Str = flatten(io_lib:format("~p", [Num])), T = gs:create(text, can1, [{coords, [to_pix(Sq#sq.cc)]}, {fg, black}, {text, Str}]), Sq2 = Sq#sq{text = T}, incr(set_sq(Sq2, Board)). to_pix({X, Y}) -> {(X*30) + 10, (Y*30) + 8}. %% Return all {Blanks, Borders} originating at Sq get_blanks(Sq, Board) -> get_blanks(Board, neighbours(Sq#sq.cc), [Sq#sq.cc], []). get_blanks(_Board, [], Blanks, Borders) -> {Blanks, Borders}; get_blanks(Board, [H|T], Blanks, Borders) -> case {member(H, Blanks), member(H, Borders)} of {false, false} -> case {has_graphic_object(get_sq(H, Board)), count_mines(Board, neighbours(H))} of {false, 0} -> get_blanks(Board, join(T, neighbours(H)), [H|Blanks], Borders); {false, _} -> get_blanks(Board, T, Blanks, [H|Borders]); {_, _} -> get_blanks(Board, T, Blanks, Borders) end; _ -> get_blanks(Board, T, Blanks, Borders) end. join(L1, L2) -> (L1 -- L2) ++ L2. neighbours({X, Y}) -> [{X1, Y1} || X1 <- [X-1, X, X+1] -- [-1,10], Y1 <- [Y-1, Y, Y+1] -- [-1,10]]. count_mines(Board, Neighbours) -> foldl(fun(N, Ack) -> Sq = get_sq(N, Board), if Sq#sq.mine == true -> 1 + Ack; Sq#sq.mine == false -> Ack end end, 0, Neighbours). loop(Board, S) when S == run, Board#board.count == 100 -> explode(Board, blue); loop(Board, S) -> receive {gs, _, buttonpress,_, [1, X0, Y0 |_]} when S == run -> loop(down(Board, {X0 div 30, Y0 div 30}), S); {gs, _, buttonpress, _, [_, X0, Y0 |_]} when S == run -> loop(cross(Board, {X0 div 30, Y0 div 30}), S); {gs,_Id,destroy,_Data,_Arg} -> exit(normal); {gs,_, _,[],["Exit",_]} -> exit(normal); {gs,_,click, {difficulty, Diff}, _} -> put(difficulty, Diff), loop(Board, S); {gs,_,_,[],["Explode",_]} -> explode(Board, red); {gs,_,_,[],["Start",_]} -> clear(Board), loop(#board{}, run); _ -> loop(Board, S) end. explode(Board, Colour) -> B2 = traverse_board(Board, fun(B,Sq) when Sq#sq.mine == true -> B2 = destroy(Sq, B), draw_ball(B2, Sq#sq.cc, Colour); (B, _Sq) -> B end), loop(B2, idle). clear(Board) -> traverse_board(Board, fun(B, Sq) -> destroy(Sq, B) end). %% Traverse the board, apply Fun and return %% a new board traverse_board(Board, Fun) -> traverse_board(Board, ?BSZ, ?BSZ, Fun). traverse_board(Board, -1, _, _) -> Board; traverse_board(Board, I, -1, Fun) -> traverse_board(Board, I-1, ?BSZ, Fun); traverse_board(Board, I, J, Fun) -> B2 = Fun(Board, get_sq({I, J}, Board)), traverse_board(B2, I, J-1, Fun).