Skip to content

Commit

Permalink
feat: support constraints as arrays/dicts and with opts
Browse files Browse the repository at this point in the history
This code is crazy, hopefully the unit tests are enough to combat it.
  • Loading branch information
russmatney committed Apr 3, 2024
1 parent 8bb54c8 commit 273e3ab
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 109 deletions.
5 changes: 4 additions & 1 deletion addons/core/Util.gd
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,10 @@ static func setup_popup_items(popup: PopupMenu, items: Array, on_select: Callabl


############################################################
## random ###########################################################
## repeate/random ###########################################################

static func repeat(s, n):
return range(n).map(func(_x): return s)

static func rand_of(arr, n=1):
if len(arr) == 0:
Expand Down
195 changes: 94 additions & 101 deletions src/dino/modes/vania/RoomInputs.gd
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,20 @@ var entities
var room_shape
var room_shapes
var tilemap_scenes
var constraints

func _init(opts={}):
entities = opts.get("entities", [])
room_shape = opts.get("room_shape")
room_shapes = opts.get("room_shapes", [])
tilemap_scenes = opts.get("tilemap_scenes", [])
constraints = opts.get("constraints", [])

func to_printable():
return {
entities=entities,
tilemap_scenes=tilemap_scenes,
room_shape=room_shape,
room_shapes=room_shapes,
}

## merge ######################################################

Expand All @@ -82,14 +88,11 @@ func merge(b: RoomInputs):
room_shape=U._or(b.room_shape, room_shape),
room_shapes=U.distinct(U.append_array(room_shapes, b.room_shapes)),
tilemap_scenes=U.distinct(U.append_array(tilemap_scenes, b.tilemap_scenes)),
constraints=U.append_array(constraints, b.constraints),
})

## update room def ######################################################

func update_def(def: VaniaRoomDef):
def.constraints = constraints

if room_shape:
def.local_cells = room_shape
elif not room_shapes.is_empty():
Expand Down Expand Up @@ -127,39 +130,79 @@ static func merge_many(inputs):
return inputs.reduce(func(a, b): return a.merge(b))

static func apply_constraints(conses, def: VaniaRoomDef):
var shape = def.local_cells
var ri = conses.reduce(RoomInputs.apply_constraint, RoomInputs.new())
def.constraints = conses

var existing_shape = def.local_cells

if conses is RoomInputs:
conses.update_def(def)
if existing_shape != null and not existing_shape.is_empty():
# maintain current local_cells
def.local_cells = existing_shape
return conses

if conses is Dictionary:
conses = [conses]

if not conses is Array:
Log.warn("Unhandled constraint collection passed to apply_constraints, aborting", conses)
return

var ri = conses.map(func(cons):
# map constants to dicts with default opts
if cons is String:
return {cons: {}}
elif cons is Dictionary:
return cons
else:
Log.warn("Unhandled constraint passed to apply_constraints, ignoring", cons)
return null
).reduce(
func(agg_inp, cons_dict):
if cons_dict == null:
return agg_inp
# apply each constraint_dict in succession
return RoomInputs.apply_constraint(agg_inp, cons_dict),
RoomInputs.new())

ri.update_def(def)
if shape != null and not shape.is_empty():
# maintain current local_cells
def.local_cells = shape

if existing_shape != null and not existing_shape.is_empty():
# maintain pre-existing local_cells
def.local_cells = existing_shape
return ri

static func apply_constraint(inp: RoomInputs, constraint):
var cons_inp = RoomInputs.get_constraint_data(constraint)
return inp.merge(cons_inp)

static func get_constraint_data(constraint):
match constraint:
IN_LARGE_ROOM: return large_room()
IN_SMALL_ROOM: return small_room()
IN_WOODEN_BOXES: return wooden_boxes()
IN_SPACESHIP: return spaceship()
IN_KINGDOM: return kingdom()
IN_VOLCANO: return volcano()
IN_GRASSY_CAVE: return grassy_cave()

IS_COOKING_ROOM: return cooking_room()

HAS_BOSS: return has_boss()
HAS_ENEMY: return has_enemy()
HAS_TARGET: return has_target()
HAS_LEAF: return has_leaf()
HAS_PLAYER: return has_player()

HAS_COOKING_POT: return has_entity("CookingPot")
HAS_BLOB: return has_entity("Blob")
HAS_VOID: return has_entity("Void")
static func apply_constraint(inp: RoomInputs, cons_dict):
for k in cons_dict.keys():
var cons_inp = RoomInputs.get_constraint_data(k, cons_dict.get(k))
inp = inp.merge(cons_inp)
return inp

