├── Danuka( Connect four game)
├── src
│ └── main
│ │ ├── resources
│ │ ├── style
│ │ │ ├── CreatePlayer.css
│ │ │ ├── Style.css
│ │ │ └── Board.css
│ │ ├── asset
│ │ │ ├── error.png
│ │ │ ├── info.png
│ │ │ ├── warning.png
│ │ │ └── connect-four.png
│ │ └── view
│ │ │ ├── CreatePlayer.fxml
│ │ │ └── Board.fxml
│ │ └── java
│ │ └── lk
│ │ └── ijse
│ │ └── dep
│ │ ├── service
│ │ ├── Piece.java
│ │ ├── BoardUI.java
│ │ ├── Player.java
│ │ ├── Board.java
│ │ ├── HumanPlayer.java
│ │ ├── Winner.java
│ │ ├── BoardImpl.java
│ │ └── AiPlayer.java
│ │ ├── util
│ │ ├── Launcher.java
│ │ ├── AppInitializer.java
│ │ └── DEPAlert.java
│ │ └── controller
│ │ ├── CreatePlayerController.java
│ │ └── BoardController.java
└── pom.xml
├── LICENSE
└── README.md
/Danuka( Connect four game)/src/main/resources/style/CreatePlayer.css:
--------------------------------------------------------------------------------
1 | .jfx-text-field{
2 | -fx-font-size: 24px;
3 | -fx-alignment: center;
4 | }
5 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/service/Piece.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.service;
2 |
3 | public enum Piece {
4 | BLUE,GREEN,EMPTY
5 | }
6 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/resources/asset/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danukarangith/Connect-4-Games/HEAD/Danuka( Connect four game)/src/main/resources/asset/error.png
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/resources/asset/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danukarangith/Connect-4-Games/HEAD/Danuka( Connect four game)/src/main/resources/asset/info.png
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/resources/asset/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danukarangith/Connect-4-Games/HEAD/Danuka( Connect four game)/src/main/resources/asset/warning.png
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/resources/asset/connect-four.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danukarangith/Connect-4-Games/HEAD/Danuka( Connect four game)/src/main/resources/asset/connect-four.png
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/service/BoardUI.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.service;
2 |
3 | public interface BoardUI {
4 | void update(int col, boolean isHuman);
5 | void notifyWinner(Winner winner);
6 | }
7 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/util/Launcher.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep;
2 |
3 | public class Launcher {
4 |
5 | public static void main(String[] args) {
6 | lk.ijse.dep.AppInitializer.main(args);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/service/Player.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.service;
2 |
3 | public abstract class Player {
4 | protected Board board;
5 |
6 | public Player(Board board) {
7 | this.board = board;
8 | }
9 |
10 | public abstract void movePiece(int col);
11 | }
12 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/resources/style/Style.css:
--------------------------------------------------------------------------------
1 | *{
2 | -fx-font-family: Ubuntu Serif;
3 | }
4 |
5 | .label{
6 | -fx-font-size: 14px;
7 | }
8 |
9 | .title{
10 | -fx-font-size: 28px;
11 | -fx-font-weight: 700;
12 | }
13 |
14 | .pane{
15 | -fx-background-color: white;
16 | }
17 |
18 | .jfx-button{
19 | -fx-font-size: 20px;
20 | -fx-font-weight: bold;
21 | -fx-background-color: #00b2ff;
22 | -fx-text-fill: #ffffff;
23 | -fx-cursor: hand;
24 | }
25 |
26 |
27 |
28 | .small{
29 | -fx-font-size: 10px;
30 | }
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/service/Board.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.service;
2 |
3 | import java.util.Random;
4 |
5 | public interface Board {
6 | int NUM_OF_ROWS = 5;
7 | int NUM_OF_COLS = 6;
8 | Random RANDOM_GENERATOR = new Random();
9 |
10 | BoardUI getBoardUI();
11 | int findNextAvailableSpot(int col);
12 | boolean isLegalMove(int col);
13 | boolean existLegalMoves();
14 | void updateMove(int col, Piece move);
15 | void updateMove(int col, int row, Piece move);
16 | Winner findWinner();
17 | BoardImpl getBoardImpl();
18 | }
19 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/service/HumanPlayer.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.service;
2 |
3 | public class HumanPlayer extends Player {
4 |
5 | public HumanPlayer(Board board) {
6 | super(board);
7 | }
8 |
9 | @Override
10 | public void movePiece(int col) {
11 | if (board.isLegalMove(col)) {
12 | board.updateMove(col, Piece.BLUE);
13 | board.getBoardUI().update(col, true);
14 |
15 | if (board.findWinner().getWinningPiece() == Piece.EMPTY) {
16 |
17 | if (!board.existLegalMoves()) {
18 | board.getBoardUI().notifyWinner(board.findWinner());
19 | }
20 | }
21 | else board.getBoardUI().notifyWinner(board.findWinner());
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/util/AppInitializer.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep;
2 |
3 | import javafx.application.Application;
4 | import javafx.fxml.FXMLLoader;
5 | import javafx.scene.Scene;
6 | import javafx.stage.Stage;
7 |
8 | import java.io.IOException;
9 |
10 | public class AppInitializer extends Application {
11 |
12 | public static void main(String[] args) {
13 | launch(args);
14 | }
15 |
16 | @Override
17 | public void start(Stage primaryStage) throws IOException {
18 | primaryStage.setScene(new Scene(FXMLLoader.load(getClass().getResource("/view/CreatePlayer.fxml"))));
19 | primaryStage.setResizable(false);
20 | primaryStage.setTitle("Connect 4 Game - Create Player");
21 | primaryStage.show();
22 | primaryStage.centerOnScreen();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Danuka Rangith kavinda
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/util/DEPAlert.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.util;
2 |
3 | import javafx.scene.control.Alert;
4 | import javafx.scene.control.ButtonType;
5 | import javafx.scene.image.Image;
6 | import javafx.scene.image.ImageView;
7 |
8 | public class DEPAlert extends Alert {
9 |
10 | public DEPAlert(AlertType alertType, String title, String header, String message, ButtonType... buttonTypes) {
11 | super(alertType, message, buttonTypes);
12 | setTitle(title);
13 | setHeaderText(header);
14 |
15 | String image = null;
16 | switch (alertType){
17 | case ERROR:
18 | image = "/asset/error.png";
19 | break;
20 | case INFORMATION:
21 | image = "/asset/info.png";
22 | break;
23 | case WARNING:
24 | image = "/asset/warning.png";
25 | break;
26 | }
27 |
28 | if (image !=null){
29 | ImageView imgView = new ImageView(new Image(image));
30 | imgView.setFitWidth(32);
31 | imgView.setFitHeight(32);
32 | setGraphic(imgView);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/service/Winner.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.service;
2 |
3 | public class Winner {
4 | private Piece winningPiece;
5 | private int col1;
6 | private int row1;
7 | private int col2;
8 | private int row2;
9 | public Winner(Piece winningPiece){
10 | this.winningPiece = winningPiece;
11 | this.col1 = -1;
12 | this.row1 = -1;
13 | this.col2 = -1;
14 | this.row2 = -1;
15 | }
16 |
17 | public Winner(Piece winningPiece, int col1, int row1, int col2, int row2) {
18 | this.winningPiece = winningPiece;
19 | this.col1 = col1;
20 | this.row1 = row1;
21 | this.col2 = col2;
22 | this.row2 = row2;
23 | }
24 |
25 | public Piece getWinningPiece() {
26 | return winningPiece;
27 | }
28 |
29 | public int getCol1() {
30 | return col1;
31 | }
32 |
33 | public int getRow1() {
34 | return row1;
35 | }
36 |
37 | public int getCol2() {
38 | return col2;
39 | }
40 |
41 | public int getRow2() {
42 | return row2;
43 | }
44 |
45 | public void setWinningPiece(Piece winningPiece) {
46 | this.winningPiece = winningPiece;
47 | }
48 |
49 | public void setCol1(int col1) {
50 | this.col1 = col1;
51 | }
52 |
53 | public void setRow1(int row1) {
54 | this.row1 = row1;
55 | }
56 |
57 | public void setCol2(int col2) {
58 | this.col2 = col2;
59 | }
60 |
61 | public void setRow2(int row2) {
62 | this.row2 = row2;
63 | }
64 |
65 | @Override
66 | public String toString() {
67 | return "Winner{" +
68 | "winningPiece=" + winningPiece +
69 | ", col1=" + col1 +
70 | ", row1=" + row1 +
71 | ", col2=" + col2 +
72 | ", row2=" + row2 +
73 | '}';
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/resources/style/Board.css:
--------------------------------------------------------------------------------
1 | .col{
2 | -fx-border-width: 1 1 2 1;
3 | -fx-border-color: #dcdcdc;
4 | -fx-border-radius: 5px;
5 | -fx-background-radius: 5px;
6 | -fx-border-style: dashed solid solid solid;
7 | }
8 |
9 | .col-first{
10 | -fx-border-width: 1 1 2 2;
11 | }
12 |
13 | .col-last{
14 | -fx-border-width: 1 2 2 1;
15 | }
16 |
17 | .col-ai{
18 | -fx-background-color: #e0ffcc;
19 | -fx-border-width: 2;
20 | -fx-border-color: #a6a6a6;
21 | -fx-border-radius: 5px;
22 | }
23 |
24 | .col-filled:hover{
25 | -fx-background-color: #ffbfbf !important;
26 | -fx-border-color: #ff4f4f !important;
27 | }
28 |
29 | .col-human:hover{
30 | -fx-cursor: hand;
31 | -fx-background-color: #d7f7ff;
32 | -fx-border-width: 2;
33 | -fx-border-color: #a6a6a6;
34 | -fx-border-radius: 5px;
35 | }
36 |
37 | .four{
38 | -fx-font-size: 42px;
39 | }
40 |
41 | #lblStatus{
42 | -fx-font-size: 24px;
43 | -fx-background-color: #f1f1f1;
44 | -fx-font-weight: bold;
45 | -fx-background-radius: 10px;
46 | }
47 |
48 | .circle-ai{
49 | -fx-stroke-width: 0;
50 | -fx-fill: #00f504;
51 | }
52 |
53 | .circle-human{
54 | -fx-stroke-width: 0;
55 | -fx-fill: #1e90ff;
56 | }
57 |
58 | .human{
59 | -fx-text-fill: #1e90ff;
60 | -fx-background-color: #eafffc !important;
61 | }
62 |
63 | .ai{
64 | -fx-text-fill: #00f531;
65 | -fx-background-color: #eaffe4 !important;
66 | }
67 |
68 | .final{
69 | -fx-text-fill: #ff0089;
70 | }
71 |
72 | .winning-rect{
73 | -fx-fill: rgb(255, 184, 0);
74 | -fx-opacity: 0.5;
75 | -fx-stroke-width: 1;
76 | -fx-stroke: #ff0089;
77 | -fx-arc-height: 10px;
78 | -fx-arc-width: 10px;
79 | }
80 |
81 | #pneOver{
82 | -fx-background-color: rgba(100, 98, 98, 0.43);
83 | -fx-background-radius: 10px;
84 | }
85 |
86 | .jfx-button:hover{
87 | -fx-text-fill: black !important;
88 | }
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/resources/view/CreatePlayer.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/controller/CreatePlayerController.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.controller;
2 |
3 | import com.jfoenix.controls.JFXButton;
4 | import com.jfoenix.controls.JFXTextField;
5 | import javafx.application.Platform;
6 | import javafx.event.ActionEvent;
7 | import javafx.event.Event;
8 | import javafx.fxml.FXMLLoader;
9 | import javafx.scene.Scene;
10 | import javafx.scene.control.Alert;
11 | import javafx.scene.input.MouseEvent;
12 | import javafx.scene.shape.CubicCurve;
13 | import javafx.stage.Stage;
14 | import lk.ijse.dep.util.DEPAlert;
15 |
16 | import java.io.IOException;
17 |
18 | public class CreatePlayerController {
19 | public JFXTextField txtName;
20 | public JFXButton btnPlay;
21 | public CubicCurve curve;
22 |
23 | public void btnPlayOnAction(ActionEvent actionEvent) throws IOException {
24 | String name = txtName.getText();
25 | if (name.isBlank()){
26 | new DEPAlert(Alert.AlertType.ERROR, "Error", "Empty Name", "Name can't be empty").show();
27 | txtName.requestFocus();
28 | txtName.selectAll();
29 | return;
30 | }else if (!name.matches("[A-Za-z ]+")){
31 | new DEPAlert(Alert.AlertType.WARNING, "Error", "Invalid Name", "Please enter a valid name").show();
32 | txtName.requestFocus();
33 | txtName.selectAll();
34 | return;
35 | }
36 | Stage stage = new Stage();
37 | FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/view/Board.fxml"));
38 | stage.setScene(new Scene(fxmlLoader.load()));
39 | ((BoardController)(fxmlLoader.getController())).initData(name);
40 | stage.setResizable(false);
41 | stage.setTitle("Connect 4 Game - Player: " + name);
42 | stage.show();
43 | stage.centerOnScreen();
44 | // stage.setOnCloseRequest(Event::consume);
45 | btnPlay.getScene().getWindow().hide();
46 | Platform.runLater(stage::sizeToScene);
47 | }
48 |
49 | public void rootOnMouseExited(MouseEvent mouseEvent) {
50 | curve.setControlX2(451.8468017578125);
51 | curve.setControlY2(-36);
52 | }
53 |
54 | public void rootOnMouseMove(MouseEvent mouseEvent) {
55 | curve.setControlX2(mouseEvent.getX());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Connect Four Game
2 |
3 | This is a Java-based implementation of the classic Connect Four game, with support for both human and AI players. The game uses the Minimax Monte Carlo Tree Search (MCTS) algorithm to control the AI opponent, making it challenging for players.
4 |
5 | ## Features
6 |
7 | - **Two-player mode:** Play against another person.
8 | - **AI mode:** Challenge the AI, which uses MCTS for decision-making.
9 | - **Graphical user interface (GUI):** Visualize the game board and moves.
10 | - **Winner detection:** Automatically identifies the winner or detects a draw.
11 | - **Legal move checking:** Ensures moves are made within the rules.
12 | - **MVC architecture:** Organized code structure following Model-View-Controller design pattern.
13 |
14 |
15 | ### Key Classes
16 |
17 | 1. **`AiPlayer.java`**
18 | - Represents the AI player using MCTS.
19 | - Implements the logic to decide the best column to place the piece.
20 | - Checks for legal moves and updates the game state accordingly.
21 |
22 | 2. **`Board.java`**
23 | - Handles the game board's state and operations.
24 | - Contains methods for checking the legality of moves, updating the board, and detecting winners.
25 |
26 | 3. **`HumanPlayer.java`**
27 | - Represents a human player in the game.
28 |
29 | 4. **`CreatePlayerController.java`**
30 | - Manages the player creation and selection of human vs. AI mode.
31 |
32 | 5. **`Launcher.java`**
33 | - Entry point for launching the application.
34 |
35 | ### How to Play
36 |
37 | 1. **Run the Application**
38 | - Use an IDE like IntelliJ or Eclipse to run the `Launcher.java` file.
39 |
40 | 2. **Select Mode**
41 | - Choose to play against another player or the AI.
42 |
43 | 3. **Make Moves**
44 | - Players take turns dropping pieces in one of the columns.
45 | - The first player to connect four pieces vertically, horizontally, or diagonally wins.
46 |
47 | 4. **Game End**
48 | - If no more legal moves are possible and no winner is found, the game ends in a draw.
49 |
50 | ## How to Build
51 |
52 | 1. **Prerequisites**
53 | - Java Development Kit (JDK) 8 or above.
54 | - An IDE or command-line tools for Java development.
55 |
56 | 2. **Steps**
57 | - Clone the repository.
58 | - Open the project in your favorite Java IDE.
59 | - Build the project to ensure all dependencies are met.
60 |
61 | ## Future Enhancements
62 |
63 | - Add difficulty levels for the AI.
64 | - Implement online multiplayer mode.
65 | - Enhance the UI with animations.
66 |
67 | ## License
68 |
69 | This project is licensed under the MIT License. See the `LICENSE` file for details.
70 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | lk.ijse.dep
8 | connect-four-assignment
9 | 0.1.0
10 |
11 |
12 | 11
13 | 11
14 |
15 |
16 |
17 |
18 | org.openjfx
19 | javafx-fxml
20 | 18.0.2
21 |
22 |
23 |
24 | com.jfoenix
25 | jfoenix
26 | 9.0.1
27 |
28 |
29 |
30 |
31 |
32 |
33 | org.apache.maven.plugins
34 | maven-compiler-plugin
35 | 3.8.1
36 |
37 | 11
38 |
39 |
40 |
41 | org.openjfx
42 | javafx-maven-plugin
43 | 0.0.8
44 |
45 | lk.ijse.dep.AppInitializer
46 |
47 |
48 |
49 | org.apache.maven.plugins
50 | maven-shade-plugin
51 | 3.2.0
52 |
53 |
54 | package
55 |
56 | shade
57 |
58 |
59 | true
60 | project-classifier
61 | shade\${project.artifactId}.jar
62 |
63 |
65 | lk.ijse.dep.Launcher
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/service/BoardImpl.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.service;
2 |
3 | import lk.ijse.dep.controller.BoardController;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | public class BoardImpl implements Board {
9 | private Piece[][] pieces;
10 | private BoardUI boardUI;
11 | public Piece piece = Piece.BLUE;
12 | public int cols;
13 | public BoardImpl(BoardUI boardUI) {
14 | this.boardUI = boardUI;
15 | pieces = new Piece[NUM_OF_COLS][NUM_OF_ROWS];
16 |
17 |
18 | for (int i = 0;i < NUM_OF_COLS;i++) {
19 | for (int j = 0;j < NUM_OF_ROWS;j++){
20 | pieces[i][j] = Piece.EMPTY;
21 | }
22 | }
23 | }
24 |
25 | @Override
26 | public BoardUI getBoardUI() {
27 | return this.boardUI;
28 | }
29 |
30 | @Override
31 | public int findNextAvailableSpot(int col) {
32 |
33 | for (int i = 0;i < NUM_OF_ROWS;i++){
34 | if ( pieces[col][i] == Piece.EMPTY ){
35 | return i;
36 | }
37 | }
38 |
39 | return -1;
40 | }
41 |
42 | @Override
43 | public boolean isLegalMove(int col) {
44 |
45 | return this.findNextAvailableSpot(col) > -1;
46 | }
47 |
48 | @Override
49 | public boolean existLegalMoves() {
50 |
51 | for (int i = 0; i < NUM_OF_COLS; i++) {
52 | if (this.isLegalMove(i)) {
53 | return true;
54 | }
55 | }
56 | return false;
57 | }
58 |
59 | @Override
60 | public void updateMove(int col, Piece move) {
61 |
62 | this.cols=col;
63 | this.piece=move;
64 |
65 |
66 | pieces[col][findNextAvailableSpot(col)] = move;
67 | }
68 |
69 | @Override
70 | public void updateMove(int col, int row, Piece move) {
71 |
72 | pieces[col][row] = move;
73 | }
74 |
75 | @Override
76 | public Winner findWinner() {
77 |
78 |
79 | for (int col = 0; col < NUM_OF_COLS; col++) {
80 | for (int row = 0; row < NUM_OF_ROWS; row++) {
81 | Piece currentPiece = pieces[col][row];
82 |
83 | if (currentPiece != Piece.EMPTY) {
84 |
85 | if (row + 3 < NUM_OF_ROWS &&
86 | currentPiece == pieces[col][row + 1] &&
87 | currentPiece == pieces[col][row + 2] &&
88 | currentPiece == pieces[col][row + 3]) {
89 |
90 | return new Winner(currentPiece, col, row, col, row + 3);
91 | }
92 |
93 |
94 | if (col + 3 < NUM_OF_COLS &&
95 | currentPiece == pieces[col + 1][row] &&
96 | currentPiece == pieces[col + 2][row] &&
97 | currentPiece == pieces[col + 3][row]) {
98 | return new Winner(currentPiece, col, row, col + 3, row);
99 | }
100 | }
101 | }
102 | }
103 |
104 | return new Winner(Piece.EMPTY);
105 | }
106 |
107 |
108 |
109 | public BoardImpl(Piece[][] pieces, BoardUI boardUI){
110 | this.pieces = new Piece[NUM_OF_COLS][NUM_OF_ROWS];
111 |
112 | //copies existing 2D array to newly created array here
113 | for (int i = 0;i < NUM_OF_COLS;i++){
114 | for (int j = 0;j < NUM_OF_ROWS;j++){
115 | this.pieces[i][j] = pieces[i][j];
116 | }
117 | }
118 | this.boardUI = boardUI;
119 | }
120 |
121 | @Override
122 | public BoardImpl getBoardImpl() {
123 | return this;
124 | }
125 |
126 |
127 | public List getAllLegalNextMoves() {
128 | Piece nextPiece = piece == Piece.BLUE? Piece.GREEN : Piece.BLUE;
129 |
130 | List nextMoves = new ArrayList<>();
131 | for (int col = 0; col < NUM_OF_COLS; col++) {
132 | if (findNextAvailableSpot(col) > -1) {
133 | BoardImpl legalMove = new BoardImpl(this.pieces,this.boardUI);
134 | legalMove.updateMove(col, nextPiece);
135 | nextMoves.add(legalMove);
136 | }
137 | }
138 | return nextMoves;
139 | }
140 |
141 | public BoardImpl getRandomLegalNextMove(){
142 | final List legalMoves = getAllLegalNextMoves();
143 | if (legalMoves.isEmpty()) {
144 | return null;
145 | }
146 | final int random;
147 | random = RANDOM_GENERATOR.nextInt(legalMoves.size());
148 | return legalMoves.get(random);
149 | }
150 |
151 | public boolean getStatus(){
152 | if (!existLegalMoves()) {
153 | return false;
154 | }
155 | Winner winner = findWinner();
156 | if (winner.getWinningPiece() != Piece.EMPTY) {
157 | return false;
158 | }
159 | return true;
160 | }
161 | }
162 |
163 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/resources/view/Board.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/controller/BoardController.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.controller;
2 |
3 | import com.jfoenix.controls.JFXButton;
4 | import javafx.animation.KeyFrame;
5 | import javafx.animation.Timeline;
6 | import javafx.animation.TranslateTransition;
7 | import javafx.application.Platform;
8 | import javafx.event.ActionEvent;
9 | import javafx.scene.Group;
10 | import javafx.scene.control.Label;
11 | import javafx.scene.layout.AnchorPane;
12 | import javafx.scene.layout.Pane;
13 | import javafx.scene.layout.VBox;
14 | import javafx.scene.shape.Circle;
15 | import javafx.scene.shape.Rectangle;
16 | import javafx.util.Duration;
17 | import lk.ijse.dep.service.*;
18 |
19 | public class BoardController implements BoardUI {
20 |
21 | private static final int RADIUS = 42;
22 |
23 | public Label lblStatus;
24 | public Group grpCols;
25 | public AnchorPane root;
26 | public Pane pneOver;
27 | public JFXButton btnPlayAgain;
28 |
29 | private String playerName;
30 | private boolean isAiPlaying;
31 | private boolean isGameOver;
32 |
33 | private Player humanPlayer;
34 | private Player aiPlayer;
35 |
36 | private void initializeGame() {
37 | Board newBoard = new BoardImpl(this);
38 | humanPlayer = new HumanPlayer(newBoard);
39 | aiPlayer = new AiPlayer(newBoard);
40 | }
41 |
42 | public void initialize() {
43 | initializeGame();
44 | grpCols.getChildren().stream().map(n -> (VBox) n).forEach(vbox -> vbox.setOnMouseClicked(mouseEvent -> colOnClick(vbox)));
45 | }
46 |
47 | private void colOnClick(VBox col) {
48 | if (!isAiPlaying && !isGameOver) humanPlayer.movePiece(grpCols.getChildren().indexOf(col));
49 | }
50 |
51 | public void initData(String playerName) {
52 | this.playerName = playerName;
53 | }
54 |
55 | @Override
56 | public void update(int col, boolean isHuman) {
57 | if (isGameOver) return;
58 | VBox vCol = (VBox) grpCols.lookup("#col" + col);
59 | if (vCol.getChildren().size() == 5)
60 | throw new RuntimeException("Double check your logic, no space available within the column: " + col);
61 | if (!isHuman) {
62 | vCol.getStyleClass().add("col-ai");
63 | }
64 | Circle circle = new Circle(RADIUS);
65 | circle.getStyleClass().add(isHuman ? "circle-human" : "circle-ai");
66 | vCol.getChildren().add(0, circle);
67 | if (vCol.getChildren().size() == 5) vCol.getStyleClass().add("col-filled");
68 | TranslateTransition tt = new TranslateTransition(Duration.millis(250), circle);
69 | tt.setFromY(-50);
70 | tt.setToY(circle.getLayoutY());
71 | tt.playFromStart();
72 | lblStatus.getStyleClass().clear();
73 | lblStatus.getStyleClass().add(isHuman ? "ai" : "human");
74 | if (isHuman) {
75 | isAiPlaying = true;
76 | grpCols.getChildren().stream().map(n -> (VBox) n).forEach(vbox -> vbox.getStyleClass().remove("col-human"));
77 | KeyFrame delayFrame = new KeyFrame(Duration.millis(300), actionEvent -> {
78 | if (!isGameOver) lblStatus.setText("Wait, AI is playing");
79 | });
80 | KeyFrame keyFrame = new KeyFrame(Duration.seconds(0.5), actionEvent -> {
81 | if (!isGameOver) aiPlayer.movePiece(-1);
82 | });
83 | new Timeline(delayFrame, keyFrame).playFromStart();
84 | } else {
85 | KeyFrame delayFrame = new KeyFrame(Duration.millis(300), actionEvent -> {
86 | grpCols.getChildren().stream().map(n -> (VBox) n).forEach(vbox -> {
87 | vbox.getStyleClass().remove("col-ai");
88 | vbox.getStyleClass().add("col-human");
89 | });
90 | });
91 | new Timeline(delayFrame).playFromStart();
92 | isAiPlaying = false;
93 | lblStatus.setText(playerName + ", it is your turn now!");
94 | }
95 | }
96 |
97 | @Override
98 | public void notifyWinner(Winner winner) {
99 | isGameOver = true;
100 | lblStatus.getStyleClass().clear();
101 | lblStatus.getStyleClass().add("final");
102 | switch (winner.getWinningPiece()) {
103 | case BLUE:
104 | lblStatus.setText(String.format("%s, you have won the game !", playerName));
105 | break;
106 | case GREEN:
107 | lblStatus.setText("Game is over, AI has won the game !");
108 | break;
109 | case EMPTY:
110 | lblStatus.setText("Game is tied !");
111 | }
112 | if (winner.getWinningPiece() != Piece.EMPTY) {
113 | VBox vCol = (VBox) grpCols.lookup("#col" + winner.getCol1());
114 | Rectangle rect = new Rectangle((winner.getCol2() - winner.getCol1() + 1) * vCol.getWidth(),
115 | (winner.getRow2() - winner.getRow1() + 1) * (((RADIUS + 2) * 2)));
116 | rect.setId("rectOverlay");
117 | root.getChildren().add(rect);
118 | rect.setLayoutX(vCol.localToScene(0, 0).getX());
119 | rect.setLayoutY(vCol.localToScene(0, 0).getY() + (4 - winner.getRow2()) * ((RADIUS + 2) * 2));
120 | rect.getStyleClass().add("winning-rect");
121 | }
122 | pneOver.setVisible(true);
123 | pneOver.toFront();
124 | Platform.runLater(btnPlayAgain::requestFocus);
125 | }
126 |
127 | public void btnPlayAgainOnAction(ActionEvent actionEvent) {
128 | initializeGame();
129 | isAiPlaying = false;
130 | isGameOver = false;
131 | pneOver.setVisible(false);
132 | lblStatus.getStyleClass().clear();
133 | lblStatus.setText("LET'S PLAY !");
134 | grpCols.getChildren().stream().map(n -> (VBox) n).forEach(vbox -> {
135 | vbox.getChildren().clear();
136 | vbox.getStyleClass().remove("col-ai");
137 | vbox.getStyleClass().remove("col-filled");
138 | vbox.getStyleClass().add("col-human");
139 | });
140 | root.getChildren().remove(root.lookup("#rectOverlay"));
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/Danuka( Connect four game)/src/main/java/lk/ijse/dep/service/AiPlayer.java:
--------------------------------------------------------------------------------
1 | package lk.ijse.dep.service;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.Comparator;
6 | import java.util.List;
7 |
8 | public class AiPlayer extends Player {
9 |
10 | public AiPlayer(Board board) {
11 | super(board);
12 | }
13 |
14 | @Override
15 | public void movePiece(int col) {
16 |
17 |
18 |
19 | Mcts mcts = new Mcts(board.getBoardImpl());
20 | col = mcts.startMCTS();
21 |
22 |
23 |
24 | if (board.isLegalMove(col)) {
25 | board.updateMove(col, Piece.GREEN);
26 | board.getBoardUI().update(col, false);
27 |
28 | if (board.findWinner().getWinningPiece() == Piece.EMPTY) {
29 |
30 | if (!board.existLegalMoves()) {
31 | board.getBoardUI().notifyWinner(board.findWinner());
32 | }
33 | }
34 | else board.getBoardUI().notifyWinner(board.findWinner());
35 | }
36 | }
37 |
38 | static class Mcts {
39 |
40 | private final BoardImpl board;
41 |
42 | private final Piece AiID=Piece.GREEN;
43 |
44 | private final Piece HumanID=Piece.BLUE;
45 |
46 | public Mcts(BoardImpl board) {
47 | this.board = board;
48 | }
49 |
50 |
51 | public int startMCTS(){
52 | System.out.println("MCTS working.");
53 | int count=0;
54 |
55 |
56 | Node tree= new Node(board);
57 |
58 |
59 | while (count<4000){
60 | count++;
61 |
62 |
63 |
64 | Node promisingNode = selectPromisingNode(tree);
65 |
66 |
67 | Node selected=promisingNode;
68 |
69 | if (selected.board.getStatus()){
70 | selected= expandNodeAndReturnRandom(promisingNode);
71 | }
72 |
73 |
74 | Piece resultPiece=simulateLightPlayout(selected);
75 |
76 |
77 | backPropagation(resultPiece,selected);
78 | }
79 |
80 | Node best= tree.getChildWithMaxScore();
81 |
82 | System.out.println("Best move scored " + best.score + " and was visited " + best.visits + " times");
83 |
84 | return best.board.cols;
85 | }
86 |
87 | private void backPropagation(Piece resultPiece, Node selected) {
88 |
89 | Node node=selected;
90 |
91 | while (node!=null){
92 | node.visits++;
93 |
94 | if (node.board.piece==resultPiece){
95 | node.score++;
96 | }
97 | node = node.parent;
98 | }
99 | }
100 |
101 | private Piece simulateLightPlayout(Node promisingNode) {
102 |
103 | Node node=new Node(promisingNode.board);
104 | node.parent=promisingNode.parent;
105 |
106 | Winner winner=node.board.findWinner();
107 |
108 | if (winner.getWinningPiece()==Piece.BLUE){
109 | node.parent.score=Integer.MIN_VALUE;
110 |
111 | return node.board.findWinner().getWinningPiece();
112 | }
113 |
114 | while (node.board.getStatus()){
115 | BoardImpl nextMove=node.board.getRandomLegalNextMove();
116 | Node child = new Node(nextMove);
117 | child.parent=node;
118 | node.addChild(child);
119 | node=child;
120 |
121 |
122 |
123 | }
124 | return node.board.findWinner().getWinningPiece();
125 | }
126 |
127 | private Node expandNodeAndReturnRandom(Node node) {
128 |
129 | Node result=node;
130 | BoardImpl board= node.board;
131 | List legalMoves= board.getAllLegalNextMoves();
132 |
133 | for (int i = 0; i < legalMoves.size(); i++) {
134 | BoardImpl move=legalMoves.get(i);
135 | Node child =new Node(move);
136 | child.parent=node;
137 | node.addChild(child);
138 |
139 | result=child;
140 | }
141 |
142 |
143 | int random = Board.RANDOM_GENERATOR.nextInt(node.children.size());
144 |
145 | return node.children.get(random);
146 | }
147 |
148 | private Node selectPromisingNode(Node tree) {
149 | Node node=tree;
150 | while (node.children.size()!=0){
151 | node=UCT.findBestNodeWithUCT(node);
152 | }
153 | return node;
154 | }
155 | }
156 | static class Node {
157 |
158 | public BoardImpl board;
159 |
160 | public int visits;
161 |
162 | public int score;
163 |
164 | List children= new ArrayList<>();
165 |
166 | Node parent=null;
167 |
168 | public Node(BoardImpl board) {
169 | this.board = board;
170 | }
171 |
172 | Node getChildWithMaxScore() {
173 | Node result = children.get(0);
174 | for (int i = 1; i < children.size(); i++) {
175 | if (children.get(i).score > result.score) {
176 | result = children.get(i);
177 | }
178 | }
179 | return result;
180 | }
181 |
182 | void addChild(Node node) {
183 | children.add(node);
184 | }
185 | }
186 |
187 | static class UCT {
188 |
189 | public static double uctValue(
190 | int totalVisit, double nodeWinScore, int nodeVisit) {
191 | if (nodeVisit == 0) {
192 | return Integer.MAX_VALUE;
193 | }
194 | return ((double) nodeWinScore / (double) nodeVisit)
195 | + 1.41 * Math.sqrt(Math.log(totalVisit) / (double) nodeVisit);
196 | }
197 |
198 | public static Node findBestNodeWithUCT(Node node) {
199 | int parentVisit = node.visits;
200 | return Collections.max(
201 | node.children,
202 | Comparator.comparing(c -> uctValue(parentVisit,
203 | c.score, c.visits)));
204 | }
205 | }
206 | }
207 |
208 |
--------------------------------------------------------------------------------