package com.androidside.demo.tetris; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.View; public class BoardView extends View { static int squareLength; // square 의 크기 (in pixel) static int COLS = Tetris4Android.COLS; // columns static int ROWS = Tetris4Android.ROWS; // rows int colors[]; Rect rectClient = new Rect(); // 사용 가능한 화면 영 Rect rectBoard = new Rect(); // 순수하게 테트리스 판 영역 // 판의 내용 byte boardPrev[][]; // 직전 판의 내용 byte boardCurr[][]; // 현재 (변경 후) 판의 내용 Square pieceCurr[] = new Square[4]; // 현재의 블럭 private byte mode = READY; // 현재 게임의 상태 public static final byte PAUSE = 0; // 일시 중지 public static final byte READY = 1; // 게임 시작 전 준비 완료 public static final byte RUNNING = 2; // 게임 진행 중 public static final byte END = 3; // 게임 종료됨 public BoardView(Context context) { super(context); init(); } public BoardView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public BoardView(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 내부에서 사용되는 각종 내용에 대한 초기 설정 */ void init() { colors = new int[8]; // 내부에서 사용되는 색깔들을 지정한다. colors[0] = 0xff000060; // 배경색 // 블럭에서 사용되는 색깔 colors[1] = 0xffff0000; // red colors[2] = 0xff00c800; // green colors[3] = 0xff00c8ff; // light blue colors[4] = 0xffffff00; // yellow colors[5] = 0xffff9600; // orange colors[6] = 0xffd200f0; // purple colors[7] = 0xff2800f0; // dark bluer // 판을 저장하는 공간을 할당한다. boardPrev = new byte[Tetris4Android.COLS][Tetris4Android.ROWS + 4]; boardCurr = new byte[Tetris4Android.COLS][Tetris4Android.ROWS + 4]; // fillBoard(); showAllBlocks(); // cleanBoard(); } /* 영역에 표시가 잘 되는지 확인하기 위한 테스트용 */ private void fillBoard() { for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { boardCurr[c][r] = (byte) (((r + c) % (colors.length - 1)) + 1); } } } public byte getMode() { return mode; } public void setMode(byte mode) { this.mode = mode; } /* 모든 블럭을 생성하여 화면에 출력해 준다 */ private void showAllBlocks() { for (int i = 0; i <= 6; i++) { newBlock(i); moveCurrentPiece(0, (6 - i) * 3, false); } } /* 판의 모든 내용을 지우고 현재의 블럭도 초기화한다. 게임 시작 전 준비 단계이다. */ void cleanBoard() { for (int c = 0; c < COLS; c++) { for (int r = 0; r < ROWS; r++) { boardPrev[c][r] = -1; boardCurr[c][r] = 0; } } pieceCurr = new Square[4]; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 주어진 화면 영역을 바탕으로 실제 사용될 영역을 계산한다. int width = w / COLS; int height = h / ROWS; squareLength = Math.min(width, height); rectClient = new Rect(0, 0, w, h); int xoffset = (w - squareLength * COLS) / 2; int yoffset = (h - squareLength * ROWS) / 2; rectBoard = new Rect( xoffset, yoffset, xoffset + squareLength * COLS, yoffset + squareLength * ROWS); Log.v(Tetris4Android.TAG, " SQUARE LEN = " + squareLength); Log.v(Tetris4Android.TAG, "CLIENT RECT (" + rectClient.left + "," + rectClient.top + ")-(" + rectClient.right + "," + rectClient.bottom + ")"); Log.v(Tetris4Android.TAG, " BOARD RECT (" + rectBoard.left + "," + rectBoard.top + ")-(" + rectBoard.right + "," + rectBoard.bottom + ")"); } /* * 변경 전 위치에서 변경 후 위치로 옮긴다. 이 때, 옮겨질 수 있는지 여부도 확인한다. */ private boolean moveBlocks(Square from[], Square to[]) { outerlabel: for (int i = 0; i < to.length; i++) { if (!to[i].isInBounds()) { return false; } if (boardCurr[to[i].getX()][to[i].getY()] != 0) { for (int j = 0; j < from.length; j++) { if (to[i].isEqual(from[j])) { continue outerlabel; } } return false; } } // blank old piece for (int i = 0; i < from.length; i++) { if (from[i].isInBounds()) { boardCurr[from[i].getX()][from[i].getY()] = 0; boardPrev[from[i].getX()][from[i].getY()] = -1; } } for (int i = 0; i < to.length; i++) { boardCurr[to[i].getX()][to[i].getY()] = to[i].getColor(); } return true; } /* * 새로운 블럭을 생성한다. 만약, 새로운 블럭을 생성할 수 없다면 * (공간이 꽉 찬 경우) false 를 반환한다. */ protected boolean newBlock() { // 새로운 블럭이므로, 기존의 위치는 판 밖의 영역으로 지정한다. Square old[] = new Square[4]; old[0] = old[1] = old[2] = old[3] = new Square(-1, -1, (byte) 0); // 임의의 모양을 가져오도록 한다. int blockType = (int) (Math.random() * 7); // 블럭을 생성한다. newBlock(blockType); // 생성한 블럭을 이동한다. // 만약, 이동에 실패한다면(화면 중앙 상단에 새로운 블럭이 생성되는 위치에 // 이미 다른 블럭이 존재하는 경우) false 를 반환한다. return moveBlocks(old, pieceCurr); } /* * 화면 중앙 상단에 지정된 형태의 새로운 블럭을 생성한다. 생성된 블럭은 현재의 블럭으로 지정된다. */ private void newBlock(int type) { int m = COLS / 2; switch (type) { case 0: // #### pieceCurr[0] = new Square(m - 1, 0, (byte) 1); pieceCurr[1] = new Square(m - 2, 0, (byte) 1); pieceCurr[2] = new Square(m, 0, (byte) 1); pieceCurr[3] = new Square(m + 1, 0, (byte) 1); break; case 1: // ### // # pieceCurr[0] = new Square(m, 0, (byte) 5); pieceCurr[1] = new Square(m, 1, (byte) 5); pieceCurr[2] = new Square(m - 1, 0, (byte) 5); pieceCurr[3] = new Square(m + 1, 0, (byte) 5); break; case 2: // ## // ## pieceCurr[0] = new Square(m, 0, (byte) 2); pieceCurr[1] = new Square(m - 1, 1, (byte) 2); pieceCurr[2] = new Square(m, 1, (byte) 2); pieceCurr[3] = new Square(m + 1, 0, (byte) 2); break; case 3: // ## // ## pieceCurr[0] = new Square(m, 0, (byte) 7); pieceCurr[1] = new Square(m + 1, 1, (byte) 7); pieceCurr[2] = new Square(m, 1, (byte) 7); pieceCurr[3] = new Square(m - 1, 0, (byte) 7); break; case 4: // ## // ## pieceCurr[0] = new Square(m - 1, 1, (byte) 3); pieceCurr[1] = new Square(m, 1, (byte) 3); pieceCurr[2] = new Square(m - 1, 0, (byte) 3); pieceCurr[3] = new Square(m, 0, (byte) 3); break; case 5: // # // ### pieceCurr[0] = new Square(m, 1, (byte) 6); pieceCurr[1] = new Square(m - 1, 1, (byte) 6); pieceCurr[2] = new Square(m + 1, 1, (byte) 6); pieceCurr[3] = new Square(m + 1, 0, (byte) 6); break; case 6: // # // ### pieceCurr[0] = new Square(m, 1, (byte) 4); pieceCurr[1] = new Square(m + 1, 1, (byte) 4); pieceCurr[2] = new Square(m - 1, 1, (byte) 4); pieceCurr[3] = new Square(m - 1, 0, (byte) 4); break; } } /** * 현재의 블럭을 이동한다. * * @param byx * 수평 이동 변위 * @param byy * 수직 이동 변위 * @param rotate * 회전 여부 * * @return 이동 가능한 경우 true, 그렇지 못할 경우 false */ synchronized boolean moveCurrentPiece(int byx, int byy, boolean rotate) { Square newpos[] = new Square[4]; for (int i = 0; i < 4; i++) { if (rotate) { int dx = pieceCurr[i].getX() - pieceCurr[0].getX(); int dy = pieceCurr[i].getY() - pieceCurr[0].getY(); newpos[i] = new Square(pieceCurr[0].getX() + dy, pieceCurr[0].getY() - dx, pieceCurr[i].getColor()); } else { newpos[i] = new Square(pieceCurr[i].getX() + byx, pieceCurr[i].getY() + byy, pieceCurr[i].getColor()); } } if (!moveBlocks(pieceCurr, newpos)) return false; pieceCurr = newpos; return true; } /** * 화면에 그리는 부분이다. */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.v(Tetris4Android.TAG, "DRAWING BOARD"); Paint p = new Paint(); p.setColor(0xffffffff); canvas.drawRect(rectClient, p); p.setColor(colors[0] & 0xe0ffffff); canvas.drawRect(rectBoard, p); for (int c = 0; c < COLS; c++) { for (int r = 0; r < ROWS; r++) { { p.setColor(colors[boardCurr[c][r]]); // Log.v(Tetris4Android.TAG4LOG, // "("+rectSquare.left+","+rectSquare.top+")-("+rectSquare.right+","+rectSquare.bottom+") COLOR = " // + colors[boardCurr[c][r]]); // RoundRect { RectF rectSquare = new RectF( rectBoard.left + squareLength * c, rectBoard.top + squareLength * r , rectBoard.left + squareLength * (c + 1) - 1, rectBoard.top + squareLength * (r + 1) - 1 ); canvas.drawRoundRect(rectSquare, 4F, 4F, p); } // // BasicRect // { // Rect rectSquare = new Rect( // rectBoard.left + squareLength * c, rectBoard.top + squareLength * r // , rectBoard.left + squareLength * (c + 1) - 1, rectBoard.top + squareLength * (r + 1) - 1 // ); // canvas.drawRect(rectSquare, p); // } boardPrev[c][r] = boardCurr[c][r]; } } } } /* 채워진 라인이 있다면 지운다. */ void removeCompleteLines() { for (int r = ROWS - 1; r >= 0; r--) { int c; for (c = 0; c < COLS; c++) { if (boardCurr[c][r] <= 0) // 빈 공간이 있다면, 더 살펴볼 필요가 없다. break; } // 채워졌다면... if (c == COLS) { for (int k = r; k > 0; k--) { for (int l = 0; l < COLS; l++) { boardCurr[l][k] = boardCurr[l][k - 1]; } } // 현재 한 줄이 올라갔으므로, 다시 현재의 줄을 검사한다. r++; } } } /* 현재의 상태를 저장할 수 있도록 한다. 이것은 restoreState() 와 쌍을 이루어야 한다. */ public Bundle saveState() { Bundle map = new Bundle(); byte board[] = new byte[COLS * ROWS]; for (int c = 0; c < COLS; c++) { for (int r = 0; r < ROWS; r++) { board[r * COLS + c] = boardCurr[c][r]; } } map.putByte("mode", mode); map.putByteArray("boardCurr", board); return map; } /* 현재의 상태를 복원한다. 이것은 saveState() 와 쌍을 이루어야 한다. */ public void restoreState(Bundle map) { setMode(PAUSE); byte board[]; board = map.getByteArray("boardCurr"); for (int c = 0; c < COLS; c++) { for (int r = 0; r < ROWS; r++) { boardCurr[c][r] = board[r * COLS + c]; } } setMode(map.getByte("mode")); } }