Saturday, August 10, 2013

Programming Aggressiveness, Building Personality Traits Into Game AI (Part 2)

Howdy! Welcome to Part 2 of Building Personality Traits Into Game AI. Part 1 can be found here. The personality trait that I will be covering in this section is aggressiveness. Similar to the bravery trait, I'm looking for a function that will return a value of either true or false. This returned value will indicate whether or not a unit is aggressive enough to attack an enemy.

Drawing Board: Idea #1 - Skewed Randoms

Originally I had thought of using skewed random chance to determine if a unit was aggressive enough to attack--meaning that when an enemy entered a unit's sight, a number between 1 and 100 would be randomly generated and then be compared to the unit's aggressiveness trait (also a number between 1 and 100). If the number was less than the unit's aggressiveness trait, then the unit would be aggressive enough to attack the enemy.

The problem with this method is that the AI behavior is a little too random--too inconsistent. The player would have trouble figuring out how exactly the aggressiveness trait affected a unit's behavior. So I decided to do some research.

Drawing Board: Idea #2 - Interpersonal Space Multiplier

The most applicable (and interesting) thing that I found while researching was the concept of Proxemics, pioneered by Edward T. Hall.

Take a look at the following diagram regarding interpersonal space:


Edward T. Hall's personal reaction bubbles, showing radius in feet and meters.

This is what Wikipedia has to say about interpersonal space:

Interpersonal space is the psychological "bubble" that exists when one person stands too close to another. Research has revealed that there are four different zones of interpersonal space:
  • Intimate distance ranges from touching to about 18 inches (46 cm) apart, and is reserved for lovers, children, close family members, friends, and pet animals.
  • Personal distance begins about an arm's length away; starting around 18 inches (46 cm) from the person and ending about 4 feet (122 cm) away. This space is used in conversations with friends, to chat with associates, and in group discussions.
  • Social distance ranges from 4 to 8 feet (1.2 m - 2.4 m) away from the person and is reserved for strangers, newly formed groups, and new acquaintances.
  • Public distance includes anything more than 8 feet (2.4 m) away, and is used for speeches, lectures, and theater. Public distance is essentially that range reserved for larger audiences.
To summarize:
  • Intimate space <= 1.5 ft
  • Personal Space <= 4 ft
  • Social Space <= 8 ft
  • Public Space <= 25 ft

How is this useful?


How can I relate the concept of interpersonal space to my game? My AI units have a limited sight range (each unit can only see a certain distance around itself) that essentially creates a sight "bubble" around each unit. See where I'm going with this? What if I utilized the different levels of interpersonal space to help determine whether or not a unit is aggressive enough to attack an enemy? Taking the most literal translation of the diagram, I made each section of interpersonal space correlate to a certain distance from the unit. Each section would be relative to a certain percentage of a unit's sight range.

If I were to equate the outer radius (25ft) of the diagram to the outer radius of a unit's sight bubble, I would get the following ratios:
  • Intimate space:
    1.5 ft / 25 ft -> 0.06
    At or closer than 6% of a unit's sight range
     
  • Personal Space:
    4 ft / 25 ft -> 0.16
    At or closer than 16% of a unit's sight range
     
  • Social Space:
    8 ft / 25 ft -> 0.32
    At or closer than 32% of a unit's sight range
     
  • Public Space:
    25 ft / 25 ft -> 1
    At or closer than 100% of a unit's sight range
     
Currently I'm using a distance of 100 for a unit's initial sight range so:
  • Intimate Space <= 6
  • Personal Space <= 16
  • Social Space <= 32
  • Public Space <= 100
For reference, each unit's body has an initial radius of 6. This causes an issue. This means that intimate space would be inside of a unit's body! Considering that we will not have a fixed body size (units can grow) or sight range, this could get difficult to manage. I'm going to scrap this idea for now. Yes, there is a workaround for this issue, but I've already done a considerable amount of work beforehand in determining the best way (for me) to evaluate aggressiveness, and I've found a better, less complicated solution. However, this is still a valuable concept so I'm going to finish explaining my train of thought.

