# Experimenting with Genetic Algorithms

#### September 13, 2016

Genetic algorithm trying to find a series of four mathematical operations (e.g. -3*4/7+9) that would result in the number 42.

I’m teaching a numerical methods class that’s partly an introduction to programming, and partly a survey of numerical solutions to different types of problems students might encounter in the wild. I thought I’d look into doing a session on genetic algorithms, which are an important precursor to things like networks that have been found to be useful in a wide variety of fields including image and character recognition, stock market prediction and medical diagnostics.

The ai-junkie, bare-essentials page on genetic algorithms seemed a reasonable place to start. The site is definitely readable and I was able to put together a code to try to solve its example problem: to figure out what series of four mathematical operations using only single digits (e.g. +5*3/2-7) would give target number (42 in this example).

The procedure is as follows:

• Initialize: Generate several random sets of four operations,
• Test for fitness: Check which ones come closest to the target number,
• Select: Select the two best options (which is not quite what the ai-junkie says to do, but it worked better for me),
• Mate: Combine the two best options semi-randomly (i.e. exchange some percentage of the operations) to produce a new set of operations
• Mutate: swap out some small percentage of the operations randomly,
• Repeat: Go back to the second step (and repeat until you hit the target).

And this is the code I came up with:

genetic_algorithm2.py

''' Write a program to combine the sequence of numbers 0123456789 and
the operators */+- to get the target value (42 (as an integer))
'''

'''
Procedure:
1. Randomly generate a few sequences (ns=10) where each sequence is 8
charaters long (ng=8).
2. Check how close the sequence's value is to the target value.
The closer the sequence the higher the weight it will get so use:
w = 1/(value - target)
3. Chose two of the sequences in a way that gives preference to higher
weights.
4. Randomly combine the successful sequences to create new sequences (ns=10)
5. Repeat until target is achieved.

'''
from visual import *
from visual.graph import *
from random import *
import operator

# MODEL PARAMETERS
ns = 100
target_val = 42 #the value the program is trying to achieve
sequence_length = 4  # the number of operators in the sequence
crossover_rate = 0.3
mutation_rate = 0.1
max_itterations = 400

class operation:
def __init__(self, operator = None, number = None, nmin = 0, nmax = 9, type="int"):
if operator == None:
n = randrange(1,5)
if n == 1:
self.operator = "+"
elif n == 2:
self.operator = "-"
elif n == 3:
self.operator = "/"
else:
self.operator = "*"
else:
self.operator = operator

if number == None:
#generate random number from 0-9
self.number = 0
if self.operator == "/":
while self.number == 0:
self.number = randrange(nmin, nmax)
else:
self.number = randrange(nmin, nmax)
else:
self.number = number
self.number = float(self.number)

def calc(self, val=0):
# perform operation given the input value
if self.operator == "+":
val += self.number
elif self.operator == "-":
val -= self.number
elif self.operator == "*":
val *= self.number
elif self.operator == "/":
val /= self.number
return val

class gene:

def __init__(self, n_operations = 5, seq = None):
#seq is a sequence of operations (see class above)
#initalize
self.n_operations = n_operations

#generate sequence
if seq == None:
#print "Generating sequence"
self.seq = []
self.seq.append(operation(operator="+"))  # the default operation is + some number
for i in range(n_operations-1):
#generate random number
self.seq.append(operation())

else:
self.seq = seq

self.calc_seq()

#print "Sequence: ", self.seq
def stringify(self):
seq = ""
for i in self.seq:
seq = seq + i.operator + str(i.number)
return seq

def calc_seq(self):
self.val = 0
for i in self.seq:
#print i.calc(self.val)
self.val = i.calc(self.val)
return self.val

def crossover(self, ingene, rate):
# combine this gene with the ingene at the given rate (between 0 and 1)
#  of mixing to create two new genes

#print "In 1: ", self.stringify()
#print "In 2: ", ingene.stringify()
new_seq_a = []
new_seq_b = []
for i in range(len(self.seq)):
if (random() < rate): # swap
new_seq_a.append(ingene.seq[i])
new_seq_b.append(self.seq[i])
else:
new_seq_b.append(ingene.seq[i])
new_seq_a.append(self.seq[i])

new_gene_a = gene(seq = new_seq_a)
new_gene_b = gene(seq = new_seq_b)

#print "Out 1:", new_gene_a.stringify()
#print "Out 2:", new_gene_b.stringify()

return (new_gene_a, new_gene_b)

def mutate(self, mutation_rate):
for i in range(1, len(self.seq)):
if random() < mutation_rate:
self.seq[i] = operation()

def weight(target, val):
if val <> None:
#print abs(target - val)
if abs(target - val) <> 0:
w = (1. / abs(target - val))
else:
w = "Bingo"
print "Bingo: target, val = ", target, val
else:
w = 0.
return w

def pick_value(weights):
#given a series of weights randomly pick one of the sequence accounting for
# the values of the weights

# sum all the weights (for normalization)
total = 0
for i in weights:
total += i

# make an array of the normalized cumulative totals of the weights.
cum_wts = []
ctot = 0.0
cum_wts.append(ctot)
for i in range(len(weights)):
ctot += weights[i]/total
cum_wts.append(ctot)
#print cum_wts

# get random number and find where it occurs in array
n = random()
index = randrange(0, len(weights)-1)
for i in range(len(cum_wts)-1):
#print i, cum_wts[i], n, cum_wts[i+1]
if n >= cum_wts[i] and n < cum_wts[i+1]:

index = i
#print "Picked", i
break
return index

def pick_best(weights):
# pick the top two values from the sequences
i1 = -1
i2 = -1
max1 = 0.
max2 = 0.
for i in range(len(weights)):
if weights[i] > max1:
max2 = max1
max1 = weights[i]
i2 = i1
i1 = i
elif weights[i] > max2:
max2 = weights[i]
i2 = i

return (i1, i2)

# Main loop
l_loop = True
loop_num = 0
best_gene = None

##test = gene()
##test.print_seq()
##print test.calc_seq()

# initialize
genes = []
for i in range(ns):
genes.append(gene(n_operations=sequence_length))
#print genes[-1].stringify(), genes[-1].val

f1 = gcurve(color=color.cyan)

while (l_loop and loop_num < max_itterations):
loop_num += 1
if (loop_num%10 == 0):
print "Loop: ", loop_num

# Calculate weights
weights = []
for i in range(ns):
weights.append(weight(target_val, genes[i].val))
# check for hit on target
if weights[-1] == "Bingo":
print "Bingo", genes[i].stringify(), genes[i].val
l_loop = False
best_gene = genes[i]
break
#print weights

if l_loop:

# indicate which was the best fit option (highest weight)
max_w = 0.0
max_i = -1
for i in range(len(weights)):
#print max_w, weights[i]
if weights[i] > max_w:
max_w = weights[i]
max_i = i
best_gene = genes[max_i]
##        print "Best operation:", max_i, genes[max_i].stringify(), \
##              genes[max_i].val, max_w
f1.plot(pos=(loop_num, best_gene.val))

# Pick parent gene sequences for next generation
# pick first of the genes using weigths for preference
##        index = pick_value(weights)
##        print "Picked operation:  ", index, genes[index].stringify(), \
##              genes[index].val, weights[index]
##
##        # pick second gene
##        index2 = index
##        while index2 == index:
##            index2 = pick_value(weights)
##        print "Picked operation 2:", index2, genes[index2].stringify(), \
##              genes[index2].val, weights[index2]
##

(index, index2) = pick_best(weights)

# Crossover: combine genes to get the new population
new_genes = []
for i in range(ns/2):
(a,b) = genes[index].crossover(genes[index2], crossover_rate)
new_genes.append(a)
new_genes.append(b)

