Monday, 18 September 2017

Debugging your TTS mods with console++ : Functions

Don't just jump in here: start at the beginning!  Once you've loaded the module in Tabletop Simulator remember to switch to the Game tab and turn on command mode with '>'.

In the previous posts we've seen how we can display variables with 'ls' (or 'ls -v') and objects with 'ls -o'; the final option available is to display functions with 'ls -f'.


Here you can see the functions defined in the example mod.  We can do more than just display them, however; we can also call them.  Type 'call dice_total', then roll the dice and call it again.


What if we want to store the value a function outputs?  console++ defines a special path for the last returned value: the '~' character.  You can see this if you display it with 'ls ~', and all other commands can use it like they would any other path; this includes 'add' and 'set'.  Add a variable called total using the '~' path and check it's set to the right value:


The 'add' and 'set' commands can also be used to store a result directly, by calling them with no value argument before the 'call' command.  If you do so the the next 'call' command will store its result where you have specified.  Roll the dice again and then try it by typing 'set total' and then 'call dice_total':



Note that console++ also has a short-cut command to display the last result: simply type the result character on its own to display its contents: '~'


We can pass parameters to functions just as you would expect, by adding them to the 'call' command.  Let's use the is_face_up function on one of the cards.  Remembers, the card objects are stores in the cards table; you can see them by typing 'ls -o cards'.
Type 'call is_face_up cards/2' to call is_face_up on the 2 card, then flip the card and issue the command again (use up-arrow to get the last command typed instead of typing it again).



Note how when we 'ls -f' we see only the functions defined in the mod; we don't see all the built-in Lua functions.  That's not because they are not present; it's because console++ hides them under the 'sys' label.  If you do an 'ls' you will see two things at the bottom of the tables that look like tables, except they are in a salmon colour instead of yellow; these are labels used to hide globals to keep things tidy.  You can see what they are hiding by calling 'ls' on them;
try it now: 'ls sys -f'


At the point in the Lua code where you #include console++ it automatically scans all the globals and hides them under the `sys` label, thus keeping your mod easy to interact with in the console.  All these things still exist in the root table, though they may also be accessed via the label they are hidden under. i.e. 'ls /math' and 'ls /sys/math' are the same thing:



