Puzzle game android with openCV
Tạo game đơn giản trên android với openCV giới thiệu giới thiệu về openCV trong android và cách import cơ bản cho openCV đã được giới thiệu trong loạt bài trước của bạn Võ Tuấn Dũng. Nên mình sẽ không nói lại nữa: http://viblo.framgia.vn/dzung.votuan/posts/57rVRqYVR4bP Và sau đây mình sẽ ...
Tạo game đơn giản trên android với openCV
- giới thiệu
giới thiệu về openCV trong android và cách import cơ bản cho openCV đã được giới thiệu trong loạt bài trước của bạn Võ Tuấn Dũng. Nên mình sẽ không nói lại nữa: http://viblo.framgia.vn/dzung.votuan/posts/57rVRqYVR4bP Và sau đây mình sẽ đi vào chi tiết cách làm cho app đố vui, sử dụng camera và openCV trong android. Trò chơi ghép hình đã quá quen thuộc với các bạn từ khi còn nhỏ. Các miếng ghép được sắp xếp lộn xộn, và chỉ có 1 vị trí trống, nhiệm vụ của các bạn là di chuyển các ô, để trở thành 1 bức tranh hoàn chỉnh. 2. Tạo ứng dụng
2.1 khởi tạo app và import opencv lib
tạo mới 1 project android, và import file opencv jar trong thư mục libs. 2.2 Khai báo trong file Manifest
Chúng ta cần khai báo các thuộc tính sau để có thể sử dụng camera trên thiết bị android:
<uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> <uses-feature android:name="android.hardware.camera.front" android:required="false" /> <uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false" />
2.3 Class Puzzle15Processor
Class này sẽ chứa chủ yếu các xử lí của game.
Tạo các biến cơ bản:
private static final int GRID_SIZE = 4; private static final int GRID_AREA = GRID_SIZE * GRID_SIZE; private static final int GRID_EMPTY_INDEX = GRID_AREA - 1; private static final String TAG = "Puzzle15Processor"; private static final Scalar GRID_EMPTY_COLOR = new Scalar(0x33, 0x33, 0x33, 0xFF); private int[] mIndexes; private int[] mTextWidths; private int[] mTextHeights; private Mat mRgba15; private Mat[] mCells15;
Khởi tạo class:
public Puzzle15Processor() { mTextWidths = new int[GRID_AREA]; mTextHeights = new int[GRID_AREA]; mIndexes = new int [GRID_AREA]; for (int i = 0; i < GRID_AREA; i++) mIndexes[i] = i; }
2.4 Oncreate Trở lại MainActivity. Implement các hàm cơ bản trong onCreate:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); mOpenCvCameraView = (CameraBridgeViewBase) new JavaCameraView(this, -1); // set view cho mainActivity là giao diện hiển thị từ camera setContentView(mOpenCvCameraView); mOpenCvCameraView.setCvCameraViewListener(this); mPuzzle15 = new Puzzle15Processor(); mPuzzle15.prepareNewGame(); }
trong hàm này, chúng ta sử dụng hàm prepareNewGame dùng để khởi tạo màn hình đầu tiên cho game.
public synchronized void prepareNewGame() { do { shuffle(mIndexes); } while (!isPuzzleSolvable()); }
Với hàm xáo trộn vị trí các miếng ghép của trò chơi: shuffle(mIndexes) và hàm check khả năng win của màn hình với miếng ghép đã bị xáo trộn: isPuzzleSolvable(). Nếu không có khả năng win, sẽ tiếp tục xáo trộn.
private static void shuffle(int[] array) { for (int i = array.length; i > 1; i--) { int temp = array[i - 1]; int randIx = (int) (Math.random() * i); array[i - 1] = array[randIx]; array[randIx] = temp; } }
private boolean isPuzzleSolvable() { int sum = 0; for (int i = 0; i < GRID_AREA; i++) { if (mIndexes[i] == GRID_EMPTY_INDEX) sum += (i / GRID_SIZE) + 1; else { int smaller = 0; for (int j = i + 1; j < GRID_AREA; j++) { if (mIndexes[j] < mIndexes[i]) smaller++; } sum += smaller; } } return sum % 2 == 0; } }
2.5 onCameraViewStarted Set view cho game khi nhận được dữ liệu màn hình từ camera:
public void onCameraViewStarted(int awidth, int height) { mGameWidth = awidth; mGameHeight = height; mPuzzle15.prepareGameSize(awidth, height); }
Chuẩn bị game size cho game tương ứng với camera:
public synchronized void prepareGameSize(int awidth, int height) { mRgba15 = new Mat(height, awidth, CvType.CV_8UC4); mCells15 = new Mat[GRID_AREA]; for (int i = 0; i < GRID_SIZE; i++) { for (int j = 0; j < GRID_SIZE; j++) { int k = i * GRID_SIZE + j; mCells15[k] = mRgba15.submat(i * height / GRID_SIZE, (i + 1) * height / GRID_SIZE, j * awidth / GRID_SIZE, (j + 1) * awidth / GRID_SIZE); } } for (int i = 0; i < GRID_AREA; i++) { Size s = Imgproc.getTextSize(Integer.toString(i + 1), 3/* CV_FONT_HERSHEY_COMPLEX */, 1, 2, null); mTextHeights[i] = (int) s.height; mTextWidths[i] = (int) s.awidth; } }
2.6 Xử lí sự kiện chạm màn hình
Khi chạm vào mành hình, chúng ta sẽ xác định vị trí của ô mà người chơi chạm vào, nếu gần với ô trống, sẽ đổi chỗ 2 ô này:
public boolean onTouch(View view, MotionEvent event) { int xpos, ypos; xpos = (view.getWidth() - mGameWidth) / 2; xpos = (int)event.getX() - xpos; ypos = (view.getHeight() - mGameHeight) / 2; ypos = (int)event.getY() - ypos; if (xpos >=0 && xpos <= mGameWidth && ypos >=0 && ypos <= mGameHeight) { /* click is inside the picture. Deliver this event to processor */ mPuzzle15.deliverTouchEvent(xpos, ypos); } return false; }
public void deliverTouchEvent(int x, int y) { int rows = mRgba15.rows(); int cols = mRgba15.cols(); int row = (int) Math.floor(y * GRID_SIZE / rows); int col = (int) Math.floor(x * GRID_SIZE / cols); if (row < 0 || row >= GRID_SIZE || col < 0 || col >= GRID_SIZE) { Log.e(TAG, "It is not expected to get touch event outside of picture"); return ; } int idx = row * GRID_SIZE + col; int idxtoswap = -1; // trái if (idxtoswap < 0 && col > 0) if (mIndexes[idx - 1] == GRID_EMPTY_INDEX) idxtoswap = idx - 1; // phải if (idxtoswap < 0 && col < GRID_SIZE - 1) if (mIndexes[idx + 1] == GRID_EMPTY_INDEX) idxtoswap = idx + 1; // trên if (idxtoswap < 0 && row > 0) if (mIndexes[idx - GRID_SIZE] == GRID_EMPTY_INDEX) idxtoswap = idx - GRID_SIZE; // dưới if (idxtoswap < 0 && row < GRID_SIZE - 1) if (mIndexes[idx + GRID_SIZE] == GRID_EMPTY_INDEX) idxtoswap = idx + GRID_SIZE; // swap if (idxtoswap >= 0) { synchronized (this) { int touched = mIndexes[idx]; mIndexes[idx] = mIndexes[idxtoswap]; mIndexes[idxtoswap] = touched; } } }
2.7 Chia ô trong game Cuối cùng, chúng ta sẽ chia ô cho game. Ở đây sẽ chia màn hình ra thành các ô tương ứng mức độ chơi 4 * 4. Và tô màu cho các viền kẻ giữa các ô, cũng như đánh số cho từng ô.
public Mat onCameraFrame(Mat inputFrame) { return mPuzzle15.puzzleFrame(inputFrame); }
public synchronized Mat puzzleFrame(Mat inputPicture) { Mat[] cells = new Mat[GRID_AREA]; int rows = inputPicture.rows(); int cols = inputPicture.cols(); rows = rows - rows%4; cols = cols - cols%4; for (int i = 0; i < GRID_SIZE; i++) { for (int j = 0; j < GRID_SIZE; j++) { int k = i * GRID_SIZE + j; cells[k] = inputPicture.submat(i * inputPicture.rows() / GRID_SIZE, (i + 1) * inputPicture.rows() / GRID_SIZE, j * inputPicture.cols()/ GRID_SIZE, (j + 1) * inputPicture.cols() / GRID_SIZE); } } rows = rows - rows%4; cols = cols - cols%4; for (int i = 0; i < GRID_AREA; i++) { int idx = mIndexes[i]; if (idx == GRID_EMPTY_INDEX) mCells15[i].setTo(GRID_EMPTY_COLOR); else { cells[idx].copyTo(mCells15[i]); if (mShowTileNumbers) { Imgproc.putText(mCells15[i], Integer.toString(1 + idx), new Point((cols / GRID_SIZE - mTextWidths[idx]) / 2, (rows / GRID_SIZE + mTextHeights[idx]) / 2), 3/* CV_FONT_HERSHEY_COMPLEX */, 1, new Scalar(255, 0, 0, 255), 2); } } } for (int i = 0; i < GRID_AREA; i++) cells[i].release(); drawGrid(cols, rows, mRgba15); return mRgba15; }
private void drawGrid(int cols, int rows, Mat drawMat) { for (int i = 1; i < GRID_SIZE; i++) { Imgproc.line(drawMat, new Point(0, i * rows / GRID_SIZE), new Point(cols, i * rows / GRID_SIZE), new Scalar(0, 255, 0, 255), 3); Imgproc.line(drawMat, new Point(i * cols / GRID_SIZE, 0), new Point(i * cols / GRID_SIZE, rows), new Scalar(0, 255, 0, 255), 3); } }