From 6baa056bd4bce1d9f230fadc2bb578d1c4174a27 Mon Sep 17 00:00:00 2001 From: Steve Wampler Date: Mon, 29 Apr 2024 15:37:12 -0700 Subject: [PATCH 1/6] A few simple programs demonstrating different features of the uni library. Signed-off-by: Steve Wampler --- uni/gprogs/qmazer.icn | 303 +++++++++++++++++++++++++++++++++++++++ uni/progs/complex_ex.icn | 19 +++ uni/progs/rankvote.icn | 163 +++++++++++++++++++++ 3 files changed, 485 insertions(+) create mode 100644 uni/gprogs/qmazer.icn create mode 100644 uni/progs/complex_ex.icn create mode 100644 uni/progs/rankvote.icn diff --git a/uni/gprogs/qmazer.icn b/uni/gprogs/qmazer.icn new file mode 100644 index 000000000..e06a8c9fb --- /dev/null +++ b/uni/gprogs/qmazer.icn @@ -0,0 +1,303 @@ +#

+# qmazer -- create a maze and then find the shortest path through it. +# The maze creation was taken from a Unicon solution to a RosettaCode +# problem. +#

+#

+# (In this version, the maze creation only builds mazes with a single +# successful path but the path search is generalized to locate the shortest +# path in any maze with a single entry.) +#

+#

+# The default maze size is suitable for an HD display, for a 4K display +# try cols=185 and rows=100. +#

+ +global showMice, showPaths +global start, finish +global delayms +global fullPath + +import util +link printf + +$define CELL 20 # cell size in pixels +$define BORDER 30 # border size in pixels + +#

+# Display a 2D maze, wait for a mouse click then solve the maze and wait for a +# second click to terminate program. +#

