I am making a super simple and dumb terminal text game to try out unittest in python. I have ran into a problem. When I run the test, the game loop is ran, and I have to manually insert input to continue to the tests.
Is there a way to not run the game loop when I run my tests, so I can test the class methods only?
Any other advice on the code in general would be helpful appreciated as well.
Game concept: Player is prompted to build, gather, recruit, or attack every turn and when someone's castle health is <= 0, game is over.
import os, random
class Player():
def __init__(self):
self.castle = 1
self.villagers = 1
self.warriors = 0
self.food = 0
self.stone = 0
def takeTurn(self):
action = 0
action = int(input("1) Build\n2) Gather\n3) Recruit\n4) Attack\n"))
if action == 1:
self.build()
elif action == 2:
self.gather()
elif action == 3:
self.recruit()
elif action == 4:
self.attack()
else:
self.takeTurn()
def build(self):
if self.stone >= 1:
self.castle += 1
self.stone -= 1
else:
os.system('clear')
if self.__class__.__name__ == "Player":
print("You don't have enough stone!\n")
printMenu()
self.takeTurn()
def gather(self):
self.food += self.villagers
self.stone += self.villagers
def recruit(self):
if self.food >= 1:
self.warriors += 1
self.food -= 1
else:
os.system('clear')
if self.__class__.__name__ == "Player":
print("You don't have enough food!\n")
printMenu()
self.takeTurn()
def attack(self):
if self.warriors >= 1:
if self.__class__.__name__ == "Player":
ai.castle -= self.warriors
elif self.__class__.__name__ == "AI":
player.castle -= self.warriors
class AI(Player):
def __init__(self):
super().__init__()
def makeMove(self):
action = 0
# Win if player castle weak
if player.castle == self.warriors:
player.castle -= self.warriors
# Build if castle weak
elif self.castle == player.warriors:
if self.stone >= 1:
self.castle += 1
else:
action = random.randint(2, 4)
else:
action = random.randint(1, 4)
if action == 1:
super().build()
if action == 2:
super().gather()
if action == 3:
super().recruit()
if action == 4:
super().attack()
player = Player()
ai = AI()
def printMenu():
print("You:\nCastle: " + str(player.castle) + " Food: " + str(player.food) + " Stone: " + str(player.stone) + " Villagers: " + str(player.villagers) + " Warriors: " + str(player.warriors) + "\n")
print("Computer:\nCastle: " + str(ai.castle) + " Food: " + str(ai.food) + " Stone: " + str(ai.stone) + " Villagers: " + str(ai.villagers) + " Warriors: " + str(ai.warriors) + "\n")
def gameLoop():
# MAIN LOOP
playing = False
if playing:
printMenu()
player.takeTurn()
ai.makeMove()
if ai.castle <= 0:
playing = False
print("You Win!\n")
elif player.castle <= 0:
playing = False
print("You Lose!")
else:
os.system('clear')
gameLoop()
import unittest
from game.main import Player
class TestPlayer(unittest.TestCase):
def setUp(self):
self.player = Player()
def test_build(self):
# Should fail
self.player.castle = 2
self.player.stone = 1
self.player.build()
self.assertEqual(self.player.castle, 1)
if __name__ == '__main__':
unittest.main()
1 Answer 1
You already have the answer to your "bug" given in the comments, but I will try to give you a more in-depth review. Nevertheless, I will repeat the aforementioned solution: to avoid the program auto-running on import, but your main call inside of a if __name__ == "__main__"
(read more here if interested).
For your __init__
method, having your method initializers as (default) arguments tend to give more flexibility down the road as the project grows:
def __init__(self, castle=1, villagers=1, warriors=0, food=0, stone=0):
self.castle = castle
self.villagers = villagers
self.warriors = warriors
self.food = food
self.stone = stone
For your first real method, I would recommend staying with the Python standard of snake_case
over camelCase
. Furthermore, initialising the variable action
to 0 doesn't really do you anything since you overwrite it in the following line. And since you rely on that input I think you should figure out what to do when you don't get an int
, or if you get something like 11 (because 11 here would likely be a typo, given the options).
This, quite naturally, leads on to the purpose of testing (yay!). A nice thing about tests is that they allow you to think about what exactly you want to happen when you create an object or call on a function. For your takeTurns
function, that would maybe be that you're only allowed to enter digits, and if you don't enter a single digit in the range 0–5 it throws an exception or it asks for another input. You would then basically implement your function (after the tests) to meet all the requirements you set forth in the beginning. You may then also find that maybe you were thinking about it the wrong way; in this case it could be that maybe take_turns
(I renamed it for you...) shouldn't be asking for input()
but rather be accepting an argument take_turns(user_choice)
.
To finalise, I'll add some smaller pieces of advice.
Instead of
self.__class__.__name__ == "Player":
you could (and should) useisinstance(self, Player)
.Instead of
action = random.randint(1, 4)
coupled with 4if
-statements, there's a handy little function inrandom
calledchoice
(have a look here). You could also make yourAI
class much smaller by rethinking how the AI differs (or doesn't differ) from aPlayer
.f-strings are super nice. Instead of
print("You:\nCastle: " + str(player.castle) + " Food: " + str(player.food) + " Stone: " + str(player.stone) + " Villagers: " + str(player.villagers) + " Warriors: " + str(player.warriors) + "\n")
, you could have (and notice that I removedstr()
since you don't need it):print(f"""You: Castle: {player.castle} Food: {player.food} Stone: {player.stone} Villagers: {player.villagers} Warriors: {player.warriors}""")
-
3\$\begingroup\$ There's a typo here but it's not enough characters to submit an edit:
instanceof
should beisinstance
\$\endgroup\$LimeHunter7– LimeHunter72020年03月26日 20:49:02 +00:00Commented Mar 26, 2020 at 20:49 -
\$\begingroup\$ Thank you very much! Lots of good advice. \$\endgroup\$CliffJ– CliffJ2020年03月27日 22:09:30 +00:00Commented Mar 27, 2020 at 22:09
Explore related questions
See similar questions with these tags.
gameloop()
call inside aif __name__ == '__main__':
block so it doesn't run when you import the module. \$\endgroup\$