This commit is contained in:
2018-04-16 13:40:41 +02:00
commit e619971762
69 changed files with 2759 additions and 0 deletions

47
src/Main.java Normal file
View File

@@ -0,0 +1,47 @@
import board.Board;
import events.FourInARow;
import events.IWinCondition;
import interfaces.FourInARowGUI;
import interfaces.NInARowGUI;
import interfaces.TicTacToeGUI;
import objects.Empty;
import javax.swing.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
class Main {
private static final int BOARD_WIDTH = 7;
private static final int BOARD_HEIGHT = 6;
private enum Gamemode {TICTACTOE, FOURINAROWCLASSIC, FOURINAROW}
public static void main(String[] args) {
HashMap<String, Gamemode> modeList = new HashMap<>();
modeList.put("Tic-Tac-Toe", Gamemode.TICTACTOE);
modeList.put("Four in a row classic", Gamemode.FOURINAROWCLASSIC);
modeList.put("Four in a row", Gamemode.FOURINAROW);
Object[] keys = modeList.keySet().toArray();
String selectedValue = (String)JOptionPane.showInputDialog(null, "Select a game from the list below:", "Car model...", JOptionPane.QUESTION_MESSAGE,null, keys, keys[0]);
if (selectedValue == null) {
System.exit(0);
}
List<IWinCondition> con = new ArrayList<>();
con.add(new FourInARow());
switch (modeList.get(selectedValue)) {
case TICTACTOE:
new TicTacToeGUI();
break;
case FOURINAROWCLASSIC:
new FourInARowGUI(BOARD_WIDTH, BOARD_HEIGHT, new Board(BOARD_WIDTH, BOARD_HEIGHT, new Empty(), con));
break;
case FOURINAROW:
new NInARowGUI(BOARD_WIDTH, BOARD_HEIGHT, new Board(BOARD_WIDTH, BOARD_HEIGHT, new Empty(), con));
break;
default:
System.exit(0);
}
}
}

70
src/Plan.txt Normal file
View File

@@ -0,0 +1,70 @@
Two dimensional array.
Render each subarray from bottom to top.
Place each piece in the start of the array.
Let user choose from a button above each row where to place a chip.
Keep count of player names and colors, maybe with some text input.
objects.Player has name and color.
Turn + Events
Width = 7, Height = 6
Check from each block of a color (maybe keep track of blocked pieces for effectiveness).
Maybe allow for custom combinations required to win.
Game
- Winning Conditions
- board.Board
- GameObjects
- Random events
- Input/Output
- Players
Players
Class with info about player <- passive
- name
- color (of token)
Interface for a player's actions <- active
- Methods for throwing token into column.
- +Info
Input/Output
- Input interface
* int getNextColumn(objects.Player p); //Retrieve user input
- Output interface
* void displayBoard(board.IBoard b);
* void displayMessage(String s);
Random Events
- Interface events.IRandomEvent
* void act(board.IBoard b);
* String announcement();
events.IRandomEvent
Abstract events.RandomEvent implements events.IRandomEvent (void flipCoin // Does something if heads)
Shuffle extends events.RandomEvent
objects.GameObject
objects.Token
Owner instanceof objects.Player
GameObjects(objects.Token, empty)
* One class
* getColor();
Winning Conditions
- Interface events.IWinCondition
* boolean hasWon(objects.Player p, board.IBoard board);
"Main" method (Game)
- Outer loop: "While the game has not been finished"
1) Determine who's turn
2) Get next input: int j = input.getNextColumn(p);
3) Execute move: p throws in column j.
4) Check if p has won.
5) Update view (output.displayBoard())
6) events.RandomEvent.act();

149
src/board/Board.java Normal file
View File

