자바_GUI_프로젝트_스도쿠게임

728x90

[전체 게임 기획/설계]

[게임 실행 영상]

스도쿠게임_실행영상

[게임 개발 설명]

UI 관련 클래스들

[Main]

-주된 UI를 제공하는 MainFrmae객체를 생성.

Main

public class Main {

	public static final int SCREEN_WIDTH = 800;
	public static final int SCREEN_HEIGHT = 600;
	
	public static void main(String[] args) {
		
		new MainFrame();
	}

}

 

[MainFrame 클래스]

-게임 UI의 주된 컴포넌트 처리를 이 안에서 해결했다.

동적인 화면 전환을 위해 여러 가지 Panel을 사용하고 boolean 변수로 현재의 screenImage를 컨트롤했다. 

MainFrame 클래스 

public class MainFrame extends JFrame{
	
	//필드 
	private JPanel windowPanel;
	private JPanel buttonSelectionPanel;
	private SudokuPanel sPanel;
	private JPanel bottomPanel; //랭킹버튼 넣을 패널 
	private JLabel label;
	private JButton RankButton;
	
	private Image screenImage;
	private Graphics screenGraphic;
   	boolean isMainScreen = false; // 현재 창이 게임선택 (main)창인지 여부 
	
	private SudokuPuzzleType selectedPuzzle; //선택한 퍼즐 타입
	private int fontSize;
	
	//이미지 아이콘 생성 
	
	//시작하기 버튼 이미지(기본)
	private ImageIcon startButtonBasicImage = new ImageIcon(Main.class.getResource("/Images/startButton.png"));

	//종료하기 버튼 이미지(기본)
	private ImageIcon quitButtonBasicImage = new ImageIcon(Main.class.getResource("/Images/quitButton.png"));

	//introImage
	private Image introImage= new ImageIcon(Main.class.getResource("/Images/introImage.png")).getImage();

	//메뉴바(JLabel)
	private JLabel menuBar =  new JLabel(new ImageIcon(Main.class.getResource("/Images/menuBar.png")));

	//X 버튼 이미지(기본)
	private ImageIcon exitBasicImage = new ImageIcon(Main.class.getResource("/Images/exitButtonBasic.png"));

	//게임 선택창 
	private Image selectedImage;
	//left 이미지
	private ImageIcon leftButtonImage = new ImageIcon(Main.class.getResource("/Images/Left.png"));
	//right 이미지
	private ImageIcon rightButtonImage = new ImageIcon(Main.class.getResource("/Images/Right.png"));

	//선택하기 버튼 이미지 
	private ImageIcon selectButtonImage = new ImageIcon(Main.class.getResource("/Images/selectButton.png"));

	//back 버튼 이미지
	private ImageIcon backButtonImage = new ImageIcon(Main.class.getResource("/Images/backButton.png"));
	
	
	//ArrayList로 게임선택 Track 관리 
	ArrayList<Track> trackList = new ArrayList<Track>();
	
	private int nowSelected = 0; //선택 인덱스 기본 0초기화

	//J버튼에 각 이미지아이콘 넣어주기
	private JButton exitButton = new JButton(exitBasicImage); 
	private JButton startButton = new JButton(startButtonBasicImage);
	private JButton quitButton = new JButton(quitButtonBasicImage);
	private JButton leftButton = new JButton(leftButtonImage); 
	private JButton rightButton = new JButton(rightButtonImage);
	private JButton selectButton = new JButton(selectButtonImage);
	private JButton backButton = new JButton(backButtonImage);
	
	//마우스 좌표 변수 
	private int mouseX, mouseY;
	
