[CODE] Match, Gauntlet, & AI Test modes

All code submission.
Locked
Psyringe
Posts: 1163
Joined: Mon Aug 31, 2009 10:53 am

[CODE] Match, Gauntlet, & AI Test modes

Post by Psyringe »

I'm currently trying to add some new playing modes to my personal copy of Wagic. These modes are mainly meant to aid AI development and AI deck construction, although I'll try to implement them in a way that they can be used by players as well, should that be desired and should the devs want to implement these feature into the official codebase.

Work is proceeding very slowly though (actually so slowly that I can't guarantee that I'll ever get anywhere with this), because ...
a) I've never programmed in C++ (or even C) before
b) I have no experience with VC 2008 or any other Microsoft IDE
c) I still have trouble to understand the current structure of the source code

In this situation, I'm prone to make beginner's mistakes. In order to prevent these, I'm creating this thread and telling people what I plan to do. I'll also try to keep it updated when I make progress or change plans. This way, the more experienced devs can hopefully warn me before I do anything stupid. Also, this way the devs can inform me about any plans *they* have which may help or impede my current plans. I'd like to implement these ideas as much in line with current official dev plans as possible, to a) make it easier to implement these features into official Wagic (if desired), and b) make it easier for *me* to adapt my code if any official changes affect the same parts of the program.

Planned Features:

Here's the current plan. I'd like to implement the following three features:

F1) new game mode: AI Test. This is similar to demo mode, however I'll be able to choose the two AI decks that play against each other.

F2) new opponent: Gauntlet. If "Gauntlet" is chosen as opponent, then the game will loop through all opponents available. It first starts a game with AI deck1 as opponent, immediately after this one it starts a game with AI deck2 as an opponent, etc.

F3) new game feature: matches over several games. After the two opponents for the match are chosen, I'd be able to specify how many games these two opponents will play. Wagic then loops over that many games between the two opponents and tallies the results.


Main Goal:

The main goal is to make the following workflow possible:
- I create or modify an AI deck, let's call it "Lazy"
- I click on "Play" and choose "AI Test" mode
- I choose the new "Lazy" deck as first player
- I choose "Gauntlet" as second player
- I choose a number of games per match (let's say 10)
- Wagic now plays 10 games of "Lazy" against the first AI deck (currently "Nightmare"). Afterwards, it plays 10 games of "Lazy" against second AI deck, and so on.
- Results are logged to a file,
- I can now check that file and assess the general performance of the deck (total wins vs losses), perhaps even specific weaknesses (e.g. has a high chance of losing against white decks).
- With this information, I can modify the AI deck again and repeat the above steps, until I have a deck that the AI can play well.

This will hopefully be helpful in AI deck development, and serve as a replacement for the (unfortunately largely missing) player feedback.


Current Status:

I'm reading through GameStateMenu.cpp and GameStateDuel.cpp in order to understand their structure, and to find out where the planned features need to be attached. I also found GameState.cpp, but it contains only the GameState::fillDeckMenu function, and currently I don't understand why this function is separate from the others.

Unless I find something in the code that speaks against this idea, I'll try to implement the "AI Test mode" (feature F1) first. I'm currently uncertain whether it'll be better to add this as a new mode, or to modify the existing "Demo" mode. The former might be easier to maintain if the "Demo mode" gets changed by the devs, while the latter might be easier to get working right now.

Any comments, ideas, warnings, help, etc. are welcome. :)
Psyringe
Posts: 1163
Joined: Mon Aug 31, 2009 10:53 am

Re: Match, Gauntlet, & AI Test modes

Post by Psyringe »

Update: After a while of searching, I have discovered two things:

1. VC 2008 has a nice debugging mode which allows me to step through the code. Yay me. It's not as useful as it could be though, since I find myself stepping through lots of code that just generates nice visual effects, only to then miss the branch that I was actually looking for. I guess this will get better when I have a better understanding of the code structure in general, but suggestions on how to handle this better are welcome nevertheless.

2. The game constructs the menus for player decks and AI decks at rather different places. The reason for this may have been efficiency - so far there existed a possibility to need the "choose human player deck" menu twice (in 2-player mode), but the "choose AI player deck" menu would only be needed once. Hence the opponentmenu currently gets constructed when player 1's deck is about to be determined, which means that this menu isn't prepared when I want it to choose decks for player 0. I'll move the construction of opponentmenu to the loop that already constructs the player's deckmenu.
wololo
Site Admin
Posts: 3728
Joined: Wed Oct 15, 2008 12:42 am
Location: Japan

Re: Match, Gauntlet, & AI Test modes

Post by wololo »

Psyringe wrote: I'll move the construction of opponentmenu to the loop that already constructs the player's deckmenu.
Note that the construction of the opponent's menu is currently extremely slow on the PSP. It has to open and read one file for each deck (to get its name and description) as well as computing statistics (to display hard/easy messages). It will need some caching mechanism at some point, but keep that in mind if you move the code somewhere else ;)
Psyringe
Posts: 1163
Joined: Mon Aug 31, 2009 10:53 am

Re: Match, Gauntlet, & AI Test modes

Post by Psyringe »

Thanks, that's good to know. :) So the menu for player 1 should definitely re-use the menu from player 0, if it can. That's important because the menus can't be identical (options like "Evil Twin" only make sense for player 1), so I have to find a way to use the menu player 0, then add some buttons to it, and then use it for player 1.

Status update: I get a feeling that the "minimal intrusive" approach I wanted to take isn't going to work. There are many implicit assumptions in the code, for example the current code assumes that if there is one human player, then it will always be player 0. It also assumes that the user can only ever choose an AI deck for player 1. I may have to decide to either add a hack on top of other hacks, or rewrite more than I intended to. Neither of the two is ideal, as both have a high risk of breaking as soon as there are any official changes to the same code.

