package hu.supercluster.gameoflife.app.view; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.view.Surface; import android.view.SurfaceHolder; import com.squareup.otto.Subscribe; import java.util.LinkedList; import java.util.List; import hu.supercluster.gameoflife.game.cellularautomaton.CellularAutomaton; import hu.supercluster.gameoflife.game.event.CellStateChange; import hu.supercluster.gameoflife.game.event.PaintWithBrush; import hu.supercluster.gameoflife.game.event.Pause; import hu.supercluster.gameoflife.game.event.Reset; import hu.supercluster.gameoflife.game.event.Restart; import hu.supercluster.gameoflife.game.event.Resume; import hu.supercluster.gameoflife.game.grid.Grid; import hu.supercluster.gameoflife.game.manager.GameParams; import hu.supercluster.gameoflife.game.visualization.brush.Brush; import hu.supercluster.gameoflife.game.visualization.brush.DefaultBlockBrush; import hu.supercluster.gameoflife.game.visualization.cell.CellColors; import hu.supercluster.gameoflife.util.EventBus; class AutomatonThread extends Thread { private final CellularAutomaton automaton; private final SurfaceHolder surfaceHolder; private final CoordinateTranslator translator; private final List<CellStateChange> cellStateChanges; private final int cellSizeInPixels; private final int timeForAFrame; private final CellColors cellColors; private final GameParams params; private boolean isRunning; private boolean shouldReset; private boolean shouldRestart; private boolean paused; private long cycleTime; private Bitmap buffCanvasBitmap; private Canvas buffCanvas; private Brush brush; public AutomatonThread(CellularAutomaton automaton, SurfaceHolder surfaceHolder, GameParams params) { translator = new CoordinateTranslator(params.getScreenOrientation(), automaton.getSizeX(), automaton.getSizeY()); cellStateChanges = new LinkedList<>(); this.automaton = automaton; this.params = params; this.surfaceHolder = surfaceHolder; this.cellSizeInPixels = params.getCellSizeInPixels(); timeForAFrame = 1000 / params.getFps(); cellColors = params.getCellColors(); brush = new DefaultBlockBrush(); paused = params.startPaused(); createBuffCanvas(); initialDraw(); } protected void createBuffCanvas() { final Point displaySize = params.getDisplaySize(); buffCanvasBitmap = Bitmap.createBitmap(displaySize.x, displaySize.y, Bitmap.Config.ARGB_8888); buffCanvas = new Canvas(); buffCanvas.setBitmap(buffCanvasBitmap); } private void initialDraw() { final Grid grid = automaton.getCurrentState(); for (int j = 0; j < grid.getSizeY(); j++) { for (int i = 0; i < grid.getSizeX(); i++) { paintCell(i, j, grid.getCell(i, j).getState()); } } } private void paintCell(int i, int j, int cellState) { Point p = translator.translate(new Point(i, j)); int x = p.x; int y = p.y; Paint paint = cellColors.getPaint(cellState); Rect rect = new Rect( x * cellSizeInPixels, y * cellSizeInPixels, (x + 1) * cellSizeInPixels, (y + 1) * cellSizeInPixels ); buffCanvas.drawRect(rect, paint); } @Subscribe synchronized public void onEvent(PaintWithBrush event) { final Point p = translator.reverseTranslate(new Point(event.x, event.y)); brush.paint(automaton, p); } @Subscribe public void onEvent(CellStateChange cellStateChange) { synchronized (cellStateChanges) { cellStateChanges.add(cellStateChange); } } @Subscribe synchronized public void onEvent(Pause event) { paused = true; } @Subscribe synchronized public void onEvent(Resume event) { paused = false; } @Subscribe synchronized public void onEvent(Reset event) { shouldReset = true; } @Subscribe synchronized public void onEvent(Restart event) { shouldRestart = true; } public void setRunning(boolean v) { this.isRunning = v; } @Override public void run() { EventBus.getInstance().register(this); while (isRunning) { canvasCycle(); } EventBus.getInstance().unregister(this); } protected void canvasCycle() { Canvas canvas = null; try { canvas = surfaceHolder.lockCanvas(); gameCycle(canvas); } catch (InterruptedException e) { e.printStackTrace(); } finally { unlockCanvasAndPost(canvas); } } protected void gameCycle(Canvas canvas) throws InterruptedException { if (canvas != null) { measuredCycleCore(canvas); sleepToKeepFps(); } } protected void measuredCycleCore(Canvas canvas) { synchronized (surfaceHolder) { long t0 = System.currentTimeMillis(); cycleCore(canvas); long t1 = System.currentTimeMillis(); cycleTime = t1 - t0; } } private void cycleCore(Canvas canvas) { handleFlags(); if (!paused) { stepAutomaton(); } draw(canvas); } private void handleFlags() { handleReset(); handleRestart(); resetFlags(); } private void handleReset() { if (shouldReset) { clearCanvas(automaton.getDefaultCellState()); automaton.reset(); } } private void handleRestart() { if (shouldRestart) { clearCanvas(automaton.getDefaultCellState()); automaton.reset(); automaton.randomFill(params.getFill()); } } private void clearCanvas(int state) { final Point displaySize = params.getDisplaySize(); Rect rect = new Rect(0, 0, displaySize.x, displaySize.y); Paint paint = cellColors.getPaint(state); buffCanvas.drawRect(rect, paint); } private void resetFlags() { shouldReset = false; shouldRestart = false; } private void stepAutomaton() { automaton.step(); } private void draw(Canvas canvas) { LinkedList<CellStateChange> changes; synchronized (cellStateChanges) { changes = new LinkedList<>(this.cellStateChanges); cellStateChanges.clear(); } for (CellStateChange change : changes) { paintCell(change.x, change.y, change.stateSnapshot); } canvas.drawBitmap(buffCanvasBitmap, 0, 0, null); } private void sleepToKeepFps() throws InterruptedException { long sleepTime = timeForAFrame - cycleTime; if (sleepTime > 0) { sleep(sleepTime); } } private void unlockCanvasAndPost(Canvas canvas) { if (canvas != null) { surfaceHolder.unlockCanvasAndPost(canvas); } } private static class CoordinateTranslator { private final int screenOrientation; private final int automatonSizeX; private final int automatonSizeY; private CoordinateTranslator(int screenOrientation, int automatonSizeX, int automatonSizeY) { this.screenOrientation = screenOrientation; this.automatonSizeX = automatonSizeX; this.automatonSizeY = automatonSizeY; } private Point translate(Point p) { switch (screenOrientation) { case Surface.ROTATION_0: return p; case Surface.ROTATION_90: return new Point(p.y, automatonSizeX - p.x); case Surface.ROTATION_180: return new Point(automatonSizeX - p.x, automatonSizeY - p.y); case Surface.ROTATION_270: return new Point(automatonSizeY - p.y, p.x); default: throw new AssertionError(); } } private Point reverseTranslate(Point p) { switch (screenOrientation) { case Surface.ROTATION_0: return p; case Surface.ROTATION_90: return new Point(automatonSizeX - p.y, p.x); case Surface.ROTATION_180: return new Point(automatonSizeY - p.x, automatonSizeX - p.y); case Surface.ROTATION_270: return new Point(p.y, automatonSizeY - p.x); default: throw new AssertionError(); } } } }