틱택토 게임

틱택토 게임 제작기 2

잡코신 2024. 5. 30. 18:00
728x90
반응형

지난 시간

지난번엔 간단한 코딩으로 틱택토 게임을 만들었다.

이번엔 지난번 코드를 더 발전 시켜서 GUI를 적용시키고 프로그램으로 만든다.

 

틱택토 게임 만들기

프로그래밍 언어인 자바을 이용해서 틱택토 게임 룰에 기반한 틱택토 게임을 만든다.

오늘은 GUI만 적용할 것이기 때문에 저번에 만들어놓은 TicTacToe 클래스는 유지하고

Main 부분을 바꿀 것이다.

public class Main extends JFrame {

    @Serial
    private static final long serialVersionUID = 1L;

    // 플레이어 표시 상수
    private static final String PLAYER_O = "O";
    private static final String PLAYER_X = "X";

    // UI요소
    private final JLabel title = new JLabel("TicTacToe | ");
    private final JLabel displayCurrentPlayer = new JLabel("Player 1");
    private final JLabel scoreLabel = new JLabel(" | " + score1 + " : " + score2);
    private final JButton startNewGame = new JButton("새 게임 시작");

    // 패널
    private final JPanel titleBar = new JPanel();
    private final JPanel nineRoom = new JPanel();

    // 게임 상태 변수
    private static final int START_PLAYER = 1;
    private boolean isGameEnd = false;
    private int score1 = 0;
    private int score2 = 0;
    
    private final TicTacToe ticTacToe = new TicTacToe(START_PLAYER);
    
}

GUI의 틀이 되어줄 JFrame을 상속하고 사용할 변수들을 선언해준다.

    public Main() {
        super("TicTacToe");
        this.setSize(400, 300);
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        drawWindow();
        this.setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(Main::new);
    }

생성자를 만들고 main에서 호출하여 프로그램을 시작하고 GUI를 생성하는 함수로 만든다.

SwingUtilities.invokeLater를 사용하여 안전하게 GUI를 초기화한다.

    // 창 구성 요소 설정
    private void drawWindow() {
    	// 타이틀 바 구성
        titleBar.setLayout(new FlowLayout());
        titleBar.add(title);
        titleBar.add(displayCurrentPlayer);
        titleBar.add(scoreLabel);
        titleBar.add(startNewGame);

	// 현재 플레이어 표시 설정
        displayCurrentPlayer.setText("Player " + START_PLAYER);
        add(titleBar, BorderLayout.NORTH);

	// 3x3 버튼 그리드 설정
        nineRoom.setLayout(new GridLayout(3, 3));
        for (int i = 0; i < 9; i++) {
            JButton tempButton = new JButton("");
            tempButton.setFont(new Font("Impact", Font.PLAIN, 22));
            tempButton.addActionListener(e -> handleButtonPress(tempButton));
            nineRoom.add(tempButton);
        }

        add(nineRoom, BorderLayout.CENTER);

        // 새 게임 시작 버튼 이벤트 설정
        startNewGame.addActionListener(e -> resetGame());
    }

Main에서 호출했던 창을 구성하는 모든 요소들을 설정하는 drawWindow 함수를 만든다.

각 버튼에 대한 액션 리스너도 이 메소드에 추가한다.

    private void handleButtonPress(JButton tempButton) {
        if (isGameEnd) return; // 게임이 종료되었는지 확인

	 // 이미 눌린 버튼인지 확인
        if (!tempButton.getText().isEmpty()) {
            JOptionPane.showMessageDialog(nineRoom, "이미 둔 곳입니다.");
            return;
        }

        // 현재 플레이어에 따라 버튼 텍스트 설정 및 플레이어 전환
        if (ticTacToe.getCurrentPlayer() == 1) {
            tempButton.setText(PLAYER_O);
            displayCurrentPlayer.setText("Player " + 2);
        } else {
            tempButton.setText(PLAYER_X);
            displayCurrentPlayer.setText("Player " + 1);
        }
        ticTacToe.changeTurn();

        // 현재 보드 상태를 배열로 변환
        int[][] ticArr = new int[3][3];
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                String pl = ((JButton) nineRoom.getComponent(j + i * 3)).getText();
                ticArr[i][j] = pl.equals(PLAYER_O) ? 1 : pl.equals(PLAYER_X) ? 2 : 0;
            }
        }

        // 게임 결과 처리
        int result = ticTacToe.inputCurrentStage(ticArr);
        handleGameResult(result);
    }

버튼이 눌렸을 때의 동작을 정의하는 handleButtonPress 함수이다.

게임 종료 여부, 이미 눌린 버튼 여부 등을 확인하고, 현재 플레이어에 따라 버튼 텍스트를 설정한 후,

게임 상태를 업데이트한다.

 

    // 게임 결과 반환
    private void handleGameResult(int result) {
    	// 특정 플레이어 승리 또는 무승부 처리
        if (result == 1 || result == 2) {
            JOptionPane.showMessageDialog(nineRoom, "플레이어 " + result + "의 승리입니다.");
            if (result == 1) score1++;
            else score2++;

            scoreLabel.setText(" | " + score1 + " : " + score2);
            isGameEnd = true;
        } else if (result == 99) {
            JOptionPane.showMessageDialog(nineRoom, "비겼습니다.");
            isGameEnd = true;
        }
    }

handleGameResult 메소드는 게임 결과를 처리하여 승리 또는 무승부를 알리고, 점수를 업데이트다.

 

    // 게임 상태 초기화
    private void resetGame() {
        ticTacToe.resetGame(START_PLAYER);
        isGameEnd = false;
        for (Component component : nineRoom.getComponents()) ((JButton) component).setText("");
        displayCurrentPlayer.setText("Player " + START_PLAYER);
    }

reset 메소드는 새로운 게임을 시작할 때 게임 상태를 초기화한다.

 

틱택토(ver.2) 플레이해보기

TicTacToe.java

public class TicTacToe {

    private int currentPlayer;
    private boolean isGameOver = false;
    private int[][] endStage;
    private int currentTurn = 1;

    public TicTacToe(int currentPlayer) {
        this.currentPlayer = currentPlayer;
    }

    public int getCurrentPlayer() {
        return currentPlayer;
    }

    public void setCurrentPlayer(int currentPlayer) {
        this.currentPlayer = currentPlayer;
    }

    public int[][] getEndStage() {
        return endStage;
    }

    public void resetGame(int currentPlayer) {
        this.isGameOver = false;
        this.currentPlayer = currentPlayer;
        this.endStage = null;
        this.currentTurn = 1;
    }

    public void changeTurn() {
        currentPlayer = (currentPlayer == 1) ? 2 : 1;
    }

    private boolean isPlayerWin(int player, String rowFrag, String colFrag, String diagFrag1, String diagFrag2) {
        String p = String.valueOf(player);
        boolean result = false;
        String[] arr = {rowFrag, colFrag, diagFrag1, diagFrag2};
        for (String s : arr) {
            result = !s.contains("0") && s.equals(p + p + p);
            if (result) return result;
        }
        return result;
    }

    public int inputCurrentStage(int[][] currentStage) {
        // 게임이 끝났다면 더 이상 진행하는 의미가 없으므로 판단 중단
        if(isGameOver) return -99;

        for(int i = 0; i < currentStage.length; i++) {
            StringBuilder rowStr = new StringBuilder();
            StringBuilder colStr = new StringBuilder();
            StringBuilder diagStr1 = new StringBuilder();
            StringBuilder diagStr2 = new StringBuilder();
            for(int j = 0; j < currentStage[i].length; j++) {
                rowStr.append(currentStage[i][j]);
                colStr.append(currentStage[j][i]);
            }
            for(int j = 0; j < currentStage.length; j++) {
                diagStr1.append(currentStage[j][j]);
                diagStr2.append(currentStage[j][2 - j]);
            }

            // 가로 판단
            if(isPlayerWin(2, rowStr.toString(), colStr.toString(), diagStr1.toString(), diagStr2.toString())) {
                isGameOver = true;
                endStage = currentStage;
                return 2;
            } else if(isPlayerWin(1, rowStr.toString(), colStr.toString(), diagStr1.toString(), diagStr2.toString())) {
                isGameOver = true;
                endStage = currentStage;
                return 1;
            } else if(currentTurn == 9) {
                return 99;
            }
        }
        currentTurn++;
        return 0;
    }
}

Main.java

import javax.swing.*;
import java.awt.*;
import java.io.Serial;

public class Main extends JFrame {

    @Serial
    private static final long serialVersionUID = 1L;

    private static final String PLAYER_O = "O";
    private static final String PLAYER_X = "X";

    private final JLabel title = new JLabel("TicTacToe | ");
    private final JLabel displayCurrentPlayer = new JLabel("Player 1");
    private int score1 = 0;
    private int score2 = 0;
    private final JLabel scoreLabel = new JLabel(" | " + score1 + " : " + score2);
    private final JButton startNewGame = new JButton("새 게임 시작");

    private final JPanel titleBar = new JPanel();
    private final JPanel nineRoom = new JPanel();

    private static final int START_PLAYER = 1;
    private boolean isGameEnd = false;
    private final TicTacToe ticTacToe = new TicTacToe(START_PLAYER);

    public Main() {
        super("TicTacToe");
        this.setSize(400, 300);
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        drawWindow();
        this.setVisible(true);
    }

    private void drawWindow() {
        titleBar.setLayout(new FlowLayout());
        titleBar.add(title);
        titleBar.add(displayCurrentPlayer);
        titleBar.add(scoreLabel);
        titleBar.add(startNewGame);

        displayCurrentPlayer.setText("Player " + START_PLAYER);
        add(titleBar, BorderLayout.NORTH);

        nineRoom.setLayout(new GridLayout(3, 3));
        for (int i = 0; i < 9; i++) {
            JButton tempButton = new JButton("");
            tempButton.setFont(new Font("Impact", Font.PLAIN, 22));
            tempButton.addActionListener(e -> handleButtonPress(tempButton));
            nineRoom.add(tempButton);
        }

        add(nineRoom, BorderLayout.CENTER);

        startNewGame.addActionListener(e -> resetGame());
    }

    private void handleButtonPress(JButton tempButton) {
        if (isGameEnd) return;

        if (!tempButton.getText().isEmpty()) {
            JOptionPane.showMessageDialog(nineRoom, "이미 둔 곳입니다.");
            return;
        }

        if (ticTacToe.getCurrentPlayer() == 1) {
            tempButton.setText(PLAYER_O);
            displayCurrentPlayer.setText("Player " + 2);
        } else {
            tempButton.setText(PLAYER_X);
            displayCurrentPlayer.setText("Player " + 1);
        }
        ticTacToe.changeTurn();

        int[][] ticArr = new int[3][3];
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                String pl = ((JButton) nineRoom.getComponent(j + i * 3)).getText();
                ticArr[i][j] = pl.equals(PLAYER_O) ? 1 : pl.equals(PLAYER_X) ? 2 : 0;
            }
        }

        int result = ticTacToe.inputCurrentStage(ticArr);
        handleGameResult(result);
    }

    private void handleGameResult(int result) {
        if (result == 1 || result == 2) {
            JOptionPane.showMessageDialog(nineRoom, "플레이어 " + result + "의 승리입니다.");
            if (result == 1) score1++;
            else score2++;

            scoreLabel.setText(" | " + score1 + " : " + score2);
            isGameEnd = true;
        } else if (result == 99) {
            JOptionPane.showMessageDialog(nineRoom, "비겼습니다.");
            isGameEnd = true;
        }
    }

    private void resetGame() {
        ticTacToe.resetGame(START_PLAYER);
        isGameEnd = false;
        for (Component component : nineRoom.getComponents()) ((JButton) component).setText("");
        displayCurrentPlayer.setText("Player " + START_PLAYER);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(Main::new);
    }
}

이제 코드를 최종 완성 했으니 실행시켜보자.

게임 시작 화면
게임 결과 화면

잘 실행되는 것을 확인 할 수 있었다.

Next

다음엔 더 편한 기능을 추가하고 게임답게 기능을 추가할 것이다.

728x90
반응형

'틱택토 게임' 카테고리의 다른 글

틱택토 게임 제작기 1  (0) 2024.04.11