Вы находитесь на странице: 1из 8

15 Puzzle

CS 483 Due 11:59 pm Friday February 3, 2012

Introduction

For this project you will create an iOS 5 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 4.2 that uses the iOS Single View Application template. Choose iPhone for the device family. Uncheck Use Storyboard (we will use a NIB le to lay out the user interface) and use Automatic Reference Counting (ARC). 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. Either way, using git is a good idea. Figure 1 shows the supporting les in your project under the Project Navigator tab.

Laying out the View

Select FifteenViewController.xib in the Project Navigator and edit the NIB le as illustrated in Figure 2. 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. 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 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 3. 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.

created by single-view app template

} Application Delegate } View Controller } Board Model


NIB Application Property List main (don't edit)

}
2.1 Adding a boardView outlet

Frameworks our app uses

Figure 1: Content of project.

Since the controller will frequently access the tiles of our board view, create an outlet for it in the controller class: @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). Make sure you provide the corresponding @synthesize directive in the implementation. Use the Connections Inspector in IB to connect the boardView outlet with this instance variable.

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.

300

75 75

UIView
416 300 320

UIView

UIBarButtomItem

UIToolBar

UIButton

Figure 2: 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.

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 programmatically. 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 [board scramble:NUM_SHUFFLES]; // scramble tiles [self arrangeBoardView]; // sync view with model }

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. 3

view controller

main view

board view

Figure 3: 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. -(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 instance variable for it (and make sure to synthesize its setter and getters in the implementation): @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; For faster compiles, you only need the terse directive @class FifteenBoard; in the controllers header le; this merely tells the compiler that the FifteenBoard class exists.

Model View Controller

Figure 4 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.

CONTROLLER
FifteenPuzzleViewController

boardView board tileSelected: scrambleTiles:

outlet

g tar

FifteenPuzzleBoard

char state[4][4]; init getTileAtRow:Column: getRow:Column:ForTile: isSolved canSlideTileUpAtRow:Column: canSlideTileDownAtRow:Column: canSlideTileLeftAtRow:Column: canSlideTileRightAtRow:Column: slideTileAtRow:Column: scramble:

et/a ctio n

MODEL VIEW
Figure 4: 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. 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.

4.1

Sliding Tiles

Figure 5 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; [board getRow:&row Column:&col ForTile:tag]; // (2) CGRect buttonFrame = sender.frame; if ([board canSlideTileUpAtRow:row Column:col]) { [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: 5

CONTROLLER
FifteenPuzzleViewController

boardView board tileSelected: scrambleTiles:

1 4

3
FifteenPuzzleBoard

char state[4][4]; init getTileAtRow:Column: getRow:Column:ForTile: isSolved canSlideTileUpAtRow:Column: canSlideTileDownAtRow:Column: canSlideTileLeftAtRow:Column: canSlideTileRightAtRow:Column: slideTileAtRow:Column: scramble:

MODEL VIEW
Figure 5: 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.

[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.

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 { [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 = 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 = [board getTileAtRow:row Column:col]; if (tile > 0) { __weak UIButton *button = (UIButton *)[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.

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. You could also add an background images to your buttons as shown on the left of Figure 6. I added the cougar.png to my project (just drag and drop into the Project Navigator) and then used Core Graphics to divvy it up: - (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 6 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. 7

Figure 6: Using images for the button backgrounds. We can allow the user to choose an image from their photo library.

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.

Вам также может понравиться