Шаг 83.
Библиотека OWL.
Игра Баше

    На этом шаге мы рассмотрим реализацию этой игры.

    Студент 4 курса (2013-14 уч.год) Добрыдин Антон реализовал игру Баше средствами бибилиотеки OWL.

    Для начала опишем правила игры Баше. На столе лежит n спичек, компьютер и игрок берут со стола по очереди от одной до трех спичек, выигрывает тот, кто забирает последнюю спичку. Количество спичек n, лежащее на столе во время начала новой игры, задает пользователь. Это значение не превосходит 100 (n<100).

    Опишем выигрышную стратегию компьютера. Если количество лежащих спичек на столе в начале игры не кратно четырем, то компьютер берет столько спичек, чтобы на столе осталось количество спичек, кратное четырем. Если количество лежащих спичек на столе в начале игры не кратно четырем, то такая ситуация проигрышна для игрока, который ходит первым, поэтому предлагаем первым сходить игроку. Если игрок соглашается, то дальше компьютер придерживается стратегии описанной выше, иначе компьютер берет любое количество спичек от одной до трех, и продолжает брать случайное число спичек до тех пор, пока игрок не допустит ошибку (если такое случится) т.е. пока после хода игрока на столе не останется количество спичек не кратное четырем. После ошибки игрока компьютер продолжает действовать по первоначальной стратегии.

    Внешний вид приложения приведен на рисунке 1.


Рис.1. Внешний вид приложения

    Приведем текст программы.

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\dialog.h>
#include <owl\radiobut.h>
#include <owl\edit.h>
#include <owl\dc.h>
#include <owl\validate.h>
#include <string.h>
#include <cstring.h>
#include "pr57_1.rc"

struct TTransBuf
{
  char N[3];  // Буфер обмена.
}transBuf;

// Класс приложения.
class TApp: public TApplication
{
  public:
	 TApp():TApplication() {}
	 void InitMainWindow();
};

// Класс главного окна.
class TWndw : public  TFrameWindow
{
  public:
	 TWndw (TWindow *parent, const char far *title);
  protected:
	int flag,n,k;
	TClientDC *clientDC;
	TBitmap *bitmapl,*bitmapl1;
	void SetupWindow();
	void CleanUpWindow();
	void CmGetData();
	void CmGetData2();
	void CmGetData3();
	void Ubrat(int);
	void DrawObject (TPoint &point, TBitmap *bitmap);
	void Paint(TDC&, BOOL, TRect&);
	void CmGame();

	DECLARE_RESPONSE_TABLE(TWndw);
};

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
	  EV_COMMAND (CM_GETDATA, CmGetData),
	  EV_COMMAND (CM_GETDATA2, CmGetData2),
	  EV_COMMAND (CM_GETDATA3, CmGetData3),
	  EV_COMMAND(CM_GAME,CmGame),
END_RESPONSE_TABLE;

//Класс диалогового окна
class TDlg:public TDialog
{
  public:
	  TDlg(TWindow *parent, TResId resld);
  protected:
	  void SetupWindow();
	  BOOL CanClose();
};

// TWndw::TWndw()
// Это конструктор главного окна.
TWndw::TWndw(TWindow *parent, const char far *title):
			 TFrameWindow (parent, title)
{
  // Допускает переход по клавише табуляции между
  // управляющими элементами.
  EnableKBHandler();
  // Установить меню окна.
  AssignMenu(MENU_1);
  // Определить размеры и расположение окна.
  Attr.X = 230;
  Attr.Y = 60;
  Attr.W=680;
  Attr.H=600;
  // Инициализировать буфер обмена.
  memset (&transBuf, 0, sizeof(transBuf)); 
  // Создать управляющие элементы в окне.
  new TButton(this, CM_GAME, "Новая игра", 240, 40, 150, 20);
  new TButton(this, CM_GETDATA, "Убрать одну спичку", 240, 80, 150, 20);
  new TButton(this, CM_GETDATA2, "Убрать две спички", 240, 105, 150, 20);
  new TButton(this, CM_GETDATA3, "Убрать три спички", 240, 130, 150, 20);
}

