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.

Hex Board
Hex Board Inspector in Unity

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.

Hex Board

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();
}