On the bright side, my understanding of how the code works is definitely improving. :)
Psyringe
Posts: 1163
Joined: Mon Aug 31, 2009 10:53 am

Re: Match, Gauntlet, & AI Test modes

Post by Psyringe »

Status update:

I now managed to get feature F1 working on my version. As expected in my last post, this required more rewriting than I initially expected. I do think that the rewrites make sense though - for example, in my version the game now skips building the player's deckmenu when in Momir or random deck mode (previously the deckmenu would get constructed even though it wouldn't be needed). Also, the new structure allowed for easy integration of a feature requested by Blublub here, an option to choose a random player deck. I'm not sure how useful this is since many player will have unfinished decks in their deckmenu that they wouldn't want to be selected, but that doesn't mean we shouldn't include the feature for the people who want it - people who don't, simply won't use it.

Here's what I did so far:

- changed the program flow from this:
  1. build player deckmenu (if there is a human player in the duel)
  2. Check for game type. If game type is CLASSIC, then proceed with deck selection. Otherwise construct the decks for the current game type (Momir or Random) and go straight to "Play"
  3. choose deck for player 0
  4. display transition from player 0 to player 1
  5. choose deck for player 1
  6. display transition from player 1 to play mode
  7. play
to this:
  1. Check for game type. If game type is CLASSIC, then proceed with deck selection. Otherwise construct the decks for the current game type (Momir or Random) and go straight to "Play"
  2. Prepare the deckmenu. This step constructs the deckmenu as needed for the current player (be it a human or an AI). It also re-uses an existing deckmenu from the previous player (if it makes sense).
  3. Let the player choose a deck from the deckmenu prepared in (2)
  4. display transition to next state. If all players have their decks, go to "Play". Otherwise repeat steps (2) to (4) for next player.
  5. play
The new structure made it (imho) easier to manage the deckmenus, since there's now only one place in the code where the menus get constructed. This also means we can add menu items for "Random" or "Cancel" without having to duplicate the code, once for the player's deckmenu and once for the AI's deckmenu.

- I also removed the variable "opponentmenu". This variable made sense in the previous structure, but it's easier to have only one variable to check. (As a side-effect, this should make the structure usable even if more than two players were to choose decks - which definitely won't be implemented an time soon, but who knows what the future brings, and I like to keep code extensible.)

- I *kept* the variable "opponentMenuFont". I don't understand why it's there because it's set to mFont anyways, but this may be a feature that's intended to be implemented in the future. Any information on this would be welcome, as it's slightly awkward to have a variable called "opponent(...)" that's now not really related to opponents anymore ("aiMenuFont" may be more descriptive for the function it currently has in my code).


The design should be easily extensible for the following situations:
- having more than 2 players
- having more game types
- having the player's deckmenu and the AI deck menu read in once at program start (and update it if the player adds or removes decks). This could help performance on the PSP, because usually the Ai decks won't change while the application is running. This would require a method to "copy" a menu and one to "update" menu strings according to whatever statistics are to be displayed.


I can see three limitations in my current design, please inform me if these are problematic:
- Statistics are only calculated for player 1's menu, and only if player 0 was a human. The reason is that the menu for player 0 cannot have statistical information about previous matches (because the other player hasn't been determined yet). So, if player 0 is an AI, then its deckmenu won't have statistical info. Then, if player 1 is an AI too, it simply re-uses the menu of player 0. Adding statistics to the menu at that stage would require constructing the whole menu from scratch, which (according to wololo) is slow on the PSP. Hence I think it's the better option to keep the AI deckmenu without statistics in this situation. (I don't think many players would be interested in AI-vs-AI statistics anyway - the info would only be useful for AI deck creators, and these would want a more controlled environment for obtaining their data, which I hope to provide with features F2 and F3.)

- Some menu items (like "Random mode") are added to the end of the menu, i.e. they aren't very visible. This is due to fact that SimpleMenu currently only allows adding items via push_back. From a short look at the code, it seems easy to add a method that allows for addition of items via push_front. But I didn't want to intrude into the territory of the UI programmer, so I'm asking whether such an addition would be possible, and if yes, with whom I should consult in order to add it.

- The current implementation is a change to "Demo mode". Previously "Demo mode" ran continuously, now it stops after each game (for a new selection of decks). I'd like to include an option "stop after demo game", which defaults to "no". This way, Demo Mode would have the previous behavior (continuous matches) unless the player sets the option to "yes", in which case the game will return to the menu after each AI-vs.-AI-match. I've already added new options as a test, when I was learning to understand the code, and it seems pretty easy to do (good, extensible code architecture). However, the current implementation of Options seems to be a bit buggy (see bug reports in SVN thread and on Googlecode), so I didn't want to meddle with it at this stage.


During implementation, I also ran across a couple of things that I didn't understand. I'm listing them here, hoping that someone may answer the questions. :)

- In GameStateDuel.h there is a "#define CHOOSE_OPPONENT 7" that never gets used as far as I can see. What's its purpose?

- In the same file, the class GameStateDuel declares a variable "Player * mCurrentPlayer" that's also never used as far as I can see. Same question here.

- Why is "opponentMenuFont" kept as a distinct variable from mFont if it gets set to mFont anyway and never changes its value?

- Is there a specific reason why "menu" (the main menu in duel mode) uses numbers (12 and 13) to identify its components, whereas the other menus use constants specified in ENUMs?

- Is there a team policy on code layout? VC 2008 complains that many files have line endings in two different formats (Windows and Linux). Also, some lines seem to use Tab for initial whitespaces, while others use Space, which makes the code look a bit messy in my VC editor - or am I just lacking the right settings to display them neatly?

