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:])