317 lines
10 KiB
GDScript
317 lines
10 KiB
GDScript
extends Control
|
|
|
|
signal is_ready()
|
|
signal gem_matched(matched_results)
|
|
signal gems_swapped()
|
|
|
|
const GemScene = preload("res://entities/gem.tscn")
|
|
|
|
# Random generator
|
|
var rnd = Random.new()
|
|
|
|
# Grid dimensions
|
|
const grid_width = 8
|
|
const grid_height = 8
|
|
|
|
# The grid
|
|
var grid = LogicalGrid.new(grid_width, grid_height)
|
|
|
|
# Cursor position
|
|
var cursor_i: int = 0
|
|
var cursor_j: int = 0
|
|
# Status of the cursor
|
|
var gem_selected: bool = false
|
|
# Counter for events sent by gems
|
|
var event_count: int = 0
|
|
|
|
# State machine for animations and event handling
|
|
# NONE : Do not change state
|
|
# INIT : Initial state, only here before the end of the _ready function
|
|
# READY : Accept input
|
|
# SWAPPING : Reject input, change the position of gems
|
|
# MATCHING : Reject input, match gems
|
|
#
|
|
# --------- ------------ ------------
|
|
# | READY |--(gem swapped)-->| SWAPPING |--(swap ended, gems are matching)-->| MATCHING |
|
|
# --------- ------------ ------------
|
|
# ^ ^ | |
|
|
# | ------------(match occured)----------------- |
|
|
# | |
|
|
# -------------------------------(no match)-----------------------------------
|
|
#
|
|
enum State { NONE, INIT, READY, SWAPPING, MATCHING }
|
|
var current_state = State.INIT
|
|
var next_state = State.NONE
|
|
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready():
|
|
# Init the grid
|
|
for i in range(grid_width):
|
|
for j in range(grid_height):
|
|
add_gem(i, j, i, j)
|
|
# match gems (without emitting events)
|
|
match_gems()
|
|
# change the direction
|
|
grid.set_flow_direction(LogicalGrid.FlowDirection.DOWN)
|
|
# State is ready
|
|
current_state = State.READY
|
|
# Tell all gems the grid is ready
|
|
emit_signal("is_ready")
|
|
|
|
# State machine
|
|
func _process(delta):
|
|
if (next_state != State.NONE) and (current_state != next_state):
|
|
match next_state:
|
|
State.READY:
|
|
show_cursor()
|
|
current_state = next_state
|
|
next_state = State.NONE
|
|
State.SWAPPING:
|
|
hide_cursor()
|
|
event_count = 0
|
|
emit_signal("gems_swapped")
|
|
current_state = next_state
|
|
next_state = State.NONE
|
|
State.MATCHING:
|
|
current_state = next_state
|
|
match_gems() # Computes next state
|
|
else:
|
|
next_state = State.NONE
|
|
|
|
|
|
# Add a new gem on the board to replace a gem at i,j
|
|
# On screen initialy displayed at position screen_i,screen_j
|
|
func add_gem(i, j, screen_i, screen_j):
|
|
var g = GemScene.instance()
|
|
g.randomize(rnd)
|
|
g.set_grid_position(screen_i, screen_j)
|
|
if current_state != State.INIT:
|
|
g.enable_animations()
|
|
else:
|
|
connect("is_ready", g, "enable_animations")
|
|
connect("gems_swapped", g, "update_position")
|
|
g.connect("position_reached", self, "on_gem_moved")
|
|
$Gems.add_child(g)
|
|
grid.push_value(i, j, g)
|
|
|
|
# Swap gems between cursor position and given position
|
|
func gem_swap(i_to, j_to):
|
|
if (i_to >= 0) and (j_to >= 0) and (i_to < grid_width) and (j_to < grid_width):
|
|
# Swap gem
|
|
grid.swap_values(cursor_i, cursor_j, i_to, j_to)
|
|
grid.at(cursor_i, cursor_j).move_to_position(cursor_i, cursor_j)
|
|
grid.at(i_to, j_to).move_to_position(i_to, j_to)
|
|
# Move cursor
|
|
gem_selected = false
|
|
cursor_i = i_to
|
|
cursor_j = j_to
|
|
update_cursor()
|
|
# Change state
|
|
next_state = State.SWAPPING
|
|
|
|
# Called each time a gems have finished moving
|
|
func on_gem_moved():
|
|
event_count += 1
|
|
if event_count >= (grid_width * grid_height):
|
|
# All gems have responded : matching
|
|
next_state = State.MATCHING
|
|
|
|
func match_gems():
|
|
# Init signaled result
|
|
var results = Dictionary()
|
|
for t in Gem.Type.values():
|
|
for e in Gem.Effect.values():
|
|
results[[t, e]] = 0
|
|
# Check loop
|
|
var check_loop: bool = true
|
|
var no_match = true
|
|
while check_loop:
|
|
no_match = true
|
|
check_loop = false
|
|
# Mark the gems to match.
|
|
check_matched()
|
|
# Collect the gems to match in results
|
|
var to_remove = []
|
|
for i in range(grid_width):
|
|
for j in range(grid_height):
|
|
var g = grid.at(i, j)
|
|
if g.matched:
|
|
# Loop if we are in the init state
|
|
check_loop = (current_state == State.INIT)
|
|
no_match = false
|
|
results[[g.type, g.effect]] = results[[g.type, g.effect]] + 1 + g.bonus
|
|
# The indexes to remove are placed by pushing front,
|
|
# so that the last indexes are removed first
|
|
to_remove.push_front([i, j])
|
|
# Remove the gems
|
|
grid.remove_values(to_remove)
|
|
# Add new gems to fill the empty spaces.
|
|
# The initial positions depends on the flow direction.
|
|
var counts = [0]
|
|
var dir = grid.get_flow_direction()
|
|
if LogicalGrid.isFlowDirectionVertical(dir):
|
|
counts.resize(grid_width)
|
|
else:
|
|
counts.resize(grid_height)
|
|
for i in range(counts.size()):
|
|
counts[i] = 0
|
|
for idx in to_remove:
|
|
var screen_idx = []
|
|
var i = 0
|
|
# Gems are not added on the position they are on the grid,
|
|
# but outside so that the animation looks correct
|
|
match dir:
|
|
LogicalGrid.FlowDirection.UP:
|
|
i = idx[0]
|
|
screen_idx = [i, grid_height + counts[i]]
|
|
LogicalGrid.FlowDirection.DOWN:
|
|
i = idx[0]
|
|
screen_idx = [i, -1 - counts[i]]
|
|
LogicalGrid.FlowDirection.LEFT:
|
|
i = idx[1]
|
|
screen_idx = [grid_width + counts[i], i]
|
|
LogicalGrid.FlowDirection.RIGHT:
|
|
i = idx[1]
|
|
screen_idx = [-1 - counts[i], i]
|
|
counts[i] = counts[i] + 1
|
|
add_gem(idx[0], idx[1], screen_idx[0], screen_idx[1])
|
|
# Update gems next position
|
|
for i in range(grid_width):
|
|
for j in range(grid_height):
|
|
grid.at(i, j).move_to_position(i, j)
|
|
if current_state == State.INIT:
|
|
grid.at(i, j).update_position()
|
|
# Emit events and compute next state
|
|
if current_state != State.INIT:
|
|
emit_signal("gem_matched", results)
|
|
if no_match:
|
|
next_state = State.READY
|
|
else:
|
|
next_state = State.SWAPPING
|
|
|
|
# If a gem is surrounded by two gem of the same type vertically or horizontally,
|
|
# mark the gem and its surronding gems as matched
|
|
func check_matched():
|
|
for i in range(grid_width):
|
|
for j in range(grid_height):
|
|
check_matched_at(i, j)
|
|
|
|
func check_matched_at(i, j):
|
|
# check vertically
|
|
var up_i = i
|
|
var up_j = j - 1
|
|
var down_i = i
|
|
var down_j = j + 1
|
|
if (up_j >= 0) and (down_j < grid_height):
|
|
if same_type(i, j, up_i, up_j) and same_type(i, j, down_i, down_j):
|
|
# match
|
|
mark_matched(i, j)
|
|
mark_matched(up_i, up_j)
|
|
mark_matched(down_i, down_j)
|
|
# Add a bonus if the effect is the same
|
|
if same_effect(i, j, up_i, up_j) and same_effect(i, j, down_i, down_j):
|
|
mark_bonus(i, j)
|
|
# Check horizontally
|
|
var left_i = i - 1
|
|
var left_j = j
|
|
var right_i = i + 1
|
|
var right_j = j
|
|
if (left_i >= 0) and (right_i < grid_width):
|
|
if same_type(i, j, left_i, left_j) and same_type(i, j, right_i, right_j):
|
|
# match
|
|
mark_matched(i, j)
|
|
mark_matched(left_i, left_j)
|
|
mark_matched(right_i, right_j)
|
|
# Add a bonus if the effect is the same
|
|
if same_effect(i, j, left_i, left_j) and same_effect(i, j, right_i, right_j):
|
|
mark_bonus(i, j)
|
|
|
|
func mark_matched(i, j):
|
|
var g = grid.at(i, j)
|
|
g.set_matched()
|
|
# disconnect signals related to gem displacement
|
|
if is_connected("gems_swapped", g, "update_position"):
|
|
disconnect("gems_swapped", g, "update_position")
|
|
if g.is_connected("position_reached", self, "on_gem_moved"):
|
|
g.disconnect("position_reached", self, "on_gem_moved")
|
|
|
|
func mark_bonus(i, j):
|
|
var g = grid.at(i, j)
|
|
g.set_bonus()
|
|
|
|
# True if gems at (i,j) and (x,y) have the same type
|
|
func same_type(i, j, x, y):
|
|
return grid.at(i, j).type == grid.at(x, y).type
|
|
|
|
# True is gems at (i,j) and (x,y) have the same effect
|
|
func same_effect(i, j, x, y):
|
|
return grid.at(i, j).effect == grid.at(x, y).effect
|
|
|
|
# Cursor management
|
|
func update_cursor():
|
|
# Position
|
|
$Cursor.set_position(64 * Vector2(cursor_i,cursor_j))
|
|
# Selected ?
|
|
var offset = 0
|
|
if gem_selected:
|
|
offset = 128
|
|
$Cursor.texture.region.position.x = offset
|
|
|
|
func show_cursor():
|
|
$Cursor.show()
|
|
|
|
func hide_cursor():
|
|
$Cursor.hide()
|
|
|
|
# Input handling
|
|
# Only handle events if state is READY and not in transition
|
|
func accept_input():
|
|
return (current_state == State.READY) and (next_state == State.NONE)
|
|
|
|
func up():
|
|
if accept_input():
|
|
if gem_selected:
|
|
gem_swap(cursor_i, cursor_j - 1)
|
|
else:
|
|
cursor_j = cursor_j - 1
|
|
if cursor_j < 0:
|
|
cursor_j = grid_height - 1
|
|
update_cursor()
|
|
|
|
func down():
|
|
if accept_input():
|
|
if gem_selected:
|
|
gem_swap(cursor_i, cursor_j + 1)
|
|
else:
|
|
cursor_j = (cursor_j + 1) % grid_height
|
|
update_cursor()
|
|
|
|
func left():
|
|
if accept_input():
|
|
if gem_selected:
|
|
gem_swap(cursor_i - 1, cursor_j)
|
|
else:
|
|
cursor_i = cursor_i - 1
|
|
if cursor_i < 0:
|
|
cursor_i = grid_width - 1
|
|
update_cursor()
|
|
|
|
func right():
|
|
if accept_input():
|
|
if gem_selected:
|
|
gem_swap(cursor_i + 1, cursor_j)
|
|
else:
|
|
cursor_i = (cursor_i + 1) % grid_width
|
|
update_cursor()
|
|
|
|
func select():
|
|
if accept_input():
|
|
gem_selected = !gem_selected
|
|
update_cursor()
|
|
|
|
func cancel():
|
|
if accept_input():
|
|
gem_selected = false
|
|
update_cursor()
|