You can also create your own labels to hide things under by calling console.hide_globals in the mod's Lua code.  Type 'ls' and you'll see that as well as the 'sys' label there's also a 'guids' label; if you look at the end of the GUIDS block in the Lua code you'll see the command that created it; whenever you call console.hide_globals it will scan for global variables/functions etc and hide them under the label you specify (provided they haven't already been hidden under another label).



To come back to the point: all the built-in functions are available should you wish to call them.  For example, we can get a color using stringColorToRGB then display the RGB values:
'call stringColorToRGB Blue' then '~':



We can then store it in a variable and use it to highlight an object:
'add blue ~' then 'call /decks/b/highlightOn blue 100'


Thursday, 7 September 2017

Debugging your TTS mods with console++ : Manipulating Variables

If you haven't read it then you should check out the first installment of this series, which goes over how to get set up.

Last time we saw how to look at our program's structure.  This time we'll see how we can manipulate it in real-time by changing and adding variables.

First things first though; last time we were entering commands by typing the command symbol ('>') and then the command.  Let's shortcut past that by entering command mode.  You can type '>cmd' at the prompt to enter command mode, and when in command mode you can type 'exit' to leave it.  When you are in command mode the console will interpret all your inputs as commands, so you no longer need to prefix everything with '>'.
But we're not going to type those!  Instead, an even easier way to enter and exit command mode is to just type the command character on it's own; if you hit '>' then enter it will toggle you in and out of command mode.  Try it now:


After I entered command mode I typed 'cd cards' (just like last time), and you can see that while in command mode any commands you perform will end with a prompt which includes our current path.

OK, so now we can simply type commands without a prefix, let's try a new one out: the 'tgl' (or 'toggle') command flips a boolean.  Let's go back to the root table with 'cd /' then remind ourselves what is there with 'ls'.


At the bottom you can see the four global variables in the mod, and the bottom one is a boolean, currently set to false.  If we look at the code we can see what it does:


update_cards acts as a sentinel on the onUpdate event. Let's turn it on so the mod actually does what it's supposed to do.  Type: 'tgl update_cards' and see what happens.


Well, that isn't good.  The mod clearly doesn't work right, and worse, because the bug is in the onUpdate event it is spamming errors every time it engages.  Investigating will be impossible while the console is being filled with error messages, so turn it off again: in the console hit up-arrow to get the previous command and enter to execute it; this will toggle the update_cards boolean back off, disabling the faulty onUpdate event code.  Note this technique; it's always a good idea to wrap timer-event driven code inside a boolean like this so that if things go wrong you can easily disable the code without aborting from the faulty session.

So what's the problem?  The error is on line 1542, so go to the global script in Atom and hit ctrl-g and enter 1542 to go to the offending line number.  The Atom plugin will take into account any #includes in the code, and will take you to the correct line, which in this case is the one that reads 'cards[count].flip()'.

The error is suggesting that we're trying to index a nil value, so either cards is nil, or the individual location of cards[count] is nil.  Let's have a look: type 'ls cards' at the console.
Nothing? Oh, the cards are game objects; try 'ls -o cards'.


There they are; cards is clearly not nil.  Let's look again at the code - what is the cards table supposed to do?  You can see in the onUpdate event it is looping over the numbers 2 to 10 - the card values on the table.  The table has been set up such that to access the 2 of Spades we use cards[2], to get the 3 of Diamonds we use cards[3], etc.  In the onUpdate event we are looping over each of the cards from 2 to 10 and possibly flipping them.  However, look at the 'ls' output: the cards are not indexed from 2 to 10, they are indexed from 1 to 9.  Looks like the table has been set up wrong! There is no cards[10] so when it tries to access it on the last iteration of the loop it doesn't find anything, thus the error message.
Before we jump back into the code to try to track this down, let's confirm that that is the problem; we'll add a card at that index and see if the code works.  To add a variable we use the 'add' command.  We'll set it to the card that's not being looped over: cards[1].
Type 'add cards/10 cards/1', and then 'ls cards' again.


You can see it has a card at index 10, and it has the same GUID as the one in index 1.  Turn on the code by toggling the boolean: 'tgl update_cards'. No errors - nice!  The update code is working, but our table is still set up wrong; all the indexes are off by one (as you can tell by the card 3 being face up, but the total of the dice only being 2).  Select the dice and hit 'r' to roll them.  The cards will update, but always to the wrong value.

We now know what to fix in the code though, and on investigation you can see the part that sets up the table:



This looks pretty straight-forward; the table is created with a null value in index 1, so that when it loops over the GUIDs of the cards it will add them to slot 2, 3, 4... etc. all the way up to 10.  However, if you are more familiar with Lua than the programmer of this code you will know that nil does not behave just like null does in other languages; in Lua when you assign nil you are actually deleting that entry, so this code is no different from typing 'cards = {}'... it makes an empty table.  The fix should be simple: change the line to 'cards = {'dummy'}' and hit Save And Play.  Now we can go back into command mode with '>' and type 'ls -o cards' to see that they are correctly indexed, 2-10.  (An exercise to the reader: where is the 'dummy' card?)

Toggle the update_cards variable to turn on the event code.  No errors, and the card being displayed matches the total of the dice!  Select the dice and hit 'r' to roll them, and you can watch the cards flip to match.

It feels a bit clunky though; the cards are updating very slowly.  Type 'ls' again to see what variables we have to play with: sure enough, there's a 'check_delay' number set to 1.5 seconds.  Let's set it to a shorter delay using the 'set' command.  Type 'set check_delay 0.3' then select the dice and roll them.  Much more responsive!  If you were writing this code you could easily play with varying values for check_delay until you found the one you liked the best, and then amend the code afterwards to match.

That covers the basics of manipulating variables in the run-time environment.  You can see how we can use it to debug our code in a controlled manner, and also to help in refining control variables. We've now looked at simple variables and objects; next time we'll look at the third of the triad: functions.

Monday, 4 September 2017

Debugging your TTS mods with console++ : Introduction


The typical cycle for debugging a game in Tabletop Simulator is to play it, wait for an error message to emerge, use that error message to find the problem, write a potential fix, then start again.  This is less than ideal in a lot of ways: the error message can be a good clue for what the problem is, but sometimes it can be extremely vague (sometimes it doesn't even have a line number!).  Potentially more time-consuming is that when your game is getting fairly well developed the bugs become hidden further in; it may take a lot of setup to get to the specific circumstances that trigger the bug.  Typically this means that once you think you've written a fix you then need to play the game out exactly the same was as before, coordinating any playtesters to do what they did before, until the bug manifests again... or does not, and is potentially fixed - but how do you know for sure?

console++ gives you live access to your mod's structure, treating it as if it were a file system.  It lets you examine your variables, functions and objects, and also lets you update them, all without leaving the game.

To follow along to this tutorial you need to get console++ from the link above and subscribe to the example mod on the Steam Workshop.  Once you've loaded it into Tabletop Simulator, save it to your own hard disk so you can save your edits.

Source code is visible here.
This post will cover navigating and displaying your program using the two basic commands in console++: 'ls' and 'cd'.  As a quick aside, I'll be using the unix-like command names during this tutorial ('ls'), but console++ includes DOS-like alternatives, so if you prefer DOS you can use those instead.  In this case, anywhere I say 'ls' you can type 'dir' instead.

What does 'ls' do?  Well, we could ask for help on it, but lets do the impetuous thing and just type it into the console; make sure you are on the Game tab of the console, not the Global tab it defaults to (also it would be a good idea to hit the cog and disable autohide if you have it turned on).

So, hit enter, type 'ls' and hit enter again.  Congrats, you just said 'ls'; the console is just the chat window after all, and you didn't tell it you wanted to use a command!  To use a command you type the prefix '>' before it, so lets try again, typing '>ls'.  Much more interesting, and if you looked at the source file above it should look a little familiar:


You can see the variables we defined in the source are there; the 'ls' command lists all the tables and variables in your current location.  Right now we're in the root of our program ('/') which is the global level.  You can see at the top of the listing some labels in yellow: these are tables.  Let's see what's in them; type '>ls decks'. Nothing there?  That's because by default the 'ls' command only displays tables and basic variables (numbers, strings and booleans); to get it to show game objects we need to use the '-o' option.  Try '>ls decks -o' and you'll see:


There are our deck objects: table entries a, b, and c.  We can see their GUIDs listed next to them; right click a deck on the table and check the Scripting submenu to see it matches up.

We can refer to each of these decks by their path: if you type '>ls decks/a' you'll get the listing for only deck a.  Now, if we were debugging something to do with these decks, and were going to type their names a lot it would be tedious to have to keep specifying their location.  Instead we can change our current location to suit our needs, with the 'cd' command.  Type '>cd decks' and then '>ls -o'.  As you can see we have changed the table we are in; we can now refer to the decks simply by their letters: '>ls a'.  Note that when we specify an exact path we do not need the '-o' option.

Paths behave just as you would expect for a file-system; you can specify relative paths from your current location or absolute paths from the root table (by starting your path with a '/') You can refer to a parent table by using '..' and your current table with '.'

Let's go back to the root table: type either 'cd ..' to go up one level (we are only one level below the root) or 'cd /'.  This example mod is clearly very small and simple, but you can imagine in a big game you will have lots of variables and tables; simply typing '>ls' and scrolling up and down looking for things could be cumbersome.  In that situation it would be good to use a wildcard to trim down what you're looking at: try '>ls c*'


That's the basics of navigating and examining your program from within TTS with console++, except to return to what was said at the top of the post about asking for help: you can type '>help' or more simply '>?' to get help on any command, and the commands with more complicated options will also support a '-?' option to provide more detailed information.  If you type '>? ls' you'll see it has a few options we haven't tried yet.  To wrap up lets try '>ls -ar' ...



Next time we'll look at actually interacting with your program by modifying variables in real time.

Thursday, 24 August 2017

Atom Tabletop Simulator package, experimental #include system

A note about the experimental additions, specifically the #include:

You can #include other files into your source; when you hit Save And Play the plugin will go and grab those files and inline them in your code at the point you have placed them. The files are looked for inside the folder specified, or you can specify a complete path to their location.

For example, on the game I'm working my Global.-1.ttslua is now simply:

-- workshop id = 945458382

#include Shard/shard
When I hit Save And Play it will look for the shard file in the folder location specified in the settings: by default this is your USER_FOLDER/Documents/Tabletop Simulator. So in my case it will get the file:

C:\Users\onelivesleft\Documents\Tabletop Simulator\Shard\shard.ttslua
- Shard\shard.ttslua being specified by the #include 

My shard.ttslua file looks like this:
Code:
#include constants
#include guids
#include globals
#include levels
#include !/utils

#include admin
#include game
#include draft
#include rewards
#include dice
#include expansions

--NO_SAVE_OR_LOAD = true  -- To remove any state for clean upload to workshop
--DEBUG_DISPLAY   = true

game_started = false
loaded = false

function onload(saved_data)
    current_level = nil
    ...
When the plugin fetches it, it will then look through it inlining the includes it has, and so on. This time it won't look in the folder specified in the settings, it will look in the same folder as the file which included them. My file structure looks like this (and is a git repo!):



Almost all the files included by shard.ttslua are in the same folder as it, so I only need their name.

Note the line: #include !/utils - the !/ is an identifier that specifies the folder specified in the package settings.  In this way I can have a utils library sitting in the root of my TTS folders, accessible by all my games.  You may have a vector library you wish to include in all your objects' code; now, this is easy to do.  You may also use the symbol ~/ to start a path from your user home folder (even on Windows), or specify a full path on the #include line if you want to.

In practice how this works is it takes all the text in the files you are including and dumps them into the source at the point they are included.  This means if you have a bunch of functions you want your objects to have access to then you can, but they are in effect being repeated in every object. 

As it stands this means all your modules are sharing the same namespace; a global in one is accessible in all.  I'm OK with this (it's the Lua spirit), but if you want to make them behave like modules you can, you just have to do it explicitly.  

  1. Start the module by declaring itself as a table.
  2. Prefix all it's "globals" and function with that table.

Let's say you have a module to greet the players, saved as greetings.ttslua :

greetings = {}

greetings.default_greeting = "Hello!"

function greetings.greet(message)
    local message = message or greetings.default_greetings
    for p, player in pairs(getSeatedPlayers()) do
        printToColor(message, player, rgb)
    end
end

Then you can use it in your program by:

#include greetings

greetings.greet()
greetings.greet("Hi!")
greetings.default_greeting = "Yo!"
greetings.greet()

When you get the script from TTS the atom plugin will automatically strip out the inlined code and give you what you expect.  Note that the package commands like Go To Function will take into account all your includes, and when you use them will jump to the correct place.  If you are in your Global.-1.ttslua file and type a line number into the Go To Function dialog it is smart enough to know which line you are referring to and take you to it.  i.e. even though the program I listed at the top of this post only has 3 lines when I see it in the editor, when TTS says there's a bug on line 4345 I can hit ctrl-g and type 4345, and it will take me to the correct line (opening up the relevant included file if necessary).

Finally, note that when the file is loaded into atom from TTS the undo buffer will record the changes made by this system, so if you want to see the raw code that TTS is seeing you merely have to hit ctrl-z a couple of times after loading.

Thursday, 6 April 2017

Shard changes after 01/04/17


  • Energy Shield
    Passive and active now grant +2 TN against ranged (instead of +1 soak dice)

  • Wrist Blaster
    Now a reaction, can be activated on any coach's turn (ranged 1 dam 4)

  • Pulse Pistol
    Increased attack roll to AGI+1 (up from AGI)

  • Pulse SMG
    Increased attack roll to AGI+1 (up from AGI)

  • Pulse Cannon
    Increased attack roll to AGI+2 (up from AGI+1)

  • Launcher
    Increased attack roll to AGI+2 (up from AGI+1)

  • Moment of Truth
    Buffed built in actions (was just a [move]). Now reads:
    If you only have one player who is not KO'd or incapacitated then this card may be played on them (even if this is not their card). They may take any or all of the actions listed below (instead of choosing just one of them). // [[move] or [attack] or [use]]
  • Passing Play
  • Buffed ability text to:
    If this player passes the Shard this turn roll [dice:+1] for the pass. If the receiver catches the Shard and has not been activated this turn then when you activate them you may have them take a [move] instead of playing a card.
  • Coruin Hlest - Steal
    Fixed so he can steal from an adjacent player without having to move first.

  • Shadow Dancer - The Weaver
    Nerfed: removed riposte ability of parry.

  • From Dawning Minutiae
    Buffed their passive (before it only triggered if they were discarding to stand up):
    When a coach wants to play a card belonging to a player who is adjacent to From Dawning Minutiae they must first discard another matching card from their hand.
  • Gaffer Jones
    Changed his ability for attaching cards to Automata (added turn restriction, but allowed any Basic card):
    [react] : During Gaffer Jones' activation, once per turn, attach a card from your hand to an Automaton (discarding any card already there). The card must match Gaffer Jones or be Basic.
  • Gaffer Jones - Automaton
    Gave Automata an in-built Use ability:
    [use] : Move [mp] then [melee]
  • The King
    Removed his passive that made removing an injury not cost a star.  Instead gave him:
    When flipping a card for this player from the Critical Strike deck flip an extra card and choose which one they get.  Use this ability after any other abilities which deal with cards flipped from the Critical Strike deck.
  • Pickles - Deploy
    Gave Pickles' Emblem card (which was a standard Deploy: [use]+[move]) the Team trait.

  • Switchback - Energy Shackle
    Fixed wording, using written "attack" and "move" instead of icons.  Now reads:
    [use] : Make a [target:2] on an enemy player.  If you hit then they are held immobile, unable to move or attack, for as long as Switchback stays upright in the same square or until the start of your next turn, whichever is sooner.

Clarified the jargon on "teammate"  and "ally": an ally is another unit in your team, while a teammate is an ally who is also a player.  Used the correct one of these terms on these players:
  • Oubliette - Barricade
  • Webspinner
  • With Unfailing Anticipation - Recall

Sunday, 26 March 2017

Shard changes after 25/03/17

Blister

Removed the range restriction on her emblem card ability.  It now reads:
Choose an enemy player who Blister can see who is not in cover and has an empty adjacent square under the LOS. Warp Blister to that square then [melee] them. 

Havok

Changed his emblem card ability to:
Place the Suppression marker in any square.  Whenever an enemy enters a square within 2 squares of it Havok may make a [ranged] against them if able. Havok gains [dice:+1] when making a ranged attack against anyone within 2 squares of the Suppression marker.  Remove it from the board at the end of your next turn.  
Place the Suppression marker in any square; if Havok is Downed remove it. Whenever an enemy enters a square within 2 squares of it Havok makes a [ranged] against them if able.
If it is still on the board at the start of your next turn then Havok makes a [ranged] against one enemy within 2 squares of it, then remove it. 
Place the Suppression marker in any square; if Havok is Downed remove it.  The first time in a turn an enemy enters a square within 2 squares of it Havok makes a [ranged] against them if able.
If it is still on the board at the start of your next turn then Havok may make a [ranged] against one enemy within 2 squares of it, then remove it regardless.
A player may only take one [attack] action per turn. 
 


Rules

 Adding this rule:
Whenever you make any roll for a player carrying the Shard they will fumble it if you roll as many [crit] as their Agility; scatter it from their square.  When they attempt to pass the Shard the roll is first checked against the thrower then the receiver (scattering from the relevant square if applicable)
"Fumble TN" is no longer a thing: adjacent enemy players no longer affect this mechanic.

 Considering adding this rule:
Whenever the Shard carrier makes any roll (other than to contain the Shard), if they roll as many [crit] as their Agility then the Shard will surge.

Wednesday, 4 January 2017

Standout Games of 2016

The Witness




Finding a screenshot for The Witness was hard.  Well, I should say choosing one was hard, because the game is unendingly beautiful to look at; as you explore it every new horizon you walk over yields some new work of art.  You could take pretty much any screenshot that wasn't a close-up of a puzzle panel and it would look good on this page.

That's not why The Witness is on this list though (though it certainly helps); The Witness perfectly accomplishes what every good puzzle game tries to do: make the player feel like a genius.  Over and over you'll be stuck, and confused, and uncertain, and then your brain will click, will make the magical click noise in your head as the bulb flashes on and you get it.  There is lots and lots to get, extending even beyond the game you think you're playing.

I love Metroidvania games; a genre named after its two main antecedents, Metroid and Castlevania. In these games you explore a generally open environment, and as you progress you gain power-ups that affect your ability to traverse the terrain.  For example, a ledge you walked past on the first screen was too high to jump to, but once you get the double-jump power up you can go back and see what's up there.  This type of gameplay just pushes my brain's buttons, and so too does The Witness, because it's a Metroidvania game in disguise.  There are no power-ups to collect, not in the game: the power-ups are inside your head.  As your neurons evolve to overcome the puzzles in front of you they unlock locations you had previously deemed inaccessible.  Puzzles you had to ignore because they just didn't make any sense snap into focus, and you spend your time wandering over the island again and again, round and round, and it never gets dull because (a) it's beautiful and (b) you're a genius.


Audioshield


It was hard to find a screenshot for Audioshield too, but that's because there are barely any online. Worse, while its visuals are great when you're playing it, they hardly look great in 2D on a monitor. Audioshield is a VR game; VR for Virutal Reality.  Wearing a headset, like those giant face-lawnmowers you saw in the 90's, except these days they look more like something from The Matrix and are awesome instead of naff.

Audioshield is the game that pushed me over the edge into buying a Vive.  Space Pirate Trainer was the first VR game I played, and for Shock & Awe it's hard to beat; the visuals are amazing, the soundtrack makes you feel instantly badass, and then you pick up the pistols and double-down.  But Space Pirate Trainer isn't on this list and Audioshield is, because the former makes you feel like a badass for 10 minutes, while the latter fills you with joy for hours and hours.  Audioshield is the VR game I have the most time played in by a factor of ten (discounting Binary Trigger for obvious reasons).

Superficially Audioshield appears to be a rhythm game, like Guitar Hero or Osu.  Blue and orange orbs fly at you, and you punch them in time to the music.  However, this isn't quite accurate.  You can play it this way; in fact the developer patched in a more extreme difficulty just for the players who want this, but this is not what I like about it.  Audioshield is not a rhythm game; Audioshield is a dancing game.

Playing Audioshield is like the best bits of being a slightly drunk in a nightclub; dancing without inhibition to thumping tunes, working out your body, (your aggression if you want), like you're in the start of Blade, forever, but without all the crap bits of being in a nightclub: no smoke, no drunks, no shitty DJs, no trying to look cool so someone will have sex with you, no worrying about finding someone to have sex with you, no vampires ripping your throat open...  None of that shit.  It's just dancing and punching and the best music you can think of because you're the DJ and what you want to dance to is what gets played.


Slither.io



Slither.io is the spiritual successor to Agar.io.  You start off as a little tiny snake, which moves towards your mouse pointer.  You'll eat the little coloured blobs of light that are lying around (perhaps not realising they are being shat out by all the other snakes), and like in the ancient Nokia game, they'll make you grow longer.  Slowly, ever so slowly, but you'll grow longer and fatter.  Of course, the arena is full of other snakes all doing the same as you, all controlled by another player, all wanting to get bigger, to be the biggest.

The shining lance of game design that makes this game amazing is the very simple, very brutal rule at the core of its gameplay: no matter how big you are, no matter how many other snakes you've killed, if your snake runs face-first into any other snake, you die.  No hitpoints.  No extra lives. You die, and will be reborn the smallest little snake you can imagine.  Again.
You can loop around over your own body as much as you like, but touch any other snake with your nose and you're dead, and when you die you leave behind all the blobs of light you've eaten so far.  If you want to get big in Slither.io, and get big quick, killing big snakes and eating their remains is the way to do it.

The beauty of this mechanic is in the interaction between your size and everyone else's.  Once you are a tidal behemoth, leviathan, roaming the edges of the arena like some vast oil tanker, you have the same problem the oil tanker does: you are no longer maneuverable. You take an age to turn.  You can speed boost for years (you speed boost by holding down the mouse button, but it uses up your girth so smaller snakes can't do it very long, while monsters can do it practically forever), but always you are afraid, because one little knock, one dunt on your nose and you're toast, and some little shit is going to eat your corpse.  Meanwhile the tiny little snakes can only speed boost in short spurts, but they are so very agile, turning on a dime.  They're also without fear: what does the newborn care of death?  If it tries to kill you and dies, it gets reborn the same size; no loss.  Baby snakes will happily kamikaze face-first into a giant, because it's 50/50 who dies, and they have everything to gain and nothing to lose, while the giant can win the equivalent of a grain of rice, but stands to lose everything.

