patternpythonMinor
Chopsticks game in Python
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
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
However, this looks a bit cryptic and can probably be improved. Indeed using the
Making code clearer
In the following code:
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:
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 :
can be rewritten:
Do not repeat yourself
In :
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):
The same idea can be reapplied in different places at different levels.
Then, the whole
This could be reduced further but I am still thinking about the best way to do so.
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 == 0can be rewritten:
self.person_left == self.person_right == 0Do 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 Falseit 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 FalseThe 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 FalseThis 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 == 0self.person_left == self.person_right == 0Context
StackExchange Code Review Q#126603, answer score: 4
Revisions (0)
No revisions yet.