Приклад номер два: хрестики-нулики C + + Builder

Хрестики-нулики – одна з найстаріших ігор, відомих людині Вона не складна, і я думаю, що більшість людей знайомі з її правилами У нашій версії хрестиків-нуликів (програма називатиметься Tic-Tac-Toe – Таке англійське на звання гри) ми вивчимо докладніше внутрішні особливості графіки в CBuilder, процес користувача введення / виводу і можливості форми в малюванні і відображенні обєктів

На рис 34 показана закінчена форма з розташованими на ній компонентами VCL, необхідними для успішного функціонування програми Ну так, вона порожня Вся робота по промальовуванню форми буде зроблена самим додатком

Рис 34 Форма програми Tic-Tac-Toe (хрестики-нулики)

Вихідний текст програми Tic-Tac-Toe (хрестики-нулики) знаходиться на компакт-диску

У цьому прикладі ми збираємося динамічно створювати растрові малюнки і їх образи (images) в програмі Самі малюнки гранично прості (власне, це горезвісні хрестик і нулик), але на їх прикладі ми дізнаємося, як можна використовувати обєкт TBitmap для створення власних малюнків безпосередньо в додатку, не покладаючись на зовнішні файли або інші ресурси Для початку треба оголосити деякі змінні в заголовному файлі додатки Додайте в заголовний файл Unit1h наступні описи:

class TForm1 : public TForm

{

__published: // IDE-managed Components void __fastcall FormPaint(TObject *Sender

void __fastcall FormMouseDown(TObject *Sender,

Приклад номер два: хрестики-нулики TMouseButton Button, TShiftState Shift, int X, int Y) private: / / User declarations

Graphics::TBitmap *FpXBitmap Graphics::TBitmap *FpYBitmap int FnGameBoard[3][3]

int FnWhichPlayer  public: // User declarations

__fastcall TForm1(TComponent* Owner)

}

Два обєкти TBitmap будуть використані для внутрішньої отрисовки растрових малюнків і їх відображення на формі відповідно до ходів гравців Масив FnGameBoard використовується для зберігання поточних обраних клітин ігрового поля і приналежності цих клітин І нарешті, змінна класу FnWhichPlayer використовується для відстеження, чий хід

Після того як ви додали всі змінні, прийшов час звернутися до конструктора форми У ньому ми инициализируем всі змінні – члени класу і проробимо роботу з ініціалізації обєктів – растрових малюнків (TBitmap) В системі Давайте спочатку поглянемо на код, а потім будемо розбиратися, як він працює:

__fastcall TForm1::TForm1(TComponent* Owner)

: TForm(Owner)

{

/ / Привласнюємо право ходу першого гравця

FnWhichPlayer = 1

/ / Ініціалізіруем ігрове поле FpXBitmap = new Graphics :: TBitmap FpXBitmap-> Width = ClientWidth / 3 – 2 FpXBitmap-> Height = ClientHeight / 3 – 2

/ / Заливаємо малюнок кольором форми

FpXBitmap-&gtCanvas-&gtBrush-&gtColor = Color Trect r

rLeft = 0

rTop = 0

rRight = FpXBitmap-&gtWidth rBottom = FpXBitmap-&gtheight FpXBitmap-&gtCanvas-&gtFillRect(  r) FpYBitmap = new Graphics::TBitmap

FpYBitmap-&gtWidth = ClientWidth/3 – 2 FpYBitmap-&gtHeight = ClientHeight/3 – 2

/ / Заливаємо малюнок кольором форми FpYBitmap-> Canvas-> Brush-> Color = Color FpYBitmap-> Canvas-> FillRect (r)

/ / Відображаємо малюнок X (хрестик) FpXBitmap-> Canvas-> MoveTo (0,0)

FpXBitmap-&gtCanvas-&gtLineTo(FpXBitmap-&gtWidth,   FpXBitmap-&gtHeight) FpXBitmap-&gtCanvas-&gtMoveTo(FpXBitmap-&gtWidth,0)

FpXBitmap-&gtCanvas-&gtLineTo(0,  FpXBitmap-&gtHeight)

/ / Відображаємо малюнок Y (нулик) FpYBitmap-> Canvas->

Ellipse(0,0,FpYBitmap-&gtWidth,FpYBitmap-&gtHeight)

}

Перша пара блоків конструктора порівняно зрозуміла Перед тим як рухатися далі, ми присвоюємо змінним класу розумні початкові значення Важка частина починається зі створення обєкта TBitmap

Життя і пригоди обєкта TBitmap C + + Builder

