11/16/2024
The three controllers will each have their own micro:bit that will control their respective snake (Red, Yellow, and Blue). Each controller will have a button to launch a projectile (captured egg) to hit other snakes that are blocking an intersection ahead.Â
The controllers work based on their accelerometer measuring the roll of the controller and mapping how it is being held to a value from -3 (LEFT) to +3 (RIGHT). These values are sent over the micro:bit radio to the main micro:bit in the French Cleat that controls the LED strips for the three snake tracks.
10/17/2024
First demo of Cleat Runner in action...Â
Starts in LED rainbow mode. Press micro:bit logo to start game and seed the snakes and start moving them from the three controllers.
10/11/2024
The previous state of the game allowed players to control their snakes to move back and forth, grow when they reach a new end of their track, and crash into each other at intersections. We also implemented logic to move a snake from the top track down to track 1 if the snake on track 1 crashes first, so the two remaining snakes can continue playing on tracks that intersect.
The goal we have been working on over the past several weeks is to handle what to do at the end of a round of the game. Previously, when only one snake was left standing. The game play stopped...
We have now implemented a state-machine that controls the flow of the game to initialize variables according to what phase the game is in (pre-game, rounds 1,2, and 3, transitions between rounds, and the end of the game). The game now includes three rounds of gameplay where the snakes cycle to a new starting track for each round to give each player an opportunity to play on each track.Â
9/25/2024
Started working on score keeping today. First step was to come up with the concept for how the game should be scored.
Scoring concepts
Capture an egg = 3 pts.
Cause another snake to collide with your body = 2 pts.
Round survival
Last snake alive = 2 pts
2nd to last snake alive = 1pt
Implementation
Initialized a snakeScore array for each snake's score to be tracked. Start at score of 1 and earn points based on scoring concepts detailed above.
Egg captured happens when canScoreLeft or canScoreRight is TRUE and snake reaches that end of their current track-> Call growSnake. growSnake now includes a 3 pt score command.
To implement awarding points for causing a collision we needed to restructure isPixelBlocked function. Now this function returns the snake number (0,1, or 2) if a snake is blocking an intersection when a new snake enters (dies). If the intersection is not blocked, we now return -1 (previous it returned 0).
DisplayScore function is used to show the score of each snake. The 5x5 matrix cycles through the number of each snake 0, 1, and 2 and shows a bar graph of that snakes score (in that snake's body color) on track 0 (straight track at top of cleat). After cycling through each individual snake's score. We implemented a bar graph of the proportion of each snake's points compared to the total of all snake's points. Future goal is to make the winning snakes LEDs flash along with that snake's number on the 5x5 matrix.
8/22/2024
We were seeing some weird behavior at the far right intersection between track 0 and track 1, where snakes were not responding as if they were colliding, even though they "should" have been hitting each other. After some scrutiny, we discovered a small typo in the translation between the hand drawn sketch of intersections and the prettier one I made in Illustrator. I misread the pixel number as 64, but it really was 54. I have corrected that on the LED map shown below and, more importantly, in the array in the code that tracks the intersection pixels.
Screenshot of the initial coding exploration with a focus on using an array in Makecode to store info on the state of the snake.
8/22/2024
This spawnSnake function is used to "safely" place a snake on the selected track in a place that doesn't extended beyond the edges of that track and doesn't collide with an existing snake that is occupying an intersection with the selected track. We first set the variable spawnSuccess to false and repeat the subsequent loop until the spawn succeeds and flip that boolean to true, thus ending the while loop.
The while loop uses the track information to attempt to randomly place the snake's head along the snake away from the ends of that track. It also randomly selects a direction of 0 or 1 which is translated to -1 for left and 1 for right. Then based on that information, we calculate and store the left and right edge pixels for the candidate snake location. We then loop through all of the pixels between the left and right edge of the snake and check to see if those pixels are blocked with our new isPixelBlocked function (described above). If we aren't able to place the snake at it's current location we continue in the while loop until we succeed and flip the spawnSuccess boolean to true.
8/22/2024
This new isPixelBlocked function is a refactor of the former checkIntersectionStatus function. The new function takes two parameters (candPixel, candTrack). This allows us to check if a snake is entered an occupied intersection pixel on it's currentTrack. This allows more flexibility for checking for collisions than we had previously with checkIntersectionStatus because now snake's will be moving between tracks and we need to know which track they are on to determine if a given pixel they are entering is blocked.
8/22/2024
snakeFuneral is called within the onCommandReceived function (see below) that is responsible for interpreting commands that are received from the players' controllers. When a snake collides with another snake, snakeAlive is set to 0, meaning dead. Then we call snakeFuneral to handle this event.
First we showSnake on the snake that died, to make it disappear. It has already been set to snakesAlive = 0, therefore showing it will make it display only black (off) pixels.
Then we see if the snake that died was on track 1 (middle track). If the deceased snake was on Track 1, we see which snake is on Track 0, temporarily set it to dead (snakesAlive = 0) so it will disappear and then set it back to alive, change it's length to double it's previous length (because the pixel density on Track 0 is much lower, and we want to make it look about the same size on the new higher density track 1).Â
Finally, we call our new spawnSnake function to place the new snake in a "safe" non-obstructed location on track 1 and then display it with showSnake.
One of the biggest challenges for me as I've been thinking through the game is how to handle what happens when two snakes collide at one of the 10 intersection points of the 3 tracks. We have been thinking through issues with the game concepts of what should happen when a snake gets to an occupied intersection (second snake dies!) and how to make it happen in code. Today we started coding the function to determine if a specific intersection is occupied by a snake on another track and what to do if that happens.
Conceptually, we have made a plan for what to do when two snakes collide.
If the snake on the "middle" track (track1) dies, the other two snakes will never collide because only track1 intersects the other two tracks. So from a game plan perspective, this is a problem. For now, the plan is for the two snakes to move to track1 (middle) and track2 (bottom) so there are still places for the two snakes to interact. If one of the other snakes dies first, then the snake on track1 can still interact with the remaining snake on track0 or track2 for the rest of the round.
This enteredOccupiedIntersection? function checks whether the current snake that has been sent a command (and therefore passed into this function as a parameter).
For the given snake we need to see if the snake is on Track 0, 1, or 2. The if/else statements are run based on the track of the current snake being evaluated.
We set currentIntersectionIndex to the index of the intersection that the snakePositionOfHead is touch for the current snake. The "find index of" block will either return the position in the appropriate intersections array where the head is currently located, or return -1 if the head isn't in an intersection on their current track.
We use blockingSnakeIndex to get the direction of the snake that is potentially in the intersection.
If the direction = 1, the blocking Snake is facing RIGHT and we use compound AND statements to determine if the blocking snake is in the intersection (based on the positionOfHead and snakeLength).
If the direction = -1. we are moving LEFT, the math is slightly different, so it has a separate set of conditional statements to determine if the snake is blocking that intersection.
8/15/2024 - Overview of the code as of now. There have been lots of updates in the last week. Details below...
Updated 8/15/2024
onCommandReceived is the function that is responsible for handling commands as they come in to the main Micro:bit from the three player controllers.
There have been several updates in the last week...
Updated 8/15/2024
The second part of onCommandReceived is responsible for controlling how a snakes takes steps forward.
Magnitude = absolute value of the movement parameter
We loop through the next section for each pixel we WANT to TRY to move (based on Magnitude of the movement). There is no guarantee that there is enough space on the strip for a snake to move 3 spaces, but it might be able to move 1 or 2 spaces.Â
August 5, 2024
Glenn suggested that we rethink how we are storing the attributes of each snakes and try to design something that will be easier to understand when revisiting the code or explaining it to students. In the previous version, there was an array for each snake that held all of the attributes for that snake (head position, length, direction, color, etc.) Then there was a second array that held each snake (thus a 2D array). This ended up being very confusing when dereferencing bits of data to use in formulas and made thinking about the code much more confusing than we had hoped for.Â
Glenn proposed instead making an array for each attribute (snakeHeadPosition, snakeDirection, snakeColor, etc) and storing the characteristics for each aspect of each snake in these containers. This makes the naming system much more readable and easier to use. For example snakeColor[0] would store the color of snake0.
Here's the link to the updated Cleat Runner v2 game.
August 2, 204
Met with Glenn today and reviewed code we worked on earlier this week. Cleaned up a few things to make the code easier to understand and removed some code that was extraneous.
Created a moveSnake function with a currentSnake parameter to allow the program to pass a specific snake and move based on current position of head and direction of motion.
August 2, 2024
This was the bulk of the work Glenn and I made today. The purpose of this function is to take passed parameters of which snake is moving and a direction of movement and adjust the data for that snake to account for the requested motion.Â
First we check to see if the direction has changes or not. If the direction changed, we need to swap the location of the positionOfHead to the other side of that snake.
Second we check to see if a snake is moving into an open area or is already at an edge (and therefore cannot move any farther). If a snake is moving left we check to see if position 0 is open on that track. If a snake is moving right we check to see if the far right position of the track (1 less than that track's length) is open or not. If the space is open, we call moveSnake to move one space in the current direction.
Click photo above or here for video of snakes in action. Controlled with two micro:bits!
This is the updated onStart function that creates and initializes the variables and arrays for the Cleat Runner v2 game. As you can see, each array is given three values either by manually setting them or by using a for loop to set their values based on which snakeIndex we are on. At the end of onStart, all of the variables and arrays have been set and the initLEDs function is called to create the data structure to display the snake data on the LED strips.
The updated onCommandReceived function takes advantage of the new array data structure and is much more readable. snakeIndex is passed to this function which identifies which snake we are working on this makes the variables and code easier to understand.
Thanks again to Glenn for figuring out how to create an array of LED strips so that we can loop through the code for each strip instead of rewriting the same code three times!
These simple functions allow for setting the specific snakeHead and snakeBody colors based on each Snake's color of 0, 1, 2.
This function uses the stripArray to set the correct strip for each snake to the correct head and body colors.
Here's a screenshot of the snakes being displayed on the LED strips in the Makecode simulator. The snakes can be moved back and forth using the A and B buttons. When a snake reaches an edge it can "grow" by one pixel.
Next steps....
Consider adding other SnakeCharacteristics array
snakeAlive = set to 1 at init. set to 0 at collision with another snake
snakeGrowthAmt = set to 1 for snakes on track 0. set to 2 for snakes on track 1 and track 2. Will make snakes grow more proportionally to each other in terms of actual length of growth for each point earned. For now, I have made the initial length of the snake on track 0 = 1/2 the length of the other two snakes to make them appear to be more similar in overall length
snakeScore = set to 0 at start. Score could be separate from length or could be length. Snakes could start back at an initial length for each round or they could retain length from when they died in last round
Start working on collisions at intersections. This is what made me think about snakeAlive array.
Start thinking about how to respawn snakes onto other tracks for round 2 and round 3 of game.
Start working on score
Start working on controllers
8/15/2024
We moved the arrays that store a variety of important data about each track into this initTracks function.
trackLengths array stores the # of pixels for Track 0,1,2
trackFrictionCoefficients array is not currently used (could allow for different movement / speed based on pixel density differences for the track0 vs track1+2
set Track(0->1)Intersections stores the pixel location on track 0 for where it crosses track 1.
set Track(1->0)Intersections stores the pixel location on track 1 for where it crosses track 0.
set Track(1->2)Intersections stores the pixel location on track 1 for where it crosses track 2.
set Track(2->1)Intersections stores the pixel location on track 2 for where it crosses track 1.
July 31, 2024
Over the last several years, I have been very fortunate to have the opportunity to learn from a friend and mentor who has served as our Engineer in Residence at Dawson. Glenn Salaman is that friend and I reach out to him to see if he would be interested in talking through the concept of the game and help me think about how to design the data structure for the game to store all the key information. Having worked with him on multiple programming projects in the past, I was able to explore using Makecode's array blocks enough to fill the array with an initial set of snake position data and I had been moving the "snake" by shifting the data around in the array. That is what the previous post and Makecode project called Array Playground was for...
Glenn and I spent some time yesterday talking through all of the information I would like to store about each snake and came up with a 2 dimensional array concept to store the following data for each snake - (Position, Length, Direction, CanScoreLeft?, CanScoreRight?, Color, Track). Each snake would be an array with these 7 pieces of information and then there would be a higher level array of SnakeData that holds each of the individual snakes with their respective 7 elements.
July 31, 2024
The screenshot to the right shows how the arrays are constructed...
trackLengths stores the number of pixels in each of the three tracks.
trackFrictionCoefficients will be used later to level the playing field of how snakes move on each track based on the physical properties of the track (LED density)
set snake0, snake1, snake2 creates three empty array containers to store the snake data later
snakesData is the larger high level array container to hold each individual snake
July 31, 2024
We used a for loop to cycle through each snake and store the initial values for the characteristics of each snake. In order to do so, we created a tempSnake array and set it to the empty array for the current index of the loop. So we first set tempSnake to Snake0 and begin to initialize the characteristics of this first snake.Â
We select a random number (0 or 1) to choose initail direction and then include a small loop to convert the value to -1 if 0 was selected. This will allow us to use the -1 or +1 value to move the snake.
The tempSnake is then filled with a random starting pixel, a length of 1, the direction that was selected (-1 or 1), both CanScoreLeft? and CanScopreRight? are set to 1 because at first each snake can score on either end. The snake color is set to the current index of the for loop, (0 will mean red, 1 will mean yellow, 2 will mean blue), and the track will be set by the index as well (0,1,2).
Finally the values in the tempSnake are written into the correct snake array based on the index (snake0, then snake1, and finally snake2).
Makecode link: https://makecode.microbit.org/S61104-46920-25838-81378 to code shown above....
This game is going to push my coding abilities and challenge me to think about the key features of the game and breakdown how each of those elements will work together to build a working game. Here are the key components that I have identified so far.
Storing the current state of a snake
location
length
direction
edge where it can grow next (it can only grow by touching the opposite side from where it last grew. Except for the first edge touch which can happen at either end).
I began my experimentation for this project by exploring ways to store the current information about the snake. I have used arrays in the past for things involving shifting colors of pixels for LEDs, so that was my natural inclination for this project. I have not used arrays in Makecode, so getting used to the features that are available for constructing, manipulating, and accessing arrays was my first step.
I am experimenting with documenting my learning using a tool called Unrulr. You can check out my Cleat Runner Game journey on Unrulr.
I am trying to map (see below) the intersection points between each strip of LEDs and I wrote some code to help in that process. A user can select strip1, strip2, or strip3, by pressing the A+B buttons. Pressing A or B moves the illuminated LED on each strip Left (A) or right (B). Pressing the capacitive touch micro:bit Logo gives the user a diagnostic readout on the 5x5 matrix which displays which strip is currently being modified and the current illuminated LED position.
https://makecode.microbit.org/S85698-70283-68243-10448
Game idea: I've been trying to conceptualize the concept / gameplay for this French Cleat LED game. In talking with my colleague (Nar!) today we brainstormed a version the classic snake game where each "snake" would live on it's own LED strip. The snakes would move back and forth along their own path and players would control their snakes side-to-side movement using their own micro:bit controller. The controllers could work using the A | B buttons or potentially use the accelerometer to add variability to the speed of the snake based on how far the micro:bit is tilted in either direction.
The goal of the game is to go completely back and forth across the screen as many times as possible without hitting another snake. Each time a snake reaches the opposite side of their strip they get a point and grow by one pixel. There are variety of inequities in this game concept -- the most obvious being that the 2nd strip crosses paths with both the 1st and 3rd strip, but neither the 1st or 3rd ever touch each other. This means that each strip has a different number of potential collisions. (See intersection mapping below)
Strip 1: 4 intersection points
Strip 2: 10 intersection points
Strip 3: 6 intersection points
Another challenge in terms of equity between players (snakes) is that the density of the pixels for the top strip and the other two aren't the same. The lower two strips have more pixels per inch than the top one. This means they will move differently and vary in length as they grow.
Potential solution: Each game consists of three rounds. Each player's snake get one round in each position and the total score is cumulative for the three rounds.