I'll do some more testing on my code, and post it here after cleaning it up a little. In the meantime, any answers to the above questions would be very welcome (especially the last one since it'll affect the cleanup).
wololo
Site Admin
Posts: 3728
Joined: Wed Oct 15, 2008 12:42 am
Location: Japan

Re: Match, Gauntlet, & AI Test modes

Post by wololo »

Psyringe wrote: - In GameStateDuel.h there is a "#define CHOOSE_OPPONENT 7" that never gets used as far as I can see. What's its purpose?

- In the same file, the class GameStateDuel declares a variable "Player * mCurrentPlayer" that's also never used as far as I can see. Same question here.
Probably old code. If searching for it yields no result, it can be deleted.


- Why is "opponentMenuFont" kept as a distinct variable from mFont if it gets set to mFont anyway and never changes its value?
mFont should be used, and opponentMenuFont deleted
- Is there a specific reason why "menu" (the main menu in duel mode) uses numbers (12 and 13) to identify its components, whereas the other menus use constants specified in ENUMs?
Old code that was created when I didn't know the existence of ENUMS, and blindly copied JGE's tutorials code. It should be replaced with ENUMS
- Is there a team policy on code layout? VC 2008 complains that many files have line endings in two different formats (Windows and Linux). Also, some lines seem to use Tab for initial whitespaces, while others use Space, which makes the code look a bit messy in my VC editor - or am I just lacking the right settings to display them neatly?
Glad you asked.
UNIX encoding
No tabs, use spaces. 1 tab = 2 spaces

A few people use the incorrect settings, it is difficult to enforce.
Psyringe
Posts: 1163
Joined: Mon Aug 31, 2009 10:53 am

Re: Match, Gauntlet, & AI Test modes

Post by Psyringe »

Status update:
  • completed implementation of F1
  • tested premade decks, Momir mode, random deck selection for humans and AI, new "Cancel" button in deckmenu, Evil Twin mode. No problems found.
  • removed old code as identified by wololo
  • turned numeric values for old game menu items into ENUMerated constants, as suggested by wololo. While I was at it, I turned the codes for "Evil Twin mode" etc. into ENUMerated constants too, imho that's more readable than the numeric values "-1", "0" etc. that were used before.
  • cleaned up code layout
  • added comments
The three limitations mentioned in my previous post still stand. Also, implementation of opponent selection in "Evil Twin" mode is slightly inefficient, and opponent selection in "Randomly select a deck" mode is duplicated in the code. Both are due to assumptions that AIPlayerFactory is making. I could change the code there as well, but didn't want to expand my changes to yet another file at this stage.

I'm adding the code (with my changes included) in the following two codeboxes, and I'll attach it to this post. Reviews would be *very* welcome since this is the first time I've ever coded anything serious in C++. In the meantime, I'll start working on features F2 and F3.

GameStateDuel.h

Code: Select all

#ifndef _GAME_STATE_DUEL_H_
#define _GAME_STATE_DUEL_H_


#include "../include/GameState.h"
#include "../include/SimpleMenu.h"
#include "../include/MTGDeck.h"
#include "../include/GameObserver.h"

#define NMB_PLAYERS 2  // Number of players in the game. Currently that's always 2.

#ifdef TESTSUITE
class TestSuite;
#endif
class Credits;


class GameStateDuel: public GameState, public JGuiListener
{
private:
#ifdef TESTSUITE
  TestSuite * testSuite;
#endif
  Credits * credits;
  int mGamePhase;
  int mCurrentPlayerNumber;  // is 0 for the first player (player 0), 1 for the next player, etc.
  Player * mPlayers[NMB_PLAYERS];
  MTGPlayerCards * deck[NMB_PLAYERS];
  GameObserver * game;
  SimpleMenu * deckmenu;  // menu for choosing a deck from a list of (human, AI or premade) decks
  SimpleMenu * menu;         // gets called by pressing START during the duel
  bool premadeDeck;          // is TRUE if the player chose a premade deck to play with
  int nmbDecks;                   // number of decks in the last constructed deckmenu
  JLBFont* mFont;

  void loadPlayer(int playerId, int decknb = 0, int isAI = 0);
  void loadPlayerMomir(int playerId, int isAI);
  void loadPlayerRandom(int playerId, int isAI, int mode);

public:
  GameStateDuel(GameApp* parent);
  virtual ~GameStateDuel();
#ifdef TESTSUITE
  void loadTestSuitePlayers();
#endif
  virtual void ButtonPressed(int ControllerId, int ControlId);
  virtual void Start();
  virtual void End();
  virtual void Update(float dt);
  virtual void Render();

};


#endif
GameStateDuel.cpp:

Code: Select all

#include "../include/config.h"
#include "../include/GameStateDuel.h"
#include "../include/GameOptions.h"
#include "../include/utils.h"
#include "../include/AIPlayer.h"
#include "../include/AIMomirPlayer.h"
#include "../include/PlayerData.h"
#include "../include/DeckStats.h"
#include "../include/MTGRules.h"
#include "../include/Credits.h"
#include "../include/Translate.h"

#ifdef TESTSUITE
#include "../include/TestSuiteAI.h"
#endif

enum ENUM_DUEL_STATE
{
  DUEL_STATE_START,
  DUEL_STATE_END,
  DUEL_STATE_CHECK_GAME_TYPE,
  DUEL_STATE_PREPARE_DECKMENU,
  DUEL_STATE_CHOOSE_DECK,
  DUEL_STATE_DECKMENU_IS_CLOSING,
  DUEL_STATE_CANCEL_DECKMENU,
  DUEL_STATE_ERROR_NO_DECK,
  DUEL_STATE_CANCEL,
  DUEL_STATE_PLAY,
  DUEL_STATE_BACK_TO_MAIN_MENU,
  DUEL_STATE_MENU,
  DUEL_STATE_ERROR
};

