Python Forum
Mastermind/Guess the Code Game
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Mastermind/Guess the Code Game
#1
Hello all,

I am relatively new to Python programming but I have managed to make a Mastermind-type game in IDLE that I would like to share with all of you.

I am happy to receive feedback on my program - e.g. I only used 1 function in the program, is this okay, or is it better to use more functions and local variables? I have written comments throughout my program, especially in the section where the code tries to determine how many black and white pegs should be awarded to the player (the logic in this part is very difficult). If anyone can spot any errors in this part of the program, or anywhere else, I'd be very happy if you could tell how to fix them (or at least give me some hints).

Thank you,

from random import choice

def feedback(blackPegs,whitePegs):
    feedback1 = str(blackPegs) + " BLACK PEGS"
    feedback2 = str(whitePegs) + " WHITE PEGS"
    
    if blackPegs == 1:
        feedback1 = feedback1[:-1]

    if whitePegs == 1:
        feedback2 = feedback2[:-1]

    # This is to take out the "S" if the number of white pegs or black pegs is singular

    feedback = feedback1 + " — " + feedback2 + "\n"
    return print(feedback)

print("Guess The Code Game")
print("-------------------\n")

print("INSTRUCTIONS:")
print("Colour scheme: green, red, yellow, blue, orange, purple.")
print("I'm thinking of a 4 colour code. Try and guess what it is!")
print("Note: duplicates are allowed.")
print("Input your guess like this: 'RRYB'.")
print("You will then be awarded one or more black or white pegs.")
print("A black peg means right colour, right position.")
print("A white peg means right colour, wrong position.")
print("You have 7 guesses. Good luck!")

colours = ["G","R","Y","B","O","P"]

play = "Y"
while play == "Y":
# This sets up the program so that later on the player has the option to play again

    print()

    code = []
    for i in range(4):
        code.append(choice(colours))
        # This randomly selects the code from the colours array
    
    # print(code)

    win = False
    for g in range(7): # You only get 7 guesses

        validated = False
        while validated == False:
            validated = True
            # You assume the user input is valid until you find out otherwise                    
            guess = input("INPUT guess: ").upper()
            if len(guess) != len(code):
                print("You have to type in 4 colours.")
                validated = False
            else:
                for i in range(4):
                    if guess[i] not in colours:
                        print("Unexpected character. Please try again.")
                        validated = False
                        break
     
        guess = list(guess)
        
        blackPegs = 0
        whitePegs = 0
        for i in range(4):
            if guess[i] == code[i]:
                blackPegs += 1
                guess[i] += "PEG!"
                code[i] += "PEG!"
        
        for i in range(4):
            if guess[i] in code and guess[i] != code[i]:
                whitePegs += 1
                code[code.index(guess[i])] += "PEG!"
        
        for i in range(4):
            if len(code[i]) > 1:
                code[i] = (code[i])[0]
                
        '''
Lines 66-81
This part of the program might seem unneccesarily complicated, but it is to make up for some of the
subtleties in the rules in how you award the black or white pegs.

"If there are duplicate colours in the guess, they cannot all be awarded a key peg unless they
correspond to the same number of duplicate colours in the hidden code." - Wikipedia

E.g.(1)
for i in range(4):
    if guess[i] == code[i]:
        blackPegs += 1
    elif guess[i] in code:
        # The elif statement means guess[i] != code[i]
        whitePegs += 1

Why it doesn't work:
If the code was "PPYP" and your guess was "OYYO",
you would be awarded 1 white peg and 1 black peg
as the first 'Y' in the guess is in the code but
not directly corresponding. However, there is 1
too many 'Y' in the guess and therefore there
should be 1 black peg and 0 white pegs instead.

E.g.(2)
for i in range(4):
    if guess[i] == code[i]:
        blackPegs += 1
        code[i] += "PEG!"

for i in range(4):
    if guess[i] in code and guess[i] != code[i]:
        whitePegs += 1
        code[code.index(guess[i])] += "PEG!"
        # This finds the 1st place in the code where
        # the colour in the guess is

for i in range(4):
    if len(code[i]) > 1:
        code[i] = (code[i])[0]
        # This resets the code back to what it was

Improvements:
This time, the number of black pegs is determined
first because otherwise a white peg could be given
too early when a black peg should have
been given instead (see Example 1).

Also, the issue of double counting has been
partially avoided. E.g. if the code was "ROOO"
and your guess was "GRRR" then only 1 white peg
would be awarded (rather than 3) because the 
code array would become ["RPEG!", "O", "O", "O"]
after 1 white peg.

Why it stil doesn't work:
*sigh* Unfortunately, this still doesn't work. For
example, if the code was "BBGG" and your guess was
"OBOO" then you would be given 1 black peg and 1
white peg, but a single coloured peg can't count
towards a black peg and a white peg.

E.g.(2) solves the problem of a coloured peg in the
code resulting in more than 1 black/white peg, but it
doesn't solve the problem of a coloured peg in the
guess resulting in more than 1 black/white peg.

My solution:
blackPegs = 0
whitePegs = 0
for i in range(4):
    if guess[i] == code[i]:
        blackPegs += 1
        guess[i] += "PEG!"
        code[i] += "PEG!"
        
for i in range(4):
    if guess[i] in code and guess[i] != code[i]:
        whitePegs += 1
        code[code.index(guess[i])] += "PEG!"
        
for i in range(4):
    if len(code[i]) > 1:
        code[i] = (code[i])[0]

Improvements:
I have incorporated all the improvements of E.g.(2),
but now if a coloured peg from the guess has counted
towards a black peg, it cannot also be counted
towards a white peg (this is as a result of guess[i]
+= "PEG", meaning that when it goes through the second
for loop guess[i] is not in code).In the second for
loop, it is unneccessary to write guess[i] += "PEG!" as
it cannot be double counted anyway as that is the last
time it will be counted towards a white peg or a black
peg.
        '''
        
        if blackPegs == 4:
            win = True
            break
        else:
            if g < 6:
                feedback(blackPegs,whitePegs)

    if win == True:
        print("You win! The code is " + "".join(code) + ".")

    else:
        print("Sorry — the code was " + "".join(code) + ". You lose.")

    print()
    play = input("Play again? (Y/N) ").upper()
    while play != "Y" and play != "N":
        print("Please respond with the letter 'Y' or 'N' only.")

