Table of Contents
- [[Retrieve the top card inside a given holder|Useful-code-snippets#retrieve-the-top-card-inside-a-given-holder-1]]
- [[Take an action when a players enters or leaves the room|Useful-code-snippets#take-an-action-when-a-player-enters-or-leaves-the-room]]
- [[Add background image to a holder|Useful-code-snippets#add-background-image-to-a-holder-1]]
- [[Add holders to every card in a deck|Useful-code-snippets#add-holders-to-every-card-in-a-deck-1]]
- [[Disallow multiple people with same name|Useful-code-snippets#disallow-multiple-people-with-same-name-1]]
- [[Remove duplicates from an array|Useful-code-snippets#remove-duplicates-from-an-array-1]]
- [[Autosize holder|Useful-code-snippets#autosize-holder-1]]
- [[Counteract leaveRoutine activating twice|Useful-code-snippets#counteract-leaveRoutine-activating-twice-1]]
- [[Check if variable is a number|Useful-code-snippets#check-if-variable-is-a-number-1]]
- [[Move card to bottom of holder|Useful-code-snippets#move-card-to-bottom-of-holder-1]]
- [[Append value to an array property|Useful-code-snippets#Append-value-to-an-array-property-1]]
- [[Auto adjust stackOffsetX|Useful-code-snippets#Auto-adjust-stackOffsetX-1]]
- [[Use enterRoutine with new pile|Useful-code-snippets#Use-enterRoutine-with-new-pile-1]]
- [[Shuffle in place|Useful-code-snippets#Shuffle-in-place-1]]
- [[Move to holders connected to occupied seats|Useful-code-snippets#move-to-holders-connected-to-occupied-seats-1]]
- [[Toggle visibility to seats as an array|Useful-code-snippets#toggle-visibility-to-seats-as-an-array-1]]
- [[Undo properties|Useful-code-snippets#undo-properties-1]]
- [[Actions for different player counts|Useful-code-snippets#actions-for-different-player-counts-1]]
- Retrieve the top card inside a given holder
- Take an action when a player enters or leaves the room
- Add background image to a holder
- Add holders to every card in a deck
- Disallow multiple people with same name
- Remove duplicates from an array
- Autosize holder
- Counteract leaveRoutine activating twice
- Check if variable is a number
- Move card to bottom of holder
- Append value to an array property
- Auto adjust stackOffsetX
- Use enterRoutine with new pile
- Shuffle in place
- Move to holders connected to occupied seats
- Toggle visibility to seats as an array
- Undo properties
- Actions for different player counts
This page holds small code snippets that you might find useful when building a game. The intent is that they should require minimal modification to be used in line or as a callable Routine. If you want more complete tools that do more complicated activities (such as generate tooltips or game tiles), check out https://virtualtabletop.io/DemoLibrary. This wiki entry is for smaller bits of code.
Retrieve the top card inside a given holder
Take an action when a players enters or leaves the room
Add background image to a holder
Add holders to every card in a deck
Disallow multiple people with same name
Remove duplicates from an array
Autosize holder
Counteract leaveRoutine activating twice
Check if variable is a number
Move card to bottom of holder
Append value to an array property
Auto adjust stackOffsetX
Use enterRoutine with new pile
Shuffle in place
Move to holders connected to occupied seats
Toggle visibility to seats as an array
Undo properties
Actions for different player counts
Retrieve the top card inside a given holder
If holderID is the id
of a holder, this code will return a collection consisting of the top card in the deck inside that holder. Note that in the unlikely case that there are two decks in the holder, results may be unexpected, as this code simply returns the card in the holder which has the highest z
-value.
{
"func": "SELECT",
"property": "parent",
"value": "holderID"
},
{
"func": "SELECT",
"source": "DEFAULT",
"type": "card",
"property": "id",
"relation": "!=",
"value": null,
"sortBy": {
"key": "z",
"reverse": true
}
},
{
"func": "SELECT",
"source": "DEFAULT",
"max": 1,
"property": "id",
"relation": "!=",
"value": null
}
Take an action when a player enters or leaves the room
Add a timer to the room, but you can hide it offscreen. The timer must be running for this to work. To the timer, add the following code:
"changeRoutine": [
{
"func": "SELECT",
"property": "id",
"value": "${thisID}"
},
{
"func": "SET",
"property": "playerlist",
"value": "${activePlayers}"
}
],
"playerlistGlobalUpdateRoutine": [
{
"func": "CALL",
... then use CALL to do what you want ...
}
]
The way this works is that the changeRoutine
, which executes every time the timer clicks, saves the active player list in the playerlist
property. The playerlistGlobalUpdateRoutine
fires when that list changes, allowing you to take whatever actions you want.
Add background image to a holder
Holders do not have an image
property so it is not possible to use that to add an image to a holder. However, using CSS, it is possible to turn a holder into an image. Add the following CSS to a holder to add a starfield image to the holder (or replace the URL portion with any image on VTT or the full URL to an image anywhere on the internet). The background-size portion of the CSS ensures that the background size does properly adjusts when the border updates from dragging and dropping.
"css": "background: no-repeat center url(/assets/206954176_1013912); background-size:cover;border:calc(1px / var(--scale)) solid transparent"
Add holders to every card in a deck
If you are making a board game and need a lot of holders for the game pieces as they move around the board, you might consider this technique. Create a deck of "cards" that contain whatever visual image you want for the spaces on the board. Place the "cards" as tiles or in a circle or in whatever shape is needed for the board. Then create a holder that is the size and shape you want it to be. Finally, use the following code to put a make a button that will put a holder on each card. When done, you can delete the button. At the bottom of the code, change "targetDeck"
and "targetHolder"
to the names of your deck and holder.
{
"type": "button",
"id": "holderButton",
"clickRoutine": [
{
"func": "SELECT",
"property": "deck",
"value": "${PROPERTY targetDeck}"
},
{
"func": "SELECT",
"property": "id",
"value": "${PROPERTY targetHolder}",
"collection": "holder"
},
{
"func": "FOREACH",
"loopRoutine": [
{
"func": "CLONE",
"source": "holder",
"properties": {
"x": 0,
"y": 0,
"parent": "${widgetID}"
}
}
]
}
],
"targetDeck": "nameofDeck",
"targetHolder": "nameofHolder"
}
Here's another use for this routine. If you are building a rummy-like or solitaire-like game that requires players to drag stacks of cards when every card below a selected card is moved, then you can add small invisible holders to the bottom of the cards. This will allow players to either make piles or make vertical stacks as needed in the game. To use this technique, create the button as described above except change the FOREACH "properties" of "x" to 5 and "y" to 135. To the deck, add:
"cardDefaults": {
"css": "overflow:visible"
},
Finally, the "targetHolder" (with id of "stackHolder") should be:
{
"type": "holder",
"id": "stackHolder",
"x": 0,
"y": 0,
"width": 93,
"height": 20,
"movable": false,
"movableInEdit": false,
"css": "border: 0px solid black !important; background: transparent",
"dropLimit": 1,
"dropOffsetX": -4,
"dropOffsetY": -85
}
Once you use the button, you can delete it and "stackHolder".
Disallow multiple people with same name
The code below should be attached as the clickRoutine
for a seat widget. It will require the player to enter a nonempty name that does not match the name of any other player in the room (member of activePlayers
). You may be tempted to optimize this routine by placing the thenRoutine
for the second IF
statement inside the last thenRoutine
inside the first one. Don't do that. The actual value of playerName
is not set until after a routine (in this case the upper thenRoutine
) terminates, so you will get odd results from the CLICK
with ignoreClickRoutine
set.
"clickRoutine": [
"var OK = false",
{
"func": "IF",
"operand1": "${PROPERTY player}",
"relation": "==",
"operand2": "",
"thenRoutine": [
{
"func": "INPUT",
"header": "Enter your name.",
"fields": [
{
"type": "string",
"label": "Name",
"variable": "player",
"value": ""
}
]
},
"var duplicate = ${activePlayers} includes ${player}",
"var notSelf = ${player} != ${playerName}",
"var duplicate = ${duplicate} && ${notSelf}",
"var OK = ! ${duplicate}",
"var notEmpty = ${player} != ''",
"var OK = ${OK} && ${notEmpty}",
{
"func": "IF",
"condition": "${OK}",
"thenRoutine": [
"var playerName = ${player}"
]
}
]
},
{
"func": "IF",
"condition": "${OK}",
"thenRoutine": [
{
"func": "CLICK",
"collection": "thisButton",
"mode": "ignoreClickRoutine"
},
{
"func": "IF",
"operand1": "${player}",
"relation": "!=",
"operand2": "",
"thenRoutine": [
{
"func": "CALL",
"routine": "setupPlayerRoutine"
}
]
}
]
}
]
Remove duplicates from an array
Given an array in array
, this code returns a de-duplicated array in result
(that is, result
contains x
if array
does, and result
contains no element more than once).
"deDupRoutine": [
"var result = []",
{
"func": "FOREACH",
"in": "${array}",
"loopRoutine": [
"var firstIndex = ${array} indexOf ${value}",
{
"func": "IF",
"operand1": "${key}",
"relation": "==",
"operand2": "${firstIndex}",
"thenRoutine": [
"var result = push ${value}"
]
}
]
}
],
Autosize holder
To make a holder exactly fit the size of the stacked and aligned widgets it contains (assuming they are all the same width), insert the following code into the holder. Set hAnchorPos
to be "left", "right", or "center", depending on which way you want the holder to expand. Set minWidth
to the size that contains one widget, which in the case of a standard deck of cards is 111. stackOffsetX
can be whatever number best suits the layout. This routine does not work properly for "hands" (childrenPerOwner:true
). If you need this technique for hands, see the Autosize Holder demo room (Hands variant) in https://virtualtabletop.io/DemoLibrary. The hand option does not make the cards fit into the given space, however. It expands the hand to be the size of the cards.
"enterRoutine": [
{
"func": "CALL",
"routine": "widthRoutine"
}
],
"hAnchorPos": "center",
"leaveRoutine": [
{
"func": "CALL",
"routine": "widthRoutine"
}
],
"minWidth": 111,
"onEnter": {
"activeFace": 1
},
"stackOffsetX": 30,
"widthRoutine": [
"var rightX = ${PROPERTY x} + ${PROPERTY width}",
{
"func": "SET",
"collection": "thisButton",
"property": "rightX",
"value": "${rightX}"
},
{
"func": "SELECT",
"property": "parent",
"value": "${PROPERTY id}"
},
{
"func": "COUNT"
},
{
"func": "IF",
"operand1": "${COUNT}",
"operand2": 0,
"thenRoutine": [
{
"func": "SET",
"collection": "thisButton",
"property": "width",
"value": "${PROPERTY minWidth}"
},
"var newWidth = ${PROPERTY minWidth}"
],
"elseRoutine": [
"var revCount = ${COUNT} - 1",
"var revCount = ${revCount} * ${PROPERTY stackOffsetX}",
"var newWidth = ${revCount} + ${PROPERTY minWidth}"
]
},
{
"func": "IF",
"operand1": "${PROPERTY hAnchorPos}",
"operand2": "left",
"thenRoutine": [
"var newX = ${PROPERTY x}"
],
"elseRoutine": [
{
"func": "IF",
"operand1": "${PROPERTY hAnchorPos}",
"operand2": "right",
"thenRoutine": [
"var newX = ${PROPERTY rightX} - ${newWidth}"
],
"elseRoutine": [
{
"func": "IF",
"operand1": "${PROPERTY hAnchorPos}",
"operand2": "center",
"thenRoutine": [
"var halfWidth = ${newWidth} / 2",
"var centerX = ${PROPERTY width} / 2",
"var centerX = ${PROPERTY x} + ${centerX}",
"var newX = ${centerX} - ${halfWidth}"
]
}
]
}
]
},
{
"func": "SET",
"collection": "thisButton",
"property": "x",
"value": "${newX}"
},
{
"func": "SET",
"collection": "thisButton",
"property": "width",
"value": "${newWidth}"
}
]
Counteract leaveRoutine activating twice
Currently, there is a bug/feature that causes a leaveRoutine
to activate twice when a widget is leaving a holder with a leaveRoutine
function. It fires once as soon as the widget is clicked and starts to move. It fires again once the widget has completely left the holder. While there is a PR to change this, it probably will not be merged soon. If you are working on a room and this is a problem, consider the following solution. Add the enterRoutine
and leaveRoutine
below to the affected holder. Where the ellipsis appear, you can add your own functions.
"enterRoutine": [
{
"func": "SET",
"property": "doubleLeaveRoutineCompensation",
"value": true,
"collection": "thisButton"
},
{
...
}
],
"leaveRoutine": [
{
"func": "IF",
"operand1": "${PROPERTY doubleLeaveRoutineCompensation}",
"operand2": true,
"thenRoutine": [
{
"note": "this branch of the IF is called directly after you start dragging the card",
"func": "SET",
"property": "doubleLeaveRoutineCompensation",
"collection": "thisButton",
"value": false
},
{
...
}
],
"elseRoutine": [
{
"note": "this branch of the IF is called when the card exits the holder entirely",
"func": "SET",
"property": "doubleLeaveRoutineCompensation",
"value": true,
"collection": "thisButton"
}
]
}
]
Check if variable is a number
This code uses a simple IF
condition to determine whether a variable is a number or not. In this example, checkThis will be the variable that is being checked.
"var isNum = ${checkThis} / 1",
{
"func": "IF",
"operand1": "${isNum}",
"operand2": "${checkThis}",
"thenRoutine": [
---here do whatever you want to do if the variable is a number---
],
"elseRoutine": [
---here do whatever you want to do if the variable is not a number---
]
}
Move card to bottom of holder
This can be placed as an enterRoutine
on a holder to automatically move a card that is being dragged into a holder ("myHolder" in this snippet) to the bottom of the pile. This uses a holder that can be placed off screen as a temporary holder ("tempHolder" in this snippet). You can also use this code as part of a button. In that case, you only need the 3 MOVE
functions and not the rest of the code. Limits: this will not work for hands (holders with "childrenPerOwner":true
) and will likely cause strange visual effects if there are CSS transition effects on the cards.
{
"func": "IF",
"condition": "${PROPERTY active}",
"elseRoutine": [
{
"func": "SET",
"collection": "thisButton",
"property": "active",
"value": true
},
{
"func": "MOVE",
"from": "myHolder",
"to": "tempHolder",
"count": 1
},
{
"func": "MOVE",
"from": "myHolder",
"to": "tempHolder",
"count": "all"
},
{
"func": "MOVE",
"from": "tempHolder",
"to": "myHolder",
"count": "all
},
{
"func": "SET",
"collection": "thisButton",
"property": "active",
"value": false
}
]
}
Append value to an array property
"clickRoutine": [
"// append value 7 to array property",
{
"func": "SET",
"collection": "name",
"property": "foo",
"relation": "concatArray",
"value": 7
}
]
Auto adjust stackOffsetX
This will prevent cards added to a holder from extending beyond the right edge of the holder by adjusting the stackOffsetX as needed. To use, set the "cardWidth" variable to the width of the cards in the holder and the "normalOffset" variable to the offset value that will be used until space runs out.
"enterRoutine": [
{
"func": "CALL",
"routine": "offSetRoutine"
}
],
"leaveRoutine": [
{
"func": "CALL",
"routine": "offSetRoutine"
}
],
"offSetRoutine": [
"var cardWidth = 103",
"var normalOffset = 40",
{
"func": "SELECT",
"property": "parent",
"value": "${PROPERTY id}"
},
{
"func": "COUNT"
},
{
"func": "IF",
"operand1": "${COUNT}",
"relation": "<=",
"operand2": 1,
"thenRoutine": [
{
"func": "SET",
"collection": "thisButton",
"property": "stackOffsetX",
"value": "${normalOffset}"
}
],
"elseRoutine": [
"var modCount = ${COUNT} - 1",
"var available = ${PROPERTY width} - ${cardWidth}",
"var dropOffset = ${PROPERTY dropOffsetX} * 2",
"var available = ${available} - ${dropOffset}",
"var perCard = ${available} / ${modCount}",
"var offset = ${perCard} min ${normalOffset}",
{
"func": "SET",
"collection": "thisButton",
"property": "stackOffsetX",
"value": "${offset}"
}
]
}
]
The above routine will not work with a "hand" where multiple players have cards only visible to themselves. The modified routine below will work with hands. You need to set "dropShadow": false
on the holder as this routine does not work well with that feature active. If using a routine that MOVE
's cards to seats, you need to CALL
the "offSetRoutine" after the MOVE
to get it to work.
"enterRoutine": [
{
"func": "CALL",
"routine": "offSetRoutine"
}
],
"leaveRoutine": [
{
"func": "CALL",
"routine": "offSetRoutine"
}
],
"offSetRoutine": [
"var normalOffset = ${PROPERTY stackOffsetX}",
"var owners = []",
{
"func": "SELECT",
"property": "parent",
"value": "${PROPERTY id}"
},
{
"func": "GET",
"property": "owner",
"aggregation": "array",
"variable": "ownersArray"
},
{
"func": "FOREACH",
"in": "${ownersArray}",
"loopRoutine": [
"var firstIndex = ${ownersArray} indexOf ${value}",
{
"func": "IF",
"operand1": "${key}",
"relation": "==",
"operand2": "${firstIndex}",
"thenRoutine": [
"var owners = push ${value}"
]
}
]
},
{
"func": "FOREACH",
"in": "${owners}",
"loopRoutine": [
{
"func": "SELECT",
"source": "DEFAULT",
"property": "owner",
"value": "${value}",
"collection": "ownedCards",
"sortBy": {
"key": "z"
}
},
{
"func": "GET",
"source": "ownedCards",
"property": "width",
"variable": "cardWidth"
},
{
"func": "COUNT",
"collection": "ownedCards"
},
"var modCount = ${COUNT} - 1",
"var available = ${PROPERTY width} - ${cardWidth}",
"var dropOffset = ${PROPERTY dropOffsetX} * 2",
"var available = ${available} - ${dropOffset}",
"var perCard = ${available} / ${modCount}",
"var offset = ${perCard} min ${normalOffset}",
"var newX = ${PROPERTY dropOffsetX}",
{
"func": "FOREACH",
"collection": "ownedCards",
"loopRoutine": [
{
"func": "SET",
"collection": "DEFAULT",
"property": "x",
"value": "${newX}"
},
"var newX = ${newX} + ${offset}"
]
}
]
}
]
Use enterRoutine with new pile
Pile formation and enterRoutines
do not work together in an intuitive way. For example, if you have an enterRoutine
that you want to do something to the pile (for example, make it not movable), the enterRoutine
will not work as the pile is formed (when you put a second card in the holder). Of course, it will also not work after that (when you add a third card) because the pile already exists in the holder. However, if you drag an existing pile into the holder, the enterRoutine
will work. So instead of an enterRoutine
, put the code in the cardDefaults
portion of the deck like in the example below. This code below works on only 1 "myHolder" (so it doesn't prevent piles from moving in other locations).
"cardDefaults": {
"onPileCreation": {
"parentGlobalUpdateRoutine": [
{
"func": "IF",
"operand1": "${PROPERTY parent}",
"operand2": "myHolder",
"thenRoutine": [
{
"func": "SET",
"collection": [
"${thisID}"
],
"property": "movable",
"value": false
}
]
},
{
"func": "SET",
"collection": [
"${thisID}"
],
"property": "parentGlobalUpdateRoutine",
"value": null
}
]
}
},
Shuffle in place
At the start of a game you may have widgets already laid out in the room in the position that you want them, but you want random widgets in each of those positions. For example, there are 5 face down widgets on the table at the start at certain x,y coordinates but they are not in a holder. You could use a routine like the following to randomize those face down widgets. If you put this in a gameStartRoutine
, this would be randomized every time the game is loaded.
{
"func": "SELECT",
"property": "faceDown",
"value": true
},
{
"func": "SELECT",
"source": "DEFAULT",
"property": "parent",
"value": null
},
{
"func": "GET",
"property": "x",
"aggregation": "array"
},
{
"func": "GET",
"property": "y",
"aggregation": "array"
},
{
"func": "SHUFFLE"
},
{
"func": "SELECT",
"source": "DEFAULT",
"property": "parent",
"value": null,
"sortBy": "z"
},
"var i = 0",
{
"func": "FOREACH",
"loopRoutine": [
{
"func": "SET",
"property": "x",
"value": "${x.$i}"
},
{
"func": "SET",
"property": "y",
"value": "${y.$i}"
},
"var i = ${i} + 1"
]
}
Move to holders connected to occupied seats
For each seat that is occupied, this code will move from specified "origin_holders" (change to suit your room) to a specified "destination_holder" associated with that seat. The destination holder must have a number at the end of the name that is the same as the index
of the seat.
{
"func": "FOREACH",
"collection": "activeSeats",
"loopRoutine": [
"var playerID = ${PROPERTY index OF $widgetID}",
{
"func": "MOVE",
"from": [
"<origin_holder1>",
"<origin_holder2>"
],
"to": "destination_holder${playerID}",
"count": 1
}
]
}
Toggle visibility to seats as an array
This code will toggle visibility for "myWidget" on and off for each player that presses the button. This only works for players that are in seats.
"var visibleToSeats = ${PROPERTY onlyVisibleForSeat OF myWidget}",
"var checkArray = ${visibleToSeats} isArray",
{
"func": "IF",
"condition": "${checkArray}",
"elseRoutine": [
"var visibleToSeats = []"
]
},
"var inVisibility = ${visibleToSeats} indexOf ${seatID}",
{
"func": "IF",
"operand1": "${inVisibility}",
"relation": ">",
"operand2": -1,
"thenRoutine": [
"var visibleToSeats = remove ${inVisibility} 1"
],
"elseRoutine": [
"var visibleToSeats = push ${seatID}"
]
},
{
"func": "SET",
"collection": [
"myWidget"
],
"property": "onlyVisibleForSeat",
"value": "${visibleToSeats}"
}
Undo properties
A complete undo functionality is currently beyond the scope of what VTT can handle easily. But these snippets allow you to restore specified properties on widgets to a previous state. You need several things to implement this. First you need to specify the properties and the widgets you want to include in the undo button and add an undoProperties
array on each widget. For example, in the array below, the parent, x/y coords, and activeFace of the widget will be recorded. Be sure to list parent before x and y in this situation.
"undoProperties": [
"parent",
"x",
"y",
"activeFace"
],
Next, you need a widget with a function to create and store the arrays. You should use an off-screen widget for this named undoRoutine
with the property "resetArray":[]
and the following routine:
"undoArrayRoutine": [
"var resetTempArray = []",
"var resetTempObj = {}",
"var resetArray = ${PROPERTY resetArray}",
"var resetStates = 100",
{
"func": "SELECT",
"property": "undoProperties",
"relation": "!=",
"value": null,
"sortBy": {
"key": "id",
"reverse": false
}
},
{
"func": "GET",
"aggregation": "array",
"property": "id"
},
{
"func": "FOREACH",
"in": "${id}",
"loopRoutine": [
"var widgetID = ${value}",
{
"func": "GET",
"collection": [
"${widgetID}"
],
"property": "undoProperties"
},
{
"func": "FOREACH",
"in": "${undoProperties}",
"loopRoutine": [
{
"func": "GET",
"collection": [
"${widgetID}"
],
"property": "${value}",
"variable": "propValue"
},
"var resetTempArray = push ${propValue}",
"var resetTempObj.$widgetID = ${resetTempArray}"
]
},
"var resetTempArray = []"
]
},
"var arrayLen = length ${PROPERTY resetArray}",
"var resetArray.$arrayLen = ${resetTempObj}",
{
"func": "IF",
"operand1": "${arrayLen}",
"operand2": ${resetStates},
"thenRoutine": [
"var resetArray = remove 0 1"
]
},
{
"func": "SET",
"collection": "thisButton",
"property": "resetArray",
"value": "${resetArray}"
}
]
In this example, the maximum number of times the undo button can be clicked is 100. That can be changed on the needs of the game. At large values or with large numbers of widgets/properties, players may have laggy performance.
Next you need to decide when the array should be updated. For example, every time a widget changes holders, after a turn button is pressed, after the game is restarted, etc. That trigger event should:
{
"func": "CALL",
"widget": "undoRoutine",
"routine": "undoArrayRoutine"
}
When that routine is activated, the resetArray
is updated. The array contains a series of arrays with each array, in order, being the saved states of the properties. Within each inner arrary are a series of objects, each labeled with the name of the widget. Each object has the property values associated with that widget during that update.
Finally, you need a way for players to activate the undo capability. A button with this clickRoutine should be added:
"clickRoutine": [
"var resetArray = ${PROPERTY resetArray OF undoRoutine}",
"var arrayLen = length ${resetArray}",
"var lastIndex = ${arrayLen} - 1",
"var resetArray = remove ${lastIndex} 1",
"var newArray = []",
"var newLastIndex = ${lastIndex} - 1",
{
"func": "SET",
"collection": [
"undoRoutine"
],
"property": "resetArray",
"value": "${resetArray}"
},
{
"func": "IF",
"operand1": "${newLastIndex}",
"relation": ">=",
"operand2": 0,
"thenRoutine": [
"var newProps = ${resetArray.$newLastIndex}",
{
"func": "SELECT",
"property": "undoProperties",
"relation": "!=",
"value": null,
"sortBy": {
"key": "id",
"reverse": false
}
},
{
"func": "GET",
"aggregation": "array",
"property": "id"
},
{
"func": "FOREACH",
"in": "${id}",
"loopRoutine": [
"var widgetID = ${value}",
"var props = ${newProps.$widgetID}",
{
"func": "GET",
"collection": [
"${widgetID}"
],
"property": "undoProperties"
},
{
"func": "FOREACH",
"in": "${undoProperties}",
"loopRoutine": [
{
"func": "SET",
"collection": [
"${widgetID}"
],
"property": "${value}",
"value": "${props.$key}"
}
]
}
]
}
]
}
]
Actions for different player counts
In a room with seats, there is an easy way to take different actions depending on how many players there are. You could use this to SELECT
only certain cards to be included in a deal, deal a certain number of cards, etc. To use this, just add the functions appropriate for each player count or CALL
a routine for code related to that player count. The following example assumes a player count will be between 1 and 6. Player counts of 1-3 are handled one way, with 4 players something else happens, and 5 and 6 players each have other things happening.
"var playerCount = length ${activeSeats}",
"var 3p = ${playerCount} < 4",
"var 4p = ${playerCount} == 4",
"var 5p = ${playerCount} == 5",
{
"func": "IF",
"condition": "${3p}",
"thenRoutine": [
"// 3p actions"
],
"elseRoutine": [
{
"func": "IF",
"condition": "${4p}",
"thenRoutine": [
"// 4p actions"
],
"elseRoutine": [
{
"func": "IF",
"condition": "${5p}",
"thenRoutine": [
"// 5p actions"
],
"elseRoutine": [
"// More than 5p actions"
]
}
]
}
]
}
Quick Links
Home
1. Basics
2. Developer Documentation
- Widgets
- Functions, automation, and routines
- Dynamic Expressions and using variables
- Math, string, array, color, JSON functions
- Cards and Decks
- Editing JSON
- Using CSS
- Fonts and Symbols
3. Available Resources
- Publicly available games
- Tutorials
- Available icons, card backs, button styles, and other images
- Demonstration Features
- Useful Code Snippets
4. Usage Guidelines and Copyrights
5. Other Technical Information
- Download Repository
- Using GitHub and creating Pull Requests
- Internals Overview
- Testing with TestCafé
- URL-addressing rooms
- Using Docker containers
- Config.json file