diff --git a/devin_32017267.py b/devin_32017267.py
index a778708cdb6511d2037fd6428aed09ecda694ea7..69dfded23d29083dc8431d7f0a74e10998181cf0 100644
--- a/devin_32017267.py
+++ b/devin_32017267.py
@@ -1,4 +1,6 @@
-from reedsolo import RSCodec, ReedSolomonError
+import math
+from reedsolo import RSCodec
+import numpy
 
 def generate_code(text):
     """Given an input string (can be a plain text message or a URL), converts it into a QR code.
@@ -89,21 +91,26 @@ def __RSencode_bits(data, num_codewords=7):
 def make_matrix(code, version=1):
     """
     Given the complete encoded binary (with metadata, padding & error correction added),
-    generate from it the QR code matrix for a byte-mode QR code.
+    generate from it a QR code matrix containing:
+    - requisite finder, timing & seperator patterns
+    - modules explicitly reserved for formatting info
+    - the data itself
 
     THIS METHOD ONLY WORKS FOR VERSIONS 1 AND 2 QR CODES: IT DOES NOT ADD/ENCODE THE
     VERSION INFORMATION REQUIRED BY HIGHER VERSIONS.
 
     Args:
-        code (String): Binary strinng representing the complete binary encoding of the original string, with metadata, padding & error correction added
+        code (String): Binary strinng representing the complete binary encoding of the original data string, with metadata, padding & error correction added
         version(int): Version of the QR code (version 1 by default)
 
     Returns:
