Re: [SPOILER] Solution to Perl 'Expert' Quiz of the Week #26
Jimmy Selgen <spamtrap1-x/claU4MwXpaa/[email protected]> Sun, 31 Oct 2004 14:00:29 +0100
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
Hi.
I think i got a more correct simulation now.
Calculating and summing the potential energy for each step, and
calculating the velocity from that.
At least it matches the simulation videos better
It might still be wrong, but at least (i think) it's better than my
previous solution.
Also attached as a file to preserve line breaks etc.
/Jimmy
-----------------------------------------------------------------
from Tkinter import *
import tkMessageBox,tkFileDialog,sys,os,math,time,tkSimpleDialog
class rollercoaster:
'''
Perl QOTW expert quiz #26 solution
Opens file specified in argv[1], or file opened from menu.
'''
def __init__(self,args):
self.rc_tag = None
self.height = 400
self.width = 600
self.gravity = 1
self.mass = 1
self.time_mult = 100
self.debug = 0
self.wnd = None
self.init_display()
if(len(args)):
self.read_rc(args[0])
self.root.mainloop()
def init_display(self):
'''
Initialize main window
'''
self.root = Tk()
self.root.title("Rollercoaster Simulation, Perl-QOTW #26")
self.speedvar = StringVar()
self.main_frame = Frame(self.root)
self.main_frame.pack(fill=BOTH,expand=1)
self.run_button =
Button(self.main_frame,text="Run",command=self.run)
self.canvas =
Canvas(self.main_frame,width=self.width,height=self.height)
speed_label = Label(self.main_frame,text="Velocity")
self.speedometer =
Entry(self.main_frame,textvariable=self.speedvar)
self.run_button.pack()
self.canvas.pack()
speed_label.pack()
self.speedometer.pack()
self.mainmenu = Menu(self.root)
self.root.config(menu=self.mainmenu)
self.filemenu = Menu(self.mainmenu)
self.filemenu.add_command(label="Open",command=self.open_rc_handler)
self.filemenu.add_command(label="Run",command=self.run)
self.filemenu.add_command(label="Setup",command=self.config_dialog)
self.filemenu.add_command(label="Exit",command=self.root.destroy)
self.mainmenu.add_cascade(label="File",menu=self.filemenu)
def ok_pressed(self):
'''
Config dialog callback for OK
'''
self.gravity = float(self.grav_str.get())
self.mass = float(self.mass_str.get())
self.time_mult = float(self.time_mult_str.get())
self.debug = self.dbg_var.get()
print "new settings, gravity = %02f, mass = %02f, time_mult = %
02f" % (self.gravity,self.mass,self.time_mult)
self.wnd.destroy()
self.wnd = None
def cancel_pressed(self):
'''
Config dialog callback for cancel
'''
self.wnd.destroy()
self.wnd = None
def config_dialog(self):
'''
Configuration dialog for setting mass, gravity, time multiplier
and turning debugging on/off
'''
if(self.wnd==None):
self.wnd = Toplevel()
self.wnd.title("Rollercoaster configuration")
self.frm = Frame(self.wnd)
self.grav_str = StringVar()
self.mass_str = StringVar()
self.time_mult_str = StringVar()
self.dbg_var = IntVar()
self.grav_str.set(self.gravity)
self.mass_str.set(self.mass)
self.time_mult_str.set(self.time_mult)
self.dbg_var.set(self.debug)
grav_label = Label(self.frm,text="Gravity")
self.grav_entry = Entry(self.frm,textvariable=self.grav_str)
mass_label = Label(self.frm,text="Mass")
self.mass_entry = Entry(self.frm,textvariable=self.mass_str)
delay_label = Label(self.frm,text="Delay multiplier (dist/v
* mult)")
self.time_mult_entry =
Entry(self.frm,textvariable=self.time_mult_str)
debug_btn =
Checkbutton(self.frm,text="Debug",variable=self.dbg_var)
ok_btn = Button(self.frm,text="OK",command=self.ok_pressed)
cancel_btn =
Button(self.frm,text="Cancel",command=self.cancel_pressed)
grav_label.pack()
self.grav_entry.pack()
mass_label.pack()
self.mass_entry.pack()
delay_label.pack()
self.time_mult_entry.pack()
debug_btn.pack()
ok_btn.pack()
cancel_btn.pack()
self.frm.pack()
def draw_cart(self,coord):
'''
Draws the "cart" from the given coordinate set
'''
#print "draw_cart, coord = ",coord
self.bbox = [coord[0]-2,self.height -
(coord[1]-2),coord[0]+2,self.height - (coord[1]+2)]
if(self.cart != None):
self.canvas.coords(self.cart,*self.bbox)
else:
self.cart = self.canvas.create_oval(self.bbox,fill="red")
self.canvas.update()
def draw_rc(self):
'''
Draws the rollercoaster from the current coordinate set
'''
self.coords = []
self.cart = None
self.canvas.delete('all')
for p in range(len(self.rc)) :
if(p < len(self.rc)-1):
point = self.rc[p].rstrip('\n').split(' ')
point[0] = float(point[0])
point[1] = float(point[1])
self.coords.append(point)
if(self.rc_tag != None):
self.canvas.delete(self.rc_tag)
self.rc_tag = []
for p in range(len(self.coords)):
if p < len(self.coords)-1:
co = [self.coords[p][0],self.height -
self.coords[p][1],self.coords[p+1][0],self.height - self.coords[p+1][1]]
self.rc_tag.append(self.canvas.create_line(co))
self.canvas.pack(fill=BOTH)
def calc_vel(self, curr_state, next_point):
'''
Calculates new speed and direction for cart for a given point
curr_state : current state of cart
next_point : the current point of the cart
returns : new state with updated cart speed,direction and
time to travel distance
'''
dist = 0
nv = 0
co = self.coords[curr_state['coord']]
nco = self.coords[next_point]
new_state = curr_state
#calculate vertical and horizontal "grid" distance travelled
dist = co[0] - nco[0]
nh = co[1] - nco[1]
dist_travelled = math.sqrt(dist**2 + nh**2)
if(self.debug):
print "dist :",dist," height :",nh,"
dist_travelled :",dist_travelled
curr_state['pa'] += (curr_state['m'] * curr_state['g'] * nh)
if(curr_state['pa'] > 0):
nv = math.sqrt(curr_state['pa'] )
else:
nv = -(math.sqrt(-curr_state['pa']))
if(self.debug):
print "nv = ",nv
new_state['v'] = nv
#Change cart direction if velocity is less than zero
if(new_state['v']<0):
new_state['direction'] = 0 - new_state['direction']
#change negative velocity to positive velocity going the
other way :)
new_state['v'] = -new_state['v']
if(self.debug):
print "new v :",new_state['v']
new_state['coord'] = next_point
#Set time to travel distance at new velocity
new_state['t'] = dist_travelled/ (new_state['v']+1)
if(self.debug):
print "new t = ",new_state['t']
return(new_state)
def read_rc(self,fname):
'''
Read a .rc file from disk
'''
try:
f = open(fname)
self.rc = f.readlines()
if(self.debug):
print "read %d lines from %s" % (len(self.rc),fname)
except:
tkMessageBox.showerror('Error opening file','Error opening
file "'+fname+'"')
self.rc = None
self.draw_rc()
def open_rc_handler(self):
'''
Menu callback for "File->Open"
'''
fname = tkFileDialog.askopenfilename(filetypes=[(".rc", ".rc")],
title="Select the file")
if(fname != None):
self.read_rc(fname)
def tick(self):
'''
"Mainloop" of the simulation. Callback to the windowmanager for
every iteration of the simulation
'''
self.curr_state =
self.calc_vel(self.curr_state,self.curr_state['coord']+self.curr_state['direction'])
self.draw_cart(self.coords[self.curr_state['coord']])
self.speedvar.set(round(self.curr_state['v'],2))
nco = self.curr_state['coord'] + self.curr_state['direction']
if(self.curr_state['v'] != 0 and nco >= 0 and nco <
len(self.coords)):
self.root.after(int(self.curr_state['t']*self.time_mult),self.tick)
else:
return(0)
def run(self):
'''
Initialize and start mainloop of simulation
'''
self.curr_state = {
'coord' : 0, #Index into rollercoaster coordinates
'v':0, #Cart Velocity in the current
direction
'm':self.mass, #Mass
'g':self.gravity, #Gravity
't':0, #Time to travel distance
'direction':1, #Direction of cart, 1 = left to
right, -1 = right to left
'pa':0
}
self.calc_vel(self.curr_state,1)
self.root.after(int(self.curr_state['t']*self.time_mult),self.tick)
if __name__ == '__main__' :
rc = rollercoaster(sys.argv[1:])
rollercoaster.py
(application/x-python, 9.2 KB)
#!/usr/bin/env python ############################################# # File name : rollercoaster.py # Date : Fri Oct 22 04:41:40 2004 # Author : Jimmy Selgen Nielsen # Email : spamtrap1-x/claU4MwXpaa/[email protected] # Description : # Changelog : # Fri Oct 22 04:41:40 2004 : Initial version ############################################# from Tkinter import * import tkMessageBox,tkFileDialog,sys,os,math,time,tkSimpleDialog class rollercoaster: ''' Perl QOTW expert quiz #26 solution Opens file specified in argv[1], or file opened from menu. ''' def __init__(self,args): self.rc_tag = None self.height = 400 self.width = 600 self.gravity = 1 self.mass = 1 self.time_mult = 100 self.debug = 0 self.wnd = None self.init_display() if(len(args)): self.read_rc(args[0]) self.root.mainloop() def init_display(self): ''' Initialize main window ''' self.root = Tk() self.root.title("Rollercoaster Simulation, Perl-QOTW #26") self.speedvar = StringVar() self.main_frame = Frame(self.root) self.main_frame.pack(fill=BOTH,expand=1) self.run_button = Button(self.main_frame,text="Run",command=self.run) self.canvas = Canvas(self.main_frame,width=self.width,height=self.height) speed_label = Label(self.main_frame,text="Velocity") self.speedometer = Entry(self.main_frame,textvariable=self.speedvar) self.run_button.pack() self.canvas.pack() speed_label.pack() self.speedometer.pack() self.mainmenu = Menu(self.root) self.root.config(menu=self.mainmenu) self.filemenu = Menu(self.mainmenu) self.filemenu.add_command(label="Open",command=self.open_rc_handler) self.filemenu.add_command(label="Run",command=self.run) self.filemenu.add_command(label="Setup",command=self.config_dialog) self.filemenu.add_command(label="Exit",command=self.root.destroy) self.mainmenu.add_cascade(label="File",menu=self.filemenu) def ok_pressed(self): ''' Config dialog callback for OK ''' self.gravity = float(self.grav_str.get()) self.mass = float(self.mass_str.get()) self.time_mult = float(self.time_mult_str.get()) self.debug = self.dbg_var.get() print "new settings, gravity = %02f, mass = %02f, time_mult = %02f" % (self.gravity,self.mass,self.time_mult) self.wnd.destroy() self.wnd = None def cancel_pressed(self): ''' Config dialog callback for cancel ''' self.wnd.destroy() self.wnd = None def config_dialog(self): ''' Configuration dialog for setting mass, gravity, time multiplier and turning debugging on/off ''' if(self.wnd==None): self.wnd = Toplevel() self.wnd.title("Rollercoaster configuration") self.frm = Frame(self.wnd) self.grav_str = StringVar() self.mass_str = StringVar() self.time_mult_str = StringVar() self.dbg_var = IntVar() self.grav_str.set(self.gravity) self.mass_str.set(self.mass) self.time_mult_str.set(self.time_mult) self.dbg_var.set(self.debug) grav_label = Label(self.frm,text="Gravity") self.grav_entry = Entry(self.frm,textvariable=self.grav_str) mass_label = Label(self.frm,text="Mass") self.mass_entry = Entry(self.frm,textvariable=self.mass_str) delay_label = Label(self.frm,text="Delay multiplier (dist/v * mult)") self.time_mult_entry = Entry(self.frm,textvariable=self.time_mult_str) debug_btn = Checkbutton(self.frm,text="Debug",variable=self.dbg_var) ok_btn = Button(self.frm,text="OK",command=self.ok_pressed) cancel_btn = Button(self.frm,text="Cancel",command=self.cancel_pressed) grav_label.pack() self.grav_entry.pack() mass_label.pack() self.mass_entry.pack() delay_label.pack() self.time_mult_entry.pack() debug_btn.pack() ok_btn.pack() cancel_btn.pack() self.frm.pack() def draw_cart(self,coord): ''' Draws the "cart" from the given coordinate set ''' #print "draw_cart, coord = ",coord self.bbox = [coord[0]-2,self.height - (coord[1]-2),coord[0]+2,self.height - (coord[1]+2)] if(self.cart != None): self.canvas.coords(self.cart,*self.bbox) else: self.cart = self.canvas.create_oval(self.bbox,fill="red") self.canvas.update() def draw_rc(self): ''' Draws the rollercoaster from the current coordinate set ''' self.coords = [] self.cart = None self.canvas.delete('all') for p in range(len(self.rc)) : if(p < len(self.rc)-1): point = self.rc[p].rstrip('\n').split(' ') point[0] = float(point[0]) point[1] = float(point[1]) self.coords.append(point) if(self.rc_tag != None): self.canvas.delete(self.rc_tag) self.rc_tag = [] for p in range(len(self.coords)): if p < len(self.coords)-1: co = [self.coords[p][0],self.height - self.coords[p][1],self.coords[p+1][0],self.height - self.coords[p+1][1]] self.rc_tag.append(self.canvas.create_line(co)) self.canvas.pack(fill=BOTH) def calc_vel(self, curr_state, next_point): ''' Calculates new speed and direction for cart for a given point curr_state : current state of cart next_point : the current point of the cart returns : new state with updated cart speed,direction and time to travel distance ''' dist = 0 nv = 0 co = self.coords[curr_state['coord']] nco = self.coords[next_point] new_state = curr_state #calculate vertical and horizontal "grid" distance travelled dist = co[0] - nco[0] nh = co[1] - nco[1] dist_travelled = math.sqrt(dist**2 + nh**2) if(self.debug): print "dist :",dist," height :",nh," dist_travelled :",dist_travelled curr_state['pa'] += (curr_state['m'] * curr_state['g'] * nh) if(curr_state['pa'] > 0): nv = math.sqrt(curr_state['pa'] ) else: nv = -(math.sqrt(-curr_state['pa'])) if(self.debug): print "nv = ",nv new_state['v'] = nv #Change cart direction if velocity is less than zero if(new_state['v']<0): new_state['direction'] = 0 - new_state['direction'] #change negative velocity to positive velocity going the other way :) new_state['v'] = -new_state['v'] if(self.debug): print "new v :",new_state['v'] new_state['coord'] = next_point #Set time to travel distance at new velocity new_state['t'] = dist_travelled/ (new_state['v']+1) if(self.debug): print "new t = ",new_state['t'] return(new_state) def read_rc(self,fname): ''' Read a .rc file from disk ''' try: f = open(fname) self.rc = f.readlines() if(self.debug): print "read %d lines from %s" % (len(self.rc),fname) except: tkMessageBox.showerror('Error opening file','Error opening file "'+fname+'"') self.rc = None self.draw_rc() def open_rc_handler(self): ''' Menu callback for "File->Open" ''' fname = tkFileDialog.askopenfilename(filetypes=[(".rc", ".rc")], title="Select the file") if(fname != None): self.read_rc(fname) def tick(self): ''' "Mainloop" of the simulation. Callback to the windowmanager for every iteration of the simulation ''' self.curr_state = self.calc_vel(self.curr_state,self.curr_state['coord']+self.curr_state['direction']) self.draw_cart(self.coords[self.curr_state['coord']]) self.speedvar.set(round(self.curr_state['v'],2)) nco = self.curr_state['coord'] + self.curr_state['direction'] if(self.curr_state['v'] != 0 and nco >= 0 and nco < len(self.coords)): self.root.after(int(self.curr_state['t']*self.time_mult),self.tick) else: return(0) def run(self): ''' Initialize and start mainloop of simulation ''' self.curr_state = { 'coord' : 0, #Index into rollercoaster coordinates 'v':0, #Cart Velocity in the current direction 'm':self.mass, #Mass 'g':self.gravity, #Gravity 't':0, #Time to travel distance 'direction':1, #Direction of cart, 1 = left to right, -1 = right to left 'pa':0 } self.calc_vel(self.curr_state,1) self.root.after(int(self.curr_state['t']*self.time_mult),self.tick) if __name__ == '__main__' : rc = rollercoaster(sys.argv[1:])