Membuat Game Mencocokan Objek dengan C++ (Part 5) Oleh: Sendy PK
Pada Tutorial kali ini kita akan menambahkan lebih banyak pilihan untuk menyesuaikan dalam game. Kita akan menambahkan kemampuan bagi pengguna untuk menyesuaikan ukuran blok dan jumlah blok di board game. Dalam rangka untuk mendapatkan nilai-nilai dari user kita akan membuat dialog box untuk memint...
Pada Tutorial kali ini kita akan menambahkan lebih banyak pilihan untuk menyesuaikan dalam game. Kita akan menambahkan kemampuan bagi pengguna untuk menyesuaikan ukuran blok dan jumlah blok di board game. Dalam rangka untuk mendapatkan nilai-nilai dari user kita akan membuat dialog box untuk meminta mereka untuk masukan. Langkah pertama adalah untuk memperbarui game board dan dokumen kelas untuk dapat menyesuaikan pilihan ini. Dimulai dengan game board ,kita akan membuat beberapa penyesuaian yang sangat kecil untuk file header saja. Kita sudah memiliki fungsi "getter" di kelas game board untuk mendapatkan nilai-nilai lebar dan tinggi dari blok dalam piksel dan jumlah baris dan kolom di board tulis. Sekarang kita akan menambahkan fungsi "setter" untuk mengatur nilai-nilainya. Update wilayah fungsi accessor pada game board di kelas SameGameBoard.h seperti berikut ini. /* Accessor functions to get/set board size information */ int GetWidth(void) const { return m_nWidth; } void SetWidth(int nWidth)
{ m_nWidth = (nWidth >= 3) ? nWidth : 3; } int GetHeight(void) const { return m_nHeight; } void SetHeight(int nHeight) { m_nHeight = (nHeight >= 3) ? nHeight : 3; } int GetColumns(void) const { return m_nColumns; } void SetColumns(int nColumns) { m_nColumns = (nColumns >= 5) ? nColumns : 5; } int GetRows(void) const { return m_nRows; } void SetRows(int nRows) { m_nRows = (nRows >= 5) ? nRows : 5; }
Kita telah menambahkan set function setelah get function untuk lebih mudah dibaca dan memahami kelas. Fungsi yang cukup sederhana, kita menggunakan tes sederhana pada masing-masing nilai memastikan bahwa mereka tidak pergi di bawah nilai tertentu, tiga untuk pixel tinggi / lebar dan lima untuk nilai baris / kolom. Jumlah yang lebih kecil dari ini akan mengacaukan estetika pada game board. Berikutnya kita update dokumen dengan cara yang sama yaitu tambahkan fungsi "setter" untuk menuju fungsi"getter". Fungsi-fungsi ini ditambahkan ke SameGameDoc.h file header pada game board seperti berikut. int GetWidth(void) void SetWidth(int nWidth) int GetHeight(void) void SetHeight(int nHeight) int GetColumns(void) void SetColumns(int nColumns) int GetRows(void) void SetRows(int nRows)
{ { { { { { { {
return m_board.GetWidth(); } m_board.SetWidth(nWidth); } return m_board.GetHeight(); } m_board.SetHeight(nHeight); } return m_board.GetColumns(); } m_board.SetColumns(nColumns); } return m_board.GetRows(); } m_board.SetRows(nRows); }
Sekarang yang akan kita lakukan adalah mengubah ukuran game board, mengubah ukuran jendela dan member warna semua ini ada di event handler pada pilihan menu. Kita akan membuat dialog baru untuk melakukannya. Membuat dialog baru dengan cara Open Resource View dibawah "View" menu "Source View" atau tombol akselerator Ctrl + Shift + E. Anda akan mengenali pandangan ini dari ketika kita diedit sistem menu untuk game kita. Kali ini bukan membuka opsi Menu, buka opsi Dialog. Anda akan melihat bahwa ada satu dialog sudah ada dengan ID dari IDD_ABOUTBOX. Jika Anda perhatikan di bawah menu About ada dialog yang muncul dengan informasi "About”. Dialog ini secara otomatis dihasilkan oleh MFC Application Wizard pada saat kita pertama kali set up project. Tambahkan dialog baru, cukup klik-kanan pada pilihan Dialog di Source View dan pilih "Insert Dialog" dan New, sebagian besar masih kosong, dialog akan muncul dengan ID dari IDD_DIALOG1 seperti gambar di bawah.
Setelah Anda klik dua kali pada pilihan IDD_DIALOG1, editor dialog akan muncul di jendela utama dalam Visual Studio, lalu akan tampil seperti gambar berikut
Kita akan menggunakan dialog ini untuk meminta pengguna untuk kedua board ukuran, baris dan kolom, dan ukuran blok, lebar dan tinggi, jadi kita harus membuatnya generik. Dialog ini tidak akan berbuat banyak sampai kita menambahkan beberapa kontrol baru. Kita akan menambahkan label, mengedit kotak, dan tombol lain untuk dialog untuk membuatnya fberfungsi. Pertama kita perlu untuk membuka editor Toolbox dialog melalui menu View kemudian "Toolbox" atau Ctrl + Alt + X. Mari kita lihat pada Toolbox.
Ini adalah daftar dari Common Controls. Kita akan menggunakan "Static Text" kontrol untuk menunjukkan apa yang input pengguna yang seharusnya masukan untuk masing-masing "Kontrol Edit". Kita juga akan menambahkan sebuah tombol untuk memungkinkan pengguna untuk mengembalikan nilai default. Untuk menambahkan kontrol, cukup klik dan tarik kontrol yang diinginkan dari Toolbox tepat untuk dialog dalam Dialog Editor. Mari kita mulai dengan tombol, klik dan tarik salah satu tepat di bawah Ok dan Cancel, seperti berikut.
Untuk mengubah teks pada tombol, klik pada button1 lalu pada text properti ketikkan "Default". Sekarang kita perlu beberapa "Edit Controls" tambahnya. Klik dan tarik beberapa pada dialog. Dalam rangka untuk garis mereka Anda dapat klik pada penguasa atas dan ke kiri dalam Dialog Editor untuk membuat panduan yang snap ke kontrol. Saya telah menambahkan beberapa untuk berbaris mengedit kontrol saya dengan tombol.
Sekarang kita tambahkan beberapa "Static Text" kontrol untuk menjelaskan kepada pengguna apa yang diketik ke dalam Edit Controls. Tambahkan dua new guides yang akan melapisi teks statis sampai ke tengah mengedit kontrol dan memperluas ukuran teks statis. Kontrol teks ini akan memiliki teks mereka dimasukkan pemrograman sehingga dapat berubah tergantung pada jenis data yang kita inginkan.
Sekarang kita akan mengubah judul dialog, teks statis dan nilai-nilai dalam mengedit kontrol. Untuk melakukannya kita perlu membuat beberapa perubahan pada ID dari kontrol yang kita telah menambahkan. Ketika mereka duduk kita tidak dapat berinteraksi dengan kontrol Teks Static; mereka membutuhkan ID baru. Tarik jendela properti dengan menekan Alt + Enter atau dari menu View-> Windows- Lain> Properties Window. Ketika Anda mengklik pada kontrol, properti untuk kontrol yang akan datang. Saya sudah memilih opsi ID yang perlu diubah.
ID IDC_STATIC adalah ID disediakan yaitu untuk semua kontrol teks statis generic, kita ubah menjadi IDC_STATIC_TEXT_1. Lalu kita ubah ID untuk semua kontrol dan dialog. Untuk melakukan hal ini, biarkan jendela properti dan klik pada kontrol yang berbeda. Hal ini akan mengisi properti untuk itu kontrol tertentu. Kemudian hanya mengubah ID. Kita akan membutuhkan teks statis atas menjadi IDC_STATIC_TEXT_1 dan lainnya IDC_STATIC_TEXT_2. Kemudian mengubah nama mengedit kontrol untuk IDC_EDIT_VALUE_1 untuk bagian atas kontrol edit dan IDC_EDIT_VALUE_2 lainnya. Kita melakukan ini agar kita secara dinamis dapat mengubah teks tergantung pada data yang kita ingin pengguna untuk memasukkan. Tombol kita menambahkan untuk "Default" kita akan mengubah nama untuk IDC_BUTTON_DEFAULTS. Akhirnya mengubah ID dialog dari IDD_DIALOG1 ke IDD_DIALOG_OPTIONS. Perubahan ini akan membantu saat membuat dialog dan menampilkan kepada pengguna. Setelah dialog diatur dalam editor dialog, kita perlu untuk menghasilkan kode untuk itu. Cukup
sederhana, klik kanan pada dialog itu sendiri, tidak ada kontrol, dan pilih "Add Class ..." kita akan melihat MFC Class Wizard. Ini memberi kita sebuah kelas yang mewakili dialog ini yang telah kita buat yang kita dapat gunakan dalam kode kita. Tambahkan nama kelas COptionsDialog dan semuanya akan mengisi sendirinya. Mengklik finish akan menghasilkan file OptionsDialog.h dan OptionsDialog.cpp.
Sebelum kita melihat kode yang telah dibuat bagi kita mari kita membuat beberapa variabel di kelas ini yang mewakili kontrol yang kita menambahkan. Kembali ke editor dialog dan klik kanan pada masing-masing kontrol, satu per satu, dan pilih "Add Variable " untuk membuka Add Member Variabel Wizard. Ini akan menambahkan variabel ke kelas yang baru saja kita buat dan mengasosiasikannya dengan kontrol yang kita benar-diklik. Saat kita pertama kali mengklik kontrol teks statis wizard akan terlihat seperti gambar di bawah ini. Isi Nama Variabel dengan m_ctrlStaticText1 seperti yang saya lakukan dan klik Finish. Ini akan menambahkan semua kode yang diperlukan untuk kelas dialog.
Kita ingin variabel kontrol dari jenis CStatic sehingga kita dapat mengubah teks dalam kontrol teks statis setiap kali kita inginkan. Kita akan memilih pilihan yang berbeda ketika kita sampai ke edit kontrol. Ulangi ini dengan kontrol teks statis kedua memberikan nama variabel m_ctrlStaticText2. Sekarang klik kanan pada kontrol edit pertama dan pilih "Add Variable ..." Kali ini kita ingin "Kontrol Variabel" tetapi variabel kontrol Nilai jadi drop-down "Kategori" dan pilih nilai. Ini akan mengubah "Variabel Type" pilihan dari jenis kontrol MFC untuk jenis nilai default menjadi CString, tapi pilih "int". Ketik nama variabel m_nValue1. Di sini kita menetapkan integer sebagai tempat penyimpanan untuk nilai di kontrol edit dan ketika pengguna mengklik Ok, nilai yang akan disimpan dalam variabel yang telah kita buat tanpa kita menulis kode untuk melakukannya. Lihat gambar di bawah untuk melihat apa yang kita lakukan.
Ulangi proses ini dengan kontrol edit kedua dan beri nama m_nValue2. Sekarang kita akan menambahkan event-handler kelas untuk tombol Default. Cukup klik kanan pada tombol dan pilih "Add Event Handler ..." lalu akan muncul Event Handler Wizard yang akan memungkinkan Anda untuk membuat segala macam event untuk semua kelas Anda. Jika Anda mengklik kanan pada tombol Default wizard akan default ke BN_CLICKED, yang merupakan singkatan dari tombol diklik, event untuk tombol default. Cukup klik "Add dan Edit" dan Anda akan dibawa ke kode pilihan dialog di mana event handler tombol akan menunggu untuk Anda.
Terakhir yang kita akan butuhkan adalah override untuk fungsi OnInitDialog. Dari header OptionsDialog membuka jendela properti dan pilihOverride Buttons. Lalu akan tampil daftar override, kita sedang mencari untuk OnInitDialog. Klik drop-down dan Add OnInitDialog. akan terlihat seperti gambar di bawah ini.
Pada poin ini akan ada banyak MFC yang dihasilkan oleh kode, pertama mari kita lihat, dan tambahkan, file header OptionsDialog.h. Berikut adalah source codenya. #pragma once
#include "afxwin.h" // COptionsDialog dialog class COptionsDialog : public CDialog { DECLARE_DYNAMIC(COptionsDialog) public: // Standard Constructor COptionsDialog(bool bRowColumn, CWnd* pParent = NULL); virtual ~COptionsDialog(); // Dialog Data enum { IDD = IDD_DIALOG_OPTIONS }; protected: // DDX/DDV support virtual void DoDataExchange(CDataExchange* pDX); DECLARE_MESSAGE_MAP() public: CStatic m_ctrlStaticText1; CStatic m_ctrlStaticText2; int m_nValue1; int m_nValue2; afx_msg void OnBnClickedButtonDefaults(); virtual BOOL OnInitDialog(); private: /* Is this dialog for row/column (true) or width/height (false)? */ bool m_bRowColumnDialog; };
Kita tambahkan variabel lain ke dalam daftar argumen untuk konstruktor sehingga dialog dapat dibangun untuk kedua baris / kolom dan informasi lebar / tinggi. Jika kita lulus dalam "true" maka dialog akan meminta pengguna untuk jumlah baris dan kolom pada game board. Jika itu adalah "false" dialog akan meminta lebar dan tinggi blok di game board. Berikut source codenya. // OptionsDialog.cpp : implementation file #include "stdafx.h" #include "SameGame.h" #include "OptionsDialog.h" // COptionsDialog dialog IMPLEMENT_DYNAMIC(COptionsDialog, CDialog) COptionsDialog::COptionsDialog(bool bRowColumn, CWnd* pParent) : CDialog(COptionsDialog::IDD, pParent) , m_nValue1(0) , m_nValue2(0) , m_bRowColumnDialog(bRowColumn) {
} COptionsDialog::~COptionsDialog() { } void COptionsDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_STATIC_TEXT_1, m_ctrlStaticText1); DDX_Control(pDX, IDC_STATIC_TEXT_2, m_ctrlStaticText2); DDX_Text(pDX, IDC_EDIT_VALUE_1, m_nValue1); DDX_Text(pDX, IDC_EDIT_VALUE_2, m_nValue2); } BEGIN_MESSAGE_MAP(COptionsDialog, CDialog) ON_BN_CLICKED(IDC_BUTTON_DEFAULTS, &COptionsDialog::OnBnClickedButtonDefaults) END_MESSAGE_MAP() // COptionsDialog message handlers void COptionsDialog::OnBnClickedButtonDefaults() { // Do things differently for if(m_bRowColumnDialog) m_nValue1 = m_nValue2 = 15; else m_nValue1 = m_nValue2 = 35; // Have the controls updated UpdateData(false);
the different dialogs //
15x15 board
// 35x35 blocks to the new values
} BOOL COptionsDialog::OnInitDialog() { CDialog::OnInitDialog(); // Setup the dialog based on the dialog type if(m_bRowColumnDialog) { // First update the title of the dialog SetWindowText(_T("Update Block Count")); // Next change the static text labels m_ctrlStaticText1.SetWindowText(_T("Rows")); m_ctrlStaticText2.SetWindowText(_T("Columns")); } else { // First update the title of the dialog SetWindowText(_T("Update Block Size"));
// Next change the static text labels m_ctrlStaticText1.SetWindowText(_T("Block Width")); m_ctrlStaticText2.SetWindowText(_T("Block Height")); } return TRUE; }
Hal pertama yang kita lakukan adalah memperbarui konstruktor untuk mengambil nilai Boolean yang memberitahu kita apa jenis dialog yang kita buat. Perubahan berikutnya kita buat adalah untuk mengatur ulang nilai-nilai ke default, untuk baris / kolom nilai-nilai yang baik 15 dan lebar / tinggi nilai-nilai yang baik 35. Kemudian untuk memperbarui kontrol dengan nilai-nilai baru yang kita harus memanggil Fungsi UpdateData lewat di salah sebagai argumen. Argumennya adalah bendera Boolean yang menunjukkan arah update atau apa yang sedang diperbarui, kontrol (palsu) atau variabel (benar). Melewati sejati akan me-reset perubahan yang baru saja Anda membuat dengan pergi ke kontrol, membaca isi, dan menyimpannya ke variabel. Kita ingin memperbarui kontrol dengan nilai baru dari variabel sehingga kita lulus dalam palsu. Terakhir kita menempatkan kode ke dalam fungsi OnInitDialog. Ini adalah fungsi sebelum dialog pertama ditampilkan kepada pengguna. Di sinilah kita dapat mengatur dialog. Jadi untuk dialog baris / kolom kita menetapkan judul dialog untuk "Update Blok Hitung" dengan fungsi SetWindowText dan _T () makro yang kita bicarakan dalam artikel kedua. Kemudian kita memperbarui teks dalam kontrol teks statis dengan menggunakan fungsi dengan nama yang sama dalam objek kontrol. Kita menetapkan mereka untuk "Rows" dan "Kolom". Jika itu adalah dialog lebar / tinggi kita mengubah judul dan label untuk mencerminkan hal itu. Itu adalah semua yang perlu dilakukan dalam dialog, semuanya harus berfungsi dengan baik sekarang. Langkah terakhir kita adalah untuk mengatur beberapa event dalam tampilan untuk dua pilihan menu yang kita bekerja dengan. Kita melakukan hal ini melalui jendela Properties dari file header untuk tampilan (SameGameView.h). Klik pada tombol acara, salah satu yang terlihat seperti petir, memperluas pilihan ID_SETUP_BLOCKCOUNT dan menambahkan event handler COMMAND. Lakukan ini dengan pilihan ID_SETUP_BLOCKSIZE juga. Kita tidak perlu membuat perubahan ke tampilan ini file header selain perubahan yang menambahkan dua penangan lakukan secara otomatis. Berikut adalah satu-satunya perubahan yang dibuat secara otomatis. afx_msg void OnSetupBlockcount(); afx_msg void OnSetupBlocksize();
Perubahan terjadi pada iplementasi atau source file dari view pada SameGameView.cpp. Untuk menggunakan opsi dialog yang baru saja kita buat kita harus menyertakan file header di file sumber tampilan ini. Berikut adalah source codenya. #include "stdafx.h" #include "SameGame.h"
#include "SameGameDoc.h" #include "SameGameView.h" #include "OptionsDialog.h" #ifdef _DEBUG #define new DEBUG_NEW #endif
Selanjutnya kita akan mengisi dua event handler yang baru saja kita menambahkan. Kedua fungsi ini pada dasarnya sama kecuali di lima lokasi seperti source code yang kita lihat di bawah. void CSameGameView::OnSetupBlockcount() { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Create the options dialog COptionsDialog dlg(true, this); // Set the row and column values dlg.m_nValue1 = pDoc->GetRows(); dlg.m_nValue2 = pDoc->GetColumns(); // Display the dialog if(dlg.DoModal() == IDOK) { // First delete the board pDoc->DeleteBoard(); // Get the user selected values pDoc->SetRows(dlg.m_nValue1); pDoc->SetColumns(dlg.m_nValue2); // Update the board pDoc->SetupBoard(); // Resize the view ResizeWindow(); } } void CSameGameView::OnSetupBlocksize() { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Create the options dialog COptionsDialog dlg(false, this); // Set the width and height values
dlg.m_nValue1 = pDoc->GetWidth(); dlg.m_nValue2 = pDoc->GetHeight(); // Display the dialog if(dlg.DoModal() == IDOK) { // First delete the board pDoc->DeleteBoard(); // Get the user selected values pDoc->SetWidth(dlg.m_nValue1); pDoc->SetHeight(dlg.m_nValue2); // Update the board pDoc->SetupBoard(); // Resize the view ResizeWindow(); } }
Sama seperti setiap event handler lain yang sudah kita buat, pertama kita mendapatkan pointer ke dokumen. Kemudian kita buat dialog dengan instantiating sebuah instance dari kelas itu. Berikut adalah perbedaan pertama antara dua fungsi. Dalam fungsi pertama, OnSetupBlockcount, kita lulus dalam nilai sebenarnya, dan dalam fungsi kedua, kita lulus dalam palsu. Dua baris berikutnya diatur integer publik menghargai m_nValue1 dan m_nValue2 menjadi baris dan kolom untuk fungsi pertama dan lebar dan tinggi untuk yang kedua. Dengan menetapkan nilai-nilai ini sebelum memanggil DoModal pada dialog kita memastikan bahwa nilai-nilai mulai di mengedit kontrol yang sesuai mereka. Baris berikutnya adalah di mana kita benar-benar pop-up dan menampilkan dialog dengan fungsi DoModal. Hal ini menunjukkan dialog dan tidak kembali kontrol untuk aplikasi sampai pengguna mengklik Ok, Batal atau tombol X untuk menutup. Kemudian fungsi mengembalikan nilai didasarkan pada bagaimana pengguna menutup dialog. Berikut kita uji untuk Ok jadi kita membandingkan nilai kembali terhadap IDOK. Jika pengguna mengklik Ok maka kita terus melakukan perubahan. Jika tidak kita mengabaikan segala sesuatu yang terjadi dan melanjutkan. Jika pengguna mengklik Ok pertama-tama kita harus menghapus board game tua dan membebaskan memori. Setelah selesai kita bisa menggunakan kita "setter" fungsi pada dokumen untuk menyimpan nilai-nilai bahwa pengguna yang dipilih. Ini adalah set terakhir dari perbedaan; kita menggunakan setter yang berbeda tergantung pada fungsi karena kita bekerja sama dengan nilai yang berbeda di masing-masing fungsi. Setelah mereka ditetapkan dalam dokumen dan board game kita perlu membuat sebuah board game baru dengan memanggil SetupBoard. Ini akan membuat board game baru dengan opsi yang dipilih baru. Akhirnya kita mengubah ukuran jendela untuk mencocokkan nomor baru baris / kolom atau ukuran blok baru. Game Anda sekarang harus dapat terlihat seperti ini.
Game kita hampir selesai. Pada tutorial ini kita menciptakan sebuah opsi dialog baru untuk dapat meminta pengguna untuk informasi ukuran. Kita kemudian diperbarui board game yang sesuai. Pilihan ini memungkinkan pengguna untuk memiliki banyak pengalaman yang berbeda. Apa tingkat dapat Anda benar-benar jelas pada dengan board game lima baris dengan lima kolom? Ada banyak kombinasi dari pilihan yang dapat mengubah kesulitan game dan membuat Anda mengubah strategi Anda. Itulah yang membuat game menyenangkan, mulai dapat memutar ulang dengan pilihan yang berbeda dengan cara yang berbeda. Kita hampir selesai dengan Game kita ini. Kita telah membahas beberapa topik mulai dari pemrograman event driven pemrograman GDIgraphics. Banyak topik yang kita bahas melampaui kesenjangan antara pemrograman game dan aplikasi pemrograman pada umumnya. Membangun aplikasi MFC adalah salah satu topik tersebut, tidak banyak game yang ditulis dalam MFC. Topik tutorial akhir ini adalah kita akan membahas bagaimana membuat sebuah undo / redo stack untuk game kita. Undo / redo merupakan fitur penting untuk sebagian besar aplikasi. Undo / Redo Stack Kita menyebutnya fitur ini "undo / redo stack" karena tipe data abstrak (ADT) stack. Stack adalah koleksi objek-objek yang mirip dengan stack piring di dapur Anda, satu-satunya cara Anda bisa mendapatkan pelat bawah adalah untuk pertama bergerak yang atas. Untuk menambahkan piring Anda hanya menempatkan mereka di atas stack. Dengan kata lain itu sejenis "Last In First Out" (LIFO) Ini adalah cara yang berguna untuk menyimpan bergerak terakhir Anda. Ketika Anda bergerak dalam game keadaan sebelumnya diletakkan di atas stack undo sehingga dapat
dikembalikan dalam urutan terbalik bahwa itu dibuat. Cara kita akan melakukan ini adalah untuk menyimpan salinan lama objek board game di undo stack sebelum kita menghapus potongan-potongan dari yang baru. Ketika kita membatalkan bergerak, board saat dimasukkan ke redo stack dan board atas dari undo stack sekarang dewan saat ini. Operasi redo adalah justru sebaliknya, menempatkan board saat di undo stack dan mengambil board atas off dari redo stack dan membuat board saat. Kita perlu untuk membuat beberapa perubahan untuk game board kita untuk membuatnya . Kita perlu membuat copy constructor untuk melakukan deep copy. Cukup tambahkan fungsi prototipe copy constructor tepat antara konstruktor default dan destructor di SameGameBoard.h. /* Default Constructor */ CSameGameBoard(void); /* Copy Constructor */ CSameGameBoard(const CSameGameBoard& board); /* Destructor */ ~CSameGameBoard(void);
Kita menggunakan deep copy constructor karena kita memiliki pointer ke beberapa memori yang dialokasikan secara dinamis. Ini berarti kita tidak bisa hanya menyalin pointer tetapi dinamis mengalokasikan lebih banyak memori dan kemudian menyalin isi ke dalam ruang memori. Kita tambahkan deep copy constructor source file pada game board (SameGameBoard.cpp) dengan menambahkan fungsi implementasi. #pragma once #include "SameGameBoard.h" #include <stack> class CSameGameDoc : public CDocument { protected: // create from serialization only CSameGameDoc(); virtual ~CSameGameDoc(); DECLARE_DYNCREATE(CSameGameDoc) // Operations public: /* Functions for accessing the game board */ COLORREF GetBoardSpace(int row, int col) { return m_board->GetBoardSpace(row, col); } void SetupBoard(void) { m_board->SetupBoard(); } int GetWidth(void) { return m_board->GetWidth(); } void SetWidth(int nWidth) { m_board->SetWidth(nWidth); } int GetHeight(void) { return m_board->GetHeight(); } void SetHeight(int nHeight) { m_board->SetHeight(nHeight); } int GetColumns(void) { return m_board->GetColumns(); } void SetColumns(int nColumns) { m_board->SetColumns(nColumns); }
int GetRows(void) { return m_board->GetRows(); } void SetRows(int nRows) { m_board->SetRows(nRows); } void DeleteBoard(void) { m_board->DeleteBoard(); } bool IsGameOver() { return m_board->IsGameOver(); } /* Notice we removed the implementation of this function */ int DeleteBlocks(int row, int col); int GetRemainingCount() { return m_board->GetRemainingCount(); } int GetNumColors() { return m_board->GetNumColors(); } void SetNumColors(int nColors); /* Undo/redo functions */ void UndoLast(); bool CanUndo(); void RedoLast(); bool CanRedo(); // Overrides public: virtual BOOL OnNewDocument(); protected: /* Functions to clear undo/redo stacks */ void ClearUndo(); void ClearRedo(); /* Instance of the game board--notice that we made it a pointer */ CSameGameBoard* m_board; /* Undo stack */ std::stack
m_undo; /* Redo stack */ std::stack m_redo;
// Generated message map functions protected: DECLARE_MESSAGE_MAP() };
Pertama-tama kita perlu menyertakan stack header sehingga kita dapat menggunakan stack class. Karena kita akan mengubah variabel m_board ke pointer kita harus mengubah dari menggunakan dot operator untuk panah atau penunjuk operator melalui setiap fungsi dalam dokumen. Berikutnya pada daftar perubahan adalah kenyataan bahwa kita bergerak pelaksanaan fungsi DeleteBlocks ke source file. Fungsi ini telah menjadi lebih terlibat dari sekedar satu baris sehingga kita akan memindahkannya. Kemudian kita tambahkan enam fungsi baru, empat adalah public function dan dua protected function. Fungsi umum dibagi menjadi dua kelompok, satu set, UndoLast function dan RedoLast, benar-benar melakukan undo dan redo, sedangkan set kedua, CanUndo dan CanRedo, untuk tes sederhana kita akan gunakan untuk mengaktifkan dan menonaktifkan pilihan menu ketika mereka tidak tersedia. Protected function yang fungsi pembantu sederhana untuk membersihkan dan deallocate memori terkait dari kedua stack. Akhirnya kita menambahkan dua deklarasi stack undo /
redo. Dengan perubahan pointer ke board game, ada beberapa hal yang perlu ditambahkan ke fungsi yang ada sebelum kita menambahkan fungsi baru. Berikut ini adalah source code untuk dokumen baru di SameGameDoc.cpp #include "stdafx.h" #include "SameGame.h" #include "SameGameDoc.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // CSameGameDoc IMPLEMENT_DYNCREATE(CSameGameDoc, CDocument) BEGIN_MESSAGE_MAP(CSameGameDoc, CDocument) END_MESSAGE_MAP() // CSameGameDoc construction/destruction CSameGameDoc::CSameGameDoc() { // There should always be a game board m_board = new CSameGameBoard(); } CSameGameDoc::~CSameGameDoc() { // Delete the current game board delete m_board; // Delete everything from the undo stack ClearUndo(); // Delete everything from the redo stack ClearRedo(); } BOOL CSameGameDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // Set (or reset) the game board m_board->SetupBoard(); // Clear the undo/redo stacks ClearUndo(); ClearRedo();
return TRUE; } void CSameGameDoc::SetNumColors(int nColors) { // Set the number of colors first m_board->SetNumColors(nColors); // Then reset the game board m_board->SetupBoard(); }
int CSameGameDoc::DeleteBlocks(int row, int col) { // Save the current board in the undo stack m_undo.push(new CSameGameBoard(*m_board)); // Empty out the redo stack ClearRedo(); // Then delete the blocks int blocks = m_board->DeleteBlocks(row, col); // Clear the undo stack at the end of a game if(m_board->IsGameOver()) ClearUndo(); // Return the number of blocks return blocks; } void CSameGameDoc::UndoLast() { // First make sure that there is a move to undo if(m_undo.empty()) return; // Take the current board and put it on the redo m_redo.push(m_board); // Take the top undo and make it the current m_board = m_undo.top(); m_undo.pop(); } bool CSameGameDoc::CanUndo() { // Can undo if the undo stack isn't empty return !m_undo.empty(); } void CSameGameDoc::RedoLast() { // First make sure that there is a move to redo if(m_redo.empty()) return; // Take the current board and put it on the undo
m_undo.push(m_board); // Take the top redo and make it the current m_board = m_redo.top(); m_redo.pop(); } bool CSameGameDoc::CanRedo() { // Can redo if the redo stack isn't empty return !m_redo.empty(); } void CSameGameDoc::ClearUndo() { // Delete everything from the undo stack while(!m_undo.empty()) { delete m_undo.top(); m_undo.pop(); } } void CSameGameDoc::ClearRedo() { // Delete everything from the redo stack while(!m_redo.empty()) { delete m_redo.top(); m_redo.pop(); } }
Pertama-tama kita perlu menyertakan stack kepala sehingga kita dapat menggunakan kelas stack. Karena kita akan mengubah variabel m_board ke pointer kita harus mengubah dari menggunakan dot operator untuk panah atau penunjuk operator melalui setiap fungsi dalam dokumen. Berikutnya pada daftar perubahan adalah kenyataan bahwa kita bergerak pelaksanaan fungsi DeleteBlocks ke file sumber. Fungsi ini telah menjadi lebih terlibat dari sekedar satu baris sehingga kita akan memindahkannya. Kita kemudian menambahkan enam fungsi baru, empat adalah fungsi umum dan dua dilindungi. Fungsi umum dibagi menjadi dua kelompok, satu set fungsi, UndoLast dan RedoLast, benar-benar melakukan undo dan redo, sedangkan set kedua, CanUndo dan CanRedo, tes sederhana kita akan gunakan untuk mengaktifkan dan menonaktifkan pilihan menu ketika mereka tidak tersedia. Fungsi lindung yang fungsi pembantu sederhana untuk membersihkan dan deallocate memori terkait dari kedua stack. Akhirnya kita menambahkan dua deklarasi stack undo / redo. Dengan perubahan pointer ke board game, ada beberapa hal yang perlu ditambahkan ke fungsi yang ada sebelum kita menambahkan fungsi baru. Berikut ini adalah source code untuk dokumen baru di SameGameDoc.cpp
/* Functions for undo/redo */ afx_msg void OnEditUndo(); afx_msg void OnEditRedo(); /* Functions to update the undo/redo menu options */ afx_msg void OnUpdateEditUndo(CCmdUI *pCmdUI); afx_msg void OnUpdateEditRedo(CCmdUI *pCmdUI);
Sekarang mari kita lihat source file. Pada message map anda akan menemukan empat baris baru yang mengatur event, associating event, ID dan fungsi. Sekali lagi kita melihat ini sebelumnya. ON_COMMAND(ID_EDIT_UNDO, &CSameGameView::OnEditUndo) ON_COMMAND(ID_EDIT_REDO, &CSameGameView::OnEditRedo) ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CSameGameView::OnUpdateEditUndo) ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, &CSameGameView::OnUpdateEditRedo)
Implementasi dari dua event ON_COMMAND cukup sederhana dan mengikuti pola yang telah kita lihat sebelumnya, mendapatkan pointer ke dokumen, memanggil fungsi pada dokumen dan akhirnya menyebabkan view untuk redraw. Lakukan ini untuk kedua undo dan redo. void CSameGameView::OnEditUndo() { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Call undo on the document pDoc->UndoLast(); // Force the view to redraw Invalidate(); UpdateWindow(); } void CSameGameView::OnEditRedo() { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Call redo on the document pDoc->RedoLast(); // Force the view to redraw Invalidate(); UpdateWindow();
}
Event handler untuk event ON_UPDATE_COMMAND_UI, pertama mendapatkan pointer ke dokumen untuk akses ke game board. void CSameGameView::OnUpdateEditUndo(CCmdUI *pCmdUI) { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Enable option if it is available pCmdUI->Enable(pDoc->CanUndo()); } void CSameGameView::OnUpdateEditRedo(CCmdUI *pCmdUI) { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Enable option if it is available pCmdUI->Enable(pDoc->CanRedo()); }
Sekarang stack undo / redo sekarang sudah berfungsi. Periksalah pada pilihan menu undo, sekarang sudah dapat diaktifkan. Klik dan kita akan melihat original game board game. Periksa pilihan menu redo dan akan diaktifkan sekarang. Klik itu dan Anda akan kembali ke tempat sebelum undo. Berikuttampilan game yang dapat kita lihat sekarang.
Coba tekan Ctrl + Z setelah kita buat beberapa pergerakan objek dan kita akan melihat undo yang bekerja dengan keyboard. Sekarang coba tekan Ctrl + Y untuk redo. Apa itu bekerja? Tidak? Nah kita bisa memperbaikinya. Ingat bahwa pada pilihan menu untuk redo kita menunjukkan kepada pengguna bahwa Ctrl + Y akan mengirim ON_COMMAND untuk ID_EDIT_REDO. Itulah yang disebut akselerator.
Untuk mengakses akselerator, buka Resource View dari menu View (di bawah Other Windows) atau tekan accelerator dengan tekan Ctrl + Shift + E pada Visual Studio. Kemudian buka opsi Accelerator bawah SameGame.rc dan klik dua kali pada IDR_MAINFRAME untuk membuka Accelerator Editor. Pada gambar di bawah ini kita telah menambahkan akselerator untuk perintah redo.
Untuk menambahkannya, klik pada baris kosong pada akselerator terakhir, lalu di kolom ID; ini akan tampil menu drop-down yang memungkinkan untuk memilih ID_EDIT_REDO, ID dari pilihan menu untuk perintah redo; memberikan kunci Y dan modifikator Ctrl (Ctrl + Y). Sekarang kita kompile game dan menjalankannya. Seperti yang kita telah menambahkan kombinasi keystroke yang sekarang mengirimkan ON_COMMAND untuk ID_EDIT_REDO.
Sekarang kita sudah selesai membuat game, Let’s Play and Let’s Have Fun Together :D
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