1
@@ -21,7 +21,7 @@
|
||||
<a href="/8-bit/index.html">8-bit</a>
|
||||
<a href="/space/space/SpaceInvaders-master/index.html">spaceinvaders</a>
|
||||
<a href="roberts games/clash-royale-api-master/robertw games/public/index.html">clash Royal</a>
|
||||
<a href="">pac man</a>
|
||||
<a href="pac man/pacman-mazegen-gh-pages/pacman-mazegen-gh-pages/index.htm">pac man</a>
|
||||
<body><input type = "button" onclick = "EventHandler();" name = "ok" value = "fanf2"/></body>
|
||||
<p>miles is not outing for 2 days this will get updated every day</p>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
# Pac-Man Maze Generator
|
||||
|
||||
Generating random mazes aesthetically similar to those found in Pac-Man and Ms. Pac-Man.
|
||||
The [tetris](tetris) solution yielded best results:
|
||||
|
||||
<img src="tetris/working.png" />
|
||||
|
||||
<a href="http://shaunlebron.github.com/pacman-mazegen">Click here for an explanation.</a>
|
||||
|
||||
## History
|
||||
|
||||
Research conducted summer 2012.
|
||||
|
||||
- The [randomfill](randomfill) folder contains a Python solution that uses a special
|
||||
heuristic for progressively placing random valid walls.
|
||||
- The [answerset](answerset) folder contains a
|
||||
[Clingo](http://potassco.sourceforge.net/#clingo) solution that specifies
|
||||
declarative constraints. This [blog post](http://eis-blog.ucsc.edu/2011/10/map-generation-speedrun/)
|
||||
is a good introduction to map generation using Clingo.
|
||||
- The [spanningtree](spanningtree) folder is a lead to apply a modified version of conventional maze generation algorithms.
|
||||
- The [tetris](tetris) folder contains a solution for stacking pieces in a tetris like manner.
|
||||
@@ -0,0 +1,31 @@
|
||||
import sys,re
|
||||
|
||||
def print_map(line):
|
||||
walls = {}
|
||||
blank = {}
|
||||
xdim = {}
|
||||
ydim = {}
|
||||
for m in re.finditer('wall\((\d+),(\d+)\)', line):
|
||||
walls[(int(m.group(1)),int(m.group(2)))] = True
|
||||
for m in re.finditer('blank\((\d+),(\d+)\)', line):
|
||||
blank[(int(m.group(1)),int(m.group(2)))] = True
|
||||
for m in re.finditer('xdim\((\d+)\)', line):
|
||||
xdim[int(m.group(1))] = True
|
||||
for m in re.finditer('ydim\((\d+)\)', line):
|
||||
ydim[int(m.group(1))] = True
|
||||
for y in range(max(ydim.keys())):
|
||||
for x in range(max(xdim.keys())):
|
||||
if (x+1,y+1) in walls:
|
||||
sys.stdout.write('|')
|
||||
elif (x+1,y+1) in blank:
|
||||
sys.stdout.write('_')
|
||||
else:
|
||||
sys.stdout.write('.')
|
||||
print ''
|
||||
|
||||
if __name__ == "__main__":
|
||||
lines = sys.stdin.readlines()
|
||||
for i,line in enumerate(lines):
|
||||
if line.startswith("Answer:"):
|
||||
print_map(lines[i+1])
|
||||
print ""
|
||||
@@ -0,0 +1,320 @@
|
||||
||||||||||||||||||||||||||||
|
||||
|....||..............||....|
|
||||
|.||.||.||||||||||||.||.||.|
|
||||
|.||.||.||||||||||||.||.||.|
|
||||
|.||......||....||......||.|
|
||||
|.|||||||.||.||.||.|||||||.|
|
||||
|.|||||||.||.||.||.|||||||.|
|
||||
|......||....||....||......|
|
||||
|||.||.||.||.||.||.||.||.|||
|
||||
|||.||....||.||.||....||.|||
|
||||
|...||.||.||.||.||.||.||...|
|
||||
|.||||.||..........||.||||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|...||....|______|....||...|
|
||||
|||.|||||.|______|.|||||.|||
|
||||
|||.|||||.|______|.|||||.|||
|
||||
|......||.||||||||.||......|
|
||||
|.||||.||..........||.||||.|
|
||||
|.|__|.||.||.||.||.||.|__|.|
|
||||
|.||||.||.||.||.||.||.||||.|
|
||||
|.........||.||.||.........|
|
||||
||||.|||||||.||.|||||||.||||
|
||||
||||.|||||||.||.|||||||.||||
|
||||
........||........||........
|
||||
||||.||.||.||||||.||.||.||||
|
||||
___|.||.||.||||||.||.||.|___
|
||||
___|.||..............||.|___
|
||||
___|.|||||||.||.|||||||.|___
|
||||
||||.|||||||.||.|||||||.||||
|
||||
.............||.............
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
.....||..............||.....
|
||||
||||.||.||||||||||||.||.||||
|
||||
___|.||.||||||||||||.||.|___
|
||||
___|....||........||....|___
|
||||
___|.||.||.||||||.||.||.|___
|
||||
||||.||....|____|....||.||||
|
||||
.....||.||.||||||.||.||.....
|
||||
|.||.||.||........||.||.||.|
|
||||
|.||.||.||||||||||||.||.||.|
|
||||
|.||.||.||||||||||||.||.||.|
|
||||
|..........................|
|
||||
|.|||||||.||||||||.|||||||.|
|
||||
|.|||||||.|______|.|||||||.|
|
||||
|.||......|______|......||.|
|
||||
|.||.||||.|______|.||||.||.|
|
||||
|....||||.||||||||.||||....|
|
||||
|||||||..............|||||||
|
||||
|||||||.||||||||||||.|||||||
|
||||
|.......||||||||||||.......|
|
||||
|.|||.||||........||||.|||.|
|
||||
|.|||.||||.||||||.||||.|||.|
|
||||
|.......||.||||||.||.......|
|
||||
|||||||.||........||.|||||||
|
||||
|||||||.||.||||||.||.|||||||
|
||||
|....||.||.||||||.||.||....|
|
||||
|.||.||.||........||.||.||.|
|
||||
|.||....||.||||||.||....||.|
|
||||
|.||.||.||.||||||.||.||.||.|
|
||||
|....||..............||....|
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
|.........||....||.........|
|
||||
|.||.||||.||.||.||.||||.||.|
|
||||
|.||.||||....||....||||.||.|
|
||||
|.||...||.||||||||.||...||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.||||.||.||....||.||.||||.|
|
||||
|.........||.||.||.........|
|
||||
||||.||||.||.||.||.||||.||||
|
||||
||||.|__|.||.||.||.|__|.||||
|
||||
..||.||||.||.||.||.||||.||..
|
||||
|.||....................||.|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||.||||.|______|.||||.||.|
|
||||
|.........|______|.........|
|
||||
|||||||||.|______|.|||||||||
|
||||
|||||||||.||||||||.|||||||||
|
||||
..||....................||..
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|....||...||.||.||...||....|
|
||||
|||||||||.||.||.||.|||||||||
|
||||
|||||||||.||.||.||.|||||||||
|
||||
|.........||....||.........|
|
||||
|.|||||.||||.||.||||.|||||.|
|
||||
|.|___|.||||.||.||||.|___|.|
|
||||
|.|___|......||......|___|.|
|
||||
|.|___|.||||||||||||.|___|.|
|
||||
|.|||||.||||||||||||.|||||.|
|
||||
|..........................|
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
||.......||||||||||.......||
|
||||
||.||.||.||......||.||.||.||
|
||||
||.||.||.||.||||.||.||.||.||
|
||||
||.||.||.||.||||.||.||.||.||
|
||||
||....||............||....||
|
||||
|||||.||.||||||||||.||.|||||
|
||||
|||||.||.||||||||||.||.|||||
|
||||
||....||............||....||
|
||||
||.||.|||||.||||.|||||.||.||
|
||||
||.||.|||||.||||.|||||.||.||
|
||||
||.||..................||.||
|
||||
||.||.|||.||||||||.|||.||.||
|
||||
||.||.|||.||||||||.|||.||.||
|
||||
||.||.....||||||||.....||.||
|
||||
||.||.|||.||||||||.|||.||.||
|
||||
||.||.|||.||||||||.|||.||.||
|
||||
||.||..................||.||
|
||||
||.||.||||||.||.||||||.||.||
|
||||
||....||||||.||.||||||....||
|
||||
|||||.||.....||.....||.|||||
|
||||
|||||.||.||||||||||.||.|||||
|
||||
||....||.||||||||||.||....||
|
||||
||.||.||.||......||.||.||.||
|
||||
||.||....||.||||.||....||.||
|
||||
||.||.||.||.||||.||.||.||.||
|
||||
||.||.||............||.||.||
|
||||
||.||.|||||.||||.|||||.||.||
|
||||
||.||.|||||.||||.|||||.||.||
|
||||
||........................||
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
||||||||||||||||||||||||||||
|
||||
|.......||........||.......|
|
||||
|.||.||.||.||||||.||.||.||.|
|
||||
|.||.||.||.||||||.||.||.||.|
|
||||
|.||.||.||........||.||.||.|
|
||||
|.||.||.||.||||||.||.||.||.|
|
||||
|.||.||.||.||||||.||.||.||.|
|
||||
|.......||...||...||.......|
|
||||
|.||||||||||.||.||||||||||.|
|
||||
|.||||||||||.||.||||||||||.|
|
||||
|..........................|
|
||||
|||||.|||.||||||||.|||.|||||
|
||||
|||||.|||.||||||||.|||.|||||
|
||||
|.........||||||||.........|
|
||||
|.|||||||.||||||||.|||||||.|
|
||||
|.|||||||.||||||||.|||||||.|
|
||||
|.||....................||.|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|....||.||||.||.||||.||....|
|
||||
|.||.||.||...||...||.||.||.|
|
||||
|.||.||.||.||||||.||.||.||.|
|
||||
|.||.||.||.||||||.||.||.||.|
|
||||
|.......||........||.......|
|
||||
||||.|||||.||||||.|||||.||||
|
||||
||||.|||||.||||||.|||||.||||
|
||||
||...........||...........||
|
||||
||.|||||||||.||.|||||||||.||
|
||||
||.|||||||||.||.|||||||||.||
|
||||
||...........||...........||
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
|.........||....||.........|
|
||||
|.||||.||.||.||.||.||.||||.|
|
||||
|.||||.||....||....||.||||.|
|
||||
|......||.||||||||.||......|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.||||.......||.......||||.|
|
||||
|...|||||.||.||.||.|||||...|
|
||||
|||.|||||.||....||.|||||.|||
|
||||
|||.||....||||||||....||.|||
|
||||
|...||.||.||||||||.||.||...|
|
||||
|.||||.||..........||.||||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.........||||||||.........|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.||...||.||||||||.||...||.|
|
||||
|.||.||||..........||||.||.|
|
||||
|.||.||||.||.||.||.||||.||.|
|
||||
|.........||.||.||.........|
|
||||
||||.||||.||.||.||.||||.||||
|
||||
||||.||||.||.||.||.||||.||||
|
||||
|.........||.||.||.........|
|
||||
|.|||||.||||....||||.|||||.|
|
||||
|.|||||.||||.||.||||.|||||.|
|
||||
|............||............|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|.||.||..............||.||.|
|
||||
|....||||||||||||||||||....|
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
|............||............|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|.||.||..............||.||.|
|
||||
|....||||.||||||||.||||....|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||...||..........||...||.|
|
||||
|.||||.||.||.||.||.||.||||.|
|
||||
|.||||.||.||.||.||.||.||||.|
|
||||
|......||.||.||.||.||......|
|
||||
|||.||.||..........||.||.|||
|
||||
|||.||.||.||||||||.||.||.|||
|
||||
|...||.||.||||||||.||.||...|
|
||||
|.||||....||||||||....||||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|...||.||.||||||||.||.||...|
|
||||
|||.||.||..........||.||.|||
|
||||
|||.||.||.||||||||.||.||.|||
|
||||
|......||.||||||||.||......|
|
||||
|.|||||||..........|||||||.|
|
||||
|.|||||||.||||||||.|||||||.|
|
||||
|.........||||||||.........|
|
||||
|||||||.||||....||||.|||||||
|
||||
|||||||.||||.||.||||.|||||||
|
||||
||...........||...........||
|
||||
||.||||.||.||||||.||.||||.||
|
||||
||.||||.||.||||||.||.||||.||
|
||||
||......||........||......||
|
||||
||||||||||||||||||||||||||||
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
|..........................|
|
||||
|.|||.|||.||||||||.|||.|||.|
|
||||
|.|||.|||.||||||||.|||.|||.|
|
||||
|..........................|
|
||||
|.|||||.||||||||||||.|||||.|
|
||||
|.|||||.||||||||||||.|||||.|
|
||||
|....||..............||....|
|
||||
|.||.||||.||.||.||.||||.||.|
|
||||
|.||.||||.||.||.||.||||.||.|
|
||||
|.||...||.||.||.||.||...||.|
|
||||
|.||||.||..........||.||||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.........||||||||.........|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||...||.||||||||.||...||.|
|
||||
|.||||.||..........||.||||.|
|
||||
|.||||.||.||.||.||.||.||||.|
|
||||
|......||.||.||.||.||......|
|
||||
|.||||.||.||.||.||.||.||||.|
|
||||
|.||||....||.||.||....||||.|
|
||||
|...|||||.||.||.||.|||||...|
|
||||
|||.|||||.||....||.|||||.|||
|
||||
|||.||....||||||||....||.|||
|
||||
|...||.||.||||||||.||.||...|
|
||||
|.||||.||....||....||.||||.|
|
||||
|.||||.||.||.||.||.||.||||.|
|
||||
|.........||.||.||.........|
|
||||
||||||||||||....||||||||||||
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
||||||||||||||||||||||||||||
|
||||
|....||......||......||....|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|.||.||.||........||.||.||.|
|
||||
|.......||.||||||.||.......|
|
||||
|.||.|||||.||||||.|||||.||.|
|
||||
|.||.|||||...||...|||||.||.|
|
||||
|.||....||||.||.||||....||.|
|
||||
|.||.||.||||.||.||||.||.||.|
|
||||
|.||.||..............||.||.|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.........||||||||.........|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||....................||.|
|
||||
|.||||.||||.||||.||||.||||.|
|
||||
|.||||.||||.||||.||||.||||.|
|
||||
|........||......||........|
|
||||
|||.||||.||.||||.||.||||.|||
|
||||
|||.||||.||.||||.||.||||.|||
|
||||
|.....||............||.....|
|
||||
|.|||.|||||.||||.|||||.|||.|
|
||||
|.|||.|||||.||||.|||||.|||.|
|
||||
|..........................|
|
||||
|.|||||||||.||||.|||||||||.|
|
||||
|.|||||||||.||||.|||||||||.|
|
||||
|..........................|
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
||||||||||||||||||||||||||||
|
||||
||||||||||||||||||||||||||||
|
||||
|..........................|
|
||||
|.||||.||||||||||||||.||||.|
|
||||
|.||||.||||||||||||||.||||.|
|
||||
|...||.......||.......||...|
|
||||
|||.|||||.||.||.||.|||||.|||
|
||||
|||.|||||.||.||.||.|||||.|||
|
||||
|.........||....||.........|
|
||||
|.|||.|||.||||||||.|||.|||.|
|
||||
|.|||.|||.||||||||.|||.|||.|
|
||||
|..........................|
|
||||
|||||.|||.||||||||.|||.|||||
|
||||
|||||.|||.||||||||.|||.|||||
|
||||
||........||||||||........||
|
||||
||.||.|||.||||||||.|||.||.||
|
||||
||.||.|||.||||||||.|||.||.||
|
||||
||.||..................||.||
|
||||
||.||||||.||||||||.||||||.||
|
||||
||.||||||.||||||||.||||||.||
|
||||
||........................||
|
||||
||.||||||.||||||||.||||||.||
|
||||
||.||||||.||||||||.||||||.||
|
||||
||...||..............||...||
|
||||
||||.||.||||.||.||||.||.||||
|
||||
||||.||.||||.||.||||.||.||||
|
||||
|.......||...||...||.......|
|
||||
|.||||||||.||||||.||||||||.|
|
||||
|.||||||||.||||||.||||||||.|
|
||||
|..........................|
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#/bin/bash
|
||||
|
||||
clingo --rand-prob --seed=$RANDOM rules.lp | python draw.py
|
||||
@@ -0,0 +1,168 @@
|
||||
#const width=28.
|
||||
#const height=31.
|
||||
#const homeleft=11.
|
||||
#const homeright=18.
|
||||
#const hometop=13.
|
||||
#const homebottom=17.
|
||||
#const pacy=24.
|
||||
#const pacx1=13.
|
||||
#const pacx2=16.
|
||||
|
||||
xdim(1..width).
|
||||
ydim(1..height).
|
||||
|
||||
step(1,0;;0,1;;-1,0;;0,-1).
|
||||
diag(-1,-1;;1,1;;1,-1;;-1,1).
|
||||
surround(DX,DY) :- step(DX,DY).
|
||||
surround(DX,DY) :- diag(DX,DY).
|
||||
|
||||
% choose arbitrary walls
|
||||
% make sure there are less than 520 wall tiles
|
||||
% (maps should have around 310 walkable tiles)
|
||||
{ wall(X,Y) :xdim(X) :ydim(Y) } 520.
|
||||
|
||||
% choose no more than 40 blanks
|
||||
{ blank(X,Y) :xdim(X) :ydim(Y) } 40.
|
||||
|
||||
% prevent skinny blanks
|
||||
:- blank(X,Y), wall(X+1,Y), wall(X-1,Y).
|
||||
:- blank(X,Y), wall(X,Y+1), wall(X,Y-1).
|
||||
|
||||
% prevent blanks from touching paths
|
||||
:- blank(X,Y), path(X+DX,Y+DY), surround(DX,DY).
|
||||
|
||||
% prevent walls from touching more than one blank by step
|
||||
%:- wall(X,Y), 2 { blank(X+DX,Y+DY) :step(DX,DY) } 2.
|
||||
:- wall(X,Y), blank(X+1,Y), blank(X,Y+1), blank(X+1,Y+1).
|
||||
:- blank(X,Y), wall(X+1,Y), blank(X,Y+1), blank(X+1,Y+1).
|
||||
:- blank(X,Y), blank(X+1,Y), wall(X,Y+1), blank(X+1,Y+1).
|
||||
:- blank(X,Y), blank(X+1,Y), blank(X,Y+1), wall(X+1,Y+1).
|
||||
|
||||
% prevent padding blanks with more than one wall
|
||||
:- blank(X,Y), wall(X+1,Y), wall(X+2,Y).
|
||||
:- wall(X,Y), wall(X+1,Y), blank(X+2,Y).
|
||||
:- blank(X,Y), wall(X,Y+1), wall(X,Y+2).
|
||||
:- wall(X,Y), wall(X,Y+1), blank(X,Y+2).
|
||||
|
||||
% prevent a blank block from being adjacent to the edge of the map
|
||||
:- blank(X,Y), wall(X+1,Y), not xdim(X+2).
|
||||
:- blank(X,Y), wall(X-1,Y), not xdim(X-2).
|
||||
:- blank(X,Y), wall(X,Y+1), not ydim(Y+2).
|
||||
:- blank(X,Y), wall(X,Y-1), not ydim(Y-2).
|
||||
|
||||
% define paths as tiles that are not walls or blanks
|
||||
path(X,Y) :- not wall(X,Y), not blank(X,Y), xdim(X), ydim(Y).
|
||||
|
||||
% prevent paths on top and bottom
|
||||
:- path(X,1), xdim(X).
|
||||
:- path(X,height), xdim(X).
|
||||
|
||||
% create 0 to 2 tunnels
|
||||
{ path(1,Y) :ydim(Y) } 2.
|
||||
:- path(1,Y), path(1,Y-1).
|
||||
:- path(1,Y), path(1,Y+1).
|
||||
|
||||
% define ghost home
|
||||
wall(homeleft..homeright,hometop).
|
||||
wall(homeleft..homeright,homebottom).
|
||||
wall(homeleft,hometop..homebottom).
|
||||
wall(homeright,hometop..homebottom).
|
||||
blank(homeleft+1..homeright-1,hometop+1..homebottom-1).
|
||||
path(homeleft-1,hometop-1..homebottom+1).
|
||||
path(homeright+1,hometop-1..homebottom+1).
|
||||
path(homeleft-1..homeright+1,hometop-1).
|
||||
path(homeleft-1..homeright+1,homebottom+1).
|
||||
|
||||
% define pacman start
|
||||
path(pacx1..pacx2,pacy).
|
||||
|
||||
% ensure all paths are reachable
|
||||
reachable(pacx1,pacy).
|
||||
reachable(X,Y) :- path(X,Y), reachable(X+DX,Y+DY), step(DX,DY).
|
||||
:- path(X,Y), not reachable(X,Y).
|
||||
|
||||
% prevent fat paths:
|
||||
:- path(X,Y), path(X,Y+1), path(X+1,Y), path(X+1,Y+1).
|
||||
|
||||
% prevent this
|
||||
% |.
|
||||
% .|
|
||||
% and
|
||||
% .|
|
||||
% |.
|
||||
:- wall(X,Y), wall(X+1,Y+1), path(X+1,Y), path(X,Y+1).
|
||||
:- path(X,Y), path(X+1,Y+1), wall(X+1,Y), wall(X,Y+1).
|
||||
|
||||
% prevent thin walls
|
||||
:- wall(X,Y), path(X+1,Y), path(X-1,Y).
|
||||
:- wall(X,Y), path(X,Y+1), path(X,Y-1).
|
||||
|
||||
% prevent this
|
||||
% |.
|
||||
% .|
|
||||
:- wall(X,Y), wall(X,Y+1), path(X+1,Y), path(X-1,Y+1).
|
||||
:- wall(X,Y), wall(X,Y+1), path(X-1,Y), path(X+1,Y+1).
|
||||
% .
|
||||
% ||
|
||||
% .
|
||||
:- wall(X,Y), wall(X+1,Y), path(X,Y-1), path(X+1,Y+1).
|
||||
:- wall(X,Y), wall(X+1,Y), path(X+1,Y-1), path(X,Y+1).
|
||||
|
||||
% prevent 2x2 wall island
|
||||
:- path(X,Y), path(X+1,Y), path(X+2,Y), path(X+3,Y),
|
||||
path(X,Y+1), path(X+3,Y+1),
|
||||
path(X,Y+2), path(X+3,Y+2),
|
||||
path(X,Y+3), path(X+1,Y+3), path(X+2,Y+3), path(X+3,Y+3).
|
||||
|
||||
% prevent 3x2 wall island
|
||||
:- path(X,Y), path(X+1,Y), path(X+2,Y), path(X+3,Y),
|
||||
path(X,Y+1), path(X+3,Y+1),
|
||||
path(X,Y+2), path(X+3,Y+2),
|
||||
path(X,Y+3), path(X+3,Y+3),
|
||||
path(X,Y+4), path(X+1,Y+4), path(X+2,Y+4), path(X+3,Y+4).
|
||||
|
||||
% prevent 3x2 wall island
|
||||
:- path(X,Y), path(X,Y+1), path(X,Y+2), path(X,Y+3),
|
||||
path(X+1,Y), path(X+1,Y+3),
|
||||
path(X+2,Y), path(X+2,Y+3),
|
||||
path(X+3,Y), path(X+3,Y+3),
|
||||
path(X+4,Y), path(X+4,Y+1), path(X+4,Y+2), path(X+4,Y+3).
|
||||
|
||||
% TODO: limit number of contiguous pieces (~20)
|
||||
% TODO: limit size of a contiguous piece
|
||||
|
||||
% prevent 3x3 wall
|
||||
:- wall(X,Y), wall(X+1,Y), wall(X+2,Y),
|
||||
wall(X,Y+1), wall(X+1,Y+1), wall(X+2,Y+1),
|
||||
wall(X,Y+2), wall(X+1,Y+2), wall(X+2,Y+2).
|
||||
|
||||
% prevent this:
|
||||
% .| |.
|
||||
% ||| |||
|
||||
% |. .|
|
||||
:- path(X,Y), wall(X+1,Y), wall(X,Y+1), wall(X+1,Y+1), wall(X+2,Y+1), wall(X+1,Y+2), path(X+2,Y+2).
|
||||
:- wall(X+1,Y), path(X+2,Y), wall(X,Y+1), wall(X+1,Y+1), wall(X+2,Y+1), path(X,Y+2), wall(X+1,Y+2).
|
||||
|
||||
% prevent dead end paths
|
||||
:- path(X,Y), path(X,Y+1), wall(X-1,Y), wall(X+1,Y), wall(X,Y-1).
|
||||
:- path(X,Y), path(X,Y-1), wall(X-1,Y), wall(X+1,Y), wall(X,Y+1).
|
||||
:- path(X,Y), path(X-1,Y), wall(X,Y+1), wall(X,Y-1), wall(X+1,Y).
|
||||
:- path(X,Y), path(X+1,Y), wall(X,Y+1), wall(X,Y-1), wall(X-1,Y).
|
||||
:- path(X,Y), wall(X+1,Y), wall(X,Y+1), wall(X,Y-1), wall(X-1,Y).
|
||||
|
||||
% prevent tight corners
|
||||
:- path(X,Y), path(X+1,Y), path(X+1,Y+1), path(X+2,Y+1).
|
||||
:- path(X,Y), path(X-1,Y), path(X-1,Y+1), path(X-2,Y+1).
|
||||
:- path(X,Y), path(X,Y+1), path(X+1,Y+1), path(X+1,Y+2).
|
||||
:- path(X,Y), path(X,Y+1), path(X-1,Y+1), path(X-1,Y+2).
|
||||
|
||||
% prevent horizontal asymmetry
|
||||
:- wall(X,Y), not wall(width-X+1,Y).
|
||||
:- blank(X,Y), not blank(width-X+1,Y).
|
||||
|
||||
% prevent thick outer wall
|
||||
:- wall(2,Y), path(3,Y), ydim(Y).
|
||||
:- wall(width-1,Y), path(width-2,Y), ydim(Y).
|
||||
:- wall(X,2), path(X,3), xdim(X).
|
||||
:- wall(X,height-1), path(X,height-2), xdim(X).
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#const width=28.
|
||||
#const height=31.
|
||||
#const homeleft=11.
|
||||
#const homeright=18.
|
||||
#const hometop=13.
|
||||
#const homebottom=17.
|
||||
#const pacy=24.
|
||||
#const pacx1=13.
|
||||
#const pacx2=16.
|
||||
|
||||
xdim(1..width).
|
||||
ydim(1..height).
|
||||
|
||||
step(-1;1,0;;0,-1;1).
|
||||
diag(-1;1,-1;1).
|
||||
surround(DX,DY) :- step(DX,DY).
|
||||
surround(DX,DY) :- diag(DX,DY).
|
||||
|
||||
% choose arbitrary walls
|
||||
% make sure there are less than 520 wall tiles
|
||||
% (maps should have around 310 walkable tiles)
|
||||
{ wall(X,Y) :xdim(X) :ydim(Y) } 520.
|
||||
|
||||
% choose no more than 40 blanks
|
||||
{ blank(X,Y) :xdim(X) :ydim(Y) } 40.
|
||||
|
||||
% prevent long walls
|
||||
:- wall(X+DX,Y), DX:=1..8, Y>1, Y<height.
|
||||
:- wall(X,Y+DY), DY:=1..8, X>1, X<width.
|
||||
|
||||
% prevent blanks from touching paths
|
||||
:- blank(X,Y), path(X+DX,Y+DY), surround(DX,DY).
|
||||
|
||||
% prevent walls from touching more than one blank
|
||||
:- wall(X,Y), 2 { blank(X+DX,Y+DY) :step(DX,DY) } 2.
|
||||
|
||||
% prevent padding blanks with more than one wall
|
||||
:- blank(X,Y), wall(X+DX,Y+DY), wall(X+2*DX,Y+2*DY), step(DX,DY).
|
||||
|
||||
% prevent a blank block from being adjacent to the edge of the map
|
||||
:- blank(X,Y), wall(X+DX,Y+DY), not xdim(X+2*DX), not ydim(Y+2*DY), step(DX,DY).
|
||||
%:- blank(X,Y), wall(X+1,Y), not xdim(X+2).
|
||||
%:- blank(X,Y), wall(X-1,Y), not xdim(X-2).
|
||||
%:- blank(X,Y), wall(X,Y+1), not ydim(Y+2).
|
||||
%:- blank(X,Y), wall(X,Y-1), not ydim(Y-2).
|
||||
|
||||
% define paths as tiles that are not walls or blanks
|
||||
path(X,Y) :- not wall(X,Y), not blank(X,Y), xdim(X), ydim(Y).
|
||||
|
||||
% prevent paths on top and bottom
|
||||
:- path(X,1), xdim(X).
|
||||
:- path(X,height), xdim(X).
|
||||
|
||||
% create 0 to 2 tunnels
|
||||
{ path(1,Y) :ydim(Y) } 2.
|
||||
:- path(1,Y), path(1,Y-1).
|
||||
:- path(1,Y), path(1,Y+1).
|
||||
|
||||
% define ghost home
|
||||
wall(homeleft..homeright,hometop;homebottom).
|
||||
wall(homeleft;homeright,hometop..homebottom).
|
||||
blank(homeleft+1..homeright-1,hometop+1..homebottom-1).
|
||||
path(homeleft-1;homeright+1,hometop-1..homebottom+1).
|
||||
path(homeleft-1..homeright+1,hometop-1;homebottom+1).
|
||||
|
||||
% define pacman start
|
||||
path(pacx1..pacx2,pacy).
|
||||
|
||||
% ensure all paths are reachable
|
||||
reachable(pacx1,pacy).
|
||||
reachable(X,Y) :- path(X,Y), reachable(X+DX,Y+DY), step(DX,DY).
|
||||
:- path(X,Y), not reachable(X,Y).
|
||||
|
||||
% prevent fat paths:
|
||||
:- path(X,Y), path(X,Y+1), path(X+1,Y), path(X+1,Y+1).
|
||||
|
||||
% prevent this
|
||||
% |.
|
||||
% .|
|
||||
% and
|
||||
% .|
|
||||
% |.
|
||||
:- wall(X,Y), wall(X+1,Y+1), path(X+1,Y), path(X,Y+1).
|
||||
:- path(X,Y), path(X+1,Y+1), wall(X+1,Y), wall(X,Y+1).
|
||||
|
||||
% prevent thin walls
|
||||
:- wall(X,Y), path(X+1,Y), path(X-1,Y).
|
||||
:- wall(X,Y), path(X,Y+1), path(X,Y-1).
|
||||
|
||||
% prevent this
|
||||
% |.
|
||||
% .|
|
||||
:- wall(X,Y), wall(X,Y+1), path(X+1,Y), path(X-1,Y+1).
|
||||
:- wall(X,Y), wall(X,Y+1), path(X-1,Y), path(X+1,Y+1).
|
||||
% .
|
||||
% ||
|
||||
% .
|
||||
:- wall(X,Y), wall(X+1,Y), path(X,Y-1), path(X+1,Y+1).
|
||||
:- wall(X,Y), wall(X+1,Y), path(X+1,Y-1), path(X,Y+1).
|
||||
|
||||
% prevent 2x2 wall island
|
||||
:- path(X,Y), path(X+1,Y), path(X+2,Y), path(X+3,Y),
|
||||
path(X,Y+1), path(X+3,Y+1),
|
||||
path(X,Y+2), path(X+3,Y+2),
|
||||
path(X,Y+3), path(X+1,Y+3), path(X+2,Y+3), path(X+3,Y+3).
|
||||
|
||||
% TODO: limit number of contiguous pieces (~20)
|
||||
% TODO: limit size of a contiguous piece
|
||||
|
||||
% prevent 3x3 wall
|
||||
:- wall(X,Y), 9 { wall(X+DX,Y+DY) :DX=0..2 :DY=0..2 } 9.
|
||||
|
||||
% prevent this:
|
||||
% .| |.
|
||||
% ||| |||
|
||||
% |. .|
|
||||
:- path(X,Y), wall(X+1,Y), wall(X,Y+1), wall(X+1,Y+1), wall(X+2,Y+1), wall(X+1,Y+2), path(X+2,Y+2).
|
||||
:- wall(X+1,Y), path(X+2,Y), wall(X,Y+1), wall(X+1,Y+1), wall(X+2,Y+1), path(X,Y+2), wall(X+1,Y+2).
|
||||
|
||||
% prevent dead-ends
|
||||
:- path(X,Y), 3 { wall(X+DX,Y+DY) :step(DX,DY) } 4.
|
||||
|
||||
% prevent tight corners
|
||||
:- path(X,Y), path(X+1,Y), path(X+1,Y+1), path(X+2,Y+1).
|
||||
:- path(X,Y), path(X-1,Y), path(X-1,Y+1), path(X-2,Y+1).
|
||||
:- path(X,Y), path(X,Y+1), path(X+1,Y+1), path(X+1,Y+2).
|
||||
:- path(X,Y), path(X,Y+1), path(X-1,Y+1), path(X-1,Y+2).
|
||||
|
||||
% prevent horizontal asymmetry
|
||||
:- wall(X,Y), not wall(width-X+1,Y).
|
||||
:- blank(X,Y), not blank(width-X+1,Y).
|
||||
|
||||
% prevent thick outer wall
|
||||
:- wall(2,Y), path(3,Y), ydim(Y).
|
||||
:- wall(width-1,Y), path(width-2,Y), ydim(Y).
|
||||
:- wall(X,2), path(X,3), xdim(X).
|
||||
:- wall(X,height-1), path(X,height-2), xdim(X).
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
// direction enums (in clockwise order)
|
||||
var DIR_UP = 0;
|
||||
var DIR_RIGHT = 1;
|
||||
var DIR_DOWN = 2;
|
||||
var DIR_LEFT = 3;
|
||||
|
||||
// get direction enum from a direction vector
|
||||
var getEnumFromDir = function(dir) {
|
||||
if (dir.x==-1) return DIR_LEFT;
|
||||
if (dir.x==1) return DIR_RIGHT;
|
||||
if (dir.y==-1) return DIR_UP;
|
||||
if (dir.y==1) return DIR_DOWN;
|
||||
};
|
||||
|
||||
// set direction vector from a direction enum
|
||||
var setDirFromEnum = function(dir,dirEnum) {
|
||||
if (dirEnum == DIR_UP) { dir.x = 0; dir.y =-1; }
|
||||
else if (dirEnum == DIR_RIGHT) { dir.x =1; dir.y = 0; }
|
||||
else if (dirEnum == DIR_DOWN) { dir.x = 0; dir.y = 1; }
|
||||
else if (dirEnum == DIR_LEFT) { dir.x = -1; dir.y = 0; }
|
||||
};
|
||||
|
||||
// size of a square tile in pixels
|
||||
var tileSize = 8;
|
||||
|
||||
// constructor
|
||||
var Map = function(numCols, numRows, tiles) {
|
||||
|
||||
// sizes
|
||||
this.numCols = numCols;
|
||||
this.numRows = numRows;
|
||||
this.numTiles = numCols*numRows;
|
||||
this.widthPixels = numCols*tileSize;
|
||||
this.heightPixels = numRows*tileSize;
|
||||
|
||||
// ascii map
|
||||
this.tiles = tiles;
|
||||
|
||||
this.resetCurrent();
|
||||
this.parseWalls();
|
||||
};
|
||||
|
||||
// reset current tiles
|
||||
Map.prototype.resetCurrent = function() {
|
||||
this.currentTiles = this.tiles.split(""); // create a mutable list copy of an immutable string
|
||||
};
|
||||
|
||||
// This is a procedural way to generate original-looking maps from a simple ascii tile
|
||||
// map without a spritesheet.
|
||||
Map.prototype.parseWalls = function() {
|
||||
|
||||
var that = this;
|
||||
|
||||
// creates a list of drawable canvas paths to render the map walls
|
||||
this.paths = [];
|
||||
|
||||
// a map of wall tiles that already belong to a built path
|
||||
var visited = {};
|
||||
|
||||
// we extend the x range to suggest the continuation of the tunnels
|
||||
var toIndex = function(x,y) {
|
||||
if (x>=-2 && x<that.numCols+2 && y>=0 && y<that.numRows)
|
||||
return (x+2)+y*(that.numCols+4);
|
||||
};
|
||||
|
||||
// a map of which wall tiles that are not completely surrounded by other wall tiles
|
||||
var edges = {};
|
||||
var i=0,x,y;
|
||||
for (y=0;y<this.numRows;y++) {
|
||||
for (x=-2;x<this.numCols+2;x++,i++) {
|
||||
if (this.getTile(x,y) == '|' &&
|
||||
(this.getTile(x-1,y) != '|' ||
|
||||
this.getTile(x+1,y) != '|' ||
|
||||
this.getTile(x,y-1) != '|' ||
|
||||
this.getTile(x,y+1) != '|' ||
|
||||
this.getTile(x-1,y-1) != '|' ||
|
||||
this.getTile(x-1,y+1) != '|' ||
|
||||
this.getTile(x+1,y-1) != '|' ||
|
||||
this.getTile(x+1,y+1) != '|')) {
|
||||
edges[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// walks along edge wall tiles starting at the given index to build a canvas path
|
||||
var makePath = function(tx,ty) {
|
||||
|
||||
// get initial direction
|
||||
var dir = {};
|
||||
var dirEnum;
|
||||
if (toIndex(tx+1,ty) in edges)
|
||||
dirEnum = DIR_RIGHT;
|
||||
else if (toIndex(tx, ty+1) in edges)
|
||||
dirEnum = DIR_DOWN;
|
||||
else
|
||||
throw "tile shouldn't be 1x1 at "+tx+","+ty;
|
||||
setDirFromEnum(dir,dirEnum);
|
||||
|
||||
// increment to next tile
|
||||
tx += dir.x;
|
||||
ty += dir.y;
|
||||
|
||||
// backup initial location and direction
|
||||
var init_tx = tx;
|
||||
var init_ty = ty;
|
||||
var init_dirEnum = dirEnum;
|
||||
|
||||
var path = [];
|
||||
var pad; // (persists for each call to getStartPoint)
|
||||
var point;
|
||||
var lastPoint;
|
||||
|
||||
var turn,turnAround;
|
||||
|
||||
/*
|
||||
|
||||
We employ the 'right-hand rule' by keeping our right hand in contact
|
||||
with the wall to outline an individual wall piece.
|
||||
|
||||
Since we parse the tiles in row major order, we will always start
|
||||
walking along the wall at the leftmost tile of its topmost row. We
|
||||
then proceed walking to the right.
|
||||
|
||||
When facing the direction of the walk at each tile, the outline will
|
||||
hug the left side of the tile unless there is a walkable tile to the
|
||||
left. In that case, there will be a padding distance applied.
|
||||
|
||||
*/
|
||||
var getStartPoint = function(tx,ty,dirEnum) {
|
||||
var dir = {};
|
||||
setDirFromEnum(dir, dirEnum);
|
||||
if (!(toIndex(tx+dir.y,ty-dir.x) in edges))
|
||||
pad = that.isFloorTile(tx+dir.y,ty-dir.x) ? 5 : 0;
|
||||
var px = -tileSize/2+pad;
|
||||
var py = tileSize/2;
|
||||
var a = dirEnum*Math.PI/2;
|
||||
var c = Math.cos(a);
|
||||
var s = Math.sin(a);
|
||||
return {
|
||||
// the first expression is the rotated point centered at origin
|
||||
// the second expression is to translate it to the tile
|
||||
x:(px*c - py*s) + (tx+0.5)*tileSize,
|
||||
y:(px*s + py*c) + (ty+0.5)*tileSize,
|
||||
};
|
||||
};
|
||||
while (true) {
|
||||
|
||||
visited[toIndex(tx,ty)] = true;
|
||||
|
||||
// determine start point
|
||||
point = getStartPoint(tx,ty,dirEnum);
|
||||
|
||||
if (turn) {
|
||||
// if we're turning into this tile, create a control point for the curve
|
||||
//
|
||||
// >---+ <- control point
|
||||
// |
|
||||
// V
|
||||
lastPoint = path[path.length-1];
|
||||
if (dir.x == 0) {
|
||||
point.cx = point.x;
|
||||
point.cy = lastPoint.y;
|
||||
}
|
||||
else {
|
||||
point.cx = lastPoint.x;
|
||||
point.cy = point.y;
|
||||
}
|
||||
}
|
||||
|
||||
// update direction
|
||||
turn = false;
|
||||
turnAround = false;
|
||||
if (toIndex(tx+dir.y, ty-dir.x) in edges) { // turn left
|
||||
dirEnum = (dirEnum+3)%4;
|
||||
turn = true;
|
||||
}
|
||||
else if (toIndex(tx+dir.x, ty+dir.y) in edges) { // continue straight
|
||||
}
|
||||
else if (toIndex(tx-dir.y, ty+dir.x) in edges) { // turn right
|
||||
dirEnum = (dirEnum+1)%4;
|
||||
turn = true;
|
||||
}
|
||||
else { // turn around
|
||||
dirEnum = (dirEnum+2)%4;
|
||||
turnAround = true;
|
||||
}
|
||||
setDirFromEnum(dir,dirEnum);
|
||||
|
||||
// commit path point
|
||||
path.push(point);
|
||||
|
||||
// special case for turning around (have to connect more dots manually)
|
||||
if (turnAround) {
|
||||
path.push(getStartPoint(tx-dir.x, ty-dir.y, (dirEnum+2)%4));
|
||||
path.push(getStartPoint(tx, ty, dirEnum));
|
||||
}
|
||||
|
||||
// advance to the next wall
|
||||
tx += dir.x;
|
||||
ty += dir.y;
|
||||
|
||||
// exit at full cycle
|
||||
if (tx==init_tx && ty==init_ty && dirEnum == init_dirEnum) {
|
||||
that.paths.push(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// iterate through all edges, making a new path after hitting an unvisited wall edge
|
||||
i=0;
|
||||
for (y=0;y<this.numRows;y++)
|
||||
for (x=-2;x<this.numCols+2;x++,i++)
|
||||
if (i in edges && !(i in visited)) {
|
||||
visited[i] = true;
|
||||
makePath(x,y);
|
||||
}
|
||||
};
|
||||
|
||||
Map.prototype.posToIndex = function(x,y) {
|
||||
if (x>=0 && x<this.numCols && y>=0 && y<this.numRows)
|
||||
return x+y*this.numCols;
|
||||
};
|
||||
// retrieves tile character at given coordinate
|
||||
// extended to include offscreen tunnel space
|
||||
Map.prototype.getTile = function(x,y) {
|
||||
if (x>=0 && x<this.numCols && y>=0 && y<this.numRows)
|
||||
return this.currentTiles[this.posToIndex(x,y)];
|
||||
|
||||
// extend walls and paths outward for entrances and exits
|
||||
if ((x==-1 && this.getTile(x+1,y)=='|' && (this.isFloorTile(x+1,y+1)||this.isFloorTile(x+1,y-1))) ||
|
||||
(x==this.numCols && this.getTile(x-1,y)=='|' && (this.isFloorTile(x-1,y+1)||this.isFloorTile(x-1,y-1))))
|
||||
return '|';
|
||||
if ((x==-1 && this.isFloorTile(x+1,y)) ||
|
||||
(x==this.numCols && this.isFloorTile(x-1,y)))
|
||||
return ' ';
|
||||
};
|
||||
|
||||
// determines if the given character is a walkable floor tile
|
||||
Map.prototype.isFloorTileChar = function(tile) {
|
||||
return tile==' ' || tile=='.' || tile=='o';
|
||||
};
|
||||
|
||||
// determines if the given tile coordinate has a walkable floor tile
|
||||
Map.prototype.isFloorTile = function(x,y) {
|
||||
return this.isFloorTileChar(this.getTile(x,y));
|
||||
};
|
||||
|
||||
// function to draw the map as a tile map
|
||||
Map.prototype.draw = function(ctx,left,top,print) {
|
||||
|
||||
// save state
|
||||
ctx.save();
|
||||
ctx.translate(0.5,0.5); // pixel perfect lines?
|
||||
|
||||
// translate to the position of the map
|
||||
ctx.translate(left,top);
|
||||
|
||||
// clip the drawing surface
|
||||
ctx.beginPath();
|
||||
ctx.rect(0,0,this.widthPixels, this.heightPixels);
|
||||
ctx.clip();
|
||||
|
||||
// set colors
|
||||
ctx.fillStyle = print?"#333":this.wallFillColor;
|
||||
ctx.strokeStyle = print?"#333":this.wallStrokeColor;
|
||||
|
||||
var x,y;
|
||||
var i,j;
|
||||
var tile;
|
||||
|
||||
for (i=0; i<this.paths.length; i++) {
|
||||
var path = this.paths[i];
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(path[0].x, path[0].y);
|
||||
for (j=1; j<path.length; j++) {
|
||||
if (path[j].cx != undefined)
|
||||
ctx.quadraticCurveTo(path[j].cx, path[j].cy, path[j].x, path[j].y);
|
||||
else
|
||||
ctx.lineTo(path[j].x, path[j].y);
|
||||
}
|
||||
ctx.quadraticCurveTo(path[j-1].x, path[0].y, path[0].x, path[0].y);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// draw pellets for each path tile
|
||||
ctx.fillStyle=print?"#bbb":this.pelletColor;
|
||||
var pelletSize = print?tileSize:2;
|
||||
for (y=0; y<this.numRows; y++) {
|
||||
for (x=0; x<this.numCols; x++) {
|
||||
if (this.isFloorTile(x,y)) {
|
||||
ctx.fillRect(
|
||||
x*tileSize+tileSize/2-pelletSize/2,
|
||||
y*tileSize+tileSize/2-pelletSize/2,
|
||||
pelletSize,pelletSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw grid
|
||||
ctx.strokeStyle=print?"rgba(0,0,0,0.3)":"rgba(255,255,255,0.3)";
|
||||
ctx.beginPath();
|
||||
for (y=0; y<=this.numRows; y++) {
|
||||
ctx.moveTo(0,y*tileSize);
|
||||
ctx.lineTo(this.widthPixels,y*tileSize);
|
||||
}
|
||||
for (x=0; x<=this.numCols; x++) {
|
||||
ctx.moveTo(x*tileSize,0);
|
||||
ctx.lineTo(x*tileSize,this.heightPixels);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// draw title
|
||||
if (this.name) {
|
||||
ctx.fillStyle = print?"#000":"#fff";
|
||||
ctx.fillText(this.name, 0,tileSize/2);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
};
|
||||
|
||||
// function to draw the map using simple representation of the paths as straight lines
|
||||
Map.prototype.drawPath = function(ctx,left,top) {
|
||||
var print = true;
|
||||
|
||||
// save state
|
||||
ctx.save();
|
||||
ctx.translate(0.5,0.5); // pixel perfect lines?
|
||||
|
||||
// translate to the position of the map
|
||||
ctx.translate(left,top);
|
||||
|
||||
// clip the drawing surface
|
||||
ctx.beginPath();
|
||||
ctx.rect(0,0,this.widthPixels, this.heightPixels);
|
||||
ctx.clip();
|
||||
|
||||
var x,y;
|
||||
var i,j;
|
||||
var tile;
|
||||
|
||||
// draw pellets for each path tile
|
||||
ctx.lineWidth = 2.0;
|
||||
ctx.strokeStyle="rgba(0,0,0,0.8)";
|
||||
ctx.beginPath();
|
||||
for (y=0; y<this.numRows-1; y++) {
|
||||
for (x=0; x<this.numCols-1; x++) {
|
||||
if (this.isFloorTile(x,y)) {
|
||||
if (this.isFloorTile(x+1,y)) {
|
||||
ctx.moveTo(x*tileSize,y*tileSize);
|
||||
ctx.lineTo((x+1)*tileSize,y*tileSize);
|
||||
}
|
||||
if (this.isFloorTile(x,y+1)) {
|
||||
ctx.moveTo(x*tileSize,y*tileSize);
|
||||
ctx.lineTo(x*tileSize,(y+1)*tileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// draw grid
|
||||
ctx.lineWidth = 1.0;
|
||||
ctx.strokeStyle=print?"rgba(0,0,0,0.3)":"rgba(255,255,255,0.3)";
|
||||
ctx.beginPath();
|
||||
for (y=0; y<this.numRows; y++) {
|
||||
ctx.moveTo(0,y*tileSize);
|
||||
ctx.lineTo(this.widthPixels-tileSize,y*tileSize);
|
||||
}
|
||||
for (x=0; x<this.numCols; x++) {
|
||||
ctx.moveTo(x*tileSize,0);
|
||||
ctx.lineTo(x*tileSize,this.heightPixels-tileSize);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// draw title
|
||||
if (this.name) {
|
||||
ctx.fillStyle = print?"#000":"#fff";
|
||||
ctx.fillText(this.name, 0,tileSize/2);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
This was an attempt to illustrate why conventional maze generation routines
|
||||
couldn't be used to generate Pac-Man maps, but in the process I've actually
|
||||
found a lead that may suggest otherwise.
|
||||
-->
|
||||
<head>
|
||||
<script src="Map.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
|
||||
<script>
|
||||
|
||||
window.onload = function() {
|
||||
|
||||
var x,y;
|
||||
var cols=4,rows=4;
|
||||
var size = 10; // size of a tile in this spanning tree example
|
||||
var scale = 4;
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
canvas.width = (cols*4*size+3*size+size)*scale;
|
||||
canvas.height = (rows*size+size)*scale;
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.scale(scale,scale);
|
||||
ctx.translate(0.5,0.5);
|
||||
|
||||
var drawGrid = function() {
|
||||
ctx.beginPath();
|
||||
for (y=0; y<=rows; y++) {
|
||||
ctx.moveTo(0,y*size);
|
||||
ctx.lineTo(cols*size,y*size);
|
||||
}
|
||||
for (x=0; x<=cols; x++) {
|
||||
ctx.moveTo(x*size,0);
|
||||
ctx.lineTo(x*size,rows*size);
|
||||
}
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
var drawMaze = function() {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0,0);
|
||||
ctx.lineTo(0,size);
|
||||
ctx.moveTo(0,2*size);
|
||||
ctx.lineTo(0,4*size);
|
||||
ctx.lineTo(4*size,4*size);
|
||||
ctx.lineTo(4*size,3*size);
|
||||
ctx.moveTo(4*size,2*size);
|
||||
ctx.lineTo(4*size,0);
|
||||
ctx.lineTo(0,0);
|
||||
ctx.moveTo(0,2*size);
|
||||
ctx.lineTo(size,2*size);
|
||||
ctx.lineTo(size,size);
|
||||
ctx.lineTo(size,3*size);
|
||||
ctx.lineTo(size*2,3*size);
|
||||
ctx.moveTo(size*2,2*size);
|
||||
ctx.lineTo(size*2,size);
|
||||
ctx.lineTo(size*3,size);
|
||||
ctx.lineTo(size*3,0);
|
||||
ctx.moveTo(size*4,size*2);
|
||||
ctx.lineTo(size*3,size*2);
|
||||
ctx.lineTo(size*3,size*3);
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
|
||||
ctx.strokeStyle="rgba(0,0,0,0.2)";
|
||||
drawGrid();
|
||||
|
||||
ctx.translate((cols+1)*size,0);
|
||||
drawGrid();
|
||||
|
||||
ctx.strokeStyle="#000";
|
||||
drawMaze();
|
||||
|
||||
ctx.translate((cols+1)*size,0);
|
||||
drawMaze();
|
||||
|
||||
ctx.translate((cols+1)*size,0);
|
||||
|
||||
var map = new Map(12,12,
|
||||
"||||||||||||" +
|
||||
"|.......||.|" +
|
||||
"|.||.|||||.|" +
|
||||
"|.||.|||||.|" +
|
||||
"..||.||....|" +
|
||||
"||||.||.||||" +
|
||||
"||||.||.||||" +
|
||||
"|.||....||.." +
|
||||
"|.|||||.||.|" +
|
||||
"|.|||||.||.|" +
|
||||
"|..........|" +
|
||||
"||||||||||||");
|
||||
|
||||
var s = 1/(map.numRows*tileSize/(size*rows));
|
||||
ctx.scale(s,s);
|
||||
|
||||
|
||||
map.draw(ctx,0,0,print=true);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,321 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="Map.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<form>
|
||||
<input id="chkPrint" type="checkbox" onchange="update();">
|
||||
<label for="chkPrint">printer friendly</label>
|
||||
</form>
|
||||
|
||||
<canvas id="canvas"></canvas>
|
||||
|
||||
<script>
|
||||
|
||||
var update = function() {
|
||||
draw(scale=3.0,print=document.getElementById('chkPrint').checked);
|
||||
};
|
||||
|
||||
window.onload = function() {
|
||||
update();
|
||||
};
|
||||
|
||||
var draw = function(scale,print) {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
canvas.width=(28*tileSize*3+tileSize*2)*scale;
|
||||
canvas.height=(36*tileSize*2+tileSize)*scale;
|
||||
ctx.scale(scale,scale);
|
||||
|
||||
ctx.fillStyle = print?"#fff":"#000";
|
||||
ctx.fillRect(0,0,canvas.width, canvas.height);
|
||||
|
||||
ctx.font = "bold " + 2*tileSize + "px sans-serif";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.textAlign = "left";
|
||||
|
||||
var mapPacman = new Map(28, 36, (
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"|............||............|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|o|__|.|___|.||.|___|.|__|o|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|..........................|" +
|
||||
"|.||||.||.||||||||.||.||||.|" +
|
||||
"|.||||.||.||||||||.||.||||.|" +
|
||||
"|......||....||....||......|" +
|
||||
"||||||.||||| || |||||.||||||" +
|
||||
"_____|.||||| || |||||.|_____" +
|
||||
"_____|.|| ||.|_____" +
|
||||
"_____|.|| |||--||| ||.|_____" +
|
||||
"||||||.|| |______| ||.||||||" +
|
||||
" . |______| . " +
|
||||
"||||||.|| |______| ||.||||||" +
|
||||
"_____|.|| |||||||| ||.|_____" +
|
||||
"_____|.|| ||.|_____" +
|
||||
"_____|.|| |||||||| ||.|_____" +
|
||||
"||||||.|| |||||||| ||.||||||" +
|
||||
"|............||............|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|o..||....... .......||..o|" +
|
||||
"|||.||.||.||||||||.||.||.|||" +
|
||||
"|||.||.||.||||||||.||.||.|||" +
|
||||
"|......||....||....||......|" +
|
||||
"|.||||||||||.||.||||||||||.|" +
|
||||
"|.||||||||||.||.||||||||||.|" +
|
||||
"|..........................|" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"____________________________" +
|
||||
"____________________________"));
|
||||
|
||||
mapPacman.name = "Pac-Man";
|
||||
mapPacman.wallStrokeColor = "#47b897"; // from Pac-Man Plus (easier to see)
|
||||
//mapPacman.wallStrokeColor = "#2121ff"; // from original
|
||||
mapPacman.wallFillColor = "#000";
|
||||
mapPacman.pelletColor = "#ffb8ae";
|
||||
|
||||
// Ms. Pac-Man map 1
|
||||
|
||||
var mapMsPacman1 = new Map(28, 36, (
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"|......||..........||......|" +
|
||||
"|o||||.||.||||||||.||.||||o|" +
|
||||
"|.||||.||.||||||||.||.||||.|" +
|
||||
"|..........................|" +
|
||||
"|||.||.|||||.||.|||||.||.|||" +
|
||||
"__|.||.|___|.||.|___|.||.|__" +
|
||||
"|||.||.|||||.||.|||||.||.|||" +
|
||||
" .||.......||.......||. " +
|
||||
"|||.||||| |||||||| |||||.|||" +
|
||||
"__|.||||| |||||||| |||||.|__" +
|
||||
"__|. .|__" +
|
||||
"__|.||||| |||--||| |||||.|__" +
|
||||
"__|.||||| |______| |||||.|__" +
|
||||
"__|.|| |______| ||.|__" +
|
||||
"__|.|| || |______| || ||.|__" +
|
||||
"|||.|| || |||||||| || ||.|||" +
|
||||
" . || || . " +
|
||||
"|||.|||||||| || ||||||||.|||" +
|
||||
"__|.|||||||| || ||||||||.|__" +
|
||||
"__|....... || .......|__" +
|
||||
"__|.|||||.||||||||.|||||.|__" +
|
||||
"|||.|||||.||||||||.|||||.|||" +
|
||||
"|............ ............|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|.|__|.|||||.||.|||||.|__|.|" +
|
||||
"|.|__|.||....||....||.|__|.|" +
|
||||
"|o|__|.||.||||||||.||.|__|o|" +
|
||||
"|.||||.||.||||||||.||.||||.|" +
|
||||
"|..........................|" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"____________________________" +
|
||||
"____________________________"));
|
||||
|
||||
mapMsPacman1.name = "Ms. Pac-Man (1)";
|
||||
mapMsPacman1.wallFillColor = "#FFB8AE";
|
||||
mapMsPacman1.wallStrokeColor = "#FF0000";
|
||||
mapMsPacman1.pelletColor = "#dedeff";
|
||||
|
||||
// Ms. Pac-Man map 2
|
||||
|
||||
var mapMsPacman2 = new Map(28, 36, (
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
" ||..........|| " +
|
||||
"|||||| ||.||||||||.|| ||||||" +
|
||||
"|||||| ||.||||||||.|| ||||||" +
|
||||
"|o...........||...........o|" +
|
||||
"|.|||||||.||.||.||.|||||||.|" +
|
||||
"|.|||||||.||.||.||.|||||||.|" +
|
||||
"|.||......||.||.||......||.|" +
|
||||
"|.||.|||| ||....|| ||||.||.|" +
|
||||
"|.||.|||| |||||||| ||||.||.|" +
|
||||
"|......|| |||||||| ||......|" +
|
||||
"||||||.|| ||.||||||" +
|
||||
"||||||.|| |||--||| ||.||||||" +
|
||||
"|......|| |______| ||......|" +
|
||||
"|.||||.|| |______| ||.||||.|" +
|
||||
"|.||||. |______| .||||.|" +
|
||||
"|...||.|| |||||||| ||.||...|" +
|
||||
"|||.||.|| ||.||.|||" +
|
||||
"__|.||.|||| |||| ||||.||.|__" +
|
||||
"__|.||.|||| |||| ||||.||.|__" +
|
||||
"__|.........||||.........|__" +
|
||||
"__|.|||||||.||||.|||||||.|__" +
|
||||
"|||.|||||||.||||.|||||||.|||" +
|
||||
" ....||... ...||.... " +
|
||||
"|||.||.||.||||||||.||.||.|||" +
|
||||
"|||.||.||.||||||||.||.||.|||" +
|
||||
"|o..||.......||.......||..o|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|..........................|" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"____________________________" +
|
||||
"____________________________"));
|
||||
|
||||
mapMsPacman2.name = "Ms. Pac-Man (2)";
|
||||
mapMsPacman2.wallFillColor = "#47b8ff";
|
||||
mapMsPacman2.wallStrokeColor = "#dedeff";
|
||||
mapMsPacman2.pelletColor = "#ffff00";
|
||||
|
||||
// Ms. Pac-Man map 3
|
||||
|
||||
var mapMsPacman3 = new Map(28, 36, (
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"|.........||....||.........|" +
|
||||
"|o|||||||.||.||.||.|||||||o|" +
|
||||
"|.|||||||.||.||.||.|||||||.|" +
|
||||
"|.||.........||.........||.|" +
|
||||
"|.||.||.||||.||.||||.||.||.|" +
|
||||
"|....||.||||.||.||||.||....|" +
|
||||
"||||.||.||||.||.||||.||.||||" +
|
||||
"||||.||..............||.||||" +
|
||||
" ....|||| |||||||| ||||.... " +
|
||||
"|.|| |||| |||||||| |||| ||.|" +
|
||||
"|.|| ||.|" +
|
||||
"|.|||| || |||--||| || ||||.|" +
|
||||
"|.|||| || |______| || ||||.|" +
|
||||
"|. || |______| || .|" +
|
||||
"|.|| |||| |______| |||| ||.|" +
|
||||
"|.|| |||| |||||||| |||| ||.|" +
|
||||
"|.|| ||.|" +
|
||||
"|.|||| ||||| || ||||| ||||.|" +
|
||||
"|.|||| ||||| || ||||| ||||.|" +
|
||||
"|......||....||....||......|" +
|
||||
"|||.||.||.||||||||.||.||.|||" +
|
||||
"|||.||.||.||||||||.||.||.|||" +
|
||||
"|o..||....... .......||..o|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|.||||.|||||.||.|||||.||||.|" +
|
||||
"|......||....||....||......|" +
|
||||
"|.||||.||.||||||||.||.||||.|" +
|
||||
"|.||||.||.||||||||.||.||||.|" +
|
||||
"|......||..........||......|" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"____________________________" +
|
||||
"____________________________"));
|
||||
|
||||
mapMsPacman3.name = "Ms. Pac-Man (3)";
|
||||
mapMsPacman3.wallFillColor = "#de9751";
|
||||
mapMsPacman3.wallStrokeColor = "#dedeff";
|
||||
mapMsPacman3.pelletColor = "#ff0000";
|
||||
|
||||
// Ms. Pac-Man map 4
|
||||
|
||||
var mapMsPacman4 = new Map(28, 36, (
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"|..........................|" +
|
||||
"|.||.||||.||||||||.||||.||.|" +
|
||||
"|o||.||||.||||||||.||||.||o|" +
|
||||
"|.||.||||.||....||.||||.||.|" +
|
||||
"|.||......||.||.||......||.|" +
|
||||
"|.||||.||.||.||.||.||.||||.|" +
|
||||
"|.||||.||.||.||.||.||.||||.|" +
|
||||
"|......||....||....||......|" +
|
||||
"|||.|||||||| || ||||||||.|||" +
|
||||
"__|.|||||||| || ||||||||.|__" +
|
||||
"__|....|| ||....|__" +
|
||||
"||| ||.|| |||--||| ||.|| |||" +
|
||||
" ||.|| |______| ||.|| " +
|
||||
"||||||. |______| .||||||" +
|
||||
"||||||.|| |______| ||.||||||" +
|
||||
" ||.|| |||||||| ||.|| " +
|
||||
"||| ||.|| ||.|| |||" +
|
||||
"__|....||||| || |||||....|__" +
|
||||
"__|.||.||||| || |||||.||.|__" +
|
||||
"__|.||.... || ....||.|__" +
|
||||
"__|.|||||.|| || ||.|||||.|__" +
|
||||
"|||.|||||.|| || ||.|||||.|||" +
|
||||
"|.........|| ||.........|" +
|
||||
"|.||||.||.||||||||.||.||||.|" +
|
||||
"|.||||.||.||||||||.||.||||.|" +
|
||||
"|.||...||..........||...||.|" +
|
||||
"|o||.|||||||.||.|||||||.||o|" +
|
||||
"|.||.|||||||.||.|||||||.||.|" +
|
||||
"|............||............|" +
|
||||
"||||||||||||||||||||||||||||" +
|
||||
"____________________________" +
|
||||
"____________________________"));
|
||||
|
||||
mapMsPacman4.name = "Ms. Pac-Man (4)";
|
||||
mapMsPacman4.wallFillColor = "#2121ff";
|
||||
mapMsPacman4.wallStrokeColor = "#ffb851";
|
||||
mapMsPacman4.pelletColor = "#dedeff";
|
||||
|
||||
// Iwatani's Prototype
|
||||
var mapSketch = new Map(28, 36, (
|
||||
"____________________________" +
|
||||
"____________________________" +
|
||||
"______||||||||||||||||______" +
|
||||
"______|..............|______" +
|
||||
"______|.||.||||||.||.|______" +
|
||||
"______|.||.||||||.||.|______" +
|
||||
"______|.||...||...||.|______" +
|
||||
"______|.||||.||.||||.|______" +
|
||||
"|||||||.||||.||.||||.|||||||" +
|
||||
"........||........||........" +
|
||||
"|||.||||||.||||||.||||||.|||" +
|
||||
"|||.||||||.||||||.||||||.|||" +
|
||||
"|.......||...||...||.......|" +
|
||||
"|.|||||.||||.||.||||.|||||.|" +
|
||||
"|.|||||.||||.||.||||.|||||.|" +
|
||||
"|....||..............||....|" +
|
||||
"||||.||||.|||--|||.||||.||||" +
|
||||
"||||.||||.|______|.||||.||||" +
|
||||
"..||......|______|......||.." +
|
||||
"|.||.||||.|______|.||||.||.|" +
|
||||
"|....||||.||||||||.||||....|" +
|
||||
"|.||.||..............||.||.|" +
|
||||
"|.||.||.||||||||||||.||.||.|" +
|
||||
"|.||.||.||||||||||||.||.||.|" +
|
||||
"..||.||......||......||.||.." +
|
||||
"||||.|||||||.||.|||||||.||||" +
|
||||
"||||.|||||||.||.|||||||.||||" +
|
||||
"|..........................|" +
|
||||
"|.||.||||||||||||||||||.||.|" +
|
||||
"|.||.|________________|.||.|" +
|
||||
"|.||.|________________|.||.|" +
|
||||
"|.||.|________________|.||.|" +
|
||||
"|....|________________|....|" +
|
||||
"||||||________________||||||" +
|
||||
"____________________________" +
|
||||
"____________________________"));
|
||||
|
||||
mapSketch.name = "Original Prototype";
|
||||
mapSketch.wallFillColor = "#555";
|
||||
mapSketch.wallStrokeColor = "#fff";
|
||||
mapSketch.pelletColor = "#dedeff";
|
||||
|
||||
var w = mapPacman.widthPixels+tileSize;
|
||||
var h = mapPacman.heightPixels+tileSize;
|
||||
|
||||
// change these functions to 'draw' or 'drawPath' to change map representations
|
||||
// when using 'drawPath', the 'print' checkbox must be selected for now to prevent blackout.
|
||||
mapSketch.drawPath(ctx,0,0,print);
|
||||
mapPacman.drawPath(ctx,w,0,print);
|
||||
mapMsPacman1.drawPath(ctx,2*w,0,print);
|
||||
mapMsPacman2.drawPath(ctx,0,h,print);
|
||||
mapMsPacman3.drawPath(ctx,w,h,print);
|
||||
mapMsPacman4.drawPath(ctx,2*w,h,print);
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
We represent the maps using ASCII characters representing three possible tiles: wall(|), path(.), and blank(_).
|
||||
|
||||
- A **wall** (|) tile is the boundary of a wall.
|
||||
- A **void** (_) tile is the interior or exterior of a wall.
|
||||
- A **path** (.) tile is a walkable area.
|
||||
|
||||
ORIGINAL PROTOTYPE PAC-MAN MS. PAC-MAN (1)
|
||||
____________________________ ____________________________ ____________________________
|
||||
____________________________ ____________________________ ____________________________
|
||||
______||||||||||||||||______ ____________________________ ____________________________
|
||||
______|..............|______ |||||||||||||||||||||||||||| ||||||||||||||||||||||||||||
|
||||
______|.||.||||||.||.|______ |............||............| |......||..........||......|
|
||||
______|.||.||||||.||.|______ |.||||.|||||.||.|||||.||||.| |.||||.||.||||||||.||.||||.|
|
||||
______|.||...||...||.|______ |.|__|.|___|.||.|___|.|__|.| |.||||.||.||||||||.||.||||.|
|
||||
______|.||||.||.||||.|______ |.||||.|||||.||.|||||.||||.| |..........................|
|
||||
|||||||.||||.||.||||.||||||| |..........................| |||.||.|||||.||.|||||.||.|||
|
||||
|.......||........||.......| |.||||.||.||||||||.||.||||.| __|.||.|___|.||.|___|.||.|__
|
||||
|||.||||||.||||||.||||||.||| |.||||.||.||||||||.||.||||.| |||.||.|||||.||.|||||.||.|||
|
||||
|||.||||||.||||||.||||||.||| |......||....||....||......| ....||.......||.......||....
|
||||
|.......||...||...||.......| ||||||.|||||.||.|||||.|||||| |||.|||||.||||||||.|||||.|||
|
||||
|.|||||.||||.||.||||.|||||.| _____|.|||||.||.|||||.|_____ __|.|||||.||||||||.|||||.|__
|
||||
|.|||||.||||.||.||||.|||||.| _____|.||..........||.|_____ __|......................|__
|
||||
|....||..............||....| _____|.||.|||--|||.||.|_____ __|.|||||.|||--|||.|||||.|__
|
||||
||||.||||.|||--|||.||||.|||| ||||||.||.|______|.||.|||||| __|.|||||.|______|.|||||.|__
|
||||
||||.||||.|______|.||||.|||| ..........|______|.......... __|.||....|______|....||.|__
|
||||
..||......|______|......||.. ||||||.||.|______|.||.|||||| __|.||.||.|______|.||.||.|__
|
||||
|.||.||||.|______|.||||.||.| _____|.||.||||||||.||.|_____ |||.||.||.||||||||.||.||.|||
|
||||
|....||||.||||||||.||||....| _____|.||..........||.|_____ .......||..........||.......
|
||||
|.||.||..............||.||.| _____|.||.||||||||.||.|_____ |||.||||||||.||.||||||||.|||
|
||||
|.||.||.||||||||||||.||.||.| ||||||.||.||||||||.||.|||||| __|.||||||||.||.||||||||.|__
|
||||
|.||.||.||||||||||||.||.||.| |............||............| __|..........||..........|__
|
||||
..||.||......||......||.||.. |.||||.|||||.||.|||||.||||.| __|.|||||.||||||||.|||||.|__
|
||||
||||.|||||||.||.|||||||.|||| |.||||.|||||.||.|||||.||||.| |||.|||||.||||||||.|||||.|||
|
||||
||||.|||||||.||.|||||||.|||| |...||................||...| |..........................|
|
||||
|..........................| |||.||.||.||||||||.||.||.||| |.||||.|||||.||.|||||.||||.|
|
||||
|.||.||||||||||||||||||.||.| |||.||.||.||||||||.||.||.||| |.|__|.|||||.||.|||||.|__|.|
|
||||
|.||.|________________|.||.| |......||....||....||......| |.|__|.||....||....||.|__|.|
|
||||
|.||.|________________|.||.| |.||||||||||.||.||||||||||.| |.|__|.||.||||||||.||.|__|.|
|
||||
|.||.|________________|.||.| |.||||||||||.||.||||||||||.| |.||||.||.||||||||.||.||||.|
|
||||
|....|________________|....| |..........................| |..........................|
|
||||
||||||________________|||||| |||||||||||||||||||||||||||| ||||||||||||||||||||||||||||
|
||||
____________________________ ____________________________ ____________________________
|
||||
____________________________ ____________________________ ____________________________
|
||||
|
||||
|
||||
MS. PAC-MAN (2) MS. PAC-MAN (3) MS. PAC-MAN (4)
|
||||
____________________________ ____________________________ ____________________________
|
||||
____________________________ ____________________________ ____________________________
|
||||
____________________________ ____________________________ ____________________________
|
||||
|||||||||||||||||||||||||||| |||||||||||||||||||||||||||| ||||||||||||||||||||||||||||
|
||||
.......||..........||....... |.........||....||.........| |..........................|
|
||||
||||||.||.||||||||.||.|||||| |.|||||||.||.||.||.|||||||.| |.||.||||.||||||||.||||.||.|
|
||||
||||||.||.||||||||.||.|||||| |.|||||||.||.||.||.|||||||.| |.||.||||.||||||||.||||.||.|
|
||||
|............||............| |.||.........||.........||.| |.||.||||.||....||.||||.||.|
|
||||
|.|||||||.||.||.||.|||||||.| |.||.||.||||.||.||||.||.||.| |.||......||.||.||......||.|
|
||||
|.|||||||.||.||.||.|||||||.| |....||.||||.||.||||.||....| |.||||.||.||.||.||.||.||||.|
|
||||
|.||......||.||.||......||.| ||||.||.||||.||.||||.||.|||| |.||||.||.||.||.||.||.||||.|
|
||||
|.||.||||.||....||.||||.||.| ||||.||..............||.|||| |......||....||....||......|
|
||||
|.||.||||.||||||||.||||.||.| .....||||.||||||||.||||..... |||.||||||||.||.||||||||.|||
|
||||
|......||.||||||||.||......| |.||.||||.||||||||.||||.||.| __|.||||||||.||.||||||||.|__
|
||||
||||||.||..........||.|||||| |.||....................||.| __|....||..........||....|__
|
||||
||||||.||.|||--|||.||.|||||| |.||||.||.|||--|||.||.||||.| |||.||.||.|||--|||.||.||.|||
|
||||
|......||.|______|.||......| |.||||.||.|______|.||.||||.| ....||.||.|______|.||.||....
|
||||
|.||||.||.|______|.||.||||.| |......||.|______|.||......| ||||||....|______|....||||||
|
||||
|.||||....|______|....||||.| |.||.||||.|______|.||||.||.| ||||||.||.|______|.||.||||||
|
||||
|...||.||.||||||||.||.||...| |.||.||||.||||||||.||||.||.| ....||.||.||||||||.||.||....
|
||||
|||.||.||..........||.||.||| |.||....................||.| |||.||.||..........||.||.|||
|
||||
__|.||.||||.||||.||||.||.|__ |.||||.|||||.||.|||||.||||.| __|....|||||.||.|||||....|__
|
||||
__|.||.||||.|__|.||||.||.|__ |.||||.|||||.||.|||||.||||.| __|.||.|||||.||.|||||.||.|__
|
||||
__|.........|__|.........|__ |......||....||....||......| __|.||.......||.......||.|__
|
||||
__|.|||||||.|__|.|||||||.|__ |||.||.||.||||||||.||.||.||| __|.|||||.||.||.||.|||||.|__
|
||||
|||.|||||||.||||.|||||||.||| |||.||.||.||||||||.||.||.||| |||.|||||.||.||.||.|||||.|||
|
||||
.......||..........||....... |...||................||...| |.........||....||.........|
|
||||
|||.||.||.||||||||.||.||.||| |.||||.|||||.||.|||||.||||.| |.||||.||.||||||||.||.||||.|
|
||||
|||.||.||.||||||||.||.||.||| |.||||.|||||.||.|||||.||||.| |.||||.||.||||||||.||.||||.|
|
||||
|...||.......||.......||...| |......||....||....||......| |.||...||..........||...||.|
|
||||
|.||||.|||||.||.|||||.||||.| |.||||.||.||||||||.||.||||.| |.||.|||||||.||.|||||||.||.|
|
||||
|.||||.|||||.||.|||||.||||.| |.||||.||.||||||||.||.||||.| |.||.|||||||.||.|||||||.||.|
|
||||
|..........................| |......||..........||......| |............||............|
|
||||
|||||||||||||||||||||||||||| |||||||||||||||||||||||||||| ||||||||||||||||||||||||||||
|
||||
____________________________ ____________________________ ____________________________
|
||||
____________________________ ____________________________ ____________________________
|
||||
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
@@ -0,0 +1,194 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Pac-Man Maze Generation</title>
|
||||
<script src="tetris/colors.js"></script>
|
||||
<script src="tetris/mapgen.js"></script>
|
||||
<script src="tetris/Map.js"></script>
|
||||
<script>
|
||||
|
||||
var draw = function() {
|
||||
var size = 40;
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
canvas.width = 1024;
|
||||
canvas.height = 1024;
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = "#FFF";
|
||||
ctx.fillRect(0,0,canvas.width,canvas.height);
|
||||
|
||||
var map = mapgen();
|
||||
var x,y;
|
||||
|
||||
x = size;
|
||||
y = size;
|
||||
drawCells(ctx,x,y,size,"Simple Model",{'drawNumbers':true});
|
||||
|
||||
x += (cols+1)*size;
|
||||
drawCells(ctx,x,y,size,"Height Adjustments",{'drawRaiseHeightCandidate':true, 'drawRaiseHeight':true});
|
||||
|
||||
x += (cols+1)*size;
|
||||
drawCells(ctx,x,y,size,"Width Adjustments",{'drawShrinkWidthCandidate':true, 'drawShrinkWidth':true});
|
||||
|
||||
x += (cols+1)*size;
|
||||
drawCells(ctx,x,y,size,"Border Cells and Tunnels",
|
||||
{'drawJoinCandidate':true,
|
||||
'drawSingleDeadEnd':true,
|
||||
'drawDoubleDeadEnd':true,
|
||||
'drawVoidTunnel':true,
|
||||
'drawEdgeTunnel':true,
|
||||
});
|
||||
|
||||
x = size;
|
||||
y += (rows+1)*size;
|
||||
ctx.save();
|
||||
ctx.translate(x,y);
|
||||
ctx.scale(2,2);
|
||||
map.name = "Final Paths";
|
||||
map.drawPath(ctx,0,0);
|
||||
ctx.restore();
|
||||
|
||||
x = 13*size;
|
||||
ctx.save();
|
||||
ctx.translate(x,y);
|
||||
ctx.scale(2,2);
|
||||
map.name = "Final Tiles";
|
||||
map.draw(ctx,0,0,true);
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
window.onload = function() {
|
||||
draw();
|
||||
};
|
||||
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background:#AAA;
|
||||
}
|
||||
p, li {
|
||||
line-height:1.5em;
|
||||
}
|
||||
#container {
|
||||
padding: 20px;
|
||||
background:#FFF;
|
||||
width: 800px;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<p>
|
||||
<h1>Pac-Man Maze Generation</h1>
|
||||
<p>
|
||||
by <a href="http://twitter.com/shaunlebron">Shaun LeBron</a>.
|
||||
(<a href="https://github.com/shaunlebron/pacman-mazegen">code on GitHub</a>)
|
||||
</p>
|
||||
|
||||
<button onclick="draw()">Click to generate new example</button>
|
||||
</p>
|
||||
<canvas id='canvas'></canvas>
|
||||
|
||||
<p>
|
||||
|
||||
<h2><span style="color:red">Work in Progress</span></h2>
|
||||
<p>
|
||||
<i>
|
||||
Generating random Pac-Man mazes is a deceptively difficult problem that I spent some months working on. It is not easy to describe clearly. I hope you are patient. This page is an effort to begin communicating how the algorithm works. It will slowly be refined (your feedback appreciated) until it is all stated as clearly as possible.
|
||||
</i>
|
||||
</p>
|
||||
|
||||
<h2>Contraints</h2>
|
||||
<p>
|
||||
The mazes are built carefully to closely match design patterns deduced from the original maps found in Pac-Man and Ms. Pac-Man:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Map is 28x31 tiles.</li>
|
||||
<li>Paths are only 1 tile thick</li>
|
||||
<li>No sharp turns (i.e. intersections are separated by atleast 2 tiles).</li>
|
||||
<li>There are 1 or 2 tunnels</li>
|
||||
<li>No dead-ends.</li>
|
||||
<li>Only I, L, T, or + wall shapes are allowed, including the occasional rectangular wall.</li>
|
||||
<li>Any non-rectangular wall pieces must only be 2 tiles thick.</li>
|
||||
</ul>
|
||||
|
||||
<h2>It's like Tetris</h2>
|
||||
|
||||
<p>
|
||||
We start by stacking tetris pieces on a 5x9 grid. Gravity pulls the pieces in the left direction rather than down. The edges of the resulting tetris pieces correspond to walkable paths in the maze. This grid is then mirrored across the left vertical axis to create a symmetric map, then scaled by 3 to form an original-size 28x31 map.
|
||||
</p>
|
||||
|
||||
<h2>Definitions</h2>
|
||||
|
||||
<p>
|
||||
For clarity, I call the squares in the initial 5x9 grid, <b>cells</b>, and the squares in the final 28x31 grid, <b>tiles</b>. So, this algorithm first creates the <b>cells</b> and transforms them into <b>tiles</b>
|
||||
</p>
|
||||
|
||||
<h2>Simple Model</h2>
|
||||
|
||||
<p>
|
||||
Shown in the above diagram titled "Simple Model" is the 5x9 grid of tetris pieces. The pieces are created one cell at a time using some algorithm to limit the type of pieces at certain locations (they are numbered to show the order of creation).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The ghost pen and the edge between rows 7 and 8 at column 1 are present in every map, since the starting location of Pac-Man and the ghost pen location are constant.
|
||||
</p>
|
||||
|
||||
<h2>Height and Width Adjustments</h2>
|
||||
|
||||
<p>
|
||||
Cells are directly transformed into a 3x3 group of tiles. Unfortunately, this creates a resulting map that is too short by 1 tile and too wide by 1 tile. So, we increase the height of one cell for every column, and decrease the width of one cell for every row, allowing the generated map to fit in the exact dimensions of the original game.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Shown in the above diagrams titled "Height Adjustments" and "Width Adjustments", the highlighted cells are the candidate cells whose size can be changed without creating ugly walls (i.e. walls that have non-uniform thickness).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Arrows occupy cells which have been chosen for size adjustment. Care is taken to prevent discontinuities in the edges as a result of the shifting of cells from the size change.
|
||||
</p>
|
||||
|
||||
<h2>Border Cells and Tunnels</h2>
|
||||
|
||||
<p>
|
||||
I won't explain too much about this right now. But the above diagram titled "Border Cells and Tunnels" has arrows to indicate the tunnel candidates. The highlighted cells show the type of tunnel candidates by color. Some cell edges are erased to create some variation in how walls connect with the boundary of the map (shown in green). The tunnel creation algorithm is sophisticated in how it chooses different types of tunnels.
|
||||
</p>
|
||||
|
||||
<h2>Final Paths</h2>
|
||||
|
||||
<p>
|
||||
When the cells are finally transformed into tiles, what you are left with is shown in the diagram above titled "Final Paths". Here you can directly map a cell to a 3x3 group of tiles. You can even pick out the cells whose height are width have been adjusted by 1 tile in this map.
|
||||
</p>
|
||||
|
||||
<h2>Final Tiles</h2>
|
||||
|
||||
<p>
|
||||
See how the above diagram titled "Final Tiles" differs from "Final Paths". The paths are shifted from the tile <em>edges</em> toward the tile <em>centers</em>. Each tile with a path going through its center is turned into a path tile. Finally, any tile that touches a path tile becomes a wall tile. The map structure is now complete.
|
||||
</p>
|
||||
|
||||
<h2>Results</h2>
|
||||
|
||||
<p>
|
||||
<a href="tetris/many.htm">Click here to see many generated Pac-Man mazes together.</a>
|
||||
</p>
|
||||
|
||||
<h2>Appendix</h2>
|
||||
|
||||
<h3>Original Maps</h3>
|
||||
<img src="img/origmaps_2x.png" width="100%" />
|
||||
|
||||
<h3>Original Maps (plain)</h3>
|
||||
<img src="img/origmaps_2x_print.png" width="100%" />
|
||||
|
||||
<h3>Original Maps (paths)</h3>
|
||||
<img src="img/origmaps_path.png" width="100%" />
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/python
|
||||
import unittest
|
||||
from walls import *
|
||||
|
||||
class MapTest(unittest.TestCase):
|
||||
|
||||
def assertMap(self,expected):
|
||||
actual = str(self.tileMap).splitlines()
|
||||
expected = format_map_str(expected,"\n").splitlines()
|
||||
self.assertEqual(actual,expected, "\n".join(repr(a)+' '+repr(b) for a,b in zip(actual,expected)))
|
||||
|
||||
def test1(self):
|
||||
self.tileMap = Map(9,9,"""
|
||||
.........
|
||||
..||.....
|
||||
..||.....
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
""")
|
||||
self.tileMap.add_wall_obstacle(1,4)
|
||||
self.assertMap("""
|
||||
.........
|
||||
..||.....
|
||||
..||.....
|
||||
.........
|
||||
..||.....
|
||||
..||.....
|
||||
..||.....
|
||||
.........
|
||||
.........
|
||||
""")
|
||||
|
||||
def test2(self):
|
||||
self.tileMap = Map(9,9,"""
|
||||
.........
|
||||
..||.....
|
||||
..||.....
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
""")
|
||||
self.tileMap.add_wall_obstacle(5,1)
|
||||
self.assertMap("""
|
||||
.........
|
||||
..||.....
|
||||
..||.|||.
|
||||
.....|||.
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
.........
|
||||
""")
|
||||
|
||||
def test3(self):
|
||||
self.tileMap = Map(11,7,"""
|
||||
...........
|
||||
...||......
|
||||
...||......
|
||||
...........
|
||||
..||.......
|
||||
..||.......
|
||||
...........
|
||||
""")
|
||||
self.tileMap.add_wall_obstacle(7,2)
|
||||
self.assertMap("""
|
||||
...........
|
||||
...||......
|
||||
...||......
|
||||
......||||.
|
||||
..||.|||||.
|
||||
..||.||....
|
||||
...........
|
||||
""")
|
||||
|
||||
def test4(self):
|
||||
self.tileMap = Map(15,8,"""
|
||||
...............
|
||||
.....||.||.....
|
||||
.....||.||.....
|
||||
.....||....|||.
|
||||
...........|||.
|
||||
.||........|||.
|
||||
.||............
|
||||
.........||.||.
|
||||
""")
|
||||
self.tileMap.add_wall_obstacle(3,4)
|
||||
self.assertMap("""
|
||||
...............
|
||||
.....||.||.....
|
||||
.....||.||.....
|
||||
.....||....|||.
|
||||
........||.|||.
|
||||
.||.||||||.|||.
|
||||
.||.||||.......
|
||||
.........||.||.
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,431 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Running this script spits out a random Pac-Man maze
|
||||
|
||||
example with extend phase off:
|
||||
||||||||||||||||||||||||||||
|
||||
|..........................|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.||||......||||......||||.|
|
||||
|.||||.||||..||..||||.||||.|
|
||||
|......|||||.||.|||||......|
|
||||
|.||||.|||||....|||||.||||.|
|
||||
|.||||.|||||.||.|||||.||||.|
|
||||
|.||||.|||||.||.|||||.||||.|
|
||||
|.........||.||.||.........|
|
||||
|.|||.|||..........|||.|||.|
|
||||
|.|||.|||.||||||||.|||.|||.|
|
||||
|.|||.....||||||||.....|||.|
|
||||
|.|||.|||.||||||||.|||.|||.|
|
||||
|.|||.|||.||||||||.|||.|||.|
|
||||
|.........||||||||.........|
|
||||
|.||||.||..........||.||||.|
|
||||
|.||||.||||.||||.||||.||||.|
|
||||
|........||.||||.||........|
|
||||
|.|||.||.||......||.||.|||.|
|
||||
|.|||.||.....||.....||.|||.|
|
||||
|......||.||.||.||.||......|
|
||||
|.||||.||.||.||.||.||.||||.|
|
||||
|.||||................||||.|
|
||||
|......||.||||||||.||......|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|.||||.||.||||||||.||.||||.|
|
||||
|..........................|
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
example with extend phase on:
|
||||
||||||||||||||||||||||||||||
|
||||
|..........................|
|
||||
|.|||.||||.||||||.||||.|||.|
|
||||
|.|||.||||.||||||.||||.|||.|
|
||||
|.|||......||||||......|||.|
|
||||
|.||||||||........||||||||.|
|
||||
|.||||||||.||..||.||||||||.|
|
||||
|..........||..||..........|
|
||||
|.|||||||.||....||.|||||||.|
|
||||
|.|||||||.||||||||.|||||||.|
|
||||
|....||||.||||||||.||||....|
|
||||
|.||.||||..........||||.||.|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||......||||||||......||.|
|
||||
|.||..|||.||||||||.|||..||.|
|
||||
|.||..|||.||||||||.|||..||.|
|
||||
|.||.||||.||||||||.||||.||.|
|
||||
|.||.||..............||.||.|
|
||||
|.......||.||||||.||.......|
|
||||
|.||||||||.||||||.||||||||.|
|
||||
|.||||||||.||||||.||||||||.|
|
||||
|.......||.||||||.||.......|
|
||||
|.|||||.||.||||||.||.|||||.|
|
||||
|.|||||.||........||.|||||.|
|
||||
|...........||||...........|
|
||||
|.|||||.|||.||||.|||.|||||.|
|
||||
|.|||||.|||.||||.|||.|||||.|
|
||||
|.|||||.|||.||||.|||.|||||.|
|
||||
|.|||||.|||.||||.|||.|||||.|
|
||||
|..........................|
|
||||
||||||||||||||||||||||||||||
|
||||
|
||||
OVERVIEW:
|
||||
|
||||
This currently works by starting with an empty half map with a ghost
|
||||
house.
|
||||
|
||||
PLACE PHASE
|
||||
We add walls by placing 2x2 blocks in areas that allow for a one
|
||||
tile wide margin.
|
||||
|
||||
.... ....
|
||||
.... .||.
|
||||
.... > .||.
|
||||
.... ....
|
||||
|
||||
GROW PHASE
|
||||
After placing a new wall piece, a gap-filling heuristic is used to grow the piece.
|
||||
Basically, the wall is grown to fill in adjacent areas that cannot be filled by new pieces.
|
||||
|
||||
........... ........... ...........
|
||||
...||...... ...||...... ...||......
|
||||
...||...... ...||...... ...||......
|
||||
........... > ........||. > ......||||.
|
||||
..||....... ..||....||. ..||.|||||.
|
||||
..||....... ..||....... ..||.||....
|
||||
........... ........... ...........
|
||||
|
||||
(start) (new piece) (after growth)
|
||||
|
||||
EXTEND PHASE
|
||||
An additional extend phase is employed which applies contiguous blocks
|
||||
in a straight random direction, and starting again in the same direction
|
||||
but rotated 90 degrees. The turning is triggered by a certain distance
|
||||
traveled or by a dead end. Each block that is placed also undergoes
|
||||
its own grow phase to fill in gaps along the way.
|
||||
|
||||
|
||||
CURRENT PROBLEMS:
|
||||
|
||||
Walls are very fragmented. Make the pieces grow more by extending in a random direction after initial mandatory growing.
|
||||
We could alternatively do a post-process to join smaller pieces together:
|
||||
....... .......
|
||||
.||.||. > .|||||.
|
||||
.||.||. .|||||.
|
||||
....... .......
|
||||
(One solution is the extend phase mentioned above)
|
||||
|
||||
Some gaps aren't filled, need to study them some more and add appropriate test cases.
|
||||
|
||||
There is currently a path around the entire border.
|
||||
Could possibly extend some contiguous pieces to the border to fix this.
|
||||
|
||||
Seems rare, but sometimes dead ends and single tile thick walls are formed.
|
||||
It could be easier to just throw out a map if this conditions are detected.
|
||||
|
||||
Conditions:
|
||||
a 2x2 empty block => dead end
|
||||
a wall tile that is not part of a 2x2 wall block => single tile wall
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import random
|
||||
|
||||
# TODO:
|
||||
# define an Obstacle class to represent a single group of contiguous wall tiles
|
||||
# Obstacle class
|
||||
# Box Obstacle
|
||||
# Line Obstacle
|
||||
# map from tile to Obstacle
|
||||
|
||||
def all(iter):
|
||||
for e in iter:
|
||||
if not e: return False
|
||||
return True
|
||||
|
||||
def any(iter):
|
||||
for e in iter:
|
||||
if e: return True
|
||||
return False
|
||||
|
||||
|
||||
# takes multi-line map string, trims indentation, replaces newlines with given separator
|
||||
def format_map_str(tiles,sep):
|
||||
return sep.join(line.strip() for line in tiles.splitlines())
|
||||
|
||||
class Map:
|
||||
def __init__(self,w,h,tile_str=None):
|
||||
|
||||
if tile_str is None:
|
||||
# just create a clear map
|
||||
self.tiles = []
|
||||
self.w = w
|
||||
self.h = h
|
||||
for i in xrange(w*h):
|
||||
self.tiles.append('.')
|
||||
else:
|
||||
self.setMap(w,h,tile_str)
|
||||
|
||||
# sets logging verbosity (on|off)
|
||||
self.verbose = False
|
||||
|
||||
# create a map from a tile string
|
||||
def setMap(self,w,h,tile_str):
|
||||
self.w = w
|
||||
self.h = h
|
||||
self.tiles = list(format_map_str(tile_str,""))
|
||||
|
||||
# creates a string of the current map
|
||||
def __str__(self):
|
||||
s = "\n"
|
||||
i = 0
|
||||
for y in xrange(self.h):
|
||||
for x in xrange(self.w):
|
||||
s += self.tiles[i]
|
||||
i += 1
|
||||
s += "\n"
|
||||
return s
|
||||
|
||||
# converts x,y to index
|
||||
def xy_to_i(self,x,y):
|
||||
return x+y*self.w
|
||||
|
||||
# converts index to x,y
|
||||
def i_to_xy(self,i):
|
||||
return i%self.w, i/self.w
|
||||
|
||||
# validates x,y
|
||||
def xy_valid(self,x,y):
|
||||
return x >= 0 and x < self.w and y>=0 and y<self.h
|
||||
|
||||
# gets tile at x,y or returns None if invalid
|
||||
def get_tile(self,x,y):
|
||||
if not self.xy_valid(x,y):
|
||||
return None
|
||||
return self.tiles[x+y*self.w]
|
||||
|
||||
# adds a single wall tile at x,y
|
||||
def add_wall_tile(self,x,y):
|
||||
if self.xy_valid(x,y):
|
||||
self.tiles[x+y*self.w] = '|'
|
||||
|
||||
def is_wall_block_filled(self,x,y):
|
||||
return all(self.get_tile(x+dx,y+dy) == '|' for dy in range(1,3) for dx in range(1,3))
|
||||
|
||||
# adds a 2x2 block inside the 4x4 block at the given x,y coordinate
|
||||
def add_wall_block(self,x,y):
|
||||
self.add_wall_tile(x+1,y+1)
|
||||
self.add_wall_tile(x+2,y+1)
|
||||
self.add_wall_tile(x+1,y+2)
|
||||
self.add_wall_tile(x+2,y+2)
|
||||
|
||||
# determines if a 2x2 block can fit inside the 4x4 block at the given x,y coordinate
|
||||
# (the whole 4x4 block must be empty)
|
||||
def can_new_block_fit(self,x,y):
|
||||
if not (self.xy_valid(x,y) and self.xy_valid(x+3,y+3)):
|
||||
return False
|
||||
for y0 in xrange(y,y+4):
|
||||
for x0 in xrange(x,x+4):
|
||||
if self.get_tile(x0,y0) != '.':
|
||||
return False
|
||||
return True
|
||||
|
||||
# create a list of valid starting positions
|
||||
def update_pos_list(self):
|
||||
self.pos_list = []
|
||||
for y in xrange(self.h):
|
||||
for x in xrange(self.w):
|
||||
if self.can_new_block_fit(x,y):
|
||||
self.pos_list.append((x,y))
|
||||
|
||||
# A connection is a sort of dependency of one tile block on another.
|
||||
# If a valid starting position is against another wall, then add this tile
|
||||
# to other valid start positions' that intersect this one so that they fill
|
||||
# it when they are chosen. This filling is a heuristic to eliminate gaps.
|
||||
def update_connections(self):
|
||||
self.connections = {}
|
||||
for y in xrange(self.h):
|
||||
for x in xrange(self.w):
|
||||
if (x,y) in self.pos_list:
|
||||
if any(self.get_tile(x-1,y+y0)=='|' for y0 in range(4)): self.add_connection(x,y,1,0)
|
||||
if any(self.get_tile(x+4,y+y0)=='|' for y0 in range(4)): self.add_connection(x,y,-1,0)
|
||||
if any(self.get_tile(x+x0,y-1)=='|' for x0 in range(4)): self.add_connection(x,y,0,1)
|
||||
if any(self.get_tile(x+x0,y+4)=='|' for x0 in range(4)): self.add_connection(x,y,0,-1)
|
||||
|
||||
# the block at x,y is against a wall, so make intersecting blocks in the direction of
|
||||
# dx,dy fill the block at x,y if they are filled first.
|
||||
def add_connection(self,x,y,dx,dy):
|
||||
def connect(x0,y0):
|
||||
src = (x,y)
|
||||
dest = (x0,y0)
|
||||
if not dest in self.pos_list:
|
||||
return
|
||||
if dest in self.connections:
|
||||
self.connections[dest].append(src)
|
||||
else:
|
||||
self.connections[dest] = [src]
|
||||
if (x,y) in self.pos_list:
|
||||
connect(x+dx,y+dy)
|
||||
connect(x+2*dx,y+2*dy)
|
||||
if not (x-dy,y-dx) in self.pos_list: connect(x+dx-dy,y+dy-dx)
|
||||
if not (x+dy,y+dx) in self.pos_list: connect(x+dx+dy,y+dy+dx)
|
||||
if not (x+dx-dy,y+dy-dx) in self.pos_list: connect(x+2*dx-dy, y+2*dy-dx)
|
||||
if not (x+dx+dy,y+dy+dx) in self.pos_list: connect(x+2*dx+dy, y+2*dy+dx)
|
||||
|
||||
# update the starting positions and dependencies
|
||||
def update(self):
|
||||
self.update_pos_list()
|
||||
self.update_connections()
|
||||
|
||||
# expand a wall block at the given x,y
|
||||
# return number of tiles added
|
||||
def expand_wall(self,x,y):
|
||||
visited = []
|
||||
def expand(x,y):
|
||||
count = 0
|
||||
src = (x,y)
|
||||
if src in visited:
|
||||
return 0
|
||||
visited.append(src)
|
||||
if src in self.connections:
|
||||
for x0,y0 in self.connections[src]:
|
||||
if not self.is_wall_block_filled(x0,y0):
|
||||
count += 1
|
||||
self.add_wall_block(x0,y0)
|
||||
count += expand(x0,y0)
|
||||
return count
|
||||
return expand(x,y)
|
||||
|
||||
def get_most_open_dir(self,x,y):
|
||||
dirs = ((0,-1),(0,1),(1,0),(-1,0))
|
||||
max_dir = random.choice(dirs)
|
||||
max_len = 0
|
||||
for dx,dy in dirs:
|
||||
len = 0
|
||||
while (x+dx*len,y+dy*len) in self.pos_list:
|
||||
len += 1
|
||||
if len > max_len:
|
||||
max_dir = (dx,dy)
|
||||
max_len = len
|
||||
return max_dir
|
||||
|
||||
# start a wall at block x,y
|
||||
def add_wall_obstacle(self,x=None,y=None,extend=False):
|
||||
self.update()
|
||||
if not self.pos_list:
|
||||
return False
|
||||
|
||||
# choose random valid starting position if none provided
|
||||
if (x is None or y is None):
|
||||
x,y = random.choice(self.pos_list)
|
||||
|
||||
# add first block
|
||||
self.add_wall_block(x,y)
|
||||
|
||||
# initialize verbose print lines
|
||||
first_lines = str(self).splitlines()
|
||||
grow_lines = [""]*(self.h+2)
|
||||
extend_lines = [""]*(self.h+2)
|
||||
|
||||
# mandatory grow phase
|
||||
count = self.expand_wall(x,y)
|
||||
if count > 0:
|
||||
grow_lines = str(self).splitlines()
|
||||
|
||||
# extend phase
|
||||
if extend:
|
||||
|
||||
# desired maximum block size
|
||||
max_blocks = 4
|
||||
|
||||
# 35% chance of forcing the block to turn
|
||||
# turn means the turn has been taken
|
||||
# turn_blocks is the number of blocks traveled before turning
|
||||
turn = False
|
||||
turn_blocks = max_blocks
|
||||
if random.random() <= 0.35:
|
||||
turn_blocks = 4
|
||||
max_blocks += turn_blocks
|
||||
|
||||
# choose a random direction
|
||||
dx,dy = random.choice(((0,-1),(0,1),(1,0),(-1,0)))
|
||||
orig_dir = (dx,dy)
|
||||
|
||||
i = 0
|
||||
while count < max_blocks:
|
||||
x0 = x+dx*i
|
||||
y0 = y+dy*i
|
||||
# turn if we're past turning point or at a dead end
|
||||
if (not turn and count >= turn_blocks) or not (x0,y0) in self.pos_list:
|
||||
turn = True
|
||||
dx,dy = -dy,dx # rotate
|
||||
i = 1
|
||||
# stop if we've come full circle
|
||||
if orig_dir == (dx,dy): break
|
||||
else: continue
|
||||
|
||||
# add wall block and grow to fill gaps
|
||||
if not self.is_wall_block_filled(x0,y0):
|
||||
self.add_wall_block(x0,y0)
|
||||
count += 1 + self.expand_wall(x0,y0)
|
||||
i += 1
|
||||
extend_lines = str(self).splitlines()
|
||||
|
||||
# print the map states after each phase for debugging
|
||||
if self.verbose:
|
||||
print "added block at ",x,y
|
||||
for a,b,c in zip(first_lines, grow_lines, extend_lines):
|
||||
print a,b,c
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# initial empty map with standard ghost house
|
||||
tileMap = Map(16,31,"""
|
||||
||||||||||||||||
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|.........||||||
|
||||
|.........||||||
|
||||
|.........||||||
|
||||
|.........||||||
|
||||
|.........||||||
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
|...............
|
||||
||||||||||||||||
|
||||
""")
|
||||
|
||||
# verbosity option (-v)
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "-v":
|
||||
tileMap.verbose = True
|
||||
|
||||
# generate map by adding walls until there's no more room
|
||||
while tileMap.add_wall_obstacle(extend=True):
|
||||
pass
|
||||
|
||||
# reflect the first 14 columns to print the map
|
||||
for line in str(tileMap).splitlines():
|
||||
s = line[:14]
|
||||
print s+s[::-1]
|
||||
@@ -0,0 +1 @@
|
||||
The goal here is to explore a simple algorithm that can be implemented more easily in z80
|
||||
@@ -0,0 +1,783 @@
|
||||
|
||||
import sys
|
||||
import random
|
||||
|
||||
mapwidth = 5
|
||||
mapheight = 9
|
||||
|
||||
######################################################################
|
||||
# Define all possible pieces.
|
||||
|
||||
class Piece:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return self.string
|
||||
|
||||
def setFromStr(self, s):
|
||||
self.string = s
|
||||
offsets = []
|
||||
self.leftHeight = 0
|
||||
|
||||
y = 0
|
||||
foundTop = False
|
||||
for line in s.split('\n'):
|
||||
x = 0
|
||||
for char in line.rstrip():
|
||||
if char == '#':
|
||||
if x == 0:
|
||||
self.leftHeight += 1
|
||||
if not foundTop:
|
||||
foundTop = True
|
||||
for p in offsets:
|
||||
p[1] -= y
|
||||
y = 0
|
||||
x += 1
|
||||
continue
|
||||
offsets.append([x,y])
|
||||
x += 1
|
||||
y += 1
|
||||
|
||||
self.offsets = [(a[0],a[1]) for a in offsets]
|
||||
self.offsets.append((0,0))
|
||||
|
||||
self.miny = min(y for x,y in self.offsets)
|
||||
self.maxy = max(y for x,y in self.offsets)
|
||||
self.maxx = max(x for x,y in self.offsets)
|
||||
self.size = len(self.offsets)
|
||||
|
||||
def makePieces(a):
|
||||
pieces = []
|
||||
groups = a.split('\n\n')
|
||||
i = 0
|
||||
for group in groups:
|
||||
if not group.strip():
|
||||
continue
|
||||
piece = Piece()
|
||||
piece.index = i
|
||||
piece.setFromStr(group)
|
||||
pieces.append(piece)
|
||||
i += 1
|
||||
return pieces
|
||||
|
||||
pieces = makePieces("""
|
||||
|
||||
# 0
|
||||
|
||||
## 1
|
||||
|
||||
# 2
|
||||
#
|
||||
|
||||
## 3
|
||||
#
|
||||
|
||||
# 4
|
||||
##
|
||||
|
||||
## 5
|
||||
#
|
||||
|
||||
# 6
|
||||
##
|
||||
|
||||
### 7
|
||||
#
|
||||
|
||||
# 8
|
||||
###
|
||||
|
||||
# 9
|
||||
##
|
||||
#
|
||||
|
||||
# 10
|
||||
##
|
||||
#
|
||||
|
||||
### 11
|
||||
#
|
||||
|
||||
# 12
|
||||
###
|
||||
|
||||
### 13
|
||||
#
|
||||
|
||||
# 14
|
||||
###
|
||||
|
||||
## 15
|
||||
#
|
||||
#
|
||||
|
||||
## 16
|
||||
#
|
||||
#
|
||||
|
||||
# 17
|
||||
###
|
||||
#
|
||||
|
||||
### 18
|
||||
#
|
||||
#
|
||||
|
||||
# 19
|
||||
#
|
||||
###
|
||||
|
||||
# 20
|
||||
###
|
||||
#
|
||||
|
||||
# 21
|
||||
###
|
||||
#
|
||||
|
||||
#### 22
|
||||
#
|
||||
|
||||
#### 23
|
||||
#
|
||||
|
||||
# 24
|
||||
####
|
||||
|
||||
# 25
|
||||
####
|
||||
|
||||
# 26
|
||||
##
|
||||
#
|
||||
#
|
||||
|
||||
# 27
|
||||
#
|
||||
##
|
||||
#
|
||||
|
||||
# 28
|
||||
##
|
||||
#
|
||||
#
|
||||
|
||||
# 29
|
||||
#
|
||||
##
|
||||
#
|
||||
|
||||
""")
|
||||
|
||||
######################################################################
|
||||
# Determine valid pieces for each position regarding map boundaries.
|
||||
|
||||
def makeValidPieceTable(pieces):
|
||||
valid_pieces = {}
|
||||
for x in xrange(mapwidth-1):
|
||||
for y in xrange(mapheight):
|
||||
valid_pieces[(x,y)] = []
|
||||
|
||||
def inPen(x,y):
|
||||
return x >= 0 and x <= 1 and y >= 3 and y <= 4
|
||||
|
||||
for x,y in valid_pieces:
|
||||
|
||||
# disallow anything in ghost pen
|
||||
if inPen(x,y):
|
||||
continue
|
||||
|
||||
for p in pieces:
|
||||
|
||||
# disallow anything in ghost pen
|
||||
if any(inPen(x+dx,y+dy) for dx,dy in p.offsets):
|
||||
continue
|
||||
|
||||
# disallow piece of size 1 anywhere but the top and bottom rows
|
||||
if p.size == 1 and y > 0 and y < 8:
|
||||
continue
|
||||
|
||||
# special restrictions on first column due to symmetry
|
||||
if x == 0:
|
||||
# disallow anything too wide
|
||||
if p.maxx > 1:
|
||||
continue
|
||||
|
||||
# disallow anything too large
|
||||
if p.size > 4:
|
||||
continue
|
||||
|
||||
# disallow complicated reflections
|
||||
if p.size == 4 and p.leftHeight == 1:
|
||||
continue
|
||||
|
||||
# disallow pieces obstructing pacman's starting point
|
||||
if y == 5 and p.leftHeight > 2:
|
||||
continue
|
||||
if y == 6 and p.leftHeight > 1:
|
||||
continue
|
||||
|
||||
# disallow pieces that don't fit inside map
|
||||
maxx = x + p.maxx
|
||||
miny = y + p.miny
|
||||
maxy = y + p.maxy
|
||||
if maxx >= mapwidth or miny < 0 or maxy >= mapheight:
|
||||
continue
|
||||
|
||||
valid_pieces[(x,y)].append(p)
|
||||
|
||||
return valid_pieces
|
||||
|
||||
def shuffleValidPieces(v):
|
||||
for a in v.values():
|
||||
random.shuffle(a)
|
||||
|
||||
valid_pieces = makeValidPieceTable(pieces)
|
||||
|
||||
######################################################################
|
||||
# Create a tile map class for searching piece configurations.
|
||||
|
||||
UP = 0
|
||||
RIGHT = 1
|
||||
DOWN = 2
|
||||
LEFT = 3
|
||||
|
||||
class Cell:
|
||||
def __init__(self,tile_map,x,y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.nextCells = [None] * 4
|
||||
def isConnected(d):
|
||||
dx = [0,1,0,-1][d]
|
||||
dy = [-1,0,1,0][d]
|
||||
|
||||
# make sure connection is horizontally reflected at x==0
|
||||
if x == 0 and dx == -1:
|
||||
dx = 1
|
||||
|
||||
val = tile_map.getTile(x,y)
|
||||
|
||||
# empty tiles (always on the right side in a completed map) always open to the right
|
||||
#if val == 0 and x == mapwidth-1 and dx = 1:
|
||||
# return True
|
||||
|
||||
x0 = x+dx
|
||||
y0 = y+dy
|
||||
|
||||
if x0 < 0 or x0 >= mapwidth or y0 < 0 or y0 >= mapheight:
|
||||
return False
|
||||
|
||||
adjacentVal = tile_map.getTile(x0,y0)
|
||||
|
||||
return val == adjacentVal
|
||||
|
||||
self.connect = [isConnected(i) for i in xrange(4)]
|
||||
|
||||
|
||||
class TileMap:
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def getPieceList(self):
|
||||
return [valid_pieces[(x,y)][i].index for i,x,y in self.piece_stack]
|
||||
|
||||
def getShell(self):
|
||||
s = ""
|
||||
for row in self.tiles:
|
||||
for c in row:
|
||||
if c == 0:
|
||||
s += "0"
|
||||
else:
|
||||
s += "1"
|
||||
return s
|
||||
|
||||
def getState(self):
|
||||
return (self.getShell(), self.hasTopSquare, self.hasBottomSquare, self.numSize2, self.numSize5)
|
||||
|
||||
def reset(self):
|
||||
self.tiles = [[0 for i in xrange(mapwidth)] for j in xrange(mapheight)]
|
||||
self.setTile(0,3,1)
|
||||
self.setTile(1,3,1)
|
||||
self.setTile(0,4,1)
|
||||
self.setTile(1,4,1)
|
||||
self.num_pieces = 1
|
||||
|
||||
self.piece_stack = []
|
||||
self.pos_dict = dict(((x,y),None) for x in range(mapwidth-1) for y in range(mapheight))
|
||||
|
||||
# state
|
||||
self.hasTopSquare = False
|
||||
self.hasBottomSquare = False
|
||||
self.numSize2 = 0
|
||||
self.maxSize2 = 2
|
||||
self.numSize5 = 0
|
||||
self.maxSize5 = 1
|
||||
|
||||
def setTopConfig(self,i):
|
||||
x,y = 0,0
|
||||
for p in top_configs[i]:
|
||||
piece = pieces[p]
|
||||
self.writePiece(piece,x,y)
|
||||
y += piece.leftHeight
|
||||
|
||||
def setBottomConfig(self,i):
|
||||
x,y = 0,5
|
||||
for p in bottom_configs[i]:
|
||||
piece = pieces[p]
|
||||
self.writePiece(piece,x,y)
|
||||
y += piece.leftHeight
|
||||
|
||||
def setTile(self,x,y,c):
|
||||
self.tiles[y][x] = c
|
||||
|
||||
def getTile(self,x,y):
|
||||
return self.tiles[y][x]
|
||||
|
||||
def buildCells(self):
|
||||
# build table of cells
|
||||
self.cells = [[Cell(self,x,y) for x in xrange(mapwidth)] for y in xrange(mapheight)]
|
||||
|
||||
# allow the referencing of adjacent cells
|
||||
for y in xrange(mapheight):
|
||||
for x in xrange(mapwidth):
|
||||
c = self.cells[y][x]
|
||||
|
||||
if y+1 < mapheight:
|
||||
cd = self.cells[y+1][x]
|
||||
c.nextCells[DOWN] = cd
|
||||
cd.nextCells[UP] = c
|
||||
|
||||
if x+1 < mapwidth:
|
||||
cr = self.cells[y][x+1]
|
||||
c.nextCells[RIGHT] = cr
|
||||
cr.nextCells[LEFT] = c
|
||||
|
||||
def __str__(self):
|
||||
s = ""
|
||||
for row in self.tiles:
|
||||
for col in row:
|
||||
if col == 1:
|
||||
col = ' '
|
||||
elif col == 0:
|
||||
col = '.'
|
||||
s += ('%3s' % str(col))
|
||||
s += "\n"
|
||||
return s
|
||||
|
||||
def canPieceFit(self,piece,x,y):
|
||||
if piece.size == 1:
|
||||
if y == 0:
|
||||
if self.hasTopSquare:
|
||||
return False
|
||||
elif y == mapheight-1:
|
||||
if self.hasBottomSquare:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
elif piece.size == 2:
|
||||
if self.numSize2 == self.maxSize2:
|
||||
return False
|
||||
elif piece.size == 5:
|
||||
if self.numSize5 == self.maxSize5:
|
||||
return False
|
||||
|
||||
# prevent two horizontal pieces from being on top of one another
|
||||
if (piece.index == 1 and
|
||||
(x,y-1) in self.pos_dict and
|
||||
self.pos_dict[(x,y-1)] == 1):
|
||||
return False
|
||||
for dx,dy in piece.offsets:
|
||||
if self.getTile(x+dx,y+dy) > 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def writePiece(self,piece,x,y):
|
||||
self.num_pieces += 1
|
||||
n = self.num_pieces
|
||||
for dx,dy in piece.offsets:
|
||||
self.setTile(x+dx,y+dy,n)
|
||||
|
||||
# update constraints
|
||||
if piece.size == 1:
|
||||
if y == 0:
|
||||
self.hasTopSquare = True
|
||||
else:
|
||||
self.hasBottomSquare = True
|
||||
elif piece.size == 2:
|
||||
self.numSize2 += 1
|
||||
elif piece.size == 5:
|
||||
self.numSize5 += 1
|
||||
|
||||
def erasePiece(self,piece,x,y):
|
||||
self.num_pieces -= 1
|
||||
n = 0
|
||||
for dx,dy in piece.offsets:
|
||||
self.setTile(x+dx,y+dy,n)
|
||||
|
||||
# update constraints
|
||||
if piece.size == 1:
|
||||
if y == 0:
|
||||
self.hasTopSquare = False
|
||||
else:
|
||||
self.hasBottomSquare = False
|
||||
elif piece.size == 2:
|
||||
self.numSize2 -= 1
|
||||
elif piece.size == 5:
|
||||
self.numSize5 -= 1
|
||||
|
||||
def pushPiece(self,i,x,y):
|
||||
if isinstance(i,Piece):
|
||||
piece = i
|
||||
i = 0
|
||||
for j in valid_pieces[(x,y)]:
|
||||
i += 1
|
||||
if j == piece:
|
||||
break
|
||||
else:
|
||||
piece = valid_pieces[(x,y)][i]
|
||||
self.pos_dict[(x,y)] = piece.index
|
||||
self.piece_stack.append((i,x,y))
|
||||
self.writePiece(piece,x,y)
|
||||
|
||||
def popPiece(self):
|
||||
i,x,y = self.piece_stack.pop()
|
||||
self.pos_dict[(x,y)] = None
|
||||
piece = valid_pieces[(x,y)][i]
|
||||
self.erasePiece(piece,x,y)
|
||||
return i,x,y
|
||||
|
||||
def getNextOpenTile(self,x,y):
|
||||
for x0 in xrange(x,mapwidth-1):
|
||||
for y0 in xrange(y,mapheight):
|
||||
if self.getTile(x0,y0) == 0:
|
||||
return x0,y0
|
||||
y = 0
|
||||
return None
|
||||
|
||||
def depthFirstSearch(self,x,y,solutionCallback=None,shouldStop=None,debug=False):
|
||||
i = 0
|
||||
while True:
|
||||
# We start this iteration knowing that we are at an open tile.
|
||||
|
||||
# Try to find a piece that fits in our open tile.
|
||||
found_piece = False
|
||||
potential_pieces = valid_pieces[(x,y)][i:]
|
||||
for piece in potential_pieces:
|
||||
if self.canPieceFit(piece,x,y):
|
||||
if debug:
|
||||
print "can fit piece %d at (%d,%d)" % (piece.index,x,y)
|
||||
print self
|
||||
found_piece = True
|
||||
break
|
||||
i += 1
|
||||
|
||||
|
||||
# If we have found a piece to place at current tile.
|
||||
if found_piece:
|
||||
self.pushPiece(i,x,y)
|
||||
pos = self.getNextOpenTile(x,y)
|
||||
if not pos:
|
||||
self.buildCells()
|
||||
self.setResizeCandidates()
|
||||
if self.chooseTallRows() and self.chooseNarrowCols():
|
||||
if solutionCallback:
|
||||
solutionCallback()
|
||||
if debug:
|
||||
print "SOLUTION:"
|
||||
print self
|
||||
if shouldStop and shouldStop(None,None):
|
||||
break # stop search
|
||||
else:
|
||||
# TODO: set x,y to position before the first offending row or col that prevented resize
|
||||
pass
|
||||
else:
|
||||
i = 0
|
||||
x,y = pos
|
||||
|
||||
if shouldStop and shouldStop(x,y):
|
||||
if solutionCallback:
|
||||
solutionCallback()
|
||||
|
||||
# We have an open tile to continue with, so skip backtracking
|
||||
else:
|
||||
continue
|
||||
|
||||
# Backtrack to find next open tile
|
||||
try:
|
||||
|
||||
# Pop pieces until we get to a position whose possibilities haven't been exhausted.
|
||||
while True:
|
||||
i,x,y = self.popPiece()
|
||||
if debug:
|
||||
print "removed piece %d at (%d,%d)" % (valid_pieces[(x,y)][i].index, x,y)
|
||||
print self
|
||||
try:
|
||||
i += 1
|
||||
valid_pieces[(x,y)][i]
|
||||
# position not exhausted, use it.
|
||||
break
|
||||
except IndexError:
|
||||
# position exhausted, continue popping
|
||||
continue
|
||||
|
||||
except IndexError:
|
||||
# exit search
|
||||
break
|
||||
|
||||
def setResizeCandidates(self):
|
||||
for y in xrange(mapheight):
|
||||
for x in xrange(mapwidth):
|
||||
c = self.cells[y][x]
|
||||
q = c.connect
|
||||
|
||||
# _
|
||||
# |_| or | |
|
||||
if not q[LEFT] and not q[RIGHT] and (not q[UP] or not q[DOWN]):
|
||||
c.isRaiseHeightCandidate = True
|
||||
|
||||
# __
|
||||
# |__|
|
||||
if x+1 < mapwidth:
|
||||
cr = c.nextCells[RIGHT]
|
||||
qr = cr.connect
|
||||
if (not q[LEFT] and not q[UP] and not q[DOWN] and q[RIGHT] and
|
||||
not qr[RIGHT] and not qr[UP] and not qr[DOWN] and qr[LEFT]):
|
||||
c.isRaiseHeightCandidate = cr.isRaiseHeightCandidate = True
|
||||
|
||||
# _ _
|
||||
# _| or |_
|
||||
if not q[UP] and not q[DOWN] and q[LEFT] != q[RIGHT]:
|
||||
c.isShrinkWidthCandidate = True
|
||||
|
||||
# empty cell on right border
|
||||
if x == mapwidth-1 and self.getTile(x,y) == 0:
|
||||
c.isShrinkWidthCandidate = True
|
||||
|
||||
# _
|
||||
# |
|
||||
# _|
|
||||
if y+1 < mapheight:
|
||||
cd = c.nextCells[DOWN]
|
||||
qd = cd.connect
|
||||
if (q[LEFT] and not q[UP] and not q[RIGHT] and q[DOWN] and
|
||||
qd[LEFT] and not qd[DOWN] and not qd[RIGHT] and qd[UP]):
|
||||
c.isShrinkWidthCandidate = cd.isShrinkWidthCandidate = True
|
||||
|
||||
def chooseTallRows(self):
|
||||
return True
|
||||
|
||||
def chooseNarrowCols(self):
|
||||
return True
|
||||
|
||||
######################################################################
|
||||
# Create preset piece configurations for segments of the map.
|
||||
|
||||
top_right_configs = [[p.index] for p in pieces if p.size > 1 and (p.maxx,p.miny) in p.offsets]
|
||||
bottom_right_configs = [[p.index] for p in pieces if p.size > 1 and (p.maxx,p.maxy) in p.offsets]
|
||||
|
||||
def createTopLeftConfigs():
|
||||
configs = []
|
||||
tile_map = TileMap()
|
||||
shouldStop = lambda x,y: y > 2
|
||||
def callback():
|
||||
configs.append(tile_map.getPieceList())
|
||||
tile_map.depthFirstSearch(0,0,solutionCallback=callback,shouldStop=shouldStop)
|
||||
return configs
|
||||
|
||||
def createBottomLeftConfigs():
|
||||
configs = []
|
||||
tile_map = TileMap()
|
||||
shouldStop = lambda x,y: x > 0
|
||||
def callback():
|
||||
configs.append(tile_map.getPieceList())
|
||||
tile_map.depthFirstSearch(0,5,solutionCallback=callback,shouldStop=shouldStop)
|
||||
return configs
|
||||
|
||||
top_left_configs = createTopLeftConfigs()
|
||||
bottom_left_configs = createBottomLeftConfigs()
|
||||
|
||||
def printConfigInfo():
|
||||
print "TOP LEFT", len(top_left_configs)
|
||||
print "BOTTOM LEFT", len(bottom_left_configs)
|
||||
print "TOP RIGHT", len(top_right_configs)
|
||||
print "BOTTOM RIGHT", len(bottom_right_configs)
|
||||
|
||||
def printConfig(label, configs):
|
||||
print label
|
||||
for config in configs:
|
||||
print config
|
||||
|
||||
printConfig("TOP LEFT", top_left_configs)
|
||||
printConfig("BOTTOM LEFT", bottom_left_configs)
|
||||
printConfig("TOP RIGHT", top_right_configs)
|
||||
printConfig("BOTTOM RIGHT", bottom_right_configs)
|
||||
|
||||
def getTopRightXY(piece):
|
||||
x = mapwidth-1-piece.maxx
|
||||
y = -piece.miny
|
||||
return x,y
|
||||
|
||||
def getBottomRightXY(piece):
|
||||
x = mapwidth-1-piece.maxx
|
||||
y = mapheight-1-piece.maxy
|
||||
return x,y
|
||||
|
||||
def findTopConfigs():
|
||||
configs = []
|
||||
tile_map = TileMap()
|
||||
for i,tl in enumerate(top_left_configs):
|
||||
tile_map.reset()
|
||||
x,y = 0,0
|
||||
for p in tl:
|
||||
piece = pieces[p]
|
||||
tile_map.pushPiece(piece,x,y)
|
||||
x,y = tile_map.getNextOpenTile(x,y)
|
||||
for j,tr in enumerate(top_right_configs):
|
||||
piece = pieces[tr[0]]
|
||||
x,y = getTopRightXY(piece)
|
||||
if tile_map.canPieceFit(piece,x,y):
|
||||
configs.append((i,j))
|
||||
return configs
|
||||
|
||||
|
||||
def findBottomConfigs():
|
||||
configs = []
|
||||
tile_map = TileMap()
|
||||
for i,bl in enumerate(bottom_left_configs):
|
||||
tile_map.reset()
|
||||
x,y = 0,5
|
||||
for p in bl:
|
||||
piece = pieces[p]
|
||||
tile_map.pushPiece(piece,x,y)
|
||||
x,y = tile_map.getNextOpenTile(x,y)
|
||||
for j,br in enumerate(bottom_right_configs):
|
||||
piece = pieces[br[0]]
|
||||
x,y = getBottomRightXY(piece)
|
||||
if tile_map.canPieceFit(piece,x,y):
|
||||
configs.append((i,j))
|
||||
return configs
|
||||
|
||||
top_configs = findTopConfigs()
|
||||
bottom_configs = findBottomConfigs()
|
||||
|
||||
def getAllConfigs():
|
||||
configs = []
|
||||
for i,tc in enumerate(top_configs):
|
||||
tl_index,tr_index = tc
|
||||
tl_config = top_left_configs[tl_index]
|
||||
tr_config = top_right_configs[tr_index]
|
||||
for j,bc in enumerate(bottom_configs):
|
||||
bl_index,br_index = bc
|
||||
bl_config = bottom_left_configs[bl_index]
|
||||
br_config = bottom_right_configs[br_index]
|
||||
|
||||
# Write the top left and bottom left configs
|
||||
x,y = 0,0
|
||||
tile_map = TileMap()
|
||||
valid = True
|
||||
for p in tl_config + bl_config:
|
||||
piece = pieces[p]
|
||||
if tile_map.canPieceFit(piece,x,y):
|
||||
tile_map.writePiece(piece,x,y)
|
||||
else:
|
||||
valid = False
|
||||
break
|
||||
x,y = tile_map.getNextOpenTile(x,y)
|
||||
|
||||
if valid:
|
||||
# Write the top right config
|
||||
piece = pieces[tr_config[0]]
|
||||
x,y = getTopRightXY(piece)
|
||||
if tile_map.canPieceFit(piece,x,y):
|
||||
tile_map.writePiece(piece,x,y)
|
||||
else:
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
# Write the bottom right config
|
||||
piece = pieces[br_config[0]]
|
||||
x,y = getBottomRightXY(piece)
|
||||
if tile_map.canPieceFit(piece,x,y):
|
||||
tile_map.writePiece(piece,x,y)
|
||||
else:
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
configs.append((i,j))
|
||||
|
||||
return configs
|
||||
|
||||
all_configs = getAllConfigs()
|
||||
|
||||
def makePresetTileMap(i):
|
||||
top_config, bottom_config = all_configs[i]
|
||||
|
||||
# Get configs
|
||||
tl_index,tr_index = top_configs[top_config]
|
||||
bl_index,br_index = bottom_configs[bottom_config]
|
||||
tl_config = top_left_configs[tl_index]
|
||||
tr_config = top_right_configs[tr_index]
|
||||
bl_config = bottom_left_configs[bl_index]
|
||||
br_config = bottom_right_configs[br_index]
|
||||
|
||||
#print "top indexes:",tl_index, tr_index
|
||||
#print "bottom indexes:",bl_index, br_index
|
||||
#print "topleft:",tl_config
|
||||
#print "bottomleft:",bl_config
|
||||
#print "topright:",tr_config
|
||||
#print "bottomright:",br_config
|
||||
|
||||
# Make tile map
|
||||
tile_map = TileMap()
|
||||
tile_map.preset_segments = [tl_index, tr_index, bl_index, br_index]
|
||||
tile_map.preset_pieces = [
|
||||
pieces[tr_config[0]].index,
|
||||
pieces[br_config[0]].index,
|
||||
]
|
||||
|
||||
# Write the top left and bottom left configs
|
||||
x,y = 0,0
|
||||
for p in tl_config+bl_config:
|
||||
piece = pieces[p]
|
||||
tile_map.preset_pieces.append(piece.index)
|
||||
tile_map.writePiece(piece,x,y)
|
||||
x,y = tile_map.getNextOpenTile(x,y)
|
||||
|
||||
# Write the top right config
|
||||
piece = pieces[tr_config[0]]
|
||||
x,y = getTopRightXY(piece)
|
||||
tile_map.writePiece(piece,x,y)
|
||||
|
||||
# Write the bottom right config
|
||||
piece = pieces[br_config[0]]
|
||||
x,y = getBottomRightXY(piece)
|
||||
tile_map.writePiece(piece,x,y)
|
||||
|
||||
return tile_map
|
||||
|
||||
def genMapForAllRoots():
|
||||
numRoots = len(all_configs)
|
||||
|
||||
shouldStop = lambda x,y: x is None and y is None
|
||||
for i in xrange(numRoots):
|
||||
shuffleValidPieces(valid_pieces)
|
||||
print >> sys.stderr, "%d / %d:" % (i+1,numRoots)
|
||||
tile_map = makePresetTileMap(i)
|
||||
success = [False]
|
||||
def callback():
|
||||
success[0] = True
|
||||
x,y = tile_map.getNextOpenTile(0,0)
|
||||
tile_map.depthFirstSearch(x,y,solutionCallback=callback,shouldStop=shouldStop)
|
||||
if success[0]:
|
||||
print tile_map.preset_segments, tile_map.preset_pieces
|
||||
|
||||
|
||||
######################################################################
|
||||
# Main.
|
||||
|
||||
def main():
|
||||
genMapForAllRoots()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
We want a tool to help enumerate all possible maps, but mostly sub-maps because:
|
||||
- there are too many to generate
|
||||
- sub-maps will help us identify choke points for focusing on segments
|
||||
- identifying patterns that can help us compress our representation of these mazes in z90
|
||||
|
||||
Generating all cases up to x=2 y=4:
|
||||
time: 8 minutes
|
||||
number of solutions: 10 million
|
||||
|
||||
time growth: factor of
|
||||
|
||||
on the order of 10 trillion solutions
|
||||
|
||||
Don't bother generating all, but identify unique pairings of KEYS and STATES
|
||||
- this will allow us to FOLD
|
||||
- a KEY = a silhouette of the current structure of a map
|
||||
- a STATE = current counters for piece limits
|
||||
|
||||
We need a way to save and load intermediate results:
|
||||
|
||||
Perhaps a good enough shorcut to generate these maps would be to come up with
|
||||
an algorithm for jumping between a given set of maps that we find to be valid.
|
||||
|
||||
|
||||
A map is represented as a sequence of piece numbers to be stacked top to bottom, left to right.
|
||||
|
||||
------------------
|
||||
|
||||
Steps toward z80
|
||||
|
||||
* Find simple way to generate valid tilings of 5x9 map
|
||||
* Prune out tilings that cannot be height- and width-adjusted
|
||||
* Scale up to game tiles
|
||||
* Add tunnels
|
||||
* Add fruit paths
|
||||
* Add pellets
|
||||
* Add energizers
|
||||
|
||||
|
||||
------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
Spanning Tree Method
|
||||
--------------------
|
||||
|
||||
Conventional mazes can be created by generating a spanning tree of a
|
||||
rectangular grid of nodes. [Check out this great presentation by Jamis Buck to
|
||||
learn about conventional maze generators](http://www.jamisbuck.org/presentations/rubyconf2011/index.html).
|
||||
|
||||
We can translate such spanning trees to be used in a Pac-Man tilemap by placing each node in the center of a 3x3 tile cell:
|
||||
|
||||
<img src="../img/spanning1.png"/>
|
||||
@@ -0,0 +1 @@
|
||||
/node_modules
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
<script src="index.js" type="module"></script>
|
||||
@@ -0,0 +1,169 @@
|
||||
const UP = 0;
|
||||
const RIGHT = 1;
|
||||
const DOWN = 2;
|
||||
const LEFT = 3;
|
||||
|
||||
const numRows = 9;
|
||||
const numCols = 5;
|
||||
|
||||
const ghostRow = 3;
|
||||
|
||||
function makeCell({ x, y }) {
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
filled: false,
|
||||
connect: [false, false, false, false],
|
||||
next: {},
|
||||
no: undefined,
|
||||
group: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function setGhostHomeCells(table) {
|
||||
let c;
|
||||
c = table[ghostRow][0];
|
||||
c.filled = true;
|
||||
c.connect[LEFT] = c.connect[RIGHT] = c.connect[DOWN] = true;
|
||||
|
||||
c = table[ghostRow][1];
|
||||
c.filled = true;
|
||||
c.connect[LEFT] = c.connect[DOWN] = true;
|
||||
|
||||
c = table[ghostRow + 1][0];
|
||||
c.filled = true;
|
||||
c.connect[LEFT] = c.connect[UP] = c.connect[RIGHT] = true;
|
||||
|
||||
c = table[ghostRow + 1][1];
|
||||
c.filled = true;
|
||||
c.connect[UP] = c.connect[LEFT] = true;
|
||||
}
|
||||
|
||||
function makeTable() {
|
||||
// initialize cells
|
||||
const table = [];
|
||||
const cells = [];
|
||||
for (let y = 0; y < numRows; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < numCols; x++) {
|
||||
const cell = makeCell({ x, y });
|
||||
cells.push(cell);
|
||||
row.push(cell);
|
||||
}
|
||||
table.push(row);
|
||||
}
|
||||
|
||||
// allow each cell to refer to surround cells by direction
|
||||
for (const cell of cells) {
|
||||
const { x, y } = cell;
|
||||
if (x > 0) cell.next[LEFT] = table[y][x - 1];
|
||||
if (x < numCols - 1) cell.next[RIGHT] = table[y][x + 1];
|
||||
if (y > 0) cell.next[UP] = table[y - 1][x];
|
||||
if (y < numRows - 1) cell.next[DOWN] = table[y + 1][x];
|
||||
}
|
||||
|
||||
setGhostHomeCells(table);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
function getLeftMostEmptyCells(table) {
|
||||
const leftCells = [];
|
||||
for (let x = 0; x < numCols; x++) {
|
||||
for (let y = 0; y < numRows; y++) {
|
||||
const c = table[y][x];
|
||||
if (!c.filled) leftCells.push(c);
|
||||
}
|
||||
if (leftCells.length > 0) break;
|
||||
}
|
||||
return leftCells;
|
||||
}
|
||||
|
||||
function makeState() {
|
||||
return {
|
||||
table: makeTable(),
|
||||
|
||||
cell: null,
|
||||
firstCell: null,
|
||||
firstCell: null,
|
||||
|
||||
numFilled: 0,
|
||||
numGroups: 0,
|
||||
|
||||
size: 0,
|
||||
|
||||
singleCount: {
|
||||
0: 0,
|
||||
[numRows - 1]: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function fillCell(state, cell) {
|
||||
cell.filled = true;
|
||||
cell.no = state.numFilled++;
|
||||
cell.group = state.numGroups;
|
||||
}
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function randomElement(list) {
|
||||
const n = list.length;
|
||||
if (n > 0) return list[getRandomInt(0, n - 1)];
|
||||
}
|
||||
|
||||
const probTopAndBotSingleCellJoin = 1;
|
||||
|
||||
function trySingleCellGroup(state) {
|
||||
const { cell, singleCount } = state;
|
||||
// randomly allow one single-cell piece on the top or bottom of the map.
|
||||
if (
|
||||
cell.x < numCols - 1 &&
|
||||
cell.y in singleCount &&
|
||||
Math.random() <= probTopAndBotSingleCellJoin &&
|
||||
singleCount[cell.y] == 0
|
||||
) {
|
||||
cell.connect[cell.y == 0 ? UP : DOWN] = true;
|
||||
singleCount[cell.y]++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function startNewGroup(state) {
|
||||
const { table } = state;
|
||||
const openCells = getLeftMostEmptyCells(table);
|
||||
const cell = randomElement(openCells);
|
||||
if (cell) {
|
||||
fillCell(state, cell);
|
||||
state.firstCell = state.cell = cell;
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
function genRandomCells() {
|
||||
const state = makeState();
|
||||
|
||||
while (true) {
|
||||
const cell = startNewGroup(state);
|
||||
if (!cell) break;
|
||||
|
||||
if (trySingleCellGroup(state)) continue;
|
||||
|
||||
state.size = 1;
|
||||
|
||||
if (cell.x === numCols - 1) {
|
||||
cell.connect[RIGHT] = true;
|
||||
cell.isRaiseHeightCandidate = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(state);
|
||||
break;
|
||||
|
||||
state.numGroups++;
|
||||
}
|
||||
}
|
||||
|
||||
genRandomCells();
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"scripts": {
|
||||
"start": "http-server -o"
|
||||
},
|
||||
"devDependencies": {
|
||||
"http-server": "^0.11.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
async@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
||||
|
||||
[email protected]:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
||||
|
||||
corser@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
|
||||
|
||||
debug@=3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^2.2.0:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
ecstatic@^3.0.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-3.3.0.tgz#91cd417d152abf85b37b1ab3ebf3bd25cdc64e80"
|
||||
dependencies:
|
||||
he "^1.1.1"
|
||||
mime "^1.6.0"
|
||||
minimist "^1.1.0"
|
||||
url-join "^2.0.5"
|
||||
|
||||
eventemitter3@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.5.8"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.8.tgz#1dbfe13e45ad969f813e86c00e5296f525c885a1"
|
||||
dependencies:
|
||||
debug "=3.1.0"
|
||||
|
||||
he@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
|
||||
http-proxy@^1.8.1:
|
||||
version "1.17.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a"
|
||||
dependencies:
|
||||
eventemitter3 "^3.0.0"
|
||||
follow-redirects "^1.0.0"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
http-server@^0.11.1:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/http-server/-/http-server-0.11.1.tgz#2302a56a6ffef7f9abea0147d838a5e9b6b6a79b"
|
||||
dependencies:
|
||||
colors "1.0.3"
|
||||
corser "~2.0.0"
|
||||
ecstatic "^3.0.0"
|
||||
http-proxy "^1.8.1"
|
||||
opener "~1.4.0"
|
||||
optimist "0.6.x"
|
||||
portfinder "^1.0.13"
|
||||
union "~0.4.3"
|
||||
|
||||
mime@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
|
||||
[email protected]:
|
||||
version "0.0.8"
|
||||
resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
minimist@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
minimist@~0.0.1:
|
||||
version "0.0.10"
|
||||
resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
|
||||
[email protected]:
|
||||
version "0.5.1"
|
||||
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
[email protected]:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
opener@~1.4.0:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
|
||||
|
||||
[email protected]:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
|
||||
dependencies:
|
||||
minimist "~0.0.1"
|
||||
wordwrap "~0.0.2"
|
||||
|
||||
portfinder@^1.0.13:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.17.tgz#a8a1691143e46c4735edefcf4fbcccedad26456a"
|
||||
dependencies:
|
||||
async "^1.5.2"
|
||||
debug "^2.2.0"
|
||||
mkdirp "0.5.x"
|
||||
|
||||
qs@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404"
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
|
||||
union@~0.4.3:
|
||||
version "0.4.6"
|
||||
resolved "https://registry.yarnpkg.com/union/-/union-0.4.6.tgz#198fbdaeba254e788b0efcb630bc11f24a2959e0"
|
||||
dependencies:
|
||||
qs "~2.3.3"
|
||||
|
||||
url-join@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728"
|
||||
|
||||
wordwrap@~0.0.2:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
|
||||
@@ -0,0 +1,396 @@
|
||||
// direction enums (in clockwise order)
|
||||
var DIR_UP = 0;
|
||||
var DIR_RIGHT = 1;
|
||||
var DIR_DOWN = 2;
|
||||
var DIR_LEFT = 3;
|
||||
|
||||
// get direction enum from a direction vector
|
||||
var getEnumFromDir = function(dir) {
|
||||
if (dir.x==-1) return DIR_LEFT;
|
||||
if (dir.x==1) return DIR_RIGHT;
|
||||
if (dir.y==-1) return DIR_UP;
|
||||
if (dir.y==1) return DIR_DOWN;
|
||||
};
|
||||
|
||||
// set direction vector from a direction enum
|
||||
var setDirFromEnum = function(dir,dirEnum) {
|
||||
if (dirEnum == DIR_UP) { dir.x = 0; dir.y =-1; }
|
||||
else if (dirEnum == DIR_RIGHT) { dir.x =1; dir.y = 0; }
|
||||
else if (dirEnum == DIR_DOWN) { dir.x = 0; dir.y = 1; }
|
||||
else if (dirEnum == DIR_LEFT) { dir.x = -1; dir.y = 0; }
|
||||
};
|
||||
|
||||
// size of a square tile in pixels
|
||||
var tileSize = 8;
|
||||
|
||||
// constructor
|
||||
var Map = function(numCols, numRows, tiles) {
|
||||
|
||||
// sizes
|
||||
this.numCols = numCols;
|
||||
this.numRows = numRows;
|
||||
this.numTiles = numCols*numRows;
|
||||
this.widthPixels = numCols*tileSize;
|
||||
this.heightPixels = numRows*tileSize;
|
||||
|
||||
// ascii map
|
||||
this.tiles = tiles;
|
||||
|
||||
this.resetCurrent();
|
||||
this.parseWalls();
|
||||
};
|
||||
|
||||
// reset current tiles
|
||||
Map.prototype.resetCurrent = function() {
|
||||
this.currentTiles = this.tiles.split(""); // create a mutable list copy of an immutable string
|
||||
};
|
||||
|
||||
// This is a procedural way to generate original-looking maps from a simple ascii tile
|
||||
// map without a spritesheet.
|
||||
Map.prototype.parseWalls = function() {
|
||||
|
||||
var that = this;
|
||||
|
||||
// creates a list of drawable canvas paths to render the map walls
|
||||
this.paths = [];
|
||||
|
||||
// a map of wall tiles that already belong to a built path
|
||||
var visited = {};
|
||||
|
||||
// we extend the x range to suggest the continuation of the tunnels
|
||||
var toIndex = function(x,y) {
|
||||
if (x>=-2 && x<that.numCols+2 && y>=0 && y<that.numRows)
|
||||
return (x+2)+y*(that.numCols+4);
|
||||
};
|
||||
|
||||
// a map of which wall tiles that are not completely surrounded by other wall tiles
|
||||
var edges = {};
|
||||
var i=0,x,y;
|
||||
for (y=0;y<this.numRows;y++) {
|
||||
for (x=-2;x<this.numCols+2;x++,i++) {
|
||||
if (this.getTile(x,y) == '|' &&
|
||||
(this.getTile(x-1,y) != '|' ||
|
||||
this.getTile(x+1,y) != '|' ||
|
||||
this.getTile(x,y-1) != '|' ||
|
||||
this.getTile(x,y+1) != '|' ||
|
||||
this.getTile(x-1,y-1) != '|' ||
|
||||
this.getTile(x-1,y+1) != '|' ||
|
||||
this.getTile(x+1,y-1) != '|' ||
|
||||
this.getTile(x+1,y+1) != '|')) {
|
||||
edges[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// walks along edge wall tiles starting at the given index to build a canvas path
|
||||
var makePath = function(tx,ty) {
|
||||
|
||||
// get initial direction
|
||||
var dir = {};
|
||||
var dirEnum;
|
||||
if (toIndex(tx+1,ty) in edges)
|
||||
dirEnum = DIR_RIGHT;
|
||||
else if (toIndex(tx, ty+1) in edges)
|
||||
dirEnum = DIR_DOWN;
|
||||
else
|
||||
throw "tile shouldn't be 1x1 at "+tx+","+ty;
|
||||
setDirFromEnum(dir,dirEnum);
|
||||
|
||||
// increment to next tile
|
||||
tx += dir.x;
|
||||
ty += dir.y;
|
||||
|
||||
// backup initial location and direction
|
||||
var init_tx = tx;
|
||||
var init_ty = ty;
|
||||
var init_dirEnum = dirEnum;
|
||||
|
||||
var path = [];
|
||||
var pad; // (persists for each call to getStartPoint)
|
||||
var point;
|
||||
var lastPoint;
|
||||
|
||||
var turn,turnAround;
|
||||
|
||||
/*
|
||||
|
||||
We employ the 'right-hand rule' by keeping our right hand in contact
|
||||
with the wall to outline an individual wall piece.
|
||||
|
||||
Since we parse the tiles in row major order, we will always start
|
||||
walking along the wall at the leftmost tile of its topmost row. We
|
||||
then proceed walking to the right.
|
||||
|
||||
When facing the direction of the walk at each tile, the outline will
|
||||
hug the left side of the tile unless there is a walkable tile to the
|
||||
left. In that case, there will be a padding distance applied.
|
||||
|
||||
*/
|
||||
var getStartPoint = function(tx,ty,dirEnum) {
|
||||
var dir = {};
|
||||
setDirFromEnum(dir, dirEnum);
|
||||
if (!(toIndex(tx+dir.y,ty-dir.x) in edges))
|
||||
pad = that.isFloorTile(tx+dir.y,ty-dir.x) ? 5 : 0;
|
||||
var px = -tileSize/2+pad;
|
||||
var py = tileSize/2;
|
||||
var a = dirEnum*Math.PI/2;
|
||||
var c = Math.cos(a);
|
||||
var s = Math.sin(a);
|
||||
return {
|
||||
// the first expression is the rotated point centered at origin
|
||||
// the second expression is to translate it to the tile
|
||||
x:(px*c - py*s) + (tx+0.5)*tileSize,
|
||||
y:(px*s + py*c) + (ty+0.5)*tileSize,
|
||||
};
|
||||
};
|
||||
while (true) {
|
||||
|
||||
visited[toIndex(tx,ty)] = true;
|
||||
|
||||
// determine start point
|
||||
point = getStartPoint(tx,ty,dirEnum);
|
||||
|
||||
if (turn) {
|
||||
// if we're turning into this tile, create a control point for the curve
|
||||
//
|
||||
// >---+ <- control point
|
||||
// |
|
||||
// V
|
||||
lastPoint = path[path.length-1];
|
||||
if (dir.x == 0) {
|
||||
point.cx = point.x;
|
||||
point.cy = lastPoint.y;
|
||||
}
|
||||
else {
|
||||
point.cx = lastPoint.x;
|
||||
point.cy = point.y;
|
||||
}
|
||||
}
|
||||
|
||||
// update direction
|
||||
turn = false;
|
||||
turnAround = false;
|
||||
if (toIndex(tx+dir.y, ty-dir.x) in edges) { // turn left
|
||||
dirEnum = (dirEnum+3)%4;
|
||||
turn = true;
|
||||
}
|
||||
else if (toIndex(tx+dir.x, ty+dir.y) in edges) { // continue straight
|
||||
}
|
||||
else if (toIndex(tx-dir.y, ty+dir.x) in edges) { // turn right
|
||||
dirEnum = (dirEnum+1)%4;
|
||||
turn = true;
|
||||
}
|
||||
else { // turn around
|
||||
dirEnum = (dirEnum+2)%4;
|
||||
turnAround = true;
|
||||
}
|
||||
setDirFromEnum(dir,dirEnum);
|
||||
|
||||
// commit path point
|
||||
path.push(point);
|
||||
|
||||
// special case for turning around (have to connect more dots manually)
|
||||
if (turnAround) {
|
||||
path.push(getStartPoint(tx-dir.x, ty-dir.y, (dirEnum+2)%4));
|
||||
path.push(getStartPoint(tx, ty, dirEnum));
|
||||
}
|
||||
|
||||
// advance to the next wall
|
||||
tx += dir.x;
|
||||
ty += dir.y;
|
||||
|
||||
// exit at full cycle
|
||||
if (tx==init_tx && ty==init_ty && dirEnum == init_dirEnum) {
|
||||
that.paths.push(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// iterate through all edges, making a new path after hitting an unvisited wall edge
|
||||
i=0;
|
||||
for (y=0;y<this.numRows;y++)
|
||||
for (x=-2;x<this.numCols+2;x++,i++)
|
||||
if (i in edges && !(i in visited)) {
|
||||
visited[i] = true;
|
||||
makePath(x,y);
|
||||
}
|
||||
};
|
||||
|
||||
Map.prototype.posToIndex = function(x,y) {
|
||||
if (x>=0 && x<this.numCols && y>=0 && y<this.numRows)
|
||||
return x+y*this.numCols;
|
||||
};
|
||||
// retrieves tile character at given coordinate
|
||||
// extended to include offscreen tunnel space
|
||||
Map.prototype.getTile = function(x,y) {
|
||||
if (x>=0 && x<this.numCols && y>=0 && y<this.numRows)
|
||||
return this.currentTiles[this.posToIndex(x,y)];
|
||||
|
||||
// extend walls and paths outward for entrances and exits
|
||||
if ((x==-1 && this.getTile(x+1,y)=='|' && (this.isFloorTile(x+1,y+1)||this.isFloorTile(x+1,y-1))) ||
|
||||
(x==this.numCols && this.getTile(x-1,y)=='|' && (this.isFloorTile(x-1,y+1)||this.isFloorTile(x-1,y-1))))
|
||||
return '|';
|
||||
if ((x==-1 && this.isFloorTile(x+1,y)) ||
|
||||
(x==this.numCols && this.isFloorTile(x-1,y)))
|
||||
return ' ';
|
||||
};
|
||||
|
||||
// determines if the given character is a walkable floor tile
|
||||
Map.prototype.isFloorTileChar = function(tile) {
|
||||
return tile==' ' || tile=='.' || tile=='o';
|
||||
};
|
||||
|
||||
// determines if the given tile coordinate has a walkable floor tile
|
||||
Map.prototype.isFloorTile = function(x,y) {
|
||||
return this.isFloorTileChar(this.getTile(x,y));
|
||||
};
|
||||
|
||||
// function to draw the map as a tile map
|
||||
Map.prototype.draw = function(ctx,left,top,print) {
|
||||
|
||||
// save state
|
||||
ctx.save();
|
||||
ctx.translate(0.5,0.5); // pixel perfect lines?
|
||||
|
||||
// translate to the position of the map
|
||||
ctx.translate(left,top);
|
||||
|
||||
// clip the drawing surface
|
||||
ctx.beginPath();
|
||||
ctx.rect(0,0,this.widthPixels, this.heightPixels);
|
||||
ctx.clip();
|
||||
if (!print) {
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillRect(0,0,this.widthPixels, this.heightPixels);
|
||||
}
|
||||
|
||||
// set colors
|
||||
ctx.fillStyle = print?"#333":this.wallFillColor;
|
||||
ctx.strokeStyle = print?"#333":this.wallStrokeColor;
|
||||
|
||||
var x,y;
|
||||
var i,j;
|
||||
var tile;
|
||||
|
||||
for (i=0; i<this.paths.length; i++) {
|
||||
var path = this.paths[i];
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(path[0].x, path[0].y);
|
||||
for (j=1; j<path.length; j++) {
|
||||
if (path[j].cx != undefined)
|
||||
ctx.quadraticCurveTo(path[j].cx, path[j].cy, path[j].x, path[j].y);
|
||||
else
|
||||
ctx.lineTo(path[j].x, path[j].y);
|
||||
}
|
||||
ctx.quadraticCurveTo(path[j-1].x, path[0].y, path[0].x, path[0].y);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// draw pellets for each path tile
|
||||
var pelletSize = print?tileSize:2;
|
||||
var energizerSize = 3;
|
||||
for (y=0; y<this.numRows; y++) {
|
||||
for (x=0; x<this.numCols; x++) {
|
||||
var t = this.getTile(x,y);
|
||||
if (t=='o' || t=='.' || t==' ') {
|
||||
ctx.fillStyle=print?"#bbb":this.pelletColor;
|
||||
ctx.fillRect(
|
||||
x*tileSize+tileSize/2-pelletSize/2,
|
||||
y*tileSize+tileSize/2-pelletSize/2,
|
||||
pelletSize,pelletSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw grid
|
||||
ctx.strokeStyle=print?"rgba(0,0,0,0.3)":"rgba(255,255,255,0.3)";
|
||||
ctx.beginPath();
|
||||
for (y=0; y<=this.numRows; y++) {
|
||||
ctx.moveTo(0,y*tileSize);
|
||||
ctx.lineTo(this.widthPixels,y*tileSize);
|
||||
}
|
||||
for (x=0; x<=this.numCols; x++) {
|
||||
ctx.moveTo(x*tileSize,0);
|
||||
ctx.lineTo(x*tileSize,this.heightPixels);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// draw title
|
||||
if (this.name) {
|
||||
ctx.textBaseline = "top";
|
||||
ctx.font = "20px sans-serif";
|
||||
ctx.fillStyle = print?"#000":"#fff";
|
||||
ctx.fillText(this.name, 0,tileSize/2);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
};
|
||||
|
||||
// function to draw the map using simple representation of the paths as straight lines
|
||||
Map.prototype.drawPath = function(ctx,left,top) {
|
||||
var print = true;
|
||||
|
||||
// save state
|
||||
ctx.save();
|
||||
ctx.translate(0.5,0.5); // pixel perfect lines?
|
||||
|
||||
// translate to the position of the map
|
||||
ctx.translate(left,top);
|
||||
|
||||
// clip the drawing surface
|
||||
ctx.beginPath();
|
||||
ctx.rect(0,0,this.widthPixels, this.heightPixels);
|
||||
ctx.clip();
|
||||
|
||||
var x,y;
|
||||
var i,j;
|
||||
var tile;
|
||||
|
||||
// draw pellets for each path tile
|
||||
ctx.lineWidth = 2.0;
|
||||
ctx.strokeStyle="rgba(0,0,0,0.8)";
|
||||
ctx.beginPath();
|
||||
for (y=0; y<this.numRows-1; y++) {
|
||||
for (x=0; x<this.numCols-1; x++) {
|
||||
if (this.isFloorTile(x,y)) {
|
||||
if (this.isFloorTile(x+1,y)) {
|
||||
ctx.moveTo(x*tileSize,y*tileSize);
|
||||
ctx.lineTo((x+1)*tileSize,y*tileSize);
|
||||
}
|
||||
if (this.isFloorTile(x,y+1)) {
|
||||
ctx.moveTo(x*tileSize,y*tileSize);
|
||||
ctx.lineTo(x*tileSize,(y+1)*tileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// draw grid
|
||||
ctx.lineWidth = 1.0;
|
||||
ctx.strokeStyle=print?"rgba(0,0,0,0.3)":"rgba(255,255,255,0.3)";
|
||||
ctx.beginPath();
|
||||
for (y=0; y<this.numRows; y++) {
|
||||
ctx.moveTo(0,y*tileSize);
|
||||
ctx.lineTo(this.widthPixels-tileSize,y*tileSize);
|
||||
}
|
||||
for (x=0; x<this.numCols; x++) {
|
||||
ctx.moveTo(x*tileSize,0);
|
||||
ctx.lineTo(x*tileSize,this.heightPixels-tileSize);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// draw title
|
||||
if (this.name) {
|
||||
ctx.fillStyle = print?"#000":"#fff";
|
||||
ctx.font = "20px sans-serif";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillText(this.name, 0,tileSize/2);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
# Generate Pac-Man Mazes using Tetris-stacking
|
||||
|
||||
## Progress
|
||||
|
||||
This is a sample of the current generating model:
|
||||
|
||||
<img src="working.png" />
|
||||
|
||||
[Click here to view the actual demo.](http://shaunlebron.github.com/pacman-mazegen)
|
||||
|
||||
## Summary
|
||||
|
||||
In the pursuit of a simple maze generator for Pac-Man, we first visualize the
|
||||
structure of the original Pac-Man maps as a tiling of blocks. Then, we attempt
|
||||
to simplify this structure by lowering its resolution while still maintaining features.
|
||||
(The maps are symmetric, so only the middle to the right half are shown.)
|
||||
|
||||
The first row shows the simplified representations. The second row shows the
|
||||
edits to each cell that must be performed after upscaling by a factor of 3.
|
||||
A blue down arrow means the cell's height must be increased by 1. A red left
|
||||
arrow means the cell's width must be decreased by 1.
|
||||
|
||||
<img src="simplify.png" />
|
||||
|
||||
We propose that one may generate a random simplified map (phase 1), then transform
|
||||
it to a correctly sized map by upscaling and applying some clever
|
||||
shifting/resizing of a few key wall segments (phase 2).
|
||||
|
||||
## Contents
|
||||
|
||||
* index.htm currently displays a demo of random simple maps using mapgen.js.
|
||||
* drawpresets.htm draws the simplified versions of the original maps.
|
||||
@@ -0,0 +1,149 @@
|
||||
// source: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
|
||||
|
||||
/**
|
||||
* Converts an RGB color value to HSL. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||
* returns h, s, and l in the set [0, 1].
|
||||
*
|
||||
* @param Number r The red color value
|
||||
* @param Number g The green color value
|
||||
* @param Number b The blue color value
|
||||
* @return Array The HSL representation
|
||||
*/
|
||||
function rgbToHsl(r, g, b){
|
||||
r /= 255, g /= 255, b /= 255;
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h, s, l = (max + min) / 2;
|
||||
|
||||
if(max == min){
|
||||
h = s = 0; // achromatic
|
||||
}else{
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch(max){
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return [h, s, l];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||
* returns r, g, and b in the set [0, 255].
|
||||
*
|
||||
* @param Number h The hue
|
||||
* @param Number s The saturation
|
||||
* @param Number l The lightness
|
||||
* @return Array The RGB representation
|
||||
*/
|
||||
function hslToRgb(h, s, l){
|
||||
var r, g, b;
|
||||
|
||||
if(s == 0){
|
||||
r = g = b = l; // achromatic
|
||||
}else{
|
||||
function hue2rgb(p, q, t){
|
||||
if(t < 0) t += 1;
|
||||
if(t > 1) t -= 1;
|
||||
if(t < 1/6) return p + (q - p) * 6 * t;
|
||||
if(t < 1/2) return q;
|
||||
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||
return p;
|
||||
}
|
||||
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1/3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1/3);
|
||||
}
|
||||
|
||||
r *= 255;
|
||||
g *= 255;
|
||||
b *= 255;
|
||||
|
||||
return [r,g,b];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an RGB color value to HSV. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSV_color_space.
|
||||
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||
* returns h, s, and v in the set [0, 1].
|
||||
*
|
||||
* @param Number r The red color value
|
||||
* @param Number g The green color value
|
||||
* @param Number b The blue color value
|
||||
* @return Array The HSV representation
|
||||
*/
|
||||
function rgbToHsv(r, g, b){
|
||||
r = r/255, g = g/255, b = b/255;
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h, s, v = max;
|
||||
|
||||
var d = max - min;
|
||||
s = max == 0 ? 0 : d / max;
|
||||
|
||||
if(max == min){
|
||||
h = 0; // achromatic
|
||||
}else{
|
||||
switch(max){
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return [h, s, v];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSV color value to RGB. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSV_color_space.
|
||||
* Assumes h, s, and v are contained in the set [0, 1] and
|
||||
* returns r, g, and b in the set [0, 255].
|
||||
*
|
||||
* @param Number h The hue
|
||||
* @param Number s The saturation
|
||||
* @param Number v The value
|
||||
* @return Array The RGB representation
|
||||
*/
|
||||
function hsvToRgb(h, s, v){
|
||||
var r, g, b;
|
||||
|
||||
var i = Math.floor(h * 6);
|
||||
var f = h * 6 - i;
|
||||
var p = v * (1 - s);
|
||||
var q = v * (1 - f * s);
|
||||
var t = v * (1 - (1 - f) * s);
|
||||
|
||||
switch(i % 6){
|
||||
case 0: r = v, g = t, b = p; break;
|
||||
case 1: r = q, g = v, b = p; break;
|
||||
case 2: r = p, g = v, b = t; break;
|
||||
case 3: r = p, g = q, b = v; break;
|
||||
case 4: r = t, g = p, b = v; break;
|
||||
case 5: r = v, g = p, b = q; break;
|
||||
}
|
||||
|
||||
r *= 255;
|
||||
g *= 255;
|
||||
b *= 255;
|
||||
|
||||
return [r,g,b];
|
||||
}
|
||||
|
||||
function rgbString(rgb) {
|
||||
var r = Math.floor(rgb[0]);
|
||||
var g = Math.floor(rgb[1]);
|
||||
var b = Math.floor(rgb[2]);
|
||||
return 'rgb('+r+','+g+','+b+')';
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
var UP = 0;
|
||||
var RIGHT = 1;
|
||||
var DOWN = 2;
|
||||
var LEFT = 3;
|
||||
|
||||
var rows = 9;
|
||||
var cols = 5;
|
||||
var createCells = function() {
|
||||
var cells = {};
|
||||
for (i=0; i<rows*cols; i++) {
|
||||
cells[i] = {
|
||||
x: i%cols,
|
||||
y: Math.floor(i/cols),
|
||||
filled: false,
|
||||
connect: [true, true, true, true],
|
||||
colors: {},
|
||||
next: [],
|
||||
no: undefined,
|
||||
group: undefined,
|
||||
};
|
||||
}
|
||||
for (i=0; i<rows*cols; i++) {
|
||||
var c = cells[i];
|
||||
if (c.x > 0)
|
||||
c.next[LEFT] = cells[i-1];
|
||||
if (c.x < cols - 1)
|
||||
c.next[RIGHT] = cells[i+1];
|
||||
if (c.y > 0)
|
||||
c.next[UP] = cells[i-cols];
|
||||
if (c.y < rows - 1)
|
||||
c.next[DOWN] = cells[i+cols];
|
||||
}
|
||||
return {
|
||||
makeWalls: function(walls, color) {
|
||||
color = color || "#000";
|
||||
var wallLen = walls.length;
|
||||
var i,j,x,y,dir;
|
||||
var wall;
|
||||
for (i=0; i<wallLen; i++) {
|
||||
wall = walls[i];
|
||||
var row = wall[0];
|
||||
var col = wall[1];
|
||||
var dir = wall[2];
|
||||
if (dir.length) {
|
||||
var dirlen = dir.length;
|
||||
for (j=0; j<dirlen; j++) {
|
||||
this.makeWall(row,col,dir[j], color);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.makeWall(row,col,dir, color);
|
||||
}
|
||||
}
|
||||
},
|
||||
makeWall: function(row, col, dir, color) {
|
||||
color = color || "#000";
|
||||
var cell = cells[col+row*cols];
|
||||
cell.connect[dir] = false;
|
||||
cell.colors[dir] = color;
|
||||
cell.next[dir].connect[(dir+2)%4] = false;
|
||||
cell.next[dir].colors[(dir+2)%4] = color;
|
||||
if (cell.x == 0 && dir == RIGHT) {
|
||||
cell.connect[LEFT] = false;
|
||||
cell.colors[LEFT] = color;
|
||||
}
|
||||
},
|
||||
display: function(ctx,x,y) {
|
||||
var canvas = document.createElement('canvas');
|
||||
var pad = 5;
|
||||
var size = 40;
|
||||
canvas.width = size*cols+pad*2;
|
||||
canvas.height = size*rows+pad*2;
|
||||
document.getElementById('container').appendChild(canvas);
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = "#FFF";
|
||||
ctx.fillRect(0,0,canvas.width,canvas.height);
|
||||
drawCells(ctx,cells,pad,pad,size);
|
||||
},
|
||||
};
|
||||
};
|
||||
var drawCells = function(ctx,cells,left,top,size,title,options) {
|
||||
|
||||
title = title || "";
|
||||
options = options || {};
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(left,top);
|
||||
|
||||
// draw title
|
||||
ctx.font = "bold " + size/3 + "px sans-serif";
|
||||
ctx.textBaseline = "bottom";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(title, 0, -5);
|
||||
|
||||
ctx.beginPath();
|
||||
for (y=0; y<=rows; y++) {
|
||||
ctx.moveTo(0,y*size);
|
||||
ctx.lineTo(cols*size,y*size);
|
||||
}
|
||||
for (x=0; x<=cols; x++) {
|
||||
ctx.moveTo(x*size,0);
|
||||
ctx.lineTo(x*size,rows*size);
|
||||
}
|
||||
ctx.lineWidth = "1";
|
||||
ctx.strokeStyle = "#AAA";
|
||||
ctx.stroke();
|
||||
|
||||
// set cell number font
|
||||
ctx.font = size/3 + "px sans-serif";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.textAlign = "center";
|
||||
|
||||
var arrowsize = size/6;
|
||||
|
||||
ctx.lineWidth = "3";
|
||||
for (i=0; i<cols*rows; i++) {
|
||||
var c = cells[i];
|
||||
var x = i % cols;
|
||||
var y = Math.floor(i / cols);
|
||||
|
||||
if (options.drawRaiseHeightCandidate && c.isRaiseHeightCandidate) {
|
||||
ctx.fillStyle = "rgba(0,0,255,0.2)";
|
||||
ctx.fillRect(x*size,y*size,size,size);
|
||||
}
|
||||
|
||||
if (options.drawShrinkWidthCandidate && c.isShrinkWidthCandidate) {
|
||||
ctx.fillStyle = "rgba(255,0,0,0.2)";
|
||||
ctx.fillRect(x*size,y*size,size,size);
|
||||
}
|
||||
|
||||
if (options.drawJoinCandidate && c.isJoinCandidate) {
|
||||
ctx.fillStyle = "rgba(0,255,0,0.2)";
|
||||
ctx.fillRect(x*size,y*size,size,size);
|
||||
}
|
||||
|
||||
if (options.drawSingleDeadEnd && c.isSingleDeadEndCandidate) {
|
||||
ctx.fillStyle = "rgba(255,255,0,0.4)";
|
||||
ctx.fillRect(x*size,y*size,size,size);
|
||||
}
|
||||
|
||||
if (options.drawDoubleDeadEnd && c.isDoubleDeadEndCandidate) {
|
||||
ctx.fillStyle = "rgba(0,255,255,0.2)";
|
||||
ctx.fillRect(x*size,y*size,size,size);
|
||||
}
|
||||
|
||||
if (options.drawVoidTunnel && c.isVoidTunnelCandidate) {
|
||||
ctx.fillStyle = "rgba(0,0,0,0.2)";
|
||||
ctx.fillRect(x*size,y*size,size,size);
|
||||
}
|
||||
|
||||
if (options.drawChosenTunnel && c.topTunnel) {
|
||||
ctx.beginPath();
|
||||
ctx.save();
|
||||
ctx.translate(x*size+size/2,y*size+5);
|
||||
ctx.moveTo(-arrowsize,arrowsize);
|
||||
ctx.lineTo(0,0);
|
||||
ctx.lineTo(arrowsize,arrowsize);
|
||||
ctx.strokeStyle = "rgba(0,255,0,0.7)";
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
else if (options.drawEdgeTunnel && c.isEdgeTunnelCandidate) {
|
||||
ctx.beginPath();
|
||||
ctx.save();
|
||||
ctx.translate(x*size+size/2,y*size+5);
|
||||
ctx.moveTo(-arrowsize,arrowsize);
|
||||
ctx.lineTo(0,0);
|
||||
ctx.lineTo(arrowsize,arrowsize);
|
||||
ctx.strokeStyle = "rgba(0,0,0,0.7)";
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if (options.drawRaiseHeight && c.raiseHeight) {
|
||||
ctx.beginPath();
|
||||
ctx.save();
|
||||
ctx.translate(x*size+size/2,y*size+size-arrowsize);
|
||||
ctx.moveTo(-arrowsize,-arrowsize);
|
||||
ctx.lineTo(0,0);
|
||||
ctx.lineTo(arrowsize,-arrowsize);
|
||||
ctx.strokeStyle = "rgba(0,0,255,0.7)";
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if (options.drawShrinkWidth && c.shrinkWidth) {
|
||||
ctx.beginPath();
|
||||
ctx.save();
|
||||
ctx.translate(x*size+size-arrowsize-arrowsize,y*size+size/2);
|
||||
ctx.moveTo(arrowsize,-arrowsize);
|
||||
ctx.lineTo(0,0);
|
||||
ctx.lineTo(arrowsize,arrowsize);
|
||||
ctx.restore();
|
||||
ctx.strokeStyle = "rgba(255,0,0,0.7)";
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// draw cell number (order)
|
||||
if (options.drawNumbers && c.no != undefined) {
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillText(c.no, x*size+size/2, y*size+size/2);
|
||||
}
|
||||
}
|
||||
|
||||
var i;
|
||||
for (i=0; i<cols*rows; i++) {
|
||||
var c = cells[i];
|
||||
var x = i % cols;
|
||||
var y = Math.floor(i / cols);
|
||||
|
||||
// draw walls
|
||||
ctx.lineWidth = "3";
|
||||
ctx.lineCap = 'round';
|
||||
if (!c.connect[UP]) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x*size, y*size);
|
||||
ctx.lineTo(x*size+size, y*size);
|
||||
ctx.strokeStyle = c.colors[UP];
|
||||
ctx.stroke();
|
||||
}
|
||||
if (!c.connect[DOWN]) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x*size, y*size+size);
|
||||
ctx.lineTo(x*size+size, y*size+size);
|
||||
ctx.strokeStyle = c.colors[DOWN];
|
||||
ctx.stroke();
|
||||
}
|
||||
if (!c.connect[LEFT]) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x*size, y*size);
|
||||
ctx.lineTo(x*size, y*size+size);
|
||||
ctx.strokeStyle = c.colors[LEFT];
|
||||
ctx.stroke();
|
||||
}
|
||||
if (!c.connect[RIGHT]) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x*size+size, y*size);
|
||||
ctx.lineTo(x*size+size, y*size+size);
|
||||
ctx.strokeStyle = c.colors[RIGHT];
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background: #DDD;
|
||||
}
|
||||
#container {
|
||||
padding-left: 40px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
padding-right: 40px;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
background: #FFF;
|
||||
width: 500px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='container'>
|
||||
<h1>Pac-Man Maze Generation</h1>
|
||||
|
||||
<p>We start with the following 5x9 grid of tiles:</p>
|
||||
|
||||
<script>
|
||||
var cells = createCells();
|
||||
cells.makeWalls([
|
||||
[3,0,UP],
|
||||
[3,1,[UP,RIGHT]],
|
||||
[4,1,[DOWN,RIGHT]],
|
||||
[4,0,DOWN],
|
||||
],"#F00");
|
||||
cells.makeWalls([
|
||||
[6,0,DOWN]
|
||||
],"#00F");
|
||||
cells.display();
|
||||
</script>
|
||||
|
||||
<p>In <span style="color:red">red</span> is the path around the ghost pen.
|
||||
In <span style="color:blue">blue</span> is Pac-Man's starting position.</p>
|
||||
|
||||
<p>
|
||||
We then proceed by playing tetris. Pieces fall to the left-hand side.
|
||||
No gaps can be made.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
The edges create paths inside the maze. We erase some of them on the
|
||||
border for variation.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="mapgen.js"></script>
|
||||
<script src="colors.js"></script>
|
||||
<script src="Map.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<button onclick="draw()">Click to generate new maps</button>
|
||||
</p>
|
||||
|
||||
<canvas id='canvas'></canvas>
|
||||
|
||||
<script>
|
||||
var draw = function() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
|
||||
var rows = 4;
|
||||
var cols = 4;
|
||||
var w,h;
|
||||
w = 28*8;
|
||||
h = 36*8;
|
||||
canvas.width = w*cols;
|
||||
canvas.height = h*rows;
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var map;
|
||||
var x,y;
|
||||
for (y=0; y<rows; y++) {
|
||||
for (x=0; x<cols; x++) {
|
||||
map = mapgen();
|
||||
map.draw(ctx,x*w,y*h);
|
||||
}
|
||||
}
|
||||
|
||||
//drawResult(ctx,(cols+2)*size,size,size);
|
||||
//drawResult2(ctx,(2*cols+3)*size,size,size);
|
||||
//drawTiles(ctx,(cols+2)*size,size,size);
|
||||
|
||||
};
|
||||
|
||||
window.onload = function() {
|
||||
draw();
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 23 KiB |