Battleship with Micro:bit
In chapter 10 of our book, Networking with the Micro:bit, we ask learners to develop a simplified version of a famous board game Battleship.
To program Battleship with your micro:bits, you will use networking knowledge. The game requires unicast and bidirectional communication, which we cover in Chapters 5 and 6 in our book. In this blog post, I show a solution where unicast communication is achieved using a unique radio group number between the two players. Chapters 5 and 6 guide learners to program a better solution for unicast.
With this basic solution, programming Battleship enables learners to practice:
Display and its coordinates
Variables and random numbers
The Battleship is a two-person game.
To play the Battleship with micro:bits, we need to program the following into two micro:bits:
Using the micro:bit display as a Battleship board.
Firing a shot to a battleship on the opponent’s micro:bit
Receiving a shot and announcing a hit or a miss
First, we start with setting up the game and getting our display ready for the game.
Setting up the game
Since a micro:bit has a 5×5 display, this does not allow for many ships or big ones. So, a fleet will be 5 ships, each with a size of 1. To know if a shot was a hit or a miss, we need to reserve the top row to display hits and misses. If the opponent’s micro:bit says “Hit”, our micro:bit will light the leftmost LED on the top row. If it was, unfortunately, a miss, our micro:bit will light the rightmost LED.
To create our battle area, our program will randomly place 5 points in the battle area, which is a 4 x 5 matrix. The corresponding LEDs will be lit up to represent the ship on our display. Note that when LED coordinates are given as “(x,y)”, x is the column number and y is the row number, and the numbers start at zero. For more information, see https://www.microbit.co.uk/device/screen
In our set-up, we will create the necessary variables to represent our battle area “On start”. For this we will have the following in our program:
The battle area is called my_battle_area. This is an array of size 25 initialized to all 0s. We will treat the first 5 elements of this array as row 1 of our display, the second set of 5 elements as row 2 and so on.
number_of_ships is the variable keeping the number of ships to be placed. This is initially 5.
A “while” loop randomly flips 5 elements in the array from 0 to 1 (these represent our ships on the display).
The while loop continues as long as there are ships to be placed. Inside the loop, x and y coordinates (position_x and position_y) are picked randomly. For position_x, a number between 0 and 4 is picked. For position_y, we cannot use the top row (remember, this row indicates hits and misses). So position_y needs to be between 1 and 4. If the selected position in the matrix is empty, then that array element is flipped to 1 and the corresponding LED on the display is lit up.
Note how the new ship position is calculated: position_y advances the index of the array by multiples of 5 to select the correct row number. And the position_x advances the index to the correct column number. So, for (position_x=3,position_y=4), my_battle_area is set to 1.
Once the new ship is placed, we must reduce number_of_ships by 1. Otherwise, our program would enter an infinite loop.
The program sets the radio group number (to 123 in this example). This number needs to be unique for two micro:bits to do unicast between them. Or, use the method described in Chapter 5 of our book.
We will need other variables in the rest of our program. So, we initialize them also “On Start”:
A variable for the number of hits received: hit_count
x and y coordinate variables for our shot: fire_x and fire_y
Firing a shot
The position of our shot is set to (0,1) “On Start” by setting fire_x=0 and fire_y=1.
Pressing button A increments fire_x and button B increments fire_y. Pressing both buttons together fires a shot.
In the program above, fire_x is incremented by 1 modulo 5. So, as the button A gets pressed, fire_x follows the following sequence: 0, 1, 2, 3, 4, 0, 1, 2, 3, 4…
fire_y, which is initialized to 1, is incremented similarly. Pressing button B adds 1 to its value modulo 5. Whenever its value hits 0, it is set back to 1. This way, fire_y follows this sequence: 1, 2, 3, 4, 1, 2, 3, 4…
When both buttons are pressed, the radio sends the index of the battle area array (calculated as explained before). Then, the fire_x and fire_y variables are reset.
Receiving a shot
During this game, the micro:bit radio can either receive a number or a string. The number is the index of the array the opponent fired a shot at. The string is the shot result from the opponent.
If a number is received, we need to check whether there is a ship in that position.
If there is a ship at that position, we need to sink our ship (by setting the array element at that position to 0). We also turn off the LED at that position. To find the coordinates of the LED, we need to do the reverse calculation from array’s index to the corresponding x,y. To do that, we divide the received number by 5 to find the row number y. Then subtracting 5*y from the received number gives us the column number x. We unplot the LED at (x,y). Finally, we send the result to our opponent: “Hit”.
If there is no ship in that area, we send “Miss”.
Receiving a shot result
We receive the shot result as a string. In that case, we check whether it is a “Hit” or “Miss”. If it is a “Hit”, we turn on the LED at (0,0). We increment the hit_count. If it reached 5, we won!
Otherwise, it is a “Miss” and we turn on the LED at (4,0).
Here, we also do some housekeeping and turn off the LEDs that may be showing the previous turn’s result.
Putting it all together
Combine these three pieces and you have a Battleship game on micro:bit.
In this blog, I tried to keep things simple. But, several improvements are possible. For instance, the selection of fire_x and fire_y can be more intuitive by showing on the micro:bit’s screen which position has been selected. Many more such improvements can be imagined.