Search code examples
cloopsrepeat

Yahtzee game code is telling me I have an invalid roll despite no input and is doing the first roll twice


My code is meant to play the game Yahtzee and everything works fine until I enter the names of the players and it does the first roll and tells me that my roll is invalid even though I haven't picked which dice rolls I want to keep yet. Whats strange to me is that when it loops around to player 2 it works perfectly fine. If you run the code, you'll see you get two roll 1's at first and there in lies the problem.

Here's the code

// Include C Libraries
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

// Macros
#define ONE 1
#define TWO 2
#define NAME 20
#define ZERO 0
#define SIX 6
#define ROLLS 3
#define THREE 3
#define FOUR 4
#define FIVE 5
#define DICE 5
#define FALSE 0
#define TRUE 1

// Define enumeration scores
enum scores {one, two, three, four, five, six, three_kind, four_kind, full_house, sm_str, lg_str, yahtzee, chance};

// Function Prototypes
void welcomeScreen();
void playGame();
void displayEmptyCard();
void displayDice();
void displayRandomDice();
int rollDie();
void initDice();
void rollDice();
int selectDice();
void displayRoll();
int isValid();
void trimTrailing();

// Main Function
int main()
{
    // Calls welcomeScreen function
    welcomeScreen();

    //random # generator
    srand((unsigned)time(NULL));
  
    // Calls playGame function
    playGame();

    // Program executed successfully
    return 0;
}

// welcomeScreen function displays YAHTZEE logo/rules
void welcomeScreen()
{
    // Prints Title
    printf("\tY       Y     A         H       H   TTTTTTTTT  ZZZZZZZZ   EEEEEEEE   EEEEEEEE\n");
    printf("\t Y     Y     A A        H       H       T            Z    E          E       \n");
    printf("\t  Y   Y     A   A       H       H       T           Z     E          E       \n");
    printf("\t   Y Y     A     A      HHHHHHHHH       T          Z      EEEE       EEEE    \n");
    printf("\t    Y     AAAAAAAAA     H       H       T         Z       E          E       \n");
    printf("\t    Y    A         A    H       H       T        Z        E          E       \n");
    printf("\t    Y   A           A   H       H       T      ZZZZZZZZ   EEEEEEEE   EEEEEEEE\n");
    printf("\n");

    // Prints Game Rules Title
    printf("YAHTZEE GAME RULES\n");
    printf("\n");

    // Prints Game Rules
    printf("1. Five six-sided dice are rolled.\n");
    printf("2. Players roll all five dice.\n");
    printf("3. Players can roll selected dice three times per turn.\n");
    printf("4. Players must score one of the 13 categories per turn.\n");
    printf("5. Players alternate turns.\n");
    printf("6. Game ends when all players score 13 categories.\n");
    printf("7. Player with the highest score wins!\n");
    printf("\n");
}

// Provides The Actual Game Play
void playGame()
{
  // Declaring Characters
  char playerOne[NAME];
  char playerTwo[NAME];
  int currentPlayer = ONE;
  int loop = ZERO;
  int dice[DICE]; 
  int keep[DICE]; 

  // Player 1 Name Input
  printf("Player One, enter your name: ");
  scanf("%s", playerOne);
  
  // Player 2 Name Input
  printf("Player Two, enter your name: ");
  scanf("%s", playerTwo);
  printf("\n");
  
  // Lets Play Display
  printf("%s and %s, let's play Yahtzee!\n", 
  playerOne, playerTwo);
  printf("\n");

  while(loop < 2)
  {
    if (currentPlayer == ONE)
    {
      // Tells The Player Whos Turn It Is
      printf("%s it's your turn.\n", playerOne);
      printf("\n");

      // Displays Empty Card/Dice
      displayEmptyCard();
      printf("\n");
      initDice(dice);
      initDice(keep);
      fflush(stdin);
      rollDice(dice, keep);
      printf("\n");
      currentPlayer = TWO;
    }
    else if (currentPlayer == TWO)
    {
      // Tells The Player Whos Turn It Is
      printf("%s it's your turn.\n", playerTwo);
      printf("\n");

      // Displays Empty Card/Dice
      displayEmptyCard();
      printf("\n");
      initDice(dice);
      initDice(keep);
      fflush(stdin);
      rollDice(dice, keep);
    }
    //Increment loop control variable
    loop++;
  }
}

// Function to Display The Empty Card
void displayEmptyCard()
{
  printf("|---------------------------------------|\n");
  printf("|  UPPER SECTION   |    LOWER SECTION   |\n");
  printf("|---------------------------------------|\n");
  printf("|---------------------------------------|\n");
  printf("| Aces   |         | 3 Kind  |          |\n");
  printf("| Twos   |         | 4 Kind  |          |\n");
  printf("| Threes |         | Full Hs |          |\n");
  printf("| Fours  |         | Sm Str  |          |\n");
  printf("| Fives  |         | LG Str  |          |\n");
  printf("| Sixes  |         | Yahtzee |          |\n");
  printf("| Total  |         | Chance  |          |\n");
  printf("| Bonus  |         | Total   |          |\n");
  printf("|---------------------------------------|\n");
}

