commit 3ee40cae0c118ed808c69ae588a6aa2c677749e8 Author: Feufochmar Date: Tue May 24 18:12:10 2022 +0200 Initial commit. Matching game diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a96203 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.import/ diff --git a/Main.gd b/Main.gd new file mode 100644 index 0000000..329e501 --- /dev/null +++ b/Main.gd @@ -0,0 +1,20 @@ +extends Node + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(_delta): + if Input.is_action_just_pressed("ui_up"): + $GemGrid.up() + elif Input.is_action_just_pressed("ui_down"): + $GemGrid.down() + elif Input.is_action_just_pressed("ui_left"): + $GemGrid.left() + elif Input.is_action_just_pressed("ui_right"): + $GemGrid.right() + elif Input.is_action_just_pressed("ui_accept"): + $GemGrid.select() + elif Input.is_action_just_pressed("ui_cancel"): + $GemGrid.cancel() diff --git a/Main.tscn b/Main.tscn new file mode 100644 index 0000000..62944d8 --- /dev/null +++ b/Main.tscn @@ -0,0 +1,126 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://entities/gem_grid.tscn" type="PackedScene" id=1] +[ext_resource path="res://entities/mana_counter.tscn" type="PackedScene" id=2] +[ext_resource path="res://Main.gd" type="Script" id=3] + +[node name="Main" type="Node"] +script = ExtResource( 3 ) + +[node name="GemGrid" parent="." instance=ExtResource( 1 )] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -256.0 +margin_top = -256.0 +margin_right = 256.0 +margin_bottom = 256.0 + +[node name="Counters" type="Control" parent="."] +margin_right = 256.0 +margin_bottom = 600.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ManaCounter" parent="Counters" instance=ExtResource( 2 )] + +[node name="ManaCounter2" parent="Counters" instance=ExtResource( 2 )] +margin_top = 32.0 +margin_bottom = 64.0 +type = 1 + +[node name="ManaCounter3" parent="Counters" instance=ExtResource( 2 )] +margin_top = 64.0 +margin_bottom = 96.0 +type = 2 + +[node name="ManaCounter4" parent="Counters" instance=ExtResource( 2 )] +margin_top = 96.0 +margin_bottom = 128.0 +type = 3 + +[node name="ManaCounter5" parent="Counters" instance=ExtResource( 2 )] +margin_top = 128.0 +margin_bottom = 160.0 +type = 4 + +[node name="ManaCounter6" parent="Counters" instance=ExtResource( 2 )] +margin_top = 160.0 +margin_bottom = 192.0 +type = 5 + +[node name="ManaCounter7" parent="Counters" instance=ExtResource( 2 )] +margin_top = 192.0 +margin_bottom = 224.0 +type = 6 + +[node name="ManaCounter8" parent="Counters" instance=ExtResource( 2 )] +margin_top = 224.0 +margin_bottom = 256.0 +type = 7 + +[node name="ManaCounter9" parent="Counters" instance=ExtResource( 2 )] +margin_top = 256.0 +margin_bottom = 288.0 +effect = 1 + +[node name="ManaCounter10" parent="Counters" instance=ExtResource( 2 )] +margin_top = 288.0 +margin_bottom = 320.0 +type = 1 +effect = 1 + +[node name="ManaCounter11" parent="Counters" instance=ExtResource( 2 )] +margin_top = 320.0 +margin_bottom = 352.0 +type = 2 +effect = 1 + +[node name="ManaCounter12" parent="Counters" instance=ExtResource( 2 )] +margin_top = 352.0 +margin_bottom = 384.0 +type = 3 +effect = 1 + +[node name="ManaCounter13" parent="Counters" instance=ExtResource( 2 )] +margin_top = 384.0 +margin_bottom = 416.0 +type = 4 +effect = 1 + +[node name="ManaCounter14" parent="Counters" instance=ExtResource( 2 )] +margin_top = 416.0 +margin_bottom = 448.0 +type = 5 +effect = 1 + +[node name="ManaCounter15" parent="Counters" instance=ExtResource( 2 )] +margin_top = 448.0 +margin_bottom = 480.0 +type = 6 +effect = 1 + +[node name="ManaCounter16" parent="Counters" instance=ExtResource( 2 )] +margin_top = 480.0 +margin_bottom = 512.0 +type = 7 +effect = 1 + +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter2" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter3" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter4" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter5" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter6" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter7" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter8" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter9" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter10" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter11" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter12" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter13" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter14" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter15" method="update_count"] +[connection signal="gem_matched" from="GemGrid" to="Counters/ManaCounter16" method="update_count"] diff --git a/default_env.tres b/default_env.tres new file mode 100644 index 0000000..20207a4 --- /dev/null +++ b/default_env.tres @@ -0,0 +1,7 @@ +[gd_resource type="Environment" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] + +[resource] +background_mode = 2 +background_sky = SubResource( 1 ) diff --git a/entities/gem.gd b/entities/gem.gd new file mode 100644 index 0000000..fabccf2 --- /dev/null +++ b/entities/gem.gd @@ -0,0 +1,110 @@ +extends Control +class_name Gem + +signal position_reached() + +# Gem type enum +enum Type { Thermic, Optic, Kinetic, Electromagnetic, Acoustic, Biologic, Chemic } #, Skull } +# Gem effect enum +enum Effect { ManaNegative, ManaPositive, EffectNegative, EffectPositive } +const effect_distribution = { + Effect.ManaNegative: 45, + Effect.ManaPositive: 45, + Effect.EffectNegative: 5, + Effect.EffectPositive: 5 + } + +# Variables +const unscaled_size = 128 +export(Type) var type = Type.Thermic +export(Effect) var effect = Effect.ManaNegative +# Indicate if animation are played +var animate = false +# For removing +var matched: bool = false +var bonus: int = 0 +# For moving +var next_i: int = 0 +var next_j: int = 0 +var current_i: int = 0 +var current_j: int = 0 +var move_dist: float = 0.0 +var move_inc: float = 0.0 +var moving: bool = false + +# Called when the node enters the scene tree for the first time. +func _ready(): + update_sprite() + +# Called periodically +func _process(delta): + if moving: + move_inc += 4*delta + if (move_inc >= move_dist) or ((current_i == next_i) and (current_j == next_j)): + reach_position() + else: + var pos = size() * lerp(Vector2(current_i, current_j), Vector2(next_i, next_j), move_inc / move_dist) + set_position(pos) + +# Randomize a gem +func randomize(rnd: Random): + type = rnd.pick_from_array(Type.values()) + effect = rnd.pick_from_distribution(effect_distribution) + update_sprite() + +# Update the sprite from its type and effect +func update_sprite(): + $Sprite.region_rect.position.x = unscaled_size * (type % Type.size()) + $Sprite.region_rect.position.y = unscaled_size * (effect % Effect.size()) + +# Used for movement and position +func size(): + return $Sprite.transform.get_scale().x * unscaled_size + +# Match a gem +func set_matched(): + if !matched: + matched = true + if animate: + $Sprite.hide() + $ParticlesMatch.emitting = true + $ParticlesTimer.start() + else: + queue_free() + +# Bonus value +func set_bonus(): + bonus += 1 + +# Enable animations and particles +func enable_animations(): + animate = true + +# Set the position on the grid +func set_grid_position(i, j): + next_i = i + next_j = j + current_i = i + current_j = j + set_position(size() * Vector2(i,j)) + +# Move to grid position +func move_to_position(i, j): + next_i = i + next_j = j + move_dist = Vector2(current_i, current_j).distance_to(Vector2(next_i, next_j)) + +func update_position(): + if animate: + moving = true + else: + reach_position() + +func reach_position(): + move_inc = 0.0 + moving = false + current_i = next_i + current_j = next_j + set_position(size() * Vector2(next_i, next_j)) + if animate: + emit_signal("position_reached") diff --git a/entities/gem.tscn b/entities/gem.tscn new file mode 100644 index 0000000..6495442 --- /dev/null +++ b/entities/gem.tscn @@ -0,0 +1,44 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://entities/gem.gd" type="Script" id=1] +[ext_resource path="res://entities/gems.svg" type="Texture" id=2] +[ext_resource path="res://entities/matched-particle.svg" type="Texture" id=3] + +[sub_resource type="AtlasTexture" id=1] +flags = 4 +atlas = ExtResource( 2 ) +region = Rect2( 0, 0, 1024, 512 ) + +[node name="Gem" type="Control"] +margin_right = 64.0 +margin_bottom = 64.0 +rect_min_size = Vector2( 64, 64 ) +script = ExtResource( 1 ) + +[node name="Sprite" type="Sprite" parent="."] +scale = Vector2( 0.5, 0.5 ) +texture = SubResource( 1 ) +centered = false +region_enabled = true +region_rect = Rect2( 0, 0, 128, 128 ) + +[node name="ParticlesMatch" type="CPUParticles2D" parent="."] +position = Vector2( 32, 32 ) +z_index = 1 +emitting = false +amount = 25 +one_shot = true +explosiveness = 0.5 +texture = ExtResource( 3 ) +emission_shape = 1 +emission_sphere_radius = 32.0 +angular_velocity = 90.0 +angular_velocity_random = 0.5 +scale_amount = 0.5 +scale_amount_random = 0.5 + +[node name="ParticlesTimer" type="Timer" parent="."] +wait_time = 5.0 +one_shot = true + +[connection signal="timeout" from="ParticlesTimer" to="." method="queue_free"] diff --git a/entities/gem_grid.gd b/entities/gem_grid.gd new file mode 100644 index 0000000..b29f84c --- /dev/null +++ b/entities/gem_grid.gd @@ -0,0 +1,328 @@ +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) +#var grid: Array = [Array(), Array(), Array(), Array(), Array(), Array(), Array(), Array()] + +# 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. The initial positions depends on the flow direction. + var counts = [0] + var dir = grid.get_flow_direction() + match dir: + LogicalGrid.FlowDirection.Up: + counts.resize(grid_width) + LogicalGrid.FlowDirection.Down: + counts.resize(grid_width) + LogicalGrid.FlowDirection.Left: + counts.resize(grid_height) + LogicalGrid.FlowDirection.Right: + counts.resize(grid_height) + for i in range(counts.size()): + counts[i] = 0 + for idx in to_remove: + var screen_idx = [] + var i = 0 + 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]) + ## Add new gems to fill the empty spaces + #for i in range(grid_width): + # var count = 0 + # for _j in range(grid[i].size(), grid_height): + # # gems are not added on the position they are on the grid, + # # but outside so that animation plays correctly + # add_gem(i, count + grid_height) + # count += 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() diff --git a/entities/gem_grid.tscn b/entities/gem_grid.tscn new file mode 100644 index 0000000..94d5fe9 --- /dev/null +++ b/entities/gem_grid.tscn @@ -0,0 +1,44 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://entities/gem_grid.gd" type="Script" id=1] +[ext_resource path="res://entities/gem_selector.svg" type="Texture" id=2] + +[sub_resource type="AtlasTexture" id=1] +flags = 4 +atlas = ExtResource( 2 ) +region = Rect2( 0, 0, 128, 128 ) + +[node name="GemGrid" type="Control"] +margin_right = 512.0 +margin_bottom = 512.0 +rect_clip_content = true +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ColorRect" type="ColorRect" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +color = Color( 0.603922, 0.647059, 0.65098, 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Gems" type="Control" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Cursor" type="TextureRect" parent="."] +margin_right = 64.0 +margin_bottom = 64.0 +rect_min_size = Vector2( 64, 64 ) +texture = SubResource( 1 ) +expand = true +stretch_mode = 1 +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/entities/gem_selector.svg b/entities/gem_selector.svg new file mode 100644 index 0000000..b7a35f6 --- /dev/null +++ b/entities/gem_selector.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + diff --git a/entities/gem_selector.svg.import b/entities/gem_selector.svg.import new file mode 100644 index 0000000..e0f623d --- /dev/null +++ b/entities/gem_selector.svg.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/gem_selector.svg-7ce7dce652630ac09e42a628d1b9123f.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://entities/gem_selector.svg" +dest_files=[ "res://.import/gem_selector.svg-7ce7dce652630ac09e42a628d1b9123f.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/entities/gems.svg b/entities/gems.svg new file mode 100644 index 0000000..86e6e93 --- /dev/null +++ b/entities/gems.svg @@ -0,0 +1,1399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/entities/gems.svg.import b/entities/gems.svg.import new file mode 100644 index 0000000..aa90f48 --- /dev/null +++ b/entities/gems.svg.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/gems.svg-4ba00769f724f70394c92013b0796a53.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://entities/gems.svg" +dest_files=[ "res://.import/gems.svg-4ba00769f724f70394c92013b0796a53.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/entities/mana_counter.gd b/entities/mana_counter.gd new file mode 100644 index 0000000..cc2e2df --- /dev/null +++ b/entities/mana_counter.gd @@ -0,0 +1,20 @@ +extends Control + +export(Gem.Type) var type = Gem.Type.Thermic +export(Gem.Effect) var effect = Gem.Effect.ManaNegative + +var count: int = 0 + +# Called when the node enters the scene tree for the first time. +func _ready(): + $Gem.type = type + $Gem.effect = effect + $Gem.update_sprite() + $Label.text = str(count) + +func update_count(matched: Dictionary): + for k in matched.keys(): + if (k[0] == type) and (k[1] == effect): + count += matched[k] + break + $Label.text = str(count) diff --git a/entities/mana_counter.tscn b/entities/mana_counter.tscn new file mode 100644 index 0000000..f23e505 --- /dev/null +++ b/entities/mana_counter.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://entities/gem.tscn" type="PackedScene" id=1] +[ext_resource path="res://entities/mana_counter.gd" type="Script" id=2] + +[node name="ManaCounter" type="Control"] +margin_right = 192.0 +margin_bottom = 32.0 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Gem" parent="." instance=ExtResource( 1 )] +rect_scale = Vector2( 0.5, 0.5 ) + +[node name="Label" type="Label" parent="."] +margin_left = 40.0 +margin_top = 8.0 +margin_right = 192.0 +margin_bottom = 22.0 +text = "0000" +clip_text = true diff --git a/entities/matched-particle.svg b/entities/matched-particle.svg new file mode 100644 index 0000000..5fc308b --- /dev/null +++ b/entities/matched-particle.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/entities/matched-particle.svg.import b/entities/matched-particle.svg.import new file mode 100644 index 0000000..3ede317 --- /dev/null +++ b/entities/matched-particle.svg.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/matched-particle.svg-416c5e1325ce98f7ffcae31d9908d4ad.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://entities/matched-particle.svg" +dest_files=[ "res://.import/matched-particle.svg-416c5e1325ce98f7ffcae31d9908d4ad.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..c98fbb6 Binary files /dev/null and b/icon.png differ diff --git a/icon.png.import b/icon.png.import new file mode 100644 index 0000000..a4c02e6 --- /dev/null +++ b/icon.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..a47442d --- /dev/null +++ b/project.godot @@ -0,0 +1,53 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +_global_script_classes=[ { +"base": "Control", +"class": "Gem", +"language": "GDScript", +"path": "res://entities/gem.gd" +}, { +"base": "Reference", +"class": "LogicalGrid", +"language": "GDScript", +"path": "res://tools/logical_grid.gd" +}, { +"base": "Reference", +"class": "Random", +"language": "GDScript", +"path": "res://tools/random.gd" +} ] +_global_script_class_icons={ +"Gem": "", +"LogicalGrid": "", +"Random": "" +} + +[application] + +config/name="Match Battle" +run/main_scene="res://Main.tscn" +config/icon="res://icon.png" + +[display] + +window/stretch/mode="2d" +window/stretch/aspect="keep" + +[physics] + +common/enable_pause_aware_picking=true + +[rendering] + +quality/driver/driver_name="GLES2" +vram_compression/import_etc=true +vram_compression/import_etc2=false +environment/default_environment="res://default_env.tres" diff --git a/tools/logical_grid.gd b/tools/logical_grid.gd new file mode 100644 index 0000000..d9c3233 --- /dev/null +++ b/tools/logical_grid.gd @@ -0,0 +1,102 @@ +extends Reference +class_name LogicalGrid + +# Grid with a flow direction for the item added / removed +# The position on grid must be translated as the position in the array is different + +enum FlowDirection { Up, Down, Left, Right } + +# Fields +var width: int # width +var height: int # height +var table: Array = [] # table = array of columns or rows depending on the direction +var flow_direction = FlowDirection.Up + +# Constructor +func _init(width: int, height: int): + self.width = width + self.height = height + # By default, array is initialized with empty arrays + for x in range(0, width): + table.push_back(Array()) + +# Get flow direction +func get_flow_direction(): + return self.flow_direction + +# Set flow direction +func set_flow_direction(dir): + # Keep old values + var old_table = self.table + var old_dir = self.flow_direction + # Update the flow direction + self.flow_direction = dir + # Update the array to reflect the change in flow direction + self.table = [] + if (dir == FlowDirection.Up) or (dir == FlowDirection.Down): + for nx in range(width): + self.table.push_back(Array()) + for ny in range(height): + self.table[nx].push_back(false) + else: + for ny in range(height): + self.table.push_back(Array()) + for nx in range(width): + self.table[ny].push_back(false) + # Set the real values + for i in range(width): + for j in range(height): + var old_idx = get_idx(i, j, old_dir) + var new_idx = get_idx(i, j, dir) + self.table[new_idx[0]][new_idx[1]] = old_table[old_idx[0]][old_idx[1]] + +# Get the index in the table depending on the flow direction +func get_idx(x, y, dir = self.flow_direction): + match dir: + FlowDirection.Up: + return [x, y] + FlowDirection.Down: + return [x, height - 1 - y] + FlowDirection.Left: + return [y, x] + FlowDirection.Right: + return [y, width - 1 - x] + +# Get value at position +func at(x: int, y: int): + var idx = get_idx(x, y) + return self.table[idx[0]][idx[1]] + +# Set value at position +func set_at(x: int, y: int, val): + var idx = get_idx(x, y) + self.table[idx[0]][idx[1]] = val + +# Remove a value from the table +#func remove_at(x: int, y:int): +# var idx = get_idx(x, y) +# self.table[idx[0]].remove(idx[1]) + +# Remove the values from a list of indexes +class IdxSorter: + static func higher_idx_first(a, b): + return (a[0] > b[0]) or ((a[0] == b[0]) and (a[1] > b[1])) +func remove_values(lst_idx: Array): + # Tranform the values + var new_idx = [] + for idx in lst_idx: + new_idx.push_back(get_idx(idx[0], idx[1])) + new_idx.sort_custom(IdxSorter, "higher_idx_first") + for idx in new_idx: + self.table[idx[0]].remove(idx[1]) + +# Push a value to the grid, the position indicate the row or column to push to +func push_value(x: int, y: int, val): + var idx = get_idx(x, y) + self.table[idx[0]].push_back(val) + +# Swap two values +func swap_values(x1: int, y1: int, x2: int, y2: int): + var v = self.at(x1, y1) + self.set_at(x1, y1, self.at(x2, y2)) + self.set_at(x2, y2, v) diff --git a/tools/random.gd b/tools/random.gd new file mode 100644 index 0000000..e60f129 --- /dev/null +++ b/tools/random.gd @@ -0,0 +1,36 @@ +extends Reference +class_name Random + +# Generator +var generator: RandomNumberGenerator = RandomNumberGenerator.new() + +# Init +func _init(): + generator.randomize() + +# Generate from a range (inclusive) +func pick_from_range(v1: int, v2: int): + return generator.randi_range(v1, v2) + +# Generate from an Array +func pick_from_array(a: Array): + var idx = generator.randi_range(0, a.size() - 1) + return a[idx] + +# Generate from a dictionary of (value, weight) +func pick_from_distribution(d: Dictionary): + var keys = d.keys() + var weight_sum = 0 + for k in keys: + weight_sum += d[k] + # pick a number between 0 and the total minus 1 + var val = generator.randi_range(0, weight_sum - 1) + # Compute the sums until val < current_sum + var current_sum = 0 + var ret = keys.front() + for k in keys: + current_sum += d[k] + ret = k + if val < current_sum: + break + return ret