إنشاء لعبة منطقية لمنصة الألعاب

مرحبًا.

أريد أن أشارك قصتي في التعارف مع منصة الألعاب Gameduino 3 ، بالإضافة إلى القليل من الحديث عن برمجة أبسط لعبة منطقية لهذه المنصة ، والتي يتم استخدامها مع Arduino Uno.

ما هو Gameduino 3؟ Gameduino 3 عبارة عن لوحة توسعة تسمح لك بتحويل Arduino إلى وحدة تحكم لعبة محمولة (بحجم الحجم). لدهشتي ، لم أتمكن من العثور على أي معلومات تفصيلية على هذا المنتدى على المحور. أود سد هذه الفجوة ، خاصة وأن المجلس ، في رأيي ، يستحق الاهتمام.

القليل من التاريخ


مؤلف المشروع المسمى Gameduino هو جيمس بومان ، الذي أنشأ في عام 2011 النسخة الأولى من اللوحة. ثم تم وضعه كوحدة VGA لـ Arduino. تم استدعاء اللوحة Gameduino واستندت إلى عائلة Xilinx Spartan-3A من FPGA المنطقية القابلة للبرمجة. تم تثبيت موصلات لتوصيل شاشة VGA ومكبرات صوت استريو على اللوحة. الصورة

ميزات Gameduino (1):
  • إخراج فيديو VGA بدقة 400 × 300 بكسل ، 512 لونًا ؛
  • تتم معالجة جميع التدرج اللوني في FPGA بدقة 15 بت ؛

رسومات الخلفية:

  • مساحة من رسومات الخلفية الرمزية 512x512 بكسل ؛
  • 256 حرفًا ، لكل منها لوحة مستقلة بأربعة ألوان ؛
  • تنفيذ تأثير التفاف السلاسل النصية مع تجانس بكسل ؛

الرسومات الأمامية:

  • كل نقش متحرك له دقة 16x16 بكسل.
  • يمكن أن تحتوي كل نقش متحرك على لوحة ألوان 256 أو 16 أو 4 ألوان ؛
  • دعم خوارزميات الدوران الأفقي والدوران رباعي الاتجاهات ؛
  • 96 sprites لكل خط نقطي ، 1536 عنصر نسيج لكل خط نقطي ؛
  • آلية للكشف عن التقاطعات المحتملة للعفاريت ؛

إخراج الصوت:

  • 12 بت مزج تردد ثنائي القناة ؛
  • تعدد الأصوات 64 صوتًا في نطاق التردد من 10 إلى 8000 هرتز.

يتم عرض الصورة على شاشة جهاز عرض VGA قياسي بدقة 400 × 300 بكسل ،
الحفاظ على التوافق مع أي شاشات VGA قياسية بدقة 800 × 600 بكسل.

في عام 2013 ، تم إصدار الإصدار الثاني من اللوحة - Gameduino 2 ، حيث ، على عكس الإصدار السابق ، كان هناك بالفعل شاشة تعمل باللمس مقاومة 4.3 بوصة بدقة 480x272 ، ومقياس تسارع ثلاثي المحاور ، وفتحة بطاقة ذاكرة microSD ، وإخراج صوتي لسماعات الرأس.

الصورة

كان "قلب" اللوحة هو جهاز التحكم الرسومي EVE (محرك الفيديو المضمن - باللغة الروسية يمكن ترجمته كـ "وحدة الفيديو المضمنة") FT800 ، التي تتمتع بقدرات حوسبة قوية ، تجمع بين العديد من الوظائف في نفس الوقت: تكوين الصورة وإخراجها على شاشة شاشة TFT ، شاشة تعمل باللمس ، توليد الصوت.

رسم تخطيطي وظيفي لوحدة تحكم الرسومات FT800
الصورة

يتم تضمين الكتل الوظيفية التالية في هيكل الدائرة المصغرة: وحدة التحكم الرسومية ، وحدة التحكم في الصوت ، وحدة التحكم في لوحة اللمس المقاومة. تم تصميم شريحة FT800 للتحكم في الشاشات بدقة تصل إلى 512 × 512 بكسل. يدعم FT800 أيضًا LCD WQVGA (480 × 272) و QVGA (320 × 240). يعد EVE (محرك الفيديو المضمن) FT800 حلاً جاهزًا لإنشاء واجهة مستخدم رسومية. تولد الدائرة المصغرة إشارات تحكم في العرض ، وتحتوي على وظائف رسومية مضمنة لعرض النقاط والخطوط والصور النقطية والأزرار الحجمية والنصوص وما إلى ذلك.


هيكل النظام بناءً على وحدة تحكم الرسومات FT800
يعتمد تكوين الصورة على مجموعة من الأوامر (قائمة العرض) ، والتي يتم نقلها بواسطة متحكم التحكم إلى FT800 عبر واجهة I2C أو SPI (في Gameduino 2 ، يتم الاتصال بين Arduino و FT800 عبر واجهة SPI). ميزات FT800 تخفف بشكل كبير من تحكم المضيف للنظام.

الصورة

على سبيل المثال ، لعرض عدد من الأزرار ، يكفي نقل أمر واحد إلى وحدة تحكم الرسومات (أربع كلمات 32 بت) ، وستشكل FT800 بشكل مستقل صورة لهذه الأزرار على شاشة عرض TFT. تتضمن مجموعة أوامر وحدة تحكم الرسومات FTDI أكثر من 50 وظيفة يمكن استخدامها لعرض صور متنوعة على شاشة العرض بتأثيرات مختلفة.

