# CLUE Rock, Paper, Scissors Game using Bluetooth

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/093/319/medium800/bluefruit___ble_clue-advanced-rpsgamil-introscreen.jpg?1595099919)

This project features three versions of a rock, paper, scissors game in CircuitPython. The first is a very simple, single-player text game showing the basic logic of the game. The second is a two-player game using Bluetooth Low Energy advertising to exchange the players' choices. The third is a multi-player game building on the foundation of the second version, adding [displayio](https://learn.adafruit.com/circuitpython-display-support-using-displayio "Adafruit Learn: CircuitPython Display Support Using displayio") graphics and sound. It has been tested with six players and potentially can work with far more.

The programs are written in CircuitPython for version 5.3.0 or later. The code runs on the CLUE and the Circuit Playground Bluefruit (CPB) with TFT (LCD) Gizmo screen. The third version of the game can also be used on just a CPB with the NeoPixels for output.

Thank-you to Matilda for recording the sound samples for the announcer in the third version of the game and the [TMS5220 chip](https://en.wikipedia.org/wiki/Texas_Instruments_LPC_Speech_Chips "Wikipedia: Texas Instruments LPC Speech Chips") for inspiring the post-processing on these.

## Parts

### CLUE
### Adafruit CLUE - nRF52840 Express with Bluetooth® LE

[Adafruit CLUE - nRF52840 Express with Bluetooth® LE](https://www.adafruit.com/product/4500)
Do you feel like you just don't have a CLUE? Well, we can help with that - get a CLUE here at Adafruit by picking up this sensor-packed development board. We wanted to build some projects that have a small screen and a lot of sensors. To make it compatible with existing projects, we made...

Out of Stock
[Buy Now](https://www.adafruit.com/product/4500)
[Related Guides to the Product](https://learn.adafruit.com/products/4500/guides)
![Animated GIF showing CLUE board  displaying data from the many on-board sensors.](https://cdn-shop.adafruit.com/product-videos/640x480/4500-04.jpg)

### Circuit Playground Bluefruit with TFT Gizmo
### Circuit Playground Bluefruit - Bluetooth® Low Energy

[Circuit Playground Bluefruit - Bluetooth® Low Energy](https://www.adafruit.com/product/4333)
 **Circuit Playground Bluefruit** is our third board in the Circuit Playground series, another step towards a perfect introduction to electronics and programming. We've taken the popular Circuit Playground Express and made it even better! Now the main chip is an nRF52840...

Out of Stock
[Buy Now](https://www.adafruit.com/product/4333)
[Related Guides to the Product](https://learn.adafruit.com/products/4333/guides)
![shot of a Black woman's neon-green manicured hand holding up a Circuit Playground Bluefruit glowing rainbow LEDs.](https://cdn-shop.adafruit.com/640x480/4333-11.jpg)

### Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier

[Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier](https://www.adafruit.com/product/4367)
Extend and expand your Circuit Playground projects with a bolt on TFT Gizmo that lets you add a lovely color display in a sturdy and reliable fashion. This PCB looks just like a round TFT breakout but has permanently affixed M3 standoffs that act as mechanical and electrical...

In Stock
[Buy Now](https://www.adafruit.com/product/4367)
[Related Guides to the Product](https://learn.adafruit.com/products/4367/guides)
![Hand pressing buttons on circuit playground, then turning over to show TFT gizmo display an image of a friendly robot or snake](https://cdn-shop.adafruit.com/product-videos/640x480/4367-05.jpg)

### USB cable - USB A to Micro-B

[USB cable - USB A to Micro-B](https://www.adafruit.com/product/592)
This here is your standard A to micro-B USB cable, for USB 1.1 or 2.0. Perfect for connecting a PC to your Metro, Feather, Raspberry Pi or other dev-board or microcontroller

Approximately 3 feet / 1 meter long

In Stock
[Buy Now](https://www.adafruit.com/product/592)
[Related Guides to the Product](https://learn.adafruit.com/products/592/guides)
![USB cable - USB A to Micro-B - 3 foot long](https://cdn-shop.adafruit.com/640x480/592-01.jpg)

# CLUE Rock, Paper, Scissors Game using Bluetooth

## Design

![](https://cdn-learn.adafruit.com/assets/assets/000/093/302/medium800/bluefruit___ble_microbit-network-book-rock-paper-scissors-threecharacterrules-hires-43.jpg?1595019822 The rules of rock, paper, scissors from Nominet's Networking With The micro:bit, copyright Nominet.)

## Rules

To play the game:

- Each of the two players make an independent decision and reveal that to each other _simultaneously_.
- The rules to determine the winner are shown in the diagram above. If the two players make the same choice then the game is a draw (tie).

### More than Two Players

The game is normally a two-player game but can be extended to multiple players by simply using the player's choice against each opponent. The diagram above can also serve to demonstrate how a three player game works, with each arrow representing a game:

- green player (scissors)
  - win vs blue,
  - lose vs red;

- blue player (paper),
  - win vs red,
  - lose vs green;

- red player (rock),
  - win vs green,
  - lose vs blue.

## Flow Diagram

The flowchart below shows a high-level view of a multi-round game. This broadly matches the Advanced game, although the final implementation deviates from this playing games indefinitely.

![](https://cdn-learn.adafruit.com/assets/assets/000/093/311/medium800/bluefruit___ble_general-rpsgame-main-flowchart-v3.png?1595026870 Simplified flow diagram for the Advanced rock, player, scissors game.)

The next pages discuss some aspects of the design of the different versions of the game. These include the user interface (UI), how the TFT Gizmo on a Circuit Playground Bluefruit can be detected, how the players' choices are exchanged between multiple devices and how the scores are presented at the end of each game.

# CLUE Rock, Paper, Scissors Game using Bluetooth

## User Interface

![](https://cdn-learn.adafruit.com/assets/assets/000/093/301/medium800thumb/bluefruit___ble_cpb-advanced-game-neopixel-colours.jpg?1595019635 Advanced Game on Circuit Playground Bluefruit using three NeoPixels to display the player's selection. The NeoPixels appear less colourful due to over-exposure in the photograph.)

## User Interface

### Choice selection

The game needs:

- a method for the player to select a choice and
- a method to declare that choice is the final choice and exchange it with other player(s).

A three button interface could combine those two actions. The CLUE has two buttons and three touch-capable pads. The Circuit Playground Bluefruit (CPB) has two buttons, a switch and seven touch-capable pads.

A player needs to keep their choice secret from the other player(s) until they are exchanged. While it is easy to prevent others from spying on the choice directly there is the possibility of exposing this value if the opponent(s) can see an indirect indication of the choice. This could be from observing which touch pad a finger presses or by counting the number of presses on a button from finger movements or hearing button clicks. This is a form of [side-channel attack.](https://en.wikipedia.org/wiki/Side-channel_attack "Wikipedia: side-channel\_attack")

Avoiding the use of the touch pads for a one pad-per-choice selection helps to reduce the risk of overlooking. This also allows the boards to be used with a case that covers the touch pads, keeps the button actions identical between the CLUE and the CPB and maintains a consistent button layout.

Using a single button to cycle through the selections still needs some caution to avoid revealing the choice. It must

- not reset the player's choice to a fixed value per round
- and must not leave the player's choice on their last selected value.

The Simple and Advanced versions of the game both use the left button to select the choice and the right button to send it to the other players. The Advanced version deals with the issue of "button press counting" by randomising the player's choice each round.

#### Display on NeoPixels

The Advanced game can be played on a display-less Circuit Playground Bluefruit. The NeoPixels are used to show output to the player. These can be very bright and reflected light might be visible to others indicating the player's choice. The risk of this is reduced by using very low brightness values for the selected value indicated by the NeoPixels.

The NeoPixel position in addition to the colour is also used to represent the player's choice. It's tempting to use the default order of the NeoPixels but this would result in the third NeoPixel being underneath/near the player's left finger/thumb. The NeoPixels at the top of the Circuit Playground Bluefruit are used as they are clear of any fingers using buttons. The clockwise representation is:

- rock, red on NeoPixel 0;
- paper, purple on NeoPixel 9;
- scissors, sapphire blue on NeoPixel 8.

These can be seen in the animated image at the top of screen which cycles through the three.

The game number and round number are shown at the start of each round using the NeoPixels. They briefly light up in green with the number of NeoPixels illuminated indicating the game number. One NeoPixel then flashes white, the position indicates the round number.

The scores for the game are shown for each player in turn by illuminating the NeoPixels gradually in a circle. For 1-10 orange is used. For values larger than 10 a new colour is used after orange has been lit: yellow for 11-20, green for 21-30, blue for 31-40, indigo for 41-50 and violet for 51-60.

### Screens

A game may have different phases. The Advanced game warrants:

1. A title screen.
2. A brief user guide.
3. A player list for the assembling players.
4. The local player's selection for the current round including information about the round number and a summary of the results so far.
5. The score at the end of each game for all players.

#### Screen Transitions

The transition between screens could just be implemented with an immediate change like a [cut](https://en.wikipedia.org/wiki/Cut_(transition) "Wikipedia: Cut (transition)") in a film but there are other options to transition to a subsequent screen and provide a visual cue that something significant has changed or occurred.

The CLUE and TFT Gizmo's screens use [liquid-crystal display (LCD)](https://en.wikipedia.org/wiki/Liquid-crystal_display "Wikipedia: liquid-crystal display") technology. These displays use a backlight for illumination. Fortunately, this backlight has a brightness control and this provides a very efficient way to implement a smooth fade to black and back.

![](https://cdn-learn.adafruit.com/assets/assets/000/093/945/medium800thumb/circuitpython_clue-fadedown3-fadeup1-O3.jpg?1597015351 Video (dithered animated gif) showing fades using backlight. Fade to black in 3 seconds and fade up in 1 second with 20 steps in each. The fade to black might look better with more steps.)

### Sprites

The [displayio](https://circuitpython.readthedocs.io/en/5.3.x/shared-bindings/displayio/ __init__.html "CircuitPython Docs: displayio library") library includes a `TileGrid` object which can be used together with the [adafruit\_imageload](https://circuitpython.readthedocs.io/projects/imageload/en/latest/index.html "CircuitPython Docs: adafruit\_imageload library") library to [load a sprite sheet](https://learn.adafruit.com/circuitpython-display-support-using-displayio/sprite-sheet "Adafruit Learn: Circuitpython Display Support Using displayio: Sprite Sheet") into memory. A sprite sheet is an ordered set of [sprites](https://en.wikipedia.org/wiki/Sprite_(computer_graphics) "Wikipedia: sprite (computer graphics)") contained in a single image file.

The Advanced game makes intentional use of low resolution 16x16 pixel images with a very limited palette for a retro feel. The three sprites representing the rock, paper and scissors yield a 48x16 sprite sheet. These were:

1. Drawn in the basic sprite editor in [MakeCode Arcade.](https://arcade.makecode.com/ "MakeCode Arcade")
2. [Displayed on screen](https://arcade.makecode.com/87887-04584-25598-92317 "MakeCode Arcade: Sprites for RPS") to allow them to be saved from the editor. MakeCode Arcade is unusual in embedding its source code inside a [png](https://en.wikipedia.org/wiki/Portable_Network_Graphics "Wikipedia: Portable Network Graphics") file which is its native file format.
3. Cropped and [converted to an indexed bmp in GIMP](https://learn.adafruit.com/creating-your-first-tilemap-game-with-circuitpython/indexed-bmp-graphics "Adafruit Learn: Creating Your First Tilemap Game with Circuitpython: Indexed BMP Graphics").

The rock is inspired by classic Arcade games and the paper by the [Xerox Star](https://en.wikipedia.org/wiki/Xerox_Star "Wikipedia: Xerox Star") [Document Icon](https://en.wikipedia.org/wiki/Xerox_Star#/media/File:Evolution_of_the_document_icon_shape.jpg "Wikipedia: Xerox Star: Document Icons"). The pictorial form of an [icon](https://en.wikipedia.org/wiki/Icon_(computing) "Wikipedia: icon") is a conventional feature of a [graphical user interface (GUI)](https://en.wikipedia.org/wiki/Graphical_user_interface "Wikipedia: graphical user interface"). It also provides a language-neutral representation for the choice reducing the work for [internationalization (i18n)](https://en.wikipedia.org/wiki/Internationalization_and_localization "Wikipedia: internationalization and localization") of a program or product.

The enlarged sprite sheet is shown below with a blue border.

![](https://cdn-learn.adafruit.com/assets/assets/000/093/313/medium800/bluefruit___ble_rps-sprites-ind4-16x.png?1595029617)

# CLUE Rock, Paper, Scissors Game using Bluetooth

## TFT Gizmo Detection

![](https://cdn-learn.adafruit.com/assets/assets/000/093/482/medium800thumb/circuitpython_tft-gizmo-pulldown-zoomin-1-O3.jpg?1595601916 Circuit Playground TFT Gizmo schematic showing A3 controlling the backlight with a MOSFET transistor. A 10k pull-down resistor is present on A3.)

There are two approaches to supporting the _optional_ [TFT Gizmo](https://learn.adafruit.com/adafruit-tft-gizmo "Adafruit Learn: Adafruit Circuit Playground TFT Gizmo") display on a Circuit Playground Bluefruit (CPB):

1. Provide configuration data to turn on (enable) the display. For a board with an attached display the enable would be manually set in the configuration for that board.
2. Detect the presence of the attached display.

The configuration approach could be implemented with a [configuration file](https://en.wikipedia.org/wiki/Configuration_file "Wikipedia: configuration file") to cleanly separate the per-instance data from the code. Detection of the screen can either be an alternative approach or the two can be combined.

## The Challenge of Detecting the TFT Gizmo

The TFT Gizmo connects to a Circuit Playground Bluefruit (or Circuit Playground Express) using four of the pads for the display interface and one other pad for the backlight control. All of those pads are set as _outputs_ making the interface _unidirectional_&nbsp;as a whole. This is known as [simplex communication](https://en.wikipedia.org/wiki/Simplex_communication "Wikipedia: simplex communication"). Unfortunately, this means there is no method via standard signalling to detect whether the TFT Gizmo is present or not.

The 10k [pull-down resistor](https://en.wikipedia.org/wiki/Pull-up_resistor "Wikipedia: pull-up resistor") in the backlight circuitry shown in the schematic at the top of the page luckily provides a crude way to detect the presence of that resistor. The **A3** pad can momentarily be configured as an _input_ with the processor's internal _weaker_ (higher resistance) pull-up competing with the pull-down resistor. The external pull-down resistor is likely to win and this provides a method of detecting the TFT Gizmo. It can be fooled by other things attached to **A3** but this is good enough to differentiate between nothing and the TFT Gizmo.

A sample CircuitPython function to detect the TFT Gizmo is shown below.

```python
def tftGizmoPresent():
    """Determine if the TFT Gizmo is attached.
       The TFT's Gizmo circuitry for backlight
       features a 10k pull-down resistor.
       This attempts to verify the presence
       of the pull-down to determine
       if TFT Gizmo is present.
       This is likely to get confused if
       anything else is connected to pad A3.
       Only use this on Circuit Playground Express
       or Circuit Playground Bluefruit boards."""
    present = True
    try:
        with digitalio.DigitalInOut(board.A3) as backlight_pin:
            backlight_pin.pull = digitalio.Pull.UP
            present = not backlight_pin.value
    except ValueError:
        ### The Gizmo is already initialised, i.e. showing console output
        pass

    return present
```

This assumes that if **A3** is already in use then the display is already active. Once the TFT Gizmo is used on a CPB (or CPX) it remains active until the power is removed.

# CLUE Rock, Paper, Scissors Game using Bluetooth

## Exchanging Choices

![](https://cdn-learn.adafruit.com/assets/assets/000/093/211/medium800thumb/bluefruit___ble_UoT-Ishikawa-Oku-Lab-RPS-Robot-v2-0.25speed.jpg?1594845219 Ishikawa Oku Laboratory robot version 2 cheating at rock, player, scissors at University of Tokyo. Video runs at 0.25 speed.)

## Cheating

The classic game between human players uses a style of play where both players present their choice at the same time to reduce the possibility of cheating by reacting to the other player's choice. [Human reaction times](https://learn.adafruit.com/circuit-playground-bluefruit-quick-draw-duo/reaction-times "Adafruit Learn: Circuit Playground Bluefruit Quick Draw Duo: Reaction Times") make this a reasonable approach.

If there is not an agreed time to exchange choices then this allows a nefarious player to observe/receive an opponent's choice and then react to it. The video above shows a crafty, custom robot which carefully watches the opponent's hand and rapidly reacts with a winning choice within the human hand's movement time.

The game can be written to avoid immediately revealing the opponent's choice. This would prevent a player waiting and then reacting to that opponent. Unfortunately, a _modified_ game could still reveal this and facilitate cheating. This could be solved with:

1. some method for preventing modification of the code or robustly verifying its integrity;
2. a truly simultaneous exchange;
3. a trusted third party, an arbitrator, collecting and then distributing the choices or wins together;
4. an approach where the choices are exchanged in some form but cannot be read until all are received.

The first option isn't feasible as CircuitPython and the associated hardware don't support this feature and code modification is intentionally trivial. The second option seems tempting but it is difficult to synchronise the devices to exchange data precisely enough. The exchange could only be near simultaneous with more than two devices and even two devices would struggle to achieve this with the busy radio spectrum used by Bluetooth.

The fourth option is desirable and practical as it removes the need in the third option for a referee or asymmetric roles for the players. It also doesn't place any constraints on the timing of communication between devices.

## Simultaneous Exchange of Player Choices

The players could send an encrypted version of their choice and then send the key only when they have received the choices from all of the opponents. Since the key is revealed it cannot be reused making it a _per-message_ key.

### First Idea

An encryption algorithm E might produce the following with a certain key k:

- E<sub>k</sub>(rock) = **chey**
- E<sub>k</sub>(paper) = **ennem**
- E<sub>k</sub>(scissors) = **roncttla**

It only takes a few seconds to see the flaw in this approach. The length of the output [ciphertext](https://en.wikipedia.org/wiki/Ciphertext "Wikipedia: ciphertext") gives away the choice because the input [plainttext](https://en.wikipedia.org/wiki/Plaintext "Wikipedia: plaintext") has a known format with a well-known, small set of possible values with _unique_ lengths. This is a very simple form of [traffic analysis](https://en.wikipedia.org/wiki/Traffic_analysis "Wikipedia: traffic analysis").

**Second Idea**

Adding some characters to the messages to make them the same length removes the ability to deduce the choice in this case. This is referred to as [padding](https://en.wikipedia.org/wiki/Padding_(cryptography) "Wikipedia: padding (cryptography)").

The [one-time pad](https://en.wikipedia.org/wiki/One-time_pad "Wikipedia: one-time pad") (also known as Vernam cipher) is a cipher which has the useful property of being unbreakable when used correctly. It should not be confused with padding mentioned in the previous paragraph. Given the short plaintext it is a very tempting choice here. An example of this is shown below.

```python
>>> random.seed(0)
>>> plaintext = "rock....".encode("ascii")
>>> key = bytes([random.randrange(256) for _ in range(len(plaintext))])
>>> ciphertext = bytes([pt ^ k for pt, k in zip(plaintext, key)])
>>> ciphertext
b'\x9e\x1c4\x8b\x93~w\xb0'
```

There are two issues here. The first is hinted at by the inclusion of a superfluous looking execution of `random.seed()`, the second is a bit more subtle and discussed later.

> Any one who considers arithmetical methods of producting random digits is, of course, in a state of sin.
> 
> John Von Neumann (1951)

The seed sets where a random library starts its random number sequence and that sequence will be predictable from a seed value. CircuitPython running on nRF52840-based boards like the CLUE and Circuit Playground Bluefruit initialise the seed with a number from the [os.random()](https://circuitpython.readthedocs.io/en/5.3.x/shared-bindings/os/ __init__.html#os.urandom "CircuitPython: Docs: os.random()") function. `os.random()` provides true random numbers from a hardware generator.

### Third Idea

The one-time pad seems suitable if the seed is initialised with a true random number. Unfortunately for cryptography, the [pseudo-random number generator (PRNG)](https://en.wikipedia.org/wiki/Pseudorandom_number_generator "Wikipedia: Pseudorandom number generator") commonly found in libraries produces a predictable stream of values. This is clearly noted in both the [Python](https://docs.python.org/3/library/random.html "Python 3: random library") and [CircuitPython](https://circuitpython.readthedocs.io/en/5.3.x/shared-bindings/random/ __init__.html "CircuitPython Docs: random library") documentation.

> Numbers from this module are not cryptographically strong! Use bytes from [`os.urandom`](https://circuitpython.readthedocs.io/en/5.3.x/shared-bindings/os/ __init__.html#os.urandom "os.urandom") directly for true randomness.

The values from a standard PRNG must not be used for proper cryptography. In this case the key is intentionally revealed to everyone by the design of the protocol including any eavesdroppers. Over time, this provides an attacker with a sequence of _consecutive_ numbers from the PRNG which is a very useful aid to determine the seed.

### Fourth Idea

The solution looks robust in terms of the key if a true random number generator is used to generate that key.

```python
>>> plaintext = "rock....".encode("ascii")
>>> key = os.urandom(len(plaintext))
>>> ciphertext = bytes([pt ^ k for pt, k in zip(plaintext, key)])
>>> ciphertext
b'\x82\x0e\xe7\xf4\xe3y]\x9c'
```

The CircuitPython `^` (xor) operator applies the key to the plaintext and is a classic final step for [stream ciphers](https://en.wikipedia.org/wiki/Stream_cipher "Wikipedia: stream cipher") to create the ciphertext. This creates the more subtle problem raised previously as it makes discovering a new alternative key for a different plaintext trivial and very fast. This alternative key can be given to an opponent who will decrypt the message producing a plaintext which is different to the original one.

An example is shown below where the player has chosen **rock** and sent the encrypted **rock** but the player can then send an alternative key to convince an opponent that **paper** was their choice.

```python
>>> desiredpt = "paper...".encode("ascii")
>>> newkey = bytes([dpt ^ ct for dpt, ct in zip(desiredpt, ciphertext)])
>>> newkey
b'\xf2o\x97\x91\x91Ws\xb2'
>>> bytes([ct ^ k for ct, k in zip(ciphertext, key)])
b'rock....'
>>> bytes([ct ^ nk for ct, nk in zip(ciphertext, newkey)])
b'paper...'
```

![](https://cdn-learn.adafruit.com/assets/assets/000/093/903/medium800/circuitpython_The_Triumph_of_Death_by_Pieter_Bruegel_the_Elder-43whitelb.jpg?1596814936 “We Rolled Our Own Crypto” from Classic Programmer Paintings. Also known as The Triumph of Death (1562-1563) by Pieter Bruegel the Elder.)

### Fifth Idea

The one-time pad cannot be used with this simple protocol for the aforementioned reason. An essential property for all other encryption algorithms is the key cannot be selected or found easily to provide a chosen output for encryption or decryption. Hence, one of the common algorithms is likely to solve this issue.

[AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard "Wikipedia: Advanced Encryption Standard") (Rijndael) is a modern, widely-used [block cipher](https://en.wikipedia.org/wiki/Block_cipher "Wikipedia: block cipher") but it is not currently offered by the standard CircuitPython libraries. Its block size is 128bit (16 bytes) which is a little coarse for direct use with short messages. The [Chacha20](https://en.wikipedia.org/wiki/Salsa20 "Wikipedia: Salsa20") stream cipher is easy to implement in Python and was selected for this program. Its [key length](https://en.wikipedia.org/wiki/Key_size "Wikipedia: key size") was shortened as this is just for fun. A short key makes a system vulnerable to [brute-force attacks](https://en.wikipedia.org/wiki/Brute-force_attack "Wikipedia: brute-force attack").

The number of flaws quickly found here shows how mistakes can easily be made with cryptography. A slightly more complex and more robust protocol for doing this can be found in the _Simultaneous Exchange of Secrets_ section of [Bruce Schneier's Applied Cryptography](https://www.schneier.com/books/applied_cryptography/ "Bruce Schneier: Applied Cryptography (Second Edition)").

Warning: 

## Identity and Authentication

For a game over Bluetooth, the players can be identified by the network (MAC) hardware address if the [address privacy feature](https://www.bluetooth.com/blog/bluetooth-technology-protecting-your-privacy/ "Bluetooth SIG: Bluetooth Technology Protecting Your Privacy") is not in use or by a Bluetooth name. The issue of player and message [authentication](https://en.wikipedia.org/wiki/Authentication "Wikipedia: authentication") has not been covered. For a rock, paper, scissors game outside of a formal competition this seems reasonable!

Bluetooth Low Energy (BLE) typically has limited range, often around 5-10m (16-33ft) which can be perceived as a privacy feature. The range can be far higher with sensitive receivers or powerful transmitters. [Exploring Bluetooth 5 - Going the Distance](https://www.bluetooth.com/blog/exploring-bluetooth-5-going-the-distance/ "Bluetooth SIG: Exploring Bluetooth 5 - Going the Distance") cites an example of 350m (1150ft) range for BLE!

# CLUE Rock, Paper, Scissors Game using Bluetooth

## Networking

![](https://cdn-learn.adafruit.com/assets/assets/000/093/430/medium800thumb/circuitpython_clue-advanced-joingame-sixplayers1-gifO3.jpg?1595423615 Advanced game discovering other players shown in cyan font alongside the RSSI value.)

## Connection-oriented and Connection-less Communication

Bluetooth Low Energy (BLE) allows a device to make a connection to another device. In BLE terminology, a device with the _central_ role connects to one with a _peripheral_ role. The connection provides reliable, bidirectional data exchange between _two_ devices.

The [BLE advertising mechanism](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gap "Adafruit Learn: Introduction to Bluetooth Low Energy: GAP") which as its name suggests is used by a device to advertise its presence can also be used to transmit a small amount of data. Bluetooth's use of ([omnidirectional](https://en.wikipedia.org/wiki/Omnidirectional_antenna "Wikipedia: omnidirectional antenna")) radio as a medium makes it inherently a broadcast based technology. BLE advertising uses the term broadcasting in the sense that all nearby devices receive and process the packet if they are in scanning mode. Advertising uses three disparate channels (radio frequencies) to transmit data to increase the the reliability of transmission.

The original BLE advertising format was limited to 31 bytes of `AdvData` data. This is now sometimes referred to as the legacy format as Bluetooth 5 introduced an extended format for longer packets, shown below.

![](https://cdn-learn.adafruit.com/assets/assets/000/094/052/medium800/circuitpython_ble-advertising-4-5-extended-43stretch.jpg?1597271799 Format of Bluetooth 4 advertising packets and extended advertising packets from Bluetooth 5. Figure from 'A Survey on Bluetooth 5.0 and Mesh: New Milestones of IoT'.)

Broadcasting data is attractive as an efficient mechanism for sensors or [BLE beacons](https://en.wikipedia.org/wiki/Bluetooth_low_energy_beacon "Wikipedia: Bluetooth low energy beacon") to send data to multiple nearby devices - the BLE roles here are _broadcaster_ and _observer_. This can also be used for other forms of communication like simple multi-player games. However, if there is a requirement for reliable communication then the application or a library needs to add features to provide that.

Protocols like [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol "Wikipedia: Transmission Control Protocol") check for lost packets and re-transmit these even on [LANs](https://en.wikipedia.org/wiki/Local_area_network "Wikipedia: local area network") which when lightly-loaded can be almost loss-free. The [unlicensed Bluetooth spectrum](https://www.bluetooth.com/learn-about-bluetooth/bluetooth-technology/radio-versions/ "Bluetooth SIG: Radio Versions") is shared with other systems like Wi-Fi and is often very busy in areas crowded with people and their devices. This creates a high degree of packet loss. For advertising, a lower [layer in BLE](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fsds_s140%2FSDS%2Fs1xx%2Fble_protocol_stack%2Fble_protocol_stack.html "Nordic SemitCondcutors: nRF52 Series: SoftDevices: S140 SoftDevice: S140 SoftDevice Specification: Bluetooth Low Energy protocol stack") discards corrupted packets detected by a 3 byte (24 bit) [CRC](https://en.wikipedia.org/wiki/Cyclic_redundancy_check "Wikipedia: cyclic redundancy check") but it needs additional features for a sender to detect end-to-end loss and re-transmit.

### Custom Advertisement Packets using ManufacturerData

[AdafruitColor](https://github.com/adafruit/Adafruit_CircuitPython_BLE/blob/master/adafruit_ble/advertising/adafruit.py "GitHub: adafruit/Adafruit\_CircuitPython\_BLE master/adafruit\_ble/advertising/adafruit.py") is an example of an Advertisement class in CircuitPython. The source file mentions how classes can be created for other applications using the `ManufacturerDataField` field and [Adafruit's Bluetooth company identifier](https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/ "Bluetooth SIG: company identifiers") (`0x0822`):

> Adafruit manufacturing data is key encoded like advertisement data and the Apple manufacturing data. However, the keys are 16-bits to enable many different uses. Keys above 0xf000 can be used by Adafruit customers for their own data.

This game uses values in that range for each field in the `Advertisement` sub-classes. There is currently no registry for allocation of these values so they were chosen at random for this game.

## Simple Game

This version of the game is a two-player game which makes the exchange of players' choices relatively straightforward. A simple approach in the face of packet loss is to send the packets lots of times with the expectation that one will get through. This is a crude form of low-performance [forward error correction (FEC)](https://en.wikipedia.org/wiki/Forward_error_correction "Wikipedia: forward error correction").

The diagram below shows how two players exchange data. Time runs downwards in the diagram. **Player 1** presses their button first to transmit their choice followed by **Player 2**.

![](https://cdn-learn.adafruit.com/assets/assets/000/093/599/medium800/circuitpython_simple-rpsgame-protocol-v6.png?1595980116 A simplified pseudo-sequence diagram showing how the Simple rock, paper, scissors game exchanges players' choices between two programs using a single Advertisement type. The advertising period is far longer than shown and terminates after a fixed duration when the first packet is received or an upper maximum timeout. The lightning flashes indicate collisions or other forms of packet loss.)

This diagram is not a true sequence diagram as it does not show the complete algorithm with all the timeouts, rather it's showing a specific, abbreviated example of unsynchronised players and some packet loss.

The [start\_scan()](https://circuitpython.readthedocs.io/projects/ble/en/latest/api.html#adafruit_ble.BLERadio.start_scan "CircuitPython Docs: adafruit\_ble: BLERadio.start\_scan") method returns an [iterable object](https://docs.python.org/3/tutorial/classes.html#iterators "Python 3 Docs: Classes: Iterators") returning any received advertising packets in real-time until the (optional) specified `timeout` value. The returned packets which are parsed into an `Advertisement` class or sub-class are represented on the diagram with the dotted lines.

The advertising interval is set to the minimum value of 20 milliseconds with the hardware adding a random 0-10ms to the interval. This [jitter](https://en.wikipedia.org/wiki/Jitter "Wikipedia: jitter") is added to reduce the chance of a prolonged clash with another device advertising at the same interval and starting at the same time.

The algorithm is transmitting packets using [start\_advertising()](https://circuitpython.readthedocs.io/projects/ble/en/latest/api.html#adafruit_ble.BLERadio.start_advertising "CircuitPython Docs: adafruit\_ble: BLERadio.start\_advertising") for a specified maximum amount of time, this is shortened to a fixed duration when a packet is received from the other player. A single application-specific type of packet is used by both players which holds a text representation of the player's choice. These duration values need to be chosen to:

- allow for the time difference between players sending their choice by pressing their right button;
- provide enough time for the volume of packets sent to provide a very high probability of receiving at least one;
- keep the overall time low enough for players not to get bored or frustrated.

The final implementation uses 20 seconds for the maximum advertising duration reduced to 6 seconds when the other player's choice is received. The minimum permissible advertising interval of 20ms is used. Apple recommends this very short advertising interval in the context of a peripheral advertising for a connection in [Accessory Design Guidelines for Apple Devices](https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf "Accessory Design Guidelines for Apple Devices"):

> The accessory should first use the recommended advertising interval of 20 ms for at least 30 seconds.
> 
> If it is not discovered within the initial 30 seconds, Apple recommends using one of the following longer  
> intervals to increase chances of discovery by the device: 152.5ms, 211.25ms, 318.75ms, 417.5ms, 546.25ms, 760ms, 852.5ms, 1022.5ms, 1285ms.

This short advertising interval should only be used for _brief periods_ particularly when multiple devices are advertising simultaneously in a coordinated fashion.

Info: 

## Advanced Game
This version of the game allows multiple players to play and has a more sophisticated protocol featuring four types of message represented by different `Advertisement` sub-classes.

### Making the List of Players

The Advanced game first has to determine who is playing. This could be part of the configuration data but that's not a very flexible approach.

The game advertises itself once at start-up using `JoinGameAdvertisement` packets. Up to eight players are shown on the display as they are discovered together with the first [received signal strength indicator (RSSI)](https://en.wikipedia.org/wiki/Received_signal_strength_indication "Wikipedia: received signal strength indication") value. The names and RSSI come from the default [scan response](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gap#advertising-process-621419-3 "Adafruit Learn: Introduction to Bluetooth Low Energy: GAP: Advertising").

The RSSI isn't strictly needed but it's interesting to see the value. It provides an approximate indication of proximity and reliability. Typical values will be between `-30` for very close and `-90` for distant (~6m/20ft) and unreliable.

For those interested in signal strength, there are some links for articles and research on attempts at using RSSI for [range-finding](https://en.wikipedia.org/wiki/Rangefinder "Wikipedia: rangefinder") in [Adafruit Forums: Proximity estimation using BLE](https://forums.adafruit.com/viewtopic.php?f=53&t=164851 "Adafruit Forums: Proximity estimation using BLE").

### Exchanging Data between Players

The diagram below shows how two players exchange data. The sequence and algorithm is the same for more players. The essence of this per-round exchange is:

1. Each program encrypts the player's choice and sends it in an `RpsEncDataAdvertisement` message. When the same message is received from _all_ of the opponents then an acknowledgement (ack) field is added or updated in the sent message.
2. Each program then sends their encryption key in an `RpsKeyDataAdvertisement` message when they have received all the encrypted values from the opponents.
3. The final `RpsRoundEndAdvertisement` message is sent. Its main purpose is to provide a reasonable assurance that the ack has been received by _all_ for the previous message.

All of these messages also contain the round number of the game to ensure the players agree on the current round number.

The diagram below could show more players but it would get even more crowded! Time runs downward with **Player 1** pressing their button, then **Player 2**.

![](https://cdn-learn.adafruit.com/assets/assets/000/093/603/medium800/circuitpython_advanced-rpsgame-protocol-v3.png?1596056197 A simplified pseudo-sequence diagram showing how the Advanced rock, paper, scissors game exchanges players' choices between two programs using three Advertisement types. The advertising period completes when acknowledgements are received from all other games. The lightning flashes indicate collisions or other forms of packet loss.)

Again this is not a true sequence diagram as a lot of the logic in the algorithm is not shown and it shows a particular sequence of transmissions reacting to packet loss indicated by the lightning symbols.

The arrows between the **CLUE 1** and **CLUE 2** are showing the BLE advertising broadcasts with a representation of the data inside each packet. The first one, `RpsEncDataAd(cipherText, round)`, has a value of `"xxxxxxxx:2:4:3"` which is representing:

- ` xxxxxxxx` - 8 bytes of cipherText,
- a round number of `2`,
- a message sequence number of `4` and
- an ack of message `3` from the previous round.

When **Player 1** first transmits, **Player 2** is not yet scanning for the messages. **Player 1** re-transmits the message at the advertising interval. A similar thing happens when there is packet loss. The diagram shows how the ack values are only incremented when the packet is actually received.

If there were more than two players this can take a lot longer as the message needs to be received by every opponent before the next message is sent in this simple protocol.

The advertising interval is increased when there are more than four players to reduce the use and congestion on the BLE advertising channels.

Practical and efficient [reliable multicast](https://en.wikipedia.org/wiki/Reliable_multicast "Wikipedia: reliable multicast") protocols are complex and difficult to design particularly in the face of high or uneven packet loss. There are lots of improvements that could be made in this protocol but this is a functioning starting point and one that can be refined in further work. [Bluetooth Mesh](https://en.wikipedia.org/wiki/Bluetooth_mesh_networking "Wikipedia: Bluetooth mesh networking") offers some features for basic reliable multicast but this is not currently supported by CircuitPython.

# CLUE Rock, Paper, Scissors Game using Bluetooth

## Scores

![](https://cdn-learn.adafruit.com/assets/assets/000/093/382/medium800thumb/circuitpython_rpsgame-advanced-fourplayer-game1-gizmo-bubblesort.jpg?1595334431 Score table at the end of each Advanced game with visible bubble sort. A grey question mark indicates comparison of two scores and orange right arrow indicates they are being swapped.)

Scores are fundamental to many competitive games. A two-player rock, paper, scissors game is normally scored with a simple tally of the wins. The scoring can be extended for a multi-player game to something more akin to a league.

## Multi-player Scoring

The Advanced game allows more than two players and this means the draw can become more significant as a tie-breaker between players with the same number of wins. To recognise this, this version of the game uses a score of

- 2 for a win and
- 1 for a draw.

For comparison, some sports have moved to [three points for a win](https://en.wikipedia.org/wiki/Three_points_for_a_win "Wikipedia: three points for a win") system.

## Score Table Presentation

The final scores are initially presented with the local player (yellow) at the top followed by the remote players ([cyan](https://en.wikipedia.org/wiki/Cyan "Wikipedia: cyan")) in the order in which they joined the game. CircuitPython has the `sorted()` function and `sort()` method from Python but for novelty, the scores are visually sorted with a [bubble sort](https://en.wikipedia.org/wiki/Bubble_sort "Wikipedia: bubble sort"). The swapping of the elements is intentionally slow enough to be able to observe the algorithm at work. The video at the top of the page shows four players being sorted into descending score order.

The CircuitPython builtin sorts use the [quicksort](https://en.wikipedia.org/wiki/Quicksort "Wikipedia: quicksort")algorithm. As an [O](https://en.wikipedia.org/wiki/Big_O_notation "Wikipedia: Big O notation")(_n_&nbsp;log&nbsp;_n_) algorithm, this is more efficient than the bubble sort's [O](https://en.wikipedia.org/wiki/Big_O_notation "Wikipedia: Big O notation")(_n<sup>2</sup>_).

# CLUE Rock, Paper, Scissors Game using Bluetooth

## CircuitPython on CLUE

[CircuitPython](https://github.com/adafruit/circuitpython) is a derivative of [MicroPython](https://micropython.org) designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the **CIRCUITPY** &nbsp;flash drive to iterate.

The following instructions will show you how to install CircuitPython. If you've already installed CircuitPython but are looking to update it or reinstall it, the same steps work for that as well!

## Set up CircuitPython Quick Start!

Follow this quick step-by-step for super-fast Python power :)

[Download the latest version of CircuitPython for CLUE from circuitpython.org](https://circuitpython.org/board/clue_nrf52840_express/)
 **Click the link above to download the latest version of CircuitPython for the CLUE.**

Download and save it to your desktop (or wherever is handy).

![adafruit_products_CLUE_UF2_Downloaded.png](https://cdn-learn.adafruit.com/assets/assets/000/088/037/medium640/adafruit_products_CLUE_UF2_Downloaded.png?1580840077)

Plug your CLUE into your computer using a known-good USB cable.

**A lot of people end up using charge-only USB cables and it is very frustrating! So make sure you have a USB cable you know is good for data sync.**

Double-click the **Reset** button on the top (magenta arrow) on your board, and you will see the NeoPixel RGB LED (green arrow) turn green. If it turns red, check the USB cable, try another USB port, etc. **Note:** The little red LED next to the USB connector will pulse red. That's ok!

If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!

![adafruit_products_Clue_Reset_NeoPixel_bootloader.png](https://cdn-learn.adafruit.com/assets/assets/000/087/919/medium640/adafruit_products_Clue_Reset_NeoPixel_bootloader.png?1580496467)

You will see a new disk drive appear called **CLUEBOOT**.

Drag the **adafruit-circuitpython-clue-etc.uf2** file to **CLUE**** BOOT.**

![adafruit_products_CLUE_CLUEBOOT.png](https://cdn-learn.adafruit.com/assets/assets/000/088/042/medium640/adafruit_products_CLUE_CLUEBOOT.png?1580841287)

![adafruit_products_CLUE_drag_UF2.png](https://cdn-learn.adafruit.com/assets/assets/000/088/043/medium640/adafruit_products_CLUE_drag_UF2.png?1580841295)

The LED will flash. Then, the **CLUEBOOT** drive will disappear and a new disk drive called **CIRCUITPY** will appear.

If this is the first time you're installing CircuitPython or you're doing a completely fresh install after erasing the filesystem, you will have two files - **boot\_out.txt** , and **code.py** , and one folder - **lib** on your **CIRCUITPY** drive.

If CircuitPython was already installed, the files present before reloading CircuitPython should still be present on your **CIRCUITPY** drive. Loading CircuitPython will not create new files if there was already a CircuitPython filesystem present.

That's it, you're done! :)

![adafruit_products_CLUE_CIRCUITPY.png](https://cdn-learn.adafruit.com/assets/assets/000/088/044/medium640/adafruit_products_CLUE_CIRCUITPY.png?1580841453)

# CLUE Rock, Paper, Scissors Game using Bluetooth

## CircuitPython on Circuit Playground Bluefruit

# Install or Update CircuitPython

Follow this quick step-by-step to install or update CircuitPython on your Circuit Playground Bluefruit.

[Download the latest version of CircuitPython for this board via circuitpython.org](https://circuitpython.org/board/circuitplayground_bluefruit/)
 **Click the link above and download the latest UF2 file**

Download and save it to your Desktop (or wherever is handy)

![adafruit_products_CPB_Download_UF2.png](https://cdn-learn.adafruit.com/assets/assets/000/080/530/medium640/adafruit_products_CPB_Download_UF2.png?1567715178)

Plug your Circuit Playground Bluefruit into your computer using a known-good data-capable USB cable.

**A lot of people end up using charge-only USB cables and it is very frustrating! So make sure you have a USB cable you know is good for data sync.**

Double-click the small **Reset** button in the middle of the CPB (indicated by the red arrow in the image). The ten NeoPixel LEDs will all turn red, and then will all turn green. If they turn all red and stay red, check the USB cable, try another USB port, etc. The little red LED next to the USB connector will pulse red - this is ok!

If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!

(If double-clicking doesn't do it, try a single-click!)

![adafruit_products_CPB_Front_Reset_Button_Arrow.jpg](https://cdn-learn.adafruit.com/assets/assets/000/080/532/medium640/adafruit_products_CPB_Front_Reset_Button_Arrow.jpg?1567715535)

You will see a new disk drive appear called **CPLAYBTBOOT**.

&nbsp;

&nbsp;

&nbsp;

Drag the **adafruit\_circuitpython\_etc.uf2** file to **CPLAYBTBOOT.**

![adafruit_products_CPB_CPLAYBTBOOT.png](https://cdn-learn.adafruit.com/assets/assets/000/080/533/medium640/adafruit_products_CPB_CPLAYBTBOOT.png?1567715858)

![adafruit_products_CBP_drag_UF2.png](https://cdn-learn.adafruit.com/assets/assets/000/080/534/medium640/adafruit_products_CBP_drag_UF2.png?1567715871)

The LEDs will turn red. Then, the **CPLAYBTBOOT** drive will disappear and a new disk drive called **CIRCUITPY** will appear.

That's it, you're done! :)

![adafruit_products_CBP_CIRCUITPY.png](https://cdn-learn.adafruit.com/assets/assets/000/080/535/medium640/adafruit_products_CBP_CIRCUITPY.png?1567716034)

# CLUE Rock, Paper, Scissors Game using Bluetooth

## CircuitPython

## Libraries

Once you've gotten CircuitPython onto your CLUE or Circuit Playground Bluefruit (CPB) board, it's time to add some libraries. You can&nbsp;[follow this guide page](https://learn.adafruit.com/adafruit-circuit-playground-bluefruit/circuitpython-libraries)&nbsp;for the basics of downloading and transferring libraries to the board.

[Download the latest library bundle from circuitpython.org](https://circuitpython.org/libraries)
### Libraries for Very Simple Game

_No libraries are required from the bundle_ for this version.

The `random` library is used but this library is built into the CircuitPython interpreter.

### Libraries for Simple Game

From the library bundle you downloaded in that guide page, transfer the following libraries onto the CLUE board's **/lib** &nbsp;directory:

- **adafruit\_ble**
- **adafruit\_display\_text**
- **adafruit\_imageload**
- **neopixel.mpy**

The **adafruit\_imageload** and **neopixel.mpy** aren't used by this version but are needed later for the Advanced game...

![circuitpython_rps-clue-lib.png](https://cdn-learn.adafruit.com/assets/assets/000/093/486/medium640/circuitpython_rps-clue-lib.png?1595607698)

Info: 

### Libraries for Advanced Game for CLUE

From the library bundle you downloaded in that guide page, transfer the following libraries onto the CLUE board's **/lib** &nbsp;directory:

- **adafruit\_ble**
- **adafruit\_display\_text**
- **adafruit\_imageload**
- **neopixel.mpy**

![circuitpython_rps-clue-lib.png](https://cdn-learn.adafruit.com/assets/assets/000/093/487/medium640/circuitpython_rps-clue-lib.png?1595607731)

### Libraries for Advanced Game for Circuit Playground Bluefruit with TFT Gizmo

From the library bundle you downloaded in that guide page, transfer the following libraries onto the CPB board's **/lib** &nbsp;directory:

- **adafruit\_ble**
- **adafruit\_display\_text**
- **adafruit\_imageload**
- **adafruit\_gizmo**
- **neopixel.mpy**
- **adafruit\_st7789.mpy**

![circuitpython_rps-cpb-with-tftgizmo-lib.png](https://cdn-learn.adafruit.com/assets/assets/000/093/488/medium640/circuitpython_rps-cpb-with-tftgizmo-lib.png?1595607759)

### Libraries for Advanced Game for Circuit Playground Bluefruit only

From the library bundle you downloaded in that guide page, transfer the following libraries onto the CPB board's **/lib** &nbsp;directory:

- **adafruit\_ble**
- **adafruit\_display\_text**
- **neopixel.mpy**

The **adafruit\_display\_text&nbsp;** is not actively used but is needed due to how the implementation uses and `import`s libraries.

![circuitpython_rps-cpb-lib.png](https://cdn-learn.adafruit.com/assets/assets/000/093/489/medium640/circuitpython_rps-cpb-lib.png?1595607791)

### Development Testing

During development, the application was tested on the CLUE and CPB boards using CircuitPython 5.3.1 with libraries from the **adafruit-circuitpython-bundle-5.x-mpy-20200825.zip** bundle. It should work on subsequent versions, the [latest version is recommended for the CLUE](https://circuitpython.org/board/clue_nrf52840_express/ "CircuitPython: CLUE NRF52840 Express") and [CPB](https://circuitpython.org/board/circuitplayground_bluefruit/ "CircuitPython: Circuit Playground Bluefruit").

# CLUE Rock, Paper, Scissors Game using Bluetooth

## Very Simple Game

![](https://cdn-learn.adafruit.com/assets/assets/000/093/170/medium800thumb/bluefruit___ble_clue-verysimple-rpsgame-tty-screengrab-2.jpg?1594764338 Video of terminal playing Very Simple version of rock, paper, scissors.)

This is a very simple, single-player version of the [rock, paper, scissor game written by Chris Bradfield](https://www.youtube.com/watch?v=dhaaZQyBP2g "Youtube: KidsCanCode: Learning to Code with Python: Lesson 1.9 - Rock Paper Scissors Game") at KidsCanCode which demonstrates the essence of the game in a remarkably short program.

It is a text game which plays over the USB serial console, harking back to the [teleprinters](https://en.wikipedia.org/wiki/Teleprinter "Wikipedia: teleprinter") and terminals from the early days of computing. The video (animated gif) above shows the game being played.

This runs on any board that supports CircuitPython and can also run on CPython.

## Installing Project Code

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your **CIRCUITPY** drive. Then you need to update **code.py** with the example script.

Thankfully, we can do this in one go. In the example below, click the **Download Project Bundle** button below to download the necessary libraries and the **code.py** file in a zip file. Extract the contents of the zip file, open the directory **CLUE\_Rock\_Paper\_Scissors/very-simple/** and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your **CIRCUITPY** drive.

Your **CIRCUITPY** drive should now look similar to the following image:

![CIRCUITPY](https://adafruit.github.io/Adafruit_Learning_System_Guides/CLUE_Rock_Paper_Scissors_very-simple.png )

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CLUE/CLUE_Rock_Paper_Scissors/very-simple/code.py

## Code Discussion

The code uses a `while` loop which runs forever repeating game after game. The player's text input is compared against the computer's random choice with the following code.

```python
# Logic to determine winner
    if player_move == computer_move:
        print("Tie")
    elif player_move + computer_move in player_wins:
        print("You win!")
    else:
        print("You lose!")
```

The `if` is checking for a draw (tie) with a straightforward comparison. The `elif` is using a different technique, it [concatenates](https://en.wikipedia.org/wiki/Concatenation "Wikipedia: concatenation") the two players' values with `+` and uses `in` to check if they are present in the `player_wins` `list`. Note: this is different to the `in` used with [for statements](https://docs.python.org/3/tutorial/controlflow.html#for-statements "Python 3 Docs: More Control Flow Tools¶").

```python
player_wins = ["pr", "sp", "rs"]
```

This is a form of [lookup table](https://en.wikipedia.org/wiki/Lookup_table "Wikipedia: lookup table") where the data is stored in a Python list. The winning combinations are stored without any accompanying additional data - their presence indicates a win. If draws have already been eliminated then the only remaining result if it is not a win is a loss. The `else` statement efficiently implements this.

In this case, the hybrid use of the equality test plus a lookup table keeps the code very compact. Lookup tables are often used to increase performance with the trade-off of some additional memory/storage use. They can be used in hardware too and are sometimes known by the acronym LUT.

There are a few features missing from this game. One notable absence is the lack of [data validation](https://en.wikipedia.org/wiki/Data_validation "Wikipedia: data validation") on the player's typed input. Invalid input is not reported and causes the player to lose.

## Evolving the Game and some History

While very old multi-user computers are often associated with green screen terminals, many of the first electrical computers in the 1950s did have graphical screens and inevitably some games were written for these.

The console screens were small cathode-ray tubes. These were related to a type of dynamic memory technology from that era, the [Williams-Kilburn](https://en.wikipedia.org/wiki/Williams_tube "Wikipedia: Williams tube") tube.

Christopher Strachey's photographs of his noughts and crosses (tic-tac-toe) game on the [EDSAC](https://en.wikipedia.org/wiki/EDSAC "Wikipedia: EDSAC") and draughts (checkers) on the [Ferranti Mark I](https://en.wikipedia.org/wiki/Ferranti_Mark_1 "Wikipedia: Ferranti Mark 1") from 1952 are shown here.

Alvy Ray Smith's photograph is from a replica of the [Manchester Baby (SSEM)](https://en.wikipedia.org/wiki/Manchester_Baby "Wikipedia: Manchester Baby") proving that simple animation of images was possible in 1948.

The images here are from [The Dawn of Digital Light](https://www.researchgate.net/publication/279279099_The_Dawn_of_Digital_Light "Alvy Ray Smith: The Dawn of Digital Light (2015)").

![bluefruit___ble_edsac-oxo-1-43bordered.png](https://cdn-learn.adafruit.com/assets/assets/000/093/252/medium640/bluefruit___ble_edsac-oxo-1-43bordered.png?1595001655)

![bluefruit___ble_ferranti-draughts-1.png](https://cdn-learn.adafruit.com/assets/assets/000/093/255/medium640/bluefruit___ble_ferranti-draughts-1.png?1595001853)

![bluefruit___ble_baby-replica-graphics-animation-pixar.jpg](https://cdn-learn.adafruit.com/assets/assets/000/093/256/medium640/bluefruit___ble_baby-replica-graphics-animation-pixar.jpg?1595001879)

The next version of the game uses the TFT LCD screen on the CLUE or TFT Gizmo attached to a Circuit Playground Bluefruit for a display. It is a two-player game using Bluetooth Low Energy for communication.

# CLUE Rock, Paper, Scissors Game using Bluetooth

## Simple Game

![](https://cdn-learn.adafruit.com/assets/assets/000/093/403/medium800/circuitpython_clue-simple-rpsgame-showingwin.jpg?1595355358 The Simple version of the rock, paper, scissors game on a CLUE. This player has just won with rock-blunts-scissors indicated by the inverted colours on rock and cyan cursor indicating the opponent's losing choice, scissors.)

This is a two-player version of rock, paper, scissors game which uses Bluetooth Low Energy (BLE) advertising packets to exchange the players' choices and presents them on a graphical screen. It uses text to keep the program relatively short and simple.

## Example Video

The video below shows the game being played on a pair of devices side-by-side. The two players would normally hide their choices. Bluetooth facilitates this as the game works well up to 4m (13ft) apart. The boards have been placed next to each other in this demonstration to ease the video recording.

https://www.youtube.com/watch?v=lEpHvIiuwag

The sequence of events in the video:

- 00:03 The program has already started before the video. The choices are selected on the two boards with the left button.
- 00:06 The right buttons are pressed starting the exchange of choices over BLE.
- 00:12 The appearance of the cyan cursor on the right indicates the opponent's choice has been received. The winning choice is evaluated and flashes ([inverts foreground/background](https://en.wikipedia.org/wiki/Reverse_video "Wikipedia: reverse video")) on screen. In this case, _rock blunts scissors_.
- 00:23 Start of second round.
- 00:29 _Paper wraps rock_, paper flashes.
- 00:39 Start of third round.
- 00:46 Cyan cursor without any flashing indicates both players have chosen paper - _a draw (tie)_.

## Installing Project Code

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your **CIRCUITPY** drive. Then you need to update **code.py** with the example script.

Thankfully, we can do this in one go. In the example below, click the **Download Project Bundle** button below to download the necessary libraries and the **code.py** file in a zip file. Extract the contents of the zip file, open the directory **CLUE\_Rock\_Paper\_Scissors/simple/** and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your **CIRCUITPY** drive.

Your **CIRCUITPY** drive should now look similar to the following image:

![CIRCUITPY](https://adafruit.github.io/Adafruit_Learning_System_Guides/CLUE_Rock_Paper_Scissors_simple.png )

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CLUE/CLUE_Rock_Paper_Scissors/simple/code.py

## Code Discussion

The main loop runs forever checking for three conditions.

```python
while True:
    if round_no > TOTAL_ROUND:
        print("Summary: ",
              "wins {:d}, losses {:d}, draws {:d}, void {:d}".format(wins, losses, draws, voids))

        ### Reset variables for another game
        round_no = 1
        wins = 0
        losses = 0
        draws = 0
        voids = 0
        round_no = 1

    if button_left():
        while button_left():
            pass
        my_choice_idx = (my_choice_idx + 1) % len(choices)
        setCursor(my_choice_idx, "mine")

    if button_right():
```

The first `if` checks to see if the last round of the game has occurred in order to print a summary to the serial console and resets the per-round counters. The astute reader will spot an unintentional repetition of `round_no = 1` - this is harmless but does waste a few bytes of memory.

The second `if` checks to see if the left button has been pressed. A function is used here to allow the button to be customised for the board being used. The short `while` loop is waiting for the finger to release the button and then the `my_choice_idx` is incremented. This wraps around the three choices, the value after `2` is `0`. `setCursor()` updates the display, the second parameter is controlling whose selection needs updating. The success of waiting for `button_left()` not to be `True` (pressed) as a debounce mechanism is discussed further down.

The first part of the code inside the third `if` is shown below.

```python
# Right button code excerpt 1/3
    if button_right():
        tx_message = RpsAdvertisement()

        choice = choices[my_choice_idx]
        tx_message.test_string = choice
        d_print(2, "TXing RTA", choice)

        opponent_choice = None
        ble.start_advertising(tx_message, interval=MIN_AD_INTERVAL)
        sending_ns = time.monotonic_ns()

        # Timeout value is in seconds
        # RSSI -100 is probably minimum, -128 would be 8bit signed min
        # window and interval are 0.1 by default - same value means
        # continuous scanning (sending Advertisement will interrupt this)
        for adv in ble.start_scan(RpsAdvertisement,
                                  minimum_rssi=-90,
                                  timeout=MAX_SEND_TIME_S):
            received_ns = time.monotonic_ns()
            d_print(2, "RXed RTA",
                    adv.test_string)
            opponent_choice_bytes = adv.test_string

            # Trim trailing NUL chars from bytes
            idx = 0
            while idx < len(opponent_choice_bytes):
                if opponent_choice_bytes[idx] == 0:
                    break
                idx += 1
            opponent_choice = opponent_choice_bytes[0:idx].decode("utf-8")
            break
            
        # We have received one message or exceeded MAX_SEND_TIME_S
        ble.stop_scan()
```

This is creating a message using the `RpsAdvertisement` class and setting `test_string` (a poor name leftover from a prototype!) to the lower-case text representation of the player's choice. `start_advertising()` then starts sending that data repeatedly by broadcasting an advertising packet - this occurs in the background until it's explicitly stopped. Advertisements are received with the use of `start_scan()` which is commonly used in a loop to iterate over each packet as it arrives. For a two-player game the code is only waiting for the opponent's choice. As soon as the first `RpsAdvertisement` packet is received it's complete in terms of receiving data, hence the `break` to terminate the `for` loop. A `stop_scan()` then terminates the scanning.

The data needs to be converted from the underlying _fixed-size_ `bytes` type back to a text string and this involves removing any NUL padding. There's no guarantee this data is from a trusted source since it's an unauthenticated packet received over the air. A robust program would validate this data as soon as possible. A few other programming languages have a feature to tag risky data from less trusted sources which has _not been validated_, for example [taint checking](https://en.wikipedia.org/wiki/Taint_checking "Wikipedia: taint checking").

The code then continues to send advertising packets as it does not know if these have been received. This is shown in the next code excerpt below.

```python
# Right button code excerpt 2/3
        # Ensure we send our message for a minimum period of time
        # constrained by the ultimate duration cap
        if opponent_choice is not None:
            timeout = False
            remaining_ns = MAX_SEND_TIME_NS - (received_ns - sending_ns)
            extra_ad_time_ns = min(remaining_ns, MIN_SEND_TIME_NS)
            # Only sleep if we need to, the value here could be a small
            # negative one too so this caters for this
            if extra_ad_time_ns > 0:
                sleep_t  = extra_ad_time_ns / NS_IN_S
                d_print(2, "Additional {:f} seconds of advertising".format(sleep_t))
                time.sleep(sleep_t)
        else:
            timeout = True

        ble.stop_advertising()

        d_print(1, "ROUND", round_no,
                "MINE", choice,
                "| OPPONENT", opponent_choice)
```

It does this for `MIN_SEND_TIME_NS` or less if that would exceed `MAX_SEND_TIME_NS` in total. `NS` stands for nanoseconds, billionths of a second. Nanosecond precision is not required here, it's simply the units returned by `time.monotonic_ns()` which is the [most precise time function available](https://learn.adafruit.com/clue-sensor-plotter-circuitpython/time-in-circuitpython "Adafruit Learn: CLUE Sensor Plotter in CircuitPython: Time in CircuitPython"). The advertising packets are sent in the background, the code only needs to sleep for the calculated duration and then run `stop_advertising()`.

The final part of the code

1. checks who has won,
2. shows the opponent's choice on the display with `setCursor()`,
3. indicates a win or lose with `flashWinner()` and
4. then finally increments the integer variable `round_no`.

```python
# Right button code excerpt 3/3
        win, draw, void = evaluate_game(choice, opponent_choice)

        if void:
            voids += 1
        else:
            opp_choice_idx = choices.index(opponent_choice)
            setCursor(opp_choice_idx, "opp", visibility="show")
            if draw:
                time.sleep(4)
                draws += 1
            elif win:
                flashWinner(my_choice_idx, "mine")
                wins += 1
            else:
                flashWinner(opp_choice_idx, "opp")
                losses += 1
            setCursor(opp_choice_idx, "opp", visibility="hide")
        d_print(1, "wins {:d}, losses {:d}, draws {:d}, void {:d}".format(wins, losses, draws, voids))

        round_no += 1
```

The tempting variable name `round` should be avoided as this clashes with CircuitPython's `round()` function. The code would be valid and would run but it would be likely to cause confusion, bugs or both.

The `evaluate_game()` function is a little different to the technique used in the Very Simple game for deciding the winner. This version makes no use of lookup tables and is far longer to the extent that **pylint** doesn't like it. The `else` is only reached if the inputs are invalid - this is indicated by the `void` variable in the returned tuple being set to `True`. C/C++ programmers would instinctively avoid the use of `void` as a variable name as it's a [reserved word](https://en.wikipedia.org/wiki/Reserved_word "Wikipedia: reserved word") in those languages but Python does not use it.

```python
def evaluate_game(mine, yours):
    """Determine who won the game based on the two strings mine and yours_lc.
       Returns three booleans (win, draw, void)."""
    # Return with void at True if any input is None
    try:
        mine_lc = mine.lower()
        yours_lc = yours.lower()
    except AttributeError:
        return (False, False, True)

    r_win = r_draw = r_void = False
    # pylint: disable=too-many-boolean-expressions
    if (mine_lc == "rock" and yours_lc == "rock"
            or mine_lc == "paper" and yours_lc == "paper"
            or mine_lc == "scissors" and yours_lc == "scissors"):
        r_draw = True
    elif (mine_lc == "rock" and yours_lc == "paper"):
        pass  # r_win default is False
    elif (mine_lc == "rock" and yours_lc == "scissors"):
        r_win = True
    elif (mine_lc == "paper" and yours_lc == "rock"):
        r_win = True
    elif (mine_lc == "paper" and yours_lc == "scissors"):
        pass  # r_win default is False
    elif (mine_lc == "scissors" and yours_lc == "rock"):
        pass  # r_win default is False
    elif (mine_lc == "scissors" and yours_lc == "paper"):
        r_win = True
    else:
        r_void = True

    return (r_win, r_draw, r_void)
```

The return type permits some "illegal" combinations of values. The first and second elements in the tuple could be `True` which means the player has both won and drawn. The current implementation is small enough to verify and will never return this combination. This would be more risky in a larger or more complex function.

A less typical form of assignment is used in `evaluate_game()`.

```python
r_win = r_draw = r_void = False
```

This is a compact way to assign the same value to several variables. It works because Python is like many other languages and has a [right-associative](https://en.wikipedia.org/wiki/Operator_associativity#Right-associativity_of_assignment_operators "Wikipedia: opperator\_associativity") `=` operator. This means `r_void = False` occurs first, and then the result of this is the value `False` which is assigned to `r_draw` and then `r_win`.

## Current Issues

### Debouncing

A user who plays the game for a while will notice that occasionally the selection advances by two choices rather than one. This happens infrequently and it would be easy to dismiss this as a mysterious glitch but in this case it is a straightforward case of [switch bounce](https://learn.adafruit.com/make-it-switch/debouncing "Adafruit Learn: Make It Switch: Debouncing").

For this program the main `while` loop can iterate very rapidly as there's only a small amount of code to be run for the case of a left button press. If the display updates were immediate then this processing would probably take long enough for the button's contacts to stop bouncing but `displayio` updates are part of a background task - they happen soon after but not necessarily immediately.

This could be fixed with the help of the [adafruit\_debouncer](https://learn.adafruit.com/debouncer-library-python-circuitpython-buttons-sensors/overview "Adafruit Learn: Python Debouncer Library for Buttons and Sensors") library.

### BLE - Advertising vs Connections

If a game is inherently a two-player game or there is no requirement or future possibility of a multi-player game then the more common connection-based approach is going to be superior in most cases.

## Evolving the Game

The game's [look](https://en.wikipedia.org/wiki/Look_and_feel "Wikipedia: look and feel") is a bit dull and it doesn't make much use of the 240x240 graphical display. It also has a slow [feel](https://en.wikipedia.org/wiki/Look_and_feel "Wikipedia: look and feel") to the exchange of player's choices over BLE. The next version adds some graphics, sound and has a more sophisticated networking protocol for the exchange improving the responsiveness and facilitating more than two players.

# CLUE Rock, Paper, Scissors Game using Bluetooth

## Advanced Game

![](https://cdn-learn.adafruit.com/assets/assets/000/093/402/medium800/circuitpython_clue-advanced-rpsgame-introscreenwithselection.jpg?1595355227 The introduction screen with button guide for the Advanced rock, paper, scissors game.)

This is a multi-player version of rock, paper, scissors with:

- Simple `displayio` graphics.
- An announcer and sound effects implemented with sound samples in the `wav` format.
- A dynamic group for the players, formed after the button guide appears when the program starts at power-up (or reset).
- A more sophisticated data exchange protocol providing anti-cheat features and a more responsive feel.
- Configurable name for each player.
- A sorted score table per game for the players.

This has been tested with up to six players.

## Example Video

The video below shows the game being played on four devices. Each player would normally hide their choice. Bluetooth facilitates this as the game works well up to 4m (13ft) apart. The boards have been placed near each other in this demonstratation to ease the video recording.

https://www.youtube.com/watch?v=porq81JGtZI

The sequence of events in the video:

- 00:00 The program has started running on three of the boards as those boards were reset about 2 seconds before the video starts.
- 00:04 The introduction screen on the three boards with screens.
- 00:08 One click on reset button of the Circuit Playground Bluefruit with no display. The introduction is shorter without a display, hence the staggered start.
- 00:10 The button guide scrolls into view.
- 00:21 The **join game** phase, the boards send and receive packets to establish the group for the game.
- 00:23 The names of the players and the rssi of the first advertising packet received are shown on the devices with screens. All devices briefly flash blue (this is disabled by default in the code) on the NeoPixels when an advertising packet is received in the **join game** phase.
- 00:44 The boards conclude their search for other players and then commence a four player game with three rounds per game.
- 01:00 All four players have made their choice and are exchanging them, the game announces "Ready" and hums during transmission.
- 01:14 Exchange of data concludes and results from the first round are shown and announced on each device.
- 01:56 Second round begins.
- 02:38 Third and final round begins.
- 02:53 The Circuit Playground Bluefruit shows the scores on the NeoPixels.
- 03:07 The other three boards with displays show the scores for the game and sort them into descending order.

## Installing Project Code

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your **CIRCUITPY** drive. Then you need to update **code.py** with the example script.

Thankfully, we can do this in one go. In the example below, click the **Download Project Bundle** button below to download the necessary libraries and the **code.py** file in a zip file. Extract the contents of the zip file, open the directory **CLUE\_Rock\_Paper\_Scissors/advanced/** and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your **CIRCUITPY** drive.

Your **CIRCUITPY** drive should now look similar to the following image:

![CIRCUITPY](https://adafruit.github.io/Adafruit_Learning_System_Guides/CLUE_Rock_Paper_Scissors_advanced.png )

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CLUE/CLUE_Rock_Paper_Scissors/advanced/code.py

## Configuration

If you wish, the player's name can be set by adding an entry to the&nbsp; **settings.toml** file. This can be either `"rps_name"` or `"ble_name"`, for example:

```auto
rps_name="Huey"
```

The file is typically used by projects for confidential information like [credentials](https://en.wikipedia.org/wiki/Credential#Cryptography "Wikipedia: Credential"). Here, it's just a useful file to reuse as it keeps the configuration separate from the code.

## Code Discussion

This is a much larger program. It's split into a number of files with related functions and there's some limited use of classes.

**The clue and cpb Objects**

This program does not use the [clue](https://circuitpython.readthedocs.io/projects/clue/en/latest/api.html "CircuitPython Docs: adafruit\_clue library") and [cpb](https://circuitpython.readthedocs.io/projects/circuitplayground/en/latest/api.html#adafruit-circuitplayground-bluefruit "CircuitPython Docs: adafruit\_circuitplayground.bluefruit") objects from the libraries `adafruit_clue` and `adafruit_circuitplayground.bluefruit`, respectively. These are very useful for interactive experimentation and small programs but they do `import` a lot of libraries. For larger programs like the Advanced game that do not use most of the functionality, it is more economical on memory to `import` the specific libraries used in the program.

**Join Game**

After the introduction screen and button guide, the game establishes the group playing with a `JoinGameAdvertisement` message sent amongst all the boards that want to play the game. The code is show below.

```python
def addPlayer(name, addr_text, address, ad):
    # pylint: disable=unused-argument
    # address is part of call back
    """Add the player name and mac address to players global variable
       and the name and rssi (if present) to on-screen list."""

    rssi = ad.rssi if ad else None

    players.append((name, addr_text))
    rps_display.addPlayer(name, rssi=rssi)


# Make a list of all the player's (name, mac address as text)
# where both are strings with this player as first entry
players = []
my_name = ble.name
rps_display.fadeUpDown("down")
addPlayer(my_name, addrToText(ble.address_bytes), None, None)


# These two functions mainly serve to adapt the call back arguments
# to the called functions which do not use them
def jgAdCallbackFlashBLE(_a, _b, _c):
    """Used in broadcastAndReceive to flash the NeoPixels
       when advertising messages are received."""
    return rps_display.flashBLE()

def jgEndscanCallback(_a, _b, _c):
    """Used in broadcastAndReceive to allow early termination of the scanning
       when the left button is pressed.
       Button may need to be held down for a second."""
    return button_left()

# Join Game
gc.collect()
d_print(2, "GC before JG", gc.mem_free())

sample.play("searching", loop=True)
rps_display.fadeUpDown("up")
jg_msg = JoinGameAdvertisement(game="RPS")
(_, _, _) = broadcastAndReceive(ble,
                                jg_msg,
                                scan_time=JG_MSG_TIME_S,
                                scan_response_request=True,
                                ad_cb=(jgAdCallbackFlashBLE
                                       if JG_FLASH
                                       else None),
                                endscan_cb=jgEndscanCallback,
                                name_cb=addPlayer)
del _  # To clean-up with GC below
sample.stop()
gc.collect()
d_print(2, "GC after JG", gc.mem_free())
```

The `addPlayer()` function adds a player to the the `players` list, a global variable, and calls the `addPlayer()` method on the (global) `rps_display` object to add the player's details to the list on the display. This is used once to add the local player and then passed as a [callback](https://en.wikipedia.org/wiki/Callback_(computer_programming) "Wikipedia: callback (computer programming)") to `broadcastAndReceive()` to add the remote players as the device receives `JoinGameAdvertisement` from other players. The use of the callback means the return values are not needed from `broadcastAndReceive()` - a Python convention is to assign these to the `_` variable and in this case only the third element in the tuple will survive, the rest are over-written. This isn't needed and in order to minimise memory in use it is deleted. This makes the variable's previous value available for [garbage collection](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science) "Wikipedia: garbage collection (computer\_science)") by the subsequent `gc.collect()`.

Some other callbacks are used here:

- `ad_cb` is called whenever an `Advertisement` is received and is used for flashing the NeoPixel's blue if `JG_FLASH` is `True` (code currently has it set to `False`);
- `endscan_db` is called periodically and will terminate the scanning if it returns a `True` value, a left button press returns `True` here.

A sound effect is playing continuously during this part of the program, the `sample` object is a `SampleJukebox` which is described further down.

### Main Loop

The main loop has the same three conditional statements as the previous game plus one extra one to deal with rounds per game.

The first `if` checks for the end of each game, displaying the score on the display or NeoPixels using `rps_display.showGameResult()`&nbsp;and then resetting all the variables for the next game.

```python
# Main loop code excerpt 1/8 - end of a game
while True:
    if round_no > TOTAL_ROUNDS:
        print("Summary: ",
              "wins {:d}, losses {:d},"
              " draws {:d}, void {:d}\n\n".format(wins, losses,
                                                  draws, voids))

        rps_display.showGameResult(players, scores,
                                   rounds_tot=TOTAL_ROUNDS)

        # Reset variables for another game
        round_no = 1
        wins = 0
        losses = 0
        draws = 0
        voids = 0
        scores = [0] * len(players)
        game_no += 1
```

The second `if` checks a boolean to see if it is the start of a new round. The game and round number are updated with `rps_display.showGameRound()` and then a random starting choice is made for the player and displayed on screen using the `rps_display.showChoice()` with surrounding fades for a visually pleasant transition.

```python
# Main loop code excerpt 2/8 - end of a round
    if new_round_init:
        rps_display.showGameRound(game_no=game_no, round_no=round_no,
                                  rounds_tot=TOTAL_ROUNDS)
        # Make a new initial random choice for the player and show it
        my_choice_idx = random.randrange(len(CHOICES))
        rps_display.fadeUpDown("down")
        rps_display.showChoice(my_choice_idx,
                               game_no=game_no, round_no=round_no,
                               rounds_tot=TOTAL_ROUNDS,
                               won_sf=wins, drew_sf=draws,
                               lost_sf=losses)
        rps_display.fadeUpDown("up")
        new_round_init = False
```

The `if` for the left button is very similar to the Simple game with the `rps_display.`Here, `showChoice()` takes the place of `setCursor()` from the Simple game.

```python
# Main loop code excerpt 3/8 - left button press
    if button_left():
        while button_left():  ### Wait for button release
            pass
        my_choice_idx = (my_choice_idx + 1) % len(CHOICES)
        rps_display.showChoice(my_choice_idx,
                               game_no=game_no, round_no=round_no,
                               rounds_tot=TOTAL_ROUNDS,
                               won_sf=wins, drew_sf=draws,
                               lost_sf=losses)
```

The final `if` for the right button contains a lot of code for the exchange of choices between all the players. This would be better if it was split into some functions to make the code easier to read and understand.

The first part creates a short, per-message 8 byte encryption `key` with `generateOTPPadKey()` stretched with `enlargeKey()`. The player's choice is padded and and encrypted and incorporated into the `RpsEncDataAdvertisement` object. The padding here just adds NUL characters to ensure the message is 8 bytes long.

Padding schemes like [PKCS#5](https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7 "Wikipedia: padding (cryptography)") are used for real applications using encryption.

```python
# Main loop code excerpt 5/8 - right button press i
    if button_right():
        gc.collect()
        d_print(2, "GC before comms", gc.mem_free())

        # This sound cue is really for other players
        sample.play("ready")

        my_choice = CHOICES[my_choice_idx]
        player_choices = [my_choice]

        # Repeating key four times to make key for ChaCha20
        short_key = generateOTPadKey(KEY_SIZE)
        key = enlargeKey(short_key, KEY_ENLARGE)
        d_print(3, "KEY", key)

        plain_bytes = bytesPad(my_choice, size=8, pad=0)
        cipher_bytes = encrypt(plain_bytes, key, CRYPTO_ALGO,
                               nonce=static_nonce)
        enc_data_msg = RpsEncDataAdvertisement(enc_data=cipher_bytes,
                                               round_no=round_no)

        # Wait for ready sound sample to stop playing
        sample.wait()
```

The next part, shown below, starts the humming sound sample which runs while the program is sending messages. The three messages, `RpsEncDataAdvertisement`, `RpsKeyDataAdvertisement` and `RpsRoundEndAdvertisement`, are sent in that order and only when the prior message has been received from all other players. The data from the other players' messages is accumulated and left in `allmsg_by_addr`.

```python
# Main loop code excerpt 6/8 - right button press ii
        sample.play("start-tx")
        sample.wait()
        sample.play("txing", loop=True)
        # Players will not be synchronised at this point as they do not
        # have to make their choices simultaneously - much longer 12 second
        # time to accomodate this
        _, enc_data_by_addr, _ = broadcastAndReceive(ble,
                                                     enc_data_msg,
                                                     RpsEncDataAdvertisement,
                                                     RpsKeyDataAdvertisement,
                                                     scan_time=FIRST_MSG_TIME_S,
                                                     ad_interval=ad_interval,
                                                     receive_n=num_other_players,
                                                     seq_tx=seq_tx)

        key_data_msg = RpsKeyDataAdvertisement(key_data=short_key, round_no=round_no)
        # All of the programs will be loosely synchronised now
        _, key_data_by_addr, _ = broadcastAndReceive(ble,
                                                     key_data_msg,
                                                     RpsEncDataAdvertisement,
                                                     RpsKeyDataAdvertisement,
                                                     RpsRoundEndAdvertisement,
                                                     scan_time=STD_MSG_TIME_S,
                                                     ad_interval=ad_interval,
                                                     receive_n=num_other_players,
                                                     seq_tx=seq_tx,
                                                     ads_by_addr=enc_data_by_addr)
        del enc_data_by_addr

        # Play end transmit sound while doing next decrypt bit
        sample.play("end-tx")

        re_msg = RpsRoundEndAdvertisement(round_no=round_no)
        # The round end message is really about acknowledging receipt of
        # the key_data_msg by sending a non-critical message with the ack
        _, re_by_addr, _ = broadcastAndReceive(ble,
                                               re_msg,
                                               RpsEncDataAdvertisement,
                                               RpsKeyDataAdvertisement,
                                               RpsRoundEndAdvertisement,
                                               scan_time=LAST_ACK_TIME_S,
                                               ad_interval=ad_interval,
                                               receive_n=num_other_players,
                                               seq_tx=seq_tx,
                                               ads_by_addr=key_data_by_addr)
        del key_data_by_addr, _  # To allow GC

        # This will have accumulated all the messages for this round
        allmsg_by_addr = re_by_addr
        del re_by_addr
```

To free up as much memory as possible any data structures not needed at this point are `del`'ed. The other players' message(s) are then decrypted.

```python
# Main loop code excerpt 6/8 - right button press iii
        # Decrypt results
        # If any data is incorrect the opponent_choice is left as None
        for p_idx1 in range(1, len(players)):
            print("DECRYPT GC", p_idx1, gc.mem_free())
            opponent_name = players[p_idx1][0]
            opponent_macaddr = players[p_idx1][1]
            opponent_choice = None
            opponent_msgs = allmsg_by_addr.get(opponent_macaddr)
            if opponent_msgs is None:
                opponent_msgs = []
            cipher_ad = cipher_bytes = cipher_round = None
            key_ad = key_bytes = key_round = None
            # There should be either one or two messges per type
            # two occurs when there
            for msg_idx in range(len(opponent_msgs)):
                if (cipher_ad is None
                        and isinstance(opponent_msgs[msg_idx][0],
                                       RpsEncDataAdvertisement)):
                    cipher_ad = opponent_msgs[msg_idx][0]
                    cipher_bytes = cipher_ad.enc_data
                    cipher_round = cipher_ad.round_no
                elif (key_ad is None
                      and isinstance(opponent_msgs[msg_idx][0],
                                     RpsKeyDataAdvertisement)):
                    key_ad = opponent_msgs[msg_idx][0]
                    key_bytes = key_ad.key_data
                    key_round = key_ad.round_no

            if cipher_ad and key_ad:
                if round_no == cipher_round == key_round:
                    key = enlargeKey(key_bytes, KEY_ENLARGE)
                    plain_bytes = decrypt(cipher_bytes, key, CRYPTO_ALGO,
                                          nonce=static_nonce)
                    opponent_choice = strUnpad(plain_bytes)
                else:
                    print("Received wrong round for {:d} {:d}: {:d} {:d}",
                          opponent_name, round_no, cipher_round, key_round)
            else:
                print("Missing packets: RpsEncDataAdvertisement "
                      "and RpsKeyDataAdvertisement:", cipher_ad, key_ad)
            player_choices.append(opponent_choice)

        # Free up some memory by deleting any data that's no longer needed
        del allmsg_by_addr
        gc.collect()
        d_print(2, "GC after comms", gc.mem_free())
```

The decrypted choices of the opponents are now checked against the local player's choice to show who has won. Perhaps surprisingly, if there's more than one opponent then they are checked against each other. This is required to calculate the complete score table for _all_ the players shown at the end of each game.

```python
# Main loop code excerpt 7/8 - right button press iv
        sample.wait()  # Ensure end-tx has completed

        # Chalk up wins and losses - checks this player but also has to
        # check other players against each other to calculate all the
        # scores for the high score table at the end of game
        for p_idx0, (p0_name, _) in enumerate(players[:len(players) - 1]):
            for p_idx1, (p1_name, _) in enumerate(players[p_idx0 + 1:], p_idx0 + 1):
                # evaluateRound takes text strings for RPS
                result = evaluateRound(player_choices[p_idx0],
                                       player_choices[p_idx1])

                # this_player is used to control incrementing the summary
                # for the tally for this local player
                this_player = 0
                void = False
                if p_idx0 == 0:
                    this_player = 1
                    p0_ch_idx = None
                    p1_ch_idx = None
                    try:
                        p0_ch_idx = CHOICES.index(player_choices[p_idx0])
                        p1_ch_idx = CHOICES.index(player_choices[p_idx1])
                    except ValueError:
                        void = True  # Ensure this is marked void
                        print("ERROR", "failed to decode",
                              player_choices[p_idx0], player_choices[p_idx1])

                    # showPlayerVPlayer takes int index values for RPS
                    rps_display.showPlayerVPlayer(p0_name, p1_name, p_idx1,
                                                  p0_ch_idx, p1_ch_idx,
                                                  result == WIN,
                                                  result == DRAW,
                                                  result == INVALID or void)
```

Finally the results from the round are added to the sub-totals.

```python
# Main loop code excerpt 8/8 - right button press v
               if result == INVALID or void:
                    voids += this_player
                elif result == DRAW:
                    draws += this_player
                    scores[p_idx0] += POINTS_DRAW
                    scores[p_idx1] += POINTS_DRAW
                elif result == WIN:
                    wins += this_player
                    scores[p_idx0] += POINTS_WIN
                else:
                    losses += this_player
                    scores[p_idx1] += POINTS_WIN

                d_print(2,
                        p0_name, player_choices[p_idx0], "vs",
                        p1_name, player_choices[p_idx1],
                        "result", result)

        print("Game {:d}, round {:d}, wins {:d}, losses {:d}, draws {:d}, "
              "void {:d}".format(game_no, round_no, wins, losses, draws, voids))

        round_no += 1
        new_round_init = True
```

The `evaluateRound()` function has a different style of implementation compared to the Simple version.

```python
def evaluateRound(mine, yours):
    """Determine who won the round in this game based on the two strings mine and yours.
       Returns WIN, DRAW, LOSE or INVALID for bad input."""
    # Return INVALID if any input is None
    try:
        mine_lc = mine.lower()
        yours_lc = yours.lower()
    except AttributeError:
        return INVALID

    if mine_lc not in CHOICES or yours_lc not in CHOICES:
        return INVALID

    # Both inputs are valid choices if we got this far
    if mine_lc == yours_lc:
        return DRAW
    elif (mine_lc == "rock" and yours_lc == "scissors"
          or mine_lc == "paper" and yours_lc == "rock"
          or mine_lc == "scissors" and yours_lc == "paper"):
        return WIN

    return LOSE
```

This more compact version makes use of Python's [in](https://en.wikipedia.org/wiki/Operator_overloading "Wikipedia: operator overloading") operator to check if the inputs are present in the tuple of valid `CHOICES` for data validation. The `CHOICES` variable could be a list or a tuple. A [tuple](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences "Python 3: Data Structures: Tuples and Sequences") is used as form of [defensive programming](defensive%20programming "Wikipedia: defensive programming") since tuples are immutable (read-only) in Python - this prevents accidental modification to this [constant](https://en.wikipedia.org/wiki/Constant_(computer_programming) "Wikipedia: constant (computer programming)") sequence.

The return type has changed and is now a form of [enumerated type](https://en.wikipedia.org/wiki/Enumerated_type "Wikipedia: enumerated type") using [const()](https://circuitpython.readthedocs.io/en/latest/docs/library/micropython.html#micropython.const "Circuitpython Docs: micropython library: const()") global variables. CircuitPython's `const()` can only be used for integers but that's sufficient here. CPython offers the useful [Enum](https://docs.python.org/3/library/enum.html "Python 3 Docs: enum")&nbsp;class for enumerations but this is not currently present in CircuitPython.

The long expression in `elif` uses a mixture of `and` and `or` boolean operators. The `and` expressions are evaluated first as the programmer intended due to Python's [precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence "Python 3 Docs: expressions: operator precedence") rules, placing `and` higher than `or`.

## SampleJukebox class

Using a separate class for playing the sound samples tidies up the code a little but the main motivation for creating this class was to add a workaround to reduce the chance of fatal `MemoryError` exceptions, possibly related to memory [fragmentation](https://en.wikipedia.org/wiki/Fragmentation_(computing) "Wikipedia: fragmentation (computing)"). The `PWMAudioOut` library currently allocates memory dynamically under the covers. The `SampleJukebox` class attempts to use the library in a way where the 2048 byte buffer is immediately re-allocated just after it is de-allocated. This means the code is almost guaranteed to be able to reuse the previous 2048 contiguous section of memory.

## RPSDisplay class

This is a large class containing all the code to send output to the display and/or to the NeoPixels for each of the screens in the game. Its constructor takes the `sample` object to let it play samples during some of the simple animations.

## Dynamic Advertising Interval

The advertising interval seen in the code above is set just before the main loop using.

```python
ad_interval = MIN_AD_INTERVAL if len(players) <= 4 else len(players) * 0.007
```

This increases the interval for five or more players, 5 players will be 35 milliseconds, 6 players will be 42ms. This is an attempt to keep the [collisions](https://en.wikipedia.org/wiki/Collision_domain "Wikipedia: collision domain") at a low level to maintain good efficiency.

`MIN_AD_INTERVAL` would be expected to be `0.02` seconds (20ms) for Bluetooth Low Energy. The actual value used in the code is `0.02001`. This is needed to work around a [minor bug in adadfruit\_ble library's start\_advertising()](https://github.com/adafruit/circuitpython/issues/2930 "GitHub: adafruit/circuitpython start\_advertising interval does not accept the minimum 20ms value and may not range check correctly #2930") which relates to the fundamental limited precision of floating-point. Some general background on this can be found in the [Number Representation section of Clue Sensor Plotter in CircuitPython](https://learn.adafruit.com/clue-sensor-plotter-circuitpython/number-representation "Adafruit Learn: Clue Sensor Plotter in CircuitPython: Number Representation").

## Advertisement Matching

The `adafruit_ble` library provides a feature for efficiently filtering advertising packets including any scan responses in [start\_scan()](https://circuitpython.readthedocs.io/projects/ble/en/latest/api.html#adafruit_ble.BLERadio.start_scan "CircuitPython Docs: adafruit\_ble library: start\_scan"). If any classes are passed as arguments then only packets matching those classes will be returned. This is implemented with a simple prefix mechanism where a list of 1 or more prefix byte sequences are checked against the advertising data.

For `RpsRoundEndAdvertisement` the class sets `match_prefixes` attribute which the library code then uses to construct the prefix.

```python
# match_prefixes tuple replaces deprecated prefix
    match_prefixes = (
        struct.pack(
            _PREFIX_FMT,
            MANUFACTURING_DATA_ADT,
            ADAFRUIT_COMPANY_ID,
            struct.calcsize("<H" + _DATA_FMT_ROUND),
            RPS_ROUND_ID
        ),
    )
```

The `MANUFACTURING_DATA_ADT` has a value of `0xff`, the `ADAFRUIT_COMPANY_ID` is `0x0822` and the `RPS_ROUND_ID` is `0xfe43`. The prefix ends up as the bytes (in hex) `0bff22080343fe`. The `0b` is a length field and automatically prepended by the library. Some values appear "reversed", this is due to BLE values being encoded in [little-endian](https://en.wikipedia.org/wiki/Endianness "Wikipedia: endianness") order. Python's `struct` library uses `"<"` to represent this. The example below shows how this prefix will match the data in the `RpsRoundEndAdvertisement` packet regardless of the per-instance field values and correctly doesn't match a different packet.

![](https://cdn-learn.adafruit.com/assets/assets/000/094/481/medium800/circuitpython_ble-prefix-matching-example1-1800x1350.png?1598890136 An example of prefix matching for two RpsRoundEndAdvertisement messages with different values and a JoinGameAdvertisement message which differs and does not match. The length fields for the Manufacturer's Data field and within it are shown with a darker background.)

The order of the `round_no` and `sequence_number` is critical for this prefix matching to work as the identifier number associated with `round_no` (`0xfe43`) is being used as part of the prefix to identify the class.

## Current Issues

The game works well but in common with almost all large codebases there are a number of issues.

### Flicker when Changing Player's Choice

There is a noticeable flicker when a player presses the left button to advance the choice between the rock, paper and scissors icons. This is a common problem with simple or naive graphics code and was left in the code on purpose to demonstrate the phenomena.

The implementation of the `showChoice()` method replaces _all_ the screen objects and then recreates them including ones that have not been updated like the text at the top and bottom of the display. The extra display updates from these unnecessary changes are slow enough for the player to see the transition as flicker. This flicker can be reduced or eliminated by only changing the `displayio` objects which need to be updated.

In general, if lots of changes are made to `displayio` objects and if these are best displayed at once then briefly turning [auto\_refresh](https://circuitpython.readthedocs.io/en/latest/shared-bindings/displayio/#displayio.Display.auto_refresh "CircuitPython Docs: displayio auto\_refresh") off is an option. This will coalesce the changes into one display update.

Another occurrence of this type of flashing/flicker has been discussed in the [Adafruit CircuitPython and MicroPython](https://forums.adafruit.com/viewtopic.php?f=60&t=168528 "Adafruit Forums: Displayio unwanted flashing") forum.

### Dependency on dict Key Ordering&nbsp; - Now Fixed

The four custom `Advertisement` sub-classes were dependent on the behaviour of CircuitPython's `dict` type for the identifier numbers to match the prefixes for each message.

A simple example on CircuitPython 5.3.0's REPL below shows the nature of this issue.

```python
>>> letters = {"a": 1, "b": 2, "c" :3}
>>> letters
{'c': 3, 'a': 1, 'b': 2}
```

The order of the keys is _not maintained_ in CircuitPython, `"a"` is the first key in `letters` as it is constructed but `"c"` is returned as the first one. This is a common feature for data types built upon a rudimentary [hash table](https://en.wikipedia.org/wiki/Hash_table "Wikipedia: hash table"). Depending on order which is not specified is a common source of pernicious, latent bugs.

CPython started returning `dict` keys in _insert-order_ from [version 3.6](https://docs.python.org/3.6/whatsnew/3.6.html#new-dict-implementation "Python 3.6 Docs: What's new") and this formed part of the specification in [version 3.7](https://docs.python.org/3/whatsnew/3.7.html "Python 3.7 Docs: What's new").

A few languages also randomly vary their hash tables to counter denial attacks, see [Daniel Lemire: Use random hashing if you care about security?](https://lemire.me/blog/2012/01/17/use-random-hashing-if-you-care-about-security/ "Daniel Lemire Blog: Use random hashing if you care about security?")

### Improvement

This was fixed with a [new feature in the adafruit\_ble library](https://github.com/adafruit/Adafruit_CircuitPython_BLE/pull/97 "GitHub: adafruit/Adafruit\_CircuitPython\_BLE: ManufacturerData tweak to offer control over ordering of its data fields #97") to maintain the order of fields _within_ a `ManufacturerData` field based on the order of assignment. CircuitPython offers an `OrderedDict` data type which was used to implement this.&nbsp;

### CPB Only Needs adafruit\_display\_text Library

The `RPSDisplay` class uses `Group` and `Label`. This creates the unfortunate, unnecessary dependency on having the `adafruit_display_text` library even when the Circuit Playground Bluefruit is used without a display. This could be fixed with some sub-classes that implement the relevant code for each type of output in separate files and conditional `import` statements for those sub-classes.

### Sequence Number Wraparound

The sequence number used in advertising messages starts at 1 and is transmitted as an unsigned 8bit number giving it a maximum of 255. The code currently does not deal with exceeding 255 and will probably break in the 28th game.

It's common for packet identifier and sequence numbers to use a fairly small data size as the number only needs to span the maximum number of packets which are "in flight" and can wraparound back to 0. For comparison, [IPv4 has a 16bit identifier](https://en.wikipedia.org/wiki/IPv4#Packet_structure "Wikipedia: IPv4#Packet\_structure") for each packet and [TCP has a 32bit sequence number](https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure "Wikipedia: Transmission Control Protocol") per connection.

### Protocol Versioning

The messages do not have a version number. For a simple game this isn't a serious problem but for any software where there's an expectation of new features or different versions of the software in use concurrently then having a version number in the protocol/messages is useful to be able to detect or support old/new message formats, possibly concurrently.

BLE advertising packets are self-describing to some extent in the sense they have types in the fields so this is not as problematic as with some other formats.

### Very Infrequent MemoryError Allocating 65536 or 42441 Bytes

A `MemoryError` exception is raised when a program runs out of memory from the heap. In CircuitPython the accompanying stack trace is printed to the serial console. There is an elusive bug somewhere that causes an allocation attempt for a relatively large amount of memory.

```python
Traceback (most recent call last):
  File "code.py", line 448, in <module>
  File "rps_comms.py", line 340, in broadcastAndReceive
  File "rps_comms.py", line 115, in startScan
  File "adafruit_ble/__init__.py", line 263, in start_scan
MemoryError: memory allocation failed, allocating 65536 bytes
```

The program here does not do anything obvious which needs such a large amount of memory particularly around the call to `start_scan()`. The sizes are suspicious:

- both values appear far larger than any piece of data in the program;
- `42441` only has two factors, 53 \* 797, suggesting it is not a simple repeated data type;
- `65536` is 2<sup>16</sup>.

There's no obvious pattern so far for when this occurs although it does appear to always happen on the third call to `broadcastAndReceive()`. The frequency of occurrence is approximately 1 round in 500. It may occur more frequently when many (5+) players are playing. This is logged as [issue in GitHub](https://github.com/adafruit/circuitpython/issues/3119 "GitHub: adafruit/circuitpython: BLE start\_scan on 5.3.0 dies with MemoryError: memory allocation failed, allocating 65536 bytes #3119 ").

# CLUE Rock, Paper, Scissors Game using Bluetooth

## Going Further

## Ideas for Areas to Explore

- Customise the game:
  - Replace the sound samples (16k, 8bit mono) with your own. [Microcontroller Compatible Audio File Conversion](https://learn.adafruit.com/microcontroller-compatible-audio-file-conversion "Adafruit Learn: Microcontroller Compatible Audio File Conversion") can help with this.
  - Replace the three sprites - the 48x16 [bmp](https://en.wikipedia.org/wiki/BMP_file_format "Wikipedia: BMP file format") file can be changed without changing the code if the dimensions are preserved.

- Enhance the game:
  - Use the accelerometer to detect shaking as an alternate way to start the transmission of the player's choice.
  - Add a cumulative high score table to the Advanced game.
  - Enhance the Circuit Playground Bluefruit only version to show players joining on the NeoPixels.
  - Port the Simple game to the Circuit Playground Express using infrared for communication - this is very directional and would need careful alignment making the game finickety to play.

- Bluetooth related:
  - Update the RSSI number on the player list screen as new Bluetooth packets arrive if you are interested in this value.
  - Write a distance estimation function and add a feature for the right button to toggle between RSSI and a distance estimate.
  - Test different communication conditions to see how the RSSI varies and how the game degrades or fails. Distance, different types of obstacles and close proximity to busy Wireless Access Points are all interesting factors to experiment with.

- Try implementing another multi-player game. Note: A connection based approach is likely to be a better solution for two-player games and may also suit card games featuring a dealer.
- Investigate _lots_ of players. The Advanced game has been tested and works well with six players. It would be interesting to see how well it works in a classroom environment with 20+ devices. The `MAX_PLAYERS` _will need increasing_ as it's currently set to `8` but this is only because it's how many players fit on the joining screen with the chosen font size. This can be increased and they should just overspill harmlessly.

## Related Projects

- [CircuitPython BLE Advertising Beacons](https://learn.adafruit.com/circuitpython-ble-advertising-beacons "Adafruit Learn: CircuitPython BLE Advertising Beacons")
- [Bluetooth LE Sensor Nodes to Raspberry Pi WiFi Bridge](https://learn.adafruit.com/bluetooth-le-broadcastnet-sensor-node-raspberry-pi-wifi-bridge "Adafruit Learn: Bluetooth LE Sensor Nodes to Raspberry Pi WiFi Bridge")
- [Build an ML Rock Paper Scissors Game with Lobe](https://learn.adafruit.com/lobe-rock-paper-scissors "Adafruit Learn: Build an ML Rock Paper Scissors Game with Lobe") - object/gesture recognition with machine learning on a Raspberry Pi 4.

## Further Reading

- [Numberphile: Winning at Rock Paper Scissors](https://www.youtube.com/watch?v=rudzYPHuewc "Numberphile: Winning at Rock Paper Scissors") (YouTube) - Hannah Fry describing research and strategies against human players.
- [Bluetooth SIG: Topology Options](https://www.bluetooth.com/learn-about-bluetooth/bluetooth-technology/topology-options/ "Bluetooth SIG: Topology Options") - high level comparison of point-to-point (1:1), broadcast (1:many) and mesh (many:many _with forwarding_) communication.
- [Introduction to Bluetooth Low Energy](https://learn.adafruit.com/introduction-to-bluetooth-low-energy "Adafruit Learn: Introduction to Bluetooth Low Energy")
- [Maxim Integrated: Bluetooth Low Energy: Understanding GAP Roles - Part 3 of 7](https://www.youtube.com/watch?v=dAmZudlm60E "Maxim Integrated: Bluetooth Low Energy: Understanding GAP Roles - Part 3 of 7") (YouTube) - fairly detailed explanation of advertising in BLE GAP.
- [American Scientist: Random Paths to Frequency Hopping](https://www.americanscientist.org/article/random-paths-to-frequency-hopping "American Scientist: Random Paths to Frequency Hopping") - the history of inventions relating to spread spectrum communication.
- [Bluefruit nRF52 Feather Learning Guide](https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/introduction "Adafruit Learn: Bluefruit nRF52 Feather Learning Guide") - C++/Arduino progamming for the nRF52832-based [Feather nRF52 Bluefruit LE](https://www.adafruit.com/product/3406 "Adafruit:Feather nRF52 Bluefruit LE - nRF52832")
- [Bluetooth SIG: An Intro to Bluetooth Mesh part 1](https://www.bluetooth.com/blog/an-intro-to-bluetooth-mesh-part1/ "Bluetooth SIG: An Intro to Bluetooth Mesh part 1")
- **[Yin, Yang, Cao, Liu, Zhou, Wu: A Survey on Bluetooth 5.0 and Mesh: New Milestones of IoT](https://www.researchgate.net/publication/333528241_A_Survey_on_Bluetooth_50_and_Mesh_New_Milestones_of_IoT "Yin, Yang, Cao, Liu, Zhou, Wu: A Survey on Bluetooth 5.0 and Mesh: New Milestones of IoT")**
- [Networking With The micro:bit](https://microbit.nominetresearch.uk/networking-book-online/ "Nominet: Networking With The micro:bit (book)") - an online book from Nominet using the [BBC micro:bit](https://www.adafruit.com/product/3530 "Adafruit: BBC micro:bit") to explain networking principles. There is also a [MicroPython Edition](https://microbit.nominetresearch.uk/networking-book-online-python/ "Nominet: Networking with the micro:bit (Python Edition)").
- [code.org: The Internet: Packets, Routing & Reliability](https://www.youtube.com/watch?v=AYdF7b3nMto "YouTube: code.org: The Internet: Packets, Routing & Reliability") (YouTube) - a short, high-level introduction to TCP/IP.
- [Hannah Makes: Project 3: Use two microbits to help you keep your distance](https://www.youtube.com/watch?v=LaDvhGsvZro "YouTube: Hannah Makes: Project 3: Use two microbits to help you keep your distance") (YouTube) - a straightforward demonstration of approximate distance estimation using the BBC micro:bit, MakeCode and radio RSSI.
- [Tom Jennings' Gas Tube Random Number Generator](https://www.sr-ix.com/Objects/GTNG/index.html "Tom Jennings' Gas Tube Random Number Generator") - using a [thyratron](https://en.wikipedia.org/wiki/Thyratron "Wikipedia:thyratron") to produce random numbers like it's 1948.
- [ERNIE (Electronic Random Number Indicator Equipment)](https://en.wikipedia.org/wiki/Premium_Bond#ERNIE "Wikipedia: Premium\_Bond (ERNIE)") - the original machine [ERNIE 1 resides in the Science Museum London's collection](https://collection.sciencemuseumgroup.org.uk/objects/co62675/gpo-ernie-i-number-selector "Science Museum London Collection: GPO ERNIE I"). This used [a neon tube to produce random numbers](https://www.i-programmer.info/history/machines/6317-ernie-a-random-number-generator.html "I Programmer: ERNIE - A Random Number Generator") in 1957.&nbsp;
- [General Post Office: The Importance of being E.R.N.I.E. (1964)](https://www.youtube.com/watch?v=rOAfbb5D3Dw&t=5m56s "YouTube: Rod Willerton: General Post Office: The Importance of being E.R.N.I.E.") (YouTube) - video showing ERNIE 1 being used to choose Premium Bond winners.
- [Rand Corporation: A Million Random Digits with 100,000 Normal Deviates (1955)](https://en.wikipedia.org/wiki/A_Million_Random_Digits_with_100,000_Normal_Deviates "Wikipedia: A Million Random Digits with 100,000 Normal Deviates") - a useful book for insomniacs.
- [Secura: Zerologon: Unauthenticated domain controller compromise by subverting Netlogon cryptography (CVE-2020-1472)](https://www.secura.com/pathtoimg.php?id=2055 "Secura: Zerologon: Unauthenticated domain controller compromise by subverting Netlogon cryptography (CVE-2020-1472)") - a serious flaw discovered by Tom Tervoort in the [NTLM protocol](https://en.wikipedia.org/wiki/NT_LAN_Manager "Wikipedia: NT LAN Manager") in Microsfoft Windows caused by an elementary blunder setting an [initialization vector](https://en.wikipedia.org/wiki/Initialization_vector "Wikipedia: initialization vector").


## Featured Products

### Adafruit CLUE - nRF52840 Express with Bluetooth® LE

[Adafruit CLUE - nRF52840 Express with Bluetooth® LE](https://www.adafruit.com/product/4500)
Do you feel like you just don't have a CLUE? Well, we can help with that - get a CLUE here at Adafruit by picking up this sensor-packed development board. We wanted to build some projects that have a small screen and a lot of sensors. To make it compatible with existing projects, we made...

Out of Stock
[Buy Now](https://www.adafruit.com/product/4500)
[Related Guides to the Product](https://learn.adafruit.com/products/4500/guides)
### Circuit Playground Bluefruit - Bluetooth® Low Energy

[Circuit Playground Bluefruit - Bluetooth® Low Energy](https://www.adafruit.com/product/4333)
 **Circuit Playground Bluefruit** is our third board in the Circuit Playground series, another step towards a perfect introduction to electronics and programming. We've taken the popular Circuit Playground Express and made it even better! Now the main chip is an nRF52840...

Out of Stock
[Buy Now](https://www.adafruit.com/product/4333)
[Related Guides to the Product](https://learn.adafruit.com/products/4333/guides)
### Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier

[Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier](https://www.adafruit.com/product/4367)
Extend and expand your Circuit Playground projects with a bolt on TFT Gizmo that lets you add a lovely color display in a sturdy and reliable fashion. This PCB looks just like a round TFT breakout but has permanently affixed M3 standoffs that act as mechanical and electrical...

In Stock
[Buy Now](https://www.adafruit.com/product/4367)
[Related Guides to the Product](https://learn.adafruit.com/products/4367/guides)
### USB cable - USB A to Micro-B

[USB cable - USB A to Micro-B](https://www.adafruit.com/product/592)
This here is your standard A to micro-B USB cable, for USB 1.1 or 2.0. Perfect for connecting a PC to your Metro, Feather, Raspberry Pi or other dev-board or microcontroller

Approximately 3 feet / 1 meter long

In Stock
[Buy Now](https://www.adafruit.com/product/592)
[Related Guides to the Product](https://learn.adafruit.com/products/592/guides)

## Related Guides

- [Adafruit Circuit Playground Bluefruit](https://learn.adafruit.com/adafruit-circuit-playground-bluefruit.md)
- [Adafruit Circuit Playground TFT Gizmo](https://learn.adafruit.com/adafruit-tft-gizmo.md)
- [Introducing Adafruit CLUE](https://learn.adafruit.com/adafruit-clue.md)
- [AdaBox 014](https://learn.adafruit.com/adabox014.md)
- [CLUE Metal Detector in CircuitPython](https://learn.adafruit.com/clue-metal-detector-circuitpython.md)
- [No-Touch Hand Wash Timer for Circuit Playground Express and CLUE](https://learn.adafruit.com/no-touch-hand-wash-timer-for-cpx-and-clue.md)
- [CLUE Slim Case](https://learn.adafruit.com/clue-slim-case.md)
- [Now Playing: Bluetooth Apple Media Service Display](https://learn.adafruit.com/now-playing-bluetooth-apple-media-service-display.md)
- [Bluetooth TV Zapper](https://learn.adafruit.com/bluetooth-tv-zapper.md)
- [PyLeap Button Controlled NeoPixels for Circuit Playground Bluefruit](https://learn.adafruit.com/pyleap-buttons-neopixels.md)
- [Frozen-Inspired Animated Pendant with Temperature Sensing](https://learn.adafruit.com/frozen-gizmo-pendant-with-temperature-sensing.md)
- [Bluefruit TFT Gizmo ANCS Notifier for iOS](https://learn.adafruit.com/ancs-gizmo.md)
- [Chauncey the Flower Care Bot with CLUE and Bonsai Buckaroo](https://learn.adafruit.com/chauncey-flower-watering-bot-clue.md)
- [Which CircuitPython Board is Right for You?](https://learn.adafruit.com/choose-your-circuitpython-board.md)
- [CLUE Step Counter](https://learn.adafruit.com/clue-step-counter-st-lsm6ds33.md)
- [How to use the Puppet Module in the Bluefruit Playground App](https://learn.adafruit.com/how-to-use-the-puppet-module-in-the-bluefruit-playground-app.md)
- [Neopixel Crystal Chandelier with CircuitPython Animations and Speed Control](https://learn.adafruit.com/neopixel-crystal-chandelier-with-circuitpython-animations-and-speed-control.md)
