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