본문 바로가기
학교생활!

[231104] 고급 자바프로그래밍 과제: Puzzle

by Daybreak21 2023. 11. 13.

고급자바프로그래밍 실습과제

조건문과 GUI, 저장, 로드 등을 활용한 퍼즐 만들기

  • 20X20pixel 크기의 블록 100개와 총 23개의 버튼을 활용한 퍼즐
  • 다양한 조건문과 이벤트리스너, 파일 관리 등을 잘 활용할 줄 알 아야 가능 

 

완성된 화면

 

 

※구현형태

  • 20*20px 흰색 RGB(255,255,255) 이미지 1개와 20*20px 검정색 RGB (0,0,0) 이미지 1개 만들기
  • 간격은 2pixel 로 10*10 (총 100개) 하얀색 이미지를 배치하기
  • 버튼 10개를 행의 맨 왼쪽에, 버튼 10개를 열의 맨 위쪽에 배치
  • 맨 위에 버튼 3개(초기화, 저장, 불러오기)

※구현조건

  • 행 버튼 1개와 열 버튼 1개를 누르면 그 두 버튼이 만나는 지점 의 색이 바뀜 (흰색-> 검정색, 검정색-> 흰색)
  • 행 버튼 2개 또는 열 버튼 2개를 누르면 에러 메시지 출력
  • 초기화를 할 경우 전부 흰 이미지로 초기화
  • 저장 버튼을 누르면 현재 검/흰 이미지 상태 저장
  • 불러오기 버튼을 누르면 저장해둔 상태가 그대로 보여짐 (파일이 없는 채로 불러오기 할 경우, 파일에 내용이 없는 경우 에러출력)

 


코드전체

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.*;

public class Puzzle {
    private JFrame frame;
    private boolean[][] colorState;  // true: white, false: black
    private JLabel[][] blockLabel;

    public static void main(String[] args) {
        Puzzle p = new Puzzle();
        p.showGUI();
    }

    private void showGUI() {
        frame = new JFrame("10x10 puzzle");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(320, 360);

        JButton resetButton = new JButton("Reset");
        JButton saveButton = new JButton("Save");
        JButton loadButton = new JButton("Load");

        //resetButton 구현
        resetButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for (int i = 0; i < 10; i++)
                    for (int j = 0; j < 10; j++)
                        if (!(colorState[i][j])) setColor(i, j, Color.WHITE); //Black image->White image
            }
        });

        //saveButton 구현
        saveButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    File file = new File("Puzzle.txt");
                    BufferedWriter writer = new BufferedWriter(new FileWriter(file));

                    for (int i = 0; i < 10; i++) {
                        for (int j = 0; j < 10; j++) {
                            writer.write(colorState[i][j] + " "); //Boolean값을 10x10으로 저장 
                        }
                        writer.write('\n');
                    }
                    writer.close();
                    JOptionPane.showMessageDialog(frame, "File saved successfully.", "Success", JOptionPane.INFORMATION_MESSAGE);
                } catch (IOException ex) {
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(frame, "An error occurred while saving the file.", "Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        });

        //loadButton 구현
        loadButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    File file = new File("Puzzle.txt");
                    BufferedReader reader = new BufferedReader(new FileReader(file)); 

                    for (int i = 0; i < 10; i++) {
                        String[] blocks = (reader.readLine()).split(" "); //읽어들인 한 줄을 공백단위로 나눔-> blockLine[0]= false, blockLine[1]= true ...
                        for (int j = 0; j < 10; j++) {
                            setColor(i, j, Boolean.parseBoolean(blocks[j]) ? Color.WHITE : Color.BLACK);
                        }
                    }
                    reader.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(frame, "File is not found or an error occurred.", "Error", JOptionPane.ERROR_MESSAGE);
                } catch (ArrayIndexOutOfBoundsException ex) { //파일 형식이 망가지면 읽어들인 데이터를 index에 알맞게 저장하지 못함 -> ArrayIndexOutOfBoundsException
                    resetButton.doClick(); //망가진 파일의 데이터가 나타난 것을 reset
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(frame, "Please check the file format.", "Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        });

        JPanel TopPanel = new JPanel();
        TopPanel.add(resetButton);
        TopPanel.add(saveButton);
        TopPanel.add(loadButton);

        //11X11 퍼즐판
        JPanel puzzlePanel = new JPanel(new GridLayout(11, 11, 2, 2));

        JButton dummyButton = new JButton(); // (0,0) dummy button
        dummyButton.setPreferredSize(new Dimension(20, 20));
        dummyButton.setEnabled(false);
        puzzlePanel.add(dummyButton);

        JButton[] colButton = new JButton[10];
        JButton[] rowButton = new JButton[10];

        blockLabel = new JLabel[10][10];
        colorState = new boolean[10][10];

        for (int i = 0; i < 10; i++) { //행 버튼
            colButton[i] = new JButton();
            colButton[i].setPreferredSize(new Dimension(20, 20));
            puzzlePanel.add(colButton[i]);

            int colIndex = i;
            colButton[i].addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    switchColor("Col", colIndex);
                }
            });
        }

        for (int i = 0; i < 10; i++) {
            rowButton[i] = new JButton(); //열 버튼
            rowButton[i].setPreferredSize(new Dimension(20, 20));
            puzzlePanel.add(rowButton[i]);

            int rowIndex = i;
            rowButton[i].addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    switchColor("Row", rowIndex);
                }
            });

            for (int j = 0; j < 10; j++) { // 10x10 이미지
                blockLabel[i][j] = new JLabel(createIcon(Color.white));
                colorState[i][j] = true;
                puzzlePanel.add(blockLabel[i][j]);
            }
        }

        frame.add(TopPanel, BorderLayout.NORTH);
        frame.add(puzzlePanel, BorderLayout.CENTER);

        frame.setVisible(true);
    }


    static int row = -1;
    static int col = -1;
    private void switchColor(String matrix, int index) { //-> drawPuzzle
        try {
            if (matrix.equals("Row") && row == -1) row = index;
            else if (matrix.equals("Col") && col == -1) col = index;
            else throw new Exception(); //행 버튼 2개, 열 버튼 2개를 누른 경우
        } catch (Exception e) {
            row = -1; col = -1;
            JOptionPane.showMessageDialog(frame, "행, 열 버튼을 1번씩 눌러주세요", "Error", JOptionPane.ERROR_MESSAGE);
        }

        if (row != -1 && col != -1) { //row와 column index가 정상적으로 하나씩 들어왔을때
            setColor(row, col, colorState[row][col] ? Color.BLACK : Color.WHITE); // colorState가 true(white)->매개변수: Black || false(black)->매개변수: White
            row = -1; col = -1;
        }
    }

    private void setColor(int row, int col, Color color) { // (row, col)의 이미지를 매개변수로 받은 색으로 바꿔주는 함수
        blockLabel[row][col].setIcon(createIcon(color));
        colorState[row][col] = (color == Color.white); // white-> true, black-> false
    }

    private ImageIcon createIcon(Color color) { // Graphics를 이용한 image생성
        BufferedImage image = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();

        g.setColor(color);
        g.fillRect(0, 0, 20, 20);

        return new ImageIcon(image);
    }
}

 

 

 

 

 

  • saveButton구현 
saveButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            File file = new File("Puzzle.txt");
            BufferedWriter writer = new BufferedWriter(new FileWriter(file));

            for (int i = 0; i < 10; i++) {
                for (int j = 0; j < 10; j++) {
                    writer.write(colorState[i][j] + " "); //Boolean값을 10x10으로 저장 
                }
                writer.write('\n');
            }
            writer.close();
            JOptionPane.showMessageDialog(frame, "File saved successfully.", "Success", JOptionPane.INFORMATION_MESSAGE);
        } catch (IOException ex) {
            ex.printStackTrace();
            JOptionPane.showMessageDialog(frame, "An error occurred while saving the file.", "Error", JOptionPane.ERROR_MESSAGE);
        }
    }
});


colorState배열의 Boolean값 true, false를 파일에 저장한다. 

 

 

 

  • loadButton 구현 
loadButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            File file = new File("Puzzle.txt");
            BufferedReader reader = new BufferedReader(new FileReader(file)); 

            for (int i = 0; i < 10; i++) {
                String[] blocks = (reader.readLine()).split(" "); //읽어들인 한 줄을 공백단위로 나눔-> blockLine[0]= false, blockLine[1]= true ...
                for (int j = 0; j < 10; j++) {
                    setColor(i, j, Boolean.parseBoolean(blocks[j]) ? Color.WHITE : Color.BLACK);
                }
            }
            reader.close();
        } catch (IOException ex) {
            ex.printStackTrace();
            JOptionPane.showMessageDialog(frame, "File is not found or an error occurred.", "Error", JOptionPane.ERROR_MESSAGE);
        } catch (ArrayIndexOutOfBoundsException ex) { //파일 형식이 망가지면 읽어들인 데이터를 index에 알맞게 저장하지 못함 -> ArrayIndexOutOfBoundsException
            resetButton.doClick(); //망가진 파일의 데이터가 나타난 것을 reset
            ex.printStackTrace();
            JOptionPane.showMessageDialog(frame, "Please check the file format.", "Error", JOptionPane.ERROR_MESSAGE);
        }
    }
});

 


BufferReader로 읽어들인 String 을 Boolean으로 바꿔서 그에 맞게 컬러를 설정한다. 

만약 파일이 손상된 상태라면 반복문안에서 배열에 저장하는 과정이 제대로 실행되지 못해서 ArrayIndexOutofBoundsException 예외가 발생하게 된다. 그 부분도 예외처리 해주었다. 

 

 

 

 

  • buttonEvent 처리 
static int row = -1;
static int col = -1;

private void switchColor(String matrix, int index) { //-> drawPuzzle
    try {
        if (matrix.equals("Row") && row == -1) row = index;
        else if (matrix.equals("Col") && col == -1) col = index;
        else throw new Exception(); //행 버튼 2개, 열 버튼 2개를 누른 경우
    } catch (Exception e) {
        row = -1; col = -1;
        JOptionPane.showMessageDialog(frame, "행, 열 버튼을 1번씩 눌러주세요", "Error", JOptionPane.ERROR_MESSAGE);
    }

    if (row != -1 && col != -1) { //row와 column index가 정상적으로 하나씩 들어왔을때
        setColor(row, col, colorState[row][col] ? Color.BLACK : Color.WHITE); // colorState가 true(white)->매개변수: Black || false(black)->매개변수: White
        row = -1; col = -1;
    }
}


n열버튼과 n행버튼을 누르면 (n, n)의 이미지의 색을 바꿔주는 요청을 보낸다. 

열버튼과 행버튼을 두번씩 누른 것에 대한 예외를 처리하기 위해 row, col의 변수는 -1로 초기화 된다.  그리고 각각 현재 이벤트 처리를 할려고 하는 열과 행이 비어있을때만 정상적으로 색이 바뀐다. 
row가 다른값으로 저장되있는 상태에서 또 값을 저장하지 못한다. 


 

 

 

 

  • setColor 함수 
private void setColor(int row, int col, Color color) { // (row, col)의 이미지를 매개변수로 받은 색으로 바꿔주는 함수
    blockLabel[row][col].setIcon(createIcon(color));
    colorState[row][col] = (color == Color.white); // white-> true, black-> false
}

 


block의 색과 colorState를 바꿔주는 함수이다. 
처음 퍼즐을 생성할때 말고는 항상 라벨을 바꿔주면서  state배열도 바꿔주어야 하기 때문에 함수로 만들었다. 

 

 

 

 

 

보완, 추가해야 할 점 

  • 버튼 크기, 간격설정이 안됨
  • 파일 입출력 stream으로 boolean값을 String이 아닌 0, 1의 형태로 저장하도록 해야 자원을 절약할 수 있다.