Extending a Single .NET* App Across Two Platforms

Introduction

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.

Requirements

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.

Getting Started

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.

 

Testgrid.gif

Gaia.jpg

At this point, the form and the project itself are both ready. Let's code.

Building the Tile Class

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

 

Building the Grid Class

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.

UI Code

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.

Testing the Pocket PC Application

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?

Conclusion

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.

Related Links

 

 

About the Author

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.

 

Appendix

 

 

CODE: Tile.vb

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

 

CODE: Grid.vb

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

 

CODE: SlidePuzzle.vb for Pocket PC

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

 

CODE: SlidePuzzle.vb for Desktop

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

 

Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.
Categorias: