Appendix A. Programs from the Book

This appendix contains the source code for the programs in the book. You can also find the source code online at the author’s website, as well as built into techBASIC and techBASIC Sampler. Look in the Maker Books folder in the techBASIC Programs directory.

This text-based BASIC program appears in Chapter 14. It is a one-dimensional simulation of the flight of Juno. The program will run in almost any implementation of BASIC with little or no change.

You can easily change the program to simulate other rocket flights. The lines to edit appear near the top of the listing.

rocketMass is the mass of the rocket in kilograms. The best way to get this value is to weigh the rocket on a kitchen scale. The weight includes the weight of the parachute and recovery wadding, but not the weight of the motor:

REM Rocket mass in kg, w/o the motor.
rocketMass = 0.036

You can calculate the coefficient of drag using the techniques in Chapter 13. You can also estimate the Cd. Use a value of 0.4 to 0.6 for most rockets, with 0.4 reserved for polished rockets with airfoil fins. Go higher if the rocket has lots of decorations hanging off it:

Cd = 0.524: REM Coefficient of drag.

The frontal area of the rocket is the area at the base of the nose cone. Given the diameter of the base of the nose cone, the area is:

techBASIC, QBASIC, and Other BASICs

The area is in square meters:

REM Frontal area of the rocket.
Ar = 0.000483

ρ (rho) is the density of the atmosphere in kilograms per cubic meter. The default value is the air density at sea level:

rho = 1.225: REM Density of the air.

Subtract about 4% per 1,000 feet if your launch site is significantly above sea level.

dt is the simulation time step. Lower numbers are generally more accurate, but make the program take longer to run:

dt = 0.01: REM Simulation time step.

motorName$ is the filename of a motor file in RASP format. You can find these files at http://www.thrustcurve.org. There are three preloaded in techBASIC (the other two are Estes_A8.eng and Estes_B6.eng):

motorName$ = "Estes_C6.eng"

You can install other motor files using iTunes; see the techBASIC Quick Start Guide for details. You can find the techBASIC Quick Start Guide at the techBASIC documentation page.

Example A-1 lists the complete source for the Juno Simulation program.

Example A-1. Text-based rocket simulator
REM Simple Text-Based Rocket Simulator.
REM
REM This simulation is set up to simulate a sea-level flight of Juno,
REM a rocket in the book "Make: Rockets, Down to Earth Rocket Science."
REM Change the variables in the next section to simulate a different rocket
REM rocket flight. See the book for details on how this simulator works.

DIM SHARED thrust(50), time(50), mass(50)
DIM SHARED propellantWeight, motorWeight
DIM SHARED bounds AS INTEGER
DIM SHARED tokens(20) AS STRING, tokenCount AS INTEGER

debug = 0

REM Initialize the rocket data. Change these values for different rockets,
REM rockets, motors, or flight conditions.
DIM rocketMass, d, d0, v0, a, fg, fd, ft, t, f, dt, Ar, rho, Cd
REM Rocket mass in kg, w/o the motor.
rocketMass = 0.036
Cd = 0.524: REM Coefficient of drag.
REM The Frontal area of the rocket.
Ar = 0.000483
rho = 1.225: REM Density of the air.
dt = 0.01: REM Simulation time step.

motorName$ = "Estes_C6.eng"

REM Load the motor data.
CALL LoadMotor (motorName$)
IF debug THEN
  PRINT propellantWeight, motorWeight
  FOR i = 1 TO bounds
    PRINT time(i), thrust(i), mass(i)
  NEXT
END IF

REM Run the simulation.
MaxA = 0.0
MaxV = 0.0
fg = (rocketMass + motorWeight)*9.81
DO
  t = t + dt
  fd = Cd*rho*v0*v0*Ar
  ft = Interpolate(t, time(), thrust())
  f = ft - fd - fg
  a = f/(rocketMass + Interpolate(t, time(), mass()))
  IF a > MaxA THEN MaxA = a
  d = d0 + v0*dt + a*t0*t0/2
  d0 = d
  v0 = v0 + a*dt
  fg = (rocketMass + Interpolate(t, time(), mass()))*9.81
  IF v0 > MaxV THEN MaxV = v0
  IF debug THEN PRINT t, d, v0, a
LOOP UNTIL (t > 1) AND (v0 < 0)

REM Print the results.
PRINT "Maximum altitude = "; d; " meters."
PRINT "Maximum velocity = "; MaxV; " m/s."
PRINT "Maximum acceleration = "; MaxA; " m/s^2."
PRINT "Ideal ejection delay = "; t - time(bounds); " seconds."
END


REM Use a specific time to interpolate in a table of time-based values.
REM
REM Inputs:
REM    t - The desired time.
REM    time - An array of times, from lowest to highest.
REM    value - A table of values, one for each elemnt element in the time array.
REM
REM Returns: A linearly interpolated value from the value array. If t is
REM    less than the first time value, the first value in the value array
REM    is returned. If t is greater that than the last time, the last value in
REM    the value array is returned.

FUNCTION Interpolate (t, time(), value())
IF t < time(1) THEN
  Interpolate = value(1)*t/time(1)
ELSE
  IF t < time(bounds) THEN
    DIM index AS INTEGER
    index = 1
    WHILE time(index) < t
      index = index + 1
    WEND
    Interpolate = value(index - 1) + _
       (t - time(index - 1))*(value(index) - value(index - 1))_
       /(time(index) - time(index - 1))
  ELSE
    Interpolate = value(bounds)
  END IF
END IF
END FUNCTION


REM Load a motor file.
REM
REM Parameters:
REM    fileName - The name of the motor file.
REM
REM Global values set:
REM    propellantWeight - The total weight of the propellant in kg.
REM    motorWeight - The total weight of the motor, with propellant
REM    in kg.
REM    time - The times for which thrust data is available.
REM    thrust - The thrust at each time, in newtons.
REM    mass - The motor mass values at each time step.

SUB LoadMotor (fileName AS STRING)
DIM aLine AS STRING, count AS INTEGER

REM Scan the file, reading the data.
OPEN fileName FOR INPUT AS #1
bounds = -1
WHILE NOT EOF(1)
  LINE INPUT #1, aLine
  IF LEN(aLine) > 0 AND LEFT$(aLine, 1) <> ";" THEN
    CALL Parse(aLine)
    bounds = bounds + 1
    IF bounds = 0 THEN
      propellantWeight = VAL(tokens(5))
      motorWeight = VAL(tokens(6))
    ELSE
      time(bounds) = VAL(tokens(1))
      thrust(bounds) = VAL(tokens(2))
    END IF
  END IF
WEND
CLOSE #1

REM Find the total impulse.
DIM impulse
impulse = TotalImpulse(time(), thrust(), bounds)
IF debug THEN PRINT "Total Impulse = "; impulse

REM Assume the mass loss is proportional to the impulse. Find
REM the motor mass as a function of time.
DIM currentMass, t0, f0, di, i AS INTEGER
currentMass = motorWeight
FOR i = 1 TO bounds
  di = (f0 + thrust(i))*(time(i) - t0)/2
  currentMass = currentMass - propellantWeight*di/impulse
  mass(i) = currentMass
  f0 = thrust(i)
  t0 = time(i)
NEXT
END SUB


REM A utility subroutine for LoadMotor that breaks a line into space-
REM delimited strings. The tokens are placed in the global array tokens.
REM
REM Parameters:
REM    aLine - The line to parse.

SUB Parse (aLine AS STRING)
REM Declare and initialize our local variables.
DIM index AS INTEGER
tokenCount = 0

REM Strip any leading space from the line.
WHILE LEFT$(aLine, 1) = " "
  aLine = RIGHT$(aLine, LEN(aLine) - 1)
WEND

REM Collect the tokens.
WHILE LEN(aLine) > 0 AND tokenCount < 10
  tokenCount = tokenCount + 1
  index = SpaceIndex(aLine)
  IF index = 0 THEN
    tokens(tokenCount) = aLine
    aLine = ""
  ELSE
    tokens(tokenCount) = LEFT$(aLine, index - 1)
    aLine = RIGHT$(aLine, LEN(aLine) - index)
    WHILE LEFT$(aLine, 1) = " "
      aLine = RIGHT$(aLine, LEN(aLine) - 1)
    WEND
  END IF
WEND
END SUB


