
UTF-8 in Games
web html frontend ui-ux design games python
When you’re reading this you probably don’t think much about text. When I go to setup a new html page from scratch I typically just hit !
which in vs code produces:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
Even just in this small bit of code there’s a lot of information, but the one I want to focus on is:
<meta charset="UTF-8">
When we see text we typically don’t care at all about the complexities involved in rendering it, the complexity of the font infrastructure to make rendering all this possible, or the encoding.
*There’s an accompanying repo with the code examples here
What is Encoding?
For those who are unaware what encoding is, I would recommend checking out this page for a detailed explanation, but in short when you have text like this paragraph it isn’t actually text. Instead each letter is simply a number, and the encoding is what tells the browser how to take these numbers and represent them as text.
For a quick example in python consider the text:
def encode_str(input_text:str) -> list[int]:
result = []
for letter in input_text:
letter = ord(letter) # convert letter to number
result.append(letter)
return result
def decode_str(input_text:list[int]) -> str:
result = ""
for letter in input_text:
letter = chr(letter)
result += letter
return result
# Testing
original_text = "Hello World"
encoded_string = encode_str(original_text)
# Prints: [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]
print(encoded_string)
decoded_string = decode_str(encoded_string)
# Prints: Hello World
print(decoded_string)
# Whether the string is the same
is_same = True if original_text == decoded_string else False
# Prints: Is decoded string the same as original? Yes
print(f"Is decoded string the same as original? {'Yes' if is_same else 'No'}")
This is what powers text in files, and by extension the majority of the internet.
The wonderful world of UTF-8
So, let’s get to the point. UTF-8 is the standard that runs most of the world. It is the character set chosen for most things by default (in english speaking countries at least), and as such it has a ton of support. This is handy because when UTF-8 (also called unicode), was created they added more than your traditional “characters”, in fact they added all you need to make tons of common games. For example, dominos, Mahjong, chess, dice, playing cards, here’s a suit of spades entirely in text:
🂠 🂡 🂢 🂣 🂤
🂥 🂦 🂧 🂨 🂩
🂪 🂫 🂬 🂭 🂮
There are some quirks with most of these, including some challenges you might not be used to. For example those cards are hard to see. Most typical text isn’t so information dense, so let me show off the same characters, but more interactive to help see the potential:
🂠
🂡
🂫
🂬
🂮
Game Dev
I am terrible at building games. I find the engines hard to reason about, and I often find it’s easier to “start from scratch” than using lots of modern engines. If you could make a game with basic HTML, and a small amount of javascript, now you get:
- A runtime that has standards going back 20 years of compatability
- Lots of tutorials
- A ton of pre-developed design materials
- Easy ways to distribute
When developing simple games like the ones that have character codes, you can use UTF-8 to generate procedural (code generated) assets quickly. No one wants to see test messages and logs, UTF-8 provides a chance to create game assets that work with all the features of HTML (event listeners, animations, etc.). In fact this means we can develop games with only basic web development skills.
UTF-8 🤝 Browsers
The handy thing about UTF-8 and HTML is a feature I forgot to mention, the encoding. There’s a feautre in HTML that allows you to specify characters you can do this with the format &#x<value>;
where Value is a hexadecimal number. For example 🂡
is 🂡 (ace of spades). For cards we need to care about the last two digits ǰ
is constant. The last two digits are:
- The letter: A-D representing ♠ ♥ ♦ ♣
- The number; 1-E(14) 2-10 are just 2-10, 1 is ace, A is jack, B is queen, E is King
- We skip 12 because it’s the knight (told you these get weird some times)
With this we can create a fully functioning deck of cards in ~80 lines of code .
The code (collapsed for easy reading)
class Card{
/**
Suit {"♠"|"♥"|"♦"|"♣"} - The suit of the card
value {Number} - The value of the card between 1-13 (ace is 1 and becomes 14 if acesHigh)
acesHigh {Boolean} - If ace should be worth 11 or 1
*/
constructor (suit, value, acesHigh = false){
let color = "black"
if (suit ==="♥" || suit ==="♦"){
color = "red"
}
let letter = "A"
switch (suit){
case "♠": letter = "A"; break;
case "♥": letter = "B"; break;
case "♦": letter = "C"; break;
case "♣": letter = "D"; break;
default: throw new Error(`Provided suit is invalid ${suit}`);break;
}
let characterNumber = value
if (value>9){ // Need to start with hex values A-E
if (value >11){
characterNumber = (value+1).toString(16).toUpperCase()
} else{
characterNumber = value.toString(16).toUpperCase()
}
}
this.suit = suit
this.character = `ǰ${letter}${characterNumber};` // UTF-8 Character
this.html = `<span class="card" style="color:${color}">ǰ${letter}${characterNumber};</span>`
if(value == 1 && acesHigh){
this.value = 14
}
this.value = value // The raw value (i.e. queen > jack since queen is higher value)
if (value == 14){ // Aces high, and it's an ace
this.number = 11
} else if (value > 10){ // The base value (i.e. queen == jack since both are 10)
this.number = 10
} else{
this.number = value
}
}
}
class Deck{
constructor (){
let cards = []
let deck = {
"♠":[],
"♥":[],
"♦":[],
"♣":[]
}
for (const suit of ["♠","♥","♦","♣"]){
for (let i=1;i<=13; i++){
let newCard = new Card(suit, i)
cards.push(newCard)
deck[newCard.suit].push(newCard)
}
}
this.cards = cards // The cards in the deck in an unordered array
this.deck = deck // The cards in the deck, by suit
this.remaining = cards.length // Number of cards remaining
}
/** Gets a random card, if remove is True will remove it from this.remaining*/
getRandomCard(remove=false){
if (this.cards.length){
const cardIndex = Math.floor(Math.random()*this.cards.length)
const card = this.cards[cardIndex]
if (remove){
this.deck[card.suit].splice(this.deck[card.suit].indexOf(card), 1)
this.cards.splice(cardIndex, 1)
}
this.remaining = this.cards.length
return card
} else {
throw new Error("No Cards remaining")
}
}
}
Showing off cards
We now have a deck that we can do what we want with. If I want to show off every card now I can use:
<style>
#cards{
display: grid;
grid-template-columns: repeat(13, 1fr);
grid-template-rows: repeat(4, 1fr);
grid-column-gap: 2px;
grid-row-gap: 2px;
font-size:4rem;
}
</style>
<div id="cards"></div>
<script>
let a = new Deck()
for (const card of a.cards){
document.getElementById("cards").innerHTML += card.html
}
</script>
Which looks like this (and because it’s HTML we can do an interactive demo):
Black Jack
Now because we have a re-usable asset we can start creating card games while focusing on the game logic. For a game like black jack the logic can now be done in less than 70 lines:
The code (collapsed for easy reading)
let dealerCards = []
let playerCards = []
let deck = new Deck()
function getHandValues(hand){
let totalNumber = 0
let totalValue = 0
for (const card of hand){
totalNumber += card.number
totalValue += card.value
}
return [totalNumber, totalValue]
}
function checkBust(hand){
let [total, _] = getHandValues(hand)
if (total > 21){
return true
}
return false
}
function hit(hand){
hand.push(deck.getRandomCard(true))
}
function startGame(){
let acesHigh = false
deck = new Deck(acesHigh)
dealerCards = [deck.getRandomCard(true), deck.getRandomCard(true)]
playerCards = [deck.getRandomCard(true), deck.getRandomCard(true)]
}
function endGame(){
let t = getHandValues(playerCards)
playerTotalNumber = t[0]
playerTotalValue = t[1]
t = getHandValues(dealerCards)
dealerTotalNumber = t[0]
dealerTotalValue = t[1]
if (checkBust(playerCards) || playerTotalNumber < dealerTotalNumber){
console.log("You Lose")
} else if (playerTotalNumber == dealerTotalNumber){ // Tie
if (playerTotalValue > dealerTotalValue){
console.log("You win")
} else{
console.log("You Lose")
}
} else{
// dealer Tries to win
while (dealerTotalNumber <= playerTotalNumber){
hit(dealerCards)
t = getHandValues(dealerCards)
dealerTotalNumber = t[0]
dealerTotalValue = t[1]
}
if (checkBust(dealerCards)){
console.log("You win")
} else{
if (dealerTotalValue >= playerTotalValue ){
console.log("You Lose")
} else {
console.log("You win")
}
}
}
}
Now we have the logic, we can replace the console.log()
’s with a div, and insert the text into it to make our UI:
<style>
#gameDemo{
background: #1bbc1b7a;
font-size: 1.4rem;
padding: 1rem;
border-radius: 1.1rem;
}
#playerCards,
#dealerCards,
#gameOutcome{
background:white;
padding: .4rem;
border-radius:1.1rem;
min-height:40px;
max-width:fit-content;
margin-bottom:.5rem;
min-width:40%;
}
#gameDemo button{
border: 1px solid black;
border-radius: .7rem;
padding:.6rem;
background:white;
font-size:1.2rem;
font-weight:400;
height:min-content;
}
#gameDemo .card2{
font-size:5rem;
}
</style>
<div id="gameDemo">
<div style="">
<span>Player:
<div id="playerCards"></div>
</span>
</div>
<div style="">
<span>Dealer:
<div id="dealerCards"></div>
</span>
</div>
<div style="display:flex; gap:2rem;">
<button type="button" onclick="startGame()">Start Game</button>
<button type="button" onclick="endGame()">End Game</button>
<button type="button" onclick="hit(playerCards)">+🂠</button>
</div>
Outcome
<div id="gameOutcome"></div>
<div>
We can create a helper function called displayCards()
to show each hand:
function displayCards(){
document.getElementById("playerCards").innerHTML = ""
for (const card of playerCards){
document.getElementById("playerCards").innerHTML += card.html
}
document.getElementById("playerCards").innerHTML += `total: ${getHandValues(playerCards)[0]}`
document.getElementById("dealerCards").innerHTML = ""
for (const card of dealerCards){
document.getElementById("dealerCards").innerHTML += card.html
}
document.getElementById("dealerCards").innerHTML += `total: ${getHandValues(dealerCards)[0]}`
}
We then just need to update the div
with an ID of gameOutcome
for the end of the game and we get something that looks like this:
Conclusion
A fully functional HTML-based game with nothing but knowledge of how to do web development to get it to work. Now I’ll admit this isn’t the prettiest game ever, but because it’s made with simple web technologies it’s easy to integrate with whatever design you want. It’s also easy to embed in whatever website you want. All without having to learn a proper game engine.