GDI+ Graphics: Adjusting Gamma

Summary

I’m going to do something a little different here.  I’m going to start a series of posts about the GDI+ graphics interface (in C#).  If you have followed my blog for a while, you already know that I have a demonstration game I wrote Battle Field One.  The last version I blogged about uses SVG graphics to render the output in a browser.  In a future post I’m going to show how to use GDI+ graphics and replace the web interface with a standard windows forms interface.  So I’ll cover a few GDI+ techniques along the way and then I’ll incorporate these techniques in the game changes coming up.

GDI+

First, I need to explain GDI+.  GDI+ is the new version of the 2 dimensional graphics engine that can be accessed directly from the forms paint event (GDI stands for Graphic Design Interface).  GDI is not fast.  For fast, we would need to use DirectX (I’ll blog about DirectX and Direct 3D later).  Since Battle Field One is a turn-based game, we don’t need fast.  What we need is simple.  That’s why I’m going to use GDI+.

To setup a quick and dirty example, you can create a new Visual Studio project of type “Windows Form Application”.  Go to the code part of your form.  You’ll need to add the following “usings” at the top of your initial form:

using System.Drawing;
using System.Drawing.Imaging;


Now switch back to your form design and switch to the events tab of your properties window:


Scroll down until you find the “Paint” event and double-click on it.  A new event called “Form1_Paint” will be created in your code.  All of your GDI+ code will be entered into this event for now.

Now let’s put some png files in a folder of the project.  Create a new folder named “img”, download and copy this image into that folder:


mountains_01.png

Now put this code inside your Form1_Paint event method:

Image Mountains = Image.FromFile(“../../img/mountains_01.png“);

Graphics g = pe.Graphics;
g.DrawImage(Mountains, 100, 100, Mountains.Width, Mountains.Height);


Now run your program.  You should see a hex shaped image with some mountainous terrain:


Of course, this program is just rendering the png image that is sitting in the img directory.  The reason for the double “../” in the path is because the program that executes is inside the bin/Debug folder.  So the relative path will be two directories back and then into the img directory.  If you don’t specify the path correctly, you’ll get a giant red “X” in your window:


Adjusting the Gamma

One of the capabilities that we’ll need when switching the game to use GDI+ is that we need to darken the hex terrain where the current units cannot see.  In the game the visible cells will be rendered with a gamma that is 1.0, and the non-visible cells will be rendered with a gamma of 3.0 (larger numbers are darker, less than one will be brighter).

Now I want to demonstrate three mountain cells that represent a gamma of 1.0, 0.5 and 2.0.  In order to modify the gamma setting we’ll need to use the ImageAttributes object:

ImageAttributes imageAttributes = new ImageAttributes();
imageAttributes.SetGamma(1.0f, ColorAdjustType.Bitmap);


The DrawImage object can accept the ImageAttributes as a parameter, but we also need to add a few more parameters.  So I’m going to show the code here, and then I’ll discuss it:


Image Mountains = Image.FromFile(“../../img/mountains_01.png”);

Graphics g = pe.Graphics;

ImageAttributes imageAttributes = new ImageAttributes();
 
// normal gamma
imageAttributes.SetGamma(1.0f, ColorAdjustType.Bitmap);
g.DrawImage(Mountains,
        new Rectangle(100, 100, Mountains.Width, Mountains.Height),
        0,
        0,
        Mountains.Width,
        Mountains.Height,
        GraphicsUnit.Pixel,
        imageAttributes);

// lighter
imageAttributes.SetGamma(0.5f, ColorAdjustType.Bitmap);
g.DrawImage(Mountains,
        new Rectangle(200, 100, Mountains.Width, Mountains.Height),
        0,
        0,
        Mountains.Width,
        Mountains.Height,
        GraphicsUnit.Pixel,
        imageAttributes);

// darker
imageAttributes.SetGamma(2.0f, ColorAdjustType.Bitmap);
g.DrawImage(Mountains,
        new Rectangle(300, 100, Mountains.Width, Mountains.Height),
        0,
        0,
        Mountains.Width,
        Mountains.Height,
        GraphicsUnit.Pixel,
        imageAttributes);

This is all the code you’ll need for this demo to work.  When you specify a rectangle for the second parameter, you will need to use the x,y coordinates of that rectangle to determine where the image will be plotted.  Leave the DrawImage x,y set to zero.  Use the image.Width and image.Height if you want to maintain the scaling of the image itself.  Otherwise you can adjust these parameters to blow up or shrink an image.

If you run the sample, you’ll see that there is a normal mountain hex on the left, a lighter hex to the right of it and then a darker hex to the right of that:

Download the Sample Code

You can go here to download the zip file for this demo application: 

GDIPlusGammaDemo.zip




 

The Game – Artillery


Summary

So BattleField One has gotten a little more serious.  My goal in this project is to design and build a strategy game engine.  Eventually I’ll demonstrate how to modularize the interface so that the game can operate with DirectX instead of a web-based interface using SVG.  My method of development is to do this in tiny chunks the way that XP programming is performed.  I never want to get into a situation where I need to do months worth of work in order to get the program back into a playable position.  So I’m building a feature and making the game playable, then building a feature, etc.




Artillery Units


In this blog post I’m going to show how to incorporate the unit range feature by adding artillery.  For those who have no military knowledge, artillery is nothing more than a large cannon with lots of range.  Artillery positions are normally miles away from the target.  But artillery and their crew cannot see very far away, so they need a spotter.  There is also a movement restriction on artillery, but I’ll be ignoring that for now (most artillery need a vehicle to tow it into position). 


So I drew a unit type and modified the game to include this unit in it’s case statement (see the UnitClass.cs file).  Here’s what the new unit looks like:


This unit has an attack strength of 14 a defense of 2, the range is 3 hex cells and it is able to move one cell per turn.  The tiny “14” at the top is the unit number, which I use for troubleshooting purposes.  I have updated the javascript side of this program to prevent this unit from attacking an enemy unit that is not visible.  I also updated the SnapTo() javascript function to allow a range greater than 1.



New Map

I designed a new map to represent a beach invasion scenario.  The new map looks like this:


The ocean cells will block any unit from passing.  The beach cells act the same as grass.  The goal of this game is to take all cities that the German units are protecting.  So I modified the CheckForEndOfGameCondition() method to call a new object that can be initialized with different goal conditions.  The new object is called VictoryCalculator().  It can be setup to a condition where the enemy (or allies) must defend a given number of cities instead of just destroying all enemy units.  I have also designed to to be extendable to allow game conditions where German or Allied units must retain a certain number of units to win.  I don’t currently have a game turn limit and (number of turns to meet objective) but it could also be added to this object in the future.

Getting the Code

You can go to my GitHub account and download the zipped up version of this project (Click Here).



 

The Game – Forest Terrain

Summary

In my last blog post on the game I demonstrated how to setup blocked terrain cells (aka mountains).  Now I’m going to introduce a forest cell that will block tanks but not infantry.


The Forest Terrain Type

I wanted to introduce a forest terrain type so that I could prevent tanks from penetrating (assume this is a forest of very large trees), but allow infantry to penetrate.  First, I used the same Google Maps screen shot technique to create a forest hex cell:


Then I setup a test map to test my shortest path algorithm:


Modifying the Code

In my previous code, I created a property to handle the blocked terrain.  This was located inside the GameMap object.  In order to test both the terrain and unit combinations I had to change this into a method that uses the unit type integer as a parameter.  The forest terrain number is 7 and a tank is unit type 2.  So I setup the Blocked() method to check for this combination and return true (as in, terrain will block this unit type).  Eventually, I’ll need to extend this simplistic method to include an array of terrain types verses unit types.  Before I do that, I’ll probably introduce a technique of slowing down the progress of a unit.  For this instance I’ll make the unit travel slower through the forest.  For now though, I’m going to just provide a flag for blocked or not blocked.

Now that the Blocked property has been converted to a method, any calling methods need to be re-factored to pass the unit type.  Then I tested to see if the tank went around the wall of forest and the unit went through the forest.


Adding Unit Tests

I ended up adding the unit tests after I made this work.  Technically, I could have added them first and used the unit tests in order to build the code.  The purpose of adding unit tests after the fact is that I want to make sure that future changes don’t break this feature.  So you’ll see two new unit tests that will test an infantry unit and a tank unit path calculation for the sample map above.


Getting the Code

You can go to my GitHub account and download the zipped up version of this project (Click Here).

 

The Game – A* Algorithm

Summary

I’ve been working on this game called Battlefield One and it has a very simple algorithm for computing the next move that an enemy unit will take in order to reach it’s goal.  The algorithm used searched all 6 surrounding hex and eliminates illegal hex points (off the grid or occupied by a unit).  Then it computes the distance from each grid point to the destination and takes the shortest one.  In this blog post, I’m going to put in some obstacles in the game and demonstrate the flaw in the algorithm I chose.  Then I’m going to demonstrate how to use the A* algorithm to work on a hex grid instead of an 8-way or 4-way square matrix.

Adding Mountains

First, I painted a mountain hex in Photoshop.  You can make up any hex terrain you wish, but I just went to Google Maps and copied some of the Rockey mountains onto the clipboard and then pasted it into a hex block and erased to match the hex shape:


Then I turned off the green hex shape layer and saved my hex-shaped mountain terrain as a mountain.png file:


Fortunately, I had planned on having many terrain types, so I coded the DrawTerrain method to contain a switch for each terrain picture to render.  If you look at the code at GitHub you’ll see there are 4 grass terrains (I added some variety) and a new “mountains_01.png” terrain file type.


Next I modified the GameMap object to include a new “Blocked” property.  If the terrain is equal to 6 (representing a mountain terrain type), then Blocked returns true, otherwise it’s false.

The FindAdjacentCells(x,y) method inside the GameBoard object was modified to use this Blocked property.  This method will return all cells that are on the map board but not mountain cells.  There are several unit tests that check different conditions (edge of map, corner of map, near mountains and in the middle).


Now to setup a test board to test the blocked cells:


This is a 7 by 6 cell gameboard with a German unit on cell 0,3 a city on cell 6,3 a spare allied unit in the lower right corner (to keep the game from ending with a German win).  Finally, I put a line of mountains blocking the German unit from reaching the city.  With the simple straight line algorithm of navigation, the German unit will go to the wall and toggle between cells 2,3 and 2,2.  



The A* Algorithm

Next searched for an A* algorithm that I could adapt.  I ended up using this web site to explain the details and I wrote the entire thing from the ground up:

A* Pathfinding for Beginners

The A* algorithm uses two lists to contain search nodes.  The open list and the closed list.  I created an object that represented one search node and called it AStartNode.  This node needs to contain the F, G and H variables as described in the beginner guide (I set those to integers).  The F variable is nothing more than G + H, so I just made a getter that adds G and H and returns the result as F.  The next variables I needed was the X,Y coordinate of the cell that this node will represent and finally the location of the cell that was searched from (called Source).  The constructor for the AStarNode just stores the values in the getters and then it computes the distance to the destination (which is an approximation of the distance as the crow flies).

Next, I created a list to contain AStarNodes.  This generic list class is called AStarNodeList (yeah, not creative, but obvious).  Instances of this list becomes the open and closed lists.  That means that I can put all the methods I need inside this list to manipulate the nodes being processed.  The FindSmallestNode() method is very useful.  It finds the node with the lowest F value and returns it (after it removes it from the list).  This is where I will grab the smallest node and find all it’s surrounding nodes and then push it on the closed list.  I created a Contains() method to be used when I get surrounding nodes and I want to not save the ones that are already in the closed list.  I created a GetNode(x,y) so I can read the nodes off the list in order to build the way point list (the goal of this whole exercise).

Next is an object called ShortestPath.  This object is what does the work of putting the first node on the open list, then calling the FindPath() method which will recursively find the smallest node and find it’s neighbors until it runs into the destination point.  I put in an iterations counter and made sure I exited if it hit 50.  This max might need to be incremented if the map size is larger (you’ll know if the unit goes almost to its destination and stops).  I wanted to make sure I didn’t get an infinite loop while I’m testing.

There is a method called GetWayPoint(x,y) inside the ShortestPath object.  This is used by the game to get the next way point.  Basically, to ensure that I can provide backwards compatibility, I just made my CollectGermanMovementData() method call this method first.  This method will check to see if the WayPoint list has any nodes.  If not, then a null is returned and CollectGermanMovementData() will grab the coordinates using the old fashioned direct calculation method (because lCoordinates will be null).  If the first way point variable is equal to the unit x,y coordinates, then it is removed and the next coordinates are returned (but not removed from the list).  The reason this is left on the list is in case the unit runs into an allied or even a German unit and is temporarily blocked.  For now, it will stop and attack (if Allied unit) and continue on its way when it is no longer blocked.  In the future, I’ll make it recompute the path (if it’s just going around and not attacking).  Baby steps.


How to Build Something This Complicated

This algorithm is a bit complicated and there is a lot going on.  So I create a visio document with the test map fully numbered.  Then I plotted the F, G and H numbers for the first iteration:


I also put arrows on the diagram to point to the previous cell.  Then I put a breakpoint in my AStarNode object and I ran the program.  The first AStarNode was node 0,3.  Then 0,2.  That’s when I checked to make sure that G=1, H=6 and F=7 (the red numbers in the corners of each cell).  Then I checked the next cell, which turned out to be cell 0,4, and so on.  On the next iteration, things got a bit complex so I added Log4Net to my application, spit out the node being worked on and listed the nodes that were pushed onto the open list.  Eventually, I was able to walk through the log file and see the order that the algorithm was using to walk toward the goal of cell 6,3.  

The last task of this project was to make sure the list was read back into the way point list.  You have to walk the list backwards because the AStarNode contains the x,y coordinates for the previous node.  A simple while loop walked this back to the starting point and I just inserted it backwards into the WayPoint list, leaving the starting point out of the list (since the GetNextWaypoint() method takes care of that anyway.


Get the Code

You can go to my GitHub account and download the zipped up version of this project (Click Here).  Assuming you have Visual Studio 2012 or better, you should be able to re-build the project and make this work.  If you want to run the sample as shown in this blog post, you can go to the HomeController.cs file and change the GameData.InitializeGame(1) to a 3.  Then go to the GameBoard.cs file (in the core project) and at the top is a TestMode boolean variable that you should set to true.  Then run the game and you’ll see the map pictured earlier in this post.  You can just click the next phase button and watch the German unit navigate to the city on the right side of the map.  If you want to dig through the code, you can un-comment the log4net logging inside the ShortestPath.cs file and run the program, then look at the log file (which will be located in c:logs.

Last, you should take a look at how the non-A* algorithm failed.  You can do that by going to the GameClass.cs file.  Search for the SetEnemyStrategy() method and you’ll see two places where the ComputePath() method is called.  Comment these two lines and run the program.  You’ll see the German unit walk right up to the mountains and get stuck going back and forth.

 

Game Design, Back to Battlefield One

Summary

A while back I wrote a sample game called Battlefield One.  This game was a turn-based war game that was built on SVG, javascript and C#.  I have since converted this game into an MVC application (although it still uses the same code-behind techniques and I keep track of the game state with a session).  I have also refactored much of the game to expand it, add unit tests, make the dimensions more flexible and I added tanks.
 

Review

OK, here’s how the game was designed:

1. The game board is rendered in hex cells.
2. Game units do not stack, only one unit can occupy a cell at a time
3. Attack distance assumed to always be 1 for simplicity.
4. No terrain effects.
5. Capture all cities to win.
6. Destroy all enemy units to win.
7. Movement phase then attack phase.
8. Areas of board not visited will be blacked out.
9. Areas not visible to any unit will be dimmed and not display any enemy units.

I improved the AI some since the first game was written:

1. Enemy units will attack the lowest defense numbered unit.  This causes many enemy units to gang up on one nearby unit.  Reducing the number of Allied units quicker and reducing the number of future Allied attacks.

2. All enemy units will choose a city to move toward.  

3. There is a test flag in the GameBoard object that will allow you to turn off the masks so you can test the game.  This mode will also draw red lines between the units and the destination they have chosen.

4. If there are two allied units next to an enemy unit and one allied unit is on a city, the enemy unit will attack the unit on the city first.


Algorithms Used

The Die Roller object was improved.  Instead of using one 6-sided die, I used a combination of three die rolls.  The problem with one die is that the outcome is linear and the game is boring.  As you use more dice, the outcome becomes more like the normal distribution of events and your attack and defense outcomes become more realistic.  This was not much of a problem until I introduced tanks which could inflict a damage of 1 or 2.  I wanted to have a damage of 1 occur more often than a damage of 2.  Wolfram Alpha has an article on this here.  My interest was in this chart (from the wolfram article), which explains it all visually:




I added unit tests to complex parts of the game.  The original game was written without any unit tests.  This was due to the fact that the original game was only a demonstration of what can be done and I only needed it for an example in a blog post.  Now I intend to turn this into a project (aka hobby) that I will add features and blog about the features (like AI enhancements).

I added a unit list class.  This is a list of units for the entire game and it allowed me to refactor some methods that were performed against the list of units.   This object also has an AddUnit method to insert a unit into the list without referencing the list of units (called “Items”) directly.

I added a game board class to contain all the information and methods pertaining to the game board itself.  Each cell now references a GameMap object containing variables for the terrain, mask and visibility settings.  The previous version of this game contained three arrays to keep track of this information and I wanted a way to be able to change the game board size at game startup.

I changed the map background pieces into individual hex pictures so I could create a board of any size.  The original game had a large painted background that was fixed in size and had 4 cities painted in place.  By creating hex images of the green areas and a hex image of a city, I can just set the array at game start with the map size and city positions I want.  Future versions can contain a mixture of different textures to make the game more interesting.

There was a bug involving the movement of playing pieces.  The movement calculator would follow a straight line and then snap the unit to the closest board hex.  The problem with this algorithm is that any piece traveling close to the edge of the map would stop because the closest hex position was off the map and the edge detector prevented the unit from going off the board.  The new algorithm gathers a list of up to 6 surrounding hex squares.  It will eliminate any hex that is off the board and any hex that is already occupied by another unit.  The remaining list of hex positions is measured by distanced to the destination and the shortest one is chosen as the next hex to enter.  This allows units to go around each other if necessary and the destination will always be reached unless it is totally blocked.


Playing the Game

You can go here to play the game on-line, or you can download the source code and experiment with the code yourself.  To download the source code, go to https://github.com/fdecaire/BattleFieldOne and download the zip file (there’s a button in the lower right corner).