자바fx 테트리스 - jabafx teteuliseu

안뇽하세요 부르곰입니다. 

자바fx 테트리스 - jabafx teteuliseu

테트리스에서 발생하는 에러들을 계속 찾고 있는데요

아직도 계속 문제가 발생하네요 ㅠㅠㅠ

우선 맨처음 해결하지 못한 블럭이 나오지 않는 것은 해결이 되었어요!!!

무려!!!!.......... 코드를 2줄 빼먹었더라구요......

Main.java에

package main.agl.gui;

import java.net.URL;

import java.util.ResourceBundle;

import main.agl.app.GameController;

import javafx.application.Application;

import javafx.fxml.FXMLLoader;

import javafx.scene.Parent;

import javafx.scene.Scene;

import javafx.stage.Stage;

public class Main extends Application {

@Override

public void start(Stage primaryStage) throws Exception {

URL location = getClass().getClassLoader().getResource("main/resources/GameLayout.fxml");

ResourceBundle rb = null;

FXMLLoader fl = new FXMLLoader(location, rb);

Parent root = fl.load();

GuiController c = fl.getController();

primaryStage.setTitle("JavaFX Tetris");

Scene scene = new Scene(root, 400, 510);

primaryStage.setScene(scene);

primaryStage.show();

        new GameController(c); // 이 두줄을 추가하니까 실행이 잘됩니다.

}

public static void main(String args[]){

launch(args);

}

}

블럭이 저기 이하로 내려오지 않고 계속 에러가 발생하더라구요..

계속 찾고 있는 중이니 빠르시일 내에 해결하겠씁니다!!

자바fx 테트리스 - jabafx teteuliseu
 

안녕하세요 부르곰입니다. 

자바fx 테트리스 - jabafx teteuliseu

오늘은 테트리스를 위한 메인 레이아웃을 만들어 보았습니다.

GitHUb에 올라온 오픈 소스를 참조했습니다. 

뭐 참조라기보단 그대로 따라해 본 것이지만요 ㅎㅎ

오픈 소스에서 빠진 코드가 있어서 찾느라 고생좀 했습니다.

자바fx 테트리스 - jabafx teteuliseu
 

이제 기본 틀은 만들었으니 블록을 만들어야겠죠?

차차 블록을 만들어보겠습니다.

그리고 오늘은 발렌타인 데이인데요~~
모두 즐거운 발렌타인 데이되세요~~~~

자바fx 테트리스 - jabafx teteuliseu
 

그렇다면 본격적으로 개발을 시작해봅시다. 먼저 게임에 대한 전반적인 로직을 담당할 Game 클래스를 만들어보겠습니다. net.gondr.tetris 패키지에 Game 클래스를 다음과 같이 작성합니다.

package net.gondr.tetris;

import javafx.animation.AnimationTimer;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import net.gondr.domain.Block;
import net.gondr.domain.Player;

public class Game {
	private GraphicsContext gc;
	public Block[][] board;
	
	private double width;
	private double height;
	
	private AnimationTimer mainLoop; //메인루프
	private long before; //이전시간 기록변수
	
	private Player player;
	private double blockDownTime = 0;
	
	private int score = 0;
	
	public Game(Canvas canvas) {
		//캔버스의 너비와 높이를 가져온다.
		width = canvas.getWidth();
		height = canvas.getHeight();
		
		double size = (width - 4) / 10;
		
		board = new Block[20][10]; //게임 판 만들고
		
		for(int i = 0; i < 20; i++) {
			for(int j = 0; j < 10; j++) {
				board[i][j] = new Block(j * size + 2, i * size + 2, size);
			}
		}
		
		this.gc = canvas.getGraphicsContext2D();
		
	}
	
	//업데이트 매서드
	public void update(double delta) {
		//매 프레임마다 실행되는 update매서드 블럭의 자동하강 로직을 담당.
	}
	
	public void checkLineStatus() {
		//라인이 꽉 찼는지 체크해주는 매서드
	}
	
	//렌더 메서드
	public void render() {
		//매 프레임마다 화면을 그려주는 메서드
	}
	
	public void keyHandler(KeyEvent e) {
		player.keyHandler(e); //키보드 핸들링을 담당하는 매서드
	}
	
}

아직 안쓰이는 멤버변수들이 꽤 많은 것입니다. 여태까지 배웠던 것을 제외하고 설명을 해보면 Block[][] 은 2차원 배열로 테트리스의 게임판 역할을 할 것입니다. render 매서드에서는 이 block 배열을 for문으로 순회하며 전부 그려줄 것입니다. width와 height는 캔버스의 크기를 측정하고 블럭들의 크기를 만들기 위해 측정한 변수입니다. 사실 고정적으로 캔버스의 크기를 활용할 경우에는 이부분을 크게 신경쓰지 않아도 됩니다. 차후 유동적으로 캔버스 크기가 변경될 것을 감안하여 제작하였습니다.

처음으로 나오는 AnimationTimer는 javafx에서 일정시간 간격으로 계속해서 작업을 실행해야할 때 쓰이는 쓰레드입니다. 이 쓰레드에서 update와 render를 담당할 것입니다. 게임에서는 이것을 프레임이라고 합니다.

before변수는 이 mainLoop와 연관이 있습니다. 해당 프레임이 이전 프레임 실행 후 몇 초후에 실행된 것인지를 판단하기 위해서는 이전 프레임이 실행되었던 시간을 알고 있어야 합니다. 이것을 저장할 변수가 바로 before입니다.

Player 형 변수 player는 게임의 플레이어에 관한 것들이 모여있습니다. 여기서는 플레이어가 직접 조정하는 블럭을 의미합니다.

blockDownTime은 블럭이 자동으로 내려올때까지 걸리는 시간입니다. 이 시간이 0.5초에 도달하면 자동으로 블럭이 한칸씩 내려오게 됩니다. 이 0.5초라는 시간은 고정이 아니라 게임을 진행하며 스코어가 올라갈 수록 속도를 조정하는 작업을 해주어도 됩니다. 마지막으로 score 변수는 말그대로 점수변수입니다. 테트리스에서 한 줄을 없앨때마다 score가 1점씩 오르게 됩니다.

다만 지금은 구현도 구현이거니와 Player와 Block이라는 클래스가 없기 때문에 해당 코드는 에러를 내게 됩니다.

테트리스는 겉으로는 복잡해보이지만 실제 내부적으로 보면 2차원 배열을 실시간으로 그려주는 역할을 하는 프로그램집합에 불과합니다. 아래 그림과 같이 말이죠.

자바fx 테트리스 - jabafx teteuliseu

해당 클래스들을 만들어봅시다. 패키지로 net.gondr.domain 이라는 패키지를 만들어줍니다. 그리고 그 안에

Player 클래스를 다음과 같이 작성합니다.

package net.gondr.domain;

import java.util.Random;

import javafx.geometry.Point2D;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import net.gondr.tetris.App;

public class Player {
	private Point2D[][][] shape = new Point2D[7][][]; //총 7개의 테트리스 블럭이 존재함.
	private int current = 0; //현재 모양
	private int rotate = 0; //현재 회전상태
	private int nowColor = 0;
	private Color[] colorSet = new Color[7]; //총 7개 색깔
	
	private Random rnd;
	
	private int x = 5;
	private int y = 2;
	
	private Block[][] board;
	
	public Player(Block[][] board) {
		this.board = board;
		//작대기
		//네모
		//ㄴ 모양
		// 역 ㄴ 모양		
		// _┌━ 모양
		// ─┐_ 모양
		// ㅗ 모양
		//색상 넣기
		colorSet[0] = Color.ALICEBLUE;
		colorSet[1] = Color.AQUAMARINE;
		colorSet[2] = Color.BEIGE;
		colorSet[3] = Color.BLUEVIOLET;
		colorSet[4] = Color.CORAL;
		colorSet[5] = Color.CRIMSON;
		colorSet[6] = Color.DODGERBLUE;
		
		rnd = new Random();
		current = rnd.nextInt(shape.length);
		nowColor = rnd.nextInt(colorSet.length);
		
		draw(false);
	}
	
