Aidan Mala

IT professional

Tag: Software engineering

  • Good old diagonal Sudoku solution in C#

    Good old diagonal Sudoku solution in C#

    Today I was scrolling HackerRank for some interesting problems to solve and I stumbled across something I thought could challenge me for an evening. The following is the description for the challenge:

    Given a 9×9 grid with empty cells marked as 0, fill the grid so that each row, column, 3×3 block, and both main diagonals contain numbers 1 to 9 exactly once.

    Once I realised the posted solutions were wrong and the test cases published were invalid, I found a nice cafe in North Vancouver (shout out 49th parallel for elite wifi!) and dug my teeth in to fix that.

    To ensure I actually learn something, all was written by hand (as is the theme of things posted on my website)

    Solution Overview

    Lets start with the intuition behind my solution to give you an idea of what in general is happening at each step:

    1. Start with a sudoku grid state
    2. Update all internal sets that represents columns, rows, squares, and diagonals
    3. Find positions in the grid that have a missing value
    4. For a given position with a missing value, find all candidate values that could go there
    5. For each candidate, update the grid and repeat the process
    graph LR
        %% Node Definitions
        Start(["Start: Sudoku Grid State (Input)"])
        Step1["Update Internal Sets:<br>Rows, Columns, Squares, and Diagonals"]
        Step2{"Found a<br>Position with<br>Missing Value?"}
        SolvedGrid(["Sudoku Puzzle Solved / Success"])
        
        GetPos["Get Position (i, j)"]
        Step3["Find All Valid<br>Candidates for (i, j)"]
        CheckCandidates{"Candidates<br>Exist?"}
        Backtrack1(["Return Null"])
    
        subgraph RecursiveCall["Loop"]
            Step4["For Each candidate value at a given position:"]
            RecursiveStep["Update Grid: <br>Grid[i, j] = candidate<br>2. Call solve(Updated Grid)"]
            ResultCheck{"Did recursive<br>call find solution?"}
            Reset["Continue Loop"]
        end
    
       
    
        %% Connections
        Start --> Step1
        Step1 --> Step2
        Step2 -- No --> SolvedGrid
        Step2 -- Yes --> GetPos
        GetPos --> Step3
        Step3 --> CheckCandidates
        CheckCandidates -- No --> Backtrack1
        CheckCandidates -- Yes --> Step4
    
        Step4 --> RecursiveStep
        RecursiveStep --> ResultCheck
        RecursiveStep --> Start
        ResultCheck -- No --> Reset
        Reset --> Step4
    
        
     
    
        %% Styling 
        classDef startEnd fill:#fff5cc,stroke:#d4b35e,stroke-width:1px;
        classDef step fill:#cce5ff,stroke:#85a8cc,stroke-width:1px;
        classDef decision fill:#d9f2d9,stroke:#90c290,stroke-width:1px;
    
        class Start,SolvedGrid,Backtrack1,ReturnSuccess,ReturnFailure startEnd;
        class Step1,GetPos,Step3,Step4,RecursiveStep,Reset step;
        class Step2,CheckCandidates,ResultCheck decision;
        
        style RecursiveCall fill:#f5e6fa,stroke:#c299cc,stroke-width:1px;
    

    Helper Functions

    Let’s start with some simple helper functions that solve small but important bits of this solution.

    Square ID

    Beginning with a function that maps a given position on a 9×9 grid to its corresponding “ID” on a 3×3 grid. All positions in the first 3×3 square in the top left of the 9×9 grid are given a square Id of ‘0’ and all the positions the last 3×3 square in the bottom right of the 9×9 grid are given a square ID of ‘8’:

    static int getSquareId(int row, int col)
    {
        return (row / 3) * 3 + (col / 3);
    }
    

    Create sets from grid

    We want to be able to search what number(s) from 1-9 are not present in at a location in our sudoku grid. My solution requires the rows, columns, squares, and diagonals to be in sets, because it lets us do a neat trick to solve this problem which I will explain further on. So I have a function that does this list to set conversion, noting that additionally, if a ‘0’ value is found, it adds this location to a list of missing values.

    static (List<HashSet<int>> squares,
                List<HashSet<int>> columns,
                List<HashSet<int>> rows,
                List<HashSet<int>> diagonals,
                List<(int,int)> missingList) 
                createSetsFromGrid(List<List<int>> grid)
            {
                List<HashSet<int>> squares = new List<HashSet<int>>();
                List<HashSet<int>> columns = new List<HashSet<int>>();
                List<HashSet<int>> rows = new List<HashSet<int>>();
                List<HashSet<int>> diagonals = new List<HashSet<int>>(); 
                // 0 is leading diagonal
    
                List<(int,int)> missingList = new List<(int,int)>();
            
                // We initialise two hashsets for the two diagonals
                diagonals.Add(new HashSet<int>());
                diagonals.Add(new HashSet<int>());
                
                // Iterate over each element, adding to respective set
                for (int i = 0; i < grid.Count(); i ++)
                {
                    List<int> row = grid[i];
                    rows.Add(row.ToHashSet());
                    
                    for (int j = 0; j < grid.Count(); j++)
                    {
                        
                        int element = row[j];
                        
                        if (element == 0)
                        {
                            missingList.Add((i,j));
                        }
                        
                        // will create 9 sets representing each column
                        if (i == 0)
                        {
                            columns.Add(new HashSet<int>());
                        }
                        
                        columns[j].Add(element);
                        
                        // Build the squares hashset
                        int squareSelection = getSquareId(i, j);
                        
                        // will create 9 sets representing each 3x3 square
                        if (squareSelection >= squares.Count())
                        {
                            squares.Add(new HashSet<int>());
                        }
                        squares[squareSelection].Add(element);
                        
    
                        // Adds value to diagonal
                        if (i == j) {
                            diagonals[0].Add(element);
                        }
                        if (i == 8 - j)
                        {
                            diagonals[1].Add(element);
                        }
                    }
                }
                
                return (squares, columns, rows, diagonals, missingList);
            }
    

    It’s important to note that a location is mapped to at minimum 3 sets, possibly 4 sets if it is on the diagonal. For example, position row = 0 and column = 8 will be mapped to the rows[0], columns[8], squares[2],diagonal[1].

    Find Candidates

    This is where the magic of our efforts to convert lists to sets pays off. To know what possible values can go on a missing square can be thought of by a Venn diagram. We start with all possible values for a spot (1-9) and remove options based on our columns, sets, rows, and (maybe) the diagonal.

    A={1,2,3,4,5,6,7,8,9}A = \{1,2,3,4,5,6,7,8,9\}
    A(RCS)A \setminus (R \cup C \cup S)

    If we subtract the union of all three sets on the left to the set of all values on the right, the remaining set of values meets our criteria of only being in a row, column, and square a single time. We can get this behaviour using the `ExceptWith` method on our sets.

    HashSet<int> getCandidates(
                List<HashSet<int>> squares,
                List<HashSet<int>> columns,
                List<HashSet<int>> rows,
                List<HashSet<int>> diagonals,
                int row, int col, 
                HashSet<int> cache = null)
            {
                int i = row;
                int j = col;
                
                HashSet<int> candidates = new HashSet<int>() {1, 2, 3, 4, 5, 6, 7, 8, 9};
                
                // subtract rows, cols, squares from candidates set
                candidates.ExceptWith(rows[i]);
                candidates.ExceptWith(columns[j]);
                candidates.ExceptWith(squares[getSquareId(i, j)]);
                
                // only subtract diagonal set if value lies on diagonal
                if (i == j) candidates.ExceptWith(diagonals[0]);
                if (i == 8 - j) candidates.ExceptWith(diagonals[1]);
                    
     
                
                
                return candidates;  
            }
    

    Note we only subtract the diagonal set if the position we are finding our candidates on lies on the leading or trailing diagonal.

    Solve

    With all our helper functions out the way, we can now start to solve the sudoku puzzle. The general loop is as follows:

    1. Use our helper function to create sets from a given grid state
    2. Get the position of an empty cell
    3. Use our helper function to find all candidates for the empty cell
    4. Iterate through each candidate, updating the grid with the value
    5. Call the solve method on the updated grid until a solution is found
    List<List<int>>? solve(List<List<int>> grid, Dictionary<(int, int), HashSet<int>> cache) 
            {
                (List<HashSet<int>> squares, List<HashSet<int>> columns, List<HashSet<int>> rows, List<HashSet<int>> diagonals, List<(int,int)> missingList) = createSetsFromGrid(grid);
                
                // solution has been found
                if (missingList.Count == 0) return grid;
                
                // get position to find candidates for
                (int i, int j) = missingList[0];
                
                HashSet<int> candidates = getCandidates(squares,columns,rows,diagonals, i, j);
                
                // If no candidates and there are missing values, return
                if (candidates.Count == 0)
                {
                    return null;
                }
                
                
                foreach (int candidate in candidates.AsEnumerable())
                {
                    grid[i][j] = candidate;
                    List<List<int>>? candidateGrid = solve(grid, cache);
                    if (candidateGrid != null) return candidateGrid;
                    else continue;
                }
                
                // If no solutions have been found, reset the position
                grid[i][j] = 0;
                return null;
                
            }
    

    A few things to note:

    • We can assume the grid is solved when there are no more missing values
    • If execution reaches to a point where there are missing values but there are no candidates for a missing value, there is no solution to this ‘path’

    Final solution

    All together there is a reasonably fast way to solve a sudoku puzzle

        public static List<List<int>> completeDiagonalSudokuGrid(List<List<int>> grid)
        {
    
            
            static int getSquareId(int row, int col)
            {
                return (row / 3) * 3 + (col / 3);
            }
            
            // Builds data structures and finds missing values
            static (List<HashSet<int>> squares,
                List<HashSet<int>> columns,
                List<HashSet<int>> rows,
                List<HashSet<int>> diagonals,
                List<(int,int)> missingList) 
                createSetsFromGrid(List<List<int>> grid)
            {
                List<HashSet<int>> squares = new List<HashSet<int>>();
                List<HashSet<int>> columns = new List<HashSet<int>>();
                List<HashSet<int>> rows = new List<HashSet<int>>();
                List<HashSet<int>> diagonals = new List<HashSet<int>>(); // 0 is leading diag
                List<(int,int)> missingList = new List<(int,int)>();
            
                // two hashsets for diagonal
                diagonals.Add(new HashSet<int>());
                diagonals.Add(new HashSet<int>());
                
                for (int i = 0; i < grid.Count(); i ++)
                {
                    List<int> row = grid[i];
                    rows.Add(row.ToHashSet());
                    
                    for (int j = 0; j < grid.Count(); j++)
                    {
                        
                        int element = row[j];
                        
                        if (element == 0)
                        {
                            missingList.Add((i,j));
                        }
                        
                        if (i == 0)
                        {
                            columns.Add(new HashSet<int>());
                        }
                        
                        columns[j].Add(element);
                        
                        // Build the squares hashset
                        int squareSelection = getSquareId(i, j);
                        
                        
                        if (squareSelection >= squares.Count())
                        {
                            squares.Add(new HashSet<int>());
                        }
                        squares[squareSelection].Add(element);
                        
    
                        
                        if (i == j) {
                            diagonals[0].Add(element);
                        }
                        if (i == 8 - j)
                        {
                            diagonals[1].Add(element);
                        }
                    }
                }
                
                return (squares, columns, rows, diagonals, missingList);
            }
    
            
            // build hashsets from input
            
            
            // Gets candidates for a specific position on the grid
            HashSet<int> getCandidates(
                List<HashSet<int>> squares,
                List<HashSet<int>> columns,
                List<HashSet<int>> rows,
                List<HashSet<int>> diagonals,
                int row, int col)
            {
                int i = row;
                int j = col;
                
                HashSet<int> candidates = new HashSet<int>() {1, 2, 3, 4, 5, 6, 7, 8, 9};
    
                candidates.ExceptWith(rows[i]);
                candidates.ExceptWith(columns[j]);
                candidates.ExceptWith(squares[getSquareId(i, j)]);
                if (i == j) {
                    candidates.ExceptWith(diagonals[0]);
                }
                if (i == 8 - j)
                {
                    candidates.ExceptWith(diagonals[1]);
                } 
                
                
                return candidates;  
            }
            
    
            
            
            
            
            
            List<List<int>>? solve(List<List<int>> grid) 
            {
                (List<HashSet<int>> squares, List<HashSet<int>> columns, List<HashSet<int>> rows, List<HashSet<int>> diagonals, List<(int,int)> missingList) = createSetsFromGrid(grid);
                
                if (missingList.Count == 0) return grid;
                
                (int i, int j) = missingList[0];
                
                HashSet<int> candidates = getCandidates(squares,columns,rows,diagonals, i, j);
                
                if (candidates.Count == 0)
                {
                    return null;
                }
                
                
                foreach (int candidate in candidates.AsEnumerable())
                {
                    grid[i][j] = candidate;
                    List<List<int>>? candidateGrid = solve(grid);
                    if (candidateGrid != null) return candidateGrid;
                    else continue;
                }
                
                grid[i][j] = 0;
                return null;
                
            }
            
            List<List<int>>? solvedGrid = solve(grid);
            
            return solvedGrid;
            
        }
    
    }
    

  • Applied ARM-Assembly Software Engineering: A Pure Arm Implementation of Pong on Micro:bit

    Applied ARM-Assembly Software Engineering: A Pure Arm Implementation of Pong on Micro:bit

    This was written 100% by hand without any assistance from our silicon based overlords.

    Initialisation

    .syntax unified
    .global main
    
    .type main, %function
    main:
      
      initialise:
        @ Set all pins on microbit to output
        bl initialise_output_pins
        
        @ start animation on death and startup
        game_start:
        @ print heart on
        ldr r1, =heart_on
        ldr r0, [r1]
        ldr r1, =game_state
        str r0, [r1]
        mov r0, 0xfff
    
        pi_1:
          cmp r0, 0
          beq exit_pi_1
          bl print_game
          sub r0, 1
          b pi_1
        exit_pi_1:
    
        @ print heart off
        ldr r1, =heart_off
        ldr r0, [r1]
        ldr r1, =game_state
        str r0, [r1]
        mov r0, 0xfff
    
        pi_2: 
          cmp r0, 0
          beq exit_pi_2
          bl print_game
          sub r0, 1
          b pi_2
        exit_pi_2:
    
        @ set the game state to intended initial gamestate
        ldr r1, =reset_game_state
        ldr r0, [r1]
        ldr r2, =game_state
        str r0, [r2]
    
        mov r4, 4 @ Direction for ball  between 0 and 5 inclusive
        mov r5, 6 @ Position for ball between 0 and 24 inclusive
        mov r6, 2 @ Direction for paddle between 0 and 1 inclusive
        mov r7, 1 @ Position for paddle between 0 and 4 inclusive
          @ note: that the position of the paddle is of the left pixel
          @       so the other part of the paddle will always be one
          @       to the right.
        
        push {r4-r7} @ Store values
    
    

    Game loop

      play_game:
    
        mov r0, 0x2000 @ good pause amount
    
        pause_g: @pause and print the game
          cmp r0, 0
          beq exit_g
          bl print_game
          sub r0, 1
          b pause_g
        exit_g:
    
    
        bl step_game @ step the entire game
        
        @ checks if game hase been lost, if it has restart
        cmp r5, 4
        ble game_start
    
        @ else:
        b play_game
    

    Update the game state

    step_game:
      @ steps the entire game to its next logical state
    
        pop {r4-r7} @ get position and direction values
        push {lr}
       
        step_ball:
        @ Move ball according to the direction register
    
          .type move_ball, %function
          @ args:
          @   r4: direction of ball
          @   r5: position of ball
          @   r6: direction of paddle
          @   r7: position of paddle
          move_ball:
    
            bl check_game
    
            cmp r4, 0
            beq move_up_left
            cmp r4, 1
            beq move_up
            cmp r4, 2
            beq move_up_right
            cmp r4, 3
            beq move_down_left
            cmp r4, 4
            beq move_down
            cmp r4, 5
            beq move_down_right
    
            move_up_left:
              mov r3, 6
              b change_gamestate_b
    
            move_up:
              mov r3, 5
              b change_gamestate_b
            
            move_up_right:
              mov r3, 4
              b change_gamestate_b
    
            move_down_left:
              mov r3, -4
              b change_gamestate_b
    
            move_down:
              mov r3, -5
              b change_gamestate_b
            
            move_down_right:
              mov r3, -6
              b change_gamestate_b
    
            .type change_gamestate_b, %function
            @ args:
            @   r3: how much to change ball position by
            change_gamestate_b:
              
    
              ldr r1, =game_state
              ldr r2, [r1]
              push {r1}
            
              @ make a variable to remove position of last ball
              mov r0, 0b1
              mov r1, 24
              sub r1, r5
              lsl r0, r1
    
              eor r2, r0 @ remove last position of ball in game state
    
              add r5, r3 @ changes the position of the ball
    
              @ add new position of ball to game state
              mov r0, 0b1
              mov r1, 24
              sub r1, r5
              lsl r0, r1
              orr r2, r0
    
              pop {r1}
              str r2, [r1] @ store the game back into memory
    
    

    Update the paddle

        step_paddle:
        @ If paddle can move:
        @   move paddle
        @ Else:
        @   step timer
          cmp r6, 0
          beq move_left_p
          cmp r6, 1
          beq move_right_p
          cmp r6, 2
          beq no_move_p
          
          move_left_p:
            ldr r1, =game_state
            ldr r2, [r1]
            push {r1}
          
            @ makes a copy of r7 to alter
            mov r3, r7
            sub r3, 1
    
            @ removes the right side of the paddle from game state
            mov r0, 0b1
            mov r1, 24
            sub r1, r3
            lsl r0, r1
            eor r2, r0
    
            @ adds pixel to the left
            sub r1, 2
            mov r0, 0b1
            lsl r0, r1
            orr r2, r0
    
            add r7, 1 @ changes location of the paddle
    
            pop {r1}
            str r2, [r1]
    
            pop {lr}
            push {r4-r7}
            bx lr
          
           move_right_p:
            ldr r1, =game_state
            ldr r2, [r1]
            push {r1}
            @ makes a copy of r7 to alter
            mov r3, r7
    
            @ removes the left side of the paddle from game state
            mov r0, 0b1
            mov r1, 24
            sub r1, r3
            lsl r0, r1
            eor r2, r0
    
            @ adds pixel to the right
            add r1, 2
            mov r0, 0b1
            lsl r0, r1
            orr r2, r0
    
            sub r7, 1 @ changes location of the paddle
    
            pop {r1}
            str r2, [r1] @ store result back
    
            pop {lr}
            push {r4 - r7}
            bx lr
    
          no_move_p:
            pop {lr}
            push {r4-r7}
            bx lr
    

    Handle the ball rebounding off wall/paddle

    Rebounding off the walls

      check_game:
        @ checks if ball is on bound or paddle and changes direction accordingly
    
    
        .type check_ball, %function
        @ args:
        @   r5: position of ball
        @ returns:
        @   r4: adjested direction
        check_ball:
        @ Checks if the ball is on any bound
          
        @ Checks if ball is in top corner
        cmp r5, 20
        beq corner_rebound
        cmp r5, 24
        beq corner_rebound
    
        @ Checks if ball on roof
        cmp r5, 20
        bgt roof_rebound
    
        push {lr}
        @ Checks if ball is on right wall
        cmp r5, 5
        beq right_wall_rebound
    
        cmp r5, 10
        beq right_wall_rebound
        
        cmp r5, 15
        beq right_wall_rebound
    
        @ Checks if ball is on left wall
        cmp r5, 9
        beq left_wall_rebound
    
        cmp r5, 14
        beq left_wall_rebound
    
        cmp r5, 19
        beq left_wall_rebound
    
        @ if not on the edge, check if ball is on paddle
        b check_paddle 
    
        corner_rebound:
          push {lr}
          bl random_number @ Generate random number
          pop {lr}
          @ See if random number is odd
          mov r1, 0b1
          tst r0, r1
          bne set_down
          b set_opp
    
          set_down:
            @ set direction of ball to down
            
            mov r4, 0x4
            b check_paddle_pos @ returns to step ball
          
          set_opp:
            @ if direction up right, set direction down left
            cmp r4, 2
            beq set_down_left 
            
            @ else set the direction to down right
            mov r4, 5
            b check_paddle_pos @ returns to step ball
    
            set_down_left:
            mov r4, 3
            b check_paddle_pos @ returns to step ball
        
        roof_rebound:
          add r4, 3 @ changes direction of ball to the downwards (in same direction)
          b check_paddle_pos @ returns to step ball
    
        left_wall_rebound:
        @ makes sure when rebounding off wall it flips with direction
          pop {lr} @ needs to pop this as was not used (so later on can recall to main thred)
    
          cmp r4, 3
          blt lw_rebound_up @ sees if the ball is going up or down
    
          @ if it is going down, set to down rebound
          cmp r4, 4
          beq check_paddle_pos @ if ball going down directly, don't rebound
    
          mov r4, 5
          b check_paddle_pos 
    
            @ else set to up rebound
            lw_rebound_up:
    
              cmp r4, 1
              beq check_paddle_pos @ if ball going up directly, don't rebound
    
              mov r4, 2
              b check_paddle_pos
    
        
        right_wall_rebound:
        @ makes sure when rebounding off wall it flips with direction
          pop {lr} @ needs to pop this as was not used (so later on can recall to main thred)
    
          cmp r4, 3
          blt rw_rebound_up @ sees if the ball is going up or down
    
          @ if it is going down, set to down rebound
          cmp r4, 4
          beq check_paddle_pos @ if ball going down directly, don't rebound
    
          mov r4, 3
          b check_paddle_pos 
    
            @ else set to up rebound
            rw_rebound_up:
              cmp r4, 1
              beq check_paddle_pos @ if ball going up directly, don't rebound
    
              mov r4, 0
              b check_paddle_pos
    
    

    Rebounding off the paddle

    
    
        check_paddle:
        @ Checks if the ball is on the paddle
        @ If it is:
        @   go direction according to which pixel the ball hits on the paddle and if the paddle is moving
        
          mov r1, r7 @ coppies pos of paddle to scratch register
    
          add r1, 4 @ changing value of r1 to compair it to pssilble row
          cmp r1, r5 @ checks if ball is on paddle, left side
          IT eq
          bleq paddle_rebound
    
          add r1, 1
          cmp r1, r5 @ checks if ball is on paddle, right side
          IT eq
          bleq paddle_rebound
    
          bl check_paddle_pos
    
          pop {lr} @ return to step ball
    
          bx lr
    
          paddle_rebound:
            @ ball rebound from paddle
            @ changes direction of ball to something random
            push {lr}
            bl random_number @ Generate random number
            pop {lr}
            @ See if random number is odd
            mov r1, 0b1
            tst r0, r1
            bne set_up_p @ change the direction of the ball to directly up
            b set_away_p @ set the direction of the ball to logically opposite
    
            set_up_p:
              mov r4, 1
              bx lr
            
            set_away_p:
              @ checks if ball is heading down
              cmp r4, 4
              beq mix_dir @ if it is make sure it doesn't head straight up
    
              sub r4, 3 @ else make it go opposite direction
              bx lr
              
              mix_dir:
                @ set ball going left
                mov r4, 0
                bx lr
    
    

    Paddle AI

          check_paddle_pos:
            push {lr}
    
            bl ai_dir
    
            @ checks if paddle will try and go into the right wall
            cmp r7, 1
            IT eq
            bleq check_paddle_dir_right
    
            @ checks if paddle will try and go into the left wall
            cmp r7, 4
            IT eq
            bleq check_paddle_dir_left
    
            pop {lr}
            bx lr
    
            check_paddle_dir_right:
              cmp r6, 1
              IT ne
              bxne lr @ return if it is not
    
              @ else set direction to left 
              mov r6, 0
              bx lr
            
            check_paddle_dir_left:
              cmp r6, 0
              IT ne
              bxne lr @ returns if not trying to go into wall
    
              @ else set direction to right
              mov r6, 1
              bx lr
            
            ai_dir:
            @ change direction based on random number gen
              push {lr}
              bl random_number
              pop {lr}
    
              mov r1, 0b1
              tst r0, r1
              bne change_paddle_left
    
              mov r6, 1 @ change paddle direction right (ev)
              bx lr
    
              change_paddle_left:
              mov r6, 0 @ change paddle direction left
              bx lr
    
    

    Random Number Generator

      .type random_number, %function
      @ returns:
      @      r0: randomly generated number
    
      random_number:
        ldr r1, =0x4000D000 @ Base address to RNG
        mov r0, 0b1
        str r0, [r1] @ starts random number genorator
        mov r0, 0xffff
        pause_r:
          cmp r0, 0
          beq exit_r
          sub r0, 1
          b pause_r
    
        exit_r:
        @ stops rRNG
        mov r0, 0b1 
        str r0, [r1, 0x4]
        ldr r0, [r1, 0x508] @ stores result of RNG to output
        bx lr
    

    Static Variables

    .data
    game_state:
      .word  0b1100001000000000000000000
    reset_game_state:
      .word  0b1100001000000000000000000
    
    heart_on:
      .word 0b0010001110111111111101010 
    
    heart_off:
      .word 0b0010001010100011010101010