Skip to main content

Switch / Case

Sandstone provides a powerful switch/case abstraction that compiles to efficient Minecraft commands. Depending on what you're switching on, Sandstone automatically chooses the optimal dispatch strategy.

Basic Syntax​

The recommended way to use switch/case is with the fluent API:

import { _, Objective, say } from 'sandstone'

const state = Objective.create('state')('@s')

MCFunction('switch_demo', () => {
_.switch(state, _
.case<Score>(0, () => say('State is zero'))
.case(1, () => say('State is one'))
.case(2, () => say('State is two'))
.default(() => say('Unknown state'))
)
})

Switching on Scores​

When switching on a Score with static numeric values, Sandstone generates O(1) dispatch using macros. Each case becomes a separate function named after its value (e.g., case_0, case_1, case_2), and the score value is used directly to call the matching function.

Try it out​

const level = Objective.create('level')('@s')

MCFunction('level_check', () => {
_.switch(level, _
.case<Score>(1, () => say('Level 1 - Tutorial'))
.case(2, () => say('Level 2 - Easy'))
.case(3, () => say('Level 3 - Medium'))
.case(4, () => say('Level 4 - Hard'))
.default(() => say('Invalid level'))
)
})

If a case doesn't exist for the current value and there's a default/fallback, the switch gracefully handles it using execute store success to detect failed macro calls.

Switching on NBT Data​

You can also switch on NBT data stored in entities, blocks, or storage. Sandstone uses an efficient lookup table from data storage to map values to case indices:

const itemType = Data('storage', 'game:data', 'CurrentItem.type')

_.switch(itemType, _
.case({ type: 'sword' }, () => say('Melee weapon equipped'))
.case({ type: 'bow' }, () => say('Ranged weapon equipped'))
.case({ type: 'staff' }, () => say('Magic weapon equipped'))
.default(() => say('Unknown item type'))
)

Condition-Based Cases​

For more complex matching logic, you can use condition functions instead of static values:

const score = Objective.create('score')('@s')

_.switch(score, _
.case<Score>((v) => v.matches([0, 10]), () => {
say('Score is 0-10 (low)')
})
.case((v) => v.matches([11, 50]), () => {
say('Score is 11-50 (medium)')
})
.case((v) => v.matches([51, 100]), () => {
say('Score is 51-100 (high)')
})
.default(() => say('Score out of expected range'))
)

Condition cases are checked sequentially using execute if chains. They're evaluated in order until one matches.

Type Annotation

When using condition functions, you may need to provide a type annotation on the first .case() call: _.case<Score>((v) => ...). This helps TypeScript infer the correct type for the value parameter.

Mixed Cases​

You can combine static cases with condition cases. Static cases are checked first (O(1)), and if none match, condition cases are evaluated sequentially:

_.switch(score, _
.case(0, () => say('Exactly zero'))
.case(1, () => say('Exactly one'))
.case((v) => v.matches([10, 20]), () => say('Between 10-20'))
.case((v) => v.greaterOrEqualThan(100), () => say('100 or more'))
.default(() => say('Some other value'))
)

Array Syntax​

An alternative array-based syntax is also available:

_.switch(score, [
['case', 0, () => say('Zero')],
['case', 1, () => say('One')],
['case', (v) => v.matches([10, 20]), () => say('10-20')],
['default', () => say('Default')],
])

This syntax is equivalent to the fluent API but may be preferred in some situations.

Return Values​

Switch cases can return values that propagate to the caller. This is useful when using switches inside execute store:

execute.store.result.score(resultScore).run(() =>
_.switch(input, _
.case(0, () => {
// Case body...
// The return value of the last command is captured
})
)
)

Performance Considerations​

Switch TypeLookup ComplexityBest For
Score with static valuesO(1)Known discrete values (0, 1, 2, ...)
NBT with static valuesO(1)*Enum-like string/object matching
Condition cases onlyO(n)Range checks, complex predicates
Mixed (static + conditions)O(1) then O(n)Common values + edge cases

Static cases are always checked first, so put your most common cases as static values when possible.