I’ve been implementing and refining the core gameplay loop. Since last week, many more mechanics have been implemented and wired up. Opponents have AI, mechanisms have costs, you can win or lose, effects are applied resolve on different cadences, and a lot more. There were several refactoring passes to consolidate logic, switch to more scalable models, and to unify nomenclatures. Unit testing and seeding for deterministic experiences have been incredibly important for my development; it allows me to exercise specific mechanics over and over as I iterate.

In the short-term, I’m using a text-based logging system instead of graphics. This way, I’m focusing on the logic, not presentation. I did make some refinements to the logging to make it easier to debug and to built out the tests. Here’s a sample from a simple conflict where the player damages an opponent, the opponent retaliates, and the player eventually wins.

==> StatePlayerChoice enter(), round 1 - H:10/10 S:0 E:60 SC:0
  Action: action_status_opponents
    0) opponent_flitter - H:10/10 S:0
  Action: action_card_play - { "card": card_beam_1_light:ATTACK OPPONENT (5), "opponent": opponent_flitter - H:10/10 S:0 }
    _on_effect: 'opponent_flitter' resolving effect_damage x 3 @ OPPONENT on PLAY in 0
      EffectResolverDamage: 3 to opponent_flitter - H:7/10 S:0
  Action: action_status_player - TEST_PLAYER, specialization_maintenance - H:10/10 S:0 E:55 SC:0
  Action: action_card_play - { "card": card_beam_1_light:ATTACK OPPONENT (5), "opponent": opponent_flitter - H:7/10 S:0 }
    _on_effect: 'opponent_flitter' resolving effect_damage x 3 @ OPPONENT on PLAY in 0
      EffectResolverDamage: 3 to opponent_flitter - H:4/10 S:0
  Action: action_player_end_turn
<== StatePlayerChoice exit(), round 1 - H:10/10 S:0 E:50 SC:0

...

==> StateOpponentTurn enter(), round 1 - H:10/10 S:0 E:50 SC:0
  OpponentTurn: opponent_flitter - H:4/10 S:0
    Sequence:
      0) --> [effect_damage x 4 @ OPPONENT on PLAY in 0]
      1) [effect_defend x 6 @ SELF on PLAY in 0]
      2) [effect_damage x 3 @ OPPONENT on PLAY in 0, effect_damage x 7 @ OPPONENT on PLAY in 0]
    _on_effect: 'TEST_PLAYER' resolving effect_damage x 4 @ OPPONENT on PLAY in 0
      EffectResolverDamage: 4 to TEST_PLAYER, specialization_maintenance - H:6/10 S:0 E:50 SC:0
<== StateOpponentTurn exit(), round 2 - H:6/10 S:0 E:50 SC:0

...

==> StateDraw enter(), round 2 - H:6/10 S:0 E:50 SC:0
  Deck:
    cards_draw (0)
    cards_discard (3)
      0) card_shield_1_light:DEFEND SELF (5)
      1) card_shield_1_light:DEFEND SELF (5)
      2) card_shield_1_light:DEFEND SELF (5)
    cards_spent (0)
  Hand: (5)
    0) card_beam_1_light:ATTACK OPPONENT (5)
    1) card_shield_1_light:DEFEND SELF (5)
    2) card_beam_1_medium:ATTACK OPPONENT (7)
    3) card_beam_1_light:ATTACK OPPONENT (5)
    4) card_beam_1_medium:ATTACK OPPONENT (7)
<== StateDraw exit(), round 2 - H:6/10 S:0 E:50 SC:0

==> StatePlayerChoice enter(), round 2 - H:6/10 S:0 E:50 SC:0
  Action: action_card_play - { "card": card_beam_1_medium:ATTACK OPPONENT (7), "opponent": opponent_flitter - H:4/10 S:0 }
    _on_effect: 'opponent_flitter' resolving effect_damage x 2 @ OPPONENT on PLAY in 0
      EffectResolverDamage: 2 to opponent_flitter - H:2/10 S:0
    _on_effect: 'opponent_flitter' resolving effect_damage x 3 @ OPPONENT on PLAY in 0
  !! 'opponent_flitter' destroyed
      EffectResolverDamage: 3 to opponent_flitter - H:0/10 S:0
<== StatePlayerChoice exit(), round 2 - H:6/10 S:0 E:43 SC:0

==> StateFinal enter(), round 2 - H:6/10 S:0 E:43 SC:0
  Conflict resolved: 'TEST_PLAYER' won
