It is currently 16 Apr 2024, 07:21
   
Text Size

Drawing cards simultanously while not losing the game

Discuss Upcoming Releases, Coding New Cards, Etc.
PLEASE DO NOT REPORT BUGS HERE!

Moderators: BAgate, drool66, Aswan jaguar, gmzombie, stassy, CCGHQ Admins

Drawing cards simultanously while not losing the game

Postby FastEddie » 13 Jul 2020, 13:34

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?
Attachments
loseGameMutex.zip
(110.81 KiB) Downloaded 225 times
Last edited by FastEddie on 16 Aug 2020, 18:42, edited 1 time in total.
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 246
Joined: 24 Dec 2019, 10:59
Has thanked: 15 times
Been thanked: 19 times

Re: Drawing cards simultanously while not losing the game

Postby drool66 » 16 Aug 2020, 16:37

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);
}
User avatar
drool66
Programmer
 
Posts: 1163
Joined: 25 Nov 2010, 22:38
Has thanked: 186 times
Been thanked: 267 times

Re: Drawing cards simultanously while not losing the game

Postby FastEddie » 17 Aug 2020, 07:01

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?
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 246
Joined: 24 Dec 2019, 10:59
Has thanked: 15 times
Been thanked: 19 times

Re: Drawing cards simultanously while not losing the game

Postby drool66 » 17 Aug 2020, 07:36

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...
User avatar
drool66
Programmer
 
Posts: 1163
Joined: 25 Nov 2010, 22:38
Has thanked: 186 times
Been thanked: 267 times

Re: Drawing cards simultanously while not losing the game

Postby FastEddie » 17 Aug 2020, 07:46

So being able to choose whether to win or loose is the issue... can you catch that somehow?
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 246
Joined: 24 Dec 2019, 10:59
Has thanked: 15 times
Been thanked: 19 times

Re: Drawing cards simultanously while not losing the game

Postby drool66 » 17 Aug 2020, 07:50

Working on it.
User avatar
drool66
Programmer
 
Posts: 1163
Joined: 25 Nov 2010, 22:38
Has thanked: 186 times
Been thanked: 267 times

Re: Drawing cards simultanously while not losing the game

Postby FastEddie » 17 Aug 2020, 12:36

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.
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 246
Joined: 24 Dec 2019, 10:59
Has thanked: 15 times
Been thanked: 19 times


Return to Development

Who is online

Users browsing this forum: No registered users and 20 guests


Who is online

In total there are 20 users online :: 0 registered, 0 hidden and 20 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 20 guests

Login Form