From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/wx/examples/sudoku/Makefile | 66 +++++ lib/wx/examples/sudoku/sudoku.erl | 53 ++++ lib/wx/examples/sudoku/sudoku.hrl | 44 +++ lib/wx/examples/sudoku/sudoku_board.erl | 358 +++++++++++++++++++++++ lib/wx/examples/sudoku/sudoku_game.erl | 503 ++++++++++++++++++++++++++++++++ lib/wx/examples/sudoku/sudoku_gui.erl | 391 +++++++++++++++++++++++++ 6 files changed, 1415 insertions(+) create mode 100755 lib/wx/examples/sudoku/Makefile create mode 100755 lib/wx/examples/sudoku/sudoku.erl create mode 100755 lib/wx/examples/sudoku/sudoku.hrl create mode 100755 lib/wx/examples/sudoku/sudoku_board.erl create mode 100755 lib/wx/examples/sudoku/sudoku_game.erl create mode 100755 lib/wx/examples/sudoku/sudoku_gui.erl (limited to 'lib/wx/examples/sudoku') diff --git a/lib/wx/examples/sudoku/Makefile b/lib/wx/examples/sudoku/Makefile new file mode 100755 index 0000000000..b86c654fdd --- /dev/null +++ b/lib/wx/examples/sudoku/Makefile @@ -0,0 +1,66 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 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% +# + +include ../../vsn.mk +include ../../config.mk + +TOPDIR = ../.. +SRC = . +BIN = . +ERLINC = $(TOPDIR)/include +ERLC = erlc +TESTMODS = sudoku sudoku_board sudoku_game sudoku_gui +TESTTARGETS = $(TESTMODS:%=%.beam) +TESTSRC = $(TESTMODS:%=%.erl) + +# Targets +opt debug: $(TESTTARGETS) +clean: + rm -f $(TESTTARGETS) + rm -f *~ core erl_crash.dump + +docs: + +run: opt + erl -smp -detached -pa $(TOPDIR)/ebin -s sudoku + +ifneq ($(INSIDE_ERLSRC),true) + +%.beam: %.erl + $(ERLC) -W -I$(ERLINC) -bbeam -o$(BIN) $< + +else +EXRELSYSDIR = $(RELSYSDIR)/examples/sudoku +include $(ERL_TOP)/make/otp_release_targets.mk + +docs: + +release_spec: + $(INSTALL_DIR) $(EXRELSYSDIR) + $(INSTALL_DATA) $(TESTSRC) sudoku.hrl $(EXRELSYSDIR) + $(INSTALL_DATA) $(TESTTARGETS) $(EXRELSYSDIR) + +release_tests_spec: + +release_docs_spec: + +endif + + + diff --git a/lib/wx/examples/sudoku/sudoku.erl b/lib/wx/examples/sudoku/sudoku.erl new file mode 100755 index 0000000000..01caeb9524 --- /dev/null +++ b/lib/wx/examples/sudoku/sudoku.erl @@ -0,0 +1,53 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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% +%%%------------------------------------------------------------------- +%%% File : sudoku.erl +%%% Author : Dan Gudmundsson +%%% Description : Sudoku +%%% +%%% Created : 13 Mar 2007 by Dan Gudmundsson +%%%------------------------------------------------------------------- + +-module(sudoku). + +-export([go/0]). + +-compile(export_all). + +-include("sudoku.hrl"). + +start() -> + spawn_link(fun() -> init(halt) end). +go() -> + spawn_link(fun() -> init(keep) end). + +init(Halt) -> + ?TC(sudoku_gui:new(self())), + receive {gfx, GFX} -> ok end, + case sudoku_game:init(GFX) of + Halt -> erlang:halt(); + Stop -> exit(Stop) + end. + +tc(Fun,Mod,Line) -> + case timer:tc(erlang, apply, [Fun,[]]) of + {_,{'EXIT',Reason}} -> exit(Reason); + {T,R} -> + io:format("~p:~p: Time: ~p\n", [Mod, Line, T]), + R + end. diff --git a/lib/wx/examples/sudoku/sudoku.hrl b/lib/wx/examples/sudoku/sudoku.hrl new file mode 100755 index 0000000000..775b563bdc --- /dev/null +++ b/lib/wx/examples/sudoku/sudoku.hrl @@ -0,0 +1,44 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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% +%%%------------------------------------------------------------------- +-include("../../include/wx.hrl"). + +-record(s,{gfx,gen,games=[],p,m,mr,mc,v}). +-define(TC(Cmd), tc(fun() -> Cmd end, ?MODULE, ?LINE)). + +-define(NEW, 121). +-define(EMPTY, 122). +-define(HINT, 123). + +-define(OPEN, 130). +-define(SAVE, 131). +-define(RULES, 132). +-define(CLEAR, 135). +-define(SHOW_ERROR, 136). +-define(PRINT, 137). +-define(PRINT_PRE, 138). +-define(PRINT_PAGE_SETUP, 139). + +-define(TRIVIAL, 240). +-define(EASY, 235). +-define(NORMAL, 230). +-define(HARD, 225). +-define(HARDEST, 210). + +-define(QUIT, ?wxID_EXIT). %% Use OS specific version if available +-define(ABOUT, ?wxID_ABOUT). %% Use OS specific diff --git a/lib/wx/examples/sudoku/sudoku_board.erl b/lib/wx/examples/sudoku/sudoku_board.erl new file mode 100755 index 0000000000..756837582f --- /dev/null +++ b/lib/wx/examples/sudoku/sudoku_board.erl @@ -0,0 +1,358 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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% +%%%------------------------------------------------------------------- +%%% File : sud_board.erl +%%% Author : +%%% Description : Manages the gui board +%%% +%%% Created : 9 Jan 2008 by +%%%------------------------------------------------------------------- +-module(sudoku_board). + +-export([new/1, setup_board/2, clear_board/1, left/1, + get_board_data/1,set_board_data/2, + set_butt/3, butt_correct/3, + draw/3, + %% Callbacks + init/1, handle_sync_event/3, + handle_event/2, handle_info/2, handle_call/3, + code_change/3, terminate/2]). + +-include("sudoku.hrl"). + +-record(state, {win, parent, board=[], pen, fonts=[]}). +-record(sq, {key,val,correct=true,given=false}). +-define(BRD,10). +-define(ARC_R, 10). + +-behaviour(wx_object). + +%% API +new(ParentObj) -> + wx_object:start_link(?MODULE, [ParentObj, self()], []). + +setup_board(Board, Init) -> + wx_object:call(Board, {setup_board, Init}). + +clear_board(Board) -> + wx_object:call(Board, clear_board). + +butt_correct(Board, Key, Correct) -> + wx_object:call(Board, {butt_correct, Key, Correct}). + +set_butt(Board, Indx, Val) when is_integer(Indx) -> + {R,C,_} = sudoku_game:rcm(Indx), + set_butt(Board, {R,C}, Val); +set_butt(Board, Id, Val) -> + wx_object:call(Board, {set_butt, Id, Val}). + +left(Board) -> + wx_object:call(Board, left). + +get_board_data(Board) -> + wx_object:call(Board, get_board_data). +set_board_data(Board, List) -> + wx_object:call(Board, {set_board_data, List}). + + +draw(Board, DC, Size) -> + wx_object:call(Board, {draw, DC, Size}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([ParentObj, ParentPid]) -> + Win = wxPanel:new(ParentObj, [{style, ?wxFULL_REPAINT_ON_RESIZE}]), + wxWindow:setFocus(Win), %% Get keyboard focus + wxWindow:setSizeHints(Win, {250,250}), + wxWindow:connect(Win, paint, [callback]), + wxWindow:connect(Win, size, []), + wxWindow:connect(Win, erase_background, []), + wxWindow:connect(Win, key_up, [{skip, true}]), + wxWindow:connect(Win, left_down, [{skip, true}]), + wxWindow:connect(Win, enter_window, [{skip, true}]), + + %% Init pens and fonts + Pen = wxPen:new({0,0,0}, [{width, 3}]), + Fs0 = [{Sz,wxFont:new(Sz, ?wxSWISS, ?wxNORMAL, ?wxNORMAL,[])} || + Sz <- [8,9,10,11,12,13,14,16,18,20,22,24,26,28,30,34,38,42,44,46]], + TestDC = wxClientDC:new(Win), + CW = fun({Sz,Font},Acc) -> + case wxFont:ok(Font) of + true -> + wxDC:setFont(TestDC, Font), + CH = wxDC:getCharHeight(TestDC), + [{CH,Sz,Font} | Acc]; + false -> + Acc + end + end, + Fs = lists:foldl(CW, [], Fs0), + wxClientDC:destroy(TestDC), + {Win, #state{win=Win, board=[], pen=Pen, fonts=Fs, parent=ParentPid}}. + +handle_sync_event(#wx{event=#wxPaint{}}, _Obj, State = #state{win=Win}) -> + %% io:format("EPaint~n",[]), + Size = wxWindow:getSize(Win), + DC = wxPaintDC:new(Win), + wxDC:destroyClippingRegion(DC), + redraw(DC,Size,State), + wxPaintDC:destroy(DC), + %%io:format("...EPaint~n",[]), + ok. + +handle_event(#wx{event=#wxMouse{type=enter_window}}, State = #state{win=Win}) -> + wxWindow:setFocus(Win), %% Get keyboard focus + {noreply,State}; +handle_event(#wx{event=#wxKey{keyCode=KeyC, x=X,y=Y}}, + S = #state{parent=Pid, win=Win}) -> + Val = if KeyC > 47, KeyC < 58 -> KeyC - $0; + KeyC > 325, KeyC < 336 -> KeyC - 326; %% NUM LOCK + true -> 0 + end, + case get_butt(X,Y,S) of + error -> %% Mac don't get correct coordinates. + Global = wx_misc:getMousePosition(), + {CX,CY} = wxWindow:screenToClient(Win, Global), + case get_butt(CX,CY,S) of + error -> ignore; + Id -> Pid ! {set_val,Id,Val} + end; + Id -> + Pid ! {set_val,Id,Val} + end, + {noreply, S}; +handle_event(#wx{event=#wxMouse{type=left_down,x=X,y=Y}}, + S = #state{parent=Gui, win=F}) -> + Id = get_butt(X,Y,S), + case Id of + error -> ignore; + _ -> create_popup_menu(Gui,Id,X,Y,F) + end, + {noreply, S}; +handle_event(#wx{event=#wxSize{}}, State) -> + redraw(State), + {noreply,State}; +handle_event(_Ev, State) -> + {noreply,State}. + +%%%%%%%%%%%%%%%%%%% + +handle_call({set_butt, Key, 0},_From,S0=#state{board=B0}) -> %% Reset + B = lists:keydelete(Key,2,B0), + S = S0#state{board=B}, + redraw(S), + {reply, ok, S}; + +handle_call({set_butt, Key, Val},_From,S0=#state{board=B0}) -> + case lists:keysearch(Key,2,B0) of + {value, _} -> + B = lists:keyreplace(Key, 2, B0, #sq{key=Key,val=Val}); + false -> + B = [#sq{key=Key, val=Val}|B0] + end, + S = S0#state{board=B}, + redraw(S), + {reply, ok, S}; + +handle_call({butt_correct, Key, Correct},_From, S0=#state{board=B0}) -> + case lists:keysearch(Key,2,B0) of + {value, Butt} -> + B = lists:keyreplace(Key, 2, B0, Butt#sq{key=Key,correct=Correct}); + false -> + B = B0 + end, + S = S0#state{board=B}, + redraw(S), + {reply, ok, S}; + +handle_call({setup_board, Init},_From, State) -> + B = [#sq{given=true, correct=true, key=Key, val=Val} || {Key,Val} <- Init], + S = State#state{board=B}, + redraw(S), + {reply, ok, S}; + +handle_call(clear_board,_From, State = #state{board=B0}) -> + B = [Butt || Butt = #sq{given=true} <- B0], + S = State#state{board=B}, + redraw(S), + Given = [{Key, Val} || #sq{key=Key,val=Val,given=true} <- B], + {reply, Given, S}; +handle_call(get_board_data,_From, S=#state{board=B0}) -> + {reply, B0, S}; +handle_call({set_board_data, B},_From, S0) -> + S = S0#state{board=B}, + redraw(S), + G1 = [{Key, Val} || #sq{key=Key,val=Val,given=true} <- B], + G2 = [{Key, Val} || #sq{key=Key,val=Val,given=false,correct=true} <- B], + G3 = [{Key, Val} || #sq{key=Key,val=Val,given=false,correct=false} <- B], + {reply, G1 ++ G2 ++ G3, S}; +handle_call(left,_From, S = #state{board=B}) -> + Res = 81 - length([ok || #sq{correct=C} <- B, C /= false]), + {reply, Res, S}; +handle_call({draw, DC, Size},_From, S) -> + redraw(DC,Size,S), + {reply, ok, S}. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + +handle_info(Msg, State) -> + {stop, {info, Msg}, State}. + +terminate(_Reason, _State) -> + normal. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_butt(X, Y, #state{win=Win}) -> + {W0,H0} = wxWindow:getSize(Win), + BoxSz = getGeomSz(W0,H0), + %% io:format("~p ~p ~p ~p~n", [{X,Y}, {W0,H0}, BoxSz, calc_pos(X-?BRD,Y-?BRD, BoxSz)]), + case calc_pos(X-?BRD,Y-?BRD, BoxSz) of + Pos = {R,C} when 0 < R, R < 10, 0 < C, C < 10 -> Pos; + _ -> error + end. + +calc_pos(X,Y, BoxSz) -> + {1+(Y*3 div BoxSz), 1+(X*3 div BoxSz)}. + +redraw(S = #state{win=Win}) -> + DC0 = wxClientDC:new(Win), + DC = wxBufferedDC:new(DC0), + Size = wxWindow:getSize(Win), + redraw(DC, Size, S), + wxBufferedDC:destroy(DC), + wxClientDC:destroy(DC0), + ok. + +redraw(DC, Size, S) -> + wx:batch(fun() -> + wxDC:setBackground(DC, ?wxWHITE_BRUSH), + wxDC:clear(DC), + BoxSz = draw_board(DC,Size,S), + F = sel_font(BoxSz div 3,S#state.fonts), + [draw_number(DC,F,BoxSz,Sq) || Sq <- S#state.board] + end). + +sel_font(_BS,[{_H,_Sz,F}]) -> + %% io:format("Font sz ~p height ~p in BS ~p~n",[_Sz,_H, _BS]), + F; +sel_font(BS,[{H,_Sz,F}|_]) when BS > (H + 6) -> + %% io:format("Font sz ~p height ~p in BS ~p~n",[_Sz,H, BS]), + F; +sel_font(BS,[_|Fs]) -> + sel_font(BS,Fs). + +draw_number(DC,F,Sz,#sq{key={R,C},val=Num,given=Bold,correct=Correct}) -> + {X,Y} = get_coords(Sz,R-1,C-1), + TBox = Sz div 3, + if Bold -> + wxFont:setWeight(F,?wxBOLD), + wxDC:setTextForeground(DC,{0,0,0}); + Correct =:= false -> + wxFont:setWeight(F,?wxNORMAL), + wxDC:setTextForeground(DC,{255,40,40,255}); + true -> + wxFont:setWeight(F,?wxNORMAL), + wxDC:setTextForeground(DC,{50,50,100,255}) + end, + wxDC:setFont(DC,F), + CH = (TBox - wxDC:getCharHeight(DC)) div 2, + CW = (TBox - wxDC:getCharWidth(DC)) div 2, + wxDC:drawText(DC, integer_to_list(Num), {X+CW,Y+CH+1}), + ok. + +get_coords(Sz,R,C) -> + TBox = Sz div 3, + R1 = R div 3, + R2 = R rem 3, + C1 = C div 3, + C2 = C rem 3, + {?BRD + C1*Sz + C2*TBox, + ?BRD + R1*Sz + R2*TBox}. + +draw_board(DC,{W0,H0},#state{pen=Pen}) -> + BoxSz = getGeomSz(W0,H0), + BS = ?BRD+3*BoxSz, + + wxPen:setWidth(Pen, 3), + wxPen:setColour(Pen, {0,0,0}), + wxDC:setPen(DC,Pen), + + wxDC:drawRoundedRectangle(DC, {?BRD,?BRD,3*BoxSz+1,3*BoxSz+1}, + float(?ARC_R)), + %% Testing DrawLines + wxDC:drawLines(DC, [{?BRD+BoxSz, ?BRD}, {?BRD+BoxSz, BS}]), + wxDC:drawLine(DC, {?BRD+BoxSz*2, ?BRD}, {?BRD+BoxSz*2, BS}), + wxDC:drawLine(DC, {?BRD, ?BRD+BoxSz}, {BS, ?BRD+BoxSz}), + wxDC:drawLine(DC, {?BRD, ?BRD+BoxSz*2}, {BS, ?BRD+BoxSz*2}), + + %% Draw inside lines + wxPen:setWidth(Pen, 1), + wxDC:setPen(DC,Pen), + TBox = BoxSz div 3, + wxDC:drawLine(DC, {?BRD+TBox, ?BRD}, {?BRD+TBox, BS}), + wxDC:drawLine(DC, {?BRD+TBox*2, ?BRD}, {?BRD+TBox*2, BS}), + wxDC:drawLine(DC, {?BRD+TBox+BoxSz, ?BRD}, {?BRD+TBox+BoxSz, BS}), + wxDC:drawLine(DC, {?BRD+TBox*2+BoxSz, ?BRD}, {?BRD+TBox*2+BoxSz, BS}), + wxDC:drawLine(DC, {?BRD+TBox+BoxSz*2, ?BRD}, {?BRD+TBox+BoxSz*2, BS}), + wxDC:drawLine(DC, {?BRD+TBox*2+BoxSz*2, ?BRD}, {?BRD+TBox*2+BoxSz*2, BS}), + %% Vert + wxDC:drawLine(DC, {?BRD, ?BRD+TBox}, {BS, ?BRD+TBox}), + wxDC:drawLine(DC, {?BRD, ?BRD+TBox*2}, {BS, ?BRD+TBox*2}), + wxDC:drawLine(DC, {?BRD, ?BRD+TBox+BoxSz}, {BS, ?BRD+TBox+BoxSz}), + wxDC:drawLine(DC, {?BRD, ?BRD+TBox*2+BoxSz}, {BS, ?BRD+TBox*2+BoxSz}), + wxDC:drawLine(DC, {?BRD, ?BRD+TBox+BoxSz*2}, {BS, ?BRD+TBox+BoxSz*2}), + wxDC:drawLine(DC, {?BRD, ?BRD+TBox*2+BoxSz*2}, {BS, ?BRD+TBox*2+BoxSz*2}), + BoxSz. + +getGeomSz(W,H) -> + Small = if W < H -> W; true -> H end, + (Small - 2*?BRD) div 3. + + +%% popupmenu + +create_popup_menu(GFX,Butt,X,Y,Frame) -> + Port = wx:get_env(), + spawn_link(fun() -> create_popup_menu1(GFX,Butt,Port,X,Y,Frame) end). + +create_popup_menu1(GFX,Butt,Port,X,Y,Frame) -> + wx:set_env(Port), + PopupMenu = wxMenu:new(), + create_popup_menu2(1, PopupMenu), + + wxEvtHandler:connect(PopupMenu, command_menu_selected), + wxWindow:popupMenu(Frame,PopupMenu,X,Y), + receive + #wx{event=#wxCommand{type=command_menu_selected},id=10} -> + GFX ! {set_val,Butt,0}; + #wx{event=#wxCommand{type=command_menu_selected},id=What} -> + GFX ! {set_val,Butt,What} + end. + +create_popup_menu2(N,PP) when N > 9 -> + wxMenu:append(PP, 10, "Clear"); +create_popup_menu2(N,PP) -> + wxMenu:append(PP, N,integer_to_list(N)), + create_popup_menu2(N+1,PP). + diff --git a/lib/wx/examples/sudoku/sudoku_game.erl b/lib/wx/examples/sudoku/sudoku_game.erl new file mode 100755 index 0000000000..470aee0e3b --- /dev/null +++ b/lib/wx/examples/sudoku/sudoku_game.erl @@ -0,0 +1,503 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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(sudoku_game). +-compile(export_all). +-include("sudoku.hrl"). + +init(GFX) -> + Empty = empty_table(#s{}), + Add = fun({Butt,Val},SN) -> + validate(rcm(Butt),Val,false,SN) + end, + + Game = test(), + GFX ! {init, Game}, + Self = self(), + Gen = spawn_opt(fun() -> create_games(levels(),Self) end, + [link, {priority,low}]), + loop(lists:foldl(Add,Empty#s{gfx=GFX, gen=Gen},Game)). + +%%%%%%%%%%%%%%%%%% Game Engine %%%%%%%%%%%%%%%%%% + +empty_table(S) -> + Nine = lists:seq(1,9), + D = gb_sets:from_ordset(Nine), + Mat = list_to_tuple([D || _ <- Nine]), + Poss = list_to_tuple([D || _ <- lists:seq(1,9*9)]), + Vals = list_to_tuple([0 || _ <- lists:seq(1,9*9)]), + Must = list_to_tuple([gb_sets:empty() || _ <- lists:seq(1,9*3)]), + S#s{p=Poss,m=Mat,mr=Must,mc=Must,v=Vals}. + +loop(S0 = #s{gfx = Gfx, v=Vs}) -> + receive + quit -> + halt; + {'EXIT', Gfx, Reason} -> + io:format("The GUI crashed: ~p~n", [Reason]); + {validate, Butt, Val} -> + Ix = indx(Butt), + case element(Ix,Vs) of + Val -> loop(S0); + 0 -> + S = validate(rcm(Butt),Val,true,S0), + loop(S); + _ -> + S1 = S0#s{v=setelement(Ix,Vs,0)}, + S2 = rebuild_all(rcm(Butt),S1), + S = validate(rcm(Butt),Val,true,S2), + loop(S) + end; + {loaded, Game} -> + S1 = empty_table(S0), + Add = fun({Butt,Val},SN) -> + validate(rcm(Butt),Val,true,SN) + end, + loop(lists:foldl(Add,S1,Game)); + {op,?EMPTY} -> + loop(empty_table(S0)); + {op,?NEW, Level} -> + case find_game(Level,S0) of + {Game, S1} -> + S0#s.gen ! {gen_game, Level}, + Gfx ! {busy,start}, + Gfx ! {init, Game}; + false -> + S1 = S0, + Gfx ! {busy,start}, + Temp = new_game(S0), + Game = pick_shown(Temp,Level,Gfx), + S0#s.gen ! {gen_game, Level}, + Game + end, + S2 = empty_table(S1), + Add = fun({Butt,Val},SN) -> + validate(rcm(Butt),Val,false, SN) + end, + Gfx ! {init, Game}, + Gfx ! {busy,stop}, + loop(lists:foldl(Add,S2,Game)); + {solve, All} -> + Res = solve(S0, All), + [Gfx ! {set_val, Ind, Val} || {Ind,Val} <- element(2,Res)], + loop(S0); + {get_game, Pid} -> + Pid ! {game, get_known(S0)}, + loop(S0); + {game, Game} -> + loop(S0#s{games=[Game|S0#s.games]}); + CMD -> + io:format("Game loop got ~p~n", [CMD]), + ?MODULE:loop(S0) + end. + +validate({R,C,_M},0,Send,St = #s{gfx=Gfx}) -> + if Send -> Gfx ! {correct, {R,C}}; true -> ok end, + St; +validate(RCM={R,C,_M},Val,Send,St = #s{gfx=Gfx,v=Vs}) -> + S = poss(RCM,St), + case gb_sets:is_member(Val,S) of + true -> + if Send -> Gfx ! {correct, {R,C}}; true -> ok end, + add(RCM,Val,St); + false -> + if Send -> Gfx ! {wrong, {R,C}}; true -> ok end, + St#s{v=setelement(indx(R,C),Vs,Val)} + end. + +rebuild_all(_, S0) -> + Solved = get_known(S0), + S1 = empty_table(S0), + lists:foldl(fun({Indx,Val},Acc) -> + add(rcm(Indx),Val,Acc) + end, S1, Solved). + +is_ok({RI,CI,MI}, Vals) -> + [Ri,Ci,Mi] = all(RI,CI,MI), + case element(indx(RI,CI),Vals) of + 0 -> true; + Val -> + Vs = [[element(indx(R,C),Vals)||{R,C} <- Obs, + not ((R == RI) and (C == CI))] + || Obs <- [Ri,Ci,Mi]], + not lists:member(Val,lists:flatten(Vs)) + end. + +test() -> %% Known to solvable + [{{1,2},6}, {{1,4},1}, {{1,6},4}, {{1,8},5}, + {{2,3},8}, {{2,4},3}, {{2,6},5}, {{2,7},6}, + {{3,1},2}, {{3,9},1}, + {{4,1},8}, {{4,4},4}, {{4,6},7}, {{4,9},6}, + {{5,3},6}, {{5,7},3}, + {{6,1},7}, {{6,4},9}, {{6,6},1}, {{6,9},4}, + {{7,1},5}, {{7,9},2}, + {{8,3},7}, {{8,4},2}, {{8,6},6}, {{8,7},9}, + {{9,2},4}, {{9,4},5}, {{9,6},8}, {{9,8},7}]. + +new_game(S) -> + {X,Y,Z} = erlang:now(), + random:seed(Y,X,Z), + case new_game(1,1,gb_sets:empty(),empty_table(S#s{}),[], 0) of + stop -> new_game(S); + Game -> Game + end. + + +new_game(_,_,_,_St,_Acc,Cnt) when Cnt > 200 -> + %% Backtracked 200 times, Bad path lets start over + stop; +new_game(R,C,BT,St,Acc,Cnt) when R < 10, C < 10 -> + M = mat(R,C), + U = poss({R,C,M},St), + S = gb_sets:difference(U,BT), + case gb_sets:size(S) of + 0 -> + [{{BR,BC},BVal,BBT,BST}|BAcc] = Acc, + new_game(BR,BC,gb_sets:add(BVal,BBT),BST,BAcc,Cnt+1); + Size -> + Ind = random:uniform(Size), + V = lists:nth(Ind,gb_sets:to_list(S)), + new_game(R,C+1,gb_sets:empty(), + add({R,C,M},V,St), + [{{R,C},V,BT,St}|Acc], Cnt) + end; +new_game(R,_C,Bt,S,Acc,Cnt) when R < 10 -> + new_game(R+1,1,Bt,S,Acc,Cnt); +new_game(_,_,_,S,_Acc,_Cnt) -> +%% io:format("Backtracked ~p ~n",[_Cnt]), + S. + +pick_shown(S0, Level, Gfx) -> + Given = gb_sets:from_ordset([I || I <- lists:seq(1,9*9)]), + get_known(pick_shown(Given,Given,S0,level(Level),Gfx)). + +get_known(#s{v=Vals}) -> + lists:foldl(fun(Index,Acc) -> + case element(Index,Vals) of + 0 -> Acc; + Val -> + {R,C,_} = rcm(Index), + [{{R,C},Val}|Acc] + end + end, [], lists:seq(1,9*9)). + +pick_shown(Given,Left,S0,Level,Gfx) -> + LeftSz = gb_sets:size(Left), + GivenSz = gb_sets:size(Given), + if LeftSz == 0 -> + io:format("No left ~p~n", [GivenSz]), + S0; + GivenSz < Level -> + io:format("Below level ~p ~p~n", [GivenSz,Level]), + S0; + true -> + Ran = random:uniform(LeftSz), + V = lists:nth(Ran,gb_sets:to_list(Left)), + S1 = rebuild_all(rcm(V),S0#s{v=setelement(V,S0#s.v,0)}), + case solve(S1, true) of + {true, _, _} -> + catch Gfx ! {working, 100-LeftSz}, + pick_shown(gb_sets:delete(V,Given), + gb_sets:delete(V,Left), + S1, Level,Gfx); + {false,_,_} -> + pick_shown(Given,gb_sets:delete(V,Left), + S0, Level,Gfx) + end + end. + +solve(St=#s{v=Vals},All) -> + Unsolved = [I || I <- lists:seq(1,9*9), element(I,Vals) == 0], + solve(Unsolved, All, St, [], [], lists:reverse(Unsolved)). + +solve(Rem, false, _St, [Solved|_], Unsolved, _) -> {true, [Solved], Rem ++ Unsolved}; +solve([], _, _St, Solved, [], _) -> {true, Solved, []}; +solve([], _, _St, Solved, Unsolved, Unsolved) -> {false, Solved, Unsolved}; +solve([], _, St, Solved, Unsolved, _Orig) -> + solve(Unsolved,true,St,Solved,[],lists:reverse(Unsolved)); +solve([Index|Rest],All, St, S, US, Orig) -> + RCM = rcm(Index), + Poss = poss(RCM,St), + case gb_sets:size(Poss) of + 1 -> + %% io:format("S1 ~n",[]), + [Val] = gb_sets:to_list(Poss), + solve(Rest, All, add(RCM,Val,St), [{Index,Val}|S],US,Orig); + _ -> + case solve_1(RCM, Poss, St) of + false -> + solve(Rest, All, St, S, [Index|US],Orig); + Val -> + solve(Rest, All, add(RCM,Val,St), [{Index,Val}|S],US,Orig) + end + end. + +solve_1(RCM={R,C,_M}, Avail, St) -> + All = all(RCM), + Poss = fun({RI,CI},Acc) when (RI == R) and (CI == C) -> Acc; + ({RI,CI},Acc) -> gb_sets:union(poss(rcm({RI,CI}),St),Acc) + end, + D = fun({RI,CI},Acc) when (RI == R) and (CI == C) -> + io:format("~p:~p: ignore~n",[RI,CI]), + Acc; + ({RI,CI},Acc) -> + Res = gb_sets:union(poss(rcm({RI,CI}),St),Acc), + io:format("~p:~p: ~p => ~p ~n",[RI,CI,gb_sets:to_list(poss(rcm({RI,CI}),St)),gb_sets:to_list(Res)]), + Res + end, + solve_2(All,{Poss,D},Avail). + +solve_2([],_, _) -> false; +solve_2([First|R],{Poss,D},Avail) -> + All = lists:foldl(Poss, gb_sets:empty(), First), + Res = gb_sets:difference(Avail, All), + case gb_sets:size(Res) of + 1 -> + %% lists:foldl(D, gb_sets:empty(), First), + %% io:format("Poss: ~w~nA: ~p O:~p ~n",[First,gb_sets:to_list(Avail),gb_sets:to_list(All)]), + [Val] = gb_sets:to_list(Res), + Val; + _ -> + solve_2(R,{Poss,D},Avail) + end. + +all({RI,CI,MI}) -> all(RI,CI,MI). +all(RI,CI,MI) -> + MR = ((MI-1) div 3)*3, + MC = ((MI-1) rem 3)*3, + Ri = [{RI,N} || N <- lists:seq(1,9)], + Ci = [{N,CI} || N <- lists:seq(1,9)], + Mi = [{1+MR,1+MC},{1+MR,2+MC},{1+MR,3+MC}, + {2+MR,1+MC},{2+MR,2+MC},{2+MR,3+MC}, + {3+MR,1+MC},{3+MR,2+MC},{3+MR,3+MC}], + [Ri,Ci,Mi]. + +other_mats(N) -> + if N < 4 -> P1=3, P2= 6; + N < 7 -> P1=-3,P2= 3; + true -> P1=-6,P2=-3 + end, + case (N-1) rem 3 of + 0 -> [N+1,N+2,N+P1,N+P2]; + 1 -> [N-1,N+1,N+P1,N+P2]; + 2 -> [N-2,N-1,N+P1,N+P2] + end. + +check_must(S=#s{p=Poss,m=MS,mr=MR0,mc=MC0}) -> + List = lists:seq(1,9), + {MR,MC} = lists:foldl(fun(Val,{MRT,MCT}) -> + check_must2(List,Val,Poss,MS,MRT,MCT) + end, {MR0,MC0}, List), + S#s{mr=MR,mc=MC}. + +check_must2([M|Rest],Val,Poss,Ms,MR0,MC0) -> + case gb_sets:is_member(Val, element(M,Ms)) of + true -> + {Rows,Cols} = rc_in_mat(M), + MR1 = check_must3(Rows,Val,Poss,row,MR0), + MC1 = check_must3(Cols,Val,Poss,col,MC0), + check_must2(Rest,Val,Poss,Ms,MR1,MC1); + false -> + check_must2(Rest,Val,Poss,Ms,MR0,MC0) + end; +check_must2([],_,_,_,MR,MC) -> {MR,MC}. + +check_must3({F1,F2,F3},Val,Check,Type,Must0) -> + R1 = not gb_sets:is_member(Val, get_poss(F1,Check,gb_sets:empty())), + R2 = not gb_sets:is_member(Val, get_poss(F2,Check,gb_sets:empty())), + R3 = not gb_sets:is_member(Val, get_poss(F3,Check,gb_sets:empty())), + %% io:format("M=~p ~p ~p ~p ~p~n",[M,[R1,R2,R3],gb_sets:to_list(element(F1,Check)),gb_sets:to_list(element(F2,Check)),gb_sets:to_list(element(F3,Check))]), + if R1,R2 -> update_must(Type,F3,Val,Must0); + R1,R3 -> update_must(Type,F2,Val,Must0); + R2,R3 -> update_must(Type,F1,Val,Must0); + true -> Must0 + end. + +update_must(Type,[Indx|_],Val,Must) -> + N = mindx(Type, Indx), + %% io:format("~p ~p ~p must contain ~p~n",[Type,N,rcm(Indx),Val]), + Set = element(N,Must), + setelement(N,Must, gb_sets:add(Val,Set)). + +add(RCM={R,C,M},Val,S=#s{p=P0,m=MS,v=Vals,mr=MR0,mc=MC0}) -> + Ri = mindx(R,M), + Ci = mindx(M,C), + MR = delete(Val,Ri,MR0), + MC = delete(Val,Ci,MC0), + P1 = setelement(indx(RCM),P0,gb_sets:empty()), + check_must(S#s{p=delete(Val,lists:flatten(all(RCM)),P1), + m=delete(Val,M,MS), + mr=MR,mc=MC, + v=setelement(indx(RCM),Vals,Val)}). + +poss(RCM={R,C,M}, #s{p=P,v=Vals,mr=MR,mc=MC}) -> + I = indx(R,C), + case element(I, Vals) of + 0 -> + Rm = mindx(R,M), + Cm = mindx(M,C), + T1 = gb_sets:intersection(element(Rm,MR),element(Cm,MC)), + case gb_sets:size(T1) of + 1 -> T1; + _ -> + Not = get_nots(RCM,MR,MC), + gb_sets:difference(element(I,P),Not) + end; + _ -> + gb_sets:empty() + end. + +get_nots({R,C,M},MR,MC) -> + [RM1,RM2,CM1,CM2] = other_mats(M), + R1 = get_poss([mindx(R,RM1),mindx(R,RM2)],MR,gb_sets:empty()), + R2 = get_poss([mindx(CM1,C),mindx(CM2,C)],MC,R1), + %% io:format("~p:~p:~p ~p ~p~n", + %% [C,CM1,CM2, + %% gb_sets:to_list(element(mindx(CM1,C),MC)), + %% gb_sets:to_list(element(mindx(CM2,C),MC))]), + R2. + +get_poss([],_,Tot) -> Tot; +get_poss([H|R],What,Tot) -> + %% io:format("~p~n",[H]), + get_poss(R,What, gb_sets:union(element(H,What),Tot)). + +r2rs(R) -> + R0 = (R-1)*3, + [R0+1,R0+2,R0+3]. + +c2cs(C) -> + C0 = (C-1) rem 9, + [C0+1, C0+10, C0+19]. + +mindx(row,Indx) -> + {R,_C,M} = rcm(Indx), + mindx(R,M); +mindx(col,Indx) -> + {_R,C,M} = rcm(Indx), + mindx(M,C); + +mindx(R,M) -> + 1+(R-1)*3 + (M-1) rem 3. + +rcm(Indx) when is_integer(Indx) -> + rcm({((Indx-1) div 9)+1, (Indx-1) rem 9+1}); +rcm({R,C}) -> + M = mat(R,C), + {R,C,M}. +mat(R,C) -> + 1+(C-1) div 3 + ((R-1) div 3)*3. + +rc_in_mat(M) -> + R1 = 1+3*((M-1) div 3), + C1 = 1+3*((M-1) rem 3), + {{[indx({R1+0,C1+0}),indx({R1+0,C1+1}),indx({R1+0,C1+2})], + [indx({R1+1,C1+0}),indx({R1+1,C1+1}),indx({R1+1,C1+2})], + [indx({R1+2,C1+0}),indx({R1+2,C1+1}),indx({R1+2,C1+2})]}, + + {[indx({R1+0,C1+0}),indx({R1+1,C1+0}),indx({R1+2,C1+0})], + [indx({R1+0,C1+1}),indx({R1+1,C1+1}),indx({R1+2,C1+1})], + [indx({R1+0,C1+2}),indx({R1+1,C1+2}),indx({R1+2,C1+2})]}}. + +indx(Indx) when is_integer(Indx) -> Indx; +indx({Row, Col}) -> + indx(Row,Col); +indx({Row, Col,_}) -> + indx(Row,Col). +indx(Row, Col) -> + (Row-1)*9+Col. + +delete(_Val,[],S0) -> S0; +delete(Val,[I1|R],S0) -> + I = if is_integer(I1) -> I1; + true -> indx(I1) + end, + S = setelement(I,S0,gb_sets:delete_any(Val, element(I,S0))), + delete(Val,R,S); +delete(Val,I,S) -> + setelement(I,S,gb_sets:delete_any(Val, element(I,S))). + +%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Pre generate games on low priority +create_games(Levels,Engine) -> + gen_loop(Levels, Engine, 5). + +gen_loop([], Engine,_) -> + receive + {gen_game, Level} -> + gen_loop([Level], Engine,5) + end; +gen_loop([Level|Ls], Engine, N) when N > 0 -> + Empty = empty_table(#s{}), + Temp = new_game(Empty), + Game = pick_shown(Temp,Level,undefined), + ResLev = length(Game), + Engine ! {game, {ResLev, Game}}, + case ResLev =< level(Level) of + true -> + gen_loop(Ls,Engine, 5); + false -> + gen_loop([Level|Ls],Engine, N-1) + end; +gen_loop([_|Ls],Engine, _) -> + gen_loop(Ls,Engine, 5). + +find_game(_, #s{games=[]}) -> false; +find_game(hardest, S = #s{games=Gs0}) -> + Hard = level(hard), + case lists:sort(Gs0) of + [{Level,G}|Gs] when Level < (Hard-5) -> + {G, S#s{games=Gs}}; + _ -> false + end; +find_game(Level, S = #s{games=Gs0}) -> + case find_game2(level(Level), lists:reverse(lists:sort(Gs0)), []) of + false -> false; + {Game, Gs} -> {Game,S#s{games=Gs}} + end. + +find_game2(Hard, [{Level,G}|Gs], Acc) when Level =< Hard, Level > (Hard-5) -> + {G, Gs ++ Acc}; +find_game2(Hard, [G|Gs], Acc) -> + find_game2(Hard, Gs, [G|Acc]); +find_game2(_Hard, [], _ ) -> false. + +levels() -> + [trivial,easy,normal,hard,hardest]. + +level(Level) when is_atom(Level) -> + case Level of + all -> 100; + trivial -> 40; + easy -> 35; + normal -> 30; + hard -> 25; + hardest -> 0 + end; +level(Int) when is_integer(Int) -> + if + Int =< 20 -> hardest; + Int =< 25 -> hard; + Int =< 30 -> normal; + Int =< 35 -> easy; + true -> trivial + end. + + + diff --git a/lib/wx/examples/sudoku/sudoku_gui.erl b/lib/wx/examples/sudoku/sudoku_gui.erl new file mode 100755 index 0000000000..4aaecfe086 --- /dev/null +++ b/lib/wx/examples/sudoku/sudoku_gui.erl @@ -0,0 +1,391 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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% +%%%------------------------------------------------------------------- +%%% File : sudoku_gui.erl +%%% Author : +%%% Description : The Gui and it's event loop +%%% +%%% Created : 9 Jan 2008 by +%%%------------------------------------------------------------------- +-module(sudoku_gui). + +-export([init/1, handle_info/2, handle_call/3, handle_event/2, + terminate/2, code_change/3]). + +-compile(export_all). + +-behaviour(wx_object). + +-include("sudoku.hrl"). + +-import(sudoku_game, [indx/1]). + +%%%%%%%%%% Graphic engine %%%%%%%%%%%%%% + +-record(gs,{board,show_err=true,level=hard,game,frame,orig=[], print_d, print_psdd}). + +new(Game) -> + wx:new(), + wx_object:start_link(?MODULE, [Game], []). + +%%%%%%%%%%%%%%%%%%%%% Server callbacks %%%%%%%%%%%%% + +init([Game]) -> + {Frame, Board} = wx:batch(fun() -> create_window() end), + Game ! {gfx, self()}, + {Frame, init_printer(#gs{board=Board,game=Game,frame=Frame})}. + +create_window() -> + Frame = wxFrame:new(wx:null(), -1, "Sudoku", []), + + wxFrame:createStatusBar(Frame,[]), + wxFrame:connect(Frame, close_window), + + MenuBar = wxMenuBar:new(), + File = wxMenu:new([]), + Opt = wxMenu:new([]), + Help = wxMenu:new([]), + + wxMenu:append(File, ?NEW, "&New Game"), + wxMenu:append(File, ?OPEN, "&Open Game"), + wxMenu:append(File, ?SAVE, "&Save Game"), + wxMenu:appendSeparator(File), + wxMenu:append(File, ?PRINT, "Print"), + wxMenu:append(File, ?PRINT_PAGE_SETUP, "Page Setup"), + wxMenu:append(File, ?PRINT_PRE, "Print Preview"), + wxMenu:appendSeparator(File), + wxMenu:append(File, ?QUIT, "&Quit Game"), + + wxMenu:append(Help, ?RULES, "Rules"), + wxMenu:append(Help, ?ABOUT, "About"), + + wxMenu:appendRadioItem(Opt, ?TRIVIAL, "Level: Trivial"), + wxMenu:appendRadioItem(Opt, ?EASY, "Level: Easy"), + LItem = wxMenu:appendRadioItem(Opt, ?NORMAL, "Level: Normal"), + wxMenu:appendRadioItem(Opt, ?HARD, "Level: Hard"), + wxMenu:appendRadioItem(Opt, ?HARDEST, "Level: Hardest"), + wxMenu:appendSeparator(Opt), + EItem = wxMenu:appendCheckItem(Opt, ?SHOW_ERROR, "Show errors"), + + wxMenuBar:append(MenuBar, File, "&File"), + wxMenuBar:append(MenuBar, Opt, "O&ptions"), + wxMenuBar:append(MenuBar, Help, "&Help"), + + wxFrame:setMenuBar(Frame, MenuBar), + wxFrame:connect(Frame, command_menu_selected), + + MainSz = wxBoxSizer:new(?wxVERTICAL), + Top = wxBoxSizer:new(?wxHORIZONTAL), + + Panel = wxPanel:new(Frame), + NewGame = wxButton:new(Panel, ?NEW, [{label,"New Game"}]), + wxButton:connect(NewGame, command_button_clicked), + Empty = wxButton:new(Panel, ?EMPTY, [{label,"Empty Board "}]), + wxButton:connect(Empty, command_button_clicked), + Clean = wxButton:new(Panel, ?CLEAR, [{label,"Clear"}]), + wxButton:connect(Clean, command_button_clicked), + Hint = wxButton:new(Panel, ?HINT, [{label, "Hint"}]), + wxButton:connect(Hint, command_button_clicked), + + wxSizer:addSpacer(Top,2), + SF = wxSizerFlags:new(), + wxSizerFlags:proportion(SF,1), + wxSizer:add(Top, NewGame, wxSizerFlags:left(SF)), + wxSizer:addSpacer(Top,3), + wxSizer:add(Top, Empty, wxSizerFlags:center(SF)), + wxSizer:addSpacer(Top,3), + wxSizer:add(Top, Clean, wxSizerFlags:center(SF)), + wxSizer:addSpacer(Top,3), + wxSizer:add(Top, Hint, wxSizerFlags:right(SF)), + + wxSizer:addSpacer(MainSz,5), + wxSizer:add(MainSz, Top, wxSizerFlags:center(wxSizerFlags:proportion(SF,0))), + wxSizer:addSpacer(MainSz,10), + + Board = sudoku_board:new(Panel), + + wxSizer:add(MainSz, Board, wxSizerFlags:proportion(wxSizerFlags:expand(SF),1)), + wxWindow:setSizer(Panel,MainSz), + wxSizer:fit(MainSz, Frame), + wxSizer:setSizeHints(MainSz,Frame), + wxWindow:show(Frame), + %% Check after append so it's initialized on all platforms + wxMenuItem:check(LItem), + wxMenuItem:check(EItem), + {Frame, Board}. + +status(Win, F, A) -> + Str = lists:flatten(io_lib:format(F, A)), + wxFrame:setStatusText(Win, Str). + +%%%%%%%%%%%%%%%% Info i.e. messages %%%%%%%%%%%%%%%%%%%%% + +handle_info(quit, S=#gs{game=G,frame=F}) -> + wxWindow:close(F), + wx_core:quit(), + G ! quit, + {stop, shutdown, S}; + +handle_info({init, Init}, S = #gs{board=Board,frame=F}) -> + sudoku_board:setup_board(Board, Init), + status(F, "Given ~p Left ~p", [length(Init), sudoku_board:left(Board)]), + {noreply, S#gs{orig=[indx(Id)||{Id,_}<-Init]}}; +handle_info({correct, ButtI}, S = #gs{board=Board, orig=Orig,frame=F}) -> + sudoku_board:butt_correct(Board, ButtI, true), + case sudoku_board:left(Board) of + 0 -> + Str = "Congrats, now try a harder one", + MD = wxMessageDialog:new(F,Str, + [{style, ?wxOK bor ?wxICON_INFORMATION}, + {caption, "Complete"}]), + wxDialog:showModal(MD), + wxDialog:destroy(MD), + status(F, "Given ~p Left ~p", [length(Orig), 0]); + L -> + status(F, "Given ~p Left ~p", [length(Orig), L]) + end, + {noreply, S}; +handle_info({wrong, ButtI}, S = #gs{board=Board}) -> + case S#gs.show_err of + true -> + sudoku_board:butt_correct(Board, ButtI, false); + false -> + ignore + end, + {noreply, S}; +handle_info({set_val, ButtI, Val}, S = #gs{game=G,board=Board,orig=Orig}) -> + case lists:member(indx(ButtI), Orig) of + false -> set_val(ButtI, Val, Board, G); + true -> ignore + end, + {noreply, S}; +handle_info({working, Done}, S = #gs{frame=F}) -> + status(F, "Thinking: ~p%", [Done]), + {noreply, S}; +handle_info({busy, Mode},S) -> + case Mode of + start -> wx_misc:beginBusyCursor(); + stop -> wx_misc:endBusyCursor() + end, + {noreply, S}. + +%%%%%%%%%%%%%%%%% GUI-Events %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(#wx{id=?HINT, event=#wxCommand{type=command_button_clicked}}, + S = #gs{game=G}) -> + G ! {solve,false}, + {noreply,S}; + +handle_event(#wx{event=#wxClose{}}, + S = #gs{game=G,frame=F}) -> + catch wxWindow:'Destroy'(F), + G ! quit, + {stop, shutdown, S}; + +handle_event(#wx{id=?QUIT, event=#wxCommand{type=command_menu_selected}}, + S = #gs{game=G,frame=F}) -> + wxWindow:close(F,[]), + G ! quit, + {stop, shutdown, S}; + +%% type=command_button_clicked, +handle_event(#wx{id=?NEW, event=#wxCommand{}}, + S = #gs{game=G, board=Board}) -> + G ! {op,?NEW,S#gs.level}, + sudoku_board:setup_board(Board,[]), + {noreply, S#gs{orig=[]}}; +handle_event(#wx{id=?EMPTY, event=#wxCommand{}}, + S = #gs{game=G, board=Board}) -> + G ! {op,?EMPTY}, + sudoku_board:setup_board(Board,[]), + {noreply, S#gs{orig=[]}}; +handle_event(#wx{id=?CLEAR, event=#wxCommand{}}, + S = #gs{game=G,board=Board}) -> + Vals = sudoku_board:clear_board(Board), + G ! {loaded, Vals}, + {noreply, S}; +handle_event(#wx{id=ID, event=#wxCommand{}}, S) when ID > 125 -> + New = dialog(ID, S), + {noreply, New}; +handle_event(Msg,S) -> + io:format("~p: Unhandled event ~p~n",[?MODULE, Msg]), + %%sudoku_board:event(Msg, Ids), + {noreply, S}. + +handle_call(What, _From, State) -> + {stop, {call, What}, State}. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + +terminate(_Reason, _State) -> + normal. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dialog(?SHOW_ERROR, S=#gs{show_err=Show}) -> + S#gs{show_err = not Show}; +dialog(ID, S) when ID >= 210, ID =< 240 -> + Level = sudoku_game:level(ID-200), + S#gs{level = Level}; +dialog(?SAVE, S=#gs{frame=Frame, board=Board}) -> + FD = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor + ?wxFD_OVERWRITE_PROMPT}]), + case wxFileDialog:showModal(FD) of + ?wxID_OK -> + Path = wxFileDialog:getPath(FD), + {ok,Fd} = file:open(Path, [write]), + List = sudoku_board:get_board_data(Board), + io:format(Fd, "~w.~n", [List]), + file:close(Fd); + _ -> + ignore + end, + wxDialog:destroy(FD), + S; +dialog(?OPEN, S=#gs{game=Server, frame=Frame, board=Board}) -> + FD = wxFileDialog:new(Frame,[{style, ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]), + case wxDialog:showModal(FD) of + ?wxID_OK -> + Path = wxFileDialog:getPath(FD), + case file:consult(Path) of + {ok, [Game]} when is_list(Game) -> + Vals = sudoku_board:set_board_data(Board, Game), + Server ! {loaded, Vals}; + _ -> + ignore + end; + _ -> + ignore + end, + wxFileDialog:destroy(FD), + S; +dialog(?ABOUT, S=#gs{frame=Frame}) -> + Str = "I'm just testing WxWidgets.\n" + "Testing various features and usages.\n/Dgud", + MD = wxMessageDialog:new(Frame,Str, + [{style, ?wxOK bor ?wxICON_INFORMATION}, + {caption, "About box"}]), + wxDialog:showModal(MD), + wxDialog:destroy(MD), + S; +dialog(?RULES, S) -> + wx_misc:launchDefaultBrowser("http://www.sudoku.com"), + S; + +dialog(?PRINT_PAGE_SETUP, S = #gs{frame=Frame, print_psdd=PsDD0, print_d=PD0}) -> + wxPageSetupDialogData:setPrintData(PsDD0, PD0), + PSD = wxPageSetupDialog:new(Frame, [{data,PsDD0}]), + wxPageSetupDialog:showModal(PSD), + + PSDD1 = wxPageSetupDialog:getPageSetupData(PSD), + PD1 = wxPageSetupDialogData:getPrintData(PSDD1), + %% Create new objects using copy constr. + PD = wxPrintData:new(PD1), + PsDD = wxPageSetupDialogData:new(PSDD1), + wxPageSetupDialog:destroy(PSD), + wxPageSetupDialogData:destroy(PsDD0), + wxPrintData:destroy(PD0), + S#gs{print_psdd=PsDD, print_d=PD}; +dialog(?PRINT_PRE, S = #gs{frame=Frame, print_d=PD}) -> + PDD = wxPrintDialogData:new(PD), + Printout1 = wxPrintout:new("Print", fun(This,Page) -> printout(This,Page,S) end, + [{getPageInfo, fun getPageInfo/1}]), + Printout2 = wxPrintout:new("Print", fun(This,Page) -> printout(This,Page,S) end, + [{getPageInfo, fun getPageInfo/1}]), + Preview = wxPrintPreview:new(Printout1, [{printoutForPrinting,Printout2},{data,PDD}]), + case wxPrintPreview:isOk(Preview) of + true -> + PF = wxPreviewFrame:new(Preview, Frame, [{title, "Print Preview"}]), + wxPreviewFrame:centre(PF, [{dir, ?wxBOTH}]), + wxPreviewFrame:initialize(PF), + wxPreviewFrame:centre(PF), + wxPreviewFrame:show(PF); + false -> + io:format("Could not create preview window.\n" + "Perhaps your current printer is not set correctly?~n", []), + wxPrintPreview:destroy(Preview) + end, + S; +dialog(?PRINT, S = #gs{frame=Frame, print_d=PD}) -> + PDD = wxPrintDialogData:new(PD), + Printer = wxPrinter:new([{data,PDD}]), + Printout = wxPrintout:new("Print", fun(This,Page) -> printout(This,Page,S) end, + [{getPageInfo, fun getPageInfo/1}]), + + case wxPrinter:print(Printer, Frame, Printout, [{prompt,true}]) of + false -> + case wxPrinter:getLastError() of + ?wxPRINTER_ERROR -> + io:format("There was a problem printing.\n" + "Perhaps your current printer is not set correctly?~n", []); + _ -> + io:format("You canceled printing~n", []) + end, + wxPrinter:destroy(Printer), + S; + true -> + PDD2 = wxPrinter:getPrintDialogData(Printer), + PD2 = wxPrintDialogData:getPrintData(PDD2), + %% Copy data PD2 will be deleted when Printer is destroyed + PD3 = wxPrintData:new(PD2), + wxPrintData:destroy(PD), + wxPrinter:destroy(Printer), + S#gs{print_d = PD3} + end; + +dialog(Other, S) -> + io:format("other ~p~n",[Other]), + S. + +init_printer(S) -> + PD = wxPrintData:new(), + + %% You could set an initial paper size here + %% g_printData->SetPaperId(wxPAPER_LETTER); // for Americans + %% g_printData->SetPaperId(wxPAPER_A4); // for everyone else + + PSDD = wxPageSetupDialogData:new(PD), + wxPageSetupDialogData:setMarginTopLeft(PSDD,{15,15}), + wxPageSetupDialogData:setMarginBottomRight(PSDD,{15,15}), + + S#gs{print_d=PD, print_psdd=PSDD}. + +getPageInfo(_This) -> + {1,1,1,1}. + +printout(This, _Page, #gs{board=Board, print_psdd=PsDD}) -> + MX = MY = 500, + wxPrintout:fitThisSizeToPageMargins(This, {MX,MY}, PsDD), + + _DBG = {_X,_Y,W,H} = wxPrintout:getLogicalPageMarginsRect(This, PsDD), + wxPrintout:offsetLogicalOrigin(This,(W-MX) div 2, (H-MY) div 2), +%% io:format("~p ->{~p,~p} ~n", [_DBG, (W-MX) div 2, (H-MY) div 2]), + + DC = wxPrintout:getDC(This), + sudoku_board:draw(Board, DC, {500,500}), + true. + +set_val(Id, Val, Board, G) -> + sudoku_board:set_butt(Board, Id,Val), + G ! {validate, Id, Val}, + ok. + + + -- cgit v1.2.3