يمكن العثور على دليل برمجة وحدة تحكم مفصل وأمثلة على العمل مع بيئات تصميم مختلفة في ملاحظات التطبيق على موقع FTDI على الويب.

باللغة الروسية ، يوجد وصف جيد للوظيفة والمبادئ العامة وأمثلة للعمل هنا .

ميزات Gameduino 2:
  • دقة شاشة 480x272 بكسل بألوان 24 بت ؛
  • مجموعة من الأوامر بأسلوب OpenGL ؛
  • ما يصل إلى 2000 نقشات من أي حجم ؛
  • 256 كيلوبايت من ذاكرة الفيديو ؛
  • دوران العفريت السلس والتحجيم مع الترشيح الثنائي.
  • دائرة ناعمة ونمط خطي في الأجهزة - تنعيم 16x ؛
  • فك تشفير أجهزة JPEG ؛
  • عرض مدمج للتدرجات والنصوص والأقراص والأزرار.

يتم إخراج الصوت من خلال مقبس سماعة رأس مقوى.
يدعم النظام اختيار العينات والأدوات المدمجة.

ROM تحكم سلكي بالفعل:

  • خطوط عالية الجودة (6 أحجام) ؛
  • عينات من 8 آلات موسيقية تعزف عليها مذكرة MIDI ؛
  • عينات من 10 أصوات قرع.

وبالطبع ، يمكنك تحميل الخطوط الخاصة بك ودغات الصوت في 256 كيلوبايت من ذاكرة الوصول العشوائي.

استخدام منصة Arduino ليس شرطًا أساسيًا: يمكن توصيل لوحة Gameduino 2 بأي لوحة تحكم دقيقة أو واجهة متحكم بواجهة SPI.

في عام 2017 ، تم إصدار الإصدار الثالث من اللوحة - Gameduino 3 ، والتي تبدو متطابقة تقريبًا مع Gameduino 2. بدلاً من FT800 ، يتم استخدام وحدة تحكم الرسومات الجديدة FT810 ، والتي تتوافق مع الإصدارات السابقة مع FT800 (أي أن كل كود لـ Gameduino2 يعمل على Gameduino3). ولكن في نفس الوقت لديها إمكانات حوسبة أكبر 4 مرات ، مثل فك تشفير JPEG للأجهزة بشكل أسرع ، وفك تشفير الفيديو ، وما يصل إلى 1 ميغابايت من ذاكرة الوصول العشوائي ، وما إلى ذلك.

يتميز Gameduino 3:
  • وحدة فك ترميز الفيديو لشاشة فيديو كاملة بمعدل 30 إطارًا في الثانية ؛
  • 1 ميغا بايت من ذاكرة الوصول العشوائي الداخلية ؛
  • موصلات لبطاقات microSD وخرج الصوت ؛
  • 4.3 بوصة 480x272 لوحة LCD عالية التباين مع شاشة تعمل باللمس مقاوم ؛
  • دعم الخرائط التي تم إنشاؤها باستخدام محرر الخرائط المتجانبة ؛
  • تحميل صورة PNG من microSD ؛
  • تسريع فك تشفير JPEG ؛
  • صورة الأجهزة / تبديل المناظر الطبيعية ؛
  • دعم Arduino و ESP8266 و Teensy 3.2 ؛
  • أدوات عبر الإنترنت لإعداد الرسومات والصوت والخط والفيديو ؛


نشر جيمس بومان مكتبة لمشروعه مع العديد من الأمثلة التي تعمل مباشرة خارج الصندوق. المكتبة الحالية التي تمكنت من العثور عليها موجودة هنا . دليل البرمجة (باللغة الإنجليزية) ، حيث يتم وصف كل شيء بالتفصيل. الكثير من المعلومات المفيدة حول تثبيت IDE ، وما إلى ذلك.

برمجة


بطريقة أو بأخرى ، تجولت حول مساحات مسرح Bolshoi Internet ، لقد صادفت مشروعًا مثيرًا للاهتمام لـ Arduino - اللعبة المنطقية "Columns" ، مكتوبة تحت الشاشة الصينية الملونة غير المكلفة المعتادة بحجم 128x160 بكسل. كنت أرغب في تكرار هذه اللعبة ، ولكن على لوحتي ، سأسميها FT810 (باسم GPU) ، التي كانت في ذلك الوقت بين يدي بالفعل. تمكنت بالفعل من دراسة دليل البرمجة والأمثلة من المكتبة ، لذا فإن "يدي" تشعر بالحكة من الرغبة في كتابة شيء خاص بهم. الذي بدأته على الفور.

أول شيء كان علي فعله هو عرض النص على الشاشة.

بفضل وجود خطوط مضمنة ، فإن إخراج النص على الشاشة سهل للغاية. هنا رسم تجريبي من المكتبة (مع تعليقاتي):

رسم helloworld.ino
#include <EEPROM.h> #include <SPI.h> #include <GD2.h> void setup() { Serial.begin(1000000); //   ,    1000000  GD.begin(0); //  ,   . } void loop() { GD.ClearColorRGB(0x103000); //    . GD.Clear(); //   (   ) GD.cmd_text( //  ,  GD.w / 2, //     2 GD.h / 2, //     2 31, //   OPT_CENTER, // ,         . "Hello world"); //   GD.swap(); //         (   ). } 
ونتيجة لذلك ، نحصل هنا على نص جميل:
الصورة