Note: on line 44 it prints the code out to the user. I had meant to take out that out before I copied it across to this forum, but I can no longer edit my original post. If you want to use it for testing then fine, but otherwise please comment it out/delete it when you play the game.
Reply
#2
I would put the play loop into it's own function named play or master_mind, and then add an if/name block:

if __name__ == '__main__':
    master_mind()
That way you can import it without the game starting, but running the module itself still plays the game.

return print(feedback) is redundant. The print function returns None, so that return statement returns None. But functions return None by default, so you don't need the return statement.

I would also make the multiple print statements a multiline string (with triple quotes), and then print it with one line in the master_mind function.

Avoid looping over indexes. Use enumerate if you need the index as well as the item.

(code[i])[0] is the same as code[i][0].

Also, I think this works for white pegs/black pegs, without looping over indexes:

black_pegs = 0
white_pegs = 0
for color in colours:
    white_pegs += min(code.count(color), guess.count(color))
for code_peg, guess_peg in zip(code, guess):
    if code_peg == guess_peg:
        black_pegs += 1
white_pegs -= black_pegs
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
Reply
#3
Thank you very much for the feedback. May I ask why it is a bad coding practice to loop over indexes? Also, is there any chance you could explain in more detail what an if/name block is, and why it is preferable to what I did?
Reply
#4
The looping over indexes comes up so much we have a special tutorial for it. Sorry, I should have linked to that in my first post.

The if/main block is a common Python technique. Each module has a __name__ attribute. If the module is imported, it's the name of the file. If the module is executed as the top level module, __name__ is set to __main__. So the if/main block can be used to do something special when the module is not imported. Sometimes you might put test code in that section. Other times you would run a certain function if the module is executed at the top level, but not run it if the module is imported.

In this case, the if/main block may not be needed. If this is just a one-off game and you aren't going to use any of the code elsewhere, you don't really need one. But using code elsewhere is a good idea, and you may not realize until later that you want to do it. So encapsulating code into functions and making it easy to import them is a good habit to get into.
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
Reply
#5
Oh, and that's a good first game program. Keep at it.
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
Reply
#6
I had exact same problem as you concerning the
game logic when marking pegs when writing my
GUI Mastermind clone, Bletchley.

The solution was beyond me, but in the end I found
some guys non-GUI code here:
https://repl.it/@FGratt/Mastermind
and managed somehow to integrate his checking routine into
my code. I credited him BTW.

The source to Bletchley can be found here:
https://stevepython.wordpress.com/2018/1...ed-kind-of
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Guess your number joe_momma 0 2,418 Oct-23-2020, 02:53 AM
Last Post: joe_momma
  Guess my number foksikrasa 0 2,361 May-28-2020, 04:12 PM
Last Post: foksikrasa
  guess my number GAME ronblue77 2 2,716 Nov-24-2019, 04:23 PM
Last Post: CodingStranger
  Guess the dice roll mini-game tawnnx 6 7,245 May-22-2018, 02:12 PM
Last Post: malonn

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020