-        int[][]: Binary matrix representing the QR code (0s represent a whi bn vbnte pixel, 1s represent a black pixel)
+        int[][]: Binary matrix representing the QR code (0s represent a white pixel, 1s represent a black pixel, 'R' for any modules reserved for formatting information)
     """
 
     n = 4*(version-1)+21 # Calculate # of pixels on each side from version number
-    QR_matrix = [[0]*n for _ in range(n)] # All squares are white (0) by default. Can be set to black (1)
+    QR_matrix = [['E']*n for _ in range(n)] # All squares are empty 'E' by default. Can be set to white (0) or black (1)
+
+    # |__________________________________ FINDER PATTERNS _______________________________________________________|
 
     # Paint finder patterns  in the topleft, topright and bottomleft corners
 
@@ -128,17 +135,27 @@ def make_matrix(code, version=1):
                 else: # Paint inner square black
                     QR_matrix[y_offset+i][x_offset+j] = 1
     
-    # Add white seperators on edges of finder patters that touch the inside of the QR code
-    # First index of each tuple denotes row idx in matrix for the horizontal part of the seperator,
-    # Second idx denotes column idx for the vertical part
-    seperator_positions = [(7, 7), (7, n-8), (n-8, 7)]
-    for horiz_ypos, vert_xpos in seperator_positions:
-        # Each side of the seperator is 8 bits long
-        for i in range(8): 
-            QR_matrix[horiz_ypos][i] = 0
-        
-        for i in range(8): 
-            QR_matrix[i][vert_xpos] = 0
+    # |_____________________________ SEPERATORS ____________________________________|
+
+    # Add white seperators on edges of finder patterns that touch the inside of the QR code
+    # Each side of the seperator is 8 bits long
+
+    # Fill seperators for topleft finder pattern
+    for i in range(8): 
+        QR_matrix[7][i] = 0
+        QR_matrix[i][7] = 0
+
+    # Seperators on bottomleft
+    for i in range(8):
+        QR_matrix[n-8][i] = 0
+        QR_matrix[(n-8)+i][7] = 0
+
+    # Seperators on topright
+    for i in range(8):
+        QR_matrix[7][(n-8)+i] = 0
+        QR_matrix[i][n-8] = 0
+
+    # |____________________________ TIMING PATTERNS ___________________________________|
 
     # Insert timing patterns
     # Horizontal timing pattern is on 7th row (idx 6) of matrix between the seperators (i.e. from columns (by index) 8 up to [not including] n-8)
@@ -158,7 +175,9 @@ def make_matrix(code, version=1):
     # that is located at coordinate (8, [4*V]+9) for version V
     QR_matrix[(4*version)+9][8] = 1
 
-    # Reserve format information area - set reserved modules as value 'F'
+    # |_______________________________ RESERVE FORMAT INFO _____________________________________|
+
+    # Reserve format information area - set reserved modules as value 'R'
     # Reserve:
     #   Near topleft finder pattern, a 1-module 
     #   Near topright finder pattern, a 1-module horizontal strip below the seperator
@@ -174,23 +193,269 @@ def make_matrix(code, version=1):
             continue
 
         # Reserve horiz. strip
-        QR_matrix[8][i] = 'F'
+        QR_matrix[8][i] = 'R'
     
         # Reserve vertical strip
-        QR_matrix[i][8] = 'F'
+        QR_matrix[i][8] = 'R'
 
     # Reserve horizontal strip near topright finder
     # No dark modules in this area, so no need to check
     for i in range(n-8,n):
-        QR_matrix[8][i] = 'F'
+        QR_matrix[8][i] = 'R'
     
     # Reserve vertical strip near bottomleft finder
     # A single dark module is at the top of this strip, so start iteration at module below it
     for i in range(n-7,n):
-        QR_matrix[i][8] = 'F'
+        QR_matrix[i][8] = 'R'
+
+    # |______________________________________ PLACE DATA BITS _________________________________________________|
 
     # Finally, place the data bits into the matrix
     # Bits are placed starting at the bottom right and proceeding upward in a 2-module wide column
     # When the column hits the top, the next 2-module column starts immediately to the left and continues downward.
     # If a reserved area or function pattern is encountered, data bits are placed in the next unused module
+    i = n-1
+    j = n-1
+    bit = 0
+    going_up = True
+    while j > 0:
+        
+        # Skip over column 7 (idx 6) entirely - this contains the vertical timing pattern
+        if j == 6:
+            j -= 1
+            continue
+        
+        # Change direction & shift to next column if hit top or bottom of matrix
+        if i < 0 or i > n-1:
+            if going_up:
+                going_up = False
+                i += 1
+            else:
+                going_up = True
+                i -= 1
+            j -= 2
+
+        if QR_matrix[i][j] == 'E':
+            QR_matrix[i][j] = code[bit]
+            QR_matrix[i][j-1] = code[bit+1]
+            bit += 2
+
+        if going_up:
+            i -= 1
+        else:
+            i += 1
+
+    return QR_matrix
+
+
+def mask_matrix(QR_matrix, version=1):
+    """
+    Apply module masking (toggling) as appropriate to a given QR matrix
+    The input matrix should have patterns and reserved areas added as required, and data bits inserted.
+
+    THIS FUNCTION ONLY WORKS FOR VERSIONS 1 AND 2 QR CODES
+
+    Args:
+        QR_Matrix (int[][]): Binary matrix representing the QR code (0s represent a white module, 1s represent a black module, 'R' for any reserved modules)
+        version (int): Version of the QR code (version 1 by default)
+
+    Returns:
+        int[][]: Binary matrix representing the QR code with masking applied
+    """
+
+    # Masking determined by applying 8 different masking patterns to the QR matrix
+    # and comparing them. The pattern that gives the lowest 'penalty score' is the one that will be used.
+    
+    masking_patterns = [
+        lambda r, c: (r+c) % 2 == 0,
+        lambda r, c: r % 2 == 0,
+        lambda r, c: c % 3 == 0,
+        lambda r, c: (r+c) % 3 == 0,
+        lambda r, c: (r//2 + c//3) % 2 == 0,
+        lambda r, c: (r*c % 2) + (r*c % 3) == 0,
+        lambda r, c: (r*c % 2) + (r*c % 3) == 0,  
+        lambda r, c: ((r*c % 2) + (r*c % 3)) % 2  == 0,  
+        lambda r, c: (((r+c) % 2) + (r*c % 3)) % 2  == 0
+    ]
+
+    # Find pattern that incurs the minimum penalty
+    min_penalty = math.inf
+    min_penalty_matrix = None
+    for pattern in masking_patterns:
+        masked_mat = __apply_masking_pattern(pattern, QR_matrix)
+        pen = __calculate_mask_penalty(masked_mat)
+
+        if pen < min_penalty:
+            min_penalty = pen
+            min_penalty_matrix = masked_mat
+
+
+    return min_penalty_matrix
+
+
+def __apply_masking_pattern(pattern, QR_matrix, version=1):
+    """
+    Apply, to a QR matrix, a masking pattern defined by a lambda function.
+
+    Args:
+        pattern (Lambda(row, col): Boolean): The mask pattern - A Lambda that returns true if QR_matrix[row][col] is to be toggled, otherwise false.
+        QR_Matrix (int[][]): Binary matrix representing the QR code (0s represent a white module, 1s represent a black module, 'R' for any reserved modules)
+        version (int): Version of the QR code (version 1 by default)
+
+    Returns:
+        int[][]: Binary matrix representing the QR code with masking pattern applied
+    """
+    n = len(QR_matrix)
+
+    for row in range(n):
+        for col in range(n):
+
+            # Skip over timing patterns and reserved areas
+            if (row == 6 or col == 6) or (QR_matrix[row][col] == 'R'):
+                continue
+            # Skip over finder patterns & seperators
+            if (row < 8 and col < 8) or (row < 8 and col > n-9) or (row > n-9 and col < 8):
+                continue
+            # Skip over the dark module
+            if row == n-8 and col == 8:
+                continue
+
+            if pattern(row, col): # Toggle modules that meet pattern condition
+                QR_matrix[row][col] = 1 if QR_matrix[row][col] == 0 else 0
+
+    return QR_matrix
+
+
+def __calculate_mask_penalty(QR_matrix):
+    """
+    Calculates a mask's overall 'penalty' score for a QR code matrix with a masking pattern applied.
+
+    Args:
+        QR_Matrix (int[][]): Binary matrix representing the QR code (0s represent a white module, 1s represent a black module, 'R' for any reserved modules), with a mask pattern applied
+
+    Returns:
+        int: total (summation of 4 individual scores) mask penalty score
+    """
+
+    # 4 Evaluation conditions need to be checked to compute the total penalty
+    # The final penalty is the sum of the penalties calculated for each condition - so we can just keep a running total as we resolve each condition
+    # 
+    # All modules in matrix included - even those reserved or in finder patterns
+    total_penalty  = 0
+    n = len(QR_matrix)
+
+    # |_______________________________ CONDITION 1 ________________________________|
+
+    # Evaluation Condition #1 - penalize for each group of five or more same-colored modules in a row (or column).
+    # Examine this condition w.r.t rows
+    for row in QR_matrix: 
+        curr_module = None
+        curr_module_occurences = 0
+        for module in row:
+
+            if module == curr_module and module != 'R': # Make sure we skip over modules marked as reserved
+                curr_module_occurences += 1
+            else:
+                # If there are five consecutive modules of the same color, add 3 to the penalty
+                # If there are more modules of the same color after the first five, add 1 for each additional module of the same color
+                if curr_module_occurences >= 5:
+                    total_penalty += curr_module_occurences-2
+                curr_module = module
+                curr_module_occurences = 1
+
+    # Same thing, but this time with columns instead of rows
+    for c in range(n):
+        curr_module = None
+        curr_module_occurences = 0
+        for r in range(n):
+
+            module = QR_matrix[r][c]
+            if module == curr_module and module != 'R': # Make sure we skip over modules marked as reserved
+                curr_module_occurences += 1
+            else:
+                if curr_module_occurences >= 5:
+                    total_penalty += curr_module_occurences-2
+                curr_module = module
+                curr_module_occurences = 1
+
+    # |_______________________________ CONDITION 2 ________________________________|
+
+    # Look for areas of same colour that are are least 2x2 modules or larger
+    # For every 2x2 single-colour block , add 3 to the penalty score (also count overlapping 2x2 blocks)
+    for r in range(0, n-1, 2): # iterate in chunks of 2 in both directions - this way we can inspect 2x2 blocks
+        for c in range(0, n-1, 2):
+            # Check if all modules in a 2x2 block are the same color
+            if QR_matrix[r][c] == QR_matrix[r+1][c] == QR_matrix[r][c+1] == QR_matrix[r+1][c+1]:
+                total_penalty += 3
+
+    # |_______________________________ CONDITION 3 ________________________________|
+
+    # This condition looks for sequences of modules that resemble the finder patterns at the corners of the matrix
+    # More specifically, patterns in form dark-light-dark-dark-dark-light-dark that have 4 light modules on either side.
+    # This is done both row-wise and column-wise.
+    # 40 (!) is added to the total penalty for every one of these kinds of pattern found.
+
+    # Pattern can either have 4 white modules at left or right
+    pattern1 = ['1', '0', '1', '1', '1', '0', '1', '0', '0', '0', '0']
+    pattern2 = ['0', '0', '0', '0', '1', '0', '1', '1', '1', '0', '1']
+
+    # Search row-wise first
+    for row in QR_matrix:
+        for i in range(n-10):
+            if (row[i:i+11] == pattern1) or (row[i:i+11] == pattern2):
+                total_penalty += 40
+        
+    # Now search column-wise. To do this, can simply use numpy to
+    # transpose the matrix and apply the above row-search process.
+    mat_transpose = numpy.transpose(QR_matrix).tolist()
+    for row in mat_transpose:
+        for i in range(n-10):
+            if (row[i:i+11] == pattern1) or (row[i:i+11] == pattern2):
+                total_penalty += 40
+
+    # |_______________________________ CONDITION 4 ________________________________|
+
+    # This condition is based on the ratio of dark to light modules
+
+    # Count # of dark modules
+    num_dark_modules = 0
+    for r in range(0, n, 2):
+       for c in range(0, n, 2):
+           if QR_matrix[r][c] == 1:
+               num_dark_modules += 1
+    
+    # Calculate % of modules that are dark (total # of modules given by n^2)
+    # round to a whole integer for convenience
+    percentage_dark = round( (num_dark_modules/(n**2)) * 100.0 )
+
+    # Determine the previous and next multiple of 5 of this percentage
+    prev_multiple = (percentage_dark//5)*5 # Calculate previous multiple by floor dividing by 5 then multiplying back by 5
+    next_multiple = prev_multiple + 5
+    
+    # Subtract 50 from these mutliples and take the absolute values of the results
+    # Divide these by five, then take the smallest of the 2 results and multiply it by 10
+    # for the penalty score #4
+    penalty_score_4 = max( abs(next_multiple - 50)/5, abs(prev_multiple - 50)/5 )
+    total_penalty += penalty_score_4 
+
+    return total_penalty
+
+
+def add_format_info(QR_matrix, version=1):
+    """
+    Encodes formatting information into the reserved ('R') modules in the QR matrix.
+    The input matrix should have patterns and reserved areas added as required, data bits inserted, 
+    and a masking pattern applied. 
+
+    THIS FUNCTION ONLY WORKS FOR VERSIONS 1 AND 2 QR CODES
+
+    Args:
+        QR_Matrix (int[][]): Binary matrix representing the QR code (0s represent a white module, 1s represent a black module, 'R' for any reserved modules)
+        version (int): Version of the QR code (version 1 by default)
+
+    Returns:
+        int[][]: Binary matrix representing the QR code with format info added
+    """
+
+   
 
diff --git a/main.py b/main.py
index 52a4619dce337d6ddf84a8d5d39ad6b3ca6783a8..d9e0faba84c1b7f884ff0094aa23949226c80d1a 100644
--- a/main.py
+++ b/main.py
@@ -1,6 +1,8 @@
-from devin_32017267 import generate_code, make_matrix
+from devin_32017267 import generate_code, make_matrix, mask_matrix
 
 code = generate_code("Hello world")
 matrix = make_matrix(code)
+matrix = mask_matrix(matrix)
 
-print(matrix)
\ No newline at end of file
+for row in matrix:
+    print([str(i) for i in row])
\ No newline at end of file