HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Chopsticks game in Python

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
gamechopstickspython

Problem

As a beginning project, I decided to code something that plays the game Chopsticks in Python 3. I'm using the Minimax algorithm, and exploring the tree using simple recursion. One of the main problems is performance. I can only search through about five moves deep; anything more takes too long. How can I improve my performance?

This is my first small to middle sized project, after doing some simple stuff (<20 lines). This is not homework, a challenge, etc. I'm just doing it for fun (which it is), also to finally beat my friend at chopsticks.

As of right now, this is pretty useless, at least in the beginning stages, because it can't think far enough ahead to actually reach some, or any, leaf nodes.

```
import itertools
from copy import deepcopy

class State:
def __init__(self):
self.person_left = 1
self.person_right = 1
self.computer_left = 1
self.computer_right = 1
self.is_computer_turn = False

def tap(self, is_to_left, is_from_left):
if self.is_computer_turn:
if is_to_left:
if is_from_left:
if self.computer_left != 0 and self.person_left != 0:
self.person_left += self.computer_left
else:
return False
else:
if self.computer_right != 0 and self.person_left != 0:
self.person_left += self.computer_right
else:
return False
else:
if is_from_left:
if self.computer_left != 0 and self.person_right != 0:
self.person_right += self.computer_left
else:
return False
else:
if self.computer_right != 0 and self.person_right != 0:
self.person_right += self.computer_right
else:
return False

Solution

A better use of itertools

You wrote itertools.product(*((True, False), (True, False))) which shows knowledge of the itertools package which is good (because it is so awesome) and of the star-argument syntax (I ignore what the real name is).

However, this looks a bit cryptic and can probably be improved. Indeed using the repeat argument, you could simply write :

itertools.product((True, False), repeat=2)


Making code clearer

In the following code:

for arg in itertools.product((True, False), repeat=2):
    testing_state = deepcopy(state)
    if testing_state.tap(*arg):
        child_nodes.append(testing_state)


You've used star-arguments again. I think that the code would be much clearer by using tuple unpacking so that you can give proper name to the values:

for to_left, from_left in itertools.product((True, False), repeat=2):
    testing_state = deepcopy(state)
    if testing_state.tap(to_left, from_left):
        child_nodes.append(testing_state)


Having fun with chained comparisons

From the doc:


Comparisons can be chained arbitrarily, e.g., x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).

In your case, things like :

self.person_left == 0 and self.person_right == 0


can be rewritten:

self.person_left == self.person_right == 0


Do not repeat yourself

In :

if is_from_left:
    if self.computer_left != 0 and self.person_left != 0:
        self.person_left += self.computer_left
    else:
        return False
else:
    if self.computer_right != 0 and self.person_left != 0:
        self.person_left += self.computer_right
    else:
        return False


it looks like we are doing the same thing twice but using a different value. You could easily rewrite this with less duplicated code (the variable name introduced is terrible):

value = self.computer_left if is_from_left else self.computer_right
if value != 0 and self.person_left != 0:
    self.person_left += value 
else:
    return False


The same idea can be reapplied in different places at different levels.

Then, the whole tap functions becomes :

if self.is_computer_turn:
    computer_value = self.computer_left if is_from_left else self.computer_right
    if is_to_left:
        if computer_value != 0 and self.person_left != 0:
            self.person_left += computer_value
        else:
            return False
    else:
        if computer_value != 0 and self.person_right != 0:
            self.person_right += computer_value
        else:
            return False
else:
    person_value = self.person_left if is_from_left else self.person_right
    if is_to_left:
        if person_value != 0 and self.computer_left != 0:
            self.computer_left += person_value
        else:
            return False
    else:
        if person_value != 0 and self.computer_right != 0:
            self.computer_right += person_value
        else:
            return False


This could be reduced further but I am still thinking about the best way to do so.

Code Snippets

itertools.product((True, False), repeat=2)
for arg in itertools.product((True, False), repeat=2):
    testing_state = deepcopy(state)
    if testing_state.tap(*arg):
        child_nodes.append(testing_state)
for to_left, from_left in itertools.product((True, False), repeat=2):
    testing_state = deepcopy(state)
    if testing_state.tap(to_left, from_left):
        child_nodes.append(testing_state)
self.person_left == 0 and self.person_right == 0
self.person_left == self.person_right == 0

Context

StackExchange Code Review Q#126603, answer score: 4

Revisions (0)

No revisions yet.