Skip to content

Instantly share code, notes, and snippets.

@nikclayton
Created May 25, 2023 15:47
Show Gist options
  • Save nikclayton/5401aa0e95785d33f803f96892a72f06 to your computer and use it in GitHub Desktop.
Save nikclayton/5401aa0e95785d33f803f96892a72f06 to your computer and use it in GitHub Desktop.
Rock, Paper, Scissors in Dart

Took a little play with this, and there's some stuff you might want to try.

Let's think about the type of data in the game.

You already know types like int, string, etc (more on types in https://nikclayton.writeas.com/on-values-types-variables-functions-and-function-hoisting).

We can also create our own types, and decide what are legal values for those types to have. This is especially useful when we're trying to model something like a game, where there's generally a restricted set of values that are valid at any given time.

You already know that it's possible for types to have a limited number of possible values. Whether it's an integer type that can't be used for floating point values, or a boolean type that can only be used for the values true and false.

In this game there are at least two types of values that are restricted.

  1. The game result. It's either win, lose, or draw. There are no other options.

  2. The choice of move. It's either rock, paper, or scissors. There are no other options.

When you see something like that one of your first thoughts should be "enum". Short for "enumeration".

An "enum" in languages like Dart is a way of restricting a type to one of a handful of values. For example:

enum GameResult {
  win,
  lose,
  draw
}

Now we can declare variables of this type, give them values, and use them in expressions.

final result = GameResult.win;
print("The result was: $result");  // -> "The result was: GameResult.win"

if (result == GameResult.draw) {
  print("Boring, it's a draw");
}

So we start with the GameResult enum shown above, and add another enum for the move.

enum Move {
  rock,
  paper,
  scissors
}

Now we have that we know we're going to want the user to type in their move, one of either r, p, or s. We'll need to convert from the string they entered in to one of the values for Move, and we can write a function that does that.

Move? moveFromString(String s) {
  return switch (s.toLowerCase()) {
      "r" => Move.rock,
      "p" => Move.paper,
      "s" => Move.scissors,
      _ => null
  };
}

This takes the string and converts it to lowercase, and uses a switch statement to perform the test. If you've not seen switch before then check out https://dart.dev/language/branches#switch-statements. It's an easy way to write multiple if-like statements together in a way that can be more readable.

Notice how this function has to deal with the fact that the user might have entered something other than r, p, or s. If that happens the default (_) behaviour is to return null. The code that calls this function can check that and decide what to do with it.

We also need a way to know what the result of playing one move against another is, we can write another function to do that.

GameResult play(Move first, Move second) {
  if (first == second) return GameResult.draw;
    
  return switch ((first, second)) {
      (paper, rock) => GameResult.win,
      (scissors, paper) => GameResult.win,
      (rock, paper) => GameResult.win,
      _ => GameResult.lose
  };
}

This covers all the possible game cases.

  1. If the moves are identical then it must be a draw
  2. The 3 possible winning moves (for the first player) are spelled out
  3. Everything else (the _ clause) means the first player loses

Then this can be put together with a final main() function like this:

void main() {
  final rng = Random();

  print("R, P, S?");
    
  final input = stdin.readLineSync();   
  final move = moveFromString(input);
    
  if (move == null) {
    print("Invalid move");
    return;
  }
    
  final computerMove = Move.values[rng.nextInt(3)];

  print("Your move: $move");
  print("Computer move: $computerMove");

  final result = play(move, computerMove);
  switch (result) {
    case GameResult.win: print("You win, $move beats $computerMove");
    case GameResult.lose: print("You lose, $computerMove beats $move");
    case GameResult.draw: print("It's a draw");
  }
}

Hopefully you can see how this would be easy to extend to other variants of the game, such as https://www.wikihow.com/Play-Rock-Paper-Scissors-Lizard-Spock. Most of the changes would be in the Move enum, play function, and getting the user's choice of move. The rest of the code would be unchanged.


There are some other things that could be done. Some of the switch statements can be simplified, play could be made a method on the Move enum, but hopefully this has been helpful.

Some of that is in https://dartpad.dev/?id=8ed3329ea21dbfcc617378276dd1417f if you want to see some of those additional things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment