I've always loved working with computers. From simple Batch scripts in Pre-K to running one of the world's most popular Minecraft servers, I've had a deep passion for the crossroads of computing, creativity, and community.
Software engineering became my way to translate these ideas in my head to reality—from glue to keep game servers running to early machine-learning concepts for community moderation.
Nowadays, I work at Broadcom (née VMware, Pivotal) helping clients solve all sorts of technical and human challenges. From cloud platform buildouts, to solving megascale hurdles, or even starting training bootcamps, nothing is 'not my job'.
I practice Extreme Programming, focusing with User-Centered Design, and implementing with pair programming and test-driven development. I have strong opinions about platforms as products.
Also: a lot of bad bird & astro pictures, bike rides, and a Cannonball attempt.
Can't say I'm a good photographer, but I do find it a lot of fun :)
I read every email, usually within a day or two. Happy to talk about any technical issue you're facing, or if you just wanted to chat about something.
My friends and I really like the card game Dominion. There are already official online offerings of Dominion, but we like playing with house rules, custom printed cards, and competitive matches with rewards. In addition, I thought the UX of the existing online Dominion services were a bit difficult. So, I set out to make my own.
I built a verb-based engine where every Dominion card is expressed as a composition of low-level primitives (called verbs for common actions, like gain_card or trash_card. Cards register themselves via decorators into a central dispatch registry, which are looked up by the core engine loop as events come in. Branching, multi-step interactions (i.e. "trash a card, then gain a card costing $2 more than it") collapse into an atomic state machine installed as a "pending action", pausing the game loop while the nested action completes.
The payoff of this architecture is its' extensibility; adding a new card, a full custom expansion, or a house rule is a single decorated function leveraging these API-like verbs—no changes to the engine required.
# ─── 1. CARD EFFECT: runs when the card is played ───────────────────────────
@card_effect("Avatar of Francesco")
def _effect_avatar_of_francesco(cur_player, cur_pid):
options = supply_options()
if options:
# set_pending halts the turn here. The engine won't advance to the
# next phase until a matching @action_choice fires and clears it.
# The "gain" tag is the discriminator the choice handler dispatches on.
set_pending("Avatar of Francesco", "gain", options=sorted(options))
else:
add_log(f"No cards to gain")
# ─── 2. ACTION CHOICE: resolves the pending the effect installed ────────────
@action_choice("Avatar of Francesco", "gain")
def _handle_avatar_francesco_gain(cur_player, cur_pid, data, pending):
# `data` is the client's response payload; `pending` is the dict we set above.
card_name = data.get("card_name")
options = pending.get("options", [])
clear_pending(pending) # release the engine; we'll re-block below if needed
if not card_name or card_name not in options:
return # invalid selection -> silently no-op; engine continues
# gain_from_supply runs the full gain pipeline (on-gain hooks, triggers, etc.)
if not gain_from_supply(cur_player, card_name, destination="hand", pid=cur_pid):
return
add_log(f"{cur_player['name']} gains {card_name} to hand")
# Fan out to every opponent. enqueue_opponent registers a pending
# attack-response keyed by pid; each opponent will independently
# answer via the @attack_response handler below.
for pid in state.game["player_order"]:
if pid != cur_pid:
gain.enqueue_opponent(pid, {
"card": "Avatar of Francesco",
"chosen_card": card_name,
})
# The gain above may have triggered on-gain reactions (Watchtower, Hostelry,
# Border Village...) that installed their own pending. If so, set_pending
# is identity-aware and defers via a thunk so OUR pending fires only after
# those resolve. Either way, we install a "waiting" pending so the turn
# player stays blocked until every opponent has answered.
if gain.any_opponent_pending():
set_pending("Avatar of Francesco", "waiting")
# ─── 3. ATTACK RESPONSE: one invocation per opponent, accept or decline ─────
# (attack_response is a bit of a misnomer, but the engine started out as only
# "attacks" being the thing off-turn players responded to (i.e. trash a card),
# but as cards grew in complexity, attack_response became the pipeline to handle
# ALL off-turn action responses. I could rename this; but that's a significant
# burden for very little pay off.)
@attack_response("Avatar of Francesco")
def _respond_avatar_francesco(player, pid, data, attack):
# `player`/`pid` here are the OPPONENT, not the turn player.
# `attack` is the dict we enqueued via gain.enqueue_opponent.
chosen_card = attack.get("chosen_card")
if data.get("accept"):
# off_turn_actor swaps the "current actor" for the duration of the block.
# Without it, any on-gain hook (e.g. Watchtower trashing the Curse)
# would fire against the turn player's state instead of this opponent's.
with off_turn_actor(pid):
if gain_from_supply(player, "Curse", pid=pid):
gain_from_supply(player, chosen_card, pid=pid)
add_log(f"{player['name']} gains a Curse and {chosen_card}")
else:
add_log(f"{player['name']} accepts but no Curses available")
else:
add_log(f"{player['name']} declines")
# pop_opponent clears this opponent's queued attack-response. When the
# last one pops, the turn player's "waiting" pending unblocks and the
# turn resumes.
gain.pop_opponent(pid) {
"Avatar of Matthew": {
"type": "avatar-treasure-curse",
"cost": 7,
"coins": 5,
"vp": -3,
"desc": "+$5. −3 VP.",
"expansion": "Aspects",
"tags": [
"+Coins"
]
}
}