	//생성자
	public MainFrame() {
		setUndecorated(true); //기본 메뉴바는 없애주기 (내가 설정한 메뉴바로 )
		setSize(Main.SCREEN_WIDTH, Main.SCREEN_HEIGHT); //창 크기 
		setResizable(false);//한 번 만들어진 창은 사용자가 인위적으로 변형 금지시킴 
		setLocationRelativeTo(null); //실행 시 컴퓨터 정중앙에 뜸
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //게임창 종료시 프로그램 완전 종료
		setVisible(true); //게임창 화면에 정상 보여주기
		setBackground(new Color(0, 0, 0, 0));
		setLayout(null);
		
		//trackList(게임 선택창 화면) 
		trackList.add(new Track("level1.png")); //0번
		trackList.add(new Track("level2.png")); //1번
		trackList.add(new Track("level3.jpg")); //2번
		
		//exitButton 처리
		exitButton.setBounds(765, 0, 30, 30); //exit 버튼 위치(메뉴바 위의 오른쪽 위치로) 
		exitButton.setBorderPainted(false);
		exitButton.setContentAreaFilled(false);
		exitButton.setFocusPainted(false);
		exitButton.addMouseListener(new MouseAdapter() { //마우스 이벤트 처리 
			@Override
			public void mouseClicked(MouseEvent e) { //클릭 시 
				// TODO 자동 생성된 메소드 스텁
				super.mouseClicked(e);
				System.exit(0); //프로그램 종료 
			}
		});
		add(exitButton);	
		
		//메뉴바 처리
		menuBar.setBounds(0, 0, 800, 10); //메뉴바 위치
		menuBar.addMouseListener(new MouseAdapter() { //메뉴바 클릭이벤트
					public void mouseClicked(MouseEvent e) {
						mouseX = e.getX();
						mouseY = e.getY();
					}
				});
		menuBar.addMouseMotionListener(new MouseMotionAdapter() {
					//메뉴바 마우스 드레그 시, 마우스 좌표값 따라서 화면창도 움직임 
					@Override
					public void mouseDragged(MouseEvent e) {
						// TODO 자동 생성된 메소드 스텁
						super.mouseDragged(e);
						int x = e.getXOnScreen();	
						int y = e.getYOnScreen();
						setLocation(x - mouseX, y - mouseY);
					}
		});
		add(menuBar);
			
		//backButton 처리
		backButton.setVisible(false);
		backButton.setBounds(5, 50, 60, 60); //위치
		backButton.setBorderPainted(false);
		backButton.setContentAreaFilled(false);
		backButton.setFocusPainted(false);
		backButton.addMouseListener(new MouseAdapter() { //마우스 이벤트 처리 
			@Override
			public void mouseClicked(MouseEvent e) { //클릭 시 
				// TODO 자동 생성된 메소드 스텁
				super.mouseClicked(e);
				//되돌아가기 이벤트
				backMain();
			}
		});
		add(backButton);	
		
		//게임창 관련 패널 믂음
		windowPanel = new JPanel();
		
		windowPanel.setBounds(0, 0, 800, 600);
		windowPanel.setBackground(Color.black);

		sPanel = new SudokuPanel(); //(스도쿠창) 패널 객체 생성 
		
		buttonSelectionPanel = new JPanel(); //(버튼창) 패널 객체 생성 
		buttonSelectionPanel.setSize(500, 90); 
		buttonSelectionPanel.setBackground(Color.black);
		
		bottomPanel = new JPanel(); //게임창 하단 패널 
		bottomPanel.setSize(new Dimension(540, 50));
		bottomPanel.setBackground(Color.GREEN);
		
		label = new JLabel("랭킹 확인 -->");
		label.setForeground(Color.white);
		label.setFont(new Font("나눔고딕", Font.BOLD, 30));
		bottomPanel.add(label);
		
		RankButton = new JButton("GO 랭킹");
		RankButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
            	
            	GameOver();//호출 
            }
        });
		bottomPanel.add(RankButton);
		
		windowPanel.add(buttonSelectionPanel);
		windowPanel.add(sPanel);
		windowPanel.add(bottomPanel);
		
		this.add(windowPanel);
		
		windowPanel.setVisible(false);	
		buttonSelectionPanel.setVisible(false);
		sPanel.setVisible(false);
		bottomPanel.setVisible(false);
		
		//startButton 처리
		startButton.setBounds(180, 370, 200, 100); //위치
		startButton.setBorderPainted(false);
		startButton.setContentAreaFilled(false);
		startButton.setFocusPainted(false);
		startButton.addMouseListener(new MouseAdapter() { //마우스 이벤트 처리 
			@Override
			public void mouseClicked(MouseEvent e) { //클릭 시 -> 화면 전환 
				// TODO 자동 생성된 메소드 스텁
				super.mouseClicked(e);
				selectTrack(0); //시작버튼 눌렀을 때 전환되는 첫 트랙은 0인덱스
				startButton.setVisible(false); //화면에 안보이게
				quitButton.setVisible(false);
				leftButton.setVisible(true); //화면에 보이게 
				rightButton.setVisible(true);
				selectButton.setVisible(true);
				introImage = new ImageIcon(Main.class.getResource("/Images/1.jpg")).getImage();
				isMainScreen = true;
			}
		});
		add(startButton);
		
		//quitButton
		quitButton.setBounds(390, 370, 200, 100); //위치
		quitButton.setBorderPainted(false);
		quitButton.setContentAreaFilled(false);
		quitButton.setFocusPainted(false);
		quitButton.addMouseListener(new MouseAdapter() { //마우스 이벤트 처리 
			
			@Override
			public void mouseClicked(MouseEvent e) { //클릭 시 
				// TODO 자동 생성된 메소드 스텁
				super.mouseClicked(e);
				System.exit(0); //프로그램 종료 
			}
		});
		add(quitButton);
	
				//leftButton 처리
				leftButton.setVisible(false); //처음에는 보이면 안됨
				leftButton.setBounds(100, 310, 60, 60); //위치
				leftButton.setBorderPainted(false);
				leftButton.setContentAreaFilled(false);
				leftButton.setFocusPainted(false);
				leftButton.addMouseListener(new MouseAdapter() { //마우스 이벤트 처리 
					
					@Override
					public void mouseClicked(MouseEvent e) { //클릭 시 
						// TODO 자동 생성된 메소드 스텁
						super.mouseClicked(e);
						selectLeft(); //왼쪽 이동 
					}
				});
				add(leftButton);
				
				//rightButton 처리
				rightButton.setVisible(false);
				rightButton.setBounds(660, 310, 60, 60); //위치
				rightButton.setBorderPainted(false);
				rightButton.setContentAreaFilled(false);
				rightButton.setFocusPainted(false);
				rightButton.addMouseListener(new MouseAdapter() { //마우스 이벤트 처리 
					
					@Override
					public void mouseClicked(MouseEvent e) { //클릭 시 
						// TODO 자동 생성된 메소드 스텁
						super.mouseClicked(e);
						selectRight(); //오른쪽 이동 
					}
				});
				add(rightButton);	
				
				//selectButton 처리
				selectButton.setVisible(false);
				selectButton.setBounds(280, 460, 250, 67); //위치
				selectButton.setBorderPainted(false);
				selectButton.setContentAreaFilled(false);
				selectButton.setFocusPainted(false);
				selectButton.addActionListener(new ActionListener() {
		            @Override
		            public void actionPerformed(ActionEvent e) {
		            	gameStart(nowSelected);
		            }
		        });
				add(selectButton);	
		
	}
	
	//그림 그리는 메소드 
	public void paint(Graphics g) {
		screenImage = createImage(Main.SCREEN_WIDTH, Main.SCREEN_HEIGHT);
		screenGraphic = screenImage.getGraphics();
		screenDraw(screenGraphic);
		g.drawImage(screenImage, 0, 0, null); 

	}
	//이미지 그리기용
	public void screenDraw(Graphics g) {
		g.drawImage(introImage, 0, 0, null);
		
		if(isMainScreen) { // 선택 main화면 T값인 경우에 한해서 (이미지 교체)
			g.drawImage(selectedImage, 200, 100, null); //게임 설명창(선택)
		}
		paintComponents(g); //add처리한 컴포넌트 그려주기
		this.repaint();
	}
	
	//게임 level 선택 이미지 관련 메소드 
	public void selectTrack(int nowSelected) {
		//게임선택이미지 (track에서 가져옴)
		selectedImage = new ImageIcon(Main.class.getResource("/Images/" + trackList.get(nowSelected).getSelectGameImage())).getImage();
	}
	public void selectLeft() { //left 선택시
		if(nowSelected == 0) { //만약 현재 가장 첫번째 트랙이면
			nowSelected = trackList.size() - 1; //끝으로 이동 
		}
		else nowSelected--;
		selectTrack(nowSelected);
	}
	public void selectRight() { //right 선택시
		if(nowSelected == trackList.size()-1) { //만약 현재 가장 끝 트랙이면
			nowSelected = 0; //0인덱스로 이동 
		}
		else nowSelected++;
		selectTrack(nowSelected);
	}
	
	//메소드(창 rebuild)
	public void rebuildInterface(SudokuPuzzleType puzzleType, int fontSize) {
		SudokuPuzzle generatedPuzzle = new SudokuGenerator().generateRandomSudoku(puzzleType);
		sPanel.newSudokuPuzzle(generatedPuzzle);
		sPanel.setFontSize(fontSize);
		
		buttonSelectionPanel.removeAll(); //기존 버튼패널 removeAll 처리 
		
		for(String value : generatedPuzzle.getValidValues()) {
			JButton b = new JButton(value);
			b.setSize(new Dimension(40, 40));
			b.setBackground(Color.PINK);
			b.addActionListener(sPanel.new NumActionListener());
			buttonSelectionPanel.add(b);
		}
		sPanel.repaint();
		buttonSelectionPanel.revalidate();
		buttonSelectionPanel.repaint();
	}
	
	public void gameStart(int nowSelected) { //게임 선택화면으로 들어가기
		isMainScreen = false; 
		
		leftButton.setVisible(false);
		rightButton.setVisible(false);
		selectButton.setVisible(false);
		backButton.setVisible(true); //보이게
		
		if(nowSelected == 0) {
			rebuildInterface(SudokuPuzzleType.SIXBYSIX,30);
		}else if (nowSelected == 1) {
			rebuildInterface(SudokuPuzzleType.NINEBYNINE,26);
		}else if (nowSelected == 2) {
			rebuildInterface(SudokuPuzzleType.TWELVEBYTWELVE,20);
		}
		
		windowPanel.setVisible(true);
		buttonSelectionPanel.setVisible(true);
		sPanel.setVisible(true);
		bottomPanel.setVisible(true);
	}
	
	public void backMain() { //돌아가기 처리 함수
		isMainScreen = true;
		buttonSelectionPanel.setVisible(false);
		sPanel.setVisible(false);
		windowPanel.setVisible(false);
		introImage = new ImageIcon(Main.class.getResource("/Images/1.jpg")).getImage(); //화면 전환
		leftButton.setVisible(true);
		rightButton.setVisible(true);
		selectButton.setVisible(true);
		backButton.setVisible(false); 
		selectTrack(nowSelected);
	}
	
	public void GameOver() {
		new GameOverFrame();
		
	}
	
}

