1

I'm new to python and I need to write a "Bulls and Cows" game (a.k.a Mastermind) and it goes like this:

You get 2 inputs at first; 1 for the length of the secret (can be 4-6), and 2nd for the base (can be 6-10). You can ASSUME that both the secret and the guess will have the given length (you don't need to make sure of that) Later on, you have another 2 inputs. 1 for the secret (a chain of numbers seperated by space) and the 2nd for the base (a chain of numbers seperated by space). if the secret has a number that equals or exceeds the base, the program will output ERROR and exit.

An example to clarify:

5 (First input, length should be 5)
8 (The base. It means that no number is allowed to be 8 or beyond. You are only allowed to use 0,1,2,3,4,5,6,7)
1 2 7 2 5 (this is the secret)
7 2 2 1 1 (this is the guess)

OUTPUT:

1 BULLS 3 COWS

Another example:

6
9
0 0 0 0 0 6
8 22 2 2 1 4

OUTPUT:

0 BULLS 0 COWS

Ok, so I started writing the code, and I wasn't sure what exactly I am supposed to be using, so I did this so far:

#get the length of the guess - an int number  between 4(included) to 6(included)
secretL = input()
#get the base of the guess - an int number  between 6(included) to 10(included)
secretB = input()
#get the guess
secret = raw_input()
secretsNumbers = map(int, secret.split())
#turn the chain into a singular INT number in order to make sure each of its digits does not equal or exceeds base, using %10
secretsNumbersMerge = int(''.join(map(str,secretsNumbers)))
newSecretsNumbersMerge = secretsNumbersMerge
while newSecretsNumbersMerge!= 0:
    checker = newSecretsNumbersMerge%10
    if checker<secretBase:
        newSecretsNumbersMerge = newSecretsNumbersMerge/10
    else:
        print ("ERROR")
        quit()

guess = raw_input()
guessNumbers = map(int, guess.split())

So far it's all good. This really makes sure the secret meets the base demands. Now I'm at the main point where I should check for bulls and cows and I'm not quite sure how to proceed from this point.

My idea is to first check for Bulls and then remove them (so it won't get mixed with cows), and then check for cows but yeah.. I'm clueless.

I'm not even sure what to use in Python.

Thanks in advance!

Chandranshu
  • 3,669
  • 3
  • 20
  • 37
  • You need to read about [list comprehensions](http://docs.python.org/2/tutorial/datastructures.html#list-comprehensions). It is one of the most powerful features of the language and it'll change the way you think about problems. The solution to your question is almost present on that page. Also, once you understand the list comprehensions, you can write a one-liner for validating that the secret input is valid for the given base :) – Chandranshu Nov 15 '13 at 17:52

1 Answers1

1

Here is your code but please try to understand each line of it:

import re
from collections import Counter

#get the length of the guess - an int number  between 4(included) to 6(included)
secretL = input()
#get the base of the guess - an int number  between 6(included) to 10(included)
secretB = input()
#get the guess
secret = raw_input()

# Check if the secret input uses a digit greater than the base value
# You can use the check on the next line as well. It can be made faster
# too by using search in place of findall.
# if len(re.findall(r"[^0-" + str(secretB) + r" ]+", secret)):
if sum((i!=" " and int(i) > secretB) for i in secret) > 0:
  print("Error")
  quit()

secretNumbers = map(int, secret.split())
guess = raw_input()
guessNumbers = map(int, guess.split())

# Count the bulls by iterating over the two lists simultaneously
bulls = sum(i==j for i, j in zip(secretNumbers, guessNumbers))

# Remove the bulls before counting the cows
secretNumbers, guessNumbers = zip(*((x,y) for x,y in zip(secretNumbers, guessNumbers) if x!=y))

# Cows are defined as the elements present in both the guess and the secret
# but not in the right position.
# If we ignore duplicates, this is the way to go about it.
cows = len(set(secretNumbers) & set(guessNumbers))

## If we count [1,1,2,4] and [5,3,1,1] to have 2 cows, this is how it should be:
#counter = Counter(secretNumbers) & Counter(guessNumbers)
#cows = sum(counter.itervalues())

print(str(bulls) + " BULLS " + str(cows) + " COWS")

Let me know if something is not clear and I'll add an explanation.

Also, I'm not aware of the rules of mastermind and have inferred them from your description. I'm not clear how you got 0 cows for the second example.

UPDATE 1

if sum(i > secretB for i in secretNumbers) > 0: how does it work exactly?

Please use backticks(`) to quote small pieces of code in comments, questions or answers. Now, let's break down this code into easily understandable pieces:

  1. Consider the list comprehension [ i > secretB for i in secretNumbers]. This will generate a list of booleans. So, if your base is 6 and secretNumbers is [1,2,7,2,5], it will return [False, False, True, False, False]. (As per your comment, this is not the way the rule was to be interpreted but I'm pretty sure you can modify this condition to be what you need.)
  2. sum() is a very useful standard built-in function in Python which returns the sum of any iterable passed to it. So, sum([1,2,3,4]) will return 10.
  3. It remains to answer what does it mean to sum a list of boolean values. It might initially seem strange to add True to True this way, but I don't think it's unpythonic; after all, bool is a subclass of int in all versions since 2.3:

_

>>>issubclass(bool, int)
True

So, there you go: sum(i > secretB for i in secretNumbers) tells you exactly how many numbers in the input exceed the base.

UPDATE 2:

After a clarification from OP, the explanation of Update 1 doesn't apply to the code under discussion anymore though OP now understands the general technique of using list comprehensions to do powerful things.

Also, the condition for counting the COWS has been clarified and the code now lists two ways to do it - one, while ignoring duplicates; and second by counting each duplicate instance.

Community
  • 1
  • 1
Chandranshu
  • 3,669
  • 3
  • 20
  • 37