I wanted to know what the modules you recommend for plotting lists of coordinates on the X-Y plane are. The purpose is to implement Game of Life; I've tried matplotlib.pyplot but I've got some issues with axes ratio and in general it doesn't seem like the best way to plot some sort of animation. The main idea in the script is to plot the world in which the cells live, with a tick given from time.sleep.
import matplotlib.pyplot as plt
class Cell:
#Cell class, with coordinates and an ON-OFF state
def __init__(self,x=0,y=0,state=0):
self.state=state
self.x=x
self.y=y
self.coord=[self.x,self.y]
def __repr__(self):
if self.state:
return "*"
else:
return " "
#methods to turn on and off the cells:
def turn_on(self):
self.state=1
return self
def turn_off(self):
self.state=0
return self
class Mondo:
#the world in which the cells live:
def __init__(self,width=10,height=30,starting_cells=[]):
self.width=width
self.height=height
self.grid=[[Cell(i,j) for j in range(height)] for i in range(width)]
for cell in starting_cells:
self.grid[cell.x][cell.y]=cell
def __repr__(self):
#this method is needed just if I wanted to use just the print statement instead of matplotlib
return "_"*(2*self.height+3)+"\n"+"\n".join([" ".join(["|"]+[" "+str(cell)+" " for cell in self.grid[row]]+["|"]) for row in range(self.width)])+"\n"+"_"*(2*self.height+3)
def plotlib(self):
#this is the best I can do:
Xaxis=[cell.x for i in range(self.width) for cell in self.grid[i] if cell.state]
Yaxis=[cell.y for i in range(self.width) for cell in self.grid[i] if cell.state]
plt.axis([0,self.width, 0, self.height])
return plt.show(plt.scatter(Xaxis,Yaxis,c="000000", marker='s'))
def __getitem__(self,row):
return self.grid[row]
def pos_cell(self,coord):
#retrieves the cell given the coordinates
return self.grid[coord[0]][coord[1]]
def cell_state(self,coord):
#outputs the cell's state on given coord
return self.pos_cell(coord).state
def neighbors_state(self,coord,cont=0):
#sums the neghbors' cell state
cell=self.pos_cell(coord)
x,y=cell.x,cell.y
for hor in [-1,0,1]:
for ver in [-1,0,1]:
if not hor==ver==0 and (0<=x+hor<self.width and 0<=y+ver<self.height):
cont+=self.cell_state([(x+hor)%self.width,(y+ver)%self.height])
return cont
def alive_dead(self,coord):
#for each cell, tells wether it must switch its state or not
cell=self.pos_cell(coord)
if not cell.state:
if self.neighbors_state(cell.coord)==3:
return True
else:
return False
elif cell.state:
if self.neighbors_state(cell.coord) in [2,3]:
return True
else:
return False
#NB starting cells must be given as a list, not as a grid
def game_of_life(width,height,starting_cells):
#the heart of the script
test=[[Cell(i,j) for j in range(height)] for i in range(width)]
world=Mondo(width,height,starting_cells)
world.plotlib()
for i in range(world.width):
for j in range(world.height):
if world.alive_dead([i,j]):
test[i][j].turn_on()
starting_cells=[test[i][j] for i in range(width) for j in range(height)]
plt.pause(0.5)
return game_of_life(width,height,starting_cells)
#Example of an oscillator:
#print game_of_life(10,10,[Cell(1,2,1),Cell(2,3,1),Cell(3,1,1),Cell(3,2,1),Cell(3,3,1)])
#Example of the Gosper gun:
gosper=[[Cell(i,j) for j in range(70)] for i in range(30)]
for i in range(len(gosper)):
for j in range(len(gosper[0])):
if [i,j] in [[1,26],[2,24],[2,26],[3,14],[3,15],[3,22],[3,23],[3,36],[3,37],[4,36],[4,37],[4,22],[4,23],[4,13],[4,17],[5,12],[5,18],[5,22],[5,23],[5,1],[5,2],[6,1],[6,2],[6,12],[6,16],[6,18],[6,19],[6,24],[6,26],[7,26],[7,18],[7,12],[8,13],[8,17],[9,14],[9,15]]:
gosper[i][j].turn_on()
gosp=[gosper[i][j] for i in range(len(gosper)) for j in range(len(gosper[0])) if gosper[i][j].state]
print game_of_life(30,70,gosp)
#Mondo(30,70,gosp).plotlib()
#print Mondo(30,70,gosp)
I gave a look at the matplotlib.animation but maybe there's too much stuff for me to use just for a Uni assignment.
Is there some module I can use to print some simple animation? Should I try harder with matplotlib.pyplot, and if so, how can I adjust axes ratio and stuff?
SUrfing the questions I found the module graphics, may it be useful?
Thank you
Related
The code is part of a program for moving an avatar "X" around a room, but I can't get past the initialization of the X and the room:
import curses
from curses import wrapper
def main(stdscr):
test = curses.newpad(30, 50)
test.border()
test.noutrefresh(0,0,20,70,66,239)
guy = Agent([32, 100], "X")
guy.move(stdscr,1,1)
stdscr.noutrefresh()
curses.doupdate()
class Agent():
def __init__(self, position, avatar):
self.position = postion
self.avatar = avatar
def move(self, stdscr, direction, distance):
stdscr.addstr(self.position[0], self.position[1], self.avatar)
while True:
curses.wrapper(main)
curses.curs_set(0)
input()
I've tried all sorts of permutations, with essentially the same result; either the X prints before the room borders, or else it doesn't show up at all
I'm guessing that I am probably misunderstanding something fundamental, like inheritance of classes or something, but I can't think what.
I've got some issues with this simple chess code in python. It is part of a weekly assignment; this is what I got so far:
from math import sqrt
from random import randint,shuffle,seed
def rand_pos():
return [randint(0,7),randint(0,7)]
#first, classes defining the kings and the rook;
#their only attribute is a randomly generated position on the chessboard.
#Each of them has its special print method that will be used in the Chessboard (Scacchiera) class.
class W_king:
def __init__(self,coord=rand_pos()):
self.coord=coord
self.x=coord[1]
self.y=coord[0]
def __repr__(self):
return "R"
class B_king:
def __init__(self,coord=rand_pos()):
self.coord=coord
self.x=coord[1]
self.y=coord[0]
def __repr__(self):
return "r"
class Rook:
def __init__(self,coord=rand_pos()):
self.coord=coord
self.x=coord[1]
self.y=coord[0]
def __repr__(self):
return "T"
#the following will be used to calculate the distance between the kings and between a king and the rook;
#I'll use it in the while statements later in the Scacchiera class to check if the kings are generated too near or stuff
def distance(n1,n2):
return sqrt(sum((n1.coord[i]-n2.coord[i])**2 for i in [0,1]))
class Scacchiera:
def __init__(self,w_king=W_king(),b_king=B_king(),rook=Rook(),boxes=[[" " for y in range(8)] for x in range(8)]):
self.w_king=w_king
self.b_king=b_king
self.rook=rook
self.boxes=boxes
#here it is: while the two kings are generated too near,
#get the black king new coordinates
while distance(self.b_king,self.w_king)<2:
self.b_king.coord=[randint(0,7),randint(0,7)]
#...and, while the white king (or the black king) and the rook have the same coordinates
#or the black king is in the rook's sight,
#get a new pair of coordinates for the rook:
while self.w_king.coord==self.rook.coord or self.b_king.coord==self.rook.coord or self.rook.x==self.b_king.x or self.rook.y==self.b_king.y:
self.rook.coord=[randint(0,7),randint(0,7)]
print distance(self.b_king,self.w_king) #to check, just for me
#the function conv switches to the chessboard's coordinates e.g. e4, h5, etc
print conv(self.w_king.coord),conv(self.b_king.coord),conv(self.rook.coord)
def __repr__(self):
#self.boxes is an array of blank spaces " ",
#and in the right place the kings and the rook are placed
scacchiera=self.boxes[:]
scacchiera[self.w_king.x][self.w_king.y]=self.w_king
scacchiera[self.b_king.x][self.b_king.y]=self.b_king
scacchiera[self.rook.x][self.rook.y]=self.rook
return "\n".join([str(8-i)+" "+" ".join(str(scacchiera[i][j]) for j in range(8)) for i in range(8)])+"\n "+" ".join([chr(97+k) for k in range(8)])
def check(self,king):
#no need for this for now
return self.rook.x==king.x or self.rook.y==king.y
def black_legal_moves(self,mossa):
future_king=B_king([self.b_king.y+mossa[0],self.b_king.x+mossa[1]])
if distance(self.w_king,future_king)<2 or self.check(future_king):
return False
else:
return True
def new_mossa_random(self):
#this method chooses randomly a new position for the black king from the list of adjacent cells
#and tests if it's legal with the method above. If it's not, it deletes it from the list and re-tries
moves_list=[[self.b_king.y+hor,self.b_king.x+ver] for ver in [-1,0,1] for hor in [-1,0,1] if not hor==ver==0]
shuffle(moves_list)
move=moves_list[0]
#while it's not legal or the coordinates are out of the board:
while not self.black_legal_moves(move) or not 0<=move[0]<=7 or not 0<=move[1]<=7:
del moves_list[0]
if not moves_list:
return None
move=moves_list[0]
return move
def conv(coord):
return [chr(coord[0]+97),8-coord[1]]
#you just need to run it:
seed()
scacchiera=Scacchiera()
print scacchiera
print conv(scacchiera.new_mossa_random())
The issues are two:
My code, though incomplete, seems correct to me in the chessboard generation section. Nonetheless, often (nearly three times out of ten) the kings are next to each other or the rook and a king are placed one over the other, or the random move for the black king isn't even near of his box.
Very often, the code keeps running and won't print any chessboard; it seems like it sticks on the two whiles at the beginning of Scacchiera.
NB: F5-ing the script on your PC will print, in the following order:
The distance between the two kings,
The coordinates on the chessboard of: the white king, the black king, and then the rook
The chessboard with the pieces on the board
The coordinates of a new random move for the black king.
Let me know if I should add some other info.
In case of collision, you're changing the coord member on the pieces. But the positions are also stored in x and y, which are not updated.
I'd suggest that you keep only x and y or only coord in your classes. if you want to get fancy, you could keep coord and make x and y into properties by using #property.
Your problems most likely stem from an incorrect use of default parameters.
Short answer, do this:
def __init__(self, coord=None):
coord = coord or rand_pos()
Explanation: Common gotcha with Python default args
Thank you for your help! Finally I removed the classes for the kings and the rook; they were pointless, all I needed was just a dictionary, just like in the code here.
Here is the solution I went through
from random import randint,shuffle,choice
def rand_pos():
return [randint(0,7),randint(0,7)]
def dist(n1,n2):
return (sum([(n1[i]-n2[i])**2 for i in [0,1]]))**(0.5)
def invconv(coord):
return [ord(coord[0])-97,8-coord[1]]
#the core of the game is here; Scacchiera means Checkboard, and it basically generates coordinates
#for the kings and the rook while they are in illegal positions.
#then there's a bunch of methods to determine wether a move is legal or not
#and, just for black, a random move is chosen.
#finally, all the stuff is gathered in the Partita (=Game) function.
class Scacchiera:
def __init__(self,w_king=rand_pos(),b_king=rand_pos(),rook=rand_pos()):
self.w_king=w_king
self.b_king=b_king
self.rook=rook
while dist(self.b_king,self.w_king)<=1.5:
self.b_king=rand_pos()
while self.w_king==self.rook or self.b_king==self.rook or self.rook[0]==self.b_king[0] or self.rook[1]==self.b_king[1]:
self.rook=rand_pos()
self.pezzi={"R":self.w_king,"r":self.b_king,"T":self.rook}
self.mosse=[self.pezzi[item] for item in ["r","R","T"]]
def __repr__(self):
griglia=[["." for j in range(8)] for i in range(8)]
for item in self.pezzi:
griglia[self.pezzi[item][0]][self.pezzi[item][1]]=item
return "\n".join([str(8-j)+" "+" ".join(str(griglia[i][j]) for i in range(8)) for j in range(8)])+"\n "+" ".join([chr(97+k) for k in range(8)])
def move(self,pezzo,end):
if not end:
return
end=[end[0]-self.pezzi[pezzo][0],end[1]-self.pezzi[pezzo][1]]
self.pezzi[pezzo][0]+=end[0]
self.pezzi[pezzo][1]+=end[1]
if self.pezzi["r"]==self.pezzi["T"]:
del self.pezzi["T"]
self.mosse.append(self.pezzi.values())
return
def check(self):
return self.pezzi["T"][0]==self.pezzi["r"][0] or self.pezzi["T"][1]==self.pezzi["r"][1]
def black_legal_move(self,end):
kings_dist=dist(self.pezzi["R"],end)
rook_king_dist=dist(self.pezzi["T"],self.pezzi["R"])
if kings_dist<=1.5:
return False
elif self.pezzi["T"]==end and rook_king_dist>1.5:
return True
elif self.pezzi["T"][0]==end[0] or self.pezzi["T"][1]==end[1] or end[0] not in range(8) or end[1] not in range(8):
return False
return True
def mosse_legali_b(self):
moves_list=[[self.pezzi["r"][0]+hor,self.pezzi["r"][1]+ver] for ver in [-1,0,1] for hor in [-1,0,1] if not hor==ver==0]
shuffle(moves_list)
elle=[]
for i in range(len(moves_list)):
if self.black_legal_move(moves_list[i]):
elle.append(moves_list[i])
if not elle:
return None
return elle
def mossa_random_black(self):
print "Tocca al nero.\n"
end=choice(self.mosse_legali_b())
if not end:
return None
self.move("r",end)
print self
return
def scacco_matto(self):
return self.check() and self.mosse_legali_b()==None
def stallo(self):
return self.mosse_legali_b()==None
def ripetizione(self):
return 3 in [self.mosse.count(item) for item in self.mosse]
def white_king_lmove(self,beg,end):
return dist(beg,end)<=1.5 and dist(self.pezzi["r"],end)>1.5 and beg!=end
def white_rook_lmove(self,beg,end):
return (beg[0]==end[0] or beg[1]==end[1]) and beg!=end
def white_legal_move(self,beg,end):
if self.pezzi["R"]==beg:
return self.white_king_lmove(beg,end)
else:
return self.white_rook_lmove(beg,end)
def mossa_white(self):
print "\n**Tocca al bianco**"
mossa=raw_input("Inserisci la prossima mossa:\n")
beg=invconv([mossa[0],int(mossa[1])])
end=invconv([mossa[2],int(mossa[3])])
if not self.white_legal_move(beg,end):
print "\nMossa non valida."
return self.mossa_white()
if self.pezzi["R"]==beg:
pezzo="R"
elif self.pezzi["T"]==beg:
pezzo="T"
self.move(pezzo,end)
if self.check():
print "Scacco!\n",self
else:
print self
return
I'm having another problem with the game I'm making, I want the Asteroids sprite to be randomized every time I new asteroid is created by the spawner class, but I keep getting this error 'non-default argument follows default argument', and I'm pretty much stumped on what to do, The actual randomized image is stored inside the spawner and is then imported to the Asteroid class. Any help would be greatly appreciated, the images list is a global variable.
images = [games.load_image("asteroid_small.bmp"),
games.load_image("asteroid_med.bmp"),
games.load_image("asteroid_big.bmp")]
def check_drop(self):
""" Decrease countdown or drop asteroid and reset countdown. """
if self.time_til_drop > 0:
self.time_til_drop -= 0.7
else:
asteroid_size = random.choice(images)
new_asteroid = Asteroid(x = self.x,image = asteroid_size)
games.screen.add(new_asteroid)
And then this is the part of the asteroid class that the randomized image will be stored in
def __init__(self, x, y = 10,image):
""" Initialize a asteroid object. """
super(Asteroid, self).__init__(image = image,
x = x, y = y,
dy = Asteroid.speed)
Your problem isn't with how you instantiate the asteroid, it is how you define it:
def __init__(self, x, y = 10,image):
If you look, image is last, after y, which has a default argument. In Python you cannot do such things. You have two options:
def __init__(self, x, y = 10, image = None):
# default the argument to some sentinel value
# Test for sentinel and the reassign if not matched.
image = image if image else random.choice(images)
or
def __init__(self, x, image, y = 10):
# re-order your arguments.
# Also note that image, x, y might be a better order
# (#see comment by Micael0x2a)
I have class Dot
class Dot:
def __init__(self, x, y):
self.x=x
self.y=y
I have class Cluster
class Cluster:
ic=0
List=[Dot]
colour=0
def __init__(self, Dot):
self.List[self.ic]=Dot
self.ic=self.ic+1
def includeDot(self, Dot):
self.List[self.ic]=Dot
self.ic=self.ic+1
Which include list of dots(List).
And I have class ClusterMaker where is list of clusters(and some other procedures, but this is not important for this question)
class ClusterMaker:
total=0
i=0
CList=[Cluster]
def addCluster(self,Cluster):
self.CList.append(Cluster)
Finally, there is button on my form which starts creating dots and clusters
def onChoose(self):
# ClMaker=ClusterMaker()
self.total=self.ent.get().strip() #how many dots we haver
self.CM=ClusterMaker()
i=0
while (i < int(self.total)):
dot=Dot(randint(0, 575), randint(0,670))
clst=Cluster(dot)
clst.colour= randrange(100, 999, 15)
self.CM.addCluster(clst)
box.showerror('j', str(str(self.CM.CList[i].List[0].x)+str(clst.List[0].x)))
this box shows us x coord of every dot in our cluster list
self.canvas.create_oval(clst.List[0].x, clst.List[0].y, clst.List[0].x+10, clst.List[0].y+10, fill=str("#"+str(clst.colour)))
i=i+1
b=0
while(b<6):
box.showerror('j', str(self.CM.CList[b].List[0].x))
and this box shows us x coords too
b=b+1
But what is going on in my lists? Why when I ask to show x coords for the second time it show the same x coord for all dots in all clusters?
Class attributes are instantiated once and shared between instances. You have to create fresh list in __init__:
def __init__(self, Dot):
self.List = [Dot]
self.List[self.ic]=Dot
self.ic=self.ic+1
I am trying to do an animation of a Particle Swarm Optimization using Python and Mayavi2.
The animation is working fine, my problem is that it is not possible to interact with the plot while it is animating the movement. Specifically i would like to turn the graph and zoom. Maybe someone has experience doing animations?
The way i do it is first to calculate the positions of the particles and then to store them. After the calculation is finished i plot the positions of the particle at the first instace of time with point3d() and then i iterate through time updating the data using the set() method.
Is there a way to make it possible to turn the graph? I have heard about something with threads, disabeling the the rendering, but i could not figure out how to do it in my code. Besides lots of other stuff, I have read:
http://code.enthought.com/projects/mayavi//docs/development/html/mayavi/mlab_animating.html
http://code.enthought.com/projects/mayavi//docs/development/html/mayavi/tips.html#acceleration-mayavi-scripts
but it can't see how to use it.
Any suggestions?
Here is my code:
#!/usr/bin/env python
'''
#author rt
'''
import pylab as plt
from numpy import *
from mayavi import mlab
from threading import Thread # making plotting faster?
import ackley as ac
class Swarm(Thread, object):
'''
constructor for the swarm
initializes all instance variables
'''
def __init__(self,objective_function):
Thread.__init__(self)
# optimization options
self.omega = 0.9 # inertial constant
self.c1 = 0.06 # cognitive/private constant
self.c2 = 0.06 # social constant
self.objective = objective_function # function object
self.max_iteration = 100 # maximal number of iterations
# Swarm stuff
self.number = 0
self.best = [] # gbest; the global best position
self.particles = [] # empty list for particles
# temporary
self.min = self.objective.min
self.max = self.objective.max
self.best_evolution = []
# self.dimensions = 2 # dimensions NB!
'''
add particles to the swarm
find the best position of particle in swarm to set global best
'''
def add_particles(self, n):
for i in range(n):
particle = Particle(self)
if i == 0: # initialize self.best
self.best = particle.position
if particle.eval() < self._eval(): # check if there is a better and if, set it
self.best = copy(particle.position)
self.particles.append(particle) # append the particle to the swarm
def _eval(self):
return self.objective.evaluate(self.best)
def plot(self):
for i in range(self.max_iteration):
pos_x = []
pos_y = []
pos_z = []
#print pos_x
for particle in self.particles:
[x,y,z] = particle.trail[i]
pos_x.append(x)
pos_y.append(y)
pos_z.append(z)
#print pos_x
if i ==0:
g = mlab.points3d(pos_x, pos_y,pos_z, scale_factor=0.5)
ms =g.mlab_source
ms.anti_aliasing_frames = 0
ms.set(x=pos_x, y = pos_y, z = pos_z,scale_factor=0.5) #updating y value
#print pos_y
#ms.set(x=pos_x) # update x values
#ms.set(y=pos_y) #updating y value
#ms.set(z=pos_z) #updating y value
#for p in self.particles:
#p.plot()
def plot_objective(self):
delta = 0.1
v = mgrid[self.min:self.max:delta,self.min:self.max:delta]
z = self.objective.evaluate(v)
#mlab.mesh(v[0],v[1],z)
mlab.surf(v[0],v[1],z) # surf creates a more efficient data structure than mesh
mlab.xlabel('x-axis', object=None)
mlab.ylabel('y-axis', object=None)
mlab.zlabel('z-axis', object=None)
def _info(self):
self.plot()
print '----------------------------'
print 'The best result is:'
print 'Coordinates:', self.best
print 'Value: ', self._eval()
#print 'with ', nreval, 'evaluations'
print 'nr of particles: ', len(self.particles)
print '----------------------------'
def run(self):
self.plot_objective()
self.best = self.particles[0].get_position()
iteration = 0
while iteration < self.max_iteration:
#if iteration!= 0: obj.scene.disable_render = True
#disable_render = True
for particle in self.particles:
rnd_c1 = array([random.uniform(0,1),random.uniform(0,1)])
rnd_c2 = array([random.uniform(0,1),random.uniform(0,1)])
particle.velocity = self.omega * array(particle.velocity) + \
self.c1 * rnd_c1 * (array(particle.best) - array(particle.position)) + \
self.c2 * rnd_c2 * (array(self.best) - array(particle.position)) # TODO: change so independent rnd for components
particle.position = array(particle.position) + particle.velocity
if particle.eval() < particle.best_eval():
particle.best = copy(particle.position)
if particle.eval() < self._eval():
self.best = copy(particle.position)
particle.update() # add the point to the trail
iteration +=1
self.best_evolution.append(self._eval())
#obj.scene.disable_render = False
print 'finished: ', iteration
self._info()
'''
Class modeling particle
'''
class Particle():
def __init__(self, swarm):
self.swarm = swarm
x_rand = random.uniform(self.swarm.min,self.swarm.max)
y_rand = random.uniform(self.swarm.min,self.swarm.max)
self.position = array([x_rand,y_rand])
v_x_rand = random.uniform(self.swarm.min,self.swarm.max)
v_y_rand = random.uniform(self.swarm.min,self.swarm.max)
self.velocity = array([v_x_rand, v_y_rand])
self.size = 0.5
self.best = self.position
# visualization
self.trail = []
def plot(self):
[x,y] = self.position
z = self.eval()
mlab.points3d(x,y,z,scale_factor=self.size)
def eval(self):
return self.swarm.objective.evaluate(self.position)
def best_eval(self):
return self.swarm.objective.evaluate(self.best)
def get_position(self):
return self.position
def update(self):
[x,y] = self.position
z = self.eval()
#print [x,y,z]
self.trail.append([x,y,z])
def plot_trail(self,index):
[x,y,z] = self.trail[index]
mlab.points3d(x,y,z,scale_factor=self.size)
# Make the animation
mlab.figure(1, bgcolor=(0, 0, 0), size=(1300, 700)) # create a new figure with black background and size 1300x700
objective = ac.Ackley() # make an objective function
swarm = pso.Swarm(objective) # create a swarm
nr_of_particles = 25 # nr of particles in swarm
swarm.add_particles(nr_of_particles)
swarm.run()
#swarm.start()
mlab.show()
print '------------------------------------------------------'
print 'Particle Swarm Optimization'
#objective.info()
print 'Objective function to minimize has dimension = ', objective.get_dimension()
print '# of iterations = ', 1000
print '# of particles in swarm = ', nr_of_particles
print '------------------------------------------------------'
In my case, even though I was somewhat able to do what Brandon Rhodes suggested for a mock program (https://stackoverflow.com/questions/16617814/interacting-with-mlab-scene-while-it-is-being-drawn), I could not manage to convert my already existing larger program.
Then I found this link: http://wiki.wxpython.org/LongRunningTasks
So, I just sprinkled a lot of wx.Yield() s inside my loops. This way I did not need to change my program structure, and I am able to interact with the window. I think better ways are explained in the link.
Your problem is that the wx event loop, which runs the Mayavi GUI window and listens for mouse clicking and dragging and responds by moving the scene, is not getting any time to run during your animation because you are keeping Python captive in your loop without ever letting it return control.
Instead of keeping control of the program with a loop of your own, you need to create a wx.Timer class that advances the scene by one frame update, and that then returns control to the wx event loop after scheduling itself again. It will look something like this:
import wx
...
class Animator(wx.Timer):
def Notify(self):
"""When a wx.Timer goes off, it calls its Notify() method."""
if (...the animation is complete...):
return
# Otherwise, update all necessary data to advance one step
# in the animation; you might need to keep a counter or
# other state as an instance variable on `self`
# [DATA UPDATE GOES HERE]
# Schedule ourselves again, giving the wx event loop time to
# process any pending mouse motion.
self.Start(0, oneShot=True) # "in zero milliseconds, call me again!"
I played with slightly higher values like 1 for the number of milliseconds that wx gets to run the UI with, but could not really tell a difference between that and just choosing 0 and having control returned "immediately".