Approach
Written by Jeffrey Yao from CSESoc Education.
Let's break down what we need to do, then step through each task.
- Scan in player moves
- Check that the moves are valid
- If they are, check who won for each turn/round
- Print the winner
Miscellaneous style
To improve code clarity, we define constants for commonly used character/numbers in the header, and a struct for player data and valid moves.
#define SCISSORS 'S'#define PAPER 'P'#define ROCK 'R'#define MAX_TURNS 3// Store moves in char array of length 6.// Why 7: Array length includes turns (T), spaces between turns (S),// the EOF character (\n) AND the null terminator (\0).// | T | S | T | S | T | \n | \0 |#define MAX_LENGTH 7struct player{ int points; char moves[MAX_LENGTH];};struct moves_valid{ bool p1; bool p2;};
We extract the steps with lengthy logic to separate functions with appropriate names. This ensures our main function's logic is clear.
- Validating moves →
check_move
- Checking winner →
player_won
- Printing winner →
print_winner
Scan in player moves
To scan in strings, we use the fgets()
function in <stdio.h>
.
fgets(player1.moves, sizeof(player1.moves), stdin);fgets(player2.moves, sizeof(player2.moves), stdin);
Check that the moves are valid
We perform the validation logic in a boolean function check_move
, which checks that the current player's moves are valid. If not, it returns false
. This requires the <stdbool.c>
library.
bool check_move(struct player player){ // Each turn's move can only be scissors, paper or rock. if ((player.moves[0] == SCISSORS || player.moves[0] == PAPER || player.moves[0] == ROCK) && (player.moves[2] == SCISSORS || player.moves[2] == PAPER || player.moves[2] == ROCK) && (player.moves[4] == SCISSORS || player.moves[4] == PAPER || player.moves[4] == ROCK)) { return true; } else // Characters other than S, P and R have been entered. Moves invalid. { return false; }}
Why use the array indices 0, 2 and 4? Recall that after reading the input in using fgets
, the 'moves' char array looks like this:
T = player's move on the turnS = space specified in input format\n = EOF character\0 = null terminator indicating end of string| 0 | 1 | 2 | 3 | 4 | 5 | 6 |-------------------------------| T | S | T | S | T | \n | \0 |
The relevant array indices for turns 1, 2, 3 are [0]
, [2]
and [4]
.
If they are, check who won for each turn
We check the winner in a boolean function player_won
. It uses the following algorithm:
- In each turn, both players can draw the following combinations: rock and scissors, scissors and paper, or paper and rock.
- Rock wins against scissors, scissors wins against paper, paper wins against rock.
- If the combination is rock and scissors and the active player drew rock, they must have won the round. The same logic applies for the other combinations.
- Else, the other player won.
We can check for the presence of a certain combination using the fact that characters correspond to integer ASCII codes in C. This means that if we 'add' characters together, they sum to an integer value (i.e. R + S = 82 + 83 = 165). Hence, we check for the presence of certain combinations by their integer sum.
bool player_won( struct player active, struct player player1, struct player player2, int i){ if ((player1.moves[i] + player2.moves[i] == ROCK + SCISSORS && active.moves[i] == ROCK) || (player1.moves[i] + player2.moves[i] == SCISSORS + PAPER && active.moves[i] == SCISSORS) || (player1.moves[i] + player2.moves[i] == PAPER + ROCK && active.moves[i] == PAPER)) { return true; } else { return false; }}
Each player has points, starting from 0. For each round they win, their points are incremented by 1. If the round is a draw, no points are awarded for either player.
Print the winner
If either player has more points than the other, it means they won. If their points are equal it must be a draw.
void print_winner(struct player player1, struct player player2){ if (player1.points > player2.points) { printf("Player 1 won!"); } else if (player2.points > player1.points) { printf("Player 2 won!"); } else { printf("It's a draw!"); }}