enum ENUM_DUEL_MENUS
{
  DUEL_MENU_GAME_MENU,
  DUEL_MENU_CHOOSE_DECK,
  DUEL_MENU_CHOOSE_OPPONENT
};

enum ENUM_DECKMENU_ITEMS
{
  DECKMENU_ITEM_SPECIAL = 33333,
  DECKMENU_ITEM_CREATE_DECK,
  DECKMENU_ITEM_RANDOM,
  DECKMENU_ITEM_EVIL_TWIN,
  DECKMENU_ITEM_GAUNTLET,
  DECKMENU_ITEM_CANCEL
};

enum ENUM_GAMEMENU_ITEMS
{
  GAMEMENU_ITEM_BACK_TO_MAIN_MENU,
  GAMEMENU_ITEM_CANCEL
};


GameStateDuel::GameStateDuel(GameApp* parent): GameState(parent) {
  for (int i = 0; i<NMB_PLAYERS; i ++){
    deck[i]=NULL;
    mPlayers[i]=NULL;
  }
  premadeDeck = false;
  nmbDecks = 0;
  game = NULL;
  deckmenu = NULL;
  menu = NULL;
#ifdef TESTSUITE
  testSuite = NULL;
#endif

  credits = NULL;
}

GameStateDuel::~GameStateDuel() {
  End();
}

void GameStateDuel::Start()
{
  JRenderer * renderer = JRenderer::GetInstance();
  renderer->EnableVSync(true);


#ifdef TESTSUITE
  SAFE_DELETE(testSuite);
  testSuite = NEW TestSuite(RESPATH"/test/_tests.txt",mParent->collection);
#endif

  mGamePhase = DUEL_STATE_CHECK_GAME_TYPE;
  mCurrentPlayerNumber = 0;
  credits = NEW Credits();
  mFont = resources.GetJLBFont(Constants::MENU_FONT);
  mFont->SetBase(0);

  // (PSY!) move this to "Play" state
  menu = NEW SimpleMenu(DUEL_MENU_GAME_MENU, this, mFont, SCREEN_WIDTH/2-100, 25);
  menu->Add(GAMEMENU_ITEM_BACK_TO_MAIN_MENU,"Back to main menu");
  menu->Add(GAMEMENU_ITEM_CANCEL, "Cancel");
}

void GameStateDuel::loadPlayerRandom(int playerId, int isAI, int mode){
  int color1 = 1 + rand() % 5;
  int color2 = 1 + rand() % 5;
  int color0 = Constants::MTG_COLOR_ARTIFACT;
  if (mode == GAME_TYPE_RANDOM1) color2 = color1;
  int colors[]={color1,color2,color0};
  int nbcolors = 3;

  string lands[] = {"forest", "forest", "island", "mountain", "swamp", "plains", "forest"};


  MTGDeck * tempDeck = NEW MTGDeck(mParent->collection);
  tempDeck->addRandomCards(9,0,0,-1,lands[color1].c_str());
  tempDeck->addRandomCards(9,0,0,-1,lands[color2].c_str());
  tempDeck->addRandomCards(1,0,0,'U',"land",colors,nbcolors);
  tempDeck->addRandomCards(1,0,0,'R',"land",colors,nbcolors);
  tempDeck->addRandomCards(12,0,0,-1,"creature",colors,nbcolors);
  tempDeck->addRandomCards(2,0,0,-1,"sorcery",colors,nbcolors);
  tempDeck->addRandomCards(2,0,0,-1,"enchantment",colors,nbcolors);
  tempDeck->addRandomCards(2,0,0,-1,"instant",colors,nbcolors);
  tempDeck->addRandomCards(2,0,0,-1,"artifact",colors,nbcolors);

  string deckFile = "random";
  string deckFileSmall = "random";

  deck[playerId] = NEW MTGPlayerCards(mParent->collection, tempDeck);
  if (!isAI) // Human Player
    mPlayers[playerId] = NEW HumanPlayer(deck[playerId], deckFile, deckFileSmall);
  else
    mPlayers[playerId] = NEW AIPlayerBaka(deck[playerId],deckFile, deckFileSmall, "");
  delete tempDeck;
}


void GameStateDuel::loadPlayerMomir(int playerId, int isAI){
  string deckFileSmall = "momir";
  char empty[] = "";
  MTGDeck * tempDeck = NEW MTGDeck(options.profileFile("momir.txt","",true).c_str(), mParent->collection);
  deck[playerId] = NEW MTGPlayerCards(mParent->collection, tempDeck);
  if (!isAI) // Human Player
    mPlayers[playerId] = NEW HumanPlayer(deck[playerId], options.profileFile("momir.txt","",true).c_str(), deckFileSmall);
  else
    mPlayers[playerId] = NEW AIMomirPlayer(deck[playerId], options.profileFile("momir.txt","",true).c_str(), deckFileSmall, empty);
  delete tempDeck;
}