The idea was to add a multiplier (dependent on which bubble an enemy was located in) to how likely a unit was to be aggressive. I tried to translate each bubble into a value based on the premise of how someone might react towards a stranger within that bubble.

Here are my interpretations of aggressiveness in each bubble:
  • Intimate Space:
    The hug zone. This space starts at about elbow's length away. This is a space where someone would be very uncomfortable if a stranger were to get within. This space represents an area where physical damage can easily be inflicted and an attack would be hard to dodge.
    Multiplier: 2
     
  • Personal Space:
    This is about arm's length away. This space represents an area where physical damage can easily be inflicted, but an attack would be fairly easy to dodge.
    Multiplier: 1.5
     
  • Social Space:
    This is an acceptable space for strangers to be located in, but this space also implies that there is likely some form of interaction between everyone within. This is the space where someone might start to get defensive.
    Multiplier: 1.25
     
  • Public Space:
    This is an acceptable space for strangers to be in and does not imply any sort of direct interaction.
    There is no multiplier for this space.
     
The final piece of this concept involves a comparison. Presuming we could generate a value, between 1 and 100, based on proximity, we would take that value and multiply it by the interpersonal space multiplier. If the resulting value was greater than or equal to the aggressiveness trait (also a value between 1 and 100), then the unit would be considered aggressive enough to attack an enemy and the function would return true. Let's continue on to the next idea.

Drawing Board: Idea #3 - Territory Multiplier

