Blog
Your Categories
Game Talk
Game Talk
March 31, 2011
I released the latest version, today. It has some new game play features and a UI tweak. The new features are:
- Mineral Resource
- New Units
- Terraforming
Mineral Resource
I changed Coral and White systems from producing units to producing a resource. I gave the resource the traditional name, Adamantine.
In the game's backstory, Adamantine is a rare mineral that is only found on Coral and Whites. Whites mine 1/3 of a metric ton per turn, while Corals produce 1/2 of a metric ton. This mineral is accumulated in a reserve each turn. It takes 1 metric ton to produce one of the new units, described below. Adamantine production is subject to the same bonus as that of the remaining systems that produce units. Whenever taxes are due (research costs), these systems stop mining and contribute to reducing the debt. The unit production value of the system (plus bonus) is used in this case.
The “reserve” is a public data member in the Player Class of type “float.” The “mine” is a field in the star system data structure, also, of type “float.” This field is initialized by the normal method that initializes the other fields of this structure. I, also, use this field as a flag to quickly determine whether or not a given system produces units or Adamantine since non-mining systems have a “zero” in this field.
When a player's Update Production method is called at its phase of the turn, the player's list of owned systems is first searched for the Adamantine producers. As these systems are found, the production bonus is calculated and the results accumulated in the reserve. This ensures that the total available Adamantine reserve can be used to build new units as they are produced by the other systems.
New Units
When a sufficient amount of Adamantine is available (one metric ton) one of the following new units becomes available to the players:
- Hornet (Attack Class)
- Attack: 4
- Defense: 1
- Yellow Jacket (Defense Class)
- Attack: 1
- Defense: 3
All other unit statistics are the same as for older units. As long as the Adamantine reserve is greater than or equal to one, a new unit is produced, otherwise the older unit is made. Old units are upgraded according to the order in which they are produced. I may give players more direct control over this but that is not a high priority, right now.
Naturally, these new units impacted the combat engine a bit. My solution was to design an attack and defense order for all units.
The attack order is
- Hornets
- Wasps
- Yellow Jackets
- Bees
- Yellow Jackets
- Bees
- Wasps
- Hornets
Terraforming
The last feature I added was terraforming or “stellar rejuvenation” which is more in line with the game's backstory, such as it is. The idea here is that each and every star, except yellow stars, are upgraded one level during a rejuvenation event. Yellow stars, being the ultimate in life support, are not affected. In this discussion, I'll use the terms “terraforming” and “rejuvenation” interchangeably.
There are two rejuvenation events in the game. One occurs when players reach 150 experience points, the other when players hit 300 points. The costs are 2000 production units per system for level 1 rejuvenation and 3000 for level 2.
By the end of the game, the galaxy contains mostly Yellow, Orange and Purple stars. Unfortunately, as Corals and Whites become Purple and Orange, they are still limited to producing Adamantine only. I'll probably change this in a future release, but that's a big step. In any event, the increase in a player's production totals as a result of rejuvenation more than compensates for the loss of Corals and Whites as unit producers.
The coding and debugging of this feature was a little tricky since upgrades are only applied to one star at a time. Gains and losses of systems by combat, also, can affect the algorithm.
This feature is controlled by several methods and data members in the Player Class. Since terraforming occurs one system at a time, I use a field in the star system data structure to contain the current terraforming level of that system. During the initialization of this structure, all yellow systems are assigned a value of “level 2”. This indicates that this system is at it's maximum level. All other systems have this value “zero'ed out”.
The Player Class has the following methods to control terraforming:
- InitTformingList
- Add2TformningList
- RemoveFromTformingList
The data memebers are:
- tFormingAmt – used to accumulate each system's contribution to the cost
- tFormingLvl – used to indicate the current level of terraforming within a player's empire.
- tFormingList – linked list of pointers to stars requiring terraforming.
The player's “Update Production” method was modified such that after all research costs have been paid, system production is directed to terraforming. Note that paying off research costs comes before accumulating terraforming costs. When updating production, a system is “terraformed” whenever “tFormingAmt” is greater than the current cost for terraforming a system (level 1 = 2000 units per system, level 2 = 3000). At this point, the system at the head of the terraforming list is upgraded. This involves
- Setting the system's terraforming level to “level 2” if it has been upgraded to a yellow star or if not, incrementing this value by “one”.
- If the star is at the current empire level of terraforming, its data structure pointer is removed from the terraforming list by calling “RemoveFromTformingList.”
- The star's production value is set according to its new type.
- The star's display tile sprite is, also, adjusted, accordingly.
- A display method is called to update the view screen.
Now, you may ask, what about systems I gain while terraforming is in progress? Not to worry, this is handled whenever players add new stars to their “owned list.” Code in this method (“AddStar2Owned”) checks whether or not the newly acquired star is at the empire's current level of terraformation. If it is not, “Add2TformingList” is called and the new system will start terraforming on the next turn. This method works even if the player is not, currently, undergoing an empire-wide terraforming event.
On the other hand, if a system on the terraforming list is lost, “RemoveFromTformingList” is called. If by some unhappy coincidence this system was the last on the list, measures are taken to redistribute any residue in “tFormingAmt” as described above.
And that, with a little bit of hand-waving, is stellar rejuvenation in a nut shell.
UI Changes
I'm, slowly, replacing the Windows Message Boxes I use to display some game information with elements more in keeping with the overall graphics theme of the game. This first effort reports combat results in a control panel overlay. Here's what it looks like:
In addition to printing the combat results, the system under contention is centered in the view screen. A small animation highlights the system for the player. Note that typing can be used, as well as the mouse, to close the panel and continue with the turn. This is the first time, I've added a keyboard input to the game.
February 2, 2011
I create a Graph class which I use as a library for any class that needs the functionality. The class contains two structures. The first is for the incidence list, the second is for edges.
typedef struct
{
int incidenceListCnt; // number of elements on the list
CDlList incidenceList; // head of the list
} incidence_t;
typedef struct
{
int cost; // distance to neighbor
void *pNeighbor; // pointer to neighbor
} edge_t;
I then add two public methods to the class:
void AddEdge (incidence_t *pList, void *pNeighbor, int cost);
void CleanList (incidence_t *pList);
“AddEdge” allocates list and edge structures from the heap, fills in the fields with the input arguments and adds the new edge to the list sorted by cost (low-to-high).
“CleanList” returns all of the allocated structures on the input list to the heap.
The vertices of the graph are just an array of star system data structures created during game initialiation. I add an element of type “pointer to incidence_t” to hold the incidence list for any given system.
This array of star system structures has been around since the early days of development. Whenever I've mentioned methods that use or manipulate star system data in one way or another, I'm implying that these methods all have access to pointers or lists of pointers into this array.
The star system array is initialized just after the star map is generated during game initialization. The creation of an incidence list for each star system is the last step in star system initialization.
The scanner method described earlier is modified to create the incidence lists for each system (vertex). The only input to the method is, now, a pointer to a given star system. The 2D array of “char” is not needed and the list of stars in range is, now, the incidence list element of the star data structure itself.
The set up for the scan is, virtually, identical to the one described, previously. Only the inner loop has been changed, somewhat. Here it is:
.
.
.
for (int j = xStart; j <= xEnd; j++)
{
if (((starInfo_t *pNeighbor=GetStar(j,i)) && // there's a star here
((j!=pVertex->loc.x) || (i!=pVertex->loc.y))) // not the source
{
int cost = GetDistance (&pVertex->loc, &pNeighbor->loc);
AddEdge (&pVertex->incidenceList, (void *)pNeighbor, cost);
}
}
.
.
.
where,
pVertex – the input argument (pointer to a star data structure)
GetDistance – calculates the Pythagorean distance between 2 points
GetStar – returns a pointer to a star data structure at that location or NULL if there isn't one
loc – an element of the star data structure that holds its “x/y” map location. It is of type POINT.
The fourth line ensures that the vertex itself is not put on the incidence list.
February 1, 2011
I released a new version of the game, today. It includes the two, new features I have been recently discussing, to wit, random starts and ship range limits. I, also, made some tweaks to the AI and to the map generator and fixed a few bugs.
AI Tweaks
I had, purposely, handicapped the AI a little during the early expansion phase of the game. It was, probably, obvious to everyone after a little trial-and-error, that 2 Wasps are going to fail to take that first star system more often than not. It, therefore, behooves a player to add one or more Bees to the fleet before embarking on that first conquest. I, usually, add two.
The AI was dumbed-down to only ever send 2 Wasps against the first system, which could result in a very slow start. Eventually, sheer luck would give it that first system, after which, it would make stronger fleets (2 Wasps + 1 Bee or 3 Wasps) for what was left of the initial, system grab phase of the game.
Now, the AI makes a fleet of 2 Wasps + 2 Bees before sallying forth to glory. Subsequent fleet creation is, also, stronger. In this part of the game, AI fleets must be one of the following:
2 Wasps + 2 Bees
3 Wasps + 1 Bee
4 Wasps
This has the intended effect of making the AI much more aggressive in the early game than in the previous version.
Map Tweaks
With the new range limits in place, I am a bit concerned that some maps may place systems outside the range of players' starships, especially, in the early game. This is unbalanced and unacceptable.
My first take on the problem is to squeeze the area within a quadrant in which systems can be placed. The normal algorithm places systems anywhere within the borders of the quadrant. There is a limit on how close stars may be to each other and, also, on how close to a border they can be. I, basically, reduce the size of a quadrant from 2..29, inclusively, to 2..26, inclusively. This helps a lot but is still not enough.
As a final (at least for now) condition, I stipulate that each home system must have at least 3 star systems within its initial range in order to continue with the game initialization. This does the trick. There are still outliers but with the two bonus range increases at 100 points and at 250 points, all the problems with out-of-range systems seem to be solved. I can't guarantee that last statement, but so far, so good.
These changes produce an interesting side-effect on game maps. Many maps, now, have somewhat large gaps in them, here and there. I think this makes for some very different types of gameplay, depending on where these gaps occur.
I find these changes to have made the game, such as it is, a lot more fun to play. I no longer can win every game, as I could before. Even using the minimap to pick a good start is not a sure way to victory.
Bug Fixes
I use the current star sprites in the minimap. The first release was still using my old sprites and looked very confusing, I'm sure. Sorry about that.
I fixed a problem selecting a defending fleet from within a swarm of moving fleets. This would, especially, occur around the rally point.
And, last, but not least, the scanner has been fixed.
January 30, 2011
With the scanner issue at long last finally resolved once and for all, I can focus on adding two features which are core to strategy games. The first is to generate random starts for the players' home systems. The second is to restrict the distances a fleet can travel given the current ship engine technology.
Random starts
This feature is part-and-parcel of strategy games since the dawn of time. It gives a feeling of novelty to each game as it mixes things up a bit. The original notion of fixing a player's starting position in the far corner was to give each player a chance at expansion without the danger of early conflict. They could come out of their corners fighting, so to speak.
However, after much play testing, I found that this notion produced a feeling of “sameness” to each game. Sure, every galaxy was different but the gameplay felt the same. Grab yellow, grab orange, grab purple. Back fill coral and white. Mow-down red, roll-over white, cream blue.
So, random starts seemed like a good way to break things up. However, I still need to ensure the CPs don't engage the player or each other too soon. This is because of the AI's war economy mode. When preparing for war, the CPs build auto-defenders and relatively large fleets. These are both costly and can cripple a CP that is still in the early stages of expansion.
To keep the CPs at some distance from each other, I experiment with a few restrictions on the randomness of the start. I limit starts to be within the first half of each quadrant. This is fine and dandy for the CPs but it makes the player's start not much better than being in the corner. I, finally, decide on an algorithm that let's the player's start range within the first 2/3rds of the quadrant while the CPs are restricted to the first half of their respective quadrants, more or less.
Restricted Travel
I think that as long as I'm mixing things up, I may as well add some starship engine technology. The concept of increasing the speed of the engines has been in the game since day zero. I, now, add the notion of setting the range of the engine, too. The idea of putting limits on starship range is pretty much an integral component of space strategy games.
This should be a piece of cake, he says to himself, knowingly. A private data member in the Player class can hold the maximum value of the allowable range. Then I can use my new-fangled scanner. As systems are vanquished, the scanner returns a list of newly discovered stars around the conquered system. I'll just calcumalate some new Pythagoreans based on distances from the current star and divide 'em up so's in-range stars go on the goal list and out-rangers go on the, um, out-of-range list which I'll just churn up and plug in with all the other lists. Oh, yeah.
So, I have a method for handling the range of newly discovered star systems but what happens when a player loses that system? I, uh, re-scan the star, find all the stars in the neighborhood and remove them from the goal list and put them on the out-of-range list. Which is great, unless one or more of those systems might still be in range of some other system the player owns. Well, then I can ...
To make a long story short, all the linked lists the memory can hold and all the cross-referencing the CPU can crunch ain't gonna get things done. I need something new, a new data structure. Something I, naively, thought I would never need. I need a graph.
I guess I could re-title this blog “How not to design a game.” How embarrassing. With a bit of dismay, I start to research graph theory on the net.
Graphs
The net is chock-full of information on graphs. What they are. Where they came from. What they do. Etc. After a little digging, I find what I want to know: how to make them. I visited several sites to get the general idea, so, I won't mention any one as particularly informative.
I discovered I was going to make an undirected, acyclic graph. Since the matrix is sparse, I was going to use an adjacency list (Oh, goody! Lists!) to hold the edges of the vertices. More specifically I was going to use an incidence list since each edge structure would hold the Pythagorean distance between the two neighbors.
I was still a little confused about the nature of the adjacency list but a poster on a gaming forum supplies the clue I was looking for. He notes that a graph requires a list of vertices (check) AND a list of edges. The adjacency list associated with each vertex points into the edge list. Another poster notes that, often, the edge list can be eliminated and the edges stored in the adjacency list, itself. Click! A light bulb turns on.
So, what are the vertices? They are the star system data structures which I've mentioned earlier in my notes on the production model in the game. And the edges? They are pointers to all of the other systems' data structures that are within SCANNING range of the given system.
I can't believe what a great designer ...er... how lucky I am! I have one of the two main data structures I need and the edge generating method is already in place. Well, the scanner needs a little tweaking but it's easily extensible to this application and is ready and waiting to go. No strawmen this time, dear reader, it really was a fortuitous circumstance.
I can plunk the edges onto the adjacency list since I only have 60 vertices. If I had 1000 vertices I might reconsider this decision. So far, my in-game statistics show I average a little less than 600 edges per graph. I don't think these numbers are earth-shattering.
When I'm done coding and testing, the scanner, as described earlier, is virtually gone. I use it during game initialization to create the graph and then keep one, modified instance of it for the UI. The graph becomes the scanner for the rest of the game.
January 23, 2011
I've spent a significant amount of time these past weeks attempting to make the AI play more defensively. While I thought I had a few good ideas, they all turned out badly for one reason or another. I decide to move on, fix some bugs and add a few, new, strategy-like features.
Scanners Revisited
My first attempt at implementing sector scanning was very naive. It “worked” if the player used an expansion strategy similar to the one I imposed on the AI. This method assumed that systems are acquired according to their distances from the home system.
If this simple-minded approach is followed, the scan algorithm works just great. As soon as a player starts leap-frogging systems, in order to get the juicy ones first, the design falls into the pond.
The original idea was that I only needed to know the last value on the x-axis that had been scanned for any given 'y'. This information could be held in a 1-D array. Scans of new systems would just pick up where the previous scanned had stopped. The notion was simple, elegant and completely wrong. The technique assumed that all of the squares up to the 'x' value in the array had been scanned. This was, however, not always the case. Basically, a 1-D array cannot solve a 2-D problem. D'Oh!
I bite the proverbial bullet and add a 2-D array of 'char' for each player. What's another 14,400 bytes (60x60x4x1) or so among friends, huh? I consider using a bit map instead (900 bytes). But, eventually, I decide that designing the bit manipulating routines, to make sure the code is extensible to larger galaxies, is just not worth the effort, at this time.
The scan area for each system, not near a border, is 337 squares. Since these areas can overlap each other quite a bit, virtually, every square on the map is scanned multiple times . I take some solace in the fact that scans are infrequent and only occur during turn taking.
Another bright spot is that I no longer need to calculate the Pythagorean distances for each scan. I am able to use a fixed array of normalized distances from the origin for the operation. While the new algorithm is a memory hog, it's much easier on the CPU and that's the way it should be in a just world.
The inputs to the scanning method are:
x – the 'x' location of the star being scanned
y – the 'y' location of the star being scanned
scan[][cMapSizeX] – the player's scan array
*pList – pointer to the head of the list to hold the results
Implied inputs are:
maxScan – the maximum scan distance (a constant)
pythDist[.] - integer array of Pythagorean distances (also, a constant)
Remember: 'maxScan' is 15 as described in an earlier blog. 'x' and 'y' can each range from 0 to 59.
The algorithm scans from north to south along the y-axis. For each value of 'y', an east-west scan is performed. To kick off the computation, I need to calculate three values, namely,
yStart – the map location along the y-axis at which the scan starts
yEnd – the map location along the y-axis at which the scan ends
arryIdx – the starting index into the fixed array of Pythagorean distances
Each of these values must be adjusted to the map borders, of course. This shows what a scan looks like:
The star being scanned is at the origin.
As a simple example, let's look at a star located at x=20, y=17.
yStart = y – maxScan + 1 = 3
yEnd = y + maxScan – 1 = 31
arryIdx = abs(y-maxScan) – (y%maxScan) = 0
Since the value of 'x' at 'y-maxScan' is '0', the scan starts at 'y-maxScan+1'. The value for 'x' at this location is '5' (= sqrt(15^2 – 14^2)). The scan progresses from top to bottom, as shown in the picture above.
Remember: The (0,0) origin of the map is the upper left-hand corner of the display.
Now, I calculate
xStart = x – pythDist[arryIdx] = 15
xEnd = x + pythDist[arryIdx] = 25
So, here goes:
for (int i = yStart; i <= yEnd; i++)
{
int tmp = pythDist[arryIdx++];
xStart = x – tmp;
xEnd = x + tmp;
char *pScan = &scan[i][xStart];
for (int j = xStart; j <= xEnd; j++)
{
if (!(*pScan)) // has this square been scanned?
{
*pScan = 1; // no, mark it as 'scanned'
if (list pStar=GetStar (j, i)) // is there a star here?
Add2Tail (pList, pStar); // yes, add it to the list
}
pScan++;
} //ENDFOR: scan x-axis
} // ENDFOR: traverse y-axis
And that's pretty much how it works, given the magic of “GetStar” and “Add2Tail.” The method exits with 'pList' containing the list of newly scanned star systems. The CPs move the contents of this list to their goal list. The player code “lights up” the listed stars, so, they can be seen on the map.
December 21, 2010
I've been spending most of my time, lately, preparing for today's release. A link to the downloadable zip is available on my Home page. Now that I have some breathing space, I can catch up on things I've neglected here. An alpha release is the first time that code is sent outside the development environment for evaluation. This tells players that the code is very, very new and, essentially, is untested.
Most of the effort went into tweaking the UI and these are too numerous to even list, so, I'll stick to talking about some of the bigger items. In no particular order, here goes.
Full-Screen Issue Solved!
WooHoo! This occurred whenever I ALT-TAB'ed from the full-screen game window to the Desktop and then ALT-TAB'ed back to the game. This always caused the game to exit by posting a “Quit” message to windows.
This had been bothering me for the longest. I had, of course, coded a routine to test the cooperative level of the device before rendering the scene. With a little bit of “printf” debugging, I found that the device reset method never completed without an error.
To make a long story short, I found, much to my dismay, that I had forgotten to release all of my textures before resetting the graphics device. Yes, it was one of the new UI features. After I added the code to release this texture before “reset”, everything was hunky dory, well, almost everything.
Now, I had the good, old aspect stretching problem when going from 1024x768 (windowed) to 1680x1050 (full-screen). All of my nice, round stars, now, looked like footballs. I tried some aspect-ratio scaling when rendering the sprites. This helped, immensely, to restore roundness to the stars but caused some other problems.
One problem was that the game had an asymmetrical black border, now. While this may be a minor problem, it is still unsightly. Also, all of my button locations were wrong as they are tightly coupled to the 1024x768 layout. Oooops! So, saving these problems for a rainy day, I have released only a windowed version of the game.
New Development Tool
After removing MFC from the game, there was no need to keep using VC++ .NET 2003 for development. I opted to use VC++ Express 2010. The port went very smoothly. There were only two minor issues.
The first involved converting all of my CRT functions (sprintf, strcpy, fopen, etc.) to the “safe” version. The second was to specify the static, runtime multi-threaded library during compilation. This, actually, tripled the size of the executable. However, players do not have to install the VC++ 2010 runtime environment to play the game, which,to me, is much more important.
Now, a bit about game play.
Simultaneous Combat
I mentioned earlier in this blog that I was not a big fan of I-go-you-go combat resolution. The implementation of simultaneous combat was not as hard as I, originally, thought.
The original I-go-you-go algorithm looks something like this:
while (1)
{
// Attackers go
StartCombat (&attackers, &defenders);
if (CheckCombatResults(&defenders) == LOSE)
return WIN; // Defenders rubbed out, Attackers win
// Defenders go
StartCombat (&defenders, &attackers);
if (CheckCombatResults(&attackers) == LOSE)
return LOSE; // Attackers rubbed out
}
where,
attackers = attacker's unit stack
defenders = defender's unit stack
The “StartCombat” method uses the 2nd argument as the defending stack and stores the combat results therein. The “WIN/LOSE” return value just means that the method is called by the attacker.
To make the combat simultaneous, that is, both units take their best shots before losses are calculated, I add two variables to shadow the attacker's and the defender's units. The algorithm looks like this:
attackerShadow = attackers; // save initial values
defenderShadow = defenders;
while (1)
{
StartCombat (&attackers, &defenderShadow);
StartCombat (&defenders, &attackerShadow);
attackers = attackerShadow; // save current results
defenders = defenderShadow;
if (CheckCombatResults(&attackers) == LOSE)
return LOSE; // Attackers rubbed out, Defenders win
if (CheckCombatResults(&defenders) == LOSE)
return WIN; // Defenders rubbed out, Attackers win
}
That's pretty much it. No, other changes are needed. Notice that by checking the attacker's results first, ties are, automatically, awarded to the defender which is intended.
Artificial Ignorance
To be honest, the AI is a dolt when it comes to self-defense. However, I, now, have a couple of ideas on how to improve this and I want to focus on this change in the short term. In lieu of actually improving the AI, I decided to add some goal states that should boost the AI's aggressiveness if not its tactics.
The first change was to beef up the AI's fleets, especially, during the initial system grab. Simultaneous combat eliminated the “aggressor's advantage” of the I-go-you-go algorithm. Units suffer higher attrition during combat, now, even against weak neutrals.
The next change was to modify the conditions under which CPs shifted to their war economies. Whenever war starts, the CPs, first, add their auto-defenders and then start to rally large attack fleets.
These conditions were:
1. The acquisition of 10 or more star systems.
or
2. The loss of a star system to an enemy.
With the current burden of 3900 production points per star system for auto-defenders, condition (1) could take longer than 15 turns to pay off this debt. While the CPs are paying down this debt, the player can acquire enough systems to dominate the game at a very early stage. As for condition (2), it's just plain dumb. CPs can “see” when they're under attack, so, why wait until it's too late to do anything about it?
I changed these conditions to be:
1. The acquisition of 15 or more star systems.
or
2. Spotting an attacking fleet heading for a CP's system
or
3. Sending an attacking fleet to any non-neutral player
I, also, reduce the cost of auto-defenders to 3000 production units per system. This is justified by the fact that auto-defenders do not have interstellar engines, so, they should cost less than a mobile unit. I, then, throw in a slight discount for buying in quantity.
Under these new rules, the AI is a bit more challenging, if not any smarter.
Miscellaneous Mods
Finally, I'll just mention some changes that I've talked about elsewhere in this blog.
1. The defensive “all clear” has been reduced from 20 turns to 5. While leaving newly conquered systems open to re-conquest, it frees valuable attack-size fleets for more important work.
2. “Yellow” player is now “Green” player. My graphics director thinks yellow is way too harsh on the eyeball. Who am I to disagree? I'm red/green color blind.
3. What I had earlier referred to as “taxes,” i.e., the fees charged for auto-defenders and experience upgrades, is, now, called “research.” Yes, these items, some day, will exist in a research tree.
November 7, 2010
There's a lot to report since I last updated this blog. The first is my decision to make the game, such as it is, available for download. I had been thinking of the directions I can go for the next phase and figured that what I have, so far, can use some constructive criticism. As soon as I said this, BOOM!, I start getting the graphic design feedback I've been looking for all these long months.
The right panel has to go! Those ugly push buttons need replacing! Where did those scrollbars come from? The Goodwill? That chrome makes my eyes water! Your fonts are terrible! And those “planets”? You're kidding me, right? Don't you know anything about graphic design and layout? Uh, no, I don't.
So, it's time for a makeover. Well, well, well. And it's like re-arranging the living room. The piano goes in that corner, the couch against that wall and the entertainment center over there. Hmmmm, no, put the couch over here and the piano over there and move the entertainment center into the rec room and bring the coffee table downstairs.
Now, don't get me wrong. I appreciate all the help I can get with this matter, but is this how it works elsewhere? I have always found that implementing UIs is the most thankless job a programmer is ever asked to do. Everybody has an opinion about which side the do-dads go on but nobody can ever tell you until they, actually, see it. I am sure that this is why modular programming was invented: just so the do-dad generating function can put the do-dad anywhere anyone wants to put it at any time with only a few key strokes needed from the programmer. But I digress.
Graphics issues aside, I do need to tweak the UI and smarten up the AI a little. As far as the UI goes, the major changes involved replacing all of the MFC buttons, scrollbars and static windows with ones of my own. This is no mean feat, as you can imagine. However, when I finished, MFC dialogs had been banished from my code, forever.
Here's a shot of the new screen. I've added a Windows title bar so I can move the game screen around my desktop. I've, also, given the opus a working title, Interstellar Brawl. Cute, huh?
October 12, 2010
On August 9th, I'm able to take the game on its maiden voyage. It took a little less than two months to design, code and debug the program. I, pretty much, focused on the plan that I outlined at the beginning of this blog and have achieved these goals.
The blog was written after the fact but, fairly accurately, describes the process involved in obtaining the current results. I'll grant that my original objectives were very modest and that there is still a lot to do. However, I think that what has been accomplished will serve as a strong base for what is to come.
First, I'll talk a little about the game play, as it stands. I'll, then, discuss the short list of things I want to add in the next phase of development.
Game Play
The game only operates in windowed mode. It does crash in full screen mode when I try to ignore those blinky push buttons I described earlier. This worries me, of course, but the game has not, yet, crashed in windowed mode. I know this sounds like “famous last words” that's why I hedged the statement with a “yet.”
The game averages about 200 turns with 145 being the fastest and 350 being the longest. First contact with either red or white player occurs about turn 100. A typical game takes about 20 minutes to play.
Mid-game can become somewhat of a click-fest as each turn I scroll around the map looking for both potential targets and for potential threats. The multi-pronged attack from the AI keeps the game jumping. I expect red and white to attack from around turn 100 on and for blue to join the fray at about turn 120.
I keep an eye on my share of global production which is 1500 points (out of 6000, total). If my share drops below this, the game is pretty much lost. In fact, I lose if I can't sustain an income over 2000 points and, slowly, grow that. If I'm having trouble staying above 2000, I'm pretty much toast. While I cannot lose experience points, I can lose the production advantage. The end game is all about production.
The game ends when one player acquires 40 systems or more or when I hit the Exit button. I've lost games by both expanding too quickly and by expanding too slowly. When I expand too quickly I leave my systems exposed with only weak auto-defenders to fend off an attack. If I expand too slowly, I give the AI a leg up on both production and experience bonuses.
My current strategy is to expand rapidly to acquire about 1800 production points and then to slowly build up my offense. I choose a fleet rally point close to but not on my front. I use this fleet to reinforce my frontier as needed. As I get more and more attack-sized fleets, I use these to back up my perimeter. When I have a few of these, I start attacking. There is still a bit of risk and some luck involved with these tactics, but I can, generally, recover from any set backs that might happen.
Does this mean the game has a single path to victory? Perhaps. Does this mean the game becomes boring after this strategy is learned? Most definitely. Does this mean I wasted a year of my life for nothing? No, it means it's time to move on to the next phase: coding some interesting variations.
October 7-10, 2010
To wrap up this section, as promised, I'll now talk about some touches to the game display.
Slider Control
The lower left of the bottom panel displays three MFC scrollbars. These are labeled
Ship 1 – Production points assigned to making Wasps (tanks)
Ship 2 – Production points assigned to making Bees (infantry)
Gold - N/A
When I left-click on a system that I own, the ship producing scrollbars are enabled. The “Gold” scrollbar is reserved for future use when I expand the economy.
The use of MFC scrollbars instead of slider controls is a bit cheesy. I wanted the arrow buttons to allow for single-stepping the control. MFC sliders (trackbars) do not have this feature, AFAIK. I consider overlaying the scroll area with a slider control, exposing only the arrow controls. I decide this is not worth the effort as I intend to, eventually, replace all MFC controls with custom designs.
I code the “OnHScroll” routine in the recommended manner (see the MSDN entry on CWnd::OnHScroll). I, then, add code to make the two active scrollbars work together. For example, increases to the production of Bees, automatically, decreases the production of Wasps by the same amount. Additionally, I look at the amount of production already accumulated to building Wasps, for instance. I use the new value to move some those points already spent on Wasps and put it into Bee production. Maybe code speaks louder than words:
if (pSb->GetDlgCtrlID() == cSbShip2Id) // ship2 adjusted
{
pStar->pcItem2 = (float)curPos/c_fScrollRange;
pStar->pcItem1 = 1.0f - pStar->pcItem2;
adj = pStar->item1Amt*(1.0f-pStar->pcItem1); // extra old prod to new prod
pStar->item2Amt += adj;
pStar->item1Amt -= adj;
}
else
{
pStar->pcItem1 = (float)curPos/c_fScrollRange;
pStar->pcItem2 = 1.0f - pStar->pcItem1;
adj = pStar->item2Amt*(1.0f-pStar->pcItem2);
pStar->item1Amt += adj;
pStar->item2Amt -= adj;
}
Where,
pSb – pointer to a scrollbar (input argument)
pStar – pointer to the star system's data structure
curPos – the current position of the thumb control
c_fScrollRange – the floating point value of the scrollbar range
Reminder: The code tracks the amount of production assigned to building a given unit as a fraction and not as an integer. Thus, the line:
pStar->pcItem2 = (float)curPos/c_fScrollRange;
These two lines adjust the two values I use to store the production points to be spent on each unit:
pStar->pcItem2 = (float)curPos/c_fScrollRange;
pStar->pcItem1 = 1.0f - pStar->pcItem2;
Note that the second line is, simply, the inverse of the first calculation.
The next three lines compute the excess production of the unselected unit, if any, and add that amount to the selected unit while subtracting it from the unselected unit:
adj = pStar->item1Amt*(1.0f-pStar->pcItem1); // extra old prod to new prod
pStar->item2Amt += adj;
pStar->item1Amt -= adj;
This method may not be copacetic with the way production is adjusted in other strategy games. It does reduce any production already put to making Wasps, for example. However, it does ensure that production points are not forever lost. (Consider the case where pStar->pcItem1 = 0.)
Finally, I call a method to display the newly adjusted values in the static windows associated with each scrollbar.
On Comings And Goings
I need a way to, visually, see what attacks I've set in motion, as well as, to see who is coming after me. To do this, I create a Graphics Line base class. While Direct3D lines are easy to use, they are a bit tricky to understand in detail. I'll leave these explanations to the experts and focus on a cookbook approach.
Line display can be broken into two parts. Firstly, specifying what the line looks like and, secondly, drawing the line. After I create the line using “D3DXCreateLine”, I call the following methods to tell the system how I want the line to look:
SetWidth(1.0f) – the names says it all
SetPattern(0xFFFFFFFF) – sets the stipple pattern to a solid line
SetPatternScale(1.0f) – do not stretch or shrink the pattern
I forgo anti-aliasing for the time being since it seems to wash out the colors somewhat.
To draw the line, first define the start and end positions as two elements of a 1D array of type D3DXVECTOR2. The first element defines the starting location in pixels. The second element is for the ending position, also, in pixels. The only other variable to specify is the color of the line, since the vertex count is always “2”.
I, now, add a few public methods to add and to remove lines I, also, need a method to update a line and, finally, to render the lines. All of the data members of the Graphics Line class are private. These data include a linked list of line data structures which are declared as
typedef struct
{
D3DXVECTOR2 points[2];
D3DCOLOR color;
} lineData_t;
These data are allocated from the heap as needed and returned when I'm done with them. Since the lines are drawn from fleets to their destinations, I add a pointer to the line data in the fleet data structure.
Lines are created in two places. The first is in the code I use to select a fleet for moving. This is fairly simple. Whenever I select a destination for a fleet, a line structure is created with the fleet location as a starting point for the line and the location of the destination as the ending point.
The method to assign lines to enemy fleets that are attacking me is, also, straight-forward. Every time a CP fleet is assigned to a destination, the code checks whether or not I'm the owner of that destination. If I am, that enemy fleet gets a line. Both techniques call the Graphics Line method, “AddLine”, to pop the line onto the linked list.
Lines are updated during the “Move Fleets” phase of each turn. Only the source of the line, i.e., the current position of the fleet, needs to be updated. Whenever a fleet arrives at its destination, if it has a line associated with it, the line is removed from the linked list and its data are returned to the heap.
The Graphics Line render method steps through the linked list. It performs a check to ensure the line is in view. If it is, it converts map locations to view screen locations and the “Draw” function does the rest. This is similar in design to the method used to render fleet sprites. It just differs in detail, since I need to check whether either the start or the end of the line are in view. I handle drawing lines that have sources or destinations that are out-of-range of the view screen by rendering the lines before rendering the game display. This handles a lot of ugly I don't want to deal with.
Here's a screen shot of this feature. Why are the lines broken, you may well ask. This is because the lines are animated. They not only show who's going where, they, also, indicate the direction in which they are going.
Direct3D Line Animation
The first thing to note is that Direct3D line patterns are defined as a 32-bit stipple. If a bit is on (=1), then its position in the stipple is shown. If its off (=0), its position is transparent. So, if the stipple pattern, 0xFFFFFFFF, is a solid line, the pattern, 0xFF00FF00, is a dashed line with 8 bits on followed by 8 bits off.
Now, alternating a display of 0xFF00FF00 with its complement, 0x00FF00FF, gives the illusion that the line is moving. When the location of the destination is to the right of the source, on either axis, the line appears to move from left to right. However, when the destination is to the LEFT of the source, the same alternating pattern seems to be moving from right to left. This may not be intuitive, but is true.
I try a few patterns on for size and decide an alternating pattern of 28 bits on followed by 4 bits off looks nice. I create an array of integer constants to hold these values:
const int linePattern[] = {0xFFFFFFF0, 0xFFFFFF0F, 0xFFFFF0FF, 0xFFFF0FFF,
0xFFF0FFFF, 0xFF0FFFFF, 0xF0FFFFFF, 0x0FFFFFFF};
I, now, create a data member, private to the Graphics Line class, to hold an index into this array so the render method “knows” which pattern to display. If I were to change the line pattern on every display frame, the resulting animation is wicked, crazy. I need something a little less perky.
I use a variable to count display frames. I only update the pattern when the frame counter hits a certain value. In this case, every ten frames. Here's the code:
if ((++frameCnt % 10) == 0)
{
if (++patternIdx >= cPatternArrySize) // 8 patterns @ 4 bits off/pattern
patternIdx = 0;
pattern = linePattern[patternIdx];
}
pLine->SetPattern (pattern);
where, cPatternArrySize = sizeof(linePattern)/sizeof(int);
The first line uses modulo arithmetic to decide when to change the pattern. This could be optimized, of course. The second line ensures I don't overflow the array. The third line sets the pattern for the next 10 display frames. If I assume a frame rate of 60 Hz., then the pattern is updated 6 times/second. This pace is much less frantic than 60 updates/second and is easier on the CPU and on the GPU, too.
This pretty much concludes everything I have to say about adding a real player to the game. If I skipped anything, I'll write an addendum, later.
Streaming Attacks
I make one change to the combat engine before I play my first game. This has to do with an observation I have made while watching the CPs slug it out. The fact is that the winner of combat, either attacker or defender, almost always loses a majority, if not all, of its Bees (infantry) with hardly any damage to its Wasps (tanks). I conclude that this is a consequence of the stack-to-stack algorithm used in the combat engine.
This algorithm specifies that a player must be pit its entire stack of Wasps against even a handful of Bees. This is not too terribly smart. If a player only has one shot per stack against an enemy, logic says that the stack with the greatest destructive possibility should be the target. If I have 10 Wasps and no Bees and the CP has 6 Wasps and 1 Bee, it would behoove me to ignore the Bee and to attack the Wasps. However, this is not allowed.
To mitigate this problem, I modify the combat engine to allow streaming attacks. This means that if a stack is attacking and it should wipe out all of an enemy's Bees, any unit in the stack that has not fired is, now, allowed to attack the enemy's Wasps. This way I keep the Bees as a first line of defense but allow an attacking stack to take advantage of its full strength.
This has the intended effect. There is, also, one side effect. The TKO, completely, disappears from the game. Remember: the TKO gives the defender the win, if the combat loop is executed a given number of times without a resolution. 15 iterations is the magic number, here. Prior to this change, TKOs were rare but they did happen. After this change, even though the TKO code is still in place, it hasn't happened, not even once. Although this was unexpected, I think it is a good thing.
October 6, 2010
Fleet Statistics
I add code to the interface to display simple fleet statistics. I use a left click on a fleet sprite for this purpose. At first, I use an MFC message box for this. Eventually, I extend the Graphics Text class to display a small info-screen on an unused area of the bottom panel of the game display.
The information shown is
The number of tank units in the fleet, which are called “Wasps”
The number of infantry units, called “Bees”
The number of turns to reach the fleet's destination, if it is moving
I can use these data to decide if one of my fleets is large enough for combat. I can, also, see the size of incoming fleets.
Moving Fleets By Hand
I need a way to select a fleet and to send it on its merry way. This code must handle the following conditions:
1. Select a fleet
This means that the physical mechanism for selecting fleets must be in place. This method was alluded to when I discussed “Fleet Statistics”, above.
2. Ensure that the selected fleet is allowed to move
The games rules specify that only fleets in the “Idle” or in the “Defending” states can be moved. An unstated supposition, here, is that the fleet is mine to move.
3. Select a destination for the fleet
This is new code. I, again, use a click of the left mouse button to drive this function.
4. Cancel the move
This, again, is new code. I design the code to handle multiple conditions for this requirement. Firstly, hitting the turn button cancels a move. Secondly, selecting another fleet cancels the move-in-progress for the current fleet. Lastly, selecting the fleet's current star system ends the “move fleet” sequence.
This process shows how programs, typically, come into being. The first step is the specification. A specification is, usually, no more than a statement of the requirements for a feature or function. This is followed by the initial design phase. This is a step-by-step list of what it takes to code the requirement. The next and, in this case, final phase of the design is to detail the method to implement the requirement.
So, what's the best method I can use to implement this? It's a state machine, of course. Here's the state transition diagram for this feature.
I add this state machine to the Apollo class. Here are the conditions that cause state transitions:
Fleet Selected
The fleet is one of mine AND
The fleet is in the idle state OR
The fleet is in the defending state
New Fleet Selected
The same conditions as for the “Fleet Selected” transition.
Destination Selected
I have left-clicked a star system (one of my own or an enemy's)
Fleet Move Canceled
I have selected the fleet's home system OR
I have hit the turn button
These are coded as “if” statements in the OnLButtonUp routine except for the “turn button cancel” condition. The “OnTurnButtonClicked” routine, automatically, resets the state machine (to its Idle State) whenever it is called.
This describes a more traditional state machine than the ones I have talked about earlier. In this case, unlike the others, the state machine always returns to the Idle State after it completes its job or when the job is canceled.
I consider this process to be typical of the way I work:
Specify->Design->Code->Debug
I hope I haven't belabored this point, too much.
Rallying
I, now, implement my version of the AI's rally function. It's purpose is the same as the AI's. It allows me to, automatically, generate attack-size fleets. Unlike the AI, however, I can rally at star systems, only.
I create a “Rally Button” for this feature. At one level, this button functions as a switch. If this switch is off, turn it on. If the switch is on, turn it off.
However, to ensure the rally is really “on” I have to make sure a rally point has been selected. When the switch goes from “off” to “on”, I use the left mouse button to select the destination system for the rally. The logic for this code is, virtually, identical to what I described for the “move fleet” function. Only the names have changed to protect the integrity of the code.
At this time, I can only select one rally point at a time. I hope to expand this feature some time later. The rally only affects units or fleets in the Idle State. Fleets in the Defending State must be moved by hand.
Reminder: Units, when first created at a star system, are put into the Idle State.
Whenever fleets arrive at a destination, they are put into the Defending State. This applies to both systems I already own and to systems I have, recently, acquired by force. This means that rallied fleets are defending fleets and must be moved by hand. This is the intended effect.
Auto-Defenders
I give myself the option to add auto-defenders to my star systems. This is a one-time feature that I can select at any point in the game, or not. It has the same effect and consequences as the AI's auto-defenders.
All current, and future, systems I own have a home fleet of 5 Wasps and 3 Bees. If the system is attacked, these defenders are added to any other fleet, idle or defending, at that star system to fend off the assault. The cost is 3900 production points per system. All current production is focused on paying for these defenders, but these defenders are available, immediately.
I create an MFC push button for this feature. Once selected, the button is disabled. I add a BOOlean to the Apollo class to test whether this function is enabled or not in the few places where it's needed.
Here's a screen shot of the game window described, above. The info screen on the bottom panel shows data for the fleet at Acrux. The fleet position to the lower right of Acrux shows the fleet is in the Defending State. The position of the fleet to the upper right of Sol indicates that this fleet is in the Idle State.
October 4, 2010
There are still a few things needed before I can play my first game.
Spreading Out
The star sprites on the new, 1024x768 pixel game board seem a little crowded. When dozens of fleets sprites are added, the overall presentation is downright confusing. I need to spread things out a little.
I decide to double the size of the squares in the viewing area from 16x16 pixels to 32x32 pixels. The star sprites themselves remain the same size, i.e., 16x16 pixels, however. I add two new constants. These are
c_iTileScaler – the integer value of the tile size
c_fTileScaler – the floating point value of the tile size
In order to make sure everything works according to plan, I set these values as
#define cStarTileSize 16
#define c_iTileScaler cTileSize
#define c_fTileScaler (float)c_iTileScaler
The first constant, “cStarTileSize”, has been used, up until now, for all of the code that transforms map coordinates to view screen coordinates and vice versa. Whenever, I needed a floating point version of this value, I used a cast of “cStarTileSize” to type “float”.
Now, all I need to do is to globally find-and-replace most instances of “cStarTileSize” with “c_iTileSize” and of “(float)cStarTileSize” to “c_fTileSize”. I, also, search for all instances of hard-coded values of “16” and “16.0f” to make sure I catch all of the appropriate references. These hard-coded values occur more often than I care to admit. The original identifier, “cStarTileSize”, is, now, only used for referencing the size of the sprites, themselves.
After a bit of checking and double checking, I test this change and find that all is good. I, then, take the next step which doubles everything.
#define c_iTileScaler 2*cStarTileSize
I test this and everything works as planned, including the map scrolling. I give myself a good pat on the back, making sure I don't hurt my arm too much.
Star Names
My artist consultant has been complaining about those boxed star sprites that I have been using to indicate player ownership. I had toyed with DirectX fonts in the Combat Simulator. Now, it was time to put that effort to use.
While I'm working on the design of this feature, I search the net for star names. I need 60 of these, well, 59 really, since yellow's home planet is always named...tah dah, Sol. These sites prove invaluable:
http://www.naic.edu/~gibson/starnames/starnames.html
http://www.obliquity.com/skyeye/misc/name.html
http://en.wikipedia.org/wiki/Stars_and_planetary_systems_in_fiction
I key in 59 of these names into a text file, in no particular order, reserving the first slot for “Sol”. I add code to the Star class to read this file and assign the names to the star systems created by the map generator, i.e., to their data structures.
I realize, right away, that using this fixed order could get a bit repetitious from game to game. So, I decide to mix it up a little. I duplicate the 59 star names in the text file with a copy-and-paste. I, then, add a random number generator to the code that reads the file. The generator gives me a number between 1 and 58, inclusively. I then read the first entry, “Sol”, throw away the next “N” entries according to the generator and then read in the next 59. This, of course, does not randomize the names, in any sense of that word, but it does break up the monotony a little.
I, now, create the Graphics Font class, deriving it, you guessed it, from the Sprite class. The render method for this class takes a pointer to a structure that contains the following:
typedef struct
{
char *pText; // ptr. to the text to be displayed
int length; // text length (NULL terminated)
RECT loc; // location of the text rectangle (in pixels)
D3DCOLOR color; // owner's color (gray = neutral)
} textData_t;
These, of course, are 4 of the 5 parameters needed by the “DrawText” method to draw the text. The other parameter is hard-coded as “DT_NOCLIP | DT_WORDBREAK”.
I add this structure to the star data structure so that “pText” and “length” are only calculated once, during game initialization. The “color” field is changed, dynamically, when stars change ownership. In order to simplify the code, I render the star names in the same routine I render the star sprites. This strongly couples the star name rendering with the star sprite rendering which is a bad thing from a design point of view. I hang my head in shame and move on.
Here is what the new game display looks like. It's much nicer, if I must say so, myself. The artiste agrees.
October 1, 2010
I'll, now, talk about the most significant change to the game engine, to date. I make the code modifications needed to give me a seat at the game. Up until this point, everything I have discussed has been under program control. The only thing I could do was to click the turn button and watch the game play itself.
I had, of course, anticipated this day and had added a lot of special functionality to my team, the yellow player. Yellow is assigned to the northwest quadrant (upper left) of the game board. The game always starts from yellow's point of view.
All of the physical controls such as map scrolling and scanning were first designed from yellow's perspective. The game information currently on display are yellow's. Also, whenever I click on a star sprite, the game enables the production sliders if, and only if, yellow owns that system. Otherwise, these are disabled. These features are, judiciously, controlled by “if (playerId == cYellowPlayer)” statements scattered throughout the code in the appropriate places.
The Game Engine In Action
I can, actually, watch the game play out from the POV of the other players. This is done by a global find-and-replace of “cYellowPlayer” with “cBluePlayer”, for example. I do this with each player to ensure the game engine works from any perspective.
In fact, there is a major bug in the scanning routine for the players in the eastern hemisphere (red and blue players). I'm disappointed with this, naturally. I put it on the “To Do” list as it will only matter if I want to play these positions. Remember, the scanner is a limitation I placed on myself and has no affect on the AI.
Another observation is that I can almost guarantee yellow's victory by controlling its starting conditions. To do this, I just generate maps until yellow gets a good start with a few fat systems near its home. I expected this outcome and would have considered anything else to indicate that there's a huge bug in the code. Whew!
Does this mean the game is unbalanced, since whoever gets a good start will be the likely victor? Yes, but I hope this will not happen too often. It takes a good half dozen tries or more to generate one of these optimal maps. Also, when I don't cheat for yellow, the game seems to be up for grabs. I don't have any statistics to back this up, however. It's just a guess, based on a limited number of trials.
I, also, notice that yellow's first contact is with red player, more often than not. I suspect the map generator to be at fault, here. I won't go into the hairy details of why this may be a problem. Suffice it to say, that I'll look into this, later.
Some Hairy Details
In order to play the game, I derive a new class from the Player Class. I then remove all of the “if (playerId == cYellowPlayer)” statements and override all the methods that contained these statements in the new class. These methods, of course, are defined as “virtual” in the base (Player) class. This allows me to keep one of the major loop controls in the game engine. This control is of the form:
for (i = 1; i < cMaxPlayers; i++)
g_pPlayers[i]->DoSomething();
where,
g_pPlayers[] is an array pointers to Player objects
and
g_pPlayers[0] is NULL (this is the Neutral player)
g_pPlayers[1] points to an object of the Apollo Class
g_pPlayers[2..4] points to objects the Player base class
I use this control all over the place. The use of “virtual” methods ensure that the code, correctly, accesses the base or the derived class method in each iteration of the loop.
Finally, I remove any and all methods related to the user interface from the Player Class. I add these methods to the new class.
The new class, which, for lack of a better name, I'll call the Apollo Class, is, now, virtually, complete. All of the goal related methods remain in the Player Class with “private” access. I add a few globals needed by the game initializer and by the GUI to instantiate the Apollo Class and to access its public members. This completes about 90% of the effort. Isn't C++ great?
September 29, 2010
Display Changes
I think that if I can't have a full screen version, yet, I could, at least, have a larger display. I decide to make the game display 1024x768 pixels instead of its current size of 640x480 pixels.
I face this change with some trepidation. Although I have been careful to use constants instead of hard coded values throughout the program, I am concerned that I have overlooked something and that this may not be enough to make this change go smoothly.
The first thing that is required is to re-work the frame. Up to this point, I have rendered the frame from several separate pieces, namely,
- across frame (8x576)
- side frame (8x416)
- corner frame (8x8)
- right panel (64x416)
- bottom panel (64x640)
These are all placed in their own *.png files. I use Photoshop to put the images on a black background that is a power of 2. Here is a shot of the bottom panel. The panel itself is 64x640 but the background extends the file size to 64x1024 to accommodate the DirectX requirement.
I duly prepare to resize all of the images for the new window size. First, I, simply, double the width of the right panel and the height of the bottom panel from 64 pixels to 128 pixels. And, then, a light bulb goes off in my head.
Why should I waste CPU cycles building the window each sync frame when I can just construct the entire window in Photoshop? I don't know why I shouldn't do this, so, I do it. Here is the result. Notice the game window is placed on a transparent background that is 1024x1024 pixels. This simplifies window rendering so much, it's embarrassing.
I then make the necessary changes to the code by changing the constants “cWndCoordX” from 640 to 1024 and “cWndCoordY” from 480 to 768 and re-locate all of the MFC components. I then re-compile, cross my fingers and hit “Run” on the Debug toolbar. Much to my delight, everything works perfectly, even the scrolling which is what I was most concerned about.
Here is a shot of the new game window and of the minimap. Notice that on the minimap, I dimmed the background star field by about 80% to make the mini-star sprites show up a little more clearly. I, also, staggered the informational statics for aesthetic purposes. Another minor change is that I added an MFC font class and reduced the size of the scrollbars and statics to make them prettier, too.
September 28, 2010
Full Screen Mode
During the course of development, I've switched between windowed and full screen mode. In order to change modes, the “Windowed” field of the D3DPRESENT_PARAMETERS structure is, simply, changed from TRUE to FALSE. Full screen mode is preferred because it allows for an immersive gaming experience. However, there is one annoying problem when I go to full screen mode. My MFC buttons flicker like crazy.
The static windows and the scroll bars behave the same as in windowed mode. The check buttons, also, appear normal. Only the push buttons are acting up.
I try several fixes. The first is the recommended one of overriding “CWND:OnEraseBkgnd” and returning FALSE. This does not help. I, also, try to increase the presentation interval from its default value (= video sync rate) to “ONE”, “TWO” and higher (up to “FOUR”). This slows the flicker down but it's still there and it's still annoying.
I search my beloved internet for a solution, but in vain. I conclude there is bad mojo between MFC and DirectX and that MFC must go. However, switching from MFC to a homegrown GUI is not going to be easy. I decide to live with windowed mode for the time being.
September 10, 2010
Maintenance/Bonuses
I decide to mess with the economy a bit. A per unit maintenance charge is pretty standard in strategy games. So, I add a maintenance fee for every unit produced including star system defenders. I think a nominal fee of 4 production points per tank and 2 production points per infantry is fair. This is only 2/3% of the unit cost.
I add the maintenance to the player's taxes which means no new units can be produced until the tax is paid off. The fee, of course, is applied each turn since this is the standard definition of “maintenance.” Much to my chagrin, the game comes to a screeching halt. What a mess.
I do a quick calculation and find that even this, apparently, low maintenance cost accounts for 1/4 of production with only 1 yellow, 1 green and 1 blue star. Ugh! Well, now, I know how to slow down the game when the time comes for that. At this point in development, however, I want a fast game with, relatively, large fleets.
I replace the maintenance expense with a production bonus. I set this bonus to ownedStarCount/20. This is calculated each turn as follows:
float bonus = 1.0f + (float)(ownedStarsCnt)/20.0f;
and is applied during the “update production” phase of the turn as:
availProd = prodCap * bonus;
and, finally, the production accumulation becomes:
item1Amt += (availProd * pcItem1);
I, now, make good use of the fact that I had typed all of these variables as floating point. As expected, the game goes into overdrive. Lucky players can double production by mid-game, i.e., by the time they've conquered 20 star systems. Losers, of course, are penalized.
I use the raw “owned stars count” value instead of some other valid measure such as % of total galactic production, so that I won't be tempted to use it as an exploit against the AI. I'm pretty sure that I'll, eventually, modify this calculation in some way but, for now, I keep it as is.
Goals: Yet, Another Iteration
I am, now, ready to smarten up the AI a bit. As I mentioned in a previous post, the AI sends all attack-sized fleets to the same goal. When that goal is achieved, these fleets are then sent to the next goal on the list.
Part of the dumbness comes from the fact that I don't allow the AI or myself, for that matter, to re-direct fleets in mid-journey. Fleets can only be assigned to a goal when they are sitting idle at a star system. It's not that this would be hard to do. It's just a decision I made about the game mechanics.
So, the game, as described, so far, has most of the AI's fleets traveling to locations that have already been conquered. There is one interesting consequence of this decision that I'll mention in passing. Should a system be lost to another CP, there is a good chance that the player that just lost the system has a fleet arriving at that location and can re-take the system. Even so, it's still a bonehead move.
I add an array of type BOOLean to each star system, one element for each player. Before assigning a goal to a fleet, the code checks the player's element which is an index into the array based on the player's ID. If the element is FALSE, the fleet is assigned to this goal. Otherwise, the code keeps checking the list until an unassigned goal is found.
if (!pStar->assigned[playerId])
{
pStar->assigned[playerId] = TRUE;
MoveFleet (pFleet, x, y);
return;
}
where,
pStar – pointer to a star system's data structure
pFleet – pointer to a fleet's data structure
assigned[] - array of BOOLeans
x, y – map location of the goal
If the fleet should lose the battle at the destination, the value of “assigned[playerId]” is set to FALSE, so that the goal can be re-assigned to the next available fleet.
I have one more tweak in this area, but I'll describe that in another post.
Star System Defenders
As I test all of the good stuff I've added, I notice another big hole in the AI. As soon as they have conquered another CP's system, they are immediately assigned a new goal. They leave this system lightly defended, knowing darn well that the owner is going to come back for it as soon as possible.
I have mentioned the idea of a zone defense which is the way I would handle the problem. That is, keep a large fleet in the general area that could hop over to a system under attack and provide some meaningful defense. I, also, said that implementing this technique requires some serious cogitation on my part.
I opt out for a simpler, if not elegant, solution. I add the concept of keeping the attacking fleet in orbit around the system until the “all clear” is sounded. I add another linked list to the fleet class to hold all of these temporary defenders.
Each turn the code cycles through a player's defending list and checks if the system is under threat from an enemy. How will it know this? Easy! It checks the same array it used when assigning the system as a goal. Only this time, it looks at the other players' entries to find out whether or not the system has been targeted for re-conquest.
If the system has NOT been targeted for re-conquest after a certain number of turns, the fleet can then be assigned a new goal. I found, by observation, that a CP can take up to 15 turns to decide to re-take a lost system. So, I set the defenders delay to 20 turns.
If the defending fleet should find that its system is being targeted, the turn counter is reset (to zero). An array of integers, attached to the star data, holds the current number of turns the defending fleet has waited for the “all clear.”
This method, while not great, is somewhat effective. That's a lot of hedges. Sigh!
Fleet Display: Color Modulation
I, now, have an AI with a fair idea of ordering goals and achieving them. I still have a screen full of fleet sprites and I don't know what they're doing. Are they attacking? Defending? Rallying? Wandering aimlessly about?
I overload the sprite class rendering method to accept an argument that modulates the color of a sprite. Color modulation is used to dim, brighten or even make a sprite invisible. I modulate the color of fleets moving to the rally point to about 80% of full. Attacking and defending fleets are set to full on.
Here is a screen shot of the game as described. Yellow has just won the game which means it has conquered 2/3rds of the galaxy (40 systems). The fleet in the white circle is the fleet at the rally point. Fleets circled in red are defending. Note that blue is defending two systems. The yellow circles show fleets on the attack. All of the dimmed fleets are moving to the rally point.
The 3 static windows in the center of the bottom panel are (from bottom to top):
Turn count
Number of tanks/number of infantry units
Total empire production
The white box on the mini-map indicates the map area of the first display.
September 7, 2010
I, now, add the other 2 players, namely, red and white. I'm very pleased with the results. The game behaves as expected. I can, now, start tweaking the game mechanics to make it playable.
The first major problem is that the game stalemates around turn 150 as system defense is stronger than the attack strength. I consider two standard ways to overcome this problem, namely:
- Factor in experience
- Set up a fleet rally point
Experience
In all of the strategy games I am familiar with, experience is assigned on a per unit basis. Also, experience comes from these units winning battles. All units start out as conscripts or with some other lowly title. Then, as they engage in and survive combat, their experience level increases to veteran, elite, ultra-elite, etc. With each increase in level, the unit gains strength in some area such as attacks, defense or percent to hit.
In this game, the units are really stacks of individual units which I call fleets. Since fleets can merge with one another, it becomes necessary to keep track of individual units within a fleet with respect to their experience level and subsequent combat bonuses. This is not so bad in that combat is very much unit-to-unit already and memory is free on modern computers. Right? “Uh huh”, I hear you say with a somewhat sarcastic tone.
I decide to back-burner this traditional method and go for empire-wide levels of experience. I can make up some ad hoc element to the back-story to justify this. For example, the fleets are not really autonomous but are under control of a central command on the home system. The units are not manned or womanned or personned, if you will, but are cyborged. The experience of one can be instantly transmitted and absorbed by all. See? That was easy.
I, also, decide to only enhance two areas of the empire's fleet statistics. These are percent to hit and interstellar speed. As described elsewhere, each unit of both types starts with a percent to hit of 60. Interstellar speed is, initially, 2 squares per turn.
I use two integers to track experience. These are “experVal” to hold the player's current experience points and “experLevel” to simplify the process of assigning rewards. Each value of “experLevel” could be named rookie, vet, champion, etc.
Each time a player conquers a system, they are given 10 experience points. These points are given even if the combat involves re-conquering a lost system. Experience points can only increase. There's no mechanism, yet, for losing experience points.
I reward each player's first successful combat with a bump of 5% to the “to hit” value. Thereafter, a player's experience level is bumped for every 50 points and a reward is given. The following chart shows this:
| Level | Points | Reward |
|---|---|---|
| 0 | 0 | N/A |
| 1 | 10 | 65% to hit |
| 2 | 50 | 70% to hit |
| 3 | 100 | 75% to hit, speed 3 |
| 4 | 150 | 80% to hit |
| 5 | 200 | 85% to hit |
| 6 | 250 | 90% to hit, speed 4 |
| 7 | 300 | speed 5 |
| 8 | 350 | speed 6 |
| 9 | 400 | speed 8 |
This moves the game along very nicely. Notice that the maximum “to hit” value is 90% and that it can be achieved by mid-game (25 or so systems conquered). I could back off these numbers a bit, but since the game engine needs a lot of work, I want a fairly fast game, for now.
Rallying Fleets
Attack fleets are still too weak even with the experience enhancement. I ponder the mini-map and think about tactics that not only increase fleet strength but, also, place fleets in a good offensive/defensive position. Fleet rallies will solve the first problem, zones, the second.
I first consider 3 rally points for each player. These are to be in the north/south, the east/west and the central zone, depending on a player's starting location. However, as soon as I try to design an algorithm for this, my head starts spinning.
How do I calculate the location of these zones? How do I assign fleets to them? How do I assign goals to these fleets? I soon zone out. I finally decide that a single rally point in the center of a player's sector will have to do.
Rallying is not needed during the early game since the main function of a rally is to build fleets large enough to attack non-neutrals. I specify that the AI will not rally fleets until it has at least two systems producing tanks and two systems producing infantry. This is for balance more than anything else.
I fix the rally point for each player at dead center in its sector. Since a player's empire may not have extended that far when rallying begins, I allow rallies to occur in mid-space and not at a star system. This simplifies the implementation, immensely. I leave it up to the reader to figure out how these fleets get fueled in the middle of nowhere. Hey! I can't think of everything.
Goals, Revisited
I need a technique to implement my rally feature. The use of a state machine, immediately, comes to mind. State machines have one extremely important feature, they eliminate a lot of conditionals. Statements such as “if this but not that and sometimes the other but only when...” can be incorporated into a single value called a state. In the language of the biz, state machines contain the context of their execution.
State machines are, also, very flexible in their design. Should a new set of circumstance come up that I need to take into account, I, simply, add another state to cover it. I won't claim that state machines are easy to get your head around, at first. They are, however, worth every bit of the effort it takes to learn them. Enough!
I design a very simple 3-state state machine to handle the following:
| State | Conditions | Transition | Fleet Size |
|---|---|---|---|
| 0 | Initial System Grab | 2 tank producers AND 2 infantry producers | 2 tanks OR 3 infantry |
| 1 | Preparing for War | War is declared | 6 tanks AND 4 infantry OR 10 tanks OR 10 infantry |
| 2 | State of War | N/A | 12 tanks AND 9 infantry OR 15 tanks OR 15 infantry |
State 0 is a non-rallying state. This is the game as described up to this point. Now, things get interesting. State 1 is preparing for enemy contact. Notice the fleets have a good chance of overcoming an enemy's standard defensive fleet of 5 tanks and 3 infantry. War is declared when a player loses a system to an enemy. This is not very proactive but there's always room for improvement.
This state machine is simple in that states can only transition to the next higher state and the last state, once reached, is permanent. In the rallying states (1 and 2), the code sends all newly produced units to the rally point. As fleets arrive here, they are merged with the fleet that is already present, if there is one. When the fleet reaches its designated size, according to its state, it is assigned a goal and sent off for the greater glory of empire.
Each player executes the state machine each turn just after the “update fleet move” phase of the turn. As an aside, should a fleet drop below its designated size due to attrition from combat, it returns to the rally point to pick up more units. This is one way fleets can grow large.
Finally, note that the algorithm for handling experience is, also, a simple state machine.
September 6, 2010
At this point, I have some of the basic functions of the game engine in place. These include
- A random map generator
- Fleet creation
- A (very) primitive sense of purpose for the AI
- Combat resolution
- A rendering method for both star and fleet tiles
It's time to add some competition, i.e., another CP. Before this can be done, however, a few more features need to be added. In no particular order, these are
- A method to display enemy fleet that are in range
- A sense of possession for the CPs
- Star system defenders for CPs
Displaying Enemy Fleets
It would be nice to see enemy fleets in the view screen. I already have a method to render “my” fleets, i.e., the yellow player's fleets. I could use this for rendering all players' fleets, except for one important restriction on viewing enemy fleets. I only want to see enemy fleets within my scan range.
In my post on “Scanning”, I described how I use an array of integers to track the current map area that I have scanned. I can use this same array to determine whether or not enemy fleets are within that area.
I overload the fleet rendering method to take one argument, namely, a pointer to my scan array. The body of this rendering method is, essentially, the same as that used in displaying my fleets. That is, the moving, idle and defending fleets lists are searched for fleets in the view screen. The one important difference is that these fleets most not only be in the view but also within my scan area. The test is simply this:
void Render (int *pScan)
{
int i;
dlListT *pL;
fleetData_t *pFleet;
D3DXVECTOR3 viewArea;
pL = idleList.GetHead();
for (i = 0; i < idleListCnt; i++)
{
pL=idleList.GetNext(pL);
pFleet = (fleetData_t *)pL->pData;
if (IsInView (&pFleet->curLoc, &viewArea)) // check if fleet is in view area
{
int x = (int)(pFleet->curLoc.x)/cStarTileSize;
int y = (int)(pFleet->curLoc.y)/cStarTileSize;
if (x <= pScan[y)) // check if fleet is within my scan area
pUnitSprite->Render (&fwdUnitRect[playerId], &viewPort);
}
}
.
.
.
}
In this code snippet, the first 10 lines of the function body are a very standard method of walking through a linked list.
The function, IsInView, is of type BOOLean and, when it returns TRUE, it has converted the fleet's map location (in pixels) to the corresponding view location (also, in pixels).
As usual, this snippet is for illustration purposes, only. For example, in the game code, I shadow the fleet's current location, curLoc, with an variable of type, CPoint. I use this whenever I need a fleet's location in map coordinates and not in pixels. There are, of course, other optimizations that can be made to this snippet but I won't get into those, now.
Losses
When a player loses combat, they, also, lose possession of the star system under contention. In order to handle this loss, I add a method called, PlyrRemoveOwned. This function, called after combat resolution completes, searches the player's owned list and removes the star system (data) from it. The star system is then added back onto the player's goal list.
Since the goal list is sorted by distance from the player's home system, the lost system, usually, finds itself at the head of this list. This means it will be targeted for takeover during the next “move fleets” phase of the next turn or soon thereafter.
So, pretty much after first contact, the game degenerates into a game of “hot potato” where players' go back-and-forth, vying for the same system, over-and-over-and-over, again. Something must be done about this!
Star System Defenders
One deterrent to a totally uninteresting game of hot potato is to give enemy star systems defenders. These defenders behave in much the same manner as the neutral's defenders except they are a tad bit stronger. Additionally, players must “pay” for these fleets just as they do for a normal fleet.
The defensive fleet is set at 5 tanks and 3 infantry whose cost is 3900 gold or, as described so far, production points (5*600 + 3*300 = 3900). This cost is taken from players' production in the form of a “tax.”
So, as players' star systems produce units, the players' tax obligation is first checked. If taxes are due, the production is used to reduce the tax instead of generating units. Any production points left over after the tax is paid are plowed back into the system's production accumulator.
This is, mostly, a burden in the early game where rapid expansion can bring production to a halt for as many as 20 turns. In the mid to late game, it is not so much of an issue as players are cranking out a dozen or so units per turn.
I give players their defenses, immediately, since they are, computationally, honest. I know they will pay for these units when they can because they, simply, don't have a choice in the matter.
Also, I reduce their taxes by 3900 points should they lose a system, ensuring the taxes never go below zero. If the tax is below 3900, at the time, the amount already put to the tax is lost. This is a small price to pay for an interest-free loan.
This mechanism does, in fact, affect the hot potato aspect of the game in that players cannot just walk-over enemy systems. However, a better solution is required. I'll discuss this in a future post.
Turn Order
Turn-taking is sequentially ordered as follows:
- yellow player, first
- red player, second
- white player, third
- blue player, last
This fixed order is, generally, benign but does have consequences for combat. If 2 players should simultaneously attack a neutral or enemy system, the first player in the sequence attacks the system's defenders. If that player wins, the second player attacks the remnant of the first player's fleet (plus defenders). So, in affect, the first player loses combat initiative to the second player.
For example, yellow and blue attack a neutral. Yellow is pitted against the neutral's defenders. If he wins, he takes possession of the system and is awarded a defensive fleet on loan. That's it for yellow's turn.
Now, blue gets his turn and is put up against whatever was left of yellow's attack fleet plus the newly “created” defenders. Blue has the attack initiative but yellow has a defense advantage. Most times, yellow can hold onto the system.
A Note On Source Control
I mentioned in an earlier post that I didn't have screen shots of the game in this early stage of development. This is not, strictly, true. Because I put the code under source control, I can go back to the code at any point during its development.
In this day and age, using a source control system for a coding project should be a no-brainer. I use the free version of Perforce but can, also, recommend CVS with the Windows GUI.
The Game, So Far
Here is a screen shot of the game as described in the last few posts.
It's a lot cleaner than before even with all of those fleet sprites. Yellow and blue are engaged in a game of hot potato over the yellow system in the middle of the map (currently, owned by yellow) and the white system slightly to the southwest (currently, owned by blue).
The “Load” and “Save” buttons don't work, yet, and the sliders in the lower left corner are, also, useless. The “Map” button displays a mini-map of the entire game. It looks like this.
I should note that this is not a map of the same game shown, above.
I'm fairly happy with the progress of development, so far. I added blue player without any major headaches and the game is behaving as predicted. It took one month of coding to get to this point. The effort was enhanced greatly by the time I took to lay down the foundation. This groundwork included windows creation, MFC programming and Direct3D coding.
September 4, 2010
Game turns are divided into a few, distinct phases. These are
- Updating production
- Updating fleet moves
- Engaging in combat
- Assigning goals to idle fleets
I've alluded to some of these phases in previous posts. Now, I'll describe them in a little more detail.
Production Updates
I have already mentioned, in passing, the “owned stars” list, which I'll simply call the owned list. This is the list of star systems that a given player has control over. As enemy systems are conquered, the star system is removed from the player's goal list and put on the owned list. “Star system” is short-hand for a data structure that contains all relevant information about a star. These data include:
- The owner's identification (an integer from 0..4, inclusive)
- The base tile type (an enumeration type from “yellowStarTileType..whiteStarTileType”)
- The system's map coordinates
- The star's name
- The turn number that the star system “joined” the empire (heh heh)
- The Pythagorean distance from the player's home system (an array of floats)
- The maximum production capacity (according to star type)
- The production available for this turn
- The percentage of production put to making type-one star ships (tanks)
- The percentage of production put to making type-two star ships (infantry)
- The current amount of production that has been put to making tanks (a sum)
- The current amount of production that has been put to making infantry (also, a sum)
At the moment, the maximum production and the available production are the same. Now, let
prodCap – maximum production capacity
pcItem1 - % production going to type-one star ships
item1Amt – the current amount that has been put into producing type-one star ships
So, for each item on the owned star list the formula
item1Amt += prodCap*pcItem1;
accumulates the amount of production spent, so far, on making a tank. This next line tests for completion of production:
if (item1Amt >= cItem1Cost)
{
AddFleet (cUnit1Type,1, x, y);
item1Amt -= cItem1Cost;
}
if true, a fleet is created at the given star system as represented by “x” and “y”. Any left over production is then used to start the next production cycle.
The same algorithm is used for producing type-two star ships.
The use of percentages to calculate the amount of production allocated to a given ship will allow me to split production between the various ship types should I want to do such a thing.
Updating Fleet Moves
I have already covered this in some detail in an earlier post, so, I won't belabor the point. I'll just put in a reminder that when a fleet arrives at an enemy star system, it is put on the arrival list instead of the idle list.
Engaging In Combat
During this phase, the code steps through the player's arrival list and pits each fleet on the list against the enemy fleet that is located at the given star system. The fleet is first removed from the arrival list and then the method that starts combat is called. The combat engine returns WON or LOST for each engagement.
If the combat is WON, the star system data is removed from the player's goal list and put onto the owned list. The fleet itself is put onto the idle list. If the combat is LOST, the code frees the fleet, i.e., it returns the allocated data structure to the heap.
Assigning Goals
The last phase of the turn is to scan the player's idle list and assign any and all fleets of the appropriate size to a goal. At the stage of development I am describing, all fleets are sent to the star system that is at the head of the goal list. This, of course, is the dumbest of the dumb for an AI, but have faith, this won't be the case for long.
September 1, 2010
Attack Fleet Size
As I mentioned before, fleets are created by star system production. These single-unit fleets, however, are not strong enough to be used reliably in an attack. I specify that an attack fleet must have either 2 tanks or 3 infantry in them. These fleets can overcome a neutral defense of 1 tank and 1 infantry about 2 out of 3 times, maybe more.
Fleet Movement, Set Up
When fleets are created, their current location is set to the map coordinates of the star system which made them. When these fleets reach attack size, the “move fleet” method of the fleet class is called. This method does the following:
- sets the destination star system as per the function's inputs
- calculates the Pythagorean distance, as follows
dX = destLoc.x – curLoc.x;
dY = destLoc.y – curLoc.y;
vLen = sqrtf (dX*dX + dY*dY)/fleetSpeed;
where, “fleetSpeed” is the number of map squares the fleet travels per turn. Initially, it is “2”.
- calculates the number of pixels the fleet covers per turn, as follows:
xStep = dX*cStarTileSize/vLen; //# of pixels along the x-axis
yStep = dY*cStarTileSize/vLen; //# of pixels along the y-axis
stepCnt = (int)(vLen+0.5f); //# of turns to arrive at destination
Note that all of these variables types are floating point, except “stepCnt” which is an integer. Also, note that “stepCnt” is rounded-up before truncation.
The fleet is then removed from the idle list and added to the tail of the moving list. After which, the routine is exited.
Moving Fleets
During the movement phase of a game turn, each player calls its “update fleet moves” method of the fleet class. This works as follows:
For every fleet on the player's moving list, fleet moves are updated by
if (--stepCnt > 0)
{
curLoc.x += xStep;
curLoc.y += yStep;
}
else
HandleArrival (pFleet);
When “stepCnt” for a given fleet reaches “0”, the “HandleArrival” method does the following:
- remove the fleet from the moving list
- set the current map location to the destination coordinates
curLoc.x = destLoc.x;
curLoc.y = destLoc.y;
- check if there are any fleets already at the destination. If there are, then merge this fleet with the other, exit the function and continue stepping through the moving list.
- if this fleet is the first to arrive at the destination, check if this destination is owned by the player.
- if the player owns the destination, change the fleet's state from “moving” to “idle” and add the fleet to the idle list. Exit the function and continue stepping through the moving list.
- if the player DOES NOT own the destination, change the fleet's state from “moving” to “attacking” and add the fleet to the arrival list. Exit the function and continue checking for moving fleets.
And that, with a little bit of hand-waving, is how fleets move in the game. Now, I sure would like to see these fleets and their current locations in the view.
Displaying Fleets
Since the fleet class is derived from the sprite class, it can render fleets in the game view. What does a fleet look like? Here's my first cut:

The fleet tile is the usual 16x16 pixel size, as are the other tiles in the game. The fleet constructor creates the sprite during the instantiation of the fleet object in much the same manner as the star sprites are created.
Each player's fleet class render method is called by the game engine's rendering method and does the following:
1. Render all fleets on the idle list that are in view
2. Render all fleets on the defending list that are in view (I haven't talked much about this one, yet)
3. Render all fleets on the moving list that are in view
I can see, right away, that the fleet rendering method is grossly inefficient. The first tests of the game engine generate more than 200 fleet objects. I'm a little worried about this, so I check CPU usage with the Windows Task Manager. I'm surprised and somewhat relieved to find that CPU usage averages about 15% with occasional peaks of 20%. I decide to back-burner the optimization of the fleet rendering method for another day.
Now, I have dozens of fleet sprites scurrying across the map and I don't know what they're doing or where they're going. Remember: these are the very first tests of the game and its all under code control. So, to at least see the direction a fleet is traveling in, I create the following sprite file with the aid of Photoshop:

Note the sprites are, now, on a transparent background instead of a black background. Directionality, is only needed for fleets that are moving. Idle and defending fleets always face forward. The fleet's direction is determined from the “xStep” field of the fleet's data structure, described in detail, above. When “xStep >= 0”, fleets face forward, otherwise, they face backwards, if you will.
Here's the latest sprite file I am using for all of the fleets in the game:

I wish I had some screen shots of the game in this early phase of development, but I'm sorry to say that I don't.
August 28, 2010
Finally, I can add the combat simulator I had created, earlier, to the game. Remember that one? It was the first game module I made during my DirectX tutorial days. Now, I just cut-and-paste it into the game code and away we go! Is that laughter I hear? Yes, you're right it needs some changes.
First, I need to make some rules about combat. The first rule to make is “who goes first?” There are many ways to determine this, but it, usually, boils down to a “you-go-then-I-go” formula.
With respect to this issue, I've always thought of the naval battles from the world wars or the “days of sail.” When fleets came within distance of each other, they just start whaling away. I guess that's a little disrespectful, but the notion of simultaneous attacks has always intrigued me, at least from a gaming point of view. However, it does add a layer of complication I'd rather not address, at the moment. So, I put it on the “think about it some more” list and code a you-go etc., model.
I decide that the attacker goes first based on no more than the idea that this is, currently, a game of raw aggression and, so, I reward that. I have not built in the idea of “initiative” with respect to attacking and defending fleets, although, that can be, easily, done (he says without any intentional irony). I, also, stipulate the order of combat.
I have, always, liked the idea of “combined-arms” attacks. This ensures that units are not obsoleted during the course of the game. It, also, should ensure that no single unit type is favored over another. I must test this, though, I, also, make a rule that while attackers always attack with their best unit, defenders, likewise, always defend with their best defensive unit.
Combat, at this time, is abstract. I want to, eventually, code a combat screen and give myself control over combat, but this is way down the “to do” list. I hope to, minimally, provide a cut-away scene of the combat action, some day. But, as it stands right now, all I get is a report of any unit losses should I win.
Combat Details, The First Cut
Allow me to create a short hand for the currently designed units. Let me call the attacker unit “the space tank” or “tank” for short, and the defender unit “the space infantry” or “infantry” for short. Aw, you knew that all along, didn't you?
My first cut at the combat engine is unit-to-unit, i.e., tanks concentrate all of their attacks, regardless of the number of tanks in the fleet, on all of the defenders infantry. If the defender's infantry gets wiped out, the attacker then sends all of his infantry against the defender's tanks. If the defender's infantry was not, in fact, vanquished, the attacker sends their entire infantry at whatever remains of the defender's infantry.
This is the so-called stack-to-stack model of combat and is a stand by in MOO. After the attacker has exhausted t heir turn, the defender goes using the same order. First, tanks, if any, against infantry, then infantry against remaining infantry or onto the tanks should the attacker's infantry have been wasted. This continues until one fleet is obliterated, then combat ends. I keep track of how many times the combat loop goes around and declare a “TKO” in favor of the defender after so many cycles without a winner.
So, combat, once engaged, is to the death. I should provide an option to retreat should one side find itself overwhelmed. This is easier said than done, however. A good AI should know the enemy's capabilities and only attack with necessary force. How many strategy game AI's have been criticized for sending a string of lame attacks against the player? A lot, I'm afraid to say. But I digress.
Combat Resolution
Here's the heart of the matter. For each attack unit, an attack roll is calculated as follows:
int CalcToHit (unitDataT *pUnit)
{
int i, cnt=0;
for (i = 0; i < pUnit->attacks; i++)
{
if ((rand()/328+1) <= pUnit->toHit) // 1..100
cnt++;
}
return cnt;
}
where, “pUnit” points to either a tank or an infantry unit type. So, a tank, with 3 attacks, gets 3 rolls. For every roll equal to or under the tank's percent “to hit,”, a counter is bumped. The result is returned to the caller.
The same procedure is then called for the defender, substituting “pUnit->defense” for “pUnit->attacks” in the loop control statement. Of course, the code, above, is just for illustration, as I have only one “ToHit” routine for both attacks and defenses.
To use the results of the “ToHit” computation:
if (curAttack <= curDefense) // miss
return ALIVE;
where, “curAttack” is the results of the attack roll and “curDefense” holds the defense roll.
The attack roll has to be larger than the defense roll to succeed, naturally. So, it is and we move on
attacks = curAttack - curDefense;
This is the number of attacks that got through the defense.
Now, I resolve the combat:
while (pDef->unitCnt) // step thru the defender stack
{
// one attack takes out 1 hit
if (--pDef->unitCnt <= 0) // this defender is a goner, remove it from its stack
return DEAD; // all the defenders are gone
if (--attacks <= 0) // attacks left?
return ALIVE; // no, defender stack was damaged but still alive
// a single defending unit has died
// but some attacks are left over
curDefense = CalcToHit(pDef->defense); // next unit's defense roll
if ((attacks -= curDefense) <= 0) // recalc. attacks
return ALIVE; // miss
} //ENDWHILE: defenders left in the unit stack
return ALIVE;
Let me go over this algorithm using a real-world example. I'll pit a stack of 3 tanks against a stack of 3 infantry. Let both roll full attack and full defense, so,
attacks = 3 attack - 2 defense = 1
The statement
if (--pDef->unitCnt <= 0)
is FALSE since 2 units remain in the defender's stack as
3 original defenders – 1 killed = 2 defenders left
Since the statement
--attacks <= 0
is TRUE, as
1 attacks – 1 defender's hits = 0
the function exits, declaring the defender's infantry stack is still ALIVE and is ready for the next attack.
Now, the attacker can send in his next tank. Let's assume, for the sake of brevity, that the tank rolls 2 attacks and the defender rolls 0 defense.
So,
attacks = 2 – 0 = 2
--pDef->unitCnt = 2 remaining units – 1 unit killed = 1 unit left
and
--attacks = 2 attack – 1 defender hit = 1 attack left
Now, the remaining defender gets a roll and, oh, no!, it's another 0.
attacks -= curDefense = 1 – 0 = 1 attack gets through
and, finally,
--pDef->unitCnt = 1 left – 1 killed = 0 units left
the function returns DEAD and the attacker now pits his infantry against the defender's tanks, if any.
You can see from this snippet, that since units can only ever take 1 hit, any attacks that get through are guaranteed to take out 1 defender. Handling multi-hit units is a bit more involved and, in fact, the game code can handle multi-hit units. I'll talk about this algorithm some other time.
August 26, 2010
Fleets are stacks of units. By stack, I mean one or more units of any or all of the unit types available in the game. So, a fleet can have 100 defenders or 1 attacker or 50 of both. The game is designed to pit large stacks of mixed units against each other. “Large” being a relative term, here. You won't see the tens of thousands of units that a game of MOO can produce. You may see fleets that have a few dozen units with both types in them, though.
In order to manage player fleets, I create a fleet class. Since this class will contain a method to display fleets, I use multiple inheritance and derive the class from both the unit and the sprite base classes. The fleet class, also, manages combat.
Besides combat, the fleet class manages the merging of fleets, their movement and their role in system defense. Fleets are created by star system production and destroyed in combat. Fleets can sit idling, they can be assigned to defense, they can be rallied to a location to make even bigger fleets and, of course, they can be sent off to do the dirty work of expanding the empire.
Each player instantiates its fleet object during game initialization. That's fancy talk that just means each player has its own fleet manager. Each fleet is a structure that contains, among other things,
- the number of units of each type in the fleet
- the fleet's current state (idle, moving, defending, attacking)
- the fleet's current location on the map (not the view)
- the fleet's destination, if any
- the number of turns to arrive at a destination, if any
- the distance per turn the fleet moves along the x-axis
- the distance per turn the fleet moves along the y-axis
- a variable to modulate the fleet's color, when needed
So, that's what a fleet is, more or less, from the game program's point of view.
As you may have noticed, most of the fields in the fleet data structure have to do with movement. That's because moving is the single, most complicated thing that fleets do, even more so than when they engage in combat.
I use 4 lists to manage a player's fleets. Please note that from now on, when I mention lists, I mean doubly-linked lists. These lists are:
- the idle list
- the moving list
- the defending list
- the arrival list
Fleet Creation
When a star system produces a unit, it calls the “AddFleet” method from the fleet class. This routine first searches the idle list and then the defending list to see if there is already a fleet in the star system. If there is, then the count for that unit type in the fleet structure is bumped and the function returns. This is the essence of the technique used to merge fleets.
If there is no fleet present, one is created by “calloc'ing” the fleet structure from the program heap. The structure is then initialized with the appropriate values and the function returns.
Since this is not really a tutorial, I'll hand-wave the hairy coding details away. Suffice it to say that the idle list is searched during the movement phase of the turn (Oh, no! Forward references, again! Please, bear with me) to find a fleet to send to a goal.
Fleet Movement
I'll discuss turns in detail another time. For now, I'll just mention that there are several phases to each turn. One of these phases is the “move fleets” phase. During this phase, the code scans the idle list for fleets of a given size. Remember: Neutral systems are guarded, so, sending single-unit fleets against them is not wise.
If a fleet of the proper size is found, a goal is selected from the goal list. The fleet is then sent on its merry way. The “move fleet” method of the fleet class is used to set the destination and fill in the movement fields of the fleet structure. The fleet is then removed from the idle list and added to the moving list and, voilà!, a game is born.
August 25, 2010
The Neutrals
I should mention that there is a 5th player, namely, the neutrals. These unsung heroes of most conquest games exist for one, and only one, purpose: to be, first, vanquished and then exploited by the other players. OK, they exist for two purposes.
I stipulate that each neutral star is lightly defended so that conquering them is easy but not quite guaranteed. The neutrals' defensive fleet has one attacker and one defender. The units' “to hit” is fixed at 60%, each. I, also, specify that neutral fleets are “healed” should they win the combat. This means that the next fleet that shows up finds a fully-armed defensive fleet, ready for action.
I'll discuss fleets and combat in more detail, later. I'll just mention here that the neutral fleets are an abstraction. You won't see them orbiting their stars on the map view. In fact, they are created, out of whole cloth, just before combat begins. This saves a lot of wear-and-tear on the game engine.
Scanners
I add the concept of limiting the players' view of the map. This can be thought of as a planetary scanner that can only detect stars within a certain range. This can be justified by the notion that the scan is for “habitable” planets which is, somewhat, in line with recent advances in astronomy.
The CPs, of course, can “see” as much or as little as I deem necessary. To ensure, flexibility, I make they use scanners, too. This will, eventually, allow gamers to choose their starting position and color.
The scan is within an arc along the y-axis. The scan represents distances along the x-axis that are in range. This means the scan is at its greatest near the y-origin and is reduced as it travels away from it. The scan is 15 squares along either axis. This length was determined by trial-and-error.
I use a 1D array of integers to hold the current scan of each player. The array size is fixed at “cMaxTilesY”, i.e., the size of the y-dimension. The values of the array elements are the number of tiles along the x-dimension that the player, currently, can see.
The geometry for the scan is a little tricky given the following:
Rotation around the origin is counterclockwise. Remember: the origin of the viewing area is the upper, left-hand corner and not in the center. The yellow area shows a scan of the southeast quadrant. Once the quadrants are taken into consideration, the algorithm uses simple, Pythagorean distances to calculate the particular x values along the y-axis. Since I store the results of the current scan, there is little danger of re-scanning an area.
I considered using a table to hold the x-axis values as they are constant to a normalized origin. However, I decide to go ahead and calculate the distances in place, since scanning does not occur too frequently. Old Pythagoras is surely getting a work-out in this code and there's more to come.
Whenever a new star system is conquered, part of taking over the system is to call the scan method. Based on the system's location on the map (not the view), the scanner sets up the parameters for the new scan.
The routine then steps through an ordered list of stars within range of the scan. It pops any star systems it finds onto another list. (Note: A pointer to the head of this second list was passed into the scan method by the caller.) When the scan function returns to the caller, the caller pulls the items off of this “new systems” list and puts them onto its goal list, sorting the new item by distance from the player's home system. Yep, there's Pythagoras, again.
So, now, the CP knows what to do but doesn't know how to do it, yet. First, I want to talk a little about fleets and combat and then come back to goal fulfillment, so to speak.
August 24, 2010
The Production Model
Now that I have the basic map display working, it's time to focus on game play. The first thing I set up is the production model. Each star represents a production center, presumably, based on planets revolving around them. As of this writing, and probably for some time to come, these planets are not shown. So, when I talk about “star production,” I'm talking about these invisible, planetary systems.
I think of the units of production as “gold.” I know this is not too sophisticated but then neither is this game's economy. Each star produces a fixed amount of gold which is then funneled into the manufacturing of things. At this point, there is no empire-wide economy, although I have put hooks into the code to add this later.
The fixed production of each star is, rather arbitrarily, assigned as follows:
Yellow Stars – 150 gold
Green Stars – 125 gold
Blue Stars – 100 gold
Red Stars – 75 gold
White Stars – 50 gold
Each turn, the base production is added to the production goal. When the goal is achieved, any excess is used to start the next cycle. I use floating point variables in the production model so that I can modify base production, later, and ensure that nothing is lost.
This,naturally, begs the question: What is being produced? The answer is that units are being produced.
Units
Although, the code can accommodate several unit types, I have, currently, only designed two, namely, an attacker and a defender. I am concerned about game balance and intend to only add more units if I can ensure that they won't break the game.
The units are:
Attacker
3 attack
1 defense
1 hit
60% chance to hit, initially
Defender
1 attack
2 defense
1 hit
60% chance to hit, initially
These two are pretty well balanced, especially, given the “luck” factor (chance to hit). Attackers, rarely, hit with full attack and defenders, also, rarely, defend with full defense. In addition, I allow mixed unit stacks, as opposed, to the one-on-one combat rule of Empire Deluxe and some other great games.
For the time being, I ignore both interstellar and combat movement. Interstellar movement is set to 2 squares per turn for both units, initially. Combat involves instantaneous, ranged attacks. These are futuristic arms, after all. The game engine, also, has hooks for attack, defense and hit modifiers, but, so far, I haven't added any.
I fix the unit cost as follows:
Attacker – 600 gold
Defender – 300 gold
And set the stars to the following production goals:
Yellow Stars – produce attackers
Green Stars – produce attackers
Blue Stars – produce defenders
Red Stars – produce defenders
White Stars – produce defenders
So, far, this has worked out quite well.
Now, the game can generate units every 3 to 4 turns, depending, and these units can form fleets. What to do next?
Setting Goals
Since this is a pure conquest game, right now, the goal is “See a star, attack it.” That sounds simple, doesn't it? Well, hold on. Let's take one thing at a time.
Does the CP attack that close, useless White star or that distant, fat Green star that will get the fleet growing? If I sort the stars by production, and that's what we're talking about, “sorting,” there's the distinct reality that the CPs will leap-frog from one high production star to the next, leaving all of the low production stars alone. This, also, affects the fleet “mix” where attacker units will outnumber defender units. This is not good or balanced.
I consider adding heuristics to force the CPs, after a certain amount of expansion, to “back fill” their conquests with the low producers. This becomes a little tricky as the definition of “after a certain amount of expansion” is very difficult to determine before hand. This has to do, mostly, with the random nature of the map. Since 3 out 5 stars could be considered low producers, there is a good chance that a number of high producers will be at the fringe of the CPs' quadrant. This means outward expansion might be fast and then a lot of back filling is needed.
I, also, think about the white stars and wonder, "why, oh, why would anyone waste an ounce of effort on such a miserable star?" I consider putting some "goodies" on the white stars that add to the base unit values, goodies such as +1 attack, +1 defense +1 hit. I consider it and then put the idea on the "to do" list since it adds complications I don't want to think about just yet. I put the hooks in for it, though.
Why am I sweating this detail? You may well ask. It is because that is how I believe a gamer would play it. Rush to get all the good stars and then add the not-so-good stars, later, if at all. If the AI is a push-over, all has been in vain.
All things considered, I opt to sort the stars by linear, i.e., Pythagorean, distance from the home planet. This then comprises the primary goal list for the AI.
A Word on Lists
“Sort,” you say? “List”, you say? What's all this then? Yes, the main data structure in this game is the linked list, in particular, the doubly-linked list, which is my personal favorite. This is an extremely flexible data structure and I use it whenever I need a way to track lists of items that can grow or shrink. Items such as the CPs goals. As goals are attained, the items are removed from the goal list and added to another list, the “owned stars” list, in fact.
Good descriptions of how to create and use linked lists, including sample code, are all over the net. I won't mention any one in particular. I create a doubly-linked list class and treat it as if it were a “library” function, i.e., make it, globally, available to every class that needs it.
August 23, 2010
I approached this feature with some trepidation. However, it ended up having a very straight-forward solution. The idea, of course, is to allow the player to scroll around the map by clicking on a location in the viewing area. The only rule is that, given the limits of the viewing area, when the player clicks on a location in the view, that becomes the new center of the map. After some trial-and-error, I decide to use the right mouse button to scroll, while the left map button is used to select map objects such as stars and fleets.
First, I declare some useful constants:
#define cWndSizeX 640
#define cWndSizeY 480
#define cRightPanelSize 64
#define cBottomPanelSize 64
#define cViewSizeX (cWndSizeX-cRightPanelSize)/cStarTileSize
#define cViewSizeY (cWndSizeY-cBottomPanelSize)/cStarTileSize
Then, I declare and initialize a constant to hold the coordinates of the center of the view.
const CPoint viewCenter (cViewSizeX/2, cViewSizeY/2);
Finally, I declare a variable to hold the map center and initialize it to the view center.
CPoint mapCenter (viewCenter.x, viewCenter.y);
Here's the code to handle scrolling:
void CGameWindow::OnRButtonUp (UINT flg, CPoint point)
{
int x, y, tmpX, tmpY;
// call the base class method
CWnd::OnRButtonUp (flg, point);
// convert to map coordinates
x = point.x/cStarTileSize;
y = point.y/cStarTileSize;
// ignore, if not in view area
if ((x >= viewSizeX) || (y >= viewSizeY))
return;
// get the map coordinates
tmpX = mapCenter.x – viewCenter.x + x;
tmpY = mapCenter.y – viewCenter.y + y;
// check for edge conditions and adjust, accordingly
if ((x > viewCenter.x) && ((tmpX + viewCenter.x) > cMapSizeX))
tmpX = cMapSizeX-viewCenter.x;
else if ((x < viewCenter.x) && ((tmpX - viewCenter.x) < 0))
tmpX = viewCenter.x;
if ((y > viewCenter.y) && ((tmpY + viewCenter.y) > cMapSizeY))
tmpY = cMapSizeY-viewCenter.y;
else if ((y < viewCenter.y) && ((tmpY - viewCenter.y) < 0))
tmpY = viewCenter.y;
mapCenter.x = tmpX;
mapCenter.y = tmpY;
}
Now, I can re-write the tile rendering method as:
int row, col, x, y, tmpX;
D3DXVECTOR3 loc;
loc.z = 0.0f;
// get the starting coordinates of the map
y = mapCenter.y - viewCenter.y;
tmpX = mapCenter.x - viewCenter.x;
for (row = 0; row < viewSizeY; row++, y++)
{
x = tmpX; // reset x
for (col = 0; col < viewSizeX; col++, x++)
{
if (starTile[y][x])
{
loc.x = (float)(col*cStarTileSize);
loc.y = (float)(row*cStarTileSize);
pSprite->draw (pTexture, &starTile[starMap[y][x]], NULL,
&loc, colorMod);
}
} //ENDFOR: traverse x-axis
} //ENDFOR: traverse y-axis
Now, it displays only that portion of the map that is in view. Notice that in this snippet, the 2D array indices have been “reversed.” This is because 'C' and C++, process a 2D array row (y-dimension), first, and then column (x-dimension). This efficiency may or may not be worth the mental hassle, since we, usually, think of Cartesian coordinates as “x” by “y.” You decide.
The image, below, shows the overall theory of operation (not to scale). The game window and the viewing area are fixed and the map scrolls, if you will, "under" the viewing area.
Whew! That's a lotta hairy details. I'll discuss something more interesting, next time. Promise.
August 16, 2010
Each star tile is 16x16 pixels. This means the board size is 960x960 pixels (60*16 x 60*16) which is, of course, somewhat larger than the 640x480 window I am using. This means I'll need to scroll the map, ideally, smooth-scroll the map. Additionally, I put a 64x416 pixel area on the right for buttons and a 64x640-pixel area on the bottom for scroll bars and information windows. Now, my viewing area is 576x416 pixels.
So, now, I'm ready to generate a map and display some images. Hold on! Display what images? Not only am I not a game designer, I'm not artistically inclined, either. In fact, I'm red-green color blind. When it comes to computer colors, I cannot tell the difference between the two “blues” (blue and purple) and the two “yellows” (yellow and green). I almost always have to play as “red” or “white.” Even with this restriction, I don't know how many wars I, accidentally, started by attacking my “yellow” friend who I thought was my “green” enemy. But enough about me and my troubles.
I use Photoshop to make five, colored images. These are simply colored circles. I think of them as the “blobs” and hope they are just place holders until I can get some good images. I then Photoshop them onto a single image that is 16x128 pixels. The first image is just “background-filled”, which is what us non-graphic artists types call “blank”. BTW, what I don't know about Photoshop would fill a library, if you haven't guessed, yet. The 5 blobs come next and the last two slots are left blank. I save the file as type “.png” because what do I know about image file types? I do know that these files must have a height and width that are a power of 2. If they are not, DirectX will, kindly, make them so. To avoid surprises, I ensure all of my image files are a power of 2 in both directions.
My wife, who is an artist, really and truly, takes pity on me and offers me two of her star creations. I replace the green and the blue blob with her creations. The results look like this.
The colored frames around the last 4 images indicate ownership. Every player starts with 1 yellow star. In all, there are 11 images. Since 11*16 is not a power of 2, the image file is extended by 5 to make the image width 256 pixels. The algorithm for using these tiles is:
First, some constant are needed
#define cStarTileSize 16 // tiles are square, so, only 1D is needed
typedef enum
{
cNoStarTile,
cYellowStarTile,
cRedStarTile,
cWhiteStarTile,
cBlueStarTile,
reservedTile1,
reservedTile2,
cYplyrHomeStarTile,
cRplyrHomeStarTile,
cWplyrHomeStarTile,
cBplyrHomeStarTile,
cMaxStarTiles
} starTileE;
Next, an array of rectangles is declared
RECT starTile[cMaxStarTiles];
Now, the rectangles can be initialized
int hIdx, wIdx, i;
for (i = 0; i < cMaxStarTiles; i++)
{
hIdx = i*cStarTileSize; // upper left x
wIdx = hIdx + cStarTileSize; // lower right x
SetRect (&starTile[i], hIdx, 0, wIdx, cStarTileSize);
}
When it's time to render the map, the program runs through a list that contains the type of star tile at a given location. For illustration, I'll use a sparse, 2D matrix, defined as
#define cMaxTilesX 60
#define cMaxTilesY 60
starTileE starMap[cMaxTilesX][cMaxTilesY];
The map generator zeroes the array and then fills it. At then end of map generation, array elements either have a star tile value or are zero.
This is a snippet of the render method
D3DXVECTOR3 loc;
loc.z = 0.0f;
for (int x = 0; x < cMaxTilesX; x++)
for (int y = 0; y < cMaxTilesY; y++)
// check if something's here
if (starMap[x][y] != cNoStarTile)
{
loc.x = (float)(x*cStarTileSize);
loc.y = (float)(y*cStarTileSize);
pSprite->draw (pTexture, &starTile[starMap[x][y]], NULL, &loc, colorMod);
}
This is not the world's most efficient algorithm, in fact it is far from it, but it should show one basic technique for rendering tiles. One obvious inefficiency is that this method renders the entire map each time even though only a portion of it will ever be in the player's view. (Remember: the view is only 576x416 pixels while the map is 960x960 pixels.)
Something needs to be said about these 2 lines:
loc.x = (float)(x*cStarTileSize);
loc.y = (float)(y*cStarTileSize);
These lines convert the map coordinates to the screen coordinates. For example, a star located at starMap[2][2] becomes a tile at screen position 32x32. To convert from a screen position to a map location, simply, divide by “cStarTileSize.” The code that tracks the mouse position and the player's mouse clicks do this conversion.
HCURSOR arrowCur = LoadCursor (NULL, IDC_ARROW);
HCURSOR crossCur = LoadCursor (NULL, IDC_CROSS);
void CGameWindow::OnMouseMove (UINT flg, CPoint point)
{
if (starMap[point.x/cStarTileSize][point.y/cStarTileSize] != cNoStarTile)
SetCursor (crossCur);
else
SetCursor (arrowCur);
}
This snippet changes the cursor from an arrow to a cross-hair whenever the player mouses over a star tile on the screen.
Here's my first cut at rendering the star map.
Pretty nasty, huh? I haven't added the right-side panel, yet, and the spacing between stars is way too close. And what's with all those annoying dots!?! At least, the “New” button works and I can generate random star maps all day long. Baby steps.
August 13, 2010
The first task was to do a little research on mixing MFC with DirectX. This would determine my window creation method. I knew enough about MFC that it's best to add it at the beginning, during the creation of the project, then it is to shoe-horn it in later.
I'm afraid to say that I lost the basic reference to creating an “MFC-ready” window app. As I recall, there are two choices. Derive the window functions from CWnd or from CFrameWnd. Both use CWinApp for the window application. I decided to use CWnd since I didn't expect to need the full power of MFC that CFrameWnd provides. There's a minor hassle getting and storing a window handle (HWND) for the Direct3D device but it's not insurmountable.
BTW, I did not use the class wizard or the resource editor that are part of Visual C/C++ .Net. I did it by hand. I guess I'm a glutton for punishment. Actually, I like to “feel the road”, especially, during my first, semi-serious effort at something new.
Here is what looks like the definitive net resource on MFC and DirectX:
http://www.gamedev.net/reference/articles/article1778.asp
This article uses DirectX 8.1 but, by this time, I could pretty much ignore the Direct3D sample and focus on MFC. There are a lot of good tips in this article. The main hint is to call the CWnd method, Invalidate(FALSE), after rendering. This forces Windows to redraw the button, slider and static child windows I had made. Another interesting ideas was to override CWinApp::OnIdle and to call the rendering loop from this method.
So, now, I could create a window, initialize the Direct3D device and render a border around my D3DX viewport. Putting MFC aside for the moment, it was time to create a random map.
Some Simplifying Assumptions
The original Master of Orion had 5 star types. Each star, generally, contained a planet which was at one of nine levels of development. This seemed a bit much to start coding up, so, I started thinking of some simplifications.
Eventually, I remembered that classic war game, Empire. As I recalled, the resource centers were “cities” with different base production capacity. Neutral cities were lightly defended. The goal was to capture cities and then crank out units with different, attack, defensive and movement capabilities and send them in a directed attack against the CPs.
One of the nice things about Empire and its follow-on, Empire Deluxe, was that the CPs' aggression was unbiased. They “hated” each other as much as they did the human player. They didn't gang up on you, unless, you were unfortunate enough to get caught between them. Now, I set a short term goal to create Empire of Orion.
The Map
Star maps offer a few advantages over terrain maps. One is that they are sparsely populated. Every star map tile doesn't require a sprite. The other advantage is that pathfinding is rather straight-forward. No A* or some such algorithm is needed. Line-of-sight works just fine, thank you.
First, I specify some rules for map generation. There will be 5, basic star types, namely, yellow, green, blue, red and white. Wait! Are there green stars? Sure there are, we just haven't seen them, yet.
There are to be 3 instances of each star type for a total of 15 stars. Since there are 4 players (3 CPs and 1 human), there are a total of 60 map objects, i.e., stars.
The map is divided into quadrants. Each player starts in a corner and is guaranteed to have a set of 3 iterations of the 5 star types. These are randomly distributed within each quadrant. No star can be less than 5 squares from another. This evens out the distribution a little and avoids clumping. The board, itself, is a 60x60 grid of squares, which is, also, a simplification.
August 11, 2010
The biggest unknown on the development list is DirectX. The only thing I knew about DirectX when I started this effort was that it was the MS interface commonly used in game development. If you want good graphics, you need to access the power of modern graphics cards and DirectX has the means to do this. I'd heard about OpenGL, of course, and was aware that, ultimately, for cross-platform development, this is preferred to DirectX. But if it weren't for bad decisions, I wouldn't make any decisions at all, so, I forged ahead with DirectX.
There are some good tutorials for learning basic DirectX. Here are two I found very helpful:
http://32bits.co.uk/
http://www.drunkenhyena.com/cgi-bin/dx9.pl
Both provide clear explanations of the basic concepts and sample code. I have found, over the years, that sample code is the best way to learn new technologies. A good sample is worth a thousand words of theory.
During my preliminary investigations, I had come across the term, “sprite,”, usually, in the context of “this game still uses 2D sprites.” This implied ancient technology, much like a stylus on clay tablets. So, I figured, OK, let's start with something old. “Take baby steps”, Dr. Marvin tells Bob. So, I set a goal to animate a 2D sprite.
The first effort can be seen here.
It took about 8 months of on-again-off-again (mostly, off-again) effort to get to this point. Most of that time was spent working through the DirectX tutorials. It doesn't look like much but here's what's in it:
A 640x480 popup window with a 640x400 viewport (the black portion)
2 32x32-pixel spaceship sprites (Marauder and Foxbat from MOO)
2 border sprites (“across” and “side” (reused))
1 animated “fireball” (reused to make 2)
A line primitive (it's animated, too, from left to right)
A text sprite with a D3DX font
Underlying this display is a working, combat simulator (more on this later). I used Adobe Photoshop to make the sprites (spaceship, fireball and border). The spaceships, themselves, are rotated during rendering to face each other. I gleaned all this, except the fireball animation, from the tutorials. The technique used to animate the fireball involved nothing more than high school math (slope-intercept formula). The formula for rotating the spaceships uses basic trigonometry. You might be so unkind as to mention that I didn't get this quite right. But, as the old saying goes, “Nothing ventured, nothing lost,” or some such thing.
So, there's my first effort. The code is ugly and the rendering is suboptimal, to say the least. Nothing to write home about but it's a start and, now, on to bigger and better things.
Combat Simulator
I started with a combat simulator because I was pretty sure this would be a self-contained piece of a larger effort. I knew the game would have several screens, for instance, an intro screen, a strategic map screen, several information screens, a ship design screen, a combat screen, etc. The simulator, also, seemed like the best test for how well I had learned DirectX basics and how far I could push those concepts.
For combat resolution, I used the method described in the MOM FAQ , sections 4.5 and 4.6.
Foxbat, on the left side, is, computationally, a six-figure unit with 3 attack, 2 defense, 1 hit with a 60% chance to hit. Marauder is a single figure with 7 attack, 5 defense, 8 hits and, also, has a 60% change to hit. The simulation was set up to answer the age-old question: “Why does my brand-new Spyder the Rogue get whacked by a miserable bunch of swordsmen?” As expected, bad luck is the answer.
What I ended up with is a very useful sprite class (tip o' the hat to the good folks at 32Bits), a pretty good unit class and, literally, a mess of working code that could be cut-and-pasted into something a little more grand.
August 9, 2010
I have been interested in strategy games for a long time, now. Even as a kid, I was drawn to games such as chess, Risk and some of the Avalon Hill titles like Gettysburg and D-Day. I first got hooked on computer strategy games with Civilization back in the days of my 486-DX2. I had tried my hand at other types of computer games, such as, adventure, arcade and RPGs but nothing grabbed my imagination (and time!) like the original CIV did. Since then, I have been a fan of these games and have played through a long list of turn-based classics and near-classics, the details of which I won't bore you with.
Recently, I decided to code up just such a strategy game. At this point, I've got a “playable” prototype and want to share some of my experiences with others. Right now, I consider this effort a hobby and it just exists for my own amusement.
I know that it takes much effort and many years to go from a hobbiest to an indie game developer. It takes a lot of passion for gaming, as well as, the ability to produce a series of increasingly popular versions of a concept, while moonlighting. So, patience, dedication and incremental improvements may be the key to success. Oh, and a good, basic idea might also help. I hope I have some of the former, if not the later. We'll see.
So, where to start? How about a good, basic idea? How about a tired, old, derivative idea? “To boldly go where many have gone before!” I'm thinking of space, magic and conquest. Master of Orion meets Master of Magic or some such thing. “Been there, done that”, you may well say and you'd be right. And, so, operating on the assumption that getting something, anything, going is better than nothing, here goes nothing.
Coding Environment
I have a lot of professional experience coding in 'C', so, this is my language of choice. Actually, it's C++, but more on that later. The platform is the PC, the operating system is Windows. Why program to this particular environment? I'm familiar with it. There's no other reason.
A few years, ago, I tried my hand at a Win console, space war simulation. The graphics, such as they were, were essentially DOS-based string manipulations. The “game play” was automatic. Four CP's slug it out for control of the galaxy. This effort was, eventually, lost to a disk crash. Gone but not forgotten. One thing I figured out: Win console is a dead end. Spiffiness is needed and is needed from the get go. This means a windows “app” and some “real” graphics.
Another important element is the user interface. This means buttons, sliders, statics and things I haven't even thought of. MFC should do the trick. Oh, what a fool I am, but more on this later.
Now, Microsoft's graphics API, Direct3D, is part of their DirectX package and DirectX is free. Then there's Visual C++ Express which, also, has the right price, but what about MFC? Visual C++ Express 2010 has added auto-generated GUI features. Although the results look very nice, I'm just not too crazy about Windows Forms. I know there are some work-arounds to adding MFC functionality to Express but my rule of thumb for Microsoft tools is “if they don't support it, then neither do you.” Caution is the better part of valor, here. So, I break out my Visual C/C++ .Net 2003 and hope for the best.
A Windows application means that C++ is preferred over plain, vanilla, ANSI 'C'. C++ implies object oriented design and programming. I'm no stranger to OOD or OOP but writing “Get” and “Set” methods for every member that a class must surface to another class, just gets plain tiring. Especially, when one can just change the access from “private” to “public” with no more than a simple cut-and-paste. And, besides, no one is going to ever want to see this code, so, I'm safe. I use C++ but use it, more or less, as a “smart” 'C'. Coherency and encapsulation are the goals here and that comes, naturally, from clean living and a heapin' helpin' of good, ol' structured design.
At the risk of alienating some readers, here's the short list of skills I needed to get this project together and my self-evaluation of each:
'C' programming - very good
C++ programming – competent
Visual Studio – good
Windows – OK, but not great
MFC – Yes, I've messed with this on occasion
DirectX – What is this?
The main source of information for all things programmable is the world wide web. Great tutorials are available for each item listed, above. I'll mention the ones I used as I go along.
In part, this blog is not just for self-aggrandizement but, also, to pay back, in some small way, the many dedicated contributors on the web that have made my life easier.
The Idea
I haven't kept a diary about my effort, this will have to suffice. I was attracted to a space empire game for a number of reasons. First off, I am a huge fan of sci fi and, secondly, coding something based on a handful of sprites seemed like the path of least resistance. I'll go into this later. Oh, yes, this is a 2D idea, no meshing around with 3D for me, at least not at first.
Now, here's a vague notion of something MOO-like, with some MOM magic thrown in. Also, there might be an extra level or two where the base game rules are bent ala Myrror or the Shadow World (from Shadow Magic). Details, details!
It's got to be a four-X game and it's gonna be turn-based, oh, and maybe it should be fun. We'll see.
Here's a quick list of ingredients to put into the four-X pot:
Map Generation
User created maps are important but random map generation is a must. Random maps should be fair, that is, resources are spread evenly throughout the board. Who hates playing Civilization IV for three days only to find out their territory doesn't have any oil or uranium? I do! Yes, yes, there are ways around this, including cheats but to me that's not fun. OK, I admit it. I love strategy games but I'm not very good at them.
eXplore
Exploration is integral to the genre. From CIV to Heroes of Might and Magic stepping into the unknown is part of the hook. What am I up against? Where are the goodie huts? Where be there monsters?
Path Finding
Players have to know their way around the map and they have to be smart about it.
eXpand
There must be ways to acquire more resources. More resources more mean money and more money means more things. Important things like armies and weapons that are needed to get and to keep more territory.
eXploit
Your empire needs to produce things. Things you need. Things, like
Research – for faster, meaner weapons and a happier life for all
An Economy – to provide a way to funnel resources where they're needed most, be it light bulbs for research, shields for production or Elvi for happiness. The wise allocation of resources is what distinguishes losers from winners.
Ship Design – to allow trade offs in weapons. Do I reduce armor to increase speed? Can 1000 cheap units take on a budget-killing, mega-doom monstrosity? They could, ya know?
eXterminate
Yes, the evil ones are out there and they're coming to get you. And, so, the best defense is …. Well, you know that. Sci fi games are good this way since the “others” can be bugs or worms or rock-eating, silicon-based freakatoids. Not only are these guys mean, they're not even human, so, wiping them out is not only necessary but imperative. That being said, there is, also, a place for peaceful expansion, aka diplomacy, as well as, a way of getting specials that, say, only rock-eaters can provide.
Code is needed to build a war machine, disperse the war machine, handle hostile encounters, just to mention a few. To paraphrase Joe Dante, it's easier to write “100 gremlins march down the street” than it is to show 100 gremlins marching down the street.
That's just a brief list and it's off the top of my head. The problem is that thinking of all this stuff at once is overwhelming. The solution is to break the big problem into smaller pieces. These smaller pieces should be independent of each other, so that each can be coded and tested separately. As each piece is completed it is then integrated with the piece that went before.
In summary, I want a classic “god” game. I want it to be fair, i.e., the CP's have no real advantages. I want it to be easy to learn, i.e., intuitive, but, also, to be immersive. I want it to be easy to play with no micromanagement for micromangement's sake. And when it's all over, I want to say, “Now, that was fun. Let's do it again.”
So where to start?