+# Arguments (all are optional): +#
+#  help -- display a help message
+#  showpaths -- put a mark on each maze cell as it's visited
+#  showmice -- show the individual mice as they traverse the maze
+#  cols=COLS -- set the number of columns [default: 88]
+#  rows=ROWS -- set the number of rows [default: 48]
+#  delay=MS  -- delay between mouse movements in milliseconds [default: 0]
+#
+procedure main(A) + Args(A) + if \Args().getOpt("help","yes") then helpMesg() + showPaths := Args().getOpt("showpaths","yes") # Show mice (dead or alive) + showMice := Args().getOpt("showmice","yes") # Show all live mice + mh := Args().getOpt("rows") | 48 # Maze height (rows) + mw := Args().getOpt("cols") | 88 # Maze width (columns) + delayms := Args().getOpt("delay") # Delay between mice creations + fullPath := [] # Points in best path + + mz := DisplayMaze(GenerateMaze(mh,mw)) # Build and show maze + + QMouse(mz.maze,findStart(mz.maze),&null,0) # Start first quantum mouse + waitForCompletion() # block until all quantum mice have finished + + # Mark the best path into the maze and display it. + if showPath(mz.maze) then DisplayMazeSolution(mz) + else { # Give the person time to examine the failed maze search... + write("No path found for maze!") + DisplayMazeSolution(mz) + } +end + +procedure helpMesg() + write(&errout,"Usage: qMazer [--showmice] [--cols=C] [--rows=R]") + write(&errout,"\twhere:") + write(&errout,"\t\t--showmice # displays all mice as they search") + write(&errout,"\t\t--showpaths # displays all mice paths as they search") + write(&errout,"\t\t--cols=C # sets maze width to C (default 188) columns") + write(&errout,"\t\t--rows=R # sets maze height to R (default 100) rows") + write(&errout,"\t\t--delay=ms # delay ms millisecond (default none) between mice creations") + stop() +end + +# A "Quantum-mouse" for traversing mazes. Each mouse lives for exactly one +# cell, but can spawn additional mice to examine adjacent cells as needed. + +global qMice, bestMouse, bestMouseLock, region, qMiceEmpty +record Position(r,c) + +# Must match values used in maze generation! +$define FINISH 64 # exit +$define START 32 # entrance +$define PATH 128 +$define SEEN 16 # bread crumbs for generator +$define NORTH 8 # sides ... +$define EAST 4 +$define SOUTH 2 +$define WEST 1 +$define EMPTY 0 # like new + +class QMouse(maze, loc, parent, len, val) + + method getLoc(); return loc; end + + method getParent(); return \parent; end + + method getLen(); return len; end + + method atEnd(); return EMPTY ~= iand(val, FINISH); end + + method goNorth() + if EMPTY ~= iand(val,NORTH) then return visit(loc.r-1, loc.c) + end + + method goSouth() + if EMPTY ~= iand(val,SOUTH) then return visit(loc.r+1, loc.c) + end + + method goEast() + if EMPTY ~= iand(val,EAST) then return visit(loc.r, loc.c+1) + end + + method goWest() + if EMPTY ~= iand(val,WEST) then return visit(loc.r, loc.c-1) + end + + method visit(r,c) + local p + # Only visit a cell if it makes sense to do so (not already seen and + # not on a path longer than the best known solution). + critical region[r,c]: + if EMPTY = iand(maze[r,c],SEEN) then { + if /bestMouse | (len <= bestMouse.getLen()) then { # Keep going + mark(maze, r,c) + unlock(region[r,c]) + return Position(r,c) + } + } + end + +initially (m, l, p, n) + initial { # Construct critical region mutexes and completion condvar + qMice := mutex(set()) + qMiceEmpty := condvar() + bestMouseLock := mutex() + region := list(*m) # Minimize critical region size + every r := 1 to *m do region[r] := list(*m[1]) + every !!region := mutex() + } + maze := m + loc := l + parent := p + len := n+1 + val := maze[loc.r,loc.c] | fail # Fail if outside maze + insert(qMice, self) + delay(\delayms) + thread { + if atEnd() then { + critical bestMouseLock: + if /bestMouse | (len < bestMouse.getLen()) then bestMouse := self + } + else { # Try to spawn more mice to look for finish + QMouse(maze, goNorth(), self, len) + QMouse(maze, goSouth(), self, len) + QMouse(maze, goEast(), self, len) + QMouse(maze, goWest(), self, len) + } + + delete(qMice, self) + if /showPaths then markCell(loc.r,loc.c,"white",5) + if *qMice=0 then signal(qMiceEmpty) + } +end + +procedure mark(maze, r,c, fg) + /fg := "grey" + maze[r,c] := ior(maze[r,c],SEEN) + if \(showMice|showPaths) then markCell(r,c,fg,5) + return Position(r,c) +end + +procedure clearMaze(maze) # Clear out dregs from maze creation + every r := 1 to *maze & c := 1 to *maze[1] do # remove breadcrumbs + maze[r,c] := iand(maze[r,c],NORTH+EAST+SOUTH+WEST+START+FINISH) +end + +procedure findStart(maze) # Anywhere in maze + clearMaze(maze) # Remove breadcrumbs + every r := 1 to *maze & c := 1 to *maze[1] do # Locate START cell + if EMPTY ~= iand(maze[r,c],START) then + return mark(maze, r,c, "red") +end + +procedure showPath(maze) + if path := \bestMouse then { # Mark it in maze + repeat { + loc := path.getLoc() + # Remember the path so its cells can be connected by line segments + put(fullPath,CELL*(loc.c-1),CELL*(loc.r-1)) + maze[loc.r,loc.c] +:= PATH + path := \path.getParent() | break + } + return + } +end + +procedure waitForCompletion() + critical qMiceEmpty: while *qMice > 0 do wait(qMiceEmpty) +end + +procedure GenerateMaze(r,c) #: Depth First Maze Generation +static maze,h,w,rd + if /maze then { # BEGING - No maze yet + /h := integer(1 < r) | runerr(r,205) # valid size 2x2 or better + /w := integer(1 < c) | runerr(r,205) + every !(maze := list(h)) := list(w,EMPTY) # shiny new empty maze + start := [?h,?w,?4-1,START] # random [r,c] start & finish + finish := [?h,?w,(start[3]+2)%4,FINISH] # w/ opposite side exponent + every x := start | finish do { + case x[3] := 2 ^ x[3] of { # get side from exponent and + NORTH : x[1] := 1 # project r,c to selected edge + EAST : x[2] := w + SOUTH : x[1] := h + WEST : x[2] := 1 + } + maze[x[1],x[2]] +:= x[3] + x[4] # transcribe s/f to maze + } + rd := [NORTH, EAST, SOUTH, WEST] # initial list of directions + GenerateMaze(start[1],start[2]) # recurse through maze + return 1(.maze,maze := &null) # return maze, reset for next + } + else { # ----------------------- recursed to clear insize of maze + if iand(maze[r,c],SEEN) = EMPTY then { # in bounds and not SEEN yet? + maze[r,c] +:= SEEN # Mark current cell as visited + every !rd :=: ?rd # randomize list of directions + every d := !rd do + case d of { # try all, succeed & clear wall + NORTH : maze[r,c] +:= ( GenerateMaze(r-1,c), NORTH) + EAST : maze[r,c] +:= ( GenerateMaze(r,c+1), EAST) + SOUTH : maze[r,c] +:= ( GenerateMaze(r+1,c), SOUTH) + WEST : maze[r,c] +:= ( GenerateMaze(r,c-1), WEST) + } + return # signal success to caller + } + } +end + +record mazeinfo(window,maze,filename) # keepers + +procedure DisplayMaze(maze) #: show it off + if CELL < 8 then runerr(205,CELL) # too small + + wh := (ch := (mh := *maze ) * CELL) + 2 * BORDER # win, cell, maze height + ww := (cw := (mw := *maze[1]) * CELL) + 2 * BORDER # win, cell, maze width + + wparms := [ sprintf("Maze %dx%d",*maze,*maze[1]), # window parameters + "g","bg=white","canvas=hidden", + sprintf("size=%d,%d",ww,wh), + sprintf("dx=%d",BORDER), + sprintf("dy=%d",BORDER)] + + &window := open!wparms | stop("Unable to open Window") + + Fg("black") # Draw full grid + every DrawLine(x := 0 to cw by CELL,0,x,ch+1) # . verticals + every DrawLine(0,y := 0 to ch by CELL,cw+1,y) # . horizontals + + Fg("white") # Set to erase lines + every y := CELL*((r := 1 to mh)-1) & x := CELL*((c := 1 to mw)-1) do { + WAttrib("dx="||x+BORDER,"dy="||y+BORDER) # position @ cell r,c + if iand(maze[r,c],NORTH) ~= EMPTY then DrawLine(2,0,CELL-1,0) + if iand(maze[r,c],EAST) ~= EMPTY then DrawLine(CELL,2,CELL,CELL-1) + if iand(maze[r,c],SOUTH) ~= EMPTY then DrawLine(2,CELL,CELL-1,CELL) + if iand(maze[r,c],WEST) ~= EMPTY then DrawLine(0,2,0,CELL-1) + } + + WAttrib(&window,"canvas=normal") + WAttrib("dx="||(dxy:=BORDER+CELL/2),"dy="||dxy) + colorCell(start[1],start[2], "red", 3) + colorCell(finish[1],finish[2], "green", 3) + until Event() == &lpress + return mazeinfo(&window,maze,sprintf("maze-%dx%d-%d.gif",r,c,&now)) +end + +procedure DisplayMazeSolution(mz) #: draw marked PATH + &window := mz.window + maze := mz.maze + WAttrib("dx="||(dxy:=BORDER+CELL/2),"dy="||dxy) + every (r := 1 to *maze) & (c := 1 to *maze[1]) do { + fg := "blue" + if iand(maze[r,c],START) ~= EMPTY then fg := "red" + if iand(maze[r,c],FINISH) ~= EMPTY then fg := "green" + if iand(maze[r,c],PATH) ~= EMPTY then markCell(r,c,fg,3) + } + # Now, draw a line through the full path. + if *fullPath > 0 then { + DrawLine!fullPath + } + until Event() == &lpress + close(&window) + return mz +end + +procedure markCell(r,c,fg,sz) + initial WAttrib("dx="||(dxy:=BORDER+CELL/2),"dy="||dxy) + colorCell(r,c,fg,sz) +end + +procedure colorCell(r,c,fg,sz) + Fg(fg) + FillCircle(CELL*(c-1),CELL*(r-1),CELL/sz) +end + diff --git a/uni/progs/complex_ex.icn b/uni/progs/complex_ex.icn new file mode 100644 index 000000000..1d923b5b0 --- /dev/null +++ b/uni/progs/complex_ex.icn @@ -0,0 +1,19 @@ +#

+# complex_ex Example using Unicon's operator overloading and the +# Complex class (complex negation is not provided by the class) +# Requires that Unicon be configured with the --enable-ovld +# flag set. +# +import math + +procedure main() + write("c1: ",(c1 := Complex(1.5,3)).toString()) + write("c2: ",(c2 := Complex(1.5,1.5)).toString()) + write("+: ",(c1+c2).toString()) + write("-: ",(c1-c2).toString()) + write("*: ",(c1*c2).toString()) + write("/: ",(c1/c2).toString()) + write("additive inverse: ",c1.addInverse().toString()) + write("multiplicative inverse: ",c1.multInverse().toString()) + write("conjugate of (4,-3i): ",Complex(4,-3).conjugate().toString()) +end diff --git a/uni/progs/rankvote.icn b/uni/progs/rankvote.icn new file mode 100644 index 000000000..20b44db6a --- /dev/null +++ b/uni/progs/rankvote.icn @@ -0,0 +1,163 @@ +#

+# Implement ranked choice voting. +#

+#

+# Author: Stephen Wampler (sbw@tapestry.tucson.az.us) +#

+#

+# This program is in the public domain. +#

+ +import util +link printf # For sprintf + +global ballots, # Individual ballots + totalVotes, # Total votes for first candidate on each ballot, per round + results, # How each round turned out + maxcands # Most candidates on any ballot (needed in tie-breaks) + +#

+# Read in ranked-choice ballots where each ballot is a single line with the +# candidates ordered from first to last choice separated by commas. +# Output the winning candidate. +#

+#

+# The arguments, if present, are the files that +# contain the ballots. Multiple ballots may appear in each file. If no files +# are listed then &input is used. +# Also accepts an option --verbose to see more details of the +# rounds of the ranked-choice voting and --help can be used to get +# a simple help message. +# <[param A - arguments and options, if any]> +#

+procedure main(A) + Args(A,set(["verbose","help"]),helpMesg) + if Args().getOpt("help") then { + helpMesg() + stop() + } + files := Args().getArgs() + if *files > 0 then every readBallots(f := open(!files)) do close(f) + else readBallots(&input) + while not majority() do removeWorst() + showResult() +end + +#

+# Display a help message. +#

+procedure helpMesg() + write(&errout, "Usage: rankvote [--verbose] [ballotfile...]") + write(&errout, " reads ballots from &input if no ballotfiles listed") +end + +#

+# Constructs a list of ballots as read from a file. +# Each line is a single ballot with candidates ordered from first to last +# choice, separated by commas. A candidate's name may include commas if it +# is embedded in double quotes. +# <[param f - file containing ballots, already opened]> +#

+procedure readBallots(f) + initial { + ballots := [] + maxcands := 0 + } + every put(ballots, b := parseCSV(!f,',')) do maxcands <:= *b + return +end + +#

+# Check to see if any candidate has a clear majority. +#<[succeeds if a candidate has a clear majority]> +#<[fails if no candidate has a clear majority yet]> +#

+procedure majority() + results := computeRanks() + if Args().getOpt("verbose") then showAllResults() + if percentVote(results[-1]) > 50.0 then return +end + +#

+# Compute the current ranking based on first choices. +# <[returns a list of candidates ranked from last to first]> +#

+procedure computeRanks() + curRanking := table() + totalVotes := 0 + # Start by tallying counts for all first choices + every *(b := !ballots) > 0 do { # Ignore empty ballots + every c := !b do /curRanking[c] := 0 # Pass all candidates through + curRanking[b[1]] +:= 1 + totalVotes +:= 1 + } + return sort(curRanking,2) # Worst candidate is first in list! +end + +#

+# Find the remaining candidate with the fewest first-place votes and +# remove them. +# Handles tie breaks. +#

+procedure removeWorst() + worst := results[1] + worst2 := results[2] # Needed to check for ties... + if percentVote(worst) < percentVote(worst2) then removeCandidate(worst) + else tieBreak(worst1,worst2) +end + +#

+# Remove a candidate (always one with the fewest first-place votes, of course!) +# <[param worst - worst candidate as a list [name,number of votes for first]> +#

+procedure removeCandidate(worst) + write("\tRemoving '",worst[1],"' with ",percentVote(worst),"% of votes") + every b := !ballots do + every i := 1 to *b do if worst[1] == b[i] then delete(b,i) +end + +#

+# Break a tie for the worst remaining candidate by removing the candidate +# that has the lowest cummulative ranking. Factor in that some ballots may +# not include all candidates. +#

+procedure tieBreak(A[]) + if Args().getOpt("verbose") then write("\tGoing to tie breaker!") + tb := table() + every c := \!A do { + cVotes := 0 + every b := !ballots do { + every i := 1 to *b do if b[i] == c[1] then cVotes +:= (maxcands+1)-i + } + tb[c[1]] := cVotes # 0 if candidate c not on ballot b + } + worst := sort(tb,2)[1] + every i := 1 to *results do + if worst[1] == results[i][1] then break removeCandidate(results[i]) +end + +#

+# Compute the percent of first-place votes a candidate has in the current +# round. +# <[param candidate - a candidate as a list [name,number of votes for first]>. +# <[returns percent of total votes that the candidate has]> +#

+procedure percentVote(candidate) + return sprintf("%8.4r",100.0*candidate[2]/real(totalVotes)) +end + +#

+# Show the results for the current round. +#

+procedure showAllResults() + write("Current results for ",*results," candidate:") + every c := !results do write("\t",c[1]," -> ",percentVote(c),"%") +end + +#

+# Show the final round's winner. +#

+procedure showResult() + write("Winner is '",results[-1][1],"' with ",percentVote(results[-1]),"% ", + "of votes.") +end From f419e432f165b01598f916db1555fc1e7c041e7b Mon Sep 17 00:00:00 2001 From: Steve Wampler Date: Mon, 29 Apr 2024 15:43:26 -0700 Subject: [PATCH 2/6] Fix typo in UniDoc comment. Signed-off-by: Steve Wampler --- uni/progs/complex_ex.icn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uni/progs/complex_ex.icn b/uni/progs/complex_ex.icn index 1d923b5b0..ef3e2b30a 100644 --- a/uni/progs/complex_ex.icn +++ b/uni/progs/complex_ex.icn @@ -3,7 +3,7 @@ # Complex class (complex negation is not provided by the class) # Requires that Unicon be configured with the --enable-ovld # flag set. -# +#

import math procedure main() From d32009f5b8be6f13f3171b729a4d64d8dcdf8c3e Mon Sep 17 00:00:00 2001 From: Steve Wampler Date: Tue, 30 Apr 2024 11:10:15 -0700 Subject: [PATCH 3/6] Draw a fatter line through the cells on the best path. Signed-off-by: Steve Wampler --- uni/gprogs/qmazer.icn | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/uni/gprogs/qmazer.icn b/uni/gprogs/qmazer.icn index e06a8c9fb..ca07bcc88 100644 --- a/uni/gprogs/qmazer.icn +++ b/uni/gprogs/qmazer.icn @@ -276,16 +276,17 @@ procedure DisplayMazeSolution(mz) #: draw marked PATH &window := mz.window maze := mz.maze WAttrib("dx="||(dxy:=BORDER+CELL/2),"dy="||dxy) + # First, draw a line through the full path. + if *fullPath > 0 then { + push(fullPath, Clone("linewidth=3","fg=blue")) + DrawLine!fullPath + } every (r := 1 to *maze) & (c := 1 to *maze[1]) do { fg := "blue" if iand(maze[r,c],START) ~= EMPTY then fg := "red" if iand(maze[r,c],FINISH) ~= EMPTY then fg := "green" if iand(maze[r,c],PATH) ~= EMPTY then markCell(r,c,fg,3) } - # Now, draw a line through the full path. - if *fullPath > 0 then { - DrawLine!fullPath - } until Event() == &lpress close(&window) return mz From caa59dcacd8b5704ad6a0806691303eb1c34e857 Mon Sep 17 00:00:00 2001 From: Steve Wampler Date: Tue, 30 Apr 2024 11:11:00 -0700 Subject: [PATCH 4/6] Fix so UniDoc preface shows up in html. Signed-off-by: Steve Wampler --- uni/progs/complex_ex.icn | 1 + 1 file changed, 1 insertion(+) diff --git a/uni/progs/complex_ex.icn b/uni/progs/complex_ex.icn index ef3e2b30a..1ddcac1f7 100644 --- a/uni/progs/complex_ex.icn +++ b/uni/progs/complex_ex.icn @@ -4,6 +4,7 @@ # Requires that Unicon be configured with the --enable-ovld # flag set. #

+ import math procedure main() From eb4aee5789daa1af7fdd6c67550c5baa6a63514c Mon Sep 17 00:00:00 2001 From: Steve Wampler Date: Tue, 30 Apr 2024 12:16:46 -0700 Subject: [PATCH 5/6] Use line segments to show all attempted paths. Signed-off-by: Steve Wampler --- uni/gprogs/qmazer.icn | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/uni/gprogs/qmazer.icn b/uni/gprogs/qmazer.icn index ca07bcc88..4b3ffb4b9 100644 --- a/uni/gprogs/qmazer.icn +++ b/uni/gprogs/qmazer.icn @@ -115,12 +115,17 @@ class QMouse(maze, loc, parent, len, val) end method visit(r,c) + static cw := Clone("linewidth=2","fg=grey") local p # Only visit a cell if it makes sense to do so (not already seen and # not on a path longer than the best known solution). critical region[r,c]: if EMPTY = iand(maze[r,c],SEEN) then { if /bestMouse | (len <= bestMouse.getLen()) then { # Keep going + if \showPaths then { + DrawLine(cw,CELL*(c-1),CELL*(r-1), + CELL*(loc.c-1),CELL*(loc.r-1)) + } mark(maze, r,c) unlock(region[r,c]) return Position(r,c) From e0d7f5c3fe78e1a721f1d80217b9b2135c4572ed Mon Sep 17 00:00:00 2001 From: Steve Wampler Date: Thu, 2 May 2024 12:22:06 -0700 Subject: [PATCH 6/6] Various fixes. Signed-off-by: Steve Wampler --- uni/gprogs/qmazer.icn | 108 +++++++++++++++++++++++++++-------------- uni/progs/rankvote.icn | 1 + 2 files changed, 72 insertions(+), 37 deletions(-) diff --git a/uni/gprogs/qmazer.icn b/uni/gprogs/qmazer.icn index 4b3ffb4b9..fdde4c4f3 100644 --- a/uni/gprogs/qmazer.icn +++ b/uni/gprogs/qmazer.icn @@ -16,7 +16,7 @@ global showMice, showPaths global start, finish global delayms -global fullPath +global fullPath, visited import util link printf @@ -31,7 +31,7 @@ $define BORDER 30 # border size in pixels # Arguments (all are optional): #
 #  help -- display a help message
-#  showpaths -- put a mark on each maze cell as it's visited
+#  showpaths -- put a mark on each maze cell as it's seen
 #  showmice -- show the individual mice as they traverse the maze
 #  cols=COLS -- set the number of columns [default: 88]
 #  rows=ROWS -- set the number of rows [default: 48]
@@ -65,8 +65,8 @@ procedure helpMesg()
     write(&errout,"\twhere:")
     write(&errout,"\t\t--showmice  # displays all mice as they search")
     write(&errout,"\t\t--showpaths # displays all mice paths as they search")
-    write(&errout,"\t\t--cols=C    # sets maze width to C (default 188) columns")
-    write(&errout,"\t\t--rows=R    # sets maze height to R (default 100) rows")
+    write(&errout,"\t\t--cols=C    # sets maze width to C (default 88) columns")
+    write(&errout,"\t\t--rows=R    # sets maze height to R (default 48) rows")
     write(&errout,"\t\t--delay=ms  # delay ms millisecond (default none) between mice creations")
     stop()
 end
@@ -203,41 +203,75 @@ procedure waitForCompletion()
    critical qMiceEmpty: while *qMice > 0 do wait(qMiceEmpty)
 end
  
-procedure GenerateMaze(r,c)                     #: Depth First Maze Generation
-static maze,h,w,rd 
-   if /maze then {                              # BEGING - No maze yet
-      /h := integer(1 < r) | runerr(r,205)      # valid size 2x2 or better
-      /w := integer(1 < c) | runerr(r,205)
-      every !(maze := list(h)) := list(w,EMPTY) # shiny new empty maze
-      start  := [?h,?w,?4-1,START]              # random [r,c] start & finish
-      finish := [?h,?w,(start[3]+2)%4,FINISH]   # w/ opposite side exponent
-      every x := start | finish do {
-         case x[3] := 2 ^ x[3] of {             # get side from exponent and 
-            NORTH : x[1] := 1                   # project r,c to selected edge
-            EAST  : x[2] := w
-            SOUTH : x[1] := h         
-            WEST  : x[2] := 1
-            }   
-         maze[x[1],x[2]] +:= x[3] + x[4]        # transcribe s/f to maze
-         }
-      rd := [NORTH, EAST, SOUTH, WEST]          # initial list of directions     
-      GenerateMaze(start[1],start[2])           # recurse through maze     
-      return 1(.maze,maze := &null)             # return maze, reset for next
-   }
-   else {         # ----------------------- recursed to clear insize of maze
-      if iand(maze[r,c],SEEN) = EMPTY then {        # in bounds and not SEEN yet?
-         maze[r,c] +:= SEEN                     # Mark current cell as visited   
-         every !rd :=: ?rd                      # randomize list of directions
+procedure GenerateMaze(r,c)     # Non-recursive depth first maze generation
+local maze,h,w,rd 
+   /h := integer(1 < r) | runerr(r,205)      # valid size 2x2 or better
+   /w := integer(1 < c) | runerr(r,205)
+   # The maze is a 2-D array implemented as a list of lists, where each cell
+   #   holds bit flags (see line 80 and following for the possible flags).
+   #   Note that the flags denoting directions indicate a direction to another
+   #   cell that is reachable by going in that direction (e.g. the flag NORTH
+   #   indicates that there is no wall between this cell and its northern
+   #   neighbor).
+   every !(maze := list(h)) := list(w,EMPTY) # shiny new empty maze
+                                             #   (each cell starts with 4 walls)
+   start  := [?h,?w,?4-1,START]              # random [r,c] start & finish
+   finish := [?h,?w,(start[3]+2)%4,FINISH]   # w/ opposite side exponent
+   every x := start | finish do {
+      case x[3] := 2 ^ x[3] of {             # get side from exponent and 
+         NORTH : x[1] := 1                   # project r,c to selected edge
+         EAST  : x[2] := w
+         SOUTH : x[1] := h         
+         WEST  : x[2] := 1
+         }   
+      maze[x[1],x[2]] +:= x[3] + x[4]        # transcribe s/f to maze
+      }
+   maze[start[1],start[2]] +:= SEEN
+   push(visited := [], Position(start[1],start[2]))
+   rd := [NORTH, EAST, SOUTH, WEST]          # initial list of directions     
+   while *visited > 0 do {
+      p := pop(visited)
+      r := p.r
+      c := p.c
+      if maze[r,c] then {                    # in bounds
+         every !rd :=: ?rd                    # randomize list of directions
+         xp := &null                          # This forces depth-first!
          every d := !rd do
-            case d of {                         # try all, succeed & clear wall
-               NORTH :  maze[r,c] +:= ( GenerateMaze(r-1,c), NORTH)
-               EAST  :  maze[r,c] +:= ( GenerateMaze(r,c+1),  EAST)
-               SOUTH :  maze[r,c] +:= ( GenerateMaze(r+1,c), SOUTH)
-               WEST  :  maze[r,c] +:= ( GenerateMaze(r,c-1),  WEST)   
+            case d of {                       # try all, if succeed: clear wall
+               NORTH :  if np := chkPos(maze,r-1,c,p, NORTH) then {
+                           if /xp then (push(visited,p),xp := "putback")
+                           push(visited, np)
+                           break
+                           }
+               EAST  :  if np := chkPos(maze,r,c+1,p, EAST) then {
+                           if /xp then (push(visited,p),xp := "putback")
+                           push(visited, np)
+                           break
+                           }
+               SOUTH :  if np := chkPos(maze,r+1,c,p, SOUTH) then {
+                           if /xp then (push(visited,p),xp := "putback")
+                           push(visited, np)
+                           break
+                           }
+               WEST  :  if np := chkPos(maze,r,c-1,p, WEST) then {
+                           if /xp then (push(visited,p),xp := "putback")
+                           push(visited, np)
+                           break
+                           }
                }
-         return                                 # signal success to caller
-         }
-   }
+          }
+      }
+   return maze
+end
+
+procedure chkPos(maze,r,c,p,wall)
+   # If the new cell is inside the maze and hasn't been visited yet,
+   #    remove the wall between the old and new cells and return the new cell
+   if iand(maze[r,c], SEEN) = EMPTY then {
+      maze[r,c] +:= SEEN
+      maze[p.r,p.c] +:= wall
+      return Position(r,c)
+      }
 end
  
 record mazeinfo(window,maze,filename)               # keepers
diff --git a/uni/progs/rankvote.icn b/uni/progs/rankvote.icn
index 20b44db6a..6c6e4d8e7 100644
--- a/uni/progs/rankvote.icn
+++ b/uni/progs/rankvote.icn
@@ -20,6 +20,7 @@ global ballots,      # Individual ballots
 #  Read in ranked-choice ballots where each ballot is a single line with the
 #  candidates ordered from first to last choice separated by commas.
 #  Output the winning candidate.
+#  This is not intended for use where a "split-ballot" is used.
 #

#

# The arguments, if present, are the files that