Animals have been known to be more aggressive in areas that they consider to be their territory. This concept is fairly simple. What if I used a multiplier (dependent on where, in regards to the unit's territory, the unit and enemy are located) in determining whether or not a unit is aggressive enough to attack?

Scenarios and interpretations translated into multipliers:
  • The unit is in the unit's territory while the enemy is not:
    This could be equated to being in your house, or on your lawn, and watching someone ride a bike down the middle of the street. This might prompt a unit to be slightly more aggressive because it is in its own territory, but not too aggressive because the enemy is not in the unit's territory.
    Multiplier: 1.25
     
  • Both the unit and the enemy are in the unit's territory: This could be equated to having a stranger enter your property while you are on the premises. You aren't too concerned about the stranger because you can keep your eye on them and can potentially stop any form of property damage/theft they might intend to do. However, there is still a stranger on your property so you will be more aggressive than normal.
    Multiplier: 1.5
     
  • The enemy is in the unit's territory while the unit is not:
    This could be equated to being at your neighbor's house and watching someone walk up to your down. There is an unattended stranger on your property. If the stranger were to attempt any property damage or theft, it would be very difficult for you to stop them. You might be very aggressive in this situation.
    Multiplier: 2
     
  • Neither the unit nor the enemy is in the unit's territory:
    This would be like seeing a stranger in public.
    There is no multiplier for this scenario.
     
Final Implemented Idea:

I wanted something inherently simple for implementation so I settled with a form of proximity based aggressiveness. The aggressiveness trait would correlate with a certain percentage of a unit's sight range. So a unit with a value of 50 for aggressiveness would be aggressive enough to attack any enemies that are at or closer than 50% of the unit's sight range. This idea utilizes a linear progression between the aggressiveness trait value and how aggressive a unit actually is. However, this idea still retains dynamism due to varying sight ranges and aggressiveness trait values. If it wasn't apparent, this idea is a modified version of the Interpersonal Space Multiplier.

Here is the function:
float percentOfSightEnemyIsAt = Vector2.Distance(Position, enemy.Position) / Sight; 

if (percentOfSightEnemyIsAt <= Personality.Aggressiveness / 100f) 
{ 
 return (true); 
} 
else 
{ 
 return (false); 
}

This function is designed so that an aggressiveness trait value of 100 will always result in a unit being aggressive enough to attack an enemy within the unit's sight range. While this is an elegant solution to defining aggressiveness, this function seems to be a little too simple. The aggressiveness behavior would greatly benefit from some additional logic. I'm going to try and work the Territory Multiplier concept into this function.

Here is the final function:
float territoryMultiplier = 1; 

if (isUnitInTerritory) 
{ 
 territoryMultiplier = 2f; 

 if (isEnemyInTerritory) 
 { 
  territoryMultiplier = 3f; 
 } 
} 

float percentOfSightEnemyIsAt = Vector2.Distance(Position, enemy.Position) / Sight; 

if (percentOfSightEnemyIsAt <= (territoryMultiplier * Personality.Aggressiveness) / 100f) 
{ 
 return (true); 
}
else 
{ 
 return (false); 
}

As you can see, the territory multiplier increases the range at which a unit will be aggressive. This leads to some interesting behavior.

Here's a video demoing this behavior:


That's all I have for today! I know that there are other ways this could have been done, so feel free to share any of your thoughts!

Sunday, August 4, 2013

Programming Bravery, Building Personality Traits Into Game AI (Part 1)

Hello there ladies and gentlemen!  Today I am going to explain how to program bravery into AI!

Take a look at this function I wrote:
        public Boolean BraveEnoughToAttack(AI_SingleUnit enemy)
        {

            //how many attacks it would take to kill the enemy
            float howManyAttacksToKill = (float)Math.Ceiling((float)enemy.Health / DamageDealing.MeleeDamage);

            //how long would it take to kill the enemy
            TimeSpan timeTillKill = TimeSpan.FromSeconds(howManyAttacksToKill * (1f / Attacking.AttackSpeed));


            //how many attacks from the enemy it would take to kill me
            float howManyAttacksTillDeath = (float)Math.Ceiling((float)Health / enemy.DamageDealing.MeleeDamage);

            //how long would it take for the enemy to kill me
            TimeSpan timeTillDeath = TimeSpan.FromSeconds(howManyAttacksTillDeath * (1f / enemy.Attacking.AttackSpeed));

            if (timeTillKill < timeTillDeath)
            {
                //should win
                return (true);
            }
            else //should lose or tie
            {
                //How many of me would it take to kill them?

                //given this equation:
                //newTimeToKill = timeTillKill * 1/numberOfMe
                //newTimeTillDeath = timeTillDeath * numberOfMe

                //newTimeToKill = newTimeTillDeath
                //timeTillKill * 1/numberOfMe = timeTillDeath * numberOfMe
                //timeTillKill = timeTillDeath * numberOfMe^2
                //timeTillKill / timeTillDeath = numberOfMe^2
                //sqrt(timeTillKill/timeTillDeath) = numberOfMe

                //because timeTillKill > timeTillDeath, numberOfMe will always be greater than sqrt(1)
                float numberOfMeItWouldTakeToKill = (float)Math.Sqrt(timeTillKill.TotalSeconds/timeTillDeath.TotalSeconds);

                if (1 - (1f / numberOfMeItWouldTakeToKill) <= Personality.Bravery / 100f)
                {
                    return (true);
                }
                else
                {
                    return (false);
                }
            }
        }

So let's get explaining!  The purpose of this function is to determine whether or not a unit is brave enough to consider attacking an enemy.  A unit is either brave enough or it isn't, so the function returns either true or false.  True being brave enough; false being not brave enough.

Let's explain some variables!

howManyAttacksToKill
//how many attacks it would take to kill the enemy
float howManyAttacksToKill = (float)Math.Ceiling((float)enemy.Health / DamageDealing.MeleeDamage);

This is the number of attacks it would take the unit to kill the enemy. This is calculated by dividing the amount of health the enemy has by the amount of damage the unit can do in one hit. We round the value up because, when it comes to hitting, there are no halfsies. A unit either hits the enemy or it doesn't.

timeTillKill
//how long would it take to kill the enemy
TimeSpan timeTillKill = TimeSpan.FromSeconds(howManyAttacksToKill * (1f / Attacking.AttackSpeed));

This is how long it would take the unit to kill the enemy.  This is calculated by multiplying the number of attacks it would take the unit to kill the enemy by the attack speed of the unit.

howManyAttacksTillDeath
//how many attacks from the enemy it would take to kill me
float howManyAttacksTillDeath = (float)Math.Ceiling((float)Health / enemy.DamageDealing.MeleeDamage);

This is the number of attacks it would take the enemy to kill the unit. This is calculated by dividing the amount of health the unit has by the amount of damage the enemy can do in one hit. Once again, we round the value up because we don't hit like little sissies.

timeTillDeath
 //how long would it take for the enemy to kill me
TimeSpan timeTillDeath = TimeSpan.FromSeconds(howManyAttacksTillDeath * (1f / enemy.Attacking.AttackSpeed));

This is how long it would take the enemy to kill the unit.  This is calculated by multiplying the number of attacks it would take the enemy to kill the unit by the attack speed of the enemy.

Let's look at the first conditional statement:
if (timeTillKill < timeTillDeath)
{
     //should win
     return (true);
}

In this case, the unit has calculated that it will be able to kill the enemy faster than the enemy can kill the unit, so we say that the unit is brave enough to attack the enemy.

In order to really explain this, I think I need to define "bravery" first.
Google says that bravery is "courageous behavior or character." 
So what exactly does "courageous" mean then? 
Google says that courageous means "not deterred by danger or pain." 
Those are some pretty "human" definitions. They need to be translated so that they are more relevant to my AI. My AI is not programmed to sense pain, so let's talk about danger. To the unit, a dangerous enemy would be an enemy that, given a fair fight, would kill the unit if it were to engage in combat. As long as our unit thinks it will be able to kill the enemy, then it's not really being "brave" because it doesn't think there is any danger. So, given a supposedly guaranteed victory, the unit is brave enough to engage because it doesn't even have to consider being brave. To the unit, there is nothing to worry about. We return true.

Let's look at the next part of the conditional statement:
else //should lose or tie
{
     //How many of me would it take to kill them?

     //given this equation:
     //newTimeToKill = timeTillKill * 1/numberOfMe
     //newTimeTillDeath = timeTillDeath * numberOfMe

     //newTimeToKill = newTimeTillDeath
     //timeTillKill * 1/numberOfMe = timeTillDeath * numberOfMe
     //timeTillKill = timeTillDeath * numberOfMe^2
     //timeTillKill / timeTillDeath = numberOfMe^2
     //sqrt(timeTillKill/timeTillDeath) = numberOfMe

     //because timeTillKill > timeTillDeath, numberOfMe will always be greater than sqrt(1)
     float numberOfMeItWouldTakeToKill = (float)Math.Sqrt(timeTillKill.TotalSeconds/timeTillDeath.TotalSeconds);

     if (1 - (1f / numberOfMeItWouldTakeToKill) <= Personality.Bravery / 100f)
     {
          return (true);
     }
     else
     {
          return (false);
     }
}

In this case, the unit has calculated that the enemy would kill the unit faster than the unit would kill the enemy (a losing situation) or that they would both kill each other at the same time (a neutral situation).  This is where I really need to define "bravery".  I need to make bravery measurable.  So how do I do that?  I think it's fair to say that, in a fight with a stronger opponent, the stronger the opponent is, the more brave it would be to engage in combat.  So it would be more brave to fight someone who is three times stronger than you, rather than someone who is two times stronger than you.  I need to quantify how much stronger the enemy is in comparison to the unit.  Let me break it down for you.

How many units would it take to kill the enemy faster than the enemy could kill the units?

This problem can be solved with either an equation or through trial and error.  With the amount of times this code would be executing in my game, trial and error is simply not an option.  I need to come up with an equation.

Since the conditional compares how long it would take the unit to kill the enemy versus how long it would take the enemy to kill the unit, I am going to keep the comparison between kill times.

To start, I've already defined when the unit will kill the enemy faster than the enemy will kill the unit: when timeTillKill is greater than timeTillDeath.  I need to figure out when the new timeTillKill will be greater than the new timeTillDeath. For the sake of simplicity, it's okay to just figure out when the new timeTillKill will equal the new timeTillDeath because that is where the threshold of victory lies.

So, the equation can be defined as: 
newTimeTillKill = newTimeTillDeath

Now I need to define newTimeTillKill and newTimeTillDeath.  This is what I came up with:

newTimeTillKill = oldTimeTillKill / numberOfMe
newTimeTillDeath = oldTimeTillDeath * numberOfMe

I realize that these definitions are not perfect and are only an approximation, but these are the only definitions I could come up with that returned a solvable equation.  If you can think of a more accurate solution, please let me know.  On a side note, we do not need a perfectly accurate definition because bravery is a relatively subjective matter.

Solving for numberOfMe:
newTimeTillKill = newTimeTillDeath

oldTimeTillKill / numberOfMe = oldTimeTillDeath * numberOfMe

oldTimeTillKill = oldTimeTillDeath * numberOfMe2

oldTimeTillKill / oldTimeTillDeath  = numberOfMe2

sqrt(oldTimeTillKill / oldTimeTillDeath) = numberOfMe

Final equation:
numberOfMe = sqrt(timeTillKill / timeTillDeath)

So, now that we have an idea of how much stronger the enemy is, we need to evaluate whether or not a unit is brave enough to consider attacking the enemy.  In my game, DATABITS, the bravery attribute is a number between 1 and 100 and could be considered a percentage.  50 would mean 50% bravery.  Because I want the bravery attribute to account for enemies that are between (inclusive) 1 and (exclusive) infinity times stronger, I need to have some kind of parabolic relationship in my evaluation of bravery.  This is where the next conditional statement comes in.

if (1 - (1f / numberOfMeItWouldTakeToKillEnemy) <= Personality.Bravery / 100f)
{
     return (true);
}
else
{
     return (false);
}

By taking the inverse of numberOfMeItWouldTakeToKillEnemy, we're returning how strong the unit is in comparison to the enemy.  So if it takes three units to kill the enemy, the unit can be considered to be as strong as 33.3% of the enemy.  This is a nice number to have, but because a higher bravery attribute means that a unit is more brave, we need to subtract the calculated value from 100% when comparing the value to a unit's bravery attribute.  This way, it takes 77.7% bravery to fight an enemy that is three times stronger than the unit.  Because we know that this code will only be reached in neutral or losing conditions, we know that the variable numberOfMeItWouldTakeToKillEnemy will always be greater than or equal to one.

That's all I have for today!  I know that there are various other ways this could be done, so feel free to share any of your thoughts!

...Part 2...

Thursday, August 1, 2013

Visual Representation Of Numbers In Minimal Space (Part 2)

After some further thought on my ring system from part 1, I decided that each ring shouldn't consist of ten pieces.  Each ring should consist of nine pieces.

Here are three reasons why:
  1. You cannot fit the number ten into one place value.  Ten pieces should mean some kind of overflow into an outer ring.
  2. With the way the system is now, I cannot use the number zero because each ring needs to have at least one piece in order for the user to know that there is a ring there.  If a user does not know that a ring is supposed to be in a certain location, they will misinterpret the number the system is trying to represent.
  3. For larger numbers it is difficult to tell whether a ring has nine or ten pieces because the representation gets visually "busy". 
I decided to modify the system in two ways:
  1. Each ring now has nine pieces.
  2. A ring that is supposed to represent the number zero is now a continuous circle.
Here is the old representation of the number ten next to the new representation of the number ten:

Old 10
New 10
Which one is quicker to read?  The modified system has at least two benefits:
  1. Representations of "zeros" actually look like zeros.
  2. There is a crisp, visual distinction when a ring is full (or empty if you're pessimistic) that makes larger numbers easier to interpret.

So after all of that thought and modification, it turns out that my system isn't so practical.  I was correct in my previous assumption that my system might be difficult to read with larger numbers (too many rings).

Here are some examples:
1,219,481,632
1,905,050,212
1,628,402,560
1,656,228,427
1,090,540,523
878,576,028
854,733,186
76,512,244
506,638,468
663,987,316
Try counting the rings.  Takes awhile, doesn't it?  It turns out that humans are bad at counting large numbers of objects.  I created my ring system because I wanted quick comparison between large numbers.  And guess what?  It didn't answer my problems!  So back to the drawing board.

Why is it difficult for humans to compare large numbers?

  1. Large numbers have to be compared column by column.  The more columns, the more comparison.
  2. The eye has to go back and forth between each number several times.  A faster attempt at a comparison is more error prone as the person is very likely to lose their place and screw up the comparison.

New thought: what if, in the center, I put a count of the number of rings that are in the representation?

Pros:
  • Users would quickly know that a representation with a larger number is greater than a representation with a smaller number.  Using a 64-bit unsigned integer, the largest count would be 20.  When using the count, the largest comparison between two numbers would involve only four columns (two each).  Humans can compare two to two pretty quickly.
  • It wouldn't increase the radius of space that the system takes up.
Cons:
  • This would increase the learning curve for understanding the system.
  • Users would still have to count ring pieces if the counts were the same. 

Here are two examples of this idea:


Pretty easy to tell which number is larger, right?  Now tell me each number's value.  Go ahead, I'll wait.

So let me point out the obvious.  I need to be able to quickly compare numbers and large numbers take too long to compare.  

Let's narrow this experiment down to the confines of my game.  What do I really care about?  I planned on using this system to keep track of the generations of units.  

Quick background for how the game works: 
  1. You start off with units.
  2. Your units acquire points.
  3. You spend your points to upgrade your future units.
  4. Every time you spend points, the next generation of units is created.
Currently, DATABITS supports 50 live units per player.  I'm only concerned with comparing the local player's generations so I only need to compare 50 generations at most.  The only generations I care about are the ones with living units.  Ultimately (because it would be cool) I want to be able to constantly display what generation each unit is from, but doing so would look ugly when the numbers start to get large.

So what if I constantly ranked and displayed the relative age of the generations for the current living units?

What if I ranked them from oldest to newest?  With a max of 50 units, that's a number range of 1 to 50.  50 is a reasonable number for my system (or a standard textual form) to represent.

So if I had seven living units from multiple generations, let's say generations [1, 22, 65, 83, 83, 100, 340], they would be displayed as [1, 2, 3, 4, 4, 5, 6].

I want to keep a certain aesthetic in my game, and numbers look tacky, so I'm going to forego using a textual representation for a constant display.  I'm considering having a toggle-able display for the relative age of a unit (represented with my ring system).  Then I would couple that with allowing the user to select a unit and drill down to view its actual generation number in a textual representation.  However, I still want the user to be able to have an idea, with only a quick glance, of the actual difference in generation numbers (not just the rank).  So I'm going to throw in a color system. (To Be Continued).

Tuesday, July 30, 2013

Visual Representation Of Numbers In Minimal Space (Part 1)

Given an 8 x 8 pixel grid and only one solid color, I challenged myself to come up with intuitive, visual representations for the numbers one through ten while trying to keep at least one axis of symmetry.  These are the results.

        1        2         3        4         5        6         7        8        9        10

1)

2)

