All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.stathissideris.ascii2image.text.TextGrid Maven / Gradle / Ivy

// THIS FILE HAS BEEN GENERATED BY A PREPROCESSOR.
/* +=======================================================================
 * |
 * |      PlantUML : a free UML diagram generator
 * |
 * +=======================================================================
 *
 * (C) Copyright 2009-2024, Arnaud Roques
 *
 * Project Info:  https://plantuml.com
 *
 * If you like this project or if you find it useful, you can support us at:
 *
 * https://plantuml.com/patreon (only 1$ per month!)
 * https://plantuml.com/liberapay (only 1€ per month!)
 * https://plantuml.com/paypal
 *
 *
 * PlantUML is free software; you can redistribute it and/or modify it
 * under the terms of the Revised BSD License.
 *
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * * Neither the name of the University of California, Berkeley nor the
 *   names of its contributors may be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * PlantUML can occasionally display sponsored or advertising messages. Those
 * messages are usually generated on welcome or error images and never on
 * functional diagrams.
 * See https://plantuml.com/professional if you want to remove them
 *
 * Images (whatever their format : PNG, SVG, EPS...) generated by running PlantUML
 * are owned by the author of their corresponding sources code (that is, their
 * textual description in PlantUML language). Those images are not covered by
 * this BSD license.
 *
 * The generated images can then be used without any reference to the BSD license.
 * It is not even necessary to stipulate that they have been generated with PlantUML,
 * although this will be appreciated by the PlantUML team.
 *
 * There is an exception : if the textual description in PlantUML language is also covered
 * by any license, then the generated images are logically covered
 * by the very same license.
 *
 * This is the IGY distribution (Install GraphViz by Yourself).
 * You have to install GraphViz and to setup the GRAPHVIZ_DOT environment variable
 * (see https://plantuml.com/graphviz-dot )
 *
 * Icons provided by OpenIconic :  https://useiconic.com/open
 * Archimate sprites provided by Archi :  http://www.archimatetool.com
 * Stdlib AWS provided by https://github.com/milo-minderbinder/AWS-PlantUML
 * Stdlib Icons provided https://github.com/tupadr3/plantuml-icon-font-sprites
 * ASCIIMathML (c) Peter Jipsen http://www.chapman.edu/~jipsen
 * ASCIIMathML (c) David Lippman http://www.pierce.ctc.edu/dlippman
 * CafeUndZopfli ported by Eugene Klyuchnikov https://github.com/eustas/CafeUndZopfli
 * Brotli (c) by the Brotli Authors https://github.com/google/brotli
 * Themes (c) by Brett Schwarz https://github.com/bschwarz/puml-themes
 * Twemoji (c) by Twitter at https://twemoji.twitter.com/
 *
 */
package org.stathissideris.ascii2image.text;

import java.awt.Color;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.stathissideris.ascii2image.core.ProcessingOptions;


/**
 *
 * @author Efstathios Sideris
 */
public class TextGrid {

	private static final boolean DEBUG = false;

	private ArrayList rows;

	private static char[] boundaries = {'/', '\\', '|', '-', '*', '=', ':'};
	private static char[] undisputableBoundaries = {'|', '-', '*', '=', ':'};
	private static char[] horizontalLines = {'-', '='};
	private static char[] verticalLines = {'|', ':'};
	private static char[] arrowHeads = {'<', '>', '^', 'v', 'V'};
	private static char[] cornerChars = {'\\', '/', '+'};
	private static char[] pointMarkers = {'*'};
	private static char[] dashedLines = {':', '~', '='};

	private static char[] entryPoints1 = {'\\'};
	private static char[] entryPoints2 = {'|', ':', '+', '\\', '/'};
	private static char[] entryPoints3 = {'/'};
	private static char[] entryPoints4 = {'-', '=', '+', '\\', '/'};
	private static char[] entryPoints5 = {'\\'};
	private static char[] entryPoints6 = {'|', ':', '+', '\\', '/'};
	private static char[] entryPoints7 = {'/'};
	private static char[] entryPoints8 = {'-', '=', '+', '\\', '/'};



	private static HashMap humanColorCodes = new HashMap();
	static {
		humanColorCodes.put("GRE", "9D9");
		humanColorCodes.put("BLU", "55B");
		humanColorCodes.put("PNK", "FAA");
		humanColorCodes.put("RED", "E32");
		humanColorCodes.put("YEL", "FF3");
		humanColorCodes.put("BLK", "000");

	}

	private static HashSet markupTags =
		new HashSet();

	static {
		markupTags.add("d");
		markupTags.add("s");
		markupTags.add("io");
		markupTags.add("c");
		markupTags.add("mo");
		markupTags.add("tr");
		markupTags.add("o");
	}

	public void addToMarkupTags(Collection tags){
		markupTags.addAll(tags);
	}



	public TextGrid(){
		rows = new ArrayList();
	}

	public TextGrid(int width, int height){
		String space = StringUtils.repeatString(" ", width);
		rows = new ArrayList();
		for(int i = 0; i < height; i++)
			rows.add(new StringBuffer(space));
	}

	public static TextGrid makeSameSizeAs(TextGrid grid){
		return new TextGrid(grid.getWidth(), grid.getHeight());
	}


	public TextGrid(TextGrid otherGrid){
		rows = new ArrayList();
		for(StringBuffer row : otherGrid.getRows()) {
			rows.add(new StringBuffer(row));
		}
	}

	public void clear(){
		String blank = StringUtils.repeatString(" ", getWidth());
		int height = getHeight();
		rows.clear();
		for(int i = 0; i < height; i++)
			rows.add(new StringBuffer(blank));
	}

//	duplicated code due to lots of hits to this function
	public char get(int x, int y){
		if(x > getWidth() - 1
			|| y > getHeight() - 1
			|| x < 0
			|| y < 0) return 0;
		return rows.get(y).charAt(x);
	}

	//duplicated code due to lots of hits to this function
	public char get(Cell cell){
		if(cell.x > getWidth() - 1
			|| cell.y > getHeight() - 1
			|| cell.x < 0
			|| cell.y < 0) return 0;
		return rows.get(cell.y).charAt(cell.x);
	}

	public StringBuffer getRow(int y){
		return rows.get(y);
	}

	public TextGrid getSubGrid(int x, int y, int width, int height){
		TextGrid grid = new TextGrid(width, height);
		for(int i = 0; i < height; i++){
			grid.setRow(i, new StringBuffer(getRow(y + i).subSequence(x, x + width)));
		}
		return grid;
	}

	public TextGrid getTestingSubGrid(Cell cell){
		return getSubGrid(cell.x - 1, cell.y - 1, 3, 3);
	}


	public String getStringAt(int x, int y, int length){
		return getStringAt(new Cell(x, y), length);
	}

	public String getStringAt(Cell cell, int length){
		int x = cell.x;
		int y = cell.y;
		if(x > getWidth() - 1
			|| y > getHeight() - 1
			|| x < 0
			|| y < 0) return null;
		return rows.get(y).substring(x, x + length);
	}

	public char getNorthOf(int x, int y){ return get(x, y - 1); }
	public char getSouthOf(int x, int y){ return get(x, y + 1); }
	public char getEastOf(int x, int y){ return get(x + 1, y); }
	public char getWestOf(int x, int y){ return get(x - 1, y); }

	public char getNorthOf(Cell cell){ return getNorthOf(cell.x, cell.y); }
	public char getSouthOf(Cell cell){ return getSouthOf(cell.x, cell.y); }
	public char getEastOf(Cell cell){ return getEastOf(cell.x, cell.y); }
	public char getWestOf(Cell cell){ return getWestOf(cell.x, cell.y); }

	public void writeStringTo(int x, int y, String str){
		writeStringTo(new Cell(x, y), str);
	}

	public void writeStringTo(Cell cell, String str){
		if(isOutOfBounds(cell)) return;
		rows.get(cell.y).replace(cell.x, cell.x + str.length(), str);
	}

	public void set(Cell cell, char c){
		set(cell.x, cell.y, c);
	}

	public void set(int x, int y, char c){
		if(x > getWidth() - 1 || y > getHeight() - 1) return;
		StringBuffer row = rows.get(y);
		row.setCharAt(x, c);
	}

	public void setRow(int y, String row){
		if(y > getHeight() || row.length() != getWidth())
			throw new IllegalArgumentException("setRow out of bounds or string wrong size");
		rows.set(y, new StringBuffer(row));
	}

	public void setRow(int y, StringBuffer row){
		if(y > getHeight() || row.length() != getWidth())
			throw new IllegalArgumentException("setRow out of bounds or string wrong size");
		rows.set(y, row);
	}

	public int getWidth(){
		if(rows.size() == 0) return 0; //empty buffer
		return rows.get(0).length();
	}

	public int getHeight(){
		return rows.size();
	}