static func get_constraint_data(cons_key, opts={}):
match cons_key:
IN_LARGE_ROOM: return large_room(opts)
IN_SMALL_ROOM: return small_room(opts)
IN_TALL_ROOM: return tall_room(opts)
IN_WIDE_ROOM: return wide_room(opts)
IN_WOODEN_BOXES: return wooden_boxes(opts)
IN_SPACESHIP: return spaceship(opts)
IN_KINGDOM: return kingdom(opts)
IN_VOLCANO: return volcano(opts)
IN_GRASSY_CAVE: return grassy_cave(opts)

IS_COOKING_ROOM: return cooking_room(opts)

HAS_BOSS: return has_boss(opts)

HAS_ENEMY: return has_entity("Enemy", opts)
HAS_TARGET: return has_entity("Target", opts)
HAS_LEAF: return has_entity("Leaf", opts)
HAS_CANDLE: return has_entity("Candle", opts)
HAS_PLAYER: return has_entity("Player", opts)
HAS_COOKING_POT: return has_entity("CookingPot", opts)
HAS_BLOB: return has_entity("Blob", opts)
HAS_VOID: return has_entity("Void", opts)

_: return RoomInputs.new()


Expand Down Expand Up @@ -192,32 +235,28 @@ const IN_SMALL_ROOM = "in_small_room"
const IN_TALL_ROOM = "in_tall_room"
const IN_WIDE_ROOM = "in_wide_room"

static func large_room():
static func large_room(_opts={}):
return RoomInputs.new({
room_shape=[all_room_shapes._4x, all_room_shapes._4x_wide].pick_random(),
constraints=[IN_LARGE_ROOM]
})

static func small_room():
static func small_room(_opts={}):
return RoomInputs.new({
room_shape=all_room_shapes.small,
constraints=[IN_SMALL_ROOM]
})

static func tall_room():
static func tall_room(_opts={}):
return RoomInputs.new({
room_shape=[all_room_shapes.tall, all_room_shapes.tall_3].pick_random(),
constraints=[IN_TALL_ROOM]
})

static func wide_room():
static func wide_room(_opts={}):
return RoomInputs.new({
room_shape=[
all_room_shapes.wide,
all_room_shapes.wide_3,
all_room_shapes.wide_4,
].pick_random(),
constraints=[IN_WIDE_ROOM]
})

## tilemaps ######################################################33
Expand All @@ -228,34 +267,29 @@ const IN_KINGDOM = "in_kingdom"
const IN_VOLCANO = "in_volcano"
const IN_GRASSY_CAVE = "in_grassy_cave"

static func wooden_boxes():
static func wooden_boxes(_opts={}):
return RoomInputs.new({
tilemap_scenes=["res://addons/reptile/tilemaps/WoodenBoxesTiles8.tscn",],
constraints=[IN_WOODEN_BOXES]
})

static func spaceship():
static func spaceship(_opts={}):
return RoomInputs.new({
tilemap_scenes=["res://addons/reptile/tilemaps/SpaceshipTiles8.tscn",],
constraints=[IN_SPACESHIP]
})

static func kingdom():
static func kingdom(_opts={}):
return RoomInputs.new({
tilemap_scenes=["res://addons/reptile/tilemaps/GildedKingdomTiles8.tscn",],
constraints=[IN_KINGDOM]
})

static func volcano():
static func volcano(_opts={}):
return RoomInputs.new({
tilemap_scenes=["res://addons/reptile/tilemaps/VolcanoTiles8.tscn",],
constraints=[IN_VOLCANO]
})

static func grassy_cave():
static func grassy_cave(_opts={}):
return RoomInputs.new({
tilemap_scenes=["res://addons/reptile/tilemaps/GrassyCaveTileMap8.tscn",],
constraints=[IN_GRASSY_CAVE]
})

## entities ######################################################33
Expand All @@ -270,64 +304,23 @@ const HAS_VOID = "has_void"
const HAS_PLAYER = "has_player"
const HAS_CANDLE = "has_candle"

static func has_entity(ent):
var cons
match ent:
"CookingPot": cons = HAS_COOKING_POT
"Blob": cons = HAS_BLOB
"Void": cons = HAS_VOID
var ri = RoomInputs.new({entities=[ent]})
if cons:
ri.constraints = [cons]
return ri
static func has_entity(ent, opts={}):
var inp = RoomInputs.new({entities=U.repeat(ent, opts.get("count", 1))})
return inp