	private void draw(boolean remove) {
		//블럭을 판에서 표시해주거나 없애주는 매서드
	}
	
	public Point2D[] getPointArray(String pointStr) {
	// 0,-1:0,0:0,1:0,2 형식으로 데이터가 들어오면 해당 데이터를 Point 객체 배열로 변경해주는 매서드
		Point2D[] arr = new Point2D[4];
		String[] pointList = pointStr.split(":");
		for(int i = 0; i < pointList.length; i++) {
			String[] point = pointList[i].split(","); //컴마를 기준으로 나누고 
			double x = Double.parseDouble(point[0]);
			double y = Double.parseDouble(point[1]); //x,y좌표를 숫자로 변경해서
			arr[i] = new Point2D(x, y);
		}
		return arr;
	}
	
	public void keyHandler(KeyEvent e) {
		//키보드 입력을 처리하는 매서드
	}
	
	private void move(int dx, int dy, boolean rot) {
		//블럭을 이동시키는 매서드
	}
	
	public boolean down() {
		//블럭을 한칸 아래로 내리는 매서드
		return false;
	}
	
	private void getNextBlock() {
		//다음블럭 가져와서 초기화
		current = rnd.nextInt(shape.length);
		nowColor = rnd.nextInt(colorSet.length);
		x = 5;
		y = 2;
		rotate = 0;
	}
	
	private boolean checkPossible() {
		//블럭의 이동이 가능한지 체크하는 매서드
		return true;		
	}
}

가장 핵심이 되는 shape는 모든 테트리스 블럭의 모양을 담고있는 3차원 배열이 될 것입니다. 따라서 총 7개의 블럭에, 각 블럭이 가지고 있는 모양의 갯수와, 각 모양별로 블럭의 위치를 기억하고 있어야 하기 때문에 모양 X 개수 X 블럭위치의 3차원 배열로 만들어져 있습니다.

current, rotate, nowColor는 현재 플레이어가 조종하고 있는 블럭이 현재 무엇인지, 회전상태는 어떤지, 색상은 어떤지를 나타내주는 변수입니다.

colorSet은 블럭이 나올때마다 7가지 색상중에 랜덤한 색상이 선택되어 나오도록 만들어진 배열입니다. 그리고 rnd는 블럭의 선정부터 색상의 선정까지를 랜덤하게 선택하기 위해 사용하는 랜덤객체입니다.

x, y는 현재 플레이어가 조종하고 있는 블록의 위치를 나타냅니다. 마지막으로 board 배열은 Game에 있던 board 배열과 동일합니다. 생성자에서 이 배열을 받아 함께 저장해서 플레이어 객체에서도 board에 있는 값들을 참조할 수 있도록 할 것입니다.

현재 생성자에서는 별도의 블럭을 생성하지 않고 블럭처리부분은 모두 주석처리되어 있습니다. 다만 생성자로 받은 board를 자신의 board에 넣어주고 랜덤 객체를 생성하고, 컬러셋의 값들을 적당한 컬러로 채워넣어 주었습니다.

getPointArray 매서드는 차후 블럭 생성을 좀 더 편하게 하기 위해서 만든 매서드입니다. 일렬로 된 스트링을 받아서 해당 정보로 Point 객체를 만들어서 반환해주는 것입니다. 예를 들어 다음과 같은 테트리스 블럭이 있습니다.

아래의 블럭은 흔히 작대기로 불리는 블럭입니다. 이 블럭의 중심점을 두번째 칸으로 잡고 중심점의 좌표를 0,0으로 한다면 해당 블럭은 0,-1 0,0 0,1 0,2 의 4개의 블럭으로 이루어진 것으로 볼 수 있습니다. 이것을 0,-1:0,0:0,1:0,2 의 문자열로 표시하려 합니다. 그리고 이 문자열을 getPointArray매서드에 보내면 각각의 점객체들로 만들어서 배열로 리턴해줄 것입니다.

자바fx 테트리스 - jabafx teteuliseu