3)

4)

5)

6)

7)

8)

This was a difficult challenge.  While these icons might be functional, they aren't necessarily intuitive.  These are my notes on the results.

1) The icons for 1, 6, 9, and 10 break pattern.  There are two visual aids in determining an icon's value here.  The first aid being that a larger amount of positive space correlates to a higher value.  The second aid is a little harder to explain, but the pairs [2,3], [4,5], and [7,8] all have it.  If you were to duplicate the lower valued icon, rotate the duplication 180 degrees around the center, and overlay the duplication over the original icon, then you would get the higher valued icon.  This is not very intuitive and does not work for icons that have both vertical and horizontal symmetry.

2) The icons for 7 and 10 break pattern.  This series is very similar to the first series.  There are two visual aids in determining an icon's value here. The first aid being that a larger amount of positive space correlates to a higher value.  The second aid, once again, is a little harder to explain, but the pairs [1,2], [3,4], [5,6], and [8,9] all have it.  If you were to duplicate the lower valued icon, rotate the duplication 180 degrees around the center, and overlay the duplication over the original icon, then you would get the higher valued icon.  This is not very intuitive and does not work for icons that have both vertical and horizontal symmetry.

3) The icons keep two patterns.  The first pattern being that as value increases, so does the amount of positive space used in the icon.  The second pattern being that odd numbers only have vertical symmetry while even numbers have both vertical and horizontal symmetry.

