Sapyor o'yinini "o'ynaymiz" :)

Bekorchilikda papkalar aro sayohat qilib o’tirib, tasodifan windows tarkibidagi sapyor o’yiniga kirib qoldim (winmine.exe). Ushbu o’yinni ko’pchilik hech bo’lmaganda bir marta o’ynab ko’rgan albatta. Oddiy mantiq va tezkorlik talab qiladigan o’yin. Bu o’yinni biz bir vaqtlar talabalikda sheriklarim bilan kim tezroq tugatishdan musobaqa ham o’ynar edik. Hullas hozir bu o’yinni yana ko’rdimu, o’sha paytdagi bir gap yodimga tushib ketdi. Dasturlashni endi o’rganayotgan paytimda ushbu o’yinni dastur orqali o’ynaydigan dasturcha qilgandim. Qarangki o’sha dastur hali ham arxivimda turgan ekan. Dastur kodlariga qarab kayfiyatimni ham ko’tarib oldim. Hozir aytadigan gaplarim shu dasturcha haqida. Dasturdan ko’ra uni qanday qilinganligi qiziqroq bo’lsa kerak menimcha.

Dastur Visual Basicda tuzilgan bo’lib, quyidagicha ishlar edi:
  • Ochiq turgan (ishlayotgan) Sapyor dasturini oynasini topish;
  • Topilgan oynadan maydon holatini (hozirda qaysi kataklar ochilgan, qaysi biriga bayroq qo’yilgan kabi) o’qish;
  • Maydon holatiga qarab, mina bor bo’lishi mumkin bo’lgan va bo’sh bo’lishi aniq bo’lgan joylarni aniqlash;
  • Va Sapyor oynasida kerakli kataklarda sichqonchaning kerakli tugmalarini bosish;
Visual Basicdagi kodlarni ham har ehtimolga qarshi pastda qoldirib, ushbu dasturni C# da yozamiz.
Sapyor oynasini izlash uchun Windows API tarkibidagi FindWindow funksiyasidan foydalanamiz. Ushbu funksiya ochiq turgan oynalarni sarlavha va/yoki klassi bo’yicha izlaydi va natija sifatida topilgan oyna xendlini qaytaradi. Topilgan xendldan oyna bilan ishlovchi boshqa ko’pgina funksiyalarda foydalanish mumkin. Xendl sodda qilib aytganda oynaning shaxsiy, unikal nomeri desak ham bo’laveradi.
Ushbu funksiyani dasturda e’lon qilish uchun DllImport attributidan foydalanamiz. DllImport dasturga funksiyani qaysi tashqi kutubxonadan ekanligini bildirish va ba’zi qo’shimcha attributlarini berish uchun ishlatiladi.

[DllImport("user32.dll")]
public static extern IntPtr FindWindow (String className, String windowName);

Demak, sapyor oynasini izlaymiz:

hWindow = FindWindow(null, "Сапер");

