Game-Apps für Smartphones und Tablets

Pädagogische Hochschule Bern  
HomeOnline-Editor startenDruckenAndroid-TurtlegrafikJava-Online

FourInARow - Game


Four In A Row (auch: 4 gewinnt oder Connect four) ist ein Zweipersonen-Strategiespiel mit dem Ziel, als Erster vier der eigenen Spielsteine in eine Linie (horizontal, vertikal oder diagonal)zu bringen. Das klassische Brettspiel wird auf einem senkrecht stehenden, hohlen Spielbrett gespielt, in das die Spieler abwechselnd ihre Spielsteine fallen lassen. Das Spielbrett besteht aus sieben Spalten (senkrecht) und sechs Reihen (waagrecht). Jeder Spieler besitzt 21 gleichfarbige Spielsteine. Wenn ein Spieler einen Spielstein in eine Spalte fallen lässt, besetzt dieser den untersten freien Platz der Spalte. Gewinner ist der Spieler, der es als Erster schafft, vier seiner Spielsteine waagerecht, senkrecht oder diagonal in eine Linie zu bringen. Das Spiel endet unentschieden, wenn das Spielbrett komplett gefüllt ist, ohne dass ein Spieler eine Viererlinie gebildet hat.

In unserer Lernumgebung finden man dieses Spiel in zwei Varianten:

  • Für einen Spieler, der gegen den Computer spielt
  • Für zwei Spieler, die über das Internet spielen (im Abschnitt TCP/IP - Games)
 

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (FourInARow.zip)

 

 
QR-Code für den direkten
Download auf Smartphone
 
// 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.TRANSPARENTnullfalse);
  }

  public void main()
  {
    addTouchListener(thisGGTouch.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(-1this));
    showToast("Game started");
  }

  public void reset()
  {
    getBg().clear();
    removeActors(Token.class)//remove all tokens
    for (Token[] column : DBot.board) 
      Arrays.fill(column, new Token(-1this));
    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 = 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 = 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 = 0; j < nrOfTokens; j++)
    {
      int adjacentSameTokens = 0;
      for (int = 0; i < nrOfTokens; i++)
      {
        if ((col - + j>= && (col - + j< nbHorzCells
          && (row + - j>= && (row + - j< nbVertCells
          && getPlayerOfTokenAt(col - + j, row + - j== getPlayerOfTokenAt(col, row))
        {
          adjacentSameTokens++;
        }
      }
      if (adjacentSameTokens >= nrOfTokens)
        return true;
    }
    return false;
  }

  private boolean checkDiagonally1(int col, int row, int nrOfTokens)
  {
    for (int = 0; j < nrOfTokens; j++)
    {
      int adjacentSameTokens = 0;
      for (int = 0; i < nrOfTokens; i++)
      {
        if ((col + - j>= && (col + - j< nbHorzCells
          && (row + - j>= && (row + - j< nbVertCells
          && getPlayerOfTokenAt(col + - j, row + - 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 = 1;
    while (col - >= && getPlayerOfTokenAt(col - i, row) == getPlayerOfTokenAt(col, row))
    {
      adjacentSameTokens++;
      i++;
    }
    i = 1;
    while (col + < 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 = 1;
    while (row + < 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 = 0; i < nrOfSolutions; i++)
    {
      if (possibleSolutions.get(i) > && 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 = 0; j < nrOfTokens; j++)
    {
      int adjacentSameTokens = 1;
      for (int = 0; i < nrOfTokens; i++)
      {
        if ((col - + j>= && (col - + j< board.length
          && (row + - j>= 0
          && (row + - j< board[col].length
          && board[col - + j][row + - 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 = 0; j < nrOfTokens; j++)
    {
      int adjacentSameTokens = 1;
      for (int = 0; i < nrOfTokens; i++)
      {
        if ((col + - j>= && (col + - j< board.length
          && (row + - j>= 0
          && (row + - j< board[col].length
          && board[col + - j][row + - 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 = 0; j < nrOfTokens; j++)
    {
      int adjacentSameTokens = 1;
      for (int = 0; i < nrOfTokens; i++)
      {
        if ((col + - j>= && (col + - j< board.length
          && board[col + - 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 = 0; j < nrOfTokens; j++)
    {
      int adjacentSameTokens = 1;
      for (int = 0; i < nrOfTokens; i++)
      {
        if ((row + - j>= && (row + - j< board[col].length
          && board[col][row + - j].getPlayer() == checkTok)
        {
          adjacentSameTokens++;
        }
      }
      if (adjacentSameTokens == nrOfTokens)
        return true;
    }
    return false;
  }

  protected boolean isBoardEmpty()
  {
    for (int = 0; i < board.length; i++)
    {
      if (board[i][0].getPlayer() != -1)
        return false;
    }
    return true;
  }

  public void multiArrayCopy(Token[][] source, Token[][] destination)
  {
    for (int = 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 == && !gg.finished) // if this was human -> computer move
        gg.computerMove();
    }
  }

  public int getPlayer()
  {
    return player;
  }
}