import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.net.*;

/**
 * Program execution begins with the init() method. 
 */
public class Lattice extends Applet implements ActionListener, KeyListener
{
	int			m_rgiLevel[];
	Dimension	m_cdim;
	Image		m_imgRed;
	Image		m_imgYellow;
	Image		m_imgCyan;
	Image		m_imgSpheres[];
	CMatrix		m_cmx;
	Panel		m_cpnlControls;
	Panel		m_cpnlHighlights;
	Panel		m_cpnlOps;
	String		m_strLabSolid = "Solid Lines";
	String		m_strLabThin = "Thin Lines";
	String		m_strLabXY = "Show X-Y Plane";
	String		m_strLabXZ = "Show X-Z Plane";
	String		m_strLabYZ = "Show Y-Z Plane";
	String		m_strLabStart = "Restart";
	String		m_strLabStep = "Step";
	String		m_strLabTest = "Test";
	String		m_strLabNext = "Next matrix";
	String		m_strLabA = "A";
	String		m_strLabB = "B";
	String		m_strLabC = "C";
	String		m_strLabD = "D";
	String		m_strLabE = "E";
	String		m_strLabF = "F";
	String		m_strLabG = "G";
	String		m_strLabO = "Null";
	String		m_strLabLeft = "Left";
	String		m_strLabRight = "Right";
	String		m_strLabUp = "Up";
	String		m_strLabDown = "Down";
	String		m_strLabAnti = "Anti";
	String		m_strLabClock = "Clock";
	final Color	m_clrButtons = Color.lightGray;
	final Color m_clrText = Color.black;
	Button		m_cbtnStart;
	Button		m_cbtnStep;
	Button		m_cbtnTest;
	Button		m_cbtnStop;
	Button		m_cbtnLines;
	Button		m_cbtnX_Plane;
	Button		m_cbtnY_Plane;
	Button		m_cbtnZ_Plane;
	Button		m_cbtnA_G[];
	Button		m_cbtn0_6[];
	Button		m_cbtnOrderings[];
	byte		m_rgbyDecode[];		// mapping of operations onto Letters
	int			m_iCurLetter = 0;	// indicates last letter button press

	/*
	 * The entry point for the applet. 
	 */
	public void init()
	{
		m_rgiLevel = new int[1];
		m_rgiLevel[0] = 0;
		setBackground(m_clrButtons);
		setForeground(m_clrText);
		URL urlCodeBase = getCodeBase();
		String strImageRed = "Sphred.gif";
		String strImageYellow = "Sphyell.gif";
		String strImageCyan = "Sphcyan.gif";
		m_imgRed = this.getImage(urlCodeBase, strImageRed);
		m_imgYellow = this.getImage(urlCodeBase, strImageYellow);
		m_imgCyan = this.getImage(urlCodeBase, strImageCyan);
		m_imgSpheres = new Image[3];
		m_imgSpheres[0] = m_imgRed;
		m_imgSpheres[1] = m_imgCyan;
		m_imgSpheres[2] = m_imgYellow;
		MediaTracker tracker = new MediaTracker(this);
		tracker.addImage(m_imgRed, 1);
		tracker.addImage(m_imgYellow, 2);
		tracker.addImage(m_imgCyan, 3);
		m_cdim = getSize();
		m_cpnlControls = new Panel();
		m_cpnlControls.setBackground(m_clrButtons);
		m_cpnlControls.setForeground(m_clrText);
		m_cpnlControls.setLayout(new GridLayout(1, 4));
		m_cbtnStart = new Button(m_strLabStart);
		m_cbtnStart.setBackground(m_clrButtons);
		m_cbtnStart.setForeground(m_clrText);
		m_cpnlControls.add(m_cbtnStart);
		m_cbtnStep = new Button(m_strLabStep);
		m_cbtnStep.setBackground(m_clrButtons);
		m_cbtnStep.setForeground(m_clrText);
		m_cpnlControls.add(m_cbtnStep);
		m_cbtnTest = new Button(m_strLabTest);
		m_cbtnTest.setBackground(m_clrButtons);
		m_cbtnTest.setForeground(m_clrText);
		m_cpnlControls.add(m_cbtnTest);
		m_cbtnStop = new Button(m_strLabNext);
		m_cbtnStop.setBackground(m_clrButtons);
		m_cbtnStop.setForeground(m_clrText);
		m_cpnlControls.add(m_cbtnStop);
		m_cpnlHighlights = new Panel();
		m_cpnlHighlights.setBackground(m_clrButtons);
		m_cpnlHighlights.setForeground(m_clrText);
		m_cpnlHighlights.setLayout(new GridLayout(1, 4));
		m_cbtnLines = new Button(m_strLabThin);
		m_cbtnLines.setBackground(m_clrButtons);
		m_cbtnLines.setForeground(m_clrText);
		m_cpnlHighlights.add(m_cbtnLines);
		m_cbtnX_Plane = new Button(m_strLabXY);
		m_cbtnX_Plane.setBackground(m_clrButtons);
		m_cbtnX_Plane.setForeground(m_clrText);
		m_cpnlHighlights.add(m_cbtnX_Plane);
		m_cbtnY_Plane = new Button(m_strLabXZ);
		m_cbtnY_Plane.setBackground(m_clrButtons);
		m_cbtnY_Plane.setForeground(m_clrText);
		m_cpnlHighlights.add(m_cbtnY_Plane);
		m_cbtnZ_Plane = new Button(m_strLabYZ);
		m_cbtnZ_Plane.setBackground(m_clrButtons);
		m_cbtnZ_Plane.setForeground(m_clrText);
		m_cpnlHighlights.add(m_cbtnZ_Plane);
		//m_cpnlLabels = new Panel();
		//m_cpnlLabels.setBackground(m_clrButtons);
		//m_cpnlLabels.setForeground(m_clrText);
		//m_cpnlLabels.setLayout(new GridLayout(7, 1));
		m_cbtnA_G = new Button[7];
		m_cbtnA_G[0] = new Button(m_strLabA);
		m_cbtnA_G[1] = new Button(m_strLabB);
		m_cbtnA_G[2] = new Button(m_strLabC);
		m_cbtnA_G[3] = new Button(m_strLabD);
		m_cbtnA_G[4] = new Button(m_strLabE);
		m_cbtnA_G[5] = new Button(m_strLabF);
		m_cbtnA_G[6] = new Button(m_strLabG);

		m_cpnlOps = new Panel();
		m_cpnlOps.setBackground(m_clrButtons);
		m_cpnlOps.setForeground(m_clrText);
		m_cpnlOps.setLayout(new GridLayout(7, 3));
		m_cbtnOrderings = new Button[7];
		m_cbtn0_6 = new Button[7];
		m_cbtn0_6[0] = new Button(m_strLabO);
		m_cbtn0_6[1] = new Button(m_strLabLeft);
		m_cbtn0_6[2] = new Button(m_strLabRight);
		m_cbtn0_6[3] = new Button(m_strLabUp);
		m_cbtn0_6[4] = new Button(m_strLabDown);
		m_cbtn0_6[5] = new Button(m_strLabAnti);
		m_cbtn0_6[6] = new Button(m_strLabClock);
		
		int i;

		for (i = 0; i < 7; i++)
		{
			m_cbtnOrderings[i] = new Button();
			m_cbtnOrderings[i].setBackground(m_clrButtons);
			m_cbtnOrderings[i].setForeground(m_clrText);
			m_cpnlOps.add(m_cbtnOrderings[i]);
			m_cbtnA_G[i].setBackground(m_clrButtons);
			m_cbtnA_G[i].setForeground(m_clrText);
			m_cpnlOps.add(m_cbtnA_G[i]);
			m_cbtn0_6[i].setBackground(m_clrButtons);
			m_cbtn0_6[i].setForeground(m_clrText);
			m_cpnlOps.add(m_cbtn0_6[i]);
		}

		this.setBackground(Color.black);
		setLayout(new BorderLayout());
		add("North", m_cpnlHighlights);
		add("South", m_cpnlControls);
		add("West", m_cpnlOps);
		validate();
		m_cdim.height -= m_cpnlControls.getSize().height;
		m_cdim.height -= m_cpnlHighlights.getSize().height;
		m_cdim.width -= m_cpnlOps.getSize().width;
		m_cbtnStart.addActionListener(this);
		m_cbtnStep.addActionListener(this);
		m_cbtnTest.addActionListener(this);
		m_cbtnStop.addActionListener(this);
		m_cbtnLines.addActionListener(this);
		m_cbtnX_Plane.addActionListener(this);
		m_cbtnY_Plane.addActionListener(this);
		m_cbtnZ_Plane.addActionListener(this);

		for (i = 0; i < 7; i++)
		{
			m_cbtnA_G[i].addActionListener(this);
		}

		for (i = 0; i < 7; i++)
		{
			m_cbtn0_6[i].addActionListener(this);
		}

		try
		{
			tracker.waitForAll();
		}
		catch (InterruptedException e)
		{
			;
		}
		
		if (tracker.isErrorAny())
		{
			this.showStatus("Error loading image");
		}
		
		m_rgbyDecode = new byte[Op.nOps];

		for (i = 0; i < Op.nOps; i++)
		{
			m_rgbyDecode[i] = (byte)i;		// set all actions different
		}

		m_cmx = new CMatrix(m_cdim, m_imgSpheres, m_rgbyDecode);
		add("East", m_cmx);
		m_cmx.addKeyListener(this);
	}