Чому ми повинні встановлювати всі ці властивості TBitmap при його створенні Раніше все, що ми робили, – це створювали новий обєкт і потім або завантажували його з диска, або асоціювали з ним растровий малюнок

Як і всі порядні растрові малюнки в Windows, обєкт TBitmap починає життя з растрового малюнка Windows 1ґ1, і, щоб що-небудь в ньому намалювати, ми повинні «розтягнути» його до розміру, який збираємося використовувати У даному випадку ми повинні збільшити кожен з двох растрових малюнків до однієї третини клієнтської області нашої форми Чому клієнтської Справа в тому, що властивість Width (Ширина) форми включає в себе рамки навколо форми, а властивість Height (Висота) форми включає в себе панель заголовка Якщо використовувати ці властивості, перші дві кнопки виглядали б ширше, ніж третя Оскільки це не те, що нам треба, доведеться пошукати інші шляхи Обєкти CBuilder мають два типи висоти і ширини Властивості ClientWidth і ClientHeight являють собою відповідно ширину і висоту клієнтської області форми Клієнтська область – це те, що залишилося б на формі, якби з неї видалили меню, рамки, заголовки і панель стану

Після того як ми визначили властивості Width і Height, Обєкти – растрові малюнки (TBitmap) Можна розглядати як прості графічні обєкти, які очікують, щоб ми їх заповнили При створенні вони мають білий фон (background) Оскільки це буде погано виглядати на сталевому тлі нашої форми, ми повинні перевстановити колір фону, використовуючи властивість Brush (Дослівно – кисть) малюнка, у якого є власна властивість Color (Колір) Властивість Color властивості Brush буде використано для фону при всіх операціях, повязаних з малюванням, для даного малюнка У нашому випадку ми повинні залити білий малюнок кольором фону форми Це зроблено за допомогою методу FillRect Для використання FillRect Ініціалізується прямокутник (обєкт TRect) З межами, які хочете заповнити У даному випадку ми використовуємо кордону самого малюнка, оскільки хочемо залити його цілком

rLeft = 0

rTop = 0

rRight = FpXBitmap-&gtWidth rBottom = FpXBitmap-&gtHeight

Після того як ми отримали прямокутник для заливки і колір, вказаний у властивості Brush властивості Canvas обєкта, який ми хочемо залити (в нашому випадку растрового малюнка), ми порушуємо метод FillRect для цього обєкта (Bitmap-&gtCanvas), І все інше робиться автоматично

Наступним кроком після заливки малюнка буде малювання того образу (image), який буде відображений на малюнку Для цього ви можете використовувати будь-який з певних методів малювання Canvas або ж передати властивістьCanvas-&gtHandle  будь стандартній програмі

Windows, яка опиниться під рукою Це зручно, коли ви хочете використовувати графічну функцію третьої програми у вашому додатку на CBuilder

Для растрового малюнка X (хрестик) ми використовуємо стандартні команди Canvas MoveTo  і LineTo для малювання двох діагональних ліній, що утворюють потребується нам хрест Для растрового малюнка Y (нулик) використовується інший метод обєкта Canvas, Який називається Ellipse Ellipse відобразить еліптичну фігуру на заданому Canvas Оскільки наші малюнки майже квадратні, результат функції Ellipse буде дуже схожий на круг Чим ближче будуть значення Width і Heidht, Тим більш наближеним до кола буде і результат

Отже, тепер ми маємо закінчені растрові малюнки, які можна використовувати так само, як якщо б ми завантажили їх з файлу або асоціювали в редакторі компонентів Це чарівна риса обєктно-орієнтованого програмування Незалежно від того, як ви добились проміжного результату, виходити з нього ви будете абсолютно однаковим чином У такій гнучкості і є міць обєктно-орієнтованого програмування

CBuilder також розпорядженні методами для збереження вашого малюнка на диску Ви можете створити справжній графічний редактор на основі системи растрових малюнків CBuilder Для збереження растрового малюнка на диску створіть обєкт – потік файлового введення / виводу (file stream), використовуючи клас TFileStream, а потім використовуйте метод SaveToStream обєкта TBitmap для запису останнього на диск

У конструкторі також викликається допоміжна функція ResetBoard У ній немає ніяких сюрпризів Все, що ми робимо, – це присвоюємо всім клітинам ігрового поля певне сигнальне значення, в даному випадку 0

void  TForm1::ResetBoard(void)

{

/ / Ініціалізіруем ігрове поле

for ( int i=0, i&lt3, ++i ) for ( int j=0, j&lt3, ++j ) FnGameBoard[i][j] = 0

}

