6
\$\begingroup\$

I have written this small game for the "learn python the hard way" book. I ask for some opinions about it and how to improve it.

I know about pygame and others game engines but all the game runs in the terminal. The text file orc.txt, cyclops.txt, etc are ASCII drawings

from PIL import Image
import numpy as np
import msvcrt
from time import sleep
from random import choice, randint
previous = 15 #start position tile
def createmap():
 """
 The map is sketched in bmp, I make a array
 to use it like a tileset
 """
 global bmp
 bmp = Image.open('sketch.bmp')
 bmp_data = bmp.getdata()
 bmp_array = np.array(bmp_data)
 global map
 map = bmp_array.reshape(bmp.size)
def array_to_text_map():
 """ Tileset
 # = Wall
 . = sand
 + = door
 e = enemy
 D = dragon
 $ = gold
 @ = the player
 numbers as due to the 16 color palette used
 in the sketch
 """
 text_map = open('map.txt', 'w')
 for x in map:
 for y in x:
 if y == 0:
 text_map.write('#')
 elif y == 3:
 text_map.write('.')
 elif y == 8:
 text_map.write('+')
 elif y == 9:
 text_map.write('e')
 elif y == 10:
 text_map.write('D')
 elif y == 12:
 text_map.write('$')
 elif y == 13:
 text_map.write('@')
 elif y == 15:
 text_map.write(' ')
 text_map.write('\n')
 text_map.close()
def print_text_map():
 text_map = open('map.txt')
 for x in range(bmp.size[1]):
 print(text_map.readline(), end='')
def move_player_by_key():
 wkey = msvcrt.getch
 if wkey() == b"\xe0":
 key2 = wkey()
 if key2 == b"H":
 move_in_matrix('up')
 elif key2 == b"M":
 move_in_matrix('right')
 elif key2 == b"K":
 move_in_matrix('left')
 elif key2 == b"P":
 move_in_matrix('down')
 else:
 exit()
def position():
 ver = np.where(map == 13)[0][0]
 hor = np.where(map == 13)[1][0]
 return ver, hor
def move_in_matrix(direction):
 ver, hor = position()
 try:
 if direction == 'up':
 analize(ver-1 , hor)
 elif direction == 'right':
 analize(ver , hor+1)
 elif direction == 'left':
 analize(ver , hor-1)
 elif direction == 'down':
 analize(ver+1 , hor)
 except IndexError:
 console.append('Not that way')
def analize(verm, horm):
 """Take decision for each tile
 verm and horm = next position
 ver and hor = actual position
 previous = keeps tile number for later
 """
 global previous
 ver, hor = position()
 def restore(ver, hor, previous):
 """Restore color of the leaved tile"""
 map[ver, hor] = previous
 if map[verm, horm] == 0:
 pass
 elif map[verm, horm] == 3:
 restore(ver, hor, previous)
 previous = map[verm, horm]
 map[verm, horm] = 13
 elif map[verm, horm] == 8:
 print('Open the door (yes/no)?', end='')
 answer = input(' ')
 if door(answer):
 restore(verm, horm, 15)
 console.append('The door was opened')
 console.append(doors_dict[(verm, horm)]) #show the description
 #attached to the door
 elif map[verm, horm] == 9:
 print('Do you want to fight against the monster (yes/no)?', end='')
 answer = input(' ')
 result = fight(answer)
 if result and randint(0,1) == 1:
 restore(verm, horm, 12)
 elif result:
 restore(verm, horm, 15)
 elif map[verm, horm] == 10:
 print('the beast seems asleep, you want to wake her?(yes/no)?', end='')
 answer = input(' ')
 result = fight(answer, dragon=True)
 if result:
 win()
 elif map[verm, horm] == 12:
 print("You want to grab the items in the floor (yes/no)?", end='')
 if input(' ') == 'yes':
 gold()
 restore(verm, horm, 15)
 elif map[verm, horm] == 15:
 restore(ver, hor, previous)
 previous = map[verm, horm]
 map[verm, horm] = 13
def door(answer):
 if answer == 'yes':
 return True
 elif answer == 'no':
 return False
 else:
 console.append('Invalid command')
def identify_doors():
 """Hardcode: Identify each door and zip() it with
 a description stored previously
 """
 doors_array = np.where(map == 8)
 doors = zip(doors_array[0], doors_array[1])
 dict_doors = {}
 narrate = open('narrate.txt')
 for x in doors:
 dict_doors[x] = narrate.readline()
 return dict_doors
def fight(answer, dragon=False):
 if answer == 'yes':
 if dragon == False:
 monster = choice([Orc(), Goblin(), Cyclops()])
 else:
 monster = Dragon()
 monster.show_appearance()
 print('Fighting against', monster.name)
 sleep(2)
 while True:
 monster.show_appearance()
 print(monster.name, 'is attacking you', end = '')
 answer = input('fight (1) or defend (2)? ')
 if answer == '1':
 player.defense = 1 + player.armor
 monster.life = monster.life - player.damage
 elif answer == '2':
 player.defense = 1.5 + player.armor
 else:
 print('Invalid command')
 continue
 print(monster.name, 'counterattack!!')
 player.life = player.life - (monster.damage / player.defense)
 print(monster.life, 'remaining', monster.name, 'life')
 print(player.life, 'remaining Player life')
 if player.life <= 0:
 print('The End')
 exit()
 if monster.life <= 0:
 print('\n' * 5)
 print('Enemy defeated')
 print('You earn', monster.gold, 'gold coins')
 player.gold += monster.gold
 break 
 sleep(3)
 return True
 else:
 return False