	public void keyPressed(KeyEvent e)
	{
		if ((m_rgiLevel[0] > 0) && (m_cmx != null))
		{
			m_cmx.keyPressed(e);
		}
	}

	public void keyReleased(KeyEvent e)
	{
		if ((m_rgiLevel[0] > 0) && (m_cmx != null))
		{
			m_cmx.keyReleased(e);
		}
	}

	public void keyTyped(KeyEvent e)
	{
		if ((m_rgiLevel[0] > 0) && (m_cmx != null))
		{
			m_cmx.keyTyped(e);
		}
	}

	public void paint(Graphics gc)
	{
		m_cmx.paint(gc);
	}

	public void actionPerformed(ActionEvent ev)
	{
		String str = ev.getActionCommand();
		m_cmx.requestFocus();
		int i;

		if (str.equals(m_strLabStart))
		{
			if (m_rgiLevel[0] == 0)
			{
				m_rgiLevel[0] = 1;
			}

			m_cmx.Restart(m_rgiLevel[0], Patterns.m_strPat[m_rgiLevel[0]]);
		}
		else if (str.equals(m_strLabNext))
		{
			if (++m_rgiLevel[0] >= Patterns.m_strPat.length)
			{
				m_rgiLevel[0] = 0;
			}

			m_cmx.Restart(m_rgiLevel[0], Patterns.m_strPat[m_rgiLevel[0]]);
		}
		else if (str.equals(m_strLabSolid))
		{
			m_cbtnLines.setLabel(m_strLabThin);
			m_cmx.Solid(true);
		}
		else if (str.equals(m_strLabThin))
		{
			m_cbtnLines.setLabel(m_strLabSolid);
			m_cmx.Solid(false);
		}
		else if ((str.equals(m_strLabXY)) && (m_rgiLevel[0] > 0))
		{
			m_cmx.Highlight('Z');
		}
		else if ((str.equals(m_strLabXZ)) && (m_rgiLevel[0] > 0))
		{
			m_cmx.Highlight('Y');
		}
		else if ((str.equals(m_strLabYZ)) && (m_rgiLevel[0] > 0))
		{
			m_cmx.Highlight('X');
		}
		else if ((str.equals(m_strLabStep)) && (m_rgiLevel[0] > 0))
		{
			m_cmx.Step();
		}
		else if ((str.equals(m_strLabTest)) && (m_rgiLevel[0] > 0))
		{
			m_cmx.Test();
		}
		else if (str.equals(m_strLabA))
		{
			m_iCurLetter = 0;
		}
		else if (str.equals(m_strLabB))
		{
			m_iCurLetter = 1;
		}
		else if (str.equals(m_strLabC))
		{
			m_iCurLetter = 2;
		}
		else if (str.equals(m_strLabD))
		{
			m_iCurLetter = 3;
		}
		else if (str.equals(m_strLabE))
		{
			m_iCurLetter = 4;
		}
		else if (str.equals(m_strLabF))
		{
			m_iCurLetter = 5;
		}
		else if (str.equals(m_strLabG))
		{
			m_iCurLetter = 6;
		}
		else
		{
			// see if an operator is being associated with a new letter
			String strHold = null;

			for (i = 0; i < 7; i++)
			{
				strHold = m_cbtn0_6[i].getLabel();

				if (str.equals(strHold))
				{
					break;
				}
			}
			
			if (i < 7)
			{
				if (i != m_iCurLetter)
				{
					// swap labels and Decode map entries
					m_cbtn0_6[i].setLabel(m_cbtn0_6[m_iCurLetter].getLabel());
					m_cbtn0_6[m_iCurLetter].setLabel(strHold);
					byte byHold = m_rgbyDecode[i];
					m_rgbyDecode[i] = m_rgbyDecode[m_iCurLetter];
					m_rgbyDecode[m_iCurLetter] = byHold;
					m_cmx.repaint();
					m_cmx.Test();
				}
			}
		}
		
		byte orderings[] = m_cmx.getOrderings();

		if (orderings != null)
		{
			for (i = 0; i < 7; i++)
			{
				String [] labels =
					{ "-", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
				m_cbtnOrderings[i].setLabel(labels[orderings[i] + 1]);
			}
		}
	}
}

class Patterns
{
	static final String m_strPat[] =
	{
		"",
		"ABCDEFGGFEDCBAACEGBDFCDGFAE11210002410302",
		"BCFAGDEECGDABEEGBADCFGCFADB13204112421022",
		"FGDABCEGDACFEBBDCAFBEDCABGF1100422",
		"ADBACGAEEAFEBGCEGFDADADCBGG11110022021311"
	};
}

// Headings and orientations
class Dir
{
	// the values of the six directions below are used
	// to index the array CMatrix.m_rgrgrgSS
	static final byte	North = 0;
	static final byte	East = 1;
	static final byte	Up = 2;
	static final byte	South = 3;
	static final byte	West = 4;
	static final byte	Down = 5;
	static final int	nDirections = 6;

	static final byte	NU	= 0;
	static final byte	NE	= 1;
	static final byte	ND	= 2;
	static final byte	NW	= 3;

