{"id":4990,"date":"2026-08-30T01:50:40","date_gmt":"2026-08-29T16:50:40","guid":{"rendered":"https:\/\/donguri3.net\/server-tech\/micropython-tet-rp2040-zero-2\/"},"modified":"2026-08-30T01:50:42","modified_gmt":"2026-08-29T16:50:42","slug":"micropython-tet-rp2040-zero","status":"publish","type":"post","link":"https:\/\/donguri3.net\/en\/diy-repair\/electronics-microcontroller\/micropython-tet-rp2040-zero\/","title":{"rendered":"Building a Tetris-Style Game with MicroPython and RP2040-Zero"},"content":{"rendered":"<p>In this article, we introduce a Tetris-style game program implemented in MicroPython using ChatGPT, an RP2040-zero, and an SSD1306 OLED display.<br \/>The program implements features such as block dropping, left\/right movement, rotation (with wall kicks), line clearing, and game-over detection.<br \/>Below, we explain the implementation details and design choices for each function.<\/p>\n<p><iframe loading=\"lazy\" src=\"\/\/www.youtube.com\/embed\/jCAPbP9UgGw\" width=\"560\" height=\"314\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>https:\/\/github.com\/bomura\/blocks-fall<\/p>\n<hr \/>\n<h2>Overall Game Structure and Settings<\/h2>\n<h3>Hardware and Display Settings<\/h3>\n<ul>\n<li>Connected the SSD1306 display via <strong>I2C communication<\/strong>.<\/li>\n<li>To match the screen orientation, the logical size of the display is set to <code>DISPLAY_WIDTH = 32<\/code> and <code>DISPLAY_HEIGHT = 128<\/code>, dividing the screen into a UI area and a game area.<\/li>\n<li>The UI area is on the left side (x-coordinates 0\u20137) to display the score and the next block, while the remaining portion serves as the gameplay area.<\/li>\n<li>On our display, the UI area is orange, and the gameplay area is blue.<\/li>\n<\/ul>\n<figure id=\"attachment_949\" aria-describedby=\"caption-attachment-949\" style=\"width: 1024px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585.png\"><img decoding=\"async\" class=\"wp-image-949 size-large\" src=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-1024x576.png\" alt=\"blocks-fall hardware configuration\" width=\"1024\" height=\"576\" srcset=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-1024x576.png 1024w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-300x169.png 300w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-768x432.png 768w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-1536x864.png 1536w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-320x180.png 320w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-530x298.png 530w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-565x318.png 565w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-710x399.png 710w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-725x408.png 725w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585-160x90.png 160w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/03\/b2aab66086dce03c19116d93b9b18585.png 1920w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption id=\"caption-attachment-949\" class=\"wp-caption-text\">blocks-fall hardware configuration<\/figcaption><\/figure>\n<h3>Game Field Settings<\/h3>\n<ul>\n<li>Block size is set as <code>BLOCK_SIZE = 3<\/code>.<\/li>\n<li>The game field logically forms a grid of <code>FIELD_ROWS<\/code> \u00d7 <code>FIELD_COLS<\/code>, with each cell rendered as 3 \u00d7 3 pixels.<\/li>\n<\/ul>\n<hr \/>\n<h2>Block Definitions<\/h2>\n<p>In the <code>BLOCKS<\/code> dictionary, the shape of each block (I, U, t, S, Z, J, L) is defined as a list of coordinates for each rotation state.<br \/>This makes it easy to handle different block shapes for each rotation.<\/p>\n<details>\n<summary class=\"toggle-button\">Block Shape Definitions (Per Rotation State)<\/summary>\n<pre>BLOCKS = {\n    'I': [\n        [(0, 1), (1, 1), (2, 1), (3, 1)],\n        [(2, 0), (2, 1), (2, 2), (2, 3)],\n        [(0, 2), (1, 2), (2, 2), (3, 2)],\n        [(1, 0), (1, 1), (1, 2), (1, 3)]\n    ],\n    'U': [\n        [(0, 0), (0, 1), (0, 2), (1, 0), (1, 2)],\n        [(0, 0), (0, 1), (1, 0), (2, 0), (2, 1)],\n        [(0, 0), (0, 2), (1, 0), (1, 1), (1, 2)],\n        [(0, 0), (0, 1), (1, 1), (2, 0), (2, 1)]\n    ],\n    'T': [\n        [(0, 1), (1, 0), (1, 1), (1, 2)],\n        [(0, 1), (1, 0), (1, 1), (2, 1)],\n        [(0, 0), (0, 1), (0, 2), (1, 1)],\n        [(0, 1), (1, 1), (1, 2), (2, 1)]\n    ],\n    'S': [\n        [(0, 1), (0, 2), (1, 0), (1, 1)],\n        [(0, 0), (1, 0), (1, 1), (2, 1)]\n    ],\n    'Z': [\n        [(0, 0), (0, 1), (1, 1), (1, 2)],\n        [(0, 1), (1, 0), (1, 1), (2, 0)]\n    ],\n    'J': [\n        [(0, 0), (1, 0), (1, 1), (1, 2)],\n        [(0, 1), (1, 1), (2, 0), (2, 1)],\n        [(0, 0), (0, 1), (0, 2), (1, 2)],\n        [(0, 0), (0, 1), (1, 0), (2, 0)]\n    ],\n    'L': [\n        [(0, 0), (0, 1), (1, 0), (2, 0)],\n        [(0, 0), (1, 0), (1, 1), (1, 2)],\n        [(0, 1), (1, 1), (2, 0), (2, 1)],\n        [(0, 0), (0, 1), (0, 2), (1, 2)]\n    ]\n}<\/pre>\n<\/details>\n<h2>Explanation of Functions<\/h2>\n<h3>can_move(piece, drow, dcol)<\/h3>\n<p>This function checks whether the falling block, when moved by the specified offsets (<code>drow<\/code>, <code>dcol<\/code>):<\/p>\n<ul>\n<li>Goes out of the game field boundaries<\/li>\n<li>Collides with already fixed blocks<br \/>It returns <code>True<\/code> if the move is valid and <code>False<\/code> otherwise, so it is always called during block movement and rotation processing.<\/li>\n<\/ul>\n<details>\n<summary class=\"toggle-button\">Check if the falling block can move by the specified offsets (drow, dcol)<\/summary>\n<pre>def can_move(piece, drow, dcol):\n    for (dr, dc) in piece['shape']:\n        new_row = piece['row'] + dr + drow\n        new_col = piece['col'] + dc + dcol\n        if new_col &lt; 0 or new_col &gt;= FIELD_COLS or new_row &lt; 0 or new_row &gt;= FIELD_ROWS:\n            return False\n        if game_field[new_row][new_col]:\n            return False\n    return True<\/pre>\n<\/details>\n<h3>3.2. fix_piece(piece)<\/h3>\n<p>When a block can no longer fall downwards, it fixes the block to the game field at its current position.<br \/>After fixing, it sets &#8220;1&#8221; in the corresponding cells of the game field so they are treated as stationary blocks thereafter.<\/p>\n<details>\n<summary class=\"toggle-button\">Fix the falling block to the game field<\/summary>\n<pre>def fix_piece(piece):\n    for (dr, dc) in piece['shape']:\n        r = piece['row'] + dr\n        c = piece['col'] + dc\n        if 0 &lt;= r &lt; FIELD_ROWS and 0 &lt;= c &lt; FIELD_COLS:\n            game_field[r] = 1<\/pre>\n<\/details>\n<h3>3.3. spawn_piece()<\/h3>\n<p>This function generates a new block.<\/p>\n<ul>\n<li>Randomly selects a block type and rotation state from the <code>BLOCKS<\/code> dictionary.<\/li>\n<li>Determines the initial position (near the center of the field here) based on the width of the selected shape.<br \/>This function prepares the next block at the start of the game or after a block is fixed.<\/li>\n<\/ul>\n<details>\n<summary class=\"toggle-button\">Generate a block<\/summary>\n<pre>def spawn_piece():\n    word, shapes = random.choice(list(BLOCKS.items()))\n    rotation = random.randint(0, len(shapes) - 1)\n    shape = shapes[rotation]                               \n    width = max(dc for (_, dc) in shape) + 1\n    col = FIELD_COLS \/\/ 2\n    return {\n        'row': 0,\n        'col': col,\n        'word': word,\n        'rotation': rotation,\n        'shape': shape\n    }<\/pre>\n<\/details>\n<h3>3.4. rotate_piece(piece)<\/h3>\n<p>Handles the block rotation process.<\/p>\n<ul>\n<li>Retrieves the shape after rotation and updates the current rotation state to the next one.<\/li>\n<li>However, rotating near edges can cause the block to clip into walls. Therefore, a <strong>wall kick process<\/strong> is implemented.\n<ul>\n<li>Tests candidate offsets (e.g., shifting 1 to 2 cells left or right) in order and uses <code>can_move<\/code> to find a position without collisions.<\/li>\n<li>If a valid position is found, that position and rotation state are finalized; otherwise, it reverts to the original state.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<details>\n<summary class=\"toggle-button\">Rotate the block, and perform wall kick processing to try offsets if it clips into walls<\/summary>\n<pre>def rotate_piece(piece):\n    shapes = BLOCKS[piece['word']]\n    new_rotation = (piece['rotation'] + 1) % len(shapes)\n    new_shape = shapes[new_rotation]\n    # Offset candidates (e.g., shifting 1 or 2 cells left\/right)\n    offsets = [(0, 0), (0, -1), (0, 1), (0, -2), (0, 2)]\n    original_row = piece['row']\n    original_col = piece['col']\n    for drow, dcol in offsets:\n        # Temporarily apply new shape and offset\n        piece['row'] = original_row + drow\n        piece['col'] = original_col + dcol\n        piece['shape'] = new_shape\n        if can_move(piece, 0, 0):\n            piece['rotation'] = new_rotation\n            return  # Finalized since a valid position was found\n    # If none of the offset candidates are valid, revert to original position and shape\n    piece['row'] = original_row\n    piece['col'] = original_col<\/pre>\n<\/details>\n<h3>3.5. clear_lines()<\/h3>\n<p>A function that checks each row in the game field and deletes rows where all cells are filled.<\/p>\n<ul>\n<li>Inserts empty rows at the top of the field corresponding to the number of deleted rows, shifting the blocks upward.<\/li>\n<li>Also adds to the score according to the number of cleared lines.<\/li>\n<\/ul>\n<details>\n<summary class=\"toggle-button\">Function to delete lines<\/summary>\n<pre>def clear_lines():\n    \"\"\"\n    Check each row and delete rows where all cells are filled\n    Add score based on the number of cleared rows and insert empty rows at the top\n    \"\"\"\n    global game_field, score\n    cleared_lines = 0\n    new_field = []\n    for row in game_field:\n        if all(cell == 1 for cell in row):\n            cleared_lines += 1\n        else:\n            new_field.append(row)\n    for _ in range(cleared_lines):\n        new_field.insert(0, [0] * FIELD_COLS)\n    game_field = new_field\n    score += 100 * cleared_lines<\/pre>\n<\/details>\n<h3>3.6. draw_ui(piece)<\/h3>\n<p>A function that draws the score and the next block preview in the UI area (left side of the screen).<\/p>\n<ul>\n<li>Uses <code>display.text<\/code> to display the score and draws the next block as a smaller shape preview.<\/li>\n<li>The next block actually receives the one generated by <code>next_piece<\/code>.<\/li>\n<\/ul>\n<details>\n<summary class=\"toggle-button\">Drawing the UI Area (Score, Next Block Placeholder)<\/summary>\n<pre>def draw_ui(piece):\n    display.fill_rect(0, 0, DISPLAY_HEIGHT, UI_WIDTH, 0)\n    display.text(\"Score:%d\" % score, UI_WIDTH+CELL_H, 0, 1)\n    # Next block placeholder (expand to display actual next block as needed)\n    for (dr, dc) in piece['shape']:\n        x = dr * CELL_H\n        y = dc * CELL_W\n        display.fill_rect(x, y, CELL_W, CELL_H, 1)<\/pre>\n<\/details>\n<h3>3.7. draw_game_field(piece)<\/h3>\n<p>A function that draws the fixed blocks and the currently falling block in the game area.<\/p>\n<ul>\n<li>Draws each cell in the game field onto the display according to the specified cell size.<\/li>\n<li>The falling block is drawn at a position calculated by adding the shape offsets of each block to the current <code>row<\/code> and <code>col<\/code>.<\/li>\n<\/ul>\n<details>\n<summary class=\"toggle-button\">Drawing the Game Area (Fixed Blocks and Falling Block)<\/summary>\n<pre>def draw_game_field(piece):\n    \"\"\"\n    Drawing the game area (fixed blocks and falling block)\n    Mapping: Game field (r, c) \u2192 Display coordinates\n              x = r * CELL_H\n              y = UI_WIDTH + c * CELL_W\n    \"\"\"\n    display.fill_rect(0, UI_WIDTH, GAME_AREA_HEIGHT, GAME_AREA_WIDTH, 0)\n    for r in range(FIELD_ROWS):\n        for c in range(FIELD_COLS):\n            if game_field[r]:\n                x = r * CELL_H\n                y = UI_WIDTH + c * CELL_W\n                display.fill_rect(x, y, CELL_W, CELL_H, 1)\n    for (dr, dc) in piece['shape']:\n        r = piece['row'] + dr\n        c = piece['col'] + dc\n        x = r * CELL_H\n        y = UI_WIDTH + c * CELL_W\n        display.fill_rect(x, y, CELL_W, CELL_H, 1)<\/pre>\n<\/details>\n<hr \/>\n<h2>Game Loop and Overall Flow<\/h2>\n<ul>\n<li><strong>Input Processing<\/strong>: Monitors inputs from the left\/right movement buttons and rotation buttons, calling <code>can_move<\/code> and <code>rotate_piece<\/code> appropriately to move or rotate blocks.<\/li>\n<li><strong>Dropping Process<\/strong>: Drops the block down by one row at regular intervals; if it cannot move, it fixes it using <code>fix_piece<\/code>.<\/li>\n<li><strong>Line Clearing and Next Block Generation<\/strong>: After fixing a block, <code>clear_lines<\/code> checks for lines, and <code>spawn_piece<\/code> generates the next block.<\/li>\n<li><strong>Game Over Detection<\/strong>: If a block exists at the very top of the field after fixing, it triggers a game over and displays the final score and &#8220;GAME OVER&#8221;.<\/li>\n<\/ul>\n<hr \/>\n<h2>References<\/h2>\n<p><a href=\"https:\/\/co0-sky.com\/archives\/2394\">[sky] Tetris Theme BGM Korobeiniki Doremi Sheet Music &#8211; co sky<\/a><\/p>\n<p><a href=\"https:\/\/qumcum.net\/arduino_beep\/#toc7\">How to Sound a BEEP | Arduino Robot Programming | Qumcum Learning Plaza<\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>In this program, we used MicroPython and the SSD1306 to implement core Tetris-style features (block dropping, movement, rotation, line clearing, and game-over detection).<br \/>In particular, wall kicks during rotation are an important technique for achieving natural behavior even near edges.<br \/>Based on this program, feel free to add further features (fast dropping, enhanced rotation algorithms, score bonuses, etc.) to complete your own original Tetris-style game!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, we introduce a Tetris-style game program implemented in MicroPython using ChatGPT, an RP2040- [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":949,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_locale":"en_US","_original_post":"https:\/\/donguri3.net\/?p=909","footnotes":""},"categories":[1163],"tags":[9,51,52,271,265,13,47,277,262,321,157,106],"class_list":["post-4990","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-electronics-microcontroller","tag-diy","tag-git","tag-https","tag-iot","tag-micropython","tag-ols","tag-python","tag-raspberry-pi-pico","tag-rp2040-zero","tag-ssd1306","tag-157","tag-106","en-US"],"_links":{"self":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/4990","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/comments?post=4990"}],"version-history":[{"count":1,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/4990\/revisions"}],"predecessor-version":[{"id":4993,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/4990\/revisions\/4993"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media\/949"}],"wp:attachment":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media?parent=4990"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/categories?post=4990"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/tags?post=4990"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}