Game Developer
Hex Board Layout (C#, Unity)
This code sample shows a Unity C# script that creates a hexagonal grid for a board game. After laying out the tiles using the specified Unity prefab, it calculates each tile's adjacent tiles. It also allows for the colors of the hex tiles to be modified from the Unity Editor via the TerrainTileInfo property.


HexBoard.cs
using UnityEngine;
using System.Collections.Generic;
// Terrain Types
public enum Terrain
{
Grass,
Canyon,
Desert,
FlowerField,
Forest,
Water,
Mountain,
Castle,
}
// Test data for project
public sealed class GlobalData
{
public static Terrain[] TERRAIN_DATA =
{
Terrain.Forest, Terrain.Forest, Terrain.Forest, Terrain.Forest, Terrain.Mountain, Terrain.Mountain, Terrain.Grass, Terrain.Mountain, Terrain.Canyon, Terrain.Canyon,
Terrain.Forest, Terrain.Mountain, Terrain.Forest, Terrain.Forest, Terrain.FlowerField, Terrain.Grass, Terrain.Mountain, Terrain.Mountain, Terrain.Mountain, Terrain.Canyon,
Terrain.FlowerField, Terrain.FlowerField, Terrain.Forest, Terrain.FlowerField, Terrain.FlowerField, Terrain.FlowerField, Terrain.Grass, Terrain.Grass, Terrain.Water, Terrain.Mountain,
Terrain.Desert, Terrain.FlowerField, Terrain.FlowerField, Terrain.FlowerField, Terrain.Water, Terrain.Castle, Terrain.Grass, Terrain.Water, Terrain.Mountain, Terrain.Mountain,
Terrain.Desert, Terrain.Desert, Terrain.Desert, Terrain.Desert, Terrain.FlowerField, Terrain.Water, Terrain.Grass, Terrain.Water, Terrain.Canyon, Terrain.Canyon,
Terrain.Desert, Terrain.Canyon, Terrain.Desert, Terrain.Desert, Terrain.Desert, Terrain.Water, Terrain.Water, Terrain.Canyon, Terrain.Grass, Terrain.Canyon,
Terrain.Desert, Terrain.Desert, Terrain.Canyon, Terrain.Desert, Terrain.Desert, Terrain.Water, Terrain.FlowerField, Terrain.Castle, Terrain.Grass, Terrain.Canyon,
Terrain.Canyon, Terrain.Canyon, Terrain.Castle, Terrain.Desert, Terrain.Water, Terrain.FlowerField, Terrain.FlowerField, Terrain.FlowerField, Terrain.Grass, Terrain.Grass,
Terrain.Desert, Terrain.Canyon, Terrain.Water, Terrain.Water, Terrain.Water, Terrain.Forest, Terrain.Forest, Terrain.FlowerField, Terrain.Grass, Terrain.Grass,
Terrain.Desert, Terrain.Canyon, Terrain.Canyon, Terrain.Water, Terrain.Forest, Terrain.Forest, Terrain.Forest, Terrain.Grass, Terrain.Grass, Terrain.Grass
};
}
// Interface for a hex tile game object
public interface IHex
{
float GetWidth();
float GetHeight();
int GetX();
int GetY();
GameObject GetGameObject();
void AddSibling(IHex sibling);
void Initialize(int x, int y, Terrain terrainType, Color color);
}
public class HexBoard : MonoBehaviour
{
[System.Serializable]
public class TerrainInfo
{
public Terrain TerrainType;
public Color TerrainColor;
}
// Prefab for a hex tile, must implement IHex
public GameObject HexPrefab;
// Mapping of terrain types to material colors
public TerrainInfo[] TerrainTileInfo;
// How much spacing to place between tiles
private const float _tileEdgeOffset = 0.03f;
private List<IHex> _hexes = new List<IHex>();
private float _hexWidth;
private float _hexHeight;
private float _xHexStep, _yHexStep;
// Use this for initialization
void Start ()
{
_hexWidth = 0;
_hexHeight = 0;
IHex hex = HexPrefab.GetComponent<IHex>();
if(hex == null)
{
Debug.LogError("Specified hex prefab does not contain an IHex game object");
}
_hexWidth = hex.GetWidth();
_hexHeight = hex.GetHeight();
// Calculate the horizontal and vertical distance between hex tiles
_xHexStep = _hexWidth * 2 + _tileEdgeOffset;
_yHexStep = _hexHeight * 0.75f * 2 + _tileEdgeOffset;
BuildBoard(10, 10, GlobalData.TERRAIN_DATA);
}
void BuildBoard(int horizontalTileCount, int verticalTileCount, Terrain[] terrainData)
{
if(horizontalTileCount * verticalTileCount != terrainData.Length)
{
Debug.LogError("Terrain data specified to build board does not have the same dimensions as specified width and height");
return;
}
// Clean up if the board is being reset
if(_hexes.Count > 0)
{
foreach(IHex hexToDestory in _hexes)
Destroy(hexToDestory.GetGameObject());
_hexes.Clear();
}
// Calculate where the first hex tile is placed
float startX = -(horizontalTileCount * _xHexStep) / 2;
float startY = verticalTileCount * _yHexStep / 2;
// Create the hex board
for(int i = 0; i < verticalTileCount; i++)
{
for(int j = 0; j < horizontalTileCount; j++)
{
Vector3 tilePosition = new Vector3();
tilePosition.x = startX + j * _xHexStep;
tilePosition.z = startY - i * _yHexStep;
// Shift odd horizontal rows to the right
if(i % 2 == 1)
tilePosition.x += (_xHexStep * 0.5f);
GameObject hexGameObject = Instantiate(HexPrefab, tilePosition, Quaternion.identity) as GameObject;
IHex newHex = hexGameObject.GetComponent<IHex>();
Terrain terrain = terrainData[i * verticalTileCount + j];
newHex.Initialize(j, i, terrain, GetTerrainColor(terrain));
// Make this hex a child of the hex board
newHex.GetGameObject().transform.SetParent(transform);
_hexes.Add(newHex);
}
}
// Setup Adjacencies
for(int i = 0; i < _hexes.Count; i++)
{
IHex hex = _hexes[i];
int x = hex.GetX();
int y = hex.GetY();
int adjIndex;
bool firstInRow = (x == 0);
bool lastInRow = (x == horizontalTileCount - 1);
// Left Adjacency
if(!firstInRow)
{
adjIndex = i - 1;
hex.AddSibling(_hexes[adjIndex]);
}
// Right adjacency
if(!lastInRow)
{
adjIndex = i + 1;
hex.AddSibling(_hexes[adjIndex]);
}
// Upper adjacency
TryAddHexSibling(hex, i - horizontalTileCount);
// Lower adjacency
TryAddHexSibling(hex, i + horizontalTileCount);
// Setup other diagonal adjacencies for an odd row
if (y % 2 == 1)
{
if(!lastInRow)
{
// Upper right
TryAddHexSibling(hex, i - horizontalTileCount + 1);
// Lower right
TryAddHexSibling(hex, i + horizontalTileCount + 1);
}
}
// Setup other diagonal adjacencies for an even row
else
{
if(!firstInRow)
{
// Upper left
TryAddHexSibling(hex, i - horizontalTileCount - 1);
// Lower left
TryAddHexSibling(hex, i + horizontalTileCount - 1);
}
}
}
}
// Helper function to avoid indexing the hex data out of bounds
private void TryAddHexSibling(IHex hex, int siblingIdx)
{
if (hex == null || _hexes == null)
return;
// Range check
if (siblingIdx >= 0 && siblingIdx < _hexes.Count)
hex.AddSibling(_hexes[siblingIdx]);
}
private Color GetTerrainColor(Terrain type)
{
foreach(TerrainInfo t in TerrainTileInfo)
{
if (type == t.TerrainType)
return t.TerrainColor;
}
Debug.LogError("Failed to find color for terrain type " + type);
return Color.white;
}
}
HexBoard.cs
Mumble Word Game Solver (C#, Windows Forms)
Several years ago, I spent many hours playing a Facebook word game called "Mumble". The object was simple: given four letters of the alphabet, come up with as many words as you can that use these letters. A word scored 45 points for each of the given letters used plus 15 points for each additional letter. Then the word score was multiplied by how many given letters were used. If all four were used, the word score was multiplied by 4. This code sample shows a solver I wrote that parses a text file of English words and comes up with the best possible words for each game.

MumbleSolver.cs
using System;
using System.Windows.Forms;
using System.IO;
namespace MumbleSolver
{
public partial class MumbleSolverForm : Form
{
public MumbleSolverForm()
{
InitializeComponent();
}
// Definition for a high scoring word candidate
public struct MumbleWord
{
public String word;
public int score;
};
private void buttonSolve_Click(object sender, EventArgs e)
{
// Acquire the input letters
String inputLetters = textBoxInput.Text;
// If the input text is not valid, return early
if (inputLetters.Length != 4)
return;
// Create list of best words
MumbleWord[] bestWords = new MumbleWord[4];
// Array to tally how many scoring letters occur in a word
int[] letterCounts = new int[4];
// Open the dictionary file for reading
using (StreamReader reader = new StreamReader("dictionary.txt"))
{
// Loop through every word in the dictionary
while (!reader.EndOfStream)
{
String currentWord = reader.ReadLine();
// If word is too short or longer than maximum character length, continue to next word
if (currentWord.Length < 5 || currentWord.Length > 15)
continue;
// Initialize the number of matching letters and word score
Array.Clear(letterCounts, 0, 4);
int wordScore = 0;
// Loop through characters in the current word
foreach (char currentCharacter in currentWord)
{
bool matchFound = false;
// Loop through input letters and check for match
for (int i = 0; i < 4; i++)
{
// If word letter does not match input letter
if (inputLetters[i] != currentCharacter)
continue;
// Word uses input letter so it scores 45 points
matchFound = true;
wordScore += 45;
// Increment the number of times this letter was matched
letterCounts[i]++;
break;
}
// A non-input letter still scores 15 points
if (!matchFound)
wordScore += 15;
}
// Calculate total number of input letters used in word
int inputLettersUsed = 0;
for (int i = 0; i < 4; i++)
{
if (letterCounts[i] != 0)
inputLettersUsed++;
}
// If no input letters were used, continue
if (inputLettersUsed == 0)
continue;
// Update the word score based on letters used
wordScore *= inputLettersUsed;
// Check if word beats current best words
int lowestWordIdx = 0;
// Loop through current best words
for (int i = 1; i < 4; i++)
{
// If this best word's score is less than the lowest score, update lowest
if (bestWords[i].score < bestWords[lowestWordIdx].score)
lowestWordIdx = i;
}
// If this dictionary word's score does not beat the lowest best word, continue
if (wordScore <= bestWords[lowestWordIdx].score)
continue;
// Update the best word list
bestWords[lowestWordIdx].score = wordScore;
bestWords[lowestWordIdx].word = currentWord;
}
// Close the file
reader.Close();
}
// Calculate the final score
int finalScore = 0;
// For each best word, increment the final score by word score
for (int i = 0; i < 4; i++)
{
finalScore += bestWords[i].score;
}
// Update form text boxes with results
textBoxWord1.Text = bestWords[0].word;
textBoxWord2.Text = bestWords[1].word;
textBoxWord3.Text = bestWords[2].word;
textBoxWord4.Text = bestWords[3].word;
textBoxFinalScore.Text = finalScore.ToString();
}
private void buttonClear_Click(object sender, EventArgs e)
{
// Clear all text boxes
textBoxInput.Clear();
textBoxWord1.Clear();
textBoxWord2.Clear();
textBoxWord3.Clear();
textBoxWord4.Clear();
textBoxFinalScore.Clear();
}
}
}
Game Event System (C++)
This code sample shows an event system written for the game engine for Scurvy the Seaweed Slinger. It allows for modules to communicate without being coupled, by subscribing to events with a string name. The system uses the singleton pattern.
CEvent.h
#include <string>
using namespace std;
class CEvent
{
private:
// Event Data
void * m_pData;
// Event Name
string m_szEventName;
public:
void* GetParam(void)
{
return m_pData;
}
void* GetParam(void) const
{
return m_pData;
}
string GetName(void)
{
return m_szEventName;
}
string GetName(void) const
{
return m_szEventName;
}
CEvent(string szEventName, void *pParam = NULL)
{
m_szEventName = szEventName;
m_pData = pParam;
}
// Copy Constructor
CEvent(const CEvent& ref)
{
this->m_szEventName = ref.GetName();
this->m_pData = ref.GetParam();
}
// Assignment Operator
CEvent& operator=(const CEvent& ref)
{
return (*this);
}
};
IReceiver.h
#include "CEvent.h"
class IReceiver
{
public:
// Default constructor
IReceiver(void)
{
}
// Destructor
virtual ~IReceiver()
{
}
virtual void HandleEvent(CEvent* pEvent) = 0;
};
CEventSystem.h
#include <map>
#include <list>
using namespace std;
#include "CEvent.h"
class IReceiver;
class CEventSystem
{
private:
// Map event names to corresponding receivers
multimap<string, IReceiver*> m_pRegisteredReceivers;
list<CEvent> m_lUnprocessedEvents;
// Calls handle event all receivers subscribed to event
void DispatchEvent(CEvent* pEvent);
// Checks to see if the subscriber is registered to this event
bool IsAlreadyRegistered(string szEventName, IReceiver *pReceiver);
// Constructor
CEventSystem(void)
{
// I am making certain that I am not reusing old events or
// registered receivers
Shutdown();
}
// Copy Constructor
CEventSystem(const CEventSystem& ref) { }
// Assignment Operator
CEventSystem& operator=(const CEventSystem&) { return *this; }
// Destructor
~CEventSystem()
{
Shutdown();
}
public:
// Singleton
static CEventSystem* GetInstance(void)
{
static CEventSystem instance;
return &instance;
}
void RegisterReceiver(string szEventName, IReceiver* pReceiver);
void UnregisterReceiver(string szEventName, IReceiver* pReceiver);
// Stop receiver from receiving all events
void UnregisterReceiverAll(IReceiver* pReceiver);
// Adds the event to the list of unprocessed events
void SendEvent(string szEventName, void* pData = NULL);
// Sends all of the unprocessed events to their appropriate receivers
void ProcessEvents();
// Removes all events from the list of unprocessed events
// Useful for pausing the game or shutting it down
void ClearEvents(void);
void Shutdown(void);
};
CEventSystem.cpp
#include "CEventSystem.h"
#include "IReceiver.h"
void CEventSystem::DispatchEvent(CEvent* pEvent)
{
// Create a pair of iterators to track the first and last subscribers of the event
pair< multimap<string, IReceiver*>::iterator,
multimap<string, IReceiver*>::iterator > receivers;
receivers = m_pRegisteredReceivers.equal_range(pEvent->GetName());
// Cycle through all the receivers, calling their Handle Event functions
while (receivers.first != receivers.second)
{
IReceiver* subscriber = receivers.first->second;
if (subscriber)
{
subscriber->HandleEvent(pEvent);
receivers.first++;
}
}
}
bool CEventSystem::IsAlreadyRegistered(std::string szEventName, IReceiver *pReceiver)
{
// Create a pair of iterators to track the first and last subscribers of the event
pair< multimap<string, IReceiver*>::iterator,
multimap<string, IReceiver*>::iterator > receivers;
receivers = m_pRegisteredReceivers.equal_range(szEventName);
while (receivers.first != receivers.second)
{
// Do these receivers match?
if (receivers.first->second == pReceiver)
{
return true;
}
receivers.first++;
}
return false;
}
void CEventSystem::RegisterReceiver(string szEventName, IReceiver* pReceiver)
{
if (pReceiver == NULL || IsAlreadyRegistered(szEventName, pReceiver))
{
return;
}
m_pRegisteredReceivers.insert(pair<string, IReceiver*>(szEventName, pReceiver));
}
void CEventSystem::UnregisterReceiver(string szEventName, IReceiver* pReceiver)
{
// Create a pair of iterators to track the first and last subscribers of the event
pair< multimap<string, IReceiver*>::iterator,
multimap<string, IReceiver*>::iterator > receivers;
receivers = m_pRegisteredReceivers.equal_range(szEventName);
while (receivers.first != receivers.second)
{
// Have we found the target receiver?
if (receivers.first->second == pReceiver)
{
// Yes, we should now unregister it.
m_pRegisteredReceivers.erase(receivers.first);
break;
}
else
{
// Nope, therefore we should go to the next element
receivers.first++;
}
}
}
void CEventSystem::UnregisterReceiverAll(IReceiver *pReceiver)
{
multimap<string, IReceiver*>::iterator iter = m_pRegisteredReceivers.begin();
while (iter != m_pRegisteredReceivers.end())
{
if (iter->second == pReceiver)
{
// Sets the iterator to the next node or the last node
iter = m_pRegisteredReceivers.erase(iter);
}
else
{
iter++;
}
}
}
void CEventSystem::SendEvent(std::string szEventName, void *pData)
{
CEvent newEvent(szEventName, pData);
m_lUnprocessedEvents.push_back(newEvent);
}
void CEventSystem::ProcessEvents(void)
{
// Keep processing events until the list is empty
while (!m_lUnprocessedEvents.empty())
{
DispatchEvent(&m_lUnprocessedEvents.front());
m_lUnprocessedEvents.pop_front();
}
}
void CEventSystem::ClearEvents(void)
{
m_lUnprocessedEvents.clear();
}
void CEventSystem::Shutdown(void)
{
ProcessEvents();
m_pRegisteredReceivers.clear();
ClearEvents();
}