Esempio n. 1
0
void PGNReadFromFile (const char *file)
/****************************************************************************
 *
 *  To read a game from a PGN file.
 *
 ****************************************************************************/
{
   FILE *fp;
   char s[100], c, wmv[8], bmv[8];
   int moveno;
   leaf *p;

   fp = fopen (file, "r");
   if (fp == NULL)
   {
      printf ("Cannot open file %s\n", file);
      return;
   }

   /* Skip all the tags */
   do
   {
      if ((c = fgetc (fp)) == '[')
         fgets (s, 100, fp);
   } while (c == '[');
   ungetc (c, fp);

   InitVars ();
   while (!feof (fp))
   {
      c = fgetc(fp);
      if (c == '*') break;
      ungetc (c, fp);
      fscanf (fp, "%d. %7s %7s ", &moveno, wmv, bmv);
      p = ValidateMove (wmv);
      if (!p)
      {
	 printf ("Illegal move %d. %s\n", moveno, wmv);
	 break;
      }
      MakeMove (white, &p->move);
      strcpy (Game[GameCnt].SANmv, wmv);
      if (*bmv == '*' ) break;
      p = ValidateMove (bmv);
      if (!p)
      {
	 printf ("Illegal move %d. ... %s\n", moveno, bmv);
	 break;
      }
      MakeMove (black, &p->move);
      strcpy (Game[GameCnt].SANmv, bmv);
   }
   printf ("%d\n", GameCnt);
   fclose (fp);
   ShowBoard ();
   TTClear ();
   return;
}
Esempio n. 2
0
void test_validate_move()
{
	printf("chess-test: testing validate move (a5)...\n");
	leaf *result;
	result = ValidateMove("a5");	// invalid move returns null
	if (NULL != result)
		printf("chess-test: result was: %d\n", result->move);
	else
		printf("chess-test: result was NULL\n");
}
Esempio n. 3
0
File: engine.c Progetto: adua/chess
/*
 * Extracts a command from the engine input buffer.
 * 
 * The command is removed from the buffer.
 * If the command is a move, the move is made.
 */
void NextEngineCmd( void )
{
  char engineinput[BUF_SIZE]="";
  char enginemovestr[BUF_SIZE]="";
  leaf* enginemove;
  
  if ( strlen( engineinputbuf ) > 0 ) {
    if ( GetNextLine( engineinputbuf, engineinput ) > 0 ) {
      dbg_printf("< ENGINE: %s\n", engineinput);
      if ( strncmp( engineinput, "move", 4 ) == 0 ) {
        /* Input from engine is a move */
        sscanf( engineinput, "move %s", enginemovestr );
        enginemove = ValidateMove( enginemovestr );
        if ( enginemove == (leaf *) NULL ) {
          dbg_printf( "Bad move from engine\n" );
        } else {
          dbg_printf( "Engine move: <%s> (%d,%d)\n", enginemovestr,
                      (enginemove!=NULL ? enginemove->move : -1), 
                      (enginemove!=NULL ? enginemove->score : -1) );
          SANMove (enginemove->move, 1);
          MakeMove( board.side, &(enginemove->move) );
          strcpy (Game[GameCnt].SANmv, SANmv);
	  if ( !(flags & XBOARD) ) {
		  //ShowBoard();
                  dbg_printf("USER <: My move is : %s\n", SANmv);
		  //printf( "\nMy move is : %s\n", SANmv );
		  printf( "\n<%i:%i>\n", FROMSQ(enginemove->move), TOSQ(enginemove->move) );
                  fflush( stdout );
	  } else {
                  dbg_printf("USER <: %d. ... %s\n", GameCnt/2 + 1, enginemovestr );
                  printf ("%d. ... %s\n", GameCnt/2 + 1, enginemovestr );
                  fflush( stdout );
                  dbg_printf("USER <: My move is : %s\n", enginemovestr);
		  printf( "My move is : %s\n", enginemovestr );
                  fflush( stdout );
          }
          RealGameCnt = GameCnt;
          showprompt = 1;
          /* Check if the color must be changed, e.g. after a go command. */
          if ( changeColor ) {
            RealGameCnt = GameCnt;
            RealSide = board.side;
          }
        }
      } else {
        dbg_printf( "USER <: %s\n",engineinput );
        printf( "%s", engineinput );
        if ( flags & XBOARD ) {
          fflush( stdout );
        }
      }
    }
  }
}
Esempio n. 4
0
void BookPGNReadFromFile (const char *file)
/****************************************************************************
 *
 *  To read a game from a PGN file and store out the hash entries to book.
 *
 ****************************************************************************/
{
   FILE *fp;
   char s[100], wmv[8], bmv[8];
   int c;
   unsigned int i;
   char header[2000];
   int moveno, result, ngames = 0;
   leaf *p;
   time_t t1, t2;
   double et;
   int examinecolor, playerfound[2];

/* Only players in the table below are permitted into the opening book 
   from the PGN files. Please expand the table as needed. Generally,
   I would recommend only acknowledged GM's and IM's and oneself, but
   because of the self-changing nature of the book, anything inferior
   will eventually be eliminated through automatic play as long as
   you feed the games the program plays back to itself with "book add pgnfile"
*/
/* XXX: Is it really a good idea to have a list like this hardcoded? */

const char *const player[] = {
  "Alekhine",
  "Adams",
  "Anand",
  "Anderssen",
  "Andersson",
  "Aronin",
  "Averbakh",
  "Balashov",
  "Beliavsky",
  "Benko",
  "Bernstein",
  "Bird",
  "Bogoljubow",
  "Bolbochan",
  "Boleslavsky",
  "Byrne",
  "Botvinnik",
  "Bronstein",
  "Browne",
  "Capablanca",
  "Chigorin",
  "Christiansen",
  "De Firmian",
  "Deep Blue",
  "Deep Thought",
  "Donner",
  "Dreev",
  "Duras",
  "Euwe",
  "Evans",
  "Filip",
  "Fine",
  "Fischer",
  "Flohr",
  "Furman",
  "Gelfand",
  "Geller",
  "Gereben",
  "Glek",
  "Gligoric",
  "GNU",
  "Golombek",
  "Gruenfeld",
  "Guimard",
  "Hodgson",
  "Ivanchuk",
  "Ivkov",
  "Janowsky",
  "Kamsky",
  "Kan",
  "Karpov",
  "Kasparov",
  "Keres",
  "Korchnoi",
  "Kortschnoj",
  "Kotov",
  "Kramnik",
  "Kupreich",
  "Lasker",
  "Lautier",
  "Letelier",
  "Lilienthal",
  "Ljubojevic",
  "Marshall",
  "Maroczy",
  "Mieses",
  "Miles",
  "Morphy",
  "Mueller",     /* Every other German has this name... */
  "Nimzowitsch",
  "Nunn",
  "Opocensky",
  "Pachman",
  "Petrosian",
  "Piket",
  "Pilnik",
  "Pirc",
  "Polgar",
  "Portisch",
  "Psakhis",
  "Ragozin",
  "Reshevsky",
  "Reti",
  "Romanish",
  "Rubinstein",
  "Saemisch",
  "Seirawan",
  "Shirov",
  "Short",
  "Silman",
  "Smyslov",
  "Sokolsky",
  "Spassky",
  "Sveshnikov",
  "Stahlberg",
  "Steinitz",
  "Tal",
  "Tarjan",
  "Tartakower",
  "Timman",
  "Topalov",
  "Torre",
  "Vidmar"

  };

   et = 0.0;
   t1 = time(NULL);
   result = -1;
   fp = fopen (file, "r");
   if (fp == NULL)
   {
     fprintf(stderr, "Cannot open file %s: %s\n", 
	     file, strerror(errno));
     return;
   }

   /* Maybe add some more clever error handling later */
   if (BookBuilderOpen() != BOOK_SUCCESS)
     return;
   newpos = existpos = 0;


 nextgame:

   header[0] = 0;
   InitVars ();
   NewPosition ();
   CLEAR (flags, MANUAL);
   CLEAR (flags, THINK);
   myrating = opprating = 0;

   playerfound[black] = playerfound[white] = 0;

   /* Skip all the tags */
   /*
    * XXX: This has two problems: 1) Leading whitespace leads to
    * undefined stuff in s, and completely confuses the program.
    * 2) If there is a game between two players in the list, only
    * the moves of the white player will be added.
    */

   /* Skip whitespace */
   while ((c=fgetc(fp)) != EOF) {
     if (c != ' ' && c != '\t' && c != '\n') {
       ungetc(c, fp);
       break;
     }
   }
   
   while ((c=fgetc(fp)) == '[') {
     ungetc(c, fp);
     fgets(s, 100, fp);
     strcat(header,s);
     if (strncmp(s+1, "White ",6) == 0) {
       examinecolor = white;
       ngames++;
     } else if (strncmp(s+1, "Black ",6) == 0) {
       examinecolor = black;
     } else if (strncmp(s+1, "Result", 6) == 0) {
       if (strncmp(s+7," \"1-0",5) == 0) {
	 result = R_WHITE_WINS;
       } else if (strncmp(s+7," \"0-1",5) == 0) {
	 result = R_BLACK_WINS;
       } else if (strncmp(s+7," \"1/2-1/2",9) == 0) {
	 result = R_DRAW;
       } else {
	 result = R_NORESULT;
       }
       continue;
     } else continue;
     /*
      * We get only here if White or Black matched, it is ugly but it 
      * works. Attention: If at some point we want to put this array
      * of authorized players in some external file, we have to keep
      * track of the size of that array in some other way.
      */
     for (i = 0; i < (sizeof(player) / sizeof(*player)); i++) {
       if (strstr(s+7, player[i]) != NULL) {
	 playerfound[examinecolor] = 1;
       }
     }
   }
   ungetc(c, fp);
   while (1) {
     if (fscanf(fp, "%d. %7s ", &moveno, wmv) < 2) break;

     if (wmv[0] == '1' || wmv[0] == '[' || wmv[0] == '*'
	 || strcmp(wmv, "0-1") == 0) break;
     
     p = ValidateMove (wmv);
     if (!p) {
       puts(header);
       ShowBoard();
       printf ("Illegal move %d. %s\n", moveno, wmv);
       break;
     }
     MakeMove (white, &p->move);
     if (playerfound[white]) {
       if (BookBuilder (result, white) != BOOK_SUCCESS) break;
     }
     strcpy (Game[GameCnt].SANmv, wmv);
     
     if (fscanf(fp, "%7s ", bmv) < 1) break;
     if (bmv[0] == '1' || bmv[0] == '[' || bmv[0] == '*'
	 || strcmp(bmv, "0-1")  == 0) break;

     p = ValidateMove (bmv);
     if (!p) {
       puts (header);
       ShowBoard();
       printf ("Illegal move %d. ... %s\n", moveno, bmv);
       break;
     }
     MakeMove (black, &p->move);
     if (playerfound[black] ) {
       if (BookBuilder (result, black) != BOOK_SUCCESS) break;
     }
     strcpy (Game[GameCnt].SANmv, bmv);
   }

   /* Read to end of game but don't parse */
   while (!feof(fp)) {
     fgets(s,100,fp);
     if (s[0] == '\n') break;
   }
   /* printf ("%d(%d)\n", GameCnt,BOOKDEPTH); */
   if (!feof(fp)) {
     if (ngames % 10 == 0) printf("%d\r",ngames);
     fflush(stdout);
     goto nextgame;
   }

   fclose (fp);
   if (BookBuilderClose() != BOOK_SUCCESS) {
     perror("Error writing opening book");
   }

   /* Reset the board otherwise we leave the last position in the book
      on the board. */
   header[0] = 0;
   InitVars ();
   NewPosition ();
   CLEAR (flags, MANUAL);
   CLEAR (flags, THINK);
   myrating = opprating = 0;

   t2 = time(NULL);
   et += difftime(t2, t1);
   putchar('\n');

   /* Handle divide-by-zero problem */
   if (et < 0.5) { et = 1.0; };

   printf("Time = %.0f seconds\n", et);
   printf("Games compiled: %d\n",ngames);
   printf("Games per second: %f\n",ngames/et);
   printf("Positions scanned: %d\n",newpos+existpos);
   printf("Positions per second: %f\n",(newpos+existpos)/et);
   printf("New & unique added: %d positions\n",newpos);
   printf("Duplicates not added: %d positions\n",existpos);
}
Esempio n. 5
0
/*******************************************************************************************************
* GANE ENGINE - handles the looping of the game
*******************************************************************************************************/
void GameEngine()
{
	// user input variables
	const int userInputLimit = 11;
	char userInput[userInputLimit] = {};
	bool humanFinished = false;
	bool moveHuman = false;
	StringOO humanMove = "move south";
	int loopCount = 0;

	// location variables
	const int locationNum = 11;
	Location *Locations[locationNum] = {};
	int prevLocation = 0;
	int newLocation = 0;
	int finishLocation = locationNum - 1;

	// human player variables
	int humanLoc = 0;
	StringOO name = "Bruce";
	int strength = 10;
	int health = 100;
	int luck = 10;
	int intellegence = 10;

	// enemy variables
	const int enemyNum = 5;
	Enemy * Enemies[enemyNum] = {};

	// challenges variables
	const int challengeNum = 16;
	Challenge *Challenges[challengeNum] = {};

	// initialise Locations
	InitLocations(Locations);

	// initialise human
	Human Human(humanLoc, name, strength, health, luck, intellegence);

	// initialise Enemies
	InitEnemies(Enemies, enemyNum);

	// initialise Challenges
	InitChallenges(Challenges);
	//AllocateChallenges(Locations, locationNum);
	AllocateChallengesManually(Locations, locationNum);
	AddEnemyToChallenge(Challenges, enemyNum, challengeNum);

	// initial splash screen
	SetConsole();
	StartText(Human);

	// Game loop
	while (!humanFinished)
	{
		// print out details of room
		std::cout << Locations[Human.GetLocation()]->GetDescription() << std::endl;
		while (!moveHuman)
		{
			// get user to enter move
			std::cout << "+ ";
			std::cin.getline(userInput, userInputLimit);
			std::cout << std::endl;
			humanMove = userInput;
			if (loopCount == 5)
			{
				std::cout << "\n     ENOUGH ALREADY...YOUR MOVING SOUTH!! \n\n";
				//bored of guesses
				humanMove = "move south";
				loopCount = 0;
			}
			// determine the new location
			newLocation = PlayerMove(Human, humanMove, Locations[Human.GetLocation()]->GetAjacentLoc());

			// validate users move and if validate change their location
			if (ValidateMove(newLocation, prevLocation))
			{
				// set challenge user has to perform before progrogressing to the next room
				Human.SetChallenge(Locations[Human.GetLocation()]->GetPathChallenge(Human.GetHeading()));
				// challenge User
				Challenge *ChallengePtr = Challenges[Human.GetChallenge()];
				ApplyChallenge(ChallengePtr, Human, Enemies);
				// move user to new location
				prevLocation = Human.GetLocation();
				Human.SetPrevHeading(Human.GetHeading());
				Human.SetLocation(newLocation);
				moveHuman = true;
				loopCount = 0;
				std::cout << "\n***----------------------------*  pop!  *-------------------------------------**\n\n";
			}
			else
			{
				// invalid move try again
				moveHuman = false;
				loopCount++;
			}
		}
		// reset move
		moveHuman = false;
		//check if user is in the finish location
		if (Human.GetLocation() == finishLocation)
		{
			humanFinished = true;
			std::cout << Locations[Human.GetLocation()]->GetDescription() << std::endl;
			FinishText(Human);
		}

	}
	//DestroyEnemies(Enemies, enemyNum);
	for (int i = 0; i < enemyNum; i++)
	{
		delete Enemies[i];
	}
	//DestroyChallenges(Challenges, challengeNum);
	for (int i = 0; i < challengeNum; i++)
	{
		delete Challenges[i];
	}
	//DestroyLocations(Locations, locationNum);
	for (int i = 0; i < locationNum; i++)
	{
		delete Locations[i];
	}

}
Esempio n. 6
0
void InputCmd ()
/*************************************************************************
 *
 *  This is the main user command interface driver.
 *
 *************************************************************************/
{
   const char *color[2] = { "White", "Black" };
   int suffix;
   int i;
   leaf *ptr; 
   int ncmds;
   char *x,*trim;

   CLEAR (flags, THINK);
   memset(userinput,0,sizeof(userinput));
   memset(cmd,0,sizeof(cmd));
#ifndef HAVE_LIBREADLINE /* Why is this necessary anyway? */
   memset(inputstr,0,sizeof(inputstr));
#endif

#ifdef HAVE_LIBREADLINE
	 if (isatty(STDIN_FILENO)) {
	    sprintf(s,"%s (%d) %c ", color[board.side], (GameCnt+1)/2 + 1, prompt);
	    inputstr = readline(s);
	    if (inputstr == NULL) return;
	    if (*inputstr) {
	       add_history(inputstr);
	    }
	    if (strlen(inputstr) > INPUT_SIZE-1) {
	       printf("Warning: Input line truncated to %d characters.\n", INPUT_SIZE -1 );
	       inputstr[INPUT_SIZE-1] = '\000';
	    }
	 } else {
	    inputstr = malloc(INPUT_SIZE);
	    if (inputstr == NULL) {
	       perror("InputCmd");
	       exit(EXIT_FAILURE);
	    }
	    fgets(inputstr, INPUT_SIZE, stdin);
	    if (inputstr[0]) {
	       inputstr[strlen(inputstr)-1] = 0;
	    }
	 }
#else /* !HAVE_LIBREADLINE */
	if (!(flags & XBOARD)) {
	  printf ("%s (%d) %c ", color[board.side], (GameCnt+1)/2 + 1, prompt);
	  fflush(stdout);
        }
	fgets (inputstr, INPUT_SIZE, stdin) ;
#endif /* HAVE_LIBREADLINE */

	cmd[0] = '\n';
	strcpy(userinput,inputstr);
	sscanf (inputstr, "%s %[^\n]", cmd, inputstr);
	if (cmd[0] == '\n')
	  goto done;
	cmd[0] = subcmd[0] = setting[0] = subsetting[0] = '\0';
        ncmds = sscanf (userinput,"%s %s %s %[^\n]",
			cmd,subcmd,setting,subsetting);

   /* Put options after command back in inputstr - messy */
   sprintf(inputstr,"%s %s %s",subcmd,setting,subsetting);

   trim = inputstr + strlen(inputstr) - 1;
   while ( trim>=inputstr && *trim==' ')
                *trim--='\0';

   if (strcmp (cmd, "quit") == 0 || strcmp (cmd, "exit") == 0)
      SET (flags, QUIT);
   else if (strcmp (cmd, "help") == 0)
      ShowHelp (inputstr);
   else if (strcmp (cmd, "show") == 0)
      ShowCmd (inputstr);
   else if (strncmp (cmd, "book", 4) == 0) {
      if (strncmp(inputstr, "add",3) == 0) {
        sscanf (inputstr, "add %s", file);
        if (access(file,F_OK) < 0) {
	  printf("The syntax to add a new book is:\n\n\tbook add file.pgn\n");
        } else {
          BookPGNReadFromFile (file);
	}
      } else if (strncmp (inputstr, "on", 2) == 0 || strncmp (inputstr, "prefer", 6) == 0) {
	bookmode = BOOKPREFER;
	printf("book now on.\n");
      } else if (strncmp (inputstr, "off", 3) == 0) {
	bookmode = BOOKOFF;
	printf("book now off.\n");
      } else if (strncmp (inputstr, "best", 4) == 0) {
	bookmode = BOOKBEST;
	printf("book now best.\n");
      } else if (strncmp (inputstr, "worst", 5) == 0) {
	bookmode = BOOKWORST;
	printf("book now worst.\n");
      } else if (strncmp (inputstr, "random", 6) == 0) {
	bookmode = BOOKRAND;
	printf("book now random.\n");
      }
   } else if (strcmp (cmd, "test") == 0)
      TestCmd (inputstr);
   else if (strcmp (cmd, "version") == 0)
      ShowVersion ();
   else if (strcmp (cmd, "pgnsave") == 0)
           {     
		if ( strlen(inputstr) > 0 && strlen(inputstr) < INPUT_SIZE )
      		  PGNSaveToFile (inputstr,"");
		else
		  printf("Invalid filename.\n");
	   }
   else if (strcmp (cmd, "pgnload") == 0)
      PGNReadFromFile (inputstr);
   else if (strcmp (cmd, "manual") == 0)
      SET (flags, MANUAL);
   else if (strcmp (cmd, "debug") == 0)
   {
      SET (flags, DEBUGG);
      Debugmvl = 0;
      if (strcmp (inputstr, "debug") == 0)
      {
         while (strcmp (inputstr, s))
         {
            sscanf (inputstr, "%s %[^\n]", s, inputstr);
            ptr = ValidateMove (s);
            Debugmv[Debugmvl++] = ptr->move;
            MakeMove (board.side, &ptr->move);
         } 
         i = Debugmvl;
         while (i)
         {
            UnmakeMove (board.side, &Debugmv[--i]);
         } 
      }
   }
   else if (strcmp (cmd, "force") == 0)
	SET (flags, MANUAL);
   else if (strcmp (cmd, "white") == 0)
	;
   else if (strcmp (cmd, "black") == 0)
	;
   else if (strcmp (cmd, "hard") == 0)
	;
   else if (strcmp (cmd, "easy") == 0)
	;
   else if (strcmp (cmd, "list") == 0) {
	if (inputstr[0] == '?')
	{
	  printf("name    - list known players alphabetically\n");
	  printf("score   - list by GNU best result first \n");
	  printf("reverse - list by GNU worst result first\n");
	} else {
          sscanf (inputstr, "%s %[^\n]", cmd, inputstr);
          if (inputstr == '\000') DBListPlayer("rscore");
	  else DBListPlayer(inputstr);
	}
   }
   else if (strcmp (cmd, "post") == 0)
	SET (flags, POST);
   else if (strcmp (cmd, "nopost") == 0)
	CLEAR (flags, POST);
   else if (strcmp (cmd, "name") == 0) {
      strcpy(name, inputstr);
      x = name;
      while (*x != '\000') {
        if (*x == ' ') {
	  *x = '\000';
	  break;
	}
        x++;
      }
      suffix = 0;
      for (;;) {
	sprintf(logfile,"log.%03d",suffix);
 	sprintf(gamefile,"game.%03d",suffix);
	if (access(logfile,F_OK) < 0) {
	  ofp = fopen(logfile,"w");
	  break;
	} else 
	  suffix++;
      }
   }
   else if (strcmp (cmd, "result") == 0) {
     if (ofp != stdout) {  
	fprintf(ofp, "result: %s\n",inputstr);
	fclose(ofp); 
	ofp = stdout;
        printf("Save to %s\n",gamefile);
        PGNSaveToFile (gamefile, inputstr);
	DBUpdatePlayer (name, inputstr);
     }
   }	
   else if (strcmp (cmd, "rating") == 0) {
      sscanf(inputstr,"%d %d",&myrating,&opprating); 
      fprintf(ofp,"my rating = %d, opponent rating = %d\n",myrating,opprating); 
      /* Change randomness of book based on opponent rating. */
      /* Basically we play narrower book the higher the opponent */
      if (opprating >= 1700) bookfirstlast = 2;
      else if (opprating >= 1700) bookfirstlast = 2;
      else bookfirstlast = 2;
   }
   else if (strcmp (cmd, "activate") == 0) {
	CLEAR (flags, TIMEOUT);
	CLEAR (flags, ENDED);
   }
   else if (strcmp (cmd, "new") == 0) {
     InitVars ();
     NewPosition ();
     CLEAR (flags, MANUAL);
     CLEAR (flags, THINK);
     myrating = opprating = 0;
   }
   else if (strcmp (cmd, "time") == 0) {
     sscanf (inputstr, "%s %[^\n]", s, inputstr);
     TimeLimit[1^board.side] = atoi(s) / 100.0f ;
   }
   else if (strcmp (cmd, "otim") == 0)
	;
   else if (strcmp (cmd, "random") == 0)
	;
   else if (strcmp (cmd, "hash") == 0)
   {
      sscanf (inputstr, "%s %[^\n]", cmd, inputstr);
      if (strcmp (cmd, "off") == 0)
         CLEAR (flags, USEHASH);
      else if (strcmp (cmd, "on") == 0)
         SET (flags, USEHASH);
      printf ("Hashing %s\n", flags & USEHASH ? "on" : "off");
   }
   else if (strcmp (cmd, "hashsize") == 0)
   {
      if (inputstr[0] == 0) {
	 printf("Current HashSize is %u slots\n", HashSize);
      } else {
	 i = atoi (inputstr);
	 TTHashMask = 0;
	 while ((i >>= 1) > 0)
	 {
	    TTHashMask <<= 1;
	    TTHashMask |= 1;
	 }
	 HashSize = TTHashMask + 1;
	 printf ("Adjusting HashSize to %u slots\n", HashSize);
	 InitHashTable (); 
      }
   }
   else if (strcmp (cmd, "null") == 0)