Just An Int

Further thoughts about programming simplification led me to revisit integers. This primitive type can be coerced into doing a lot and I realized this when building a small game in Unity.

As an exercise, I was trying to make a game that fused Blackjack with Snakes and Ladders. Essentially, the outcome of a Blackjack round would influence the dice roll on a board of Snakes and Ladders. In my head, I mapped out some classes for the Blackjack portion: there would be a class for cards, one for players (the dealer would be a special type of player) and another for the game itself so that rules can be processed. This is what it could have looked like.

classDiagram class Card { -value : int -isFace : bool } class Deck { -count : int +Shuffle() +DrawCard() } Deck *-- Card class Game { -players : List[Player] } class Player { +hand : List[Cards] } class Dealer { } Player <|-- Dealer

I used the word ‘could’ because it dawned on me that this was getting overly complicated already and I had barely started. The problem with having a hammer is they make everything look like a nail. As such, modern programming techniques are powerful but for some game systems, they can be overkill. People in the past made games with limited computing power and programming techniques. Limitations forced simplicity. So I tried something different and decided to make Blackjack in Unity in the simplest way I could think of. Here’s a small portion of how it turned out.

int[] deck = new int[52];

string[] suits =  new string[]{ "Hearts", "Spades", "Clubs","Diamonds" };
string[] numbers = new string[]{ "Ace","Two", "Three", "Four", "Five", 
					"Six", "Seven", "Eight", "Nine", "Ten", 
					"Jack", "Queen", "King" };

List<int> playerHand = new List<int>();
List<int> dealerHand = new List<int>();
int deckIndex = -1;

void Start()
{
	for (var i = 0; i < 52; i++) deck[i] = i;
	Shuffle(deck);
}

void DrawCard(List<int> hands)
{
	deckIndex++;
	var card = deck[deckIndex];
	hands.Add(card);

	int suit = card / 13;
	int num = card % 13;
	string display = numbers[num];
	Debug.Log($"gave {hands} : {display} of {suits[suit]}");
}

void Update()
{
	if (playerHand.Count == 0 && dealerHand.Count == 0)
	{
		DrawCard(playerHand); DrawCard(playerHand);
		DrawCard(dealerHand); DrawCard(dealerHand);
		playerTurn = true;
	}
	
	// Input handling, turn and rule processing, etc.
}

The essence of this Monobehaviour code is using division and modulo operations to map a numeric range of 0 to 51 as playing cards.

As I wrote elsewhere, game mechanics can be expressed with mathematics. With just a few integers that represent cards, I was able to build a simple version of the Blackjack game. And this got me thinking: what else can you do with just an int? For the purpose of this question, let’s assume we’re dealing with an unsigned 16-bit integer which means the values range from 0 to 65,535.

When we look at an int as a series of bits, we can think of it as an array of boolean values. This means we can represent as many as 16 states with a single int. We can store configuration data in a single number! Assume we have a player that can unlock elemental skills over the course of a game in any order; the unlocked states of these skills can be stored in a single int. If the first four skills are unlocked, the int’s value is 15 but if only the fourth skill was unlocked, it would be 8.

When we look at an int as a decimal, each digit can represent a single number valued from 0 to 9. Since the max value is 65,535, we can only work with 4 digits; hence, we have a range of 0000 to 9999 which gives us an array of four numbers. To get a digit we need only use the formula: (number % 10^digit)/10^(digit-1). There are four cardinal directions so we can use an int to store values per direction. If we adopt this for a 32-bit integer, we get to play with 9 numbers instead of 4. With 9 unique values, we can express numeric properties for a single 2D tile and its adjacent tiles. This idea of using digits as array values gets even wilder once you consider the ability to use hexadecimals or other bases.

When we look at an int as a product of two prime numbers, we can map out a detailed combination chart for 50 elements. Taking the int’s max value’s square root, we get approximately 256 under which are 50 prime numbers from 2 to 227. We can use this for a simple crafting system: assume 11 is “fire” and 13 is “carrot,” 143 can now be cooked carrots. No other combination will be able to generate cooked carrots. Of course, we can share items among prime number products to create an illusion of combinatorial depth. We can say 7 is “microwave” and 91 can now also represent cooked carrots. I would add that “microwaved fire” is odd so perhaps 77 can result in an invalid item. Anyway, there should be C(50,2) or 1225 possible combinations.

As a combination of what we read in the prior two scenarios, a single int can represent positions on a 2D grid of max size 255x255. Some modulo math will be required to correctly traverse the grid in the 4 cardinal directions. One will also need to decide if the grid supports wrapping or just clamps when edges are reached. This can represent not just 2D maps but also UI elements.

And these are the scenarios I can think of as of writing this piece. Not all of it might be practical but it was fun to think about.

 Date: April 30, 2023
 Tags:  musings

Previous
⏪ Are All Games just Card Games?

Next
Another NewGameEveryDay Quarterly Review ⏩