REM A utility function for Parse that finds the index of the next space
REM character.
REM
REM Parameters:
REM    sval - The string.
REM
REM Returns: The index of the next space, or 0 if there are none.

FUNCTION SpaceIndex (sval AS STRING)
DIM index AS INTEGER, fund AS INTEGER
found = 0
index = 1
WHILE (index < LEN(sval)) AND (found = 0)
  IF MID$(sval, index, 1) = " " THEN
    found = 1
  ELSE
    index = index + 1
  END IF
WEND
IF found = 0 THEN
  index = 0
END IF
SpaceIndex = index
END FUNCTION


REM Find the total impulse of the motor.
REM
REM Parameters:
REM    time - The time steps where motor thrust is available.
REM    thrust - The thrust at each time step.
REM    bounds - The number of data points in time() and thrust().
REM
REM Returns: The total impuls impulse in newton-seconds.

FUNCTION TotalImpulse (time(), thrust(), bounds AS INTEGER)
DIM t0, f0, impulse, i AS INTEGER
FOR i = 1 TO bounds
  impulse = impulse + (f0 + thrust(i))*(time(i) - t0)/2
  t0 = time(i)
  f0 = thrust(i)
NEXT
TotalImpulse = impulse
END FUNCTION

The Simple Rocket Simulator (SRockS) is essentially the Juno Simulation from the previous section with a user interface for the iPhone and iPod touch. It will also run on the iPad, of course. It is preinstalled in techBASIC and techBASIC Sampler in the Maker Books folder.

The rocket mass is given in kilograms. The best way to get this value is to weigh the rocket on a kitchen scale. As before, the weight includes the weight of the parachute and recovery wadding, but not the weight of the motor.

You can calculate the coefficient of drag using the techniques in Chapter 13. You can also estimate the Cd. Use a value of 0.4 to 0.6 for most rockets, with 0.4 reserved for polished rockets with airfoil fins. Go higher if the rocket has lots of decorations hanging off it.

The area of the rocket is the frontal area, at the base of the nose cone. Tap on the value to select a new value. A list of common body tube sizes appears. Select the appropriate body tube size and the frontal area will be filled in automatically. The area is in square meters.

Atm. density is the density of the atmosphere in kilograms per cubic meter. Subtract about 4% per 1,000 feet if your launch site is significantly above sea level.

The time step is the simulation time step. Lower numbers are generally more accurate, but make the program take longer to run.

Tap the motor value to select a different motor. A picker will appear that will show all of the installed motor files. Select the new motor and tap the Done button. You can find additional motor files at http://www.thrustcurve.org. Follow the instructions in the techBASIC Quick Start Guide to install new motor files using iTunes. You can find the techBASIC Quick Start Guide at the techBASIC documentation page.

Example A-2 shows the complete source for the SRockS program.

Example A-2. One-dimensional rocket flight simulator
REM Simple Rocket Simulator.
REM
REM One-dimensional rocket flight simulator.

REM Motor variables.
DIM propellantWeight, motorWeight, motorName AS STRING
DIM thrust(1), time(1), mass(1)
DIM motorNames(1) AS STRING, motorFileNames(1) AS STRING

REM GUI elements.
DIM titleLabel AS Label
DIM quitButton AS Button, simulateButton AS Button
DIM massTextField as TextField, massLabel as Label
DIM cdTextField as TextField, cdLabel as Label
DIM arValueLabel as Label, arPicker AS Picker, arLabel as Label
DIM rhoTextField as TextField, rhoLabel as Label
DIM dtTextField as TextField, dtLabel as Label
DIM motorValueLabel as Label, motorPicker AS Picker, motorLabel as Label

DIM altitudeValueLabel as Label, altitudeLabel as Label
DIM speedValueLabel as Label, speedLabel as Label
DIM accelerationValueLabel as Label, accelerationLabel as Label
DIM delayValueLabel as Label, delayLabel as Label
DIM pickerBackground AS Label, pickerDoneButton AS Button

