为游戏平台创建逻辑游戏

你好

我想分享一下我与Gameduino 3游戏平台的相识故事,以及与该平台最简单的逻辑游戏(与Arduino Uno结合使用)的编程讨论。

什么是Gameduino 3? Gameduino 3是一块扩展板,可让您将Arduino变成现代的手持式(即尺寸)游戏机。 令我惊讶的是,我在集线器上的此板上找不到任何详细信息。 我想填补这一空白,特别是因为我认为董事会值得关注。

一点历史


名为Gameduino的项目的作者是詹姆斯·鲍曼(James Bowman),他在2011年创建了第一个版本的董事会。 然后将其定位为Arduino的VGA模块。 该板名为Gameduino,基于Xilinx Spartan-3A可编程逻辑FPGA系列。 板上安装了用于连接VGA监视器和立体声扬声器的连接器。 图片

Gameduino功能(1):
  • VGA视频输出,分辨率为400x300像素,512色;
  • 所有色域均在FPGA中以15位精度进行处理;

背景图形:

  • 符号背景图形区域512x512像素;
  • 256个字符,每个字符具有独立的4色调色板;
  • 通过像素平滑实现了包裹文本字符串的效果;

前台图形:

  • 每个精灵的分辨率为16x16像素;
  • 每个精灵可以具有256、16或4色调色板;
  • 支持四向旋转和水平旋转算法;
  • 每条栅格线96个精灵,每条栅格线1536个纹理元素;
  • 用于检测精灵可能相交的机制;

音频输出:

  • 12位双通道频率合成器;
  • 在10-8000 Hz频率范围内的64声音复音。

图像显示在标准VGA监视器的屏幕上,分辨率为400x300像素,
保持与任何分辨率为800x600像素的标准VGA显示器的兼容性。

2013年,发布了该板的第二个版本-Gameduino 2,与之前的版本不同,Gameduino 2已经有一个4.3英寸的电阻式触摸屏,分辨率为480x272,一个3轴加速度计,一个microSD存储卡插槽以及一个耳机的音频输出。

图片

板的“心脏”是图形控制器EVE(嵌入式视频引擎-俄语可以翻译为“嵌入式视频模块”)FT800,它具有强大的计算能力,同时结合了多种功能:图像形成及其输出到TFT显示屏的屏幕,触摸屏处理,声音产生。

FT800图形控制器的功能图
图片

微电路结构中包含以下功能块:图形控制器,音频控制器,电阻式触摸屏控制器。 FT800芯片旨在控制分辨率高达512 x 512像素的显示器。 FT800还支持LCD WQVGA(480 x 272)和QVGA(320 x 240)。 EVE(嵌入式视频引擎)FT800是用于创建图形用户界面的交钥匙解决方案。 微电路生成显示控制信号,具有内置的图形功能,用于显示点,线,位图图像,体积按钮,文本等。


基于FT800图形控制器的系统结构
图像的形成基于一组命令(显示列表),该命令由控制微控制器通过I2C或SPI接口传输到FT800(在Gameduino 2中,Arduino和FT800之间的通信是通过SPI接口进行的)。 FT800的功能极大地减轻了系统的主机控制器的负担。

图片

例如,要显示多个按钮,只需将一个命令传送到图形控制器(四个32位字),FT800就会在TFT显示屏的屏幕上独立形成这些按钮的图像。 FTDI图形控制器命令集包括50多种功能,可用于以各种效果在显示屏上显示各种图像。

可以在FTDI网站上的“应用笔记”中找到详细的控制器编程手册和使用各种设计环境的示例。

俄语中对功能,一般原理和工作示例进行了很好的描述。

Gameduino 2的功能
  • 屏幕分辨率为480x272像素(24位彩色);
  • 一组OpenGL风格的命令;
  • 多达2000个任何大小的精灵;
  • 256 KB视频内存;
  • 通过双线性滤波使子画面旋转和缩放平滑;
  • 硬件中的平滑圆形和线性图案-16倍平滑;
  • JPEG硬件解码;
  • 渐变,文本,转盘和按钮的内置渲染。

声音通过加强型耳机插孔输出。
该系统支持选择内置样本和工具。

控制器ROM已连接:

  • 高质量字体(6种尺寸);
  • MIDI音符演奏的8种乐器的样本;
  • 10种打击乐器声音的样本。

而且,当然,您可以将自己的字体和声音片段加载到256 KB RAM中。

并非必须使用Arduino平台:Gameduino 2板可通过SPI接口连接至任何微控制器或微控制器板。

2017年,发布了该板的第三个版本-Gameduino 3,其外观几乎与Gameduino 2相同。使用了新的FT810图形控制器而不是FT800,该控制器与FT800具有向后的软件兼容性(即Gameduino2的所有代码均可在Gameduino3上使用)。但同时它具有4倍的计算能力,例如更快的硬件JPEG解码,视频解码,高达1 MB的RAM等。

特色Gameduino 3:
  • 用于全屏30 fps视频的视频解码器;
  • 1 MB内部RAM;
  • 用于microSD卡和音频输出的连接器;
  • 4.3英寸480x272高对比度LCD面板,带电阻触摸屏;
  • 支持使用“平铺地图”编辑器创建的地图;
  • 从microSD下载PNG图片;
  • 加速JPEG解码;
  • 硬件肖像/风景切换;
  • 支持Arduino,ESP8266和Teensy 3.2;
  • 用于准备图形,音频,字体和视频的在线工具;


詹姆斯·鲍曼(James Bowman)为他的项目发布了一个库,其中包含许多开箱即用的示例。 我设法找到的当前库在这里编程指南 (英语),其中详细介绍了所有内容。 有关安装IDE等的许多有用信息

程式设计


不知何故,我在莫斯科大剧院的广阔空间中徘徊,遇到了一个有趣的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(); //         (   ). } 
结果,我们在这里得到了这么漂亮的文字:
图片

接下来,必须绘制几何形状,例如:线条。
要绘制线条,必须使用“开始”(LINES)或“开始”(LINE_STRIP)。
LINES连接每对顶点,而LINE_STRIP将所有顶点连接在一起。

我将从库中给出以下演示草图(并附上我的评论):

素描lines.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的矩形,并增加拐角半径。

素描长方体
 #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(); //         (   ) } 

结果:
图片

让我们继续画一个圆圈-将来的触摸按钮的基础。 让我们回到第一个带有文本的示例,并在loop()中添加几行。

用绘图彩色圆圈素描
 #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写入用于存储图像的micro SD卡,将slotgag_assets.h添加到项目文件夹,并将必要的8行代码添加到草图。

结果,游戏获得了以下形式:



当然,没有声音设计的游戏是什么? 仍然需要增加声音效果,特别是因为它们以高质量和多样化呈现。

Gameduino 2/3具有两个声音系统。 第一个是合成器,可以生成一组固定的声音和音符。 合成器对于将声音快速添加到项目很有用,但是由于声音的集合是固定的,因此不是很灵活。 第二是样品复制。 它以各种格式从主存储器中再现采样的声音。 该系统更加灵活,但是您需要准备样本并将其加载到RAM中。

我使用了固定的声音合成器。 合成器提供了几种简短的“打击乐”声音,主要用于用户界面。 要播放声音,必须使用声音标识符调用GD.play()。 可用声音的完整列表:

请点击
拨动开关
牛铃
缺口
hat
踢鼓
流行音乐
lack
ACK

总结


结果是这样的草图:

素描Columns.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/zh-CN423577/


All Articles