Page 1 of 1

Drawing cards simultanously while not losing the game

PostPosted: 13 Jul 2020, 13:34
by FastEddie
This is the result of trying to fix Prosperity. It has (hopefully soon had) the issue that the active player loses the game even If both players run out of cards which should end in a draw. I fixed it easily but then you throw in cards like Laboratory Maniac and suddenly things become interesting… so the idea was to write a more general function both_players_draw() for Wheel of Fortune and similar cards that takes this issue into account.

What we want is something like this:
Code: Select all
int both_players_draw(player_t player, int amount) {

  loseGameStartsRecordingIncomingCalls();
  draw_cards(player, amount);
  draw_cards(1-player, amount); // opposite player as player is always 0 or 1
  loseGameStopsRecordingIncomingCallsAndEvaluateResult();

  // If both players had enough cards we end up here, otherwise the game ends beforehand.

  return 0;
}
The idea is to tell lose_the_game to lie low and look what comes in. Only afterwards it should evaluate who lost (nobody, one side, both) and react accordingly.

I understand that there is a LOSE_GAME event in Shandalar that we could (ab)use but up to my knowledge there is no such thing in Manalink. Lacking an alternative that is more in line with the game mechanics I came up with something else. I know that this is the proverbial sledgehammer to crack a nut so suggestions how to solve this issue differently are welcome.

The code now looks like this:

Code: Select all
static jmp_buf buf;

int both_players_draw_better(player_t player, int amount) {

    loseGameRecorder::instance().startRecording();
    if (!setjmp(buf)) {
        draw_cards(player, amount);
    }
    if (!setjmp(buf)) {
        draw_cards(1-player, amount);
    }
    loseGameRecorder::instance().stopRecording();
    return 0;   
}
loseGameRecorder is a so-called mutex which is in principle a global variable that counts access requests. As long as it is >0 access it „locked“ (i.e. you can‘t lose the game). When it becomes 0 again you can lose the game again. As a service it calculates who lost and calls lost_the_game afterwards.

I didn‘t want to use a global variable as I need some functionality around this mutex and also some access control. Which is why it is implemented the way it is, namely a so-called Singleton (https://en.wikipedia.org/wiki/Singleton_pattern). My previous comment about a better world with less Singletons applies accordingly.

The next issue is coming back from lose_the_game while recording calls. We need to go back to the initial caller, here draw_cards, but we don‘t know how we got there. draw_cards might call lose_the_game directly or the Laboratory Manic or some other card might have done so. So we need to take all the previous calls from the call stack, otherwise we end up in a mess. If we want to stay within C longjmp() is the only way I know, basically a goto command across functions (goto is something that usually makes people green in the face but there are legitimate uses…). I want to stay within C as I don‘t know what calls go through the exe and what happens there. C++ would offer a more elegant solution with try/catch, basically throwing an exception instead of calling longjmp and doing nothing after the catch, but I don‘t know how the exe behaves when a exception passes by as there is probably no code to handle it (which in turn might introduce various new hard-to-track bugs).

Note that Laboratory Maniac is just an example for some card that changes the outcome of lose_the_game and adds to the call stack. You could also introduce Jace, Wielder of Mysteries or other cards that do something similar.

A toy example implementing this is attached. Since it is more future-proof than checking for the existence of individual cards (my current hack) the plan is to implement this in Manalink and see what happens.

What do you think?

Re: Drawing cards simultanously while not losing the game

PostPosted: 16 Aug 2020, 16:37
by drool66
Here's what I came up with for Prosperity:

Code: Select all
int prosperity_legacy(int player, int card, event_t event){
   if( trigger_condition == TRIGGER_REPLACE_CARD_DRAW && affect_me(player, card) &&
      !suppress_draw && ! is_humiliated(player, card) )
   {
      int trig = 0;
      int * deck = deck_ptr[reason_for_trigger_controller];
      if( deck[0] == -1 ){
         trig = 1;
      }
      if( trig == 1 ){
         if(event == EVENT_TRIGGER){
            event_result |= RESOLVE_TRIGGER_MANDATORY;
         }
         else if(event == EVENT_RESOLVE_TRIGGER){
            card_instance_t *instance = get_card_instance( player, card );
            if( reason_for_trigger_controller == player )
               instance->targets[6].player = 1;
            else if( reason_for_trigger_controller == 1-player)
               instance->targets[6].card = 1;
            suppress_draw = 1;
         }
      }
   }
   return 0;
}

int card_prosperity(int player, int card, event_t event){
/* CARD_ID_PROSPERITY   1497
Prosperity   |X|U
Sorcery
Each player draws X cards. */

   if(event == EVENT_RESOLVE_SPELL){
      card_instance_t *instance = get_card_instance( player, card);
      int legacy = create_targetted_legacy_effect(player, card, &prosperity_legacy, player, card);
      APNAP(p, {draw_cards(p,  instance->info_slot);};);
      int p_lose = (get_card_instance(player, legacy)->targets[6].player > -1);
      int o_lose = (get_card_instance(player, legacy)->targets[6].card > -1);
      if( p_lose && !o_lose )
         lose_the_game(player);
      else if( !p_lose && o_lose )
         lose_the_game(1-player);
      else if( p_lose && o_lose )
         lose_the_game(ANYBODY);
      kill_card(player, card, KILL_DESTROY);
   }

   return generic_spell(player, card, event, GS_X_SPELL, NULL, NULL, 0, NULL);
}

Re: Drawing cards simultanously while not losing the game

PostPosted: 17 Aug 2020, 07:01
by FastEddie
Nice one :D. Much more elegant than my sledgehammer solution. I run a quick test and it worked insofar as the game ends in a draw when both players run out of cards (i.e. the legacy card does what I did with the mutex, but in a way that is much more in line with how the code works).

Unfortunately, Laboratory Maniac is not properly accounted for (remember that dude?). This is because your check works on the level of cards drawn (within Prosperity) but not on the "lower" level of lose_the_game. If we could have the same mechanism for lose_the_game it should work, i.e. suppress loosing the game while a flag is set, record the incoming events (who would have lost), and only when the flag is reset put those events together and see who lost or won (basically your if cascade but not within Prosperity but within lose_the_game).

Does this make sense to you?

Re: Drawing cards simultanously while not losing the game

PostPosted: 17 Aug 2020, 07:36
by drool66
It breaks down if both players have a LabMan. The way this works is for each card either player would draw from an empty library (still done in APNAP order), he will receive a trigger from both LabMan and the Prosperity legacy. He can choose either the trigger from the Prosperity legacy and continue, or choose the LabMan trigger and win. This implementation also accommodates other triggers that would replace a card draw - consider Notion Thief, for example. Still trying to figure out that LabMan thing though...

Re: Drawing cards simultanously while not losing the game

PostPosted: 17 Aug 2020, 07:46
by FastEddie
So being able to choose whether to win or loose is the issue... can you catch that somehow?

Re: Drawing cards simultanously while not losing the game

PostPosted: 17 Aug 2020, 07:50
by drool66
Working on it.

Re: Drawing cards simultanously while not losing the game

PostPosted: 17 Aug 2020, 12:36
by FastEddie
Alright. Btw, now I understand what you meant when you proposed this idea initially to me. Back then I had no clue what TRIGGER_REPLACE_CARD_DRAW is or how to handle it. Could as well have been a Chinese speciality only served in Shenzhen downtown :lol: . Joking aside, let me know if I can be of any assistance.