///////////////////////////////////////////////////////////////// ////// START: A code for creating all possible permutations of board positions ////// of tiles in given list, starting from left-most tile in the array private void boardPosPermAddChildNodes(CandidateTileSeq candTileSeq, int currTileIndex, TreeNode parentNode, TileZeroTile[,] tileArray, VirtualBoard virtualBoard) { if (currTileIndex >= candTileSeq.getTileCount()) { return; } TileZeroTile tile = candTileSeq.getTileAt(currTileIndex); for (int currRowIndex = 0; currRowIndex < tileArray.GetLength(0); currRowIndex++) { for (int currColIndex = 0; currColIndex < tileArray.GetLength(1); currColIndex++) { int resultScore = virtualBoard.isValidMove(currRowIndex, currColIndex, tile, true, tileArray, false); if (resultScore != Cfg.NONE) { TileZeroTile[,] newTileArray = Cfg.createBoardCopy(tileArray); virtualBoard.addTile(currRowIndex, currColIndex, tile, false, newTileArray); TreeNode childNode = parentNode.addChildNodeValue(new AbstractPos(currTileIndex, currRowIndex, currColIndex, resultScore)); boardPosPermAddChildNodes(candTileSeq, currTileIndex + 1, childNode, newTileArray, virtualBoard); } } } boardPosPermAddChildNodes(candTileSeq, currTileIndex + 1, parentNode, tileArray, virtualBoard); // [SC][2016.12.08] new code }
////// END: hard ai functionality ///////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////// ////// START: very hard ai functionality #region very hard AI public void invokeVeryHardAI() { CandidateTilePos selectedPosCombo = calculateMoves(true, true, false); if (selectedPosCombo == null) // [SC] no tiles to put on a board // [SC] dropping a random tile { setSelectedTile(playerTiles.getRandomElement()); game.dropPlayerTile(playerIndex); } else if (getCanMove()) { CandidateTileSeq tileSeq = selectedPosCombo.getCandidateTileSeq(); int totalMoveCount = selectedPosCombo.getComboLength(); int currMoveCount = 0; while (currMoveCount < totalMoveCount) { AbstractPos abstrPos = selectedPosCombo.getAbstrPosAt(currMoveCount); int rowIndex = abstrPos.getRowIndex(); int colIndex = abstrPos.getColIndex(); int tileIndex = abstrPos.getTileIndex(); TileZeroTile tile = tileSeq.getTileAt(tileIndex); setSelectedTile(tile); game.setSelectedCell(rowIndex, colIndex, playerIndex); game.placePlayerTileOnBoard(playerIndex); currMoveCount++; } } }
// [2016.12.01] // [SC] place active player's tile on a board public void placePlayerTileOnBoard(int playerIndex) { if (!activeGameFlag) { return; } if (playerIndex != activePlayerIndex) { Cfg.log("It is not your turn!"); return; } Player activePlayer = players[activePlayerIndex]; // [SC] check if player can put tiles on a board if (!activePlayer.getCanMove()) { Cfg.log("Cannot move a tile after dropping a tile!"); // [TODO] return; } // [SC] check if board position is selected if (!isSelected()) { Cfg.log("Select a board position at first!"); // [TODO] return; } // [SC] check if player tile is selected if (!activePlayer.isTileSelected()) { Cfg.log("Select a tile at first!"); // [TODO] return; } TileZeroTile tile = activePlayer.getSelectedTile(); int result = putTileOnBoard(selectedRowIndex, selectedColIndex, tile, true); if (result != Cfg.NONE) { Cfg.log(String.Format(" Put tile {0} at position {1}-{2} for {3} points.", tile.ToString(), selectedRowIndex, selectedColIndex, result)); // [SC] increase player's score activePlayer.increaseScore(result); // [SC] remove the tile from the player and reset player selection activePlayer.removeSelectedTile(); // [SC] disable mismatching tiles activePlayer.disableMismatchedTiles(tile.getColorIndex(), tile.getShapeIndex()); // [SC] prevent the player from dropping tiles in the same turn activePlayer.setCanDrop(false); // [SC] reset board selection resetSelected(); } }
///////////////////////////////////////////////////////////////// ////// START: generic functions for manipulating tiles public bool addTile(TileZeroTile tile) { if (getPlayerTileCount() < Cfg.MAX_PLAYER_TILE_COUNT) { playerTiles.Add(tile); return(true); } return(false); }
// [SC] returns true if this tile has the same color and shape as another tile public bool sameVisTile(TileZeroTile tile) { if (this.colorIndex == tile.getColorIndex() && this.shapeIndex == tile.getShapeIndex()) { return(true); } else { return(false); } }
////// END: A code for creating all possible permutations of board positions ////// of tiles in given list, starting from left-most tile in the array ///////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////// ////// START: A code for creating all possible permutations of tiles in list private void tileListPermAddChildNodes(List <TileZeroTile> tileList, int childValIndex, TreeNode rootNode) { List <TileZeroTile> newList = tileList.listShallowClone(); TileZeroTile childNodeValue = newList.Pop(childValIndex); TreeNode childNode = rootNode.addChildNodeValue(childNodeValue); for (int tileIndex = 0; tileIndex < newList.Count; tileIndex++) { tileListPermAddChildNodes(newList, tileIndex, childNode); } }
public override string ToString() { string boardStr = ""; string indexRow = " "; string brRow = " "; for (int colIndex = 0; colIndex < colCount; colIndex++) { if (colIndex < 10) { indexRow += "0" + colIndex + " "; } else { indexRow += colIndex + " "; } brRow += "---"; } boardStr += indexRow + "\n"; boardStr += brRow + "\n"; for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) { string rowStr = ""; if (rowIndex < 10) { rowStr += "0" + rowIndex + "|"; } else { rowStr += rowIndex + "|"; } for (int colIndex = 0; colIndex < colCount; colIndex++) { TileZeroTile tile = tileArray[rowIndex, colIndex]; if (tile == null) { rowStr += "-- "; } else { rowStr += tile.ToString() + " "; } } boardStr += rowStr + "\n"; } return(boardStr); }
////// END: functions for manipulating a selected tile ///////////////////////////////////////////////////////////////// public void disableMismatchedTiles(int colorIndex, int shapeIndex) { if (!hasColorReq() && !hasShapeReq()) { setColorReq(colorIndex); setShapeReq(shapeIndex); } else if (hasColorReq() && hasShapeReq()) { if (sameColorReq(colorIndex) && !sameShapeReq(shapeIndex)) { resetShapeReq(); } else if (!sameColorReq(colorIndex) && sameShapeReq(shapeIndex)) { resetColorReq(); } } if (hasColorReq() && hasShapeReq()) { for (int currTileIndex = 0; currTileIndex < playerTiles.Count; currTileIndex++) { TileZeroTile tile = playerTiles[currTileIndex]; if (!sameColorReq(tile.getColorIndex()) && !sameShapeReq(tile.getShapeIndex())) { tile.setPlayable(false); } } } else if (hasColorReq() && !hasShapeReq()) { for (int currTileIndex = 0; currTileIndex < playerTiles.Count; currTileIndex++) { TileZeroTile tile = playerTiles[currTileIndex]; if (!sameColorReq(tile.getColorIndex())) { tile.setPlayable(false); } } } else if (!hasColorReq() && hasShapeReq()) { for (int currTileIndex = 0; currTileIndex < playerTiles.Count; currTileIndex++) { TileZeroTile tile = playerTiles[currTileIndex]; if (!sameShapeReq(tile.getShapeIndex())) { tile.setPlayable(false); } } } }
////// END: generic functions for manipulating tiles ///////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////// ////// START: functions for manipulating a selected tile private bool setSelectedTile(TileZeroTile tile) { // [TODO] make sure the tile is one of the player's tiles if (!tile.getPlayable()) { Cfg.log(String.Format("The tile {0}{1} is not playable.", tile.getColorIndex(), tile.getShapeIndex())); } else { selectedTile = tile; return(true); } return(false); }
public int addTile(int rowIndex, int colIndex, TileZeroTile tile, bool validCheck, TileZeroTile[,] tileArrayP) { if (tileArrayP == null) { tileArrayP = tileArray; } int result = isValidMove(rowIndex, colIndex, tile, validCheck, tileArrayP, true); if (result != Cfg.NONE) { tileArrayP[rowIndex, colIndex] = tile; } return(result); }
// [2016.12.01] protected void fillPlayerTiles(Player player) { // [SC] make sure the tile bag is not empty and not all playable tiles are used while (tileBag.Count > 0 && playedTileCount < playabelTileCount) { TileZeroTile tile = tileBag.ElementAt(0); if (player.addTile(tile)) { tileBag.Remove(tile); ++playedTileCount; } else { break; } } }
// [SC] create a shallow clone of the 2D array public static TileZeroTile[,] createBoardCopy(TileZeroTile[,] tileArray) { if (tileArray == null) { return(null); } int rowCount = tileArray.GetLength(0); int colCount = tileArray.GetLength(1); TileZeroTile[,] newTileArray = new TileZeroTile[rowCount, colCount]; for (int currRowIndex = 0; currRowIndex < rowCount; currRowIndex++) { for (int currColIndex = 0; currColIndex < colCount; currColIndex++) { newTileArray[currRowIndex, currColIndex] = tileArray[currRowIndex, currColIndex]; } } return(newTileArray); }
// [2016.12.01] protected void putStartingTiles() { int startCol = virtualBoard.getColCount() / 2 - Cfg.START_TILE_COUNT / 2; int startRow = virtualBoard.getRowCount() / 2; for (int counter = 0; counter < Cfg.START_TILE_COUNT; counter++) { int currCol = startCol + counter; TileZeroTile tile = (TileZeroTile)tileBag.ElementAt(0); int result = putTileOnBoard(startRow, currCol, tile, false); // [TODO] need to terminate the game if (result == Cfg.NONE) { Cfg.log("Error putting starting tiles"); break; } tileBag.Remove(tile); ++playedTileCount; } }
public int isValidMove(int rowIndex, int colIndex, TileZeroTile tile, bool validCheck, TileZeroTile[,] tileArrayP, bool showMsg) { if (tileArrayP == null) { tileArrayP = tileArray; } int horizScore = 0; int vertScore = 0; if (rowIndex < 0 || rowIndex >= rowCount) { if (showMsg) { Cfg.log("Invalid row index: " + rowIndex + "."); } return(Cfg.NONE); } if (colIndex < 0 || colIndex >= colCount) { if (showMsg) { Cfg.log("Invalid column index: " + colIndex + "."); } return(Cfg.NONE); } if (hasTile(rowIndex, colIndex, tileArrayP)) { if (showMsg) { Cfg.log("The cell already has a tile."); } return(Cfg.NONE); } if (validCheck) { // [SC] check if there is any tile adjacent to the destinatio position if (!hasLeftTile(rowIndex, colIndex, tileArrayP) && !hasRightTile(rowIndex, colIndex, tileArrayP) && !hasBottomTile(rowIndex, colIndex, tileArrayP) && !hasTopTile(rowIndex, colIndex, tileArrayP) ) { if (showMsg) { Cfg.log("A new tile should be placed next to the existing one."); } return(Cfg.NONE); } // [SC] temporarily put the tile tileArrayP[rowIndex, colIndex] = tile; // [SC] check validity of the horizontal sequence of tiles if (hasLeftTile(rowIndex, colIndex, tileArrayP) || hasRightTile(rowIndex, colIndex, tileArrayP)) { horizScore = isValidSequence(rowIndex, colIndex, Cfg.HORIZONTAL, tileArrayP, showMsg); if (horizScore == Cfg.NONE) { tileArrayP[rowIndex, colIndex] = null; return(Cfg.NONE); } else if (horizScore == Cfg.MAX_SEQ_SCORE) { // [SC] reward for completing a TileZero horizScore = Cfg.TILEZERO_REWARD; } } // [SC] check validity of the vertical sequence of tiles if (hasTopTile(rowIndex, colIndex, tileArrayP) || hasBottomTile(rowIndex, colIndex, tileArrayP)) { vertScore = isValidSequence(rowIndex, colIndex, Cfg.VERTICAL, tileArrayP, showMsg); if (vertScore == Cfg.NONE) { tileArrayP[rowIndex, colIndex] = null; return(Cfg.NONE); } else if (vertScore == Cfg.MAX_SEQ_SCORE) { // [SC] reward for completing a TileZero vertScore = Cfg.TILEZERO_REWARD; } } // [SC] remove the temporary tile tileArrayP[rowIndex, colIndex] = null; } return(horizScore + vertScore); }
// [2016.12.01] // [SC] put a given tile on a specified board position; validCheck is true then verify if the move conforms to game rules protected int putTileOnBoard(int rowIndex, int colIndex, TileZeroTile tile, bool validCheck) { return(virtualBoard.addTile(rowIndex, colIndex, tile, validCheck, null)); }
public void removeTile(TileZeroTile tile) { playerTiles.Remove(tile); }
// [2016.12.01] // [SC] a function for dropping a tile public void dropPlayerTile(int playerIndex) { if (!activeGameFlag) { return; } if (playerIndex != activePlayerIndex) { Cfg.log("It is not your turn!"); return; } Player activePlayer = players[activePlayerIndex]; // [SC] check if player drop tiles if (!activePlayer.getCanDrop()) { Cfg.log("Cannot drop a tile after putting a tile on a board!"); // [TODO] return; } // [SC] check if bag has tiles if (tileBag.Count == 0) { Cfg.log("Cannot drop a tile! The bag is empty."); // [TODO] return; } // [SC] check if player tile is selected if (!activePlayer.isTileSelected()) { Cfg.log("Select a tile at first!"); // [TODO] return; } TileZeroTile tile = activePlayer.getSelectedTile(); // [SC] make sure that the tile being dropped is not a replacement tile of previously dropped tile if (!tile.getCanDrop()) { Cfg.log("Cannot drop a replacement tile!"); return; } foreach (TileZeroTile newTile in tileBag) { // [SC] make sure that the new tile does not have the same features as the dropped tile if (newTile.getColorIndex() == tile.getColorIndex() && newTile.getShapeIndex() == tile.getShapeIndex()) { continue; } Cfg.log(String.Format(" Dropped tile {0}. Replaced with tile {1}.", tile.ToString(), newTile.ToString())); // [SC] remove the dropped tile from player's stack activePlayer.removeTile(tile); // [SC] add the dropped tile into the bag tileBag.Add(tile); // [SC] remove the new tile from the bag tileBag.Remove(newTile); // [SC] add the new tile to player's stack activePlayer.addTile(newTile); // [SC] make sure that the new tile cannot be dropped in the same turn newTile.setCanDrop(false); // [SC] shuffle the bag tileBag.Shuffle(); // [SC] prevent the player from moving tiles into the board activePlayer.setCanMove(false); break; } }
public void resetSelected() { selectedTile = null; }
private int isValidSequence(int rowIndex, int colIndex, int orientation, TileZeroTile[,] tileArrayP, bool showMsg) { int[] uniqueColors = new int[Cfg.MAX_VAL_INDEX]; int uniqueColorCount = 0; int[] uniqueShapes = new int[Cfg.MAX_VAL_INDEX]; int uniqueShapeCount = 0; int sequenceLength = 0; int currRow = rowIndex; int currCol = colIndex; for (int currIndex = 0; currIndex < Cfg.MAX_VAL_INDEX; currIndex++) { uniqueColors[currIndex] = Cfg.NONE; uniqueShapes[currIndex] = Cfg.NONE; } // [SC] start with the left-most or top-most tile in the sequence if (orientation == Cfg.HORIZONTAL) { while (currCol > 0 && tileArrayP[currRow, currCol - 1] != null) { currCol--; } } else { while (currRow > 0 && tileArrayP[currRow - 1, currCol] != null) { currRow--; } } // [SC] checking the validity of colors and shapes, and color-shape combination of the sequence while (currRow < rowCount && currCol < colCount) { TileZeroTile currTile = tileArrayP[currRow, currCol]; if (currTile == null) { break; } // [SC] checking the validity of colors int currColorIndex = currTile.getColorIndex(); if (uniqueColors[currColorIndex] == Cfg.NONE) { uniqueColors[currColorIndex] = currColorIndex; uniqueColorCount++; } else if (uniqueColorCount == 1) { } else { if (showMsg) { Cfg.log("Invalid color sequence."); } return(Cfg.NONE); } // [SC] checking the validity of shapes int currShapeIndex = currTile.getShapeIndex(); if (uniqueShapes[currShapeIndex] == Cfg.NONE) { uniqueShapes[currShapeIndex] = currShapeIndex; uniqueShapeCount++; } else if (uniqueShapeCount == 1) { } else { if (showMsg) { Cfg.log("Invalid shape sequence."); } return(Cfg.NONE); } sequenceLength++; if (sequenceLength > 1) { if ((uniqueColorCount == 1 && uniqueShapeCount == 1) || // [SC] both shape and color are same (uniqueColorCount > 1 && uniqueShapeCount > 1) // both shape and color are different ) { if (showMsg) { Cfg.log("Invalid combination of color and shape."); } return(Cfg.NONE); } } // [TODO] update row if (orientation == Cfg.HORIZONTAL) { currCol++; } else { currRow++; } } return(sequenceLength); }