3
\$\begingroup\$

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()
Ben A
10.8k5 gold badges38 silver badges103 bronze badges
asked Mar 25, 2020 at 14:37
\$\endgroup\$
1
  • 7
    \$\begingroup\$ Put your gameloop() call inside a if __name__ == '__main__': block so it doesn't run when you import the module. \$\endgroup\$ Commented Mar 25, 2020 at 14:46

1 Answer 1

4
\$\begingroup\$

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) use isinstance(self, Player).

  • Instead of action = random.randint(1, 4) coupled with 4 if-statements, there's a handy little function in random called choice (have a look here). You could also make your AI class much smaller by rethinking how the AI differs (or doesn't differ) from a Player.

  • 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 removed str() since you don't need it):

    print(f"""You:
    Castle: {player.castle}
    Food: {player.food}
    Stone: {player.stone}
    Villagers: {player.villagers}
    Warriors: {player.warriors}""")
    
answered Mar 26, 2020 at 18:28
\$\endgroup\$
2
  • 3
    \$\begingroup\$ There's a typo here but it's not enough characters to submit an edit: instanceof should be isinstance \$\endgroup\$ Commented Mar 26, 2020 at 20:49
  • \$\begingroup\$ Thank you very much! Lots of good advice. \$\endgroup\$ Commented Mar 27, 2020 at 22:09

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.