aboutsummaryrefslogtreecommitdiffstats
path: root/lib/wx/examples/sudoku
diff options
context:
space:
mode:
Diffstat (limited to 'lib/wx/examples/sudoku')
-rwxr-xr-xlib/wx/examples/sudoku/Makefile66
-rwxr-xr-xlib/wx/examples/sudoku/sudoku.erl53
-rwxr-xr-xlib/wx/examples/sudoku/sudoku.hrl44
-rwxr-xr-xlib/wx/examples/sudoku/sudoku_board.erl358
-rwxr-xr-xlib/wx/examples/sudoku/sudoku_game.erl503
-rwxr-xr-xlib/wx/examples/sudoku/sudoku_gui.erl391
6 files changed, 1415 insertions, 0 deletions
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 <[email protected]>
+%%% Description : Sudoku
+%%%
+%%% Created : 13 Mar 2007 by Dan Gudmundsson <[email protected]>
+%%%-------------------------------------------------------------------
+
+-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 : <[email protected]>
+%%% Description : Manages the gui board
+%%%
+%%% Created : 9 Jan 2008 by <[email protected]>
+%%%-------------------------------------------------------------------
+-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 : <[email protected]>
+%%% Description : The Gui and it's event loop
+%%%
+%%% Created : 9 Jan 2008 by <[email protected]>
+%%%-------------------------------------------------------------------
+-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.
+
+
+