static func has_boss():
static func has_boss(_opts={}):
return RoomInputs.new({
entities=[
["Monstroar"],
["Beefstronaut"],
].pick_random(),
constraints=[HAS_BOSS],
})

static func has_enemy():
return RoomInputs.new({
entities=["Enemy"],
constraints=[HAS_ENEMY],
})

static func has_target():
return RoomInputs.new({
entities=["Target"],
constraints=[HAS_TARGET],
})

static func has_leaf():
return RoomInputs.new({
entities=["Leaf"],
constraints=[HAS_LEAF],
})

static func has_player():
return RoomInputs.new({
entities=["Player"],
constraints=[HAS_PLAYER],
})

static func has_candle():
return RoomInputs.new({
entities=["Candle"],
constraints=[HAS_CANDLE],
})

## encounters ######################################################33

const IS_COOKING_ROOM = "is_cooking_room"

static func cooking_room():
static func cooking_room(_opts={}):
return RoomInputs.new({
entities=[
["Blob", "CookingPot", "Void"],
].pick_random(),
constraints=[IS_COOKING_ROOM],
}).merge(large_room())
entities=["Blob", "CookingPot", "Void"],
})
7 changes: 1 addition & 6 deletions src/dino/modes/vania/VaniaRoomDef.gd
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,7 @@ static func generate_defs(opts={}):
entity_defs=e_defs, tile_defs=t_defs,
tile_size=opts.get("tile_size")
})
if inputs is RoomInputs:
inputs.update_def(def)
elif inputs is Array:
RoomInputs.apply_constraints(inputs, def)
elif inputs is Dictionary:
RoomInputs.apply_constraints(inputs, def)
RoomInputs.apply_constraints(inputs, def)
defs.append(def)

return defs
Expand Down
53 changes: 52 additions & 1 deletion test/dino/modes/vania/room_inputs_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func test_room_def_inputs_player_room_sets_entities_and_shape():
assert_array(def.tilemap_scenes).contains(inp.tilemap_scenes)
assert_array(def.tilemap_scenes).is_not_empty()

assert_array(inp.room_shape).is_not_null()
assert_that(inp.room_shape).is_not_null()
assert_array(def.local_cells).is_not_empty()
assert_array(def.local_cells).is_equal(inp.room_shape)

Expand Down Expand Up @@ -218,3 +218,54 @@ func test_apply_constraints_keeps_local_cells():
_inp = RoomInputs.apply_constraints([RoomInputs.IN_SMALL_ROOM,], def)

assert_array(def.local_cells).is_equal(my_local_cells)

func test_apply_constraints_appends_multiple_entities():
var def = VaniaRoomDef.new()
var inp = RoomInputs.apply_constraints([
RoomInputs.HAS_TARGET,
RoomInputs.HAS_TARGET,
RoomInputs.HAS_PLAYER,
], def)

assert_array(inp.entities).contains(["Target", "Player"])
assert_int(len(inp.entities)).is_equal(3)
assert_array(def.entities).contains(["Target", "Player"])
assert_int(len(def.entities)).is_equal(3)

func test_apply_constraints_supports_dicts_and_opts():
var def = VaniaRoomDef.new()
# pass just a dict
var inp = RoomInputs.apply_constraints({
RoomInputs.HAS_TARGET: {count=4},
RoomInputs.HAS_PLAYER: {}
}, def)

assert_array(inp.entities).contains(["Target", "Player"])
assert_int(len(inp.entities)).is_equal(5)
assert_array(def.entities).contains(["Target", "Player"])
assert_int(len(def.entities)).is_equal(5)

def = VaniaRoomDef.new()
# pass just an array of dict
inp = RoomInputs.apply_constraints([{
RoomInputs.HAS_TARGET: {count=4},
RoomInputs.HAS_PLAYER: {}
}], def)

assert_array(inp.entities).contains(["Target", "Player"])
assert_int(len(inp.entities)).is_equal(5)
assert_array(def.entities).contains(["Target", "Player"])
assert_int(len(def.entities)).is_equal(5)

def = VaniaRoomDef.new()
# pass just an array of dicts
inp = RoomInputs.apply_constraints([{
RoomInputs.HAS_TARGET: {count=4},
}, {
RoomInputs.HAS_PLAYER: {}
}], def)

assert_array(inp.entities).contains(["Target", "Player"])
assert_int(len(inp.entities)).is_equal(5)
assert_array(def.entities).contains(["Target", "Player"])
assert_int(len(def.entities)).is_equal(5)

0 comments on commit 273e3ab

Please sign in to comment.