extends Node2D signal dropoff_score_changed signal game_ended # Declare member variables here. Examples: # var a = 2 # var b = "text" const Dropoff = preload("DropOff.tscn") const Pickup = preload("Pickup.tscn") const Start = preload("Start.tscn") const Empty = preload("Empty.tscn") const Link = preload("Link.tscn") const Character = preload("Character.tscn") # Configuration parameters const default_params = { 'seed': 'ld53', 'node_count': 12, 'cargo_availability': 2.0, 'pickup_count_range': Vector2(1, 2), 'empty_node_chance': 15, 'dropoff_value_range': Vector2(1, 3), 'dropoff_capacity_range': Vector2(1, 3), 'link_cost_range': Vector2(1, 2), } static func easy_params(): var p = default_params.duplicate() p.cargo_availability = INF; p.pickup_count_range = Vector2(1, 1) p.node_count = 6 p.dropoff_capacity_range = Vector2(1, 2) p.link_cost_range = Vector2(1, 1) return p static func medium_params(): var p = default_params.duplicate() return p static func hard_params(): var p = default_params.duplicate() p.pickup_count_range = Vector2(1, 3) p.cargo_availability = 1.0 p.node_count = 24 p.dropoff_capacity_range = Vector2(1, 5) p.link_cost_range = Vector2(1, 3) return p var params = null var random = RandomNumberGenerator.new() func get_levelcode(): return Marshalls.variant_to_base64(self.params) static func parse_levelcode(code): var p = Marshalls.base64_to_variant(code) # Make sure we have the keys present in the default params # Extraneous keys are ignored if p != null and !p.has_all(default_params.keys()): return null return p func restart(new_params = null): if new_params: self.params = new_params for n in self.get_children(): n.free() self._init(self.params['seed']) self.params = new_params self._ready() func _init(random_seed: String = "ld53"): self.random = RandomNumberGenerator.new() print("Seed: %s" % random_seed) self.random.set_seed(hash(random_seed)) func random_position(): var bottom = self.random.randi_range(0, 1) var right = self.random.randi_range(0, 1) var x_range = Vector2( 32 + (right * 512), 1024 + ((right - 1) * 512) ) var y_range = Vector2( 32 + (bottom * 300), 600 + ((bottom - 1) * 300) ) return Vector2( self.random.randi_range(x_range.x, x_range.y), self.random.randi_range(y_range.x, y_range.y) ) func indexed_position(index): # Positions are on a grid var row_count = 3 var col_count = 4 if self.params.node_count == 24: col_count = 6 row_count = 4 if self.params.node_count == 36: col_count = 6 row_count = 6 var row = index % row_count var col = index / row_count var position = Vector2( (1024 / col_count) * col, (600 / row_count) * row ) var jitter_value = 64 var jitter = Vector2( self.random.randi_range(-jitter_value, jitter_value), self.random.randi_range(-jitter_value, jitter_value) ) return position + Vector2(64, 64) + jitter func distance_to_closest_node(position, node): var closest = 65000 for n in get_tree().get_nodes_in_group("nodes"): if n == node: continue var distance = n.get_position().distance_to(position) if distance < closest: closest = distance return closest func link_nodes(n1, n2): assert(n1 != null) assert(n2 != null) var n = Link.instance() n.set_ends(n1, n2) n1.peers.append(n2) n2.peers.append(n1) n1.remove_from_group("unlinked") n2.remove_from_group("unlinked") add_child(n) # print("Linked ", n1, " to ", n2) # print("\tN1 peers: ", n1.peers) # print("\tN2 peers: ", n2.peers) return n func reconstruct_path(cameFrom, current): var total_path = [] while cameFrom.has(current): current = cameFrom[current] total_path.push_front(current) return total_path func find_path(start, destination): var openSet = [start] var from = {} var gScore = {} var fScore = {} fScore[start] = 1 while openSet.size() > 0: print(openSet) var current = null for n in openSet: if !fScore.has(n): fScore[n] = INF if current == null: current = n else: if fScore[n] < fScore[current]: current = n if current == destination: return reconstruct_path(from, current) openSet.remove(openSet.find(current)) for n in current.peers: if !gScore.has(current): gScore[current] = INF var tentative_score = gScore[current] + 1 if gScore.has(n) && tentative_score < gScore[n]: from[n] = current fScore[n] = tentative_score + 100 gScore[n] = tentative_score if openSet.find(n) == -1: openSet.append(n) return null func get_reachable_nodes(start): var nodes_in_starting_graph = [start] + start.peers var nodes_to_check = start.peers var nodes_checked = [start] while nodes_to_check.size() > 0: var checking_node = nodes_to_check.pop_front() #print("Checking: ", checking_node) #print("\tPeers", checking_node.peers) if nodes_checked.find(checking_node) != -1: continue for nx in checking_node.peers: if nodes_checked.find(nx) == -1: nodes_to_check.append(nx) if nodes_in_starting_graph.find(checking_node) == -1: nodes_in_starting_graph.append(checking_node) nodes_checked.append(checking_node) return nodes_in_starting_graph # Called when the node enters the scene tree for the first time. func _ready(): if self.params == null: print("Resetting params") self.params = easy_params() var nodes = 0 var n = null # Add start n = Start.instance() n.set_position(Vector2(512 - 32, 300-32)) n.add_to_group("unlinked") n.name_from_index(nodes) self.add_child(n) var start = n nodes += 1 # Add pickup(s) var count = self.random.randi_range(self.params.pickup_count_range.x, self.params.pickup_count_range.y) while count > 0: n = Pickup.instance() n.add_to_group("unlinked") n.name_from_index(nodes) self.add_child(n) count -= 1 nodes += 1 # Add other node(s) var total_to_dropoff = 0 while nodes < self.params.node_count + 1: if self.random.randi_range(0, 99) < self.params.empty_node_chance: n = Empty.instance() else: n = Dropoff.instance() n.capacity = self.random.randi_range(self.params.dropoff_capacity_range.x, self.params.dropoff_capacity_range.y) n.value = self.random.randi_range(self.params.dropoff_value_range.x, self.params.dropoff_value_range.y) total_to_dropoff += n.capacity n.add_to_group("unlinked") n.name_from_index(nodes) self.add_child(n) print("Added node: ", n) nodes += 1 # Distribute available cargo between dropoff points randomly when finite # amount available. if !is_inf(self.params.cargo_availability): var cargo_available = total_to_dropoff * self.params.cargo_availability for cargo_node in get_tree().get_nodes_in_group("pickup"): var cargo_count = self.random.randi_range(1, cargo_available) cargo_available -= cargo_count cargo_node.set_stored(cargo_count) if cargo_available > 0: get_tree().get_nodes_in_group("pickup")[-1].add_stored(cargo_available) # Each node should have at least one link var unlinked_nodes = get_tree().get_nodes_in_group("unlinked") while unlinked_nodes.size() > 0: var source = unlinked_nodes.pop_front() var targets = get_tree().get_nodes_in_group("nodes") var target = targets[self.random.randi_range(0, targets.size()-1)] while target == source: target = targets[self.random.randi_range(0, targets.size()-1)] var link = link_nodes(source, target) link.set_weight(self.random.randi_range( self.params.link_cost_range.x, self.params.link_cost_range.y )) unlinked_nodes = get_tree().get_nodes_in_group("unlinked") # For each node, make sure we can reach the start node, merging # any disjointed graphs var nodes_in_starting_graph = get_reachable_nodes(start) print("Starting graph nodes: ", nodes_in_starting_graph) for node in get_tree().get_nodes_in_group("nodes"): if nodes_in_starting_graph.find(node) == -1: print(node, " can't be reached from start") var target = nodes_in_starting_graph[self.random.randi_range(0, nodes_in_starting_graph.size()-1)] link_nodes(node, target) nodes_in_starting_graph = get_reachable_nodes(start) # Set new node positions # @TODO: Better (?) layout algorithm, eg. https://en.wikipedia.org/wiki/Graph_drawing var x_range = Vector2(32, 1024-32) var y_range = Vector2(32, 600-32) var distance_threshold = 32 var idx = 0 for node in get_tree().get_nodes_in_group("nodes"): if node.is_in_group("start"): continue var position = self.indexed_position(idx) var tries = 0 var closest = distance_to_closest_node(position, node) # While the position is within a distance of another node, rechoose while closest <= distance_threshold: if closest == 0: closest = 65000 for n2 in get_tree().get_nodes_in_group("nodes"): if n2 == node: continue var distance = n2.get_position().distance_to(position) if distance < closest: closest = distance # print("Closest point to ", position, " ", closest) tries += 1 if tries % 10 == 0: distance_threshold /= 2 print("Reducing threshold for point ", node, " to ", distance_threshold) position = self.random_position() node.set_position(position) # print("Placed node ", node, " after ", tries, " tries") idx += 1 # Update link linepositions based on the node positions for link in get_tree().get_nodes_in_group("links"): link.redraw_line() # @BUG It shouldn't be necessary to recalculate the peers # For some reason, we lose the peer on the start node if link.start == start: start.peers.append(link.end) if link.end == start: start.peers.append(link.start) # Add player n = Character.instance() add_child(n) n.add_to_group("character") n.set_location(start, true) n.connect("arrived_at", self, "on_character_arrival") func on_character_arrival(node): if node.is_in_group("dropoff"): var max_delivery = node.capacity - node.stored var max_avail = get_node("Character").stored var delta = min(max_avail, max_delivery) print("Dropoff %d to %s" % [delta, node]) get_node("Character").dropoff(delta) node.receive(delta) # Recalculate dropoff score var score = self.get_supplied() var all_supplied = score[0] >= score[1] emit_signal("dropoff_score_changed", [score[2], score[3]]) if all_supplied: emit_signal("game_ended") elif node.is_in_group("pickup"): var max_pickup = get_node("Character").capacity - get_node("Character").stored var min_avail = node.stored var delta = min(min_avail, max_pickup) print("Pickup up %d from %s" % [delta, node]) get_node("Character").pickup(delta) node.drain(delta) elif node.is_in_group("start"): print("@TODO: Start") func get_moves(): return get_node("Character").movement_cost func get_score(): var data = get_supplied() # Delivered + Supplied bonus - Movement return data[2] + data[3] - get_moves() func get_supplied(): # [nodes_done, nodes_total, earned_value, cargo_done, cargo_total] var done = 0 var dropoff_score = 0 var cargo_done = 0 var cargo_total = 0 for n in get_tree().get_nodes_in_group("dropoff"): cargo_total += n.capacity cargo_done += n.stored if n.stored >= n.capacity: dropoff_score += n.value done += 1 return [ done, get_tree().get_nodes_in_group("dropoff").size(), dropoff_score, cargo_done, cargo_total ] # Called every frame. 'delta' is the elapsed time since the previous frame. #func _process(delta): # pass