def moredamge(val):
 player.damage += val
def morearmor(val):
 player.armor += val
golds = {'Fire sword': (moredamge, 20),
 'Oak shield': (morearmor, 0.1),
 'Anhilation sword': (moredamge, 40),
 'Iron shield': (morearmor, 0.2),
 'Siege shield': (morearmor, 0.5)
 }
def gold():
 bunch = randint(10, 200)
 print('You have found', bunch, 'gold coins')
 player.gold += bunch
 sleep(1)
 print('Is there anything else?')
 if randint(0, 10) > 7:
 obtained = choice(list(golds))
 print('You have get', obtained)
 golds[obtained][0](golds[obtained][1]) #access to key: (function, quantity)
 del golds[obtained]
 else:
 print('Oooohh.. nothing else')
 sleep(2)
def win():
 print('You have win the game')
 print('You get', player.gold, 'gold coins!!')
 exit()
class Orc():
 def __init__(self):
 self.name = 'Orc'
 self.life = 100
 self.damage = 10
 self.defense = 1
 self.gold = 100
 self.appearance = open('orc.txt')
 def show_appearance(self):
 print(self.appearance.read())
class Player(Orc):
 def __init__(self):
 self.life = 1000
 self.damage = 20
 self.gold = 0
 self.appearance = open('knight.txt')
 self.armor = 0
class Goblin(Orc):
 def __init__(self):
 self.name = 'Goblin'
 self.life = 50
 self.damage = 10
 self.gold = 50
 self.appearance = open('goblin.txt')
class Dragon(Orc):
 def __init__(self):
 self.name = 'Dragon'
 self.life = 400
 self.damage = 40
 self.gold = 2000
 self.appearance = open('dragon.txt')
class Cyclops(Orc):
 def __init__(self):
 self.name = 'Cyclops'
 self.life = 150
 self.damage = 20
 self.gold = 120
 self.appearance = open('cyclops.txt')
def presentation():
 print('\n'*2)
 player.show_appearance()
 print('Welcome hero, are you ready for fight?')
 input() 
if __name__ == "__main__":
 player = Player()
 presentation()
 console = []
 createmap()
 array_to_text_map()
 doors_dict = identify_doors()
 print_text_map()
 while True:
 move_player_by_key()
 array_to_text_map()
 print_text_map()
 if console:
 for x in console:
 print(x)
 else:
 print()
 console = []
asked Feb 19, 2014 at 22:07
\$\endgroup\$

1 Answer 1

7
\$\begingroup\$

I'll assume you're fairly new to Python; tell me if you feel this stuff is too basic, and I'll start criticizing you for things that you don't deserve to be criticized for.

State

Ok, so the first thing that made me go "eww" in the code was global. In general, whenever you use global, you're probably doing something wrong. If I was to write your createmap function, it might look something more like this:

def createmap():
 """
 The map is sketched in bmp, I make a array
 to use it like a tileset
 """
 bmp = Image.open('sketch.bmp')
 bmp_data = bmp.getdata()
 bmp_array = np.array(bmp_data)
 map = bmp_array.reshape(bmp.size)
 return bmp, map

Instead of using a global bmp and map variable, I'm returning the non-global bmp and map variables. This means that I have a lot more flexibility in calling the function:

# I can use it to create the old global variables:
global bmp, map
bmp, map = createmap()
# Or some local ones
local_bmp, local_map = createmap()
# But most importantly, I can use it to create multiple sets of them
bmp1, map1 = createmap()
bmp2, map2 = createmap()

The last case is the most important; using your version of createmap(), when I call createmap() for the second time, it overwrites the previous bmp and map variables. This might not seem like a big problem; why would you ever need more than one map? but it makes it much harder to test things when they change global variables.

We can do similar things to your other functions to avoid using global variables. The array_to_text_map function currently uses the map global variable, but we can replace that with a map argument. This way we will be able to convert any map to a text map, not just the one stored in the global map variable.

One downside to this is that it does require more typing; I have to explicitly pass around all of my state. However, one of the core tenets of Python is that explicit is better than implicit, so get used to it ;) I should mention that people often wrap up their program's commonly used variables into classes, though that's probably more complex than you are looking for right now.

Switch Statements

Another thing that makes me feel uncomfortable about the code is when you have long chains of elif statements:

if y == 0:
 text_map.write('#')
elif y == 3:
 text_map.write('.')
elif y == 8:
 text_map.write('+')
elif y == 9:
 text_map.write('e')
elif y == 10:
 text_map.write('D')
elif y == 12:
 text_map.write('$')
elif y == 13:
 text_map.write('@')
elif y == 15:
 text_map.write(' ')

This is ugly, and violates the DRY principle. What you are doing here is looking up a value, based on the value of the variable y. The Pythonic (and sane) way of doing this is to define a dictionary (mapping) from the values of y to the string that should be written to text_map:

TEXT_MAP_CHARS = {
 0: "#",
 3: ".",
 8: "+",
 9: "e",
 10: "D",
 12: "$",
 15: " "
}

We can then look up the correct character, and then write that to the file:

char = TEXT_MAP_CHARS[y]
text_map.write(char)

Which is much neater, and doesn't mix up program data (the mapping from numbers to characters) and program logic (the writing of the character to a file based on the value of y).


With that said, I would praise you for your style. For every problem your code may have semantically, it is formatted very well (hooray for PEP8)

answered Feb 20, 2014 at 0:20
\$\endgroup\$

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.