REM Initialize the rocket data. Change these values for different rockets,
REM motors, or flight conditions.
DIM rocketMass, d, d0, v0, a, fg, fd, ft, t, f, dt, Ar, rho, Cd
rocketMass = 0.036 : REM Rocket mass in kilograms, without the motor.
Cd = 0.524 : REM Coefficient of drag.
Ar = 0.000483 : REM Frontal area of the rocket.
rho = 1.225 : REM Density of the air.
dt = 0.01 : REM Simulation time step.

REM Set up the program.
loadMotors
motorName$ = motorNames(1)
motorFileName$ = motorFileNames(1)
initGUI
simulate
showAbout
END


REM Create a label and an initialized TextField.
REM
REM Parameters:
REM    x, y - The top-left location for the label.
REM    title - The title for the label.
REM    labelCtrl - The label control.
REM    textFieldCtrl - The text field control.
REM    value - The initial value.
REM    scientific - Format the value using scientific notation?

SUB createEditable (x, _
                    y, _
                    title AS STRING, _
                    BYREF labelCtrl AS Label, _
                    BYREF textFieldCtrl AS TextField, _
                    value, _
                    scientific AS INTEGER)
labelCtrl = Graphics.newLabel(x, y, 140)
labelCtrl.setText(title)
labelCtrl.setAlignment(3)
textFieldCtrl = Graphics.newTextField(x + 150, y, 140)
textFieldCtrl.setText(format(value, scientific))
END SUB


REM Create a label, button, and picker. The button displays a value.
REM That value can be changed using a picker, which is displayed when
REM the user taps on the button and hidden when the user picks a value
REM or taps on a Done button, which is shared among all picker controls.
REM
REM Parameters:
REM    x, y - The top-left location for the label.
REM    title - The title for the label.
REM    labelCtrl - The label control.
REM    valueCtrl - The label for displaying a value.
REM    pickerCtrl - The picker control.
REM    values - The values to display in the picker control.
REM    value - The initial value.
REM    scientific - Format the value using scientific notation?

SUB createPicker (x, _
                  y, _
                  title AS STRING, _
                  BYREF labelCtrl AS Label, _
                  BYREF valueCtrl AS Label, _
                  value, _
                  scientific AS INTEGER)
labelCtrl = Graphics.newLabel(x, y, 140)
labelCtrl.setText(title)
labelCtrl.setAlignment(3)

valueCtrl = Graphics.newLabel(x + 150, y, 140)
valueCtrl.setText(format(value, scientific))
END SUB


REM Create a label and an initialized TextField.
REM
REM Parameters:
REM    x, y - The top-left location for the label.
REM    title - The title for the label.
REM    labelCtrl - The label control.
REM    valueCtrl - The label for displaying a value.
REM    value - The initial value.
REM    scientific - Format the value using scientific notation?

SUB createUneditable (x, _
                      y, _
                      title AS STRING, _
                      BYREF labelCtrl AS Label, _
                      BYREF valueCtrl AS Label, _
                      value, _
                      scientific AS INTEGER)
labelCtrl = Graphics.newLabel(x, y, 140)
labelCtrl.setText(title)
labelCtrl.setAlignment(3)
valueCtrl = Graphics.newLabel(x + 150, y, 140)
valueCtrl.setText(format(value, scientific))
END SUB


REM Format a floating-point value for display. This is used for all
REM fields to give a consistent, readable view of values.
REM
REM Parameters:
REM    value - The value to format.
REM    scientific - Format the value using scientific notation?
REM
REM Returns: The formatted value.

FUNCTION format (value, scientific AS INTEGER) AS STRING
DIM s$
IF scientific THEN
  PRINT $ s$ USING "#######.###^^^^^^"; value;
ELSE
  PRINT $ s$ USING "#######.###"; value;
END IF
s$ = LTRIM(s$)
format = s$
END FUNCTION


REM Set up the user interface.

SUB initGUI
System.showGraphics(1)
Graphics.setToolsHidden(1)

width = Graphics.width
height = Graphics.height

REM Add a title.
titleLabel = Graphics.newLabel(20, 20, width - 40, 40)
titleLabel.setText("Simple Rocket Simulator")
titleLabel.setAlignment(2)
titleLabel.setFont("Sans-Serif", 22, 1)

REM Create labels and initialized text boxes for the editable rocket
REM components.
DIM y, dy
y = 70
dy = 28
createEditable(20, y, "Rocket Mass:", massLabel, massTextField, _
  rocketMass, 0)
