# [Tutor] Struggling with logic .....

Alan Gauld alan.gauld at btinternet.com
Sat Jan 19 15:33:52 CET 2013

```On 19/01/13 11:14, Barry Drake wrote:

> I noticed it doesn't get the scoring right when there are duplicate
> digits!

You haven't given an example with duplicate digits so I'll need
to take your word for it!

> line is:  ['1', '2', '3', '4']   Length:  4
> Random Code:  (3, 4, 2, 3)
> Result: 0  - you have a correct number in an incorrect position
> Result: 0  - you have a correct number in an incorrect position
> Result: 0  - you have a correct number in an incorrect position
> Result: -1  - you have an incorrect number

Looks good to me

> line is:  ['4', '2', '1', '5']   Length:  4
> Random Code:  (3, 4, 2, 3)
> Result: 0  - you have a correct number in an incorrect position
> Result: 0  - you have a correct number in an incorrect position
> Result: -1  - you have an incorrect number
> Result: -1  - you have an incorrect number

Same here. Sooo... Some comments on the code.

##########################
> import random

> # generate random code
> code = (random.randrange(1, 6), random.randrange(1, 6),
>        random.randrange(1, 6), random.randrange(1, 6))
>
> line    = ['','','','']            # line from user

You probably don't need this although it does no harm

> cc  = []            # code count
> cl  = []            # line count

Not sure why you need a list for these....

> matched    = 0            # number of matched items
> result  = [-1, -1, -1, -1]  # result

> user_own    = False
> max_attempts    = 10    # XXX How to define a constant variable?

Yes, although convention puts constants in uppercase so:

MAX_ATTEMPTS = 10

It just makes the intention clear. This is not supposed to be changed by
the program.

> attempts    = 0
>
> while not user_own and attempts < max_attempts:
>    print "I have chosen a random four digit number.  You have to
>    guess what it is."
>    input_str = str(input("Give me your guess at the four digit number
> ..... Enter four digits between 1 and 6: "))
>    for i in range(len(input_str)):
>         line[i] = input_str[i]

Probably easier to just use list(), maybe with a length check too:

if len(input_str) == 4:
line = list(input_str)
else:
# print an error message
continue

>    print "line is: ", line, "  Length: ", len(line) # debug hint

Catch the error don't just display it...

>    if len(line) != 4:
>        # won't be considered an attempt
>       print "Please enter only 4 digits "
>       continue

I'd do this earlier, see above

>    # convert list members in integer
>     line = [int(l) for l in line]

You could do this in the conversion above which becomes:

if len(input_str) == 4:
line = [int(d) for d in list(input_str)]
else:
# print an error message
continue

>    # TODO check for color in range 1 to 6????
>
>    # game has 6 colors
>    cc = [0, 0, 0, 0, 0, 0]
>    cl = [0, 0, 0, 0, 0, 0]

This just overwrote the initialisation at the top rendering it irrelevant.

And where do the colors fit in? Your output only used numbers- no
reference to colors so while debugging get rid of the irrelevant
stuff... it only distracts.

>   matched = 0
>   for i in range(len(line)):
>       if line[i] == code[i]:
>           # matched guess
>           matched += 1
>       else:
>           cc[code[i] - 1] += 1
>           cl[line[i] - 1] += 1

I don't understand what this else clause is doing....

>    if matched == 4:
>        user_own = True
>        continue
>
>    # user is not own, evaluate user guesses
>    i      = 0
>    result = [-1, -1, -1, -1]

again you are overwriting the initialised value so its pointless doing
the original initialisation.

>    while i < matched:
>        # color matched along with position
>        result[i] =  1
>        i         += 1
>
>    ri = i
>    for i in range(len(cc)):
>        x = min(cc[i], cl[i])

OK, at this point I give up, this is way too complicated for what you
are doing.

Consider using sets.
If you create your line as a set of tuples (value,position)
and the same for your input.

The intersect of the two sets is your correct value and position.

Remove these items from both sets and store as the 'matched' set.

For each item in the remaining input set see if there is a corresponding
value in the line set, if so, remove it from both
and store in the 'nearly' set.

Now report how many items in matched and nearly.

Example:

1225 -> lineset = {(1,0),(2,1),(2,2),(5,3)}

1122 -> inset = {(1,0),(1,1),(2,2),(2,3)}

intersect = {(1,0),(2.2)}  -> matchset

remainder now:

lineset = {(2,1),(5,3)}
inset = {(1,1),(2,3)}

The first inset item does not match any value in linest.
The second inset item matches the value of first item in
lineset so add it to nearlyset:

nearlyset = {(2,3)}

So the final result is:

matchset = {(1,0),(2.2)}  -> len = 2
nearlyset = {2,3)         -> len = 1

So result is 2 perfect matches and 1 in wrong position.

Most of this can be done using the python set operations/methods...

I think you'll find it easier...

--
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/

```