// Эта функция реагирует на команду NEW GAME
void TWndw::CmGame()
{
  // Создать окно диалога.
  TDialog *dialog = new TDlg(this, ADDRDLG);
  // Выполнить окно диалога.
  int result = dialog->Execute();
  // Если пользователь нажал кнопку ОК...
  if (result == IDOK)
  // Заставить главное окно перерисоваться
  // с новыми данными диалогового окна.
 {
  //Кол-во спичек , которое ввел пользователь
  n=atoi(transBuf.N); 
  // Заставить окно перерисоваться.	 
  Invalidate();
  //Послать в функции ноль, признак начала новой игры.
  Ubrat(0);
 }
}

// ************************
// Реализация класса TDlg.
// ************************
// TDlg::TDlg()
// Конструктор диалогового окна.
TDlg::TDlg(TWindow *parent,TResId resId):
			TDialog(parent,resId)
{
  TEdit *edit;
  TValidator  *valid;

  // Создать  объект управления OWL для  каждого
  // управляющего  элемента  диалогового  окна,   который
  // будет участвовать  в передаче,   а  также подключить
  // объекты-валидаторы к областям
  // ввода,   которые     необходимо  проверить.
  edit=new TEdit(this,ID_EDIT,sizeof(transBuf.N));
  valid = new  TFilterValidator("0-9");
  edit->SetValidator(valid);
  //Назначить буфер обмена диалогового окна.
  TransferBuffer=&transBuf;
}

// TDlg::SetupWindow()
// Эта функция переопределяет SetupWindow() класса
// TDialog и вызывается перед выводом диалогового
// окна на экран. Она может использоваться для
// заключительной инициализации.
void TDlg::SetupWindow()
{
  // ВАЖНО! Необходимо выполнить обычную установку.
  TDialog::SetupWindow();
}

// TDlg::CanClose()
//
// Эта функция, которая переопределяет функцию
// TWindows, позволяет программе выполнять
// заключительные операции перед закрытием
// диалогового окна.
BOOL TDlg::CanClose()
{
  // Копировать данные в буфер обмена.
  TransferData(tdGetData);
  // Проверить, не являются ли пустыми важные
  // текстовые поля и правильна ли их длина.
  if ( (strlen(transBuf.N)==0)||(atoi(transBuf.N)==0) )
  {
    // Если данные ошибочные, выдать сообщение
    // пользователю и оставить на экране
    // диалоговое окно для коррекции ошибок.
   MessageBox ("Введите число не равное нулю.", "Ошибка", 
          MB_OK | MB_ICONEXCLAMATION);
   return FALSE;
  }
  else
    return TRUE;
}

// TWndw::SetupWindow()
// Эта функция, переопределяющая функцию
// SetupWindow() базового класса, обеспечивает
// сервис по установке данного окна.
void TWndw::SetupWindow()
{
  // Всегда вызывайте SetupWindow() базового класса.
  TFrameWindow::SetupWindow();
  // Получить DC (контекст устройства) для окна-
  // клиента. Как правило, вам не следует
  // создавать DC в этом месте программы и затем
  // обращаться к нему из любого места в программе.
  // Однако, для удобства в настоящей программе это
  // важное правило нарушается.
  clientDC = new TClientDC(HWindow);
  // Получить дескриптор экземпляра приложения.
  HINSTANCE instance = *GetApplication();
  // Построить объекты растрового изображения
  bitmapl = new TBitmap (instance, BITMAP_1);
  bitmapl1 = new TBitmap (instance, BITMAP_2);
}

// TWndw::CleanUpWindow()
// Эта функция переопределяет функцию, унаследованную
// от TFrameWindow. Она выполняет заключительную
// очистку этого окна.
void TWndw::CleanUpWindow()
{
  // Удалить GDI-объекты и DC.
  delete bitmapl;
  delete bitmapl1;
  delete clientDC;
}