y = y + dy
createEditable(20, y, "Cd:", cdLabel, cdTextField, Cd, 0)
y = y + dy
createPicker(20, y, "Area:", arLabel, arValueLabel, Ar, 1)
y = y + dy
createEditable(20, y, "Atm. Density:", rhoLabel, rhoTextField, rho, 0)
y = y + dy
createEditable(20, y, "Time Step:", dtLabel, dtTextField, dt, 0)
y = y + dy
createPicker(20, y, "Motor:", motorLabel, motorValueLabel, 0, 0)
motorValueLabel.setText(motorName$)
y = y + dy

REM Create the buttons.
quitButton = newButton(width - 92, height - 57, -1, "Quit")
simulateButton = newButton(20, y, 280, "Run Simulation")

REM Create the labels and uneditable text fields for the results.
y = y + 47
createUneditable(20, y, "Altitude:", altitudeLabel, _
  altitudeValueLabel, 0, 0)
y = y + dy
createUneditable(20, y, "Max Speed:", speedLabel, speedValueLabel, 0, 0)
y = y + dy
createUneditable(20, y, "Max Accel.:", accelerationLabel, _
  accelerationValueLabel, 0, 0)
y = y + dy
createUneditable(20, y, "Best Delay:", delayLabel, delayValueLabel, 0, _
  0)

REM Create the pickers and related objects. These are created last so they
REM will show up on top of other controls.
pickerBackground = Graphics.newLabel(0, 70, width, height - 70)
pickerBackground.setHidden(1)

pickerDoneButton = newButton((width - 72)/2, 320, -1, "Done")
pickerDoneButton.setHidden(1)

DIM values(7) AS STRING
values(1) = "BT-5"
values(2) = "BT-20"
values(3) = "BT-50"
values(4) = "BT-55"
values(5) = "BT-60"
values(6) = "BT-70"
values(7) = "BT-80"
arPicker = Graphics.newPicker(0, 80)
arPicker.setHidden(1)
arPicker.insertRows(values, 1)
arPicker.selectRow(3)

motorPicker = Graphics.newPicker(0, 80)
motorPicker.setHIdden(1)
motorPicker.insertRows(motorNames, 1)
motorPicker.selectRow(1)
END SUB

REM Use a specific time to interpolate in a table of time-based values.
REM
REM Inputs:
REM    t - The desired time.
REM    time - An array of times, from lowest to highest.
REM    value - A table of values, one for each elemnt in the time array.
REM
REM Returns: A linearly interpolated value from the value array. If t is
REM    less than the first time value, the first value in the value array
REM    is returned. If t is greater than the last time, the last value in
REM    the value array is returned.

FUNCTION interpolate (t, time(), value())
IF t < time(1) THEN
  interpolate = value(1)*t/time(1)
ELSE IF t < time(UBOUND(time, 1)) THEN
  DIM index AS INTEGER
  index = 1
  WHILE time(index) < t
    index = index + 1
  WEND
  interpolate = value(index - 1) + _
    (t - time(index - 1))*(value(index) - value(index - 1)) _
    /(time(index) - time(index - 1))
ELSE
  interpolate = value(UBOUND(value, 1))
END IF
END FUNCTION

REM Load a motor file.
REM
REM The values for the motor file are placed in global variables. The
REM variables set are:
REM
REM    propellantWeight - The total weight of the propellant in kg.
REM    motorWeight - The total weight of the motor, with propellant, in kg.
REM    motorName - The name for this motor.
REM    time - The times for which thrust data is available.
REM    thrust - The thrust at each time, in newtons.
REM    mass - The motor mass values at each time step.
REM
REM Parameters:
REM    fileName - The name of the motor file.

SUB loadMotor (fileName AS String)
DIM aLine AS STRING, tokens(1) AS STRING, count AS INTEGER

REM Scan the file, counting the non-comment lines. This gives
REM the number of data points.
OPEN fileName FOR INPUT AS #1
WHILE NOT EOF(1)
  LINE INPUT #1, aLine
  IF LEN(aLine) > 0 AND LEFT(aLine, 1) <> ";" THEN
    count = count + 1
  END IF
