Most of my time is spend working full-time as a programmer, so the lens through which I view many of the tools I use is software design. Fortunately, I think this perspective creates a really useful way to understand how VASSAL can be leveraged to create dynamic, controlled experiences.
You may be surprised for me to suggest that programming in VASSAL works a lot like an object-oriented language. In fact, it works a lot like an object-oriented language that supports multiple inheritance and where class instances have both a public and private view. How it gets about this is not necessarily straightforward, but it's impressive nonetheless.
Under the main Module type is the Game Piece Prototype Definitions type. Within the Game Piece Prototype Definitions type, you may add Definition entries. Definition entries created here will contain variables and methods that are useful to share between subclasses and instances.
There are a number of locations where you will implement instances of these Definition entries. I'll look just at the Card type in this article to simply things, but there are several others. Card types appear in the Deck type and is free to override and add variables and methods.
When in comes to inheritance, the Definition type and Card type both support multiple inheritance. This is done by adding the Prototype trait to the entry. The way that the inheritance works is by mixin - meaning that all traits from the specified entry will be mixed in at the location the Prototype trait was added. This will happen when the module runs, not when the trait is added.
When you create a Card type, you'll see the BasicPiece trait has been automatically added. This trait automatically adds and manages variables to each instance when the game is run that provides information about the game state. I'll copy some of these variables from the documentation so that we can have a look at them before moving on:
- BasicName returns the name of Basic Piece trait, as specified in the properties
- playerSide returns the side of the current player, as specified in the Definition of Player Sides
- CurrentMap returns the name of the current Map Window
- CurrentBoard returns the name of the current Board
- CurrentZone returns the name of the current Zone
- CurrentX returns the current map X co-ordinate
- CurrentY returns the current map Y co-ordinate
- DeckName returns the name of the Deck if the piece is currently stacked in one
- Selected returns true when the piece has been selected with the mouse
While VASSAL doesn't let you go in and write the code for your methods yourself, it provides a number of prewritten methods in the form of traits. Not every trait acts like a method, many traits exist to change the appearance of a card. An easy way to spot a trait that acts like a method is to look for a trait that contains an input for a Keyboard Command.
Understanding keyboard commands is key to understanding interactivity in VASSAL. Instead of thinking of a keyboard command as something like Ctrl + A, think of them as method names. It's a bit of a chore to enter a string of characters in VASSAL, but I find pressing Ctrl + A twice and then typing is the easiest way.
Let's look at an easy example: the Delete trait. When you select the Delete trait and press "Add -> " you'll see that it has an input for a keyboard command. Think of the Delete trait as a method that will delete the card from the board. Many traits also add themselves to the right-click menu of the card, which is why the Command name input is there. If you leave the command name blank, it won't show up in the right-click menu. Enter a keyboard command to name the method. By entering a method name such as oneTimeUse, we can now call this method to delete the card.
More complicated methods exist, and you can think of most of the inputs they offer as a set of default arguments to the method. Don't forget, our cards have variables as well, and these can also be passed into many trait methods.
All method calls will be initially triggered by some sort of player action. A number of inputs exist to initiate method calls but I'll be looking at one of the easiest to understand: the Trigger Action trait. Trigger Action is very versatile and I'll be looking into just the part of this trait that is initiated by the right-click menu on a card.
You specify the text you want in the right-click menu by filling in the Menu Command input. You also want to specify a keyboard command (its method name) even though you won't be calling it directly. Let's call the right-click command Use Card and the method name onUseCard.
Now, when the user triggers this method call through the right-click menu, we can tell it to call any number of other methods. For example, if we had another method that triggered a dice roll then called the oneTimeUse method we talked about above that deleted the card, we would enter roll2d6 in the first line of Perform these Keystrokes and oneTimeUse in the second line.
What good is a programming language without conditional code? Many times, we will want to only call a method if some condition is met. VASSAL is definitely not the king of conditional code but it does let you deal with the most common condition: an if-then. Trigger Action is once again the trait we want to employ. It has an input labeled Trigger when properties match where you can use a combination of the BasicPiece variables that contain information about game play and other variables that have been added by you to the instance or superclass of the card. It has a little pop up that I always use to enter expressions.
Be careful about adding this expression to traits that will show up in the right-click menu. If the expression doesn't match when the right-clicks, it will have that menu item grayed out. If, instead, you want one method to be called if an expression is true and another method to be called if an expression is false, you'll need 3 Trigger Action traits. The one that shows up in the right-click menu will simply call the 2 other traits. One of those traits will call one method if an expression matches and the other will call a different method if another expression matches. The two expressions could be as simple as power > 2 and power <= 2.
We add variables to our classes and instances through the Dynamic Property trait. The Name input is the variable name and the Value input is its default value. You may think that changing the value of this variable would be done through another trait, but it's done immediately below in the Key Commands area.
When you click New, it adds a row that lets you change the value through setting a specific value, adding to (or subtracting from) a value, or prompting the user. These changes can be triggered once again through the right-click menu but right now we're more interested in using a key command (method name).
The final piece of the puzzle - and perhaps the most confusing - is the Mask trait. Each card should only have one mask in its inheritance tree as the mask is what separates the front of the card (its public view) from the back of the card (its private view). Everything below the mask applies to the back of the card and everything above it applies to the front of the card.
If you add a Prototype trait where the referenced definition contains a mask, you'll need to keep this in mind. Some traits don't care about whether the card is public or private, but others will only work on one side of the card.
Everything I've discussed up until now involves taking action within a single instance. But we all know how important it is for object instances to communicate with one another and with global properties and methods. VASSAL allows for all of these methods of communication.
Locating Other Instances
What does it mean to locate an object instance in a board game? Well, we want to narrow down our cards by what board they're on, what deck they're in, what player hand they're in, and even by instance variables you've added.
This is achieved using the Global Key Command trait. Once again, you add the trait and give it a method name using a keyboard command. When triggered, this method will look through every instance of the game and check to see if your expression matches that card. Matching cards will have a method called that you specify with the Global Key Command input.
It's messy but it works!
Unlike instance methods that are added in the form of traits, global methods are added in the form of new entities. Most of these new entities are things that would appear in the main window of your game as buttons in your top bar. Some of them you do want to appear in the top bar, but you are free to make them invisible by leaving their icon and button text blank.
One such global method is the Action Button entity which, among other things, can play a sound. When you add it to your module, you may give it a method name using the Hotkey input.
To call a global method, use the Global Hotkey trait and enter the method name you specified in the Hotkey input. If you gave your Action Button global method the name playDing, you'd be calling the playDing global method using this trait.
This time, there's actually a Global Properties type under the main Module type. Here you can add a Global Property entity, name it, and give it a default value.
To set a global property, you use the Set Global Property trait. It is extremely similar to the Dynamic Property trait and you be able to manipulate the global property in a similar way.
What can you do with all of this? I'd like to provide you an example that I think illustrates these different pieces all working together.
In VASSAL, you can create special windows using the Player Hand type and give them names matching each player side. Within that window, I can create a game piece with a deal icon and use the Action Button trait to make it call a method when clicked.
The method that gets called will be the Global Key Command trait and it will look for a card checking to see if its DeckName property is DrawPile (a card in a deck named DrawPile). There is a way with this trait to limit the number of cards found and I'll limit the number of cards to 1.
We'll tell it to call the sendToPlayer method on that card. The sendToPlayer method is something I've added to the superclass of every card and is the Send to Location trait. This trait lets me specify a map to send the card to using an expression. The expression I use will contain the PlayerSide property (the currently active player) so that the card is sent to the current player's hand.
Let's say the active player is on the "Red" side. Clicking the draw button calls the sendToPlayer method on 1 card in the draw pile which sends the card to the "Red" map which is the player's hand.
While we're at it, we should stop the draw button from working once the player has their maximum hand size of 4 cards. After defining a global property named handSize, we'll use the Set Global Property trait to define a method that sets handSize to 0 every time the draw button is clicked. We can now use the Global Key Command trait to call a method named inHand on all cards in the map of the current PlayerSide that we've created by adding a Set Global Property trait to the superclass of every card that increases the value of handSize by 1.
Now, before triggering the method that will send a card from the discard deck to the player's hand, we route it through a Trigger Action trait that will only execute if the global property handSize is less than 4.
You'll really have to mess with VASSAL to get a better idea of how this all plays out. Just remember that you have to work within the limitations of the entities and traits VASSAL provides. Start thinking about key commands less as actual things the user will type on the keyboard and more as method names. Use Global Key Command to send messages to other cards. And finally, make sure to name everything sensibly. When you really get the hang of it, you'll be able to build a large game with a minimum of repetition and a lot of interactivity.