// TWndw::Paint()
// Эта функция, которая реагирует на сообщение
// WM_PAINT, вызывается каждый раз, когда
// необходима перекраска окна пользователя.
void TWndw::Paint(TDC&, BOOL, TRect&)
{
  TPoint point;
  point.x=0;
  point.y=0;
  //Отобразит растровое изображение в bitmapl1 в точке point
  DrawObject (point, bitmapl1);
  if (n||k)
  {
   char s1[5],s[30]="Компьютер взял ";
   itoa(k,s1,10);
   strcat(s,s1);
   strcat(s,", n= ");
   itoa(n,s1,10);
   strcat(s,s1);
   //Сообщение о том, сколько спичек взял компьютер и сколько осталось
   (*clientDC).TextOut(240,165,s);
  }
 //Нарисовать все спики, которые еще остались.
 for(int i=0;i<n;i++)
  {
	 point.x=10+i%22*30;
	 point.y=250+i/22*40;
         //Отобразит растровое изображение в bitmapl1 в точке point
         //(изображение спички)
	 DrawObject (point, bitmapl);
  }
}

// TWndw::DrawObject()
// Эта функция отобразжает растровое изображение в
// точке с координатами, заданными в объекте TPoint.
void TWndw::DrawObject(TPoint  &point, TBitmap *bitmap)
{
  BITMAP  bm;
 // Прочитать контекст устройства
 // для памяти, основанный на
 // контексте окна пользователя.
 TMemoryDC memDC (*clientDC);
 // Выбрать растровое изображение
 // в контекст устройства памяти.
 memDC.SelectObject (*bitmap);
 // Получить атрибуты растрового изображения.
 bitmap->GetObject (bm);
 // Отобразить растровое
 // изображение в данной точке.
  clientDC->BitBlt(point.x, point.y, bm.bmWidth,
	 bm.bmHeight, memDC, 0,0, SRCCOPY);
}

// TWndw::CmGetData()
// Эта функция реагирует на команду Get_Data 
//(нажатие на кнопку "Убрать одну спичку")
void TWndw::CmGetData()
{
  TWndw::Ubrat(1);
}

// Эта функция реагирует на команду Get_Data2 
//(нажатие на кнопку "Убрать две спички")
void TWndw::CmGetData2()
{
 TWndw::Ubrat(2);
}

// Эта функция реагирует на команду Get_Data3 
//(нажатие на кнопку "Убрать три спички")
void TWndw::CmGetData3()
{
 TWndw::Ubrat(3);
}