keyHandler, move, down 매서드는 모두 키보드 입력을 받아 플레이어가 조정하는 블럭을 이동시키기 위한 매서드들입니다.

getNextBlock은 블럭을 사용하고 난뒤 다음 블럭을 가져오는 매서드로 새로운 블럭을 랜덤하게 뽑고 해당 회전과 x, y값을 초기화해주는 역할을 하게 됩니다.

마지막으로 checkPossible 매서드는 블럭이 음직일 수 있는 지를 체크해주는 매서드입니다. 현재 블럭이 움직였을 때 이동이 가능한 상태인지를 체크해주는 매서드입니다. 블럭의 4개 요소 모두가 겹치는 부분없이 모두 배치가 가능한지를 체크하고 이중 하나라도 화면밖을 벗어나거나 겨치게되면 false를 반환하고 아니면 true를 반환하는 방식입니다.

Block을 찾을 수 없기에 아마도 에러가 나고 있을 것입니다. 다음과 같이 Block.java를 net.gondr.domain 패키지에 넣어줍시다.

package net.gondr.domain;

import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

public class Block {
	private Color color;
	private boolean fill;
	private double x;
	private double y;
	private double size;
	private double borderSize;
	
	public Block(double x, double y, double size) {
		color = Color.WHITE;
		fill = false;
		this.x = x;
		this.y = y;
		this.size = size;
		this.borderSize = 2;
	}
	
	public void render(GraphicsContext gc) {
		if(fill) {
			gc.setFill(color.darker());
			gc.fillRoundRect(x, y, size, size, 4, 4);
			
			gc.setFill(color);
			gc.fillRoundRect(x+this.borderSize, y+this.borderSize, size - 2 * this.borderSize, size - 2 * this.borderSize, 4, 4);
		}
	}
	
	public void setData(boolean fill, Color color) {
		this.fill = fill;
		this.color = color;
	}
	
	public boolean getFill() {
		return fill;
	}
	
	public Color getColor() {
		return color;
	}
	
	//블럭데이터 카피
	public void copyData(Block block) {
		this.fill = block.getFill();
		this.color = block.getColor();
	}
}

뭔가 많이 만들었는데 아직 나온 것은 없습니다. MainController.java를 다음과 같이 코드를 작성해줍니다.

package net.gondr.views;

import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;

public class MainController {
	@FXML
	private Canvas gameCanvas;
	
	@FXML
	public void initialize() {
		System.out.println("메인 레이아웃 초기화");
		
	}
}

그리고 메인 레이아웃은 다음과 같이 간단하게 캔버스 하나만 설치하고 끝냅니다.








   
      
   

여기까지 작성했다면 실행은 가능합니다. 다만 정상적인 아무것도 나오지 않고 하얀 화면만 나올 것입니다.

게임 객체가 만들어졌으니 App에서 항상 이 게임 객체를 접근할 수 있도록 싱글톤 방식으로 디자인할 것입니다. 아울러 추가적으로 scene에서 입력되는 모든 키 바인딩을 게임으로 연결시켜줄 수 있도록 할 것입니다. App.java를 다음과 같이 작성합니다.

package net.gondr.tetris;

//임포트 부분 생략
public class App extends Application
{
	public static App app; //싱글톤 기법을 활용하기 위한 스태틱 변수
	public Game game = null;
	
//중간 생략
	@Override
	public void start(Stage primaryStage) throws Exception {
		try {
			app = this; //스태틱 변수에 자기자신을 넣어서 싱글톤으로 활용함.
			FXMLLoader loader = new FXMLLoader();
			loader.setLocation(getClass().getResource("/net/gondr/views/Main.fxml"));
			AnchorPane anchorPane = (AnchorPane)loader.load();
			
			Scene scene = new Scene(anchorPane);
			
			scene.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
				if(game != null)
					game.keyHandler(e); //게임의 이벤트 핸들러로 넘겨준다.
			});
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
		
	}
}

새롭게 추가된 코드를 눈여겨 봅시다. 싱글톤이라는 말이 처음 나왔습니다. 싱글톤 기법은 프로그램을 작성할 때 빈번하게 사용되는 디자인 패턴입니다.

우리가 만들 프로그램에서 Game 객체는 단 하나만 생성됩니다. 하나의 테트리스 어플리케이션에 2개의 게임 객체는 존재할 수 없습니다. 또한 이 Game객체는 대부분의 테트리스 모듈들에서 참조가 이뤄져야합니다. 예를 들어 플레이어는 키입력시 게임안에 있는 보드판을 참조해야합니다. 이런식으로 한개만 생성되며 다른 곳에서 참조가 빈번하게 이뤄질 경우 싱글톤 패턴으로 만드는 것이 편합니다. (이번 테트리스 게임에서는 굳이 사용하지 않아도 됩니다만 강의를 위해 써보았습니다.)

자바fx 테트리스 - jabafx teteuliseu

그렇다면 싱글톤 패턴은 어떻게 만들어지는 걸까요? 간단하게 static을 활용합니다. 위의 예제에서는 App 클래스 자체를 싱글톤으로 만들었는데요. App 클래스 내에 static 변수로 app 을 만들었습니다. 그리고 맨 처음 어플리케이션이 만들어지는 순간 이 static변수에 첫번째로 생성된 인스턴스를 넣어줍니다. (start매서드는 오직 한번만 실행되기 때문에 이 첫번째로 만들어진 인스턴스가 처음이자 마지막 인스턴스입니다.)

이제 어디서든 App.app 변수를 활용하여 이 앱변수에 접근할 수 있게 되었습니다.

여기에 게임 객체를 할당하는 것은 MainController에서 하도록 하겠습니다. MainController.java에 다음과 같이 Game객체를 할당합니다.

@FXML
public void initialize() {
	System.out.println("메인 레이아웃 초기화");
	App.app.game = new Game(gameCanvas);
}