	static final byte	EU	= 4;
	static final byte	EN	= 5;
	static final byte	ED	= 6;
	static final byte	ES	= 7;

	static final byte	UE	= 8;
	static final byte	UN	= 9;
	static final byte	UW	= 10;
	static final byte	US	= 11;

	static final byte	SU	= 12;
	static final byte	SE	= 13;
	static final byte	SD	= 14;
	static final byte	SW	= 15;

	static final byte	WU	= 16;
	static final byte	WN	= 17;
	static final byte	WD	= 18;
	static final byte	WS	= 19;

	static final byte	DE	= 20;
	static final byte	DN	= 21;
	static final byte	DW	= 22;
	static final byte	DS	= 23;
	
	static final byte	nHdngOrients = 24;	// number of headings and orientations

	static boolean Valid(byte nValue)
	{
		return ((nValue >= 0) && (nValue <= 23));
	}
}

class Op
{
	static final byte	OPNUL	= 0;
	static final byte	OPLEFT	= 1;
	static final byte	OPRIGHT = 2;
	static final byte	OPUP	= 3;
	static final byte	OPDOWN	= 4;
	static final byte	OPANTI	= 5;
	static final byte	OPCLOCK	= 6;
	static final byte	nOps = 7;
	
	static boolean Valid(byte nValue)
	{
		return ((nValue >= 0) && (nValue < nOps));
	}
}

class Rotations
{
	static final byte rotNul[] =
	{
		Dir.NU, Dir.NE, Dir.ND, Dir.NW,	// this table yields a no-op
		Dir.EU, Dir.EN, Dir.ED, Dir.ES,
		Dir.UE, Dir.UN, Dir.UW, Dir.US,
		Dir.SU, Dir.SE, Dir.SD, Dir.SW,
		Dir.WU, Dir.WN, Dir.WD, Dir.WS,
		Dir.DE, Dir.DN, Dir.DW, Dir.DS
	};

	static final byte rotLeft[] =
	{
		Dir.WU, Dir.UE, Dir.ED, Dir.DW,	// NU->WU, NE->UE, ND->ED, NW->DW
		Dir.NU, Dir.DN, Dir.SD, Dir.US,	// EU->NU, EN->DN, ED->SD, ES->US
		Dir.SE, Dir.EN, Dir.NW, Dir.WS,	// UE->SE, UN->EN, UW->NW, US->WS
		Dir.EU, Dir.DE, Dir.WD, Dir.UW,	// SU->EU, SE->DE, SD->WD, SW->UW
		Dir.SU, Dir.UN, Dir.ND, Dir.DS,	// WU->SU, WN->UN, WD->ND, WS->DS
		Dir.NE, Dir.WN, Dir.SW, Dir.ES	// DE->NE, DN->WN, DW->SW, DS->ES
	};

	static final byte rotRight[] =
	{
		Dir.EU, Dir.DE, Dir.WD, Dir.UW,	// NU->EU, NE->DE, ND->WD, NW->DW
		Dir.SU, Dir.UN, Dir.ND, Dir.DS,	// EU->SU, EN->UN, ED->ND, ES->DS
		Dir.NE, Dir.WN, Dir.SW, Dir.ES,	// UE->NE, UN->WN, UW->SW, US->ES
		Dir.WU, Dir.UE, Dir.ED, Dir.DW,	// SU->WU, SE->UE, SD->ED, SW->DW
		Dir.NU, Dir.DN, Dir.SD, Dir.US,	// WU->NU, WN->DN, WD->SD, WS->US
		Dir.SE, Dir.EN, Dir.NW, Dir.WS	// DE->SE, DN->EN, DW->NW, DS->WS
	};

	static final byte rotUp[] =
	{
		Dir.US, Dir.ES, Dir.DS, Dir.WS,	// NU->US, NE->ES, ND->DS, NW->WS
		Dir.UW, Dir.NW, Dir.DW, Dir.SW,	// EU->UW, EN->NW, ED->DW, ES->SW
		Dir.ED, Dir.ND, Dir.WD, Dir.SD,	// UE->ED, UN->ND, UW->WD, US->SD
		Dir.UN, Dir.EN, Dir.DN, Dir.WN,	// SU->UN, SE->EN, SD->DN, SW->WN
		Dir.UE, Dir.NE, Dir.DE, Dir.SE,	// WU->UE, WN->NE, WD->DE, WS->SE
		Dir.EU, Dir.NU, Dir.WU, Dir.SU	// DE->EU, DN->NU, DW->WU, DS->SU
	};

	static final byte rotDown[] =
	{
		Dir.DN, Dir.WN, Dir.UN, Dir.EN,	// NU->DN, NE->WN, ND->UN, NW->EN
		Dir.DE, Dir.SE, Dir.UE, Dir.NE,	// EU->DE, EN->SE, ED->UE, ES->NE
		Dir.WU, Dir.SU, Dir.EU, Dir.NU,	// UE->WU, UN->SU, UW->EU, US->NU
		Dir.DS, Dir.WS, Dir.US, Dir.ES,	// SU->DS, SE->WS, SD->US, SW->ES
		Dir.DW, Dir.SW, Dir.UW, Dir.NW,	// WU->DW, WN->SW, WD->UW, WS->NW
		Dir.WD, Dir.SD, Dir.ED, Dir.ND	// DE->WD, DN->SD, DW->ED, DS->ND
	};
	
	static final byte rotAnti[] =
	{
		Dir.NW, Dir.NU, Dir.NE, Dir.ND,	// NU->NW, NE->NU, ND->NE, NW->ND
		Dir.EN, Dir.ED, Dir.ES, Dir.EU,	// EU->EN, EN->ED, ED->ES, ES->EU
		Dir.US, Dir.UE, Dir.UN, Dir.UW,	// UE->US, UN->UE, UW->UN, US->UW
		Dir.SE, Dir.SD, Dir.SW, Dir.SU,	// SU->SE, SE->SD, SD->SW, SW->SU
		Dir.WS, Dir.WU, Dir.WN, Dir.WD,	// WU->WS, WN->WU, WD->WN, WS->WD
		Dir.DN, Dir.DW, Dir.DS, Dir.DE	// DE->DN, DN->DW, DW->DS, DS->DE
	};

	static final byte rotClock[] =
	{
		Dir.NE, Dir.ND, Dir.NW, Dir.NU,	// NU->NE, NE->ND, ND->NW, NW->NU
		Dir.ES, Dir.EU, Dir.EN, Dir.ED,	// EU->ES, EN->EU, ED->EN, ES->ED
		Dir.UN, Dir.UW, Dir.US, Dir.UE,	// UE->UN, UN->UW, UW->US, US->UE
		Dir.SW, Dir.SU, Dir.SE, Dir.SD,	// SU->SW, SE->SU, SD->SE, SW->SD
		Dir.WN, Dir.WD, Dir.WS, Dir.WU,	// WU->WN, WN->WD, WD->WS, WS->WU
		Dir.DS, Dir.DE, Dir.DN, Dir.DW	// DE->DS, DN->DE, DW->DN, DS->DW
	};

	static final byte rot[][] =
	{
		rotNul, rotLeft, rotRight, rotUp, rotDown, rotAnti, rotClock
	};
}

class CStartStop
{
	byte			m_bySeq;	// input or output sequence (or zero)
	boolean			m_bOut;		// true if an output point

	// constructor
	CStartStop()
	{
		m_bySeq = 0;
		m_bOut = false;
	}
}

class CEntryPoint
{
	int		m_iX;
	int		m_iY;
	int		m_iZ;
	byte	m_Dir;
	
	// constructor
	CEntryPoint()
	{
		m_iX = -1;
		m_iY = -1;
		m_iZ = -1;
		m_Dir = 0;
	}
}

class CMatrix extends Canvas implements KeyListener
{
	Image				m_cimgOffScrn = null;
	Image				m_imgSel;
	Image				m_imgMain;
	Image				m_imgHigh;
	byte				m_rgrgrgMap[][][];
	CStartStop			m_rgrgrgSS[][][];
	int					m_rgrgrgiBits[][][];
	int					m_iLevel;
	// lookup tables for highlighting 'glider' paths
	static final byte	m_hdngXCoords[] = { -4, -6, 0, 4, 6, 0 };
	static final byte	m_hdngYCoords[] = { 2, 0, 6, -2, 0, -6 };
	static final byte	m_hdngX2Coords[] = { -2, -3, 0, 2, 3, 0 };
	static final byte	m_hdngY2Coords[] = { 1, 0, 3, -1, 0, -3 };
	static final byte	m_orientX[] = { -4, -1, -4, -7, -6, -4, -6, -8, 3, 2, -3, -2,
										4, 7, 4, 1, 6, 8, 6, 4, 3, 2, -3, -2 };
	static final byte	m_orientY[] = { -1, 2, 5, 2, -3, -1, 3, 1, 6, 5, 6, 7,
										-5, -2, 1, -2, -3, -1, 3, 1, -6, -7, -6, -5 };
	static final int	m_fiDimX = 3;
	static final int	m_fiDimY = 3;
	static final int	m_fiDimZ = 3;
	static final int	m_fiDimSeq = 9;		// most number of track pairs
	byte				m_Decode[];
	boolean				m_rgbSeq[];
	static final Color	m_ColorNorm = Color.cyan;
	static final Color	m_ColorHigh = Color.yellow;
	static final Color	m_ColorSel = Color.red;
	static final Color	m_ColorText = Color.white;
	static final Color	m_ColorCaption = Color.green;
	static final Color	m_ColorSolidEdge1 = Color.lightGray;
	static final Color	m_ColorSolidEdge2 = Color.lightGray;
	static final Color	m_ColorSolidDiag1 = Color.white;
	static final Color	m_ColorSolidDiag2 = Color.lightGray;
	Color				m_ColorPipeEdge1 = m_ColorSolidEdge1;
	Color				m_ColorPipeEdge2 = m_ColorSolidEdge2;
	Color				m_ColorDiagEdge1 = m_ColorSolidDiag1;
	Color				m_ColorDiagEdge2 = m_ColorSolidDiag2;
	int					m_iLMrgn = 0;
	int					m_iTMrgn = 0;
	int					m_iScale = 1;
	int					m_iOriginX;
	int					m_iOriginY;
	boolean				m_bActive = false;
	boolean				m_bInSelected = false;
	boolean				m_bOutFound = false;
	CEntryPoint			m_cEP;
	CEntryPoint			m_cExitPt;
	int					m_iX_Highlight;
	int					m_iY_Highlight;
	int					m_iZ_Highlight;
	int					m_iX_Sel;
	int					m_iY_Sel;
	int					m_iZ_Sel;
	byte				m_byInNum;
	byte				orderings[];
	byte				last_ordering;

	// constructor
	CMatrix(Dimension cdim, Image imgSpheres[], byte rgDecode[])
	{
		setSize(cdim);
		m_imgSel = imgSpheres[0];
		m_imgMain = imgSpheres[1];
		m_imgHigh = imgSpheres[2];
		m_Decode = rgDecode;
		int i;
		int j;
		m_iX_Highlight = m_fiDimX;
		m_iY_Highlight = m_fiDimY;
		m_iZ_Highlight = m_fiDimZ;
		m_iX_Sel = m_fiDimX;
		m_iY_Sel = m_fiDimY;
		m_iZ_Sel = m_fiDimZ;
		m_cEP = new CEntryPoint();
		m_cExitPt = new CEntryPoint();
		orderings = new byte[10];
		clearOrderings();
		m_rgrgrgMap = new byte [m_fiDimX][][];
		m_rgrgrgiBits = new int[m_fiDimX][][];

		for (i = 0; i < m_fiDimX; i++)
		{
			m_rgrgrgMap[i] = new byte[m_fiDimY][];
			m_rgrgrgiBits[i] = new int[m_fiDimY][];

			for (j = 0; j < m_fiDimY; j++)
			{
				m_rgrgrgMap[i][j] = new byte[m_fiDimZ];
				m_rgrgrgiBits[i][j] = new int[m_fiDimZ];
			}
		}

		// faces of a parallelapiped		
		m_rgrgrgSS = new CStartStop [Dir.nDirections][][];
		m_rgrgrgSS[Dir.North] = new CStartStop[m_fiDimX][];

		for (i = 0; i < m_fiDimX; i++)
		{
			m_rgrgrgSS[Dir.North][i] = new CStartStop[m_fiDimY];
			
			for (j = 0; j < m_fiDimY; j++)
			{
				m_rgrgrgSS[Dir.North][i][j] = new CStartStop();
			}
		}

		m_rgrgrgSS[Dir.East] = new CStartStop[m_fiDimY][];
		
		for (i = 0; i < m_fiDimY; i++)
		{
			m_rgrgrgSS[Dir.East][i] = new CStartStop[m_fiDimZ];

			for (j = 0; j < m_fiDimZ; j++)
			{
				m_rgrgrgSS[Dir.East][i][j] = new CStartStop();
			}
		}

		m_rgrgrgSS[Dir.Up] = new CStartStop[m_fiDimX][];
		
		for (i = 0; i < m_fiDimX; i++)
		{
			m_rgrgrgSS[Dir.Up][i] = new CStartStop[m_fiDimZ];

			for (j = 0; j < m_fiDimZ; j++)
			{
				m_rgrgrgSS[Dir.Up][i][j] = new CStartStop();
			}
		}

		m_rgrgrgSS[Dir.South] = new CStartStop[m_fiDimX][];
		
		for (i = 0; i < m_fiDimX; i++)
		{
			m_rgrgrgSS[Dir.South][i] = new CStartStop[m_fiDimY];

			for (j = 0; j < m_fiDimY; j++)
			{
				m_rgrgrgSS[Dir.South][i][j] = new CStartStop();
			}
		}

		m_rgrgrgSS[Dir.West] = new CStartStop[m_fiDimY][];
		
		for (i = 0; i < m_fiDimY; i++)
		{
			m_rgrgrgSS[Dir.West][i] = new CStartStop[m_fiDimZ];

			for (j = 0; j < m_fiDimZ; j++)
			{
				m_rgrgrgSS[Dir.West][i][j] = new CStartStop();
			}
		}

		m_rgrgrgSS[Dir.Down] = new CStartStop[m_fiDimX][];
		
		for (i = 0; i < m_fiDimX; i++)
		{
			m_rgrgrgSS[Dir.Down][i] = new CStartStop[m_fiDimZ];

			for (j = 0; j < m_fiDimZ; j++)
			{
				m_rgrgrgSS[Dir.Down][i][j] = new CStartStop();
			}
		}
		
		m_rgbSeq = new boolean[m_fiDimSeq];
		
		for (i = 0; i < m_fiDimSeq; i++)
		{
			m_rgbSeq[i] = false;
		}
	}

	public void Solid(boolean bPipes)
	{
		if (bPipes)
		{
			m_ColorPipeEdge1 = m_ColorSolidEdge1;
			m_ColorPipeEdge2 = m_ColorSolidEdge2;
			m_ColorDiagEdge1 = m_ColorSolidDiag1;
			m_ColorDiagEdge2 = m_ColorSolidDiag2;
		}
		else
		{
			m_ColorPipeEdge1 = Color.black;
			m_ColorPipeEdge2 = Color.black;
			m_ColorDiagEdge1 = Color.black;
			m_ColorDiagEdge2 = Color.black;
		}

		repaint();
	}

	public void Restart(int iLevel, String strPat)
	{
		int i;
		int j;
		int k;
		int index = 0;
		int seq;
		int iLen = strPat.length();

		// note the level for the caption
		m_iLevel = iLevel;

		// reset ins and outs
		m_byInNum = (byte)m_fiDimSeq;
		m_bInSelected = false;
		m_bOutFound = false;

		for (i = 0; i < m_fiDimSeq; i++)
		{
			m_rgbSeq[i] = false;
		}
		
		for (j = 0; j < m_fiDimX; j++)
		{
			for (k = 0; k < m_fiDimZ; k++)
			{
				m_rgrgrgSS[Dir.Up][j][k].m_bySeq = 0;
				m_rgrgrgSS[Dir.Down][j][k].m_bySeq = 0;
			}
		}

		for (j = 0; j < m_fiDimY; j++)
		{
			for (k = 0; k < m_fiDimZ; k++)
			{
				m_rgrgrgSS[Dir.West][j][k].m_bySeq = 0;
				m_rgrgrgSS[Dir.East][j][k].m_bySeq = 0;
			}
		}

		for (j = 0; j < m_fiDimX; j++)
		{
			for (k = 0; k < m_fiDimY; k++)
			{
				m_rgrgrgSS[Dir.North][j][k].m_bySeq = 0;
				m_rgrgrgSS[Dir.South][j][k].m_bySeq = 0;
			}
		}

		if (iLen > 0)
		{
			m_bActive = true;

			for (i = 0; i < m_fiDimX; i++)
			{
				for (j = 0; j < m_fiDimY; j++)
				{
					for (k = 0; k < m_fiDimZ; k++)
					{
						m_rgrgrgMap[i][j][k] = (byte)strPat.charAt(index++);
						m_rgrgrgiBits[i][j][k] = 0;
					}
				}
			}
			
			while (iLen - index >= 7)
			{
				seq = strPat.charAt(index++) - '0';

				if (seq > 0)
				{
					m_rgbSeq[seq - 1] = true;
				}

				i = strPat.charAt(index++) - '0';
				j = strPat.charAt(index++) - '0';
				k = strPat.charAt(index++) - '0';
				m_rgrgrgSS[i][j][k].m_bySeq = (byte)seq;
				m_rgrgrgSS[i][j][k].m_bOut = false;
				i = strPat.charAt(index++) - '0';
				j = strPat.charAt(index++) - '0';
				k = strPat.charAt(index++) - '0';
				m_rgrgrgSS[i][j][k].m_bySeq = (byte)seq;
				m_rgrgrgSS[i][j][k].m_bOut = true;
			}
		}
		else
		{
			m_bActive = false;
		}

		repaint();
	}

	public void clearOrderings()
	{
		if (orderings != null)
		{
			for (int i = 0; i < orderings.length; i++)
			{
				orderings[i] = -1;
			}

			last_ordering = -1;
		}
	}

	public void setOrdering(byte which)
	{
		if ((orderings != null) && (which < orderings.length) && (which >= 0))
		{
			if (orderings[which] == -1)
			{
				orderings[which] = ++last_ordering;
			}
		}
	}

	public byte[] getOrderings()
	{
		return orderings;
	}

	public void drawCompass(Graphics gc)
	{
		int iX = m_iLMrgn + m_iScale;
		int iY = m_iTMrgn + m_iScale;
		
		char rgch[] = new char[1];
		
		gc.setColor((m_iX_Highlight < m_fiDimX) || (m_iZ_Highlight < m_fiDimZ)
					? m_ColorHigh : m_ColorNorm);
		gc.drawLine(iX, iY - 2 * m_iScale, iX, iY + 2 * m_iScale);
		rgch[0] = 'U';
		gc.drawChars(rgch, 0, 1, iX - 3, iY - 2 * m_iScale - 5);
		rgch[0] = 'D';
		gc.drawChars(rgch, 0, 1, iX - 3, iY + 2 * m_iScale + 16);

		gc.setColor((m_iY_Highlight < m_fiDimY) || (m_iZ_Highlight < m_fiDimZ)
					? m_ColorHigh : m_ColorNorm);
		gc.drawLine(iX - 2 * m_iScale, iY, iX + 2 * m_iScale, iY);
		rgch[0] = 'W';
		gc.drawChars(rgch, 0, 1, iX - 3 * m_iScale - 3, iY + 5);
		rgch[0] = 'E';
		gc.drawChars(rgch, 0, 1, iX + 3 * m_iScale - 6, iY + 5);

		gc.setColor((m_iX_Highlight < m_fiDimX) || (m_iY_Highlight < m_fiDimY)
					? m_ColorHigh : m_ColorNorm);
		gc.drawLine(iX - 2 * m_iScale, iY + m_iScale,
					iX + 2 * m_iScale, iY - m_iScale);
		rgch[0] = 'N';
		gc.drawChars(rgch, 0, 1, iX + 3 * m_iScale - 6, iY - m_iScale);
		rgch[0] = 'S';
		gc.drawChars(rgch, 0, 1, iX - 3 * m_iScale, iY + m_iScale + 10);
	}

	public void drawIntersect(Graphics gc, int iX, int iY, int iZ)
	{
		int iXCo = m_iOriginX + ((12 * iX) + (8 * iZ)) * m_iScale;
		int iYCo = m_iOriginY - ((12 * iY) + (4 * iZ)) * m_iScale;
		int iXCo2 = iXCo - 12 * m_iScale;
		int iYCo2 = iYCo + 12 * m_iScale;
		byte bySeq = 0;
		boolean bOut = false;
		String str;

		if (iX == 0)
		{
			iXCo2 = iXCo - 3 * m_iScale;
			bySeq =	m_rgrgrgSS[Dir.West][iY][iZ].m_bySeq;
			bOut = m_rgrgrgSS[Dir.West][iY][iZ].m_bOut;
		}

		if (iX > 0)
		{
			gc.setColor(Color.black);
			gc.drawLine(iXCo, iYCo + 2, iXCo2, iYCo + 2);
			gc.drawLine(iXCo, iYCo - 2, iXCo2, iYCo - 2);
			gc.setColor(m_ColorPipeEdge1);
			gc.drawLine(iXCo, iYCo - 1, iXCo2, iYCo - 1);
			gc.setColor(m_ColorPipeEdge2);
			gc.drawLine(iXCo, iYCo + 1, iXCo2, iYCo + 1);
		}

		if ((iX > 0) || ((iX == 0) && (bySeq > 0)))
		{
			if ((iY == m_iY_Highlight) || (iZ == m_iZ_Highlight))
			{
				gc.setColor(m_ColorHigh);
			}
			else
			{
				gc.setColor(m_ColorNorm);
			}

			gc.drawLine(iXCo, iYCo, iXCo2, iYCo);
		}

		if (iX == m_fiDimX - 1)
		{
			bySeq = m_rgrgrgSS[Dir.East][iY][iZ].m_bySeq;
			bOut = m_rgrgrgSS[Dir.East][iY][iZ].m_bOut;
		}

		if ((iX == m_fiDimX - 1) && (bySeq > 0))
		{
			iXCo2 = iXCo + 3 * m_iScale;
			gc.drawLine(iXCo, iYCo, iXCo2, iYCo);
		}

		if ((bySeq > 0) && ((iX == 0) || (iX == m_fiDimX - 1)))
		{
			gc.fillRect(iXCo2 - 2, iYCo - 2, 5, 5);
			str = (bOut ? "Out " : "In ") + bySeq;
			gc.setColor(m_ColorText);
			gc.drawString(str, iXCo2 - 10, iYCo - 5);					 
		}

		if (iY == 0)
		{
			iYCo2 = iYCo + 3 * m_iScale;
			bySeq = m_rgrgrgSS[Dir.Down][iX][iZ].m_bySeq;
			bOut = m_rgrgrgSS[Dir.Down][iX][iZ].m_bOut;
		}

		if (iY > 0)
		{
			gc.setColor(Color.black);
			gc.drawLine(iXCo - 2, iYCo, iXCo - 2, iYCo2);
			gc.drawLine(iXCo + 2, iYCo, iXCo + 2, iYCo2);
			gc.setColor(m_ColorPipeEdge1);
			gc.drawLine(iXCo - 1, iYCo, iXCo - 1, iYCo2);
			gc.setColor(m_ColorPipeEdge2);
			gc.drawLine(iXCo + 1, iYCo, iXCo + 1, iYCo2);
		}

		if ((iY > 0) || ((iY == 0) && (bySeq > 0)))
		{
			if ((iX == m_iX_Highlight) || (iZ == m_iZ_Highlight))
			{
				gc.setColor(m_ColorHigh);
			}
			else
			{
				gc.setColor(m_ColorNorm);
			}
			
			gc.drawLine(iXCo, iYCo, iXCo, iYCo2);
		}

		if (iY == m_fiDimY - 1)
		{
			bySeq = m_rgrgrgSS[Dir.Up][iX][iZ].m_bySeq;
			bOut = m_rgrgrgSS[Dir.Up][iX][iZ].m_bOut;
		}

		if ((iY == m_fiDimY - 1) && (bySeq > 0))
		{
			iYCo2 = iYCo - 3 * m_iScale;
			gc.drawLine(iXCo, iYCo, iXCo, iYCo2);
		}

		if ((bySeq > 0) && ((iY == 0) || (iY == m_fiDimY - 1)))
		{
			gc.fillRect(iXCo - 2, iYCo2 - 2, 5, 5);
			str = (bOut ? "Out" : "In") + bySeq;
			gc.setColor(m_ColorText);
			gc.drawString(str, iXCo + 5, iYCo2 + 3);
		}

		if (iZ == 0)
		{
			iXCo2 = iXCo - ((8 * m_iScale) / 3);
			iYCo2 = iYCo + ((4 * m_iScale) / 3);
			bySeq = m_rgrgrgSS[Dir.South][iX][iY].m_bySeq;
			bOut = m_rgrgrgSS[Dir.South][iX][iY].m_bOut;
		}
		else
		{
			iXCo2 = iXCo - (8 * m_iScale);
			iYCo2 = iYCo + (4 * m_iScale);
		}

		if (iZ > 0)
		{
			gc.setColor(Color.black);
			gc.drawLine(iXCo - 2, iYCo - 2, iXCo2 - 2, iYCo2 - 2);
			gc.drawLine(iXCo + 2, iYCo + 2, iXCo2 + 2, iYCo2 + 2);
			gc.setColor(m_ColorDiagEdge1);
			gc.drawLine(iXCo - 1, iYCo - 1, iXCo2 - 1, iYCo2 - 1);
			gc.setColor(m_ColorDiagEdge2);
			gc.drawLine(iXCo + 1, iYCo + 1, iXCo2 + 1, iYCo2 + 1);
		}

		if ((iZ > 0) || ((iZ == 0) && (bySeq > 0)))
		{
			if ((iX == m_iX_Highlight) || (iY == m_iY_Highlight))
			{
				gc.setColor(m_ColorHigh);
			}
			else
			{
				gc.setColor(m_ColorNorm);
			}

			gc.drawLine(iXCo, iYCo, iXCo2, iYCo2);
		}

		if (iZ == m_fiDimZ - 1)
		{
			bySeq = m_rgrgrgSS[Dir.North][iX][iY].m_bySeq;
			bOut = m_rgrgrgSS[Dir.North][iX][iY].m_bOut;
		}

		if ((iZ == m_fiDimZ - 1) && (bySeq > 0))
		{
			iXCo2 = iXCo + ((8 * m_iScale) / 3);
			iYCo2 = iYCo - ((4 * m_iScale) / 3);
			gc.drawLine(iXCo, iYCo, iXCo2, iYCo2);
		}

		if ((bySeq > 0) && ((iZ == 0) || (iZ == m_fiDimZ - 1)))
		{
			gc.fillRect(iXCo2 - 2, iYCo2 - 2, 5, 5);
			str = (bOut ? "Out" : "In") + bySeq;
			gc.setColor(m_ColorText);

			if (iZ == 0)
			{
				gc.drawString(str, iXCo2 - 8, iYCo2 + 15);
			}
			else
			{
				gc.drawString(str, iXCo2 - 10, iYCo2 - 5);
			}
		}
	}

	public void addBall(Graphics gc, int iX, int iY, int iZ, byte byTag)
	{
		int iXCo = m_iOriginX + ((12 * iX) + (8 * iZ)) * m_iScale;
		int iYCo = m_iOriginY - ((12 * iY) + (4 * iZ)) * m_iScale;
		Image img;

		if ((iX == m_iX_Sel) && (iY == m_iY_Sel) && (iZ == m_iZ_Sel))
		{
			img = m_imgSel;
		}
		else if ((iX == m_iX_Highlight) || (iY == m_iY_Highlight) || (iZ == m_iZ_Highlight))
		{
			img = m_imgHigh;
		}
		else
		{
			img = m_imgMain;
		}

		gc.drawImage(img, iXCo - m_iScale, iYCo - m_iScale, null);

		char rgch[] = new char[1];
		rgch[0] = (char)byTag;
		gc.setColor(Color.black);
		gc.drawChars(rgch, 0, 1, iXCo - 1, iYCo + 2);					 
	}

	public void addCaption(Graphics gc, int iLattice)
	{
		String str = "Lattice " + iLattice;
		Dimension	Dim = getSize();	// canvas size
		gc.setColor(m_ColorCaption);
		gc.drawChars(str.toCharArray(), 0, str.length(),
					 Dim.width - 100, Dim.height - 16);
	}