# Mutate
for i in new_genes:
i.mutate(mutation_rate)

# update genes array
genes = []
for i in new_genes:
genes.append(i)

print
print "Best Gene:", best_gene.stringify(), best_gene.val
print "Number of iterations:", loop_num
##



When run, the code usually gets a valid answer, but does not always converge: The figure at the top of this post shows it finding a solution after 142 iterations (the solution it found was: +8.0 +8.0 *3.0 -6.0). The code is rough, but is all I have time for at the moment. However, it should be a reasonable starting point if I should decide to discuss these in class.

Citing this post: Urbano, L., 2016. Experimenting with Genetic Algorithms, Retrieved March 27th, 2017, from Montessori Muddle: http://MontessoriMuddle.org/ .
Attribution (Curator's Code ): Via: Montessori Muddle; Hat tip: Montessori Muddle.

# Vpython on the Web with Glowscript

#### February 25, 2016

Using glowscript.org, we can now put vpython programs online. Here’s a first shot of the coil program:

http://www.glowscript.org/#/user/lurbano/folder/Private/program/magnet-coil.py

Moving the magnet through a wire coil creates an electric current in the wire.

Some changes to the standard vpython language must be used and there are some limitations, but it seems to work.

Citing this post: Urbano, L., 2016. Vpython on the Web with Glowscript, Retrieved March 27th, 2017, from Montessori Muddle: http://MontessoriMuddle.org/ .
Attribution (Curator's Code ): Via: Montessori Muddle; Hat tip: Montessori Muddle.

# Dilation

#### December 14, 2015

Dilation (scaling) of a quadrilateral by 2x.

A quick program that animates scaling (dilation) of shapes by scaling the coordinates. You type in the dilation factor.

dilation.py

from visual import *

#axes
xmin = -10.
xmax = 10.
ymin = -10.
ymax = 10.
xaxis = curve(pos=[(xmin,0),(xmax,0)])
yaxis = curve(pos=[(0,ymin),(0,ymax)])

#tick marks
tic_dx = 1.0
tic_h = .5
for i in arange(xmin,xmax+tic_dx,tic_dx):
tic = curve(pos=[(i,-0.5*tic_h),(i,0.5*tic_h)])
for i in arange(ymin,ymax+tic_dx,tic_dx):
tic = curve(pos=[(-0.5*tic_h,i),(0.5*tic_h,i)])

#stop scene from zooming out too far when the curve is drawn
scene.autoscale = False

# define curve here
shape = curve(pos=[(-1,2), (5,3), (4,-1), (-1,-1)])

shape.append(pos=shape.pos[0])
shape.color = color.yellow
shape.visible = True

#dilated shape
for i in shape.pos:
dshape.append(pos=i)

#label
note = label(pos=(5,-8),text="Dilation: 1.0", box=False)
intext = label(pos=(5,-9),text="> x", box=False)

#scaling lines
l_scaling = False
slines = []
for i in range(len(shape.pos)):

#animation parameters
animation_time = 1. #seconds
animation_smootheness = 30
animation_rate = animation_smootheness / animation_time

x = ""
while 1:
#x = raw_input("Enter Dilation: ")
if scene.kb.keys: # event waiting to be processed?
s = scene.kb.getkey() # get keyboard info

#print s
if s <> '\n':
x += s
intext.text = "> x "+x
else:
try:
xfloat = float(x)
note.text = "Dilation: " + x

endpoints = []
dp = []
for i in shape.pos:
endpoints.append(float(x) * i)
dp.append((endpoints[-1]-i)/animation_smootheness)
#print "endpoints: ", endpoints
#print "dp:        ", dp
for i in range(animation_smootheness):
for j in range(len(dshape.pos)):
dshape.pos[j] = i*dp[j]+shape.pos[j]
rate(animation_smootheness)
if slines:
for i in range(len(shape.pos)):
slines[i].pos[1] = vector(0,0)
slines[i].pos[-1] = dshape.pos[i]

for i in range(len(shape.pos)):
dshape.pos[i] = endpoints[i]
slines[i].pos[-1] = dshape.pos[i]

for i in range(len(shape.pos)-1):
print shape.pos[i], "--->", dshape.pos[i]

except:
#print "FAIL"
failed = True
intext.text = "> x "
x = ""



Citing this post: Urbano, L., 2015. Dilation, Retrieved March 27th, 2017, from Montessori Muddle: http://MontessoriMuddle.org/ .
Attribution (Curator's Code ): Via: Montessori Muddle; Hat tip: Montessori Muddle.

# A Visual Introduction to Differentiation (using vpython)

#### October 15, 2015

Screen capture: Enter an x value and the program calculates the slope for the function and draws the tangent line.

This quick program is intended to introduce differentiation as a way of finding the slope of a line. Students know how to find the slope of a tangent line at least conceptually (by drawing). We pick a curve: in this case:

$f(x) = x^2$

then enter values of x in the program to see how x, the function value and the differential compare to each other.

x f(x) f'(x)
0.5 0.25 1
1 1 2
2 2 4
3 9 6

Because it’s quick you have to change the function in the code, and enter the values for x in the python shell.

With a sin curve.

differentiation_intro_numeric.py

from visual import *

class tangent_line:
def __init__(self):
self.dx = 0.1
self.line = curve()
self.tangent_line = curve()
self.point.visible = False
self.label = label(pos=(-5,-8))

'''CHANGE FUNCTION (y) HERE'''
# the original function
def f(self, x):
#y = sin(x)
y = x**2
return y
'''END CHANGE FUNCTION HERE'''

def find_slope(self, x):
sdx = .00001
m = (self.f(x+sdx)-self.f(x))/sdx
return round(m,3)

def draw(self):
for x in arange(xmin, xmax+self.dx, self.dx):
self.line.append(pos=(x, self.f(x)))

def draw_tangent(self, x):
m = self.find_slope(x)
y = self.f(x)
b = y - m * x
print "When x = ", x, " slope = ", m
self.label.text = "point: (%1.2f, %1.2f)\nSlope: %1.2f" % (x,y,m)
self.plot_point(x)

#draw tangent
self.tangent_line.visible = False
self.tangent_line = curve(pos=[(xmin,m*xmin+b),(xmax,m*xmax+b)], color=color.yellow)

def plot_point(self, x):
self.point.visible = True
self.point.pos = (x, self.f(x))

#axes
xmin = -10.
xmax = 10.
ymin = -10.
ymax = 10.
xaxis = curve(pos=[(xmin,0),(xmax,0)])
yaxis = curve(pos=[(0,ymin),(0,ymax)])

#tick marks
tic_dx = 1.0
tic_h = .5
for i in arange(xmin,xmax+tic_dx,tic_dx):
tic = curve(pos=[(i,-0.5*tic_h),(i,0.5*tic_h)])
for i in arange(ymin,ymax+tic_dx,tic_dx):
tic = curve(pos=[(-0.5*tic_h,i),(0.5*tic_h,i)])

#stop scene from zooming out too far when the curve is drawn
scene.autoscale = False

# draw curve
func = tangent_line()
func.draw()

# get input
while 1:
xin = raw_input("Enter x value: ")
func.draw_tangent(float(xin))



Citing this post: Urbano, L., 2015. A Visual Introduction to Differentiation (using vpython), Retrieved March 27th, 2017, from Montessori Muddle: http://MontessoriMuddle.org/ .
Attribution (Curator's Code ): Via: Montessori Muddle; Hat tip: Montessori Muddle.

# Finding Volumes Using the Disk Method

#### February 25, 2014

Student’s program to calculate the volume of a curve rotated around the x-axis using the Disk Method in Calculus.

This VPython program was written by a student, Mr. Alex Shine, to demonstrate how to find the volume of a curve that’s rotated around the x-axis using the disk method in Calculus II.

The program finds volume for the curve:

$y = -\frac{x^2}{4} + 4$

between x = 0 and x = 3.

To change the curve, change the function R(x), and to set the upper and lower bounds change a and b respectively.

volume_disk_method.py by Alex Shine.

from visual import*

def R(x):
y = -(1.0/4.0)*x**2 + 4
return y

dx = 0.5

a = 0.0

b = 3.0

x_axis = curve(pos=[(-10,0,0),(10,0,0)])

y_axis = curve(pos=[(0,-10,0),(0,10,0)])

z_axis = curve(pos=[(0,0,-10),(0,0,10)])

line = curve(x=arange(0,3,.1))
line.color=color.cyan
line.y = -(1.0/4.0) * (line.x**2) + 4

#scene.background = color.white

for i in range(-10, 11):

curve(pos=[(-0.5,i),(0.5,i)])
curve(pos=[(i,-0.5),(i,0.5)])

VT = 0

for x in arange(a + dx,b + dx,dx):

V = pi * R(x)**2 * dx

disk = cylinder(pos=(x,0,0),radius=R(x),axis=(-dx,0,0), color = color.yellow)

VT = V + VT

print V

print "Volume =", VT



Citing this post: Urbano, L., 2014. Finding Volumes Using the Disk Method, Retrieved March 27th, 2017, from Montessori Muddle: http://MontessoriMuddle.org/ .
Attribution (Curator's Code ): Via: Montessori Muddle; Hat tip: Montessori Muddle.

# Sine Curves

#### February 18, 2014

Frequency.

I’ve slapped together this simple VPython program to introduce sinusoidal functions to my pre-Calculus students.

• Left and right arrow keys increase and decrease the frequency;
• Up and down arrow keys increase and decrease the amplitude;
• “a” and “s” keys increase and decrease the phase.

Amplitude.

The specific functions shown on the graph are based on the general function:

$y = A \sin{F x + P}$

where:

• A — amplitude
• F — frequency
• P — phase

Phase. Note how the curve seems to move backward when the phase increases.

When I first introduce sinusoidal functions to my pre-Calculus students I have them make tables of the functions (from -2π to 2π with an interval of π/8) and then plot the functions. Then I’ll have them draw sets of sine functions so they can observe different frequencies, amplitudes, and phases.

from visual import *

class sin_func:
def __init__(self, x, amp=1., freq=1., phase=0.0):
self.x = x
self.amp = amp
self.freq = freq
self.phase = phase

self.curve = curve(color=color.red, x=self.x, y=self.f(x), radius=0.05)
self.label = label(pos=(xmin/2.0,ymin), text="Hi",box=False, height=30)

def f(self, x):
y = self.amp * sin(self.freq*x+self.phase)
return y

def update(self, amp, freq, phase):
self.amp = amp
self.freq = freq
self.phase = phase
self.curve.y = self.f(x)
self.label.text = self.get_eqn()

def get_eqn(self):
if self.phase == 0.0:
tphase = ""
elif (self.phase > 0):
tphase = u" + %i\u03C0/8" % int(self.phase*8.0/pi)
else:
tphase = u" - %i\u03C0/8" % int(abs(self.phase*8.0/pi))
print self.phase*8.0/pi

txt = "y = %ssin(%sx %s)" % (simplify_num(self.amp), simplify_num(self.freq), tphase)
return txt

def simplify_num(num):
if (num == 1):
snum = ""
elif (num == -1):
snum = "-"
else:
snum = str(num).split(".")[0]+" "
return snum

amp = 1.0
freq = 1.0

damp = 1.0
dfreq = 1.0

phase = 0.0
dphase = pi/8.0

xmin = -2*pi
xmax = 2*pi
dx = 0.1

ymin = -3
ymax = 3

scene.width=640
scene.height=480

xaxis = curve(pos=[(xmin,0),(xmax,0)])
yaxis = curve(pos=[(0,ymin),(0,ymax)])

x = arange(xmin, xmax, dx)
#y = f(x)

func = sin_func(x=x)
func.update(amp, freq, phase)

while 1: #theta <= 2*pi:
rate(60)

if scene.kb.keys: # is there an event waiting to be processed?
s = scene.kb.getkey() # obtain keyboard information
#print s
if s == "up":
amp += damp
if s == "down":
amp -= damp
if s == "right":
freq += dfreq
if s == "left":
freq -= dfreq

if s == "s":
phase += dphase
if s == "a":
phase -= dphase

func.update(amp, freq, phase)
#update_curve(func, y)



Citing this post: Urbano, L., 2014. Sine Curves, Retrieved March 27th, 2017, from Montessori Muddle: http://MontessoriMuddle.org/ .
Attribution (Curator's Code ): Via: Montessori Muddle; Hat tip: Montessori Muddle.

# DNA Visualization

#### February 13, 2014

Screen capture from R.M’s code.

Another interesting project that came out of the Creativity Interim was a VPython program that uses the DNA Writer translation table to convert text into a DNA sequence that is represented as a series of colored spheres in a helix.

The code, by R.M. with some help from myself, is below. It’s pretty rough but works.

dna_translator.py

from visual import *
import string

xaxis = curve(pos=[(0,0),(10,0)])

inp = raw_input("enter text: ")
inp = inp.upper()

t_table={}

t_table['0']="ATA"
t_table['1']="TCT"
t_table['2']="GCG"
t_table['3']="GTG"
t_table['4']="AGA"
t_table['5']="CGC"
t_table['6']="ATT"
t_table['7']="ACC"
t_table['8']="AGG"
t_table['9']="CAA"
t_table['start']="TTG"
t_table['stop']="TAA"
t_table['A']="ACT"
t_table['B']="CAT"
t_table['C']="TCA"
t_table['D']="TAC"
t_table['E']="CTA"
t_table['F']="GCT"
t_table['G']="GTC"
t_table['H']="CGT"
t_table['I']="CTG"
t_table['J']="TGC"
t_table['K']="TCG"
t_table['L']="ATC"
t_table['M']="ACA"
t_table['N']="CTC"
t_table['O']="TGT"
t_table['P']="GAG"
t_table['Q']="TAT"
t_table['R']="CAC"
t_table['S']="TGA"
t_table['T']="TAG"
t_table['U']="GAT"
t_table['V']="GTA"
t_table['W']="ATG"
t_table['X']="AGT"
t_table['Y']="GAC"
t_table['Z']="GCA"
t_table[' ']="AGC"
t_table['.']="ACG"

dna=""

for i in inp:

dna=dna+t_table[i]
print dna

m=0
r=3.0
f=0.5
n=0.0
dn=1.5

start_pos = 1
for i in dna:
rate(10)
n+=dn
m+=1
x = n
y=r*sin(f*n)
z=r*cos(f*n)
a=sphere(pos=(x,y,z))
#print x, y, z
if (i == "A"):
a.color=color.blue
elif (i== "G"):
a.color=color.red
elif (i== "C"):
a.color=color.green


Citing this post: Urbano, L., 2014. DNA Visualization, Retrieved March 27th, 2017, from Montessori Muddle: http://MontessoriMuddle.org/ .
Attribution (Curator's Code ): Via: Montessori Muddle; Hat tip: Montessori Muddle.

# Programming Agents

#### November 28, 2012

An artful spaceship.

For my programming elective, I thought I’d try to get students programming autonomous agents that we could all compile together into some type of game. Since it was, for most of them a first class in programming, this has proven a little too ambitious, and I’ve had to adjust a bit to build up to it.

Step 1: Build a spaceship/vehicle/agent.

A simple ship made of a sphere, ring, and pyramid, with a clear front end (pointed to the left: vector=(1,0,0).

• We’re using VPython so we can build 3d ships. Students get to exercise their creativity a bit and learn how to place things in 3d co-ordinate space.
• The ship must have a front end and all the parts must be put into a single frame so the whole thing can be moved as a unit.
• Although it’s not required, I’ve added in the beginnings of a trail so we can track the motion of the spaceship
from visual import *

f = frame()
r = ring(frame=f, thickness=.25, axis=(0,1,0))
front = pyramid(frame=f, height=1,width=1,length=2, color=color.blue)
trail = curve(pos=[f.pos,])


Step 2: Make the spaceship a class.

• Putting the spaceship in a separate class, away from the rest of the code, makes it easier to move from one program to another. That means we can import everyone’s ships into a single program at the end.
from visual import *

class spaceship1:

def __init__(self):

self.f = frame()
self.r = ring(frame=self.f, thickness=.25, axis=(0,1,0))
self.front = pyramid(frame=self.f, height=1,width=1,length=2, color=color.blue)
self.trail = curve(pos=[self.f.pos,])

ss = spaceship1()



Make the spaceship move

• We’re going to control the movement of the spaceship using the arrow keys. Left and right to turn; up and down to accelerate and decelerate.
• To allow movement, we’ll put the keyboard controls into an infinite loop, but to allow maximum flexibility (and to show how to use create methods in a class) we’re putting the actual movement in as methods in the class: the turning method is named turn_xz and movement is move_xz since all the motion is going to be restricted in the xz plane for now.
from visual import *

class spaceship1:

def __init__(self):

self.f = frame()
self.r = ring(frame=self.f, thickness=.25, axis=(0,1,0))
self.front = pyramid(frame=self.f, height=1,width=1,length=2, color=color.blue)
self.trail = curve(pos=[self.f.pos,])

def turn_xz(self, turn_rate):
self.f.axis = rotate(self.f.axis, turn_rate, (0,1,0))

def move_xz(self, speed):
self.f.pos = self.f.pos + self.f.axis*speed
self.trail.append(pos=self.f.pos)

ss = spaceship1()
turn = 0.0
speed=0.0

while 1:
rate(20)  #slows down execution of the loop

ss.turn_xz(turn)
ss.move_xz(speed)

if scene.kb.keys: # event waiting to be processed?
s = scene.kb.getkey() # get keyboard info

if s == 'left':
turn = turn + 0.01
if s == 'right':
turn = turn - 0.01

if s == 'up':
speed = speed + 0.01
if s == 'down':
speed = speed - 0.01

if s == ' ':
if turn <> 0:
turn = 0
elif speed <> 0:
speed = 0



Fire missiles

• We add in missiles by creating a class very similar to the spaceship. For now our missile is just a ball but I’m putting it into a frame anyway in case later on I want it to be a composite object.
• Since we can fire multiple missiles, in the code I create a list to hold all the missiles.
• Missiles are fired using the “a” key.
from visual import *

class spaceship1:

def __init__(self):

self.f = frame()
self.r = ring(frame=self.f, thickness=.25, axis=(0,1,0))
self.front = pyramid(frame=self.f, height=1,width=1,length=2, color=color.blue)
self.trail = curve(pos=[self.f.pos,])

def turn_xz(self, turn_rate):
self.f.axis = rotate(self.f.axis, turn_rate, (0,1,0))

def move_xz(self, speed):
self.f.pos = self.f.pos + self.f.axis*speed
self.trail.append(pos=self.f.pos)

class missile:
def __init__(self, axis, pos):

self.f = frame(axis=axis, pos=pos)
self.speed = 0.5

def move_xz(self):
self.f.pos = self.f.pos + self.f.axis*self.speed

ss = spaceship1()
turn = 0.0
speed=0.0

missiles = []  #list for missiles

while 1:
rate(20)  #slows down execution of the loop

ss.turn_xz(turn)
ss.move_xz(speed)

#move missiles
for i, m in enumerate(missiles):
m.move_xz()

if scene.kb.keys: # event waiting to be processed?
s = scene.kb.getkey() # get keyboard info

if s == 'left':
turn = turn + 0.01
if s == 'right':
turn = turn - 0.01

if s == 'up':
speed = speed + 0.01
if s == 'down':
speed = speed - 0.01

if s == ' ':
if turn <> 0:
turn = 0
elif speed <> 0:
speed = 0

#fire missile
if s == 'a':
missiles.append(missile(ss.f.axis, ss.f.pos))


• Finally, I’m going to put in a boundary, and set it up to delete the missiles if they go out of bounds.
• I’m also going to set an option so that the scene follows the ship, which makes it easier to keep track of things.
from visual import *

class spaceship1:

def __init__(self):

self.f = frame()
self.r = ring(frame=self.f, thickness=.25, axis=(0,1,0))
self.front = pyramid(frame=self.f, height=1,width=1,length=2, color=color.blue)
self.trail = curve(pos=[self.f.pos,])

def turn_xz(self, turn_rate):
self.f.axis = rotate(self.f.axis, turn_rate, (0,1,0))

def move_xz(self, speed):
self.f.pos = self.f.pos + self.f.axis*speed
self.trail.append(pos=self.f.pos)

class missile:
def __init__(self, axis, pos):

self.f = frame(axis=axis, pos=pos)
self.speed = 0.5

def move_xz(self):
self.f.pos = self.f.pos + self.f.axis*self.speed

class bounds:
def __init__(self, boundary_distance):
self.boundary_distance = boundary_distance
self.border = curve(pos=[(-boundary_distance,0,-boundary_distance),
(boundary_distance,0,-boundary_distance),
(boundary_distance,0,boundary_distance),
(-boundary_distance,0,boundary_distance),
(-boundary_distance,0,-boundary_distance)])
def in_bounds(self, pos):
if ( pos.x < -self.boundary_distance or
pos.x > self.boundary_distance or
pos.z < -self.boundary_distance or
pos.z > self.boundary_distance):
return false
else:
return true

ss = spaceship1()
turn = 0.0
speed=0.0
l_track_ship = 1
scene.range=10
scene.forward = (0,-1,0)
missiles = []

boundary_distance = 100
boundary = bounds(boundary_distance)

while 1:
rate(20)

if l_track_ship > 0:
scene.center = ss.f.pos

ss.turn_xz(turn)
ss.move_xz(speed)

#move missiles
del_list = []
for i, m in enumerate(missiles):
m.move_xz()

#delete missile if out of bounds
if boundary.in_bounds(m.f.pos) == False:
del_list.append(i)
for i in del_list:
missiles[i].f.visible = False
del missiles[i]

#if m <> None:
#    m.move_xz()

if scene.kb.keys: # event waiting to be processed?
s = scene.kb.getkey() # get keyboard info

if s == 'left':
turn = turn + 0.01
if s == 'right':
turn = turn - 0.01

if s == 'up':
speed = speed + 0.01
if s == 'down':
speed = speed - 0.01

if s == ' ':
if turn <> 0:
turn = 0
elif speed <> 0:
speed = 0

#fire missile
if s == 'a':
missiles.append(missile(ss.f.axis, ss.f.pos))



Next steps

Now that we’re cooking with charcoal — we have a functioning program — the next steps will be setting up a target to shoot at, and, if the students are ready, letting them program their ships to move autonomously. If they’re not ready then, as an example, I’ll program some opposition.

Citing this post: Urbano, L., 2012. Programming Agents, Retrieved March 27th, 2017, from Montessori Muddle: http://MontessoriMuddle.org/ .
Attribution (Curator's Code ): Via: Montessori Muddle; Hat tip: Montessori Muddle.