sokoban
Diffstat (limited to 'Level.gd')
-rw-r--r--Level.gd304
1 files changed, 304 insertions, 0 deletions
diff --git a/Level.gd b/Level.gd
new file mode 100644
index 0000000..df597f8
--- /dev/null
+++ b/Level.gd
@@ -0,0 +1,304 @@
+extends Node2D
+
+const GRID_SIZE = 16
+const grassDecorationIds = [0, 1, 2, 3, 4, 5, 6, 7]
+const treeDecorationIds = [8, 9, 10, 11]
+const stoneDecorationIds = [12, 13, 14, 15, 16]
+const mushroomDecorationIds = [17, 18, 19, 20, 21, 22, 23, 24]
+var explosionEffect = preload("res://ExplosionEffect.tscn")
+
+var thread: Thread
+var wall_positions: PoolVector2Array = []
+var crate_prefab = preload("res://Crate.tscn")
+var target_prefab = preload("res://Target.tscn")
+
+var game_over := false
+
+onready var crates = $LevelContainer/Crates
+onready var player = $LevelContainer/Player
+onready var targets = $LevelContainer/Targets
+onready var walls = $LevelContainer/Walls
+onready var floors = $LevelContainer/Floors
+onready var timer = $Timer
+onready var others = $LevelContainer/Others
+onready var cam = $LevelContainer/Player/Camera2D
+var consol
+
+var current_level := ""
+onready var tilemaps = [walls, others, floors]
+var just_started = true
+
+var level_size = Vector2(0, 0)
+
+signal game_over
+signal level_completed(completed)
+signal level_reset
+signal level_made
+
+
+func _ready():
+ player.connect("level_reset_requested", self, "_on_Player_level_reset_requested")
+ thread = Thread.new()
+ reset_time()
+
+
+func reset_time():
+ MainInstances.stopwatch.reset()
+ player.started = false
+
+
+func start_stopwatch():
+ MainInstances.stopwatch.start()
+
+
+func load_level(level: String, decorate = true):
+ $LevelContainer/Walls.modulate = Color.white
+ $LevelContainer/Player/RayCast2D.set_collision_mask_bit(0, true)
+ if thread.is_alive():
+ return
+ if thread.is_active():
+ thread.wait_to_finish()
+ reset_time()
+ consol = MainInstances.console
+ if decorate:
+ consol.Log("Generating level " + level, .5, .5)
+ SaveLoad.files.level.data.current_level = level
+ SaveLoad.save("level")
+ thread.start(self, "level_load", [level, decorate])
+
+
+func level_load(level: Array):
+ just_started = true
+ player.set_moves(0)
+ current_level = level[0]
+ call_deferred("_reset_level", level[1])
+
+
+func _exit_tree():
+ if thread.is_active():
+ thread.wait_to_finish()
+
+
+func _reset_level(decorate):
+ game_over = false
+ player.initialize()
+ delete_children(crates)
+ if decorate:
+ walls.clear()
+ others.clear()
+ delete_children(floors)
+ delete_children(targets)
+
+ if current_level == "":
+ return
+
+ var file = File.new()
+ file.open("res://Levels/%s.sokolvl" % current_level, File.READ)
+
+ var version = 0
+ var row = 0
+ var player_pos
+ level_size = Vector2(0, 0)
+
+ while !file.eof_reached():
+ var line = file.get_line()
+ if line.begins_with(";"):
+ var meta = line.split(": ", false, 1)
+ if meta[0] == ";version":
+ version = int(meta[1])
+ elif line != "":
+ if version != 1:
+ push_error("Not supported .sokolvl version: " + str(version))
+ return
+ var col = 0
+
+ for x in line:
+ var tile_pos = Vector2(col, row) * GRID_SIZE
+
+ if x == "#":
+ if decorate:
+ add_wall(tile_pos)
+ if x in [".", "X", "O", "@", "%", "A"]:
+ if decorate:
+ add_floor(tile_pos)
+ if x in ["@", "A"]:
+ player_pos = tile_pos
+ if x in ["X", "%"]:
+ add_crate(tile_pos)
+ if x in ["O", "%", "A"]:
+ if decorate:
+ add_target(tile_pos)
+
+ col += 1
+ row += 1
+ level_size.y += 1
+ level_size.x = max(level_size.x, col)
+
+ file.close()
+
+ $CanvasLayer/HUD/LevelLabel.text = "Level = %s" % current_level
+
+ var new_zoom = .5
+ new_zoom = clamp(new_zoom, get_parent().min_zoom, get_parent().max_zoom)
+ new_zoom = Vector2(new_zoom, new_zoom)
+ var level_int
+ if level_size.x > level_size.y:
+ level_int = level_size.x
+ else:
+ level_int = level_size.y
+
+ new_zoom += Vector2(level_int / 45, level_int / 45)
+ $Tween.interpolate_property(
+ cam, "zoom", cam.zoom, new_zoom, 2, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT
+ )
+ $Tween.start()
+ timer.start(2)
+ if decorate:
+ decorate(-50, 50)
+ initialize_player(player_pos)
+ Utils.unload_loading_screen()
+ yield(timer, "timeout")
+ just_started = false
+ emit_signal("level_made")
+ return
+
+
+static func delete_children(node):
+ for n in node.get_children():
+ node.remove_child(n)
+ n.queue_free()
+
+
+func decorate(x, y):
+ for tile in check_for_empty_tile(Vector2(x, y)):
+ match randi() % 101:
+ 1:
+ add_mushroom(tile)
+ 2:
+ add_rock(tile)
+ 3:
+ add_tree(tile)
+
+
+func _on_Crate_target_updated():
+ var crates_in_place = 0
+
+ for crate in crates.get_children():
+ if crate.target_count > 0:
+ crates_in_place += 1
+
+ if crates_in_place == crates.get_child_count():
+ emit_signal("level_completed")
+
+
+func _on_Player_level_reset_requested():
+ if player.tween.is_active():
+ yield(player.tween, "tween_all_completed")
+ load_level(current_level, false)
+ emit_signal("level_reset")
+
+
+func _on_Crate_game_over():
+ if not game_over:
+ game_over = true
+ emit_signal("game_over")
+
+
+func add_target(tile_pos):
+ var target = target_prefab.instance()
+ target.main = self
+ target.position = tile_pos
+ targets.add_child(target)
+
+
+func add_crate(tile_pos):
+ var crate = crate_prefab.instance()
+ crate.position = tile_pos
+ crate.main = get_parent()
+ crate.connect("target_updated", self, "_on_Crate_target_updated")
+ crate.connect("game_over_detected", self, "_on_Crate_game_over")
+ crates.add_child(crate)
+
+
+func initialize_player(tile_pos):
+ player.position = tile_pos
+ player.world = get_parent()
+
+
+func add_floor(tile_pos):
+ floors.set_cellv(tile_pos / 16, 0)
+ randomize()
+ if randi() % 5 == 2:
+ others.set_cellv(tile_pos / 16, grassDecorationIds[randi() % grassDecorationIds.size()])
+
+
+func add_wall(tile_pos):
+ wall_positions.append(tile_pos)
+ walls.set_cellv(tile_pos / 16, 1)
+ walls.update_bitmask_area(tile_pos / 16)
+
+
+func check_for_empty_tile(size: Vector2 = Vector2(-75, 75)):
+ var empty_tiles: PoolVector2Array = []
+ for x in range(size.x, size.y):
+ for y in range(size.x, size.y):
+ var tile_pos = Vector2(x, y)
+ var lower_tile_pos = tile_pos
+ var left_tile_pos = tile_pos
+ var right_tile_pos = tile_pos
+ var up_tile_pos = tile_pos
+ var down_right_tile_pos = tile_pos
+ var down_left_tile_pos = tile_pos
+ down_right_tile_pos += Vector2.DOWN + Vector2.RIGHT
+ down_left_tile_pos += Vector2.DOWN + Vector2.LEFT
+ lower_tile_pos += Vector2.DOWN
+ left_tile_pos += Vector2.LEFT
+ right_tile_pos += Vector2.RIGHT
+ up_tile_pos += Vector2.UP
+ var tile_positions = [
+ down_left_tile_pos,
+ down_right_tile_pos,
+ lower_tile_pos,
+ left_tile_pos,
+ right_tile_pos,
+ up_tile_pos,
+ tile_pos
+ ]
+ var count2 := 0
+ for tile in tile_positions:
+ var count := 0
+ for tilemap in tilemaps:
+ if empty(tilemap, tile):
+ count += 1
+ if count == tilemaps.size():
+ count2 += 1
+ if count2 == tile_positions.size():
+ empty_tiles.append(tile_pos)
+ return empty_tiles
+
+
+func empty(tilemap, tile) -> bool:
+ if tilemap.get_cellv(tile) != -1:
+ return false
+ return true
+
+
+func add_tree(tile):
+ others.set_cellv(tile, treeDecorationIds[randi() % treeDecorationIds.size()])
+
+
+func add_rock(tile):
+ others.set_cellv(tile, stoneDecorationIds[randi() % stoneDecorationIds.size()])
+
+
+func add_mushroom(tile):
+ others.set_cellv(tile, mushroomDecorationIds[randi() % mushroomDecorationIds.size()])
+
+
+func explode_walls():
+ for positions in wall_positions:
+ Utils.instance_scene_on_main(positions, explosionEffect)
+
+
+func _on_Player_won():
+ emit_signal("level_completed", true)