You are on page 1of 10

15 Puzzle

CS 483
Due 11:59 pm, Friday, February 14, 2014
1 Introduction
For this project you will create an iOS 7 Fifteen Puzzle app for the iPhone:
http://en.wikipedia.org/wiki/Fifteen_puzzle
Section 2 describes how to use the Interface Builder (IB) editor to layout your view components. The
methods for the internal game logic (which you will need to implement) are specied in Section 3. The
details of the Model View Controller (MVC) design and the ow of events is outlined in Section 4. Some
ideas of extra features you can add to your app is given in Section 5. How to submit your solution is specied
in Section 6.
1.1 Creating a new project
You app will contain a single view that displays the puzzle. To get started, create a new iOS Application
project in Xcode 5 that uses the iOS Emply Application template.
1
If you use the Single View Appli-
cation template, then Xcode will create a Storyboard for your project after this project we will focus on
using Storyboard. Choose iPhone for the device family. Name your project FifteenPuzzle and enter
edu.username (or something unique) for the Company Identier. Specify FifteenPuzzle for the Class
Prex; Xcode will use this to generate class names. Dont ever begin this string with a digit since Xcode will
have to tweak it since identiers cant begin with a digit in C. You can let Xcode generate a git repository
for you (or you can create one by hand).
2
Figure 1 shows the supporting les in your project under the
Project Navigator tab.
1.2 Adding the view controller
Add a view controller class named FifteenPuzzleViewContoller with an associated NIB le to your project
(see Figure 2). In the App delegates application:didFinishLaunchingWithOptions: method, create an
instance of your view controller and set it to be the root view controller for the Apps window:
self.viewController = [[FifteenPuzzleViewController alloc]
initWithNibName:@"FifteenPuzzleViewController" bundle:nil];
self.window.rootViewController = self.viewController;
If you happen to be using a StoryBoard instead (no NIB), then the root view controller will automatically
be set for you.
1
}
}
Application Delegate
View Controller
NIB
} Board Model
Application
Property List
main
(don't edit)
}
Frameworks
our app uses
images asset le
Figure 1: Content of project.
Figure 2: Creating view controller with associate NIB le.
2 Laying out the View
Select FifteenViewController.xib in the Project Navigator and edit the NIB le as illustrated in Figure 3.
The Xcode template will have already created a main view for you. First add a UIToolBar and another
UIView as illustrated on the gures left. Another UIView is then added which we will call the board view
since it will contain the tiles. Use the Size Inspector to set its size at 300 300 and center it as illustrated
in Figure 4. Change its background to black in the Attributes Inspector. 300 is a nice choice since its a
multiple of four and ts snuggly within the 320 pixel wide screen and wont need to be resized if we allow
the user to rotate the device. Drop a UIButton onto the board view, resize to 75 75, set the title two a
two-digit number and resize the font to taste. Replicate (copy and paste) this button 14 times and lay them
out as shown on the gures right. Set the title and the tags (via the Attribute Inspector) of each button to
1
If you are working on a ENCS lab computer you want to make sure your working directory is on the local le system as
described in class.
2
Eventually you will push this repository to a bare git repository under you home directory if you are working on a ENCS
lab machine.
2
416
320
300
300
75
75
UIToolBar
UIView
UIView
UIButton
UIBarButtomItem
(or 510)
Figure 3: Views and control in NIB le. The toolbar and view on the left are subviews of the main view.
The 300 300 view in the center (we call the board view) is a subview of the view on the left which will
contain the tiles. Each of the 15 buttons on the left are subview of the board view. Note: these are iOS 6
snapshots.
File Inspector tab
uncheck to
disable Autlolayout
centering: no "springs"
nor "struts"
Size Inspector tab
Figure 4: Using the Size Inspector to force the board view have a xed size of 300300 and to be centered
in its parent view. To be able to alter the springs and struts you rst need to disable Autolayout.
one through fteen; we will use the tags to identify each button in our our code.
The views and controls in the NIB le form a hierarchy as illustrated in Figure 5. The applications view
controller is created in the App Delegates application:didFinishLaunchingWithOptions: method which
creates its view when this NIB le is loaded. The Files Owner references the object that loaded the NIB
le, thus it is a placeholder which references the App Delegate in this case.
2.1 Adding a boardView outlet
Create an outlet so the controller can access the tiles of our board view:
@property(weak,nonatomic) IBOutlet UIView *boardView;
Since this views superview already owns this object and will provide it with the necessary lifetime, we
can safely reference it with a weak pointer. We use the nonatomic keyword since we wont be accessing it
concurrently (i.e., with multiple threads). Use the Connections Inspector in IB to connect the boardView
outlet with this instance variable.
3
3
Or you could place Xcodes editor into Tuxedo Mode and create these outlets as described in class.
3
main view
board view
view controller
Figure 5: Hierarchical view of objects in the NIB le. Files Owner is simply a placeholder that references
the object that loaded the NIB le which is our view controller in this case. All of our views and controls
are subviews of the main view which was created by the Xcode template.
2.2 Adding action methods
We need to create the appropriate callback methods that will be invoked when the user selects a tile or wants
a new game. Add the following action methods to the FiftenPuzzleViewController class:
-(IBAction)tileSelected:(UIButton*)sender;
-(IBAction)scrambleTiles:(id)sender;
I usually add code to log when the methods are invoked for diagnostic purposes:
-(IBAction)tileSelected:(UIButton*)sender {
const int tag = [sender tag];
NSLog(@"tileSelected: %d", tag);
...
}
Use the Connection Inspector in IB to connect these actions with the appropriate buttons on Touch Up
events.
2.3 Additional Setup
Usually there is more work to be done to setup the view after its NIB le is loaded. The UIViewController
class provides a method for its subclasses to override when there is additional setup to be done program-
matically. We will use FifteenPuzzleViewControllers overloaded version to create a new board (see
Section 4), shue the tiles on the board, and arrange the views tiles (represented as buttons) to reect the
state of the board:
- (void)viewDidLoad {
[super viewDidLoad];
self.board = [[FifteenBoard alloc] init]; // create/init board
[self.board scramble:NUM_SHUFFLES]; // scramble tiles
[self arrangeBoardView]; // sync view with model
}
4
3 The Model
Dene a class FifteenPuzzle that will encapsulate the puzzles logic and support the methods below. The
puzzle tiles are encoded as integers from 1 to 15 and the space is represented with a zero. The puzzle tiles
are assumed to lie in a 4 4 grid with one empty space.
-(id)init; Initialize a new 15-puzzle board with the tiles in the solved conguration (only called once
when the puzzle is instantiated).
-(void)scramble:(int)n; Choose one of the slidable tiles at random and slide it into the empty space;
repeat n times. We use this method to start a new game using a large value (e.g., 150) for n.
-(int)getTileAtRow:(int)row Column:(int)col; Fetch the tile at the given position (0 is used for the
space).
-(void)getRow:(int*)row Column:(int*)col ForTile:(int)tile; Find the position of the given tile (0
is used for the space).
-(BOOL)isSolved; Determine if puzzle is in solved conguration.
-(BOOL)canSlideTileUpAtRow:(int)row Column:(int)col; Determine if the specied tile can be slid up
into the empty space.
-(BOOL)canSlideTileDownAtRow:(int)row Column:(int)col;
-(BOOL)canSlideTileLeftAtRow:(int)row Column:(int)col;
-(BOOL)canSlideTileRightAtRow:(int)row Column:(int)col;
-(void)slideTileAtRow:(int)row Column:(int)col; Slide the tile into the empty space.
It is important that the positions of the tiles are not simply chosen at random since you may create an
unsolvable puzzle. Instead, scramble the tiles of a puzzle that we know is solvable when the user wants to
start a new game.
3.1 Creating the model
Since the view controller will be the only entity accessing our puzzle board, it will create the puzzle board
in its viewDidLoad method as described above. Create an property for it:
@property(strong,nonatomic) FifteenBoard *board;
Note that we make this a strong reference since the controller is the sole owner. Make sure to import the
FifteenBoard.h header le in the implementation;
4 Model View Controller
Figure 6 illustrates the main components of the 15-Puzzle App classied according to the MVC pattern.
The UIButtons and UIViews that are archived and reanimated at run time from the NIB le are shown on
the right. The controller references the boardView via an IB outlet so that it can manipulate the tiles on
the board. The target/action mechanism is used to inform the controller when the user initiates an event.
The controller owns the sole model object which encodes the state of the puzzle and provides methods for
querying and modifying the board. The model is not tied to any specic user interface and could be used in
another application with a dierent user interface.
5
tileSelected:
scrambleTiles:
boardView
board
FifteenPuzzleViewController
init
getTileAtRow:Column:
getRow:Column:ForTile:
isSolved
canSlideTileUpAtRow:Column:
canSlideTileDownAtRow:Column:
canSlideTileLeftAtRow:Column:
canSlideTileRightAtRow:Column:
slideTileAtRow:Column:
scramble:
char state[4][4];
FifteenPuzzleBoard
MODEL
VIEW
CONTROLLER
outlet
t
a
r
g
e
t
/
a
c
t
i
o
n
Figure 6: MVC elements of 15-puzzle app. The View objects are stored in the NIB le. When the NIB is
loaded, the boardView outlet of the Controller is bound to the associated UIView and the various UIButton
target/actions are created. The Controller creates an instance of FiftenPuzzleBoard (the sole Model object)
which encapsulates the games logic.
4.1 Sliding Tiles
Figure 7 shows the sequence of events that are triggered on a touch-up event when the user touches tile 12.
We wired this event to send a tileSelected: message to the controller. Here is snippet of the controllers
code annotated with steps 2, 3 and 4:
-(IBAction)tileSelected:(UIButton*)sender {
const int tag = [sender tag];
int row, col;
[self.board getRow:&row Column:&col ForTile:tag]; // (2)
CGRect buttonFrame = sender.frame;
if ([self.board canSlideTileUpAtRow:row Column:col]) {
[self.board slideTileAtRow:row Column:col]; // (3)
buttonFrame.origin.y = (row-1)*buttonFrame.size.height;
sender.frame = buttonFrame; // (4)
} else ...
The tag eld that was assigned in IB identies which tile was selected. We then query the model to determine
which row and column the tile is in. buttonFrame is a rectangle that species the size and position of the
button within its parent view (the boardView). If the model indicates that we can slide this tile up, the
we tell the model to slide the tile (Step 3) and then we move the button up (step 4). Changing the frame
property of the button immediately moves the button. It is amazingly simple to show the button sliding by
animating the change of the frame property as follows:
[UIView animateWithDuration:0.5 animations:^{sender.frame = buttonFrame;}];
The strange ^{...} syntax is an Objective-C block which provides a simple mechanism for sending code
snippets as arguments in method calls. All the UIKit views are backed by a Core Animation layer and it is
really simple to animate their properties.
6
tileSelected:
scrambleTiles:
boardView
board
FifteenPuzzleViewController
init
getTileAtRow:Column:
getRow:Column:ForTile:
isSolved
canSlideTileUpAtRow:Column:
canSlideTileDownAtRow:Column:
canSlideTileLeftAtRow:Column:
canSlideTileRightAtRow:Column:
slideTileAtRow:Column:
scramble:
char state[4][4];
FifteenPuzzleBoard
MODEL
VIEW
CONTROLLER
1
2
4
3
Figure 7: Communication sequence triggered when the user touches up on the 12-tile: (1) the button
sends a tileSelected: message to the Controller; (2) The Controller (using the senders tag = 12) queries
the Model for the column and row of the corresponding button; The Controller then queries the Model to
determine which direction the tile can slide (if any); Since it was determined that the tile can slide down,
(3) the Controller tells the Model to slide the tile and (4) moves the Views button by altering its frame
property.
7
4.2 Scrambling Tiles
When the user chooses a new game, we simply tell the model to scramble the tiles and send a message to
the boardView to arrange its buttons accordingly:
-(IBAction)scrambleTiles:(id)sender {
[self.board scramble:NUM_SHUFFLES];
[self arrangeBoardView];
}
Repositioning the buttons in the view to match the model is a simple matter of changing their frame
properties:
-(void)arrangeBoardView {
const CGRect boardBounds = self.boardView.bounds;
const CGFloat tileWidth = boardBounds.size.width / 4;
const CGFloat tileHeight = boardBounds.size.height / 4;
for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++) {
const int tile = [self.board getTileAtRow:row Column:col];
if (tile > 0) {
UIButton *button = (UIButton *)[self.boardView viewWithTag:tile];
button.frame = CGRectMake(col*tileWidth, row*tileHeight, tileWidth, tileHeight);
}
}
}
We assume that the tile buttons are a quarter the size of the boardView. The bounds property is similar
to the frame property except that it describes its position and size in its own coordinate system (instead of
the parent views). The types and functions that are prexed with CG are part of Core Graphics which is a
C API that we will look into with much detail later.
5 Optional Bells and Whistles
You should add 57 57 and 114 114 PNG images to be used as app icons (the larger one for Apples
Retina Display). Adding a splash screen is nice so the user doesnt see a blank screen while the app
loads.
5.1 Pretty Buttons
The stock buttons provided by IB are plain looking. You can add a background image to a button in IB
which is automagically resized to the size of the buttons frame. A better method is to create an image whose
center can be stretched to and use that for the background image. This has to be done programmatically:
- (void)viewDidLoad {
...
UIImage *blueImage = [UIImage imageNamed:@"bordered-blue-button"];
UIImage *stretchyBlueImage =
[blueImage resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)
resizingMode:UIImageResizingModeStretch];
for (int tile = 1; tile <= 15; tile++) {
UIButton *button = (UIButton *)[self.boardView viewWithTag:tile];
[button setBackgroundImage:stretchyBlueImage forState:UIControlStateNormal];
}
}
8
10
10
40!40 "stretchable"
image with
10!10 cap insets
Figure 8: Added a stretchable blue image to the project which is used for the tile backgrounds. Both a
40 40 and 80 80 image are used for standard and retina displays respectively. These images are stored
in Images.xcassets.
5.2 Background Images
Figure 9: Using images for the button backgrounds. We can allow the user to choose an image from their
photo library.
You could also divvy up a background image (using Core Graphics ) as shown on the left of Figure 9:
- (void)viewDidLoad {
...
UIImage* image = [UIImage imageNamed:@"cougar.png"];
[self setBackgroundImageOfButtons:image]; // helper method using CG
}
Perhaps you could let the user choose an image from their photo library as shown in Figure 9 this requires
using a UIImagePickerController and conforming to the UIImagePickerControllerDelegate protocol
(as well as some other CG tricks). Well shown you how to do this later.
9
6 What to submit
For this project you do not need to install your app on a device (we will be doing that later). Simply make
sure it runs as advertised under the iPhone Simulator. You will archive your project as a compressed tarball
and submit electronically via the course website (stay tuned for further instructions). Its nice to do a build
clean before archiving so that all of the build rira is not included.
10

You might also like