/** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, * copy, modify, and distribute this software in source code or binary form for use * in connection with the web services and APIs provided by Facebook. * * As with any software that integrates with the Facebook platform, your use of * this software is subject to the Facebook Developer Principles and Policies * [http://developers.facebook.com/policy/]. This copyright notice shall be * included in all copies or substantial portions of the software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.example.iconicus; import android.net.Uri; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; /** * Abstraction for a game board */ public class GameBoard { public static final int BOARD_ROWS = 9; // 9x9 board public static final int GROUP_ROWS = 3; public static final int BOARD_SIZE = BOARD_ROWS * BOARD_ROWS; // 9x9 board public static final int EMPTY_PIECE = 0; public static final int MIN_VALUE = 1; public static final int MAX_VALUE = 9; private static final Uri SHARE_URI = Uri.parse("https://fb.me/1570399853210604"); private static final String DATA_KEY = "data"; private static final String LOCKED_KEY = "locked"; private static final int [] SEED_GRID = { 1,2,3,4,5,6,7,8,9, 4,5,6,7,8,9,1,2,3, 7,8,9,1,2,3,4,5,6, 2,3,4,5,6,7,8,9,1, 5,6,7,8,9,1,2,3,4, 8,9,1,2,3,4,5,6,7, 3,4,5,6,7,8,9,1,2, 6,7,8,9,1,2,3,4,5, 9,1,2,3,4,5,6,7,8 }; private int[] board = new int[BOARD_SIZE]; private boolean[] lockedPositions = new boolean[BOARD_SIZE]; private GameBoard(int[] board, boolean[] lockedPositions) { if (board.length != this.board.length || lockedPositions.length != this.lockedPositions.length) { throw new IllegalArgumentException("boards are not the same size"); } System.arraycopy(board, 0, this.board, 0, board.length); System.arraycopy(lockedPositions, 0, this.lockedPositions, 0, lockedPositions.length); } private GameBoard(int[] board) { if (board.length != this.board.length) { throw new IllegalArgumentException("boards are not the same size"); } for (int i = 0; i < BOARD_SIZE; i++) { this.board[i] = board[i]; this.lockedPositions[i] = (this.board[i] != EMPTY_PIECE); } } /** * Generates a new valid board. * @param openPositions the number of open positions to leave on the board. * @return a new valid board. */ public static GameBoard generateBoard(final int openPositions) { Random random = new Random(System.currentTimeMillis()); int [] board = new int[BOARD_SIZE]; System.arraycopy(SEED_GRID, 0, board, 0, BOARD_SIZE); for (int i = 0; i < 9; i++) { shuffleGrid(random, board); } List<Integer> remainingPositions = new ArrayList<>(BOARD_SIZE); for (int i = 0; i < BOARD_SIZE; i++) { remainingPositions.add(i); } for (int i = 0; i < openPositions; i++) { removeOpenPosition(random, board, remainingPositions); } return new GameBoard(board); } /** * Returns a GameBoard from a Uri. * @param uri the uri that was shared. * @return a board from the Uri. */ public static GameBoard fromUri(final Uri uri) { String data = uri.getQueryParameter(DATA_KEY); if (data != null) { int [] newBoard = decodeBoard(data); String locked = uri.getQueryParameter(LOCKED_KEY); boolean [] lockedArr; if (locked != null) { lockedArr = decodeLockedPositions(locked); } else { // if there's no explicit locked param, then treat every position // passed in as being locked. lockedArr = new boolean[BOARD_SIZE]; for (int i = 0; i < lockedArr.length; i++) { if (newBoard[i] != EMPTY_PIECE) { lockedArr[i] = true; } } } return new GameBoard(newBoard, lockedArr); } return null; } /** * Clears the existing board of all pieces that weren't locked in place. */ public void clearBoard() { for (int i = 0; i < board.length; i++) { if (!lockedPositions[i]) { board[i] = EMPTY_PIECE; } } } /** * Determines whether the current position is locked or not. * @param position the position of the piece on the board. * @return whether it's locked or not. */ public boolean isLocked(final int position) { return lockedPositions[position]; } /** * Sets the value for the position on the board. Returns true if the value is successfully set, * false otherwise (e.g. the current position is locked and can't be modified). * @param value the value to set. * @param position the position of the piece on the board. * @return true if value is successfully set. */ public boolean setValue(final int value, final int position) { if (!isLocked(position) && ((value >= MIN_VALUE && value <= MAX_VALUE) || value == EMPTY_PIECE)) { board[position] = value; return true; } return false; } /** * Gets the integer value of the piece on the board. * @param position the position of the piece on the board. * @return the integer value (0-9) or -1 if there's no current value. */ public int getValue(final int position) { if (position < 0 || position >= BOARD_SIZE) { return EMPTY_PIECE; } return board[position]; } /** * Gets the string value of the piece on the board. * @param position the position of the piece on the board. * @return the String value ("0"-"9") or the empty string "" if there's no current value. */ public String getValueAsString(final int position) { int value = getValue(position); if (value < MIN_VALUE || value > MAX_VALUE) { return ""; } return "" + value; } /** * Determines whether the current position is empty or not. * @param position the position of the piece. * @return true if the current position is empty. */ public boolean isEmpty(final int position) { return board[position] == EMPTY_PIECE; } /** * Determines whether the value in the current position is valid or not. * @param position the position of the piece. * @return true if the current position is valid (empty pieces are by default valid). */ public boolean isValid(final int position) { if (isEmpty(position)) { return true; } return validateRow(position) && validateColumn(position) && validateGroup(position); } /** * Converts the current board into a Uri. * @return a Uri that represents the current board. */ public Uri toUri() { Uri.Builder shareUri = SHARE_URI.buildUpon(); shareUri.appendQueryParameter(DATA_KEY, encodeBoard()); shareUri.appendQueryParameter(LOCKED_KEY, encodeLockedPositions()); return shareUri.build(); } private String encodeBoard() { StringBuilder builder = new StringBuilder(); for (int i = 0; i < board.length; i++) { builder.append(board[i]); } return builder.toString(); } private static int[] decodeBoard(String input) { int [] newBoard = new int[BOARD_SIZE]; Arrays.fill(newBoard, EMPTY_PIECE); if (input.length() == BOARD_SIZE) { for (int i = 0; i < input.length(); i++) { newBoard[i] = Integer.parseInt(input.substring(i, i + 1)); } } return newBoard; } private String encodeLockedPositions() { StringBuilder builder = new StringBuilder(); for (int i = 0; i < lockedPositions.length; i++) { builder.append(lockedPositions[i] ? 1 : 0);;; } return builder.toString(); } private static boolean[] decodeLockedPositions(String input) { boolean [] locked = new boolean[BOARD_SIZE]; Arrays.fill(locked, false); if (input.length() == BOARD_SIZE) { for (int i = 0; i < input.length(); i++) { locked[i] = (input.charAt(i) == '1'); } } return locked; } private boolean validateRow(final int position) { int startPos = (position / BOARD_ROWS) * BOARD_ROWS; for (int i = 0; i < BOARD_ROWS; i++) { if (!checkIsValid(position, startPos + i)) { return false; } } return true; } private boolean validateColumn(final int position) { int startPos = position % BOARD_ROWS; for (int i = 0; i < BOARD_ROWS; i++) { if (!checkIsValid(position, startPos + (i * BOARD_ROWS))) { return false; } } return true; } private boolean validateGroup(final int position) { int row = position / BOARD_ROWS; int column = position % BOARD_ROWS; int group = (row / GROUP_ROWS) * GROUP_ROWS + (column / GROUP_ROWS); int startRow = (group / GROUP_ROWS) * GROUP_ROWS; int startColumn = (group % GROUP_ROWS) * GROUP_ROWS; for (int i = 0; i < GROUP_ROWS; i++) { for (int j = 0; j < GROUP_ROWS; j++) { if (!checkIsValid(position, (startRow + i) * 9 + startColumn + j)) { return false; } } } return true; } private boolean checkIsValid(final int position1, final int position2) { return (position1 == position2) || (board[position1] != board[position2]); } private static void shuffleGrid(final Random random, final int[] board) { switch (random.nextInt(5)) { case 0: shuffleRow(random, board); break; case 1: shuffleRowGroup(random, board); break; case 2: shuffleColumn(random, board); break; case 3: shuffleColumnGroup(random, board); break; case 4: transpose(board); break; default: break; } } private static void shuffleRow(final Random random, final int[] board) { // Swap two random rows. Note that it's only safe to shuffle rows within each group of 3 // i.e. row 1 can only be shuffled with either row 2 or 3. int group = random.nextInt(GROUP_ROWS); int row1 = random.nextInt(GROUP_ROWS); int row2 = randomOther(random, row1, GROUP_ROWS); int realRow1 = group * GROUP_ROWS + row1; int realRow2 = group * GROUP_ROWS + row2; Range range1 = new Range(realRow1 * BOARD_ROWS, BOARD_ROWS); Range range2 = new Range(realRow2 * BOARD_ROWS, BOARD_ROWS); swap(board, range1, range2); } private static void shuffleRowGroup(final Random random, final int[] board) { // Swap two groups of rows. i.e. swap rows 123 with rows 789 int group1 = random.nextInt(GROUP_ROWS); int group2 = randomOther(random, group1, GROUP_ROWS); Range range1 = new Range(group1 * GROUP_ROWS * BOARD_ROWS, GROUP_ROWS * BOARD_ROWS); Range range2 = new Range(group2 * GROUP_ROWS * BOARD_ROWS, GROUP_ROWS * BOARD_ROWS); swap(board, range1, range2); } private static void shuffleColumn(final Random random, final int[] board) { // Swap two random columns. Note that just like with rows, it's only safe to shuffle columns // within each group of 3 int group = random.nextInt(GROUP_ROWS); int col1 = random.nextInt(GROUP_ROWS); int col2 = randomOther(random, col1, GROUP_ROWS); int realCol1 = group * GROUP_ROWS + col1; int realCol2 = group * GROUP_ROWS + col2; swapColumn(board, realCol1, realCol2); } private static void shuffleColumnGroup(final Random random, final int[] board) { // Swap two groups of columns. i.e. swap columns 123 with columns 789 int group1 = random.nextInt(GROUP_ROWS); int group2 = randomOther(random, group1, GROUP_ROWS); for (int i = 0; i < GROUP_ROWS; i++) { int realCol1 = group1 * GROUP_ROWS + i; int realCol2 = group2 * GROUP_ROWS + i; swapColumn(board, realCol1, realCol2); } } private static void transpose(final int[] board) { for (int row = 0; row < BOARD_ROWS; ++row) { for (int col = row + 1; col < BOARD_ROWS; ++col) { int index1 = (row * BOARD_ROWS) + col; int index2 = (col * BOARD_ROWS) + row; swapPosition(board, index1, index2); } } } private static int randomOther(final Random random, final int currentValue, final int space) { return ((currentValue % space) + (random.nextInt(space - 1) + 1)) % space; } private static void swap(final int[] board, final Range range1, final Range range2) { if (range1.getSize() != range2.getSize()) { return; } int[] range2Copy = Arrays.copyOfRange(board, range2.getStart(), range2.getEnd()); System.arraycopy(board, range1.getStart(), board, range2.getStart(), range1.getSize()); System.arraycopy(range2Copy, 0, board, range1.getStart(), range1.getSize()); } private static void swapColumn(final int[] board, final int col1, final int col2) { for (int i = 0; i < BOARD_ROWS; i++) { swapPosition(board, (i * BOARD_ROWS) + col1, (i * BOARD_ROWS) + col2); } } private static void swapPosition(final int[] board, final int pos1, final int pos2) { int val2 = board[pos2]; board[pos2] = board[pos1]; board[pos1] = val2; } private static void removeOpenPosition( final Random random, final int[] board, final List<Integer> remainingPositions) { int index = random.nextInt(remainingPositions.size()); int position = remainingPositions.remove(index); board[position] = EMPTY_PIECE; } private static class Range { private int start; private int size; private int end; public Range(final int start, final int size) { this.start = start; this.size = size; this.end = start + size; } public int getStart() { return start; } public int getSize() { return size; } public int getEnd() { return end; } } }