Marvel Snap at its core is a card battler set in the Marvel Universe, where you play a deck of 12 cards and are trying to win at least 2 of 3 randomly generated locations on the board in 6 turns.

You start with 3 cards in your hand, and draw one each turn. Each card has an energy that decides what it costs to play, and a power that adds to the total to determine which player wins that location. You start at 1 energy and gain +1 energy every turn.

The game has a competitive mode, and I am horrendous at it. Over the course of the 2 years or so I’ve played this game I’ve made multiple excuses as to why I am so unlucky on the ranked ladder but ultimately I just play the game wrong and then blame my opponents. Like a true gamer.

Last month I migrated my personal journal and knowledge base to Obsidian. On a whim, I decided that it might be a fun project to build a system to track and visualize statistics about my Snap games to try and objectively answer the question - why am I so bad?

Laying down the foundation

To get started with analysis, I first need to figure out what data to collect and how.

For a typical game, the stuff you want to record is the following:

  • Your deck: The game has many archetypes, some more reliably effective than others
  • Opponent’s deck: If they are playing a deck that hard counters or just tends to always beat yours
  • Locations: Some locations benefit certain decks and can heavily influence the result
  • Outcome: Whether you won, lost or retreated
  • Cubes: You lose or gain cubes depending on the outcome and the number of players who “snapped”

Taking all this into account I settled on the following template that I would use for each individual game I recorded.

---
date: {{date}}
time: {{time}}
type: snap-game
result: win/loss/retreat
opponent_name:
opponent_deck:
my_deck:
locations:
  - location1
  - location2
  - location3
cubes: # Use positive for gains (e.g., +4), negative for losses (e.g., -2)
notes:
---

## Game Details

### Key Moments

### Lessons Learned

The YAML frontmatter is what I use to power my dashboards, and the Markdown underneath is unstructured data to record how the game actually progressed so I can also have a play-by-play if I fumbled a good play or managed to cook something clever.

Obsidian provides a very serviceable GUI to edit frontmatter fields which makes the job of filling it in pretty easy.

Obsidian's GUI for the frontmatter given above, of note is the date picker and the 'chips' pattern for entering list values for the locations field

I fed this page into the Obsidian Templates plugin which takes care of filling in the date and time automatically when I create a new page based on this template.

Building the visualization

For this purpose I leveraged the excellent Obsidian DataView plugin which provides an SQL-like query language that can introspect the Markdown documents in a standard Obsidian Vault and surface structured data from it.

As an example, here’s the code for the dashboard that is in my daily note template to see the current day’s games.

TABLE WITHOUT ID
	length(rows) as "Total Games",
	length(filter(rows, (x) => x.result = "win")) as Wins,
	length(filter(rows, (x) => x.result = "loss")) as Losses,
	length(filter(rows, (x) => x.result = "retreat")) as Retreats,
	sum(map(rows.cubes, (x) => number(x))) as "Net Cubes"
FROM "snap-games"
WHERE type = "snap-game"
	AND date = date("{{date}}")
FLATTEN file.link as FileLink
GROUP BY null

It is pretty self-explanatory, but essentially this walks through every page in my Vault that has its type field set to snap-game and today’s date, then collates the win/loss record from the other YAML frontmatter fields and makes a nice and simple table out of it.

A table with a single row showing the total games, the wins, the losses, the retreats and the net cube change for the day

This can also be extended to cover the past week

A table with a single row showing the total games, the wins, the losses, the retreats and the net cube change for the week
DataView code
TABLE
	date as Date,
	time as Time,
	result as Result,
	my_deck as "My Deck",
	opponent_deck as "Opponent's Deck",
	cubes as "Cubes Δ"
FROM "snap-games"
WHERE type = "snap-game"
	AND date >= date(today) - dur(7 days)
SORT date desc, time desc

I can see what decks I played and how they performed relative to each other

A table showing the decks I played in each row and their respective game counts, win rate and net cubes statistics
DataView code
TABLE
	length(rows) as Games,
	round(length(filter(rows, (x) => x.result = "win")) / length(rows) * 100, 1) + "%" as "Win Rate",
	sum(map(rows.cubes, (x) => number(x))) as "Net Cubes"
FROM "snap-games"
WHERE type = "snap-game"
	AND date >= date(today) - dur(7 days)
GROUP BY my_deck
SORT length(rows) desc

I can also see which locations favor me the most

A table of every location with its name, number of times I played there, my win rate and the net cubes
DataView code
TABLE
	length(rows) as "Times Encountered",
	round(length(filter(rows, (x) => x.result = "win")) / length(rows) * 100, 1) + "%" as "Win Rate",
	sum(map(rows.cubes, (x) => number(x))) as "Net Cubes"
FROM "snap-games"
WHERE type = "snap-game"
	AND date >= date(today) - dur(7 days)
FLATTEN locations
GROUP BY locations
SORT sum(map(rows.cubes, (x) => number(x))) desc

Did it help?

I don’t know!

Work getting hectic meant I had little time to actually play and the re-launch of the High Voltage mode took up most of that.

I did play a tiny amount of games when I first built this out, and did pick up on some of my tendencies that make me lose games but ultimately it’s a very small sample size. The new season of Snap will start tomorrow so hopefully I’ll be able to get back into it and actually figure out if this whole shebang was worth it.