Canvas->TextOut(BilliardTable->Left+30, BilliardTable->Bottom,
"В лузах:"+IntToStr(InLoses));
if(Player == 0 ) Canvas->Font->Style = [fsBold];
Canvas->TextOut(BilliardTable->Right-150, BilliardTable->Bottom,
"Игрок:"+IntToStr(PlayerN[0]->balls));
Canvas->Font->Style = [];
if(Player == 1 ) Canvas->Font->Style = [fsBold];
Canvas->TextOut(BilliardTable->Right-150, dh + 1,
"Компьютер:"+IntToStr(PlayerN[1]->balls));
Canvas->Brush->Color = clBlack;
Canvas->Font->Color = clYellow;
Canvas->Font->Style = [fsBold];
Canvas->Font->Style = [];
for( i = 0; i <=Lose->Count-1; i ++)
{ lz = Lose->Items[i];
lz->Draw; }
for( i = 0; i <=Ball->Count-1; i ++)
{ b = Ball->Items[i];
if(b->exist ) b->Draw; }
if(Cue->visible ) Cue->Draw; }
gsGameOver:
{ }
case } // case;break;; }
void TLose::Draw()
{ with Form1->Image1->Canvas do
{ Brush->Color = clBlack;
Pen->Color = clYellow;
Ellipse(Trunc(x-r), Trunc(y-r),
Trunc(x+r), Trunc(y+r));
Font->Color = clWhite;
TextOut(x-4,y-8, IntToStr(ballsInside)); } }
void TBall::Draw()
{ with Form1->Image1->Canvas do
{ Brush->Color = col;
Pen->Color = col;//clBlack;
Ellipse(Trunc(x-r), Trunc(y-r),
Trunc(x+r), Trunc(y+r));
Brush->Color = clWhite;
Pen->Color = clWhite;
Ellipse(Trunc(x-r*sqrt(2)/2*0.5-2), Trunc(y-r*sqrt(2)/2*0.5-2),
Trunc(x-r*sqrt(2)/2*0.5+2), Trunc(y-r*sqrt(2)/2*0.5+2));
Brush->Color = col;
Font->Color = clWhite - col;
if(ShowID )
{ TextOut(Trunc(x-4), Trunc(y-8), IntToStr(ID)); }
Refresh; } }
void TCue->Draw()
{ int x1, y1, x2, y2, x3, y3;
Brush->Color = clYellow;
Pen->Color = clYellow;
Pen->Width = 4;
x1 = Trunc(ToBall->x+cos(Angle)*(ToBall->R+energy));
y1 = Trunc(ToBall->y+sin(Angle)*(ToBall->R+energy));
x2 = Trunc(ToBall->x+cos(Angle)*(ToBall->R+CueLength+energy));
y2 = Trunc(ToBall->y+sin(Angle)*(ToBall->R+CueLength+energy));
x3 = Trunc(ToBall->x-cos(Angle)*1000);
y3 = Trunc(ToBall->y-sin(Angle)*1000);
MoveTo(x1, y1);
LineTo(x2, y2);
Pen->Width = 1;
Brush->Color = clWhite;
Pen->Color = clWhite;
Ellipse(x1-2,y1-2,x1+2,y1+2);
if(ShowLine )
{ Pen->Style = psDash;
MoveTo(x1, y1);
LineTo(x3, y3);
Pen->Style = psSolid; } } }
void TBall->Stop;
{ dx = 0;
dy = 0;
stopped = true; }
bool TBall::InLose(){
int Number;
int i;
pLose lz;
boolean inLz;
Result = False;
if(! exist ) exit;
for( i = 0; i <=BilliardTable->Lose->Count-1; i ++)
{ lz = BilliardTable->Lose->Items[i];
inLz = sqrt(sqr(x-lz->x)+sqr(y-lz->y)) <== lz->R;
if(inLz )
{ Number = i;
Result = True;
inc(InLoses);
Exit; } } }
pBall TBall->CollisedWith();
{ int j;
pBall bb;
real d, delta, ddx, ddy;
Result = NULL;
if(! exist ) exit;
for( j = 0; j <=BilliardTable->Ball->Count-1; j ++)
{ bb = BilliardTable->Ball->Items[j];
if(! bb->exist ) continue;
if(bb->ID == ID ) continue;
d = sqrt(sqr(x-bb->x)+sqr(y-bb->y));
if((d <== R + bb->R) )
{ delta = (R+bb->R - d)/2 + 1;
ddx = (bb->x-x)/d;
ddy = (bb->y-y)/d;
x = x - ddx*delta;
y = y - ddy*delta;
bb->x = bb->x + ddx*delta;
bb->y = bb->y + ddy*delta;
Result = bb;
exit; } } }
//initial
int ballSize = 10; //розмір куль
int loseSize = ballSize + 5;
int MaxEnergy= 20; // сила максимального удару
int CueLength = 200; //довжина кия
float mu = 0.97;
float Step = 0.03; // переміщення
int PyramidHeight; //величина піраміди
float MovementLimit; // переміщення
bool BallsInMove=false;
int Player=0;
float CompAngle;
int CompMove;
float CalculateAngle;
int tick;
bool MustBeHitted;
int Balls;
int ballsIn;
TForm1 *Form1;
TCue *Cue=new TCue;
TBall *Ball=new TBall;
TBilliardTable *BilliardTable= new TBilliardTable;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{}
void __fastcall TForm1::StopAll(){
{ int i;
TBall *b = new TBall;
{ for( i =0; i<=BilliardTable->Ball->Count-1; i ++)
{ //b=BilliardTable->Ball->Items[i];
b->dx = 0;
b->dy = 0; }
Cue->Visible = True; } }}
void ComputerMove(){
Cue->Visible = True;
CompAngle = CalculateAngle;
if(CompAngle > Cue->Angle)
CompMove = 1;
else CompMove = -1;}
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{{ int cur;
TBall *b= new TBall;
TBilliardTable *BilliardTable= new TBilliardTable;
{ if(BallsInMove) exit;
if(! Cue->Visible ) exit;
if(Player == 1 ) exit;// нельзя управлять во время хода компьютера!
// cur = Cue->ToBall->ID;
if(Key == VK_DOWN)
Cue->Angle = Cue->Angle - Step;
if(Key == VK_UP)
Cue->Angle = Cue->Angle + Step;
if(Key == VK_RIGHT )
{ cur = (cur + 1) % Ball->count;
//b = Ball->Items[cur];
}while( b->exists);
if(Key == VK_LEFT )
do{ cur = (cur - 1 + Ball->count) % Ball->count;
// b = Ball->Items[cur];
}while( b->exists);
if(Key == 32 ) if (Cue->Visible) Cue->Hit();
if(Key == 13 ) ComputerMove();
if (Key = 13) StopAll();
// Cue->ToBall = Ball->Items[cur]; } }}
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{{ int cur, i, num;
TBall *b, *b2 = new TBall;
boolean allstopped;
TLose *lz = new TLose;
float d;
char* Mess;
{ tick++;
/* while( Cue.Angle > 6.28 do
Cue.Angle := Cue.angle - 6.28;
while( Cue.Angle < 0 do
Cue.Angle := Cue.angle + 6.28;
*/ if (CompMove != 0 && Player == 1)
{ Cue->Angle = Cue->Angle + Step*CompMove;
if(abs(CompAngle-Cue->Angle) <= Step)
{ CompMove = 0;
Cue->Angle = CompAngle;
MustBeHitted = True; } }
if(Player == 0 ) MustBeHitted = False;
if(MustBeHitted )
if(Cue->energy > MaxEnergy/2 )
{ Cue->Hit();
MustBeHitted = False; } for(int i =0; i <=Ball->count-1; i++)
{ // b = Ball->Items[i];
if(!b->exists ) continue;
if(b->InLose(num) )
{ b->Stop();
b->exists = False;
Balls--;
ballsIn++;
//lz = BilliardTable->Lose->Items[num];
lz->ballsInside++;
//PlayerN[Player]->balls++;
if(Balls == 1 )
{ GameStatus->gsGameOver();
Timer1->Enabled = False; }
cur = Cue->ToBall->ID;
do{
cur = (cur + 1) % Ball->Count;
b = Ball->Items[cur];
}while( b->exist);
Cue->ToBall = b; }
b->dx = b->dx*mu;
b->dy = b->dy*mu;
if((b->x+b->dx > BilliardTable->Right-b->R)
|| (b->x+b->dx < BilliardTable->Left+b->R)
b->dx = -b->dx * mu;
b->x = b->x + b->dx;
if((b->y+b->dy > BilliardTable->Bottom-b->R)
|| (b->y+b->dy < BilliardTable->Top+b->R)
b->dy = -b->dy * mu;
b->y = b->y + b->dy;
b2 = b->collisedWith;
b->outFrom(b2);
d = sqrt(sqr(b->dx)+sqr(b->dy));
if(d < MovementLimit )
{ b->Stop; }
Cue->energy = Trunc(MaxEnergy/2*cos(tick/5)+MaxEnergy/2)+1; }
allstopped = True;
for( i = 0; i <=Ball->Count - 1; i ++)
{ b = Ball->Items[i];
allstopped = (b->dx == 0) && (b->dy == 0) && allstopped; }
if(allstopped && MovedLater )
{ Cue->visible = True;
if(ballsIn == 0
then Player = 1 - Player;
if(Player == 1 ) ComputerMove;
ballsIn = 0; }
MovedLater = !allstopped;
BallsInMove = !allStopped;
Draw; } }}
void __fastcall TForm1::FormKeyPress(TObject *Sender, char &Key)
{ if(BallsInMove ) exit;
if(Key in ["i", "I"] )
{ ShowID = ! ShowID; }
if(Player == 1 ) exit;// нельзя управлять во время хода компьютера!
if(Key in ["h", "H"] then ComputerMove;// help me!}
void __fastcall TForm1::FormDestroy(TObject *Sender)
{ INI = TIniFile->Create(ExtractFilePath(ParamStr(0))+"\settings.ini");
INI->Writeint ("Phisics", "ballSize", ballSize);
INI->Writeint ("Phisics", "PocketSize", loseSize);
INI->Writeint ("Phisics", "MaxEnergy", MaxEnergy);
INI->Writeint ("Phisics", "CueLength", CueLength);
INI->Writeint ("Phisics", "PyramidHeight", PyramidHeight);
INI->WriteFloat("Phisics", "Friction", mu);
INI->WriteFloat("Phisics", "AngleStep", Step);
INI->WriteFloat("Phisics", "MovementLimit", MovementLimit);
INI->Writeint ("Phisics", "TimeInterval", Timer1->Interval);}
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, j;
pBall b;
pLose luza;
boolean unique, inrect;
randomize;
Player = 0;
CompMove = 0;
Timer1->Enabled = True;
with Image1->Canvas do
{ Brush->Color = clWhite;
Pen->Color = clBlack;
Rectangle(0, 0, Width, Height); }
BilliardTable->Ball->Clear;
BilliardTable->Lose->Clear;
balls = -1;
new(b);
inc(balls);
b->x = BilliardTable->Width / 4 + BilliardTable->Left;
b->y = BilliardTable->Height / 2 + BilliardTable->Top;
b->R = ballSize;//Random(20)+10;
b->col = clLtGray;//Random(clWhite);
b->dx = Random*2-1;
b->dy = Random*2-1;
b->ID = 0;
b->exist = True;
BilliardTable->Ball->Add(b);
PlayerN[0]->balls = 0;
PlayerN[1]->balls = 0;
GameStatus = gsGame;
loses = -1;
for( j = 0; j <=1; j ++)
for( i = -1; i <=1; i ++)
{ new(luza);
inc(loses);
luza->x = BilliardTable->Left + (BilliardTable->Width / 2)*(i+1);
luza->y = BilliardTable->Top + (BilliardTable->Height / 2)*(j*2);
luza->R = loseSize;
if(abs(i)==1 )
{ luza->x = trunc(luza->x - i*luza->R*sqrt(2)/2);
luza->y = trunc(luza->y -(j*2-1)*luza->R*sqrt(2)/2); }
luza->ballsInside = 0;
BilliardTable->Lose->Add(luza); }
for( j = 1; j <=PyramidHeight; j ++)
for( i = 1; i <=j; i ++)
{ new(b);
inc(balls);
b->R = ballSize; //Random(20)+10;
b->col = clLtGray; //Random(clWhite);//clLtGray;
b->dx = Random*2-1;
b->dy = Random*2-1;
b->ID = balls;
b->exist = True;
if(j % 2 != 0
then b->y = -((j-1) / 2)*2*b->R+(i-1)*2*b->R + H / 2
else b->y = -((j-1) / 2)*2*b->R+(i-1)*2*b->R - b->R + H / 2;
b->y = b->y + dh;
b->x = (j-1)*2*b->R + 3 * BilliardTable->Width / 4 + dw + LoseSize;
BilliardTable->Ball->Add(b); }
inc(Balls);
BilliardTable->Cue->ToBall = BilliardTable->Ball->Items[0];
BilliardTable->Cue->angle = 180*Pi/180;
BilliardTable->Cue->visible = False;
StopAll;
void __fastcall TForm1::FormCreate(TObject *Sender)
{ INI = TIniFile->Create(ExtractFilePath(ParamStr(0))+"\settings.ini");
ballSize = INI->Readint ("Phisics", "ballSize", 10);
loseSize = INI->Readint ("Phisics", "PocketSize", ballSize + 5);
MaxEnergy = INI->Readint ("Phisics", "MaxEnergy", 20);
CueLength = INI->Readint ("Phisics", "CueLength", 200);
PyramidHeight = INI->Readint ("Phisics", "PyramidHeight", 5);
mu = INI->ReadFloat("Phisics", "Friction", 0.97);
Step = INI->ReadFloat("Phisics", "AngleStep", 0.03);
MovementLimit = INI->ReadFloat("Phisics", "MovementLimit", 0.01);
Timer1->Interval = INI->Readint ("Phisics", "TimeInterval", 20);
Width = Screen->Width-4;
Height = Screen->Height-4;
Panel1->Width = ClientWidth;
Panel1->Height = ClientHeight;
Image1->Width = ClientWidth;
Image1->Height = ClientHeight;
dw = (Image1->Width - W) / 2;
dh = (Image1->Height - H) / 2;
BilliardTable = TBilliardTable->Create;
BilliardTable->Ball = TList->Create;
BilliardTable->Lose = TList->Create;
BilliardTable->Cue = TCue->Create;
Left = dw + loseSize;
Top = dh + loseSize;
Width = W - LoseSize*2;
Height = H - LoseSize*2;
Right = Left + Width;
Bottom = Top + Height;
Player = 0;
Button1Click(Sender);}
Грають двоє: людина і комп'ютер. Першим робить хід чоловік. Хід передається іншому гравцеві, якщо даний гравець не забив в лузи жодної кулі (див. Додаток А).
Сила удару залежить від відстані в даний момент кия від битка (див. Додаток Б).
Управління:
Курсори:
вгору-вниз - обертання кия
вліво-управо - перемикання з однієї кулі на іншій
"пропуск" - удар києм
"H", "h" - підказка для людини (як на його місці зробив би хід чоловік)
"I", "i" - включення/виключення нумерації куль
"S", "s" - включення/виключення лінії прицілювання
Опис файлу конфігурації settings.ini:
ballsize=10 - розмір куль
Pocketsize=20 - розмір лузи
Maxenergy=20 - максимальна сила удару
Cuelength=200 - довжина кия
Friction=0,97 - коефіцієнт тертя (строго менше 1)
Pyramidheight=5 - кількість рівнів в піраміді з кулями
Anglestep=0,03 - крок повороту кия навколо кулі
Movementlimit=0,1 - межа вектора швидкості, після якого рух кулі вважається припиненим.
Timeinterval=20 - час між кадрами перемальовування (у мілісекундах)
Використання методів Canvasдля відображення графіки в проектах C++Builder допомогло реалізувати поставлену задачу. Але цей метод від малювання графіки на формі об’єктів є досить не практичний і тому важливо кожного разу перемалювати всю сцену з її об’єктами, а коли ми маємо анімацію то перемалювання сцени має ще й відбуватись непомітно для ока користувача, хоча цього часом буває досить важко добитись, особливо коли багато анімацій відбувається одночасно для декількох обєктів, що збільшує час виводу певного зображення на екран.
В даній роботі я зміг добитись пере малювання куль, кия та всього столу буз затримки картинки, що створює ілюзію анімації для людського ока. Сама логіка гри дуже проста, коли кілі торкаються одна одної то кожній передається імпульс і прискорення з початковою швидкістю, котра зменшується з часом та відбиттям від інших об’єктів, тобто зіткненням.
Програма широко використовує фізичні закони, для моделювання гри в середовищі C++Builder.
1. С++ для начинающих Липпман 2003г332 стр.
2. Введение в язык С++ Бьярн Страустрап, 1995 г. ; Книга по Си; уроки Visual C++ 2004г,560 стр.
3. http://forums.delphi.com/ab_cplus/start
4. Программирование на языке СИ Ю.Ю.Громов, С.И.Татаренко 1998г 545 стр.;
5. Applied C++: Practical Techniques for Building Better Software Авторы: Philip Romanik, Amy Muntz 2003г. 470 стр.
6. C++ Unleashed Автор: Jesse Liberty 2005г.396p.