Checkers is a fairly simple game played on an 8×8 game board. The board’s squares are typically alternating colours.
Each player then places 12 pieces on the board in fixed squares spaced evenly one square apart from each other. So one Player controls black pieces another player controls white pieces.
Lets us see what are some basic rules of the game :-
- The opponent with the darker pieces moves first.
- Pieces may only move one diagonal space forward (towards their opponent’s pieces) at the beginning of the game.
- Pieces must stay on the dark squares.
- If there is, you might be able to jump and capture that player’s piece.
Coping with Data Structure Constraints
WebAssembly doesn’t have a heap in the traditional sense. There’s no concept of a new operator. In fact, you don’t allocate memory at the object level because there are no objects. Instead, WebAssembly has linear memory. This is a contiguous block of bytes that can be declared internally within the module, exported out of a module, or imported from the host.
checkers.wat file in your project directory and define an empty module:
(module (memory $mem 1) )
The 1 in the memory declaration indicates that the memory named mem must have at least one 64KB page of memory allocated to it.
Managing Checkers Board State
As mentioned, a
checker board is an 8×8 grid. So to store this first thing that comes to our mind is a 2-D Array but in WebAssembly we don’t have this privilege. In Rust, that might look something like this:
let mut checkerboard: [[GamePiece; 8]; 8];
The solution is to linearized the two-dimensional array. The trick to linearization is figuring out the math behind
converting an (x,y) coordinate pair into a memory offset.
8 is the number of squares in a row. This would be fine if you were just linearizing a two-dimensional array into a one-dimensional space indexed as an array, but WebAssembly’s memory isn’t indexed like an array. It’s indexed by byte. This means that if you’re going to store a 32-bit integer (4 bytes) in each spot. So the equation for Offset is :
offset = (x + y*8) * 4
The foundation of the entire checkers game will be a function that determines the byte offset for a given X and Y coordinate.
(func $indexForPosition (param $coor_x i32) (param $coor_y i32) (result i32) (i32.add (i32.mul (i32.const 8) (get_local $coor_y) ) (get_local $coor_x) ) ) (func $offsetForPosition (param $coor_x i32) (param $coor_y i32) (result i32) (i32.mul (call $indexForPosition (get_local $coor_x) (get_local $coor_y)) (i32.const 4) ) )
The math performed to calculate the offset for given Position is:
= (1 + 2 * 8) * 4
Implementing Checkers Game Rules
From a rules standpoint, checkers is not all that complex of a game. All 12 of your pieces start out with the same movement capabilities, and the only special pieces ever on the board are those that have been crowned.
This checkers game implements some basic rules like crowning a piece and moving.
(func $shouldCrown (param $pieceY i32) (param $piece i32) (result i32) (i32.or (i32.and (i32.eq (get_local $pieceY) (i32.const 0) ) (call $isBlack (get_local $piece)) ) (i32.and (i32.eq (get_local $pieceY) (i32.const 7) ) (call $isWhite (get_local $piece)) ) ) ) (func $crownPiece (param $coor_x i32) (param $coor_y i32) (local $piece i32) (set_local $piece (call $getPiece (get_local $coor_x)(get_local $coor_y))) (call $setPiece (get_local $coor_x) (get_local $coor_y) (call $withCrown (get_local $piece))) (call $notify_piececrowned (get_local $coor_x)(get_local $coor_y)) ) (func $distance (param $coor_x i32)(param $coor_y i32)(result i32) (i32.sub (get_local $coor_x) (get_local $coor_y)) )
The shouldCrown function determines if a piece should be crowned. The crownPiece function is to add a crown to a piece and then store it in the gameboard. The distance function is just a simple subtraction.
Whose turn is next?
While playing the game we need to keep track of who’s turn is next. Let us define two different values to indicate piece color: 1 for black and 2 for white. In other programming languages, we can define a global variable
currentTurn. Let see how to create a global variable in WebAssembly :
(memory $mem 1) (global $currentTurn (mut i32) (i32.const 0))
we can create global variables that are either private to the module or can be shared with the host. We should initialize $currentTurn variable with a value. The currentTurn global variable will be set to 1 when it is black’s turn and 2 when it is white’s turn.
(func $getTurnOwner (result i32) (get_global $currentTurn) ) (func $toggleTurnOwner (if (i32.eq (call $getTurnOwner) (i32.const 1)) (then (call $setTurnOwner (i32.const 2))) (else (call $setTurnOwner (i32.const 1))) ) ) (func $setTurnOwner (param $piece i32) (set_global $currentTurn (get_local $piece)) ) (func $isPlayersTurn (param $player i32) (result i32) (i32.gt_s (i32.and (get_local $player) (call $getTurnOwner)) (i32.const 0) ) )
- getTurnOwner: Gets the current turn owner white or black.
- toggleTurnOwner: At the end of a turn, switch turn owner to the other player.
- setTurnOwner: Sets the turn owner.
- isPlayersTurn: Determine if it’s a player’s turn.
In conclusion, I hope you got an idea to build a basic Checkers game using WebAssembly.
Thanks for reading !!
If you want to read more content like this? Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.