WEND
CLOSE #1

REM Dimension arrays to hold the time, thrust, and weight.
DIM tm(count - 1), tr(count - 1), ma(count - 1)

REM Rescan the file, reading the data.
OPEN fileName FOR INPUT AS #1
count = 0
WHILE NOT EOF(1)
  LINE INPUT #1, aLine
  IF LEN(aLine) > 0 AND LEFT(aLine, 1) <> ";" THEN
    tokens = parse(aLine)
    IF count = 0 THEN
      motorName = tokens(1)
      propellantWeight = VAL(tokens(5))
      motorWeight = VAL(tokens(6))
    ELSE
      tm(count) = VAL(tokens(1))
      tr(count) = VAL(tokens(2))
    END IF
    count = count + 1
  END IF
WEND
CLOSE #1

REM Find the total impulse.
DIM impulse
impulse = totalImpulse(tm, tr)
IF debug THEN PRINT "Total Impulse = "; impulse

REM Assume the mass loss is proportional to the impulse. Find
REM the motor mass as a function of time.
DIM currentMass, t0, f0, di, i AS INTEGER
currentMass = motorWeight
FOR i = 1 TO UBOUND(tm, 1)
  di = (f0 + tr(i))*(tm(i) - t0)/2
  currentMass = currentMass - propellantWeight*di/impulse
  ma(i) = currentMass
  f0 = tr(i)
  t0 = tm(i)
NEXT

REM Copy the values to the global arrays.
time = tm
thrust = tr
mass = ma
END SUB

REM Scans the document directory for motor files. Any that are found are
REM placed in the two arrays of motors. The arrays are:
REM
REM    motorNames - The display names for the motors.
REM    motorFileNames - The filenames for each motor file.

SUB loadMotors
DIM fileName AS STRING, count AS INTEGER, i AS INTEGER
count = 0
fileName = DIR("*")
WHILE fileName <> ""
  IF LCASE(RIGHT(fileName, 4)) = ".eng" THEN
    count = count + 1
    loadMotor(fileName)
    DIM names(count) AS STRING, fileNames(COUNT) AS STRING
    FOR i = 1 TO count - 1
      names(i) = motorNames(i)
      fileNames(i) = motorFileNames(i)
    NEXT
    names(count) = motorName
    fileNames(count) = fileName
    motorNames = names
    motorFileNames = fileNames
  END IF
  fileName = DIR
WEND
print motorNames
print motorFileNames
END SUB

REM Creates a new button with a gradient fill.
REM
REM Parameters:
REM    x - Horizontal location.
REM    y - Vertical location.
REM    width - Width of the button, or -1 for the default width.
REM    title - Name of the button.
REM
REM Returns: The new button.

FUNCTION newButton (x, y, width, title AS STRING) AS Button
DIM b AS Button
IF width = -1 THEN width = 72
b = Graphics.newButton(x, y, width)
b.setTitle(title)
b.setBackgroundColor(1, 1, 1)
b.setGradientColor(0.6, 0.6, 0.6)
newButton = b
END FUNCTION

REM A utility subroutine for loadMotor that breaks a line into space-
REM delimited strings.
REM
REM Parameters:
REM    aLine - The line to parse.
REM
REM Returns: An array of strings.

FUNCTION parse (aLine AS STRING) (1) AS STRING
REM Declare and initialize our local variables.
DIM tokens(10) AS STRING, count AS INTEGER, index AS INTEGER
count = 0

REM Strip any leading space from the line.
WHILE LEFT(aLine, 1) = " "
  aLine = RIGHT(aLine, LEN(aLine) - 1)
WEND

REM Collect the tokens.
WHILE LEN(aLine) > 0 AND count < 10
  count = count + 1
  index = POS(aLine, " ")
  IF index = 0 THEN
    tokens(count) = aLine
    aLine = ""
  ELSE
    tokens(count) = LEFT(aLine, index)
    aLine = RIGHT(aLine, LEN(aLine) - index)
    WHILE LEFT(aLine, 1) = " "
      aLine = RIGHT(aLine, LEN(aLine) - 1)
    WEND
  END IF
WEND

REM Create an array for the final tokens and return them.
DIM finalTokens(count) AS STRING
WHILE count > 0
  finalTokens(count) = tokens(count)
  count = count - 1