Гірша частина завдання залишилася позаду Ми динамічно створили обєкт – растровий малюнок, зробили його потрібного розміру і намалювали в його полі (Canvas) ті зображення, які повинен бачити користувач Тепер наступний крок – це показати малюнок користувачеві Спочатку давайте займемося малюванням форми, а потім перейдемо до обробки введення користувача

Додайте у форму обробник для події OnPaint Найпростіший спосіб це зробити – перейти на сторінку Events в Object Inspector, знайти там подія OnPaint і двічі клацнути мишею в клітці сітки праворуч від нього CBuilder автоматично створить новий обробник з коректним назвою У загальному випадку, присвоєне йому імя буде Formxxx, де xxx – це імя події без «On» Тому для події OnPaint коректне імя буде FormPaint Ось як виглядає код для обробника події FormPaint:

void __fastcall TForm1::FormPaint(TObject *Sender)

{

/ / Спочатку малюємо вертикальні лінії на формі

Canvas-&gtMoveTo( ClientWidth/3,0 )

Canvas-&gtLineTo( ClientWidth/3,ClientHeight )

Canvas-&gtMoveTo(  (2*ClientWidth)/3,0)

Canvas-&gtLineTo( (2*ClientWidth)/3, ClientHeight )

/ / Тепер горизонтальні

Canvas-&gtMoveTo( 0, ClientHeight/3 )

Canvas-&gtLineTo( ClientWidth, ClientHeight/3 ) Canvas-&gtMoveTo( 0, (2*ClientHeight)/3 )

Canvas-&gtLineTo( ClientWidth, (2*ClientHeight)/3 )

/ / Відображаємо малюнки по клітках

for ( int i=0, i&lt3, ++i ) for ( int j=0, j&lt3, ++j )

{

int nXPos = (ClientWidth/3)*j + 1 int nYPos = (ClientHeight/3)*i + 1

/ / Якщо в клітці – хрестик

if ( FnGameBoard[i][j] == 1 )

{

Canvas-&gtDraw(nXPos, nYPos, FpXBitmap )

}

else

/ / Якщо в клітці – нулик

if (FnGameBoard[i][j] == 2 )

{

Canvas-&gtDraw(nXPos, nYPos, FpYBitmap )

}

}

}

Ніяких особливих сюрпризів у цьому коді нам не зустрілося Ми малюємо ігрове поле, пересуваючись по клієнтській області форми і малюючи горизонтальні і вертикальні лінії Потім ми перевіряємо клітини на предмет їх приналежності першого або другого гравцеві Якщо клітина належить першому гравцеві, ми малюємо в ній хрестик, використовуючи для цього растровий малюнок X, створений нами в конструкторі Якщо клітина належить другому гравцеві, ми малюємо в ній нулик, використовуючи для цього растровий малюнок Y, створений нами в конструкторі Позиція малюнка – це позиція квадрата зі зміщенням в один піксель, щоб не затерти лінії поля

На даному етапі ми завершили ту частину, яка повязана з виведенням на екрані Тепер перейдемо до обробки власне процесу гри Нам треба відстежити кожен клацання миші на ігровому полі, визначити клітку, в якій була натиснута миша, і закріпити цю клітку за тим гравцем, чий хід Звичайно, при цьому треба перевірити, чи не зайнята вже клітина

Форма не містить події клацання миші1 Проте існують події натискання кнопки миші і відпускання натиснутою кнопки миші Ми привяжемо наш обробник до події MouseDown (Натискання кнопки миші) Зазвичай не має значення, яке подія ви обробляєте, якщо, звичайно, у вас не передбачена різна конкретна реакція на події MouseDown і MouseUp (Як було у нас в прикладі Scribble з попередньої глави) Звернення до

1 Абсолютно незрозуміло, чому автор так вважає У форми ЕСТЬ подія OnClick (Виникає при клацанні миші) У подальшому коді всі посилання на подію OnMouseDown можна замінити на OnClick Проте нехай все залишиться як є – Прямуючи перев

подіїMouseDown  зумовить трохи більш швидку реакцію програми, ніж звернення до події MouseUp, Але це не буде помітно середньому користувачеві

Додайте обробник для події MouseDown, Двічі клацнувши на цю подію у вікні Object Inspector Наступний код додайте в створений при цьому у вихідному файлі обробник події FormMouseDown:

void        fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

TShiftState Shift, int x, int y)

