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 Mastermind project (#17) #32

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
---
title: Create Mastermind Game with Python
author: 3t8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you ok with using your real name here?

If so please update your name here

datePublished: 2022-11-06
description: Learn how to create Mastermind game with Python in this project tutorial
header: URL
tags:
- beginner
- python
---

# Create Mastermind Game with Python

<AuthorAvatar author_name="3t8" author_avatar="https://avatars.githubusercontent.com/u/62209650" />

![Header image](URL)

**Prerequisites:** Python fundamentals
**Versions:** Python 3.10
**Read Time:** 30 minutes

## [#](#-introduction) Introduction

With more than 55 million units sold, [Mastermind](https://en.wikipedia.org/wiki/Mastermind_(board_game)) is a one of the world’s most popular strategy games ever. The idea behind it is to guess an unknown sequence of colored pegs in the fewest number of attempts possible. Despite having simple rules the game is still very difficult and entertaining.
3t8 marked this conversation as resolved.
Show resolved Hide resolved

Because representing colors in a terminal is not an easy task, we are going to create a more *modern* version of the Mastermind game. In 1977 Invicta released an electronic version of the game where the colored pegs were replaced by an array of up to five digits. In this tutorial we are going to recreate it in Python.
3t8 marked this conversation as resolved.
Show resolved Hide resolved

![Invicta Electronic Master Mind](https://tomsk3000.com/wp-content/uploads/2020/07/200707_Electronic-Mastermind_08_lres.jpg)

## [#](#-rules) Rules

The rules are really simple:
- A random 4 digit secret code is generated
- The player will input a 4 digit number trying to guess the secret code
3t8 marked this conversation as resolved.
Show resolved Hide resolved
- The number of digits that are correct and in the right position is returned
- The number of digits that are correct but in the wrong position is returned
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick, but could you add a small note here about what "right position" and "correct" means?
The easiest way would be to use a simple mastermind example to walk through it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1


## [#](#-generate-a-random-number) Step 1. Generate a Random Number

In order to generate a random number we need to use the `random` module. More specifically we are going to use `randrange` method.

```python
from random import randrange
```

The `randrange` method returns a random number within the specified range. To generate a 4 digit number we need our range to be between 0 and 10000 (the largest number generated would be 9999)

```python
number = randrange(10000)
```

There is only a small problem. If the generated number is smaller than 1000 we will end up with a secret code that has less than 4 digits.

To fix that we need to add zeroes at the beginning of the number until it reaches 4 digits.

The easiest way to do it is converting the number to a string and justify it to the right with `rjust` by adding `0` until it has `4` digits.

``` python
number = str(number).rjust(4, '0')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename this number to secret_code and also update all the usage of this variable below, to make it more semantic?

```

We now have our random secret code.

## [#](#-read-users-guess) Step 2. Read user's guess

The next step is getting user's guess. To do that we will use the `input` function.
3t8 marked this conversation as resolved.
Show resolved Hide resolved

We use `isnumeric` to check that all the characters taken as input are numeric and `len(guess)` to make sure that the guess has 4 digits.

If any of those conditions are not met, we will ask again the user for a guess.
3t8 marked this conversation as resolved.
Show resolved Hide resolved

We set `guess = 0` to execute the instructions inside the while the first time.


```python
guess = 0
while (guess.isnumeric() == False or len(guess) != 4):
guess = input('Guess the four digit number: ')
```
The `guess` read with `input` is treated as a string, same as the secret code `number`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't number treated as a number here, not a string?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Please change guess = 0 to guess = '0'.


## [#](#-additional-variables) Step 3. Additional variables

We need a variable to count the correct digits in the right position

```python
correct_position = 0
```

and one to count the correct digits in the wrong position
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to the previous line.
"We need a variable ... in the right position and one ... wrong position.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two variables can also be combined to one python field.


```python
correct_digit = 0
```
We will also use a copy of `number` in order to keep track of the digits that have already been matched and the ones that are left unmatched
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a period.


```python
unmatched = number
```

## [#](#-count-guessed-digits-in-the-right-position) Step 4. Count guessed digits in the right position

To count how many digits of the `guess` match the ones in `number` we need to check all of them in pairs.
Because there are 4 digits we need to iterate the check 4 times so we use `range(4)`.

If both `guess` and `number` have the same digits in the same positions, we increment the `correct_position` counter.

To keep track of the matched digits and exclude them later when we count the right digits in the wrong positions, we conventionally set `unmatched` to `f` at the position of the matched digit. This way `f` will never match with any digit from 0 to 9

```python
for i in range(4):
if (guess[i] == number[i]):
unmatched = unmatched.replace(guess[i], 'f', 1)
correct_position += 1
```

## [#](#-count-guessed-digits-in-the-wrong-position) Step 5. Count guessed digits in the wrong position

There is no need to count the right digits in the wrong position if we already matched all 4.

```python
if (correct_position < 4):
```

We will only execute the next instructions if the condition above is true.

Once again we use `range(4)` to iterate over all 4 digits.
This time the digits are compared between `guess` and `unmatched`.
We check if `guess[i] in unmatched` and if that's true we increment the `correct_digit` counter.

The `guess` variable has not been changed at all, but we **must not** check the digits that have already been matched in the right position.
That is done by also checking if `guess[i] != number[i]` (or if `unmathced[i] != 'f'`).

```python
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing the aforementioned for loop here.

if (guess[i] in unmatched and guess[i] != number[i]):
unmatched = unmatched.replace(guess[i], 'f', 1)
correct_digit += 1
```
Like the last time, the matched digits in `unmatched` are set to `f` so they will be excluded in the next iteration.

## [#](#-printing-guessed-digits-and-winning) Step 6. Printing guessed digits

At this point both `correct_position` and `correct_digit` counters should have the right values. We just need to print them.

```python
print('Digits in right position: ' + str(correct_position))
print('Digits in wrong position: ' + str(correct_digit))
```

## [#](#-counting-the-number-of-guesses-and-winning) Step 7. Counting the number of guesses and winning

The whole code after the `number` generation needs to be inside a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to finish this sentence with a colon or "white loop as follows:"


```python
while (guess != number):
```

Each time the code is executed we need to increment the `tries` variable

```python
tries += 1
```

If the `while` condition is not satisfied that means `guess == number` so the secret number has been guessed.

We tell the user that he/she won and we also tell him/her how many attempts it took

```python
print('You are a mastermind! You guessed the number in ' + str(tries) + ' tries')
```

## [#](#-code) Code

The whole code should look like this:

```python
from random import randrange

# Generate a random number from 0 to 10000
number = randrange(10000)
# Add padding to the number if it doesn't have four digits
number = str(number).rjust(4, '0')
tries = 0
guess = 0

while (guess != number):
tries += 1
guess = ''
# unmatched will keep track of the digits that have not been guessed
unmatched = number
correct_digit = 0
correct_position = 0
# Read a four digit number
while (guess.isnumeric() == False or len(guess) != 4):
guess = input('Guess the four digit number: ')
# Count the guessed digits in the right postion
for i in range(4):
if (guess[i] == number[i]):
# Set the guessed digit to 'f' in unmatched
unmatched = unmatched.replace(guess[i], 'f', 1)
correct_position += 1
# Count the guessed digits in the wrong position
if (correct_position < 4):
for i in range(4):
if (guess[i] in unmatched and guess[i] != number[i]):
# Set the guessed digit to 'f' in unmatched
unmatched = unmatched.replace(guess[i], 'f', 1)
correct_digit += 1
print('Digits in right position: ' + str(correct_position))
print('Digits in wrong position: ' + str(correct_digit))
print('You are a mastermind! You guessed the number in ' + str(tries) + ' tries')
```

## [#](#-improvements) Improvements

To improve this project you could try to:

- Increase the number of digits the user has to guess. With 4 digits the possible permutations are 10000.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Increase the number of digits the user has to guess. With 4 digits the possible permutations are 10000.
- Increase the number of digits the user has to guess. With 4 digits the possible permutations are 10000, but with more digits, it would be harder for the user to guess the permutation.

- Allow the user to use a [custom seed](https://docs.python.org/3/library/random.html#random.seed) for the secret number. This could allow multiple users to compete against each other and try and guess the same number.
- Try to code your own solver by implementing [Knuth's algorithm](https://en.wikipedia.org/wiki/Mastermind_(board_game)#Worst_case:_Five-guess_algorithm).

## [#](#-more-resources) More Resources

- [Solution on GitHub](https://github.com/codedex-io/projects/blob/main/projects/create-mastermind-game-with-python/mastermind.py)
- [Documentation: random](https://docs.python.org/3/library/random.html)
- [Mastermind solver](https://nebupookins.github.io/JS-Mastermind-Solver/)
34 changes: 34 additions & 0 deletions projects/create-mastermind-game-with-python/mastermind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from random import randrange
# Generate a random number from 0 to 10000
number = randrange(10000)
# Add padding to the number if it doesn't have four digits
number = str(number).rjust(4, '0')
tries = 0
guess = 0

while (guess != number):
tries += 1
guess = ''
# unmatched will keep track of the digits that have not been guessed
unmatched = number
correct_digit = 0
correct_position = 0
# Read a four digit number
while (guess.isnumeric() == False or len(guess) != 4):
guess = input('Guess the four digit number: ')
# Count the guessed digits in the right postion
for i in range(4):
if (guess[i] == number[i]):
# Set the guessed digit to 'f' in unmatched
unmatched = unmatched.replace(guess[i], 'f', 1)
correct_position += 1
# Count the guessed digits in the wrong position
if (correct_position < 4):
for i in range(4):
if (guess[i] in unmatched and guess[i] != number[i]):
# Set the guessed digit to 'f' in unmatched
unmatched = unmatched.replace(guess[i], 'f', 1)
correct_digit += 1
print('Digits in right position: ' + str(correct_position))
print('Digits in wrong position: ' + str(correct_digit))
print('You are a mastermind! You guessed the number in ' + str(tries) + ' tries')