| October 5, 2008 12:00 AM PDT | |
This walkthrough introduces you to some of the design considerations to keep in mind while coding for multiple platforms at the same time. This paper also includes a slide puzzle extended to run on both the desktop and a Pocket PC PDA using Visual Studio .NET*.
by Justin Whitney
The growing sophistication of developer tools, coupled with the gradual and long overdue convergence of mobile technologies, gives coders a dizzying array of platform options. But when faced with a choice between desktop and PDA, why choose? Do both at the same time.
Where "porting" is a wholesale rewrite from one platform to another, today's tools give you the ability to "extend" your code across multiple platforms. By leveraging a single code base, you can double or triple your market and revenue.
But how? There's the question.
To demonstrate the concept of extending code, we’ve put together a fairly simple walkthrough of a slide puzzle extended to run on the desktop and on a Pocket PC PDA. Using Visual Studio .NET* 2003, you'll see how we created a single Visual Basic .NET code base, and then incorporated it into two different builds.
The purpose of this walkthrough is to introduce some of the design considerations you need to keep in mind when coding for multiple platforms at once. With a little pre-planning, you can easily leverage your hard work. Later, when you want to extend to the Smartphone*, or even an ASP.NET* web application, you can apply the same techniques.
Though most of this code can be used in previous versions of Visual Studio, this walkthrough assumes you are using Visual Studio .NET 2003. The Integrated Development Environment (IDE) includes a Pocket PC 2002 Emulator.
Preparation
Before I even start up that IDE, I always take the time to spec out a feature list. When coding for multiple platforms, this becomes more important. As covered in my earlier article, "Extending JAVA* Apps Across Multiple Devices", you have several key decisions to make.
1. Are your platforms connected?
If your target platforms are able to access the Internet, consider taking advantage of Web Services and/or ASP.NET Mobile Controls. This is especially true if you're working with live or frequently updated data. For the purposes of this walkthrough, I'm creating standalone applications.
2. What core features should be on all platforms?
Obviously, your application will have a core set of definitive features, which you should be able to implement on all targeted platforms. In this case, that would be the puzzle itself, including puzzle tiles and options for starting, solving, or exiting the game. Because mobile devices have vastly different display areas, I've also chosen to include a simple puzzle-resizer to take advantage of whatever real estate the game has available.
3. Which features would be most appropriate to each platform?
The smaller the device, the fewer bells and whistles. This means cutting out menu options or even entire menus. But here's the fun thing about starting with the Pocket PC: Generally speaking, the applications you create for a PocketPC will also run on the desktop. Extending to the desktop becomes a matter of si mply adding desktop-specific features.
So my recommended approach is to start with core functionality and scale up. Start with the handheld. Then add only those truly necessary components that take advantage of the more powerful platforms. This gives your users more options in choosing the best tool for their needs.
In VS.NET, though you can build to either the .NET Framework or the .NET Compact Framework (.NET CF), you can't build to both in the same Solution. So, since .NET CF is essentially a subset of .NET Framework, I'll begin with the Pocket PC version. Because I know if I can make it there, I'll make it anywhere.
- File -> New -> Project
- Under Project Type, choose "Visual Basic Projects"
- Under Templates, choose "Smart Device Application"
- Let's call this one "SlidePuzzler"
- In the Smart Device Application Wizard, target the "Pocket PC" (Windows Application)
Setting Up the Form
Before getting into the code, there's a lot you can do in the form designer.
- In the Solution Explorer, right-click "Form1.vb" and choose Properties. Change that file name to "SlidePuzzle.vb."
- Likewise, click on the form and change the form name itself to "SlidePuzzle" and the text to "Slide Puzzle."
- Also remember, as I often don't, to right-click the Project, select Properties, and change Startup Object to the new form name.
- Below the form, you'll see a MainMenu1 object. Select it and change the name (under Properties) to "PuzzleMenu."
- At the bottom of the form itself (you may need to scroll down), you'll see the menu text. We could create all this in the code, but it's easier to just build the menu here. To keep it simple, create one menu option: "Menu", with three sub-options: "New", "Solve", and "Exit."
- Name the menu options (using Properties): "MainMenu", "MenuNew", "MenuSolve", and "MenuExit", respectively.
- Throw a panel on the form and call it "PuzzleBox". The dimensions don't really matter as they'll be resized in the code, but try to cover the whole form.
- Below are two graphics you can use. "Testgrid.gif" lays out a 4 by 4 grid with tiles that measure 50 by 50 pixels, for a total size of 200 by 200 pixels. This is handy if you want to restrain the dimensions of the puzzle by eliminating the resizing portion and adding constants. "Gaia.jpg" gives you something a little nicer to work with and is sized to be just larger than the dimensions of the PDA emulator. Save one or both to your project directory, then add Gaia.jpg to the project.
- Under the Properties for the gaia.jpg graphic, change Build Action to "Embedded Resource." This packages it with the executable rather than as a separate file.
At this point, the form and the project itself are both ready. Let's code.
When extending applications, in order to leverage the most code, try to shunt as much of the operational code from the UI to other classes. Optimally, form code will only contain what is distinct to your different platforms. As you know, this type of elegant streamlining is an art unto itself. Over time, you'll no doubt find new ways to delineate functionality to get the most mileage out of extensible code.
As your most basic component, the Tile class comes first. This, of course, represents the little square that you're sliding all over the puzzle. To start, add class to your project by right-clicking the project and selecting Add -> Add Class. Name the class Tile.vb.
Since the Tile will be used as a form control held by the "PuzzleBox" panel, add the following code just under the class declaration:
Inherits System.Windows.Forms.Control
Next come the various member declarations.
Private m_PuzzlePicture As Image Private m_DisplayRect As Rectangle Public CurrentLoc As Integer
Sub New contains a few points worth noting. First, add the following:
As a form control, the Tile objects have a few extra properties than the ones you defined, such as Location, Size, and Text. These will come in handy when moving the tiles around the puzzle board, especially the Location and Size properties. Also note that in the code to follow, CurrentLoc always represents the tile's current location and Text, an object property, represents the tile's original location. An object property was used for the latter value in order to increase flexibility in referencing it elsewhere in the app.
This also exemplifies one of the many differences between .NET CF and .NET Framework. In a desktop application, Name is one of the properties that can be set programmatically. In a PDA application, however, Name can be read, but not set. So here, Text is being used instead. This is another good reason to start small and scale up.
The key to chopping up the image into small, moveable pieces is to override the Paint routine for the object. Use it to paint the specific portion of the overall image that relates to this particular tile.
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) MyBase.OnPaint(e) e.Graphics.DrawImage(m_PuzzlePicture, ClientRectangle, _ m_DisplayRect, GraphicsUnit.Pixel) End Sub
To keep things simple, code has not been added to actually stretch or resize the puzzle. The code above merely shows more of the image, if available. If you want to get a little fancier, you can add some algorithms to the tile creation that resize the image, as well. (In fact, the sample code that comes with VS.NET includes a slide puzzle written for the Pocket PC in C#. It includes a few more features than I'm showing here, as well as some different techniques for creating the puzzle, though it doesn't lend itself quite as easily to multi-platform development.)
The complete code for Tile.vb can be found here
Public Sub New(ByVal m_Image As Image, ByVal m_X As Integer, _ ByVal m_Y As Integer, ByVal m_Width As Integer, _ ByVal m_Height As Integer, ByVal m_Name As Integer) m_PuzzlePicture = m_Image m_DisplayRect = New Rectangle(m_X, m_Y, m_Width, m_Height) Location = New Point(m_X, m_Y) Size = New Size(m_Width, m_Height) Text = m_Name CurrentLoc = m_Name End Sub
First, member declarations. These should be self-explanatory.
Public TileSet As Tile() Private m_PuzzlePicture As Image Private m_TileRows, m_TileCols, m_BlankTile As Integer Private m_TileWidth As Integer Private m_TileHeight As Integer
Next, instantiation, which assigns values to all these new variables.
Public Sub New(ByVal m_Picture As Image, _
ByVal m_RowCount As Integer, ByVal m_ColCount As Integer, _
ByVal m_Width As Integer, ByVal m_Height As Integer)
m_PuzzlePicture = m_Picture
m_TileRows = m_RowCount
m_TileCols = m_ColCount
m_TileWidth = m_Width
m_TileHeight = m_Height
Setup()
End Sub
You'll notice a Setup() routine there at the end. Here's the code for it. This actually creates the puzzle board, with all of its tiles. Notice that no constants appear here.
Private Sub Setup() Dim tileCount As Integer = (m_TileRows * m_TileCols) ReDim TileSet(tileCount - 1) m_BlankTile = tileCount - 1 Dim thisX, thisY, thisRow, thisCol, thisTile As Integer For thisRow = 0 To m_TileRows - 1 For thisCol = 0 To m_TileCols - 1 thisTile = (thisRow * m_TileRows) + thisCol thisY = (thisRow * m_TileHeight) thisX = (thisCol * m_TileWidth) TileSet(thisTile) = New Tile(m_PuzzlePicture, _ thisX, thisY, m_TileWidth, m_TileHeight, _ thisTile) Next Next TileSet(m_BlankTile).Visible = False End Sub
One thing to notice about the Setup() routine is the last line: It sets the "blank" tile's Visible property to "False". This could just as easily have been added to the form instead of the Grid class. And you may want to do just that, depending on what new features you add to the UI. The reason I add it here is to demonstrate that this is one of those little opportunities for pulling code out of the UI when it works on multiple platforms.
Demonstrating that concept even further, the Swap() routine, which exchanges tiles on the board, essentially manipulates form objects without ever touching the form itself. You have many options for creating "swapping" code. This is one that not only works on multiple platforms but also works independently of the UI code.
Public Sub Swap(ByVal m_Tile1 As Integer, ByVal m_Tile2 As Integer) Dim tmpPnt As Point = TileSet(m_Tile1).Location TileSet(m_Tile1).Location = TileSet(m_Tile2).Location TileSet(m_Tile2).Location = tmpPnt Dim tmpLoc As Integer = TileSet(m_Tile1).CurrentLoc TileSet(m_Tile1).CurrentLoc = TileSet(m_Tile2).CurrentLoc TileSet(m_Tile2).CurrentLoc = tmpLoc End Sub
You'll need a simple Get() method to return the value of the blank tile.
Public Function GetBlank() As Integer Return m_BlankTile End Function
The next routine is a little trickier. It receives a reference to one of the tiles, then calculates whether or not it can be swapped with the blank tile, based on its position.
Public Sub CheckTile(ByVal m_ThisTile As Integer) Dim intXDiff, intYDiff As Integer intXDiff = Math.Abs(TileSet(m_ThisTile).Left - _ TileSet(m_BlankTile).Left) intYDiff = Math.Abs(TileSet(m_ThisTile).Top - _ TileSet(m_BlankTile).Top) If ((intXDiff = 0) And (intYDiff <= m_TileHeight)) _ Or ((intXDiff lt;= m_TileWidth) And (intYDiff = 0)) Then Swap(m_ThisTile, m_BlankTile) End If End Sub
The Shuffle() procedure simply swaps around the tiles a bunch of times. "100" was chosen as an arbitrary "that's a lot" kind of number.
Public Sub Shuffle() Dim swapper, i As Integer Randomize() For i = 1 To 100 swapper = CInt(Int(m_BlankTile * Rnd())) Swap(swapper, m_BlankTile) Next End Sub
After each move, you'll need to check to see if the puzzle has been solved.
Public Function IsSolved() As Boolean Dim solved As Boolean = True For thisTile As Integer = 0 To UBound(TileSet) If (TileSet(thisTile).CurrentLoc <> CStr(thisTile)) Then solved = False Exit For End If Next If solved Then TileSet(m_BlankTile).Visible = True Return solved End Function
Lastly, for impatient gamers like me, a Solve() routine cuts to the chase.
Public Sub Solve() Dim thisTile As Integer For loc As Integer = 0 To UBound(TileSet) thisTile = 0 While TileSet(thisTile).CurrentLoc <> loc thisTile += 1 End While Swap(thisTile, TileSet(thisTile).CurrentLoc) Next TileSet(m_BlankTile).Visible = True End Sub
The complete code for Grid.vb can be found here.
The Tile and Grid classes are intended to be sh ared between deployment platforms. The one thing that generally can't be shared, however, is the UI—though as you'll see, even that can be shared to a large degree.
Switch over to the Code View for the form, also known as SlidePuzzle.vb. Under the other variable declarations at the top, add the following:
Private m_PuzzleBoard As Grid Private m_PuzzlePicture As Image Private m_Blank As Integer Private m_TileRows, m_TileCols As Integer Private m_TileWidth, m_TileHeight As Integer
The Grid class is not yet defined, but that's ok. You'll get to that in a moment. For now, go to the bottom of the code. Just before the "End Class" statement, add the following:
Private Sub MenuExit_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuExit.Click Me.Close() End Sub
This simply closes your application. You'll find this handy once you begin testing it using the emulator. Next, add the following:
Private Sub MenuSolve_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MenuSolve.Click
m_PuzzleBoard.Solve()
MessageBox.Show("Solved!")
End Sub
The third and most complex menu handler, "New", starts out like this.
Private Sub MenuNew_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuNew.Click
Before building a new puzzle, clear out the old one, if it exists:
While PuzzleBox.Controls.Count > 0 PuzzleBox.Controls.Remove(PuzzleBox.Controls(0)) End While
For this walkthrough, all the puzzles will be four by four. You can just as easily make them other dimensions using constants or by adding simple menu options which allow the user to select his or her own desired dimensions. But for now, keep it simple.
m_TileRows = 4 m_TileCols = 4
The panel you dropped onto the form will be used to hold all of the tiles in the puzzle. Resizing the panel lets you take advantage of the full screen area of the device.
PuzzleBox.Size = New Size(ClientSize.Width, _ ClientSize.Height)
Likewise, each of the individual tiles will be sized accordingly.
m_TileWidth = CInt(ClientSize.Width / m_TileCols) m_TileHeight = CInt(ClientSize.Height / m_TileRows)
Manipulating graphics can be one of the trickier feats when extending code. While you may have half a dozen options for importing graphics into your app, some work only in the full .NET Framework. Some work on both platforms but are implemented differently.
GetManifestResourceStream() works on both platforms, so could just as easily have been added to the Grid class. But to leave my options open, I'd rather bring in the image on the form, and then pass it to the Grid class later.
If m_PuzzlePicture Is Nothing Then
m_PuzzlePicture = New Bitmap(System.Reflection.Assembly.
GetExecutingAssembly().GetManifestResourceStream
("SlidePuzzler.gaia.jpg"))
End If
(Note that the above snippet is three lines long. The m_PuzzlePicture assignment should be on a single line, but may appear to wrap in your browser.)
If you want to use a different picture for the puzzle, change the name here. Remember to add the new image to your project and change its Build Action to "Embedded Resource". Then, when referring to it in the code, use its namespace, which should be the project name unless you changed it. In this case, it's "SlidePuzzler.gaia.jpg".
Now you're ready to build a new puzzle.
m_PuzzleBoard = New Grid(m_PuzzlePicture, m_TileRows, _ m_TileCols, m_TileWidth, m_TileHeight) m_Blank = m_PuzzleBoard.GetBlank
Add all the tiles to the PuzzleBox panel, along with a click handler, which will be added in a moment.
For thisTile As Integer = 0 To UBound(m_PuzzleBoard.TileSet) PuzzleBox.Controls.Add(m_PuzzleBoard.TileSet(thisTile)) AddHandler PuzzleBox.Controls(thisTile).Click, _ AddressOf TileClick Next
Of course, a puzzle is no fun unless you shuffle it.
m_PuzzleBoard.Shuffle()
That's it for the MenuNew_Click() events, close it up.
End Sub
The last part of the code is the part that makes it all go: the event handler for the tile's Click event. This particular snippet also makes an extremely valuable point.
You may have noticed before that each tile's original location on the puzzle grid was being stored as an object property, in particular the Text property. At first glance, using a property in this way may seem lazy. After all, why not just throw the entire object back and forth between calls?
The key thing to remember here is that when coding for potentially low resource devices, you'll do good unto your user by seeking out every opportunity for optimization. This code probably has several more such opportunities, but here is one of the easiest: Instead of creating and trashing large objects on the fly, which hogs valuable cycles, send little bitty numbers instead. Remember, these are handhelds, and CPU cycles = battery life.
Public Sub TileClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs)
Dim t As Tile = sender
m_PuzzleBoard.CheckTile(t.Text)
If m_PuzzleBoard.IsSolved Then
MessageBox.Show("Congratulations! You solved it!")
End If
End Sub
Now save it all because you're finally ready to test.
The complete code for SlidePuzzle.vb can be found here.
Be forewarned, the Pocket PC 2002 Emulator hates you. It takes forever to load the first time and likes to complain about build errors that don't exist. Just persist. Once you've got it up and running the first time, don't close it down. You can leave the emulator running between tests. In fact, you probably should.
Hit F5 and take a break. Go have a nap, get a latte, see a movie. After awhile, the emulator will finish loading – don't click on it until you see your blank white form on the screen, regardless of whatever instructions it shows you. (First-time users may be tempted to actually obey its imperative to tap the screen to configure the stylus.)
If you get a build error but don't see any actual code problems, the emulator might have taken so bloody long that the code timed out. Just try again. Debug any legitimate coding errors and treat yourself to a game or two once it works.
When you're done, close the application. In fact, close the solution as well. As mentioned before, you can't include two deployment platforms in the same solution. You can, however, share code between them. Which is what you're about to do.
Setting Up the Desktop Version of the Puzzle
To set up the desktop version, create a new project, much as you did before. But this time specify a "Windows Application" instead of a "Smart Device Application." Call this one "SlidePuzzlerPC."
Before you do anything with the form, take a moment to enjoy the fruits of your labor by adding two new existing items to your project. Right-click the project and choose Add -> Add Existing Item. Navigate to your SlidePuzzler project, select both Tile.vb and Grid.vb and hit Open. You're now 2/3rds of the way done with an entirely new deployment platform in about the time it took to read this.
For this version, because you have more screen space to work with, you can afford a larger picture. The thumbnail below links to "Gaia_full.jpg", which is approximately 400 by 600. (You'll see some interesting things by introducing rectangular dimensions, but I won't give it away.)
Once you copy the image to your project directory, add it to the project. Remember to change "Build Action" to "Embedded Resource."
Creating the Desktop UI
Now to add all the code to your form. On second thought, what the heck – just use your Pocket PC version. Add it to the project the same way as the other classes. But take a moment to edit the code and make a name change: Search for SlidePuzzler.gaia.jpg and change it to SlidePuzzlerPC.gaia_full.jpg instead (note the addition of "PC" to the namespace).
To finish the job, delete Form1. Remember to edit the Properties for the project itself and change "Startup Object" to "SlidePuzzle." One small workaround: Bringing in your primary f orm from another project causes problems with its .resx file. Just navigate to this project and delete your "SlidePuzzle.resx" file so it can be recreated.
At this point, you have a working app. If you don't believe me, hit F5 and see for yourself. Just for fun, resize the form on the desktop and start a new game. Same code, but already you can see some flexibility in action.
Take Advantage of the Platform
Though you've just targeted two platforms with a single code base, you haven't really done a lot to take advantage of the desktop. As a sample of something you can do, I've added a short routine for switching out the game graphic by browsing to a new image.
First, add a few things to the form.
- Switch to Design View for SlidePuzzle.vb.
- From your toolbox, add an OpenFileDialog to the space below the form.
- In the menu, which appears at the top now, add a new menu option to the right of "Menu." Call this one "File" and name the object "FileMenu" within Properties.
- Under "File", add one new sub-menu option called "Open". In the Properties for this, call it "FileOpen."
- Double-click "Open" to create a new event handler.
This should take you to the code, to which several new objects have been added. Specifically, you'll be in the FileOpen_Click() procedure. From there, add the following code:
Me.OpenFileDialog1.Filter = _ "Image Files(*.bmp;*.jpg;*.gif)|*.bmp;*.jpg;*.gif" Dim dr As DialogResult = OpenFileDialog1.ShowDialog() If (dr = DialogResult.OK) Then Dim ImageFileName As String = OpenFileDialog1.FileName m_PuzzlePicture = New Bitmap(ImageFileName) End If
Now when the user chooses a New game, this new image will be used instead of the default image. You can easily add new code to reset the puzzle automatically or otherwise indicate that the new picture has been loaded.
The complete code for this version of SlidePuzzle.vb can be found here.
Analyze This
One last thing before deploying your application and raking in millions: analyze it! Coding for low-resource devices harkens back to the old days of trying to cram Hunt the Wumpus into 256 bytes of RAM. But with a twist. Today's devices are incredibly more complex, so optimizing means a lot more than just taking up less space with your code.
Enter Intel® VTune™ Performance Analyzer. Though it may be an overkill for a simple demo like this one, consider the Intel VTune performance analyzer to be an essential part of your toolkit when it comes to commercial deployment, especially for handheld devices. Using the Intel VTune performance analyzer, you can spot bottlenecks and performance issues, like high clockticks or long function call wait times. You can even compare performance with both the disassembly and the source code itself, giving you the ability to track down the cul prit. And for that extra assist, the Intel VTune analyzer gives you tips on how to deal with the problem.
Just for kicks, I ran SlidePuzzler through the Intel VTune analyzer. Though the bulk of the processor load came from the Framework itself, I did spot a couple of small bottlenecks in this walkthrough. Can you find them?
This walkthrough showed you some simple code that can be improved upon any number of ways. But it demonstrates several important points:
- You can easily double the impact of your coding efforts through some upfront planning.
- You can leverage your code much easier by starting with the most restrictive platform and scaling up from there.
- The more code you can remove from your UI, the less you'll have to duplicate on the other platforms. As you saw here, though, quite a lot of the UI code can be shared as well. If you're building a Smartphone or ASP.NET application, this might not always be the case.
- With a few minor design considerations, you can take advantage of increased client resources, such as larger screen size, without even coding for them.
Advances in IDE tools and the convergence of disparate technologies give developers all new possibilities. With a little pre-planning, source code can be extended across several different platforms at once, opening up new markets and new revenue opportunities. Instead of porting code after the fact, extend it. And reap the benefits.
Justin Whitney is a senior product marketing engineer within the Intel Architecture Marketing Group. His project responsibilities have included Intel Rapid BIOS Boot, Intel Express BIOS Update, and the Superconducting Liquid Cryogenic Level Sensor, for which he holds a patent. Justin holds a B.S. in mechanical engineering from Northwestern University.
Public Class Tile
Inherits System.Windows.Forms.Control
Private m_PuzzlePicture As Image
Private m_DisplayRect As Rectangle
Public CurrentLoc As Integer
Public Sub New(ByVal m_Image As Image, ByVal m_X As Integer, _
ByVal m_Y As Integer, ByVal m_Width As Integer, _
ByVal m_Height As Integer, ByVal m_Name As Integer)
m_PuzzlePicture = m_Image
m_DisplayRect = New Rectangle(m_X, m_Y, m_Width, m_Height)
Location = New Point(m_X, m_Y)
Size = New Size(m_Width, m_Height)
Text = m_Name
CurrentLoc = m_Name
End Sub
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
MyBase.OnP
aint(e)
e.Graphics.DrawImage(m_PuzzlePicture, ClientRectangle, _
m_DisplayRect, GraphicsUnit.Pixel)
End Sub
End Class
Public Class Grid
Public TileSet As Tile()
Private m_PuzzlePicture As Image
Private m_TileRows, m_TileCols, m_BlankTile As Integer
Private m_TileWidth As Integer
Private m_TileHeight As Integer
Public Sub New(ByVal m_Picture As Image, _
ByVal m_RowCount As Integer, ByVal m_ColCount As Integer, _
ByVal m_Width As Integer, ByVal m_Height As Integer)
m_PuzzlePicture = m_Picture
m_TileRows = m_RowCount
m_TileCols = m_ColCount
m_TileWidth = m_Width
m_TileHeight = m_Height
Setup()
End Sub
Private Sub Setup()
Dim tileCount As Integer = (m_TileRows * m_TileCols)
ReDim TileSet(tileCount - 1)
m_BlankTile = tileCount - 1
Dim thisX, thisY, thisRow, thisCol, thisTile As Integer
For thisRow = 0 To m_TileRows - 1
For thisCol = 0 To m_TileCols - 1
thisTile = (thisRow * m_TileRows) + thisCol
thisY = (thisRow * m_TileHeight)
thisX = (thisCol * m_TileWidth)
TileSet(thisTile) = New Tile(m_PuzzlePicture, _
thisX, thisY, m_TileWidth, m_TileHeight, thisTile)
Next
Next
TileSet(m_BlankTile).Visible = False
End Sub
Public Sub Swap(ByVal m_Tile1 As Integer, ByVal m_Tile2 As Integer)
'First, swap actual locations in the grid.
Dim tmpPnt As Point = TileSet(m_Tile1).Location
TileSet(m_Tile1).Location = TileSet(m_Tile2).Location
TileSet(m_Tile2).Location = tmpPnt
'Second, swap identifier members.
Dim tmpLoc As Integer = TileSet(m_Tile1).CurrentLoc
TileSet(m_Tile1).CurrentLoc = TileSet(m_Tile2).CurrentLoc
TileSet(m_Tile2).CurrentLoc = tmpLoc
End Sub
Public Function GetBlank() As Integer
Return m_BlankTile
End Function
Public Sub CheckTile(ByVal m_ThisTile As Integer)
Dim intThisTile, intXDiff, intYDiff As Integer
intXDiff = Math.Abs(TileSet(intThisTile).Left - _
TileSet(m_BlankTile).Left)
intYDiff = Math.Abs(TileSet(intThisTile).Top - _
TileSet(m_BlankTile).Top)
If ((intXDiff = 0) And (intYDiff <= m_TileHeight)) _
Or ((intXDiff <= m_TileWidth) And (intYDiff = 0)) Then
Swap(intThisTile, m_BlankTile)
End If
End Sub
Public Sub Shuffle()
Dim swapper, i As Integer
Randomize()
For i = 1 To 100
swapper = CInt(Int(m_BlankTile * Rnd()))
Swap(swapper, m_BlankTile)
Next
End Sub
Public Function IsSolved() As Boolean
Dim solved As Boolean = True
For thisTile As Integer = 0 To UBound(TileSet)
If (TileSet(thisTile).CurrentLoc <> CStr(thisTile)) Then
solved = False
Exit For
End If
Next
If solved Then TileSet(m_BlankTile).Visible = True
Return solved
End F
unction
Public Sub Solve()
Dim thisTile As Integer
For loc As Integer = 0 To UBound(TileSet)
thisTile = 0
While TileSet(thisTile).CurrentLoc <> loc
thisTile += 1
End While
Swap(thisTile, TileSet(thisTile).CurrentLoc)
Next
TileSet(m_BlankTile).Visible = True
End Sub
End Class
Public Class SlidePuzzle
Inherits System.Windows.Forms.Form
Friend WithEvents PuzzleMenu As System.Windows.Forms.MainMenu
Private m_PuzzleBoard As Grid
Private m_PuzzlePicture As Image
Private m_Blank As Integer
Private m_TileRows, m_TileCols As Integer
Private m_TileWidth, m_TileHeight As Integer
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
MyBase.Dispose(disposing)
End Sub
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents PuzzleBox As System.Windows.Forms.Panel
Friend WithEvents MenuNew As System.Windows.Forms.MenuItem
Friend WithEvents MenuSolve As System.Windows.Forms.MenuItem
Friend WithEvents MenuExit As System.Windows.Forms.MenuItem
Friend WithEvents MainMenu As System.Windows.Forms.MenuItem
Private Sub InitializeComponent()
Me.PuzzleMenu = New System.Windows.Forms.MainMenu
Me.MainMenu = New System.Windows.Forms.MenuItem
Me.MenuNew = New System.Windows.Forms.MenuItem
Me.MenuSolve = New System.Windows.Forms.MenuItem
Me.MenuExit = New System.Windows.Forms.MenuItem
Me.PuzzleBox = New System.Windows.Forms.Panel
'
'PuzzleMenu
'
Me.PuzzleMenu.MenuItems.Add(Me.MainMenu)
'
'MainMenu
'
Me.MainMenu.MenuItems.Add(Me.MenuNew)
Me.MainMenu.MenuItems.Add(Me.MenuSolve)
Me.MainMenu.MenuItems.Add(Me.MenuExit)
Me.MainMenu.Text = "Menu"
'
'MenuNew
'
Me.MenuNew.Text = "New"
'
'MenuSolve
'
Me.MenuSolve.Text = "Solve"
'
'MenuExit
'
Me.MenuExit.Text = "Exit"
'
'PuzzleBox
'
Me.PuzzleBox.Size = New System.Drawing.Size(240, 256)
'
'SlidePuzzle
'
Me.Controls.Add(Me.PuzzleBox)
Me.Menu = Me.PuzzleMenu
Me.Text = "Slide Puzzle"
End Sub
#End Region
Private Sub MenuExit_Click(ByVal sende
r As System.Object, _
ByVal e As System.EventArgs) Handles MenuExit.Click
Me.Close()
End Sub
Private Sub MenuSolve_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MenuSolve.Click
m_PuzzleBoard.Solve()
MessageBox.Show("Solved!")
End Sub
Private Sub MenuNew_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MenuNew.Click
'Clear out old puzzle
While PuzzleBox.Controls.Count > 0
PuzzleBox.Controls.Remove(PuzzleBox.Controls(0))
End While
'Puzzle Dimensions
m_TileRows = 4
m_TileCols = 4
'Resize Panel
PuzzleBox.Size = New Size(ClientSize.Width, _
ClientSize.Height)
'Calculate Tile size
m_TileWidth = CInt(ClientSize.Width / m_TileCols)
m_TileHeight = CInt(ClientSize.Height / m_TileRows)
'Create the Puzzle Image
'Format is [namespace].[filename]
' To use a different graphic for the puzzle, change it here.
If m_PuzzlePicture Is Nothing Then
m_PuzzlePicture = New _
Bitmap(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("SlidePuzzler.gaia.jpg"))
End If
For thisTile As Integer = 0 To UBound(m_PuzzleBoard.TileSet)
PuzzleBox.Controls.Add(m_PuzzleBoard.TileSet(thisTile))
AddHandler PuzzleBox.Controls(thisTile).Click, _
AddressOf TileClick
Next
m_PuzzleBoard.Shuffle()
End Sub
Public Sub TileClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs)
Dim t As Tile = sender
m_PuzzleBoard.CheckTile(t.Text)
If m_PuzzleBoard.IsSolved Then
MessageBox.Show("Congratulations! You solved it!")
End If
End Sub
End Class
Public Class SlidePuzzle
Inherits System.Windows.Forms.Form
Friend WithEvents PuzzleMenu As System.Windows.Forms.MainMenu
Private m_PuzzleBoard As Grid
Private m_PuzzlePicture As Image
Private m_Blank As Integer
Private m_TileRows, m_TileCols As Integer
Private m_TileWidth, m_TileHeight As Integer
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
MyBase.Dispose(disposing)
End Sub
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend Wi
thEvents PuzzleBox As System.Windows.Forms.Panel
Friend WithEvents MenuNew As System.Windows.Forms.MenuItem
Friend WithEvents MenuSolve As System.Windows.Forms.MenuItem
Friend WithEvents MenuExit As System.Windows.Forms.MenuItem
Friend WithEvents MainMenu As System.Windows.Forms.MenuItem
Friend WithEvents OpenFileDialog1 As System.Windows.Forms.OpenFileDialog
Friend WithEvents FileMenu As System.Windows.Forms.MenuItem
Friend WithEvents FileOpen As System.Windows.Forms.MenuItem
Private Sub InitializeComponent()
Me.PuzzleMenu = New System.Windows.Forms.MainMenu
Me.MainMenu = New System.Windows.Forms.MenuItem
Me.MenuNew = New System.Windows.Forms.MenuItem
Me.MenuSolve = New System.Windows.Forms.MenuItem
Me.MenuExit = New System.Windows.Forms.MenuItem
Me.PuzzleBox = New System.Windows.Forms.Panel
Me.OpenFileDialog1 = New System.Windows.Forms.OpenFileDialog
Me.FileMenu = New System.Windows.Forms.MenuItem
Me.FileOpen = New System.Windows.Forms.MenuItem
Me.SuspendLayout()
'
'PuzzleMenu
'
Me.PuzzleMenu.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.MainMenu, Me.FileMenu})
'
'MainMenu
'
Me.MainMenu.Index = 0
Me.MainMenu.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.MenuNew, Me.MenuSolve, Me.MenuExit})
Me.MainMenu.Text = "Menu"
'
'MenuNew
'
Me.MenuNew.Index = 0
Me.MenuNew.Text = "New"
'
'MenuSolve
'
Me.MenuSolve.Index = 1
Me.MenuSolve.Text = "Solve"
'
'MenuExit
'
Me.MenuExit.Index = 2
Me.MenuExit.Text = "Exit"
'
'PuzzleBox
'
Me.PuzzleBox.Location = New System.Drawing.Point(0, 0)
Me.PuzzleBox.Name = "PuzzleBox"
Me.PuzzleBox.Size = New System.Drawing.Size(240, 256)
Me.PuzzleBox.TabIndex = 0
'
'FileMenu
'
Me.FileMenu.Index = 1
Me.FileMenu.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.FileOpen})
Me.FileMenu.Text = "File"
'
'FileOpen
'
Me.FileOpen.Index = 0
Me.FileOpen.Text = "Open"
'
'SlidePuzzle
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(292, 266)
Me.Controls.Add(Me.PuzzleBox)
Me.Menu = Me.PuzzleMenu
Me.Name = "SlidePuzzle"
Me.Text = "Slide Puzzle"
Me.ResumeLayout(False)
End Sub
#End Region
Private Sub MenuExit_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MenuExit.Click
Me.Close()
End Sub
Private Sub MenuSolve_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MenuSolve.Click
m_PuzzleBoard.Solve()
MessageBox.Show("Solved!")
End Sub
Private Sub MenuNew_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MenuNew.Click
'Clear out old puzzle
While PuzzleBox.Controls.Count > 0
PuzzleBox.Controls.Remove(PuzzleBox.Controls(0))
End While
'Puzzle
Dimensions
m_TileRows = 4
m_TileCols = 4
'Resize Panel
PuzzleBox.Size = New Size(ClientSize.Width, _
ClientSize.Height)
'Calculate Tile size
m_TileWidth = CInt(ClientSize.Width / m_TileCols)
m_TileHeight = CInt(ClientSize.Height / m_TileRows)
'Create the Puzzle Image
'Format is [namespace].[filename]
' To use a different graphic for the puzzle, change it here.
If m_PuzzlePicture Is Nothing Then
m_PuzzlePicture = New _
Bitmap(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("SlidePuzzlerPC.gaia_full.jpg"))
End If
'Create Puzzle Board
m_PuzzleBoard = New Grid(m_PuzzlePicture, m_TileRows, _
m_TileCols, m_TileWidth, m_TileHeight)
m_Blank = m_PuzzleBoard.GetBlank
For thisTile As Integer = 0 To UBound(m_PuzzleBoard.TileSet)
PuzzleBox.Controls.Add(m_PuzzleBoard.TileSet(thisTile))
AddHandler PuzzleBox.Controls(thisTile).Click, _
AddressOf TileClick
Next
m_PuzzleBoard.Shuffle()
End Sub
Public Sub TileClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs)
Dim t As Tile = sender
m_PuzzleBoard.CheckTile(sender)
If m_PuzzleBoard.IsSolved Then
MessageBox.Show("Congratulations! You solved it!")
End If
End Sub
Private Sub FileOpen_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles FileOpen.Click
Me.OpenFileDialog1.Filter = _
"Image Files(*.bmp;*.jpg;*.gif)|*.bmp;*.jpg;*.gif"
Dim dr As DialogResult = OpenFileDialog1.ShowDialog()
If (dr = DialogResult.OK) Then
Dim ImageFileName As String = OpenFileDialog1.FileName
m_PuzzlePicture = New Bitmap(ImageFileName)
End If
End Sub
End Class
For more complete information about compiler optimizations, see our Optimization Notice.


pavan
its nice