Move the mouse over the maze to explore. Clicking is not necessary;
just hovering over the maze suffices. You can only explore cells
adjacent to cells that you have already visited. You cannot see
through or walk through walls.
The goal is to find the cheese. You will not know where it is -- or
even what it looks like -- until you have found it. You do not
initially have an overview of the maze. If you want to see what the
maze looks like, you have to /earn/ that information by exploring.
This is an apt metaphor for how research is done. For details,
see ./research-maze.htm
Clever strategies can make the search more efficient, but no matter
how clever you are, you will spend a goodly amount of time exploring
dead ends. This does not mean that you made a bad decision, or made a
mistake. It's just part of the cost of obtaining information.
Extra features:
'N' to start a new game.
'P' to toggle seeing python source.
'J' to toggle seeing how it compiles into javascript.
'C' to clear the print area.
Shift gives the mouse no effect on the maze (so you can move the mouse
without affecting your impressively low score).
Right-drag changes the viewing angle. Left-drag zooms.
python code
js machine code
extra stuff
####++++
#! /usr/bin/python
# everything above this line gets left out of the browser version
if type(browser) == 'undefined': browser = 1
class bogus:
def __init__(self):
0000
if browser: KeyboardInterrupt = bogus.constructor # kludge: placeholder
def jsSetpos(newpos):
this.clear()
for ii in newpos:
this.append(ii)
if browser: curve.prototype.setpos = jsSetpos # works in glowscript
else: curve.setpos = lambda self, newpos : setattr(self, 'pos', newpos) # works from cmdline
def sortme_py(stuff):
return sorted(stuff, key=manhattan) # python sort aka sorted
def sortme_js(_stuff):
stuff = _stuff[:] # because js does a sort in place
stuff.sort(mh2) # native js sort
return stuff
if browser:
args = parseQueryString()
print_options(readonly=0)
random = Math.random
def conlog():
console.log.apply(console, arguments)
def extemporaneous():
rslt = {}
for obj in arguments:
for key in dir(obj):
rslt[key] = obj[key]
return rslt
sortme = sortme_js
else:
args = extemporaneous(watch=1, debug=0)
conlog = print
sortme = sortme_py
# Manhattan metric:
def manhattan(myvec):
radius = 0
for xyz in myvec:
radius += abs(xyz)
return radius
def mh2(a, b): return manhattan(a) - manhattan(b)
# look up value if it exists; otherwise return the default
def chex(obj, ndx, dflt):
if ndx in dir(obj):
if browser:
return obj[ndx]
else:
return obj.__dict__[ndx]
return dflt
# Replacement for ternary construction: return a if q else b
# which is not implemented in rapydscript
def pick(q, a, b):
if q: return a
return b
def uniform(a, b):
return random()*(b-a) + a
def choice(seq): # random choice
if len(seq) > 0:
return seq[int(random()*len(seq))]
else:
raise IndexError()
# unlike the python version, this DOES build a range object,
# feel free to reimplement
def randrange(*args):
return choice(range(*args))
# uses Fisher-Yates algorithm to shuffle an array
def shuffle(x, random_f=random):
for i in range(len(x)):
j = int(random_f() * (i+1))
x[i], x[j] = x[j], x[i]
return x
# show the args (as specified via URL query string)
if 0:
for arg in dir(args):
val = args[arg]
tf = pick(val, 'yes', 'no')
print(" arg: ", arg, "-->", val, tf)
# some program-wide variables:
shared = extemporaneous(keylist=[], Vtoggle=0, oldV=0, zap=0)
cell = 0 # will get initialized to an array
verbosity = chex(args, 'verbosity', 0)
debug = chex(args, 'debug', 0)
dots = chex(args, 'dots', 0)
watch = chex(args, 'watch', 0)
# [dx, dy, edge]
# Note that stepping the index 2 (mod 4) gives you the opposite edge.
neighbors = [ [1, 0, 1], [0, 1, 0], [-1, 0, 1], [0, -1, 0] ]
# the edge here is the edge /along/ which we move
# (not the edge we cross, i.e. not like neighbors)
northeast = [ [1, 0, 0], [0, 1, 1] ]
# like neighbors, but without edge indicator
basis = [[1, 0], [0, 1], [-1, 0], [0, -1]]
# 0 --> [0, 16, 99, 12, 1] 13
# 0.25 --> [0, 36, 62, 26, 4] 30
# 0.5 --> [0, 41, 54, 27, 6] 33
# 0.75 --> [0, 42, 52, 28, 6] 34
# 1 --> [0, 43, 51, 27, 7] 34
# 0 1 --> [0, 35, 61, 31, 1] 32
# 0.25 1 --> [0, 39, 57, 27, 5] 32
# 0.5 1 --> [0, 44, 48, 30, 6] 36
branching = .3
breadth = 0
# breadth-first search is not particularly helpful;
# mostly it just creates lots of shallow appendices
plain = vector(1,1,1) # a generic color
OKxx = vector(1, 0, 1)
fake = -9e99 # when a vector component is required, but meaningless
if debug:
OKne = color.blue
OKsw = color.red
else:
OKne = OKxx
OKsw = OKxx
######### end of static parameters
class cheeser:
def __init__(self, wedge):
self.x = 0
self.y = 0
self.marked = 0
self.wedge = wedge
if wedge:
self.c = curve(radius=0.1, color=color.yellow, visible=0,
pos=[vector(.8, .5, 0), # vertex
vector(.2, .4, 0),
vector(.2, .6, 0),
vector(.8, .5, 0),
vector(.2, .46666, 0),
vector(.2, .53333, 0),
vector(.8, .5, 0) ] )
else:
self.c = sphere(radius=0.15, color=color.yellow, visible=0,
pos=vector(0.5, 0.5, 0))
#-# end defining __init__
# If the cheese is at the given position,
# make it visible
def check(self, xx, yy):
if xx == self.x and yy == self.y:
# It's probably already visible, but we can make sure:
cheese.marked = cheese.c.visible = 1
#-# end defining check
# cor is a vector specifying the lower-left corner of the
# cell in which the cheese is sitting
def setpos(self, cor):
self.x = cor.x
self.y = cor.y
if self.wedge:
self.c.setpos([vector(.8 + cor.x, .5 + cor.y, 0),
vector(.2 + cor.x, .4 + cor.y, 0),
vector(.2 + cor.x, .6 + cor.y, 0),
vector(.8 + cor.x, .5 + cor.y, 0),
vector(.2 + cor.x, .46666 + cor.y, 0),
vector(.2 + cor.x, .53333 + cor.y, 0),
vector(.8 + cor.x, .5 + cor.y, 0) ])
else:
self.c.pos=cor + vector(.5, .5, 0)
#-# end defining cheeser
cheese = 0 # will become a cheeser object later, after defines are done
# This class exists primarily to keep track of global state,
# e.g. stepno.
# This stands in contrast to per-cell state, which is the celler class.
class visitor:
def __init__(self):
self.stepno = 0
# Check the cell at [x,y].
# Check whether we are allowed to enter it, and if so, do.
def check(self, x, y, force=0):
x = int(x)
y = int(y)
#-- print("checking", x, y, force)
# not allowed to explore cells that you haven't peeked into:
if not force and not cell[y][x].seen:
return
# not allowed to re-visit cells that have already been visited:
if cell[y][x].visited.visible:
return
self.stepno += 1
cell[y][x].enter(self.stepno)
0-0 # end defining check
0-0 # end defining visitor
class edger:
def __init__(self):
self.c = 0 # the curve will go here
self.exists = 0
self.marked = 0
#-# End of define class edger
# RS fails for class celler(): as opposed to class celler:
class celler: # constructor
def __init__(self, xx, yy):
self.xx = xx # remember where we sit
self.yy = yy
self.looped = 0
self.iid = 0 # island ID
self.harvey = 0 # 1 ==> stepped on by the wall-banger algorithm
# 2 ==> nearest neighbor to harvey
# gutter cells on the left and the bottom have no edges at all:
if xx > 0 and yy > 0:
# otherwise, allocate right-sized array for edges:
self.e = ['bot', 'left'] # contents will get overwritten
for dx,dy,ed in northeast:
xxp = xx + dx
yyp = yy + dy
if (xx > w and not ed) or (yy > h and ed):
# gutter cells on the top have no left, and
# gutter cells on the right have no bottom:
0
else:
# otherwise allocate left or bottom of this cell
self.e[ed] = edger()
self.e[ed].c = curve(pos=[vector(xx, yy, 0), vector(xxp, yyp, 0)],
radius=0.1, visible=0, color=plain)
if xx >= 1 and xx <= w and yy >= 1 and yy <= h:
# visited.visible means stepped on during solution phase
self.d = sphere(pos=vector(xx+.5, yy+.5, 0.1), radius=0.1, visible=0)
if dots:
self.visited = sphere(pos=vector(xx+.5, yy+.5, 0), radius=0.1,
visible=0)
else:
self.visited = label(pos=vector(xx+.5, yy+.5, 0),
text='', border=0, line=0, box=0, visible=0,
opacity=0) # opacity means /background/ opacity
#-# end defining __init__
##########
# here self is a cell, i.e. an instance of class celler
# if id>0, make this a visible edge
# otherwise reset it to the invisible and nonexistent state
#
# Negative pseudo-edge-number means leave edges alone.
# ... which would make this a complete no-op,
# except that there's also an effect on self.iid.
def mk_edge(self, edge=-1, id=0):
if edge >= 0:
self.e[edge].exists = id > 0
self.e[edge].marked = 0
self.e[edge].c.visible = debug and id > 0
self.e[edge].c.color = plain
if id > 0: self.iid = id
def reset(self): # reset a single cell
xx = self.xx
yy = self.yy
self.seen = 0
self.looped = 0
self.iid = 0
self.harvey = 0
# construct the boundary box; boundary has id=1
if (xx == 1 or xx == w+1) and yy >= 1 and yy <= h: self.mk_edge(1, 1)
if (yy == 1 or yy == h+1) and xx >= 1 and xx <= w: self.mk_edge(0, 1)
# sneaky corner case: cell has no left-edge and no botttom-edge,
# but still it is a node on the boundary:
if xx == w+1 and yy == h+1: self.mk_edge(-1, 1) # sneaky boundary cella
if xx >= 1 and xx <= w and yy >= 1 and yy <= h: # working cells
self.visited.visible = 0
self.d.visible = debug
self.d.color = vector(1, 0, 1)
# mark working-cell edges as non-existent:
if xx > 1: self.mk_edge(1, 0)
if yy > 1: self.mk_edge(0, 0)
#-# end of defining reset
# Enter a cell.
# Mark it as visited.
# Mark visible neighboring cells as seen.
# Mark "near" edges of neighboring cells as appopriate.
def enter(self, stepno):
xx = self.xx
yy = self.yy
self.visited.visible = 1
if not dots: # update the text
self.visited.text = str(stepno)
# Mark all four edges of this cell
for edge in [ self.e[1], self.e[0],
cell[yy][xx+1].e[1], cell[yy+1][xx].e[0] ]:
edge.c.color = OKxx
edge.marked = edge.c.visible = edge.exists
# Did we step on the cheese?
cheese.check(xx, yy)
self.seen = 1
# Mark top and bottom of horizontally neighboring cells,
# if they are open to view:
for dx in [-1, 1]:
rx = xx + dx
ry = yy
bigx = max(xx, rx)
# See if the neighboring cell is accessible:
if not cell[ry][bigx].e[1].exists:
if rx >= 1 and rx <= w:
#-- print("h marking", rx, ry)
cell[ry][rx].seen = 1
# Can we smell the cheese?
cheese.check(rx, ry)
for box in [cell[ry][rx], cell[ry+1][rx] ]:
edge = box.e[0]
if not edge.marked:
edge.c.color = pick(dx<0, OKsw, OKne)
edge.marked = edge.c.visible = edge.exists
# Mark left and right of vertically neighboring cells:
for dy in [-1, 1]:
rx = xx
ry = yy + dy
bigy = max(yy, ry)
# See if the neighboring cell is accessible:
if not cell[bigy][rx].e[0].exists:
if ry >= 1 and ry <= h:
#-- print("v marking", rx, ry)
cell[ry][rx].seen = 1
# Can we smell the cheese?
cheese.check(rx, ry)
for box in [cell[ry][rx], cell[ry][rx+1] ]:
edge = box.e[1]
if not edge.marked:
edge.c.color = pick(dy<0, OKsw, OKne)
edge.marked = edge.c.visible = edge.exists
#-# end of defining enter
#-# End of define class celler
def xvms():
print("XVMS", cell[1][1].e[1].exists,
cell[1][1].e[1].c.visible,
cell[1][1].e[1].marked,
cell[1][1].seen,
" ", cell[1][2].e[1].exists,
cell[1][2].e[1].c.visible,
cell[1][2].e[1].marked,
cell[1][2].seen,
" ", cell[1][3].e[1].exists,
cell[1][3].e[1].c.visible,
cell[1][3].e[1].marked,
cell[1][3].seen )
## support for 'D' key:
## degree of connectivity:
def decon(on):
histo = [0] * 5
clr = ([vector(1,0,1), vector(1,0,0),
vector(1, 1, 0), vector(0, 1, 0), vector(0, 0, 1), ])
for xx in range(1, 1+w):
for yy in range(1, 1+h):
count = 0
if not cell[yy][xx].e[0].exists: count += 1
if not cell[yy][xx].e[1].exists: count += 1
if not cell[1+yy][xx].e[0].exists: count += 1
if not cell[yy][1+xx].e[1].exists: count += 1
cell[yy][xx].d.color = clr[count]
histo[count] += 1
cell[yy][xx].d.visible = on
print("decon:", branching, breadth, "-->", histo, histo[3] + histo[4])
0-0 # end defining decon
cmap = ([vector(1, 0, 1), vector(1, 0, 0), vector(1, .5, 0),
vector(1, 1, 0), vector(0, 1, 0), vector(0, 1, 1),
vector(0, 0, 1) ])
def wallbang(on):
for xx in range(1, 1+w):
for yy in range(1, 1+h):
har = cell[yy][xx].harvey
if har:
ndx = (har-1) % len(cmap)
cell[yy][xx].d.color = cmap[ndx]
cell[yy][xx].d.visible = on
##### support for the 'V' key:
def visualize(on):
if on:
#=== if browser: print("begin V on")
cheese.c.visible = 1
total = 0
for y in range(1, 2+h):
for x in range(1, 2+w):
##rate## rate(myrate)
# gutter cells on the top have no left:
if y <= h:
vv = cell[y][x].e[1].c.visible = cell[y][x].e[1].exists
total += vv
# gutter cells on the right have no bottom:
if x <= w:
vv = cell[y][x].e[0] .c.visible = cell[y][x].e[0] .exists
total += vv
#=== print("end V on; total visible:", total)
else:
#=== if browser: print("begin V off")
if grcheese:
grcheese.visible = 0
grcheese.color = vector(1,0,0)
cheese.c.visible = cheese.marked
for y in range(1, 2+h):
for x in range(1, 2+w):
##rate## rate(myrate)
# gutter cells on the top have no left:
if y <= h:
cell[y][x].e[1].c.visible = cell[y][x].e[1].marked
# gutter cells on the right have no bottom:
if x <= w:
cell[y][x].e[0] .c.visible = cell[y][x].e[0] .marked
#xx for testing only,
#xx and confusing even then: cell[1][1].e[1].c.visible = 1
#=== if browser: print("end V off")
# Asynchronous. Just put event on list to be dealt with by main loop.
def keyDown(ev):
shared.keylist.append(ev)
def key_action(mykey):
if mykey == 'E':
if browser:
if extra_win.style.height == '0px':
extra_win.style.height = '400px'
else:
extra_win.style.height = '0px'
# if not browser, just ignore
if mykey == 'P':
if browser:
if py_win.style.height == '0px':
py_win.style.height = '300px'
else:
py_win.style.height = '0px'
# if not browser, just ignore
elif mykey == 'J':
if browser:
if machine_code_win.style.height == '0px':
machine_code_win.style.height = '300px'
else:
machine_code_win.style.height = '0px'
# if not browser, just ignore
elif mykey == 'C':
if browser:
print_options(clear=True)
if printarea.style.height == '0px': printarea.style.height = '300px'
elif mykey == 'V': shared.Vtoggle = not shared.Vtoggle
elif mykey == 'Z': shared.zap = 1
elif mykey == 'D': decon(1)
elif mykey == 'W': wallbang(1)
elif mykey == 'N': shared.run = 0
elif mykey == 'c': xvms()
else:
00000 # FIXME: key not handled
# Synchronous. Called from main loop.
def check_keylist():
if len(shared.keylist) == 0: return
ev = shared.keylist.pop()
ctrl = pick(ev.ctrl, ' ctrl', '')
shift = pick(ev.shift, ' shift', '')
alt = pick(ev.alt, ' alt', '')
if 0 : print("Event type: " + ev.type,
" key: '" + ev.key + "'",
" ord: " + ord(ev.key),
" keyCode: " + ev.keyCode,
" charCode: " + ev.charCode,
ctrl, alt, shift)
###def pr(x) : print(x, '-->', ev[x])
###dir(ev).map(pr) # for debugging
key_action(ev.key)
0-0
def mouse(ev):
if 0 : print('mouse click at: ', ev.pos)
if 1: # initial allocation
h = 8
w = 16
myrange = 10
if browser: myrange = 6
if browser: display = canvas
scene = display(x=0, y=0, width=854, height=480, # position of graphics window
center = vector(w/2+1, h/2+1, 0), range=myrange)
cheese = cheeser(1) # the actual objective cheese
if browser:
scene.bind('keydown', keyDown)
scene.bind('click', mouse)
cell = [0] * (2+h)
for yy in range(0, 2+h):
cell [yy] = [0] * (2+w)
for xx in range(0, 2+w):
cell[yy][xx] = celler(xx, yy)
grcheese = blcheese = 0 # may get redefined
if 0:
grcheese = cheeser(0) # a hackish marking, mainly for debugging
grcheese.c.visible = 1
if 1:
blcheese = cheeser(0) # another marking, mainly for debugging
blcheese.setpos(vector(0, 1+h, 0))
blcheese.c.visible = 1
blcheese.c.color = vector(0, 0, 1)
visit = visitor()
class noder:
def __init__(self, x, y):
self.pos = [x, y]
self.dir = basis[:]
shuffle(self.dir)
#-# end defining noder.__init__
#-# end declaration of noder
class treer:
def __init__(self, _iid, x, y):
self.iid = _iid
self.todo = []
# allow dup connections to boundary, but nothing else:
if cell[y][x].iid > 1:
if verbosity: print("treer: avoiding dup tree during maze construction", x, y)
else:
self.todo.append(noder(x,y))
cell[y][x].iid = self.iid
# keeps trying until it creates one link
# (or runs out of options
def growsome(self, count):
todo = self.todo
while len(todo):
if random() < branching: shuffle(todo)
node = todo[len(todo)-1]
if not len(node.dir):
todo.pop()
continue
[xx, yy] = node.pos
[dx, dy] = node.dir.pop()
if self.growinto(xx, yy, xx+dx, yy+dy):
count -= 1
if count <= 0: return 1 # did all that was requested
return 0 # ran out of options
#-# end loop over things to do
#-# end defining growsome
## args: (oldx, oldy, newx, newy)
## returns 1 if a new link was created
def growinto(self, x, y, xx, yy):
rslt = 0
if xx <= 0: return 0
if yy <= 0: return 0
if xx >= w+2: return 0
if yy >= h+2: return 0
if cell[yy][xx].iid: return 0 # dest cell already part of some island
# step to new cell....
# new cell joins the island:
cell[yy][xx].iid = self.iid
# find and mark the edge that brought us here:
hor = y == yy
ver = x == xx
if hor:
smallx = min(x, xx)
smally = yy
ed = 0
elif ver:
smallx = xx
smally = min(y, yy)
ed = 1
else:
print("SNH neither hor nor ver")
return 0
edge = cell[smally][smallx].e[ed]
if edge.exists:
print("SNH retrace edge", x, y, xx, yy)
return 0 # didn't really add anything new
edge.exists = 1
edge.c.visible = debug
self.todo.append(noder(xx, yy))
return 1
#-# end defining growinto
#-# end declaring treer
### create randomly, make sure it can grow one unit
def mkroot(forest, id, todo, poslist):
done = 0
mylist = list(poslist)
while done < todo:
if not len(mylist): return
which = int(random()*len(mylist))
[xx,yy] = mylist.pop(which)
tree = treer(id, xx, yy)
tree.growsome(1)
if not len(tree.todo):
continue
forest.append(tree)
todo += -1
# Cartesian outer product.
# Not an elegant or efficient implementation, but gets the job done.
# Note that neither lambda-expressions nor generator-expressions
# work in the browser.
def outer(xrange, yrange):
rslt = []
for xx in xrange:
for yy in yrange:
rslt.append([xx, yy])
return rslt
# Call with innish[ii], ii
# The list in innish[ii] is originally unmarked.
# We split the list, and mark the cells that need to be marked,
# namely the ones that are neighbors of already-marked cells.
def innerlayer(todo, ii):
x,y = todo[0]
thresh = ii
marked = []
unmarked = []
for x,y in todo:
har = cell[y][x].harvey
if har and har <= thresh:
print("bogus innerlayer", x, y, thresh, har)
markme = 0
for dx,dy,ed in neighbors:
xx = x + dx
yy = y + dy
mx = max(x, xx)
my = max(y, yy)
# see if the step [dx,dy] is allowed, i.e. no wall:
if not cell[my][mx].e[ed].exists:
har = cell[yy][xx].harvey # check status of other cell
if har > 0 and har <= thresh:
markme = 1
break # no need to check additional neighbors
if markme:
cell[y][x].harvey = thresh+1 # set status of this cell
marked.append([x, y])
else:
unmarked.append([x, y])
#-# end loop over cells
return [marked, unmarked]
def zap():
for yy in range(0, 2+h):
for xx in range(0, 2+w):
cell[yy][xx].reset()
def check_zap():
vis_total = 0
ex_total = 0
# loop over working cells only:
for yy in range(2, 1+h):
for xx in range(2, 1+w):
for edge in range(0, 2):
if cell[yy][xx].e[edge].exists:
## print? conlog? ("zap must have failed for", xx, yy, edge)
ex_total += 1
if cell[yy][xx].e[edge].c.visible:
vis_total += 1
print("check_zap working existing:", ex_total, " visible:", vis_total)
def newgame():
# re-initialize for new game:
#-- conlog("new game")
visit.stepno = 0
zap()
if 1:
floating = []
rooted = []
# put some rooted guys on each edge of the boundary:
xrange = range(2, 1+w)
yrange = range(2, 1+h)
mkroot(rooted, 2, 3, outer(xrange, [1]))
mkroot(rooted, 2, 3, outer(xrange, [1+h]))
mkroot(rooted, 2, 1, outer([1], yrange))
mkroot(rooted, 2, 1, outer([1+w], yrange))
# some free-floating islands:
mkroot(floating, 33, 6, outer(xrange, yrange))
# At this point, the rooted guys have already grown one unit;
# now islands get some priority:
for ii in range(6):
for ff in floating:
ff.growsome(1)
# now everybody grows to completion:
everybody = floating + rooted
iix = 0
for ii in range(h*w):
iix = ii
didsome = 0
for tr in everybody:
didsome += tr.growsome(1)
if not didsome: break
#-# end of big loop
for xx in range(1, w+2):
for yy in range(1, h+2):
hh = 0
if cell[yy][xx].iid <= 2:
# This *node* is a harvey node.
# That means all four cells surrounding it
# will be stepped on by the wall-banger algorithm:
# So mark the harvey *cells* as such:
cell[yy ][xx ].harvey = 1
cell[yy-1][xx ].harvey = 1
cell[yy ][xx-1].harvey = 1
cell[yy-1][xx-1].harvey = 1
# innish[ii] is marked with harveyness of ii+1
# except fot the last element of innish, which is unmarked
innish = [ [], [] ]
for xx in range(1, w+1):
for yy in range(1, h+1):
if cell[yy][xx].harvey:
innish[0].append([xx, yy]) # the harvey cells
else:
innish[1].append([xx, yy])
for ii in range(1, h+w):
[marked, unmarked] = innerlayer(innish[ii], ii)
if not len(unmarked): break
innish[ii] = marked
innish.append(unmarked)
for ii in range(len(innish)):
x = y = cxy = -1
if len(innish[ii]) :
x,y = innish[ii][0]
cxy = cell[y][x].harvey
if 0: print("xxxxxxxxx", ii, len(innish[ii]), [x,y], cxy)
cheesable = innish[len(innish) - 1]
# keep users honest: cheese /might/ be near the edge:
if random() < 2./h + 2./w:
cheesable = innish[0]
# Don't place the cheese toooo close to the starting point:
inlen = len(cheesable)
if inlen:
# sort in ascending order:
cheesable = sortme(cheesable)
# keep only the second half:
cheesable = cheesable[int(inlen/2):]
[rx, ry] = choice(cheesable) # random choice
else:
# emergency cheese placement
# Cheese placement is optimized under the assumption that
# the maze has a roughly 2:1 aspect ratio:
rx = randrange(w/2, w) # not including w
ry = randrange(2, h) # not including h
cheese.setpos(vector(rx, ry, fake))
cheese.c.color = choice(([vector(1,1,0),
vector(0,1,0),
vector(0,0,1)]))
cheese.c.visible = debug
cheese.marked = 0
# cheese.rotate(angle=0) # causes cheese to disappear
if grcheese:
grcheese.visible = 1
grcheese.c.color = vector(0,1,0)
if 0: ######## for debugging asdf
visualize(1)
decon(1)
visit.check(1, 1, 'force') # give the user a starting point
if 0: xvms()
visualize(shared.Vtoggle)
#-# End defining newgame.
shared.run = 0 # set to zero to force a new game
stoptrigger = 0
shared.Vtoggle = debug
try:
while 1111: # loop over all games, and all action within a game
rate(30)
if not browser:
if scene.kb.keys: # check for key event
s = scene.kb.getkey() # get keyboard info
# This catches ^C on the graphics window
# (whereas keyboardinterrupt catches it on the controlling terminal).
if len(s) == 1: key_action(s)
elif len(s) > 0:
if s == 'ctrl+c': os._exit(0)
need_new = not shared.run
check_keylist()
if need_new:
newgame()
shared.run = 1
continue
if shared.zap:
zap()
#xx check_zap()
shared.zap = 0
p = scene.mouse.pos
shift = scene.mouse.shift
ctrl = scene.mouse.ctrl
if (p):
p = vector(p)
x = floor(p.x)
y = floor(p.y)
# special exercises:
if shift and not browser:
oldstop = stoptrigger
stoptrigger = x==0 and y==0
if stoptrigger and not oldstop:
shared.run = 0
if x==0 and y==h:
shared.Vtoggle = 1
if x==0 and y==h-1:
visualize(0)
if x==1 and y==h+1:
sleep(0.5)
newgame()
visualize(1)
decon(1)
if x==2 and y==h+1:
sleep(0.5)
newgame()
visualize(1)
wallbang(1)
if shared.oldV != shared.Vtoggle:
visualize(shared.Vtoggle)
decon(shared.Vtoggle)
shared.oldV = shared.Vtoggle
# The main exploration business:
if x >= 1 and x <= w and y >= 1 and y <= h and not shift:
visit.check(x,y)
## else:
## conlog("******* p was null")
0-0 # end of loop over all games
except KeyboardInterrupt as e:
print ('...Interrupt...')
os._exit(1)
####----
// ********************************************
// The javascript driver code.
// Invoke the compiler, then run the compiled code.
// First, some auxiliary functions needed at compile time,
// or otherwise needed by the driver:
Array.prototype['+'] = function(other) {
return this.concat(other)
}
var array_mul = function(arr, num){
if(Number(num) !== num || num % 1 !== 0) {
throw new Error("Can't multiply array by non-integer")
}
if (num < 0) throw new Error("Can't multiply array by negative number")
if (num == 0) return []
if (num == 1) return arr.slice() // slice is important here
var temp = array_mul(arr, Math.floor(num/2))
temp = temp.concat(temp) // concat clones both before joining them
if (num % 2) return temp.concat(arr)
return temp
}
Array.prototype['*'] = function(num) {
return array_mul(this, num)
}
Array.prototype['+'] = function(other) {
return this.concat(other)
}
var get_trace_win = function() {
var trace_win = document.getElementById('trace-win');
if (!trace_win) {
trace_win = document.createElement("pre");
document.documentElement.appendChild(trace_win)
}
return trace_win
}
var add_trace = function(trace_win, msgline, lineno, codewin,
code_line_start, color) {
var txtnode = document.createTextNode(msgline);
var span = document.createElement("span");
span.appendChild(txtnode);
trace_win.appendChild(span);
// This is an example of an HTML closure:
// Function within a parent function, has permanent access to parent's variables:
if (lineno >= 0) {
span.style.color = color
span.ondblclick = (function() {
var LN = lineno
return function() {
if (codewin.style.height == '0px') codewin.style.height = '300px'
codewin.setSelectionRange(code_line_start[LN-1],
code_line_start[LN])
codewin.focus()
}
})()
}
}
var dump_doc = function(printarea){
var all = document.getElementsByTagName("*");
for (var ii=0; ii < all.length; ii++) {
var xx = all[ii];
printarea.textContent += "t: " + xx.type
+ " c: " + xx.getAttribute("class")
+ " id: " + xx.id + "\n";
}
}
// insert A into B:
var insert = function(inner, outer, where) {
return [outer.slice(0, where), inner, outer.slice(where)].join('')
}
// The main driver:
var driver = function(){
var synerr = args['synerr']
var compiled_code = "<-->"
var errmsg = "---"
var source_line_start = {}
var run_line_start = {}
var add_pyline = function(pyline){
if (pyline > 0 && pyline <= comp.__original.text.length) {
var txt = comp.__original.text[pyline-1]
if (1||txt) {
var msgline = "<<< " + (txt) + "\n"
add_trace(trace_win, msgline, pyline, py_win, source_line_start, "red")
}
}
}
// Beware:
// For a wide range of simple syntax errors, the compiler errors out.
// It replaces the *entire* root document with a terse error message
// which is quite nasty.
//
// Also, under firefox (but not chrome), the RS compiler clears the
// javascript console and leaves it in an unusable state,
// which is amazingly nasty.
var wrapper = document.getElementById('wrapper');
// Grab the text of the vpython program to compile:
var py_xmp = document.getElementById('py_xmp');
var py_win = document.getElementById('py_win');
if (synerr) py_win.textContent = '# 1\n====ShouldFail==== # 2\n# 3\n'
else {
var prog = py_xmp.textContent
if (prog.charAt(0) === "\n") py_win.textContent = prog.substr(1)
else py_win.textContent = prog
}
var py_code = py_win.textContent;
if (verbosity) py_win.style.height = '300px'
var support_xmp = document.getElementById('support_xmp');
var support_win = document.getElementById('support_win');
if (support_win) {
support_win.textContent = support_xmp.textContent;
if (verbosity) support_win.style.height = '300px'
}
var print_win = document.getElementById('print');
var extra_win = document.getElementById('extra_win');
var machine_code_win = document.getElementById('machine_code_win');
var comp = {} // common block for compiler shared data
var errmsg // save for later
try {
var compiled_code = glowscript_compile(py_code,
{lang: "vpython", msg_handler: compiler_msg, shared: comp})
}
catch(err) {
errmsg = err.message // will be used later
var emsg = "Compiler bombed out;"
+ " thrown errmsg: (" + err.message + ")"
+ " file: (" + err.fileName + ")"
+ " line: (" + err.lineNumber + ")"
console.log(emsg)
console.log(err.stack)
var pos = 0;
// N+1 entries labeled 0 through N *inclusive*
source_line_start[0] = pos
for (var ii = 0; ii < comp.__original.text.length; ii++){
pos += 1 + comp.__original.text[ii].length
source_line_start[1+ii] = pos
}
var trace_win = get_trace_win()
add_trace(trace_win, err.message + "\n", -1)
// Kludge: thrown lineNumber is often one unit too big:
add_pyline(err.lineNumber - 1)
add_pyline(err.lineNumber)
if (!verbosity) verbosity = 1 // error forces some minimum verbosity
}
if (verbosity > 1) {
// Dump the line-number translation table so we can look at it:
for (var ii in comp.__linenumbers) {
extra_win.textContent += ii + " --> " + comp.__linenumbers[ii] + "\n"
}
}
if (verbosity || len(compiler_msg.str)) extra_win.textContent += ""
+ "Compiler message was: '" + compiler_msg.str + "'\n"
var compiled_split = compiled_code.split("\n")
if (verbosity) extra_win.textContent += "compiled code has "
+ compiled_split.length + " lines\n"
if (compiled_code === '' || compiled_code === '<-->'){
console.log('Evaluation skipped; no code.')
} else {
// Here with some possibly-usable compiled code.
if (typeof comp.__original !== 'undefined'){
extra_win.textContent += "program has "
+ comp.__original.text.length + " lines\n"
var pos = 0;
// N+1 entries labeled 0 through N *inclusive*
source_line_start[0] = pos
for (var ii = 0; ii < comp.__original.text.length; ii++){
pos += 1 + comp.__original.text[ii].length
source_line_start[1+ii] = pos
}
}
// The printarea is the /active/ print area.
// In principle, it could be switched from print_win to some other window.
var printarea = print_win
var machine_code = "";
if (verbosity) machine_code +=
"console.log('top of machine_code; printarea is:', printarea);";
// The glowscript library always requires the following incantation;
// don't ask me why:
machine_code += 'window.__context = '
+ '{ glowscript_container: $("#glowscript") };'
machine_code += support_xmp.textContent
machine_code += compiled_code
if (verbosity) machine_code += "console.log('bottom of machine_code');main"
// Not sure what this is/was supposed to do;
// it looks like it's adding a message to the top of the
// machine_code main() function
var where = machine_code.match("function main.wait. {") // (balancing "}")
if (0 && where && where.length) {
var begin = where.index // not where[0].index (don't ask me why)
var size = where[0].length
machine_code = insert(
"console.log('top of main()'"
+ ", arguments.length"
+ ", typeof arguments[0]"
+ ")",
machine_code, begin+size)
}
// Show the js machine_code, in case the user is curious:
machine_code_win.textContent = machine_code;
if (verbosity) machine_code_win.style.height = '300px'
var machine_split = machine_code.split("\n")
var pos = 0;
// N+1 entries labeled 0 through N *inclusive*
run_line_start[0] = pos
for (var ii = 0; ii < machine_split.length; ii++){
pos += 1 + machine_split[ii].length
run_line_start[1+ii] = pos
}
// This pre-evaluation apparently defines main() but doesn't run it:
var mainprog;
try {
if (verbosity >= 3)
console.log("about to pre-eval main machine_code; length=", machine_split.length)
mainprog = eval(machine_code);
if (verbosity >= 3)
console.log("OK after pre-eval main machine_code")
}
catch(err) {
console.log("Unable to pre-eval compiled code...."
+ " thrown errmsg: (" + err.message + ")"
+ " file: (" + err.fileName + ")"
+ " line: (" + err.lineNumber + ")"
)
console.log(err.stack)
return
}
if (verbosity >= 3)
console.log("machine_code pre-evaluated to obj of type '", typeof mainprog, "'");
// Actually run the program:
// BEWARE: You must call main() with an argument;
// otherwise bizarre and undocumented things will happen.
// This includes error messages getting blackholed.
// This includes parts of the compiled javascript
// getting executed twice.
try {
if (verbosity >= 3) console.log("about to invoke main(...)");
mainprog(_handbasket_);
if (verbosity >= 3) console.log("OK after invoke main(...)");
}
catch(err){
console.log("main(_handbasket_) bombed out in a way that _handbasket_ could not handle;")
console.log("it threw errmsg: «" + err.message + "»"
+ " in file: «" + err.fileName + "»"
+ " on line: «" + err.lineNumber + "»")
console.log(err.stack)
return
}
}
// Dump all the elements in the root document.
// Among other things, this allows us to see what elements
// the runtime libraries have created.
if (0 && verbosity) dump_doc(extra_win);
}
driver
// end of javascript "driver" code.
// ******************************
"use strict";
////// :::::::: Support functions needed at runtime ::::::::: //////////
// To support checking, e.g.
// browser = "Math" in globals()
// Note that globals() gets transmogrified into nonlocals()
if (typeof nonlocals === 'undefined') window.nonlocals = function() {
return window
}
var protectHTML = function(rawtext) {
return rawtext
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
if (0) { // optional, for testing keyboard events
var fmt_kbd_event = function(evt){
var msg = ""
// war-and-peace mode:
if (0) {
for (var item in evt) {
msg += item + ' --> ' + evt[item] + "; "
}
return msg
}
// relatively concise mode:
return "type: " + evt.type
+ " key: '" + evt.key + "'"
+ " keyCode: " + evt.keyCode
+ " charCode: " + evt.charCode
+ " which: " + evt.which
+ (evt.shiftKey ? " shift" : "")
+ (evt.ctrlKey ? " ctrl" : "")
+ (evt.altKey ? " alt" : "")
}
var test = document.getElementById('char-test');
test.onkeydown = function(evt){
console.log("char_test onkeydown: " + fmt_kbd_event(evt))
}
test.onkeypress = function(evt){
console.log("char_test onkeypress: " + fmt_kbd_event(evt))
}
}
var biteme = function(){
console.log("bite me")
throw new Error("bite me &")
}
var estack_parse = function(line){
var rslt = {}
var stuff = line.split(/>/);
rslt.depth = stuff.length
rslt.func = stuff[0].split('@')[0]
if (rslt.depth > 1) {
var spec = stuff[stuff.length-1].split(":")
rslt.line = Number(spec[1])
rslt.char = Number(spec[2])
var foo = stuff[stuff.length-2].split("line")
rslt.parent = Number(foo[foo.length-1])
} else {
rslt.parent = -1
rslt.line = -1
rslt.char = -1
}
return rslt
}
var trimline = function(txt, where) {
if (where > 35) {
var chomp = where - 30
txt = txt.substr(chomp)
where -= chomp
txt = "…" + txt // compare "⊕"
where++
}
if (txt.length > 70) {
txt = txt.substr(0,70) + "…"
}
return insert("▼", txt, where) // compare "☠"
}
// Object used to decode traceback stack:
var egger = function(){
this.line0 = 0
// The arg is supposed to be an Error object:
this.traceback = function(arg) {
console.log("egger::traceback begins")
var trace_win = get_trace_win()
if (typeof arg.stack !== 'undefined'){
var msg = "Tracing: runtime error was: «" + arg.message + "»\n"
add_trace(trace_win, msg, -1)
msg += " on line: «" + arg.lineNumber + "»"
+ " in file: «" + arg.fileName + "»"
console.log(msg)
// scare quotes: «»
if (!comp.hasOwnProperty('__linenumbers')) {
if (!window.hasOwnProperty('__linenumbers')) {
console.log("no _linenumbers; expect no cmpline or pyline")
} else {
console.log("attempting salvage")
comp = window // salvage situation when "shared: comp" is not implemented
}
}
msg = "" // start a new msg
var stack = arg.stack.split("\n")
console.log("Traceback stack length:", stack.length)
for (var ii = 0; ii < stack.length; ii++){
msg += "[TB " + ii + "] "
var line = stack[ii]
var x = estack_parse(line)
var cmpline = 0
var pyline = -1
var runline = x.line
var fr = ""
fr += "From " + (x.func || '()')
+ " parent: " + x.parent
+ " runline: " + runline
+ " char: " + x.char
fr += "\n"
msg += fr;
add_trace(trace_win, fr, -1)
// Show text of line from python source:
if (pyline > 0) {
var txt = comp.__original.text[pyline-1]
if (1||txt) {
var msgline = "<<< " + (txt) + "\n"
msg += msgline
add_trace(trace_win, msgline, pyline, py_win, source_line_start, "red")
}
}
// Show text of line from compiled code;
// not needed, given that the run-code is a superset,
// but useful for debugging.......
if (0 && cmpline > 0) {
var txt = compiled_split[cmpline-1]
var where = x.char-1
if (1||txt) {
var msgline = ">>> " + (trimline(txt, where)) + "\n"
msg += msgline
add_trace(trace_win, msgline, -1)
}
}
// Show text of line from run code:
if (runline > 0 && x.parent == this.parent0) {
var txt = machine_split[runline-1]
var where = x.char-1
if (1||txt) {
var msgline = ">>> " + (trimline(txt, where)) + "\n"
msg += msgline
add_trace(trace_win, msgline, runline, machine_code_win, run_line_start, "blue")
}
}
} // end loop over traceback stack
console.log(msg)
if (verbosity >= 3) {
console.log("..... and here is the traceback arg.stack, verbatim:\n", arg.stack)
}
throw ''
}
// Arg is not what we were expecting
// Whatever it is, (re)throw it, and hope for the best.
throw arg;
}
// Calibrate the "line0" value:
this.setup = function(arg){
var line = arg.stack.split("\n")
var x = estack_parse(line[0])
//xxx console.log("---- setup:", x.func, x.parent, x.line, x.char)
this.line0 = x.line
this.parent0 = x.parent
}
}
var eggs = new egger()
// This function is passed as the argument to main()
// Implemented as wrapper around egger object.
window._handbasket_ = function() {
if (arguments.length) eggs.traceback(arguments[0])
else console.log("Probably normal exit")
}
// Very rough, approximate, emergency workarounds for chr() and ord()
var ord = function(ch){
if (len(ch) > 1) return -1
return ch.charCodeAt(0)
}
var chr = function(code){
return String.fromCharCode(code)
}
/////// this needs to be the last thing before the compiled code /////////
try{throw new Error("whatever")}catch(e){eggs.setup(e)}
var sanitize_lineno = function(cmpline){
var bad = Number(comp.__linenumbers[cmpline-1])
//console.log("???????", cmpline, bad)
for (var ii = cmpline; ii < compiled_code.length; ii++) {
if (comp.__linenumbers[ii-1]) {
var val = Number(comp.__linenumbers[ii-1])
//console.log("checking ln[" + (ii-1) + "] = " + val)
if (val != bad) return
//console.log("sanitizing ln[" + (ii-1) + "] = " + val)
comp.__linenumbers[ii-1] = -2
}
}
}
// If it was in the compiled code (as opposed to auxilliary code)
// locate the line-number within the python source:
if (comp.hasOwnProperty('__linenumbers') &&
runline > this.line0 && x.parent == this.parent0) {
cmpline = runline - this.line0
if (x.func === 'main' || x.func === '__$main') sanitize_lineno(cmpline)
console.log(comp.__linenumbers.length)
pyline = comp.__linenumbers[cmpline-1]
if (pyline) pyline--
fr += " cmpline: " + cmpline
fr += " pyline: " + pyline
}
Traceback area. To see the problematic code in context,
double-click on any code-line here. Lines starting with "<<<" are
python source code-lines, while lines starting with ">>>" are the
resulting assembly-language (javascript) code-lines.
The "▼"
symbol indicates where on the line the javascript evaluation machine
thinks the problem is.