@@ -0,0 +1,149 @@
package board;
import events.IWinCondition;
import objects.GameObject;
import objects.Player;
import objects.Token;
import java.util.ArrayList;
import java.util.List;
public class Board implements IBoard{
private final Grid<GameObject> grid;
private final List<Player> players;
private int playersTurn = 0;
private final List<IWinCondition> conditionList;
private boolean won = false;
public Board(int width, int height, GameObject obj, List<IWinCondition> conditions) {
grid = new Grid<>(width, height, obj);
players = new ArrayList<>();
conditionList = conditions;
}
@Override
public boolean addPlayer(Player p) {
if (!playerExists(p)) {
players.add(p);
return true;
} else {
return false;
}
}
@Override
public ArrayList<Player> getPlayers() {
return new ArrayList<>(players);
}
/**
* Gets the next player.
*
* @return A Player
*/
public Player nextPlayer() {
Player p = players.get(playersTurn);
this.playersTurn = this.playersTurn + 2 > this.players.size() ? 0 : this.playersTurn + 1;
return p;
}
/**
* Makes sure we don't have two equal players.
*
* @param player The new player to test
* @return True if the player is non-unique
*/
private boolean playerExists(Player player) {
for (Player p : players) {
if (player.equals(p)) {
return true;
}
}
return false;
}
@Override
public boolean isFull() {
return grid.isFull();
}
/**
* Checks if anyone has won.
*
* @param i The column of the last dropped object
*/
private void checkWin(int i) {
for (IWinCondition con : conditionList) {
if (con.hasWon(i, getTopObject(i), this)) {
won = true;
}
}
}
/**
* Checks if anyone has won.
*
* @param x The x-coordinate of the last dropped object
* @param y The y-coordinate of the last dropped object
*/
private void checkWin(int x, int y) {
for (IWinCondition con : conditionList) {
if (con.hasWon(x, y, this)) {
won = true;
}
}
}
@Override
public boolean won() {
return won;
}
@Override
public void flush() {
grid.flush();
playersTurn = 0;
won = false;
}
@Override
public int getTopObject(int column) {
return grid.getTopObject(column);
}
@Override
public int getHeight() {
return grid.getHeight();
}
@Override
public int getWidth() {
return grid.getWidth();
}
@Override
public void set(int x, int y, GameObject element) {
grid.set(x, y, element);
if (element instanceof Token) {
checkWin(x, y);
}
}
@Override
public boolean drop(int i, GameObject element) {
boolean dropped = grid.drop(i, element);
if (element instanceof Token) {
checkWin(i);
}
return dropped;
}
@Override
public GameObject get(int x, int y) { return grid.get(x, y); }
@Override
public IGrid<GameObject> copy() {
return grid.copy();
}
}

100
src/board/Grid.java Normal file
View File

@@ -0,0 +1,100 @@
package board;
import objects.Token;
public class Grid<T> implements IGrid<T> {
private final T[][] grid;
private final int width;
private final int height;
private final T empty;
public Grid(int width, int height, T object) {
T[][] list = (T[][])new Object[width][height];
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
list[i][j] = object;
}
}
grid = list;
this.empty = object;
this.width = width;
this.height = height;
}
public void flush() {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
grid[i][j] = empty;
}
}
}
public boolean isFull() {
int empty = 0;
for (T[] list : grid) {
for (T item : list) {
if (this.empty.getClass().isInstance(item)) {
empty++;
}
}
}
return empty < 1;
}
@Override
public int getTopObject(int column) {
for (int j = this.height - 1; j > -1; j--) {
if (grid[column][j] instanceof Token) {
return j;
}
}
return -1;
}
@Override
public int getHeight() {
return this.height;
}
@Override
public int getWidth() {
return this.width;
}
@Override
public boolean drop(int i, T element) {
if (i > width || i < 0) {
throw new IllegalArgumentException("Index out of bounds.");
}
for (int j = 0; j < this.height; j++) {
if (this.empty.getClass().isInstance(grid[i][j])) {
grid[i][j] = element;
return true;
}
}
return false;
}
@Override
public void set(int x, int y, T element) {
if (x > width || y > height || x < 0 || y < 0) {
throw new IllegalArgumentException("Index out of bounds.");
}
grid[x][y] = element;
}
@Override
public T get(int x, int y) {
if (y >= height || x >= width || x < 0 || y < 0) {
throw new IllegalArgumentException("Index out of bound.");
}
return grid[x][y];
}
@Override
public IGrid<T> copy() {
Grid<T> newGrid = new Grid<>(width, height, empty);
System.arraycopy(grid, 0, newGrid.grid, 0, width);
return newGrid;
}
}

50
src/board/IBoard.java Normal file
View File

@@ -0,0 +1,50 @@
package board;
import objects.GameObject;
import objects.Player;
import java.util.List;
public interface IBoard extends IGrid<GameObject> {
/**
* Adds a new player to the board.
*
* @param p The player to add
* @return True if the player is valid
*/
boolean addPlayer(Player p);
/**
* Gets the next player from the board.
*
* @return A player
*/
Player nextPlayer();
/**
* Find out if someone has won.
*
* @return True if someone won
*/
boolean won();
/**
* Flushes all values in the board.
*/
void flush();
/**
* Gets the position of the topmost object in the board's grid.
*
* @param column The target column
* @return A position or -1 if all rows are empty
*/
int getTopObject(int column);
/**
* Gets a copy of the players list.
*
* @return A list of players
*/
List<Player> getPlayers();
}

70
src/board/IGrid.java Normal file
View File

@@ -0,0 +1,70 @@
package board;
public interface IGrid<T> {
/**
* @return The height of the grid.
*/
int getHeight();
/**
* @return The width of the grid.
*/
int getWidth();
/**
*
* Set the contents of the cell in the given x,y location.
*
* y must be greater than or equal to 0 and less than getHeight().
* x must be greater than or equal to 0 and less than getWidth().
*
* @param x The column of the cell to change the contents of.
* @param y The row of the cell to change the contents of.
* @param element The contents the cell is to have.
*/
void set(int x, int y, T element);
/**
* Drops an element into column i.
*
* @param i The target column
* @param element The element to be dropped
* @return True if the column had space for the element
*/
boolean drop(int i, T element);
/**
*
* Get the contents of the cell in the given x,y location.
*
* y must be greater than or equal to 0 and less than getHeight().
* x must be greater than or equal to 0 and less than getWidth().
*
* @param x The column of the cell to get the contents of.
* @param y The row of the cell to get contents of.
*/
T get(int x, int y);
/**
* Make a copy
*
* @return A fresh copy of the grid, with the same elements
*/
IGrid<T> copy();
/**
* Checks if the grid is absolutely full.
*
* @return True if it is full
*/
boolean isFull();
/**
* Retrieves the position of the topmost element from a column.
*
* @param column The target column
* @return The y-position of the top element
*/
int getTopObject(int column);
}

17
src/events/Block.java Normal file
View File

@@ -0,0 +1,17 @@
package events;
import board.IBoard;
import objects.Immovable;
public class Block extends RandomEvent {
@Override
public void act(IBoard b) {
int x = random.nextInt(b.getWidth());
b.drop(x, new Immovable());
}
@Override
public String announcement() {
return "An immovable object was placed.";
}
}

47
src/events/Bomb.java Normal file
View File

@@ -0,0 +1,47 @@
package events;
import board.IBoard;
import objects.Empty;
import objects.Token;
import java.util.ArrayList;
public class Bomb extends RandomEvent {
@Override
public void act(IBoard b) {
int x = random.nextInt(b.getWidth());
int y = b.getTopObject(x);
for (ArrayList<Integer> list : generateSquare(b, x, y, random.nextInt(2) + 1)) {
b.set(list.get(0), list.get(1), new Empty());
}
}
/**
* Gets all locations in a square a range from a point.
*
* @param b A board
* @param x The x index of the starting point
* @param y The y index of the starting point
* @param range The range of the square
* @return A list of lists of integers
*/
private ArrayList<ArrayList<Integer>> generateSquare(IBoard b, int x, int y, int range) {
ArrayList<ArrayList<Integer>> locations = new ArrayList<>();
for (int i = x - range; i <= x + range; i++) {
for (int j = y - range; j <= y + range; j++) {
if (i >= 0 && i < b.getWidth() && j >= 0 && j < b.getHeight() && b.get(i, j) instanceof Token) {
ArrayList<Integer> coords = new ArrayList<>();
coords.add(i);
coords.add(j);
locations.add(coords);
}
}
}
return locations;
}
@Override
public String announcement() {
return "A bomb just exploded!";
}
}