void GameStateDuel::loadPlayer(int playerId, int decknb, int isAI){
  /* The following block wouldn't be necessary if AIPlayerFactory
     understood the DECKMENU_ITEM_ constants, but I didn't want to
     change yet another file. */
  if (decknb == DECKMENU_ITEM_RANDOM) {
    decknb = (rand() % nmbDecks) +1; // determine the randomly selected deck here, so that AIPlayerFactory doesn't need to check all the files again
  } else if (decknb == DECKMENU_ITEM_EVIL_TWIN) {
    decknb = -1;  // AIPlayerFactory expects the parameter to be -1 for "Evil Twin" mode
  }

  if (decknb){
    if (!isAI){ //Human Player
      char deckFile[255];
      if(premadeDeck)
        sprintf(deckFile, RESPATH"/player/premade/deck%i.txt",decknb);
      else
        sprintf(deckFile, "%s/deck%i.txt",options.profileFile().c_str(), decknb);
      char deckFileSmall[255];
      sprintf(deckFileSmall, "player_deck%i",decknb);
      MTGDeck * tempDeck = NEW MTGDeck(deckFile, mParent->collection);
      deck[playerId] = NEW MTGPlayerCards(mParent->collection,tempDeck);
      delete tempDeck;
      mPlayers[playerId] = NEW HumanPlayer(deck[playerId],deckFile, deckFileSmall);
    }else{ //AI Player, deck chosen by human player
      AIPlayerFactory playerCreator;
      Player * opponent = NULL;
      if (playerId == 1) opponent = mPlayers[0];
      mPlayers[playerId] = playerCreator.createAIPlayer(mParent->collection,opponent,decknb);
      deck[playerId] = mPlayers[playerId]->game;
    }
  }else{ // AI player, deck randomly selected (not determined yet)
    AIPlayerFactory playerCreator;
    Player * opponent = NULL;
    if (playerId == 1) opponent = mPlayers[0];
    mPlayers[playerId] = playerCreator.createAIPlayer(mParent->collection,opponent);
    deck[playerId] = mPlayers[playerId]->game;
  }
}

#ifdef TESTSUITE
void GameStateDuel::loadTestSuitePlayers(){
  if (!testSuite) return;
  for (int i = 0; i < 2; i++){
    SAFE_DELETE(mPlayers[i]);
    SAFE_DELETE(deck[i]);
    mPlayers[i] = NEW TestSuiteAI(testSuite, i);
    deck[i] = mPlayers[i]->game;
  }
  mParent->gameType = testSuite->gameType;
  SAFE_DELETE(game);
  GameObserver::Init(mPlayers, 2);
  game = GameObserver::GetInstance();
  game->startGame(0,0);
  if (mParent->gameType == GAME_TYPE_MOMIR){
    game->addObserver(NEW MTGMomirRule(-1, mParent->collection));
    for (int i = 0; i < 2; i++){
      game->players[i]->life+=4;
    }
  }
}
#endif

void GameStateDuel::End()
{
#if defined (WIN32) || defined (LINUX)
  OutputDebugString("Ending GamestateDuel\n");
#endif
  SAFE_DELETE(deckmenu);
  JRenderer::GetInstance()->EnableVSync(false);
  if (mPlayers[0] && mPlayers[1]) mPlayers[0]->End();  // (PSY!) What does this do?
  GameObserver::EndInstance();
  game = NULL;

  for (int i = 0; i < NMB_PLAYERS; i++){
    SAFE_DELETE(mPlayers[i]);
    SAFE_DELETE(deck[i]);
  }

  SAFE_DELETE(credits);
  SAFE_DELETE(menu);
#ifdef TESTSUITE
  SAFE_DELETE(testSuite);
#endif
}


