|
باسلام- از وبلاگ دیگرم نیز دیدن بفرمایید. با تشکر سید وحید حیدری
*********************************************************
برای اینکه بتونیم یه ساعت عقربه ای طراحی کنیم، اول باید یه مطالبی در مورد حرکت دایره ای بدونیم. یک دایره در حالت پارامتری به صورت زیر بیان می شه: x=a*cos(t) y=b*sin(t( در فرمولهای بالا x و y مختصات نقطه خاصی رو نشون میده، که این نقطه خاص رو t به ما معرفی میکنه. t میتونه هرچیزی باشه و چون ما می خواهیم ساعت عقربه ای درست کنیم پس مسلما t باید معرف زمان باشه. اگه a=b اونوقت نمودار یک دایره را داریم. در غیر اینصورت نمودار مربوط به یک بیضی هستش. هدف ما در اینجا به دست آوردن نقاطی روی محیط دایره یا بیضی است که هم ارز ساعت ها و دقیقه ها ی ساعت هستند. مثلا ساعت 3 کدوم نقطه روی دایره هست. یا مثلا ساعت 9. اگه ما بتونیم مختصات x و y یه ساعت و یا دقیقه بخصوص رو پیدا کنیم، می تونیم با رسم یه خط از مرکز به اون نقطه، عقربه مربوطه رو رسم کنیم. به همین راحتی!! برای این منظور مختصات نقطه مورد نظر رو (x,y) در نظر می گیریم. در این صورت معادلات زیر بدست میان (a شعاع دایره است): x=a*cos(t); y=a*sin(t); در اینجا t برحسب درجه هست. برای تبدیل درجه به رادیان از فرمول زیر استفاده کنید: radian=t*3.1415/180; در نتیجه خواهیم داشت: x=a*cos(radian); y=a*sin(radian); حالا می تونیم از این معادلات پارامتری برای حرکت روی دایره ای استفاده کنیم که می خواهیم ساعت رو روش رسم کنیم. اما جهت حرکت با این معادلات پارامتری در خلاف جهت عقربه های ساعته. برای حرکت در جهت حرکت عقربه های ساعت باید یه منفی توی یه کدوم از معادلات ضرب کنیم: x=-a*cos(radian); y=a*sin(radian); فرض کنیم زمان مورد نظر به صورت h:m:s نمایش داده شود (s ثانیه و m دقیقه و h ساعت). ابتدا حرکت عقربه ثانیه شمار رو بررسی می کنیم. در این حالت، دایره به 60 قسمت تقسیم می شود (از 0 تا 59). وقتی s=0 یعنی 0 درجه از دایره. همینطور وقتی s=30 یعنی 180 درجه از دایره. پس برای اینکه در هر ثانیه حرکت عقربه ثانیه شمار رو داشته باشیم باید ثانیه مورد نظر رو در 6 ضرب کنیم. در اینصورت برای هر ثانیه درجه ای از دایره بدست میاد. پس برای حرکت عقربه ثانیه شمار فرمولهای زیر بدست میان: x=-a*sin(s*6*3.1415/180); y=a*cos(s*6*3.1415/180); اما در حرکت عقربه های ساعت 0 ثانیه باید روی 90 درجه در دایره مثلثاتی باشه. پس باید این 90 درجه را در فرمول بالا تاثیر بدیم. بنابراین این فرمولها به صورت زیر در میان: x=-a*sin( (90-s*6) *3.1415/180 ); y=a*cos( (90-s*6) *3.1415/180 ); از این فرمول نقطه ای که روی دایره حرکت می کنه و مختصاتش به ثانیه زمان بستگی داره، بدست میاد. در این فرمول a شعاع عقربه ثانیه شماره. حرکت عقربه دقیقه شمار هم کاملا شبیه حرکت عقربه ثانیه شمار هستش. برای رسم عقربه ها دقت کنید که باید به مختصات آنها مقداری را اضافه کنیم. زیرا مبدا مختصات (0,0) در حالت گرافیکی نقطه گوشه سمت چپ و بالای صفحه است. اما در مورد حرکت عقربه ساعت شمار کار کمی متفاوته. اولا در این مورد دایره را باید به 12 قسمت تقسیم کنیم. پس در اینجا باید ساعت در 30 ضرب بشه تا درجه ساعت مورد نظر را بدست بیاریم. ثانیا مشکل دیگری که وجود داره اینه که وقتی ساعت مثلا 00:59:59 است، عقربه ساعت شمار در نزدیکیهای 1 (یعنی 5 درجه) قرار می گیره. با توجه به این دو مورد می تونیم فرمول حرکت عقربه ساعت شمار را به صورت زیر بنویسیم: x=-a*sin( (90-h*30-m/60*30) *3.1415/180 ); x=a*cos( (90-h*30-m/60*30) *3.1415/180 ); در مورد اینکه ساعت ممکنه بین 0 تا 24 باشه نگران نباشین. به هر حال ساعت 3 صبح با ساعت 15 بعد از ظهر هم ارز هستن. وقتی این دو عدد به درجه تبدیل می شن، (3 در تبدیل به درجه به 90 و 15 به 360+90 تغییر می کنه) یک نقطه رو روی دایره مثلثاتی نشان می دن. حالا با استفاده از این 3 تا موضوع در مورد حرکت عقربه ها می تونیم برنامه ساعت عقربه ای رو بنویسیم. دو تا برنامه ساعت عقربه ای، یکی به زبان پاسکال و یکی هم به زبان سی: پاسکال: uses graph,crt,dos; var gd,gm,x,y,cx,cy : integer; hour,min,sec,milsec : word; begin gd:=detect; initgraph(gd,gm,'d:\tp\bgi'); cx:=trunc(getmaxx/2); cy:=trunc(getmaxy/2); repeat cleardevice; circle(cx,cy,52); gettime(hour,min,sec,milsec); { second hand } y:=-trunc(sin(pi/2+pi*sec/30)*49)+cy; x:=-trunc(cos(pi/2+pi*sec/30)*49)+cx; setlinestyle(0,0,1); line(cx,cy,x,y); { minute hand } y:=-trunc(sin(pi/2+pi*min/30)*40)+cy; x:=-trunc(cos(pi/2+pi*min/30)*40)+cx; setlinestyle(0,0,2); line(cx,cy,x,y); { hour hand } y:=-trunc((sin(pi/2+pi*(hour+min/60)/6))*30)+cy; x:=-trunc((cos(pi/2+pi*(hour+min/60)/6))*30)+cx; setlinestyle(0,0,3); line(cx,cy,x,y); delay(1000); until keypressed; end. سی: #include #include #include #include #include #define HOULEN 100 #define MINLEN 150 #define SECLEN 200 #define CENTERX 320 #define CENTERY 240 int main() { int gd=DETECT,gm; initgraph(&gd,&gm,"..\\bgi"); if (graphresult()!=grOk) return 0; struct dostime_t t; int x,y; unsigned char sec,min,hou; do{ _dos_gettime(&t); sec=t.second; min=t.minute; hou=t.hour; cleardevice(); circle(CENTERX,CENTERY,SECLEN+5); // for second hand x=SECLEN*cos(90*3.1415/180-sec*6*3.1415/180)+CENTERX; y=-SECLEN*sin(90*3.1415/180-sec*6*3.1415/180)+CENTERY; setlinestyle(0, 0, 1); line(CENTERX,CENTERY,x,y); // for minute hand x=MINLEN*cos(90*3.1415/180-min*6*3.1415/180)+CENTERX; y=-MINLEN*sin(90*3.1415/180-min*6*3.1415/180)+CENTERY; setlinestyle(0, 0, 2); line(CENTERX,CENTERY,x,y); // for second hand x=HOULEN*cos((90-hou*30-(float)min/60*30)*3.1415/180)+CENTERX; y=-HOULEN*sin((90-hou*30-(float)min/60*30)*3.1415/180)+CENTERY; setlinestyle(0, 0, 3); line(CENTERX,CENTERY,x,y); delay(1000); } while(!kbhit()); return 0; } و این یکی هم برای نمونه کد برنامه یک ساعت عقربه ای بسیار زیبا نوشته شده با پاسکال: uses graph,crt,dos; const clockbg = 1; ringbg = 4; sech = 12; minh = 15; houh = 15; textcolor = 14; patt : FillPatternType = (124, 254, 214, 254, 214, 254, 124, 1); var gd,gm,x,y,hx,hy,i,mx,my,sx,sy : integer; hour,min,sec,milsec : word; s,m,h : string; procedure XY(ch: char); begin if upcase(ch)='M' then begin my:=-trunc(sin(pi/2+pi*min/30)*49)+y; mx:=-trunc(cos(pi/2+pi*min/30)*49)+x; end else if upcase(ch)='H' then begin hy:=-trunc((sin(pi/2+pi*(hour+min/60)/6))*30)+y; hx:=-trunc((cos(pi/2+pi*(hour+min/60)/6))*30)+x; end else if upcase(ch)='S' then begin sy:= -trunc(sin(pi/2+pi*sec/30)*64)+y; sx:= -trunc(cos(pi/2+pi*sec/30)*64)+x; end; end; begin gd:=detect; initgraph(gd,gm,'..\bgi'); x:=trunc(getmaxx/2); y:=trunc(getmaxy/2); settextstyle(1,0,1); { setfillstyle(6,3);} setfillpattern(patt,3); bar(x-96,y-96,x+96,y+96); setcolor(14); setfillstyle(1,ringbg); fillellipse(x,y,95,95); setfillstyle(1,clockbg); fillellipse(x,y,83,83); setfillstyle(9,10); for i:=1 to 60 do putpixel(trunc(cos(i*pi/30)*90)+x,trunc(sin(i*pi/30)*90)+y,15); for i:=1 to 12 do fillellipse(trunc(cos(i*pi/6)*90)+x,trunc(sin(i*pi/6)*90)+y,5,5); moveto(x,y); XY('h'); XY('m'); XY('s'); repeat { digit clock } setviewport(x-trunc(textwidth(s)/2),y-trunc(textheight(s)/2)-110, x+trunc(textwidth(s)/2),y+trunc(textheight(s)/2)-110,clipoff); clearviewport; { hand clock } setviewport(0,0,639,479,clipon); gettime(hour,min,sec,milsec); { Clear old hand } setlinestyle(0,1,1); setcolor(clockbg); line(x,y,sx,sy); setlinestyle(0,1,3); setcolor(clockbg); line(x,y,mx,my); setlinestyle(0,1,3); setcolor(clockbg); line(x,y,hx,hy); {for mninute } XY('m'); setcolor(minh); setlinestyle(0,1,3); line(x,y,mx,my); { for hour } XY('h'); setcolor(houh); setlinestyle(0,1,3); line(x,y,hx,hy); { for second } XY('s'); setlinestyle(0,1,1); setcolor(sech); line(x,y,sx,sy); setfillstyle(1,15); fillellipse(x,y,5,5); str(sec,s); str(min,m); str(hour,h); s:=h+':'+m+':'+s; setviewport(x-trunc(textwidth(s)/2),y-trunc(textheight(s)/2)-110, x+trunc(textwidth(s)/2),y+trunc(textheight(s)/2)-110,clipoff); setcolor(textcolor); outtext(s); delay(1000); until keypressed; end. |
|
|
برای این کار باید به کامپیوتر اجازه بدیم که پازلی رو که قابل حل هستش به طور تصادفی بازی کنه. یعنی در یک پازل مرتب شده مثلا هزار دفعه کامپیوتر به طور تصادفی پازل رو به سمت های مختلف حرکت بده. بعد از انجام این عملیات پازل طوری به هم ریخته که کسی نمی فهمه ما چی کار کردیم.
void unsorttable()
{
randomize();
char ch;
int i;
for (i=0 ; i<1000 ; i++)
{
ch=random(4);
switch (ch)
{
case 0: // down
if (ty!=0)
{
table[ty][tx]=table[ty-1][tx];
table[ty-1][tx]=0;
ty--;
}
break;
case 1: // up
if (ty!=3)
{
table[ty][tx]=table[ty+1][tx];
table[ty+1][tx]=0;
ty++;
}
break;
case 2: // right
if (tx!=0)
{
table[ty][tx]=table[ty][tx-1];
table[ty][tx-1]=0;
}
break;
case 3: // left
if (tx!=3)
{
table[ty][tx]=table[ty][tx+1];
table[ty][tx+1]=0;
}
break;
}
}
}
این کد در واقع همون کدی هستش که ما برای حرکت دادن خانه های پازل توسط خودمون استفاده می کردیم. با این تفاوت که اینجا متغییر ch دیگه با زدن کلید مشخص نمیشه. بلکه توسط تابع تولید عدد تصادفی random() مشخص میشه. اینطوری دیگه نگرانی در مورد اینکه پازل ممکنه اصلا حل نشه نداریم. ولی باید دقت کنید وقتی این تابع فراخوانی میشه، پازل باید در یک حالت قابل حل باشه. مثلا همون حالت مرتب شده خودش خیلی خوبه. حالا دیگه می تونید بازی رو با خیال راحت انجام بدید!
خوب حالا کدی می نویسم که ببینه پازل حل شده یا نه. کد این تابع باید بعد از زدن هر کلید فراخوانی بشه.
int win()
{
int i,j;
for (i=0 ; i<4 ; i++)
for (j=0 ; j<4 ; j++)
if (table[i][j]!=sortedtable[i][j]) return 0;
return 1;
}
آرایه sortedtable آرایه ای هستش که پایان بازی رو ذخیره کرده. یعنی پازل مرتب شده رو ذخیره کرده و هر دفعه با آرایه فعلی بازی مقایسه می شه.
و در آخر هم سورس کد کامل برنامه بازی پازل:
#include
#include
#include
int sortedtable[4][4]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0};
int table[4][4]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0};
//int table[4][4]={9,4,11,7,5,6,2,12,15,1,13,3,10,8,14,0}; // gheyre momken
int tx=3,ty=3;
int win()
{
int i,j;
for (i=0 ; i<4 ; i++)
for (j=0 ; j<4 ; j++)
if (table[i][j]!=sortedtable[i][j]) return 0;
return 1;
}
void printtable()
{
clrscr();
int i,j;
for (i=0 ; i<4 ; i++)
{
for (j=0 ; j<4 ; j++)
if (table[i][j]==0) printf(" ");
else printf("%3d ",table[i][j]);
printf("\n\n");
}
}
int ingame()
{
char ch;
while (!win() && (ch=getch())!=27 )
{
switch (ch)
{
case 80: // down key
if (ty!=0)
{
table[ty][tx]=table[ty-1][tx];
table[ty-1][tx]=0;
ty--;
printtable();
}
break;
case 72: // up key
if (ty!=3)
{
table[ty][tx]=table[ty+1][tx];
table[ty+1][tx]=0;
ty++;
printtable();
}
break;
case 77: // right key
if (tx!=0)
{
table[ty][tx]=table[ty][tx-1];
table[ty][tx-1]=0;
printtable();
}
break;
case 75: // left key
if (tx!=3)
{
table[ty][tx]=table[ty][tx+1];
table[ty][tx+1]=0;
printtable();
}
break;
}
}
if (ch!=27)
{
gotoxy(36,14);
printf("You win");
getch();
}
}
void unsorttable()
{
randomize();
char ch;
int i;
for (i=0 ; i<10 ; i++)
{
ch=random(4);
switch (ch)
{
case 0: // down
if (ty!=0)
{
table[ty][tx]=table[ty-1][tx];
table[ty-1][tx]=0;
ty--;
}
break;
case 1: // up
if (ty!=3)
{
table[ty][tx]=table[ty+1][tx];
table[ty+1][tx]=0;
ty++;
}
break;
case 2: // right
if (tx!=0)
{
table[ty][tx]=table[ty][tx-1];
table[ty][tx-1]=0;
}
break;
case 3: // left
if (tx!=3)
{
table[ty][tx]=table[ty][tx+1];
table[ty][tx+1]=0;
}
break;
}
}
}
main()
{
textmode(C80);
unsorttable();
printtable();
ingame();
}
|
برای طراحی این بازی ما احتیاج به یه آرایه داریم. و چون دوز مربوط به یه جدول 3 در 3 هستش و ساده حل میشه، پس می تونیم این آرایه رو یه بعدی در نظر بگیریم. یعنی یه آرایه یه بعدی 9 عنصری. این برنامه باید طوری باشه که بشه دو نفره اون رو بازی کرد. یعنی یه بازیکن با کامپیوتر بازی کنه. پس این بازی نوبتی هستش و هر بازیکن تنها زمانی میتونه بازی کنه که نفر مقابل حرکت خودش رو کرده باشه. برای این چرخش بازی از یه بازیکن به یه بازیکن دیگه می تونیم یه متغییر تعریف کنیم، مثلا از نوع صحیح. اگه این متغییر صفر بود نفر اول باید بازی کنه (مثلا بازیکن) و اگه یک بود نفر دوم (مثلا کامپیوتر). نکته دیگه اینه که علامت X همیشه شروع کننده بازی هستش. این علامت می تونه O باشه ولی به هر حال باید ثابت باشه. و اگه کسی بخواد شروع کننده بازی باشه باید این علامت رو انتخاب کنه. حالا میریم سراغ جدول بازی. میتونیم یه جدول مثل جدول زیر برای این بازی در نظر بگیریم: (چون از آرایه یک بعدی استفاده کردیم) 1 2 3 4 5 6 7 8 9 عناصر این آرایه در شروع بازی همگی صفر هستند: 0 0 0 0 0 0 0 0 0 حالا باید برای هر کدوم از علامتها (X یا O) یه عدد در نظر بگیریم تا معرف اون علامت توی آرایه باشه. مثلا 1 برای X و 4 برای O اعداد خوبی هستند. این که چرا این اعداد رو انتخاب کردم تو ادامه مطلب توضیح میدم. پس تا اینجا ما سه تا عدد داریم. صفر و 1 و 4. که کار هرکدوم از اونا معلومه. اول از همه یه تابع مینویسیم برای چاپ جدول بازی: void printtable() { clrscr(); int i; for (i=1; i<10 ; i++) { if (table[i]==0) printf(" %d ",i); else if (table[i]==1) printf(" x "); else if (table[i]==4) printf(" o "); if (i%3==0) printf("\n\n"); } } متغییر nobat رو تعریف می کنیم تا در هر مرحله از بازی معلوم بشه که نوبت کیه. اگه این متغییر فرد بود نوبت بازیکن هستش و اگه زوج بود کامپیوتر بازی کنه. و در هر مرحله این متغییر یکی اضافه میشه. در شروع بازی ما باید از بازیکن بپرسیم که می خواد با X بازی کنه یا O. دوتا متغییر هم برای ذخیره کردن اعداد 1 و 4 تعریف می کنیم تا معلوم بشه که بازیکن کدوم علامت رو انتخاب کرده و کدوم علامت مال کامپیوتر هستش. پس یه منو طراحی میکنیم و بسته به اینکه کدوم گزینه انتخاب میشه، تنظیمات بازی رو انجام میدیم و وارد بازی میشیم: int table[10]={0}; int nobat; int player,comp; int menu() { clrscr(); printf("1.Select x (you first)\n"); printf("2.select o\n"); printf("3.exit"); char ch; int i; for (i=0 ; i<10 ; i++) table[i]=0; while ((ch=getch())!=51) { if (ch==49) { nobat=1; player=1; comp=4; printtable(); while (play()); return 1; } else if (ch==50) { nobat=0; player=4; comp=1; printtable(); while (play()); return 1; } } return 0; } تابع play() تابعی است که برنامه رو وارد بازی میکنه. اگه یکی از بازیکن ها برنده بشن و یا بازی تموم بشه،این تابع صفر رو برمی گردونه و از حلقه while خارج میشه. تابع play() اصلی ترین تابع بازی هستش. ولی قبل از معرفی این تابع باید چندتا تابع دیگه بنویسیم تا از اونا توی تابع play() استفاده کنیم. باید تابعی بنویسیم که مشخص کنه الآن توی کدوم خونه از جدول احتمال برنده شدن هست. این کار رو با همون اعدادی انجام میدیم که قبلا معرفی کردم، یعنی 1 و 4. گفتم که 1 معرف علامت X هستش. حالا فرض کنید که جدول به صورت زیر در اومده باشه: 1 0 1 0 0 4 4 0 0 اگه الآن نوبت X باشه یعنی یک، الآن توی کدوم خونه از جدول احتمال برنده شدن هست؟ خوب معلومه خونه دوم. حالا یه جدول دیگه رو بررسی کنیم: 1 0 4 0 0 1 0 4 0 توی این یکی جدول هیچ خونه ای وجود نداره که باعث برنده شدن X بشه. تفاوتی که با حالت اول داره اینه که در جدول اول جمع اعداد در ردیف اول میشه 2. ولی توی جدول دوم همچین اتفاقی نمی افته. این خصوصیت رو می تونیم برای عدد 4 هم بیان کنیم. پس توی هر ردیف یا ستون یا قطر احتمال برنده شدن X هست اگه جمع عددهای اون ردیف یا ستون یا قطر 2 بشه. در مورد O هم این عدد 8 هستش. تابع زیر با توجه به این که الآن نوبت کیه، این خونه از جدول رو پیدا میکنه: (اگه خونه ای پیدا نشه صفر رو برمی گردونه) int posswin(int p) { int i; int check_val,pos; if(p%2 == 1) check_val = 2*player; else check_val = 2*comp; i = 1; while(i<=9)//row check { if(table[i] + table[i+1] + table[i+2] == check_val) { if(table[i] == 0) return i; if(table[i+1] == 0) return i+1; if(table[i+2] == 0) return i+2; } i+=3; } i = 1; while(i<=3)//column check { if(table[i] + table[i+3] + table[i+6] == check_val) { if(table[i] == 0) return i; if(table[i+3] == 0) return i+3; if(table[i+6] == 0) return i+6; } i++; } if(table[1] + table[5] + table[9] == check_val) { if(table[1] == 0) return 1; if(table[5] == 0) return 5; if(table[9] == 0) return 9; } if(table[3] + table[5] + table[7] == check_val) { if(table[3] == 0) return 3; if(table[5] == 0) return 5; if(table[7] == 0) return 7; } return 0; } تابع زیر هم بسته به این که الآن نوبت کی هستش، علامتی رو در خونه ای از جدول که بهش میدیم قرار میده: void go(int n) { if(nobat % 2) table[n] = player; else table[n] = comp; } حالا باید تابعی بنویسیم که با استفاده از اون کامپیوتر خونه ای رو برای ادامه بازی انتخاب کنه. توی این تابع اول خونه ۵ ام بررسی میشه. اگه خالی بود انتخاب میشه وگرنه خونه های کناری بررسی میشن (خونه های ۱ و ۳ و ۷ و ۹). بعد میره سراغ خونه های ۲ و ۴ و ۶و ۸. با این ترتیب انتخاب خیالتون راحت باشه که نمی شه کامپیوتر رو برد!! اگه می خواهید کامپیوتر رو ببرید این تابع رو تغییر بدید و اول خونه های ۲ و ۴ و ۶ و ۸ رو بررسی کنید و بعد خونه های ۱ و ۳ و ۷ و ۹ رو:
int compchoice() { if(table[5] == 0) return 5; if(table[1] == 0) return 1; if(table[3] == 0) return 3; if(table[7] == 0) return 7; if(table[9] == 0) return 9; if(table[2] == 0) return 2; if(table[4] == 0) return 4; if(table[6] == 0) return 6; if(table[8] == 0) return 8;
} تابع زیر هم معلوم میکنه که آیا جایی برای انتخاب مونده یا نه: int isempty() { int i; for (i=1 ; i<10 ; i++) if (table[i]==0) return 1; return 0; } و اما مهمترین تابع بازی: همونطور که گفتم اگه nobat یه عدد فرد باشه باید بازیکن بازی کنه. حالا هر علامتی رو که انتخاب کرده باشه. و اگه یه عدد زوج باشه کامپیوتر باید بازی کنه، که توی این تابع معلومه. درمورد بازی کردن کامپیوتر هم این نکته رو بگم که برای انتخاب خونه از جدول کامپیوتر اول از همه میگرده ببینه می تونه با انتخاب یه خونه برنده بشه یا نه. بعدش میره سراغ خونه هایی که نذاره طرف مقابل برنده بشه. در غیر این صورت خونه دیگه ای رو انتخاب میکنه. int play() { if (nobat %2) // player { int pos,loopvar=1; gotoxy(1,7); printf("Your Choice: "); while (loopvar) { pos=getch(); pos-=48; if (1<=pos && pos <=9) if (table[pos]==0) loopvar=0; } if(pos == posswin(nobat)) { go(pos); printtable(); gotoxy(1,7); printf("Player Wins"); getch(); return 0; } go(pos); printtable(); } else // computer { if(posswin(nobat)) { go(posswin(nobat)); printtable(); gotoxy(1,7); printf("Computer wins"); getch(); return 0; } else if(posswin(nobat+1)) go(posswin(nobat+1)); else go(compchoice()); printtable(); } nobat++; if (!isempty()) { gotoxy(1,7); printf("Game Draw"); getch(); return 0; } } و اما برای کامل شدن برنامه بازی کافیه که توی تابع main() فقط یه خط دستور بنویسید: main() { while (menu()); } چندتا نکته رو در مورد این بازی به یاد داشته باشید: یکی اینکه این بازی هوشمند نیست. یعنی کامپیوتر نمی تونه حرکات بعدی رو پیش بینی کنه و بهترین حالت رو انتخاب کنه. ولی در کل نمیشه کامپیوتر رو برد!! به خاطر اون ترتیبی که توی بررسی خونه های جدول بکار بردیم. در واقع این قانون بازی هستش. توی این بازی اگه قانون انتخاب ها رو رعایت کنیم هیچ کدوم از بازیکن ها امکان برد ندارن!! نکته بعدی هم اینکه این برنامه اصلا گرافیکی نیست و ظاهر زیبایی نداره. برای اون دیگه خودتون یه فکری بکنید. اصلش همین بود. موفق باشید. |
توی این پست می خوام در مورد بازی snake صحبت کنم. همون ماری که با خوردن غذا بزرگتر میشه. ساخت این بازی چند روش داره. یکی اینکه ما فقط یک آرایه برای ذخیره کردن مختصات بدن مار داشته باشیم و هر دفعه که مار غذا می خوره به طول بدنش یعنی طول آرایه یکی اضافه می شه. روش دوم اینه که یک آرایه دوبعدی تعریف کنیم که صفحه بازی رو ذخیره کنه. در این صورت هر خونه ی این آرایه میتونه معرف یه چیزی باشه. مثلا بدن مار یا غذا. یا حتی دیوار که امکان داشته باشه مار بهش برخورد کنه. اگه بازی snake رو انجام داده باشید حتما میدونید که این بازی دو نوع هست. یکی کلاسیک که هیچ دیواری توی بازی وجود نداره و مار با عبور از کناره ها از سمت دیگه صفحه بازی بیرون میاد. و دومین نوع که توش دیوار هم هست. برای طراحی بازی نوع دوم باید از روش دوم ساخت مار استفاده کرد. چون باید اطلاعات دیوارها هم ذخیره بشه. برنامه ای که توی این پست معرفی می کنم یک اسنیک از نوع کلاسیک میسازه.
اول از همه گفتم که ما به یک آرایه احتیاج داریم ( البته اون رو مقدار دهی اولیه کردم. محل اولیه مار. که لازم هم نبود. چون تو یه تابع مقداردهی میشه.)
int snake[500][2]={{6,2},{5,2},{4,2},{3,2},{2,2}}; // 0 for x 1 for y
و اینکه بدونیم زمین بازی چقدریه. یعنی چند در چنده؟
#define W 25
#define H 25
همچنین باید هر بار که بازی شروع میشه و هربار که مار غذا رو میخوره مختصات غذای بعدی رو داشته باشیم. (نمیدونم چرا اسم این غذا سیب هست؟ پس اگه توی برنامه به کلمه apple برخوردید زیاد تعجب نکنید!)
int appx,appy; // apple position
با خوردن هر غذا طول مار یکی اضافه میشه. برای سادگی کار مختصات سر مار رو هم ذخیره می کنیم. البته با توجه به نحوه ی ذخیره سازی بدن مار در آرایه، خونه صفرم آرایه مختصات سر مار رو ذخیره می کنه. باید هر دفعه به اندازه طول مار (که توی یه متغییر ذخیره شده) در مختصاتهای بدن مار حروفی چاپ می کنیم تا مار چاپ بشه.
یه متغییر هم داریم برای کنترل سرعت مار (SPEED).
کل متغییرهایی که قبل از تعریف توابع لازمه اینا هستن:
#include
#include
#include
#include
#define W 25
#define H 25
int snake[500][2]={{6,2},{5,2},{4,2},{3,2},{2,2}}; // 0 for x 1 for y
int snlen=5;
int x=6,y=2;
int dir=1; // right=1 up=2 left=3 down=4
int appx,appy; // apple position
int eata;
int SPEED=70;
اولین تابعی که معرفی می کنم تابع printwall() هست که از اسمش معلومه که دیواره های کنار صفحه رو رسم می کنه:
void printwall()
{
int i,j;
textcolor(1);
for (i=1 ; i<=W+1 ; i++)
for (j=1 ; j<=H+1 ; j++)
{
if ((i==1 || i==W+1) || (j==1 || j==H+1))
{
gotoxy(i,j);
cprintf("*");
}
}
}
تابع بعدی تابعیه که مار رو رسم می کنه:
void printsnake()
{
int i;
textcolor(11);
for (i=1 ; i< snlen ; i++ )
{
gotoxy(snake[i][0],snake[i][1]);
cprintf("X");
}
textcolor(12);
gotoxy(snake[0][0],snake[0][1]);
cprintf("O");
}
تابع بعدی تابعیه که سیب (غذا) رو در محلی به صورت رندوم قرار میده. ولی دقت داشته باشید که این محل رندوم روی بدن مار نباشه، که توی تابع این موضوع هم بررسی میشه:
void insertapple()
{
int ax,ay,t,i;
do{
ax=random(W-2)+2;
ay=random(H-2)+2;
t=0;
for (i=0 ; i
if (ax==snake[i][0] && ay==snake[i][1])
{
t=1;
break;
}
} while (t);
appx=ax;
appy=ay;
gotoxy(ax,ay);
textcolor(11);
cprintf("0");
}
خوب حالا احتیاج به یه تابع داریم تا هر دفعه که فراخوانی میشه بازی جدیدی شروع بشه. یعنی تنظیمات اولیه بازی برگرده:
void newgame()
{
clrscr();
snake[0][0]=6; snake[0][1]=2;
snake[1][0]=5; snake[1][1]=2;
snake[2][0]=4; snake[2][1]=2;
snake[3][0]=3; snake[3][1]=2;
snake[4][0]=2; snake[4][1]=2;
x=6;y=2;
snlen=5;
dir=1;
printwall();
insertapple();
printsnake();
}
یکی از مهمترین موضوعاتی که توی بازی اسنیک وجود داره اینه که با حرکت مار به جلو تمام مختصاتهای قبلی مار باید به جلو حرکت داده شوند یا به عبارت دیگه باید شیفت داده شوند:
void shiftsnake()
{
int i;
for (i=snlen-2 ; i>=0 ; i--)
{
snake[i+1][0]=snake[i][0];
snake[i+1][1]=snake[i][1];
}
snake[0][0]=x;
snake[0][1]=y;
}
تابع زیر مشخص میکنه که مار به سیب رسیده و اون رو خورده یا نه:
int eatapple()
{
if (x==appx && y==appy)
{
if (snlen<499) snlen++;
return 1;
}
return 0;
}
تابع زیر هم مشخص میکنه که مار با خودش برخورد کرده یا نه:
int catchitself()
{
int i;
for (i=1 ; i
if (x==snake[i][0] && y==snake[i][1])
return 1;
return 0;
}
این هم بزگترین تابع بازی که کل بازی توی اون جریان داره.
درمورد اینکه مار به کدوم طرف حرکت میکنه باید یه متغییر تعریف کرد و هر دفعه بررسی کرد که کدوم جهت رو نشون میده و بسته به اون جهت مختصات سر مار رو تغییر داد. در این تابع این متغییر dir هست که عددهای 1 و 2 و 3 و 4 بترتیب نشان دهنده راست، بالا، چپ و پایین هستند.
با زدن کلیدهای جهت دار صفحه کلید باید عددی که در dir ذخیره شده تغییر داد. مثلا اگه مار به سمت بالا حرکت میکنه (مقدار dir عدد 4 هست) دیگه نمیتونه به پایین برگرده (یعنی dir نمیتونه 1 بشه). و همینطور در مورد بقیه جهتها.
قبل از تابع eatapple سه خط کد هست که دم مار رو پاک می کنن. یعنی با رنگ زمینه که سیاه هستش یک حرف چاپ میشه. چون مار یکی به سمت جلو حرکت کرده و اگه این کار رو نکنیم دم مار هیچ وقت پاک نمیشه و رد پای مار روی صفحه باقی میمونه.
void play()
{
delay(3000);
char ch=1;
int i;
do{
while (!kbhit()){
switch (dir){
case 1: // right
x++;
if (x==W+1) x=2;
break;
case 2: // up
y--;
if (y==1) y=H;
break;
case 3: // left
x--;
if (x==1) x=W;
break;
case 4: // down
y++;
if (y==H+1) y=2;
break;
}
gotoxy(snake[snlen-1][0],snake[snlen-1][1]);
textcolor(0);
cprintf("O");
// eat apple;
eata=eatapple();
// shift snake position
shiftsnake();
printsnake();
if (eata) insertapple();
// catch itself
if (catchitself())
{
delay(3000);
return;
}
delay(SPEED);
}
ch=getch();
if (ch==0)
ch=getch();
switch (ch){
case 80: // down key
if (dir!=2) dir=4;
break;
case 72: // up key
if (dir!=4) dir=2;
break;
case 77: // right key
if (dir!=3) dir=1;
break;
case 75: // left key;
if (dir!=1) dir=3;
break;
}
if (kbhit())
if (getch()==0)
getch();
} while (ch!=27);
}
میمونه طراحی یه منو که قبل از بازی ازمون بخواد چه کاری انجام بدیم:
void menu()
{
char ch;
do{
clrscr();
textcolor(14);
cprintf("[1] New Game");
printf("\n");
cprintf("[2] Change Speed");
printf("\n");
cprintf("[3] Exit");
ch=getch();
switch (ch){
case 49:
newgame();
play();
break;
case 50:
int st=0;
textcolor(11);
clrscr();
do{
printf("\n");
cprintf("Enter level number(1-9): ");
scanf("%d",&st);
} while (st<1 && st>9);
SPEED=160-st*10;
break;
}
} while (ch!=27 && ch!=51);
}
و در آخر تابع main برنامه به صورت زیر باید نوشته بشه:
main()
{
textmode(C4350);
_setcursortype(_NOCURSOR);
randomize();
menu();
}
این کد یه طرح کلی از snake بود و خیلی ناقصه. فقط مار به اطراف حرکت میکنه و با خوردن غذا (سیب) بزرگتر میشه. نه امتیازی داده میشه و نه جایزه ای. فکر کنم همین کد برای شروع یه پروژه بزرگ کافی باشه.
لینک دانلود سورس برنامه SNAKE
این برنامه تحت ویندوز هستش و به زبان فارسی.
دانلود برنامه پازل تحت ویندوز xp (حجم برنامه ۱۸۰ کیلوبایت)
لینک دانلود برنامه (حجم برنامه: ۱.۴۱ مگابایت)
توی این پست می خوام روشی رو معرفی کنم که بشه به وسیله اون حرکت پرتابی رو شبیه سازی کرد. یعنی یه ذره از مبدا با سرعت اولیه و زاویه دار پرتاب بشه. این برنامه به زبان ویژوال بیسیک هستش.
چیزی که در مورد حرکت پرتابی باید بدونیم اینه که این حرکت تلفیقی از دو حرکت مختلف است. یکی حرکت یکنواخت (با سرعت ثابت) در جهت محور x و دیگری حرکت شتابدار با شتاب ثابت (حرکت پرتابی در راستای قائم) در راستای محور y. خوب معادلات هر کدوم از این حرکتها به صورت زیره:
x = v0 cos(a) t
y = -1/2 g t2 + v0 sin(a) t
(x,y) مختصات ذره مورد نظر ما هستش.
در این فرمول ها v0 سرعت اولیه و a زاویه پرتاب هستش. v0 cos(a) سرعت اولیه در راستای محور x و v0 sin(a) سرعت اولیه در راستای محور y است. چیزی که برای کامل شدن معادلات لازمه t یعنی زمان هستش. زمان به ما نشون میده که ذره مورد نظر ما در یه لحظه به خصوص کجاست. مثلا در لحظه صفر هم x صفر هستش و هم y. یعنی هنوز ذره رو پرتاب نکردیم!! خوب حالا تنها کاری که باید بکنیم اینه که به t مقدار اولیه صفر بدیم و کم کم به t اضافه کنیم. اینکه توی هر مرحله چقدر باید به t اضافه بشه خیلی مهمه. اگه مقدار t رو زیاد اضافه کنیم ممکنه حرکتی رو که می بینیم خیلی گسسته به نظر بیاد. یعنی ممکنه در یه لحظه ذره پایین صفحه باشه و در لحظه بعد بره وسط صفحه.
نکته دیگه در مورد حرکت پرتابی اینه که مسیر حرکت نمودار یه سهمی هستش و دارای دو تا ریشه. چون معادله x مربوط به حرکت یکنواخت هست و شتاب نداره پس میشه در این مورد اون رو کنار گذاشت و ریشه ها رو از معادله y حساب کرد. ریشه اولش که معلومه: t=0. ریشه دوم هم: t=2v0sin(a)/g. این یعنی اینکه ذره ای که پرتاب شده حتما ارتفاعش دوباره کم میشه و میرسه به اون ارتفاعی که پرتابش کردیم. این ذره یک بار موقع پرتاب کردن ارتفاعش صفر هستش و یکبار هم در زمان 2v0sin(a)/g . پس می تونیم زمان رو تا جایی اضافه کنیم که بر میگرده به ارتفاع اولیه اش. اگه به اضافه کردن زمان همینطور ادامه بدیم ذره از ارتفاع اولیه اش هم پایین تر میره. مثلا اگه از روی پشت بام یه ذره رو پرتاب کنیم بعد از اینکه ارتفاعش با ارتفاع پشت بام یکی شد، تا جایی که جا داشته باشه پایین تر هم میره. خوب این خصوصیت سهمیه دیگه!
این هم برنامه حرکت پرتابی یا پرتاب به زبان ویژوال بیسیک
در مورد این برنامه دقت داشته باشید که با کلیک بر روی صفحه ذره آماده حرکت از اون نقطه ای هستش که کلیک کردید. برای اینکه ذره از ارتفاع اولیه اش هم پایین تر بره زمانی که در بالا گفته شد رو زیاد کنید.

