본문 바로가기

프로그래밍/미니 프로젝트

Javafx를 활용한 육목(Connect6) 만들기

1. UML diagram

 

 

2학년 자바 수업 마무리 프로젝트로 간단한 육목 게임을 만들어 보았습니다.

 

육목의 규칙은 다음과 같습니다.

 

1) 흑이 먼저 돌 한 개를 착수한다.

2) 백과 흑이 번갈아 가면서 차례로 돌을 두 개씩 착수한다.

3) 같은 색의 돌 6개를 먼저 연속하게 착수하는 쪽이 승리한다.

 

상기된 룰로만 진행되며 오목에 존재하는 3·3 착수 금지 등 복잡한 룰 없습니다. 그럼에도 양 진영 간에 균형 잡힌 승률을 달성하기 때문에 공개된 정보의 공정한 게임을 대표합니다.

 

 

2. Connect6 Class

 

Connect6 클래스를 메인 클래스로 합니다. 중심이 되는 변수는 3개입니다. 어떤 플레이어의 차례인지를 나타내는 char 변수인 whoseTurn가 있습니다. 흑돌의 순번일 때는 ‘B’이고, 백돌의 순번일 때는 ‘W’입니다. 흑돌과 백돌이 각각 두 번씩 착수하므로, 이를 나타내기 위한 int형 변수인 turnOverSwitch 변수가 있습니다. 이 변수는 스위치 역할을 합니다. 0부터 시작하여 돌이 하나씩 착수될 때마다 1씩 증가하고, 2로 나눈 나머지가 1일 때, 흑백의 순번이 바뀝니다. 마지막 변수인 winStack은 int형 변수입니다. 이는 isWon() 메서드에서 연속된 돌을 나타내기 위해 사용됩니다.

 

Cross 클래스에 속한 19*19 개의 cross가 존재합니다. 각각의 cross는 바둑판 모양으로 디자인되었으며, 클릭되었을 때 handleMouseClick() 메서드를 따른 알고리즘으로 작동합니다.

 

• String player(char c)

whoseTurn를 인자로 받아 ‘W’일 때 “White”를, ‘B’일 때 “Black”을 반환합니다.

 

• boolean isWon()

바둑판의 cross를 모두 확인합니다. 가로, 세로, 대각선 모든 방향으로 같은 색의 연속된 돌이 놓여있다면 winStack 변수를 1 증가시킵니다. 그리하여 winStack 변수가 5가 되면 true를 반환합니다. 만약 5를 채우지 못하면 다시 winStack 변수를 0으로 초기화합니다.

 

• boolean isFull()

바둑판의 cross를 모두 확인하여 비어있는 cross가 없을 경우 true를 반환합니다.

 

 

3. Cross Class

 

Cross 클래스는 Connect6의 내부 클래스이며, 바둑돌이 놓이는 교차점을 의미합니다. 놓여있는 돌을 의마하는 char 형 변수인 stone 변수가 있습니다. 기본값은 비어있는 경우를 의미하는 ‘ ’이며, 클릭되어 흑돌이 놓을 경우 ‘B’가 되고, 백돌이 놓일 경우 ‘W’가 됩니다.

 

• void handleMouseClick()

각 cross가 클릭되었을 때 handleMouseClick() 따라 이벤트가 발생합니다. 만약 클릭된 cross가 비어있고, whoseTurn 변수가 ‘ ’이 아닌 경우 클릭된 cross 객체의 stone 변수를 착수한 플레이어에 따라 ‘W’ 또는 ‘B’로 세팅합니다. 그리고 isWon() 메서드를 실행하여 true가 반환되면 착수한 플레이어의 승리로 게임을 종료합니다. isWon() 메서드에서 false가 반환되면 isFull() 메서드를 실행합니다. true가 반환될 경우 게임은 무승부로 종료되며, false가 반환될 경우 게임은 계속 진행됩니다.

 

 

 

GitHub: https://github.com/moordo91/OOP-language/tree/main/Connect6

 

GitHub - moordo91/OOP-language

Contribute to moordo91/OOP-language development by creating an account on GitHub.

github.com

 

import javafx.application.Application;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Ellipse;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import java.io.File;

public class Connect6 extends Application {
	private char whoseTurn = 'B';
	private int turnOverSwitch = 0; // 흑과 백이 각 2번 씩 번갈아가면서 착수하기 위한 스위치
	private int winStack = 0; // 연달아 이어진 돌이 있을 때마다 1스택 씩 쌓여 5개가 쌓이면 육목이 완성되는 원리
	private Cross[][] cross = new Cross[19][19];
	private Label lblStatus = new Label("Black's turn to play");
	
	@Override
	public void start(Stage primaryStage) {
		GridPane pane = new GridPane();
		for (int i = 0; i < 19; i++)
			for (int j = 0; j < 19; j++)
				pane.add(cross[i][j] = new Cross(j, i), j, i);
		
		BorderPane borderPane = new BorderPane();
		borderPane.setCenter(pane);
		borderPane.setBottom(lblStatus);
		
		Scene scene = new Scene(borderPane, 600, 650);
		primaryStage.setTitle("Connect6");
		primaryStage.setScene(scene);
		primaryStage.show();
	}
	
	public class Cross extends Pane {
		private char stone = ' ';
		
		public Cross(int i, int j) {
			if (i == 0 && j == 0)
				setStyle("-fx-background-image: url(/board/left_top.png); -fx-background-position: center");
			else if (i == 18 && j == 0)
				setStyle("-fx-background-image: url(/board/right_top.png); -fx-background-position: center");
			else if (i == 0 && j == 18)
				setStyle("-fx-background-image: url(/board/left_bottom.png); -fx-background-position: center");
			else if (i == 18 && j == 18)
				setStyle("-fx-background-image: url(/board/right_bottom.png); -fx-background-position: center");
			else if (i == 18)
				setStyle("-fx-background-image: url(/board/right.png); -fx-background-position: center");
			else if (i == 0)
				setStyle("-fx-background-image: url(/board/left.png); -fx-background-position: center");
			else if (j == 18)
				setStyle("-fx-background-image: url(/board/bottom.png); -fx-background-position: center");
			else if (j == 0)
				setStyle("-fx-background-image: url(/board/top.png); -fx-background-position: center");
			else if ((i == 3 && j == 3) || (i == 3 && j == 9) || (i == 3 && j == 15) ||
					 (i == 9 && j == 3) || (i == 9 && j == 9) || (i == 9 && j == 15) ||
					 (i == 15 && j == 3) || (i == 15 && j == 9) || (i == 15 && j == 15))
				setStyle("-fx-background-image: url(/board/star.png); -fx-background-position: center");
			else
				setStyle("-fx-background-image: url(/board/cross.png); -fx-background-position: center");
			this.setPrefSize(800, 800);
			this.setOnMouseClicked(e -> handleMouseClick());
		}
		
		public char getStone() {
			return stone;
		}
		
		public void setStone(char c) {
			stone = c;
			
			if (stone == 'B') {
				Ellipse black = new Ellipse(this.getWidth() / 2, this.getHeight() / 2, this.getWidth() / 2 - 10, this.getHeight() / 2 - 10);
				black.centerXProperty().bind(this.widthProperty().divide(2));
				black.centerYProperty().bind(this.heightProperty().divide(2));
				black.radiusXProperty().bind(this.widthProperty().divide(2).subtract(5));
				black.radiusYProperty().bind(this.heightProperty().divide(2).subtract(5));
				black.setStroke(Color.BLACK);
				black.setFill(Color.BLACK);

				getChildren().add(black);
			}
			
			else if (stone == 'W') {
				Ellipse white = new Ellipse(this.getWidth() / 2, this.getHeight() / 2, this.getWidth() / 2 - 10, this.getHeight() / 2 - 10);
				white.centerXProperty().bind(this.widthProperty().divide(2));
				white.centerYProperty().bind(this.heightProperty().divide(2));
				white.radiusXProperty().bind(this.widthProperty().divide(2).subtract(5));
				white.radiusYProperty().bind(this.heightProperty().divide(2).subtract(5));
				white.setStroke(Color.WHITE);
				white.setFill(Color.WHITE);

				getChildren().add(white);
			}
		}
		
		private void handleMouseClick() {
			if (stone == ' ' && whoseTurn != ' ') {
				setStone(whoseTurn);
				
				Media stoneSound = new Media(new File("StoneSound.mp3").toURI().toString());
				new MediaPlayer(stoneSound).play();
				
				turnOverSwitch++;
				
				if (isWon(whoseTurn)) {
					lblStatus.setText(player(whoseTurn) + " won! The game is over.");
					Media endSound = new Media(new File("EndSound.mp3").toURI().toString());
					new MediaPlayer(endSound).play();
					whoseTurn = ' ';
				}
				
				else if (isFull()) {
					lblStatus.setText("Draw! The game is over");
					whoseTurn = ' ';
				}
				
				else {
					if (turnOverSwitch % 2 == 1) {
						whoseTurn = (whoseTurn == 'B') ? 'W' : 'B';
					}
					String turnNum = (turnOverSwitch % 2 == 1) ? "1st" : "2nd";
					lblStatus.setText(player(whoseTurn) + "'s " + turnNum + " turn");
				}
			}
		}
	}
	
	public String player(char c) {
		return (c == 'B') ? "Black" : "White";
	}
	
	public boolean isWon(char stone) {
		// 세로 육목
		for (int i = 0; i < 14; i++)
			for (int j = 0; j < 19; j++) {
				for (int k = 0; k < 5; k++) {
					if (cross[i+k][j].getStone() != ' ' && cross[i+k][j].getStone() == cross[i+k+1][j].getStone()) {
						winStack++;
					}
					else {
						winStack = 0;
						break;
					}
				}
				if (winStack == 5)
					return true;
			}
		
		// 가로 육목
		for (int i = 0; i < 19; i++)
			for (int j = 0; j < 14; j++) {
				for (int k = 0; k < 5; k++) {
					if (cross[i][j+k].getStone() != ' ' && cross[i][j+k].getStone() == cross[i][j+k+1].getStone()) {
						winStack++;
					}
					else {
						winStack = 0;
						break;
					}
				}
				if (winStack == 5)
					return true;
			}
		
		// 우하향 대각선 육목
		for (int i = 0; i < 14; i++)
			for (int j = 0; j < 14; j++) {
				for (int k = 0; k < 5; k++) {
					if (cross[i+k][j+k].getStone() != ' ' && cross[i+k][j+k].getStone() == cross[i+k+1][j+k+1].getStone()) {
						winStack++;
					}
					else {
						winStack = 0;
						break;
					}
				}
				if (winStack == 5)
					return true;
			}
		
		// 좌하향 대각선 육목
		for (int i = 0; i < 14; i++)
			for (int j = 6; j < 19; j++) {
				for (int k = 0; k < 5; k++) {
					if (cross[i+k][j-k].getStone() != ' ' && cross[i+k][j-k].getStone() == cross[i+k+1][j-k-1].getStone()) {
						winStack++;
					}
					else {
						winStack = 0;
						break;
					}
				}
				if (winStack == 5)
					return true;
			}		
		return false;
	}
	
	public boolean isFull() {
		for (int i = 0; i < 19; i++)
			for (int j = 0; j < 19; j++)
				if (cross[i][j].getStone() == ' ')
					return false;
		return true;
	}
	
	public static void main(String[] args) {
		launch(args);
	}
}