tool class_name Tetris extends MarginContainer const ROWS = 20 const COLUMNS = 10 export(PoolColorArray) var colors export(PoolColorArray) var transcolors export(Color) var grid_color export(Color) var bg_color var current_shape: TetrisPiece var matrix := Logic.create_matrix(ROWS, COLUMNS) var bg_squares := [] var squares := [] var nxt_p_squares := [] var bag := Logic.piece_bag.duplicate(true) onready var next_shape: Array = bag.pop_at(randi() % len(bag)) func random_shape() -> Array: var n = next_shape next_shape = bag.pop_at(randi() % len(bag)) if bag.empty(): bag = Logic.piece_bag.duplicate(true) draw_nxt_p_shape() return n func draw_nxt_p_shape(): var clr = colors[Logic.piece_indexs[next_shape]] var n = [0, 0, 0, 0] n.append_array(next_shape.duplicate()) for i in range(4 * 4): nxt_p_squares[i].color = Color.transparent if i > len(n) - 1 or n[i] == 0: continue nxt_p_squares[i].color = clr func get_square(at: Vector2) -> Control: return squares[at.y * COLUMNS + at.x] func init_squares(on: Control): var aspect = AspectRatioContainer.new() aspect.alignment_vertical = aspect.ALIGN_BEGIN aspect.ratio = float(COLUMNS) / float(ROWS) aspect.name = "aspect" expand_control(aspect) on.add_child(aspect) var sqs := create_grid_container("squares") aspect.add_child(sqs) for _i in range(ROWS * COLUMNS): squares.append(create_square(sqs, "%d" % _i)) func create_square(to: GridContainer, name: String, bg := true) -> ColorRect: var fg_square := ColorRect.new() fg_square.color = Color.transparent fg_square.name = name fg_square.rect_min_size = Vector2(10, 10) expand_control(fg_square) to.add_child(fg_square) if bg: var bg_square := ReferenceRect.new() bg_square.editor_only = false bg_square.border_width = 1.5 bg_square.border_color = grid_color bg_square.name = name bg_square.show_behind_parent = true expand_control(bg_square) fg_square.add_child(bg_square) return fg_square func create_grid_container(name: String, cols := COLUMNS, expand := true) -> GridContainer: var g := GridContainer.new() g.columns = cols g.name = name g.add_constant_override("vseparation", 0) g.add_constant_override("hseparation", 0) if expand: expand_control(g) return g func expand_control(c: Control) -> void: c.size_flags_horizontal = SIZE_EXPAND_FILL c.size_flags_vertical = SIZE_EXPAND_FILL c.set_anchors_and_margins_preset(PRESET_WIDE) func init_bg() -> void: var c := ColorRect.new() c.name = "bg" expand_control(c) c.color = bg_color add_child(c) func init_next_preview(on: Control): var aspect := AspectRatioContainer.new() aspect.alignment_vertical = aspect.ALIGN_BEGIN aspect.name = "aspect" aspect.set_anchors_and_margins_preset(PRESET_WIDE) on.add_child(aspect) var next = create_grid_container("next-block-preview", 4, false) aspect.add_child(next) for _i in range(4 * 4): nxt_p_squares.append(create_square(next, "%d" % _i, false)) var square = ReferenceRect.new() square.name = "next-block-outline" square.border_width = 1.5 square.border_color = grid_color square.editor_only = false expand_control(square) aspect.add_child(square) func _ready() -> void: set_process(false) init_bg() var h = HBoxContainer.new() h.add_constant_override("hseparation", 5) h.name = "hbox" add_child(h) init_squares(h) init_next_preview(h) if not Engine.editor_hint: start() func start() -> void: set_process(true) # warning-ignore-all:integer_division current_shape = TetrisPiece.new(Vector2((COLUMNS / 2) - 2, 0), random_shape()) tick() func draw_board() -> void: var mat := matrix.duplicate(true) current_shape.embed(mat) var set_squares = draw_preview() for y in range(ROWS): for x in range(COLUMNS): if mat[y][x] > 0: get_square(Vector2(x, y)).color = colors[mat[y][x] - 1] elif set_squares.find(Vector2(x, y)) == -1: get_square(Vector2(x, y)).color = Color.transparent func draw_preview(shape: TetrisPiece = current_shape) -> PoolVector2Array: var p = shape.position shape.fall(matrix) var set_squares: PoolVector2Array = [] for y in range(4): for x in range(4): if Logic.get_index_or_null(shape.shape, y, x) > 0: set_squares.append(Vector2(x, y) + shape.position) get_square(Vector2(x, y) + shape.position).color = transcolors[shape.shape_type] shape.position = p return set_squares func highlight_clearable_lines() -> void: var mat := matrix.duplicate(true) current_shape.embed(mat) var lines = Logic.clear_lines(mat.duplicate(true)) for l in lines: for x in range(COLUMNS): var c = colors[mat[l][x] - 1] c.v += .2 get_square(Vector2(x, l)).color = c func _input(event): if event is InputEventSwipe: match event.direction.round().normalized(): Vector2.RIGHT: current_shape.move(Vector2.RIGHT, matrix) Vector2.LEFT: current_shape.move(Vector2.LEFT, matrix) Vector2.DOWN: current_shape.fall(matrix) tick(false) _: current_shape.rotate(matrix) tick(false) if event.is_action_pressed("left", true): current_shape.move(Vector2.LEFT, matrix) elif event.is_action_pressed("right", true): current_shape.move(Vector2.RIGHT, matrix) if event.is_action_pressed("rotate_cw"): current_shape.rotate(matrix, true) elif event.is_action_pressed("rotate_anti_cw"): current_shape.rotate(matrix, false) if event.is_action_pressed("down"): current_shape.fall(matrix) tick(false) else: draw_board() func tick(create_timer := true): var no_draw := false var t_length := .25 if not current_shape.move(Vector2(0, 1), matrix): # invalid move: cant go down if current_shape.position.y == 0: get_tree().reload_current_scene() if current_shape.stopped: print(ascii(matrix)) current_shape.embed(matrix) current_shape = TetrisPiece.new(Vector2(randi() % (COLUMNS - 4), 0), random_shape()) Logic.clear_lines(matrix) else: current_shape.stopped = true draw_board() highlight_clearable_lines() no_draw = true t_length = .5 if create_timer: var t := get_tree().create_timer(t_length, false) t.connect("timeout", self, "tick") if not no_draw: draw_board() #warning-ignore:shadowed_variable func ascii(matrix): var s = "+" for _x in range(COLUMNS): s += "-" s += "+" #warning-ignore:shadowed_variable for y in range(ROWS): s += "\n" + ("|" if y != ROWS - 1 else "+") for x in range(COLUMNS): if matrix[y][x] > 0: s += "ﱢ" else: s += "-" s += ("|" if y != ROWS - 1 else "+") return s