Tic-Tac-Toe in 98% pure AL

In my last video, I asked if viewers wanted to see me create a Tic-Tac-Toe game in Microsoft Dynamics 365 Business Central. Lots of viewers wanted that.

Tic-Tac-Toe in AL is nothing new, Vjeko did it back in 2017 as a demo of the Javascript control addin, so for this to be a challenge, it should be done in pure AL without using Javascript for UI.

And since the channel has passed a thousand subscribers I thought it would be cool to showcase tricks from others videos.

https://youtu.be/9Bn2dwtpiA4


In this video, Erik celebrates his channel reaching 1,000 subscribers by building a Tic-Tac-Toe game in Business Central using almost entirely AL code. He sets two ground rules: no cheating by doing it all in JavaScript (which has been done before), and incorporating as many tricks from previous videos on the channel as possible. The result is a fully playable game with only about 2% JavaScript — just enough to handle mouse click detection and a simple timer.

The Design Challenge: Pure AL

Tic-Tac-Toe has been implemented in AL before, but Erik wanted to make this version special. He laid out two criteria:

  1. No cheating — The game logic must be written in pure AL, not offloaded to JavaScript.
  2. Use as many channel tricks as possible — Base64 encoding, temporary tables, record references, dictionaries, the JavaScript workbench, and more all make an appearance.

The Visual Layout: A 3×3 Grid in AL

The first challenge was rendering the game board. Erik created a page with three groups (one for each row), each containing a grid layout with three fields. This gives us the classic 3×3 Tic-Tac-Toe board.

The fields are simply named B1 through B9, representing the nine positions on the board. Each field displays a bitmap image — either blank, an X, or an O.

The TTT Board Table

To hold the bitmap data for each cell, Erik created a table called TTT Board. Since you can’t create blob variables directly in AL, you need a table with blob fields. The table uses the TableType = Temporary property (a trick from a previous video), ensuring the game board is never persisted to the database:

The table has nine blob fields (one per grid position) and a primary key field placed at field number 10. This is a deliberate design choice — the field numbers (1–9) match the grid positions, making it trivial to use record references to target the correct field.

The 2% JavaScript: Click Detection and Timer

This is where the “98% pure AL” qualifier comes in. Two things simply can’t be done in AL alone: detecting mouse clicks on bitmap images and implementing a timer.

Click Detection

Erik reuses the technique from his barcode reader and point-of-sales videos — escaping out of the iframe and attaching an event listener to the parent window. Instead of listening for keydown events, this time it’s mousedown:

When a click occurs, the JavaScript inspects the DOM path (the array of parent elements from the click target up to the document root). At some level, there’s a div with a controlname attribute — this is a trick discovered in the JavaScript workbench video. The script loops through the layers, and when it finds a controlname, it fires an AL event with that name (e.g., “B1”, “B5”, etc.).

The entire click detection mechanism is roughly 10 lines of JavaScript.

Timer

The timer provides the game feel — when you click, there’s a half-second delay before the computer makes its move, simulating “thinking time.” This uses window.setInterval and clearInterval in JavaScript. AL used to have timer capabilities in the classic client (the old “ping pong” control), but in the modern client, a small JavaScript timer is the cleanest approach.

Initializing the Game

All game logic lives in a codeunit called the Game Engine. The NewGameAndClearBoard function initializes the board:

  • Loops through positions 1–9
  • Sets each position to Blank in a global array of Enum "Position Type" (which has three values: Blank, X, and O)
  • Calls SetBitmapOnPosition to render blank images in all cells
  • Sets the game state: game is running, human plays X, computer plays O

One important note about TableType = Temporary: it’s per-variable, not per-session. The temporary record must be passed around to any function that needs to work with it.

Rendering Bitmaps with Base64

The SetBitmapOnPosition function is where several tricks converge. Erik needs three image resources — a blank cell, an X, and an O. AL doesn’t have a traditional resource system, but using the Base64 technique from a previous video, the images are stored as Base64-encoded strings directly in the code.

The rendering process works like this:

  1. Get a RecordRef from the board record
  2. Create an OutStream using a TempBlob (the template pattern from another video)
  3. Use the Base64 Convert codeunit to decode the appropriate Base64 string into the stream
  4. Use a ToRecordRef function to write the blob into the correct field number
  5. Since field numbers (1–9) match grid positions, the field number is the position number
  6. Call SetTable on the RecordRef to apply changes back to the record

This approach lets you place any of the three bitmaps into any of the nine positions on demand.

Game Flow: From Click to Computer Move

When the user clicks somewhere on the page:

  1. The JavaScript detects the click and fires the ControlClicked event with the control name (e.g., “B5”)
  2. The AL code calls Engine.UserClicked with the board and control name
  3. If the click is on one of the nine valid cells and the cell is blank, the human’s X is placed
  4. The winning condition is checked
  5. A timer is started for 500 milliseconds (the “thinking” delay)
  6. When the timer fires, it checks if there’s room left on the board
  7. If no room is left, you get “a cheesy movie reference” (likely a draw/tie message)
  8. Otherwise, Engine.PlayComputer is called