<== StateFinal exit(), round 2 - H:6/10 S:0 E:43 SC:0\

Here’s a more complicated example; the player still wins, but using three different mechanics; an overclock (unlocks different cards), recycling (reclaiming card costs), and a persistent effect.

==> StatePlayerChoice enter(), round 1 - H:10/10 S:0 E:70 SC:0
  Action: action_status_opponents
    0) opponent_flitter - H:10/10 S:0
  Action: action_device_toggle_active - device_beam_1 to off
  Action: action_device_list
    0) device_beam_1:off GEN H:30/30 (GOOD) C:15
    1) device_beam_1:off RDY H:30/30 (GOOD) C:15
    2) device_shield_1:off RDY H:30/30 (GOOD) C:10
  Action: action_card_play - { "card": card_beam_1_heavy:ATTACK OPPONENT (10) OC, "opponent": opponent_flitter - H:10/10 S:0 }
    _on_effect: 'opponent_flitter' resolving effect_damage x 5 @ OPPONENT on PLAY in 0
      EffectResolverDamage: 5 to opponent_flitter - H:5/10 S:0
    _on_effect: 'opponent_flitter' resolving effect_melt x 3 @ OPPONENT on PLAY in 3
      EffectResolverMelt: applied 3 to 'opponent_flitter'
  Action: action_card_recycle - { "card": card_beam_1_heavy:ATTACK OPPONENT (10) OC }
    Reclaimed: 5 from card_beam_1_heavy:ATTACK OPPONENT (10) OC, energy is now 65
  Action: action_player_end_turn
<== StatePlayerChoice exit(), round 1 - H:10/10 S:0 E:65 SC:0

==> StateOpponentEffects enter(), round 1 - H:10/10 S:0 E:65 SC:0
    _on_effect: 'opponent_flitter' resolving effect_melt x 3 @ OPPONENT on PLAY in 3
      EffectResolverMelt: melted 3 to 'opponent_flitter'
<== StateOpponentEffects exit(), round 1 - H:10/10 S:0 E:65 SC:0

==> StateOpponentTurn enter(), round 1 - H:10/10 S:0 E:65 SC:0
  OpponentTurn: opponent_flitter - H:2/10 S:0
    Sequence:
      0) --> [effect_damage x 4 @ OPPONENT on PLAY in 0]
      1) [effect_defend x 6 @ SELF on PLAY in 0]
      2) [effect_damage x 3 @ OPPONENT on PLAY in 0, effect_damage x 7 @ OPPONENT on PLAY in 0]
    _on_effect: 'TEST_PLAYER' resolving effect_damage x 4 @ OPPONENT on PLAY in 0
      EffectResolverDamage: 4 to TEST_PLAYER, specialization_maintenance - H:6/10 S:0 E:65 SC:0
<== StateOpponentTurn exit(), round 2 - H:6/10 S:0 E:65 SC:0

...

==> StateOpponentEffects enter(), round 2 - H:6/10 S:0 E:65 SC:0
    _on_effect: 'opponent_flitter' resolving effect_melt x 2 @ OPPONENT on PLAY in 2
  !! 'opponent_flitter' destroyed
      EffectResolverMelt: melted 2 to 'opponent_flitter'
<== StateOpponentEffects exit(), round 2 - H:6/10 S:0 E:65 SC:0

==> StateFinal enter(), round 2 - H:6/10 S:0 E:65 SC:0
  Conflict resolved: 'TEST_PLAYER' won
<== StateFinal exit(), round 2 - H:6/10 S:0 E:65 SC:0

There’s a small race condition; the destruction is noted as soon as it’s happened as it’s triggered by the hull reaching zero, but the effect resolution report is rendered after it’s been marked as destroyed. I could mitigate that by stating what’s going to happen before doing it, but at this point it’s a micro-optimization.

All in all, I’m really happy with the progress I’ve made this past week. At this point, I’m ready to start building a user interface. I’m going to keep this first pass incredibly simple, so no textures, just placeholder shapes with debugging text. My intent at this stage is to determine the ultimate question: is it fun? I’ve got several folks I can bounce it off of, and I’m looking forward to seeing people playing a functional prototype well before I get to any sort of vertical slice.


I’ve also done a little bit of commercial music composing; it’s helping a friend out with a project I really enjoy, and will hopefully lead to some more work. Once it’s published, I’ll announce it here as well; I consider it symbiotic with the work that I’m doing with potential crossover audiences.