/* * Copyright (C) 2014 AChep@xda <ynkr.wang@gmail.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.bullmobi.snake; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Handler; import android.os.Message; import android.util.Log; import com.bullmobi.snake.snake.Apple; import com.bullmobi.snake.snake.Brick; import com.bullmobi.snake.snake.ColorScheme; import com.bullmobi.snake.snake.GameObject; import com.bullmobi.snake.snake.Snake; import java.util.ArrayList; import java.util.Arrays; /** * Created by Artem Chepurnoy on 23.11.2014. */ public class Logic implements IDirectionChangeListener, IDrawable { private static final String TAG = "Logic"; private static final int TICK = 1; private static final double PERIOD_MAX = 400; private static int COLLISION_MAP_DEPTH = 5; private static GameObject.Node sEmptyNode; private ArrayList<GameObject.Node> mCollisionNodes; private GameObject.Node[][][] mCollisionMap; private int[] mTempArray = new int[2]; private final IDrawable mDrawable; private final Surface mSurface; private final int mColumnsNumber; private final int mRowsNumber; private int mScoreMax; private Paint mPaint; private final Snake mSnake; private final Apple mApple; private final Brick[] mBricks; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case TICK: begin(); apply(); sendEmptyMessageDelayed(TICK, getFramePeriod()); break; } } }; public Logic(IDrawable drawable, int n, int m, ColorScheme... colorSchemes) { mColumnsNumber = n; mRowsNumber = m; mDrawable = drawable; mSurface = new Surface(this); mPaint = new Paint(); mPaint.setColor(0xFF555555); // Initialize map. mCollisionMap = new GameObject.Node[n][m][COLLISION_MAP_DEPTH]; mCollisionNodes = new ArrayList<>(); if (sEmptyNode == null) sEmptyNode = new GameObject.Node(null); // Init snake. GameObject.Node node = new GameObject.Node(this, true); node.move(n / 2, m / 2); mSnake = new Snake(this); mSnake.setHead(node); mSnake.setColorScheme(colorSchemes[0]); // Init apple. mApple = new Apple(this); mApple.setColorScheme(colorSchemes[1]); mApple.getHead().move(generateRandomPosition()); // Init unbeatable bricks. int length = (int) (Math.sqrt(n * m) / 2); mBricks = new Brick[length]; for (int i = 0; i < length; i++) { Brick brick = new Brick(this); brick.getHead().move(generateRandomPosition()); brick.setColorScheme(colorSchemes[2]); mBricks[i] = brick; } } private int[] generateRandomPosition() { int x, y; do { // Pick new position x = (int) (Math.random() * mColumnsNumber); y = (int) (Math.random() * mRowsNumber); } while (mCollisionMap[x][y][0] != null /* then it's free */); mTempArray[0] = x; mTempArray[1] = y; return mTempArray; } /** * Changes the direction of the snake to given one. * * @param direction one of the following * {@link com.bullmobi.snake.snake.Animal#DIRECTION_NONE} to not move, * {@link com.bullmobi.snake.snake.Animal#DIRECTION_LEFT} to move left, * {@link com.bullmobi.snake.snake.Animal#DIRECTION_RIGHT} to move right, * {@link com.bullmobi.snake.snake.Animal#DIRECTION_UP} to move up, * {@link com.bullmobi.snake.snake.Animal#DIRECTION_DOWN} to move down. */ @Override public void onDirectionChange(byte direction) { mSnake.setDirection(direction); } /** * {@inheritDoc} */ @Override public void tweetRedrawCall() { mDrawable.tweetRedrawCall(); } /** * */ public void resume() { mHandler.sendEmptyMessage(TICK); } /** * */ public void pause() { mHandler.removeMessages(TICK); } public void draw(Canvas canvas) { canvas.drawRect( mSurface.getPaddingLeft(), mSurface.getPaddingTop(), mSurface.getPaddingLeft() + mSurface.getWidth(), mSurface.getPaddingTop() + mSurface.getHeight(), mPaint); mSnake.draw(canvas); mApple.draw(canvas); for (Brick brick : mBricks) brick.draw(canvas); } public void begin() { mSnake.tick(); mApple.tick(); for (Brick brick : mBricks) brick.tick(); } public void commitMove(GameObject.Node node, int oldX, int oldY) { mCollisionNodes.add(node); int x = node.xp; int y = node.yp; for (int i = 0; i < COLLISION_MAP_DEPTH; i++) { GameObject.Node[] stack = mCollisionMap[oldX][oldY]; if (stack[i] == node) { int start = i + 1; System.arraycopy(stack, start, stack, start - 1, COLLISION_MAP_DEPTH - start); stack[COLLISION_MAP_DEPTH - 1] = null; break; } } for (int i = 0; i < COLLISION_MAP_DEPTH; i++) { if (mCollisionMap[x][y][i] == null) { mCollisionMap[x][y][i] = node; break; } } } public void apply() { for (GameObject.Node node : mCollisionNodes) { GameObject.Node[] stack = mCollisionMap[node.xp][node.yp]; if (stack[0] == sEmptyNode) { continue; } // Count the number of predators. int predatorsCount = 0; GameObject.Node predator = null; for (GameObject.Node n : stack) { if (n == null) break; if (n.isPredator()) { predator = n; predatorsCount++; } } // Handle the collision. if (predator != null) { GameObject.Node predatorInit = null; if (predatorsCount > 0) { predatorInit = predator.getInitNode(); } for (GameObject.Node n : stack) { if (n == null) break; if (predatorsCount > 1) { n.kill(); } else if (n != predator) { if (n.getInitNode() != predatorInit) { Log.d(TAG, "Reparenting node."); predator.getFinalNode().reparent(n); } else { Log.d(TAG, "Killing node." + predatorInit); n.kill(); } } } } else if (stack[1] != null) { Log.i(TAG, "Collision between non-predators happened!"); } // Clean collision map. stack[0] = sEmptyNode; } mCollisionNodes.clear(); // Generate new stuff. if (!mApple.hasHead()) { // Put new apple on map. GameObject.Node node = new GameObject.Node(this); node.move(generateRandomPosition()); mApple.setHead(node); } // Clean-up collision map. for (GameObject.Node[][] n : mCollisionMap) { for (GameObject.Node[] m : n) { Arrays.fill(m, null); } } // Max score. int score = mSnake.getSize(); mScoreMax = Math.max(mScoreMax, score); } /** * Defines the speed of the game. * * @return the time in millis between two frames and * {@link com.bullmobi.snake.snake.GameObject#tick() moves}. * @see #mScoreMax * @see #PERIOD_MAX */ private long getFramePeriod() { double scoreMaxPossible = mColumnsNumber * mRowsNumber / 3; double score = Math.min(mScoreMax, scoreMaxPossible); return Math.round(PERIOD_MAX * (1 - score / scoreMaxPossible)); } public int getScore() { return mSnake.getSize(); } public Snake getSnake() { return mSnake; } public Surface getSurface() { return mSurface; } public int getColumnsNumber() { return mColumnsNumber; } public int getRowsNumber() { return mRowsNumber; } }