The lastClick variable is set to zero during the delay to prevent the user from clicking twice before the computer has moved.

Checking for a Winner: Dictionaries in Action

The CheckWinning function uses a Dictionary of Integer, Integer — a technique from a previous video about using dictionaries instead of temporary tables for simple lookups.

For each grid position, there’s a set of winning pairs. For example:

  • Position 1 (top-left corner): pairs with (2,3), (4,7), and (5,9) — three possible winning lines
  • Position 2 (top-middle): pairs with (1,3) and (5,8) — two possible winning lines
  • Position 5 (center): has the most combinations

Corner positions have three winning options, edge positions have two, and the center has the most. The function GetOptionsForGridPosition populates the dictionary with these pairs, and then the code loops through each key-value pair to see if both the key position and value position match the player type being checked.

The Computer’s AI: A Scoring System

Erik built the computer’s AI with his kids, opting for a custom scoring approach rather than a perfect algorithm from Wikipedia (where every game ends in a tie — “what’s the fun in that?”).

The computer evaluates every blank position and assigns a score based on several factors:

  1. Position value: Odd positions (1, 3, 5, 7, 9 — corners and center) start with a higher base score than even positions (edges), since they participate in more winning combinations
  2. Offensive potential: If the corresponding cells in a winning line are blank or already occupied by the computer, add to the score
  3. Defensive awareness: If the human player occupies corresponding cells, that also increases the position’s value (blocking is important)
  4. Urgent defense: If the human has two of three in a line, that position scores 10 (must block)
  5. Winning move: If the computer has two of three in a line, that position scores 100 (take the win)

All scores are stored in another dictionary called moves, which maps grid positions to their computed scores. The computer then finds the position with the highest score. In case of a tie, a random selection is made among the tied positions, adding some variety to the gameplay.

A debug string is also generated showing the scores for each position, useful during development.

Checking for Room Left

The RoomLeft function simply checks whether any blank positions remain on the board. If not, and no one has won, the game ends in a draw.

The Game in Action

When deployed and running in Business Central, the game displays a clean 3×3 grid. Clicking a cell places your X, the computer “thinks” for half a second, then places its O. The AI is good enough to block obvious wins and find winning opportunities, though Erik’s daughter did find ways to beat it — which led to iterative improvements of the scoring system during the development session.

The page works well in the web client and on tablets. Landscape mode on phones almost works but isn’t quite there — as Erik notes, “this is not a mobile game.”

Project Configuration

The project targets Business Central runtime 6.0 (platform 17.0):

{
  "id": "9bcb9859-c9ae-40af-aa92-f1488b96c1f6",
  "name": "Tic-Tac-Toe",
  "publisher": "Erik Hougaard",
  "version": "1.0.0.0",
  "platform": "17.0.0.0",
  "application": "17.0.0.0",
  "idRanges": [
    {
      "from": 54500,
      "to": 54549
    }
  ],
  "showMyCode": true,
  "features": ["TranslationFile"],
  "runtime": "6.0"
}

Erik also has some entertaining analyzer rule suppressions in the alrules.json:

{
    "name": "TTT",
    "description": "TTT Rules",
    "rules": [
        {
            "id": "AA0215",
            "action": "Hidden",
            "justification": "This is OK -> Don't need MS to dictate filenames."
        },
        {
            "id": "AA0072",
            "action": "Hidden",
            "justification": "This is OK -> Don't need MS to dictate variable names."
        },
        {
            "id": "AW0009",
            "action": "Hidden",
            "justification": "We're using a blob - try to stop us!"
        }
    ]
}

Tricks from the Channel Used

This project is a celebration of techniques covered across multiple videos:

  • Temporary table type — The board table never touches the database
  • Base64 encoding — Storing image resources as Base64 strings in AL code
  • TempBlob / template pattern — Converting Base64 to blob streams
  • RecordRef — Dynamic field access using field numbers that match grid positions
  • Dictionaries — Storing winning combinations and move scores
  • JavaScript iframe escape — Attaching event listeners to the parent window
  • Control name discovery — Finding AL control names in the DOM from the JavaScript workbench video
  • Control add-in with dynamic HTML — The starting template for the JavaScript portion

Summary

This Tic-Tac-Toe implementation demonstrates that you can build surprisingly interactive experiences in Business Central with minimal JavaScript. The only non-AL code handles two things the platform doesn’t natively support: detecting clicks on image controls and implementing a timer for the computer’s response delay. Everything else — the game board rendering, bitmap management, game state tracking, win detection, and the computer’s AI — is pure AL. The source code is available on GitHub for anyone who wants to extend it, perhaps even into a multiplayer version.