슬라이드 퍼즐게임 - GUI 구현 [Layout & ActionListener 처리]
코드 구현 - 초기 GUI Ver.1
슬라이드 퍼즐 게임의 Model 클래스 {PuzzlePice}
- 퍼즐 조각 데이터를 관리하는 Model 클래스
package slidePuzzle_GUI;
// 퍼즐 조각 Data Class
public class PuzzlePiece {
private int face;
/** Constructor - PuzzlePiece 퍼즐 조각을 만듬
* @param value - 퍼즐 조각 위에 표시되는 값 */
public PuzzlePiece(int value) {
face = value;
}
/** face - 조각의 액면 값을 리턴 */
public int face() {
return face;
}
}
슬라이드 퍼즐 게임의 Model 클래스 {SlidePuzzleBoard}
- 슬라이드 퍼즐 보드판을 관리하는 Model 클래스
public class SlidePuzzleBoard {
private PuzzlePiece[][] board;
// 빈칸의 좌표
private int empty_row;
private int empty_col;
// representation invariant: board[empty_row][empty_col] == null
/** Constructor - SlidePuzzleBoard 초기 퍼즐 보드 설정 - 감소하는 순으로 나열
* */
public SlidePuzzleBoard() {
// 4 x 4 보드 만들기
board = new PuzzlePiece[4][4];
// 퍼즐 조각 1~15를 보드에 역순으로 끼우기
int number = 15;
for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++) {
board[row][col] = new PuzzlePiece(number);
number -= 1;
}
board[3][3] = null;
empty_row = 3;
empty_col = 3;
}
/** getPuzzlePiece - 퍼즐 조각을 리턴
* @param row - 가로줄 인덱스
* @param col - 세로줄 인덱스
* @return 퍼즐 조각 */
public PuzzlePiece getPuzzlePiece(int row, int col) {
return board[row][col];
}
/** 이동이 가능하면, 퍼즐 조각을 빈칸으로 이동
* @param w - 이동하기 원하는 퍼즐 조각의 번호
* @return 이동 성공하면 true를 리턴하고, 이동이 불가능하면 false를 리턴 */
public boolean move(int w) {
int row, col; // w의 위치
// 빈칸에 주변에서 w의 위치를 찾음
if (found(w, empty_row - 1, empty_col)) {
row = empty_row - 1;
col = empty_col;
}
else if (found(w, empty_row + 1, empty_col)) {
row = empty_row + 1;
col = empty_col;
}
else if (found(w, empty_row, empty_col - 1)) {
row = empty_row;
col = empty_col - 1;
}
else if (found(w, empty_row, empty_col + 1)) {
row = empty_row;
col = empty_col + 1;
}
else
return false;
// w를 빈칸에 복사
board[empty_row][empty_col] = board[row][col];
// 빈칸 위치를 새로 설정하고, w를 제거
empty_row = row;
empty_col = col;
board[empty_row][empty_col] = null;
return true;
}
/** found - board[row][col]에 퍼즐 조각 v가 있는지 확인 */
private boolean found(int v, int row, int col) {
if (row >= 0 && row <= 3 && col >= 0 && col <= 3)
return board[row][col].face() == v;
else
return false;
}
}
슬라이드 퍼즐 게임의 Controller 클래스 {PuzzleButton}
- GUI에서 사용자의 버튼클릭 이벤트를 토대로 슬라이드 퍼즐게임을 구현 하도록 통제하는 Controller 클래스
import java.awt.event.*;
import javax.swing.*;
public class PuzzleButton extends JButton implements ActionListener {
private SlidePuzzleBoard board;
private PuzzleFrame frame;
public PuzzleButton(SlidePuzzleBoard b, PuzzleFrame f) {
board = b;
frame = f;
addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
String s = getText();
if (! s.equals("") && board.move(Integer.parseInt(s)))
frame.update();
}
}
슬라이드 퍼즐 게임의 View 클래스 {PuzzleFrame}
- Controller 클래스인 PuzzleButton 으로부터 종속되는 관계이며,
PuzzleButton과 종속되어져 슬라이드 퍼즐보드판이 그려진다.
import java.awt.*;
import javax.swing.*;
public class PuzzleFrame extends JFrame {
private SlidePuzzleBoard board;
private PuzzleButton[][] button_board;
public PuzzleFrame(SlidePuzzleBoard b) {
board = b;
button_board = new PuzzleButton[4][4];
Container cp = getContentPane();
cp.setLayout(new GridLayout(4,4));
for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++) {
button_board[row][col] = new PuzzleButton(board,this);
cp.add(button_board[row][col]);
}
update();
setTitle("Slide Puzzle");
setSize(250,250);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public void update() {
PuzzlePiece pp;
for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++) {
pp = board.getPuzzlePiece(row, col);
if (pp != null)
button_board[row][col].setText(Integer.toString(pp.face()));
else
button_board[row][col].setText("");
}
}
}
슬라이드 퍼즐 게임의 Starter 클래스 {PuzzleStarter}
- Java Application의 실행을 담당하는 Starter 클래스
public class PuzzleStarter {
public static void main(String[] args) {
new PuzzleFrame(new SlidePuzzleBoard());
}
}
코드 구현 - GUI 확장 Ver.2 | Start 버튼 구현, 게임 종료 구현
슬라이드 퍼즐 게임의 Model 클래스 {PuzzlePice}
- 퍼즐 조각 데이터를 관리하는 Model 클래스
package slidePuzzle_GUI;
// 퍼즐 조각 Data Class
public class PuzzlePiece {
private int face;
/** Constructor - PuzzlePiece 퍼즐 조각을 만듬
* @param value - 퍼즐 조각 위에 표시되는 값 */
public PuzzlePiece(int value) {
face = value;
}
/** face - 조각의 액면 값을 리턴 */
public int face() {
return face;
}
}
슬라이드 퍼즐 게임의 Model 클래스 {SlidePuzzleBoard}
- 슬라이드 퍼즐 보드판을 관리하는 Model 클래스 | 게임종료, 슬라이드 보드판 랜덤생성 구현
package slidePuzzle_GUI;
import java.util.*;
public class SlidePuzzleBoard {
private PuzzlePiece[][] board;
// 빈칸의 좌표
private int empty_row;
private int empty_col;
// representation invariant: board[empty_row][empty_col] == null
// 게임의 실행을 알려주는 실행 필드변수 | on
private boolean on = false;
// SlidePuzzleBoard 생성 메소드
public SlidePuzzleBoard() {
// 4 x 4 보드 만들기
board = new PuzzlePiece[4][4];
// 퍼즐 조각 1~15를 보드에 순서대로 끼우기
int number = 1;
for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++) {
if (col != 3 || row != 3) {
board[row][col] = new PuzzlePiece(number);
number += 1;
} else {
board[3][3] = null;
empty_row = 3;
empty_col = 3;
}
}
}
/** getPuzzlePiece - 퍼즐 조각을 리턴
* @param row - 가로줄 인덱스
* @param col - 세로줄 인덱스
* @return 퍼즐 조각 */
public PuzzlePiece getPuzzlePiece(int row, int col) {
return board[row][col];
}
/** on - 게임이 진행중인지 점검하는 함수
* @return 게임이 진행중이면 true, 아니면 false */
public boolean on() {
return on;
}
/** 이동이 가능하면, 퍼즐 조각을 빈칸으로 이동
* @param w - 이동하기 원하는 퍼즐 조각
* @return 이동 성공하면 true를 리턴하고, 이동이 불가능하면 false를 리턴 */
public boolean move(int w) {
int row, col; // w의 위치
// 빈칸에 주변에서 w의 위치를 찾음
if (found(w, empty_row - 1, empty_col)) {
row = empty_row - 1;
col = empty_col;
}
else if (found(w, empty_row + 1, empty_col)) {
row = empty_row + 1;
col = empty_col;
}
else if (found(w, empty_row, empty_col - 1)) {
row = empty_row;
col = empty_col - 1;
}
else if (found(w, empty_row, empty_col + 1)) {
row = empty_row;
col = empty_col + 1;
}
else
return false;
// w를 빈칸에 복사
board[empty_row][empty_col] = board[row][col];
// 빈칸 위치를 새로 설정하고, w를 제거
empty_row = row;
empty_col = col;
board[empty_row][empty_col] = null;
return true;
}
/** found - board[row][col]에 퍼즐 조각 v가 있는지 확인
* @param v - 확인할 수
* @param row - 보드의 가로줄 인덱스
* @param col - 보드의 세로줄 인덱스
* @return 있으면 true, 없으면 false */
private boolean found(int v, int row, int col) {
if (row >= 0 && row <= 3 && col >= 0 && col <= 3)
return board[row][col].face() == v;
else
return false;
}
/** createPuzzleBoard - 퍼즐 게임 초기 보드 생성 */
public void createPuzzleBoard() {
// 0 ~ 14까지의 수를 랜덤으로 배치
int[] numbers = generateRandomPermutation(15);
int i = 0;
for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++) {
if(row != 3 || col != 3) {
board[row][col] = new PuzzlePiece(numbers[i] + 1);
i += 1;
}
else {
board[row][col] = null;
empty_row = 3;
empty_col = 3;
}
}
// 슬라이드 보드판이 생성되면서, 게임 상태를 실행 중으로 할당
on = true;
}
/** generateRandomPermutation - 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;
}
/** gameOver - 퍼즐 게임이 끝났는지를 확인
* @return 목표를 달성했으면 true, 아직 더 진행해야 하면 false
*/
public boolean gameOver() {
if (empty_row != 3 || empty_col != 3) {
return false;
}
else {
int number = 1;
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
// 마지막 영역 4행 4열에 이르면, 게임종료 | for문으로 규칙을 배정해둠.
if (row != 3 || col != 3) {
if(board[row][col].face() != number) {
return false;
}
else {
number += 1;
}
}
}
}
on = false;
return true;
}
}
}
슬라이드 퍼즐 게임의 Controller 클래스 {PuzzleButton}
- GUI에서 사용자의 버튼클릭 이벤트를 토대로 슬라이드 퍼즐게임을 구현 하도록 통제하는 Controller 클래스
package slidePuzzle_GUI;
// Action에 따른 event 처리하는 모듈 모아둠.
import java.awt.event.*;
import javax.swing.JButton;
/*
* 안드로이드의 Button Activity 클래스와 같음.
* XML과 Activity를 하나의 클래스에서 처리
*/
// PuzzleButton 클래스는 Puzzle의 클릭 이벤트를 통제하는 클래스로, 해당 프로젝트에선 Controller를 담당한다.
public class PuzzleButton extends JButton implements ActionListener{
private SlidePuzzleBoard board;
private PuzzleFrame frame;
// 생성 메소드
public PuzzleButton(SlidePuzzleBoard b, PuzzleFrame f) {
board = b;
frame = f;
// 버튼이 눌렸을 때 해당 메소드 재실행 - GUI 초기화
/*
* this는 해당 클래스를 의미한다.
*/
addActionListener(this);
}
// ActionListener 를 처리하기 위한 메소드
public void actionPerformed(ActionEvent e) {
// 게임이 진행 중일 때만, GUI 버튼 구동
if(board.on()) {
// 버튼이 눌려진 퍼즐 조각의 번호를 가져옴.
String click_ButtonPiece = getText();
/*
* Button 클릭 이벤트 예외처리
* 1. 클릭한 Button이 빈칸이 아니다.
* 2. 클릭한 Button이 움직일 수 있다.
*/
if(click_ButtonPiece != "" && board.move(Integer.parseInt(click_ButtonPiece))) {
frame.update();
if(board.gameOver()) {
frame.finish();
}
}
}
}
}
슬라이드 퍼즐 게임의 Controller 클래스 {StartButton}
- StartButton을 추가하여 게임시작 이벤트를 처리하는 Controller 클래스 구현
package slidePuzzle_GUI;
import java.awt.event.*;
import javax.swing.*;
public class StartButton extends JButton implements ActionListener {
private SlidePuzzleBoard board;
private PuzzleFrame frame;
public StartButton(SlidePuzzleBoard b, PuzzleFrame f) {
super("Start");
board = b;
frame = f;
addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
board.createPuzzleBoard();
frame.update();
}
}
슬라이드 퍼즐 게임의 View 클래스 {PuzzleFrame}
- Controller 클래스인 PuzzleButton, StartButton으로부터 종속되어진 관계이며,
PuzzleButton, StartButton과 종속되어져 슬라이드 퍼즐보드판이 그려진다.
package slidePuzzle_GUI;
import java.awt.*;
import javax.swing.*;
// JFrame 상속받아서 사용
/*
* XML와 같은 역할로
* 퍼즐보드의 Layout을 생성하고, 이에 대한 요소를 넣어준다.
*/
public class PuzzleFrame extends JFrame {
private SlidePuzzleBoard board;
private PuzzleButton[][] button_board;
public PuzzleFrame(SlidePuzzleBoard b) {
board = b;
button_board = new PuzzleButton[4][4];
// 요소들이 넣어지는 Layout 영역을 Container라고 지칭함.
/*
* 안드로이드에서 Fragment의 Layout 영역을 Container라고 함.
*/
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
// Start Button 배치
JPanel p1 = new JPanel(new FlowLayout());
p1.add(new StartButton(board, this));
// 슬라이드 퍼즐 보드판 배치
JPanel p2 = new JPanel(new GridLayout(4,4));
for(int row = 0; row < 4; row ++)
for (int col = 0; col < 4; col ++) {
button_board[row][col] = new PuzzleButton(board, this);
p2.add(button_board[row][col]);
}
cp.add(p1,BorderLayout.NORTH);
cp.add(p2,BorderLayout.CENTER);
update();
// Layout을 GridLayout으로 셋팅
// cp.setLayout(new GridLayout(4,4));
// for (int row = 0; row < 4; row++)
// for (int col = 0; col < 4; col++) {
// button_board[row][col] = new PuzzleButton(board, this);
//
// /*
// * GridLayout 특성상 요소를 추가하면
// * 요소들을 옆으로 재배정해줌.
// */
// cp.add(button_board[row][col]);
// }
// 초기 퍼즐보드판 생성 시 퍼즐보드판 업데이트
/*
* 아래의 메소드들은 JFrame의 메소드
* JFrame의 메소드가 해당 클래스에 내장되어 있음 (JFrame 클래스를 상속받았기 때문이다.)
*/
setTitle("Slide Puzzle");
setSize(250,250);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public void update() {
for (int row = 0; row < 4; row ++) {
for (int col = 0; col < 4; col ++) {
// 슬라이드 퍼즐 보드판 Model에서 만들어진 Data를 이용해
// GUI로 표시 - 퍼즐 조각 하나씩 가져옴.
PuzzlePiece pp = board.getPuzzlePiece(row, col);
if(pp != null) {
String n = Integer.toString(pp.face());
button_board[row][col].setText(n);
}
else {
// 퍼즐 조각의 값이 null일 때
button_board[row][col].setText("");
}
}
}
}
public void finish() {
button_board[3][3].setText("Done");
}
}
구현완료 | 슬라이드 퍼즐보드 게임
1. Java Application 첫 실행
2. Start Button 클릭 시 슬라이드 퍼즐보드판 랜덤배치
마치며
해당 부분에 대해선 시간이 남을 때
정리하도록 하겠다.
이 부분은 금요일 프설방 수업이 끝나고, 도교수님께 개별적으로 MVC 아키텍처에 대해서 질문드린 사항이다.
간단히 말하자면, Model 클래스에서 블랙잭의 Card, CardDeck과 같은 클래스도 Model 클래스인지 아니면, 별도의 Data 클래스로 보아야하는지를 여쭤보았다.
교수님의 답변으론, 결국 Model 클래스는 특정한 Data를 반환해주는 클래스라고 하셨고,
우리가 일상에서 임무를 수행할 때 사람마다 고유한 역할을 배정해서 일을 분담하듯이
Model 클래스도 고유한 역할을 클래스별로 의미를 부여해서 직관적으로 코드를 관리할 수 있도록 돕는다고 하셨다.
그러니깐, MVC 패턴은 우리의 일상과 아주 밀접하게 연관되어있고, 이에 따라 MVC 설계는 우리의 일상 상황에 빗대어 보며
이상적인 MVC 패턴을 설계하는게 우리의 소프트웨어학부생으로써 주어진 마땅한 임무라고 하셨다.
요약해서 이정도이고, 실무관련해서도 좋은 말씀 많이 해주셨지만, 시간이 남을 때 자세하게 기록해두겠다.
'CS 대학강의' 카테고리의 다른 글
[CS 1-2 | 시스템 프로그래밍 기초] 시스템 프로그래밍 기초 - #16 [문자열] (0) | 2022.11.15 |
---|---|
[CS 1-2 | 시스템 프로그래밍 기초] 함수 포인터 실습 15주차 (0) | 2022.11.11 |
[CS 1-2 | 대학생을 위한 실용금융] 신용관리 10주차 (0) | 2022.11.09 |
[CS 1-2 | 프로그램 설계 방법론] abstract 클래스, Interface, 상속이란 (0) | 2022.11.09 |
[CS 1-2 | 시스템 프로그래밍 기초] 함수 포인터 & 포인터 배열 14주차 (0) | 2022.11.08 |