4) This series is like the third series, but utilizes more positive space.  The icons keep two patterns.  The first pattern being that as value increases, so does the amount of positive space used in the icon.  The second pattern being that odd numbers only have vertical symmetry while even numbers have both vertical and horizontal symmetry.

5) The icons for 9 and 10 break pattern.  There are only visual aids in determining an icon's value here.  The first aid being that a larger amount of positive space correlates to a higher value.  The second aid is used in numbers 1 through 8.  The higher the positive space reaches, the higher the value.  1 is one pixel high, 2 is 2 pixels high, etc.  There were not enough vertical pixels to handle ten rows, so a pattern was embedded into the icon as it filled up.  Once I hit the eight vertical pixel, numbers 9 and 10 were created by filling in parts of the pattern.

6) The icon for 10 is the only icon that breaks pattern.  There are two visual aids in determining an icon's value here.  The first aid being that a larger amount of positive space correlates to a higher value.  The second aid is used in numbers 1 through 9.  The number of "dots" correlates to the value of the icon.  1 has one dot, 2 has two dots, etc.  These patterns all keep vertical symmetry.

7) This series is very similar to the sixth series, but better utilizes the natural movement of the eye.  This utilization of the eyes natural movement could be better implemented, but is slightly sacrificed for keeping diagonal symmetry.  The icon for 10 is the only icon that breaks pattern.  There are two visual aids in determining an icon's value here.  The first aid being that a larger amount of positive space correlates to a higher value.  The second aid is used in numbers 1 through 9.  The number of "dots" correlates to the value of the icon.  1 has one dot, 2 has two dots, etc.

