시작하며
우선, 해당 블로그의 목적은 객체 지향 프로그래밍 언어인 Java를 배웠던 프로그램 설계 방법론 강의의 마지막 과제를 리뷰하고,
강의를 들으면서 배우고 깨달은 "객체 지향 프로그래밍"에 대해서 정리하고자 블로그를 작성한다.
프로그램 설계 방법론 마지막 과제를 완수하면서, "객체 지향 프로그래밍"에 대해서 완전히 정립하는 경험을 하게되었고,
객체 지향 개발 전략이 무엇인지 깨닫게 해준 뜻깊은 마지막 과제였다.
비록, 이를 깨닫는 과정들은 험난하고, 힘든 여정일지라도 현재 "객체 지향 프로그래밍"에 대해서 깨달은 순간은 뿌듯하고 즐겁다.
마지막 과제를 하면서 "객체 지향 프로그래밍" 개발 전략에 대해서 완전히 정립하는 계기가 되었기에 이를 잊어버리지 않도록 블로그를 작성한다 ㅎㅎ..
그럼, 내가 마지막 과제를 하면서 "객체 지향 프로그래밍"에 대해서 깨달은 바를 차근차근 정리하도록 하겠다.
스도쿠 Java GUI Application 기획
# 기획된 사항을 토대로 GUI Desing 구성
스도쿠 Java GUI Application 설계 - MVC 아키텍처
해당 아키텍처는 위에서 기획한 스도쿠 GUI 기획과
스도쿠 GUI 디자인을 참고하여
MVC 아키텍처 설계를 진행했다.
# MVC 아키텍처를 설계하는 방법
스도쿠 GUI 디자인에서 사용자 입장으로 스도쿠 GUI를 직접 다뤄보는 듯이 상상하며,
스도쿠 GUI의 MVC 아키텍처를 설계했다.
이때, 아키텍처에 필요한 요소들은 GUI 개발 전략을 이해해야지만, 구성할 수 있으며
JAVA GUI 개발 경험이 부족했던 난, 프설방 강의에서 이전에 구현한 슬라이드 퍼즐 GUI 개발을 참고해서
스도쿠 GUI에서 필요한 MVC 아키텍처를 구성했다.
# MVC 아키텍처를 혼자서 설계해보고 느낀점
놀랍게도, 초기에 구성하고 들어간 MVC 아키텍처를 거의 그대로 코드구현에 사용되었고,
추가되는 요소는 없었고, 삭제되는 요소인 "스도쿠 보드판 조각 생성 Model 클래스"만 존재했다.
비록, MVC 아키텍처를 구성하는데 시간이 많이 걸렸지만, 차근차근 아키텍처를 설계한 탓인지
안정적인 MVC 아키텍처를 완성시킬 수 있었다.
그리고, 이번이 처음으로 MVC 아키텍처를 혼자서 처음부터 끝까지 설계해본 경험이고, 다른 개발에서도 아키텍처를 혼자서 처음부터 끝까지 구성해보지 않았다.
그렇기에, 이번 경험은 MVC 아키텍처를 혼자서 설계해보고, 개발까지 완료한 뜻깊은 개발 경험으로 개발 프로젝트 설계 능력을 향상시켜주는 뜻깊은 경험이 되었다.
그럼, 이렇게 뜻깊은 경험이 되었던 MVC 아키텍처를 토대로 Java로 구현한 사항을 살펴보며,
"객체 지향 프로그래밍" 개발 전략에 대해서 깨달은 사항을 정리하도록 하겠다.
스도쿠 Java GUI Application 코드 구현
우선, 시간이 남을 때, "객체 지향 프로그래밍" 개발 전략에 대해서 깨달은 사항을 녹여내도록 하겠다.
지금은, 구현 완료한 코드를 남겨두고, 시간이 남을 때 안정적으로 정리하도록 하겠다.
SelcetFrame 클래스 - View Class
import java.awt.Container;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class SelectFrame extends JFrame {
// SelectFrame 생성 메소드
public SelectFrame() {
// SelectFrame의 Container 지정 | 최상단 Layout 지정
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
JPanel p1 = new JPanel(new FlowLayout());
p1.add(new SelectEasyButton(this));
p1.add(new SelectMidiumButton(this));
p1.add(new SelectHardButton(this));
// 자식 Layout 추가
cp.add(p1);
setTitle("Slide Puzzle");
setSize(250,80);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
SelectEasyButton 클래스 - Button Class
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
public class SelectEasyButton extends JButton implements ActionListener {
// 난이도 선택 GUI
private SelectFrame frame;
public SelectEasyButton(SelectFrame f) {
super("Easy");
frame = f;
// 버튼이 눌렸을 때 해당 메소드 재실행 - GUI 초기화
/*
* this는 해당 클래스를 의미한다.
*/
addActionListener(this);
}
// ActionListener 를 처리하기 위한 메소드
public void actionPerformed(ActionEvent e) {
int hole_count = 36;
String level = "Easy Mode";
// 스도쿠 보드게 쉬움으로 GUI 동작
new SudokuBoardFrame(new SudokuBoard(hole_count), level);
}
}
SelectMidiumButton 클래스 - Button Class
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
public class SelectMidiumButton extends JButton implements ActionListener {
// 난이도 선택 GUI
private SelectFrame frame;
// 생성 메소드
public SelectMidiumButton(SelectFrame f) {
super("Midium");
frame = f;
// 버튼이 눌렸을 때 해당 메소드 재실행 - GUI 초기화
/*
* this는 해당 클래스를 의미한다.
*/
addActionListener(this);
}
// ActionListener 를 처리하기 위한 메소드
public void actionPerformed(ActionEvent e) {
int hole_count = 45;
String level = "Midium Mode";
// 스도쿠 보드게 쉬움으로 GUI 동작
new SudokuBoardFrame(new SudokuBoard(hole_count), level);
}
}
SelectHardButton 클래스 - Button Class
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
public class SelectHardButton extends JButton implements ActionListener {
// 난이도 선택 GUI
private SelectFrame frame;
// 생성 메소드
public SelectHardButton(SelectFrame f) {
super("Hard");
frame = f;
// 버튼이 눌렸을 때 해당 메소드 재실행 - GUI 초기화
/*
* this는 해당 클래스를 의미한다.
*/
addActionListener(this);
}
// ActionListener 를 처리하기 위한 메소드
public void actionPerformed(ActionEvent e) {
int hole_count = 54;
String level = "Hard Mode";
// 스도쿠 보드게 쉬움으로 GUI 동작
new SudokuBoardFrame(new SudokuBoard(hole_count), level);
}
}
SudokuBoardFrame 클래스 - View Class
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class SudokuBoardFrame extends JFrame {
// 스도쿠 게임 보드판
private SudokuBoard board;
private BoardButton[][] gui_board_buttons;
private BoardAnswerButton[] gui_boardPiece_buttons;
// 버튼 클릭 이벤트 순서 관리
public int first;
public int board_row;
public int board_col;
// 생성 메소드
public SudokuBoardFrame(SudokuBoard b, String level) {
board = b;
String Sudoku_level = level;
// 스도쿠 게임판 9X9로 버튼으로 제작
gui_board_buttons = new BoardButton[9][9];
gui_boardPiece_buttons = new BoardAnswerButton[9];
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
/*
* To DO 1. 스도쿠 게임 난이도 텍스트 출력
*/
JPanel p1_title = new JPanel(new FlowLayout());
p1_title.add(new JLabel(Sudoku_level));
cp.add(p1_title, BorderLayout.NORTH);
// 스도쿠 게임판 첫 스타트를 위해 GUI에 대해서 게임판 셋팅
/*
* To DO 2. 스도쿠 난이도에 따른 게임판 출력 (Button)
*/
JPanel p2_sudokuButton = new JPanel(new GridLayout(9,9));
for(int row = 0; row < 9; row ++)
for (int col = 0; col < 9; col ++) {
gui_board_buttons[row][col] = new BoardButton(board, this, row, col);
p2_sudokuButton.add(gui_board_buttons[row][col]);
}
cp.add(p2_sudokuButton, BorderLayout.CENTER);
/*
* To DO 3. 스도쿠 답안판 출력 (Button)
*/
JPanel p3_AnswerPiece = new JPanel(new GridLayout(1,9));
for(int i = 0; i < 9; i ++) {
gui_boardPiece_buttons[i] = new BoardAnswerButton(board, this, i + 1);
p3_AnswerPiece.add(gui_boardPiece_buttons[i]);
}
cp.add(p3_AnswerPiece, BorderLayout.SOUTH);
update();
setTitle("Sudoku");
setSize(250,250);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 스도쿠 게임보드판 Frame 제작
/*
* To do
* 스도쿠 난이도 텍스트 출력
* 스도쿠 난이도에 따른 게임판 출력 (Button)
* 스도쿠 답안판 출력 (Button)
*/
}
/*
* To DO : 스도쿠 게임판 GUI 업데이트 메소드
*/
public void update() {
for (int row = 0; row < 9; row ++) {
for (int col = 0; col < 9; col ++) {
// 스도쿠 게임판을 조각하나씩 가져옴
int board_piece = board.getPuzzleBoard(row, col);
if(board_piece != 0) {
String n = Integer.toString(board_piece);
gui_board_buttons[row][col].setText(n);
}
else {
gui_board_buttons[row][col].setText("");
}
}
}
}
/*
* To DO : 스도쿠 정답판 GUI 업데이트 메소드
*/
public void answer_update() {
for(int i = 0; i < 9; i ++) {
if(!board.answer_check(i+1)) {
gui_boardPiece_buttons[i].setText("");
gui_boardPiece_buttons[i].setVisible(false);
}
}
}
}
SudokuBoard 클래스 - Model Class
import java.util.Random;
// 스도쿠 보드판 생성 & 관리
public class SudokuBoard {
private int [][] sudokuBoard_solution;
private int [][] sudokuBoard_gameBoard;
private int hole_count;
private boolean on;
// 생성 메소드
public SudokuBoard(int count) {
sudokuBoard_solution = new int [9][9];
sudokuBoard_gameBoard = new int [9][9];
// 스도쿠 보드 생성 [정답 보드 & 스도쿠 게임 보드]
createSolutionBoard();
createPuzzleBoard(count);
// 난이도에 따른 스도쿠 빈칸 갯수
hole_count = count;
// 게임 처음시작
on = true;
}
/*
* Return 메서드
*/
/** 퍼즐 보드 배열을 리턴 한다. (겜 근황 출력)
*
* @return 퍼즐 보드 배열
*/
/*
* 스도쿠 게임 보드판 한조각씩 가져오는 메소드
*/
public int getPuzzleBoard(int row, int col) {
return sudokuBoard_gameBoard[row][col];
}
/** 빈칸의 개수를 리턴 한다. (겜 종료)
*
* @return 빈칸의 개수
*/
public int countHoles() {
return hole_count;
}
private void createSolutionBoard() {
// 1~9 범위의 무작위 시퀀스 {n1,n2,n3,n4,n5,n6,n7,n8,n9}를 만들고,
// 이를 문서에 첨부한 그림 1과 같이 solution 배열에 배치 한다.
// 일반 스도쿠 퍼즐보드 판 생성 - 스도쿠 형성 구조를 구글링 ㅎ 해서 반복구간 알아냄 ㅎㅎ
int [] arr_num = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// 1행 시작
for(int i = 0; i <= 2; i++) {
for(int j = 0; j <=8 ; j++) {
// 1
if (i == 1 && j < 6) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[0][j+3];
}
else if (i == 1 && j >= 6) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[0][j-6];
}
// 2
else if (i == 2 && j < 3) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[0][j+6];
}
else if (i == 2 && j >= 3) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[0][j-3];
}
else {
sudokuBoard_solution[i][j] = arr_num[j];
}
}
}
int [] arr_num_2 = {2, 3, 1, 5, 6, 4, 8, 9, 7};
// 3행 시작
for(int i = 3; i <= 5; i++) {
for(int j = 0; j <=8 ; j++) {
// 1
if (i == 4 && j < 6) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[3][j+3];
}
else if (i == 4 && j >= 6) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[3][j-6];
}
// 2
else if (i == 5 && j < 3) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[3][j+6];
}
else if (i == 5 && j >= 3) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[3][j-3];
}
else {
sudokuBoard_solution[i][j] = arr_num_2[j];
}
}
}
int [] arr_num_3 = {3, 1, 2, 6, 4, 5, 9, 7, 8};
// 4행 시
for(int i = 6; i <= 8; i++) {
for(int j = 0; j <=8 ; j++) {
// 1
if (i == 7 && j < 6) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[6][j+3];
}
else if (i == 7 && j >= 6) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[6][j-6];
}
// 2
else if (i == 8 && j < 3) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[6][j+6];
}
else if (i == 8 && j >= 3) {
sudokuBoard_solution[i][j] = sudokuBoard_solution[6][j-3];
}
else {
sudokuBoard_solution[i][j] = arr_num_3[j];
}
}
}
// 문서에 첨부한 그림 2와 같이 가로줄 바꾸기와 세로줄 바꾸기를 무작위로 한다.
// 무작위로 줄 바꾸기를 한다는 말은 바꿀지 말지를 무작위로 결정한다는 의미이다.
// 가로줄 바꾸기
shuffleRibbons();
// 세로줄 바꾸기
transpose();
shuffleRibbons();
transpose();
// 테스트용 메소드
// showBoard(solution);
// 스도쿠 게임판 테스트용 메소드
// showBoard(puzzle_board);
}
/** 0~n-1 범위의 정수 수열을 무작위로 섞은 배열을 리턴 한다.
*
* @param n - 수열의 길이
* @return 0~n-1 범위의 정수를 무작위로 섞어 만든 배열
*/
private int[] generateRandomPermutation(int n) {
Random random = new Random();
int[] permutation = new int[n];
for (int i = 0; i < n; i++) {
int d = random.nextInt(i+1);
permutation[i] = permutation[d];
permutation[d] = i;
}
return permutation;
}
/** 문서에 첨부한 그림 2와 같은 전략으로 solution 배열의 가로줄을 무작위로 섞는다. */
private void shuffleRibbons() {
int[][] shuffled = new int[9][9];
int[] random_index;
for (int i = 0; i < 3; i++) {
random_index = generateRandomPermutation(3);
for (int j = 0; j < 3; j++)
shuffled[i*3+random_index[j]] = sudokuBoard_solution[i*3+j];
}
sudokuBoard_solution = shuffled;
}
/** solution 배열의 행과 열을 바꾼다. */
private void transpose() {
int[][] transposed = new int[9][9];
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
transposed[i][j] = sudokuBoard_solution[j][i];
sudokuBoard_solution = transposed;
}
/** 2차원 배열 b를 콘솔 윈도우에 보여준다. (테스트용 메소드)
*
* @param b - 2차원 배열
*/
public void showBoard(int[][] b) {
System.out.println("스도쿠 보드");
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++)
System.out.print(b[i][j] + " ");
System.out.println();
}
}
/*
* TO DO 2.
*/
// [배점 = 0.5/2.0]
/** solution 배열에서 count 만큼 무작위로 빈칸을 채워 puzzle_board 배열을 만들어 리턴한다.
*
* @param count - 빈칸의 개수
*/
private void createPuzzleBoard(int count) {
// 답지 복제
// 2차원 배열 복제 - 2차원은 다르게 복제해야댐 ㅎㅎ
for(int i=0; i<sudokuBoard_gameBoard.length; i++){
System.arraycopy(sudokuBoard_solution[i], 0, sudokuBoard_gameBoard[i], 0, sudokuBoard_solution[0].length);
}
// solution 보드를 그대로 puzzle_board에 복제한다.
// 무작위로 빈칸을 선정한다. 빈칸은 구별을 위해서 0으로 채운다.
// new Random().nextInt(n) 메소드를 호출하면
// 0~n-1 범위의 정수 중에서 무작위로 하나를 고를 수 있다.
for (int i = 1; i <= count; i ++) {
// nextInt의 범위는 0 ~ 입력값 - 1이다.
int hole_low = new Random().nextInt(9);
int hole_col = new Random().nextInt(9);
System.out.println(hole_low);
if(sudokuBoard_gameBoard[hole_low][hole_col] != 0) {
sudokuBoard_gameBoard[hole_low][hole_col] = 0;
}
else {
i = i - 1;
}
}
}
// 게임 진행 시 스도 보드 업데이트 & 게임 종료 Check
/*
* TO DO 3.
*/
// [배점 0.5/2.0]
/** row번 가로줄, col번 세로줄에 digit을 채울 수 있는지 검사하여,
* 가능하면 채우고 true를 리턴하고, 불가능하면 false를 리턴 한다.
*
* @param digit - 빈칸에 채울 수 (1~9 중 하나)
* @param row - 가로줄 번호
* @param col - 세로줄 번호
* @return 퍼즐 보드 조건에 만족하여 빈칸을 채웠으면 true, 만족하지 않으면 false
*/
public boolean check(int digit, int row, int col) {
// showBoard(sudokuBoard_gameBoard);
// showBoard(sudokuBoard_solution);
if(sudokuBoard_gameBoard[row][col] == 0 && sudokuBoard_solution[row][col] == digit) {
sudokuBoard_gameBoard[row][col] = digit;
hole_count = hole_count - 1;
return true;
}
else {
return false;
}
}
public boolean on() {
return on;
}
public void gameOver() {
on = false;
}
/*
* To DO: 스도쿠 정답판의 숫자에 대해서 유효한 지 Check 메소드
*/
public boolean answer_check(int answer) {
for(int row = 0; row < 9; row ++) {
for (int col = 0; col < 9; col ++) {
if(sudokuBoard_gameBoard[row][col] == 0 && sudokuBoard_solution[row][col] == answer) {
return true;
}
}
}
return false;
}
}
BoardButton 클래스 - Button Class
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
public class BoardButton extends JButton implements ActionListener{
private SudokuBoard board;
private SudokuBoardFrame frame;
private int board_row;
private int board_col;
// 생성 메소드
public BoardButton(SudokuBoard b, SudokuBoardFrame f, int row, int col) {
board = b;
frame = f;
board_row = row;
board_col = col;
addActionListener(this);
}
// 스도쿠 버튼 클릭 시 ActionListener 처리
public void actionPerformed(ActionEvent e) {
// 스도쿠 보드판의 버튼이 클릭 됨
if(board.on()) {
frame.first = 1;
frame.board_row = board_row;
frame.board_col = board_col;
System.out.println("여길 거쳐가니??");
}
else {
System.out.println("겜 종료 수고하셨습니다 ㅎㅎㅎㅎㅎ!!!");
}
if(board.countHoles() == 0) {
board.gameOver();
}
// BoardAnswerButton 이벤트 작용 후의 업데이트
// if(board.on()) {
// frame.update();
// }
}
}
BoardAnswerButton 클래스 - Button Class
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
public class BoardAnswerButton extends JButton implements ActionListener {
private SudokuBoard board;
private SudokuBoardFrame frame;
private int piece_answers;
// 생성 메소드
public BoardAnswerButton(SudokuBoard b, SudokuBoardFrame f, int answers) {
super(Integer.toString(answers));
board = b;
frame = f;
piece_answers = answers;
addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
// 스도쿠 보드판 조각 클릭 이벤트 시 동작
if(board.on()) {
if(frame.first == 1) {
System.out.println("정답 버튼인, 여길 거쳐가니??");
if(board.check(piece_answers, frame.board_row, frame.board_col)) {
frame.update();
frame.answer_update();
frame.first = 0;
}
}
}
}
}
'CS 대학강의' 카테고리의 다른 글
[CS 1-2 | 아카데믹 글쓰기] 초고쓰기 실습 (0) | 2022.11.23 |
---|---|
[CS 1-2 | 아카데믹 글쓰기] 글쓰기의 시작, 초고 쓰는 방법 5주차 (0) | 2022.11.21 |
[CS 1-2 | 프로그램 설계 방법론] 기말 기출해석 (0) | 2022.11.18 |
[CS 1-2 | 프로그램 설계 방법론] 텍스트 파일 처리 - 사례학습 18주차 (0) | 2022.11.18 |
[CS 1-2 | 프로그램 설계 방법론] 텍스트 및 파일처리 17주차 (0) | 2022.11.17 |