|
| 1 | +# Randomized Prim's Algorithm |
| 2 | +# Choose a random cell within the maze grid (given by its width and height) and design it as a start cell. |
| 3 | +# Add the start cell to (by now empty) inCells set. |
| 4 | +# Mark cells around the start cell as frontier, i.e. add them to frontierCells set. |
| 5 | +# While frontierCells set is not empty: |
| 6 | +# Choose a random frontier cell cF from frontierCells. |
| 7 | +# Choose a random in-cell cI adjacent to cF. |
| 8 | +# Add cF to inCells. |
| 9 | +# Mark all out-cells around cF as frontier. |
| 10 | +# Add a path between cI and cF to the maze paths set. |
| 11 | +# Remove cF from frontierCells set |
| 12 | +################# |
| 13 | +# Set slowly to any value to sleep that much per drawing cycle |
| 14 | +pmaze <- function(dimx, dimy, slowly=FALSE, tcols, ...) { |
| 15 | + if(missing(tcols) ) tcols <- rainbow((dimx+dimy)/10) # a wag at count |
| 16 | +#plotting the full grid: |
| 17 | +mardef<-par()$mar |
| 18 | +par(mar=c(.1,.1,.1,.1)) |
| 19 | +#build init matrices; remember cells are ID'd by "top left" coord |
| 20 | +icell<-matrix(0,nrow=(dimy),ncol=(dimx) ) |
| 21 | +fcell<-matrix(0,nrow=(dimy),ncol=(dimx) ) |
| 22 | +walls <- NULL #store indices of removed walls |
| 23 | +dewall<-vector(length=4) |
| 24 | +# select starting cell |
| 25 | +initx<-sample(1:dimx,1) |
| 26 | +inity<-sample(1:dimy,1) |
| 27 | +icell[inity,initx] <- 1 |
| 28 | +# calculate frontier cells from that cell. Watch for borders! |
| 29 | +# zero values are ignored, hence the mod func |
| 30 | +fcell[inity, c(initx-1,(initx+1)%%(dimx+1) )]<-1 |
| 31 | +fcell[c(inity-1,(inity+1)%%(dimy+1) ), initx]<-1 |
| 32 | +#OK, so now fcell is nonempty, and can start the while loop |
| 33 | +while (sum(fcell) > 0 ) { |
| 34 | + # pick a random frontier cell. |
| 35 | + fronts <- which (fcell>0, arr.ind=TRUE) |
| 36 | + fgrab <- fronts[sample(1:nrow(fronts),1),] |
| 37 | + doorx <- fgrab[2] + c(1,-1) |
| 38 | + doorx <- doorx[doorx %in% 1:dimx] |
| 39 | + doory <- fgrab[1] + c(1,-1) |
| 40 | + doory <- doory[doory %in% 1:dimy] |
| 41 | + # want only the "cross" 4 cells, and only the ones which are in Icell |
| 42 | + adjac<-NULL |
| 43 | + for (j in 1:length(doorx) ) if( icell[fgrab[1],doorx[j]]>0 ) adjac<-rbind(adjac,c(fgrab[1],doorx[j]) ) |
| 44 | + for (j in 1:length(doory) ) if( icell[doory[j],fgrab[2]]>0 ) adjac<-rbind(adjac,c(doory[j],fgrab[2]) ) |
| 45 | + # pick one at random |
| 46 | + igrab <- adjac[sample(1:nrow(adjac),1),] |
| 47 | + # Now figure out the coordinates of the endpoints of this segment. |
| 48 | + #go from (max_row_ind,max_col_ind) and increment whichever index |
| 49 | + # was the same for both. |
| 50 | + dewall[2] <- max(igrab[1],fgrab[1]) |
| 51 | + dewall[1] <- max(igrab[2],fgrab[2]) |
| 52 | + dewall[4] <- dewall[2] + 1*(igrab[1]==fgrab[1]) |
| 53 | + dewall[3] <- dewall[1] + 1*(igrab[2]==fgrab[2]) |
| 54 | + # add it to a matrix. |
| 55 | + walls <- rbind(walls,dewall) |
| 56 | +#clean up |
| 57 | +rownames(walls) <- NULL |
| 58 | +colnames(walls) <- c('x1', 'y1','x2','y2') |
| 59 | +# need to zero the frontier cell and unzero the matching icell |
| 60 | + fcell[fgrab[1],fgrab[2]] <-0 |
| 61 | + icell[fgrab[1],fgrab[2]] <-1 |
| 62 | +# and set neighbors of fgrab to 1 in fcell. |
| 63 | +# But there's more: do NOT assign a "former" frontier cell even if it's a |
| 64 | +# neighbor of the current fgrab. This can be done by skipping neighbors |
| 65 | +# whose icell value is 1. |
| 66 | + for (j in 1:length(doorx) ) |
| 67 | + if( icell[fgrab[1],doorx[j]] == 0 ) fcell[fgrab[1], doorx[j]] <- 1 |
| 68 | + for (j in 1:length(doory) ) |
| 69 | + if( icell[doory[j],fgrab[2]] == 0 ) fcell[doory[j], fgrab[2]] <- 1 |
| 70 | +# now rinse lather repeat. |
| 71 | + } # end of while |
| 72 | +# modify limits to make it easier to plot in/out labels |
| 73 | +plot( c(1,(dimx+1),(dimx+1),1,1), c(1,1,(dimy+1),dimy+1,1),t='l',axes=F,xlab='',ylab='',xlim=c(0,dimx+2),ylim=c(0,dimy+2) ) |
| 74 | + df = as.matrix(expand.grid( xvert= seq_len(dimx),yvert= seq_len(dimy))) |
| 75 | + dfv <- cbind(df,df[,1],df[,2]+1) |
| 76 | + df2 <- as.matrix(expand.grid(yvert= seq_len(dimy), xvert= seq_len(dimx))) |
| 77 | + dfh <- cbind(df2[,2],df2[,1],df2[,2]+1,df2[,1]) |
| 78 | +# concatenate each row w/ delimiter (so that 2_13 is not same as 21_3 ) |
| 79 | +#nb alternative methods as found on SO turn out to be much slower |
| 80 | + allwalls<-rbind(dfv,dfh) |
| 81 | + allrows<-unlist(sapply(1:nrow(allwalls),function(j) paste(allwalls[j,],collapse='_')) ) |
| 82 | +# the maze walls: |
| 83 | + allfoo <- unlist(sapply(1:nrow(walls),function(j) paste(walls[j,],collapse='_'))) |
| 84 | + thewalls<-setdiff(allrows,allfoo) |
| 85 | + dowalls<-allwalls[allrows%in%thewalls,] |
| 86 | + ################################################################### |
| 87 | + # New project: start at any wall touching border, and plot all segments in |
| 88 | + # that 'tree,' defineds as segments w/ common endpoints. Then pick a remaining |
| 89 | + # segment touching border, etc. |
| 90 | + # Note: fortunately, which(dist(rbind(foo,empty_matrix))) returns nothing rather |
| 91 | + # than any crash. |
| 92 | + # pick any wall to start, remove it from starting set |
| 93 | + treecnt<-1 |
| 94 | + #kill edge walls -oops , not equality but equal to 1 or nrow/ncol |
| 95 | + vedges <- which( (dowalls[,1]==dowalls[,3]) & (dowalls[,1]==1 | dowalls[,1]==dimx+1) ) |
| 96 | + hedges <- which( (dowalls[,2]==dowalls[,4]) & (dowalls[,2]==1 | dowalls[,2]==dimy+1) ) |
| 97 | + dowalls<-dowalls[-c(vedges,hedges),,drop=FALSE] |
| 98 | + # now sort into trees |
| 99 | + # this appears to take rather a while. What can I vectorize? |
| 100 | + while(nrow(dowalls)>0 ) { |
| 101 | + tree <- matrix(dowalls[1,],nr=1) #force dimensions |
| 102 | + dowalls<-dowalls[-1,,drop=FALSE] |
| 103 | + treerow <- 1 #current row of tree we're looking at |
| 104 | + while ( treerow <= nrow(tree) ) { |
| 105 | + #only examine the first 'column' of the dist() matrix 'cause those are the |
| 106 | + # distances from the tree[] endpoints |
| 107 | +# Still seems to me that one bigass dist(dowalls) matrix should be |
| 108 | +# sortable by identified row/column per tree[] element, thus saving |
| 109 | +# an absurd amount of processing. Let's get on that |
| 110 | + touch <- c( which(dist(rbind(tree[treerow,1:2],dowalls[,1:2]) )[1:nrow(dowalls)]==0), which(dist(rbind(tree[treerow,1:2],dowalls[,3:4]) )[1:nrow(dowalls)]==0), which(dist(rbind(tree[treerow,3:4],dowalls[,1:2]) )[1:nrow(dowalls)]==0), which(dist(rbind(tree[treerow,3:4],dowalls[,3:4]) )[1:nrow(dowalls)]==0) ) |
| 111 | + if(length(touch) ) { |
| 112 | + tree <- rbind(tree,dowalls[c(touch),]) |
| 113 | + dowalls <- dowalls[-c(touch),,drop=FALSE] |
| 114 | + } |
| 115 | +# track the row of tree[] we're working with AND track how many rows there currently are in tree[] |
| 116 | + treerow <- treerow + 1 |
| 117 | + } #end of while treerow <= nrow |
| 118 | + for (k in 1:nrow(tree) ) { |
| 119 | + lines(tree[k,c(1,3)],tree[k,c(2,4)] , col=tcols[treecnt%%(length(tcols)-1) +1 ] ) |
| 120 | + if(slowly) Sys.sleep(slowly) |
| 121 | + } |
| 122 | + treecnt <- treecnt + 1 |
| 123 | +} #end of while dwalls exists |
| 124 | + # end of new project |
| 125 | + # original method: |
| 126 | + # dowalls <- dowalls[order(rowSums(dowalls[,1:2])),] |
| 127 | + # timng is kinda cruddy here. Experiment with (if(slowly & !j%%10)) sort of thing |
| 128 | + # for(j in 1:nrow(dowalls) ) { |
| 129 | + # lines(dowalls[j,c(1,3)],dowalls[j,c(2,4)],...) |
| 130 | + # if(slowly) Sys.sleep(slowly) |
| 131 | + # } |
| 132 | +## |
| 133 | +par(mar=c(mardef)) |
| 134 | +return(invisible(walls)) |
| 135 | +} |
| 136 | +# |
| 137 | +################################# |
| 138 | +# |
| 139 | +# This will "kick off" my "fill dead end" function, |
| 140 | +fillij <- function(wallmat, into=c(1,1), outof=c(apply(wallmat[,1:2],2, max)),...) { |
| 141 | +dimx<-max(wallmat[,1]) |
| 142 | +dimy<-max(wallmat[,2]) |
| 143 | +ijfoo<-cbind(expand.grid(x=1:dimx,y=1:dimy), matrix(0, nc=4)) |
| 144 | +for(j in 1:(nrow(wallmat)) ) { |
| 145 | + xind <- wallmat[j,1] |
| 146 | + yind <- wallmat[j,2] |
| 147 | +# Another win for DWIM! |
| 148 | + ijrow <- which(ijfoo[,1]==xind & ijfoo[,2]==yind) |
| 149 | + if( xind == wallmat[j,3]) { |
| 150 | + # OK, verified that initial ijfoo is dead on (at least for test case) |
| 151 | + ijfoo[ijrow,6]<- 1 |
| 152 | +# we never remove a vertical wall where x==1, so ijrow-1 is OK |
| 153 | + ijfoo[(ijrow-1),4] <- 1 |
| 154 | + } else { |
| 155 | + #only other possibility is horiz wall |
| 156 | + ijfoo[ijrow,3] <- 1 |
| 157 | +# want the row with same x values |
| 158 | +# and y values one less: ijrow- dimx , (dimx is the fast-changing index) |
| 159 | + ijfoo[ijrow-dimx,5] <- 1 |
| 160 | + } |
| 161 | + } #end for (j) loop |
| 162 | + # now turn any deadend into an x,y,0,0,0,0 row and assign a zero to |
| 163 | + # the matching wall of its neighbor. But first... |
| 164 | + # lockdown 'into' and 'outof' rows by setting to 5 and 6 |
| 165 | + oldijfoo<-ijfoo |
| 166 | + inrow <- into[1]+dimx*(into[2]-1) |
| 167 | + outrow <- outof[1] + dimx*(outof[2]-1) |
| 168 | + ijfoo[inrow,3:6]<-5 |
| 169 | + ijfoo[outrow,3:6]<-6 |
| 170 | + startijfoo<-ijfoo |
| 171 | + changed <- TRUE |
| 172 | + while (changed) { |
| 173 | + deads <- which(rowSums(ijfoo[,3:6]) == 1 ) |
| 174 | +# aha: if have two deads, and the first one cleans out the second one, |
| 175 | +# that's where I get the zero-length fillwall! |
| 176 | + for (k in deads) { |
| 177 | + #find the live wall and so on. k= 1,2,3,4 --> N,E,S,W |
| 178 | + fillwall<-which(ijfoo[k,3:6] ==1 ) |
| 179 | + if( length(fillwall) > 0) { |
| 180 | + ijfoo[k,(fillwall+2)] <- 0 |
| 181 | + # and its neighbor... |
| 182 | + switch(fillwall, |
| 183 | + {ijfoo[(k - dimx),5] <- 0 }, |
| 184 | + {ijfoo[(k + 1),6] <- 0 }, |
| 185 | + {ijfoo[(k + dimx),3] <- 0 }, |
| 186 | + {ijfoo[ (k - 1),4] <- 0} ) |
| 187 | + } |
| 188 | + } #end for k in deads loop |
| 189 | + # reset start and end cells to 'lockout' value, just in case |
| 190 | + ijfoo[inrow,3:6]<-5 |
| 191 | + ijfoo[outrow,3:6]<-6 |
| 192 | + changed <- !identical(oldijfoo,ijfoo) |
| 193 | + oldijfoo<-ijfoo |
| 194 | + } |
| 195 | +# so now ijfoo has been reduced to valid paths; all other rows sum to zero |
| 196 | +# label start and stop |
| 197 | + incell<-which(ijfoo[,3] == 5) |
| 198 | + outcell<-which(ijfoo[,3] ==6) |
| 199 | +xfoo<-ijfoo[incell,1] |
| 200 | +yfoo<-ijfoo[incell,2] |
| 201 | + text(xfoo-1.5*(xfoo==1)+ .5+ 1.5*(xfoo>=dimx), yfoo-(yfoo==1)+(yfoo>=dimy) + .5,'IN',font=2,cex=.6) |
| 202 | +xfoo<-ijfoo[outcell,1] |
| 203 | +yfoo<-ijfoo[outcell,2] |
| 204 | + text(xfoo-1.5*(xfoo==1)+ .5+ 1.5*(xfoo>=dimx), yfoo-(yfoo==1)+(yfoo>=dimy) + .5, 'OUT',font=2,cex=.6) |
| 205 | + return(invisible(ijfoo)) |
| 206 | +} |
| 207 | + |
| 208 | +############################ draw the path |
| 209 | +# |
| 210 | +# wallmat: matrix generated with pmaze() containing "door" vertex coordinates |
| 211 | +# into: coordinates of cell for start/entering the maze |
| 212 | +# outof: coordinates of cell at finish of maze |
| 213 | +# color: color of path drawn |
| 214 | +# sleeptime: delay between increments of path drawing |
| 215 | +# ... : optional arguments compatible with lines() |
| 216 | +dopath<-function(wallmat, into=c(1,1), outof=c(apply(wallmat[,1:2],2, max)), color='red', sleeptime=0, ...) { |
| 217 | +dimx<-max(wallmat[,1]) |
| 218 | +dimy<-max(wallmat[,2]) |
| 219 | +# do input validation. But if someone sends a negative dim,poop on him |
| 220 | +into <- c(min(into[1],dimx), min(into[2],dimy) ) |
| 221 | +outof <- c(min(outof[1],dimx), min(outof[2],dimy) ) |
| 222 | +# call fillij() here |
| 223 | + pathmat<-fillij(wallmat, into,outof ) |
| 224 | +# remove deadends - I think this is redundant, but leave in for the moment |
| 225 | +pathmat <- pathmat[rowSums(pathmat[,3:6])>0,] |
| 226 | +# Start at IN, find the location of distance==1, that may be in |
| 227 | +# either the IN row or IN column of distmat. Draw the path, delete/change |
| 228 | +# that "1" value and find the distance==1 for the adjacent cell we just found, |
| 229 | +# and so on until we find the "END" cell. |
| 230 | +startcell<- which(pathmat[,3]==5) |
| 231 | +distmat<- dist(pathmat[,1:2]) # object vector class 'dist' |
| 232 | +matdist<-as.matrix(distmat) |
| 233 | +mbar<-lower.tri(matdist)*matdist #kill upper triangle |
| 234 | + # run until startcell has "6" in it |
| 235 | + while (pathmat[startcell,3] != 6 ) { |
| 236 | +nextcell<- c(which(mbar[,startcell]==1),which(mbar[startcell,]==1)) |
| 237 | +# fix to "bad" distance==1 locations (with overkill): |
| 238 | + mbar[nextcell,startcell] <- 0 |
| 239 | + mbar[startcell,nextcell] <- 0 |
| 240 | +## that works because I will never want to "find" startcell as a |
| 241 | +## "nextcell" to some other cell. |
| 242 | + if(length(nextcell)>1) { |
| 243 | + mated<-vector(length=length(nextcell)) |
| 244 | + startloc<-pathmat[startcell,1:2] #move outside jj loop duh |
| 245 | + for( jj in 1: length(nextcell)) { |
| 246 | +## compare Nvs S and E vs W . Want a pair to be "1" --necessary but not sufficient |
| 247 | + delcell <- ( pathmat[startcell,3:6] * pathmat[nextcell[jj],c(5,6,3,4)] ) |
| 248 | +## now reject NS or EW if it's in wrong direction, i.e. calculate which wall/door |
| 249 | +## the two have in common. if startcell is N of nextcell, y1<y2 . and so on. |
| 250 | + nextloc <- pathmat[nextcell[jj],1:2] |
| 251 | + matchwall<-c(startloc[2]>nextloc[2], startloc[1]<nextloc[1], startloc[2]<nextloc[2], startloc[1]>nextloc[1]) |
| 252 | +# mated is TRUE only when they share a door |
| 253 | + mated[jj]<- sum(delcell * matchwall ) |
| 254 | + } |
| 255 | +## so now set actual nextcell to the mated==TRUE element |
| 256 | + nextcell<-nextcell[as.logical(mated)] |
| 257 | + } #end of if(length) block |
| 258 | +# loop over NESW |
| 259 | + for (j in 3:6) { |
| 260 | + # create the offsets for the given column |
| 261 | + offs <- c( 0.5*(-1)^(j==6), 0.5 + (j==4), 0.5*(-1)^(j==3), 0.5 +(j==5) ) |
| 262 | + if ( pathmat[nextcell,j] == 1 ) { |
| 263 | + lines(pathmat[nextcell,1]+offs[1:2], pathmat[nextcell,2]+offs[3:4], col=color, ...) |
| 264 | + } |
| 265 | + } |
| 266 | + Sys.sleep(sleeptime) |
| 267 | + # update starting location |
| 268 | + startcell<-nextcell |
| 269 | +} # end of while(not in END cell) |
| 270 | +# get the path |
| 271 | +return(invisible(pathmat)) |
| 272 | +} |
| 273 | +# |
| 274 | +############################ loop it |
| 275 | +## edited to handle any maze w/ compatible walls output |
| 276 | +# old "dopmaze" doesn't have a 'mazename' arg |
| 277 | +#dopmaze <- function(reps,slowly=.01, sleep=.2) { |
| 278 | +domaze <- function(reps,mazetype=c('rbmaze','pmaze'),slowly=.01, sleep=.2) { |
| 279 | +cols <- c('red','blue','green','purple','pink') |
| 280 | +dimrange <- 15:50 |
| 281 | + for (j in 1: reps) { |
| 282 | +# change to a do.call |
| 283 | +# foo <- pmaze(sample(dimrange,1),sample(dimrange,1),slowly=slowly ) |
| 284 | + foo<-do.call(mazetype, args=list(dimx=sample(dimrange,1),dimy=sample(dimrange,1),slowly=slowly) ) |
| 285 | +# into and outof are forced to be on |
| 286 | +# the perimeter. |
| 287 | + dimx <- max(foo[,1]) |
| 288 | + dimy <- max(foo[,2]) |
| 289 | + insamp <- rbind(c(1,dimx,sample(2:(dimx-1),2)),c(sample(2:(dimy-1),2),1,dimy) ) |
| 290 | + outsamp <- rbind(c(1,dimx,sample(2:(dimx-1),2)),c(sample(2:(dimy-1),2),1,dimy) ) |
| 291 | + into <- c(insamp[,sample(1:4,1)] ) |
| 292 | + outof <- c(outsamp[,sample(1:4,1)] ) |
| 293 | +# semiBUG: don't allow IN and OUT to be too close to each other, or |
| 294 | +# the fact that dopath never zeroes out those rows will lead to a crash |
| 295 | +# force separation of IN and OUT |
| 296 | + while (dist(rbind(into,outof))< 4 ) { |
| 297 | + outsamp <- rbind(c(1,dimx,sample(2:(dimx-1),2)),c(sample(2:(dimy-1),2),1,dimy) ) |
| 298 | + outof <- c(outsamp[,sample(1:4,1)] ) |
| 299 | + } |
| 300 | + dopath(foo, into=into, outof=outof, col=sample(cols,1), sleep = sleep) |
| 301 | + Sys.sleep(5) |
| 302 | + } |
| 303 | +# just in case I'm interested. |
| 304 | +outs <- list(fmaze=foo, into=into, outof=outof) |
| 305 | +return(invisible(outs)) |
| 306 | +} |
| 307 | + |
| 308 | +######################### a simple "replot" function to take the |
| 309 | +# output of pmaze and redraw the maze |
| 310 | +repmaze<-function(walls) { |
| 311 | + mardef<-par()$mar |
| 312 | + dimx<-max(walls[,1]) |
| 313 | + dimy<-max(walls[,2]) |
| 314 | + par(mar=c(.1,.1,.1,.1)) |
| 315 | +plot( c(1,(dimx+1)), c(1,(dimy+1)),t='n',axes=F,xlab='',ylab='') |
| 316 | +# kill the sapplys if go to post-wall cell plotting |
| 317 | +sapply(1:(dimy+1), function(j) lines(c(1,dimx+1),c(j,j)) )->ssfoo |
| 318 | +sapply(1:(dimx+1), function(j) lines(c(j,j),c(1,dimy+1)) )->ssfoo |
| 319 | +for( j in 1:nrow(walls) ) lines(walls[j,c(1,3)],walls[j,c(2,4)],col='white') |
| 320 | +par(mar=c(mardef)) |
| 321 | +} |
0 commit comments