Uknow's Lab.
article thumbnail

 

자바를 처음 배우고, 2학기에 자바의 GUI와 Swing을 배우며,

무언가 프로그램을 만들어보고 싶다는 마음에 만들어봤던 2048 게임입니다.

자바를 처음 배울때라 내부 로직은 꽤나 스파게티 코드이고, 지금보면 왜 이렇게 짰을까 하는 부분도 꽤 많습니다.

알고리즘도 사실 잘 모르던 시절이라 숫자 병합 로직이 꽤 비효율적이고, 제대로 작동이 되는게 신기할 정도입니다 ㅎㅎ;

 

 

 

조작방법은 단순히 상, 하, 좌, 우 방향키를 조작해 같은 숫자들끼리 합칠 수 있습니다.

재시작 / 게임 종료를 할 때마다 랭킹이 저장되며(자바 파일 입출력 기능),

랭킹 초기화 버튼을 통해 랭킹을 초기화할 수 있습니다.

랭킹 기능은 없냐는 친구의 말에,

아 맞다 나 파일입출력 배웠지? 그걸로 될 것 같은데? 하면서 만들었던 기억이 생각나네요.

 

 

전체 코드는 아래 깃허브에서 볼 수 있습니다.

Eclipse에서 작성되었으며, intellij에서 프로젝트가 잘 import 되지 않을 수 있습니다.

자바 파일은 3개밖에 없으니, 새 프로젝트를 만들고 자바 코드만 복붙하셔도 됩니다.

https://github.com/yoon6763/2048

 

GitHub - yoon6763/2048: 자바 Swing, 파일입출력을 이용하여 제작한 2048 게임

자바 Swing, 파일입출력을 이용하여 제작한 2048 게임. Contribute to yoon6763/2048 development by creating an account on GitHub.

github.com

 

 

 

전체 파일은 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번] [Kotlin] 2048 (Easy)

https://www.acmicpc.net/problem/12100 12100번: 2048 (Easy) 첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모

uknowblog.tistory.com

 

덤으로, 백준 12100번 2048 풀이도 한 번 포스팅한 적이 있는데,

해당 문제를 풀 때 사용한 알고리즘을 기반으로 이 프로젝트를 훨씬 깔끔한 로직으로 다시 만들 수 있을 것 같긴 한데,

지금 하고 있는 프로젝트도 너무 바빠 여유가 없네요 ㅎㅎ....

언제가 될 진 모르겠지만 2048 remake 버전을 만들어 봐야겠습니다.

profile

Uknow's Lab.

@유노 Uknow

인생은 Byte와 Double 사이 Char다. 아무말이나 해봤습니다.