Step Three: Add a W80 Word Database
With Visual Studio 2008, you can build and manage SQL Server Compact Edition 3.5 databases directly in the IDE. In the Data Sources pane, you can launch the "Add New Data Source" wizard to pull in an existing database or create a new one. Since any good word game uses lots of words, you can find a 99-word database here. Download it and pull it into your app by doing the following:
- Choose Add New Data Source
- Choose "Database" as Data Source Type
- Click New Connection
- Add Connection should indicate ".NET Framework Data Provider for Microsoft SQL Server Compact 3.5" as the Data source. Under Database you can Create or Browse for your database. If this was a new database, you could create it here and the wizard would walk you through building it column by column. But in this case, choose Browse and navigate to your download folder for the W80Base.sdf file.
- This database is not password protected, so click Test Connection to make sure you can read it. If it succeeds, then click Ok.
- Click Next. You should get a prompt that "the connection you selected uses a local data file that is not in the current project." Go ahead and copy the file to your project and modify the connection automatically.
- When choosing your Database Objects, you'll have the option of only one table and only one field (like I said, this is a very simple database). Check the box next to Tables and name the DataSet "W80BaseDataSet".
- Click Finish. The wizard should add the new database to your project and create a W80BaseDataSet.xsd file for it, along with three other files: W80BaseDataSet.Designer.cs, W80BaseDataSet.xsc, and W80BaseDataSet.xss.
- In your Data Sources pane, you should be able to navigate through your new database, including bringing up a Preview window.
Step Four: Configure and Connect to the Database
Open up W80BaseDataSet.xsd and you'll see a very simple diagram of the database, which includes just the WordList table, along with a Table Adapter. You have many ways for your app to connect to your data. Using the extra layer of abstraction generated by this process may add some overhead to your app beyond, say, hand-coding a Connection object. But it gives you a lot of nice flexibility and a fast way to get the job done.
By default, the database wizard you just ran adds all the data management queries you would need for a full read/write database. But in this case, you're just going to do simple reads, though it would be a good exercise to add word entry and editing functionality at a later date.
To remove some of the default functionality:
- Right-click on WordListTableAdapter and choose Configure.
- The default SQL Statement should be pretty simple: "SELECT [word] FROM [WordList]". Note that the TableAdapter Configuration Wizard offers you a Query Builder tool and advanced options—leave those for now and click Next.
- For Methods to Generate, you only want the first two: Fill a DataTable ("Fill") and Return a DataTable ("GetData"). If the last option is checked, then uncheck it. Click Next.
- On the review page, click Finish. You should return back to the .xsd design.
Though the adapter exists and is correctly configured, it's not going to show up in your Toolbox until you compile the project. So go ahead and Build.
Check your Toolbox. You should see WordListTableAdapter available under "W80Word Components".
To finish hooking up the database:
- Go back to the design view of the mobile form. Drag the WordListTableAdapter control onto the form (onto the section at the bottom, below the device skin). Change its name to taWords.
- Also under "W80Word Components", you should see the W80BaseDataSet control. Drag it onto the form, too. In the configuration prompt, this should be a "Typed dataset" with the name W80Words.W80BaseDataSet.
- The new control should appear below the form. Rename it to "dsWordList".
Step Five: Add Game Classes
You need a couple of classes to handle the logic of the game. Go ahead and right-click on the project and select Add -> Class. Call the first one "Guesses.cs" (these are both C# classes). This class tracks the player's guessed letters for the current game.
Open Guesses.cs and replace the contents with Listing 1.
Listing 1: Guesses.cs
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
namespace W80Words
{
class Guesses
{
private string _word = "";
const int LIMIT = 6;
public Guesses()
{
}
public bool Contains(string letter)
{
return _word.Contains(letter);
}
public void Add(string letter)
{
_word += letter;
}
public bool isGameOver()
{
return (_word.Length >= LIMIT);
}
public int ImageIndex()
{
return _word.Length;
}
public string Get()
{
return _word;
}
}
}
Take a look through the code for Guesses.cs. Nothing really new here except for the default inclusion of a System.Linq references, which isn't used in this walkthrough.
In this version of the game, you get 6 guesses, hence the Constant value for LIMIT. All guesses are kept in a private string called _word as a solid string. This allows you to use string members that are not available to chars, so you can quickly compare and manipulate your variables.
Other functions: Contains checks for the existence of a single letter, which means it's already been guessed. Add appends a new letter to the end of those already guessed. Get returns the current list of guessed letters. isGameOver returns true if the number of guessed letters equals or exceeds the LIMIT constant.
ImageIndex returns the number of letters that have been guessed. This integer is used in choosing the correct picture to display on the form. For this walkthrough, I decided to hard-code the list of graphic assets in an ImageList control, then reference the images by index by simply returning the .Length property of the guessed letter concatenation thus far. One change you might consider for your own project is to move the images, or image file names, to a SQL Server Compact Edition database, or another table in the database used here. That way you have the option in the future of allowing users to load their own images to show game progress.
The other game class you need is Answer.cs, which holds the correct answer and provides members for checking against it and tracking progress. Create a new class called Answer.cs and replace its contents with Listing 2.
Listing 2: Answer.cs
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
namespace W80Words
{
class Answer
{
private string _word = "";
private string _sofar = "";
public Answer()
{
}
public string Get()
{
return _word;
}
public void Set(string word)
{
_word = word;
for (int i = 0; i < _word.Length; i++ )
{
_sofar += "0";
}
}
public bool Contains(string letter)
{
return _word.Contains(letter);
}
public void Add(string letter)
{
letter = letter.Substring(0,1).ToLower();
string newPattern = "";
for (int i = 0; i < _word.Length; i++)
{
if (_word.Substring(i, 1) == letter)
{
newPattern += "1";
}
else
{
newPattern += _sofar.Substring(i, 1);
}
}
_sofar = newPattern;
}
public bool isGameOver()
{
return !(_sofar.Contains("0"));
}
public string Draw()
{
string answer = "";
for (int i = 0; i < _word.Length; i++)
{
if (_sofar.Substring(i, 1) == "0")
{
answer += "_";
}
else
{
answer += _word.Substring(i, 1);
}
answer += " ";
}
return answer.TrimEnd();
}
public string RevealAnswer()
{
_sofar = _sofar.Replace("0", "1");
return Draw();
}
}
}
Again, the constructor for this class does nothing. The correct answer is kept in a private string called _word, while _sofar serves as a sort of binary mask. It's used to keep track of which letters to reveal during the game and stores either 0 or 1 for each letter position in the correct answer. This string is initialized in the Set method, where the correct answer gets assigned.
Whereas Guesses.Add appends an incorrect guess to its list, Answer.Add appends a correct guess, in a sense. More accurately, it iterates through the _sofar mask and flags the position to 1 for each occurrence of the current correct guess. This way, a win condition can be checked in isGameOver simply by looking for a 0.
The Draw routine returns the player's current progress in traditional Hangman format, i.e. with letters spaced and unguessed letters replaced with underscores. In the case of a lose condition (from Guesses.isGameOver), the correct answer is revealed by flipping all 0s to 1s in the _sofar mask and calling the Draw routine again.