void TWndw::Ubrat(int j)
{
  flag=1;
  if (!j) //Если j=0, то игра началась заново.
  {
   if  (!(n%4))  //Кол-во спичек кратно 4, 
                 //проигрышная ситуация для того, кто ходит первым,
   {  	         //поэтому предлагаем сходить первому пользователю.
    if( MessageBox("Не хотите ли вы сходить первым?","", 
                       MB_YESNO | MB_ICONQUESTION)==IDYES)
    flag=0; //Если игрок согласен ходить первым то обнулим флаг
   }
   if (flag) //Компьютер ходит первым.
   {
     flag=0;
     MessageBox("Я хожу первым","",MB_OK | MB_ICONASTERISK);
     if (n%4)  //Если количество спичек не кратно 4,
	k=n%4; //то берется столько спичек, чтобы осталось число кратное 4,
               //т.е. берется кол-во, 
               равное остатку от деления количества спичек на 4
     else
     if (n%3)  //Если кол-во спичек кратно 4, то ситуация проигрышная,
	k=n%3; //возьмем кол-во спичек равное остатку от деления на 3, 
               //если кол-во спичек не кратно 3;
     else      //если кол-во спичек кратно трем, 
               //то возмем 3 спички.(можно использовать random(2)+1).
       k=3;
     n-=k;//уменьшаем кол-во оставшихся спичек на кол-во взятых.
     if(!n) //если спички кончались, то компьютер выиграл.
	MessageBox("Я выиграл","", MB_OK | MB_ICONASTERISK);
   }
  }
  else //если j!=0, значит игра продолжается и 
       //j - это кол-во взятых спичек пользователем
  {
    //заполняем строку для вывода сообщения пользователю о том, 
    //сколько спичек он взял,
    //и ск-ко спичек еще осталось.
    char s[10],s1[13]="вы взяли ";
    itoa(j,s,10);   
    strcat(s1,s);
    strcat(s1," n=");
    n-=j;//уменьшаем количество оставшихся спичек на количество взятых.
    itoa(n,s,10);
    strcat(s1,s);
    MessageBox(s1,"", MB_OK | MB_ICONASTERISK);
    // Заставить окно перерисоваться.
    Invalidate();
    if(!n)//если спички кончались, то игрок выиграл.
      MessageBox("Вы выиграли","", MB_OK | MB_ICONASTERISK);
    else //спички еще остались, ход компьютера.
    {
     if (n%4)   //Если количество спичек не кратно 4
	k=n%4;  //то берется столько спичек, чтобы осталось число кратное 4,
    else	//т.е. берется количество, 
                //равное остатку от деления количества спичек на 4
      if (n%3)	//Если кол-во спичек кратно 4, то ситуация проигрышная,
	k=n%3;  //возьмем количество спичек, равное остатку от деления на 3, 
                //если количество спичек не кратно 3
      else      //если количество спичек кратно трем, 
                //то возьмем 3 спички.(можно использовать random(2)+1).
	k=3;
    n-=k;  //уменьшаем количество оставшихся спичек на количество взятых.
    if(!n)  //если спички кончились, то компьютер выиграл.
     MessageBox("Я выиграл","",
		 MB_OK | MB_ICONASTERISK);
    }
 }
 // Заставить окно перерисоваться.
 Invalidate();
}

void TApp::InitMainWindow()
{
  TFrameWindow *wndw = new TWndw (0,"Игра БАШЕ");
  SetMainWindow(wndw);
}

int OwlMain(int,char *[])
{
  return TApp().Run();
}

    Файл ресурсов:

#ifndef WORKSHOP_INVOKED
#include "windows.h"
#endif
#include <owl\window.rh>
#define ADDRDLG     110
#define MENU_1      100
#define CM_GETDATA  200
#define CM_GETDATA2 199  
#define CM_GETDATA3 198
#define ID_EDIT     101
#define BITMAP_1    301
#define BITMAP_2    302
#define CM_EXIT     24310
#define CM_GAME     104
#ifdef RC_INVOKED
MENU_1 MENU
{
  POPUP "&File"
  {
	  MENUITEM "New game", CM_GAME
	  MENUITEM SEPARATOR
	  MENUITEM "E&xit", CM_EXIT
  }
}
BITMAP_1 BITMAP "101.bmp"
BITMAP_2 BITMAP "102.bmp"


ADDRDLG DIALOG 81, 27, 194, 119
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "BASHE"
FONT 8, "MS Sans Serif"
{
 DEFPUSHBUTTON "OK", IDOK, 45, 84, 50, 14
 PUSHBUTTON "Cancel", IDCANCEL, 111, 84, 50, 14
 LTEXT "Vvedite kol-vo spichek", -1, 59, 38, 104, 13
 EDITTEXT ID_EDIT, 55, 58, 98, 13, WS_BORDER | WS_TABSTOP
}
#endif
Текст этого приложения можно взять здесь.

    Рассмотрим некоторые функции более детально.

    Функция CmGame().

