/* * Copyright 2015 Daniel Dittmar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package dan.dit.whatsthat.util.field; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Random; import dan.dit.whatsthat.util.general.BuildException; /** * Field with basic methods that is useful for RiddleGames * that require a 2D field layout with not many fields. If there size * is arbitrary, depending on the screen size or performance critical, * you should consider using own arrays as this class involves creation of more wrapper objects * and maybe data that is not used or required.<br> * The elements are iterated row wise. There is always at least one row and one column for the rectangle field. * Created by daniel on 29.05.15. */ public class Field2D<FE extends FieldElement> implements Iterable<FE> { private int mXCount; private int mYCount; private float mFieldWidth; private float mFieldHeight; private FE[][] mFieldElements; //[0][0] top left corner, [3][1] fourth row, second column private Queue<FE> mPathfindingNodes; private Paint mClearPaint; private Rect mFieldRect; private int mFieldRectPadding = 1; private Field2D(FE[][] fieldElements, float fieldWidth, float fieldHeight) throws BuildException { int xCount = fieldElements[0].length; int yCount = fieldElements.length; if (xCount <= 0 || yCount <= 0 || fieldWidth <= 0 || fieldHeight <= 0) { throw new BuildException().setMissingData("Field2D", "Empty field: " + xCount + "x" + yCount + " a " + fieldWidth + "x" + fieldHeight); } for (FE[] fieldElement : fieldElements) { if (fieldElement.length != xCount) { throw new BuildException().setMissingData("Field2D", "Field not square: " + fieldElement.length); } } mXCount = xCount; mYCount = yCount; mFieldWidth= fieldWidth; mFieldHeight = fieldHeight; mFieldElements = fieldElements; mPathfindingNodes = new LinkedList<>(); mClearPaint = new Paint(); mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mFieldRect = new Rect(); for (FE field : this) { if (field == null) { throw new BuildException().setMissingData("Field2D", "Null field given"); } } } public void drawField(Canvas canvas) { canvas.drawPaint(mClearPaint); for (FE e : this) { setFieldRect(mFieldRect, e); e.draw(canvas, mFieldRect); } } public void setFieldRectPadding(int padding) { mFieldRectPadding = padding; } public int getFieldRectPadding() { return mFieldRectPadding; } public Rect setFieldRect(Rect rect, FE fieldElement) { rect.set((int) (mFieldWidth * fieldElement.mX) + mFieldRectPadding, (int) (mFieldHeight * fieldElement.mY) + mFieldRectPadding, (int) (mFieldWidth * fieldElement.mX + mFieldWidth) - mFieldRectPadding, (int) (mFieldHeight * fieldElement.mY + mFieldHeight) - mFieldRectPadding); return rect; } public FE travelDirection(FieldElement field1, FieldElement field2) { int x = 2 * field2.mX - field1.mX; int y = 2 * field2.mY - field1.mY; if (isValidPosition(x, y)) { return getField(x, y); } return null; } public float getFieldWidth() { return mFieldWidth; } public float getFieldHeight() { return mFieldHeight; } public FE getFieldByCoordinates(float xCoord, float yCoord) { int x = (int) (xCoord / mFieldWidth); int y = (int) (yCoord / mFieldHeight); if (isValidPosition(x, y)) { return mFieldElements[y][x]; } return null; } public FE getField(int x, int y) { return mFieldElements[y][x]; } public int isReachable(FE fromField, FieldElement target, FieldElement.Neighbor[] neighborTypes) { if (fromField.equals(target)) { return 0; } if (neighborTypes == null || neighborTypes.length == 0) { return -1; } //clear pathfinding data for (FE e : this) { e.mPathfindingValue = 0; } mPathfindingNodes.clear(); // ant algorithm, start with fromField mPathfindingNodes.add(fromField); fromField.mPathfindingValue = 1; do { FieldElement currField = mPathfindingNodes.poll(); for (FieldElement.Neighbor n : neighborTypes) { if (hasNeighbor(currField, n)) { FE nextField = getNeighbor(currField, n); if (nextField.equals(target)) { return currField.mPathfindingValue; } if (nextField.mPathfindingValue == 0 && !nextField.isBlocked()) { // field not yet reached and // the next field is not blocked, go on mPathfindingNodes.add(nextField); nextField.mPathfindingValue = currField.mPathfindingValue + 1; } } } } while (!mPathfindingNodes.isEmpty()); return -1; } public List<FE> findPath(FE fromField, FE target, FieldElement.Neighbor[] neighborTypes) { int pathLength = isReachable(fromField, target, neighborTypes); if (pathLength == -1) { return null; } List<FE> path = new ArrayList<>(pathLength); FE curr = target; path.add(curr); for (int i = 0; i < pathLength; i++) { for (FieldElement.Neighbor neighbor : neighborTypes) { if (hasNeighbor(curr, neighbor)) { FE currNeighbor = getNeighbor(curr, neighbor); if (currNeighbor.mPathfindingValue == pathLength - i) { curr = currNeighbor; path.add(curr); break; } } } } Collections.reverse(path); return path; } public FE getNeighbor(FieldElement field, FieldElement.Neighbor neighbor) { return mFieldElements[field.mY + neighbor.mYDelta][field.mX + neighbor.mXDelta]; } public boolean hasNeighbor(FieldElement field, FieldElement.Neighbor neighbor) { return field.mX + neighbor.mXDelta >= 0 && field.mX + neighbor.mXDelta < mXCount && field.mY + neighbor.mYDelta >= 0 && field.mY + neighbor.mYDelta < mYCount; } public boolean isValidPosition(int x, int y) { return x >= 0 && x < mXCount && y >= 0 && y < mYCount; } public int getFieldCount() { return mXCount * mYCount; } public FE getRandomField(Random rand) { return mFieldElements[rand.nextInt(mYCount)][rand.nextInt(mXCount)]; } public static abstract class Builder<FE extends FieldElement> { private List<List<FE>> mRows = new ArrayList<>(); private int mCurrRow = -1; public final void nextElement(FE element) throws BuildException { if (element == null || mCurrRow < 0 || mCurrRow >= mRows.size()) { throw new BuildException().setMissingData("Field2D", "NextElement null or no row yet"); } List<FE> row = mRows.get(mCurrRow); element.mY = mCurrRow; element.mX = row.size(); row.add(element); } public final void nextRow() { mCurrRow++; mRows.add(new ArrayList<FE>()); } protected abstract FE[][] makeArray(int rows, int columns); public final Field2D<FE> build(float fieldWidth, float fieldHeight) throws BuildException { if (mRows.isEmpty()) { throw new BuildException().setMissingData("Field2D", "No rows."); } FE[][] elements = makeArray(mRows.size(), mRows.get(0).size()); int y = 0; for (List<FE> row : mRows) { int x = 0; for (FE element : row) { if (elements[y].length > x) { elements[y][x] = element; x++; } else { throw new BuildException().setMissingData("Field2D", "Too many elements in row " + y + ": " + elements[y].length); } } y++; } return new Field2D<>(elements, fieldWidth, fieldHeight); } } @Override public Iterator<FE> iterator() { return new FieldIterator(); } private class FieldIterator implements Iterator<FE> { private int mCurrX; private int mCurrY; @Override public boolean hasNext() { return mCurrX < mXCount && mCurrY < mYCount; } @Override public FE next() { FE next = mFieldElements[mCurrY][mCurrX]; mCurrX++; if (mCurrX % mXCount == 0) { mCurrX = 0; mCurrY++; } return next; } @Override public void remove() { throw new UnsupportedOperationException("Cannot remove field element."); } } }