//--> 여기로 Track 클래스 옮겨줌 

class Track { //level 선택 이미지관리 Track클래스 
	
	//필드
	private String selectGameImage;
	
	//생성자
	public Track(String selectGameImage) {
		super();
		this.selectGameImage = selectGameImage;
	}

	public String getSelectGameImage() {
		return selectGameImage;
	}

	public void setSelectGameImage(String selectGameImage) {
		this.selectGameImage = selectGameImage;
	}
	
}

[SudokuPanel 클래스]

- 화면 전환과 관련된 대부분의 Panel은 MainFrame클래스 안에서 처리했지만,

스도쿠 게임의 주요 메인 뷰, 스도쿠창은 이벤트 연결 등의 처리가 추가로 필요하기 때문에 별도의 class로 만들어주었다

이 클래스 내부에서는 전체 프레임 규격(800X600) 중 오직 퍼즐창(540X450) 규격 만 컨트롤한다. 

SudokuPanel 클래스 

public class SudokuPanel extends JPanel { //스도쿠 패널 
	//필드
	private SudokuPuzzle puzzle;
	private int currentlySelectedCol;
	private int currentlySelectedRow;
	private int usedWidth;
	private int usedHeight;
	private int fontSize;
	private int score = 0;
	
	int KeepScore;	
		
	private ImageIcon greatImage = new ImageIcon(Main.class.getResource("/Images/great.png")); //great이미지 
	private ImageIcon missImage = new ImageIcon(Main.class.getResource("/Images/miss.png"));//miss이미지
	
	GreatImageObj greatImageObj;
	MissImageObj missImageObj;
	
	private boolean inputValid = false; //버튼 입력값의 정답 여부 
	
	private boolean chooseNumber = false; //숫자번호 선택 여부
	
	
	//생성자(1)
	public SudokuPanel() {
		this.setPreferredSize(new Dimension(540, 450)); //540, 450
		this.addMouseListener(new SudokuPanelMouseAdapter());
		this.puzzle = new SudokuGenerator().generateRandomSudoku(SudokuPuzzleType.NINEBYNINE); 								//랜덤 생성 퍼즐 객체
		currentlySelectedCol = -1;
		currentlySelectedRow = -1;
		usedWidth = 0;
		usedHeight = 0;
		fontSize = 26;//폰트 초기값 26
		
		greatImageObj = new GreatImageObj(greatImage.getImage(), 90, 250, 360, 80); //great 이미지객체
		missImageObj = new MissImageObj(missImage.getImage(), 90, 250, 360, 80); // miss 이미지객체
		
		//KeepScore = new Random().nextInt(100000000);	
		KeepScore = (int) (Math.random() * 1000000 +1);
		KeepScore = (int) Math.floor(KeepScore / 1000) * 100;
	}
	
