diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6b8dde0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: java +jdk: + - oraclejdk8 +os: + - linux +script: + - chmod +x buildscript.sh && ./buildscript.sh diff --git a/02.TicTacToe/pom.xml b/02.TicTacToe/pom.xml new file mode 100644 index 0000000..ead7aac --- /dev/null +++ b/02.TicTacToe/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + 02 + TicTacToe + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + junit + junit + 4.12 + + + junit + junit + 4.12 + + + + + \ No newline at end of file diff --git a/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/CreateElements.java b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/CreateElements.java new file mode 100644 index 0000000..b788eb6 --- /dev/null +++ b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/CreateElements.java @@ -0,0 +1,201 @@ +package ru.spbau.mit.alyokhina.TicTacToe; + + +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.TextArea; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; + +import java.util.function.Consumer; + +/** Create UI elements */ +public class CreateElements { + + /** + * Create button + * + * @param x abscissa coordinate + * @param y ordinate coordinate + * @param text this text will be on the button + * @return new button + */ + public static Button createButton(GridPane gridPane, double height, double width, int x, int y, String text) { + Button button = new Button(); + gridPane.add(button, x, y); + button.setText(text); + button.setPrefHeight(height); + button.setPrefWidth(width); + button.setLayoutX(x); + button.setLayoutY(y); + return button; + } + + + /** + * Create board for TicTacToe. Every cell is button + * + * @return array of buttons + */ + public static Button[] createTableView(GridPane gridPane) { + Button[] buttons = new Button[9]; + for (int i = 0; i < 9; i++) { + buttons[i] = createButton(gridPane, 60, 60, 210 + 60 * (i % 3), 110 + 60 * (i / 3), ""); + } + return buttons; + } + + /**Create Menu for Main Activity */ + public static void createMainActivity(GridPane gridPane) { + Button buttonPlayWithComp = CreateElements.createButton(gridPane, 50, 200, 0, 0, "Играть с компьютером"); + Button buttonPlayWithFriend = CreateElements.createButton(gridPane, 50, 200, 0, 1, "Играть с другом"); + Button buttonExit = CreateElements.createButton(gridPane, 50, 200, 0, 2, "Выход"); + buttonPlayWithComp.setOnAction(actionEvent -> { + GameWithComp gameWithComp = new GameWithComp(gridPane, new Statistics()); + gameWithComp.menu(); + }); + + buttonPlayWithFriend.setOnAction(actionEvent -> { + GameWithFriend gameWithFriend = new GameWithFriend(gridPane, new Statistics()); + gameWithFriend.menu(); + }); + buttonExit.setOnAction(value -> Platform.exit()); + } + + /** + * Create button for new game + * + * @param statistics is parameter for consumer + * @param consumer will be accepted after clicking on the button + */ + public static void createButtonReplay(GridPane gridPane, Statistics statistics, Consumer consumer) { + Button rePlay = CreateElements.createButton(gridPane, 50, 200, 400, 20, "Начать новую партию"); + rePlay.setOnAction(actionEvent -> { + gridPane.getChildren().clear(); + consumer.accept(statistics); + }); + } + + + /** Create button to go to the main activity */ + public static void createButtonToMainActivity(GridPane gridPane) { + Button buttonToMainActivity = CreateElements.createButton(gridPane, 50, 200, 400, 350, "В главное меню"); + buttonToMainActivity.setOnAction(actionEvent -> { + gridPane.getChildren().clear(); + createMainActivity(gridPane); + }); + } + + + /** + * Create button to go to statistics for hot seat. After clicking go to new activity + * + * @param go will be accepted after clicking button back + */ + public static void createButtonGetStatisticsForOnePlayers(GridPane gridPane, Statistics statistics, Consumer go) { + Button getStatistics = CreateElements.createButton(gridPane, 50, 200, 200, 270, "Статистика"); + getStatistics.setOnAction(actionEvent -> { + gridPane.getChildren().clear(); + CreateElements.createButtonToMainActivity(gridPane); + Button buttonBack = CreateElements.createButton(gridPane, 50, 200, 400, 20, "Назад"); + buttonBack.setOnAction(actionEvent1 -> { + gridPane.getChildren().clear(); + go.accept(statistics); + }); + TextArea textArea = new TextArea(); + textArea.setLayoutY(0); + textArea.setLayoutX(0); + textArea.setPrefHeight(600); + textArea.setPrefWidth(200); + gridPane.getChildren().add(textArea); + textArea.setText("количество побед: " + statistics.getCountWins().toString() + + "\nколичество поражений: " + statistics.getCountLose().toString() + + "\nколичество ничьих: " + statistics.getCountDraw().toString()); + + }); + } + + + /** + * Create button to go to statistics for two players. After clicking go to new activity + * + * @param go will be accepted after clicking button back + */ + public static void createButtonGetStatisticsForTwoPlayers(GridPane gridPane, Statistics statistics, Consumer go) { + Button getStatistics = CreateElements.createButton(gridPane, 50, 200, 200, 240, "Статистика"); + getStatistics.setOnAction(actionEvent -> { + gridPane.getChildren().clear(); + CreateElements.createButtonToMainActivity(gridPane); + Button button = CreateElements.createButton(gridPane, 50, 200, 400, 20, "Назад"); + button.setOnAction(actionEvent1 -> { + gridPane.getChildren().clear(); + go.accept(statistics); + }); + TextArea textAreaForFirst = new TextArea(); + //textAreaForFirst.setLayoutY(0); + //textAreaForFirst.setLayoutX(0); + textAreaForFirst.setPrefHeight(600); + textAreaForFirst.setPrefWidth(200); + gridPane.add(textAreaForFirst, 0, 0); + textAreaForFirst.setText("Х \nколичество побед: " + statistics.getCountLose().toString() + + "\nколичество поражений: " + statistics.getCountWins().toString() + + "\nколичество ничьих: " + statistics.getCountDraw().toString()); + + + TextArea textAreaForSecond = new TextArea(); + textAreaForSecond.setLayoutY(0); + textAreaForSecond.setLayoutX(200); + textAreaForSecond.setPrefHeight(600); + textAreaForSecond.setPrefWidth(200); + gridPane.add(textAreaForSecond, 5, 0); + textAreaForSecond.setText("О \nколичество побед: " + statistics.getCountWins().toString() + + "\nколичество поражений: " + statistics.getCountLose().toString() + + "\nколичество ничьих: " + statistics.getCountDraw().toString()); + }); + } + + /** Print result on screen and update statistics */ + public static void setResult(GridPane gridPane, Statistics statistics, String win) { + gridPane.getChildren().clear(); + CreateElements.createButtonToMainActivity(gridPane); + switch (win) { + case "draw": { + TextArea textArea = new TextArea(); + textArea.setLayoutY(150); + textArea.setLayoutX(250); + textArea.setPrefHeight(50); + textArea.setPrefWidth(100); + textArea.setText("draw"); + gridPane.getChildren().add(textArea); + statistics.incDraw(); + break; + } + case "X": { + TextArea textArea = new TextArea(); + textArea.setLayoutY(150); + textArea.setLayoutX(250); + textArea.setPrefHeight(50); + textArea.setPrefWidth(100); + textArea.setText("X wins"); + gridPane.getChildren().add(textArea); + statistics.incLose(); + break; + } + default: { + TextArea textArea = new TextArea(); + textArea.setLayoutY(150); + textArea.setLayoutX(250); + textArea.setPrefHeight(50); + textArea.setPrefWidth(100); + gridPane.getChildren().add(textArea); + textArea.setText("O wins"); + statistics.incWins(); + break; + } + } + + } + +} diff --git a/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Game.java b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Game.java new file mode 100644 index 0000000..bedd786 --- /dev/null +++ b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Game.java @@ -0,0 +1,138 @@ +package ru.spbau.mit.alyokhina.TicTacToe; + +import javafx.scene.control.Button; +import javafx.scene.layout.GridPane; + +/** + * Class for key actions of game + */ +public class Game { + /** + * GridPane for current game + */ + protected GridPane gridPane; + /** + * Number playing + */ + protected int numberGames = 0; + /** + * Statistics for current game + */ + protected Statistics statistics; + + /** + * Constructor + */ + public Game(GridPane gridPane, Statistics statistics) { + this.gridPane = gridPane; + this.statistics = statistics; + gridPane.getChildren().clear(); + CreateElements.createButtonToMainActivity(gridPane); + } + + /** + * Translated text from buttons array in string array + */ + protected String[] toStringArray(Button[] table) { + String[] tableString = new String[table.length]; + for (int i = 0; i < table.length; i++) { + tableString[i] = table[i].getText(); + } + return tableString; + } + + /** + * Check the state of the game + * + * @param tableString state board, "" - if cell is empty + * @return "X" - if X wins , "O" - if O wins, "" - if state is unknown, else - draw + */ + public static String check(String[] tableString) { + + boolean existEmpty = false; + for (int i = 0; i < tableString.length; i++) { + if (tableString[i].equals("")) { + existEmpty = true; + } + } + + for (int i = 0; i < 9; i += 3) { + if (!tableString[i].equals("") && tableString[i].equals(tableString[i + 1]) && tableString[i + 1].equals(tableString[i + 2])) { + return tableString[i]; + } + } + + for (int i = 0; i < 3; i++) { + if (!tableString[i].equals("") && tableString[i].equals(tableString[i + 3]) && tableString[i + 3].equals(tableString[i + 6])) { + return tableString[i]; + } + } + + if (!tableString[0].equals("") && tableString[0].equals(tableString[4]) && tableString[4].equals(tableString[8])) { + return tableString[0]; + } + + if (!tableString[2].equals("") && tableString[2].equals(tableString[4]) && tableString[4].equals(tableString[6])) { + + return tableString[2]; + } + if (existEmpty) { + return ""; + } else { + return "draw"; + } + } + + /** + * /** + * Check the state of the game + * + * @param tableString state board, "" - if cell is empty + * @param isPreliminaryCheck, true - if real state, false - if we want check move + * @return "X" - if X wins , "O" - if O wins, "" - if state is unknown, else - draw + */ + protected String check(String[] tableString, boolean isPreliminaryCheck) { + String result = check(tableString); + if (!isPreliminaryCheck && !result.equals("")) { + setResult(result); + } + return result; + } + + /** + * Print result games + */ + private void setResult(String win) { + numberGames++; + CreateElements.setResult(gridPane, statistics, win); + CreateElements.createButtonReplay(gridPane, statistics, this::goBack); + } + + /** + * For return in this activity + */ + protected void goBack(Statistics statistics) { + this.statistics = statistics; + gridPane.getChildren().clear(); + CreateElements.createButtonToMainActivity(gridPane); + menu(); + } + + /** + * Symbol to which the player move + */ + + public String getCompSymb(int player) { + return (numberGames % 2 == player) ? "X" : "O"; + } + + + /** + * Before games we should choose characteristics game + */ + public void menu() { + CreateElements.createButton(gridPane, 50, 200, 200, 70, "Легкий"); + CreateElements.createButton(gridPane, 50, 200, 200, 170, "Сложный"); + } + +} diff --git a/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithComp.java b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithComp.java new file mode 100644 index 0000000..ea5fd07 --- /dev/null +++ b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithComp.java @@ -0,0 +1,125 @@ +package ru.spbau.mit.alyokhina.TicTacToe; + + +import javafx.scene.control.Button; +import javafx.scene.layout.GridPane; + +import java.util.Random; +import java.util.function.Consumer; + +public class GameWithComp extends Game { + /** + * Constructor + * + * @param statistics for collection information + */ + public GameWithComp(GridPane gridPane, Statistics statistics) { + super(gridPane, statistics); + } + + + /** + * Menu. + * Choose level or get statistics + */ + @Override + public void menu() { + CreateElements.createButtonGetStatisticsForOnePlayers(gridPane, statistics, value -> menu()); + Button buttonEasyLevel = CreateElements.createButton(gridPane, 50, 200, 200, 70, "Легкий"); + Button buttonHardLevel = CreateElements.createButton(gridPane, 50, 200, 200, 170, "Сложный"); + buttonEasyLevel.setOnAction(value -> { + gridPane.getChildren().clear(); + CreateElements.createButtonToMainActivity(gridPane); + playGame(this::moveCompEasyLevel); + }); + buttonHardLevel.setOnAction(value -> { + gridPane.getChildren().clear(); + CreateElements.createButtonToMainActivity(gridPane); + playGame(this::moveCompHardLevel); + }); + } + + /** + * Start game + * + * @param moveComp move computer + */ + private void playGame(Consumer moveComp) { + CreateElements.createButtonReplay(gridPane, statistics, this::goBack); + Button[] table = CreateElements.createTableView(gridPane); + if (numberGames % 2 == 0) { + moveComp.accept(table); + } + for (Button button : table) { + button.setOnAction(actionEvent -> { + button.setText(getCompSymb(1)); + button.setDisable(true); + moveComp.accept(table); + }); + } + + + } + + /** Move will be get with help random */ + private void moveCompEasyLevel(Button[] table) { + Random random = new Random(); + if (check(toStringArray(table), false).equals("")) { + while (true) { + int x = random.nextInt(table.length); + if (table[x].getText().equals("")) { + table[x].setText(getCompSymb(0)); + table[x].setDisable(true); + break; + } + } + check(toStringArray(table), false); + } + } + + /** + * Analyze board. + * It's guaranteed if there is a possibility of winning in one step, then this step will be taken + */ + private void moveCompHardLevel(Button[] table) { + int move = goodMove(toStringArray(table), getCompSymb(0), getCompSymb(1)); + if (move == -1) { + moveCompEasyLevel(table); + } else { + table[move].setText(getCompSymb(0)); + table[move].setDisable(true); + check(toStringArray(table), false); + } + } + + /** + * Check, if there is a possibility of winning in one step, then return index of cell + * And interfere enemy win in the next move + * + * @return number cell + */ + public static int goodMove(String[] tableString, String symbolPlayer1, String symbolPlayer2) { + + for (int i = 0; i < tableString.length; i++) { + if (tableString[i].equals("")) { + tableString[i] = symbolPlayer1; + String whoWin = Game.check(tableString); + if (whoWin.equals(symbolPlayer1)) { + return i; + } + tableString[i] = ""; + } + } + for (int i = 0; i < tableString.length; i++) { + if (tableString[i].equals("")) { + tableString[i] = symbolPlayer2; + String whoWin = check(tableString); + if (whoWin.equals(symbolPlayer2)) { + return i; + } + tableString[i] = ""; + } + } + return -1; + } +} diff --git a/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithFriend.java b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithFriend.java new file mode 100644 index 0000000..1e7ae0b --- /dev/null +++ b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithFriend.java @@ -0,0 +1,52 @@ +package ru.spbau.mit.alyokhina.TicTacToe; + +import javafx.scene.control.Button; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; + + +public class GameWithFriend extends Game { + + /** + * Constructor + * + * @param statistics for collection information + */ + public GameWithFriend(GridPane gridPane, Statistics statistics) { + super(gridPane, statistics); + } + + /** Menu. Two categories : play and get statistics */ + @Override + public void menu() { + Button buttonPlay = CreateElements.createButton(gridPane, 50, 200, 200, 110, "Играть"); + buttonPlay.setOnAction(actionEvent -> { + gridPane.getChildren().clear(); + CreateElements.createButtonReplay(this.gridPane, statistics, this::goBack); + playGame(); + }); + CreateElements.createButtonGetStatisticsForTwoPlayers(gridPane, statistics, value -> menu()); + } + + /** Start play */ + private void playGame() { + Button[] table = CreateElements.createTableView(gridPane); + changeMove(table, 0); + } + + /** Change players move */ + private void changeMove(Button[] table, int x) { + String c = getCompSymb(x); + for (Button button : table) { + button.setOnAction(actionEvent -> { + button.setText(c); + button.setDisable(true); + check(toStringArray(table), false); + changeMove(table, 1 - x); + }); + } + + } + + +} diff --git a/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Main.java b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Main.java new file mode 100644 index 0000000..29ddc41 --- /dev/null +++ b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Main.java @@ -0,0 +1,31 @@ +package ru.spbau.mit.alyokhina.TicTacToe; + +import javafx.application.Application; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +public class Main extends Application { + + @Override + public void start(Stage primaryStage) throws Exception { + primaryStage.setTitle("Крестики - Нолики"); + GridPane gridPane = new GridPane(); + gridPane.setAlignment(Pos.CENTER); + CreateElements.createMainActivity(gridPane); + primaryStage.setScene(new Scene(gridPane, 600, 400)); + primaryStage.show(); + + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Statistics.java b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Statistics.java new file mode 100644 index 0000000..7ca02a9 --- /dev/null +++ b/02.TicTacToe/src/main/java/ru/spbau/mit/alyokhina/TicTacToe/Statistics.java @@ -0,0 +1,30 @@ +package ru.spbau.mit.alyokhina.TicTacToe; + +/** Class for collection information about games */ +public class Statistics { + private int countWins = 0; + private int countLose = 0; + private int countDraw = 0; + + public void incWins() { + countWins++; + } + + public void incLose() { + countLose++; + } + public void incDraw() { + countDraw++; + } + public Integer getCountWins() { + return countWins; + } + + public Integer getCountLose() { + return countLose; + } + + public Integer getCountDraw() { + return countDraw; + } +} diff --git a/02.TicTacToe/src/test/java/ru/spbau/mit/alyokhina/TicTacToe/GameTest.java b/02.TicTacToe/src/test/java/ru/spbau/mit/alyokhina/TicTacToe/GameTest.java new file mode 100644 index 0000000..1c99265 --- /dev/null +++ b/02.TicTacToe/src/test/java/ru/spbau/mit/alyokhina/TicTacToe/GameTest.java @@ -0,0 +1,109 @@ +package ru.spbau.mit.alyokhina.TicTacToe; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class GameTest { + @Test + public void checkIfNoResult() { + String board[] = new String[]{ + "", "", "", + "", "", "", + "", "", "" + }; + + assertEquals("", Game.check(board)); + } + + @Test + public void checkIfXWinInDiagonal() { + String board[] = new String[]{ + "X", "O", "O", + "", "X", "", + "", "", "X" + }; + + assertEquals("X", Game.check(board)); + } + + @Test + public void checkIfXWinInVertical() { + String board[] = new String[]{ + "X", "O", "", + "X", "", "O", + "X", "", "" + }; + + assertEquals("X", Game.check(board)); + } + + @Test + public void checkIfXWinInHorizontal() { + String board[] = new String[]{ + "", "O", "", + "X", "X", "X", + "", "O", "" + }; + + assertEquals("X", Game.check(board)); + } + + @Test + public void checkIfOWinInDiagonal() { + String board[] = new String[]{ + "O", "X", "X", + "", "O", "", + "", "", "O" + }; + + assertEquals("O", Game.check(board)); + } + + @Test + public void checkIfOWinInVertical() { + String board[] = new String[]{ + "O", "X", "", + "O", "", "X", + "O", "", "" + }; + + assertEquals("O", Game.check(board)); + } + + @Test + public void checkIfOWinInHorizontal() { + String board[] = new String[]{ + "", "X", "", + "O", "O", "O", + "", "X", "" + }; + + assertEquals("O", Game.check(board)); + } + + + @Test + public void checkIfDraw() { + String board[] = new String[]{ + "X", "O", "X", + "O", "X", "X", + "O", "X", "O" + }; + + assertEquals("draw", Game.check(board)); + } + + @Test + public void checkIfExistMove() { + String board[] = new String[]{ + "X", "O", "", + "O", "X", "X", + "O", "X", "O" + }; + + assertEquals("", Game.check(board)); + } + + +} \ No newline at end of file diff --git a/02.TicTacToe/src/test/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithCompTest.java b/02.TicTacToe/src/test/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithCompTest.java new file mode 100644 index 0000000..f00d64a --- /dev/null +++ b/02.TicTacToe/src/test/java/ru/spbau/mit/alyokhina/TicTacToe/GameWithCompTest.java @@ -0,0 +1,51 @@ +package ru.spbau.mit.alyokhina.TicTacToe; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class GameWithCompTest { + @Test + public void goodMoveEmptyBoard() { + String board[] = new String[]{ + "", "", "", + "", "", "", + "", "", "" + }; + + assertEquals(-1, GameWithComp.goodMove(board, "X", "O")); + } + + @Test + public void goodMoveifCanBeMoveOnDiagonal() { + String board[] = new String[]{ + "X", "", "", + "", "", "", + "O", "", "X" + }; + + assertEquals(4, GameWithComp.goodMove(board, "X", "O")); + } + + @Test + public void goodMoveIfCanBeMoveOnVertical() { + String board[] = new String[]{ + "X", "X", "", + "", "", "", + "O", "", "O" + }; + + assertEquals(2, GameWithComp.goodMove(board, "X", "O")); + } + + @Test + public void goodMoveIfCanBeMoveOnHorizontal() { + String board[] = new String[]{ + "X", "", "", + "", "X", "X", + "O", "", "O" + }; + + assertEquals(3, GameWithComp.goodMove(board, "X", "O")); + } +} \ No newline at end of file diff --git a/buildscript.sh b/buildscript.sh new file mode 100755 index 0000000..1cc07e7 --- /dev/null +++ b/buildscript.sh @@ -0,0 +1,9 @@ +#!/bin/bash +files=$(find . -maxdepth 1 -type d | grep "./0.*") +for file in $files +do + cd $file + mvn test -B + cd ../ +done +