//Function to Display Random Dice
void displayRoll(int dice[DICE])
{
  printf("\n");
  printf("+---------+ +---------+ +---------+ +---------+ +---------+\n");
  for (int i = 0; i < 5; ++i){
    if (i == 0){
      printf("|    %d    |", dice[i]);
    }
    else{
      printf(" |    %d    |", dice[i]);
    }
  } 
  printf("\n");
  printf("+---------+ +---------+ +---------+ +---------+ +---------+\n");
  printf("    (1)         (2)         (3)         (4)         (5)    \n");
  printf("\n");
}

//Function for Rolling Dice
int rollDie()
{
  int dieValue = rand() % 6 + 1;
  return dieValue;
}

//Function to Initialize Dice
void initDice(int dice[DICE]){
    for (int i = 0; i < DICE; ++i){
      dice[i] = 0;
  }
}

//Function to Roll Dice
void rollDice(int dice[DICE], int keep[DICE]){
  int roll = 0;
  while (roll != 3){
    printf("Roll %d", roll + 1);
    
    for (int i = 0; i < DICE; ++i){
      if (keep[i] == 0){
        dice[i] = rollDie();
        }
      }
    
    displayRoll(dice);
    if (selectDice(dice, keep) == 0){
      printf("The dice you have selected are invalid. Please try again.\n");
      printf("\n");
    }
    else{
      ++roll;
    }
  }
}

//Function to Select Dice
int selectDice(int dice[DICE], int keep[DICE]){
  char input[NAME];
  char data[NAME];
  char * value;
  int valid = 0;

  printf("Enter the dice you would like to keep, enter values 1 through 5 with spaces between numbers: \n");
  
  fgets(input, NAME, stdin);
  trimTrailing(input);
  strcpy(data, input);
  valid = isValid(data);
  
  printf("\n");
  
  if (valid == 0){
    return 0;
  }

  initDice(keep);
  value = strtok(input, " ");
  
  while (value != NULL){
    int number = atoi(value);
    if (number >= 1 && number <= 5){
      keep[number - 1] = 1;
      valid = TRUE;
    }
    value = strtok(NULL, " ");
  }
  return valid;
}

//Function to determine if Valid
int isValid(char data[NAME]){
  char * value;
  int valid = FALSE;
  value = strtok(data, " ");
  
  while (value != NULL){
    //printf("%d", valid);
    int number = atoi(value);
    if (number >= 1 && number <= 5){
      valid = TRUE;
    }
    else{
      valid = FALSE;
    }
    break;
  }
  value = strtok(NULL, " ");
  return valid;
}

//Function to Trim Trailing
void trimTrailing(char * str){
  
// Set default index to invalid number
  int index = -1;
  
// loop control variable
  int i = 0;
  
// Find last index of non-white space character
  while(str[i] != '\0'){
    if(str[i] != ' ' && str[i] != '\t' && str[i] != '\n'){
      index = i;
    }
    i++;
  }
  
// Mark next character to last non-white space character as NULL
  str[index + 1] = '\0';
}

I tried tracking the values that I have set to determine when to inform the user of an invalid input but I just can't seem to pin it down.


Solution

  • scanf("%s", playerTwo); leaves the trailing newline in the input buffer which fgets(input, NAME, stdin) reads in selectDice() and correctly reject it as invalid input.

    Either removed the newline with getchar() (or scanf("%c", &(char) {0})):

        scanf("%s", playerTwo);
        int ch = getchar();
        if(ch == EOF)
             exit(0);
    

    Always check the return value of all I/O functions like scanf() otherwise you may be operating on uninitialized data or end up in an infinite loop on EOF.

    Or in selectDice() use scanf() to read the numbers. An easy option would be to use:

       initDice(keep);
       int rv = scanf("%d %d %d %d %d", keep, keep+1, keep+2, keep+3, keep+4);
       if(rv == EOF)
           exit(0);
    

    Or use %[^\n] format to skip leading white space and read everything up to a newline. Always use a maximum field width when reading strings with scanf(). The macro str() generates the maximum field width and note that you you need to allocate an extra byte for input:

    #define str(s) str2(s)
    #define str2(s) #s
    
    // ...
    
       char input[NAME+1];
       int rv = scanf(" %" str(NAME) "[^\n]", input);
    

    The more robust approach is to separate I/O and parsing and read a line at at a time (fgets() or getline()) then use sscanf() or other lower level functions to parse the input. If you cannot parse something throw the line away and read another line.