void TWndw::CmGame()
{
  // Создать окно диалога.
  TDialog *dialog = new TDlg(this, ADDRDLG);
  // Выполнить окно диалога.
  int result = dialog->Execute();
  // Если пользователь нажал кнопку ОК...
  if (result == IDOK)
  // Заставить главное окно перерисоваться
  // с новыми данными диалогового окна.
 {
  //Кол-во спичек , которое ввел пользователь
  n=atoi(transBuf.N); 
  // Заставить окно перерисоваться.	 
  Invalidate();
  //Послать в функции ноль, признак начала новой игры.
  Ubrat(0);
 }
}

    Эта функция реагирует на событие NEW_GAME, которое происходит после нажатия на кнопку "Новая игра". После нажатия на кнопку "Новая игра" создается диалоговое окно с компонентом tEdit, для ввода числа количества спичек лежащих на столе во время начала игры. После закрытия диалогового окна нажатием на кнопку ОК (result == IDOK) введенное количество спичек сохраняется в буфере обмена transBuf. После закрытия диалогового окна вызывается функция Invalidate() для перерисовки окна и отображения на столе необходимого количества спичек. После перерисовки окна вызывается функция Ubrat(0), в функцию в качестве параметра посылается число ноль как признак начала новой игры.

    Функция SetupWindow().

// TWndw::SetupWindow()
// Эта функция, переопределяющая функцию
// SetupWindow() базового класса, обеспечивает
// сервис по установке данного окна.
void TWndw::SetupWindow()
{
  // Всегда вызывайте SetupWindow() базового класса.
  TFrameWindow::SetupWindow();
  // Получить DC (контекст устройства) для окна-
  // клиента. Как правило, вам не следует
  // создавать DC в этом месте программы и затем
  // обращаться к нему из любого места в программе.
  // Однако, для удобства в настоящей программе это
  // важное правило нарушается.
  clientDC = new TClientDC(HWindow);
  // Получить дескриптор экземпляра приложения.
  HINSTANCE instance = *GetApplication();
  // Построить объекты растрового изображения
  bitmapl = new TBitmap (instance, BITMAP_1);
  bitmapl1 = new TBitmap (instance, BITMAP_2);
}

    В данной функции следует обратить внимание на следующие моменты.

  1. Сохраним контекст устройства clientDC = new TClientDC(HWindow) для того, чтобы в дальнейшем использовать его при перерисовки окна приложения.
  2. Создадим два объекта растрового изображения из идентификаторов ресурса растрового изображения bitmapl = new TBitmap (instance, BITMAP_1) и bitmapl1 = new TBitmap (instance, BITMAP_2).

    Функция Paint().

// TWndw::Paint()
// Эта функция, которая реагирует на сообщение
// WM_PAINT, вызывается каждый раз, когда
// необходима перекраска окна пользователя.
void TWndw::Paint(TDC&, BOOL, TRect&)
{
  TPoint point;
  point.x=0;
  point.y=0;
  //Отобразит растровое изображение в bitmapl1 в точке point
  DrawObject (point, bitmapl1);
  if (n||k)
  {
   char s1[5],s[30]="Компьютер взял ";
   itoa(k,s1,10);
   strcat(s,s1);
   strcat(s,", n= ");
   itoa(n,s1,10);
   strcat(s,s1);
   //Сообщение о том, сколько спичек взял компьютер и сколько осталось
   (*clientDC).TextOut(240,165,s);
  }
 //Нарисовать все спики, которые еще остались.
 for(int i=0;i<n;i++)
  {
	 point.x=10+i%22*30;
	 point.y=250+i/22*40;
         //Отобразит растровое изображение в bitmapl1 в точке point
         //(изображение спички)
	 DrawObject (point, bitmapl);
  }
}

    Эта функция, реагирующая на сообщение WM_PAINT, вызывается каждый раз, когда необходима перерисовка окна пользователя. Вызов функции DrawObject (point, bitmapl1) обеспечивает отображение растрового изображения bitmapl1 ("шапка" приложения) в точке point. Условие (n||k) используется для определения: начата ли новая игра, или продолжается старая. Оно применяется для того, чтобы определить необходимо ли сообщение о том, сколько спичек взял компьютер и сколько осталось, или еще нет. Цикл реализует отображение n растровых изображений (спичек) в точках point, используя функцию DrawObject().

    Функция Ubrat().

void TWndw::Ubrat(int j)
{
  flag=1;
  if (!j) //Если j=0, то игра началась заново.
  {
   if  (!(n%4))  //Кол-во спичек кратно 4, 
                 //проигрышная ситуация для того, кто ходит первым,
   {  	         //поэтому предлагаем сходить первому пользователю.
    if( MessageBox("Не хотите ли вы сходить первым?","", 
                       MB_YESNO | MB_ICONQUESTION)==IDYES)
    flag=0; //Если игрок согласен ходить первым то обнулим флаг
   }
   if (flag) //Компьютер ходит первым.
   {
     flag=0;
     MessageBox("Я хожу первым","",MB_OK | MB_ICONASTERISK);
     if (n%4)  //Если количество спичек не кратно 4,
	k=n%4; //то берется столько спичек, чтобы осталось число кратное 4,
               //т.е. берется кол-во, 
               равное остатку от деления количества спичек на 4
     else
     if (n%3)  //Если кол-во спичек кратно 4, то ситуация проигрышная,
	k=n%3; //возьмем кол-во спичек равное остатку от деления на 3, 
               //если кол-во спичек не кратно 3;
     else      //если кол-во спичек кратно трем, 
               //то возмем 3 спички.(можно использовать random(2)+1).
       k=3;
     n-=k;//уменьшаем кол-во оставшихся спичек на кол-во взятых.
     if(!n) //если спички кончались, то компьютер выиграл.
	MessageBox("Я выиграл","", MB_OK | MB_ICONASTERISK);
   }
  }
  else //если j!=0, значит игра продолжается и 
       //j - это кол-во взятых спичек пользователем
  {
    //заполняем строку для вывода сообщения пользователю о том, 
    //сколько спичек он взял,
    //и ск-ко спичек еще осталось.
    char s[10],s1[13]="вы взяли ";
    itoa(j,s,10);   
    strcat(s1,s);
    strcat(s1," n=");
    n-=j;//уменьшаем количество оставшихся спичек на количество взятых.
    itoa(n,s,10);
    strcat(s1,s);
    MessageBox(s1,"", MB_OK | MB_ICONASTERISK);
    // Заставить окно перерисоваться.
    Invalidate();
    if(!n)//если спички кончались, то игрок выиграл.
      MessageBox("Вы выиграли","", MB_OK | MB_ICONASTERISK);
    else //спички еще остались, ход компьютера.
    {
     if (n%4)   //Если количество спичек не кратно 4
	k=n%4;  //то берется столько спичек, чтобы осталось число кратное 4,
    else	//т.е. берется количество, 
                //равное остатку от деления количества спичек на 4
      if (n%3)	//Если кол-во спичек кратно 4, то ситуация проигрышная,
	k=n%3;  //возьмем количество спичек, равное остатку от деления на 3, 
                //если количество спичек не кратно 3
      else      //если количество спичек кратно трем, 
                //то возьмем 3 спички.(можно использовать random(2)+1).
	k=3;
    n-=k;  //уменьшаем количество оставшихся спичек на количество взятых.
    if(!n)  //если спички кончились, то компьютер выиграл.
     MessageBox("Я выиграл","",
		 MB_OK | MB_ICONASTERISK);
    }
 }
 // Заставить окно перерисоваться.
 Invalidate();
}

    В данной функции стоит обратить внимание на следующий момент: если параметр j меньше либо равен нулю, то это говорит о том, что игрок начал новую игру; для этого в функции присутствует условие if(!j). Если данное условие не выполняется, то игра продолжается дальше. Остальная часть функции реализует выигрышную стратегию компьютера, описанную выше.

    На следующем шаге мы рассмотрим еще одно приложение, созданное с помощью библиотеки OWL.




Предыдущий шаг Содержание Следующий шаг