When running deepcopy from the Python copy module on instances of a class, the copy process time doubles with each iteration, even though the instances are all of the same class and (at least in my mind) should therefor take an identical amount of time to copy.
The object I'm copying uses kwargs to set a bunch of attributes. It also originally called a set_dynamic_attributes method in __init__ to configure some of the attributes according to some logic, but in order to test if that was causing the slowdown, I removed the call from the __init__ and called it specifically.
Here's the offending section, and the results of it:
for a in base_wpn.base_attacks:
t0 = time.time()
a.set_dynamic_attributes()
t1 = time.time()
print('Dyn attributes set time: ' + str(t1-t0))
atk = deepcopy(a)
t2 = time.time()
print('Deepcopy time: ' + str(t2-t1))
Result:
Dyn attributes set time: 0.0
Deepcopy time: 0.01399993896484375
Dyn attributes set time: 0.0
Deepcopy time: 0.03599882125854492
Dyn attributes set time: 0.0
Deepcopy time: 0.04999995231628418
Dyn attributes set time: 0.0
Deepcopy time: 0.09999823570251465
Dyn attributes set time: 0.0
Deepcopy time: 0.011002540588378906
Dyn attributes set time: 0.0
Deepcopy time: 0.021996021270751953
Dyn attributes set time: 0.0
Deepcopy time: 0.0429990291595459
Dyn attributes set time: 0.0
Deepcopy time: 0.08499836921691895
Dyn attributes set time: 0.0
Deepcopy time: 0.17699956893920898
Dyn attributes set time: 0.0
Deepcopy time: 0.32700061798095703
Dyn attributes set time: 0.0
Deepcopy time: 0.6589939594268799
Dyn attributes set time: 0.0
Deepcopy time: 1.4200007915496826
Dyn attributes set time: 0.0
Deepcopy time: 2.466003656387329
Dyn attributes set time: 0.0
Deepcopy time: 5.228000164031982
Dyn attributes set time: 0.0
Deepcopy time: 10.528998374938965
In case it helps (I know it's not reproable without a ton more code, lots of object references), here's the object definition that the instances are built from:
class Attack():
def __init__(self, name, weapon, **kwargs):
self.name = name
self.weapon = weapon
self.skill = [self.weapon.skill]
self.attack_mod = self.weapon.attack_mod
self.parry_mod = -self.weapon.parry_mod #Modifier to OPPONENT'S parry chance
self.stamina = self.weapon.stamina
self.main_shape = None
self.striker = None
self.hands = 1
self.damage_type = 'b'
self.base_ap = 0
self.hand = True
self.added_mass = self.weapon.added_mass
self.length = 0 #Used to add or subtract from base weapon length got added/reduced reach
self.side_restrict = True #Determines if the attack can only hit one side of the enemy (i.e. hook from R hand only hitting left side)
self.restricted_locs = [] #Locations that can never be targeted with this attack (i.e. foot with uppercut)
self.allowed_angles_r = [] #Angles that are allowed as an index of angles (0 = N-> S, 7 = NW -> SE, 8 = thrust) (i.e. N->S with an uppercut)
self.allowed_angles_l = [] #Angles that are allowed as an index of angles (0 = N-> S, 7 = NW -> SE, 8 = thrust) (i.e. N->S with an uppercut)
self.main_area = 0
self.mech_adv = 0
self.force_scalar = 1 #Used to adjust force/damage for the attack
for k in kwargs:
for key in self.__dict__:
if k == key:
self.__dict__.update(kwargs)
#self.set_dynamic_attributes()
def set_dynamic_attributes(self):
for t in self.damage_type:
if t == 'b':
if self.striker == 'main':
shape = self.weapon.main_shape
else:
shape = 'round'
if shape == 'wedge':
self.main_area = self.weapon.main_length * self.weapon.avg_main_width
self.mech_adv = self.weapon.main_depth / self.weapon.main_width
elif shape == 'round':
#Using Hertz's result, but using fixed f value built off of added_mass + fist mass (.86) and v of 40 f/s2 and fixed p_ratio for target
#Equation: https://www.quora.com/Is-the-area-of-contact-between-a-cylinder-and-a-flat-surface-infinitely-small-Is-it-a-point
if self.striker == 'main':
material = self.weapon.main_material
width = self.weapon.main_width
length = self.weapon.main_length
elif self.striker == 'shaft':
material == self.weapon.shaft_material
width = 1
length = self.weapon.shaft_length
else:
material = self.weapon.accent_material
width = length = 1
e_calc = ((1-(material.p_ratio * material.p_ratio))/(material.elasticity*10))+((1-(.4*.4))/5)
self.main_area = sqrt((4*((.86 + self.added_mass)*40)*width)/(3.514*(e_calc)*min(length, 8)))
elif self.main_shape == 'flat':
self.main_area = min(self.weapon.main_length,8) * min(self.weapon.main_width,8)
elif t == 's':
if self.main_shape == 'blade':
self.main_area = min(self.weapon.main_length, 8) * self.weapon.avg_main_width
self.mech_adv = self.weapon.main_depth / self.weapon.main_width
elif self.main_shape == 'de blade':
self.main_area = min(self.weapon.main_length, 8) * self.weapon.avg_main_width
self.mech_adv = (self.weapon.main_depth/2) / self.weapon.main_width
elif t == 'p':
if self.striker == 'main':
shape = self.weapon.main_shape
length = self.weapon.main_length
depth = self.weapon.main_depth
width = self.weapon.main_width
else:
shape = 'point'
if self.striker == 'shaft':
length = min(self.weapon.shaft_length, 8)
depth = width = 1
else:
length = depth = width = 1
if shape in ['point', 'blade']:
wedge1 = length / width
wedge2 = width / depth
#Double each (since there are two wedges per side) and multiply for full MA of tip
self.mech_adv = (wedge1*2)*(wedge2*2)
self.main_area = depth * length * width
else:
if self.main_shape == 'hook':
wedge1 = self.weapon.main_length / self.weapon.main_width
wedge2 = self.weapon.main_width / self.weapon.main_depth
#Double each (since there are two wedges per side) and multiply for full MA of tip
self.mech_adv = (wedge1*2)*(wedge2*2)
self.main_area = self.weapon.main_depth * self.weapon.main_width
if self.damage_type in ['s','b']:
self.stamina += self.weapon.weight + int(self.weapon.weight*self.weapon.axis_vs_com)
self.base_ap += min(self.weapon.weight/10, (self.weapon.weight * 10)/self.weapon.axis_vs_com)
self.added_mass = self.weapon.weight / self.weapon.com_perc
self.attack_mod += (20 - ((self.weapon.weight*10) * self.weapon.com_perc))
self.parry_mod -= ((self.weapon.weight*10) * self.weapon.com_perc)
if self.weapon.main_num > 1:
self.attack_mod += self.weapon.main_num * 5
self.parry_mod -= self.weapon.main_num * 20
else:
self.stamina += self.weapon.weight/2
self.base_ap += self.weapon.weight * 5
self.added_mass = self.weapon.weight/10
if self.damage_type == 'p':
self.attack_mod -= self.weapon.weight/10
self.parry_mod -= self.weapon.weight * 5
else:
self.attack_mod += -5 + (self.weapon.main_num * 5)
self.parry_mod -= self.weapon.main_num * 20
More code by request. Here is the code for the weapon class (which the attack references) and the materials class (which weapon references):
class Weapon:
def __init__(self, **kwargs):
self.name = ''
self.shafted = False #Used to determine if wpn has a shaft
self.allowed_main_materials = [] # List of materials applicable for the main surface. Young's modulus prevents copper and bronze swords longer than 24", for example
self.main_material = m_steel #Damage component (blade, head, etc) material
self.shaft_material = m_wood
self.grip_material = m_leather
self.accent_material = m_steel
self.attack_mod = 0
self.parry_mod = 0 #Mod to weilder's ability to parry with weapon
self.b_striker = 'accent' #Striking surface for damage type. Can be main, shaft, accent, or none
self.s_striker = 'main'
self.p_striker = 'main'
self.t_striker = 'none'
self.hands = [1] #List can include 0,1,2
self.quality = 'Average'
self.base_name = 'Weapon'
self.bname_variants = [] #A list of variant names for the weapon
self.skill = None #This is the default skill used for the weapon. String
self.length = 1
self.shaft_length = 0 #Also used as tethers for flail and whip like weapons
self.shaft_diameter = 0
self.shaft_num = 0
self.pre_load = False #Used to account for weapons that can be preloaded with velocity, like flails or staves
self.avg_main_width = 1 #1.25 average longsword
self.main_width = 1 #Absolute width at widest point
self.avg_main_depth = .1 #.14 is average for a sword blade
self.main_depth = .2 #Absolute depth at deepest point
self.main_shape = 'de blade' #Acceptable values: de blade, blade, point, wedge, round, flat, hook
self.main_num = 1 #Number of main attack surfaces, mostly used for flails/flogs
self.accent_cuin = 1 #Cubic inches of accent material, such as the crossguard and pommel on a sword
self.main_com = .5 #Center of mass for the main weapon component
self.main_loc = .1 #Location along the total length for the main weapon component
self.accent_loc = .05 #Location along the total length for the accent component
self.grip_loc = .03 #location along the total length for the grip
self.main_weight = 0
self.shaft_weight = 0
self.accent_weight = 0
self.grip_weight = 0
self.weight = 0
self.main_length = 0
self.added_mass = 0
self.damage_type = 'b'
self.stamina = 0
#Maximums; used to procedurally gen weapons
self.main_len_range = (0,0) #Tuple containing min and max range for acceptable lengths
self.main_depth_range = (0,0)
self.main_avg_depth_range = (0,0)
self.main_width_range = (0,0)
self.main_avg_width_range = (0,0)
self.length_range = (0,0)
self.shaft_length_range = (0,0)
self.shaft_diameter_range = (0,0)
self.max_main_num = 1
self.max_shaft_num = 1
self.main_only_com = 0
self.shaft_only_com = 0
self.accent_only_com = 0
self.com = 0 #Center of mass for the whole weapon
self.com_perc = 0 #com as a percentage
self.axis_vs_com = 0 #Shows COM relative to grip location (axis for swings). Used to determine AP/stamina costs.
self.main_hits = 0
self.staff_hits = 0
self.accent_hits = 0
self.min_pwr_1h = 0 #1 pwr = 1 ft/lb; accelleration = 40 f/s2; weight of average hand = .86 lb
self.min_pwr_2h = 0 #1 pwr = 1.5 ft/lb; accelleration = 40 f/s2; weight of average hand = .86 lb
self.solidness = 1 #Used in damage calc
self.sharpness = 1
self.pointedness = 1
self.base_attacks = []
self.attacks = []
self.base_maneuvers = []
self.maneuvers = []
self.guards = []
self.cost = 0
self.normality = 1
for k in kwargs:
for key in self.__dict__:
if k == key:
self.__dict__.update(kwargs)
self.set_dynamic_attributes()
def set_dynamic_attributes(self):
#Determine which variant name to use
if len(self.bname_variants) > 1:
n_idx = roll_dice(1, len(self.bname_variants))
n_idx -= 1
self.base_name = self.bname_variants[n_idx]
#Name weapon using quality and material, if applicable
if self.main_material is not m_tissue:
if self.quality != 'Average':
self.name = self.quality + ' ' + self.main_material.name + ' ' + self.base_name
else:
self.name = self.main_material.name + ' ' + self.base_name
else:
self.name = self.base_name
self.attack_mod = 20 * quality_dict.get(self.quality)
self.parry_mod = 20 * quality_dict.get(self.quality)
self.main_weight = ((self.main_length * self.avg_main_depth * self.avg_main_width)*self.main_num) * (self.main_material.density * .03)
self.shaft_weight = (self.shaft_length * self.shaft_diameter * (self.shaft_material.density * .03)) * self.shaft_num
self.accent_weight = self.accent_cuin * (self.accent_material.density * .03)
self.grip_weight = self.grip_material.density * (.3 * max(self.hands))
self.weight = self.main_weight + self.shaft_weight + self.accent_weight
self.main_only_com = ((self.main_length*self.main_com)+(self.main_loc*self.length))*self.main_weight
self.shaft_only_com = (self.shaft_length*.5)*self.shaft_weight
self.accent_only_com = (self.accent_loc*self.length)*self.accent_weight
self.com = (self.main_only_com + self.shaft_only_com + self.accent_only_com)/self.weight #Center of mass for the whole weapon
self.com_perc = self.com / self.length #com as a percentage
self.axis_vs_com = self.com_perc - self.grip_loc #Shows COM relative to grip location (axis for swings). Used to determine AP/stamina costs.
self.main_hits = (self.main_material.elasticity * 1450000) * (self.main_weight/(self.main_material.density*.03)) * self.main_material.toughness
self.min_pwr_1h = ((self.added_mass + .86) * 40)/1 #1 pwr = 1 ft/lb/s; accelleration = 40 f/s2; weight of average hand = .86 lb
self.min_pwr_2h = ((self.added_mass + 1.72) * 40)/1.5 #1 pwr = 1.5 ft/lb/s; accelleration = 40 f/s2; weight of average hand = .86 lb
if self.main_material.elasticity < 1: self.solidness = self.main_material.elasticity
if self.main_material.hardness < 1:
self.sharpness = self.main_material.hardness
self.pointedness = self.main_material.hardness
else:
self.sharpness = sqrt(self.main_material.hardness)
self.pointedness = self.main_material.hardness
#Damage calc = ((((added_mass + fist mass) * velocity) / main_area) * mech_adv) * sharpness or hardness or pointedness
main_materials_cost = self.main_material.cost * self.main_weight
shaft_materials_cost = self.shaft_material.cost * self.shaft_weight
grip_materials_cost = self.grip_material.cost * self.grip_weight
accent_materials_cost = self.accent_material.cost * self.accent_weight
#Crafting costs. 1 day skilled labor = ~5 material cost
main_crafting_cost = self.main_material.craft_diff * self.main_weight
shaft_crafting_cost = self.shaft_material.craft_diff * self.shaft_weight
grip_crafting_cost = self.grip_material.craft_diff * self.grip_weight
accent_crafting_cost = self.accent_material.craft_diff * self.accent_weight
self.cost = main_crafting_cost + main_materials_cost + shaft_crafting_cost + shaft_materials_cost + grip_crafting_cost + grip_materials_cost + accent_crafting_cost + accent_materials_cost
self.normality = self.main_material.normality * self.shaft_material.normality * self.grip_material.normality * self.accent_material.normality
class Material():
_allMaterials = []
def __init__(self, **kwargs):
#Below is to create a list of instances of this class
self._allMaterials.append(self)
self.name = None
#Goal: Hardness and elasticity set the hits for the material, toughness then scales them up or down.
self.hardness = 1 #Scalar. 1 = ~7 Brinell, roughly very hard wood. Copper 85, Bronze 180, Iron 300, Steel 400-600
self.elasticity = 1 #Scalar. Basically resistance to deformation. 1 = Wood, average modulus of elasticity ~10 GPa. Bone 18, Gold 79, Copper 100, Bronze 120, Iron 210, Steel 200
self.toughness = 1 #Scalar. Resistance to breaking when deformed. 1 = Wood, very non-brittle. 10 = Gold, very malliable. .5 = Steel, somewhat brittle. 0.1 = stone
self.p_ratio = .5 #Poisson's ratio. Non-scaled, use actual values
self.normality = 1 #Scalar. How rare the material is. 1 = Iron, very common. Gold is 4 million times rarer than Iron, but probably it will be ~.01 in game
self.cost = 1 #Scalar. Raw cost of the material (taking into account refining costs). 1 = Wood, cheap $0.23/lb. 10 = Copper, $2.50/lb. 82,000 = Pure Gold
self.craft_diff = 1 #Scalar. Difficulty of crafting. 1 = 1 day of crafting per lb of finished material. Cost of 1 day of a craftsman = 5
# For metals, hardness modifies it. Hardened Steel takes 5 days to craft 1 pound of weaponry.
self.density = 1 #Scalar. Relative density per cubic inch. 1 = Wood, .03 lb/in3. Copper = .32, Silver = .38, Gold = .7, Iron = .28, Bronze = .3, Steel = .28
for key in self.__dict__:
for k in kwargs:
if k == key:
self.__dict__.update(kwargs)
self.set_dynamic_attr()
def set_dynamic_attr(self):
if self.craft_diff == 0:
self.craft_diff = .078 * self.hardness
self.craft_diff = round(self.craft_diff, 2)
Fixed it; The issue was as user10987432 suspected, it was creating copies of all objects up the tree. I fixed it by removing the weapon reference in the constructor and passing each individual attribute as a kwarg (ugh). Makes for a very ugly 4-line call, but it works and is fast. I'll see if I can shorten it once it gets on my nerves enough; for now I can just copy/paste and only change relevant fields.
Edit: I fixed the problem much more cleanly by making the individual attacks all child classes of the Attack class and just creating instances of them for each actor. Not sure why I didn't do that originally, I'm doing it for maneuvers already, but it's much easier to read and debug now. Thanks again to everyone for the help.
Related
This is a python program that does some sort of simulation,, I am looking for any type of optimization while keeping the same p[i] form, I have tried Pypy and I got an around 3x performance gain over python. Any suggestions are welcomed
import random
from time import perf_counter
infected, non_infected = 1, 99999
infectation_chance, infection_days, death_chance = 1/100, 2/1000, 2/100
population, population_list = infected + non_infected, non_infected * [0] + infected * [1]
place = 10
p = {i: [] for i in range(1,place + 1)}
day = 1
simulation_duration = 3
while 0 < simulation_duration:
print(f"Working on day {day}..")
time1 = perf_counter()
for person in population_list:
p[random.randint(1, place)].append(person)
time2 = perf_counter()
i = 0
while i < place:
tl = []
i += 1
for crowd in p[i]:
if crowd == 0:
if (random.random() < infectation_chance * str(p[i]).count("1")) - (infectation_chance/100 * str(p[i]).count("0")):
tl.append(1)
else:
tl.append(0)
if crowd == 1:
tl.append(1)
p[i] = tl
i = 0
population_list = []
while i < place:
i += 1
population_list.append(p[i])
simulation_duration -= 1
day += 1
print(f"Total time: {perf_counter() - time1} \nInfection time: {perf_counter() - time2} \nPlacing time: {time2-time1}")
print(str(population_list).count("1"), str(population_list).count("0"))
Even tho I received lots of help I still need more optimization.Any type of optimization as far as it doesn't change the results are welcomed.Since this is fully compatible with pypy I am using pypy, I can also use python if it means better performance. Current setup:
import random
import functools
from time import perf_counter
with open("results.txt", "w") as results:
results.seek(0)
results.write("")
results.close()
time1 = perf_counter()
#functools.lru_cache(maxsize=128)
def simulation():
infected, non_infected = 1, 99999999
infectation_chance_c, death_chance, recovery_chance, reinfectation_chance, incubation_time = 1.4, 1 - 0.03, 1 - 0.97, 1 - 1 / 150, 2
death_chance, recovery_chance = death_chance / incubation_time, recovery_chance / incubation_time
population_total, population_list = infected + non_infected, non_infected * [0] + infected * [1]
place = 1
day = 0
simulation_duration = 100000000
with open("results.txt", "a") as results:
print("Starting... \nPlease wait for results, this can take lots of time!")
while infected > 0 and simulation_duration > 0:
population = population_list.count(0) + population_list.count(-1) + population_list.count(1)
healthy = population_list.count(0) + population_list.count(-1)
recovered = population_list.count(-1)
infected = population_list.count(1)
died = population_total - len(population_list)
p = {i: [] for i in range(1,place + 1)}
results.write(f"Day {day}: Infected: {infected} Healthy: {healthy} p-Imune: {recovered} Alive: {population} Died: {died} \n")
print(f"Day {day}: Infected: {infected} Healthy: {healthy} p-Imune: {recovered} Alive: {population} Died: {died}")
for person in population_list:
p[random.randint(1, place)].append(person)
i = 0
while i < place:
i += 1
p_infected = p[i].count(1)
try:
infectation_chance = 1 - float(p_infected) / (float(len(p[i])) / infectation_chance_c)
except:
pass
for j, crowd in enumerate(p[i]):
if crowd == -1:
if random.random() > reinfectation_chance:
p[i][j] = 1
elif random.random() > reinfectation_chance:
p[i][j] = 0
elif crowd:
if random.random() > death_chance:
p[i].pop(j)
elif random.random() > recovery_chance:
if random.random() > 0.4:
p[i][j] = -1
else:
p[i][j] = 0
elif not crowd:
if random.random()>infectation_chance:
p[i][j] = 1
i = 0
population_list = []
while i < place:
i += 1
population_list.extend(p[i])
simulation_duration -= 1
day += 1
print(f"Total time: {perf_counter() - time1}")
simulation()
print(f"Simulation finishsed... \nProcessing time: {perf_counter()-time1}")
here is a corrected version in pure python, commented because there were some bugs. your major time loss was counting infected/non-infected inside the for loop, though the result is always the same. it could be optimized again with numpy if you wanna use a bigger population
import random
from time import perf_counter
infected, non_infected = 1, 99999
infectation_chance, infection_days, death_chance = 1/100, 2/1000, 2/100
population, population_list = infected + non_infected, non_infected * [0] + infected * [1]
place = 10
day = 1
simulation_duration = 3
while 0 < simulation_duration:
# p must be reset here or it will grow indefinitely
p = {i: [] for i in range(1,place + 1)}
print(f"Working on day {day}..")
time1 = perf_counter()
for person in population_list:
p[random.randint(1, place)].append(person)
time2 = perf_counter()
i = 0
while i < place:
i += 1
# if you need to, count infected/non-infected here
# not in your for loop where it has always the same value
# and don't cast to str, what's the point?
# pi_infected = p[i].count(1)
# pi_sane = p[i].count(0)
for j, crowd in enumerate(p[i]):
if crowd == 0:
# your formula was broken (a-b is always true)
# i used a generic one
if random.random()>(1-infectation_chance):
# change your list in place:
# no need for temp list, save lots of cycles
p[i][j] = 1
i = 0
population_list = []
while i < place:
i += 1
# it's extend, not append here or your population list
# will have a length of #place
population_list.extend(p[i])
simulation_duration -= 1
day += 1
print(f"Total time: {perf_counter() - time1} \nInfection time: {perf_counter() - time2} \nPlacing time: {time2-time1}")
print(population_list.count(1), population_list.count(0))
numpy version
import random
import numpy as np
from time import perf_counter
infected, non_infected = 1, 99999
infectation_chance, infection_days, death_chance = 1/100, 2/1000, 2/100
place = 10
population = infected + non_infected
group_size = population//place
population_list=np.zeros((population))
population_list[:infected]=1
day = 1
simulation_duration = 3
while 0 < simulation_duration:
print(f"Working on day {day}..")
time1 = perf_counter()
# shuffle is not recursive so we need to flatten population_list
population_list=population_list.flatten()
np.random.shuffle(population_list)
population_list=population_list.reshape((place, group_size))
time2 = perf_counter()
# we need to rebuild the pure python code with no loops
# first we create randoms for all pop
randoms = np.random.rand(population).reshape((place, group_size))
# list of infected by group: a list of all p[i].count(1)
nb_infected = np.count_nonzero(population_list, axis=1).reshape((place,1))
# compute (1-infectation_chance**p[i].count(1)) for all pop
infection_map=np.full((place, group_size), 1-infectation_chance)**nb_infected
# if randoms>infection_map and population_list==0
new_infected = np.bitwise_and(randoms>infection_map, population_list==0)
# then set to 1 in place
population_list[new_infected] = 1
simulation_duration -= 1
day += 1
print(f"Total time: {perf_counter() - time1} \nInfection time: {perf_counter() - time2} \nPlacing time: {time2-time1}")
total_infected=np.count_nonzero(population_list)
print(total_infected, population-total_infected)
I have problems implementing the base power code on my Pokemon battle game on Python. Thanks if you wanna help.
Here's my code:
import time
import numpy as np
import sys
# Delay printing
def delay_print(s):
# print one character at a time
for c in s:
sys.stdout.write(c)
sys.stdout.flush()
time.sleep(0.05)
# Create the class
class Pokemon:
def __init__(self, name, types, moves, EVs, health='==================='):
# save variables as attributes
self.name = name
self.types = types
self.moves = moves
self.attack = EVs['ATTACK']
self.defense = EVs['DEFENSE']
self.health = health
self.bars = 20 # Amount of health bars
def fight(self, Pokemon2):
print("POKEMON BATTLE WOOOOOOOOOOOO!!!!!!")
print(f"\n{self.name}")
print("TYPE/", self.types)
print("ATTACK/", self.attack)
print("DEFENSE/", self.defense)
print("LVL/", 3 * (1 + np.mean([self.attack, self.defense])))
print("\nVS")
print(f"\n{Pokemon2.name}")
print("TYPE/", Pokemon2.types)
print("ATTACK/", Pokemon2.attack)
print("DEFENSE/", Pokemon2.defense)
print("LVL/", 3 * (1 + np.mean([Pokemon2.attack, Pokemon2.defense])))
time.sleep(2)
# Consider type advantages
version = ['Fire', 'Water', 'Grass']
for i, k in enumerate(version):
if self.types == k:
# Both are same type
if Pokemon2.types == k:
string_1_attack = '\nIts not very effective...'
string_2_attack = '\nIts not very effective...'
# Pokemon2 is STRONG
if Pokemon2.types == version[(i + 1) % 3]:
Pokemon2.attack *= 2
Pokemon2.defense *= 2
self.attack /= 2
self.defense /= 2
string_1_attack = '\nIts not very effective...'
string_2_attack = '\nIts super effective!'
# Pokemon2 is WEAK
if Pokemon2.types == version[(i + 2) % 3]:
self.attack *= 2
self.defense *= 2
Pokemon2.attack /= 2
Pokemon2.defense /= 2
string_1_attack = '\nIts super effective!'
string_2_attack = '\nIts not very effective...'
# Now for the actual fighting...
# Continue while pokemon still have health
while (self.bars > 0) and (Pokemon2.bars > 0):
# Print the health of each pokemon
print(f"\n{self.name}\t\tHLTH\t{self.health}")
print(f"{Pokemon2.name}\t\tHLTH\t{Pokemon2.health}\n")
print(f"{self.name}, I choose you!")
for i, x in enumerate(self.moves):
print(f"{i + 1}.", x)
index = int(input('Pick a move: '))
delay_print(f"\n{self.name} used {self.moves[index - 1]}!")
time.sleep(1)
delay_print(string_1_attack)
# Determine damage
Pokemon2.bars -= self.attack
Pokemon2.health = ""
# Add back bars plus defense boost
for j in range(int(Pokemon2.bars + .1 * Pokemon2.defense)):
Pokemon2.health += "="
time.sleep(1)
print(f"\n{self.name}\t\tHLTH\t{self.health}")
print(f"{Pokemon2.name}\t\tHLTH\t{Pokemon2.health}\n")
time.sleep(.5)
# Check to see if Pokemon fainted
if Pokemon2.bars <= 0:
delay_print("\n..." + Pokemon2.name + ' fainted. ' +
self.name + " won!")
break
# Pokemon2s turn
print(f"Let's go, {Pokemon2.name}!")
for i, x in enumerate(Pokemon2.moves):
print(f"{i + 1}.", x)
index = int(input('Pick a move: '))
delay_print(f"\n{Pokemon2.name} used {Pokemon2.moves[index - 1]}!")
time.sleep(1)
delay_print(string_2_attack)
# Determine damage
self.bars -= Pokemon2.attack
self.health = ""
# Add back bars plus defense boost
for j in range(int(self.bars + .1 * self.defense)):
self.health += "="
time.sleep(1)
print(f"{self.name}\t\tHLTH\t{self.health}")
print(f"{Pokemon2.name}\t\tHLTH\t{Pokemon2.health}\n")
time.sleep(.5)
# Check to see if Pokemon fainted;;
if self.bars <= 0:
delay_print("\n..." + self.name + ' fainted. ' +
Pokemon2.name + " won!")
break
class moves:
self.firemoves = ["Eruption", "Fire Blast", "Flamethrower", "Overheat"]
self.watermoves = ["Razor Shell", "Scald", "Hydro Pump", "Surf"]
self.grassmoves = ["Leaf Storm", "Energy Ball", "Giga Drain", "Solar Beam"]
def BasePower():
BasePower =
def Damage():
damage = ((2 * Level + 2) * Power * ATTACK / DEFENSE / 50 + 2)
money = np.random.choice(5000)
delay_print(f"\nOpponent paid you ${money}. Good luck in the future and win more batttles!\n")
if __name__ == '__main__':
# Create Pokemon
Typhlosion = Pokemon(
'Typhlosion', 'Fire',
['Eruption: 150 Base Power, power decreases as health decreases',
'Fire Blast: 110 Base Power, 10% chance to burn', 'FLamethrower: 90 Base Power, 10% chance to burn',
'Overheat, 130 Base Power, lowers attack by 2'], {
'ATTACK': 12,
'DEFENSE': 8
})
Samurott = Pokemon('Samurott', 'Water',
['Razor Shell: 75 Base Power', 'Scald: 80 Base Power', 'Hydro Pump: 120 Base Power',
'Surf: 90 Base Power'], {'ATTACK': 10, 'DEFENSE': 10
})
Serperior = Pokemon(
'Serperior', 'Grass',
['Leaf Storm: 130 Base Power, lowers attack by 2', 'Energy Ball: 90 Base Power',
'Giga Drain: 75 Base Power, yo heal 50% of damage dealt',
'Solar Beam: 120 Base Power, takes two turns to charge'], {
'ATTACK': 19,
'DEFENSE': 1
})
Typhlosion.fight(Samurott) # Get them to fight'
So as you can see here, the moves such as Typhlosion's Eruption or Serperior's Leaf Storm doesn't have their base power, as Eruption supposedly have 150 base power in full health, and Leaf Storm should have 130 base power, while dropping your attack by two.
I try to optimize a simple python algorithm I made that approximately solve the Traveling Salesman Problem :
import math
import random
import matplotlib.pyplot as plt
import datetime
#Distance between two point
def distance(point1, point2):
return math.sqrt((point2[0]-point1[0])**2+(point2[1]-point1[1])**2)
#TSP TimeTraveler Algorithm
def TSP_TimeTraveler(Set_Points):
print("Solving TSP")
#For calculating execution time
time_start = datetime.datetime.now()
#Copy the set points
points = Set_Points.copy()
route = []
#Take 3 points at random
route.append(points.pop(random.randint(0,len(points)-1)))
route.insert(0,points.pop(random.randint(0,len(points)-1)))
route.insert(1,points.pop(random.randint(0,len(points)-1)))
#Calulating the initial route length
Length = distance(route[0],route[1]) + distance(route[1],route[-1]) + distance(route[-1],route[0])
#Time Traveler Algorithm
while len(points)>0 :
print("Points left : ", len(points),' ', end="\r")
#Take a random point from the Set
point = points.pop(random.randint(0,len(points)-1))
###############################################################################################################
#### Finding the closest route segment by calculation all lengths posibilities and finding the minimum one ####
###############################################################################################################
Set_Lengths = []
for i in range(1,len(route)):
#Set of Lengths when the point is on each route segment except the last one
L = Length - distance(route[i-1],route[i]) + distance(route[i-1],point) + distance(point, route[i])
Set_Lengths.append((i,L))
#Adding the last length when the point is on the last segement
L = Length - distance(route[-1],route[0]) + distance(route[-1],point) + distance(point, route[0])
Set_Lengths.append((0,L))
###############################################################################################################
###############################################################################################################
#Sorting the set of lengths
Set_Lengths.sort(key=lambda k: k[1])
#Inserting the point on the minimum length segment
route.insert(Set_Lengths[0][0], point)
#Updating the new route length
Length = Set_Lengths[0][1]
#Connecting the start point with the finish point
route.append(route[0])
#For calculating execution time
time_end = datetime.datetime.now()
delta = (time_end-time_start).total_seconds()
print("Points left : ", len(points),' Done ',)
print("Execution time : ", delta, "secs")
return route
#######################
#Testing the Algorithm#
#######################
#Size of the set
size = 2520
#Generating a set of random 2D points
points = []
for i in range(size):
points.append([random.uniform(0, 100),random.uniform(0, 100)])
#Solve TSP
route = TSP_TimeTraveler(points)
#Plot the solution
plt.scatter(*zip(*points),s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()
The algorithm operate in 3 simple steps :
1/ First step I take 3 points at random from the points set and connect them as the initial route.
2/ Then each next step, I take a point at random from the set of points left. And try to find the closest segment of the route i have and connect it to it.
3/ I keep repeating step 2/ until the set of points left is empty.
Here is a gif of how the algorithm solve a set of 120 points : TimeTravelerAlgorithm.gif
I give it the name "Time Traveler" because it's operate like a greedy salesman algorithm. But instead traveling to the closest new city in the present, the greedy salesman time travel to the past to the closest city he had already visited and go visit that new city then continue his normal route.
The time traveler start a route of 3 cities, and the traveler add a new city each step in his past, until he reach a present where he visited all the cities and returned to his home city.
The algorithm give decent solutions fast for small set of points. Here is the execution time for each number of sets, all are made on a 2.6GHz dual-core Intel Core i5 processor Macbook :
120 points in around 0.03 secs
360 points in around 0.23 secs
2520 points in around 10 secs
10 000 points in around 3 mins
100 000 points in around 5 hours (Solution Map)
The algorithm is far from being optimized, because in some cases it gives cross routes which is suboptimal. And It's all made in pure python. Maybe using numpy or some advance library or even GPU can speed up the program.
I want your review and help on how to optimize it. I try to approximately solve without cross routes for set of points that can be extremely large (from 1 million to 100 billions points).
PS: My algorithm and codes are open. People from internet, feel free to use it in any project or any research you have.
Thanks for the comments. I re-implemented the algorithm using Objects, Sets and Linked list. I also removed the square root from distance function . Now the code look more clean :
import math
import random
import datetime
import matplotlib.pyplot as plt
#Distance between two point
def distance(point1, point2):
return (point2[0]-point1[0])**2 + (point2[1]-point1[1])**2
#Distance between two point
class Node:
def __init__(self, dataval=None):
self.dataval = dataval
self.nextval = None
class TSP_TimeTraveler():
def __init__(self, dataval=None):
self.count = 0
self.position = None
self.length = 0
def get_position():
return self.position
def next_city():
self.position = self.position.nextval
return self.position
#adding a city to the current route with Time Traveler Algorithm :
def add_city(self, point):
node = Node(point)
if self.count <=0 :
self.position = node
elif self.count == 1 :
node.nextval = self.position
self.position.nextval = node
self.length = 2*distance(self.position.dataval,node.dataval)
else :
#Creating the traveler
traveler = self.position
c = traveler.dataval #current position
n = traveler.nextval.dataval #next position
#Calculating the length of adding the city to the path
Min_L = self.length-distance(c,n)+distance(c,node.dataval)+distance(node.dataval,n)
Min_Node = traveler
traveler = traveler.nextval
while traveler != self.position :
c = traveler.dataval #current position
n = traveler.nextval.dataval #next position
#Calculating the length of adding the city to the path
L = self.length-distance(c,n)+distance(c,node.dataval)+distance(node.dataval,n)
#Searching the path to the of city with minimum length
if L < Min_L :
Min_L = L
Min_Node = traveler
traveler = traveler.nextval
#Adding the city to the minimum path
node.nextval = Min_Node.nextval
Min_Node.nextval = node
self.length = Min_L
#Incrementing the number of city in the route
self.count = self.count + 1
#Get the list of the route
def getRoute(self):
result = []
traveler = self.position
result.append(traveler.dataval)
traveler = traveler.nextval
while traveler != self.position :
result.append(traveler.dataval)
traveler = traveler.nextval
result.append(traveler.dataval)
return result
def Solve(self, Set_points):
print("Solving TSP")
#For calculating execution time
time_start = datetime.datetime.now()
#Copy the set points list
points = Set_points.copy()
#Transform the list into set
points = set(tuple(i) for i in points)
#Add
while len(points)>0 :
print("Points left : ", len(points),' ', end="\r")
point = points.pop()
self.add_city(point)
result = self.getRoute()
#For calculating execution time
time_end = datetime.datetime.now()
delta = (time_end-time_start).total_seconds()
print("Points left : ", len(points),' Done ',)
print("Execution time : ", delta, "secs")
return result
#######################
#Testing the Algorithm#
#######################
#Size of the set
size = 120
#Generating a set of random 2D points
points = []
for i in range(size):
points.append((random.uniform(0, 100),random.uniform(0, 100)))
#Solve TSP
TSP = TSP_TimeTraveler()
route = TSP.Solve(points)
#Plot the solution
plt.scatter(*zip(*points),s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()
And using PyPy instead of normal python it runs alot faster :
120 in around 0.03sec
360 in around 0.05sec
2520 in around 0.22sec
10 000 in around 2sec
100 000 in around 7min
The 100 000 case that took before 5 hours, now it's solved in 7min.
Next, I will try to implement a 2-opt with double linked list and KD-tree. So it can solve for large sets without crosses.
I improved the algorithm by adding double linked list and 2-opt at each insertion :
import math
import random
import datetime
import matplotlib.pyplot as plt
#Distance between two point
def distance(point1, point2):
return (point2[0]-point1[0])**2 + (point2[1]-point1[1])**2
#Intersection between two segments
def intersects(p1, q1, p2, q2):
def on_segment(p, q, r):
if r[0] <= max(p[0], q[0]) and r[0] >= min(p[0], q[0]) and r[1] <= max(p[1], q[1]) and r[1] >= min(p[1], q[1]):
return True
return False
def orientation(p, q, r):
val = ((q[1] - p[1]) * (r[0] - q[0])) - ((q[0] - p[0]) * (r[1] - q[1]))
if val == 0 : return 0
return 1 if val > 0 else -1
o1 = orientation(p1, q1, p2)
o2 = orientation(p1, q1, q2)
o3 = orientation(p2, q2, p1)
o4 = orientation(p2, q2, q1)
if o1 != o2 and o3 != o4:
return True
if o1 == 0 and on_segment(p1, q1, p2) : return True
if o2 == 0 and on_segment(p1, q1, q2) : return True
if o3 == 0 and on_segment(p2, q2, p1) : return True
if o4 == 0 and on_segment(p2, q2, q1) : return True
return False
#Distance Double Linked Node
class Node:
def __init__(self, dataval=None):
self.dataval = dataval
self.prevval = None
self.nextval = None
class TSP_TimeTraveler():
def __init__(self):
self.count = 0
self.position = None
self.length = 0
self.traveler = None
self.travelert_past = None
self.is_2opt = True
def get_position():
return self.position
def traveler_init(self):
self.traveler = self.position
self.travelert_past = self.position.prevval
return self.traveler
def traveler_next(self):
if self.traveler.nextval != self.travelert_past:
self.travelert_past = self.traveler
self.traveler = self.traveler.nextval
return self.traveler, False
else :
self.travelert_past = self.traveler
self.traveler = self.traveler.prevval
return self.traveler, True
#adding a city to the current route with Time Traveler Algorithm :
def add_city(self, point):
node = Node(point)
if self.count <=0 :
self.position = node
elif self.count == 1 :
node.nextval = self.position
node.prevval = node
self.position.nextval = node
self.position.prevval = self.position
self.length = 2*distance(self.position.dataval,node.dataval)
elif self.count == 2 :
node.nextval = self.position.nextval
node.prevval = self.position
self.position.nextval.prevval = node
self.position.nextval = node
self.length = 2*distance(self.position.dataval,node.dataval)
else :
#Creating the traveler
traveler = self.traveler_init()
c = traveler #current position
prev = False #inverse link
n, prev = self.traveler_next()
#Calculating the length of adding the city to the path
Min_prev = prev
Min_L = self.length-distance(c.dataval,n.dataval)+distance(c.dataval,node.dataval)+distance(node.dataval,n.dataval)
Min_Node = c
traveler = n
while traveler != self.position :
c = n #current position
n, prev = self.traveler_next()
#Calculating the length of adding the city to the path
L = self.length-distance(c.dataval,n.dataval)+distance(c.dataval,node.dataval)+distance(node.dataval,n.dataval)
#Searching the path to the of city with minimum length
if L < Min_L :
Min_prev = prev
Min_L = L
Min_Node = c
traveler = n
if Min_prev :
Min_Next_Node = Min_Node.prevval
else :
Min_Next_Node = Min_Node.nextval
node.nextval = Min_Next_Node
node.prevval = Min_Node
if Min_prev :
Min_Node.prevval = node
else :
Min_Node.nextval = node
if Min_Next_Node.nextval == Min_Node:
Min_Next_Node.nextval = node
else :
Min_Next_Node.prevval = node
self.length = Min_L
#2-OP
if self.is_2opt == True :
self._2opt(Min_Node, node, Min_Next_Node)
#Incrementing the number of city in the route
self.count = self.count + 1
#apply the 2opt to a-b-c
def _2opt(self, a, b, c):
traveler = self.traveler_init()
c1 = a
c2 = b
n1 = b
n2 = c
c = traveler #current position
t_prev = False
n, t_prev = self.traveler_next()
traveler = n
while traveler != self.position :
cross = False
if (c.dataval != c1.dataval and c.dataval != c2.dataval and n.dataval != c1.dataval and n.dataval != c2.dataval) and intersects(c.dataval, n.dataval, c1.dataval, c2.dataval):
self._2optswap(c,n,c1,c2)
cross = True
a = n
n = c1
c2 = a
if (c.dataval != n1.dataval and c.dataval != n2.dataval and n.dataval != n1.dataval and n.dataval != n2.dataval) and intersects(c.dataval, n.dataval, n1.dataval, n2.dataval):
self._2optswap(c,n,n1,n2)
cross = True
a = n
n = n1
n2 = a
if cross:
return
c = n #current position
n, t_prev = self.traveler_next()
traveler = n
#swap between the crossed segment a-b and c-d
def _2optswap(self, a, b, c, d):
if a.nextval == b :
a.nextval = c
else :
a.prevval = c
if b.prevval == a :
b.prevval = d
else :
b.nextval = d
if c.nextval == d :
c.nextval = a
else :
c.prevval = a
if d.prevval == c :
d.prevval = b
else :
d.nextval = b
self.length = self.length - distance(a.dataval,b.dataval) - distance(c.dataval,d.dataval) + distance(a.dataval,c.dataval) + distance(b.dataval,d.dataval)
#Get the list of the route
def getRoute(self):
result = []
traveler = self.traveler_init()
result.append(traveler.dataval)
traveler, prev = self.traveler_next()
while traveler != self.position :
result.append(traveler.dataval)
traveler, prev = self.traveler_next()
result.append(traveler.dataval)
return result
def Solve(self, Set_points, with_2opt = True):
print("Solving TSP")
#For calculating execution time
time_start = datetime.datetime.now()
#Copy the set points list
points = Set_points.copy()
#Transform the list into set
points = set(tuple(i) for i in points)
#Add
while len(points)>0 :
print("Points left : ", len(points),' ', end="\r")
point = points.pop()
self.add_city(point)
result = self.getRoute()
#For calculating execution time
time_end = datetime.datetime.now()
delta = (time_end-time_start).total_seconds()
L=0
for i in range(len(result)-1):
L = L + math.sqrt((result[i-1][0]-result[i][0])**2 + (result[i-1][1]-result[i][1])**2)
print("Points left : ", len(points),' Done ',)
print("Execution time : ", delta, "secs")
print("Average time per point : ", 1000*delta/len(Set_points), "msecs")
print("Length : ", L)
return result
#######################
#Testing the Algorithm#
#######################
#Size of the set
size = 1000
#Generating a set of random 2D points
points = []
for i in range(size):
points.append((random.uniform(0, 100),random.uniform(0, 100)))
#Solve TSP
TSP = TSP_TimeTraveler()
route = TSP.Solve(points, with_2opt = True)
plt.scatter(*zip(*route), s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()
Now the solution give fast results with no cross routes.
With PyPy it solves 100,000 points with no cross routes in 30 min.
Now Im working on implementing the KD-tree to solve for large sets.
I'm trying to figure out why the while loop in one of my functions is still running even after the points in my graphics are equal, which is when I set it to stop. Is there anything I'm doing wrong? I've tried to switch other things around to get it to work but no luck.
It's for a game--when the character reaches the endbox the loop needs to break, but it isn't doing that after I explicitly coded it to. It's in the second function I have:
from graphics import *
def field():
#creating the window
win = GraphWin('The Field',400,400)
win.setBackground('white')
#drawing the grid
boxlist = []
for i in range(0,400,40):
for j in range(0,400,40):
box = Rectangle(Point(i,j),Point(i+40,j+40))
box.setOutline('light gray')
box.draw(win)
boxlist.append(box)
#creating other boxes
startbox = Rectangle(Point(0,0),Point(40,40))
startbox.setFill('lime')
startbox.setOutline('light gray')
startbox.draw(win)
endbox = Rectangle(Point(360,360),Point(400,400))
endbox.setFill('red')
endbox.setOutline('light gray')
endbox.draw(win)
boxlist.append(startbox)
boxlist.append(endbox)
#creating Pete
pete = Rectangle(Point(2,2),Point(38,38))
pete.setFill('gold')
pete.draw(win)
return win,boxlist,pete
def move(win2,boxlist,pete,endbox):
peteloc = pete.getCenter()
#creating loop to move pete
while peteloc != endbox.getCenter():
click = win2.getMouse()
x = click.getX()
y = click.getY()
peteloc = pete.getCenter()
petex = peteloc.getX()
petey = peteloc.getY()
#moving pete
if x>=petex+20 and y<=petey+20 and y>=petey-20:
pete.move(40,0)
elif x<=petex-20 and y<=petey+20 and y>=petey-20:
pete.move(-40,0)
elif y>=petey+20 and x<=petex+20 and x>=petex-20:
pete.move(0,40)
elif y<=petey-20 and x<=petex+20 and x>=petex-20:
pete.move(0,-40)
peteloc = pete.getCenter()
# The main function
def main():
win2,boxlist,pete = field()
endbox = boxlist[len(boxlist)-1]
move(win2,boxlist,pete,endbox)
main()
I think maybe it is caused by precision of float. I guess pete.getCenter() and endbox.getCenter() are something like [float, float], you should avoid using != between float, such as 1.0000001 is not equal to 1.
So even if the character reaches the endbox, the position will still get a little float bias.
So you can change a != b to abs(a - b) > acceptable_error when the error is acceptable. Sample code is like:
# while peteloc != endbox.getCenter():
while abs(peteloc.getX() - endbox.getCenter().getX()) > 0.01 and abs(peteloc.getY() - endbox.getCenter().getY()) > 0.01:
Hope that will help you.
Zelle graphics Point objects don't appear to ever compare as equal:
>>> from graphics import *
>>> a = Point(100, 100)
>>> b = Point(100, 100)
>>> a == b
False
>>>
We have to extract coordinates and do our own comparison. Although #recnac provides a workable solution (+1), I'm going to suggest a more general one. We'll create a distance() method that's valid for any object that inherits from _BBox, which includes Rectangle, Oval, Circle and Line:
def distance(bbox1, bbox2):
c1 = bbox1.getCenter()
c2 = bbox2.getCenter()
return ((c2.getX() - c1.getX()) ** 2 + (c2.getY() - c1.getY()) ** 2) ** 0.5
We can now measure the distance between objects, horizontally, vertically and diagonally. Since your boxes are moving twenty pixels at a time, we can assume that if they are withing 1 pixel of each other, they are in the same location. Your code rewritten to use the distance() method and other tweaks:
from graphics import *
def field(win):
# drawing the grid
boxlist = []
for i in range(0, 400, 40):
for j in range(0, 400, 40):
box = Rectangle(Point(i, j), Point(i + 40, j + 40))
box.setOutline('light gray')
box.draw(win)
boxlist.append(box)
# creating other boxes
startbox = Rectangle(Point(0, 0), Point(40, 40))
startbox.setFill('lime')
startbox.setOutline('light gray')
startbox.draw(win)
boxlist.append(startbox)
endbox = Rectangle(Point(360, 360), Point(400, 400))
endbox.setFill('red')
endbox.setOutline('light gray')
endbox.draw(win)
boxlist.append(endbox)
# creating Pete
pete = Rectangle(Point(2, 2), Point(38, 38))
pete.setFill('gold')
pete.draw(win)
return boxlist, pete
def distance(bbox1, bbox2):
c1 = bbox1.getCenter()
c2 = bbox2.getCenter()
return ((c2.getX() - c1.getX()) ** 2 + (c2.getY() - c1.getY()) ** 2) ** 0.5
def move(win, pete, endbox):
# creating loop to move pete
while distance(pete, endbox) > 1:
click = win.getMouse()
x, y = click.getX(), click.getY()
peteloc = pete.getCenter()
petex, petey = peteloc.getX(), peteloc.getY()
# moving pete
if x >= petex + 20 and petey - 20 <= y <= petey + 20:
pete.move(40, 0)
elif x <= petex - 20 and petey - 20 <= y <= petey + 20:
pete.move(-40, 0)
elif y >= petey + 20 and petex - 20 <= x <= petex + 20:
pete.move(0, 40)
elif y <= petey - 20 and petex - 20 <= x <= petex + 20:
pete.move(0, -40)
# The main function
def main():
# creating the window
win = GraphWin('The Field', 400, 400)
win.setBackground('white')
boxlist, pete = field(win)
endbox = boxlist[-1]
move(win, pete, endbox)
main()
I'm new to Blender and new to Python, on my Layer 1 I have a ball named "BallB".
Now I want to make a simple bubbling-animation using Python in Blender but I'm unable to make a keyframe. This should happen on Layer 2.
I tried many and got many errors... all the Snippets I found didn't work and my script allways crashed with Python-Errors like
RuntimeError: Operator bpy.ops.anim.change ... expected an timeline/animation area to be activated
and many more.
Has anybody some hints for me?
I'd like to learn scripted animations in Blender so I'm very thankful for every hint which advances me ;-)
My Code:
import bpy, math, random
d = 4
anz = 100
frameAnz = 10
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 100
for anz in range (0,anz):
ball = bpy.data.objects["ballB"]
tball = ball.copy()
xpos = -1 * (d/2) + random.randint(0,(d-1))
xpos += random.random()
ypos = -1 * (d/2) + random.randint(0,(d-1))
ypos += random.random()
zpos = random.randint(0,(d-1))
zpos += random.random()
bn = str(anz).zfill(5)
bn = "zz_Ball-" + bn
tball.name = bn
tball.location = (xpos, ypos, zpos)
sz = random.uniform(0.015,0.09)
tball.scale = (sz,sz,sz)
#tball.nodes["Emission"].inputs[1].default_value = 200
tball.select = False
scene.objects.link(tball)
#print ("done!")
obj = bpy.context
for actFrame in range(1,frameAnz):
# scene = bpy.context.scene
# scene.keyframe_insert(data_path="gravity", frame = actFrame)
for ob in scene.objects:
ploc = ob.location
#print (ploc)
xpos = ploc[0]
ypos = ploc[1]
zpos = ploc[2]
zpos = zpos + random.random()
ob.location = (xpos, ypos, zpos)
#ypos = ball.location[1]
#zpos = ball.location]2]
#zpos = zpos - random.random()
#ball.location = (xpoy, ypos, zpos)
#obj.keyframe_insert_menu('Location')
#bpy.context.scene.frame_set(0)
#scene = bpy.context.scene
#scene.keyframe_insert(data_path="Location", frame=actFrame)
Actually it looks so:
You are close, you want to use obj.keyframe_insert(), using the index parameter you can keyframe just the one location value.
One issue you will have is that copying the initial object means the new object will use the same animation data, keeping them moving in unison. You can create a new object and use the same mesh data.
An objects layers property is an array of 20 booleans to specify where it is visible, when you add an object to a scene it will be set to be visible on the active layer, so set this after you link it to the scene.
import bpy, random
d = 4
anz = 100
frameAnz = 20
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 100
ball = bpy.data.objects["ballB"]
for anz in range (0,anz):
xpos = -1 * (d/2) + random.randint(0,(d-1))
xpos += random.random()
ypos = -1 * (d/2) + random.randint(0,(d-1))
ypos += random.random()
zpos = random.randint(0,(d-1))
zpos += random.random()
bn = str(anz).zfill(5)
bn = "zz_Ball-" + bn
tball = bpy.data.objects.new(bn, ball.data)
tball.location = (xpos, ypos, zpos)
sz = random.uniform(0.015,0.09)
tball.scale = (sz,sz,sz)
tball.select = False
scene.objects.link(tball)
tball.layers = [False,True] + [False]*18
for actFrame in range(1,frameAnz):
for ob in scene.objects:
ob.location.z += random.random()
ob.keyframe_insert(data_path='location', index=2, frame=actFrame)