Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rock-paper-scissors game example #1479

Merged
merged 15 commits into from
Aug 27, 2024
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ listed without any additional command line arguments.
| [cosmwasm/zero-to-hero/vote.qnt](./cosmwasm/zero-to-hero/vote.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| [cryptography/hashes.qnt](./cryptography/hashes.qnt) | :white_check_mark: | :white_check_mark: | N/A[^parameterized] | N/A[^nostatemachine] |
| [cryptography/hashesTest.qnt](./cryptography/hashesTest.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | N/A[^nostatemachine] |
| [games/rock_paper_scissors/play_rock_paper_scissors.qnt](./games/rock_paper_scissors/play_rock_paper_scissors.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| [games/secret-santa/secret_santa.qnt](./games/secret-santa/secret_santa.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| [games/tictactoe/tictactoe.qnt](./games/tictactoe/tictactoe.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| [language-features/booleans.qnt](./language-features/booleans.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Expand Down
75 changes: 75 additions & 0 deletions examples/games/rock_paper_scissors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Rock-Paper-Scissors Game Module

This project implements a simple Rock-Paper-Scissors game, allowing two players to randomly select moves and determine the winner based on the traditional rules of the game.

## Overview

The `rock_paper_scissors` module provides the core functionality for the game, including:

- **Player Setup:** Constants for player names.
- **Game Logic:** Actions to decide player moves, determine the winner, and reset the game.
- **Invariant Checking:** Ensures the game status reflects the expected winner based on the rules.

The `play_rock_paper_scissors` module demonstrates how to import and use the `rock_paper_scissors` module with specific player names.

## Components

### Module: `rock_paper_scissors`

This is the main module where the game logic resides.

- **Constants:**
- `PLAYER1`: Name of the first player.
- `PLAYER2`: Name of the second player.

- **Types:**
- `Move`: Enum representing possible moves: `Init`, `Rock`, `Paper`, `Scissors`.
- `GameStatus`: Enum representing the status of the game: `Started`, `Pending`, `Draw`, `Winner(str)`.

- **Variables:**
- `p1State`: Current move of Player 1.
- `p2State`: Current move of Player 2.
- `status`: Current status of the game.

- **Functions:**
- `beats(m1: Move, m2: Move): bool`: Determines if `m1` beats `m2` based on Rock-Paper-Scissors rules.

- **Actions:**
- `init`: Initializes the game with both players in `Init` state and the game status as `Started`.
- `decide_moves`: Randomly selects moves for both players from the set of possible moves (`Rock`, `Paper`, `Scissors`) and updates the game status to `Pending`.
- `find_winner`: Determines the winner based on the players' moves and updates the game status to `Winner(PLAYER1)`, `Winner(PLAYER2)`, or `Draw`.
- `step`: The main game loop that progresses the game through different states (`Started`, `Pending`, etc.) and resets the game for a new round.

- **Invariants:**
- `winInv`: Ensures the game status matches the expected winner based on the rules of Rock-Paper-Scissors.

### Module: `play_rock_paper_scissors`

This module imports the `rock_paper_scissors` module and assigns the player names:

```bluespec
import rock_paper_scissor(PLAYER1 = "Mahtab", PLAYER2 = "Gabriela").*
```

## How It Works

1. **Initialization:**
- The game starts with both players in the Init state, and the game status is set to Started.

2. **Move Decision:**
- The `decide_moves` action randomly selects a move for each player and updates the game status to Pending.

3. **Determine Winner:**
- The `find_winner` action evaluates the moves and determines the winner or declares a draw.

4. **Game Progression:**
- The step action orchestrates the progression of the game from initialization to move decision, winner determination, and resetting for a new round.

5. **Invariant Checking:**
- The winInv invariant ensures that the game status correctly reflects the outcome of the moves.

## Usage
- To run the spec, import the `rock_paper_scissors` module and specify the player names as shown in the `play_rock_paper_scissors` module.

- Run using the command:
- `quint run play_rock_paper_scissors.qnt --invariant=winInv`
64 changes: 64 additions & 0 deletions examples/games/rock_paper_scissors/play_rock_paper_scissors.qnt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
module rock_paper_scissors {
romac marked this conversation as resolved.
Show resolved Hide resolved
// Constants for player names
const PLAYER1 : str
const PLAYER2 : str
// Sum types for the possible moves and game status
type Move = Init | Rock | Paper | Scissors
type GameStatus = Started | Pending | Draw | Winner(str)
// Variables to hold the current state of each player's move and the game status
var p1State: Move
var p2State: Move
var status: GameStatus

def beats(m1: Move, m2: Move): bool = or {
bugarela marked this conversation as resolved.
Show resolved Hide resolved
(m1 == Rock and m2 == Scissors),
(m1 == Paper and m2 == Rock),
(m1 == Scissors and m2 == Paper),
}
/// Initialize the game with both players in 'Init' state and game status as 'Started'
action init = all {
p1State' = Init,
p2State' = Init,
status' = Started
}

/// Randomly decide moves for both players from the set of possible moves
action decide_moves = {
nondet move1 = Set(Rock, Paper, Scissors).oneOf() // Non-deterministically pick a move for player 1
nondet move2 = Set(Rock, Paper, Scissors).oneOf() // Non-deterministically pick a move for player 2
all {
p1State' = move1,
p2State' = move2,
status' = Pending,
}
}

/// Determine the winner based on the current moves of both players
action find_winner = any {
if (beats(p1State, p2State)) status' = Winner(PLAYER1) // Player 1 wins if their move beats player 2's move
else if (beats(p2State, p1State)) status' = Winner(PLAYER2) // Player 2 wins if their move beats player 1's move
else status' = Draw // It's a draw if both players chose the same move
}

action step = if (status == Started) {
bugarela marked this conversation as resolved.
Show resolved Hide resolved
decide_moves
} else if (status == Pending) all {
p1State' = p1State,
p2State' = p2State,
find_winner,
} else all {
p1State' = Init,
p2State' = Init,
status' = Started
}

/// Invariant to check if the game status matches the expected winner
val winInv = and {
status == Winner(PLAYER1) implies beats(p1State, p2State),
status == Winner(PLAYER2) implies beats(p2State, p1State),
}
}

module play_rock_paper_scissors {
import rock_paper_scissors(PLAYER1 = "Mahtab", PLAYER2 = "Gabriela").*
}
Loading