With case expressions, you can control the flow of a program from a list of choices. The case statement in Verse is a way to test one value against multiple possible values (as though you were using =), and running code based on which one matches.
The use of case expressions can be found in all kinds of applications, like in games where there is a non-playable character (NPC).
For example, let's say you use the Guard Spawner device to spawn a guard with its patrol option enabled. After the guard spawns into the game, it has a few possible active states, including Idle, Patrol, Alert, Attack, and Harvest. A high-level state-transition diagram for this could look like:
You can observe these state transitions in-game.
In this video, the guard has its patrol option enabled as the default behavior.
In the video, the guard transitions from patrolling the science base to harvesting some resources. Then the guard spots the player, which sends the guard into an alert state (indicated by the hovering question mark) before entering its attack state (indicated by the hovering exclamation mark).
Depending on the state the guard is in, it will exhibit certain behaviors, and these behaviors are typically coded as functions that are called when the program chooses to enter a specific state.
As code, this high-level guard-state transition could look like this:
case(GuardStateVariable):
idle_state =>
RunIdleAnimation()
SearchPlayerCharacter()
harvest_state =>
GatherResources()
alert_state=>
RunAlertAnimation()
PlayAlertSound()
DisplayAlertUIElement()
This case expression passes a label that tells the program which functions to run if the guard enters a specific state.
In this expression, the guard's patrol_state is the default case because a guard with patrol enabled should run its default patrol behavior.
Syntactically, this is the same as:
expression0
case (test-arg-block):
label1 =>
expression1
label2 =>
expression2
_ =>
expression3 for the default case
expression4Each pattern in the case block, such as label1 and label2, must use the form constant => block, where the constant can be an integer, logic, string, char, or enum constant. So case statements only work with int, logic, string, char, and enums.
Structure
Structurally, the Verse case expression runs code based on input of the GuardStateVariable test argument block, and it functionally works the same as a series of if expressions.
In this example, the Verse program runs expression3 if GuardStateVariable resolves to alert_state. If the program passes in patrol_state, Verse structurally jumps to the default case, and runs expression5.
Using Case with Other Control Flow
The blocks in a case statement are allowed to break and continue if the case statement is inside of a loop. Blocks of case statements are also allowed to return from the function they are in.
For example:
loop:
case (x):
42 => break
_ => {}This absurd loop will either complete immediately if x = 42 or loop forever.
Another example:
Foo(x : int) : int =
case (x):
100 => return 200
_ => return 100This example is equivalent to:
Foo(x : int) : int =
case (x):
100 => 200
_ => 100This is because the case statement is the last expression of the function.
Default Case
Case statements that do not have a _=> case (a default case) will fail if none of the cases match. It's fine to use such case statements in failure contexts (such as functions with the decides effect).
Case statements that match all of the cases of an enumeration will be non-failing even if they do not have a _=> case.