void GameStateDuel::Update(float dt)
{
  switch (mGamePhase)
  {
  case DUEL_STATE_ERROR_NO_DECK:
    if (PSP_CTRL_CIRCLE == mEngine->ReadButton())
      mParent->SetNextState(GAME_STATE_DECK_VIEWER);
    break;

  /* The next state checks whether a special game mode (like Momir)
  was selected. If yes, the game builds the required decks, loads the
  players, and goes to "Play". If no, then the game proceeds to prepare
  deck selection. (PSY) */
  case DUEL_STATE_CHECK_GAME_TYPE:

    if (mParent->gameType == GAME_TYPE_MOMIR){
      for (int i = 0; i < NMB_PLAYERS; i++){
        int isAI = 1;
        if (mParent->players[i] ==  PLAYER_TYPE_HUMAN) isAI = 0;
        loadPlayerMomir(i, isAI);
      }
      mGamePhase = DUEL_STATE_PLAY;
    } else if (mParent->gameType == GAME_TYPE_RANDOM1 || mParent->gameType == GAME_TYPE_RANDOM2){
      for (int i = 0; i < NMB_PLAYERS; i++){
        int isAI = 1;
        if (mParent->players[i] ==  PLAYER_TYPE_HUMAN) isAI = 0;
        loadPlayerRandom(i, isAI, mParent->gameType);
      }
      mGamePhase = DUEL_STATE_PLAY;
    }
#ifdef TESTSUITE
    else if (mParent->players[1] ==  PLAYER_TYPE_TESTSUITE){
      if (testSuite && testSuite->loadNext()){

        loadTestSuitePlayers();

        mGamePhase = DUEL_STATE_PLAY;
        testSuite->initGame();
        char buf[4096];
        sprintf(buf, "nb cards in player2's graveyard : %i\n",mPlayers[1]->game->graveyard->nb_cards);
        LOG(buf);
      }else{
        if (!game){
          mGamePhase = DUEL_STATE_ERROR;
        }else{
          mGamePhase = DUEL_STATE_END;
        }
      }
    }
#endif
    else{  // no special game type found, so let's proceed:
      mGamePhase = DUEL_STATE_PREPARE_DECKMENU;
    }
    break;

  // The next state prepares the deck menu that's required for deck selection. (PSY)
  case DUEL_STATE_PREPARE_DECKMENU:
    {
      bool newMenuNeeded = true;
      if (mCurrentPlayerNumber > 0)
        /* We're not at the first player. Now we can safely test whether the current player is
        of the same type (human or AI) as the previous player. If yes, then there's no need to
        construct the whole menu again (which is slow on the PSP) (PSY) */
        if (mParent->players[mCurrentPlayerNumber] == mParent->players[mCurrentPlayerNumber-1])
          newMenuNeeded = false;

      if (newMenuNeeded) {
        SAFE_DELETE(deckmenu);
        deckmenu = NEW SimpleMenu(DUEL_MENU_CHOOSE_DECK, this, mFont, 35, 25, "Choose a Deck");
        nmbDecks = 0;

        if (mParent->players[mCurrentPlayerNumber] == PLAYER_TYPE_HUMAN) {
          nmbDecks = fillDeckMenu(deckmenu,options.profileFile());
          if (!nmbDecks) {
            /* Player is a human and has no decks. We want to give him the option
              to play with premade decks, or exit and create his own deck. (PSY) */
            deckmenu->Add(DECKMENU_ITEM_CREATE_DECK,"Create your Deck!","Highly recommended to get\nthe full Wagic experience!");
            premadeDeck = true;
            nmbDecks = fillDeckMenu(deckmenu,RESPATH"/player/premade");
          }
        } else {  // (PSY) current player is an AI
          if (mCurrentPlayerNumber == 0) {
            /* This AI occupies the slot of player 0. We load the AI decks, but
              don't calculate statistics, since the other player is not known yet. (PSY) */
            nmbDecks = fillDeckMenu(deckmenu,RESPATH"/ai/baka");
          } else {
            /* This AI occupies the slot of player 1 (or later). We load the AI
              decks and calculate statistics for games between this player and
              player 0. (PSY) */
            if (options[Options::EVILTWIN_MODE_UNLOCKED].number)
              deckmenu->Add(DECKMENU_ITEM_EVIL_TWIN,"Evil Twin", "Can you play against yourself?");
            nmbDecks = fillDeckMenu(deckmenu,RESPATH"/ai/baka", "ai_baka", mPlayers[0]);
          }
        }
        if (nmbDecks > 1)
          // (PSY!) ToDo: push this item to the start of the menu instead of appending it to the end
          deckmenu->Add(DECKMENU_ITEM_RANDOM,"Random","Randomly selects a deck from the list.");
        deckmenu->Add(DECKMENU_ITEM_CANCEL,"Cancel","Go back to main menu.");
      }
      mGamePhase = DUEL_STATE_CHOOSE_DECK;
    }
    break;

  // The next state waits until the player makes his selection.
  case DUEL_STATE_CHOOSE_DECK:
    deckmenu->Update(dt);
    break;

  /* The next state waits until the menu has completely closed (animation finished),
     then it increases the player number and either proceeds to prepare the menu
     for the next player, or proceeds to "Play" if there is no next player. (PSY) */
  case DUEL_STATE_DECKMENU_IS_CLOSING:
    if (deckmenu->closed) {   // Is the closing animation finished?
      mCurrentPlayerNumber++;
      if (mCurrentPlayerNumber == NMB_PLAYERS) {  // Does every player have hs deck?
        // Yes - clean up and proceed to "Play"
        SAFE_DELETE(deckmenu);
        mGamePhase = DUEL_STATE_PLAY;
      } else
        // No - prepare dck selection for next player
        mGamePhase = DUEL_STATE_PREPARE_DECKMENU;
    } else deckmenu->Update(dt);
    break;

  // The next state occurs when the player selected "Cancel" in a deck menu:
  case DUEL_STATE_CANCEL_DECKMENU:
    deckmenu->Update(dt);
    if (deckmenu->closed)  // wait until menu has closed, then go back to main menu
      mParent->SetNextState(GAME_STATE_MENU);
    break;

  case DUEL_STATE_PLAY:
    //Stop the music before starting the game
    if (GameApp::music){
      JSoundSystem::GetInstance()->StopMusic(GameApp::music);
      SAFE_DELETE(GameApp::music);
    }
    if (!game){
      GameObserver::Init(mPlayers, NMB_PLAYERS);
      game = GameObserver::GetInstance();
      game->startGame();
      if (mParent->gameType == GAME_TYPE_MOMIR){
        game->addObserver(NEW MTGMomirRule(-1, mParent->collection));
        for (int i = 0; i < NMB_PLAYERS; i++){
          game->players[i]->life+=4;
        }
      }
    }
    //      mParent->effect->UpdateSmall(dt);
    game->Update(dt);
    if (game->gameOver){
      credits->compute(mPlayers[0],mPlayers[1], mParent);
      mGamePhase = DUEL_STATE_END;
#ifdef TESTSUITE
      if (mParent->players[1] == PLAYER_TYPE_TESTSUITE){
        if (testSuite->loadNext()){
          loadTestSuitePlayers();
          mGamePhase = DUEL_STATE_PLAY;
          testSuite->initGame();
        }else{
          mGamePhase = DUEL_STATE_END;
        }
      }else
#endif
        if (mParent->players[0] == PLAYER_TYPE_CPU && mParent->players[1] == PLAYER_TYPE_CPU){
          End();
          Start();
        }
        mFont->SetColor(ARGB(255,255,255,255));
    }
    if (mEngine->GetButtonClick(PSP_CTRL_START)){
      mGamePhase = DUEL_STATE_MENU;
    }
    break;
  
  // The next state occurs when the Game Menu is being displayed:
  case DUEL_STATE_MENU:
    //      mParent->effect->UpdateSmall(dt);
    menu->Update(dt);
    break;

  // The next state occurs when the player selected "Cancel" in the Game Menu:
  case DUEL_STATE_CANCEL:
    //      mParent->effect->UpdateSmall(dt);
    menu->Update(dt);
    if (menu->closed)  // wait until menu has closed, then proceed with Play
      mGamePhase = DUEL_STATE_PLAY;
    break;

  // The next state occurs when the player selected "Back to main menu" in the Game Menu:
  case DUEL_STATE_BACK_TO_MAIN_MENU:
    //       mParent->effect->UpdateSmall(dt);
    menu->Update(dt);
    if (menu->closed)
      mParent->SetNextState(GAME_STATE_MENU);
    break;

  default: // (PSY!) What is this for?
    if (PSP_CTRL_CIRCLE == mEngine->ReadButton()){
      mParent->SetNextState(GAME_STATE_MENU);
    }
  }
}