The other striking thing about Slither.io is the emergent behavior you start to notice once you've been playing for a while.  The threat behavior of two similarly sized snakes, using speed-boost like a cat making itself larger.  Players meet like animals in the wild; each trying to maneuver against the other for an advantage, but unwilling to engage an even or unfavorable fight.  Then there's the subtleties of snake combat, of coiling, the Qix-like mini game of trapping a smaller snake within your coil then shaving territory off in infinitesimal slices, while they try to out-bluff you with their speed boost and peg you as you turn in.  The risk/reward of killing your captured prey while other snakes draw near, hoping to catch you in your weakened state; if you are a long snake and are spread out you are hard to kill, but while coiled up you take up no space and can easily be enveloped by another.  Few things are as satisfying as killing a snake which is about to kill another smaller snake (and then killing the smaller snake too). Crucially, none of this is designed, or encoded; it comes about purely from the game's one simple rule.

I'm going to stop writing about Slither.io now (I could go on), but I realize I've written more about this free web game (go play it yourself now!) than the commercial giants listed above it.


Overwatch


If there's a game on this list that you're going to have heard of, regardless of how little you pay attention to video games, regardless if you just got back from a year-long sabatical in Zaire, it's Overwatch.  Blizzard's class-based FPS emerged and then blew-up this year, in part due to its slick-yet-cool-yet-simple gameplay, and in part due to these amazing. shorts.  I mean blew. up. I saw a guy wearing an Overwatch jacket coming out of the train station this morning.