8) This series is very similar to the seventh series, but better utilizes the natural movement of the eye.  This series does not have any symmetry.  The icon for 10 is the only icon that breaks pattern.  There are two visual aids in determining an icon's value here.  The first aid being that a larger amount of positive space correlates to a higher value.  The second aid is used in numbers 1 through 9.  The number of "dots" correlates to the value of the icon.  1 has one dot, 2 has two dots, etc.

While this challenge was an entertaining learning experience, I did have an underlying motive for it.  I wanted to intuitively represent large numbers in a minimal amount of space while still keeping a nice aesthetic.  An 8 x 8 pixel grid is not enough space to do what I wanted--especially because I wanted something more "circular" to fit in with the aesthetics of DATABITS.  What I really learned from this experience is that I need more space to work with!

To solve my problem I devised a ring based system.  Each ring represents a place value.  The rings are counted from the innermost ring to the outermost ring.  So the first ring would be the one with the smallest radius.  The first ring represents one column to the left of the decimal place, the second ring represents two columns to the left of the decimal place, etc.  The number of pieces each ring contains represents the number that would go in the corresponding column.  Here is an example of numbers 1 through 100.



There are multiple benefits to this method.  One benefit is that I have a practical system for numerical representation that will easily fit large numbers into relatively small circular spaces.  Another benefit is that, in comparison to textual representation, I save space on larger numbers.  Due to visibility, the minimum radius of a ring is 12 pixels long and each consecutive ring needs to have its radius increased by 2 pixels.

With my system, the width of the representation for a number in pixels is:
24 + (4 * (number of place value columns - 1))

With standard textual representation, the width of the representation for a number in pixels is:
(number of place value columns * the width of a character)

With large numbers, my system saves a lot of space in comparison to textual representation.

Let's assume that the width of a character in a standard font is 10 pixels wide.  My system would be saving space on any number with four or more place value columns.  So anything in the thousands or up.

The final benefit to my method that I will mention is that numbers can be quickly compared to one another. If there is a difference in radii, the physically larger representation will be representing the larger number.  If the radii are the same, starting with the outermost ring and going towards the innermost ring, a quick look at the pieces of the ring can reveal which number is larger.  Because the pieces are inserted into the bottom center of the ring and push the other pieces around the circumference (it helps to think of them as being balanced across the bottom of the ring), the achieved overall height of the ring pieces correlates to the value of the number they represent.  The higher the pieces reach, the higher the number they represent.

While I'll accept the argument that with too many rings my system might be difficult to read and could potentially lose some practical value, there is one thing that my system will always have above textual representation--and that is pleasingly symmetrical aesthetics.