Commit aa8e31ce authored by Christopher League's avatar Christopher League
Browse files

More documentation, save/restore history

parent 8cd27942
......@@ -11,9 +11,14 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.Arrays;
import java.util.Random;
import java.util.Stack;
/* This is the primary hub of the game. It contains a grid of cells, buttons to
* select colors, feedback on number of moves (and when game is over), and an
* undo button.
*/
public class GridActivity extends AppCompatActivity implements FloodGrid {
TypedArray colors;
......@@ -22,11 +27,13 @@ public class GridActivity extends AppCompatActivity implements FloodGrid {
final String COLOR_ARRAY_KEY = "COLOR_ARRAY";
final String NUM_MOVES_KEY = "NUM_MOVES";
final String HISTORY_KEY = "HISTORY";
private GridLayout gridLayout;
private LinearLayout buttonBar;
private TextView numMovesText;
private ImageButton undoButton;
private int numColors;
private int numMoves = 0;
private boolean gameOver = false;
......@@ -41,17 +48,22 @@ public class GridActivity extends AppCompatActivity implements FloodGrid {
// Grab palette
Intent intent = getIntent();
colors = getResources().obtainTypedArray(intent.getIntExtra(MainActivity.PALETTE_KEY, R.array.accentPalette));
numColors = intent.getIntExtra(MainActivity.NUM_COLORS_KEY, colors.length());
colors = getResources().obtainTypedArray(
intent.getIntExtra(MainActivity.PALETTE_KEY,
R.array.accentPalette));
numColors = intent.getIntExtra(
MainActivity.NUM_COLORS_KEY, colors.length());
// buttons are at odd offsets.
// We have to update the button bar to match the number of
// colors being used. Buttons are at odd offsets.
buttonBar = findViewById(R.id.buttonBar);
int buttonIdx = 0;
for( ; buttonIdx < numColors; buttonIdx++) {
ColorButton button = (ColorButton) buttonBar.getChildAt(2*buttonIdx+1);
ColorButton button = (ColorButton)
buttonBar.getChildAt(2*buttonIdx+1);
button.setColor(buttonIdx);
}
// Remove any subsequent buttons and gaps
// Hide any subsequent buttons and gaps
for( ; buttonIdx < MAX_COLORS; buttonIdx++) {
buttonBar.getChildAt(2*buttonIdx).setVisibility(View.GONE);
buttonBar.getChildAt(2*buttonIdx+1).setVisibility(View.GONE);
......@@ -60,21 +72,65 @@ public class GridActivity extends AppCompatActivity implements FloodGrid {
// Set up grid
gridLayout = findViewById(R.id.grid);
gridLayout.removeAllViews();
final int BOARD_SIZE = intent.getIntExtra(MainActivity.BOARD_SIZE_KEY, 5);
final int BOARD_SIZE = intent.getIntExtra(
MainActivity.BOARD_SIZE_KEY, 5);
gridLayout.setColumnCount(BOARD_SIZE);
for(int i = 0; i < BOARD_SIZE * BOARD_SIZE; i++) {
gridLayout.addView(new GridCellView(this));
}
if(savedInstanceState == null) {
FloodGridOps.randomize(this, new Random(), numColors);
gameOver = false;
setNumMoves(0);
newGame();
} else {
FloodGridOps.fromArray(this, savedInstanceState.getIntArray(COLOR_ARRAY_KEY));
// TODO: restore history
gameOver = FloodGridOps.gameOver(this);
setNumMoves(savedInstanceState.getInt(NUM_MOVES_KEY));
numMoves = savedInstanceState.getInt(NUM_MOVES_KEY);
/* Restoring history -- it's a flat array in the bundle, but
* we must parcel it back out into the stack.
*/
int[] flatHistory = savedInstanceState.getIntArray(HISTORY_KEY);
history.clear();
for(int m = 0; m < numMoves; m++) {
history.add(Arrays.copyOfRange(
flatHistory,
m*numCells(),
(m+1)*numCells()));
}
}
updateUI();
}
void newGame() {
FloodGridOps.randomize(this, new Random(), numColors);
history.clear();
gameOver = false;
numMoves = 0;
}
/* The elements of the screen to be updated on each move include
* enabling/disabling the undo button, reporting the number of moves (and
* possibly "game over"), and updating the grid with neighborhood
* information.
*/
void updateUI() {
/* undoButton is an ImageButton, which doesn't visually distinguish
* enabled from disabled, so we need to also mess with some visual
* indicator, like alpha.
*/
if(history.size() > 0) {
undoButton.setEnabled(true);
undoButton.setAlpha(1f);
}
else {
undoButton.setEnabled(false);
undoButton.setAlpha(0.5f);
}
if(gameOver) {
numMovesText.setText(String.format("Solved in %d moves", numMoves));
}
else {
numMovesText.setText(Integer.toString(numMoves));
}
undoButton.setEnabled(history.size() > 0);
recordNeighborsThroughout();
}
......@@ -83,6 +139,17 @@ public class GridActivity extends AppCompatActivity implements FloodGrid {
super.onSaveInstanceState(outState);
outState.putIntArray(COLOR_ARRAY_KEY, FloodGridOps.toArray(this));
outState.putInt(NUM_MOVES_KEY, numMoves);
/* We also need to save the history, which is a stack of integer arrays.
* Simplest thing(?) is to concatenate them all?
*/
int[] flatHistory = new int[history.size() * numCells()];
int i = 0;
for(int[] h : history) {
for(int c : h) {
flatHistory[i++] = c;
}
}
outState.putIntArray(HISTORY_KEY, flatHistory);
}
@Override
......@@ -121,7 +188,7 @@ public class GridActivity extends AppCompatActivity implements FloodGrid {
public void recordNeighborsAt(int row, int column) {
int index = row * edgeSize() + column;
GridCellView cell = (GridCellView) gridLayout.getChildAt(index);
int color = cell.color;
int color = cell.getColor();
boolean west = color == safeGetColorAt(row, column-1);
boolean northwest = color == safeGetColorAt(row-1, column-1);
......@@ -146,7 +213,7 @@ public class GridActivity extends AppCompatActivity implements FloodGrid {
@Override
public int getColorAt(int index) {
GridCellView cell = (GridCellView) gridLayout.getChildAt(index);
return cell.color;
return cell.getColor();
}
@Override
......@@ -164,32 +231,17 @@ public class GridActivity extends AppCompatActivity implements FloodGrid {
return colors.getColor(c, 0);
}
public void setNumMoves(int m) {
numMoves = m;
if(gameOver) {
numMovesText.setText(String.format("Solved in %d moves", numMoves));
}
else {
numMovesText.setText(Integer.toString(numMoves));
}
}
public void onClickUndo(View v) {
FloodGridOps.fromArray(this, history.pop());
gameOver = false;
setNumMoves(numMoves-1);
undoButton.setEnabled(history.size() > 0);
recordNeighborsThroughout();
numMoves--;
updateUI();
}
public void onClickColor(int color) {
if(gameOver) {
// Clicking when game already over restarts
FloodGridOps.randomize(this, new Random(), numColors);
gameOver = false;
setNumMoves(0);
history.clear();
undoButton.setEnabled(false);
newGame();
}
else {
history.push(FloodGridOps.toArray(this));
......@@ -197,10 +249,8 @@ public class GridActivity extends AppCompatActivity implements FloodGrid {
if (FloodGridOps.gameOver(this)) {
gameOver = true;
}
setNumMoves(numMoves + 1);
numMoves++;
}
undoButton.setEnabled(history.size() > 0);
recordNeighborsThroughout();
updateUI();
}
}
......@@ -6,25 +6,58 @@ import android.graphics.Paint;
import android.support.v7.widget.GridLayout;
import android.view.View;
/* A cell on the game grid. It stores an integer color index, and sets the
* actual color by querying the palette from the GridActivity. It also handles
* drawing the shapes and borders to make contiguous colors regions appear
* connected.
*/
public class GridCellView extends View {
int color;
private int color;
private Paint fill = new Paint();
private Paint stroke = new Paint();
private Corner[] neighbors = new Corner[] {
Corner.NO_NO,
Corner.NO_NO,
Corner.NO_NO,
Corner.NO_NO
};
/* Using width/height=0 and weight=1 makes sure everything is equally
* spaced. (In the XML, the entire GridLayout is constrained to a square
* aspect ratio.)
*/
GridCellView(GridActivity activity) {
super(activity);
GridLayout.LayoutParams lp = new GridLayout.LayoutParams();
lp.rowSpec = lp.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f);
lp.rowSpec = lp.columnSpec =
GridLayout.spec(GridLayout.UNDEFINED, 1f);
lp.width = lp.height = 0;
setLayoutParams(lp);
fill.setStyle(Paint.Style.FILL);
stroke.setStyle(Paint.Style.STROKE);
stroke.setColor(Color.BLACK);
stroke.setStrokeWidth(3f);
stroke.setAntiAlias(true);
stroke.setAlpha(127);
}
/* Could just use setBackgroundColor here.
*/
void setColor(int color) {
this.color = color;
//GridActivity activity = (GridActivity) getContext();
//setBackgroundColor(activity.colorFromPalette(color));
invalidate();
}
int getColor() {
return color;
}
/* An enumeration of each quadrant of the cell. Helpful for tracking the
* local neighborhood.
*/
enum Quadrant {
NW(0), NE(1), SE(2), SW(3);
final int value;
......@@ -33,11 +66,28 @@ public class GridCellView extends View {
}
}
/* Enumeration of the possible neighborhood conditions. For NorthWest
* quadrant, this refers to the cells to the left, above, and diagonally
* above-left. Then we use mirroring to reason about other quadrants. We
* only care about the diagonal when the two directly-adjacent neighbors are
* the same color.
*/
enum Corner {
NO_NO, NO_YES, YES_NO, YES_NO_YES, YES_YES_YES
NO_NO, // No neighbors of same color; draw as a corner piece
NO_YES, // Neighbor above, so draw vertically to edge
YES_NO, // Neighbor to left, so draw horizontally
YES_NO_YES, // Both above and left, but cut-out the diagonal
YES_YES_YES // All neighbors, so fill entire cell.
}
static Corner corner (boolean adjacent1, boolean diagonal, boolean adjacent2) {
/* Short-hand for specifying one of the five corner types using three
* boolean flags.
*/
static Corner corner(
boolean adjacent1,
boolean diagonal,
boolean adjacent2
) {
if(adjacent1) {
if(!adjacent2) return Corner.YES_NO;
else if(diagonal) return Corner.YES_YES_YES;
......@@ -47,10 +97,8 @@ public class GridCellView extends View {
else return Corner.NO_NO;
}
private Paint fill = new Paint();
private Paint stroke = new Paint();
private Corner[] neighbors = new Corner[] { Corner.NO_NO, Corner.NO_NO,Corner.NO_NO,Corner.NO_NO};
/* We rely on the parent (GridActivity) to inform us about our local neighborhood.
*/
void setNeighbors(Corner nw, Corner ne, Corner se, Corner sw) {
boolean changed = false;
if(neighbors[0] != nw) {
......@@ -72,39 +120,35 @@ public class GridCellView extends View {
if(changed) invalidate();
}
/* Draw each quadrant to match local neighborhood. Relies on helper versions of drawLine and
* drawRect to do appropriate mirroring of axes based on the quadrant.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
GridActivity activity = (GridActivity) getContext();
fill.setStyle(Paint.Style.FILL);
fill.setColor(activity.colorFromPalette(color));
stroke.setStyle(Paint.Style.STROKE);
stroke.setColor(Color.BLACK);
stroke.setStrokeWidth(3f);
stroke.setAntiAlias(true);
stroke.setAlpha(127);
final float z = getWidth();
final float c = z * 0.5f;
final float m = z * 0.07f;
for(Quadrant q : Quadrant.values()) {
System.out.print(" " + q.value + "=" + q + ":" + neighbors[q.value]);
switch(neighbors[q.value]) {
case NO_NO:
drawRect(canvas, q, m, m, c, c, fill); // corner (nn)
drawRect(canvas, q, m, m, c, c, fill); // corner
drawLine(canvas, q, m, m, m, c, stroke);
drawLine(canvas, q, m, m, c, m, stroke);
break;
case NO_YES:
drawRect(canvas, q, m, 0, c, c, fill); // vert (ny)
drawRect(canvas, q, m, 0, c, c, fill); // vert
drawLine(canvas, q, m, 0, m, c, stroke);
break;
case YES_NO:
drawRect(canvas, q, 0, m, c, c, fill); // horiz (yn)
drawRect(canvas, q, 0, m, c, c, fill); // horiz
drawLine(canvas, q, 0, m, c, m, stroke);
break;
case YES_YES_YES:
drawRect(canvas, q, 0, 0, c, c, fill); // full (yyy)
drawRect(canvas, q, 0, 0, c, c, fill); // full
break;
case YES_NO_YES:
drawRect(canvas, q, m, 0, c, c, fill); // vert
......@@ -114,10 +158,17 @@ public class GridCellView extends View {
break;
}
}
System.out.println();
}
void drawRect(Canvas canvas, Quadrant quadrant, float left, float top, float right, float bottom, Paint paint) {
/* Quadrant-sensitive version of drawRect.
*/
void drawRect(
Canvas canvas,
Quadrant quadrant,
float left, float top,
float right, float bottom,
Paint paint)
{
// Maybe flip horizontal coordinates
float tmp;
switch(quadrant) {
......@@ -137,7 +188,15 @@ public class GridCellView extends View {
canvas.drawRect(left, top, right, bottom, paint);
}
void drawLine(Canvas canvas, Quadrant quadrant, float x1, float y1, float x2, float y2, Paint paint) {
/* Quadrant-sensitive version of drawLine.
*/
void drawLine(
Canvas canvas,
Quadrant quadrant,
float x1, float y1,
float x2, float y2,
Paint paint
) {
switch(quadrant) {
case NE: case SE:
x1 = getWidth() - x1;
......
......@@ -8,13 +8,20 @@ import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/* On the options screen (MainActivity), we let players select the color
* palette. This displays swatches from a given palette, and updates to match
* the number of colors selected.
*/
public class PaletteSwatchesView extends View {
int paletteArrayId = R.array.accentPalette;
int numColors = 6;
int paletteArrayId;
int numColors;
Paint paint = new Paint();
public PaletteSwatchesView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setStyle(Paint.Style.FILL);
}
public void setPaletteId(int paletteArrayId) {
......@@ -30,8 +37,6 @@ public class PaletteSwatchesView extends View {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
TypedArray ta = getResources().obtainTypedArray(paletteArrayId);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
int h = canvas.getHeight();
int w = canvas.getWidth() / numColors;
for(int i = 0; i < numColors; i++) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment