(c)도경구 version 1.0 (2022/11/2)
version 1.1 (2022/11/6) 슬라이딩 퍼즐 게임 추가
9. GUI와 이벤트 구동 프로그래밍
9-1. 사례 학습 : 카운터
코딩 따라하기
1단계 : 초기 버전
public class Counter {
private int count;
/** Counter - 카운터 초기 설정
* @param start - 카운터의 초기값 */
public Counter(int start) {
count = start;
}
/** increment - 카운터 값 증가 */
public void increment() {
count = count + 1;
}
/** count - 카운터 값 리턴
* @return 카운터 현재 값 */
public int count() {
return count;
}
}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CounterFrame extends JFrame implements ActionListener {
private Counter counter;
private JLabel label = new JLabel("count = 0");
public CounterFrame(Counter c) {
counter = c;
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(label);
JButton button = new JButton("OK");
cp.add(button);
button.addActionListener(this); // 자신을 버튼에 연결 (버튼 이벤트가 발생하면 처리 가능하도록)
setTitle("Counter");
setSize(200,70);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
/** actionPerformed - '버튼 누르기' 액션 이벤트를 처리 */
public void actionPerformed(ActionEvent e) {
counter.increment();
label.setText("count = " + counter.count());
}
}
public class CounterStarter {
public static void main(String[] args) {
Counter model = new Counter(0);
new CounterFrame(model);
}
}
2단계 : 컨트롤러와 뷰를 분리
import java.awt.event.*;
public class CounterController implements ActionListener {
private CounterFrame view;
private Counter model;
public CounterController(Counter m, CounterFrame v) {
view = v;
model = m;
}
/** actionPerformed - '버튼 누르기' 액션 이벤트를 처리 */
public void actionPerformed(ActionEvent e) {
model.increment();
view.update();
}
}
import java.awt.*;
import javax.swing.*;
public class CounterFrame extends JFrame {
private Counter counter;
private JLabel label = new JLabel("count = 0");
public CounterFrame(Counter c) {
counter = c;
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(label);
JButton button = new JButton("OK");
cp.add(button);
button.addActionListener(new CounterController(counter, this)); // 자신을 버튼에 연결 (버튼 이벤트가 발생하면 처리 가능하도록)
setTitle("Counter");
setSize(200,70);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
/** update - 뷰 갱신 */
public void update() {
label.setText("count = " + counter.count());
}
}
3단계 : 버튼 전용 컨트롤러
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CountButton extends JButton implements ActionListener {
private CounterFrame view;
private Counter model;
/** CountButton - 버튼 컨트롤러
* @param label - 버튼에 붙는 라벨
* @param m - 협조할 모델
* @param v - 갱신할 뷰 */
public CountButton(String label, Counter m, CounterFrame v) {
super(label);
view = v;
model = m;
addActionListener(this);
}
/** actionPerformed - '버튼 누르기' 액션 이벤트를 처리
* @param e - 이벤트 */
public void actionPerformed(ActionEvent e) {
model.increment();
view.update();
}
}
import java.awt.*;
import javax.swing.*;
public class CounterFrame extends JFrame {
private Counter counter;
private JLabel label = new JLabel("count = 0");
public CounterFrame(Counter c) {
counter = c;
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(label);
cp.add(new CountButton("OK", counter, this));
setTitle("Counter");
setSize(200,70);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
/** update - 뷰 갱신 */
public void update() {
label.setText("count = " + counter.count());
}
}
4단계 : 그래픽 카운터, 구역 정리, 버튼 추가
import java.awt.event.*;
import javax.swing.*;
public class ExitButton extends JButton implements ActionListener {
/** ExitButton - 종료 컨트롤러
* @param label - 버튼에 붙는 라벨 */
public ExitButton(String label) {
super(label);
addActionListener(this);
}
/** actionPerformed - '버튼 누르기' 액션 이벤트를 처리
* @param e - 이벤트 */
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
import java.awt.*;
import javax.swing.*;
public class Drawing extends JPanel {
private Counter counter;
public Drawing(Counter c) {
counter = c;
setSize(200, 200);
}
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.red);
int x = 0, y = 0;
for (int i = 0; i != counter.count(); i++) {
g.fillOval(x*25, y*25, 20, 20);
x++;
if (x > 7) {
x = 0;
y++;
}
}
}
}
import java.awt.*;
import javax.swing.*;
public class CounterFrame extends JFrame {
private Counter counter;
private JLabel label = new JLabel("count = 0");
private Drawing panel;
public CounterFrame(Counter c, Drawing p) {
counter = c;
panel = p;
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
JPanel p1 = new JPanel(new FlowLayout());
p1.add(label);
JPanel p2 = new JPanel(new FlowLayout());
p2.add(new CountButton("OK", counter, this));
p2.add(new ExitButton("Exit"));
cp.add(p1, BorderLayout.NORTH);
cp.add(panel, BorderLayout.CENTER);
cp.add(p2,BorderLayout.SOUTH);
setTitle("Counter");
setSize(200,280);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
/** update - 뷰 갱신 */
public void update() {
label.setText("count = " + counter.count());
panel.repaint();
}
}
public class CounterStarter {
public static void main(String[] args) {
Counter model = new Counter(0);
Drawing panel = new Drawing(model);
new CounterFrame(model,panel);
}
}
9-2. 실습 : 슬라이드 퍼즐 게임 (GUI 버전)
지난 실습 시간에 작성한 퍼즐 게임을 다음의 요구사항와 맟추어 개선하자.
- 애플리케이션을 실행하면 다음과 같은 퍼즐 보드 창을 띄운다.
- 상단에 위치한
Start
버튼을 누르면 아래와 같이 우하단 구석에 있는 한 칸을 비운 채로 퍼즐 조각을 무작위로 섞은 퍼즐 보드가 생기며 퍼즐 게임을 시작한다. - 퍼즐 조각을 움직여 퍼즐 보드를 위와 똑같이 만들면 게임이 끝난다.
- 퍼즐 보드의 재배치를 완료하면 다음과 같이 우하단 빈 칸에
Done
이라고 표시되면서 게임이 끝난다.
- 게임이 끝나면 어떤 버튼을 누르더라도 퍼즐 조각이 움직이지 않아야 한다.
- 언제든지
Start
버튼을 누르면 다시 처음부터 퍼즐 게임을 할 수 있어야 한다.
설계도
/** PuzzlePiece - 슬라이드 퍼즐 게임 조각 */
public class PuzzlePiece {
private int face;
/** Constructor - PuzzlePiece 퍼즐 조각을 만듬
* @param value - 퍼즐 조각 위에 표시되는 값 */
public PuzzlePiece(int value) {
face = value;
}
/** face - 조각의 액면 값을 리턴 */
public int face() {
return face;
}
}
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;
}
}
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() {
// 코드 채우기
}
}
public class PuzzleStarter {
public static void main(String[] args) {
new PuzzleFrame(new SlidePuzzleBoard());
}
}
9-3. 사례 학습 : 슬라이드 퍼즐 (GUI 버전 2.0)
코딩 따라하기
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
private boolean on = false;
public SlidePuzzleBoard() {
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() {
int[] numbers = generateRandomPermutation(15);
int i = 0;
for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++) {
// PuzzlePiece 만들어 채우기
}
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() {
// 코드 채우기
}
}