patternpythonMinor
Pig dice game with a human and a computer player
Viewed 0 times
humanwithplayergamedicecomputerandpig
Problem
To help my son with his programming skills, I am trying refresh my limited Python knowledge. I chose a simple dice game called Pig Dice.
```
import sys
import random
HUMAN = 0
COMPUTER = 1
HOLD = False
ROLL = True
class Die:
def __init__(self):
self.value = random.randint(1, 6)
def roll(self):
self.value = random.randint(1, 6)
@staticmethod
def rolled_one():
print("Rolled 1. \n")
def __str__(self):
return "Rolled " + str(self.value) + "."
class Box:
def __init__(self):
self.value = 0
def reset_box(self):
self.value = 0
def add_dice_value(self, dice_value):
self.value = self.value + dice_value
def __str__(self):
return "Box total:" + str(self.value)
class Player:
def __init__(self, name=None):
self.name = name
self.score = 0
def add_score(self, player_score):
self.score = self.score + player_score
def set_name(self, name):
self.name = name
def __str__(self):
return str(self.name) + ": " + str(self.score)
class ComputerPlayer(Player):
def __init__(self):
Player.__init__(self, name="ELIZA")
@staticmethod
def make_decision(box):
while box.value " + str(self.human) + "\t" + str(self.computer) + "\n"
class GameManager:
def __init__(self):
self.computer_player = ComputerPlayer()
self.human_player = HumanPlayer()
self.die = Die()
self.box = Box()
self.score = Score(self.human_player, self.computer_player, self.box)
self.computer = 0
self.turn = None
self.action = None
@staticmethod
def welcome():
print("" 70)
print("Welcome to Pig Dice!" .center(70))
print("" 70)
print("The objective is to be the first to reach 100 points." .center(70))
print("On each turn, the computer will roll a die." .center(70))
print("The die value will stored in a tempora
```
import sys
import random
HUMAN = 0
COMPUTER = 1
HOLD = False
ROLL = True
class Die:
def __init__(self):
self.value = random.randint(1, 6)
def roll(self):
self.value = random.randint(1, 6)
@staticmethod
def rolled_one():
print("Rolled 1. \n")
def __str__(self):
return "Rolled " + str(self.value) + "."
class Box:
def __init__(self):
self.value = 0
def reset_box(self):
self.value = 0
def add_dice_value(self, dice_value):
self.value = self.value + dice_value
def __str__(self):
return "Box total:" + str(self.value)
class Player:
def __init__(self, name=None):
self.name = name
self.score = 0
def add_score(self, player_score):
self.score = self.score + player_score
def set_name(self, name):
self.name = name
def __str__(self):
return str(self.name) + ": " + str(self.score)
class ComputerPlayer(Player):
def __init__(self):
Player.__init__(self, name="ELIZA")
@staticmethod
def make_decision(box):
while box.value " + str(self.human) + "\t" + str(self.computer) + "\n"
class GameManager:
def __init__(self):
self.computer_player = ComputerPlayer()
self.human_player = HumanPlayer()
self.die = Die()
self.box = Box()
self.score = Score(self.human_player, self.computer_player, self.box)
self.computer = 0
self.turn = None
self.action = None
@staticmethod
def welcome():
print("" 70)
print("Welcome to Pig Dice!" .center(70))
print("" 70)
print("The objective is to be the first to reach 100 points." .center(70))
print("On each turn, the computer will roll a die." .center(70))
print("The die value will stored in a tempora
Solution
Style review of current code
Reading up on PEP8 is never a bad thing. Mostly your code is clean, but there are several enhancements which can be made.
Code and class review
Previous was global comments, but here are some more localised to your choices of classes and code structure:
-
Try avoid one line methods – In most cases have a one line method makes it harder to understand code segments as you need to jump back and forth to see what that method actually did. If the code is simple and rather intuitive, avoid the one line method. If the method is needed as you use it in contexts like filtering, mapping or similar, they could be of use (but then again you can also use lambda's).
This is a balance, where you need to balance between readability and maintainability. No black and white answers exists here, and you need to go a little bit by gut feeling, and not everyone will agree with choices we make
Code refactored
Here is the code when all of the above has been taken into account:
```
import random
def input_number(prompt='Please enter a number: ', minimum=0, maximum=None):
"""Read a positive number with the given prompt."""
while True:
try:
number = int(input(prompt))
if (number maximum)):
print('Number is not with
Reading up on PEP8 is never a bad thing. Mostly your code is clean, but there are several enhancements which can be made.
- Add more space between methods – Methods (and functions) according to guidelines should have two new lines before them. This helps separating the methods from each other
- Methods and classes should have docstrings – All methods and classes should have docstrings describing why they exist.
- You mostly have good names – Most variables, classes and methods have names according to standard. The only deviation could arguably be that internal/private methods should be named
_snake_case()something.
- I'm not too fond of the
welcome()print formatting, but this is a minor point, and in my code refactor I've just left it as is. You could have used multi-line texts, or lists of texts, and various other options.
- Verify/validate input – When asking for input, it is often useful to verify that the user entered legal options. This can be done using an external method
- Version compatible calling of parent constructor – I'm not sure if you're using Python 3 or Python 2, but a way to call the parent constructor in both version are to use
super(HumanPlayer, self).__init__(name)where you replace the current class name instead ofHumanPlayer.
- Use
var += 1to increment variables – This looks a little neater than doubling the variable name
Code and class review
Previous was global comments, but here are some more localised to your choices of classes and code structure:
- Drop the
Scoreclass – Let each player handle its own score, and then you can drop this entirely
- Allow multiple players through a list of players – If you instead of two locked player variables, uses a list of players, you can pass how many human players and how many computer players to the constructor.
- Let the turn / current player be index into player list – Instead of duplicating code, for the specialised player variables, you can let the current player be an index into the player list. This will simplify logic quite a bit. You can then start utilising the object structure, by simply adding scores to the current player, get the name of current player, and so on directly. You could take it all the way, and have a dedicated variable which holds the current player, but I chose to use the index
- To change turns, add with modulo – To change turns, you can add one to the current player index, and take the modulo of number of players.
-
Try avoid one line methods – In most cases have a one line method makes it harder to understand code segments as you need to jump back and forth to see what that method actually did. If the code is simple and rather intuitive, avoid the one line method. If the method is needed as you use it in contexts like filtering, mapping or similar, they could be of use (but then again you can also use lambda's).
This is a balance, where you need to balance between readability and maintainability. No black and white answers exists here, and you need to go a little bit by gut feeling, and not everyone will agree with choices we make
- If possible avoid globals – In your case to define
HOLD = FalseandROLL = True. With good method names, it is much more intuitive to use the original values. Letkeep_rolling()returnTrueorFalsedirectly. The two others are eliminated as we now uses proper names for each player, and use the player list for access.
- Alternative handling of rolling
1– Instead of your logic, I opted for raising an exception when rolling1. This can be a matter of taste, but I kind of felt that it a nice flow to it to use a raisedRolledOneException.
- Avoid
set_variable(), when you keep it public anyway – It looked kind of strange to havePlayer.set_name(), when you can doPlayer.name = ...without violating guidelines. If you had chosen to havePlayer._name, and possibly had other side effects needed to be executed at the same time, you could have aset_name(), but as is was I thought it looked a little strange. I neither saw the need for all the__str__you had defined, but your mileage may vary...
- The
if __name__ == '__main__':pattern – Adding this pattern before calling themain()allows for your code to be runned directly as a script, but also to be easily included as a module. I've used it the code to ask for how many humans and computers are to play, and could easily have read that from the arguments as well. And still, if used as a module, you could initiate the game from another script if you felt like it.
Code refactored
Here is the code when all of the above has been taken into account:
```
import random
def input_number(prompt='Please enter a number: ', minimum=0, maximum=None):
"""Read a positive number with the given prompt."""
while True:
try:
number = int(input(prompt))
if (number maximum)):
print('Number is not with
Code Snippets
import random
def input_number(prompt='Please enter a number: ', minimum=0, maximum=None):
"""Read a positive number with the given prompt."""
while True:
try:
number = int(input(prompt))
if (number < minimum or
(maximum is not None and number > maximum)):
print('Number is not within range: {} to {}'.format(minimum, maximum))
else:
break
except ValueError:
print('You need to enter a number')
continue
return number
class RolledOneException(Exception):
pass
class Die:
"""A die to play with."""
def __init__(self):
self.value = random.randint(1, 6)
def roll(self):
"""Returns the rolled dice, or raises RolledOneException if 1."""
self.value = random.randint(1, 6)
if self.value == 1:
raise RolledOneException
return self.value
def __str__(self):
return "Rolled " + str(self.value) + "."
class Box:
"""Temporary score box holder class."""
def __init__(self):
self.value = 0
def reset(self):
self.value = 0
def add_dice_value(self, dice_value):
self.value += dice_value
class Player(object):
"""Base class for different player types."""
def __init__(self, name=None):
self.name = name
self.score = 0
def add_score(self, player_score):
"""Adds player_score to total score."""
self.score += player_score
def __str__(self):
"""Returns player name and current score."""
return str(self.name) + ": " + str(self.score)
class ComputerPlayer(Player):
cpu_names=['Eliza', 'BigBlue', 'Foo', 'Bar']
def __init__(self, number):
"""Assigns a cpu name from cpu_names, or Cpu#."""
if number < len(self.cpu_names):
name = self.cpu_names[number]
else:
name = 'Cpu{}'.format(number)
super(ComputerPlayer, self).__init__(name)
def keep_rolling(self, box):
"""Randomly decides if the CPU player will keep rolling."""
while box.value < (10 + random.randint(1, 35)):
print(" CPU will roll again.")
return True
print(" CPU will hold.")
return False
class HumanPlayer(Player):
def __init__(self, name):
super(HumanPlayer, self).__init__(name)
def keep_rolling(self, box):
"""Asks the human player, if they want to keep rolling."""
human_decision = input_number(" 1 - Roll again, 0 - Hold? ", 0, 1)
if human_decision == 1:
return True
else:
return False
class GameManager:
def __init__(self, humans=1, computers=1):
"""Initialises the game, optionally asking for human player names."""
self.players = []
if humans == 1:
self.players.append(HumanPlayer('Human'))
else:
for i in range(humans):
Context
StackExchange Code Review Q#110965, answer score: 5
Revisions (0)
No revisions yet.