WEND

parse = finalTokens
END FUNCTION

REM Shows the About alert when the program starts.

SUB showAbout
about$ = "One-dimensional simulation of a model rocket flight."

about$ = about$ & CHR(10) & CHR(10) & "See the book, Make: Rockets, " _
  & "for a complete description of this app."

about$ = about$ & CHR(10) & CHR(10) & "This app takes basic " _
  & "information about a model rocket and simulates its flight to " _
  & "find the maximum altitude, acceleration, and speed and the " _
  & "ideal ejection charge."

about$ = about$ & CHR(10) & CHR(10) & "All units are in the meters-" _
  & "kilograms-seconds (MKS) system. Cd is the coefficient of drag, " _
  & "generally 0.4 to 0.7 for most model rockets. Area is the " _
  & "frontal area of the body tube. Atm. density is the atmospheric " _
  & "density, preset for sea level. Time step is the simulation " _
  & "time step."

about$ = about$ & CHR(10) & CHR(10) & "Tap a value to change it. Tap " _
  & "Run Simulation to run the simulation with the new values."

i = Graphics.showAlert("About This Sample", about$)
END SUB

REM Simulate a flight with the current flight characteristics.

SUB simulate
REM Load the motor data.
loadMotor(motorFileName$)
print motorFileName$
IF debug THEN
  PRINT propellantWeight, motorWeight
  FOR i = 1 TO UBOUND(time, 1)
    PRINT time(i), thrust(i), mass(i)
  NEXT
END IF

REM Get the latest values for the simulation.
rocketMass = VAL(massTextField.getText)
Cd = VAL(cdTextField.getText)
rho = VAL(rhoTextField.getText)
dt = VAL(dtTextField.getText)

REM Run the simulation.
DIM MaxA, MaxV, fg, t, fd, ft, f, a, d, d0, v0
MaxA = 0.0
MaxV = 0.0
fg = (rocketMass + motorWeight)*9.81
DO
  t = t + dt
  fd = Cd*rho*v0*v0*Ar
  ft = interpolate(t, time, thrust)
  f = ft - fd - fg
  a = f/(rocketMass + interpolate(t, time, mass))
  IF a > MaxA THEN MaxA = a
  d = d0 + v0*dt + a*t0*t0/2
  d0 = d
  v0 = v0 + a*dt
  fg = (rocketMass + interpolate(t, time, mass))*9.81
  IF v0 > MaxV THEN MaxV = v0
  IF DEBUG THEN PRINT t, d, v0, a
LOOP UNTIL t > 1 AND v0 < 0

REM Show the results.
altitudeValueLabel.setText(format(d, 0))
speedValueLabel.setText(format(MaxV, 0))
accelerationValueLabel.setText(format(MaxA, 0))
delayValueLabel.setText(format(t - time(UBOUND(time, 1)), 0))
END SUB

REM Find the total impulse of the motor.
REM
REM Parameters:
REM    time - The time steps where motor thrust is available.
REM    thrust - The thrust at each time step.
REM
REM Returns: The total impulse in newton-seconds.

FUNCTION totalImpulse (time(), thrust())
DIM t0, f0, impulse, i AS INTEGER
FOR i = 1 TO UBOUND(time, 1)
  impulse = impulse + (f0 + thrust(i))*(time(i) - t0)/2
  t0 = time(i)
  f0 = thrust(i)
NEXT
totalImpulse = impulse
END FUNCTION

REM Called when a touch ends, this subroutine checks to see if the
REM touch was inside one of the labels used to trigger the appearance
REM of a picker and, if so, displays the picker.
REM
REM Parameters:
REM    e - The touch event that triggered this call.

SUB touchesEnded (e AS EVENT)
DIM where(1, 2), x, y
where = e.where
x = where(1, 1)
y = where(1, 2)
IF x >= arValueLabel.x _
   AND x <= arValueLabel.x + arValueLabel.width _
   AND y >= arValueLabel.y _
   AND y <= arValueLabel.y + arValueLabel.height THEN
  pickerBackground.setHidden(0)
  arPicker.setHidden(0)
  pickerDoneButton.setHidden(0)