Funksiyada berilayotgan birinchi argument – izlanayotgan oynani klassi (bizning misolda har qanday nomga ega bo’lgan klass so’ralyapti, ya’ni qiymat null, ikkinchisi esa oyaning sarlavhasidagi matni (caption). Agar shularga mos oyna topilmasa, unda funksiya 0 qiymat qaytaradi.

Maydon holatini o’qishni qanday amalga oshiraman deb turganimda bir fikr kelib qoldi: Maydon holatini undagi piksellar rangidan aniqlash. Har bir katakning burchagi va markazidagi piksel rangidan uning holatini aniqlash mumkin ekan.
Avvalo oynadan maydon joylashgan qismni ajratib olamiz (rasmda qizil chiziq bilan belgilangan). Ushbu qism eni va bo’yini 16 ga (katak o’lchami shuncha pikseldan iborat) bo’lsak maydondagi ustun va qatorlar soni kelib chiqadi.

Sapyor oynasidagi tasvirni o’qish uchun avvalo oynaning kontekstini (Device Context) aniqlab so’ng kerakli sohani tasviri ma’lumotlarini olish mumkin bo’ladi.
Oynaning DC sini olishga GetDC funksiyasi yordam beradi. Oynaning xendli uning butun oynaning parametrlarini o’qish va o’zgartirish uchun kerak bo’lsa, DC ni oynaning chiziluvchi klient(ishchi) sohasini ifodalovchi identifikator deb ham tushunish mumkin. Barcha chiqarish qurilmalariga ma’lumot uzatish uning konteksti orqali amalga oshiriladi.
Oldin tashqi kutubxonalardan ishlatadigan funksiyalarni e’lon qilib olib, so’ng oynaning kontekstini aniqlaymiz:
IntPtr hDC = GetDC(hWindow); 

Endi oynaning o’lchamlarini olamiz:

// Oynaning client sohasi o'lchamlarini olish
RECT rc = new RECT();
GetClientRect(hWindow, ref rc);

// client sohasi o'lchamlaridan keraksiz sohalar
// uzunligini ayirib tashlash, ya'ni maydon chekkalarini
int cw = rc.right - rc.left - 12 - 8;
int ch = rc.bottom - rc.top - 55 - 8;

GetPixel funksiyasi berilgan konteksdagi tasvirdan berilgan koordinataga mos nuqtaning rangini olish uchun foydalaniladi. Tasvirdan har bir katak markazidagi va burchagidagi pikselning rangi yordamida maydon holatini “o’qiymiz”:

            // Maydon holatini piksellar rangiga asoslanib "o'qish"
            for (int i = 0; i < rows; i++)
            {

                for (int j = 0; j < cols; j++)
                {

                    // client oynasidagi koord.ni hisoblash
                    int x = j * 16 + 12;    
                    int y = i * 16 + 55;

                    // katak qirg'og'idagi ramkadan katak ochilgan
                    // yoki ochilmaganligi aniqlanadi:
                    if (GetPixel(hDC, x, y) == 0x808080) // ochilgan katak
                    {

                        // katak markazidagi nuqta rangi olinib,
                        // shunga asoslanib, katakda nima joylashganligi
                        // aniqlanadi
                        int color = GetPixel(hDC, x + 8, y + 8);
                        switch (color)
                        {

                            // ochilgan bo'sh joy
                            default:        field[j, i] = 0;
                                            break;
                            // 1
                            case 0xff0000:  field[j, i] = 1;
                                            break;
                            // 2
                            case 0x8000:    field[j, i] = 2;
                                            break;
                            // 3
                            case 0xff:      field[j, i] = 3;
                                            break;
                            // 4
                            case 0x800000:  field[j, i] = 4;
                                            break;
                            // 5
                            case 0x80:      field[j, i] = 5;
                                            break;
                            // 6
                            case 0x808000:  field[j, i] = 6;
                                            break;
                            // portlagan mina
                            case 0:         field[j, i] = -1;
                                            MessageBox.Show("Portlab ketibdiku :)");
                                            return;

                        }

                        
                    }
                    else // hali ochilmagan katak
                    {

                        int color = GetPixel(hDC, x + 8, y + 6);

                        switch (color)
                        {

                            default:    field[j, i] = 256; 
                                        break;
                            // bayroq qo'yilgan
                            case 0xff:  field[j, i] = 128; 
                                        break;

                        }

                    }

                }

            }

Olingan maydon holatiga qarab qayerga bayroq qo’yish yoki qayerni ochishni (mina yo’q joyni albatta) hal qilsak bo’ldi. Buni yozib o’tirmayman, dasturni ko’chirib olib ko’rish mumkin.
Va nihoyat ohirgi amal – sapyor dasturi oynasida sichqonchani bosish amali. Bunda bizga SendMessage funksiyasi yordam beradi. SendMessage oynalarga habar jo’natish funksiyasi bo’lib, ushbu funksiya yordamida OS va dasturlar, dasturlar va dasturlar o’zaro ma’lumot almashishi, habarlashishda foydalanadi. Bizga kerakli, sichqoncha holati haqidagi habarlar quyidagilardan iborat:

WM_LBUTTONDOWN
WM_LBUTTONUP
WM_RBUTTONDOWN
WM_RBUTTONUP

SendMessage funksiyasi:
SendMessage ( hWindow, msg, wParam, lParam);

Bu yerda: hWindow – habar jo’natiladigan oyna xendli, msg – habar (masalan WM_LBUTTONDOWN), wParam va lParam – habarga bog’liq ma’lumotlar.
Yuqoridagi habarlar uchun wParam – sichqoncha tugmasini bildiradi (MK_LBUTTON — chap tugma, MK_RBUTTON — o’ng tugma), lParam – sichqoncha koordinatasi, kichik 4 bayt – x, katta 4 bayt – y ni berish uchun.

// Sapyor oynasiga berilgan koord.da sichqonchaning
// o'ng tugmasi bosilgani habarini jo'natish (bayroq qo'yish).
private void buttonClickR(int col , int row) 
{
            
    // client oynasidagi koord.ni hisoblash
    int x=col*16+12;
    int y=row*16+55;
            
    SendMessage (   
                   hWindow, 
                   (int)Const.WM_RBUTTONDOWN, 
                   (int)Const.MK_RBUTTON, 
                   (y << 16) + x);
    SendMessage(
                   hWindow, 
                   (int)Const.WM_RBUTTONUP, 
                   (int)Const.MK_RBUTTON, 
                   (y << 16) + x);
    
}

Endi dasturni ishlatib natijasini kuzatishimiz mumkin.

Mana shunaqa gaplar. Umuman olganda bu narsa arzimagan oddiy narsa bo’lib tuyulishi mumkin. Biroq boshlang’ich dasturchi bo’lgan odam – menga, o’sha paytda bu narsa ancha qiziq va muammoli bo’lgan edi. Shuningdek dasturlashni kichikroq, o’yinsifat dasturlar tuzish misolida o’rganish jarayonni ancha qiziqarli qiladi manimcha.
Dastur kodi: WinminePlay.zip (94 kb)
VB dagi kod: WinminePlay_vb.zip (4 kb)
  • 1

7 комментариев

shranet
Windows 7 da ishlatib ko'rgandim, ClientRect bilan muammo chiqdi. cols/rows da adashib ketdi. Uyga borib XP da ishlatib ko'raman. Lekin qiziq narsa. Thanks.
0
U2B3K
Ha to'g'ri, Win7 da sapyorni boshqa versiyasi. Shunga kataklar kattaligi, maydon qirg'oqlari farq qilgan bo'lsa kerak. Shunisi hayoldan ko'tarilibdi )
0
geniuz
2shranet, bir paytlar karta uynaymiz deb harakat qilganimiz esimga tushib ketti :)
0
shranet
Mani hayolimga ham shu kegan.
0
Nasriddin
Siz aytgan dastur win 7da adressini ololmaydi manimca. Ruslan akani thread bn iwlaw video kursini qilib kurganimda puskni adresini ololmagan
0
U2B3K
FindWindow win7 da ishlayveradi (API bu). Gap sapyor oynasi o'lchamlari va ranglarida. Yuqorida shranet da ishlagan, faqat kataklarni adashtirib yuborgan
0
geniuz
puskni ololmaganini sababi component nomi boshqacha bulgandir…
0