ld53/Game.gd

388 lines
11 KiB
GDScript3

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