I have decided to create a discount Pokemon battle simulator using a module called guizero
. It's basically a wrapper for Tkinter that is easier to learn. Recently, I started to learn OOP and thought that this project would be a good practice for my "new-found skills". Someone in my computing group looked at my code and told me it was terrible so I thought I'd get someone else's opinion.
Edit
I just want to point out that I've been trained to comment everything I code to a standard the a beginner could understand it.
This is the code (you will have to download guizero to make it work):
# Import guizero for the GUI and random
import guizero as gui
import random
# Create an empty list for the Animals
actives = []
# Create an immutable list (tuple) for default animals
DEFAULT_ATTRS = (('Cow', 100, 10, 15, 4),
('Chicken', 40, 50, 40, 5),
('Duck', 45, 35, 70, 2))
# Create a function that checks if all values in a list are equal
def all_equal(iterable):
for i in range(len(iterable)):
if iterable[i-1] != iterable[i]:
return False
return True
# Create an Animal class for the animals
class Animal:
# Create an immutable value so that another values can't change the string
# Make a string that can be formated and evaluated
POWERSUM = 'int((({1}+{2})*{3})/{0})'
def __init__(self,name=None,strength=None,speed=None,skill=None,age=None):
assign = all_equal([name,strength,speed,skill,age]) and name == None
self.assigned = not assign
if assign:
return None
self.optimum = 50
self.name = name.title()
attr = [strength,speed,skill]
while sum(attr) > random.randint(180,220):
# If the sum is greater than 220 (or less)
# Change the max and the min values
attr[attr.index(max(attr))] -= random.randint(1,9)
attr[attr.index(min(attr))] += random.randint(1,5)
self.strength, self.speed, self.skill = attr
self.fitness = 100
self.attr = attr[:]
self.active = True
# Create a list with the values [number of battles, battles lost]
self.battles = [0,0]
self.age = age
self.power = 0
def __repr__(self):
# Create the display string
# Name. Stats: Strength, Spped, Skill and Age
attr = self.attr[:]+[self.age]
return '{}. Statistics: {},{},{} and {}'.format(self.name,*attr)
def returnPower(self):
# Get the power. The optimum age is 50
# Effectively create a parabola effect on the age
if self.age > 50:
age = self.optimum / (101-self.age)
else:
age = self.optimum / self.age
self.power = eval(self.POWERSUM.format(age,*self.attr))
return self.power
# Add the three default values
for attr in DEFAULT_ATTRS:
actives.append(Animal(*attr))
class BattleWindow(gui.App):
# Create a class that creates a GUI,
# avoiding the need for global variables; they're all attributes
def __init__(self,*animals):
super().__init__(title='Animals Battles',layout='grid')
# Create the function so that if the window is closed,
# it automatically opens the menu window
self.on_close(self.cancel)
texts = [[],[]]
for i,person in enumerate(['Animal Selected','Opponent']):
texts[i].append(person)
for cate in ['Strength','Skill','Speed','Age','Fitness','Power']:
texts[i].append(cate)
buttons = ((self.power, 'Power' ,[0,0]),
(self.opponent,'Opponent' ,[1,0]),
(self.battle, 'Battle' ,[2,0]),
(self.firstaid,'First aid',[3,0]))
for func,text,grid_xy in buttons:
self.aidbtn = gui.PushButton(self,func,text=text,grid=grid_xy)
self.animals = list(animals)
# Create 2 'empty' animals that can't do anything
# just in case the user tries to do something
self.chosen = Animal()
self.opponent = Animal()
self.displays = [[],[]]
self.options = ['None']
for animal in animals:
self.options.append(animal.name)
# Create a Combo to choose the animal
self.combo = gui.Combo(self,self.options,command=self.disp,grid=[0,2])
for i,text in enumerate(texts):
for x,tx in enumerate(text):
pos = [[x],[x]]
if i%2 == 0:
pos[0].append(1)
pos[1].append(2)
else:
pos[0].append(3)
pos[1].append(4)
gui.Text(self,text=tx+': ',grid=pos[0],align='left')
if tx != 'Animal Selected':
self.displays[i].append(gui.Text(self,grid=pos[1]))
# Display the GUI so that everything shows up
self.display()
def battle(self):
fitness = 'fitness'
if not (hasattr(self.chosen,fitness) or hasattr(self.opponent,fitness)):
gui.warn('No opponent!','You need an opponent!')
return
# Decrease the fitnesses of the animals by 75% of the value
self.opponent.fitness *= 0.75
self.chosen.fitness *= 0.75
# Add 1 to the number of battles
self.chosen.battles[0] += 1
self.opponent.battles[0] += 1
# If power has not yet been calculated,
# return so that the battle never happens
if self.displays[0][-1].get() == 'N/A':
return
if self.opponent.power > self.chosen.power:
winner = self.opponent
self.chosen.fitness *= 0.75
self.chosen.battles[1] += 1
else:
winner = self.chosen
self.opponent.fitness *= 0.75
self.chosen.battles[1] += 1
gui.info('The winner is...','The Winner is ... {}'.format(winner.name))
# Set the fitness display to the fitness to 2d.p.
self.displays[0][-2].set(round(self.chosen.fitness,2))
self.displays[1][-2].set(round(self.opponent.fitness,2))
# Check if either fitness is less than 1 as
# 0 can never be reached
if self.opponent.fitness < 1 or self.chosen.fitness < 1:
if self.opponent.fitness < 1:
self.opponent.active = False
name = self.chosen.name
popname = self.opponent.name
x = 1
if self.chosen.fitness < 1:
self.chosen.active = False
name = 'None'
popname = self.chosen.name
x = 0
# Clear the displays if the fitnesses are less than 1
for disp in self.displays[x]:
disp.clear()
# Remove the name from the dropdown options
# then destroy the combo and create a new one
# The new combo is then set to either the current
# animal or None if the user animal faints
self.options.remove(popname)
self.combo.destroy()
self.combo = gui.Combo(self,self.options,grid=[0,2])
self.combo.set(name)
# Get rid of the Animal object from the animals so that
# the random opponent can't be one of the fainted ones
actives.pop([i.name for i in actives].index(popname))
def cancel(self):
# Go back to the menu system
self.destroy()
Menu()
def disp(self,_):
# If the combo is None, set the displays to N/A
if self.combo.get() == 'None':
for disp in self.displays[0]:
disp.set('N/A')
self.chosen = self.animals[self.options.index(self.combo.get())-1]
# Create a copy of the attr attribute of self.chosen.attr
# Next add the age and the fitness to the list
attrs = self.chosen.attr[:]
attrs.append(self.chosen.age)
attrs.append(self.chosen.fitness)
# Next change the displays to the
# appropriate values
for i in range(len(attrs)):
self.displays[0][i].set(attrs[i])
# Finally set the 'Power' display to N/A
self.displays[0][-1].set('N/A')
def firstaid(self):
# Create a function that allows self.chosen to get more fitness
if self.chosen.battles[0] == 0:
return
# Check if the battle win percentage is high enough to get first aid
if 100 * (self.chosen.battles[1] / self.chosen.battles[0]) > 60:
if self.chosen.fitness > 50:
amount = 100 - self.chosen.fitness
else:
amount = 50
self.chosen.fitness += amount
self.displays[0][-2].set(round(self.chosen.fitness,2))
# Make the button disabled so that it can't be pressed again
self.aidbtn.config(state=gui.DISABLED)
else:
gui.warn('Too many losses','You haven\'t won enough battles!')
def opponent(self):
# Randomly choose an enemy. While that enemy
# is the same as the 'chosen', choose again
value = random.choice(actives)
while value == self.chosen:
value = random.choice(actives)
self.opponent = value
# Create a copy of the opponent attrs
# Then add the age, fitness and name
attrs = self.opponent.attr[:]
attrs.append(self.opponent.age)
attrs.append(self.opponent.fitness)
attrs.insert(0,self.opponent.name)
# Add the displays for the opponent
for i in range(len(attrs)):
self.displays[1][i].set(attrs[i])
self.displays[1][-1].set('N/A')
def power(self):
# Set the text to the power. Doesn't need
# the value to be assigned; happens in the returnPower() function
if self.chosen.assigned:
self.displays[0][-1].set(self.chosen.returnPower())
if self.opponent.assigned:
self.displays[1][-1].set(self.opponent.returnPower())
# Create the default window that creates
# a menu system
class Menu(gui.App):
def __init__(self):
super().__init__(title='Menu System',layout='grid',height=300)
gui.Text(self,text='Please choose an option',grid=[0,0])
# Create a 2d tuple containing the infos
options = (('Add new animal',self.addNew,[1,0]),
('Battle!!!',self.battle,[2,0]),
('Delete animal',self.delete,[3,0]))
# Create a list containing the names of the
# animals for leter
self.names = [i.name for i in actives]
# Create the buttons for the options
for text,func,grid_xy in options:
gui.PushButton(self,func,text=text,grid=grid_xy)
# Display all the widgets from the GUI
self.display()
def clear(self):
# Clear the texts used
for text in self.text:
text.destroy()
# Clear the entries used
for ent in self.entries:
ent.destroy()
# Clear and delete the 2 buttons
self.btn.destroy()
self.cancel.destroy()
del self.btn,self.cancel
def addAnimal(self):
# Create a list of the gotten values
self.got = []
for i in range(len(self.entries)):
self.got.append(self.entries[i].get())
if self.got[0] == '':
gui.error('Name','Please provide a name')
return
# Add the animal to the actives values
actives.append(Animal(*self.got))
gui.info('Animal Added','{} added!'.format(self.got[0]))
# Clear the widgets
self.clear()
def addNew(self):
# Create a tuple containg the Text widget information
texts = (('strength',[1,2],[1,1]),
('speed', [2,2],[2,1]),
('skill', [3,2],[3,1]),
('age', [4,2],[4,1]))
entries = []
text = []
text.append(gui.Text(self,text='Enter animal name: ',grid=[0,1]))
entries.append(gui.TextBox(self,grid=[0,2],width=25))
# Create the Text widgets and the Slider widgets
for t,sc,tc in texts:
text.append(gui.Text(self,text='Enter animal '+t+': ',grid=tc))
entries.append(gui.Slider(self,start=1,end=100,grid=sc))
# Create copies of the entries and text lists
self.entries = entries[:]
self.text = text[:]
# Create the 2 buttons for submitting and cancelling
self.btn = gui.PushButton(self,self.addAnimal,text='Submit',grid=[6,1])
self.cancel = gui.PushButton(self,self.clear,text='Cancel',grid=[6,2])
def battle(self):
# Destroy menu window and open BattleWindow
self.destroy()
BattleWindow(*actives)
def deleteOne(self):
# If the combo for deletion does not equal None
# pop the name from actives and give a info window
if self.todelete.get() != 'None':
index = self.names.index(self.todelete.get())
delete = actives.pop(index).name
gui.info('Deleted','{} has been deleted!'.format(delete))
self.names.pop(index)
# Destroy the button and Combo
self.todelete.destroy()
self.bn.destroy()
def delete(self):
# Create a combo for the animal and a 'delete' button
self.todelete = gui.Combo(self,['None']+self.names,grid=[0,1])
self.bn = gui.PushButton(self,self.deleteOne,text='Delete',grid=[1,1])
# Initialize the menu window to start
Menu()