برنامج باقة الزهور في ++C

c

يحتوي هذا المقال علي ثلاثة برامج , تجمع بين البهجة وبين المستوي المتقدم الذي يلقي مزيدا من الضوء علي ما للغة السي ++ والبرمجة الكائنية من إمكانيات . البرنامج الأول يستخدم فكرة "الفراكتال fractals " لإنتاج زهرة تتفتح بألوان زاهية , والثاني برنامج باسم "لعبة الحياة" يمثل نموذجا لكائن مجهري يتكاثر , أما البرنامج الثالث فهي اللعبة الشهيرة باسم ''Tic-Tac-Toc'' والتي نعرفها باسم "إكس-أو" . يستخدم البرنامج الأول إمكانيات الدوال الرسومية للغة السي++ , أما البرنامجان الأخران فيستخدمان المحارف الرسومية لجدول الأسكي .

برنامج باقة الزهور

فكرة "الفراكتال"

تتلخص فكرة "الفراكتال" في إنتاج تشكيلات ذاتية الاستنساخ من شكل أولي , أي تستطيع أن تكرر نفسها مرات ومرات بناء علي صيغة رياضية معينة .

وأفضل طريقة لفهم هذا الأسلوب من إخراج الرسومات الحاسوبية هو أعطاء مثال لها , والمثال الذي نقدمه يخرج باقة تتفتح بزهور حمراء اللون وفي كل مرة تشغيل فيها البرنامج , تحصل علي باقة جديدة

شرح البرنامج

النمط الأولي في برنامجنا غاية في البساطة , مجرد خط مستقيم , يتصل عدد من قطع منه بزاوية معينة , وبلون معين , لتكوين فروع الباقة أو أوراق الزهرة

ويتمثل تطبيق أسلوب الاستنساخ الذاتي في أن كل فرع في الباقة يتفرع عنه عدة أفرع أصغر في الطول , و يلون مختلف

والزهور حمراء أما الأفرع فهي متدرجة الألوان , تبدأ بفروع من اللون الأصفر يتفرع عنها أفرع باللون الأخضر الفاتح أصغر من الصفراء طولا , تتفرع عن أفرع أصغر باللون الأخضر , هي التي تحمل الزهور .

وإليك صياغة البرنامج , ثم نتولي شرحه بعد ذلك

 

Tendril.cpp

// tendrils.cpp


// draws "biological" forms


// UCS Laboratories


 


#include <graphics.h>         // for graphics functions


#include <stdlib.h>           // for randomize(), rand()


#include <time.h>             // for randomize()


#include <math.h>             // for sin(), cos()


#include <conio.h>            // for getch()


 


const int   X0=320;           // center of screen


const int   Y0=240;             


const float PI=3.14159;       // pi


const int   NUME=30;          // numerator of probability


const int   DENOM=100;        // denomenator of probability


const int   NUMBER = 7;       // number of tendrils per cluster


const float RAD = 3.0;        // length of straight line segments


const float DELTHETA = 0.1;   // change in theta (radians)


const int   SEGS = 60;        // max line segments per tendril


const int   REDUX = 3;        // how much to divide # of segments


const int   MIN = 1;          // minimum number of line segments


 


class cluster


   {


   public:


      void display(int size, int x, int y);


   };


 


class tendril


   {


   public:


      void display(int size, float theta, int x, int y);


   };


 


void cluster::display(int size, int x, int y)


   {


   if( kbhit() )


      exit(0);


   for(int i=0; i<NUMBER; i++)        // for each tendril


      {


      float theta = i * 2*PI/NUMBER;


      moveto(x, y);


      tendril t;                      // make a tendril


      t.display(size, theta, x, y);   // display it


      }


   }


 


void tendril::display(int size, float theta, int x, int y)


   {


   for(int j=0; j<size; j++)


      {                               // left or right


      int chng = ( random(DENOM)<NUME ) ? -1 : 1;


      theta = theta + chng*DELTHETA;  // new angle


      x = x + RAD*sin(theta);         // x and y of


      y = y + RAD*cos(theta);         //    next point


      if(size < 4) setcolor(RED);


      else if(size < 13) setcolor(GREEN);


      else if(size < 40) setcolor(LIGHTGREEN);


      else setcolor(YELLOW);


      lineto(x, y);                   // draw line


      }                               


   if( size > MIN )


      {                               // if tendril long enough


      cluster c;                      // make a new cluster


      int newsize = size / REDUX;     // but smaller than before


      c.display(newsize, x, y);       // display it


      }


   }


 


 


void main()


   {


   int driver, mode;


   driver = VGA;      // set graphics driver


   mode = VGAHI;      // and graphics mode


   initgraph(&driver, &mode, "\\bc45\\bgi");


   randomize();       // seed random number generator


          


   int x=X0, y=Y0;    // set origin of cluster


   int size = SEGS;


   cluster c;         // define a cluster


   c.display(size, x, y);   // display the cluster


   getch();           // hold image until keypress


   closegraph();      // reset graphics system


  }






شرح خرج البرنامج :



تتكون الزهرة من سبعة أفرع صفراء , يتفرع عن كل فرع سبعة أفرع أصغر في الطول , باللون الأخضر الفاتح , يتفرع عن كب فرع سبعة أفرع أصغر باللون الأخضر , وتتفرع بدورها إلي سبعة أفرع باللون الأحمر هي أصغر الفروع , وتمثل الزهور في الشكل . (كل فرع يبلغ ثلث الفرع السابق عليه تقريبا) .



يتكون كل فرع من عدد من القطع المستقيمة يحددها المتغير size . ( وهو متغير يتناقص في قيمه بمقدار الثلث عند الانتقال من لون للأخر) , وهو المتغير الذي يحدد لون الفرع أيضا . وتتصل القطع ببعضها البعض بزاوية معينة theta , وهي تعطي الفرع شكله المتقوس .



وحتي تعطي أشكال الفراكتال إيحاءات بتمثيل الطبيعة , يجب أن تعطي لمسة من عشوائية , وفي برنامجنا هذا تكون العشوائية عن طريق جعل الزاوية theta تتأرجح لليمين تارة ولليسار تارة أخري , فتتقوس الأفرع تبعا لذلك , وقد صمم البرنامج بحيث يكون احتمال التقوس لليسار أكبر , وهو ما يفسر انحناء الشكل ككل للناحية اليسري من الشاشة .



شرح البرنامج :



يتكون البرنامج من فئتين tendril : وهي التي تتولي كائناتها رسم الأفرع و cluster وتحدد كائناتها عدد الأفرع من كل لون (العدد7) , وتتحكم في عمل كائنات الفئة الأولي .



كائنات الفئة cluater :



تحتوي علي دوارة for تنفذ 7 دورات (العدد محدد بالثابت NUMBER) كل دورة لرسم من الفروع وتقوم بالأتي :



- تحديد الزاوية بالقيمة : i*2*PI/NUMBER حيث يأخذ المتغير I يأخذ المتغير I القيم من 0 إلي 6 .



- تحريك المشيرة لنقطة بدء رسم كل فرع بالأمر :



Moveto(x,y);



- تكوين كائن t من الفئة tendril لرسم الفرع .



كما تحتوي كائنات الفئة علي تشغيل البرنامج , وهو عدم الضرب علي أي مفتاح , بالشرط if kbhit



كائنات الفئة tendril :



يتلقي كائن هذه الفئة المعلومات التالية من كائن الفئة cluster الذي أنشأه : قيمة المتغير size قيمة الزاوية theta , الإحداثيين x, y .



ويحتوي الكائن علي دوارة تقوم بعمل عدد من الدورات محدد بالقيمة size تحتوي كل دورة علي الأوامر التالية :



- تحديد شرط العشوائية بالأمر :



Int chng = (random(DENOM)<NUM) ? 1-:1;



معني لك أن المتغير chng (اختصار change) سوف يأخذ قيمة بين 1 و -1 .