بعد ذلك ، كان من الضروري رسم الأشكال الهندسية ، على سبيل المثال: الخطوط.
لرسم الخطوط ، يجب استخدام Begin (LINES) أو Begin (LINE_STRIP).
تنضم LINES إلى كل زوج من القمم ، بينما ينضم LINE_STRIP إلى جميع القمم معًا.

سأقدم الرسم التوضيحي التالي من المكتبة (مع تعليقاتي):

خطوط رسم .ino
 #include <EEPROM.h> #include <SPI.h> #include <GD2.h> void setup() { Serial.begin(1000000); //   ,    1000000 . GD.begin(); //  ,   . } static void zigzag(int x) { GD.Vertex2ii(x - 10, 10); //     GD.Vertex2ii(x + 10, 60); //     () GD.Vertex2ii(x - 10, 110); GD.Vertex2ii(x + 10, 160); GD.Vertex2ii(x - 10, 210); GD.Vertex2ii(x + 10, 260); //     () } void loop() { GD.Clear(); //   (    - 0000000) GD.Begin(LINES); //     zigzag(140); //   zigzag   -   GD.Begin(LINE_STRIP); //     zigzag(240); GD.LineWidth(16 * 10); //        1/16 , .. 1/16 * 16 * 10 = 10  GD.Begin(LINE_STRIP); zigzag(340); GD.swap(); //         (   ). } 

الخطوط على الشاشة:

الصورة

من الرسم الخطي ، دعنا ننتقل إلى رسم المستطيلات.

لرسم مستطيلات ، استخدم Begin (RECTS) واضبط الزوايا المقابلة للمستطيل. لا يهم ترتيب الزوايا. يتم رسم المستطيلات بزوايا مستديرة ، باستخدام عرض الخط الحالي كنصف قطر الزاوية. تمتد الزوايا المستديرة إلى ما وراء حدود المستطيل ، لذا فإن زيادة نصف قطر الزاوية تؤدي إلى زيادة في عدد وحدات البكسل. يرسم هذا المثال مستطيل 420 × 20 ثلاث مرات مع زيادة نصف قطر الزاوية.

رسم مستطيلات .ino
 #include <EEPROM.h> #include <SPI.h> #include <GD2.h> void setup() { Serial.begin(1000000); //   ,    1000000 . GD.begin(); //  ,   . } void loop() { GD.Clear(); //   (    - 0000000) GD.Begin(RECTS); //     GD.Vertex2ii(30, 30); //       GD.Vertex2ii(450, 50); //       GD.LineWidth(16 * 10); //         1/16 , .. 1/16 * 16 * 10 = 10  GD.Vertex2ii(30, 120); //       GD.Vertex2ii(450, 140); //       GD.LineWidth(16 * 20); //         1/16 , .. 1/16 * 16 * 20 = 20  GD.Vertex2ii(30, 220); //       GD.Vertex2ii(450, 230); //       GD.swap(); //         (   ) } 

النتيجة:
الصورة

دعنا ننتقل إلى رسم دائرة - أساس أزرار اللمس المستقبلية. دعنا نعود إلى المثال الأول مع النص وإضافة بضعة أسطر للحلقة ().

رسم برسم دوائر ملونة
 #include <EEPROM.h> #include <SPI.h> #include <GD2.h> void setup() { Serial.begin(1000000); //   ,    1000000  GD.begin(0); //  ,   . } void loop() { GD.ClearColorRGB(0x103000); //    . GD.Clear(); //   (   ) GD.cmd_text( //  ,  GD.w / 2, //     2 GD.h / 2, //     2 31, //   OPT_CENTER, // ,          "Hello world"); //   GD.PointSize(16 * 30); //    ()    1/16 , .. 1/16 * 16 * 30 = 30  GD.Begin(POINTS); //     () GD.ColorRGB(0xff8000); //   orange GD.Vertex2ii(220, 100); //   ()   220,100 GD.ColorRGB(0x0080ff); //   teal GD.Vertex2ii(260, 170); //   ()   260,170 GD.swap(); //         (   ) } 

النتيجة:



للعمل بأزرار اللمس ، من الضروري تنظيم معالجة الضغط على شاشة اللمس. يتم ذلك على النحو التالي. سأشرح المبدأ بإيجاز. كل بكسل (نقطة) على الشاشة له لون. يحتوي أيضًا على قيمة علامة غير مرئية ، والتي يمكن تعيينها إلى نقطة (أو كائن بأكمله مثل الخط ، الدائرة ، المستطيل ، إلخ) وتستخدم أيضًا للكشف عن اللمسات لهذا الكائن. يُظهر الرسم التالي مثالاً على تعيين قيمة العلامة للدوائر الملونة على 100 و 101.

رسم شاشة اللمس
 #include <EEPROM.h> #include <SPI.h> #include <GD2.h> void setup() { Serial.begin(1000000); //   ,    1000000  GD.begin(0); //  ,   . } void loop() { GD.ClearColorRGB(0x103000); //    . GD.Clear(); //   (   ) GD.cmd_text( //  ,  GD.w / 2, //     2 GD.h / 2, //     2 31, //   OPT_CENTER, // ,          "Hello world"); //   GD.PointSize(16 * 30); //    ()    1/16 , .. 1/16 * 16 * 30 = 30  GD.Begin(POINTS); //     () GD.ColorRGB(0xff8000); //   orange GD.Tag(100); //       () GD.Vertex2ii(220, 100); //   ()   220,100 GD.ColorRGB(0x0080ff); //   teal GD.Tag(101); //       () GD.Vertex2ii(260, 170); //   ()   260,170 GD.swap(); //         (   ) GD.get_inputs(); //     if(GD.inputs.tag > 0) //      Serial.println(GD.inputs.tag); //       “”  } 