I think in the last couple of months my interest has waned (perhaps because I've been busy with the Vive), but for a few months in the middle of the year I played a shit-ton of Overwatch.  It's just so, so... FUN!  It's probably the most beginner friendly FPS there has ever been, thanks to a wide array of different-feeling characters, some of which you don't even need to be able to aim with (they might have a weapon that locks on to targets, or build turrets that shoot automatically), but while easy for noobs it also packs a ton of depth for the more ardent fans.

Get ready for a lot more Overwatch in 2017.


Flight


Flight is a simple little flash game I sat and played through when I had nothing better to do.  I think the word to use is: charming. It's just so nice, and sweet, and... charming.  You will be charmed.  I don't want to say much about it since discovering the game pretty much is the game, so just go play it yourself.  But whatever you do, don't throw the plane backwards, and when you do throw the plane backwards don't say I didn't warn you.


INSIDE


INSIDE is horrific.  Horrible.  Horrifying.  It tells a story, but the story is not concerned with narrative or character development. The story is about projecting emotions onto you, the player, and those emotions are oppression, ignorant cruelty, inescapable horror.  It depicts fascism, not as an artform to portray to the player why fascism is bad, or how fascism works, but instead to use fascism as a tool - fascism is the tool here, not the subject - a tool to push the player's face in.  It's a terror-realm, not of jump-scares, but of a reality fashioned from the building blocks of nightmares.  The nightmares of grown-ups, and the nightmares of their childhood.

I got INSIDE as a surprise Christmas present, having never heard of it (though having googled it since have discovered it was something of an Indie darling).  Made by the same team who created Limbo, it feels most like a modern take on Another World.  Modern in that it looks amazing, with its flat textures and faceless protagonist. Modern in that its control interface is seemless, responsive, slick. Modern in that it can be this polished mechanically while still having the ugliest, rawest soul you could dream of.

I don't know.  I've read a bit on what people think the game means, what it could mean, what the developers intended it to mean...  to me it still means what it meant as I was playing it: it puts you in the nightmare.  The nightmare you can't wake up from, where you're being chased, where you know that whatever is chasing you is going to catch you and there's nothing you can do about it.  The nightmare of when you're a kid and a dead animal is the most terrifying thing imaginable.  The nightmare of your body.  The nightmare of the ordered masses.  It's well worth your time, if you're so inclined*

*Not if you have a phobia about water as it has extended sections like that so don't do it you'll die.