Tagged Lights Puzzle
Tagged Lights Puzzle
A lights-out style puzzle: buttons toggle sets of lights discovered via a gameplay tag; solving the target pattern fires a trigger.
Original, compile-verified Verse by Bizanator (Biloxi Studios). Clean-room sample you can drop into a UEFN project and adapt.
# Tagged Lights Puzzle — Biloxi Studios Inc.
#
# A classic "lights out" style puzzle. Players press buttons; each button toggles
# a specific set of lights. When every light matches the target pattern, the
# puzzle is solved and a trigger fires. Lights are discovered at runtime via a
# gameplay tag, so you don't have to wire each one by hand.
#
# Teaches: gameplay tags + GetCreativeObjectsWithTag, per-button event handler
# objects, casting a creative object to a device type, storing & cancelling event
# subscriptions, and a <decides> "is solved?" check used in a failure context.
#
# Setup:
# * Tag every customizable_light_device in the puzzle with the puzzle_light tag.
# * Fill Buttons with the button_device(s) players press.
# * ButtonToLights[i] lists which light indices Buttons[i] toggles.
# * TargetPattern / StartPattern length must match the number of tagged lights.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Simulation/Tags }
using { /UnrealEngine.com/Temporary/Diagnostics }
log_tagged_lights_puzzle := class(log_channel){}
# Tag placed on the customizable_light_device actors that make up this puzzle.
puzzle_light := class(tag){}
# One instance per button. It remembers which light indices its button controls
# and a back-reference to the puzzle so its handler can drive the puzzle state.
button_press_handler := class():
LightIndices : []int
Puzzle : tagged_lights_puzzle
OnPressed(Player : agent) : void =
Puzzle.ToggleLights(LightIndices)
tagged_lights_puzzle := class<concrete>(creative_device):
Logger : log = log{Channel := log_tagged_lights_puzzle}
@editable
Buttons : []button_device = array{}
# ButtonToLights[i] = the light indices toggled by Buttons[i].
# NOTE: @editable does not support multidimensional arrays, so set this in
# code (or via a data table) rather than in the device's Details panel.
ButtonToLights : [][]int = array{array{0, 1}, array{1, 2}, array{0, 2}}
# Current on/off state of each light. Initial values set the lights at start.
@editable
var LightStates : []logic = array{false, false, false}
# The on/off pattern that solves the puzzle (same length as LightStates).
@editable
TargetPattern : []logic = array{true, true, true}
# Fired once when the puzzle is solved.
@editable
SolvedTrigger : trigger_device = trigger_device{}
# Light devices discovered via the puzzle_light tag at runtime.
var Lights : []customizable_light_device = array{}
# Saved button subscriptions so we can stop input once solved.
var ButtonSubscriptions : []cancelable = array{}
OnBegin<override>()<suspends> : void =
FindAndInitLights()
SubscribeButtons()
# Discover every light tagged puzzle_light and apply the starting state.
# FindCreativeObjectsWithTag yields a generator, so we first collect it into
# an array (so we can index-iterate it alongside LightStates), then cast each
# object to a light, skipping anything that isn't one.
FindAndInitLights<private>() : void =
Objects := for (Object : FindCreativeObjectsWithTag(puzzle_light{})):
Object
set Lights = for:
Index -> Object : Objects
Light := customizable_light_device[Object] # cast; skips non-lights
do:
if (ShouldBeOn := LightStates[Index]):
if (ShouldBeOn?) then Light.TurnOn() else Light.TurnOff()
Light
Logger.Print("Initialized {Lights.Length} puzzle light(s).")
# Wire each button to a handler that toggles the lights it controls.
SubscribeButtons<private>() : void =
set ButtonSubscriptions = for:
ButtonIndex -> Button : Buttons
Indices := ButtonToLights[ButtonIndex]
do:
Handler := button_press_handler{LightIndices := Indices, Puzzle := Self}
Button.InteractedWithEvent.Subscribe(Handler.OnPressed)
# Flip the given light indices, then check for a solve.
ToggleLights(Indices : []int) : void =
for:
LightIndex : Indices
WasOn := LightStates[LightIndex]
Light := Lights[LightIndex]
do:
NewState :=
if (WasOn?):
Light.TurnOff()
false
else:
Light.TurnOn()
true
if (set LightStates[LightIndex] = NewState):
Logger.Print("Toggled light {LightIndex}.")
if (IsSolved[]):
OnSolved()
OnSolved<private>() : void =
Logger.Print("Puzzle solved!")
SolvedTrigger.Trigger()
# Stop accepting input so the solved state can't be undone.
for (Subscription : ButtonSubscriptions):
Subscription.Cancel()
# Succeeds only when every light matches the target pattern. <decides> means
# it must be called in a failure context, e.g. `if (IsSolved[]):`.
IsSolved<private>()<decides><transacts> : void =
for:
Index -> State : LightStates
Goal := TargetPattern[Index]
do:
State = Goal