ELSE IF x >= motorValueLabel.x _
   AND x <= motorValueLabel.x + motorValueLabel.width _
   AND y >= motorValueLabel.y _
   AND y <= motorValueLabel.y + motorValueLabel.height THEN
  pickerBackground.setHidden(0)
  motorPicker.setHidden(0)
  pickerDoneButton.setHidden(0)
END IF
END SUB

REM Handle a tap on one of the buttons.
REM
REM Parameters:
REM    ctrl - The button that was tapped.
REM    time - The time when the event occurred.

SUB touchUpInside (ctrl AS Button, time AS DOUBLE)
IF ctrl = quitButton THEN
  STOP
ELSE IF ctrl = pickerDoneButton THEN
  arPicker.setHidden(1)
  motorPicker.setHidden(1)
  pickerDoneButton.setHidden(1)
  pickerBackground.setHidden(1)

  SELECT CASE arPicker.selection
    CASE 1: Ar = 0.000150
    CASE 2: Ar = 0.000275
    CASE 3: Ar = 0.000483
    CASE 4: Ar = 0.000892
    CASE 5: Ar = 0.001359
    CASE 6: Ar = 0.002463
    CASE 7: Ar = 0.003390
  END SELECT
  arValueLabel.setText(format(Ar, 1))

  motorName$ = motorNames(motorPicker.selection)
  motorValueLabel.setText(motorName$)
  motorFileName$ = motorFileNames(motorPicker.selection)
  loadMotor(motorFileName$)
ELSE IF ctrl = simulateButton THEN
  simulate
END IF
END SUB

Theodolite is a simple BASIC program that should run in just about any modern BASIC compiler. It takes the baseline and angles from two theodolites and calculates the altitude of a rocket. See Chapter 8 for a detailed description of how to build and use theodolites to track rockets.

Example A-3. Altitude calculator
REM Calculate the altitude of a model rocket using angles from
REM two theodolites.

REM Get the baseline and angles.
INPUT "Baseline length: "; B

REM Find the conversion from degrees to radians.
DTR = 3.1415926535/180

REM Get the angles for the first tracker.
INPUT "Tracker 1 azimuth: "; alpha1
INPUT "Tracker 1 elevation: "; epsilon1
alpha1 = alpha1*DTR
epsilon1 = epsilon1*DTR

REM Get the angles for the second tracker.
INPUT "Tracker 2 azimuth: "; alpha2
INPUT "Tracker 2 elevation: "; epsilon2
alpha2 = alpha2*DTR
epsilon2 = epsilon2*DTR

REM Calculate the altitude using the vertical midpoint method.
A1 = B*SIN(alpha2)*TAN(epsilon1)/SIN(alpha1 + alpha2)
A2 = B*SIN(alpha1)*TAN(epsilon2)/SIN(alpha1 + alpha2)
A = (A1 + A2)/2
C = ABS((A1 - A2)/(2*A))

PRINT
PRINT "Vertical Midpoint Method:"
CALL Report(A, C)

REM Calculate the altitude using the geodesic method.
se1 = SIN(epsilon1)
se2 = SIN(epsilon2)
ce1 = COS(epsilon1)
ce2 = COS(epsilon2)
sa1 = SIN(alpha1)
sa2 = SIN(alpha2)
ca1 = COS(alpha1)
ca2 = COS(alpha2)
f = se1*se2 - ce1*ce2*(ca1*ca2 - sa1*sa2)
d1 = B*(ce1*ca1 + f*ce2*ca2)/(1-f*f)
d2 = B*(ce2*ca2 + f*ce1*ca1)/(1-f*f)
A = d1*d2*(se1 + se2)/(d1 + d2)
C = B*ABS((ce2*se1*sa2 - ce1*se2*sa1)/(A*SQR(1-f*f)))

PRINT
PRINT "Geodesic Method:"
CALL Report(A, C)
PRINT
END

REM Print the results.
REM
REM Inputs:
REM    A - The altitude.
REM    C - Track closed value.

SUB Report (A, C)
PRINT "  Altitude: "; A
IF C <= 0.1 THEN
  PRINT "  Closed track; C = "; C
ELSE
  PRINT "  Track not closed; C = "; C
END IF
END SUB