{

int nRow, int nCol,

/ / Визначаємо, що за клітка

if ( X &lt ClientWidth/3 ) nCol = 0

else

if ( X &lt (2*ClientWidth)/3 ) nCol = 1

else

nCol = 2

if ( Y &lt ClientHeight/3 ) nRow = 0

else

if ( Y &lt (2*ClientHeight)/3 ) nRow = 1

else

nRow = 2

/ / Перевіряємо, чи не зайнята клітина

if ( FnGameBoard[nRow][nCol] = 0 )

{

MessageBeep(0)

}

else / / Ні – присвоюємо клітку цьому гравцеві

{

FnGameBoard[nRow][nCol] = FnWhichPlayer

/ / Передаємо хід

if ( FnWhichPlayer == 1 ) FnWhichPlayer = 2

else

FnWhichPlayer = 1 Invalidate()

/ / Перевіримо, не виграв чи хто

if ( CheckForWinner() )

{

ResetBoard() FnWhichPlayer = 1 Invalidate()

}

}

}

У цьому обробнику спочатку перевіряється, яку клітину вибрав користувач Це виконується порівнянням X і Y координат, які передає методу CBuilder, з клітинами нашої сітки Ми просто перевіряємо, де стався клацання – в першій, другій або третій третини сітки по вертикалі, а потім і по горизонталі Перетворивши отримані значення, ми отримуємо дві координати – рядок і стовпець у сітці Це відмінно підходить для нашого методу зберігання даних, які представлені у вигляді двовимірного масиву

Після того, як визначені рядок і стовпець, ігрове поле перевіряється на предмет того, чи не зайнята вже вибрана клітина Якщо це так, то викликається відомий вам метод MessageBeep, Що показує користувачеві неприпустимість повторного вибору Якщо ж клітина порожня, вона присвоюється ходячи гравцеві Нарешті, після цього ми викликаємо ще один метод форми для перевірки, не переміг чи хто Ось код для цього методу:

BOOL  Tform1::CheckForWinner(void)

{

int Winner

for ( int nPlayer = 1 nPlayer&lt3 ++nPlayer )

{

/ / Перевіряємо построчно

for ( int nRow = 0 nRow&lt3 ++nRow)

{

/ / Припускаємо, що цей гравець виграв

nWinner = nPlayer

/ / Перевіряємо всі стовпці Якщо хоча б в одному

/ / Ні ідентифікатора гравця, він не переміг тут

for (int nCol = 0 nCol&lt3 ++nCol )

if ( FnGameBoard[nRow][nCol] = nPlayer ) nWinner = 0

if ( nWinner = 0 )

{

String s = Гравець + String (nWinner) + виграв ; MessageBox (NULL, sc_str (), Переможець, MB_OK) return true

}

}

/ / Тепер перевіряємо по стовпцях

for ( int nCol = 0 nCol&lt3 ++nCol )

{

/ / Припускаємо, що цей гравець виграв

nWinner = nPlayer

/ / Перевіряємо всі рядки Якщо хоча б в одній

/ / Ні ідентифікатора гравця, він не переміг тут

for ( int nRow = 0 nRow&lt3 ++nRow )

if FnGameBoard[nRow][nCol] = nPlayer ) nWinner = 0

if ( nWinner = 0 )

{

String s = Гравець + String (nWinner) + виграв ; MessageBox (NULL, sc_str90, Переможець, MB_OK) return true

}

}

/ / Нарешті, перевіряємо діагоналі

if ( FnGameBoard[0][0] == nPlayer &amp&amp FnGameBoard[1][1] == nPlayer &amp&amp FnGameBoard[2][2] == nPlayer )

{

String s = Гравець + String (nPlayer) + виграв ; MessageBox (NULL, sc_str90, Переможець, MB_OK) return true

}

if ( FnGameBoard[0][2] == nPlayer &amp&amp FnGameBoard[1][1] == nPlayer &amp&amp FnGameBoard[2][0] == nPlayer )

{

String s = Гравець + String (nPlayer) + виграв ; MessageBox (NULL, sc_str90, Переможець, MB_OK) return true

}

}

/ / Якщо дійшли досюда, перевіряємо, чи є ще

/ / Порожні клітини (не кінець чи гри) for (int nRow = 0 nRow <3; + + nRow)

{

for ( int nCol = 0 nCol &lt3 ++nCol )

if ( FnGameBoard[nRow][nCol] == 0 ) return false

}

/ / Повідомляємо про кінець гри

MessageBox (NULL, Бойова нічия”, Кінець гри, MB_OK) return true

}

Цей метод цікавий лише з точки зору використання в ній обєкта String (Рядок) для формування повідомлення переможцю String – Один з найкорисніших класів у CBuilder, вам треба звикнути до нього Обєкти String набагато гнучкіші, ніж char * або масив символів (char []), які вам, напевно, доводилося використовувати в C або C + + в інших середовищах String також є частиною бібліотеки стандартних шаблонів, про яку ми поговоримо набагато докладніше трохи нижче

