// не закрашувати
Canvas->Font->Color = clBlack; Canvas->TextOutA(x,y,ms); // вивести текст }
1.2 Вивід зображень за допомогою пікселів
Малювати на канві можна різними способами. Перший варіант — малювання по пикселам. Для цього використовується властивість канви Pixels. Цією властивістю є двовимірний масив Canvas->Pixels[intX][int Y], який відповідає за кольори канви. Наприклад, Canvas->PixeIs[10][20] відповідає кольору пиксела, 10-го зліва і 20-го зверху. З масивом пикселов можна звертатися як з будь-якою властивістю: змінювати колір, задаючи пикселу нове значення, або визначати його колір по значенню, що зберігається в нім. Наприклад, Canvas->PixeIs[10][20]= clBlack — це завдання пикселу чорного кольору.
Давайте спробуємо намалювати графік деякої функції F(X) наканве компоненту Imagel, якщо відомий діапазон її зміни Ymax і Ymin і діапазон зміни аргументу Xmin і Хтах. Це можна зробити такою процедурою:
float X,Y; // координати функції
int PX,PY; // координати пикселов
for (РХ = 0: РХ <= Imagel->Width; Рх++)
//Х- координата, відповідна пикселу з координатою РХ
X = Xmin + РХ * (Xmax - Xmin) / Imagel->Width;
Y = F(X); //PY - координата пиксела, відповідна координаті Y
PY = imagel->Height - (Y - Ymin)*Imagel->Height/(Ymax-Ymin); //Устанавливается чорний колір вибраного пиксела
Imagel->Canvas->Pixels[PX][PY] = clBlack;}
У цьому коді вводяться змінні X і Y, що є значеннями аргументу і функції, а також змінні РХ і PY, що є координатами пикселов, відповідними X і Y. Сама процедура складається з циклу по всіх значеннях горизонтальної координати пикселов РХ компоненту Imagel. Спочатку вибране значення Рхнересчитиваєтсявсоответствующєєзначенієх. Потім проводиться виклик функції і визначається її значення Y. Це значення перераховується у вертикальну координату пиксела PY. І на закінчення колір пиксела з координатами (РХ, PY) встановлюється чорним.
Спробуйте створити відповідне застосування і подивитися, як воно працює. Хай для простоти ми орієнтуватимемося на функцію sin(X), для якої Xmin=0, Хmax=4pi (2 періоди в радіанах), Ymin=-1, Ymax=l.
Почніть новий проект, помістіть на нього компонент Image і кнопку з написом «Намалювати», в обробник події OnClick якою запишіть код, аналогічний приведеному вище, але що конкретизує функцію:
#definePi 3.14159
float X, Y; // координати функції
int РХ, PY; // координати пикселов
for (РХ = 0: РХ <- Imagel->Width; PX++)
{//X - координата, відповідна пикселу з координатою РХ
X = РХ * 4 * Pi / imagel->Width;
Y = sin(X); //PY - координата пиксела, відповідна координаті У
PY = Imagel->Height - (Y+1) * Imagel->Height / 2; //Устанавливается чорний колір вибраного пиксела
Imagel->Canvas->Pixels(PX][PY] = clBlack; }
1.3 Збереження конфігурації в файлах ini
|Файли .ini - це текстові файли, призначені для зберігання інформації про настройки різних програм. Інформація у файлі логічно групується в розділи, кожен з яких починається оператором заголовка, поміщеним в квадратні дужки. Наприклад [Desktop|]. У рядках, наступних за заголовком, міститься інформація, що відноситься до даного розділу, у формі:
<ключ>=<значення>
[dBASE| Files|]
Driver32=C|:\WINDOWS\SYSTEM\odbcjt32.dll
Файли .ini, якправило, зберігаютьсявкаталозі Windows|, якийможназнайтизадопомогоюфункції GetWindowsDirectory|.
У C++Builder|роботузфайлами .ini найпростішездійснюватизадопомогоюствореннявпрограміоб'єктутипу TIniFile|. Цей тип описаний в модулі inifiles|, який треба підключати до програми оператором uses| (автоматично це не робиться).
При створенні об'єкту типу TlniFile| в нього передається ім'я файлу .ini, з яким він зв'язується. Файл повинен існувати до створення об'єкту.
Для запису значень ключів існує багато методів: WriteString|, WriteInteger|, WriteFloat|, |і ін. Кожен з них записує значення відповідного типу. Оголошення всіх цих методів дуже схожі. Наприклад:
void| fastcall| WriteString| (const| AnsiString| Section|
const| AnsiString| Ident|, const| AnsiString|Value|);
void| fastcall| Writelnteger| (const| AnsiString| Section|
const| AnsiString| Ident|, int| Value|);
Увсіхоголошеннях Section| - розділфайлу, Ident| - ключцьогорозділу, Value| - значенняключа. Якщовідповіднийрозділабоключвідсутнійуфайлі, вінавтоматичностворюється.
Єаналогічніметодичитання: ReadString|, Readlnteger|, ReadFloat|, ReadBool|іін. Наприклад:
AnsiString| fastcall| ReadString| (const| AnsiString| Section|
const| AnsiString| Ident|, const| AnsiString|Default|);
int| fastcall| Readlnteger| (const| AnsiString| Section|
const| AnsiString| Ident|, int| Default|);
Методи повертають значення ключа| розділу Section|. Параметр Default|визначає значення, що повертається у випадку, якщо у файлі не вказано значення відповідного ключа.
Перевірити наявність значення ключа можна методом ValueExists|, в який передаються імена розділу і ключа. Метод DeleteKey| видаляє з файлу значення вказаного ключа у вказаному розділі. Перевірити наявність у файлі необхідного розділу можна методом SectionExists|. Метод EraseSection| видаляє з файлу вказаний розділ разом зі всіма його ключами. Є ще ряд методів, які ви можете подивитися у вбудованій довідці C++Builder|.
Подивимося на прикладі, як все це можна використовувати для установки програми, запам'ятовування її настройоки і для видалення програми.
Зробіть просте тестову програму. Перенесіть на форму три кнопки Button| і діалог FontDialog|. Перша кнопка (назвіть її BInst| і задайте напис Install|) імітуватиме установку програми. Точніше, не саму установку, оскільки копіювати файли з загрузочної дискети ми не будемо, а тільки створення файлу .ini у каталозі Windows|. Друга кнопка (назвіть її BUnlnst| і задайте напис Unlnstall|) імітуватиме видалення програми. Тут ми не видалятимемо саму програму з диска, а тільки видалимо з каталога Windows| наш файл, а третя кнопка (назвіть її BFont| і задайте напис Font|) дозволятиме змінювати ім'я шрифту, використовуваного у формі, і забезпечить запам'ятовування цього шрифту у файлі .ini, щоб надалі при запуску програми можна було читати цю настройку і задавати її формі.
2.1 Код гри
#include <vcl.h>
#pragma hdrstop
#include "UBilliard.h"
#include <math.h>
#include "inifiles.hpp"
#pragma package(smart_init)
#pragma resource "*.dfm"
// розмір столу
int const w = 600;
int const h = 300;
class TGameStatus {
protected: char* gsStatus;
public:
void gsBegin(){
gsStatus="qsBegin"; }
void gsGame(){
gsStatus="gsGame"; }
void gsGameOver(){
gsStatus="gsGameOver"; }};
TGameStatus *GameStatus = new TGameStatus;
struct TPlayer{
int balls; };
class TLose{
public:
int x,y;
int ballsInside;
int R;
void Draw();};
class TBall { public:
float x,y, dx,dy;
int R;
bool exists, stopped ;
int col,ID,count;
TList *Items;
void Draw();
void Stop();
bool InLose(int &Number);
void outFrom(TBall b);
TBall CollisedWith();
~TBall();};
class TCue {
public:
bool Visible;
TBall ToBall;
float Angle, energy;
void draw();
void Hit();};
void TCue::Hit(){
TBall *ToBall;
ToBall->dx =-cos(Angle)*energy;
ToBall->dy = -sin(Angle)*energy;
ToBall->stopped = False;
Visible = False;}
class TBilliardTable{
public:
int Width, Height, Left, Right, Top, Bottom;
TList* Ball;
TList* Lose;
TCue Cue;
void Draw();};
float GetAngToXY( pBall b ;float hitX, hitY){
float dx, dy, d;
dx = b->x - hitX;
dy = b->y - hitY;
d = sqrt(dx*dx+dy*dy);
if(dy>0)
Result = arccos(dx/d);
else Result = -arccos(dx/d); }
void TBall::outFrom(pBall:b){
{ AB,
aa, bb,
V1, V2,
aPrXx, aPrXy,
aPrYx, aPrYy,
aPrX, aPrY,
bPrXx, bPrXy,
bPrYx, bPrYy,
bPrX, bPrY,
alfaA, betaA, gammaA,
Extended alfaB, betaB, gammaB;
if(b == NULL ) exit;
AB = sqrt(sqr(x-b->x)+sqr(y-b->y));
V1 = sqrt(dx*dx+dy*dy);
V2 = sqrt(b->dx*b->dx+b->dy*b->dy);
aPrXx = 0;
aPrXy = 0;
bPrXx = 0;
bPrXy = 0;
////////-ball #1-
if(V1>0 ) {
if((b->y-y)>0
alfaA = arccos((b->x-x)/AB);
else alfaA = -arccos((b->x-x)/AB);
if(dy>0
then gammaA = arccos(dx/V1)
else gammaA = -arccos(dx/V1);
betaA = gammaA-alfaA;
aPrX = V1*cos(betaA);
aPrY = V1*sin(betaA);
aPrXx = aPrX*cos(alfaA);
aPrXy = aPrX*sin(alfaA);
aPrYx = aPrY*sin(alfaA);
aPrYy = aPrY*cos(alfaA); }
//////////////-ball #2-
if(V2>0 ) {
if((y-b->y)>0
then alfaB = arccos((x-b->x)/AB); //=alfaA+pi
else alfaB = -arccos((x-b->x)/AB);
if(b->dy>0)
then gammaB = arccos(b->dx/V2);
else gammaB = -arccos(b->dx/V2);
betaB = gammaB-alfaB;
bPrX = V2*cos(betaB);
bPrY = V2*sin(betaB);
bPrXx = bPrX*cos(alfaB);
bPrXy = bPrX*sin(alfaB);
bPrYx = bPrY*sin(alfaB);
bPrYy = bPrY*cos(alfaB); }
dx = ((dx - 2*aPrXx) + bPrXx)*mu; // = mu*(bPrXx - aPrXx)
dy = ((dy - 2*aPrXy) + bPrXy)*mu; // = mu*(bPrXy - aPrXy)
b->dx = ((b->dx - 2*bPrXx) + aPrXx)*mu; // = mu*(aPrXx - bPrXx)
b->dy = ((b->dy - 2*bPrXy) + aPrXy)*mu; // = mu*(aPrXy - bPrXy)}
void InitSound(){
pcm->wFormatTag = WAVE_FORMAT_PCM;
pcm->nChannels = 1;
pcm->nSamplesPerSec = 44100;
pcm->nAvgBytesPerSec = 2*44100;
pcm->nBlockAlign = 2;
pcm->wBitsPerSample = 16;
pcm->cbSize = 0;
WaveOut = 0;
open_status = waveOutOpen(&WaveOut, 0, &pcm, Form1->Handle,
0, callback_Window)}
float CalCulateAngle(){
{ int i, j;
pLose ToLz, lz;
pBall nearestBall, Bl, b;
float hitX, hitY;
minAng, a2Lz, minD,
float dx, dy, a, minDist, d;
minDist = 1.7e+308;
minD = minDist;
with BilliardTable do
{ for( j = 0; j <=Lose->Count-1; j ++)
for( i = 0; i <=Ball->Count-1; i ++)
{ lz = Lose->Items[j];
b = Ball->Items[i];
if(! b->exist ) continue;
d = sqrt(sqr(b->x-lz->x)+sqr(b->y-lz->y));
if(d < minDist )
{ minDist = d;
ToLz = lz;
Bl = b; } }
if((Bl == NULL) ) exit;
dx = Bl->x - ToLz->x;
dy = Bl->y - ToLz->y;
d = sqrt(dx*dx+dy*dy);
if((dy)>0
a2Lz = arccos(dx/d);
else a2Lz = -arccos(dx/d);
hitX = Bl->x + cos(a2Lz)*Bl->R;
hitY = Bl->y + sin(a2Lz)*Bl->R;
minAng = 1.7e+308;
for( i = 0; i <=Ball->Count-1; i ++)
{ b = Ball->Items[i];
if((b->ID == Bl->ID) || (not b->exist)
continue;
a = GetAngToXY(b, hitX, hitY);
if(abs(a2Lz-a) < minAng )
{ minAng = abs(a2Lz-a);
nearestBall = b;
Result = a; } }
for( i := 0 to Ball.Count-1 do
begin
b := Ball.Items[i];
if (b.ID = Bl.ID) or (not b.exist)
continue;
d := sqrt(sqr(b.x-Bl.x)+sqr(b.y-Bl.y));
if d < minD then
begin
minD := d;
nearestBall := b;
end;
end;
dx := Bl.x - ToLz.x;
dy := Bl.y - ToLz.y;
d := sqrt(dx*dx+dy*dy);
if (dy)>0
a2Lz := arccos(dx/d)
else a2Lz := -arccos(dx/d);
hitX := Bl.x + cos(a2Lz)*Bl.R;
hitY := Bl.y + sin(a2Lz)*Bl.R;
dx := nearestBall.x - hitX;
dy := nearestBall.y - hitY;
d := sqrt(dx*dx+dy*dy);
if (dy)>0
then a := arccos(dx/d)
else a := -arccos(dx/d);
Result := a;}
void ComputerMove(){
Cue->visible = True;
CompAngle = CalculateAngle;
if(CompAngle > Cue->angle)
CompMove = 1;
else CompMove = -1;}
void TBilliardTable::Draw(){
{ int i;
pBall *b;
pLose *lz;
char* WhoIsIt;
Canvas->Brush->Color = clBlack;
Canvas->Pen->Color = clBlack;
Canvas->Rectangle(0,0,Width, Height);
Canvas->Brush->Color = $336699;
Canvas->Pen->Color = clYellow;
Canvas->Rectangle(BilliardTable->Left - LoseSize, BilliardTable->Top - LoseSize,
BilliardTable->Right + LoseSize, BilliardTable->Bottom + LoseSize);
Canvas->Brush->Color = clGreen;
Canvas->Rectangle(BilliardTable->Left, BilliardTable->Top, BilliardTable->Right, BilliardTable->Bottom);
Canvas->Pen->Color = clYellow;
Canvas->Pen->Color = clBlack;
Canvas->Ellipse(BilliardTable->Left + (3 * BilliardTable->Width / 4)-2,
BilliardTable->Top + (BilliardTable->Height / 2)-2,
BilliardTable->Left + (3 * BilliardTable->Width / 4)+2,
BilliardTable->Top + (BilliardTable->Height / 2)+2);
Canvas->Brush->Color = $336699;
Canvas->Font->Color = clYellow;
Canvas->Font->Style = [];
if(Player == 0
then WhoIsIt = "Игрок";
if(Player == 1
then WhoIsIt = "Компьютер";
Canvas->TextOut(BilliardTable->Left+30, dh + 1,
"Ход: "+WhoIsIt+"а");