	//생성자(2)
	public SudokuPanel(SudokuPuzzle puzzle) {
		this.setPreferredSize(new Dimension(540, 450));
		this.addMouseListener(new SudokuPanelMouseAdapter());
		this.puzzle = puzzle;
		currentlySelectedCol = -1;
		currentlySelectedRow = -1;
		usedWidth = 0;
		usedHeight = 0;
		fontSize = 26;
		
		greatImageObj = new GreatImageObj(greatImage.getImage(), 90, 250, 360, 80); //great 이미지객체
		missImageObj = new MissImageObj(missImage.getImage(), 90, 250, 360, 80); // miss 이미지객체
		
	}
	
	//메소드
	public void newSudokuPuzzle(SudokuPuzzle puzzle) {
		this.puzzle = puzzle;
	}
	
	public void setFontSize(int fontSize) {
		this.fontSize = fontSize;
	}


	//패널의 paintComponent() 재정의 (스도쿠 패널 그리기)
	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2d = (Graphics2D) g;
		
		//큰사각형 그려주기용 
		g2d.setColor(Color.black); //색 지정 
		int slotWidth = this.getWidth()/puzzle.getNumColumns(); //너비 
		int slotHeight = this.getHeight()/puzzle.getNumRows(); //높이
		
		usedWidth = (this.getWidth()/puzzle.getNumColumns())*puzzle.getNumColumns();
		usedHeight = (this.getHeight()/puzzle.getNumRows())*puzzle.getNumRows();
		
		g2d.fillRect(0, 0, usedWidth,usedHeight); //큰 사각형 그려주기 (스도쿠 패널 사용영역)
		
		//가로줄 그려주기용 
		g2d.setColor(Color.MAGENTA); //색 지정 
		for(int x = 0; x <= usedWidth; x+=slotWidth) { 
			if((x/slotWidth) % puzzle.getBoxWidth() == 0) {
				g2d.setStroke(new BasicStroke(5));
				g2d.drawLine(x, 0, x, usedHeight);
			}


			else {
				g2d.setStroke(new BasicStroke(1));
				g2d.drawLine(x, 0, x, usedHeight);
			}
		}
		
		//세로줄 그려주기용 
		for(int y = 0;y <= usedHeight;y+=slotHeight) {
			if((y/slotHeight) % puzzle.getBoxHeight() == 0) { //box 라인 
				g2d.setStroke(new BasicStroke(5));
				g2d.drawLine(0, y, usedWidth, y);
			}
			else { 									//일반 라인
				g2d.setStroke(new BasicStroke(1));
				g2d.drawLine(0, y, usedWidth, y);
			}
		}
		
		Font f = new Font("Times New Roman", Font.PLAIN, fontSize); //폰트 객체 생성 
		g2d.setFont(f);
		//정확한 슬롯 영역에 랜덤퍼즐값 drawString()
		FontRenderContext fContext = g2d.getFontRenderContext();
		for(int row=0;row < puzzle.getNumRows();row++) {
			for(int col=0;col < puzzle.getNumColumns();col++) {
				if(!puzzle.isSlotAvailable(row, col)) {
					int textWidth = (int) f.getStringBounds(puzzle.getValue(row, col), fContext).getWidth();
					int textHeight = (int) f.getStringBounds(puzzle.getValue(row, col), fContext).getHeight();
					g2d.drawString(puzzle.getValue(row, col), (col*slotWidth)+((slotWidth/2)-(textWidth/2)), (row*slotHeight)+((slotHeight/2)+(textHeight/2)));
				}
			}
		}
		
		// 점수 그리기
		g2d.setColor(Color.green);
		g2d.drawString("Score : " + String.valueOf(this.score), 170, 450); // 20211206
		
		//선택된 영역에 한해서 (해당 슬롯 영역 색상 변경)
		if(currentlySelectedCol != -1 && currentlySelectedRow != -1) {
			g2d.setColor(new Color(0.0f,0.0f,1.0f,0.3f)); //new Color(0.0f,0.0f,1.0f,0.3f)
			g2d.fillRect(currentlySelectedCol * slotWidth, currentlySelectedRow * slotHeight , slotWidth, slotHeight);
		}
		
		// Great, Miss 띄우기용 drawImage
		if(currentlySelectedCol != -1 && currentlySelectedRow != -1) {			
			if(chooseNumber) {//버튼 입력된 경우
				if(inputValid) { //유효한 입력값일 경우 -> great 이미지
					g.drawImage(greatImageObj.getImage(), greatImageObj.getGreatImageX(), greatImageObj.getGreatImageY(), greatImageObj.getGreatImageWidth(), greatImageObj.getGreatImageHeight(), null);

				} else { //유효하지 않은 입력값일 경우 -> miss 이미지
					g.drawImage(missImageObj.getImage(), missImageObj.getMissImageX(), missImageObj.getMissImageY(), missImageObj.getMissImageWidth(), missImageObj.getMissImageHeight(), null);
				}
			}
			
		}
	}

	//숫자용 패널 버튼 처리 이벤트 리스너 
	public class NumActionListener implements ActionListener { 
		@Override
		public void actionPerformed(ActionEvent e) { 
			 String buttonValue = ((JButton) e.getSource()).getText(); //누른 버튼 속 텍스트 값 얻어와서 
			 
			 System.out.println("buttonValue : " + buttonValue);
			 
			 if(currentlySelectedCol != -1 && currentlySelectedRow != -1) { //선택된 영역이 유효한 행,열 내부의 값일 경우에 한해서 
				inputValid = puzzle.makeMove(currentlySelectedRow, currentlySelectedCol, buttonValue, true); //선택된 영역 속에 버튼 값 넣고 (값존재)true 넣기, 20211114 수정
				if(inputValid) {
					score += 1000;
					
				} else {
					score -= 500;
					
				}
				chooseNumber = true; // 버튼 클릭 경우 T처리 
				repaint(); //다시 repaint()
			}
		}
	}	
	//스도쿠 퍼즐 패널 위 슬롯 영역 이벤트 리스너 
	private class SudokuPanelMouseAdapter extends MouseAdapter { 
		@Override
		public void mouseClicked(MouseEvent e) { //스도쿠 패널 속 선택된 패널영역 마우스 이벤트 처리 
			
			if(e.getButton() == MouseEvent.BUTTON1) { //마우스왼쪽 클릭 시 
				int slotWidth = usedWidth/puzzle.getNumColumns(); // 슬롯너비 =  너비/ 퍼즐 열 개수 
				int slotHeight = usedHeight/puzzle.getNumRows();  // 슬롯높이 =  높이/행
				currentlySelectedRow = e.getY() / slotHeight; //선택된 행 = Y값 / 슬롯 높이 
				currentlySelectedCol = e.getX() / slotWidth; //선택된 열 = X값 / 슬롯 너비
				
				chooseNumber = false; // 슬롯 클릭 시-> great, miss 뜨지 않도록 F처리
				
				e.getComponent().repaint(); //선택된 패널 영역의(한 슬롯 칸) 그려주기용 
			}
		}
	}
	
	// Great Image 생성 객체
	public class GreatImageObj {
		//필드 
		Image image; //Great이미지	
		//Great이미지 좌표필드 x, y, width, height
		private int greatImageX;
		private int greatImageY;
		private int greatImageWidth;
		private int greatImageHeight;
		
		//생성자
		public GreatImageObj(Image image, int x, int y, int width, int height) {
			this.image = image;
			this.greatImageX = x;
			this.greatImageY = y;
			this.greatImageWidth = width;
			this.greatImageHeight = height;
		}
		
		//getter, setter() 
		public Image getImage() {
			return image;
		}
		public void setImage(Image image) {
			this.image = image;
		}
		public int getGreatImageX() {
			return greatImageX;
		}
		public void setGreatImageX(int greatImageX) {
			this.greatImageX = greatImageX;
		}
		public int getGreatImageY() {
			return greatImageY;
		}
		public void setGreatImageY(int greatImageY) {
			this.greatImageY = greatImageY;
		}
		public int getGreatImageWidth() {
			return greatImageWidth;
		}
		public void setGreatImageWidth(int greatImageWidth) {
			this.greatImageWidth = greatImageWidth;
		}
		public int getGreatImageHeight() {
			return greatImageHeight;
		}
		public void setGreatImageHeight(int greatImageHeight) {
			this.greatImageHeight = greatImageHeight;
		}
	}
	
	//miss 이미지 생성 객체 
	public class MissImageObj {
		
		//필드 
		Image image; //miss 이미지 
		//miss 이미지 x, y, width, height 좌표값 필드
		private int missImageX;
		private int missImageY;
		private int missImageWidth;
		private int missImageHeight;		

		//생성자
		public MissImageObj(Image image, int x, int y, int width, int height) {
			this.image = image;
			this.missImageX = x;
			this.missImageY = y;
			this.missImageWidth = width;
			this.missImageHeight = height;
		}
		
		public Image getImage() {
			return image;
		}
		public void setImage(Image image) {
			this.image = image;
		}
		public int getMissImageX() {
			return missImageX;
		}
		public void setMissImageX(int missImageX) {
			this.missImageX = missImageX;
		}
		public int getMissImageY() {
			return missImageY;
		}
		public void setMissImageY(int missImageY) {
			this.missImageY = missImageY;
		}
		public int getMissImageWidth() {
			return missImageWidth;
		}
		public void setMissImageWidth(int missImageWidth) {
			this.missImageWidth = missImageWidth;
		}
		public int getMissImageHeight() {
			return missImageHeight;
		}
		public void setMissImageHeight(int missImageHeight) {
			this.missImageHeight = missImageHeight;
		}
	}
}

[GameOverFrame 클래스]

-이 클래스는 작은 규격의 새로운 frame을 사용하여 textField를 띄운다.

 게임실행화면 하단에 있는 Go Ranking 버튼 컴포넌트를 클릭하여 textField로 플레이어 이름과 현재 score가 기록되어 랭킹 창으로 전환되는 이벤트가 연결되어 있다. 

또한, FileWriter를 이용하여 플레이어 이름과 현재 score를 txt 파일에 받아적은 뒤, 

OK 버튼 컴포넌트 클릭 시 GoRank() 메소드를 호출하고, 내부에서 Ranking객체를 생성시켜 랭킹 화면으로 전환된다.

[GameOverFrame.java]

class GameOverFrame extends JFrame implements ActionListener {

	private JLabel nameLabel;
    private JButton bt_OK;
    private JTextField tf;

    private String namePlayer;
    
    int SCORE = new SudokuPanel().KeepScore;
    int SCORE_REPAINT = new SudokuPanel().KeepScore;

    GameOverFrame() {
    	setTitle("랭킹 순위 기록하기");
    	setLayout(new FlowLayout());
    	setSize(400, 100);
    	setLocation(470, 300);
    	
    	nameLabel = new JLabel();
    	nameLabel.setText("이름입력");
    	
    	tf = new JTextField(20);
    	tf.setSize(getPreferredSize());
    	tf.addActionListener(this);
    	
    	bt_OK = new JButton("OK");
    	bt_OK.setSize(getPreferredSize());
    	bt_OK.addActionListener(this);
    	
    	add(nameLabel);
    	add(tf);
    	add(bt_OK);
        setVisible(true);
        
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        namePlayer = tf.getText();
        try (
                FileWriter fw = new FileWriter("test.txt", true);
                BufferedWriter bw = new BufferedWriter(fw);
        ) {
            bw.write(namePlayer);
            bw.newLine();
            bw.write(Integer.toString(SCORE));
            bw.newLine();
            bw.write(Integer.toString(SCORE_REPAINT));
            bw.newLine();
            bw.flush();
        } catch (IOException ie) {
            System.out.println(ie);
        }
        
        new Ranking();
    }

}

[Ranking 클래스]

-GameOverFrame의 이벤트에서 연결된 랭킹 객체 생성과 함께 처리되는 클래스이다. 

아까 txt 파일에 기록됐던 score를 불러와 순위를 재정렬 시킨 뒤, 다시 파일에 기록시키는 기능을 한다. 

[Ranking.java]  //랭킹 프레임 

public class Ranking extends JFrame {

    private final int X_RANK = 150;
    private final int X_NAME = 250;
    private final int X_SCORE = 350;
    private final int Y = 75;
    private final int WIDTH_LABEL = 100;
    private final int HEIGHT_LABEL = 50;

    private JLabel labelRank, labelName, labelScore ; 

    private ArrayList ls = new ArrayList();

    Ranking() {
    	setTitle("랭킹 순위");
        setLayout(null);
        setBounds(369, 100, 600, 600);

        printRanking();

        setVisible(true);
    }

    private void printRanking() {
        printRankingTitle();
        printActualRanking();
    }

    private void printRankingTitle() {
        labelRank = new JLabel("순위");
        labelRank.setBounds(X_RANK, Y, WIDTH_LABEL, HEIGHT_LABEL);
        add(labelRank);

        labelName = new JLabel("이름");
        labelName.setBounds(X_NAME, Y, WIDTH_LABEL, HEIGHT_LABEL);
        add(labelName);

        labelScore = new JLabel("점수");
        labelScore.setBounds(X_SCORE, Y, WIDTH_LABEL, HEIGHT_LABEL);
        add(labelScore);

    }

    private void printActualRanking() {
        try (
                FileReader fr = new FileReader("test.txt");
                BufferedReader br = new BufferedReader(fr);
        ) {
            String readLine = null;
            while ((readLine = br.readLine()) != null) { 
                ls.add(readLine);
            }
        } catch (IOException e) {
        }

        ArrayList<Integer> lsScore = new ArrayList<Integer>();
        
        for (int i = 1; i <= ls.size() / 3; i++) {
            lsScore.add(Integer.valueOf((String) ls.get(3 * i - 2)));
        }
        Collections.sort(lsScore); //정렬

        ArrayList<String> lsScore2 = new ArrayList<String>();
        for (int i = 0; i < lsScore.size(); i++) { //int타입인 isScore를 다시 String으로
            lsScore2.add(String.valueOf(lsScore.get(i)));
        }

        int rank = 0;
        for (int i = lsScore2.size(); i >= 1; i--) { //
            int x = ls.indexOf(lsScore2.get(i - 1)); //ls의 인덱스값 1, 4,7..찾기
            rank++;
            callAllGen(x, rank);
        }

    }

    private void callAllGen(int x, int rank) { // x는 스코어가 높은 점수대로 넣어줘야함
        // y는 x가 몇개들어가는지에따라서 1~n까지 for문으로 넣어줌 -> callAllGen메소드출력갯수만큼 똑같겠다
        genName(x - 1, rank);
        genScore(x, rank);
        genRank(Integer.toString(rank), rank);
    }

    private void genRank(String number, int rank) {
        labelRank = new JLabel(number);
        labelRank.setBounds(X_RANK, Y + 25 * rank, WIDTH_LABEL, HEIGHT_LABEL);
        add(labelRank);
    }

    private void genName(int index, int rank) {
        labelName = new JLabel((String) ls.get(index));
        labelName.setBounds(X_NAME, Y + 25 * rank, WIDTH_LABEL, HEIGHT_LABEL);
        add(labelName);
    }

    private void genScore(int index, int rank) {
        labelScore = new JLabel((String) ls.get(index));
        labelScore.setBounds(X_SCORE, Y + 25 * rank, WIDTH_LABEL, HEIGHT_LABEL);
        add(labelScore);
    }

}

 

Puzzle 생성과 관련된 클래스들

[SudokuPuzzle 클래스]

-한 퍼즐에 필요한 규격 데이터들을 내부에서 관리한다. 

SudokuPuzzle 클래스 

public class SudokuPuzzle {
	//필드
	protected String [][] board; //String [][]  
	protected boolean [][] mutable; //슬롯에 값 넣을 수 있으면 T, 이미 값이 존재하면 F 
	private final int ROWS;
	private final int COLUMNS;
	private final int BOXWIDTH;
	private final int BOXHEIGHT;
	private final String [] VALIDVALUES; //유효한 값 []
	
	//생성자(1)
	public SudokuPuzzle(int rows,int columns,int boxWidth,int boxHeight,String [] validValues) {
		this.ROWS = rows;
		this.COLUMNS = columns;
		this.BOXWIDTH = boxWidth;
		this.BOXHEIGHT = boxHeight;
		this.VALIDVALUES = validValues;
		this.board = new String[ROWS][COLUMNS];
		this.mutable = new boolean[ROWS][COLUMNS];
		
		initializeBoard(); //초기 공백 처리 
		initializeMutableSlots(); //초기 T처리(값 넣을 수 있는 상태) 
	}
	//생성자(2)
	public SudokuPuzzle(SudokuPuzzle puzzle) { //퍼즐 객체 받는 퍼즐생성자
		this.ROWS = puzzle.ROWS;
		this.COLUMNS = puzzle.COLUMNS;
		this.BOXWIDTH = puzzle.BOXWIDTH;
		this.BOXHEIGHT = puzzle.BOXHEIGHT;
		this.VALIDVALUES = puzzle.VALIDVALUES;
		this.board = new String[ROWS][COLUMNS]; 
		for(int r = 0;r < ROWS;r++) {  //받은 퍼즐객체 String 값 옮겨주고 
			for(int c = 0;c < COLUMNS;c++) {
				board[r][c] = puzzle.board[r][c];
			}
		}
		this.mutable = new boolean[ROWS][COLUMNS];
		for(int r = 0;r < ROWS;r++) { //받은 퍼즐객체 mutable T or F 값 옮겨주고 
			for(int c = 0;c < COLUMNS;c++) {
				this.mutable[r][c] = puzzle.mutable[r][c];
			}
		}
	}
	public int getNumRows() {
		return this.ROWS;
	}
	
	public int getNumColumns() {
		return this.COLUMNS;
	}
	
	public int getBoxWidth() {
		return this.BOXWIDTH;
	}
	
	public int getBoxHeight() {
		return this.BOXHEIGHT;
	}
	
	public String [] getValidValues() {
		return this.VALIDVALUES;
	}
	
	public String [][] getBoard() {
		return this.board;
	}
	
	public String getValue(int row,int col) {
		if(this.inRange(row,col)) { 
			return this.board[row][col];
		}
		return "";
	}
	

	//메소드 
	
	//<--퍼즐 처리용 메소드 -->
	
	//move
	public boolean makeMove(int row,int col, String value, boolean isMutable) { // 2021114 수정
		//, 다 잘 맞아떨어졌으면)
		if(this.isValidValue(value) && this.isValidMove(row,col,value) && this.isSlotMutable(row, col)) {
			this.board[row][col] = value; //현재의 보드[][] 에 값 넣어주고 
			this.mutable[row][col] = isMutable;
			return true;	// 2021114 수정
		}
		return false;		// 2021114 수정 
	}
	
	//not move
	public void makeSlotEmpty(int row,int col) { //공백 슬롯 만들기 (잘못된 입력값""처리용)
		this.board[row][col] = ""; 
		
	}
	
	
	//<--값 중복 여부 -->
	
	//유효한 행,열 내부인지 여부 
	public boolean inRange(int row,int col) { //범위 내부의 행, 열 값 여부 리턴 (0보다 크면서 정해진 규격 내부의 값
		return row <= this.ROWS && col <= this.COLUMNS && row >= 0 && col >= 0;
	}

	//해당 열 속 유효값 중복 여부
	public boolean numInCol(int col,String value) { 
		if(col <= this.COLUMNS) {
			for(int row=0;row < this.ROWS;row++) {
				if(this.board[row][col].equals(value)) {
					return true; //중복O 
				}
			}
		}
		return false; //중복X
	}
	//해당 행 속 유효값 중복 여부 
	public boolean numInRow(int row,String value) { //행, 값 받아서 (해당 행의 값)범위 존재여부 리턴 
		if(row <= this.ROWS) {
			for(int col=0;col < this.COLUMNS;col++) { 
				if(this.board[row][col].equals(value)) {
					return true;  //중복O
				}
			}
		}
		return false; //중복X
	}
	//해당 박스 속 유효값 중복 여부 
	public boolean numInBox(int row,int col,String value) { 
		if(this.inRange(row, col)) { //유효한 영역 내부이면서  
			
			int boxRow = row / this.BOXHEIGHT; //박스행 개수  
			int boxCol = col / this.BOXWIDTH; //박스열 개수 
			
			int startingRow = (boxRow*this.BOXHEIGHT);
			int startingCol = (boxCol*this.BOXWIDTH); 
	
			for(int r = startingRow;r <= (startingRow+this.BOXHEIGHT)-1; r++) {
				for(int c = startingCol;c <= (startingCol+this.BOXWIDTH)-1; c++) {
					if(this.board[r][c].equals(value)) {
						return true; //중복O
					}
				}
			}
		}
		return false; //중복X
	}
	//<--is메소드 -->
	
	//move가능 여부 리턴 
	public boolean isValidMove(int row,int col,String value) {
		if(this.inRange(row,col)) { 		
			//열에 해당값 없고, 행에 해당값 없고, 박스에 해당값 없을 경우, 
			if(!this.numInCol(col,value) && !this.numInRow(row,value) && !this.numInBox(row,col,value)) {
				return true; //move Ok
			}
		}
		return false; //move NO
	}
	
	//이용가능한 슬롯여부 리턴 
	public boolean isSlotAvailable(int row,int col) {
		
		//해당 슬롯이 전체 영역에서 유효한 영역이면서, 공백 상태이면서, 값넣을 수 있는 슬롯인 경우에 T 리턴 
		 return (this.inRange(row,col) && this.board[row][col].equals("") && this.isSlotMutable(row, col));
	}
	
	public boolean isSlotMutable(int row,int col) {  
		return this.mutable[row][col];
	}
	
	private boolean isValidValue(String value) { //유효값 여부 리턴 
		for(String str : this.VALIDVALUES) {
			if(str.equals(value)) return true;  //유효한 값 
		}
		return false; //잘못된 값 
	}
	
	public boolean isboardFull() { //전체 보드 full 여부 리턴 
		for(int r = 0;r < this.ROWS;r++) {
			for(int c = 0;c < this.COLUMNS;c++) {
				if(this.board[r][c].equals("")) return false; //빈 상태 
			}
		}
		return true; //찬 상태 
	}
	
	
	//<--퍼즐 초기화용 메소드-->
	
	private void initializeBoard() {  //초기 보드 
		for(int row = 0;row < this.ROWS;row++) {
			for(int col = 0;col < this.COLUMNS;col++) {
				this.board[row][col] = ""; //공백 처리 
			}
		}
	}
	
	private void initializeMutableSlots() { //초기 슬롯
		for(int row = 0;row < this.ROWS;row++) {
			for(int col = 0;col < this.COLUMNS;col++) {
				this.mutable[row][col] = true;  //사용가능 T
			}
		}
	}
}

[SudokuPuzzleType 클래스]

-이 게임의 퍼즐 Type은 6X6 / 9X9 / 12X12 총 3가지 타입으로 이루어져 있기 때문에

각 타입 관련 데이터들을 묶어서 enum클래스로 생성해주었다.  

SudokuPuzzleType 

public enum SudokuPuzzleType { //스도쿠게임 Type 열거타입 enum
	
	//게임 type 
	SIXBYSIX(6,6,3,2, new String[] {"1","2","3","4","5","6"}, "6 By 6 Game"),
	NINEBYNINE(9,9,3,3, new String[] {"1","2","3","4","5","6","7","8","9"}, "9 By 9 Game"),
	TWELVEBYTWELVE(12,12,4,3, new String[] {"1","2","3","4","5","6","7","8","9","10","11","12"}, "12 By 12 Game");
	
	//필드
	private final int rows; //행
	private final int columns; //열
	private final int boxWidth; //box너비
	private final int boxHeight; //box높이
	private final String [] validValues; //유효값[]
	private final String gameName; //게임이름 
	
	//생성자                  생성자 (행, 열, box너비, box높이, 유효값 [], 게임이름) 
	private SudokuPuzzleType(int rows,int columns,int boxWidth,int boxHeight, String [] validValues,String gameName) {
		this.rows = rows;
		this.columns = columns;
		this.boxWidth = boxWidth;
		this.boxHeight = boxHeight;
		this.validValues =validValues;
		this.gameName = gameName;
	}
	
	//Getter() 메소드 
	public int getRows() {
		return rows;
	}
	
	public int getColumns() {
		return columns;
	}
	
	public int getBoxWidth() {
		return boxWidth;
	}
	
	public int getBoxHeight() {
		return boxHeight;
	}
	
	public String [] getValidValues() {
		return validValues;
	}
	
	public String toString() {
		return gameName;
	}
}

[SudokuPuzzleGenerator 클래스]

-각 퍼즐 type 별로 초기 난수를 생성하는 랜덤 초기 퍼즐을 생성하는 클래스이다. 

이 클래스는 행, 열, 박스 안에 중복되는 숫자가 없도록 랜덤값을 받은 '정답용 퍼즐 '객체를 하나 완성한 뒤,

정답용 퍼즐 객체 속 0.2 비율만 '반환용 퍼즐 객체'에 옮겨 초기 랜덤 퍼즐을 생성하고 있다. 

SudokuGenerator 클래스 

public class SudokuGenerator { 

	//스도쿠퍼즐 타입 반환 메소드 
	public SudokuPuzzle generateRandomSudoku(SudokuPuzzleType puzzleType) {
		SudokuPuzzle puzzle = new SudokuPuzzle(puzzleType.getRows(), puzzleType.getColumns(), puzzleType.getBoxWidth(), puzzleType.getBoxHeight(), puzzleType.getValidValues());
		SudokuPuzzle copy = new SudokuPuzzle(puzzle);//퍼즐객체 copy로 옮겨받고 
		
		Random random = new Random(); //랜덤 난수 생성 객체
		
		//ArrayList<> 사용하지 않은 유효값 리스트 = 해당 퍼즐 객체의 유효값 옮김
		List<String> notUsedValidValues =  new ArrayList<String>(Arrays.asList(copy.getValidValues())); 
		
		//난수생성개수 = 해당 퍼즐타입의 행 개수만큰 생성 
		for(int r = 0;r < copy.getNumRows();r++) { 
			int randomValue = random.nextInt(notUsedValidValues.size()); //유효값 범위 내의 랜덤값 
			copy.makeMove(r, 0, notUsedValidValues.get(randomValue), true); //퍼즐패널 위에 
			notUsedValidValues.remove(randomValue); //랜덤으로 퍼즐 생성한 값 remove처리
		}
		

		//랜덤 기본 초기 퍼즐 기반 -> 역추적을 사용하여 스도쿠 퍼즐 출기
		backtrackSudokuSolver(0, 0, copy); //0행 0열부터 ++되어 퍼즐 역추적 
		
		//초기 랜덤퍼즐에 keep 할 변수의 개수  = (전체 행 X 열)의 0.2 비율로 고정 
		int numberOfValuesToKeep = (int)(0.2*(copy.getNumRows() * copy.getNumRows())); 
		//초기 랜덤난수가 퍼즐 위에서 그려질 행,열 값도 랜덤으로 받음
		for(int i = 0;i < numberOfValuesToKeep;) {
			int randomRow = random.nextInt(puzzle.getNumRows()); //랜덤 행
			int randomColumn = random.nextInt(puzzle.getNumColumns()); //랜덤 열 
			
			if(puzzle.isSlotAvailable(randomRow, randomColumn)) { //이용가능한 슬롯에 한해서
				puzzle.makeMove(randomRow, randomColumn, copy.getValue(randomRow, 					randomColumn), false); //값 넣어주고 해당 슬롯 이용불가처리 
				i++;
			}
		}
		return puzzle;
	}
	//역추적 
    private boolean backtrackSudokuSolver(int r,int c, SudokuPuzzle puzzle) { 
    	//만약 유효하지 않은 영역일 경우 F리턴 
		if(!puzzle.inRange(r,c)) {
			return false;
		}
		//이용가능한 슬롯에 한해서 
		if(puzzle.isSlotAvailable(r, c)) { 

			//for루프
			for(int i = 0;i < puzzle.getValidValues().length;i++) { //퍼즐 객체 속 유효값 길이만큼 돌며
				
				//현재의 i의 값이 각 행, 열, 박스에 존재할 수 있는 값인 경우엥 한해서 
				if(!puzzle.numInRow(r, puzzle.getValidValues()[i]) && !puzzle.numInCol(c,puzzle.getValidValues()[i]) && !puzzle.numInBox(r,c,puzzle.getValidValues()[i])) {
					
					//해당 값 넣어주기 
					puzzle.makeMove(r, c, puzzle.getValidValues()[i], true); 
					
					if(puzzle.boardFull()) {  //퍼즐 full T리턴 
						return true;
					}
					
					//go to next move 다음 움직임으로 
				if(r == puzzle.getNumRows() - 1) {
					if(backtrackSudokuSolver(0,c + 1,puzzle)) return true;
				} else {
					if(backtrackSudokuSolver(r + 1,c,puzzle)) return true;
					}
				}
			}
		}
		
		//이용가능한 슬롯이 아닐 경우
		else {
			if(r == puzzle.getNumRows() - 1) {
				return backtrackSudokuSolver(0, c + 1,puzzle); //다음 열로 이동 
			} else {
				return backtrackSudokuSolver(r + 1,c,puzzle); //다음 행으로 이동 
			}
		}
		
		//움직임 X
		puzzle.makeSlotEmpty(r, c); //해당 퍼즐 슬롯은 공백입력
		
		//backtrack
		return false;
	}
}

728x90