Найприємнішою з можливостей String є можливість перетворювати в рядок речі, які не є рядками або символами Передавши аргумент типу integer в конструктор String, Ми отримуємо уявлення цього числа у вигляді рядка Два обєкти String можуть бути обєднані за допомогою оператора «+» Використовуючи дві ці можливості, ми й створили рядок виводу про перемогу першого гравця з рядків «Гравець» та «виграв» І цифри 1, що має видатися вам очарователен вим, якщо ви звикли використовувати sprintf і хвилюватися про типи аргументів і довжині рядків

Остання перевірка в методі проводиться на предмет кінця гри Якщо всі клітини заповнені, то гра закінчена внічию На жаль, це дуже частий результат у грі хрестики-нулики – я впевнений, ви знаєте це не гірше за мене

Наша гра вже більш-менш закінчена, але нам треба звільнити ресурси, відведені для TBitmap в конструкторі обєкту Найкраще місце для цього – деструктор обєкта, і це якраз те, що ми повинні зробити Ось код деструктора:

__fastcall  TForm1::~TForm1(void)

{

delete FpXBitmap delete FpYBitmap

}

Зауважте, що ми використовуємо модифікатор fastcall для деструктора ~TForm1 – Це перевизначення деструктора базового класу TForm Оскільки TForm — обєкт VCL, а всі методи VCL повинні бути визначені з модифікатором __fastcall, То вам також треба додавати це в свої перевизначені методи Якщо ви цього не зробите, то компілятор видасть вам повідомлення про помилку Virtual function TForm1 :: ~ TForm1 conflicts with base class Forms :: TForm» («Віртуальна функція конфліктує з базовим класом») У разі появи таких помилок перевірте модифікатори

Все, що нам залишилося зробити, – це доповнити заголовний файл описами методів, які ми додали в форму у вихідному файлі Ось оновлений заголовний файл Unit1h, всі зміни в якому показані підсвічуванням:

//———————————————————

#ifndef Unit1H

#define Unit1H

//———————————————————

#include &ltvcl\Classeshpp&gt

#include &ltvcl\Controlshpp&gt

#include &ltvcl\StdCtrlshpp&gt

#include &ltvcl\Formshpp&gt

//———————————————————

class TForm1 : public TForm

{

__published: // IDE-managed components void __fastcall FormPaint(TObject *Sender)

void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)

private: // User declarations

Graphics::TBitmap  *FpXBitmap

Graphics::TBitmap  *FpYBitmap

int FnGameBoard[3][3]

int FnWhichPlayer

void ResetBoard(void)

BOOL CheckForWinner(void)

public: // User declarations

__fastcall TForm1(TComponent *Owner)

virtual     fastcall ~TForm1(void)

}

//——————————————————–

extern TForm1 *Form1

//——————————————————–

#endif

Запускаємо гру

Останній крок у програмуванні ігор завжди

найвеселіше, бо

це тестування

програмного продукту Запустіть програму і заповніть кілька кліток Спробуйте вибрати

одну і ту ж клітку двічі Заповніть клітини так, щоб переможна комбінація перебувала в

рядку, потім в стовпці, потім на діагоналі Переконайтеся, що всі варіанти працюють Нарешті, заповніть всі ігрове поле так, щоб на ньому не було переможної комбінації, щоб упевнитися в тому, що перевірка закінчення гри працює Після того як все перевірили, запросіть друга або дитини на гру і насолоджуйтеся На рис 35 показаний типовий приклад гри в процесі, з кількома хрестиками і нуликами, а на рис 36 показана гра, що завершилася перемогою одного з гравців

Рис 35 Типовий приклад гри хрестики-нулики

Рис 36 Перемога першого гравця

CBuilder надає багатий асортимент графічних обєктів і компонентів, які дозволяють легко визначати і відображати на екрані у вашому додатку растрові малюнки, іконки та іншу графіком

Ось дещо з того, що ми освоїли в цій главі:

·&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Завантаження растрових малюнків (bitmap) прямо з файлу під час виконання програми

·&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Відображення растрових малюнків на екрані при посередництві обєкта Canvas

·&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Створення растрового малюнка під час виконання і малювання в його полі

·&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Обробка виведення графіки на екран під час виконання за допомогою малювання прямо в поле форми

·&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Я сподіваюся, ви дещо засвоїли про графічної підсистеми CBuilder У наступній розділі ми глибше досліджуємо VCL (Visual Component Library, бібліотека візуальних компонентів) CBuilder і відкриємо нові таємниці компонентів

Джерело: Теллес М – Borland C + + Builder Бібліотека програміста – 1998

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*