위의 매서드는 FXML 상의 모든 컴포넌트가 로드가 완료된뒤에 실행되게 되는 매서드인 initialize이기 때문에 gameCanvas가 올바르게 생성되어 바인딩되고 난 이후에 game을 생성할 수 있게 됩니다. (이 작업을 App.java에서 하게 되면 gameCanvas에 접근할 수도 없을 뿐더러 코드의 위치에 따라 올바르게 생성되지 않을 수도 있습니다.

게임을 생성했으니 게임이 돌아가도록 해야겠지요? Animation타이머를 이용하여 게임루프가 가동되도록 Game.java를 변경해봅시다.

public Game(Canvas canvas) {
	//캔버스의 너비와 높이를 가져온다.
	width = canvas.getWidth();
	height = canvas.getHeight();
	
	double size = (width - 4) / 10;
	
	board = new Block[20][10]; //게임 판 만들고
	
	for(int i = 0; i < 20; i++) {
		for(int j = 0; j < 10; j++) {
			board[i][j] = new Block(j * size + 2, i * size + 2, size);
		}
	}
	
	this.gc = canvas.getGraphicsContext2D();
	
	mainLoop = new AnimationTimer() {
		@Override
		public void handle(long now) { //now는 나노초 단위로 들어옴.
			update( (now - before) / 1000000000d );
			before = now;
			render();
		}
	};
	
	before = System.nanoTime();		
	
	//플레이어 모양 설정
	player = new Player(board);
	mainLoop.start();
}

하이라이트된 부분을 보면 애니메이션 타이머라는 것을 만들고 있는 것을 볼 수 있습니다. 이 애니메이션 타이머를 익명클래스 구현을 통해서 만들어지고 있습니다. 이 타이머는 start매서드를 걸어주는 순간부터 지속적으로 실행되며 실행시마다 자신의 실행시간을 nano시간단위로 입력해서 들어옵니다. 이를 통해 프로그래머는 이전 실행과 지금 실행간의 시간차를 구해서 프레임의 시간을 측정할 수 있게 됩니다.

여기서는 handle매서드에서 이전시간과의 격차를 측정한후 이를 이용해 update를 실행해주고 있고 그뒤에 render 매서드를 실행해주고 있습니다.

(AnimationTimer는 인터페이스가 아닙니다. start, stop 등의 다른 매서드도 가지고 있는 클래스입니다. 따라서 이를 람다식으로 만들 수는 없습니다.)

아직 업데이트에서는 해 줄 일이 없으니 render를 먼저 만들어보도록 하겠습니다.

//렌더 메서드
public void render() {
	//스테이지 그리기
	gc.clearRect(0, 0, width, height); //전부 지우고 새로 그리기
	gc.setStroke(Color.rgb(0, 0, 0)); //검은색으로 외곽선 그리고
	gc.setLineWidth(2);
	gc.strokeRect(0, 0, width, height);
	
	for(int i = 0; i < 20; i++) {
		for(int j = 0; j < 10; j++) {
			board[i][j].render(gc);
		}
	}
}

실행시키면 스테이지의 사각형이 그려진 상태만 있을 것입니다. 실험삼아 여기에 블럭을 몇개 셋팅해서 제대로 그려지는지 확인해보겠습니다. Game의 생성자에 다음과 같은 4줄의 테스트 코드를 삽입해봅시다.

board[15][1].setData(true, Color.BEIGE);
board[15][2].setData(true, Color.BEIGE);
board[15][3].setData(true, Color.BEIGE);
board[15][4].setData(true, Color.BEIGE);

실행해보면 다음과 같이 베이지색 블럭 4개가 생긴것을 볼 수 있습니다.

자바fx 테트리스 - jabafx teteuliseu

이전에 만든 Block의 render매서드를 보면 왜 이렇게 그려지는지 알 수 있습니다.

package net.gondr.domain;

import java.util.Random;

import javafx.geometry.Point2D;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import net.gondr.tetris.App;

public class Player {
	private Point2D[][][] shape = new Point2D[7][][]; //총 7개의 테트리스 블럭이 존재함.
	private int current = 0; //현재 모양
	private int rotate = 0; //현재 회전상태
	private int nowColor = 0;
	private Color[] colorSet = new Color[7]; //총 7개 색깔
	
	private Random rnd;
	
	private int x = 5;
	private int y = 2;
	
	private Block[][] board;
	
	public Player(Block[][] board) {
		this.board = board;
		//작대기
		//네모
		//ㄴ 모양
		// 역 ㄴ 모양		
		// _┌━ 모양
		// ─┐_ 모양
		// ㅗ 모양
		//색상 넣기
		colorSet[0] = Color.ALICEBLUE;
		colorSet[1] = Color.AQUAMARINE;
		colorSet[2] = Color.BEIGE;
		colorSet[3] = Color.BLUEVIOLET;
		colorSet[4] = Color.CORAL;
		colorSet[5] = Color.CRIMSON;
		colorSet[6] = Color.DODGERBLUE;
		
		rnd = new Random();
		current = rnd.nextInt(shape.length);
		nowColor = rnd.nextInt(colorSet.length);
		
		draw(false);
	}
	
	private void draw(boolean remove) {
		//블럭을 판에서 표시해주거나 없애주는 매서드
	}
	
	public Point2D[] getPointArray(String pointStr) {
	// 0,-1:0,0:0,1:0,2 형식으로 데이터가 들어오면 해당 데이터를 Point 객체 배열로 변경해주는 매서드
		Point2D[] arr = new Point2D[4];
		String[] pointList = pointStr.split(":");
		for(int i = 0; i < pointList.length; i++) {
			String[] point = pointList[i].split(","); //컴마를 기준으로 나누고 
			double x = Double.parseDouble(point[0]);
			double y = Double.parseDouble(point[1]); //x,y좌표를 숫자로 변경해서
			arr[i] = new Point2D(x, y);
		}
		return arr;
	}
	
	public void keyHandler(KeyEvent e) {
		//키보드 입력을 처리하는 매서드
	}
	
	private void move(int dx, int dy, boolean rot) {
		//블럭을 이동시키는 매서드
	}
	
	public boolean down() {
		//블럭을 한칸 아래로 내리는 매서드
		return false;
	}
	
	private void getNextBlock() {
		//다음블럭 가져와서 초기화
		current = rnd.nextInt(shape.length);
		nowColor = rnd.nextInt(colorSet.length);
		x = 5;
		y = 2;
		rotate = 0;
	}
	
	private boolean checkPossible() {
		//블럭의 이동이 가능한지 체크하는 매서드
		return true;		
	}
}
0

fill 이 true로 설정되어 있으면 해당 색상의 어두운 색을 darker매서드로 구해서 한번 칠해주고 보더라인인 2px만큼 줄여서 다시 원래의 색상으로 칠하여서 마치 블럭같은 그낌을 주도록 그렸습니다. 이에 따라 위의 모양이 출력되는 것입니다. 또한 출력의 로직은 Block에게 맡겨서 게임은 그저 Block 객체의 render를 실행해주기만 했습니다.

이제 테스트 코드는 지우고 실제적으로 움직이는 플레이어를 만들어봅시다.

Player.java의 생성자에 다음과 같이 플레이어 블럭들을 만들어 줍니다.

package net.gondr.domain;

import java.util.Random;

import javafx.geometry.Point2D;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import net.gondr.tetris.App;

public class Player {
	private Point2D[][][] shape = new Point2D[7][][]; //총 7개의 테트리스 블럭이 존재함.
	private int current = 0; //현재 모양
	private int rotate = 0; //현재 회전상태
	private int nowColor = 0;
	private Color[] colorSet = new Color[7]; //총 7개 색깔
	
	private Random rnd;
	
	private int x = 5;
	private int y = 2;
	
	private Block[][] board;
	
	public Player(Block[][] board) {
		this.board = board;
		//작대기
		//네모
		//ㄴ 모양
		// 역 ㄴ 모양		
		// _┌━ 모양
		// ─┐_ 모양
		// ㅗ 모양
		//색상 넣기
		colorSet[0] = Color.ALICEBLUE;
		colorSet[1] = Color.AQUAMARINE;
		colorSet[2] = Color.BEIGE;
		colorSet[3] = Color.BLUEVIOLET;
		colorSet[4] = Color.CORAL;
		colorSet[5] = Color.CRIMSON;
		colorSet[6] = Color.DODGERBLUE;
		
		rnd = new Random();
		current = rnd.nextInt(shape.length);
		nowColor = rnd.nextInt(colorSet.length);
		
		draw(false);
	}
	
	private void draw(boolean remove) {
		//블럭을 판에서 표시해주거나 없애주는 매서드
	}
	
	public Point2D[] getPointArray(String pointStr) {
	// 0,-1:0,0:0,1:0,2 형식으로 데이터가 들어오면 해당 데이터를 Point 객체 배열로 변경해주는 매서드
		Point2D[] arr = new Point2D[4];
		String[] pointList = pointStr.split(":");
		for(int i = 0; i < pointList.length; i++) {
			String[] point = pointList[i].split(","); //컴마를 기준으로 나누고 
			double x = Double.parseDouble(point[0]);
			double y = Double.parseDouble(point[1]); //x,y좌표를 숫자로 변경해서
			arr[i] = new Point2D(x, y);
		}
		return arr;
	}
	
	public void keyHandler(KeyEvent e) {
		//키보드 입력을 처리하는 매서드
	}
	
	private void move(int dx, int dy, boolean rot) {
		//블럭을 이동시키는 매서드
	}
	
	public boolean down() {
		//블럭을 한칸 아래로 내리는 매서드
		return false;
	}
	
	private void getNextBlock() {
		//다음블럭 가져와서 초기화
		current = rnd.nextInt(shape.length);
		nowColor = rnd.nextInt(colorSet.length);
		x = 5;
		y = 2;
		rotate = 0;
	}
	
	private boolean checkPossible() {
		//블럭의 이동이 가능한지 체크하는 매서드
		return true;		
	}
}
1

테트리스에서 사용하는 7종의 블럭들을 전부 넣어주었습니다. 다만 3가지 모양에 대해서는 틀만 잡고 값을 넣지 않았습니다. 입력된 값들의 패턴을 이해하고 여러분이 직접 만들어보기 바랍니다.