الآن ، عندما يكتشف النظام لمسة على أي دائرة ، فإنه يبلغ عن رمز المستشعر الخاص به ، في هذه الحالة 100 أو 101. عند النقر على كائنات (دوائر ملونة) ، سيتم عرض قيم العلامات المقابلة للكائنات المضغوطة على الشاشة في نافذة المنفذ التسلسلي:



لقد قدمت أمثلة على العمليات الأساسية ، والتي كان من الممكن بالفعل استخدامها بأمان في إنشاء اللعبة. بالطبع ، لم يتم إنشاء اللعبة من الصفر ، ولكن الأساس كان رمزًا جاهزًا للعمل تم تكييفه (مع الحفاظ على الرسومات) ، لذلك كانت النسخة الأصلية من اللعبة تشبه إلى حد كبير النسخة الأصلية.

الإصدار الأول من اللعبة:



بعد اللعب لعدة أيام ، أردت تغيير شيء ما في التصميم ، على سبيل المثال ، لإضافة بعض الأشياء الأخرى غير العادية بدلاً من الخلفية البيضاء. ثم تذكرت أحد الأمثلة من المكتبة ، حيث تومض السماء المرصعة بالنجوم في الخلفية:

فتحة رسم slotgag.ino
 #include <EEPROM.h> #include <SPI.h> #include <GD2.h> #include "slotgag_assets.h" //      void setup() { Serial.begin(1000000); GD.begin(); LOAD_ASSETS(); //     } void loop() { GD.Clear(); //   ( ,     ) GD.ColorMask(1, 1, 1, 0); //        R, G, B,   GD.Begin(BITMAPS); //      GD.BitmapHandle(BACKGROUND_HANDLE); //   - GD.BitmapSize(NEAREST, REPEAT, REPEAT, 480, 272); //        GD.Vertex2ii(0, 0, BACKGROUND_HANDLE); //      0,0 GD.ColorMask(1, 1, 1, 1); GD.ColorRGB(0xa0a0a0); GD.Vertex2ii(240 - GAMEDUINO_WIDTH / 2, 136 - GAMEDUINO_HEIGHT / 2, GAMEDUINO_HANDLE); static int x = 0; GD.LineWidth(20 * 16); GD.BlendFunc(DST_ALPHA, ONE); GD.Begin(LINES); GD.Vertex2ii(x, 0); GD.Vertex2ii(x + 100, 272); x = (x + 20) % 480; //' }a GD.swap(); } 

عرض:



من أجل عدم الخوض في البراري ، لم أعلق على الكود بأكمله ، لكنني علقت فقط على الأسطر الضرورية من الكود التي قمت بنسخها في رسم عملي.

لإضافة صورة للسماء المرصعة بالنجوم كخلفية ، كان علي القيام بما يلي: أولاً ، قم بتغيير اللون الأسود للخطوط والنص إلى اللون الأبيض (بحيث تكون مرئية على خلفية سوداء) ، اكتب الملف slotgag.gd2 إلى بطاقة SD الصغيرة ، التي تخزن الصورة أضف slotgag_assets.h إلى مجلد المشروع وأضف 8 أسطر من التعليمات البرمجية الضرورية إلى الرسم التخطيطي.

ونتيجة لذلك ، حصلت اللعبة على هذا النموذج:



وبطبيعة الحال ، ما هو نوع اللعبة بدون تصميم سليم؟ يبقى إضافة المؤثرات الصوتية ، خاصة أنها تقدم بجودة عالية ومتنوعة.

يحتوي Gameduino 2/3 على نظامين للصوت. الأول - مركب - يمكن أن يولد مجموعة من الأصوات الثابتة والنوتات الموسيقية. يعتبر المزج مفيدًا لإضافة الصوت بسرعة إلى المشروع ، ولكن نظرًا لأن مجموعة الأصوات ثابتة ، فهي ليست مرنة للغاية. والثاني هو استنساخ العينة. يعيد إنتاج الصوت المأخوذ من الذاكرة الرئيسية بتنسيقات مختلفة. هذا النظام أكثر مرونة ، ولكن ستحتاج إلى إعداد وتحميل عينات في ذاكرة الوصول العشوائي.

لقد استخدمت مركب صوت ثابت. يوفر جهاز المزج عدة أصوات "قرع" قصيرة ، تستخدم بشكل أساسي في واجهات المستخدم. لتشغيل صوت ، يجب استدعاء GD.play () بمعرف صوت. قائمة كاملة بالأصوات المتاحة:

انقر
التبديل
جوبيل
الشق
هايهات
Kickdrum
موسيقى البوب
كلاك
شاك

الملخص


وكانت النتيجة مثل هذا الرسم:

رسم أعمدة .ino
 #include <SPI.h> #include <GD2.h> #include <avr/eeprom.h> #include "slotgag_assets.h" #define TAG_BUTTON_LEFT 201 #define TAG_BUTTON_RIGHT 202 #define TAG_BUTTON_ROT 203 #define TAG_BUTTON_DROP 204 #define X_BUTTON_LEFT 50 #define Y_BUTTON_LEFT 222 #define X_BUTTON_RIGHT 430 #define Y_BUTTON_RIGHT 222 #define X_BUTTON_ROT 430 #define Y_BUTTON_ROT 50 #define X_BUTTON_DROP 50 #define Y_BUTTON_DROP 50 // Color definitions #define BLACK 0x000000 #define RED 0xFF0000 #define GREEN 0x00FF00 #define BLUE 0x0000FF #define YELLOW 0xFFFF00 #define MAGENTA 0xFF00FF #define CYAN 0x00FFFF #define WHITE 0xFFFFFF #define DISPLAY_MAX_X 480 #define DISPLAY_MAX_Y 272 #define MaxX 8 #define MaxY 17 #define SmeX 3 #define SmeY 3 #define razmer 18 #define NumCol 6 #define MaxLevel 8 #define NextLevel 80 #define DISP_LEFT ((DISPLAY_MAX_X - MaxX*razmer)/2 - 2) #define DISP_RIGHT ((DISPLAY_MAX_X + MaxX*razmer)/2 + 2) #define DISP_TOP ((DISPLAY_MAX_Y - (MaxY-4)*razmer)/2 - 2) #define DISP_BOT ((DISPLAY_MAX_Y + (MaxY-4)*razmer)/2 + 2) uint8_t MasSt[MaxX][MaxY], MasTmp[MaxX][MaxY], fignext[3]; uint8_t Level=1, dx, dy, tr, flfirst=1; uint32_t MasCol[]={WHITE, BLACK, RED, BLUE, GREEN, YELLOW, MAGENTA, CYAN}; unsigned long Counter, Score=0, TScore=0, Record=0, myrecord; uint16_t tempspeed = 1000; bool fl, Demo=true, Arbeiten=false, FlZ=false; int8_t x,y; int8_t mmm [4][2]={{-1,0},{0,-1},{1,0},{0,1}}; uint16_t MasSpeed[MaxLevel]={500,450,400,350,300,250,200,100}; uint8_t state_game = 0; unsigned long time_count; byte prevkey; uint32_t btn_color = 0xff0000; /****************************************************************************************************************/ void setup(void) { Serial.begin(1000000); Serial.println("Columns"); GD.begin(); LOAD_ASSETS(); GD.BitmapHandle(BACKGROUND_HANDLE); GD.BitmapSize(NEAREST, REPEAT, REPEAT, 480, 272); randomSeed(analogRead(5)); myrecord = eeprom_read_byte((unsigned char *)1); time_count = millis() + 1000; } static struct { byte t, note; } pacman[] = { { 0, 71 }, { 2, 83 }, { 4, 78 }, { 6, 75 }, { 8, 83 }, { 9, 78 }, { 12, 75 }, { 16, 72 }, { 18, 84 }, { 20, 79 }, { 22, 76 }, { 24, 84 }, { 25, 79 }, { 28, 76 }, { 32, 71 }, { 34, 83 }, { 36, 78 }, { 38, 75 }, { 40, 83 }, { 41, 78 }, { 44, 75 }, { 48, 75 }, { 49, 76 }, { 50, 77 }, { 52, 77 }, { 53, 78 }, { 54, 79 }, { 56, 79 }, { 57, 80 }, { 58, 81 }, { 60, 83 }, { 255, 255 } }; //================================================== void loop(void) { GD.get_inputs(); byte key = GD.inputs.tag; int8_t VAL = 0; if (prevkey == 0x00) { switch (key) { case TAG_BUTTON_LEFT: VAL = -1; break; case TAG_BUTTON_RIGHT: VAL = 1; break; case TAG_BUTTON_ROT: if (!FlZ) { GD.play(HIHAT); byte aa=MasSt[x][y]; MasSt[x][y]=MasSt[x][y+2]; MasSt[x][y+2]=MasSt[x][y+1]; MasSt[x][y+1]=aa; } break; case TAG_BUTTON_DROP: if (Arbeiten) { if (!FlZ) { tempspeed=50; GD.play(NOTCH); } } else { GD.play(CLICK); Demo=false; NewGame(); } break; } } prevkey = key; if (VAL!=0 && fig_shift(VAL) && !FlZ) { for (byte i=0;i<3;i++) { MasSt[x+VAL][y+i]=MasSt[x][y+i]; MasSt[x][y+i]=0; } x=x+VAL; } ProcGame(); ViewStacan(); GD.swap(); } //================================================== // redraw one square void ViewQuad(byte i,byte j,byte mycolor) { if (j<3) return; uint16_t wy=DISP_TOP + SmeY+(j-3)*razmer-j; uint16_t wx=DISP_LEFT + SmeX+i*razmer-i; if (mycolor!=0) { GD.LineWidth(16*1); GD.ColorRGB(WHITE); GD.Begin(LINE_STRIP); GD.Vertex2ii(wx,wy); GD.Vertex2ii(wx + razmer-1,wy); GD.Vertex2ii(wx + razmer-1,wy + razmer-1); GD.Vertex2ii(wx,wy + razmer-1); GD.Vertex2ii(wx,wy); GD.Begin(RECTS); GD.ColorRGB(MasCol[mycolor]); GD.Vertex2ii(wx+1, wy+1); GD.Vertex2ii(wx+1 + razmer-2 - 1, wy+1 + razmer-2 - 1); } else { } } //================================================== void ViewStacan(void) { char myStr2[5]; // Draw background fone GD.Clear(); GD.ColorMask(1, 1, 1, 0); GD.Begin(BITMAPS); GD.BitmapHandle(BACKGROUND_HANDLE); GD.BitmapSize(NEAREST, REPEAT, REPEAT, 480, 272); GD.Vertex2ii(0, 0, BACKGROUND_HANDLE); // Print text GD.ColorRGB(WHITE); GD.cmd_text(DISP_LEFT - 30, DISP_TOP + 3, 27, OPT_CENTER, "LEVEL"); GD.cmd_text(DISP_RIGHT + 30, DISP_TOP + 3, 27, OPT_CENTER, "NEXT"); GD.cmd_text(DISP_RIGHT + 30, DISP_TOP + 100, 27, OPT_CENTER, "SCORE"); GD.cmd_text(DISP_LEFT - 30, DISP_TOP + 100, 27, OPT_CENTER, "TOP"); // Print digit Score GD.ColorRGB(RED); sprintf(myStr2,"%05d",Score ); GD.cmd_text(DISP_RIGHT + 30, DISP_TOP + 130, 27, OPT_CENTER, myStr2); // Print digit Top sprintf(myStr2,"%05d",myrecord ); GD.cmd_text(DISP_LEFT - 30, DISP_TOP + 130, 27, OPT_CENTER, myStr2); // Print digit Level sprintf(myStr2,"%02d",Level ); GD.cmd_text(DISP_LEFT - 30, DISP_TOP + 40, 31, OPT_CENTER, myStr2); // Draw color squares for (byte j=3;j<MaxY;j++) for (byte i=0;i<MaxX;i++) ViewQuad(i,j,MasSt[i][j]); // Draw Next Figure for (byte i=0;i<3;i++) { GD.ColorRGB(WHITE); GD.Begin(LINE_STRIP); GD.LineWidth(16*1); GD.Vertex2ii(DISP_RIGHT + 15, DISP_TOP + 20 + razmer*ii); GD.Vertex2ii(DISP_RIGHT + 15 + razmer-1, DISP_TOP + 20 + razmer*ii); GD.Vertex2ii(DISP_RIGHT + 15 + razmer-1, DISP_TOP + 20 + razmer*ii + razmer-1); GD.Vertex2ii(DISP_RIGHT + 15, DISP_TOP + 20 + razmer*ii + razmer-1); GD.Vertex2ii(DISP_RIGHT + 15, DISP_TOP + 20 + razmer*ii); GD.Begin(RECTS); GD.ColorRGB(MasCol[fignext[i]]); GD.Vertex2ii(DISP_RIGHT+15+1, DISP_TOP+20+razmer*i-i+1); GD.Vertex2ii(DISP_RIGHT+15+1+razmer-2-1, DISP_TOP+20+razmer*i-i+1+razmer-2-1); } // Draw "stacan" GD.ColorRGB(WHITE); GD.Begin(LINE_STRIP); GD.LineWidth(16*1); GD.Vertex2ii(DISP_LEFT + 1, DISP_TOP); GD.Vertex2ii(DISP_LEFT + 1, DISP_BOT); GD.Vertex2ii(DISP_LEFT + 1 + razmer*MaxX+5-MaxX - 1, DISP_BOT); GD.Vertex2ii(DISP_LEFT + 1 + razmer*MaxX+5-MaxX - 1, DISP_TOP); // Draw 9 vertical lines for (byte i=0; i<9; i++) { GD.ColorRGB(WHITE); GD.Begin(LINE_STRIP); GD.LineWidth(16*1); GD.Vertex2ii(DISP_LEFT + 3 + razmer*ii, DISP_TOP); GD.Vertex2ii(DISP_LEFT + 3 + razmer*ii, DISP_BOT - 2); } // Draw 1 horizontal line GD.ColorRGB(WHITE); GD.Begin(LINE_STRIP); GD.Vertex2ii(DISP_LEFT + 3, DISP_BOT - 2); GD.Vertex2ii(DISP_LEFT + 3 + razmer*MaxX-MaxX - 1, DISP_BOT - 2); // Draw "Game Over" if (!Demo && !Arbeiten) { GD.Begin(RECTS); GD.ColorRGB(WHITE); GD.Vertex2ii((DISP_LEFT + DISP_RIGHT)/2 - 60, (DISP_TOP + DISP_BOT)/2 - 40); GD.Vertex2ii((DISP_LEFT + DISP_RIGHT)/2 + 60, (DISP_TOP + DISP_BOT)/2 + 40); GD.ColorRGB(BLACK); GD.Vertex2ii((DISP_LEFT + DISP_RIGHT)/2 - 58, (DISP_TOP + DISP_BOT)/2 - 38); GD.Vertex2ii((DISP_LEFT + DISP_RIGHT)/2 + 58, (DISP_TOP + DISP_BOT)/2 + 38); GD.ColorRGB(RED); GD.cmd_text((DISP_LEFT + DISP_RIGHT)/2, (DISP_TOP + DISP_BOT)/2 - 20, 30, OPT_CENTER, "GAME"); GD.cmd_text((DISP_LEFT + DISP_RIGHT)/2, (DISP_TOP + DISP_BOT)/2 + 20, 30, OPT_CENTER, "OVER"); } // Draw Buttons GD.Begin(POINTS); GD.PointSize(16*50); // Set size of buttons (50 pix) GD.ColorRGB(btn_color); // Set fone color of buttons GD.Tag(TAG_BUTTON_LEFT); // Set TAG for BUTTON_LEFT GD.Vertex2ii( X_BUTTON_LEFT, Y_BUTTON_LEFT); // Place BUTTON1 GD.Tag(TAG_BUTTON_RIGHT); // Set TAG for BUTTON_RIGHT GD.Vertex2ii(X_BUTTON_RIGHT, Y_BUTTON_RIGHT); // Place BUTTON2 GD.Tag(TAG_BUTTON_ROT); // Set TAG for BUTTON_ROT GD.Vertex2ii( X_BUTTON_ROT, Y_BUTTON_ROT); // Place BUTTON3 GD.Tag(TAG_BUTTON_DROP); // Set TAG for BUTTON_DROP GD.Vertex2ii(X_BUTTON_DROP, Y_BUTTON_DROP); // Place BUTTON4 // Draw figures in buttons circles GD.Tag(255); GD.ColorRGB(0xffff00); GD.LineWidth(16*2); GD.Begin(LINE_STRIP); GD.Vertex2ii(X_BUTTON_LEFT + 30, Y_BUTTON_LEFT - 20); GD.Vertex2ii(X_BUTTON_LEFT, Y_BUTTON_LEFT - 20); GD.Vertex2ii(X_BUTTON_LEFT, Y_BUTTON_LEFT - 40); GD.Vertex2ii(X_BUTTON_LEFT - 40, Y_BUTTON_LEFT); GD.Vertex2ii(X_BUTTON_LEFT, Y_BUTTON_LEFT + 40); GD.Vertex2ii(X_BUTTON_LEFT, Y_BUTTON_LEFT + 20); GD.Vertex2ii(X_BUTTON_LEFT + 30, Y_BUTTON_LEFT + 20); GD.Vertex2ii(X_BUTTON_LEFT + 30, Y_BUTTON_LEFT - 20); GD.Begin(LINE_STRIP); GD.Vertex2ii(X_BUTTON_RIGHT - 30, Y_BUTTON_RIGHT - 20); GD.Vertex2ii(X_BUTTON_RIGHT, Y_BUTTON_RIGHT - 20); GD.Vertex2ii(X_BUTTON_RIGHT, Y_BUTTON_RIGHT - 40); GD.Vertex2ii(X_BUTTON_RIGHT + 40, Y_BUTTON_RIGHT); GD.Vertex2ii(X_BUTTON_RIGHT, Y_BUTTON_RIGHT + 40); GD.Vertex2ii(X_BUTTON_RIGHT, Y_BUTTON_RIGHT + 20); GD.Vertex2ii(X_BUTTON_RIGHT - 30, Y_BUTTON_RIGHT + 20); GD.Vertex2ii(X_BUTTON_RIGHT - 30, Y_BUTTON_RIGHT - 20); GD.Begin(LINE_STRIP); GD.Vertex2ii(X_BUTTON_ROT - 40, Y_BUTTON_ROT); GD.Vertex2ii(X_BUTTON_ROT, Y_BUTTON_ROT - 40); GD.Vertex2ii(X_BUTTON_ROT + 40, Y_BUTTON_ROT); GD.Vertex2ii(X_BUTTON_ROT, Y_BUTTON_ROT + 40); GD.Vertex2ii(X_BUTTON_ROT - 40, Y_BUTTON_ROT); GD.Begin(LINE_STRIP); if (Arbeiten) { GD.Vertex2ii(X_BUTTON_DROP - 40, Y_BUTTON_DROP - 10); GD.Vertex2ii(X_BUTTON_DROP + 40, Y_BUTTON_DROP - 10); GD.Vertex2ii(X_BUTTON_DROP, Y_BUTTON_DROP + 30); GD.Vertex2ii(X_BUTTON_DROP - 40, Y_BUTTON_DROP - 10); } else { GD.Vertex2ii(X_BUTTON_DROP - 10, Y_BUTTON_DROP - 40); GD.Vertex2ii(X_BUTTON_DROP + 30, Y_BUTTON_DROP); GD.Vertex2ii(X_BUTTON_DROP - 10, Y_BUTTON_DROP + 40); GD.Vertex2ii(X_BUTTON_DROP - 10, Y_BUTTON_DROP - 40); } } //================================================== void ClearMas(byte MasStx[MaxX][MaxY]) { for (byte j=0;j<MaxY;j++) for (byte i=0;i<MaxX;i++) MasStx[i][j]=0; } //================================================== void Sosed(int i,int j,int dx,int dy, byte mode) { int nx=i+dx; int ny=j+dy; if (nx>=0 && ny>=0 && nx<MaxX && ny<MaxY && MasSt[nx][ny]==MasSt[i][j]) { if (mode==1) MasTmp[i][j]++; else if (mode==2 && (MasTmp[nx][ny]>1 || MasTmp[i][j]>2 )) { MasTmp[nx][ny]=3; MasTmp[i][j]=3; } else { if (mode==3 && MasTmp[nx][ny]==3) { if (MasTmp[i][j]!=3) { MasTmp[i][j]=3; fl=true; } } } } } //================================================== void Sos(int i,int j, byte mode) { for (byte k=0;k<4;k++) Sosed(i,j,mmm[k][0],mmm[k][1],mode); } //================================================== // create next figure void GetNext(void) { x=3; y=0; for (byte i=0;i<3;i++) { if (!Demo) MasSt[x][i]=fignext[i]; fignext[i]=random(NumCol)+2; } if (!Demo) { Counter++; if (Counter==NextLevel) { Counter=0; Level++; if (Level>MaxLevel) Level=MaxLevel; } tempspeed=MasSpeed[Level-1]; } } //================================================== // find onecolor elements bool FindFull(void) { byte i,j,k; bool res; res=false; for (byte k=2;k<8;k++) { // by every color ClearMas(MasTmp); for (j=3;j<MaxY;j++) for (i=0;i<MaxX;i++) if (MasSt[i][j]==k) Sos(i,j,1); for (j=3;j<MaxY;j++) for (i=0;i<MaxX;i++) if (MasTmp[i][j]>1) Sos(i,j,2); do { fl=false; for (j=3;j<MaxY;j++) for (i=0;i<MaxX;i++) if (MasTmp[i][j]>0) Sos(i,j,3); } while (fl); for (j=3;j<MaxY;j++) for (i=0;i<MaxX;i++) if (MasTmp[i][j]==3) { MasSt[i][j]=1; TScore++; } } return(res); } //================================================ // move figure down bool fig_drop(int dy) { if (dy>0 && !FlZ) { if (y+dy+2>MaxY-1 || MasSt[x+dx][y+dy+2]>0) { if (y<3) { gameover(); } else { return true; } } else { if (y+dy+dy+2>MaxY-1 || MasSt[x+dx][y+dy+dy+2]>0) { GD.play(COWBELL); } for (byte i=0;i<3;i++) MasSt[x][y+2-i+dy]=MasSt[x][y+2-i]; MasSt[x][y]=0; y=y+dy; } } return(false); } //================================================ // move figure left/right (shift) bool fig_shift(int dx) { if (x+dx<0 || x+dx>MaxX-1) { GD.play(COWBELL); return(false); } if (dx!=0) { if (MasSt[x+dx][y+dy+2]==0) { if (x+dx+dx<0 || x+dx+dx>MaxX-1) GD.play(COWBELL); else GD.play(CHACK); return(true); } else { GD.play(COWBELL); return(false); } } return(false); } //================================================== // State-machine void ProcGame(void) { byte i,j,k; bool res = false; if (time_count < millis()) { if (Arbeiten) time_count = millis() + tempspeed; else time_count = millis() + 1000; switch (state_game) { // Demo case 0: Score=0; GetNext(); for (byte j=3;j<MaxY;j++) for (byte i=0;i<MaxX;i++) MasSt[i][j]=random(6)+2; state_game = 1; TScore=0; break; case 1: FindFull(); if (TScore>0) { FlZ=true; time_count = millis() + 500; } state_game = 2; break; case 2: for (j=0;j<MaxY;j++) { for (i=0;i<MaxX;i++) { while (MasSt[i][MaxY-1-j]==1) { for (k=0;k<MaxY-2-j;k++) MasSt[i][MaxY-1-kj] = MasSt[i][MaxY-2-kj]; res=true; } } } if(res) { if (TScore>7) Score=Score+TScore+(TScore-8)*2; else Score=Score+TScore; state_game = 1; } else { state_game = 0; } break; // Arbeiten case 3: if (fig_drop(1)) { tempspeed=MasSpeed[Level-1]; TScore=0; FindFull(); if (TScore>0) { GD.play(KICKDRUM); FlZ=true; state_game = 4; } else { FlZ=false; GetNext(); } } break; case 4: for (j=0;j<MaxY;j++) { for (i=0;i<MaxX;i++) { while (MasSt[i][MaxY-1-j]==1) { for (k=0;k<MaxY-2-j;k++) MasSt[i][MaxY-1-kj] = MasSt[i][MaxY-2-kj]; res=true; } } } if(res) { if (TScore>7) Score=Score+TScore+(TScore-8)*2; else Score=Score+TScore; state_game = 5; FlZ=true; GD.play(CLACK); } else { state_game = 3; FlZ=false; time_count = millis() + 100; } break; case 5: state_game = 3; FlZ=false; break; default: break; } } } //================================================ // start new game void NewGame() { Score = 0; FlZ = false; ClearMas(MasSt); Arbeiten = true; GetNext(); Counter = 0; Level = 1; tempspeed = MasSpeed[0]; Record = myrecord; state_game = 3; } //================================================ // draw "GAME OVER" void gameover() { if (Arbeiten==true) { GD.play(SWITCH); Arbeiten=false; if (Score>myrecord) { myrecord=Score; eeprom_write_byte((unsigned char *) 1, myrecord); } } } 
slotgag_assets.h
 #define LOAD_ASSETS() GD.safeload("slotgag.gd2"); #define BACKGROUND_HANDLE 0 #define BACKGROUND_WIDTH 256 #define BACKGROUND_HEIGHT 256 #define BACKGROUND_CELLS 1 #define GAMEDUINO_HANDLE 1 #define GAMEDUINO_WIDTH 395 #define GAMEDUINO_HEIGHT 113 #define GAMEDUINO_CELLS 1 #define ASSETS_END 220342UL static const shape_t BACKGROUND_SHAPE = {0, 256, 256, 0}; static const shape_t GAMEDUINO_SHAPE = {1, 395, 113, 0}; 

أعتقد أنني أكملت مهمة إنشاء رسم عملي الأول لهذا المنتدى. آمل أن يكون هناك شخص واحد على الأقل مهتم بقراءة قصتي. نقد وتعليقات موضع ترحيب. لا تتوقف الخطط ، وتتقدم ، وبالطبع ، تتبادل الخبرة والمعرفة.

لإثبات عمل اللوحة ، أنشر فيديو مع الصوت (تنبيه! صوت عالي!).
شكرا لكم على اهتمامكم.

Source: https://habr.com/ru/post/ar423577/


All Articles