├── .gitignore ├── .gitattributes ├── assets ├── tetrominoes.png └── tetrominoes.png.import ├── project.godot ├── icon.svg ├── icon.svg.import ├── LICENSE ├── scenes ├── hud.tscn └── tile_map.tscn └── TileMap.gd /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /assets/tetrominoes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/russs123/tetris_tut/HEAD/assets/tetrominoes.png -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="tetris_tut" 14 | config/features=PackedStringArray("4.1", "Forward Plus") 15 | config/icon="res://icon.svg" 16 | 17 | [display] 18 | 19 | window/size/viewport_width=650 20 | window/size/viewport_height=704 21 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/tetrominoes.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bwxneirljy70c" 6 | path="res://.godot/imported/tetrominoes.png-6fefb67b0c83934c1a103a79bfc6806a.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/tetrominoes.png" 14 | dest_files=["res://.godot/imported/tetrominoes.png-6fefb67b0c83934c1a103a79bfc6806a.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dmkm23qa2tdmb" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 russs123 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scenes/hud.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://psjnbhu2tahx"] 2 | 3 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xosvn"] 4 | bg_color = Color(0.6, 0.6, 0.6, 0) 5 | border_width_left = 5 6 | border_width_top = 5 7 | border_width_right = 5 8 | border_width_bottom = 5 9 | border_color = Color(0.054902, 0.054902, 0.054902, 1) 10 | 11 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_42puf"] 12 | bg_color = Color(0.443137, 0.643137, 0.643137, 1) 13 | 14 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_adx1n"] 15 | bg_color = Color(0.709804, 0.541176, 0.654902, 1) 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ngyvp"] 18 | bg_color = Color(0.470588, 0.654902, 0.490196, 1) 19 | 20 | [node name="HUD" type="CanvasLayer"] 21 | 22 | [node name="Panel" type="Panel" parent="."] 23 | offset_left = 425.0 24 | offset_top = 150.0 25 | offset_right = 625.0 26 | offset_bottom = 300.0 27 | theme_override_styles/panel = SubResource("StyleBoxFlat_xosvn") 28 | 29 | [node name="Label" type="Label" parent="."] 30 | offset_left = 430.0 31 | offset_top = 100.0 32 | offset_right = 610.0 33 | offset_bottom = 145.0 34 | theme_override_font_sizes/font_size = 30 35 | text = "NEXT PIECE:" 36 | horizontal_alignment = 1 37 | vertical_alignment = 1 38 | 39 | [node name="ScoreLabel" type="Label" parent="."] 40 | offset_left = 430.0 41 | offset_top = 350.0 42 | offset_right = 610.0 43 | offset_bottom = 395.0 44 | theme_override_font_sizes/font_size = 30 45 | text = "SCORE: 0" 46 | horizontal_alignment = 1 47 | vertical_alignment = 1 48 | 49 | [node name="GameOverLabel" type="Label" parent="."] 50 | offset_left = 72.0 51 | offset_top = 293.0 52 | offset_right = 311.0 53 | offset_bottom = 351.0 54 | theme_override_font_sizes/font_size = 40 55 | text = "GAME OVER!" 56 | horizontal_alignment = 1 57 | vertical_alignment = 1 58 | 59 | [node name="StartButton" type="Button" parent="."] 60 | offset_left = 430.0 61 | offset_top = 500.0 62 | offset_right = 610.0 63 | offset_bottom = 540.0 64 | theme_override_font_sizes/font_size = 28 65 | theme_override_styles/normal = SubResource("StyleBoxFlat_42puf") 66 | theme_override_styles/hover = SubResource("StyleBoxFlat_adx1n") 67 | theme_override_styles/pressed = SubResource("StyleBoxFlat_ngyvp") 68 | text = "NEW GAME" 69 | -------------------------------------------------------------------------------- /scenes/tile_map.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=3 uid="uid://v07im6ckn0y5"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://bwxneirljy70c" path="res://assets/tetrominoes.png" id="1_7at63"] 4 | [ext_resource type="Script" path="res://TileMap.gd" id="2_y6mwb"] 5 | [ext_resource type="PackedScene" uid="uid://psjnbhu2tahx" path="res://scenes/hud.tscn" id="3_vxlsc"] 6 | 7 | [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_a08xs"] 8 | texture = ExtResource("1_7at63") 9 | texture_region_size = Vector2i(32, 32) 10 | 0:0/0 = 0 11 | 1:0/0 = 0 12 | 2:0/0 = 0 13 | 3:0/0 = 0 14 | 4:0/0 = 0 15 | 5:0/0 = 0 16 | 6:0/0 = 0 17 | 7:0/0 = 0 18 | 19 | [sub_resource type="TileSet" id="TileSet_l1uyk"] 20 | tile_size = Vector2i(32, 32) 21 | sources/0 = SubResource("TileSetAtlasSource_a08xs") 22 | 23 | [node name="TileMap" type="TileMap"] 24 | tile_set = SubResource("TileSet_l1uyk") 25 | cell_quadrant_size = 32 26 | format = 2 27 | layer_0/name = "board" 28 | layer_0/tile_data = PackedInt32Array(0, 458752, 0, 65536, 458752, 0, 131072, 458752, 0, 196608, 458752, 0, 262144, 458752, 0, 327680, 458752, 0, 393216, 458752, 0, 458752, 458752, 0, 524288, 458752, 0, 589824, 458752, 0, 655360, 458752, 0, 720896, 458752, 0, 786432, 458752, 0, 851968, 458752, 0, 917504, 458752, 0, 983040, 458752, 0, 1048576, 458752, 0, 1114112, 458752, 0, 1179648, 458752, 0, 1245184, 458752, 0, 1310720, 458752, 0, 1376256, 458752, 0, 1, 458752, 0, 1376257, 458752, 0, 2, 458752, 0, 1376258, 458752, 0, 3, 458752, 0, 1376259, 458752, 0, 4, 458752, 0, 1376260, 458752, 0, 5, 458752, 0, 1376261, 458752, 0, 6, 458752, 0, 1376262, 458752, 0, 7, 458752, 0, 1376263, 458752, 0, 8, 458752, 0, 1376264, 458752, 0, 9, 458752, 0, 1376265, 458752, 0, 10, 458752, 0, 1376266, 458752, 0, 11, 458752, 0, 65547, 458752, 0, 131083, 458752, 0, 196619, 458752, 0, 262155, 458752, 0, 327691, 458752, 0, 393227, 458752, 0, 458763, 458752, 0, 524299, 458752, 0, 589835, 458752, 0, 655371, 458752, 0, 720907, 458752, 0, 786443, 458752, 0, 851979, 458752, 0, 917515, 458752, 0, 983051, 458752, 0, 1048587, 458752, 0, 1114123, 458752, 0, 1179659, 458752, 0, 1245195, 458752, 0, 1310731, 458752, 0, 1376267, 458752, 0, 1310721, 196608, 0, 1310722, 196608, 0, 1310723, 196608, 0, 1310724, 196608, 0, 1310725, 196608, 0, 1310727, 196608, 0, 1310728, 196608, 0, 1310729, 196608, 0, 1310730, 196608, 0, 1245185, 0, 0, 1245186, 0, 0, 1245187, 0, 0, 1245192, 0, 0, 1245193, 0, 0, 1245194, 0, 0, 1179650, 65536, 0, 1114114, 65536, 0, 1048578, 65536, 0, 983042, 65536, 0, 983041, 65536, 0, 1048577, 65536, 0, 1114113, 65536, 0, 1179649, 65536, 0, 1048585, 65536, 0, 1114121, 65536, 0, 1179657, 65536, 0) 29 | layer_1/name = "active" 30 | layer_1/enabled = true 31 | layer_1/modulate = Color(1, 1, 1, 1) 32 | layer_1/y_sort_enabled = false 33 | layer_1/y_sort_origin = 0 34 | layer_1/z_index = 0 35 | layer_1/tile_data = PackedInt32Array() 36 | script = ExtResource("2_y6mwb") 37 | 38 | [node name="HUD" parent="." instance=ExtResource("3_vxlsc")] 39 | -------------------------------------------------------------------------------- /TileMap.gd: -------------------------------------------------------------------------------- 1 | extends TileMap 2 | 3 | #tetrominoes 4 | var i_0 := [Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1), Vector2i(3, 1)] 5 | var i_90 := [Vector2i(2, 0), Vector2i(2, 1), Vector2i(2, 2), Vector2i(2, 3)] 6 | var i_180 := [Vector2i(0, 2), Vector2i(1, 2), Vector2i(2, 2), Vector2i(3, 2)] 7 | var i_270 := [Vector2i(1, 0), Vector2i(1, 1), Vector2i(1, 2), Vector2i(1, 3)] 8 | var i := [i_0, i_90, i_180, i_270] 9 | 10 | var t_0 := [Vector2i(1, 0), Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1)] 11 | var t_90 := [Vector2i(1, 0), Vector2i(1, 1), Vector2i(2, 1), Vector2i(1, 2)] 12 | var t_180 := [Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1), Vector2i(1, 2)] 13 | var t_270 := [Vector2i(1, 0), Vector2i(0, 1), Vector2i(1, 1), Vector2i(1, 2)] 14 | var t := [t_0, t_90, t_180, t_270] 15 | 16 | var o_0 := [Vector2i(0, 0), Vector2i(1, 0), Vector2i(0, 1), Vector2i(1, 1)] 17 | var o_90 := [Vector2i(0, 0), Vector2i(1, 0), Vector2i(0, 1), Vector2i(1, 1)] 18 | var o_180 := [Vector2i(0, 0), Vector2i(1, 0), Vector2i(0, 1), Vector2i(1, 1)] 19 | var o_270 := [Vector2i(0, 0), Vector2i(1, 0), Vector2i(0, 1), Vector2i(1, 1)] 20 | var o := [o_0, o_90, o_180, o_270] 21 | 22 | var z_0 := [Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(2, 1)] 23 | var z_90 := [Vector2i(2, 0), Vector2i(1, 1), Vector2i(2, 1), Vector2i(1, 2)] 24 | var z_180 := [Vector2i(0, 1), Vector2i(1, 1), Vector2i(1, 2), Vector2i(2, 2)] 25 | var z_270 := [Vector2i(1, 0), Vector2i(0, 1), Vector2i(1, 1), Vector2i(0, 2)] 26 | var z := [z_0, z_90, z_180, z_270] 27 | 28 | var s_0 := [Vector2i(1, 0), Vector2i(2, 0), Vector2i(0, 1), Vector2i(1, 1)] 29 | var s_90 := [Vector2i(1, 0), Vector2i(1, 1), Vector2i(2, 1), Vector2i(2, 2)] 30 | var s_180 := [Vector2i(1, 1), Vector2i(2, 1), Vector2i(0, 2), Vector2i(1, 2)] 31 | var s_270 := [Vector2i(0, 0), Vector2i(0, 1), Vector2i(1, 1), Vector2i(1, 2)] 32 | var s := [s_0, s_90, s_180, s_270] 33 | 34 | var l_0 := [Vector2i(2, 0), Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1)] 35 | var l_90 := [Vector2i(1, 0), Vector2i(1, 1), Vector2i(1, 2), Vector2i(2, 2)] 36 | var l_180 := [Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1), Vector2i(0, 2)] 37 | var l_270 := [Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(1, 2)] 38 | var l := [l_0, l_90, l_180, l_270] 39 | 40 | var j_0 := [Vector2i(0, 0), Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1)] 41 | var j_90 := [Vector2i(1, 0), Vector2i(2, 0), Vector2i(1, 1), Vector2i(1, 2)] 42 | var j_180 := [Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1), Vector2i(2, 2)] 43 | var j_270 := [Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 2), Vector2i(1, 2)] 44 | var j := [j_0, j_90, j_180, j_270] 45 | 46 | var shapes := [i, t, o, z, s, l, j] 47 | var shapes_full := shapes.duplicate() 48 | 49 | #grid variables 50 | const COLS : int = 10 51 | const ROWS : int = 20 52 | 53 | #movement variables 54 | const directions := [Vector2i.LEFT, Vector2i.RIGHT, Vector2i.DOWN] 55 | var steps : Array 56 | const steps_req : int = 50 57 | const start_pos := Vector2i(5, 1) 58 | var cur_pos : Vector2i 59 | var speed : float 60 | const ACCEL : float = 0.25 61 | 62 | #game piece variables 63 | var piece_type 64 | var next_piece_type 65 | var rotation_index : int = 0 66 | var active_piece : Array 67 | 68 | #game variables 69 | var score : int 70 | const REWARD : int = 100 71 | var game_running : bool 72 | 73 | #tilemap variables 74 | var tile_id : int = 0 75 | var piece_atlas : Vector2i 76 | var next_piece_atlas : Vector2i 77 | 78 | #layer variables 79 | var board_layer : int = 0 80 | var active_layer : int = 1 81 | 82 | # Called when the node enters the scene tree for the first time. 83 | func _ready(): 84 | new_game() 85 | $HUD.get_node("StartButton").pressed.connect(new_game) 86 | 87 | func new_game(): 88 | #reset variables 89 | score = 0 90 | speed = 1.0 91 | game_running = true 92 | steps = [0, 0, 0] #0:left, 1:right, 2:down 93 | $HUD.get_node("GameOverLabel").hide() 94 | #clear everything 95 | clear_piece() 96 | clear_board() 97 | clear_panel() 98 | piece_type = pick_piece() 99 | piece_atlas = Vector2i(shapes_full.find(piece_type), 0) 100 | next_piece_type = pick_piece() 101 | next_piece_atlas = Vector2i(shapes_full.find(next_piece_type), 0) 102 | create_piece() 103 | 104 | # Called every frame. 'delta' is the elapsed time since the previous frame. 105 | func _process(delta): 106 | if game_running: 107 | if Input.is_action_pressed("ui_left"): 108 | steps[0] += 10 109 | elif Input.is_action_pressed("ui_right"): 110 | steps[1] += 10 111 | elif Input.is_action_pressed("ui_down"): 112 | steps[2] += 10 113 | elif Input.is_action_just_pressed("ui_up"): 114 | rotate_piece() 115 | 116 | #apply downward movement every frame 117 | steps[2] += speed 118 | #move the piece 119 | for i in range(steps.size()): 120 | if steps[i] > steps_req: 121 | move_piece(directions[i]) 122 | steps[i] = 0 123 | 124 | func pick_piece(): 125 | var piece 126 | if not shapes.is_empty(): 127 | shapes.shuffle() 128 | piece = shapes.pop_front() 129 | else: 130 | shapes = shapes_full.duplicate() 131 | shapes.shuffle() 132 | piece = shapes.pop_front() 133 | return piece 134 | 135 | func create_piece(): 136 | #reset variables 137 | steps = [0, 0, 0] #0:left, 1:right, 2:down 138 | cur_pos = start_pos 139 | active_piece = piece_type[rotation_index] 140 | draw_piece(active_piece, cur_pos, piece_atlas) 141 | #show next piece 142 | draw_piece(next_piece_type[0], Vector2i(15, 6), next_piece_atlas) 143 | 144 | func clear_piece(): 145 | for i in active_piece: 146 | erase_cell(active_layer, cur_pos + i) 147 | 148 | func draw_piece(piece, pos, atlas): 149 | for i in piece: 150 | set_cell(active_layer, pos + i, tile_id, atlas) 151 | 152 | func rotate_piece(): 153 | if can_rotate(): 154 | clear_piece() 155 | rotation_index = (rotation_index + 1) % 4 156 | active_piece = piece_type[rotation_index] 157 | draw_piece(active_piece, cur_pos, piece_atlas) 158 | 159 | func move_piece(dir): 160 | if can_move(dir): 161 | clear_piece() 162 | cur_pos += dir 163 | draw_piece(active_piece, cur_pos, piece_atlas) 164 | else: 165 | if dir == Vector2i.DOWN: 166 | land_piece() 167 | check_rows() 168 | piece_type = next_piece_type 169 | piece_atlas = next_piece_atlas 170 | next_piece_type = pick_piece() 171 | next_piece_atlas = Vector2i(shapes_full.find(next_piece_type), 0) 172 | clear_panel() 173 | create_piece() 174 | check_game_over() 175 | 176 | func can_move(dir): 177 | #check if there is space to move 178 | var cm = true 179 | for i in active_piece: 180 | if not is_free(i + cur_pos + dir): 181 | cm = false 182 | return cm 183 | 184 | func can_rotate(): 185 | var cr = true 186 | var temp_rotation_index = (rotation_index + 1) % 4 187 | for i in piece_type[temp_rotation_index]: 188 | if not is_free(i + cur_pos): 189 | cr = false 190 | return cr 191 | 192 | func is_free(pos): 193 | return get_cell_source_id(board_layer, pos) == -1 194 | 195 | func land_piece(): 196 | #remove each segment from the active layer and move to board layer 197 | for i in active_piece: 198 | erase_cell(active_layer, cur_pos + i) 199 | set_cell(board_layer, cur_pos + i, tile_id, piece_atlas) 200 | 201 | func clear_panel(): 202 | for i in range(14, 19): 203 | for j in range(5, 9): 204 | erase_cell(active_layer, Vector2i(i, j)) 205 | 206 | func check_rows(): 207 | var row : int = ROWS 208 | while row > 0: 209 | var count = 0 210 | for i in range(COLS): 211 | if not is_free(Vector2i(i + 1, row)): 212 | count += 1 213 | #if row is full then erase it 214 | if count == COLS: 215 | shift_rows(row) 216 | score += REWARD 217 | $HUD.get_node("ScoreLabel").text = "SCORE: " + str(score) 218 | speed += ACCEL 219 | else: 220 | row -= 1 221 | 222 | func shift_rows(row): 223 | var atlas 224 | for i in range(row, 1, -1): 225 | for j in range(COLS): 226 | atlas = get_cell_atlas_coords(board_layer, Vector2i(j + 1, i - 1)) 227 | if atlas == Vector2i(-1, -1): 228 | erase_cell(board_layer, Vector2i(j + 1, i)) 229 | else: 230 | set_cell(board_layer, Vector2i(j + 1, i), tile_id, atlas) 231 | 232 | func clear_board(): 233 | for i in range(ROWS): 234 | for j in range(COLS): 235 | erase_cell(board_layer, Vector2i(j + 1, i + 1)) 236 | 237 | func check_game_over(): 238 | for i in active_piece: 239 | if not is_free(i + cur_pos): 240 | land_piece() 241 | $HUD.get_node("GameOverLabel").show() 242 | game_running = false 243 | --------------------------------------------------------------------------------