Created
September 12, 2018 21:52
-
-
Save dulimarta/42f970e7a611c874c5ed146ae8a2a6b9 to your computer and use it in GitHub Desktop.
Unit test for 1024 game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package game1024.test; | |
import game1024.*; | |
import org.junit.*; | |
import java.util.*; | |
import static org.junit.Assert.*; | |
public class TenTwentyFourTester { | |
private final static int REPEAT_COUNT = 500; | |
private final static int GAME_GOAL = 1024; | |
private static NumberSlider gameLogic; | |
private static Random gen; | |
private static int NROWS, NCOLS; | |
@BeforeClass | |
public static void globalSetup() | |
{ | |
System.out.println ("Setting up Unit Testing"); | |
gen = new Random(); | |
gameLogic = new NumberGame(); | |
for (String[] pat : MOVE_PATTERNS) | |
assertEquals(pat[0].length(), pat[1].length()); | |
} | |
@Before | |
public void setUp() throws Exception { | |
NROWS = gen.nextInt(8) + 3; /* 2-15 */ | |
NCOLS = gen.nextInt(8) + 3; | |
gameLogic.resizeBoard(NROWS, NCOLS, GAME_GOAL); | |
} | |
@AfterClass | |
public static void shutDown() throws Exception { | |
System.out.println("Shutting down Unit Testing"); | |
} | |
@Test | |
public void resetShouldShowTwoValue() | |
{ | |
gameLogic.reset(); | |
List<Cell> result = gameLogic.getNonZeroCells(); | |
assertTrue("reset() should initialize the board ", result.size() == 2); | |
Cell c1 = result.get(0); | |
Cell c2 = result.get(1); | |
assertTrue (c1.row != c2.row || c1.column != c2.column); | |
assertTrue (isPowerTwo(c1.value)); | |
assertTrue (isPowerTwo(c2.value)); | |
} | |
@Test | |
public void resetShouldInitializeGameStatus() | |
{ | |
gameLogic.reset(); | |
assertNotNull("reset() should initialize game status ", gameLogic.getStatus()); | |
assertEquals("reset() should initialize game status", GameStatus.IN_PROGRESS, | |
gameLogic.getStatus()); | |
} | |
@Test(timeout = 5000) | |
public void testSetValues() | |
{ | |
final int[][] zeros = new int[NROWS][NCOLS]; | |
final int[][] one = new int[NROWS][NCOLS]; | |
one[gen.nextInt(NROWS)][gen.nextInt(NCOLS)] = 1; | |
gameLogic.setValues(zeros); | |
List<Cell> zeroList = (List<Cell>) gameLogic.getNonZeroCells().clone(); | |
assertNotNull("getNonZeroCells() should never return null", zeroList); | |
gameLogic.setValues(one); | |
List<Cell> oneList = gameLogic.getNonZeroCells(); | |
assertFalse("setValues() is not implemented correctly", zeroList.equals(oneList)); | |
} | |
@Test(timeout = 5000) | |
public void resizeShouldHandleBoardsOfAnySize() | |
{ | |
try { | |
int[][] data; | |
for (int count = 0; count < REPEAT_COUNT; count++) { | |
int rows = gen.nextInt(39) + 2; | |
int cols = gen.nextInt(39) + 2; | |
gameLogic.resizeBoard(rows, cols, GAME_GOAL); | |
data = new int[rows][cols]; | |
for (int k = 0; k < rows; k++) | |
for (int m = 0; m < cols; m++) | |
data[k][m] = 1; | |
gameLogic.setValues(data); | |
assertEquals("Your game logic should be able to handle game board of any size", rows * cols, | |
gameLogic.getNonZeroCells().size()); | |
} | |
} | |
catch (Exception e) | |
{ | |
fail ("Your game logic can't handle game board of any size"); | |
} | |
} | |
@Test | |
public void randomValueShallBePlacedOnEmptySpot() { | |
final int N = NROWS * NCOLS; | |
for (int k = 0; k < N - 1; k++) { | |
int oldCount = gameLogic.getNonZeroCells().size(); | |
gameLogic.placeRandomValue(); | |
int newCount = gameLogic.getNonZeroCells().size(); | |
assertTrue (newCount == oldCount + 1); | |
} | |
gameLogic.placeRandomValue(); | |
} | |
private int nextTileValue() | |
{ | |
int pow = gen.nextInt(7); /* 1, 2, 4, 8, 16, 32, 64 */ | |
int num = 1; | |
for (int n = 0; n < pow; n++) | |
num *= 2; | |
return num; | |
} | |
@Test(timeout = 5000) | |
public void getNonEmptyTilesShallReturnNonZeroTiles() | |
{ | |
int[][] vals = new int[NROWS][NCOLS]; | |
for (int count = 0; count < REPEAT_COUNT; count++) { | |
for (int k = 0; k < vals.length; k++) | |
for (int m = 0; m < vals[0].length; m++) { | |
if (gen.nextBoolean()) | |
vals[k][m] = 0; | |
else { | |
vals[k][m] = nextTileValue(); | |
} | |
} | |
swiper(gameLogic, null, null, vals, vals); | |
} | |
} | |
private int countNonZero(int[][] arr) | |
{ | |
int nz = 0; | |
for (int[] row : arr) | |
for (int v : row) | |
if (v != 0) nz++; | |
return nz; | |
} | |
private boolean isPowerTwo (int z) | |
{ | |
int v = 1; | |
while (v < z) | |
v *= 2; | |
return v == z; | |
} | |
private void swiper (NumberSlider cm, SlideDirection dir, String chPat, int[][] before, int[][] after) | |
{ | |
gameLogic.setValues(before); | |
String action = ""; | |
if (dir != null) { | |
boolean b = gameLogic.slide(dir); | |
assertTrue("move() should return TRUE when game board changes", b); | |
action = "move " + dir.name() + ":"; | |
} | |
List<Cell> out = gameLogic.getNonZeroCells(); | |
int[][] actual = new int[after.length][after[0].length]; | |
for (Cell c : out) { | |
if (c.row < 0 || c.row >= after.length || c.column < 0 || c.column >= after[0].length) | |
fail("game1024.Cell index (" + c.row + "," + c.column + " is out of bound"); | |
assertTrue("getNonZeroCells() should return only non-zero cells", c.value != 0); | |
actual[c.row][c.column] = Math.abs(c.value); | |
} | |
StringBuilder afterSb = new StringBuilder(); | |
StringBuilder actualSb = new StringBuilder(); | |
for (int k = 0; k < after.length; k++) { | |
for (int m = 0; m < after[k].length; m++) | |
{ | |
afterSb.append(String.format("%3d ", after[k][m])); | |
actualSb.append(String.format("%3d ", actual[k][m])); | |
} | |
afterSb.append("\n"); | |
actualSb.append("\n"); | |
} | |
int misMatchCount = 0; | |
int misMatchRow = -1, misMatchCol = -1; | |
for (int k = 0; k < after.length; k++) | |
for (int m = 0; m < after[0].length; m++) { | |
if (after[k][m] != actual[k][m]) { | |
misMatchCount++; | |
misMatchRow = k; | |
misMatchCol = m; | |
} | |
} | |
switch (misMatchCount) | |
{ | |
case 0: | |
if (dir != null) | |
fail("move() failed to generate a new random cell after board changes"); | |
break; | |
case 1: | |
assertTrue ("move(): the new random value must show up in an empty cell", after[misMatchRow][misMatchCol] == 0); | |
assertTrue ("New generated value must be a power of 2", isPowerTwo (actual[misMatchRow][misMatchCol])); | |
break; | |
default: | |
fail(action + " Mismatch entry at (" + misMatchRow + "," + misMatchCol + | |
")\nExpected:\n" + afterSb + "\nYour answer:\n" + actualSb); | |
} | |
} | |
private void noMoveSwiper (NumberSlider cm, SlideDirection dir, int[][] mat) | |
{ | |
gameLogic.setValues(mat); | |
String action = ""; | |
if (dir != null) { | |
boolean b = gameLogic.slide(dir); | |
action = "move " + dir.name() + ":"; | |
assertFalse("move() should return FALSE when no cells can be moved", b); | |
} | |
List<Cell> out = gameLogic.getNonZeroCells(); | |
int[][] actual = new int[mat.length][mat[0].length]; | |
for (Cell c : out) { | |
if (c.row < 0 || c.row >= mat.length || c.column < 0 || c.column >= mat[0].length) | |
fail("game1024.Cell index (" + c.row + "," + c.column + " is out of bound"); | |
assertTrue("getNonZeroCells() should return only non-zero cells", c.value > 0); | |
// assertTrue(action + " (" + c.row + "," + c.column + ") should be zero", | |
// after[c.row][c.column] != 0); | |
actual[c.row][c.column] = c.value; | |
} | |
StringBuilder afterSb = new StringBuilder(); | |
StringBuilder actualSb = new StringBuilder(); | |
for (int k = 0; k < mat.length; k++) { | |
for (int m = 0; m < mat[k].length; m++) | |
{ | |
afterSb.append(String.format("%3d ", mat[k][m])); | |
actualSb.append(String.format("%3d ", actual[k][m])); | |
} | |
afterSb.append("\n"); | |
actualSb.append("\n"); | |
} | |
for (int k = 0; k < mat.length; k++) | |
for (int m = 0; m < mat[0].length; m++) { | |
if (mat[k][m] != actual[k][m]) { | |
fail(action + " Mismatch entry at (" + k + "," + m + | |
")\nExpected:\n" + afterSb + "\nYour answer:\n" + actualSb); | |
} | |
} | |
} | |
private void resetValues (int[][] arr) | |
{ | |
for (int k = 0; k < arr.length; k++) | |
for (int m = 0; m < arr[k].length; m++) | |
arr[k][m] = 0; | |
} | |
@Test(timeout = 5000) | |
public void slidingOperationsThatMovesNoTiles() { | |
int[][] vals = new int[NROWS][NCOLS]; | |
/* fill in the top row with non repeating values */ | |
vals[0][0] = 1; | |
for (int k = 1; k < NCOLS; k++) | |
vals[0][k] = 2 * vals[0][k - 1]; | |
noMoveSwiper(gameLogic, SlideDirection.LEFT, vals); | |
noMoveSwiper(gameLogic, SlideDirection.RIGHT, vals); | |
noMoveSwiper(gameLogic, SlideDirection.UP, vals); | |
resetValues(vals); | |
/* fill in the bottom row with non repeating values */ | |
vals[NROWS-1][0] = 1; | |
for (int k = 1; k < NCOLS; k++) | |
vals[NROWS-1][k] = 2 * vals[NROWS-1][k - 1]; | |
noMoveSwiper(gameLogic, SlideDirection.LEFT, vals); | |
noMoveSwiper(gameLogic, SlideDirection.RIGHT, vals); | |
noMoveSwiper(gameLogic, SlideDirection.DOWN, vals); | |
resetValues(vals); | |
/* fill in the left column with non repeating values */ | |
vals[0][0] = 1; | |
for (int k = 1; k < NROWS; k++) | |
vals[k][0] = 2 * vals[k - 1][0]; | |
noMoveSwiper(gameLogic, SlideDirection.LEFT, vals); | |
noMoveSwiper(gameLogic, SlideDirection.UP, vals); | |
noMoveSwiper(gameLogic, SlideDirection.DOWN, vals); | |
resetValues(vals); | |
/* fill in the right column with non repeating values */ | |
vals[0][NCOLS-1] = 1; | |
for (int k = 1; k < NROWS; k++) | |
vals[k][NCOLS-1] = 2 * vals[k-1][NCOLS-1]; | |
noMoveSwiper(gameLogic, SlideDirection.RIGHT, vals); | |
noMoveSwiper(gameLogic, SlideDirection.UP, vals); | |
noMoveSwiper(gameLogic, SlideDirection.DOWN, vals); | |
} | |
@Test(timeout = 5000) | |
public void slideShallReturnFalseWhenBoardUnchanged() | |
{ | |
/* TODO: this test case does not work when getNonZeroCells returns | |
a new ArrayList on each call | |
*/ | |
ArrayList<Cell> one, two; | |
for (String pat : NOMOVE_PATTERNS) | |
{ | |
gameLogic.resizeBoard(pat.length(), pat.length(), GAME_GOAL); | |
gameLogic.setValues(fillColumns(pat, makeMap(pat))); | |
one = (ArrayList<Cell>)gameLogic.getNonZeroCells().clone(); | |
assertFalse("move(UP) should return false when the board does not change", | |
gameLogic.slide(SlideDirection.UP)); | |
two = (ArrayList<Cell>) gameLogic.getNonZeroCells().clone(); | |
Set<Cell> s1 = new TreeSet<>(one); | |
Set<Cell> s2 = new TreeSet<>(two); | |
assertTrue("when board is unchanged by move(UP) getNonZeroCells() shall return the same list before and after move", | |
s1.equals(s2)); | |
gameLogic.setValues(fillColumns(reverse(pat), makeMap(pat))); | |
one = gameLogic.getNonZeroCells(); | |
assertFalse("move(DOWN) should return false when the board does not change", gameLogic.slide(SlideDirection.DOWN)); | |
two = (ArrayList<Cell>) gameLogic.getNonZeroCells().clone(); | |
s1 = new TreeSet<>(one); | |
s2 = new TreeSet<>(two); | |
assertTrue("when board is unchanged by move(DOWN) getNonZeroCells() shall return the same list before and after move", | |
s1.equals(s2)); | |
gameLogic.setValues(fillRows(pat, makeMap(pat))); | |
one = gameLogic.getNonZeroCells(); | |
assertFalse ("move(LEFT) should return false when the board does not change", gameLogic.slide(SlideDirection.LEFT)); | |
two = (ArrayList<Cell>) gameLogic.getNonZeroCells().clone(); | |
s1 = new TreeSet<>(one); | |
s2 = new TreeSet<>(two); | |
assertTrue("when board is unchanged by move(LEFT) getNonZeroCells() shall return the same list before and after move", | |
s1.equals(s2)); | |
gameLogic.setValues(fillRows(reverse(pat), makeMap(pat))); | |
one = gameLogic.getNonZeroCells(); | |
assertFalse ("move(RIGHT) should return false when the board does not change", gameLogic.slide(SlideDirection.RIGHT)); | |
two = (ArrayList<Cell>) gameLogic.getNonZeroCells().clone(); | |
s1 = new TreeSet<>(one); | |
s2 = new TreeSet<>(two); | |
assertTrue("when board is unchanged by move(RIGHT) getNonZeroCells() shall return the same list before and after move", | |
s1.equals(s2)); | |
} | |
} | |
@Test(timeout = 5000) | |
public void slidingThatMovesTilesToOppositeSide() | |
{ | |
int[][] vals = new int[NROWS][NCOLS]; | |
int[][] valsAfter = new int[NROWS][NCOLS]; | |
/* fill in the top row */ | |
for (int k = 0; k < NCOLS; k++) { | |
vals[0][k] = nextTileValue(); | |
valsAfter[NROWS-1][k] = vals[0][k]; | |
} | |
swiper(gameLogic, SlideDirection.DOWN, "pull down", vals, valsAfter); | |
resetValues(vals); | |
resetValues(valsAfter); | |
/* fill in the bottom row */ | |
for (int k = 0; k < NCOLS; k++) { | |
vals[NROWS-1][k] = nextTileValue(); | |
valsAfter[0][k] = vals[NROWS-1][k]; | |
} | |
swiper(gameLogic, SlideDirection.UP, "pull up", vals, valsAfter); | |
resetValues(vals); | |
resetValues(valsAfter); | |
/* fill in the leftmost column */ | |
for (int k = 0; k < NROWS; k++) { | |
vals[k][0] = nextTileValue(); | |
valsAfter[k][NCOLS-1] = vals[k][0]; | |
} | |
swiper(gameLogic, SlideDirection.RIGHT, "pull right", vals, valsAfter); | |
resetValues(vals); | |
resetValues(valsAfter); | |
/* fill in the rightmost column */ | |
for (int k = 0; k < NROWS; k++) { | |
vals[k][NCOLS-1] = nextTileValue(); | |
valsAfter[k][0] = vals[k][NCOLS-1]; | |
} | |
swiper(gameLogic, SlideDirection.LEFT, "pull left", vals, valsAfter); | |
} | |
private int[][] fillColumns (String template, Map<Character,Integer> valMap) | |
{ | |
final int N = template.length(); | |
int[][] mat = new int[N][N]; | |
for (int c = 0; c < N; c++) | |
for (int r = 0; r < N; r++) { | |
char ch = template.charAt(r); | |
int val; | |
if (Character.isUpperCase(ch)) | |
mat[r][c] = 2 * valMap.get(Character.toLowerCase(ch)); | |
else | |
mat[r][c] = valMap.get(ch); | |
} | |
return mat; | |
} | |
private int[][] fillRows (String template, Map<Character,Integer> valMap) | |
{ | |
final int N = template.length(); | |
int[][] mat = new int[N][N]; | |
for (int r = 0; r < N; r++) | |
for (int c = 0; c < N; c++) { | |
char ch = template.charAt(c); | |
int val; | |
if (Character.isUpperCase(ch)) | |
mat[r][c] = 2 * valMap.get(Character.toLowerCase(ch)); | |
else | |
mat[r][c] = valMap.get(ch); | |
} | |
return mat; | |
} | |
private String reverse (String s) | |
{ | |
StringBuilder t = new StringBuilder(); | |
for (int k = s.length() - 1; k >= 0; k--) | |
{ | |
t.append(s.charAt(k)); | |
} | |
return t.toString(); | |
} | |
private Map<Character,Integer> makeMap (String pattern) | |
{ | |
Set<Character> chars = new TreeSet<Character>(); | |
Set<Integer> values = new TreeSet<Integer>(); | |
Map<Character,Integer> numMap = new TreeMap<Character, Integer>(); | |
for (Character c : pattern.toCharArray()) | |
{ | |
if (c != '.') | |
chars.add(c); | |
} | |
while (values.size() != chars.size()) | |
values.add(nextTileValue()); | |
Iterator<Character> chIter = chars.iterator(); | |
Iterator<Integer> valIter = values.iterator(); | |
while (chIter.hasNext()) { | |
numMap.put(chIter.next(), valIter.next()); | |
} | |
numMap.put('.', 0); | |
return numMap; | |
} | |
private void swipeByPattern (SlideDirection dir, String before, String after) | |
{ | |
final int N = before.length(); | |
gameLogic.resizeBoard(N, N, GAME_GOAL); | |
int[][] beforeMat, afterMat; | |
Map<Character,Integer> charToVal = makeMap(before); | |
if (dir.equals(SlideDirection.UP) || dir.equals(SlideDirection.DOWN)) { | |
beforeMat = fillColumns(before, charToVal); | |
afterMat = fillColumns(after, charToVal); | |
} | |
else { | |
beforeMat = fillRows(before, charToVal); | |
afterMat = fillRows(after, charToVal); | |
} | |
swiper(gameLogic, dir, before + " ==> " + after, beforeMat, afterMat); | |
} | |
@Test(timeout = 5000) | |
public void testSwipeUp() | |
{ | |
for (String[] pat : MOVE_PATTERNS) { | |
swipeByPattern(SlideDirection.UP, pat[0], pat[1]); | |
} | |
} | |
@Test(timeout = 5000) | |
public void testSwipeDown() | |
{ | |
for (String[] pat : MOVE_PATTERNS) { | |
swipeByPattern(SlideDirection.DOWN, reverse(pat[0]), reverse(pat[1])); | |
} | |
} | |
@Test(timeout = 5000) | |
public void testSwipeLeft() | |
{ | |
for (String[] pat : MOVE_PATTERNS) { | |
swipeByPattern(SlideDirection.LEFT, pat[0], pat[1]); | |
} | |
} | |
@Test(timeout = 5000) | |
public void testSwipeRight() | |
{ | |
for (String[] pat : MOVE_PATTERNS) { | |
swipeByPattern(SlideDirection.RIGHT, reverse(pat[0]), reverse(pat[1])); | |
} | |
} | |
@Test(timeout = 5000) | |
public void testIsGameOverAfterReset() throws Exception { | |
assertTrue("After initialization isGameOver() should be false", | |
gameLogic.getStatus() == GameStatus.IN_PROGRESS); | |
} | |
@Test(timeout = 5000) | |
public void testIsGameBoardNotFull() throws Exception { | |
assertTrue("Non-full board: isGameOver() should be false", | |
gameLogic.getStatus() == GameStatus.IN_PROGRESS); | |
} | |
@Test(timeout = 5000) | |
public void testIsGameBoardFullMovePossible() throws Exception { | |
for (String pat : FULL_NOT_OVER) { | |
gameLogic.resizeBoard(pat.length(), pat.length(), GAME_GOAL); | |
Map<Character,Integer> valMap = makeMap(pat); | |
gameLogic.setValues(fillColumns(pat, valMap)); | |
assertEquals("Non-full board: movePossible() should be true", | |
GameStatus.IN_PROGRESS, | |
gameLogic.getStatus()); | |
gameLogic.setValues(fillRows(pat, valMap)); | |
assertEquals("Non-full board: isGameOver() should be true", | |
GameStatus.IN_PROGRESS, | |
gameLogic.getStatus()); | |
} | |
} | |
@Test(timeout = 5000) | |
public void testIsGameBoardFullNoMoreMoves() throws Exception { | |
StringBuilder sb = new StringBuilder(); | |
for (int count = 0; count < 10; count++) { | |
int N = 2 * gen.nextInt(3) + 2; /* force even size */ | |
int[][] mat = new int[N][N]; | |
gameLogic.resizeBoard(N, N, GAME_GOAL); | |
sb.setLength(0); | |
for (int k = 0; k < N; k++) | |
sb.append((char)('a' + k)); | |
Map<Character, Integer> valmap = makeMap(sb.toString()); | |
for (int r = 0; r < N/2; r++) { | |
for (int c = 0; c < N; c++) { | |
/* alternate the sequence within the two adjacent rows */ | |
mat[2*r ][c ] = valmap.get((char) ('a' + c)); | |
mat[2*r + 1][N-c-1] = valmap.get((char) ('a' + c)); | |
} | |
} | |
gameLogic.setValues(mat); | |
assertEquals("When no more moves are possible the game should be over", | |
GameStatus.USER_LOST, | |
gameLogic.getStatus()); | |
} | |
} | |
@Test(timeout = 1000) | |
public void testWinningValue() | |
{ | |
final int N = 10; | |
gameLogic.resizeBoard(N, N, GAME_GOAL); | |
int[][] mat = new int[N][N]; | |
for (int k = 0; k < mat.length; k++) { | |
for (int m = 0; m < mat[k].length; m++) | |
mat[k][m] = GAME_GOAL / 2; | |
} | |
gameLogic.setValues(mat); | |
gameLogic.slide(SlideDirection.DOWN); | |
assertEquals(GameStatus.USER_WON, gameLogic.getStatus()); | |
} | |
/* use DOT (.) for empty cells, be sure to limit the letters to a-f | |
* because the highest power of two use in the test is 32 */ | |
private final static String[] NOMOVE_PATTERNS = { | |
".......", "abcde", "a....", "ab...." | |
}; | |
private final static String[][] MOVE_PATTERNS = { | |
/* shift only, no merges */ | |
{".a.b.", "ab..."}, | |
{"a....b", "ab...."}, | |
{"a.bc...d", "abcd...."}, | |
{"..a.b..a..b", "abab......."}, | |
/* merge and sum, use uppercase letter to indicate doubling */ | |
{"abcdee","abcdE."}, | |
{"abbcdde", "aBcDe.."}, /* UNTESTED !!!!! */ | |
{"a.b.bbc.ccd..", "aBbCcd......."}, | |
{"abb..", "aB..."}, | |
{"ab..b", "aB..."}, | |
{"aa..bb..c", "ABc......"}, | |
{"..a.a.aa..b", "AAb........"}, | |
{"..a.b.cc..d", "abCd......."} | |
}; | |
private final static String[] FULL_NOT_OVER = { | |
"aabcde", "abbcde", "abccde", "abcdde", "abcdee" | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment