@Test public void testSimulatePlaceTetromino() { board.placeTetromino(Tetromino.S, 0, 1); board.placeTetromino(Tetromino.S, 0, 1); board.placeTetromino(Tetromino.Z, 0, 1); board.placeTetromino(Tetromino.Z, 8, 1); board.placeTetromino(Tetromino.Z, 5, 1); board.placeTetromino(Tetromino.Z, 3, 0); board.placeTetromino(Tetromino.S, 3, 0); board.placeTetromino(Tetromino.S, 2, 1); board.placeTetromino(Tetromino.S, 7, 1); board.placeTetromino(Tetromino.S, 7, 0); board.placeTetromino(Tetromino.S, 7, 1); board.placeTetromino(Tetromino.Z, 6, 0); board.placeTetromino(Tetromino.S, 6, 1); board.placeTetromino(Tetromino.S, 7, 1); board.placeTetromino(Tetromino.S, 6, 0); board.placeTetromino(Tetromino.S, 5, 1); TetrisBoard newBoard = this.board.clone(); IntSortedSet erased = newBoard.eraseCompleteLines(newBoard.placeTetromino(Tetromino.Z, 3, 0)); Assert.assertTrue(erased.isEmpty()); int[] expectedDiffs = compareBoards(board, newBoard).toArray(); TetrisBoard beforeSimulation = this.board.clone(); int[] simulatedDiffs = board.simulatePlaceTetromino(Tetromino.Z, 3, 0).changedPositions.toArray(); Assert.assertEquals(beforeSimulation, board); Assert.assertEquals(new HashSet<>(Ints.asList(expectedDiffs)), new HashSet<>(Ints.asList(simulatedDiffs))); }
public SimulationResult simulatePlaceTetromino(Tetromino tetromino, int column, int rotation) { final IntArrayList changed = new IntArrayList(); final int landingHeight = calculateLandingHeight(tetromino, column, rotation); if (landingHeight > BOARD_HEIGHT) { return new SimulationResult(changed, 0, true); } final IntArrayList posToUndo = new IntArrayList(4); final IntAVLTreeSet rowsToErase = new IntAVLTreeSet(); int newMaxColumnHeight = maxColumnHeight; final int[][] tiles = tetromino.getTiles(rotation); for (int tileRow = 0; tileRow < tiles.length; tileRow++) { for (int tileCol = 0; tileCol < tiles[tileRow].length; tileCol++) { if (tiles[tileRow][tileCol] != 0) { final int boardCol = column + tileCol; final int boardRow = BOARD_HEIGHT - landingHeight + tileRow; int pos = toPos(boardRow, boardCol); changed.add(pos); setValue(pos, TEMP_PIECE); posToUndo.add(pos); rowWidth[boardRow]++; int newSkyline = skyline[boardCol] < landingHeight - tileRow ? landingHeight - tileRow : skyline[boardCol]; if (newSkyline > newMaxColumnHeight) { newMaxColumnHeight = newSkyline; } if (rowWidth[boardRow] == BOARD_WIDTH) { rowsToErase.add(boardRow); } } } } if (!rowsToErase.isEmpty()) { final int startRow = BOARD_HEIGHT - newMaxColumnHeight; final int endRow = rowsToErase.lastInt(); boolean isGroup = false; int rowsInGroup = 0; int lastDeletedRow = Integer.MIN_VALUE; int lastGroupSize = Integer.MIN_VALUE; int numRowsToDelete = rowsToErase.size(); for (int row = startRow; row <= endRow; row++) { int rowsToLookAbove = numRowsToDelete; if (rowsToErase.contains(row)) { rowsInGroup += 1; isGroup = true; } else if (isGroup) { lastGroupSize = rowsInGroup; rowsToLookAbove -= rowsInGroup; numRowsToDelete -= rowsInGroup; lastDeletedRow = row - 1; rowsInGroup = 0; isGroup = false; } int rowAbove = row - rowsToLookAbove; if (rowAbove <= lastDeletedRow) { rowsToLookAbove += lastGroupSize; } for (int pos = toPos(row, 0); pos <= toPos(row, BOARD_WIDTH - 1); pos++) { int posAbove = pos - rowsToLookAbove * WIDTH; checkPositionChanged(changed, pos, posAbove); } } } undoBoardChanges(posToUndo); return new SimulationResult(changed, rowsToErase.size(), false); }
TetrisBoard implements Board { public SimulationResult simulatePlaceTetromino(Tetromino tetromino, int column, int rotation) { final IntArrayList changed = new IntArrayList(); final int landingHeight = calculateLandingHeight(tetromino, column, rotation); if (landingHeight > BOARD_HEIGHT) { return new SimulationResult(changed, 0, true); } final IntArrayList posToUndo = new IntArrayList(4); final IntAVLTreeSet rowsToErase = new IntAVLTreeSet(); int newMaxColumnHeight = maxColumnHeight; final int[][] tiles = tetromino.getTiles(rotation); for (int tileRow = 0; tileRow < tiles.length; tileRow++) { for (int tileCol = 0; tileCol < tiles[tileRow].length; tileCol++) { if (tiles[tileRow][tileCol] != 0) { final int boardCol = column + tileCol; final int boardRow = BOARD_HEIGHT - landingHeight + tileRow; int pos = toPos(boardRow, boardCol); changed.add(pos); setValue(pos, TEMP_PIECE); posToUndo.add(pos); rowWidth[boardRow]++; int newSkyline = skyline[boardCol] < landingHeight - tileRow ? landingHeight - tileRow : skyline[boardCol]; if (newSkyline > newMaxColumnHeight) { newMaxColumnHeight = newSkyline; } if (rowWidth[boardRow] == BOARD_WIDTH) { rowsToErase.add(boardRow); } } } } if (!rowsToErase.isEmpty()) { final int startRow = BOARD_HEIGHT - newMaxColumnHeight; final int endRow = rowsToErase.lastInt(); boolean isGroup = false; int rowsInGroup = 0; int lastDeletedRow = Integer.MIN_VALUE; int lastGroupSize = Integer.MIN_VALUE; int numRowsToDelete = rowsToErase.size(); for (int row = startRow; row <= endRow; row++) { int rowsToLookAbove = numRowsToDelete; if (rowsToErase.contains(row)) { rowsInGroup += 1; isGroup = true; } else if (isGroup) { lastGroupSize = rowsInGroup; rowsToLookAbove -= rowsInGroup; numRowsToDelete -= rowsInGroup; lastDeletedRow = row - 1; rowsInGroup = 0; isGroup = false; } int rowAbove = row - rowsToLookAbove; if (rowAbove <= lastDeletedRow) { rowsToLookAbove += lastGroupSize; } for (int pos = toPos(row, 0); pos <= toPos(row, BOARD_WIDTH - 1); pos++) { int posAbove = pos - rowsToLookAbove * WIDTH; checkPositionChanged(changed, pos, posAbove); } } } undoBoardChanges(posToUndo); return new SimulationResult(changed, rowsToErase.size(), false); } }
TetrisBoard implements Board { public SimulationResult simulatePlaceTetromino(Tetromino tetromino, int column, int rotation) { final IntArrayList changed = new IntArrayList(); final int landingHeight = calculateLandingHeight(tetromino, column, rotation); if (landingHeight > BOARD_HEIGHT) { return new SimulationResult(changed, 0, true); } final IntArrayList posToUndo = new IntArrayList(4); final IntAVLTreeSet rowsToErase = new IntAVLTreeSet(); int newMaxColumnHeight = maxColumnHeight; final int[][] tiles = tetromino.getTiles(rotation); for (int tileRow = 0; tileRow < tiles.length; tileRow++) { for (int tileCol = 0; tileCol < tiles[tileRow].length; tileCol++) { if (tiles[tileRow][tileCol] != 0) { final int boardCol = column + tileCol; final int boardRow = BOARD_HEIGHT - landingHeight + tileRow; int pos = toPos(boardRow, boardCol); changed.add(pos); setValue(pos, TEMP_PIECE); posToUndo.add(pos); rowWidth[boardRow]++; int newSkyline = skyline[boardCol] < landingHeight - tileRow ? landingHeight - tileRow : skyline[boardCol]; if (newSkyline > newMaxColumnHeight) { newMaxColumnHeight = newSkyline; } if (rowWidth[boardRow] == BOARD_WIDTH) { rowsToErase.add(boardRow); } } } } if (!rowsToErase.isEmpty()) { final int startRow = BOARD_HEIGHT - newMaxColumnHeight; final int endRow = rowsToErase.lastInt(); boolean isGroup = false; int rowsInGroup = 0; int lastDeletedRow = Integer.MIN_VALUE; int lastGroupSize = Integer.MIN_VALUE; int numRowsToDelete = rowsToErase.size(); for (int row = startRow; row <= endRow; row++) { int rowsToLookAbove = numRowsToDelete; if (rowsToErase.contains(row)) { rowsInGroup += 1; isGroup = true; } else if (isGroup) { lastGroupSize = rowsInGroup; rowsToLookAbove -= rowsInGroup; numRowsToDelete -= rowsInGroup; lastDeletedRow = row - 1; rowsInGroup = 0; isGroup = false; } int rowAbove = row - rowsToLookAbove; if (rowAbove <= lastDeletedRow) { rowsToLookAbove += lastGroupSize; } for (int pos = toPos(row, 0); pos <= toPos(row, BOARD_WIDTH - 1); pos++) { int posAbove = pos - rowsToLookAbove * WIDTH; checkPositionChanged(changed, pos, posAbove); } } } undoBoardChanges(posToUndo); return new SimulationResult(changed, rowsToErase.size(), false); } TetrisBoard(); private  TetrisBoard(TetrisBoard board); }
TetrisBoard implements Board { public SimulationResult simulatePlaceTetromino(Tetromino tetromino, int column, int rotation) { final IntArrayList changed = new IntArrayList(); final int landingHeight = calculateLandingHeight(tetromino, column, rotation); if (landingHeight > BOARD_HEIGHT) { return new SimulationResult(changed, 0, true); } final IntArrayList posToUndo = new IntArrayList(4); final IntAVLTreeSet rowsToErase = new IntAVLTreeSet(); int newMaxColumnHeight = maxColumnHeight; final int[][] tiles = tetromino.getTiles(rotation); for (int tileRow = 0; tileRow < tiles.length; tileRow++) { for (int tileCol = 0; tileCol < tiles[tileRow].length; tileCol++) { if (tiles[tileRow][tileCol] != 0) { final int boardCol = column + tileCol; final int boardRow = BOARD_HEIGHT - landingHeight + tileRow; int pos = toPos(boardRow, boardCol); changed.add(pos); setValue(pos, TEMP_PIECE); posToUndo.add(pos); rowWidth[boardRow]++; int newSkyline = skyline[boardCol] < landingHeight - tileRow ? landingHeight - tileRow : skyline[boardCol]; if (newSkyline > newMaxColumnHeight) { newMaxColumnHeight = newSkyline; } if (rowWidth[boardRow] == BOARD_WIDTH) { rowsToErase.add(boardRow); } } } } if (!rowsToErase.isEmpty()) { final int startRow = BOARD_HEIGHT - newMaxColumnHeight; final int endRow = rowsToErase.lastInt(); boolean isGroup = false; int rowsInGroup = 0; int lastDeletedRow = Integer.MIN_VALUE; int lastGroupSize = Integer.MIN_VALUE; int numRowsToDelete = rowsToErase.size(); for (int row = startRow; row <= endRow; row++) { int rowsToLookAbove = numRowsToDelete; if (rowsToErase.contains(row)) { rowsInGroup += 1; isGroup = true; } else if (isGroup) { lastGroupSize = rowsInGroup; rowsToLookAbove -= rowsInGroup; numRowsToDelete -= rowsInGroup; lastDeletedRow = row - 1; rowsInGroup = 0; isGroup = false; } int rowAbove = row - rowsToLookAbove; if (rowAbove <= lastDeletedRow) { rowsToLookAbove += lastGroupSize; } for (int pos = toPos(row, 0); pos <= toPos(row, BOARD_WIDTH - 1); pos++) { int posAbove = pos - rowsToLookAbove * WIDTH; checkPositionChanged(changed, pos, posAbove); } } } undoBoardChanges(posToUndo); return new SimulationResult(changed, rowsToErase.size(), false); } TetrisBoard(); private  TetrisBoard(TetrisBoard board); RectSize getSize(); int getMaxColumnHeight(); int[] getSkyline(); boolean isHeightExceeded(); @Override int getWidth(); @Override int getHeight(); @Override int getValue(int row, int col); @Override void setValue(int row, int col, int color); @Override int getValue(int pos); @Override void setValue(int pos, int color); @Override void invert(); @Override Board createAfterState(int row, int col, int player); @Override TetrisBoard clone(); IntArrayList placeTetromino(Tetromino tetromino, int column, int rotation); IntArrayList placeTetrominoCustom(Tetromino tetromino, int column, int rotation, int piece); Pair<IntArrayList, Integer> simulatePlaceTetrominoNaive(Tetromino tetromino, int column, int rotation); SimulationResult simulatePlaceTetromino(Tetromino tetromino, int column, int rotation); IntSortedSet eraseCompleteLines(IntArrayList changedPositions); @Override String toString(); @Override boolean equals(Object obj); @Override int hashCode(); int getHolesCount(); }
TetrisBoard implements Board { public SimulationResult simulatePlaceTetromino(Tetromino tetromino, int column, int rotation) { final IntArrayList changed = new IntArrayList(); final int landingHeight = calculateLandingHeight(tetromino, column, rotation); if (landingHeight > BOARD_HEIGHT) { return new SimulationResult(changed, 0, true); } final IntArrayList posToUndo = new IntArrayList(4); final IntAVLTreeSet rowsToErase = new IntAVLTreeSet(); int newMaxColumnHeight = maxColumnHeight; final int[][] tiles = tetromino.getTiles(rotation); for (int tileRow = 0; tileRow < tiles.length; tileRow++) { for (int tileCol = 0; tileCol < tiles[tileRow].length; tileCol++) { if (tiles[tileRow][tileCol] != 0) { final int boardCol = column + tileCol; final int boardRow = BOARD_HEIGHT - landingHeight + tileRow; int pos = toPos(boardRow, boardCol); changed.add(pos); setValue(pos, TEMP_PIECE); posToUndo.add(pos); rowWidth[boardRow]++; int newSkyline = skyline[boardCol] < landingHeight - tileRow ? landingHeight - tileRow : skyline[boardCol]; if (newSkyline > newMaxColumnHeight) { newMaxColumnHeight = newSkyline; } if (rowWidth[boardRow] == BOARD_WIDTH) { rowsToErase.add(boardRow); } } } } if (!rowsToErase.isEmpty()) { final int startRow = BOARD_HEIGHT - newMaxColumnHeight; final int endRow = rowsToErase.lastInt(); boolean isGroup = false; int rowsInGroup = 0; int lastDeletedRow = Integer.MIN_VALUE; int lastGroupSize = Integer.MIN_VALUE; int numRowsToDelete = rowsToErase.size(); for (int row = startRow; row <= endRow; row++) { int rowsToLookAbove = numRowsToDelete; if (rowsToErase.contains(row)) { rowsInGroup += 1; isGroup = true; } else if (isGroup) { lastGroupSize = rowsInGroup; rowsToLookAbove -= rowsInGroup; numRowsToDelete -= rowsInGroup; lastDeletedRow = row - 1; rowsInGroup = 0; isGroup = false; } int rowAbove = row - rowsToLookAbove; if (rowAbove <= lastDeletedRow) { rowsToLookAbove += lastGroupSize; } for (int pos = toPos(row, 0); pos <= toPos(row, BOARD_WIDTH - 1); pos++) { int posAbove = pos - rowsToLookAbove * WIDTH; checkPositionChanged(changed, pos, posAbove); } } } undoBoardChanges(posToUndo); return new SimulationResult(changed, rowsToErase.size(), false); } TetrisBoard(); private  TetrisBoard(TetrisBoard board); RectSize getSize(); int getMaxColumnHeight(); int[] getSkyline(); boolean isHeightExceeded(); @Override int getWidth(); @Override int getHeight(); @Override int getValue(int row, int col); @Override void setValue(int row, int col, int color); @Override int getValue(int pos); @Override void setValue(int pos, int color); @Override void invert(); @Override Board createAfterState(int row, int col, int player); @Override TetrisBoard clone(); IntArrayList placeTetromino(Tetromino tetromino, int column, int rotation); IntArrayList placeTetrominoCustom(Tetromino tetromino, int column, int rotation, int piece); Pair<IntArrayList, Integer> simulatePlaceTetrominoNaive(Tetromino tetromino, int column, int rotation); SimulationResult simulatePlaceTetromino(Tetromino tetromino, int column, int rotation); IntSortedSet eraseCompleteLines(IntArrayList changedPositions); @Override String toString(); @Override boolean equals(Object obj); @Override int hashCode(); int getHolesCount(); static final int EMPTY; static final int PIECE; static final int NUM_VALUES; static final int MARGIN_WIDTH; static final int BOARD_WIDTH; static final int BOARD_HEIGHT; static final int WIDTH; static final int HEIGHT; static final RectSize DEFAULT_BOARD_SIZE; }