Game-Apps für Smartphones und Tablets |
|
Bern University oh Teacher Education |
In unserer Lernumgebung finden man dieses Spiel in zwei Varianten:
|
![]() |
|
|
// FourInARow.java package app.fourinarow; import ch.aplu.android.*; import android.graphics.Color; import android.graphics.Point; import java.util.*; import ch.aplu.util.Monitor; public class FourInARow extends GameGrid implements GGTouchListener { private int currentPlayer = 0; public boolean finished = false; public Token activeToken; private IPlayer ComputerPlayer; private String moveInfo = "Drag chip and release "; private static final int nbHorzCells = 7; private static final int nbVertCells = 7; public FourInARow() { super(nbHorzCells, nbVertCells, cellZoom(60), Color.TRANSPARENT, null, false); } public void main() { addTouchListener(this, GGTouch.release | GGTouch.drag); getBg().clear(Color.WHITE); activeToken = new Token(currentPlayer, this); addActor(activeToken, new Location(0, 0), Location.SOUTH); addActor(new BG(), new Location(3, -4)); setSimulationPeriod(30); setTouchEnabled(true); doRun(); ComputerPlayer = new DBot(1, this); for (Token[] column : DBot.board) Arrays.fill(column, new Token(-1, this)); showToast("Game started"); } public void reset() { getBg().clear(); removeActors(Token.class); //remove all tokens for (Token[] column : DBot.board) Arrays.fill(column, new Token(-1, this)); currentPlayer = 0; showToast("Game restarted"); activeToken = new Token(currentPlayer, this); addActor(activeToken, new Location(0, 0), Location.SOUTH); finished = false; } public void computerMove() { setTouchEnabled(false); int col = ComputerPlayer.getColumn(); if (getOneActorAt(new Location(col, 1)) != null) { for (int i = 0; i < nbHorzCells; i++) { if (getOneActorAt(new Location(i, 1)) == null) { col = i; break; } } } activeToken.setX(col); activeToken.setActEnabled(true); currentPlayer = (currentPlayer + 1) % 2; //change Player } public boolean touchEvent(GGTouch touch) { Location mouseLoc = toLocationInGrid(touch.getX(), touch.getY()); if (touch.getEvent() == GGTouch.drag) { //move active token with mouse if (!finished && activeToken.getX() != mouseLoc.x) activeToken.setX(mouseLoc.x); return true; } if (finished) { reset(); return true; } if (getOneActorAt(new Location(mouseLoc.x, 1)) == null) { //drop Token if column isn't full activeToken.setActEnabled(true); setTouchEnabled(false); currentPlayer = (currentPlayer + 1) % 2; } else { showToast("This column is full."); } return true; } public boolean isBoardFull() { boolean isFull = true; for (int i = 0; i < nbHorzCells; i++) { if (getOneActorAt(new Location(i, 1)) == null) { isFull = false; break; } } return isFull; } public int getPlayerOfTokenAt(int x, int y) { Location loc = new Location(x, y); if (getOneActorAt(loc) == null) return -1; else return ((Token)getOneActorAt(loc)).getPlayer(); } public boolean check4Win(Location loc) { int col = loc.x; int row = loc.y; return (checkVertically(col, row, 4) || checkHorizontally(col, row, 4) || checkDiagonally1(col, row, 4) || checkDiagonally2(col, row, 4)); } private boolean checkDiagonally2(int col, int row, int nrOfTokens) { for (int j = 0; j < nrOfTokens; j++) { int adjacentSameTokens = 0; for (int i = 0; i < nrOfTokens; i++) { if ((col - i + j) >= 0 && (col - i + j) < nbHorzCells && (row + i - j) >= 1 && (row + i - j) < nbVertCells && getPlayerOfTokenAt(col - i + j, row + i - j) == getPlayerOfTokenAt(col, row)) { adjacentSameTokens++; } } if (adjacentSameTokens >= nrOfTokens) return true; } return false; } private boolean checkDiagonally1(int col, int row, int nrOfTokens) { for (int j = 0; j < nrOfTokens; j++) { int adjacentSameTokens = 0; for (int i = 0; i < nrOfTokens; i++) { if ((col + i - j) >= 0 && (col + i - j) < nbHorzCells && (row + i - j) >= 1 && (row + i - j) < nbVertCells && getPlayerOfTokenAt(col + i - j, row + i - j) == getPlayerOfTokenAt(col, row)) { adjacentSameTokens++; } } if (adjacentSameTokens >= nrOfTokens) return true; } return false; } private boolean checkHorizontally(int col, int row, int nrOfTokens) { int adjacentSameTokens = 1; int i = 1; while (col - i >= 0 && getPlayerOfTokenAt(col - i, row) == getPlayerOfTokenAt(col, row)) { adjacentSameTokens++; i++; } i = 1; while (col + i < nbHorzCells && getPlayerOfTokenAt(col + i, row) == getPlayerOfTokenAt(col, row)) { adjacentSameTokens++; i++; } return (adjacentSameTokens >= nrOfTokens); } private boolean checkVertically(int col, int row, int nrOfTokens) { int adjacentSameTokens = 1; int i = 1; while (row + i < nbVertCells && getPlayerOfTokenAt(col, row + i) == getPlayerOfTokenAt(col, row)) { adjacentSameTokens++; i++; } return (adjacentSameTokens >= nrOfTokens); } } // ----------- classBG --------------------- class BG extends Actor { public BG() { super(false, "fourinarow"); } public void reset() { setLocationOffset(new android.graphics.Point(0, 7 * gameGrid.getCellSize())); setOnTop(); } } // --------------class DBot ----------------------- class DBot extends IPlayer { public DBot(int thisPlayer, GameGrid gg) { super(gg); this.thisPlayer = thisPlayer; this.enemyPlayer = (thisPlayer + 1) % 2; } public int getColumn() { ArrayList<Integer> possibleSolutions = new ArrayList<Integer>(); ArrayList<Integer> veryBadIdeas = new ArrayList<Integer>(); int topRow = board[0].length - 1; int column; int row; if (isBoardEmpty()) { if (debug) System.out.println("me first, me choose middle!"); //debug return 4; } // Can I win in this turn? for (column = 0; column < board.length; column++) { row = insertToken(column); // if column is full, row = -1: if (row != -1 && checkXInARow(column, row, 4, thisPlayer, board)) { if (debug) System.out.println("Found something that makes me win: " + (column + 1)); // debug return column; } } // Can enemy win in his next turn? for (column = 0; column < board.length; column++) { row = insertToken(column); // if column is full, row == -1: if (row != -1 && checkXInARow(column, row, 4, enemyPlayer, board)) { if (debug) System.out.println("Found something that makes enemy win: " + (column + 1)); // debug return column; } } // stay defensive! try to destroy enemies chances to win: // put all these possibilities into ArrayList: possibleSolutions for (column = 0; column < 7; column++) { row = insertToken(column); if (row != -1 && checkXInARow(column, row, 3, enemyPlayer, board)) { if (debug) System.out.println("Found something good: " + (column + 1)); // debug possibleSolutions.add(column); } if (row != -1 && checkXInARow(column, row, 2, enemyPlayer, board)) { if (debug) System.out.println("Found something (maybe) valuable: " + (column + 1)); // debug possibleSolutions.add(column); } } // does any solution enable my enemy to win next turn? Iterator<Integer> posSolu = possibleSolutions.iterator(); int possibleColumn; while (posSolu.hasNext()) { possibleColumn = posSolu.next(); int nextRow = insertToken(possibleColumn) + 1; if (nextRow <= topRow && checkXInARow(possibleColumn, nextRow, 4, enemyPlayer, board)) { posSolu.remove(); if (debug) System.out.println("removed solutionzzz, left is: " + possibleSolutions); } } // prefer solutions in the middle of field: int nrOfSolutions = possibleSolutions.size(); for (int i = 0; i < nrOfSolutions; i++) { if (possibleSolutions.get(i) > 1 && possibleSolutions.get(i) < 5) possibleSolutions.add(possibleSolutions.get(i)); } // if there are any solutions left, return a random one // One column may be in there multiple times // -> it's a better move -> it's probability is higher! if (!possibleSolutions.isEmpty()) { Collections.shuffle(possibleSolutions); return (int)possibleSolutions.get(0); } // add illegal moves to veryBadIdeas: for (int col = 0; col < 7; col++) { if (board[col][topRow].getPlayer() != -1) veryBadIdeas.add(col); else { // add moves that enable my enemy to win to veryBadIdeas int nextRow = insertToken(col) + 1; if (nextRow <= topRow && checkXInARow(col, nextRow, 4, enemyPlayer, board)) veryBadIdeas.add(col); } } if (debug) System.out.println("Found very bad ideas: " + veryBadIdeas); Random grn = new Random(); // if there are only bad ideas, choose a random valid column: if (veryBadIdeas.size() == 7) { do { // values 2,3,4 are more probable than 0,1 or 5,6 // because stones in the middle are more valuable column = grn.nextInt(4) + grn.nextInt(4); // column = grn.nextInt(7); // then try random position } while (board[column][topRow].getPlayer() != -1); if (debug) System.out.println("Computer found too many bad ideas! Choosing: " + (column + 1)); return column; } else { do { column = grn.nextInt(4) + grn.nextInt(4); } while (veryBadIdeas.contains(column)); } if (debug) System.out.println("me " + thisPlayer + " Found some very bad ideas" + veryBadIdeas + ", but that should work: " + (column + 1)); return column; } } // ------------ class IPlayer ---------------------- abstract class IPlayer { protected int thisPlayer; //initialized @ constructor protected int enemyPlayer; public static Token[][] board = new Token[7][6]; //first x, then y coordinate protected boolean debug = true; protected GameGrid gg; //has to be overwritten: public abstract int getColumn(); public IPlayer(GameGrid gg) { this.gg = gg; } // ----- helper method: where would token land, if I took column X ----- protected int insertToken(int column) { int rowCount = 0; Token[] insertingColumn = board[column]; for (Token row : insertingColumn) { if (row.getPlayer() == -1) { return rowCount; } rowCount++; } return -1; } /* protected int insertToken(int column) { for (int row = 1; row < gg.getNbVertCells(); row++) if(gg.getOneActorAt(new Location (column, row)) != null) return row-1; return -1; // there is no free row if -1 is returned } */ // ----- helper method: check if there are X in a Row -------- protected boolean checkXInARow(int col, int row, int x, int checkPlayer, Token[][] board) { if (checkVertically(col, row, x, checkPlayer, board) || checkHorizontally(col, row, x, checkPlayer, board) || checkDiagonally1(col, row, x, checkPlayer, board) || checkDiagonally2(col, row, x, checkPlayer, board)) return true; return false; } //------checking nrOfTokens in a row private boolean checkDiagonally2(int col, int row, int nrOfTokens, int checkTok, Token[][] board) { for (int j = 0; j < nrOfTokens; j++) { int adjacentSameTokens = 1; for (int i = 0; i < nrOfTokens; i++) { if ((col - i + j) >= 0 && (col - i + j) < board.length && (row + i - j) >= 0 && (row + i - j) < board[col].length && board[col - i + j][row + i - j].getPlayer() == checkTok) { adjacentSameTokens++; } } if (adjacentSameTokens == nrOfTokens) return true; } return false; } private boolean checkDiagonally1(int col, int row, int nrOfTokens, int checkTok, Token[][] board) { for (int j = 0; j < nrOfTokens; j++) { int adjacentSameTokens = 1; for (int i = 0; i < nrOfTokens; i++) { if ((col + i - j) >= 0 && (col + i - j) < board.length && (row + i - j) >= 0 && (row + i - j) < board[col].length && board[col + i - j][row + i - j].getPlayer() == checkTok) { adjacentSameTokens++; } } if (adjacentSameTokens == nrOfTokens) return true; } return false; } private boolean checkHorizontally(int col, int row, int nrOfTokens, int checkTok, Token[][] board) { for (int j = 0; j < nrOfTokens; j++) { int adjacentSameTokens = 1; for (int i = 0; i < nrOfTokens; i++) { if ((col + i - j) >= 0 && (col + i - j) < board.length && board[col + i - j][row].getPlayer() == checkTok) { adjacentSameTokens++; } } if (adjacentSameTokens == nrOfTokens) return true; } return false; } private boolean checkVertically(int col, int row, int nrOfTokens, int checkTok, Token[][] board) { for (int j = 0; j < nrOfTokens; j++) { int adjacentSameTokens = 1; for (int i = 0; i < nrOfTokens; i++) { if ((row + i - j) >= 0 && (row + i - j) < board[col].length && board[col][row + i - j].getPlayer() == checkTok) { adjacentSameTokens++; } } if (adjacentSameTokens == nrOfTokens) return true; } return false; } protected boolean isBoardEmpty() { for (int i = 0; i < board.length; i++) { if (board[i][0].getPlayer() != -1) return false; } return true; } public void multiArrayCopy(Token[][] source, Token[][] destination) { for (int a = 0; a < source.length; a++) { System.arraycopy(source[a], 0, destination[a], 0, source[a].length); } } } // ------------------class Token -------------------- class Token extends Actor { private int player, nb; private FourInARow gg; public Token(int player, FourInARow gg) { super(false, "token", 2); this.player = player; this.gg = gg; setActEnabled(false); show(player); // 0 = yellow , 1 = red } public void act() { Location nextLoc = new Location(getX(), getY() + 1); int fallFactor = gg.getCellSize()/6; if (gameGrid.getOneActorAt(nextLoc) == null && isMoveValid()) { if (nb == 6) { nb = 0; setLocationOffset(new Point(0, 0)); move(); } else setLocationOffset(new Point(0, fallFactor * nb)); nb++; } else { //token has arrived setActEnabled(false); IPlayer.board[getX()][Math.abs(getY() - 6)] = this; //put into table for computers move if (gg.check4Win(getLocation())) { gg.showToast(player == 0 ? "You won!" : "You lost!"); gg.showToast("Click anywhere to play again."); gg.finished = true; gg.refresh(); Monitor.putSleep(2000); // wait for 2 seconds } else if (gg.isBoardFull()) { gg.showToast("It's a draw!"); gg.showToast("Click anywhere to play again."); gg.finished = true; gg.refresh(); Monitor.putSleep(2000); // wait for 2 seconds } else { // make new Token: gg.activeToken = new Token((player + 1) % 2, gg); gg.addActor(gg.activeToken, new Location(getX(), 0), Location.SOUTH); } gg.setTouchEnabled(true); if (this.player == 0 && !gg.finished) // if this was human -> computer move gg.computerMove(); } } public int getPlayer() { return player; } } |