자바를 처음 배우고, 2학기에 자바의 GUI와 Swing을 배우며,
무언가 프로그램을 만들어보고 싶다는 마음에 만들어봤던 2048 게임입니다.
자바를 처음 배울때라 내부 로직은 꽤나 스파게티 코드이고, 지금보면 왜 이렇게 짰을까 하는 부분도 꽤 많습니다.
알고리즘도 사실 잘 모르던 시절이라 숫자 병합 로직이 꽤 비효율적이고, 제대로 작동이 되는게 신기할 정도입니다 ㅎㅎ;
조작방법은 단순히 상, 하, 좌, 우 방향키를 조작해 같은 숫자들끼리 합칠 수 있습니다.
재시작 / 게임 종료를 할 때마다 랭킹이 저장되며(자바 파일 입출력 기능),
랭킹 초기화 버튼을 통해 랭킹을 초기화할 수 있습니다.
랭킹 기능은 없냐는 친구의 말에,
아 맞다 나 파일입출력 배웠지? 그걸로 될 것 같은데? 하면서 만들었던 기억이 생각나네요.
전체 코드는 아래 깃허브에서 볼 수 있습니다.
Eclipse에서 작성되었으며, intellij에서 프로젝트가 잘 import 되지 않을 수 있습니다.
자바 파일은 3개밖에 없으니, 새 프로젝트를 만들고 자바 코드만 복붙하셔도 됩니다.
https://github.com/yoon6763/2048
전체 파일은 Main, Game2048, File_IO인데, 각각 프로그램 실행, 게임 진행, 랭킹 시스템(파일 입출력)을 담당합니다.
- Main.java
public class Main {
public static void main(String[] args)
{
new Game2048();
}
}
-2048.java
import java.util.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class Game2048 extends JFrame implements KeyListener, ActionListener {
Container ct = getContentPane();
JPanel[][] box = new JPanel[4][4]; // 4*4 박스를 나타낼 패널
JLabel[][] jl = new JLabel[4][4]; // 박스 안의 값(숫자 표시용)
JLabel printscore = new JLabel();
JLabel gameovertext = new JLabel();
int[][] num = new int[4][4]; // 레이블에 대입할 실질적인 값
int score = 0;
final int scoreratio = 10; // 점수비율
int countblock = 0;
File_IO fi = new File_IO();
JTextArea rank = new JTextArea();
JTextArea ranknum = new JTextArea();
JTextArea wja = new JTextArea();
public Game2048()
{
initialize();
initialize_ranking();
randMake();
refresh();
setTitle("2048");
setSize(1200,700);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void initialize()
{
ct.setLayout(new BorderLayout());
//오른쪽 서브패널
JPanel sub = new JPanel();
sub.setLayout(new GridLayout(10,1));
sub.setBackground(Color.lightGray);
sub.setBackground(new Color(213,240,251));
//Welcome 2048
JLabel welcome = new JLabel("Welcome 2048!");
welcome.setFont(new Font("나눔고딕", Font.BOLD, 45));
welcome.setHorizontalAlignment(SwingConstants.CENTER);
sub.add(welcome);
//점수(글자, 라벨)
JLabel lscore = new JLabel(" 점수 ");
lscore.setFont(new Font("나눔고딕", Font.BOLD, 35));
lscore.setHorizontalAlignment(SwingConstants.CENTER);
sub.add(lscore);
//점수(숫자)
printscore.setHorizontalAlignment(SwingConstants.CENTER);
printscore.setFont(new Font("나눔고딕", Font.BOLD, 35));
printscore.setText(Integer.toString(score));
sub.add(printscore);
//게임오버 aptlwl
gameovertext.setText("");
gameovertext.setHorizontalAlignment(SwingConstants.CENTER);
gameovertext.setFont(new Font("나눔고딕", Font.BOLD, 45));
gameovertext.setForeground(Color.red);
sub.add(gameovertext);
//developer 메시지
JLabel developer = new JLabel("Developed by uknow");
developer.setFont(new Font("나눔고딕", Font.BOLD, 13));
developer.setHorizontalAlignment(SwingConstants.CENTER);
sub.add(developer);
//powered 메시지
JLabel powered = new JLabel("Powered by Java");
powered.setFont(new Font("나눔고딕", Font.BOLD, 13));
powered.setHorizontalAlignment(SwingConstants.CENTER);
sub.add(powered);
//안내메시지
JLabel notice = new JLabel("종료할땐 '게임 종료' 버튼을 클릭! 강제 종료 시 점수가 저장되지 않습니다!");
notice.setFont(new Font("나눔고딕", Font.BOLD, 10));
notice.setHorizontalAlignment(SwingConstants.CENTER);
sub.add(notice);
//재시작 버튼
JButton restart = new JButton("재시작");
restart.setFont(new Font("나눔고딕", Font.BOLD, 45));
restart.setBackground(new Color(206,222,255));
restart.addActionListener(this);
restart.addKeyListener(this);
sub.add(restart);
//랭킹리셋 버튼
JButton rankreset = new JButton("랭킹 초기화");
rankreset.setFont(new Font("나눔고딕", Font.BOLD, 45));
rankreset.setBackground(new Color(222,219,255));
rankreset.addActionListener(this);
rankreset.addKeyListener(this);
sub.add(rankreset);
//게임 종료 버튼
JButton exitbutton = new JButton("게임 종료");
exitbutton.setFont(new Font("나눔고딕", Font.BOLD, 45));
exitbutton.setBackground(new Color(158,184,250));
exitbutton.addActionListener(this);
sub.add(exitbutton);
//메인 프레임 (중앙 4*4 그리드 판)
JPanel fraim = new JPanel();
fraim.setLayout(new GridLayout(4,4,3,3));
for(int i = 0; i<4; i++)
{
for(int j = 0; j<4; j++)
{
num[i][j] = 0;
box[i][j] = new JPanel();
box[i][j].setLayout(new GridLayout(1,1));
box[i][j].setBackground(Color.white);
jl[i][j] = new JLabel();
jl[i][j].setFont(new Font("맑은 고딕", Font.BOLD, 45));
jl[i][j].setHorizontalAlignment(SwingConstants.CENTER);
jl[i][j].setVerticalAlignment(SwingConstants.CENTER);
box[i][j].add(jl[i][j]);
fraim.add(box[i][j]);
}
}
ct.add(sub,BorderLayout.EAST);
ct.add(fraim,BorderLayout.CENTER);
}
public int[] sort(int[] arr)
{
for(int i = arr.length - 1; i>0; i-- )
for(int j = 0; j<i; j++)
if(arr[j] < arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
return arr;
}
public void initialize_ranking()
{
//왼쪽 서브 패널
JPanel west = new JPanel();
west.setLayout(new BorderLayout());
//상단 랭크 텍스트
JPanel textPanel = new JPanel();
JLabel westText = new JLabel("랭킹");
textPanel.add(westText);
westText.setFont(new Font("나눔고딕", Font.BOLD, 30));
textPanel.setBackground(new Color(145,181,255));
west.add(textPanel,BorderLayout.NORTH);
//순위, 점수 패널
JPanel rankpanel = new JPanel();
rankpanel.setLayout(new BorderLayout());
ranknum.setEditable(false);
ranknum.addKeyListener(this);
rank.addKeyListener(this);
rank.setEditable(false);
rankpanel.add(ranknum,BorderLayout.WEST);
rankpanel.add(rank,BorderLayout.CENTER);
ranknum.setBackground(new Color(198,217,255));
rank.setBackground(new Color(219,231,255));
ranknum.setFont(new Font("나눔고딕", Font.BOLD, 20));
rank.setFont(new Font("나눔고딕", Font.BOLD, 20));
//점수 "점"
rankpanel.add(wja,BorderLayout.EAST);
wja.setEditable(false);
wja.setBackground(new Color(219,231,255));
wja.setFont(new Font("나눔고딕", Font.BOLD, 20));
west.add(rankpanel,BorderLayout.CENTER);
importRank();
ct.add(west,BorderLayout.WEST);
}
public void importRank()
{
ranknum.setText("");
rank.setText("");
wja.setText("");
String ranklist = fi.read_word();
if(ranklist.equals(""))
{
ranknum.append("1위 ");
rank.append(" 0 ");
wja.append(" 점 ");
return;
}
String[] str = ranklist.split("\n");
int[] temp = new int[str.length];
for(int i = 0; i<str.length; i++)
temp[i] = Integer.parseInt(str[i]);
temp = sort(temp);
for(int i = 0; i<str.length; i++)
{
ranknum.append(" "+String.valueOf(i+1)+"위 \n");
rank.append(" "+temp[i]+"\n");
wja.append(" 점 \n");
}
}
public void saveRank()
{
if(score != 0)
fi.saveFile(score+"\n");
}
public void keyReleased(KeyEvent e) {} // 오버라이딩
public void keyTyped(KeyEvent e) {} // 오버라이딩
public void keyPressed(KeyEvent e)
{
int key = e.getKeyCode();
activate(key); //블록 합치기 한쪽으로 몰기
refresh(); //새로고침
}
public void randMake()
{
Random rd = new Random();
int count = 0;
int max = rd.nextInt(2)+1;
if(countblock >= 12)
max = 1;
while(true)
{
//디버깅중======
if(isGameover() == true)
{
gameovertext.setText("Game Over!");
break;
}
//============
if(count == max)
break;
int x = rd.nextInt(4);
int y = rd.nextInt(4);
if(num[x][y] != 0)
continue;
int temp = rd.nextInt(3); // 0, 1, 2 랜덤 난수 생성
if(temp == 0) temp = 4;
else if(temp == 1) temp = 2; // 2가 나올 확률은 2/3, 4가 나올 확률은 1/3
num[x][y] = temp;
jl[x][y].setText(Integer.toString(num[x][y]));
count++;
}
}
public boolean emptyblock()
{
countblock = 0;
boolean empty = false;
for(int i = 0; i<4; i++)
{
for(int j = 0; j<4; j++)
{
if(num[i][j] == 0)
empty = true;
else
countblock++;
}
}
return empty;
}
public boolean isGameover()
{
boolean check = false;
int count = 0;
for(int i = 0; i<3; i++)
for(int j = 0; j<4; j++)
if(num[i][j] != num[i+1][j] && num[i][j] != 0)
count++;
for(int i = 0; i<3; i++)
for(int j = 0; j<4; j++)
if(num[j][i] != num[j][i+1] && num[j][i] != 0 && num[j][i+1] != 0)
count++;
if(count == 24)
check = true;
return check;
}
public void activate(int key) // 블록 합치기, 한쪽으로 몰기
{
// 37 왼쪽
// 38 위쪽
// 39 오른쪽
// 40 아래쪽
//
// 좌표
//
// 0,0 1,0 2,0 3,0
//
// 0,1 1,1 2,1 3,1
//
// 0,2 1,2 2,2 3,2
//
// 0,3 1,3 1,3 3,3
boolean br = false;
if(key == 37) // 왼쪽
{
for(int i = 0; i<4; i++)
{
for(int j = 1; j<=3; j++)
{
if(num[i][j-1] == 0 && num[i][j]>0)
{
num[i][j-1] = num[i][j];
num[i][j]=0;
i--;
br = true;
break;
}
}
if(br == true)
{
br = false;
continue;
}
for(int j = 0; j<3; j++)
{
if(num[i][j] == num[i][j+1])
{
score += (num[i][j]*scoreratio);
num[i][j] *= 2;
num[i][j+1] = 0;
}
}
// ************************** 디버깅 부분, 에러 있을 확률 있음
for(int j = 1; j<=3; j++)
{
if(num[i][j-1] == 0 && num[i][j]>0)
{
num[i][j-1] = num[i][j];
num[i][j]=0;
}
}
// **************************** 디버깅 부분
}
}
else if(key == 39) // 오른쪽
{
for(int i = 0; i<4; i++)
{
for(int j = 2; j>=0; j--)
{
if(num[i][j+1] == 0 && num[i][j]>0)
{
num[i][j+1] = num[i][j];
num[i][j]=0;
i--;
br = true;
break;
}
}
if(br == true)
{
br = false;
continue;
}
for(int j = 2; j>=0; j--)
{
if(num[i][j+1] == num[i][j])
{
score += (num[i][j]*scoreratio);
num[i][j+1] *= 2;
num[i][j] = 0;
}
}
for(int j = 2; j>=0; j--)
{
if(num[i][j+1] == 0 && num[i][j]>0)
{
num[i][j+1] = num[i][j];
num[i][j]=0;
}
}
}
}
else if(key == 38) // 위쪽
{
for(int i = 0; i<4; i++)
{
for(int j = 1; j<=3; j++)
{
if(num[j-1][i] == 0 && num[j][i]>0)
{
num[j-1][i] = num[j][i];
num[j][i]=0;
i--;
br = true;
break;
}
}
if(br == true)
{
br = false;
continue;
}
for(int j = 0; j<3; j++)
{
if(num[j][i] == num[j+1][i])
{
score += (num[j][i]*scoreratio);
num[j][i] *= 2;
num[j+1][i] = 0;
}
}
for(int j = 1; j<=3; j++)
{
if(num[j-1][i] == 0 && num[j][i]>0)
{
num[j-1][i] = num[j][i];
num[j][i]=0;
}
}
}
}
else if(key == 40) // 아래쪽
{
for(int i = 0; i<4; i++)
{
for(int j = 2; j>=0; j--)
{
if(num[j+1][i] == 0 && num[j][i]>0)
{
num[j+1][i] = num[j][i];
num[j][i]=0;
i--;
br = true;
break;
}
}
if(br == true)
{
br = false;
continue;
}
for(int j = 2; j>=0; j--)
{
if(num[j+1][i] == num[j][i])
{
score += (num[j][i]*scoreratio);
num[j+1][i] *= 2;
num[j][i] = 0;
}
}
for(int j = 2; j>=0; j--)
{
if(num[j+1][i] == 0 && num[j][i]>0)
{
num[j+1][i] = num[j][i];
num[j][i]=0;
}
}
}
}
else // 방향키 외 키값이 들어올 경우 아무것도 안하고 메소드 종료
return;
if(emptyblock())
randMake(); //숫자생성
}
public void actionPerformed(ActionEvent e)
{
saveRank();
String str = e.getActionCommand();
if(str.equals("게임 종료"))
System.exit(0);
else if(str.equals("재시작"))
restart();
else if(str.equals("랭킹 초기화"))
rankreset();
}
public void rankreset()
{
fi.rankreset();
importRank();
}
public void restart()
{
for(int i = 0; i<4; i++)
for(int j = 0; j<4; j++)
num[i][j] = 0;
score = 0;
gameovertext.setText("");
countblock = 0;
randMake();
importRank();
refresh();
}
public void refresh()
{
printscore.setText(Integer.toString(score));
for(int i = 0; i<4; i++)
{
for(int j = 0; j<4; j++)
{
if(num[i][j] == 0)
{
jl[i][j].setText("");
box[i][j].setBackground(Color.white);
}
else
{
jl[i][j].setText(Integer.toString(num[i][j]));
if(num[i][j] <= 2)
box[i][j].setBackground(new Color(243,243,243));
else if(num[i][j] == 4)
box[i][j].setBackground(new Color(255,228,185));
else if(num[i][j] == 8)
box[i][j].setBackground(new Color(255,208,130));
else if(num[i][j] == 16)
box[i][j].setBackground(new Color(255,172,49));
else if(num[i][j] == 32)
box[i][j].setBackground(new Color(255,168,0));
else if(num[i][j] == 64)
box[i][j].setBackground(new Color(255,66,66));
else if(num[i][j] <= 256)
box[i][j].setBackground(new Color(255,233,28));
else if(num[i][j] <= 2048)
box[i][j].setBackground(new Color(255,28,28));
else
{
box[i][j].setBackground(Color.black);
jl[i][j].setForeground(Color.white);
}
}
}
}
}
}
- File_IO.java
import java.io.*;
public class File_IO {
String path = "Ranking.txt";
BufferedReader br;
BufferedWriter bw;
File folder = new File("./");
public File_IO()
{
try {
bw = new BufferedWriter(new FileWriter(path,true));
bw.close();
} catch(IOException e) {}
}
public void saveFile(String contents)
{
try {
bw = new BufferedWriter(new FileWriter(path,true));
bw.write(contents);
bw.flush();
bw.close();
} catch(IOException e) {};
}
public void rankreset()
{
try {
bw = new BufferedWriter(new FileWriter(path));
bw.write("");
bw.flush();
bw.close();
} catch(IOException e) {};
}
public String read_word()
{
String str = "";
String temp = "";
try {
br = new BufferedReader(new FileReader(path));
while((temp = br.readLine()) != null)
str = str + temp+"\n";
} catch(IOException e) {}
return str;
}
public String[] import_file()
{
File[] list = folder.listFiles();
String[] str = new String[list.length];
for(int i = 0; i<str.length; i++)
str[i] = list[i].getName();
return str;
}
}
오랜만에 깃허브와 데스크탑/노트북/맥북/클라우드의 워크스페이스를 정리하고 있던 중,
제가 만든 첫 프로젝트이자, 꽤 재밌게 만들었던 2048 프로그램이 보이길래
반가운 마음에 포스팅을 해봤습니다.
https://uknowblog.tistory.com/65
덤으로, 백준 12100번 2048 풀이도 한 번 포스팅한 적이 있는데,
해당 문제를 풀 때 사용한 알고리즘을 기반으로 이 프로젝트를 훨씬 깔끔한 로직으로 다시 만들 수 있을 것 같긴 한데,
지금 하고 있는 프로젝트도 너무 바빠 여유가 없네요 ㅎㅎ....
언제가 될 진 모르겠지만 2048 remake 버전을 만들어 봐야겠습니다.
'언어 > Java' 카테고리의 다른 글
상속과 자바 Stack 클래스의 문제점 (0) | 2023.06.20 |
---|---|
자바, 코틀린을 위한 인텔리제이(Intellij IDEA) 설치방법 (0) | 2023.03.08 |
[Java] 자바 다운로드 및 설치 방법 및 환경 변수 설정 (2) | 2023.03.07 |
[Java] 자바 Swing을 사용한 GUI 숫자 야구 게임 (0) | 2023.02.18 |
[Java] 숫자 야구 게임 (0) | 2022.02.15 |