void GameStateDuel::Render()
{
  //Erase
  LOG("Start Render\n");
  JRenderer::GetInstance()->ClearScreen(ARGB(0,0,0,0));

  if (game)
    game->Render();
  switch (mGamePhase)
  {
  case DUEL_STATE_START: // (PSY!) Add start screen here?

    break;
  case DUEL_STATE_END:
    {
      JRenderer * r = JRenderer::GetInstance();
      r->ClearScreen(ARGB(200,0,0,0));
      credits->Render();
#ifdef TESTSUITE
      if (mParent->players[1] == PLAYER_TYPE_TESTSUITE){
        r->ClearScreen(ARGB(255,0,0,0));
        char buf[4096];
        int nbFailed = testSuite->nbFailed;
        int nbTests = testSuite->nbTests;
        if (!nbFailed){
          sprintf(buf, "All %i tests successful!", nbTests);
        }else{
          sprintf(buf, "%i tests out of %i FAILED!", nbFailed, nbTests);
        }

        mFont->DrawString(buf,0,SCREEN_HEIGHT/2);
      }
#endif
      break;
    }
  case DUEL_STATE_ERROR:
    {
      JRenderer * r = JRenderer::GetInstance();
      r->ClearScreen(ARGB(200,0,0,0));
      mFont->DrawString(_("AN ERROR OCCURED, CHECK FILE NAMES").c_str(),0,SCREEN_HEIGHT/2);
      break;
    }

  case DUEL_STATE_CHECK_GAME_TYPE:
  case DUEL_STATE_PREPARE_DECKMENU:
  case DUEL_STATE_CHOOSE_DECK:
  case DUEL_STATE_DECKMENU_IS_CLOSING:
  case DUEL_STATE_CANCEL_DECKMENU:
    if (mParent->gameType != GAME_TYPE_CLASSIC){
      mFont->DrawString(_("LOADING DECKS").c_str(),0,SCREEN_HEIGHT/2);
    }else if (deckmenu){
      deckmenu->Render();
    }

    break;
  case DUEL_STATE_ERROR_NO_DECK:
    mFont->DrawString(_("NO DECK AVAILABLE,").c_str(),0,SCREEN_HEIGHT/2);
    mFont->DrawString(_("PRESS CIRCLE TO GO TO THE DECK EDITOR!").c_str(),0,SCREEN_HEIGHT/2 + 20);
    break;
  case DUEL_STATE_MENU:
  case DUEL_STATE_CANCEL:
  case DUEL_STATE_BACK_TO_MAIN_MENU:
    menu->Render();
  }
  LOG("End Render\n");
}

void GameStateDuel::ButtonPressed(int controllerId, int controlId)
// controllerId is the ID of the menu in which the button was pressed.
// controlId is the ID of the menu item that has been selected.
{
  switch (controllerId){
    case DUEL_MENU_CHOOSE_DECK:
      {
        if (controlId == DECKMENU_ITEM_CREATE_DECK){
          mParent->SetNextState(GAME_STATE_DECK_VIEWER);
          return;
        } else if (controlId == DECKMENU_ITEM_CANCEL){
          deckmenu->Close();
          mGamePhase = DUEL_STATE_CANCEL_DECKMENU;
          return;
        }
        loadPlayer(mCurrentPlayerNumber,controlId,(mParent->players[mCurrentPlayerNumber] == PLAYER_TYPE_CPU));
        deckmenu->Close();
        mGamePhase = DUEL_STATE_DECKMENU_IS_CLOSING;
        break;
      }
    default:  // We're in the Game Menu
      {
        switch (controlId)
        {
        case GAMEMENU_ITEM_BACK_TO_MAIN_MENU:
          menu->Close();
          mGamePhase = DUEL_STATE_BACK_TO_MAIN_MENU;
          break;
        case GAMEMENU_ITEM_CANCEL:
          menu->Close();
          mGamePhase = DUEL_STATE_CANCEL;
          break;
        }
      }
  }
}
Edit: removed outdated file, check last posts of the thread for a more recent version.
Psyringe
Posts: 1163
Joined: Mon Aug 31, 2009 10:53 am

Re: Match, Gauntlet, & AI Test modes

Post by Psyringe »

Status update:

Feature F2 (Gauntlet mode) is now implemented and working. I'm not satisfied though. The architecture that I originally planned wasn't possible, and I had to resort to some ugly hacks to make it work. Which is a bit of a pity since the Gauntlet mode actually works how I wanted it to. I can set any AI player to Gauntlet mode, and the game will then loop over all available AI decks for that player. I can even set both AI players to "Gauntlet", which means that every AI deck will play against every other AI deck (twice, once as first player and once as second player).

I also learned that it is not a good idea to give an optional parameter to a method like start(), which has lots of identically named functions floating around. The game ended up calling the wrong function when the parameter wasn't specified in the call, and I spent half of the evening tracking the problem down. Ah well, we live and learn. I guess that's one mistake I won't make again.

I'll now try to implement feature F3 as well, and then see whether the code can be tidied up to something worthy to commit (after the next release). Feedback, suggestions, and code reviews are still welcome btw. :)
abrasax
Posts: 976
Joined: Wed Oct 15, 2008 7:46 am
Location: Switzerland