	public void addGliders(Graphics gc, int iX, int iY, int iZ, int iBits)
	{
		if (iBits == 0)
		{
			return;
		}

		int iXCo = m_iOriginX + ((12 * iX) + (8 * iZ)) * m_iScale;
		int iYCo = m_iOriginY - ((12 * iY) + (4 * iZ)) * m_iScale;
		int i;
		int iMask;
		int iHdng;
		int iXFrom;
		int iYFrom;
		int iXToHdng;
		int iYToHdng;
		int iXToOrient;
		int iYToOrient;

		gc.setColor(Color.red);

		for (i = 0, iMask = 1; i < Dir.nHdngOrients; i++, iMask <<= 1)
		{
			if ((iBits & iMask) != 0)
			{
				iHdng = i / 4;
				iXFrom = iXCo + m_iScale * m_hdngXCoords[iHdng] / 2;
				iYFrom = iYCo + m_iScale * m_hdngYCoords[iHdng] / 2;
				iXToHdng = iXCo + m_iScale * m_hdngX2Coords[iHdng] /2;
				iYToHdng = iYCo + m_iScale * m_hdngY2Coords[iHdng] / 2;
				iXToOrient = iXCo + m_iScale * m_orientX[i] / 2;
				iYToOrient = iYCo + m_iScale * m_orientY[i] / 2;
				gc.drawLine(iXFrom, iYFrom, iXToHdng, iYToHdng);
				gc.drawLine(iXFrom, iYFrom, iXToOrient, iYToOrient);
			}
		}
	}

	public void drawInOut(Graphics gc, CEntryPoint ep, boolean bOut)
	{
		int iXCo = m_iOriginX + ((12 * ep.m_iX) + (8 * ep.m_iZ)) * m_iScale;
		int iYCo = m_iOriginY - ((12 * ep.m_iY) + (4 * ep.m_iZ)) * m_iScale;
		int iXCo2 = iXCo;
		int iYCo2 = iYCo;
		int iHdng = ep.m_Dir / 4;

		if (bOut)
		{
			iHdng -= 3;
			
			if (iHdng < 0)
			{
				iHdng += 6;
			}
		}

		if ((iHdng == Dir.North) || (iHdng == Dir.South))
		{
			iXCo2 += m_hdngXCoords[iHdng] * m_iScale * 2 / 3;
			iYCo2 += m_hdngYCoords[iHdng] * m_iScale * 2 / 3;
		}
		else
		{
			iXCo2 += m_hdngXCoords[iHdng] * m_iScale / 2;
			iYCo2 += m_hdngYCoords[iHdng] * m_iScale / 2;
		}

		gc.setColor(m_ColorSel);
		gc.drawLine(iXCo, iYCo, iXCo2, iYCo2);
		gc.fillRect(iXCo2 - 2, iYCo2 - 2, 5, 5);
	}

	public void update(Graphics gc)
	{
		Dimension	Dim = getSize();	// canvas size

		// set margins and scaling from size - but guess for now
		m_iScale = 12;
		m_iLMrgn = 22 + 3 * m_iScale;
		m_iTMrgn = 22 + 3 * m_iScale;
		m_iOriginX = m_iLMrgn;
		m_iOriginY = m_iTMrgn +
					  ((4 * (m_fiDimZ - 1) + 12 * (m_fiDimY - 1))) * m_iScale;

		if (m_cimgOffScrn == null)
		{
			m_cimgOffScrn = createImage(Dim.width, Dim.height);
		}

		Graphics og = m_cimgOffScrn.getGraphics();
		og.setColor(Color.black);
		og.fillRect(0, 0, Dim.width, Dim.height);

		if (m_bActive)
		{
			drawCompass(og);
			addCaption(og, m_iLevel);
			
			int iX;
			int iY;
			int iZ;

			for (iZ = m_fiDimZ - 1; iZ >= 0; iZ--)
			{
				for (iX = 0; iX < m_fiDimX; iX++)
				{
					for (iY = 0; iY < m_fiDimY; iY++)
					{
						drawIntersect(og, iX, iY, iZ);
					}
				}
			}

			if (m_bInSelected)
			{
				drawInOut(og, m_cEP, false);

				if (m_bOutFound)
				{
					drawInOut(og, m_cExitPt, true);
				}
			}
			
			for (iX = 0; iX < m_fiDimX; iX++)
			{
				for (iY = 0; iY < m_fiDimY; iY++)
				{
					for (iZ = 0; iZ < m_fiDimZ; iZ++)
					{
						addBall(og, iX, iY, iZ, m_rgrgrgMap[iX][iY][iZ]);
						addGliders(og, iX, iY, iZ, m_rgrgrgiBits[iX][iY][iZ]);
					}
				}
			}
		}

		// draw the completed image on-screen
		gc.drawImage(m_cimgOffScrn, 0, 0, this);
	}

	public void Highlight(char chPlane)
	{
		switch (chPlane)
		{
		case 'X':
			{
				m_iY_Highlight = m_fiDimY;
				m_iZ_Highlight = m_fiDimZ;

				if (++m_iX_Highlight > m_fiDimX)
				{
					m_iX_Highlight = 0;
				}

				break;
			}

		case 'Y':
			{
				m_iX_Highlight = m_fiDimX;
				m_iZ_Highlight = m_fiDimZ;

				if (++m_iY_Highlight > m_fiDimY)
				{
					m_iY_Highlight = 0;
				}

				break;
			}

		case 'Z':
			{
				m_iX_Highlight = m_fiDimX;
				m_iY_Highlight = m_fiDimY;

				if (++m_iZ_Highlight > m_fiDimZ)
				{
					m_iZ_Highlight = 0;
				}

				break;
			}
		}

		repaint();
	}

	public void deSel()
	{
		m_iX_Sel = m_fiDimX;
		m_iY_Sel = m_fiDimY;
		m_iZ_Sel = m_fiDimZ;
	}

	public void Sel()
	{
		if (m_iX_Sel == m_fiDimX)
		{
			m_iX_Sel = 0;
		}

		if (m_iY_Sel == m_fiDimY)
		{
			m_iY_Sel = 0;
		}

		if (m_iZ_Sel == m_fiDimZ)
		{
			m_iZ_Sel = 0;
		}
	}

