Looping Over a Map
Before this: Looping Over a List, Maps
Next steps: Index and Value, One Chip per Player, Every Player, A HUD for Everyone
Parts that use this: Shooting Gallery, Scoreboard HUD, King of the Hill, Voting, Obstacle Course, Team Match
A for loop over a map visits every key, with the value filed under it. This
lesson counts laps for every player who crosses a finish line, and lists them
all on a board.
Contents
zone "Zone Ref" -> chip finish
chip board -> decal Text
Code
import { escape } from "sanitize"
@left in finish: zone
var laps: Map<string, int>
var names: Map<string, string>
var shown: string = "No laps yet"
@right out board = shown.Value
on ZoneEntered(zone = finish) -> (character) {
let id = character.GetUserId()
let before = laps.get(id)
let count = if before.Found then before.Value + 1 else 1
laps.set(id, count)
names.set(id, escape(character.GetDisplayName()))
var text: string = ""
for key, n in laps {
let name = names.get(key)
text = "${text}${name}: ${n}<br>"
}
shown = text
}
- The first half of the handler is the visit counter from
Maps, with one count per
player filed under their user id. A second map,
names, files each player’s display name under the same id, escaped so it is safe to show. for key, n in laps { ... }runs once per key inlaps.keyis the user id andnthe lap count filed under it.- Inside,
names.get(key)looks up the name for that id. Every id inlapswas also given a name, so the lookup always finds one. <br>starts a new line on the sign, so each player gets a row.
Two players cross the line: you twice, a friend once. How many rows does the board have, and how many times does the loop run?
Run
- Compile the chip and paste it in-game. Give a brick an Attached Zone across your finish line, and place a brick with a Text Decal.
- Wire the Attached Zone’s Zone Ref to
finish, andboardto the Text Decal’s Text. - Walk through the zone a few times. Your row counts up. A second player gets a row of their own.
Under the Hood
Open the chip. The loop is a For Each Map microchip, with its Map Var
Ref wired from the laps map. Inside, the For Each Map Entry gate has
Key and Value outputs for key and n, and an Index output too.
The names.get(key) inside the loop is a Get Map Element gate. The
names map lives outside the loop, and a wire carries its reference in
through the loop chip’s side, like any other wire into a chip.
Some languages write a map loop as for { key, value } in .... Here the
braces mean something else, and a map loop with them does not compile:
@left in finish: zone
var laps: Map<string, int>
var total: int = 0
on ZoneEntered(zone = finish) -> (character) {
laps.set(character.GetUserId(), 1)
for { key, value } in laps {
total += value
}
}
Error: [WS076] a `{ ... }` binding destructures the value in its slot, and this slot is `string`, not a record. Name the slot instead (`for v in ...`); a record-typed array or map cannot be iterated at all.
Name the slots instead: for key, value in laps.
Modify
- Add up everyone’s laps. Keep a
var total: int = 0before the loop, addnto it inside, and showTotal: ${total}under the rows. - Find the leader. Keep
var best: int = 0andvar leader: string = ""before the loop. Inside, whenn > best, set both:best = nandleader = key. After the loop, showLeader: ${names.get(leader)}. - Number the rows. A map loop has a third slot, the index:
for key, n, i in lapscounts 0, 1, 2 as it goes. Start each row with${i + 1}..
Make
Make a checkpoint race. A second zone is a checkpoint, and a lap only counts
if the player touched the checkpoint since their last lap. Keep a
Map<string, bool> of who has touched it, set it in the checkpoint’s
handler, and clear it for that player when their lap counts.
zone1 "Zone Ref" -> chip finish
zone2 "Zone Ref" -> chip checkpoint
chip board -> decal Text
Good to Know
- Do not count on the order of the rows. A map loop is not sorted by laps or by name. Sort the results yourself if the order matters.
- A respawn makes a new character for the same player, so a map keyed by the
character would start a new row after every death. Key by
GetUserId(), which stays the same. - A display name is chosen by the player. Escape it before it goes on a sign.
See Bindings in the reference for every binding a loop takes.
Next: Index and Value.