View File

@@ -0,0 +1,10 @@
package events;
import board.IBoard;
public class FourInARow extends InARow {
@Override
public boolean hasWon(int x, int y, IBoard board) {
return hasWon(x, y, board, 3);
}
}

View File

@@ -0,0 +1,8 @@
package events;
import board.IBoard;
public interface IRandomEvent {
void act(IBoard b);
String announcement();
}

View File

@@ -0,0 +1,18 @@
package events;
import board.IBoard;
/**
* A condition for winning the game.
*/
public interface IWinCondition {
/**
* Checks if someone has won according to the custom conditions.
*
* @param x The x-coordinate of the last placed element
* @param y The y-coordinate of the last placed element
* @param board The board to check
* @return True if someone has won
*/
boolean hasWon(int x, int y, IBoard board);
}

47
src/events/InARow.java Normal file
View File

@@ -0,0 +1,47 @@
package events;
import board.IBoard;
import java.awt.*;
/**
* Contains methods for implementing any n in a row rules.
*/
abstract class InARow implements IWinCondition {
/**
* Checks if the supplied position has enough neighbours in a straight line of the same type
*
* @param x The x-coordinate of the start position
* @param y The y-coordinate of the start position
* @param board The board on which to perform the task
* @param t The number of elements in a row, which constitutes a win (excluding start position)
* @return True if x, y was a winning move
*/
static boolean hasWon(int x, int y, IBoard board, int t) {
return count(x, y, 1, 0, board) + count(x, y, -1, 0, board) >= t ||
count(x, y, 0, -1, board) + count(x, y, 0, 1, board) >= t ||
count(x, y, 1, 1, board) + count(x, y, -1, -1, board) >= t ||
count(x, y, 1, -1, board) + count(x, y, -1, 1, board) >= t;
}
/**
* Counts all elements from a start position in a set direction with a matching color.
*
* @param x The x-coordinate of the start position
* @param y The y-coordinate of the start position
* @param iX Step amount in x-direction
* @param iY Step amount in y-direction
* @param b The board on which to perform the task
* @return The number of elements found
*/
private static int count(int x, int y, int iX, int iY, IBoard b) {
int count = 0;
Color target = b.get(x, y).getColor();
while ((y + iY >= 0 && y + iY < b.getHeight()) && (x + iX >= 0 && x + iX < b.getWidth()) && b.get(x + iX, y + iY).getColor() == target) {
x += iX;
y += iY;
count++;
}
return count;
}
}

View File

@@ -0,0 +1,43 @@
package events;
import board.IBoard;
import java.util.Random;
/**
* Contains basic probability methods usable by all random events.
*/
public abstract class RandomEvent implements IRandomEvent {
final Random random = new Random();
/**
* Flips a coin, and acts if it comes out as head.
*
* @param b The board to act on
* @return True if somehting happened. False otherwise
*/
public boolean flipCoin(IBoard b) {
if (random.nextInt(2) > 0) {
act(b);
return true;
}
return false;
}
/**
* Rolls n die, and acts if all die got a "6".
*
* @param b The board to act on
* @param n The number of die
* @return True if something happened. False otherwise
*/
public boolean rollDie(IBoard b, int n) {
if (random.nextInt((int)Math.pow(6, n)) < 1) {
act(b);
return true;
}
return false;
}
public abstract void act(IBoard b);
}

View File

@@ -0,0 +1,10 @@
package events;
import board.IBoard;
public class ThreeInARow extends InARow {
@Override
public boolean hasWon(int x, int y, IBoard board) {
return hasWon(x, y, board, 2);
}
}

View File

@@ -0,0 +1,136 @@
package interfaces;
import board.IBoard;
import objects.GameObject;
import objects.Player;
import objects.Token;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class FourInARowGUI extends GUI {
public FourInARowGUI(int width, int height, IBoard board) {
if (width < 3 || height < 3 || height > 50 || width > 50) {
throw new IllegalArgumentException("Invalid game size.");
}
if (width != board.getWidth() || height != board.getHeight()) {
throw new IllegalArgumentException("The board does not match the gui.");
}
this.board = board;
for (int i = 0; i < 2; i++) {
String playerName = "";
while (playerName.equals("")) {
playerName = JOptionPane.showInputDialog(String.format("Name of player %d: ", i+1));
if (playerName == null) {
System.exit(0);
}
}
Color color;
if (i == 0) {
color = Color.green;
} else {
color = Color.red;
}
Player player = new Player(playerName, color);
if (!this.board.addPlayer(player)) {
JOptionPane.showMessageDialog(null, "Player is not unique. Please try again.", "Error", JOptionPane.ERROR_MESSAGE);
i--;
}
}
initialize(width, height);
}
private void initialize(int width, int height) {
JFrame frame = new JFrame("Four in a row");
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
int fullWidth = BUTTON_WIDTH * width + (WIDTH_GAP + 1) * width;
int fullHeight = BUTTON_HEIGHT + (TILE_HEIGHT * height) + HEIGHT_GAP * (height) + TILE_HEIGHT;
this.frame = frame;
JPanel container = new JPanel();
container.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
container.setPreferredSize(new Dimension(fullWidth, fullHeight + TILE_HEIGHT + BUTTON_HEIGHT));
frame.add(container);
JPanel panel3 = new JPanel();
this.resetButton = new JButton("Reset");
this.resetButton.addActionListener(this);
panel3.add(this.resetButton);
for (Player player : board.getPlayers()) {
JLabel playerLabel = new JLabel(player.toString());
playerLabel.setForeground(player.getColor());
panel3.add(playerLabel);
}
container.add(panel3);
JPanel panel2 = new JPanel();
this.mainPanel = panel2;
panel2.setBackground(Color.BLUE);
panel2.setLayout(new FlowLayout(FlowLayout.CENTER, HEIGHT_GAP, WIDTH_GAP));
panel2.setPreferredSize(new Dimension(fullWidth, fullHeight));
dropButtons = new JButton[width];
for (int i = 0; i < width; i++) {
JButton button = new JButton();
button.addActionListener(this);
button.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
dropButtons[i] = button;
panel2.add(button, BorderLayout.PAGE_START);
}
tiles = new JTextPane[width * height];
readTiles();
container.add(panel2);
infoLabel = new JLabel();
infoLabel.setPreferredSize(new Dimension(fullWidth, BUTTON_HEIGHT));
infoLabel.setBorder(new EmptyBorder(10, 10, 10, 10));
container.add(infoLabel);
frame.validate();
frame.pack();
frame.setVisible(true);
this.currentPlayer = board.nextPlayer();
displayMessage("It's " + this.currentPlayer + "'s turn.");
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == this.resetButton) {
this.board.flush();
displayBoard();
this.currentPlayer = this.board.nextPlayer();
displayMessage("It's " + this.currentPlayer + "'s turn.");
}
if (!(this.board.won() || this.board.isFull())) {
for (int i = 0; i < this.dropButtons.length; i++) {
if (e.getSource() == this.dropButtons[i]) {
if (dropObject(i)) {
displayBoard();
if (!this.board.won()) {
if (board.isFull()) {
displayMessage("It's a draw");
} else {
this.currentPlayer = board.nextPlayer();
displayMessage("It's " + this.currentPlayer + "'s turn.");
}
} else {
GameObject winnerObject = this.board.get(i, this.board.getTopObject(i));
displayMessage(((Token)winnerObject).getOwner() + " won.");
}
} else {
displayMessage("That column is full.");
}
}
}
}
}
}

61
src/interfaces/GUI.java Normal file
View File

@@ -0,0 +1,61 @@
package interfaces;
import board.IBoard;
import objects.GameObject;
import objects.Player;
import objects.Token;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
public abstract class GUI implements ActionListener, IGameInterface {
final int BUTTON_HEIGHT = 25;
final int BUTTON_WIDTH = 50;
final int TILE_HEIGHT = 50;
final int HEIGHT_GAP = 10;
final int WIDTH_GAP = 10;
JButton[] dropButtons;
JButton resetButton;
JTextPane[] tiles;
JLabel infoLabel;
IBoard board;
JPanel mainPanel;
JFrame frame;
Player currentPlayer;
public void displayBoard() {
for (JTextPane pane : this.tiles) {
this.mainPanel.remove(pane);
}
this.tiles = new JTextPane[this.board.getWidth() * this.board.getHeight()];
readTiles();
this.frame.validate();
}
void readTiles() {
int k = 0;
for (int i = board.getHeight() - 1; i > -1; i--) {
for (int j = 0; j < board.getWidth(); j++) {
JTextPane textPane = new JTextPane();
this.tiles[k++] = textPane;
textPane.setEditable(false);
textPane.setBackground(Color.BLUE);
GameObject obj = board.get(j,i);
textPane.setBorder(BorderFactory.createLineBorder(obj.getColor(), 50, true));
textPane.setPreferredSize(new Dimension(BUTTON_WIDTH, TILE_HEIGHT));
mainPanel.add(textPane, BorderLayout.PAGE_START);
}
}
}
boolean dropObject(int i) {
return this.board.drop(i, new Token(currentPlayer));
}
public void displayMessage(String s) {
infoLabel.setText(s);
}
}

View File

@@ -0,0 +1,6 @@
package interfaces;
interface IGameInterface {
void displayMessage(String s);
void displayBoard();
}

View File

@@ -0,0 +1,181 @@
package interfaces;
import board.IBoard;
import events.Block;
import events.Bomb;
import objects.GameObject;
import objects.Player;
import objects.Token;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class NInARowGUI extends GUI {
private Bomb bomb = new Bomb();
private Block block = new Block();
private JLabel eventLabel;
public NInARowGUI(int width, int height, IBoard board) {
if (width < 3 || height < 3 || height > 50 || width > 50) {
throw new IllegalArgumentException("Invalid game size.");
}
if (width != board.getWidth() || height != board.getHeight()) {
throw new IllegalArgumentException("The board does not match the gui.");
}
this.board = board;
int playerNum = 0;
while (playerNum == 0) {
try {
String num = JOptionPane.showInputDialog("Number of players: ");
if (num == null) {
System.exit(0);
}
playerNum = Integer.parseInt(num);
if (playerNum > width) {
JOptionPane.showMessageDialog(null, "Number of players can't be higher than " + width, "Error", JOptionPane.ERROR_MESSAGE);
playerNum = 0;
}
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(null, "Invalid input.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
for (int i = 0; i < playerNum; i++) {
String playerName = "";
while (playerName.equals("")) {
playerName = JOptionPane.showInputDialog(String.format("Name of player %d: ", i+1));
if (playerName == null) {
System.exit(0);
}
}
Color color = null;
while (color == null) {
color = JColorChooser.showDialog(null, "Choose a color", Color.RED);
if (color == null) {
System.exit(0);
}
if (color == Color.lightGray || color == Color.blue || color == Color.black) {
JOptionPane.showMessageDialog(null, "The chosen color is reserved. Please choose another color.", "Error", JOptionPane.ERROR_MESSAGE);
color = null;
}
}
Player player = new Player(playerName, color);
if (!this.board.addPlayer(player)) {
JOptionPane.showMessageDialog(null, "Player is not unique. Please try again.", "Error", JOptionPane.ERROR_MESSAGE);
i--;
}
}
initialize(width, height);
}
private void initialize(int width, int height) {
JFrame frame = new JFrame("Four in a row");
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
int fullWidth = BUTTON_WIDTH * width + (WIDTH_GAP + 1) * width;
int fullHeight = BUTTON_HEIGHT + (TILE_HEIGHT * height) + HEIGHT_GAP * (height) + TILE_HEIGHT;
this.frame = frame;
JPanel container = new JPanel();
container.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
container.setPreferredSize(new Dimension(fullWidth, fullHeight + TILE_HEIGHT + 2 * BUTTON_HEIGHT));
frame.add(container);
JPanel panel3 = new JPanel();
this.resetButton = new JButton("Reset");
this.resetButton.addActionListener(this);
panel3.add(this.resetButton);
for (Player player : board.getPlayers()) {
JLabel playerLabel = new JLabel(player.toString());
playerLabel.setForeground(player.getColor());
panel3.add(playerLabel);
}
container.add(panel3);
JPanel panel2 = new JPanel();
this.mainPanel = panel2;
panel2.setBackground(Color.BLUE);
panel2.setLayout(new FlowLayout(FlowLayout.CENTER, HEIGHT_GAP, WIDTH_GAP));
panel2.setPreferredSize(new Dimension(fullWidth, fullHeight));
dropButtons = new JButton[width];
for (int i = 0; i < width; i++) {
JButton button = new JButton();
button.addActionListener(this);
button.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
dropButtons[i] = button;
panel2.add(button, BorderLayout.PAGE_START);
}
tiles = new JTextPane[width * height];
readTiles();
container.add(panel2);
infoLabel = new JLabel();
infoLabel.setPreferredSize(new Dimension(fullWidth, BUTTON_HEIGHT));
infoLabel.setBorder(new EmptyBorder(10, 10, 10, 10));
container.add(infoLabel);
eventLabel = new JLabel();
eventLabel.setPreferredSize(new Dimension(fullWidth, BUTTON_HEIGHT));
eventLabel.setBorder(new EmptyBorder(10, 10, 10, 10));
container.add(eventLabel);
frame.validate();
frame.pack();
frame.setVisible(true);
this.currentPlayer = board.nextPlayer();
displayMessage("It's " + this.currentPlayer + "'s turn.");
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == this.resetButton) {
this.board.flush();
displayBoard();
this.currentPlayer = this.board.nextPlayer();
displayMessage("It's " + this.currentPlayer + "'s turn.");
}
if (!(this.board.won() || this.board.isFull())) {
for (int i = 0; i < this.dropButtons.length; i++) {
if (e.getSource() == this.dropButtons[i]) {
if (dropObject(i)) {
displayBoard();
if (this.board.won()) {
GameObject winnerObject = this.board.get(i, this.board.getTopObject(i));
displayMessage(((Token)winnerObject).getOwner() + " won.");
} else {
triggerEvents();
if (board.isFull()) {
displayMessage("It's a draw");
} else {
this.currentPlayer = board.nextPlayer();
displayMessage("It's " + this.currentPlayer + "'s turn.");
}
}
} else {
displayMessage("That column is full.");
}
}
}
}
}
private void triggerEvents() {
if (bomb.rollDie(this.board, 2)) {
eventLabel.setText(bomb.announcement());
displayBoard();
} else if (block.rollDie(this.board, 1)) {
eventLabel.setText(block.announcement());
displayBoard();
} else {
eventLabel.setText("");
}
}
}

View File

@@ -0,0 +1,151 @@
package interfaces;
import board.Board;
import events.IWinCondition;
import events.ThreeInARow;
import objects.Empty;
import objects.GameObject;
import objects.Player;
import objects.Token;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
public class TicTacToeGUI extends GUI {
private JButton[] gameGrid = new JButton[9];
public TicTacToeGUI() {
List<IWinCondition> conditions = new ArrayList<>();
conditions.add(new ThreeInARow());
this.board = new Board(3,3, new Empty(), conditions);
int playerNum = 2;
for (int i = 0; i < playerNum; i++) {
String playerName = "";
while (playerName.equals("")) {
playerName = JOptionPane.showInputDialog(String.format("Name of player %d: ", i+1));
if (playerName == null) {
System.exit(0);
}
if (playerName.length() > 10) {
JOptionPane.showMessageDialog(null, "Player name can't be longer than 10 characters.", "Error", JOptionPane.ERROR_MESSAGE);
playerName = "";
}
}
Player player;
if (i == 1) {
player = new Player(playerName, Color.BLACK);
} else {
player = new Player(playerName, Color.WHITE);
}
if (!this.board.addPlayer(player)) {
JOptionPane.showMessageDialog(null, "Player is not unique. Please try again.", "Error", JOptionPane.ERROR_MESSAGE);
i--;
}
}
initialize();
}
private void initialize() {
JFrame frame = new JFrame("Four in a row");
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
int fullWidth = (BUTTON_WIDTH) * 3;
int fullHeight = (TILE_HEIGHT) * 3;
this.frame = frame;
JPanel container = new JPanel();
container.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
container.setPreferredSize(new Dimension(fullWidth, fullHeight + 2 * BUTTON_HEIGHT));
frame.add(container);
JPanel panel2 = new JPanel();
this.mainPanel = panel2;
panel2.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
panel2.setPreferredSize(new Dimension(fullWidth, fullHeight));
readTiles();
container.add(panel2);
infoLabel = new JLabel();
infoLabel.setPreferredSize(new Dimension(fullWidth, 15));
infoLabel.setBorder(new EmptyBorder(10, 10, 15, 10));
container.add(infoLabel);
this.resetButton = new JButton("Reset");
this.resetButton.addActionListener(this);
container.add(this.resetButton);
frame.validate();
frame.pack();
frame.setVisible(true);
currentPlayer = board.nextPlayer();
displayMessage("It's " + currentPlayer + "'s turn.");
}
public void displayBoard() {
for (JButton button : this.gameGrid) {
this.mainPanel.remove(button);
}
this.gameGrid = new JButton[this.board.getWidth() * this.board.getHeight()];
readTiles();
this.frame.validate();
}
void readTiles() {
int k = 0;
for (int i = 0; i < board.getHeight(); i++) {
for (int j = 0; j < board.getWidth(); j++) {
JButton button = new JButton();
button.addActionListener(this);
GameObject obj = board.get(j,i);
if (obj.getColor() == Color.black) {
button.setText("X");
} else if (obj.getColor() == Color.white) {
button.setText("O");
}
button.setPreferredSize(new Dimension(BUTTON_WIDTH, TILE_HEIGHT));
this.gameGrid[k++] = button;
mainPanel.add(button, BorderLayout.PAGE_START);
}
}
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == this.resetButton) {
this.board.flush();
displayBoard();
this.currentPlayer = this.board.nextPlayer();
displayMessage("It's " + this.currentPlayer + "'s turn.");
}
if (!(board.won() || board.isFull())) {
for (int i = 0; i < this.gameGrid.length; i++) {
if (e.getSource() == this.gameGrid[i]) {
if (this.gameGrid[i].getText().equals("")) {
int x = i % 3;
int y = i / 3;
this.board.set(x, y, new Token(currentPlayer));
displayBoard();
if (!board.won()) {
currentPlayer = board.nextPlayer();
displayMessage("It's " + currentPlayer + "'s turn.");
} else {
GameObject winnerObject = board.get(x, board.getTopObject(x));
displayMessage(((Token)winnerObject).getOwner() + " won.");
}
if (board.isFull()) {
displayMessage("It's a draw");
}
}
}
}
}
}
}

3
src/objects/Empty.java Normal file
View File

@@ -0,0 +1,3 @@
package objects;
public class Empty extends GameObject {}

View File

@@ -0,0 +1,11 @@
package objects;
import java.awt.*;
public abstract class GameObject {
Color color = Color.LIGHT_GRAY;
public Color getColor() {
return color;
}
}

View File

@@ -0,0 +1,9 @@
package objects;
import java.awt.*;
public class Immovable extends GameObject {
public Immovable() {
this.color = Color.BLACK;
}
}

38
src/objects/Player.java Normal file
View File

@@ -0,0 +1,38 @@
package objects;
import java.awt.*;
import java.util.Objects;
public class Player {
private final String name;
private final Color color;
public Player(String name, Color color) {
this.name = name;
this.color = color;
}
public Color getColor() {
return color;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Player player = (Player) o;
return Objects.equals(name, player.name) ||
Objects.equals(color, player.color);
}
@Override
public int hashCode() {
return Objects.hash(name, color);
}
@Override
public String toString() {
return name;
}
}

14
src/objects/Token.java Normal file
View File

@@ -0,0 +1,14 @@
package objects;
public class Token extends GameObject {
private final Player owner;
public Token(Player owner) {
this.owner = owner;
this.color = owner.getColor();
}
public Player getOwner() {
return this.owner;
}
}