	// locate entry point and direction for given path
	public boolean findEntryPoint(final byte byInNo, CEntryPoint cEP)
	{
		boolean bRes = false;
		byte byDir;
		byte i;
		byte j;
		
		for (byDir = 0; byDir < Dir.nDirections; byDir++)
		{
			switch (byDir)
			{
			case Dir.West:
			case Dir.East:
				for (i = 0; i < m_fiDimY; i++)
				{
					for (j = 0; j < m_fiDimZ; j++)
					{
						if ((m_rgrgrgSS[byDir][i][j].m_bySeq == byInNo) &&
							(!m_rgrgrgSS[byDir][i][j].m_bOut))
						{
							cEP.m_iX = (byDir == Dir.West) ? 0 : m_fiDimX - 1;
							cEP.m_iY = i;
							cEP.m_iZ = j;
							cEP.m_Dir = (byDir == Dir.West) ? Dir.EU : Dir.WU;
							bRes = true;
							break;
						}
					}
					
					if (bRes)
					{
						break;
					}
				}
				break;

			case Dir.South:
			case Dir.North:
				for (i = 0; i < m_fiDimX; i++)
				{
					for (j = 0; j < m_fiDimY; j++)
					{
						if ((m_rgrgrgSS[byDir][i][j].m_bySeq == byInNo) &&
							(!m_rgrgrgSS[byDir][i][j].m_bOut))
						{
							cEP.m_iX = i;
							cEP.m_iY = j;
							cEP.m_iZ = (byDir == Dir.South) ? 0 : m_fiDimZ - 1;
							cEP.m_Dir = (byDir == Dir.South) ? Dir.NU : Dir.SU;
							bRes = true;
							break;
						}
					}
					
					if (bRes)
					{
						break;
					}
				}
				break;

			case Dir.Down:
			case Dir.Up:
				for (i = 0; i < m_fiDimX; i++)
				{
					for (j = 0; j < m_fiDimZ; j++)
					{
						if ((m_rgrgrgSS[byDir][i][j].m_bySeq == byInNo) &&
							(!m_rgrgrgSS[byDir][i][j].m_bOut))
						{
							cEP.m_iX = i;
							cEP.m_iY = (byDir == Dir.Down) ? 0 : m_fiDimY - 1;
							cEP.m_iZ = j;
							cEP.m_Dir = (byDir == Dir.Down) ? Dir.UE : Dir.DE;
							bRes = true;
							break;
						}
					}
					
					if (bRes)
					{
						break;
					}
				}
				break;
			}
			
			if (bRes)
			{
				break;
			}
		}
		
		return bRes;
	}

	// clear the bits maps indicating track followed 
	public void clearBits()
	{
		int iX;
		int iY;
		int iZ;

		for (iX = 0; iX < m_fiDimX; iX++)
		{
			for (iY = 0; iY < m_fiDimY; iY++)
			{
				for (iZ = 0; iZ < m_fiDimZ; iZ++)
				{
					m_rgrgrgiBits[iX][iY][iZ] = 0;
				}
			}
		}
	}

	// this is the engine for following a path through the maze
	// returns true if there is an exit point, false if the input is absorbed
	// cEP is set to the initial entry point and direction and is updated.
	// On exit, cEP contains the exit point and direction or the point at
	// which the track first repeated.
	public boolean findExitPoint(CEntryPoint cEP)
	{
		boolean bRes = false;
		int iX;
		int iY;
		int iZ;
		byte byDir;
		byte byOp;

		// Initialise bit map for directions of approach to each node.
		// If a node is approached from the same direction and orientation more than
		// once, the input is absorbed.
		clearBits();
		clearOrderings();

		while (!bRes)
		{
			// check if the new node has been approached in this way before
			int iMask = (1 << cEP.m_Dir);
			
			if ((m_rgrgrgiBits[cEP.m_iX][cEP.m_iY][cEP.m_iZ] & iMask) != 0)
			{
				break;	// never coming out, so quit
			}
			else
			{
				// remember that we came here this way
				m_rgrgrgiBits[cEP.m_iX][cEP.m_iY][cEP.m_iZ] |= iMask;
			}

			// Determine operation for this node
			byOp = m_rgrgrgMap[cEP.m_iX][cEP.m_iY][cEP.m_iZ];
			setOrdering((byte)(byOp - 'A'));
			byOp = m_Decode[byOp - 'A'];

			// apply action at this node to yield new direction
			cEP.m_Dir = Rotations.rot[byOp][cEP.m_Dir];
			byDir = (byte)(cEP.m_Dir / 4);	// just get the heading

			switch (byDir)
			{
			case Dir.North:
				if (cEP.m_iZ < m_fiDimZ - 1)
				{
					cEP.m_iZ++;
				}
				else
				{
					bRes = true;
				}
				break;
			case Dir.East:
				if (cEP.m_iX < m_fiDimX - 1)
				{
					cEP.m_iX++;
				}
				else
				{
					bRes = true;
				}
				break;
			case Dir.Up:
				if (cEP.m_iY < m_fiDimY - 1)
				{
					cEP.m_iY++;
				}
				else
				{
					bRes = true;
				}
				break;
			case Dir.South:
				if (cEP.m_iZ > 0)
				{
					cEP.m_iZ--;
				}
				else
				{
					bRes = true;
				}
				break;
			case Dir.West:
				if (cEP.m_iX > 0)
				{
					cEP.m_iX--;
				}
				else
				{
					bRes = true;
				}
				break;
			case Dir.Down:
				if (cEP.m_iY > 0)
				{
					cEP.m_iY--;
				}
				else
				{
					bRes = true;
				}
				break;
			}
		}

		return bRes;
	}

	public void keyPressed(KeyEvent e)
	{
		int iKey = e.getKeyCode();
		boolean bUpdate = true;

		switch(iKey)
		{
		case KeyEvent.VK_LEFT:
			if (m_iX_Sel == 0)
			{
				deSel();
			}
			else
			{
				m_iX_Sel--;
				Sel();
			}
			break;

		case KeyEvent.VK_RIGHT:
			if (m_iX_Sel == m_fiDimX)
			{
				Sel();
			}
			else
			{
				m_iX_Sel++;
				
				if (m_iX_Sel == m_fiDimX)
				{
					deSel();
				}
			}
			break;

		case KeyEvent.VK_DOWN:
			if (m_iY_Sel == 0)
			{
				deSel();
			}
			else
			{
				m_iY_Sel--;
				Sel();
			}
			break;

		case KeyEvent.VK_UP:
			if (m_iY_Sel == m_fiDimY)
			{
				Sel();
			}
			else
			{
				m_iY_Sel++;
				
				if (m_iY_Sel == m_fiDimY)
				{
					deSel();
				}
			}
			break;

		case KeyEvent.VK_END:
			if (m_iZ_Sel == 0)
			{
				deSel();
			}
			else
			{
				m_iZ_Sel--;
				Sel();
			}
			break;

		case KeyEvent.VK_PAGE_UP:
			if (m_iZ_Sel == m_fiDimZ)
			{
				Sel();
			}
			else
			{
				m_iZ_Sel++;
				
				if (m_iZ_Sel == m_fiDimZ)
				{
					deSel();
				}
			}
			break;

		default:
			bUpdate = false;
			break;
		}

		if (bUpdate)
		{
			repaint();
		}
	}

	public void keyReleased(KeyEvent e)
	{
	}

	public void keyTyped(KeyEvent e)
	{
	}
	
	public void paint(Graphics gc)
	{
		update(gc);
	}
	
	public void Step()
	{
		do
		{
			if (++m_byInNum > (byte)m_fiDimSeq)
			{
				m_byInNum = 1;
			}
		}
		while (!m_rgbSeq[m_byInNum - 1]);

		m_bInSelected = findEntryPoint(m_byInNum, m_cEP);
		m_bOutFound = false;
		clearBits();
		repaint();
	}

	public void Test()
	{
		if (m_bInSelected)
		{
			m_cExitPt.m_Dir = m_cEP.m_Dir;
			m_cExitPt.m_iX = m_cEP.m_iX;
			m_cExitPt.m_iY = m_cEP.m_iY;
			m_cExitPt.m_iZ = m_cEP.m_iZ;
			m_bOutFound = findExitPoint(m_cExitPt);
			repaint();
		}
	}
}
