Membuat Game Mencocokan Objek dengan C++ (Part 3) Oleh: Sendy PK
Pada akhir tutorial ini kita akan memiliki versi "Playable" yang dapay dimainkan dari SameGame. Dapat dimainkan dalam tanda kutip karena kami akan memiliki pertandingan di negara yang akan memungkinkan pemain untuk mengklik untuk menghapus blok dan mengakhiri permainan ketika tidak ada gerakan lebih vali...
Pada akhir tutorial ini kita akan memiliki versi "Playable" yang dapay dimainkan dari SameGame. Dapat dimainkan dalam tanda kutip karena kami akan memiliki pertandingan di negara yang akan memungkinkan pemain untuk mengklik untuk menghapus blok dan mengakhiri permainan ketika tidak ada gerakan lebih valid kiri. Permainan tidak akan menjadi sangat kaya fitur namun akan dimainkan. Dalam artikel yang tersisa kami akan menambahkan lebih banyak fitur untuk meningkatkan kesulitan dan memungkinkan permainan untuk disesuaikan sedikit. Adapun artikel ini kita akan melihat ke dalam pemrograman event driven dan bagaimana untuk mendapatkan permainan kami untuk menanggapi klik mouse. Setelah kita dapat menanggapi klik kita akan membahas algoritma kami akan gunakan untuk menghapus blok dan akhirnya, bagaimana cara memberitahu ketika permainan berakhir.
Event Driven Programming Event Driven Programming adalah perubahan paradigma yang lengkap dalam pemrograman. Jika sampai sekarang mungkin kita hanya menulis C++ secara prosedural. Perbedaan antara kedua jenis paradigma pemrograman adalah bahwa aliran kontrol dalam event driven programming ditentukan oleh event yang tidak satu set yang telah ditentukan langkah-langkah. Ini adalah program reaktif. Pengguna melakukan sesuatu seperti klik pada tombol dan program bereaksi terhadap peristiwa yang dengan mengeksekusi beberapa kode. Loop utama dalam program didorong acara hanya menunggu untuk sebuah acara terjadi kemudian memanggil event handler yang sesuai dan kembali menunggu untuk acara lain. Event handler adalah bagian dari kode yang disebut setiap kali event tertentu terjadi.
Mouse Klik Library MFC tak mewariskan event driven, oleh karena itu membuatnya cukup mudah bagi kita untuk membuat event handler dan merespon setiap event yang kita inginkan. Untuk mengatur penanganan event di MFC, Semua list p esan pada visual studio tersedia untuk menanggapi. Dalam hal ini pesan yang identik dengan peristiwa. Semua pesan Windows konstanta yang dimulai dengan WM_ diikuti dengan nama pesan. Untuk menanggapi klik mouse di wilayah klien dari view ada pesan untuk kiri, tombol kanan mouse dan tengah.Event yang akan kita gunakan adalah
WM_LBUTTONDOWN tersebut. Pesan ini dikirim oleh framework MFC setiap kali pengguna mengklik tombol kiri mouse. Semua yang perlu kita lakukan adalah membuat sebuah event handler untuk mendengarkan pesan ini akan dikirim dan lalu direspon. Untuk menambahkan event handler buka Properties Window dari file header CSameGameView. Lakukan ini dengan menekan Alt + Enter atau dari menu View-> Windows- Other> Properties Window. Di bawah ini adalah apa yang akan Anda lihat di jendela properti. (Jika tidak, pastikan kursor ditempatkan dalam kelas deklarasi di dalam file SameGameView.h.)
Pada screenshot kursor mengarah pada "Messages", klik di atasnya. Carilah opsi WM_LBUTTONDOWN, klik di atasnya, klik dropdown seperti yang ditunjukkan pada gambar di bawah ini dan pilih "
OnLButtonDown".
Dengan Ini kita akan menambahkan event handler OnLButtonDown ke dalam view dengan beberapa kode default di dalamnya untuk memanggil pelaksanaan CView fungsi. Di sini kita akan menambahkan kode berikut untuk fungsipada body Perhatikan kode ini tidak akan di kompile, tapi kita akan lama. OK kode tidak akan dikompile, tetapi kita akan mencari tahu apa yang perlu dilakukan untuk membuat fungsi ini bekerja Jangan menunggu untuk mengkompilasi kode yang dihasilkan sampai Anda selesaipada tutorial ini, karena perubahan akan beruntun; seperti yang kita lalui bagaimana mengimplementasikan masing-masing fungsi yang kita butuhkan, kita akan menemukan fungsi yang dibutuhkan lebih banyaklagi. void CSameGameView::OnLButtonDown(UINT nFlags, CPoint point) { // First get a pointer to the document
CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Get the row and column of the block that was clicked on int row = point.y / pDoc->GetHeight(); int col = point.x / pDoc->GetWidth(); // Delete the blocks from the document int count = pDoc->DeleteBlocks(row, col); // Check if there were any blocks deleted if(count > 0) { // Force the view to redraw Invalidate(); UpdateWindow(); // Check if the game is over if(pDoc->IsGameOver()) { // Get the count remaining int remaining = pDoc->GetRemainingCount(); CString message; message.Format(_T("No more moves left\nBlocks remaining: %d"), remaining); // Display the results to the user MessageBox(message, _T("Game Over"), MB_OK | MB_ICONINFORMATION); } } // Default OnLButtonDown CView::OnLButtonDown(nFlags, point); }
Dua argumen untuk fungsi integer dari bit-flags yang dapat diabaikan dan objek CPoint. Objek CPoint berisi kordinat (x, y) di mana saat mouse diklik dalam view. Kita akan menggunakannya untuk mencari tahu mana blok mereka diklik. Untuk menemukan baris dan kolom dari blok yang diklik kita gunakan beberapa bilangan integer matematika sederhana dan membagi koordinat x dengan lebar blok dan y dengan tinggi. //
Get the row and column of the block that was clicked on int row = point.y / pDoc->GetHeight(); int col = point.x / pDoc->GetWidth();
Karena kita menggunakan pembagian integer hasilnya adalah baris yang tepat dan kolom yang diklik pengguna. Setelah kita memiliki baris dan kolom kita akan memanggil fungsi, DeleteBlocks pada dokumen untuk menghapus blok yang berdekatan. Fungsi ini akan mengembalikan jumlah blok yang dihapus.
Jika tidak ada yang dihapus maka fungsi dasarnya berakhir. Jika ada blok dihapus maka kita perlu memaksa pandangan untuk redraw sendiri sekarang bahwa kami telah mengubah papan permainan. Fungsi memanggil Invalidate () sinyal ke view yang membutuhkan wilayah klien keseluruhan untuk digambar ulang dan UpdateWindow () int count = pDoc->DeleteBlocks(row, col); // Check if there were any blocks deleted if(count > 0) { // Force the view to redraw Invalidate(); UpdateWindow(); // ... } }
Sekarang board telah diperbarui dan digambar ulang kita akan menguji apakah permainan berakhir. Pada bagian berjudul "FinishingCondition" kita akan mencari tahu bagaimana permainan berakhir. Untuk saat ini kami hanya akan menambahkan panggilannya. if(pDoc->IsGameOver()) { // Get the count remaining int remaining = pDoc->GetRemainingCount(); CString message; message.Format(_T("No more moves left\nBlocks remaining: %d"), remaining); // Display the results to the user MessageBox(message, _T("Game Over"), MB_OK | MB_ICONINFORMATION); }
Jika permainan berakhir kita mendapatkan jumlah blok yang tersisa di board dan melaporkan kepada pengguna. Kami membuat objek CString yang MFC kelas string dan memanggil built-in metode format. Metode Format berperilaku seperti sprintf (). Di sini kita menggunakan MFC _T () makro untuk memungkinkan berbagai jenis string. Akhirnya kita memanggil fungsi MessageBox () yaitu fungsi yang menampilkan dialog kecil dengan judul "Game Over" dan pesan yang kita buat dengan menggunakan metode Format. Dialog memiliki tombol OK (MB_OK) dan ikon informasi (MB_ICONINFORMATION). Sekarang event handler ini di tempat kita perlu menerapkan tiga fungsi pada dokumen yang kita sebut, IsGameOver, DeleteBlocks dan GetRemainingCount. Fungsi-fungsi ini hanya wrapper sederhana untuk fungsi yang sama di game board. Jadi mereka hanya dapat ditambahkan ke file header untuk dokumen setelah fungsi DeleteBoard, seperti berikut ini. bool IsGameOver()
{ return m_board.IsGameOver(); }
int DeleteBlocks(int row, int col) { return m_board.DeleteBlocks(row, col); } int GetRemainingCount() { return m_board.GetRemainingCount(); }
Setelah kita tambahkan fungsi wrapper ini untuk dokumen saatnya untuk memodifikasi game board untuk mengurus operasi ini. Dalam file headerpada game board kita tambahkan public methods berikut kodenya. /* Is the game over? */ bool IsGameOver(void) const; /* Get the number of blocks remaining */ int GetRemainingCount(void) const { return m_nRemaining; } /* Function to delete all adjacent blocks */ int DeleteBlocks(int row, int col);
Dua fungsi yang cukup kompleks dan akan memerlukan sedikit kode tetapi fungsi GetRemainingCount hanya mengembalikan hitungan sisa blok. Kami akan menyimpan yang menghitung variabel anggota yang disebut m_nRemaining. Kita perlu menambahkannya ke game board di bagian kelas private member. /* Number of blocks remaining */ int m_nRemaining;
Kita telah menambahkan data anggota lain untuk kelas kita, kita perlu menginisialisasi dalam konstruktor seperti berikut. CSameGameBoard::CSameGameBoard(void) : m_arrBoard(NULL), m_nColumns(15), m_nRows(15), m_nHeight(35), m_nWidth(35), // <-- don't forget the comma! m_nRemaining(0) { m_arrColors[0] = RGB( 0, 0, 0); m_arrColors[1] = RGB(255, 0, 0); m_arrColors[2] = RGB(255,255, 64); m_arrColors[3] = RGB( 0, 0,255); // Create and setup the board SetupBoard(); }
Kita juga perlu memperbarui hitungan sisa blok dalam metode SetupBoard. void CSameGameBoard::SetupBoard(void) { // Create the board if needed if(m_arrBoard == NULL) CreateBoard(); // Randomly set each square to a color for(int row = 0; row < m_nRows; row++) for(int col = 0; col < m_nColumns; col++) m_arrBoard[row][col] = (rand() % 3) + 1; // Set the number of spaces remaining m_nRemaining = m_nRows * m_nColumns; }
Menghapus blok dari board adalah terdiri dari dua langkah. Pertama kita mengubah semua warna, blok yang berdekatan sama dengan warna background, pada dasarnya menghapusnya, dan kemudian kita harus memindahkan blok atas ke bawah dan blok ke kanan, kiri. Kami menyebutnya pemadatanboard. Menghapus blok adalah kandidat utama untuk penggunaan rekursi. Kita akan membuat fungsi rekursif helper yang disebut DeleteNeighborBlocks yang secara private akan melakukan sebagian besar pekerjaan menghapus blok. Di bagian private dari kelas tepat setelah menambahkan fungsi CreateBoard () berikut. /*
Direction enumeration for deleting blocks */ enum Direction { DIRECTION_UP, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_RIGHT }; /* Recursive helper function for deleting blocks */ int DeleteNeighborBlocks(int row, int col, int color, Direction direction); /* Function to compact the board after blocks are eliminated */ void CompactBoard(void);
Kita akan menggunakan enumerasi untuk arah dalam fungsi rekursif helpe yang akan menjaga kita dari mencoba rekursif kembali ke blok awal. Algoritma untuk menghapus blok agak sederhana, mulai dengan baris dan kolom dan pastikan ada blok yang berdekatan dengan warna yang sama. Jika demikian, mengubah nilai warna untuk warna latar belakang. Lalu pergi di setiap arah dan hapus blok yang berdekatan jika itu sama. Proses ini diulang dengan setiap blok secara rekursif. Berikut adalah fungsi DeleteBlocks secara keseluruhan.
int CSameGameBoard::DeleteBlocks(int row, int col) { // Make sure that the row and column are valid if(row < 0 || row >= m_nRows || col < 0 || col >= m_nColumns) return -1; // Can't delete background blocks int nColor = m_arrBoard[row][col]; if(nColor == 0) return -1; // First check if there are any of the adjacent sides // with the same color int nCount = -1; if((row - 1 >= 0 && m_arrBoard[row - 1][col] == nColor) || (row + 1 < m_nRows && m_arrBoard[row + 1][col] == nColor) || (col - 1 >= 0 && m_arrBoard[row][col - 1] == nColor) || (col + 1 < m_nColumns && m_arrBoard[row][col + 1] == nColor)) { // Then call the recursive function to eliminate all // other touching blocks with same color m_arrBoard[row][col] = 0; nCount = 1; // Recursive call for up nCount += DeleteNeighborBlocks(row - 1, col, nColor, DIRECTION_DOWN); // Recursive call for down nCount += DeleteNeighborBlocks(row + 1,col, nColor, DIRECTION_UP); // Recursive call for left nCount += DeleteNeighborBlocks(row, col - 1, nColor, DIRECTION_RIGHT); // Recursive call for right nCount += DeleteNeighborBlocks(row, col + 1, nColor, DIRECTION_LEFT); // Finally compact the board CompactBoard(); // Remove the count from the number remaining m_nRemaining -= nCount; } // Return the total number of pieces deleted return nCount; }
Bagian pertama adalah untuk memastikan bahwa baris dan kolom yang valid dan bahwa blok yang dipilih tidak sudah blok latar belakang. Berikutnya datang cek jika ada setidaknya satu blok yang berdekatan dengan warna yang sama di atas, bawah, kiri atau kanan blok. Jika ada maka blok yang dipilih diatur untuk warna latar belakang (0) dan jumlah diatur ke satu. Ini diikuti dengan empat panggilan ke DeleteNeighborBlocks (). Panggilan pertama melewati baris di atas (baris - 1) dalam kolom yang sama dengan warna dan kemudian DIRECTION_DOWN. Kita lulus dalam ini karena
memberitahu fungsi rekursif untuk melewati arah bawah karena di situlah eksekusi berasal. Ini hanya memotong beberapa langkah tambahan dan mempercepat proses up. Ini bisa ditinggalkan dan algoritma masih akan bekerja dengan benar, tapi sedikit kurang efisien. Setelah semua empat arah diperiksa board yang dipadatkan dan jumlah blok yang telah dihapus dikurangi dari jumlah total blok yang tersisa. Fungsi The DeleteNeighborBlocks ini sangat mirip dengan fungsi DeleteBlocks. Sekali lagi beberapa baris pertama memastikan bahwa baris dan kolom yang valid dan bahwa blok ditentukan adalah warna yang sama dengan blok asli. Setelah itu kita membuat tiga panggilan rekursif untuk menghapus neighbor. Kita menggunakan argumen arah untuk memutuskan arah mana kita berasal dan kemudian melompat ke arah ini. Ini hanya sedikit optimasi yang menghilangkan panggilan rekursif sembrono. int CSameGameBoard::DeleteNeighborBlocks(int row, int col, int color, Direction direction) { // Check if it is on the board if(row < 0 || row >= m_nRows || col < 0 || col >= m_nColumns) return 0; // Check if it has the same color if(m_arrBoard[row][col] != color) return 0; int nCount = 1; m_arrBoard[row][col] = 0; // If we weren't told to not go back up, check up if(direction != DIRECTION_UP) nCount += DeleteNeighborBlocks(row - 1, col, color, DIRECTION_DOWN); // If we weren't told to not go back down, check down if(direction != DIRECTION_DOWN) nCount += DeleteNeighborBlocks(row + 1, col, color, DIRECTION_UP); // If we weren't told to not go back left, check left if(direction != DIRECTION_LEFT) nCount += DeleteNeighborBlocks(row, col - 1, color, DIRECTION_RIGHT); // If we weren't told to not go back right, check right if(direction != DIRECTION_RIGHT) nCount += DeleteNeighborBlocks(row, col + 1, color, DIRECTION_LEFT); // Return the total number of pieces deleted return nCount; }
Pada titik ini berdekatan, blok berwarna sama telah dieliminasi dan berubah menjadi warna latar belakang sehingga semua yang tersisa adalah untuk kompak board dengan memindahkan semua blok bawah dan kolom ke kiri.
void CSameGameBoard::CompactBoard(void) { // First move everything down for(int col = 0; col < m_nColumns; col++) { int nNextEmptyRow = m_nRows - 1; int nNextOccupiedRow = nNextEmptyRow; while(nNextOccupiedRow >= 0 && nNextEmptyRow >= 0) { // First find the next empty row while(nNextEmptyRow >= 0 && m_arrBoard[nNextEmptyRow][col] != 0) nNextEmptyRow--; if(nNextEmptyRow >= 0) { // Then find the next occupied row from the next empty row nNextOccupiedRow = nNextEmptyRow - 1; while(nNextOccupiedRow >= 0 && m_arrBoard[nNextOccupiedRow][col] == 0) nNextOccupiedRow--; if(nNextOccupiedRow >= 0) { // Now move the block from occupied to empty m_arrBoard[nNextEmptyRow][col] = m_arrBoard[nNextOccupiedRow][col]; m_arrBoard[nNextOccupiedRow][col] = 0; } } } } // Then move everything from right to left int nNextEmptyCol = 0; int nNextOccupiedCol = nNextEmptyCol; while(nNextEmptyCol < m_nColumns && nNextOccupiedCol < m_nColumns) { // First find the next empty column while(nNextEmptyCol < m_nColumns && m_arrBoard[m_nRows - 1][nNextEmptyCol] != 0) nNextEmptyCol++; if(nNextEmptyCol < m_nColumns) { // Then find the next column with something in it nNextOccupiedCol = nNextEmptyCol + 1; while(nNextOccupiedCol < m_nColumns && m_arrBoard[m_nRows - 1][nNextOccupiedCol] == 0) nNextOccupiedCol++; if(nNextOccupiedCol < m_nColumns) { // Move entire column to the left for(int row = 0; row < m_nRows; row++)
{ m_arrBoard[row][nNextEmptyCol] = m_arrBoard[row][nNextOccupiedCol]; m_arrBoard[row][nNextOccupiedCol] = 0; } } } } }
Pertama kita pergi kolom dengan kolom hal bergerak turun. Mulai di baris bawah (m_nRows - 1) kita lingkaran mencari baris kosong berikutnya. Setelah itu ditemukan, ada lingkaran lain yang mencari baris diduduki berikutnya. Setelah dua ini terletak maka baris kosong berikutnya diisi dengan baris diduduki berikutnya. Proses ini diulang sampai tidak ada lagi blok bergerak turun. Bagian kedua dari fungsi hampir identik dengan bagian pertama kecuali untuk luar untuk loop. Alasan kita bisa menghilangkan lingkaran luar adalah bahwa kita hanya perlu melihat baris bawah di setiap kolom, jika kosong maka seluruh kolom kosong dan kita bisa bergerak sesuatu ke tempatnya. Penyelesaian Kondisi Hanya ada satu langkah tersisa untuk menyelesaikan task Kita dari membuat "playable" game. Itu Untuk mengimplementasikan fungsi fungsi IsGameOver. Pemeriksaan fungsi fungsi untuk memindahkan yang valid dan memeriksa setiap blok untuk melihat hal apakah ada blokyYang berdekatan dengan warna yang sama. Ketika Pertama ditemukan, fungsi fungsi Sirkuit Pendek Dan Kembali Palsu Segera. Tidak perlu untuk terus memeriksa. Satu-Satunya Cara untuk mengetahui Game Hearts sebenarnya Lebih sebenarnya adalah untuk melakukan pencarian penuh dan memastikan bahwa tak ada Gerakan kiri. bool CSameGameBoard::IsGameOver(void) const { // Go column by column, left to right for(int col = 0; col < m_nColumns; col++) { // Row by row, bottom to top for(int row = m_nRows - 1; row >= 0; row--) { int nColor = m_arrBoard[row][col]; // Once we hit background, this column is done if(nColor == 0) break; else { // Check above and right if(row - 1 >= 0 && m_arrBoard[row - 1][col] == nColor) return false; else if(col + 1 < m_nColumns &&
m_arrBoard[row][col + 1] == nColor) return false; } } } // No two found adjacent return true; }
Dua untuk loop memungkinkan kita untuk mencari kolom dengan kolom dan baris demi baris untuk bergerak valid. Karena kita mencari kiri ke kanan kita tidak harus memeriksa ke kiri untuk blok yang berdekatan dengan warna yang sama. Mencari dari bawah ke atas menghilangkan kebutuhan untuk memeriksa di bawah ini untuk bergerak valid. Pesanan ini mencari juga memungkinkan kita untuk mengoptimalkan IsGameOver berfungsi sedikit lebih jauh. Setelah warna blok adalah warna latar belakang kita bisa melewatkan sisa kolom karena apa pun di atas itu akan kosong juga (berkat fungsi CompactBoard). Sekarang kita dapat memainkan game sampai komplit dan terlihat seperti ini:
Pada Tutorial ini kita telah pergi dari game yang tidak melakukan apa-apa selain menarik board game untuk versi dimainkan dari SameGame. Kita membahas pemrograman event driven serta bagaimana menerapkan bahwa menggunakan MFC. Kita menciptakan sebuah event handler untuk klik kiri mouse dan menanggapi dengan algoritma game utama. Dalam artikel berikutnya kita akan menambahkan fitur untuk game kita melalui menu.
Tentang Penulis Sendy PK Saya adalah Programmer yang memiliki impian untuk menguasai dunia kunjungi situs pribadi saya di www.spk.my.id dan Online Shop saya di www.spkshop.web.id