Re: Match, Gauntlet, & AI Test modes

Post by abrasax »

Hi,

I downloaded your code and installed... was looking for gauntlet and realized it was not there.... would enjoy to test it if it's ready. Would I need to unlock it first ? would it be a separate option (as momir) or do I need to choose 2 player or 1 player first ?

I was al little bit confuse as random is now at the end of the list but you get used to it, the "cancel" boutton is a very nice addition.

Seing also the new "comments" you added I was thinking we could add more information to the selection screen... It could also be "graphically" improved, there is a lot of empty space that could be used to display statistics (% of victory, game played, etc...) also at the moment the mention [easy] or [hard] is most of the time "off screen" it could also be put in a separate part of the screen together with the stats... finally avatar could be displayed (just for a nice addition)...welll all in all it could be made using a background picture and we could also get rid of the (in my opinion not so beautiful) spade like frame and optimize space.

Also something like:

[The extension jpg has been deactivated and can no longer be displayed.]


I know it has nothing to do with the current change you are making but when you're at it .... :)

as for the random mode (player deck) I did not test it yet because I got some "test" deck in my player deck (e.g. a deck with all double land from RV that I use to test some cards.., or also a "garbage" deck where I put all cards that I found that could have an issue...

Well, keep the good job... It goes definitely in some cooool direction ;)

Grü

Abra

EDIT:Added a quick screen of what I was thinking to...
We need your Help !!!
New to wagic ? Be sure to check the following :

Bug report: Bug reporting
Help us: Add cards & Compiling.
Customize: Themes FAQ, All images thread, Abra's Mediafire folder
Psyringe
Posts: 1163
Joined: Mon Aug 31, 2009 10:53 am

Re: Match, Gauntlet, & AI Test modes

Post by Psyringe »

Yay, feedback! :)
abrasax wrote:I downloaded your code and installed... was looking for gauntlet and realized it was not there.... would enjoy to test it if it's ready. Would I need to unlock it first ? would it be a separate option (as momir) or do I need to choose 2 player or 1 player first ?
I'll attach the new code (with Gauntlet integrated) to this post. Currently you don't need to unlock it, it's just another special option in the deckmenu. It's always present when "Random" is present (that is, whenever there are at least two decks to choose from).

I could make it an unlockable game feature (and would gladly do so if people want me to :) ), but that requires changes to some more files, and I'm trying to limit my changes to one file as long as I don't know how well they'll be received. This way, even if they don't meet with approvement, it'll be easier for me to keep them in my private version of the game, even if the official code changes in other places. (That approach has produced some inelegant code though, since I had to find ways to code around problems that I could have solved easier by changing other files too. Coding would definitely be easier if I *didn't* worry so much about limiting my changes to one file, but I'd want a green light from an "official" side before I change that approach. I prefer to be cautious here until someone tells me I don't have to. ;) ).

"Gauntlet" is currently integrated mainly as an AI Test feature. If I'd implement it as a player mode too, I'd make some adjustments. For example, currently the Gauntlet does not end when a game is lost. For a Gauntlet *player* mode, this would be better - the player could then get increasingly large rewards the longer he manages to stay alive in the gauntlet (i.e., the more consecutive victories he can pull off). It wouldn't be too hard to adapt the gauntlet mode for the player.
abrasax wrote: Seing also the new "comments" you added I was thinking we could add more information to the selection screen... It could also be "graphically" improved, there is a lot of empty space that could be used to display statistics (% of victory, game played, etc...) also at the moment the mention [easy] or [hard] is most of the time "off screen" it could also be put in a separate part of the screen together with the stats... finally avatar could be displayed (just for a nice addition)...welll all in all it could be made using a background picture and we could also get rid of the (in my opinion not so beautiful) spade like frame and optimize space.
I already planned to add some basic statistics info to the deckmenus - adding info to the displayed deck description like this: "With your selected deck, you have played 64 matches against 'Elfball'. You have won 16 of these (25%)". It seemed like a quick-and-easy addition since I was working on the deckmenus anyway, but it turned out that I need to make additions to DeckStats.cpp if I want to have anything more than "percentage of victories". That was out of the scope for my current project, so I postponed that change. However, I already talked with Daddy32 (who'll need additional statistics functions as well for his planned update to the deck editor statistics), and I think one of us is going to add the necessary functions to DeckStats.cpp, sooner or later. It's just not my current focus right now.

Graphical enhancement of the menus is something I didn't plan (or would be especially talented) to do. For me, the current menu graphics are good enough, I'd be more interested in *functional* enhancements to the menu system, like functions to add a button to the top of the menu (for the "Random" button), functions to query and update single menu elements (instead of having to reconstruct the whole menu), etc.

Regarding the display of opponent's avatars and even more statistical information: It's difficult to integrate that into the current menu system, but I planned to do something similar when implementing feature F3 (matches that span more than one game). I'd like to do a screen that displays the two players, their avatars, current standings (i.e. who won how many games in the current match), the results of the past matches between the two players, and some more statistical tidbits (e.g. total value of the current deck). This screen would be displayed before the start of the match, and after the end of each game. It should also be possible to call it at any time from the "Game menu" (the one that currently has only the options "back to main menu" and "cancel", I'd like to add "look at current standings" to that, with better wording of course). I can't tell how much of this will come to pass, but I'm looking into it right now.

Here's the download for "Gauntlet mode", check it out and tell me what you think. :)

Edit: Outdated download removed, check either first or last post(s) of thios thread for a more recent version.
Last edited by Psyringe on Wed Oct 14, 2009 10:14 am, edited 1 time in total.
Locked