	public void printDebug(){
		Iterator it = rows.iterator();
		int i = 0;
		System.out.println(
			"    "
			+StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1));
		while(it.hasNext()){
			String row = it.next().toString();
			String index = Integer.valueOf(i).toString();
			if(i < 10) index = " "+index;
			System.out.println(index+" ("+row+")");
			i++;
		}
	}

	public String getDebugString(){
		StringBuffer buffer = new StringBuffer();
		Iterator it = rows.iterator();
		int i = 0;
		buffer.append(
			"    "
			+StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)+"\n");
		while(it.hasNext()){
			String row = it.next().toString();
			String index = Integer.valueOf(i).toString();
			if(i < 10) index = " "+index;
			row = row.replaceAll("\n", "\\\\n");
			row = row.replaceAll("\r", "\\\\r");
			buffer.append(index+" ("+row+")\n");
			i++;
		}
		return buffer.toString();
	}

	public String toString(){
		return getDebugString();
	}

	/**
	 * Adds grid to this. Space characters in this grid
	 * are replaced with the corresponding contents of
	 * grid, otherwise the contents are unchanged.
	 *
	 * @param grid
	 * @return false if the grids are of different size
	 */
	public boolean add(TextGrid grid){
		if(getWidth() != grid.getWidth()
			|| getHeight() != grid.getHeight()) return false;
		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width; xi++){
				if(get(xi, yi) == ' ') set(xi, yi, grid.get(xi, yi));
			}
		}
		return true;
	}

	/**
	 * Replaces letters or numbers that are on horizontal or vertical
	 * lines, with the appropriate character that will make the line
	 * continuous (| for vertical and - for horizontal lines)
	 *
	 */
	public void replaceTypeOnLine(){
		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width; xi++){
				char c = get(xi, yi);
				if(Character.isLetterOrDigit(c)) {
					boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi);
					boolean isOnVerticalLine = isOnVerticalLine(xi, yi);
					if(isOnHorizontalLine && isOnVerticalLine){
						set(xi, yi, '+');
						if(DEBUG) System.out.println("replaced type on line '"+c+"' with +");
					} else if(isOnHorizontalLine){
						set(xi, yi, '-');
						if(DEBUG) System.out.println("replaced type on line '"+c+"' with -");
					} else if(isOnVerticalLine){
						set(xi, yi, '|');
						if(DEBUG) System.out.println("replaced type on line '"+c+"' with |");
					}
				}
			}
		}
	}

	public void replacePointMarkersOnLine(){
		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width; xi++){
				char c = get(xi, yi);
				Cell cell = new Cell(xi, yi);
				if(StringUtils.isOneOf(c, pointMarkers)
						&& isStarOnLine(cell)){

					boolean isOnHorizontalLine = false;
					if(StringUtils.isOneOf(get(cell.getEast()), horizontalLines))
						isOnHorizontalLine = true;
					if(StringUtils.isOneOf(get(cell.getWest()), horizontalLines))
						isOnHorizontalLine = true;

					boolean isOnVerticalLine = false;
					if(StringUtils.isOneOf(get(cell.getNorth()), verticalLines))
						isOnVerticalLine = true;
					if(StringUtils.isOneOf(get(cell.getSouth()), verticalLines))
						isOnVerticalLine = true;

					if(isOnHorizontalLine && isOnVerticalLine){
						set(xi, yi, '+');
						if(DEBUG) System.out.println("replaced marker on line '"+c+"' with +");
					} else if(isOnHorizontalLine){
						set(xi, yi, '-');
						if(DEBUG) System.out.println("replaced marker on line '"+c+"' with -");
					} else if(isOnVerticalLine){
						set(xi, yi, '|');
						if(DEBUG) System.out.println("replaced marker on line '"+c+"' with |");
					}
				}
			}
		}
	}

	public CellSet getPointMarkersOnLine(){
		CellSet result = new CellSet();
		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width; xi++){
				char c = get(xi, yi);
				if(StringUtils.isOneOf(c, pointMarkers)
						&& isStarOnLine(new Cell(xi, yi))){
					result.add(new Cell(xi, yi));
				}
			}
		}
		return result;
	}


	public void replaceHumanColorCodes(){
		int height = getHeight();
		for(int y = 0; y < height; y++){
			String row = rows.get(y).toString();
			Iterator it = humanColorCodes.keySet().iterator();
			while(it.hasNext()){
				String humanCode = (String) it.next();
				String hexCode = (String) humanColorCodes.get(humanCode);
				if(hexCode != null){
					humanCode = "c" + humanCode;
					hexCode = "c" + hexCode;
					row = row.replaceAll(humanCode, hexCode);
					rows.set(y, new StringBuffer(row)); //TODO: this is not the most efficient way to do this
					row = rows.get(y).toString();
				}
			}
		}
	}


	/**
	 * Replace all occurences of c1 with c2
	 *
	 * @param c1
	 * @param c2
	 */
	public void replaceAll(char c1, char c2){
		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width; xi++){
				char c = get(xi, yi);
				if(c == c1) set(xi, yi, c2);
			}
		}
	}

	public boolean hasBlankCells(){
		CellSet set = new CellSet();
		int width = getWidth();
		int height = getHeight();
		for(int y = 0; y < height; y++){
			for(int x = 0; x < width; x++){
				Cell cell = new Cell(x, y);
				if(isBlank(cell)) return true;
			}
		}
		return false;
	}


	public CellSet getAllNonBlank(){
		CellSet set = new CellSet();
		int width = getWidth();
		int height = getHeight();
		for(int y = 0; y < height; y++){
			for(int x = 0; x < width; x++){
				Cell cell = new Cell(x, y);
				if(!isBlank(cell)) set.add(cell);
			}
		}
		return set;
	}

	public CellSet getAllBoundaries(){
		CellSet set = new CellSet();
		int width = getWidth();
		int height = getHeight();
		for(int y = 0; y < height; y++){
			for(int x = 0; x < width; x++){
				Cell cell = new Cell(x, y);
				if(isBoundary(cell)) set.add(cell);
			}
		}
		return set;
	}


	public CellSet getAllBlanksBetweenCharacters(){
		CellSet set = new CellSet();
		int width = getWidth();
		int height = getHeight();
		for(int y = 0; y < height; y++){
			for(int x = 0; x < width; x++){
				Cell cell = new Cell(x, y);
				if(isBlankBetweenCharacters(cell)) set.add(cell);
			}
		}
		return set;
	}


	/**
	 * Returns an ArrayList of CellStringPairs that
	 * represents all the continuous (non-blank) Strings
	 * in the grid. Used on buffers that contain only
	 * type, in order to find the positions and the
	 * contents of the strings.
	 *
	 * @return
	 */
	public ArrayList findStrings(){
		ArrayList result = new ArrayList();
		int width = getWidth();
		int height = getHeight();
		for(int y = 0; y < height; y++){
			for(int x = 0; x < width; x++){
				if(!isBlank(x, y)){
					Cell start = new Cell(x, y);
					String str = String.valueOf(get(x,y));
					char c = get(++x, y);
					boolean finished = false;
					//while(c != ' '){
					while(!finished){
						str += String.valueOf(c);
						c = get(++x, y);
						char next = get(x + 1, y);
						if((c == ' ' || c == 0) && (next == ' ' || next == 0))
							finished = true;
					}
					result.add(new CellStringPair(start, str));
				}
			}
		}
		return result;
	}

	/**
	 * This is done in a bit of a messy way, should be impossible
	 * to go out of sync with corresponding GridPatternGroup.
	 *
	 * @param cell
	 * @param entryPointId
	 * @return
	 */
	public boolean hasEntryPoint(Cell cell, int entryPointId){
		String result = "";
		char c = get(cell);
		if(entryPointId == 1) {
			return StringUtils.isOneOf(c, entryPoints1);

		} else if(entryPointId == 2) {
			return StringUtils.isOneOf(c, entryPoints2);

		} else if(entryPointId == 3) {
			return StringUtils.isOneOf(c, entryPoints3);

		} else if(entryPointId == 4) {
			return StringUtils.isOneOf(c, entryPoints4);

		} else if(entryPointId == 5) {
			return StringUtils.isOneOf(c, entryPoints5);

		} else if(entryPointId == 6) {
			return StringUtils.isOneOf(c, entryPoints6);

		} else if(entryPointId == 7) {
			return StringUtils.isOneOf(c, entryPoints7);

		} else if(entryPointId == 8) {
			return StringUtils.isOneOf(c, entryPoints8);
		}
		return false;
	}

	/**
	 * true if cell is blank and the east and west cells are not
	 * (used to find gaps between words)
	 *
	 * @param cell
	 * @return
	 */
	public boolean isBlankBetweenCharacters(Cell cell){
		return (isBlank(cell)
				&& !isBlank(cell.getEast())
				&& !isBlank(cell.getWest()));
	}

	/**
	 * Makes blank all the cells that contain non-text
	 * elements.
	 */
	public void removeNonText(){
		//the following order is significant
		//since the south-pointing arrowheads
		//are determined based on the surrounding boundaries
		removeArrowheads();
		removeColorCodes();
		removeBoundaries();
		removeMarkupTags();
	}

	public void removeArrowheads(){
		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width; xi++){
				Cell cell = new Cell(xi, yi);
				if(isArrowhead(cell)) set(cell, ' ');
			}
		}
	}

	public void removeColorCodes(){
		Iterator cells = findColorCodes().iterator();
		while(cells.hasNext()){
			Cell cell = ((CellColorPair) cells.next()).cell;
			set(cell, ' ');
			cell = cell.getEast(); set(cell, ' ');
			cell = cell.getEast(); set(cell, ' ');
			cell = cell.getEast(); set(cell, ' ');
		}
	}

	public void removeBoundaries(){
		ArrayList toBeRemoved = new ArrayList();

		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width; xi++){
				Cell cell = new Cell(xi, yi);
				if(isBoundary(cell)) toBeRemoved.add(cell);
			}
		}

		//remove in two stages, because decision of
		//isBoundary depends on contants of surrounding
		//cells
		Iterator it = toBeRemoved.iterator();
		while(it.hasNext()){
			Cell cell = (Cell) it.next();
			set(cell, ' ');
		}
	}

	public ArrayList findArrowheads(){
		ArrayList result = new ArrayList();
		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width; xi++){
				Cell cell = new Cell(xi, yi);
				if(isArrowhead(cell)) result.add(cell);
			}
		}
		if(DEBUG) System.out.println(result.size()+" arrowheads found");
		return result;
	}


	public ArrayList findColorCodes(){
		Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}");
		ArrayList result = new ArrayList();
		int width = getWidth();
		int height = getHeight();
		for(int yi = 0; yi < height; yi++){
			for(int xi = 0; xi < width - 3; xi++){
				Cell cell = new Cell(xi, yi);
				String s = getStringAt(cell, 4);
				Matcher matcher = colorCodePattern.matcher(s);
				if(matcher.matches()){
					char cR = s.charAt(1);
					char cG = s.charAt(2);
					char cB = s.charAt(3);
					int r = Integer.valueOf(String.valueOf(cR), 16) * 17;
					int g = Integer.valueOf(String.valueOf(cG), 16) * 17;
					int b = Integer.valueOf(String.valueOf(cB), 16) * 17;
					result.add(new CellColorPair(cell, new Color(r, g, b)));
				}
			}
		}
		if(DEBUG) System.out.println(result.size()+" color codes found");
		return result;
	}

	public ArrayList findMarkupTags(){
		Pattern tagPattern = Pattern.compile("\\{(.+?)\\}");
		ArrayList result = new ArrayList();

		int width = getWidth();
		int height = getHeight();
		for(int y = 0; y < height; y++){
			for(int x = 0; x < width - 3; x++){
				Cell cell = new Cell(x, y);
				char c = get(cell);
				if(c == '{'){
					String rowPart = rows.get(y).substring(x);
					Matcher matcher = tagPattern.matcher(rowPart);
					if(matcher.find()){
						String tagName = matcher.group(1);
						if(markupTags.contains(tagName)){
							if(DEBUG) System.out.println("found tag "+tagName+" at "+x+", "+y);
							result.add(new CellTagPair(new Cell(x, y), tagName));
						}
					}
				}
			}
		}
		return result;
	}

	public void removeMarkupTags(){
		Iterator it = findMarkupTags().iterator();
		while (it.hasNext()) {
			CellTagPair pair = (CellTagPair) it.next();
			String tagName = pair.tag;
			if(tagName == null) continue;
			int length = 2 + tagName.length();
			writeStringTo(pair.cell, StringUtils.repeatString(" ", length));
		}
	}



	public boolean matchesAny(GridPatternGroup criteria){
		return criteria.isAnyMatchedBy(this);
	}

	public boolean matchesAll(GridPatternGroup criteria){
		return criteria.areAllMatchedBy(this);
	}

	public boolean matches(GridPattern criteria){
		return criteria.isMatchedBy(this);
	}


	public boolean isOnHorizontalLine(Cell cell){ return isOnHorizontalLine(cell.x, cell.y); }
	private boolean isOnHorizontalLine(int x, int y){
		char c1 = get(x - 1, y);
		char c2 = get(x + 1, y);
		if(isHorizontalLine(c1) && isHorizontalLine(c2)) return true;
		return false;
	}

	public boolean isOnVerticalLine(Cell cell){ return isOnVerticalLine(cell.x, cell.y); }
	private boolean isOnVerticalLine(int x, int y){
		char c1 = get(x, y - 1);
		char c2 = get(x, y + 1);
		if(isVerticalLine(c1) && isVerticalLine(c2)) return true;
		return false;
	}


	public static boolean isBoundary(char c){
		return StringUtils.isOneOf(c, boundaries);
	}
	public boolean isBoundary(int x, int y){ return isBoundary(new Cell(x, y)); }
	public boolean isBoundary(Cell cell){
		char c = get(cell.x, cell.y);
		if(0 == c) return false;
		if('+' == c || '\\' == c || '/' == c){
			// System.out.print("");
			if(
			       isIntersection(cell)
				|| isCorner(cell)
				|| isStub(cell)
				|| isCrossOnLine(cell)){
				return true;
			} else return false;
		}
		//return StringUtils.isOneOf(c, undisputableBoundaries);
		if(StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)){
			return true;
		}
		return false;
	}

	public boolean isLine(Cell cell){
		return isHorizontalLine(cell) || isVerticalLine(cell);
	}

	public static boolean isHorizontalLine(char c){
		return StringUtils.isOneOf(c, horizontalLines);
	}
	public boolean isHorizontalLine(Cell cell){ return isHorizontalLine(cell.x, cell.y); }
	public boolean isHorizontalLine(int x, int y){
		char c = get(x, y);
		if(0 == c) return false;
		return StringUtils.isOneOf(c, horizontalLines);
	}

	public static boolean isVerticalLine(char c){
		return StringUtils.isOneOf(c, verticalLines);
	}
	public boolean isVerticalLine(Cell cell){ return isVerticalLine(cell.x, cell.y); }
	public boolean isVerticalLine(int x, int y){
		char c = get(x, y);
		if(0 == c) return false;
		return StringUtils.isOneOf(c, verticalLines);
	}

	public boolean isLinesEnd(int x, int y){
		return isLinesEnd(new Cell(x, y));
	}

	/**
	 * Stubs are also considered end of lines
	 *
	 * @param cell
	 * @return
	 */
	public boolean isLinesEnd(Cell cell){
		return matchesAny(cell, GridPatternGroup.linesEndCriteria);
	}

	public boolean isVerticalLinesEnd(Cell cell){
		return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria);
	}

	public boolean isHorizontalLinesEnd(Cell cell){
		return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria);
	}


	public boolean isPointCell(Cell cell){
		return (
			isCorner(cell)
			|| isIntersection(cell)
			|| isStub(cell)
			|| isLinesEnd(cell));
	}


	public boolean containsAtLeastOneDashedLine(CellSet set){
		Iterator it = set.iterator();
		while(it.hasNext()) {
			Cell cell = (Cell) it.next();
			if(StringUtils.isOneOf(get(cell), dashedLines)) return true;
		}
		return false;
	}

	public boolean exactlyOneNeighbourIsBoundary(Cell cell) {
		int howMany = 0;
		if(isBoundary(cell.getNorth())) howMany++;
		if(isBoundary(cell.getSouth())) howMany++;
		if(isBoundary(cell.getEast())) howMany++;
		if(isBoundary(cell.getWest())) howMany++;
		return (howMany == 1);
	}

	/**
	 *
	 * A stub looks like that:
	 *
	 * 
	 *
	 * +- or -+ or + or + or /- or -/ or / (you get the point)
	 *             |    |                |
	 *
	 * 
* * @param cell * @return */ public boolean isStub(Cell cell){ return matchesAny(cell, GridPatternGroup.stubCriteria); } public boolean isCrossOnLine(Cell cell){ return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); } public boolean isHorizontalCrossOnLine(Cell cell){ return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); } public boolean isVerticalCrossOnLine(Cell cell){ return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); } public boolean isStarOnLine(Cell cell){ return matchesAny(cell, GridPatternGroup.starOnLineCriteria); } public boolean isLoneDiagonal(Cell cell){ return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); } public boolean isHorizontalStarOnLine(Cell cell){ return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); } public boolean isVerticalStarOnLine(Cell cell){ return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); } public boolean isArrowhead(Cell cell){ return (isNorthArrowhead(cell) || isSouthArrowhead(cell) || isWestArrowhead(cell) || isEastArrowhead(cell)); } public boolean isNorthArrowhead(Cell cell){ return get(cell) == '^'; } public boolean isEastArrowhead(Cell cell){ return get(cell) == '>'; } public boolean isWestArrowhead(Cell cell){ return get(cell) == '<'; } public boolean isSouthArrowhead(Cell cell){ return (get(cell) == 'v' || get(cell) == 'V') && isVerticalLine(cell.getNorth()); } // unicode for bullets // // 2022 bullet // 25CF black circle // 25AA black circle (small) // 25A0 black square // 25A1 white square // 25CB white circle // 25BA black right-pointing pointer public boolean isBullet(int x, int y){ return isBullet(new Cell(x, y)); } public boolean isBullet(Cell cell){ char c = get(cell); if((c == 'o' || c == '*') && isBlank(cell.getEast()) && isBlank(cell.getWest()) && Character.isLetterOrDigit(get(cell.getEast().getEast())) ) return true; return false; } public void replaceBullets(){ int width = getWidth(); int height = getHeight(); for(int yi = 0; yi < height; yi++){ for(int xi = 0; xi < width; xi++){ Cell cell = new Cell(xi, yi); if(isBullet(cell)){ set(cell, ' '); set(cell.getEast(), '\u2022'); } } } } /** * true if the cell is not blank * but the previous (west) is * * @param cell * @return */ public boolean isStringsStart(Cell cell){ return (!isBlank(cell) && isBlank(cell.getWest())); } /** * true if the cell is not blank * but the next (east) is * * @param cell * @return */ public boolean isStringsEnd(Cell cell){ return (!isBlank(cell) //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); && isBlank(cell.getEast())); } public int otherStringsStartInTheSameColumn(Cell cell){ if(!isStringsStart(cell)) return 0; int result = 0; int height = getHeight(); for(int y = 0; y < height; y++){ Cell cCell = new Cell(cell.x, y); if(!cCell.equals(cell) && isStringsStart(cCell)){ result++; } } return result; } public int otherStringsEndInTheSameColumn(Cell cell){ if(!isStringsEnd(cell)) return 0; int result = 0; int height = getHeight(); for(int y = 0; y < height; y++){ Cell cCell = new Cell(cell.x, y); if(!cCell.equals(cell) && isStringsEnd(cCell)){ result++; } } return result; } public boolean isColumnBlank(int x){ int height = getHeight(); for(int y = 0; y < height; y++){ if(!isBlank(x, y)) return false; } return true; } public CellSet followLine(int x, int y){ return followLine(new Cell(x, y)); } public CellSet followIntersection(Cell cell){ return followIntersection(cell, null); } public CellSet followIntersection(Cell cell, Cell blocked){ if(!isIntersection(cell)) return null; CellSet result = new CellSet(); Cell cN = cell.getNorth(); Cell cS = cell.getSouth(); Cell cE = cell.getEast(); Cell cW = cell.getWest(); if(hasEntryPoint(cN, 6)) result.add(cN); if(hasEntryPoint(cS, 2)) result.add(cS); if(hasEntryPoint(cE, 8)) result.add(cE); if(hasEntryPoint(cW, 4)) result.add(cW); if(result.contains(blocked)) result.remove(blocked); return result; } /** * Returns the neighbours of a line-cell that are boundaries * (0 to 2 cells are returned) * * @param cell * @return null if the cell is not a line */ public CellSet followLine(Cell cell){ if(isHorizontalLine(cell)){ CellSet result = new CellSet(); if(isBoundary(cell.getEast())) result.add(cell.getEast()); if(isBoundary(cell.getWest())) result.add(cell.getWest()); return result; } else if (isVerticalLine(cell)){ CellSet result = new CellSet(); if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); return result; } return null; } public CellSet followLine(Cell cell, Cell blocked){ CellSet nextCells = followLine(cell); if(nextCells.contains(blocked)) nextCells.remove(blocked); return nextCells; } public CellSet followCorner(Cell cell){ return followCorner(cell, null); } public CellSet followCorner(Cell cell, Cell blocked){ if(!isCorner(cell)) return null; if(isCorner1(cell)) return followCorner1(cell, blocked); if(isCorner2(cell)) return followCorner2(cell, blocked); if(isCorner3(cell)) return followCorner3(cell, blocked); if(isCorner4(cell)) return followCorner4(cell, blocked); return null; } public CellSet followCorner1(Cell cell){ return followCorner1(cell, null); } public CellSet followCorner1(Cell cell, Cell blocked){ if(!isCorner1(cell)) return null; CellSet result = new CellSet(); if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); return result; } public CellSet followCorner2(Cell cell){ return followCorner2(cell, null); } public CellSet followCorner2(Cell cell, Cell blocked){ if(!isCorner2(cell)) return null; CellSet result = new CellSet(); if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); return result; } public CellSet followCorner3(Cell cell){ return followCorner3(cell, null); } public CellSet followCorner3(Cell cell, Cell blocked){ if(!isCorner3(cell)) return null; CellSet result = new CellSet(); if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); return result; } public CellSet followCorner4(Cell cell){ return followCorner4(cell, null); } public CellSet followCorner4(Cell cell, Cell blocked){ if(!isCorner4(cell)) return null; CellSet result = new CellSet(); if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); return result; } public CellSet followStub(Cell cell){ return followStub(cell, null); } public CellSet followStub(Cell cell, Cell blocked){ if(!isStub(cell)) return null; CellSet result = new CellSet(); if(isBoundary(cell.getEast())) result.add(cell.getEast()); else if(isBoundary(cell.getWest())) result.add(cell.getWest()); else if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); else if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); if(result.contains(blocked)) result.remove(blocked); return result; } public CellSet followCell(Cell cell){ return followCell(cell, null); } public CellSet followCell(Cell cell, Cell blocked){ if(isIntersection(cell)) return followIntersection(cell, blocked); if(isCorner(cell)) return followCorner(cell, blocked); if(isLine(cell)) return followLine(cell, blocked); if(isStub(cell)) return followStub(cell, blocked); if(isCrossOnLine(cell)) return followCrossOnLine(cell, blocked); System.err.println("Umbiguous input at position "+cell+":"); TextGrid subGrid = getTestingSubGrid(cell); subGrid.printDebug(); throw new RuntimeException("Cannot follow cell "+cell+": cannot determine cell type"); } public String getCellTypeAsString(Cell cell){ if(isK(cell)) return "K"; if(isT(cell)) return "T"; if(isInverseK(cell)) return "inverse K"; if(isInverseT(cell)) return "inverse T"; if(isCorner1(cell)) return "corner 1"; if(isCorner2(cell)) return "corner 2"; if(isCorner3(cell)) return "corner 3"; if(isCorner4(cell)) return "corner 4"; if(isLine(cell)) return "line"; if(isStub(cell)) return "stub"; if(isCrossOnLine(cell)) return "crossOnLine"; return "unrecognisable type"; } public CellSet followCrossOnLine(Cell cell, Cell blocked){ CellSet result = new CellSet(); if(isHorizontalCrossOnLine(cell)){ result.add(cell.getEast()); result.add(cell.getWest()); } else if(isVerticalCrossOnLine(cell)){ result.add(cell.getNorth()); result.add(cell.getSouth()); } if(result.contains(blocked)) result.remove(blocked); return result; } public boolean isOutOfBounds(Cell cell){ if(cell.x > getWidth() - 1 || cell.y > getHeight() - 1 || cell.x < 0 || cell.y < 0) return true; return false; } public boolean isOutOfBounds(int x, int y){ char c = get(x, y); if(0 == c) return true; return false; } public boolean isBlank(Cell cell){ char c = get(cell); if(0 == c) return false; return c == ' '; } public boolean isBlank(int x, int y){ char c = get(x, y); if(0 == c) return true; return c == ' '; } public boolean isCorner(Cell cell){ return isCorner(cell.x, cell.y); } public boolean isCorner(int x, int y){ return (isNormalCorner(x,y) || isRoundCorner(x,y)); } public boolean matchesAny(Cell cell, GridPatternGroup criteria){ TextGrid subGrid = getTestingSubGrid(cell); return subGrid.matchesAny(criteria); } public boolean isCorner1(Cell cell){ return matchesAny(cell, GridPatternGroup.corner1Criteria); } public boolean isCorner2(Cell cell){ return matchesAny(cell, GridPatternGroup.corner2Criteria); } public boolean isCorner3(Cell cell){ return matchesAny(cell, GridPatternGroup.corner3Criteria); } public boolean isCorner4(Cell cell){ return matchesAny(cell, GridPatternGroup.corner4Criteria); } public boolean isCross(Cell cell){ return matchesAny(cell, GridPatternGroup.crossCriteria); } public boolean isK(Cell cell){ return matchesAny(cell, GridPatternGroup.KCriteria); } public boolean isInverseK(Cell cell){ return matchesAny(cell, GridPatternGroup.inverseKCriteria); } public boolean isT(Cell cell){ return matchesAny(cell, GridPatternGroup.TCriteria); } public boolean isInverseT(Cell cell){ return matchesAny(cell, GridPatternGroup.inverseTCriteria); } public boolean isNormalCorner(Cell cell){ return matchesAny(cell, GridPatternGroup.normalCornerCriteria); } public boolean isNormalCorner(int x, int y){ return isNormalCorner(new Cell(x, y)); } public boolean isRoundCorner(Cell cell){ return matchesAny(cell, GridPatternGroup.roundCornerCriteria); } public boolean isRoundCorner(int x, int y){ return isRoundCorner(new Cell(x, y)); } public boolean isIntersection(Cell cell){ return matchesAny(cell, GridPatternGroup.intersectionCriteria); } public boolean isIntersection(int x, int y){ return isIntersection(new Cell(x, y)); } public void copyCellsTo(CellSet cells, TextGrid grid){ Iterator it = cells.iterator(); while(it.hasNext()){ Cell cell = (Cell) it.next(); grid.set(cell, this.get(cell)); } } public boolean equals(TextGrid grid){ if(grid.getHeight() != this.getHeight() || grid.getWidth() != this.getWidth() ){ return false; } int height = grid.getHeight(); for(int i = 0; i < height; i++){ String row1 = this.getRow(i).toString(); String row2 = grid.getRow(i).toString(); if(!row1.equals(row2)) return false; } return true; } // @Override // public int hashCode() { // int h = 0; // for(StringBuffer row : rows) { // h += row.hashCode(); // } // return h; // } /** * Fills all the cells in cells with c * * @param cells * @param c */ public void fillCellsWith(Iterable cells, char c){ Iterator it = cells.iterator(); while(it.hasNext()){ Cell cell = it.next(); set(cell.x, cell.y, c); } } /* * * Fills the continuous area with if c1 characters with c2, * flooding from cell x, y * * @param x * @param y * @param c1 the character to replace * @param c2 the character to replace c1 with * @return the list of cells filled */ // public CellSet fillContinuousArea(int x, int y, char c1, char c2){ // CellSet cells = new CellSet(); // //fillContinuousArea_internal(x, y, c1, c2, cells); // seedFill(new Cell(x, y), c1, c2); // return cells; // } public CellSet fillContinuousArea(int x, int y, char c){ return fillContinuousArea(new Cell(x, y), c); } public CellSet fillContinuousArea(Cell cell, char c){ if(isOutOfBounds(cell)) throw new IllegalArgumentException("Attempted to fill area out of bounds: "+cell); return seedFillOld(cell, c); } private CellSet seedFill(Cell seed, char newChar){ CellSet cellsFilled = new CellSet(); char oldChar = get(seed); if(oldChar == newChar) return cellsFilled; if(isOutOfBounds(seed)) return cellsFilled; Stack stack = new Stack(); stack.push(seed); while(!stack.isEmpty()){ Cell cell = (Cell) stack.pop(); //set(cell, newChar); cellsFilled.add(cell); Cell nCell = cell.getNorth(); Cell sCell = cell.getSouth(); Cell eCell = cell.getEast(); Cell wCell = cell.getWest(); if(get(nCell) == oldChar && !cellsFilled.contains(nCell)) stack.push(nCell); if(get(sCell) == oldChar && !cellsFilled.contains(sCell)) stack.push(sCell); if(get(eCell) == oldChar && !cellsFilled.contains(eCell)) stack.push(eCell); if(get(wCell) == oldChar && !cellsFilled.contains(wCell)) stack.push(wCell); } return cellsFilled; } private CellSet seedFillOld(Cell seed, char newChar){ CellSet cellsFilled = new CellSet(); char oldChar = get(seed); if(oldChar == newChar) return cellsFilled; if(isOutOfBounds(seed)) return cellsFilled; Stack stack = new Stack(); stack.push(seed); while(!stack.isEmpty()){ Cell cell = (Cell) stack.pop(); set(cell, newChar); cellsFilled.add(cell); Cell nCell = cell.getNorth(); Cell sCell = cell.getSouth(); Cell eCell = cell.getEast(); Cell wCell = cell.getWest(); if(get(nCell) == oldChar) stack.push(nCell); if(get(sCell) == oldChar) stack.push(sCell); if(get(eCell) == oldChar) stack.push(eCell); if(get(wCell) == oldChar) stack.push(wCell); } return cellsFilled; } /** * * Locates and returns the '*' boundaries that we would * encounter if we did a flood-fill at seed. * * @param seed * @return */ public CellSet findBoundariesExpandingFrom(Cell seed){ CellSet boundaries = new CellSet(); char oldChar = get(seed); if(isOutOfBounds(seed)) return boundaries; char newChar = 1; //TODO: kludge Stack stack = new Stack(); stack.push(seed); while(!stack.isEmpty()){ Cell cell = (Cell) stack.pop(); set(cell, newChar); Cell nCell = cell.getNorth(); Cell sCell = cell.getSouth(); Cell eCell = cell.getEast(); Cell wCell = cell.getWest(); if(get(nCell) == oldChar) stack.push(nCell); else if(get(nCell) == '*') boundaries.add(nCell); if(get(sCell) == oldChar) stack.push(sCell); else if(get(sCell) == '*') boundaries.add(sCell); if(get(eCell) == oldChar) stack.push(eCell); else if(get(eCell) == '*') boundaries.add(eCell); if(get(wCell) == oldChar) stack.push(wCell); else if(get(wCell) == '*') boundaries.add(wCell); } return boundaries; } //TODO: incomplete method seedFillLine() private CellSet seedFillLine(Cell cell, char newChar){ CellSet cellsFilled = new CellSet(); Stack stack = new Stack(); char oldChar = get(cell); if(oldChar == newChar) return cellsFilled; if(isOutOfBounds(cell)) return cellsFilled; stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); int left; while(!stack.isEmpty()){ LineSegment segment = (LineSegment) stack.pop(); int x; //expand to the left for( x = segment.x1; x >= 0 && get(x, segment.y) == oldChar; --x){ set(x, segment.y, newChar); cellsFilled.add(new Cell(x, segment.y)); } left = cell.getEast().x; boolean skip = (x > segment.x1)? true : false; if(left < segment.x1){ //leak on left? //TODO: i think the first param should be x stack.push( //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); new LineSegment(x, left, segment.y - 1, -segment.dy)); } x = segment.x1 + 1; do { if(!skip) { for( ; x < getWidth() && get(x, segment.y) == oldChar; ++x){ set(x, segment.y, newChar); cellsFilled.add(new Cell(x, segment.y)); } stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); if(x > segment.x2 + 1) //leak on right? stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); } skip = false; //skip only once for(++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x){;} left = x; } while( x < segment.x2); } return cellsFilled; } public boolean cellContainsDashedLineChar(Cell cell){ char c = get(cell); return StringUtils.isOneOf(c, dashedLines); } public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { ArrayList lines = new ArrayList(); String[] linesArray = text.split("(\r)?\n"); for(int i = 0; i < linesArray.length; i++) lines.add(new StringBuffer(linesArray[i])); return initialiseWithLines(lines, options); } public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { //remove blank rows at the bottom rows = new ArrayList<>(); for (StringBuffer row:lines) { if(!StringUtils.isBlank(row.toString())) rows.add(row); } if(options != null) fixTabs(options.getTabSize()); else fixTabs(ProcessingOptions.DEFAULT_TAB_SIZE); // make all lines of equal length // add blank outline around the buffer to prevent fill glitch // convert tabs to spaces (or remove them if setting is 0) int blankBorderSize = 2; int maxLength = 0; int index = 0; String encoding = null; //if(options != null) encoding = options.getCharacterEncoding(); Iterator it = rows.iterator(); while(it.hasNext()){ String row = it.next().toString(); if(encoding != null){ byte[] bytes = row.getBytes(); row = new String(bytes, encoding); } if(row.length() > maxLength) maxLength = row.length(); rows.set(index, new StringBuffer(row)); index++; } it = rows.iterator(); ArrayList newRows = new ArrayList(); //TODO: make the following depend on blankBorderSize StringBuffer topBottomRow = new StringBuffer(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); newRows.add(topBottomRow); newRows.add(topBottomRow); while(it.hasNext()){ StringBuffer row = it.next(); if(row.length() < maxLength) { String borderString = StringUtils.repeatString(" ", blankBorderSize); StringBuffer newRow = new StringBuffer(); newRow.append(borderString); newRow.append(row); newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); newRow.append(borderString); newRows.add(newRow); } else { //TODO: why is the following line like that? newRows.add(new StringBuffer(" ").append(row).append(" ")); } } //TODO: make the following depend on blankBorderSize newRows.add(topBottomRow); newRows.add(topBottomRow); rows = newRows; replaceBullets(); replaceHumanColorCodes(); return true; } private void fixTabs(int tabSize){ int rowIndex = 0; Iterator it = rows.iterator(); while(it.hasNext()){ String row = it.next().toString(); StringBuffer newRow = new StringBuffer(); char[] chars = row.toCharArray(); for(int i = 0; i < chars.length; i++){ if(chars[i] == '\t'){ int spacesLeft = tabSize - newRow.length() % tabSize; if(DEBUG){ System.out.println("Found tab. Spaces left: "+spacesLeft); } String spaces = StringUtils.repeatString(" ", spacesLeft); newRow.append(spaces); } else { String character = Character.toString(chars[i]); newRow.append(character); } } rows.set(rowIndex, newRow); rowIndex++; } } /** * @return */ protected ArrayList getRows() { return rows; } public class CellColorPair{ public CellColorPair(Cell cell, Color color){ this.cell = cell; this.color = color; } public Color color; public Cell cell; } public class CellStringPair{ public CellStringPair(Cell cell, String string){ this.cell = cell; this.string = string; } public Cell cell; public String string; } public class CellTagPair{ public CellTagPair(Cell cell, String tag){ this.cell = cell; this.tag = tag; } public Cell cell; public String tag; } public class Cell{ public int x, y; public Cell(Cell cell){ this(cell.x, cell.y); } public Cell(int x, int y){ this.x = x; this.y = y; } public Cell getNorth(){ return new Cell(x, y - 1); } public Cell getSouth(){ return new Cell(x, y + 1); } public Cell getEast(){ return new Cell(x + 1, y); } public Cell getWest(){ return new Cell(x - 1, y); } public Cell getNW(){ return new Cell(x - 1, y - 1); } public Cell getNE(){ return new Cell(x + 1, y - 1); } public Cell getSW(){ return new Cell(x - 1, y + 1); } public Cell getSE(){ return new Cell(x + 1, y + 1); } public CellSet getNeighbours4(){ CellSet result = new CellSet(); result.add(getNorth()); result.add(getSouth()); result.add(getWest()); result.add(getEast()); return result; } public CellSet getNeighbours8(){ CellSet result = new CellSet(); result.add(getNorth()); result.add(getSouth()); result.add(getWest()); result.add(getEast()); result.add(getNW()); result.add(getNE()); result.add(getSW()); result.add(getSE()); return result; } public boolean isNorthOf(Cell cell){ if(this.y < cell.y) return true; return false; } public boolean isSouthOf(Cell cell){ if(this.y > cell.y) return true; return false; } public boolean isWestOf(Cell cell){ if(this.x < cell.x) return true; return false; } public boolean isEastOf(Cell cell){ if(this.x > cell.x) return true; return false; } public boolean equals(Object o){ Cell cell = (Cell) o; if(cell == null) return false; if(x == cell.x && y == cell.y) return true; else return false; } public int hashCode() { return (x << 16) | y; } public boolean isNextTo(int x2, int y2){ if(Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) return false; if(Math.abs(x2 - x) == 1 && y2 == y) return true; if(Math.abs(y2 - y) == 1 && x2 == x) return true; return false; } public boolean isNextTo(Cell cell){ if(cell == null) throw new IllegalArgumentException("cell cannot be null"); return this.isNextTo(cell.x, cell.y); } public String toString(){ return "("+x+", "+y+")"; } public void scale(int s){ x = x * s; y = y * s; } } private class LineSegment{ int x1, x2, y, dy; public LineSegment(int x1, int x2, int y, int dy){ this.x1 = x1; this.x2 = x2; this.y = y; this.dy = dy; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy