luke.b//blog

Link Scrabble

~ #project

When the pandemic started I thought it would be a fun idea to make a game that I could play with my family. I wanted to make it interesting with some technical constraints, with one crucial one: the entire game state can be represented in a URL.

With game state in a URL, players can easily cheat so the assumption is that players would want to play fairly.

If you’re already curious, you can find the game here and if you fancy joining a game I already started playing, click here: link to a game I started

A screenshot of Link Scrabble

How it works

Initially, the game state is an array of letter tile objects which each have a character, e.g. “E”, and a position. The position has a type (bag/hand/board) and extra information about the position. For example, if the type is hand, the name of the player is specified. If the type is board, the x/y coordinates of the tile are specified. Technically it is possible for the state to be invalid, e.g. with tiles sitting on the same coordinates, but it is already assumed the players will play fairly, so it might as well be assumed they wouldn’t edit the game state to make it invalid.

Note that this means no data is stored about the players scores, or exactly how each letter tile was played in the past. This required a bit of creativity in calculating player scores, which will be explained later.

Playing a turn

To play, players will start off by picking letters from the bag, which are randomly chosen by the game. When a letter is picked, it moves into the players hand.

{
  c: 'E',
  p: { t: 'b' }
}
 ->
{
  c: 'E',
  p: {
    t: 'h',
    p: 'luke'
  }
}

The next step is to place the letters on the board to achieve the highest possible score. When this happens, letters are given coordinates for their position on the board.

{
  c: 'E',
  p: {
    t: 'bo',
    i: 10,
    j: 8,
    p: 'luke',
  }
}
...

I won’t explain the rules of Scrabble here. Link Scrabble scoring is similar but works slightly differently. Each player’s score can be calculated at any point in the game by looking at the letters they have placed on the board. This is a bit unnerving to anyone who is familiar with Scrabble. In classic Scrabble, each player’s score is increased at the point they play a word but because the game state in Link Scrabble doesn’t include a history of moves made, an “instantaneous” scoring system was required.

The way it works is that for a given word, the score for each player is calculated. It’s important to consider multiple players for a word because it might have been constructed by multiple players. For example, perhaps a player put down the word “bean” where the “n” was already placed in another word, say “banana”.

  b
  e
  a
banana

In this case, the score for the word “bean” counts towards the score of the player that placed those first three letters, “B”, “E” and “A”.

However, if another player were to add the word “broad” to the beginning of the word “bean”, making it “broadbean” (which is a word, I just checked with Instascrabble), then they would effectively steal that word from the first player because the score of the letters in “broad” gives them a greater score. That player then wins the score for the entire word “broadbean”.

This means a player’s score might actually go down at some point, and it greatly encourages creating new words with existing words on the board.

Another interesting aspect is that when a player plays a short word – for example they might create a two-letter word when adding a long horizontal word to another – that word could be stolen from them easily.

banana
     turnip

In the above example, the second player adds the word “turnip” horizontally below the last “A” in “banana”. The word “at” is actually awarded to the first player because both players scored equally from that word, and the scoring calculation gives priority to the first player in the list.

However, another player can easily extend the word “at” in the example above and win it from the first player. Although, in this case, the point difference following the steal is not that great.

The first example shows a much more lucrative way to win, especially if the extension falls on a word multiplier. In that case, the player wins a large gain and the opponent receives a small loss as a kind of bonus.

Ending a turn

Once the player has laid down some letters, they are free to pick more letters from the bag before handing their turn over to the next player.

This is where the game URL comes in. When a player makes a decision and mutates the game state, the URL updates to reflect the new state. When the player shares this URL with other people, they can choose to take their next turn or another new player can join the game. Players can take turns at any point and can also join the game at any point so it is up to the players to determine whether this has been done correctly/fairly.

Joining a game is as simple as providing a name (which should be a unique string, again this is determined to be valid by the player, and this could certainly be improved by checking existing names) and picking some letters from the bag. Those letters will then be assigned to that player and as soon as they play those letters, they will be given a score.

One of the fortuitous aspects of having such a URL-based system is that nefarious players (who might not play fairly) are free to mutate the URL in any way. But once that link is mutated, that player must send it back and the other players can make a decision about whether to trust that player. At the moment there aren’t any cheat-prevention mechanisms in the game, but it would be quite simple to make sure the number of letters in the game are unchanged.

To prevent cheating outright, a running history of game state could be stored in the user’s browser and compared to the current state to check whether, for example, all letters are still owned by the same players as before. The UI could simply disable the UI if it detects cheating, although it wouldn’t be able to determine who cheated because anyone can mutate the URL at any time.

Having played a few games of Link Scrabble myself, I found that any messaging system where URLs can easily be sent to other people were a viable transport layer for Link Scrabble.

One aspect of the system that I want to cover here is how it was even possible to store game state in a URL that was short enough to send to other people.

Game state storage

Storing the game state in a URL required storing all information about the game in the fewest possible data. The options I had for doing this were limited to:

  • creating a binary format encoding letter positions
  • or implementing some kind of compression algorithm to reduce the size of stored data.

I decided purely out of curiosity to implement a compression algorithm, inspired by the widely used GZIP format. GZIP uses an LZ77 algorithm which essentially replaces occurrences of data with references to previously found identical sequences.

I decided to only use this aspect to keep things simple, and I found that due to the already quite small size of the data in question, this method was sufficient.

I think I’d like to save the compression deep-dive for another day. I’ve recently been learning Rust and I think a perfect project would be implementing an efficient compression algorithm in Rust.

But for the purposes of storing game state in a URL, the algorithm I wrote does the following when compressing game state:

  • take the JSON blob representing the game state
  • encode it using base64 encoding
  • apply LZ77-inspired algorithm to encode occurrences of previously found sequences with a reference to their first occurrence

This data is then set as the query parameter s on the webpage, and a self-referential link is presented to the user to copy.

Conclusion

There are a lot of possibilities when a game state is compressed into a unique token describing the current state and I think it would be a fun project to create a game engine and game building studio dedicated to these kinds of games. I’m hesitant to brand this a kind of “serverless” game, but it’s true that no state is stored server-side unless a user decides to upload it to one. It also decentralises the authority over controlling the game to the players themselves, which is fun.

Anyway, thanks for reading!