- التحكم في قيمة الزاوية theta بأن يضاف إليها , أو يطرح منها قيمة صغيره هي 0.1 (محددة بالثابت (deltatheta ,والجمع أو الطرح بناء علي قيمة المتغير chng , وهي الحالة العشوائية في البرنامج .



- تحديد نقطة نهاية القطعة المستقيمة (سيأتي شرح ذلك عما قليل) .



- تحديد لون الفرع المرسوم طبقا لقيمة المتغير size وبالشروط : أكبر من 40 : أصغر , بين 40 و 13 أخضر فاتح , بين 13 و 7 أخضر , أقل من 4 أحمر .



- رسم القطعة المستقيمة من الفرع بدءا من نقطة البدء (المررة من الفئة cluster) إلي نقطة النهاية بالأمر line(x, y .



- تكرار ذلك أن ينتهي رسم الفرع بأكمله .



إذا كانت قيمة المتغير size لا تزال أكبر من 1 (محدد بالثابت MIN) تقوم الفئة بالتالي :



- إنشاء كائن من الفئة cluster حتي يبدأ رسم الفرع التالي .



- تغيير قيمة المتغير size بقسمته علي 3 (محدد بالثابت REDUX) , وإحالة هذه القيمة , وقيمة أخر نقطة في الفرع المرسوم , لكائن الفئة cluster الجديد .



تسلسل الخطوات :



- تنشئ الدالة الأصلية main كائنا c بالمعلومات التالية : عدد الدورات 7 , نقطة البدء 320 , 240 , المتغير size بالقيمة 60 .



- عند الخطوة الأولي (i=0) من الكائن c يقوم بتحديد قيمة الزاوية , وينشئ كائنا t محيلا إليه هذه القيمة , بالإضافة لقيمة نقطة البدء والمتغير size .



- يقوم الكائن t بالأتي :



· رسم الفرع بعدد 60 قطعة , وباللون الأصفر (حيث إن size أكبر من 40 )



· بعد إنتهاء الدوارة (رسم الفرع) , ولأن الشرط size>MIN متحقق , فإن الكائن يقوم بإنشاء كائن c جديد وبقسمة size علي 3 ليكون الناتج 20 ويحيل هذه القيمة إلي الكائن c المنشأ .



- يتولي الكائن الجديد c بتحديد زاوية التقوس , ثم إنشاء كائن جديد t لرسم الفرع التالي , ويكون باللون الأخضر الفاتح هذه المرة , وبعدد 20 قطعة (القيمة الجديدة للمتغير size) .



- بنفس الطريقة يرسم الفرع الأخضر (قيمة size 20/3 أي 7 مع حذف الباقي لأن size من نوع العدد الصحيح ) . ثم الفرع الأحمر (قيمة size7/3 أي 2 ) .



- لا ترسم ألوان أخري بعد اللون الأحمر , حيث تكون القيمة التالية للمتغير size هي الصفر (3/2) وهي أقل من 1 . وعلي ذلك يظل التحكم في البرنامج تحت سيطرة الكائن c الخاص برسم الأفرع الحمراء إلي أن ينتهي رسمها جميعا , فتنتهي دوارة هذا الكائن , وينتقل التحكم للكائن c الخاص باللون الأخضر .



- يقوم الكائن الأخير بإنشاء كائن t يرسم فرعا أخضر جديدا ويخلق كائنا c لرسم الأفرع الحمراء له .



- بعد انتهاء رسم كافة الأفرع الخضراء يعود التحكم للكائن الذي يرسم الأفرع الخضراء الفاتحة , لرسم فرع أخضر فاتح جديد , تتفرع عنه الفروع الباقية له .



- وهكذا إلي أن يعود التحكم للكائن c الذي أنشأته الدالة الأصلية , وهو المسئول عن رسم الأفرع الصفراء , ليبدأ رسم الفرع الأصفر الثاني (i=1) وهكذا .



المعاودة recursion



نري في البرنامج أن الكائن c ينشئ كائنا t يتولي بدوره إنشاء كائن c وهكذا دواليك . يسمي هذا الأسلوب معاودة recursion ومن خطورتها أنه إذا لم يوضع شرط لإنهائها فقد يستمر البرنامج في العمل إلي مالا نهاية وقد يؤدي ذلم لانهيار النظام .



والشرط نقطة النهاية للقطع المستقيمة



يحدد الإحداثيان السيني والصادي لنقطة نهاية القطعة المستقيمة بالأمرين :



x=x+RAD*sin(theta);



y=y+RAD*cos(theta);



أي أن الإحداثي السيني لنقطة النهاية يزيد بمقدار RAD*sin(theta); والإحداثي الصادي بمقدار RAD*cos(theta); عن إحداثيات نقطة البداية (لا تنس أن نقطة نهاية كل قطعة هي نقطة بداية القطعة التالية) . ومن تطبيق قواعد الهندسة أن نقطة القيمتين المذكورتين هما ضلعي المثلث القائم الزاوية  .



والبرنامج يمكن صياغته بكل سهولة بأسلوب البرمجة الإجرائية (دون اللجوء للكائنات) عن طريق دوارات متداخلة . ولكنك تري أن أسلوب الكائنات قد أضفي علي صياغة البرنامج وضوحا لا يستهان به .



ويمكنك بتغيير ثوابت البرنامج التغيير في شكل الزهرة المتولدة كأن تجعلها مثلا تتفرع إلي جهة اليمين بدلا من اليسار , أو في عدد الأفرع أو ألوانها .

تعليقات

المشاركات الشائعة من هذه المدونة

المؤثرات الحسابية في C++

الرسم Graphics

دوال النمط الرسومي في ++C