MATLAB Minesweeper Development of a MATLAB GUI Start with an empty function function MineSweeper_GUI() end % of MineSweeper() While not strictly required, a function that creates the GUI provides flexibility for your program. If you have multiple frames in your interface, functions permit separation of code by allowing a main program to instantiate the frames independently. Create a basic figure function MineSweeper_GUI() fh = figure('Position', [400, 400, 250, 300]); % Adjust appearance set(fh, 'Resize', 'off'); set(fh, 'MenuBar', 'none'); set(fh, 'NumberTitle', 'off'); set(fh, 'Name', 'MATLAB MineSweeper'); end % of MineSweeper_GUI() These are common attributes, although not every GUI will necessarily set them. Provide the controls % Text will be provided and covered up by a button. % will disappear when clicked. The button % Control to hold text (to display after button is pushed) tbh = uicontrol('Units', 'normalized', 'Style', 'text'); set(tbh, 'String', '8', 'FontSize', 16); set(tbh, 'Position', [0, 0, 0.1, 0.1]); Now that we have one box, let’s make the code repeat it. Provide the rest of textboxes c = 1; for xpos = 1:9 for ypos = 1:9 % Control to hold text (to display after button is pushed) tb(c) = uicontrol('Style', 'text', 'Units', 'normalized'); set(tb(c), 'String', '8', 'FontSize', 16); set(tb(c), 'Position', [0.11*(xpos-1),0.11*(ypos-1),0.1,0.1]); c = c+1; end % of for ypos end % of for xpos (We assume a 9x9 grid – this can become a feature selected by the user) These all show the number ‘8’. We will change that after we have a visually complete GUI – then we can make it fully functional. Provide pushbuttons to cover % Cover with buttons c = 1; for xpos = 1:9 for ypos = 1:9 % These are drawn on the figure, not any panel u(c)=uicontrol('Style', 'pushbutton', 'Units', 'normalized'); set(u(c), 'Position', [0.11*(xpos-1),0.11*(ypos-1), 0.1, 0.1]); c = c + 1; end end We now have a visually-complete game. Now we can focus on the functionality. Add button functionality When actions are to be performed as a result of clicking on a control, MATLAB places a call to a “callback function”. It is the responsibility of the programmer to provide that function, and to tell MATLAB how to call that function. The syntax for this is a little different from typical function usage. Defining a callback function To define a callback function, simply make a function in the same file as the GUI frame. The name can be any legal function name. The first two parameters of the function are required by MATLAB – you can add any others you wish afterwards: function my_callback(H, E, a, b, c) . . . end % of my_callback() Our callback function % Callback (cb) for PushButtons (pb) function cb_pb(H, E, bh, th, c, sz) % What string is "under" the button that was pressed? s = get(th(c), 'String'); if strcmp(s, '9') hit_mine(c, sz, bh, th); elseif length(s)==0 || strcmp(s, ' ') hit_empty(c, sz, bh, th); else delete(bh(c)); uicontrol(th(c)); % set focus to current text end % of if end % of cb_pb Tell MATLAB to use the callback function % % % % % % Because clicking a button can mean deleting other buttons in this program, we want to send the entire array of button handles and text handles to the callback functions. That is why this loop is AFTER the original creation loop - if we had tried to set the callbacks in the creation loop, the early buttons would know nothing about buttons defined later. for c = 1:n^2 % Handle a left-click set(u(c), 'callback', {@cb_pb, u, th, cnt, n}); end % of for c function MineSweeper_demo() clc; n=9; fh = figure('Position', [400, 400, 250, 300]); % Adjust appearance set(fh, 'Resize', 'off'); set(fh, 'MenuBar', 'none'); set(fh, 'NumberTitle', 'off'); set(fh, 'Name', 'MATLAB MineSweeper'); c = 1; for xpos = 1:9 for ypos = 1:9 % Control to hold text (to display after button is pushed) tb(c) = uicontrol('Style', 'text', 'Units', 'normalized'); set(tb(c), 'String', '8', 'FontSize', 16); set(tb(c), 'Position', [0.11*(xpos-1),0.11*(ypos-1),0.1,0.1]); c = c+1; end % of for ypos end % of for xpos % Cover with buttons c = 1; for xpos = 1:9 for ypos = 1:9 % These are drawn on the figure, not any panel u(c)=uicontrol('Style', 'pushbutton', 'Units', 'normalized'); set(u(c), 'Position', [0.11*(xpos-1),0.11*(ypos-1), 0.1, 0.1]); c = c + 1; end end % Attach callback function for c = 1:n^2 % Handle a left-click set(u(c), 'callback', {@cb_pb, u, tb, c, n}); end % of for c end % of MineSweeper_demo() % Callback function function cb_pb(H, E, bh, th, c, sz) % What string is "under" the button? s = get(th(c), 'String'); if strcmp(s, '9') hit_mine(c, sz, bh, th); elseif length(s)==0 || strcmp(s, ' ') hit_empty(c, sz, bh, th); else delete(bh(c)); uicontrol(th(c)); % set focus to current text end end % of cb_pb() Finishing off the code At this point, most of the GUI code is complete. We now provide code that will perform the tasks desired upon certain events. If a mine is beneath a clicked button: hit_mine(c, sz, bh, th); If a space is beneath a clicked button: hit_empty(c, sz, bh, th); hit_mine() % What to do if a mine is hit function hit_mine(i, sz, bh, th) % Get rid of ALL buttons and show the mines for r=1:sz for c=1:sz % Since using vectors to hold handles v = (c-1)*sz + r; % Is there still a button here? if ishandle(bh(v)) % What is the textbox string? ts = get(th(v), 'String'); % Change textbox to show an image of a mine exploding if strcmp(ts, '9') mineimg = imread('mine10.jpg'); set(bh(v), 'cdata', mineimg); else % Get rid of the button delete(bh(v)); end end end % of for c end % of for r Add a grid for testing hit_mine() function MineSweeper_demo() % ----------------------------% Testing values n = 9; for i=1:n^2 M(i) = num2str(randi(9,1)); r = randi(2)-1; if r M(i) = ' '; end end % -----------------------------And we will change this line: set(tb(c), 'String', '8', 'FontSize', 16); To display the testing values: set(tb(c), 'String', num2str(M(c)), 'FontSize', 16); After adding the test values With the mine images hit_empty() function hit_empty(pos, sz, bh, th) % Find all of the surrounding empties and remove the buttons recursive_remove(pos, pos, sz, bh, th, 1); end % of hit_empty() (see next slide) recursive_remove() % Remove all buttons over empty strings and over numbers that touch one of % those empty strings (but don't remove over mines) function recursive_remove(from, to, sz, bh, th, level) if ~(to<=0 || to>sz^2 ||(mod(from, sz)==1 && mod(to, sz)==0) ||(mod(from, sz)==0 && mod(to, sz)==1) to_s = get(th(to), 'String'); from_s = get(th(from), 'String'); % Empty or contains a number (but not a mine) if strcmp(to_s, ' ') || (~strcmp(to_s, ' ') && str2num(to_s)>0 && str2num(to_s)<9) % Does the button still exist? if ishandle(bh(to)) % Remove the button delete(bh(to)); % If it's empty, recurse to check the surrounding buttons if strcmp(to_s, ' ') level = level + 1; recursive_remove(to, recursive_remove(to, recursive_remove(to, recursive_remove(to, recursive_remove(to, recursive_remove(to, recursive_remove(to, recursive_remove(to, level = level - 1; end end % of if end % of else end % of recursive_remove() to-sz-1, sz, bh, th, level); to-sz, sz, bh, th, level); to-sz+1, sz, bh, th, level); to-1, sz, bh, th, level); to+1, sz, bh, th, level); to+sz-1, sz, bh, th, level); to+sz, sz, bh, th, level); to+sz+1, sz, bh, th, level); Last Details One other property of the game (other than scoring) involves allowing the user to mark a location as “containing a mine” or “might contain a mine”. We will do this by allowing the user to right-click on a button and placing “?” for “might be a mine”, or “!” for “containing a mine”. Right-click Callbacks A separate callback function must be placed for handling “alternative clicks”. Note the added line: for c = 1:n^2 % Handle a left-click set(u(c), 'callback', {@cb_pb, u, tb, c, n}); % Handle a right-click set(u(cnt), 'ButtonDownFcn', {@right_click, c, u}); end % of for c right_click() % This function is executed when a button is right-clicked function right_click(h, e, pos, bh) % The button we pressed is in a figure. % click that was made: fh = ancestor(h, 'figure') clickType = get(fh, 'SelectionType') Only the figure knows the type of % 'Alt' is a right-click % % This will allow using the right-click to cycle through the options bs = get(h, 'String'); if strcmp(clickType, 'alt') if strcmp(bs, '!') % Option 1: Clear the button label set(bh(pos), 'String', ''); end if strcmp(bs, '') % Option 2: Put a query (?) on the button - user isn't sure set(bh(pos), 'String', '?'); set(bh(pos), 'FontSize', 16, 'ForegroundColor', [1, 0, 0]); end if strcmp(bs, '?') % Option 3 : Put a bang (!) on the button - user thinks this is a mine set(bh(pos), 'String', '!'); set(bh(pos), 'FontSize', 16, 'ForegroundColor', [1, 0, 0]); end end % of if end % of right_click() Source Code Function: http://kindy.egr115.com/MineSweeper.zip