这是一台三轴游戏机,每轴上有25个符号。游戏是可配置的。
这个项目外星人主题游戏机是我使用两个ATmega328P-PU微控制器尽可能实现的。游戏机只用于娱乐和教育目的。我尽了最大的努力让游戏尽可能地模拟真实的游戏机。我在业余时间花了大约两个月的时间来完成这个项目。对我来说,构建过程中最困难的部分是理解所有涉及到游戏运行的数学问题。
如何运作
游戏有三个场景模式,每个模式都有相同的独特的25个符号(包含4个8x8矩阵的组件上的一个8x8矩阵没有被使用)。有五种不同的获胜方式。如果你得到了三艘宇宙飞船,你就中了头奖。如果你得到一到两艘宇宙飞船,你也会获得一些积分。如果你得到两个或三个符号匹配,也算你获得胜利。换句话说,获胜项目是相互排斥的,你不可能在一次旋转中以两种不同的方式获胜。这使得编程更加简单。但对我来说还有很多其他的挑战。
特性
游戏机有几个有趣的功能,通过使用两个导航按钮和一个选择按钮,通过20 x 4 I2C功能LCD显示屏访问。这些按钮使用了一种相当复杂的反弹算法,利用了微控制器的外部中断能力。这是主菜单。
因为菜单中有六行,你必须使用“向下导航”按钮向下滚动才能看到整个菜单。有一个专门用来“旋转”场景的按钮。除此之外,你还可以从主菜单中选择“开始”。
最令人兴奋的功能是游戏可以在“自动”模式下玩;例如,你从LCD屏幕上的设置菜单中选择自动模式选项,游戏就会反复开始,直到你再次选择该选项或游戏次数达到100万次。这是测试游戏的关键功能。你也可以在这里禁用声音。
通过LCD上的菜单,我们还可以查看从模拟中生成的所有指标。如果使用USB线将微控制器通过RX和TX引脚连接到显示器上,这些指标也会输出,并可能在串行监视器中查看。显示的指标列表包括您的剩余游玩次数,您中头奖的次数,以及您通过任何其他方式赢得游玩体验的次数。这让我能够基于各种门槛进行模拟,并有助于建立和证明前置门槛表。门槛表本身是不可配置的,所以一旦设置好,就应该保持不变。我认为,通过使用波动指数来驱动门槛表,有可能使波动指数可配置,但这会使项目更加复杂,所以我们暂时先搁置这个改动。
Reset选项允许您将所有指标(EEprom写入除外)重置为零。该芯片将工作约10万写入EEprom。由于芯片上有512k的EEprom可用,而我们只使用其中的一小部分,当我们接近10万个写入时,实际上移动EEprom中指标的位置是可能的。我还没有实现这个功能,但它将是延长芯片寿命的一种手段。
最后,持仓量,或每一次游玩的百分比(随着时间的推移),是可配置的。记住,在执行Reset操作后,需要再次设置保持。
玩家的剩余游玩次数总是以8位数的7段显示。
数学
为了确保游戏是真实的,我们做了很多工作。我们计算了概率,并设计了门槛表,以便游戏具有可接受的波动指数(VI)。该指数衡量机器行为的可预测性。一个具有更高VI值的机器更有可能为玩家带来更多的胜利。这比VI值较低的机器更难预测。
请注意,胜率(最右边)和门槛(最左边)是显著不同的。如果这款游戏的编程是让门槛表与赔率相匹配或紧密跟随,那么它的VI就会高得令人无法接受。持有是按所付出的百分比计算的。如前所述,您可以通过LCD菜单设置保持。要知道,将hold设置为允许的最大限度并不一定会使机器产生的利润最大化,因为更高的hold可能会使玩家的体验不太好。
该电子表格(包含代码)包含三个表格,用于证明游戏的正常运行(第一个表格出现在上面)。构建电子表格的第一步是准确计算每种获胜类型的概率(计算概率列)。
三个宇宙飞船
三艘宇宙飞船出现的概率是可能组合的总数的倒数。获胜的组合数,1,除以所有可能的组合数,15625。每个卷轴上有25个唯一的符号,所以概率是1 / (25 x 25 x 25),或0.000064。这使得概率,1/概率- 1,等于1到15624。我从这里的概率学到了如何计算概率。
三符号配对(宇宙飞船除外)
除了宇宙飞船之外,三个符号匹配的概率是24(每个模块上唯一符号的数量减去宇宙飞船)除以可能组合的数量。24是分子,因为三个符号有24种组合。24/15625 = 0.001536。这使得赔率约为1比650.04。
两个宇宙飞船
两艘飞船匹配的组合是24 × 3。那是因为有三种方法可以把一艘飞船配对成两组。设X =宇宙飞船,Y =任何其他符号XXY, XYX和YXX。y有24个可能的值,所以24 X 3 / 15625 = 0.004608。赔率是1比216.01。
一艘宇宙飞船
对于每一个模块,有24 x 24的组合可能出现一艘飞船。
宇宙飞船可以出现在任何模块上,所以你需要将一个模块上可用的组合数量乘以3个模块。所以概率是24 × 24 × 3 / 15625 = 0.110592。
两个符号匹配
对于任何给定的两个符号,除了宇宙飞船,有23(25减去1艘宇宙飞船减去1个符号将使它成为3个符号匹配)x3卷x 24个符号不是宇宙飞船。概率是(23 X 3 X 24)/15625 = 0.105984。
现在我有了每种获胜的概率,我可以使用电子表格设计支付表,使波动指数可以接受(< ~20)。我在第一个表格的House Income列中输入了数值,使用了试错的过程,直到VI低于20,J10单元格中的Total接近于零。使用这些值,我在SlotMachine中设置three_spaceship_payments, three_symbol_payments, two_spaceship_payments, one_spaceship_payments和two_symbol_payments。然后,首先使用0%的控制,我运行了5个1000,001个玩法的模拟,并将参数菜单中的值输入到实际结果表(第三个表)的相应行和列中。
我观察到实际概率与计算概率密切相关,Pct Diff Prob列是合理的。我还将“房屋支付”一行的值与“了解潜在收入”表(第二表)的100万行“收入高”和“收入低”列的值的范围进行了匹配,并观察到“实际结果”表的值在“收入高”和“收入低”列指定的范围内。理解潜在收入表用90%的置信区间定义了给定持有值的预期收入范围。在下面的例子中,hold被设置为0,所以获胜的可能性与失败的可能性相匹配。如果你玩游戏100万次,那么90%的可能性收益将介于16,432到- 16,432之间。
在使用电子表格和程序并运行数百万次模拟之后,我能够找出程序中的缺陷,解决电子表格中的缺陷,并为保持VI < 20的支出表定义值。最后,我将持股比例改为15%,并运行了另一组5个模拟,以验证游戏的收益是否与预期相符(游戏邦注:如果将其部署在现实世界中)。
如果你想真正理解设置支付值背后的所有数学,我建议你检查电子表格中的公式。
代码
如果您不熟悉ATmega386上寄存器的操作,并且想了解更多关于如何在不依赖Arduino库的情况下为AVR微控制器编写代码的知识,我建议您阅读Elliott William的优秀著作《Make: AVR Programming》。在这些程序中,我在一些地方使用Arduino函数,在另一些地方直接操作寄存器。
您可能注意到的第一件事是,该程序大量使用了全局变量。在Stack Overflow上有一个关于这个主题的很好的讨论。我不打算在这里提倡或捍卫大量使用全局变量,但我鼓励您理解关于这个主题的所有观点,并认识到在只有一个程序员和有限资源的嵌入式应用程序项目中使用它们是有充分理由的。
我确实使用了一些库,没有它们这个项目对我来说是不可能的。定时器自由音库用于驱动各种频率通过被动压电扬声器。在SlotMachine.h中,你会注意到音符有大量的定义。你可以用它来组合任何你想要的旋律。当SlotMachine的微控制器启动和设置功能运行时,我只使用了一小部分游戏来玩《第三类接触》的部分主题。
一开始我选择了定时器空闲库,因为我起初认为需要定时器做一些事情,但我最终根本没有使用定时器。如果您需要,可以使用它。LED控制库被用在两个SlotMachine。和slotCreditDisplaySlave.ino。在前者,它被用来控制三个8 x 8 LED矩阵,作为游戏机模块。在slotCreditDisplaySlave。在库方便访问8位7段显示,显示玩家的剩余次数。
我试图避免使用另一个AVR芯片(ATmega328),但我无法找到一种方法来控制8 x 8矩阵和8位7段显示从单个微控制器。因此,最后我不得不创建一个I2C从机来实现这个目的。当然,您也可以使用较便宜的AVR来完成显示剩余次数的工作,但为了简化本文,我选择使用另一个ATmega328P-PU芯片。好的一面是,当你赢得一个大的头奖时,积分会继续在积分显示上计数,而你可以继续前进并再次旋转。需要LiquidCrystal/LCD和LiquidCrystal I2C库来方便访问20行x 4行LCD显示。如前所述,你可以替换一个20 x 2的LCD,如果这是你手上的所有,只是通过改变LCD_SCREEN_HEIGHT的定义从4到2。请确保您为这个项目获得的液晶显示器是I2C能力。如果不是,您将需要为LCD1602适配器板获取I2C SPI串行接口板端口模块,部件编号PCF8574,如下所示,并将其焊接到您的LCD1602显示器。
游戏可以同时处于许多不同的状态,而machineState变量会追踪这些状态。例如,它可以同时处于“旋转”和“自动模式”。我并没有在程序中大量使用这个概念;不管怎么说,没有我在其他节目中做的多。但是有一些基于状态的条件分支。还有事件的概念,事件在ProcessEvents函数中被分派和处理。如果有一个事件队列可能会更好,但我没有走到那一步。
在SlotMachine.ino的评论区有一个已知缺陷列表和“要做的事情”。有时当你“旋转”模块(通过按旋转按钮或从LCD菜单中选择“播放”选项)时,一个甚至两个模块不动。这是因为幕后的随机数生成器选择了已经为模块显示的符号。这可以被修正以使游戏看起来更真实,但这并不是一个真正的缺陷。实际上还是会从左向右旋转。这是有意为之的,以保持简单。通过对每次旋转产生的三个随机数字按升序排序,可以让模块从左向右完成旋转,但我没有这么做。
至于“待办事项”,我想在某种程度上添加棕色保护和看门狗保护,只是通过练习和学习如何做。注意,分配给全局变量的80%的空间已经被占用了。这是ATmega386和Arduino程序开始变得不稳定的点。我们的这个项目就在这一点上。我已经做了一些预算,以保持工作,我不建议添加更多的全局变量到程序中。例如,这将使向菜单的Settings部分添加更多功能变得困难,因为菜单消耗了大量全局变量空间。我确实试图通过将菜单移动到程序内存中来解决全局变量的问题,但我不能这样做来减少全局变量使用的空间,我认为这是因为编译器需要预先分配菜单的所有空间。我们还可以做更多的工作来增加游戏的趣味性:我可以更多地使用RGB LED和压电蜂鸣器,更多地庆祝胜利,也可以在失败时发出一些鼓励的声音,但我将把这些留给想要玩它的人。
我必须为游戏设计所有的符号。其中一些会让你想起经典街机游戏“太空入侵者”,我可能是从某个地方借来的。其余的都是我手工设计的,有些看起来不太专业。如果你想要调整符号,你可以在SlotMachine.h中完成,并随心所欲地使用它们。它不会影响程序逻辑。对于符号,我表示的数字以2为基数/二进制,以便您可以用您的文本编辑器设计它们。
构建
我使用一个FTDI USB串行板编程两个ATmega328P-PU微控制器到位。这些连接在Fritzing示意图中没有描述。我试图让微控制器在编程开始时通过FTDI断接板自动重置。记住将100 nF电容串联在ATmega328复位引脚(位置1/PC6/复位引脚)和FTDI输出板上的RTS之间,这样当你想对芯片编程时就不必按住复位按钮了。如果你只打算用提供的代码对芯片进行一次编程,那么从Arduino Uno中进行编程可能是最快最简单的方法。
这两个微控制器都设置在面包板上的“Arduino”芯片(ATmega328P-PU)。如果你计划通过焊接组件来最终构建这个项目,或者如果你只是想复制我在这里做的,当你制作面包板时,你会想要理解如何在面包板上设置Arduino。要做到这一点,你需要一个AVR,如AVRISP mk II或USBTiny ISP。你也可以用你的Arduino,如果你有一个,烧掉引导加载程序。
所需工具
- 烙铁
- 有个人“搭把手”
连接
- 引脚1 - RTS上的FTDI USB到串行输出板,复位按钮
- FTDI USB到串行输出板上的引脚2 - TXD
- FTDI USB到串行输出板上的引脚3 - RXD
- 引脚4 - 1K欧姆电阻-瞬间“旋转”按钮
- 引脚5 - 330欧姆电阻- RGB LED蓝色引脚
- 引脚6 -未使用,考虑接地
- 引脚7 VCC -面包板功率导轨,0.1uF电容
- 引脚8 GND -面包板接地轨,0.1uF电容
- 引脚9 XTAL1 - 16MHz晶体,22pF电容到面包板接地轨
- 引脚10 XTAL2 - 16MHz晶体,22pF电容到面包板接地轨
- 引脚11 -未使用,考虑接地
- 引脚12 -未使用,考虑接地
- 引脚13 -未使用,考虑接地
- 8x8矩阵上的引脚14 - DIN
- 引脚15 - 330欧姆电阻- RGB LED红色引脚
- 引脚16 - 330欧姆电阻- RGB LED绿色引脚
- 销17 -压电蜂鸣器正负压电蜂鸣器-面包板接地轨
- 引脚18 - CS在8x8矩阵上
- 引脚19 - CLK在8x8矩阵上
- 引脚20 AVCC -面包板功率导轨,0.1uF电容
- 引脚21 AREF -面包板动力导轨
- 引脚22 GND -面包板接地轨
- 引脚23 -让这个引脚浮动,它被用来种子随机数生成器
- 引脚24 - 1K欧姆电阻-瞬时“向上导航”按钮
- 引脚25 - 1K欧姆电阻-瞬间“向下导航”按钮
- 引脚26 - 1K欧姆电阻-瞬时“选择”按钮
- pin27 SDA -显示屏I2C ATmega328P-PU slave的pin27 SDA
- pin28 SCL -显示屏I2C ATmega328P-PU slave上的pin28 SCL
显示连接
- 引脚1 - RTS上的FTDI USB到串行输出板,复位按钮
- FTDI USB到串行输出板上的引脚2 - TXD
- FTDI USB到串行输出板上的引脚3 - RXD
- 引脚4 -未使用,考虑接地
- 引脚5 -未使用,考虑接地
- 引脚6 -未使用,考虑接地
- 引脚7 VCC -面包板功率导轨,0.1uF电容
- 引脚8 GND -面包板接地轨,0.1uF电容
- 引脚9 XTAL1 - 16MHz晶体,22pF电容到面包板接地轨
- 引脚10 XTAL2 - 16MHz晶体,22pF电容到面包板接地轨
- 引脚11 -未使用,考虑接地
- 引脚12 -未使用,考虑接地
- 引脚13 -未使用,考虑接地
- 引脚14 -未使用,考虑接地
- 销15 -压电蜂鸣器正负压电蜂鸣器-面包板接地轨
- 引脚16 - CS在七段显示器上
- 引脚17 - CLK上的七段显示
- 引脚18 - DIN在七段显示器上
- 引脚19 -未使用,考虑接地
- 引脚20 AVCC -面包板功率导轨,0.1uF电容
- 引脚21 AREF -面包板动力导轨
- 引脚22 GND -面包板接地轨
- 引脚23 -未使用,考虑接地
- 引脚24 -未使用,考虑接地
- 引脚25 -未使用,考虑接地
- 引脚26 -未使用,考虑接地
- 引脚27 SDA -游戏机I2C ATmega328P-PU的引脚27 SDA
- Pin 28 SCL -插槽机ei2c ATmega328P-PU上的Pin 28 SCL
总结
这个项目做起来很有趣。最具挑战性的部分是理解创建有效支付表所需的所有数学运算。我希望您也能从这个项目中获得乐趣。
SlotMachine.ino:
/*SlotMachine.ino
Version: 1.0
Date: 2018/07/01 - 2018/08/29
Device: ATMega328P-PU @ 16mHz
Language: C
Purpose
=======
A slot machine for entertainment and educational purposes only,
with the following features:
- AtMega328P microcontroller running at 16mHz
- Custom I2C seven segment display for displaying credit balance,
also built with an ATMega328P running at 16mHz. That program is
supplied in a seperate file.
- Three 8x8 LED matricies for displaying symbols driven by MAX7219.
- I2C LCD display 20x4, to show menus
- various buzzers, buttons and an RGB LED.
- the ability to update various settings via the LCD menu to
influence the machine's behavior.
- the ability to change the amount of the wager.
Known Defects
=============
- Sometimes one or two of the reels won't spin, not really a defect.
- crash as soon as payed out exceeds 1,000,000.
TODO
====
- add brown out detection
- add watch dog protection (wdt_enable(value), wdt_reset(), WDTO_1S, WDTO_250MS)
Warnings
========
- Beware of turning on too much debugging, it's easy to use all
of the data memory, and in general this makes the microcontroller
unstable.
- Gambling is a tax on people who are bad at math. This is for
entertainment only. It was the intent of the author to program this game
to return ~%hold of every wager to the house, similar to many slot machines.
- Why not control the LED that displays the credits with the LedControl
library? I tried that and couldn't get more than one LedControl object to
work at a time. So I had to create an I2C slave instead and use another
AVR.
Suggestions
===========
- Best viewed in an editor w/ 160 columns, most comments are at column 80
- Please submit defects you find so I can improve the quality of the program
and learn more about embedded programming.
Author
======
- Copyright 2018, Daniel Murphy <dan-murphy@comcast.net>
- Contributors: Source code has been pulled from all over the internet,
it would be impossible for me to cite all contributors.
Special thanks to Elliott Williams for his essential book
"Make: AVR Programming", which is highly recommended. Thanks also
to Cory Potter, who gave me the idea to do this.
License
=======
Daniel J. Murphy hereby disclaims all copyright interest in this
program written by Daniel J. Murphy.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Libraries
=========
- https://github.com/wayoda/LedControl
- https://bitbucket.org/teckel12/arduino-timer-free-tone/wiki/Home
- https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
- https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home
The Program
===========
- Includes */
#include <avr/io.h>
#include <avr/eeprom.h>
#include <stdlib.h> // for the abs function
#include "LedControl.h" // https://github.com/wayoda/LedControl
#include "SlotMachine.h"
#include <TimerFreeTone.h> // https://bitbucket.org/teckel12/arduino-timer-free-tone/wiki/Home
#include <Wire.h>
#include <LCD.h> // https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home
#include <LiquidCrystal_I2C.h> // https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
//- Payout Table
/* Probabilities based on a 1 credit wager
Three spaceships: 1 / (25 * 25 * 25) = 0.000064
Any three symbols: 24 / 15625 = 0.001536
Two spaceships: (24 * 3) / 15625 = 0.004608
One spaceship: (24 * 24 * 3)/ 15625 = 0.110592
Two symbols match: (23 * 3 * 24) / 15625 = 0.105984
House win, 1 minus sum of all probabilities = 0.777216
_
Use the spreadsheet to work out the payout table remembering to keep the
volatility resonable i.e. < 20.
P R O O F
Actual Actual
Winning Combination Payout Probablility Count Probability
=================== ====== ============ ======== ===========*/
#define THREE_SPACESHIP_PAYOUT 600 // 0.000064 0.00006860 see the excel spreadsheet
#define THREE_SYMBOL_PAYOUT 122 // 0.001536 0.00151760 that accompanies this program.
#define TWO_SPACESHIP_PAYOUT 50 // 0.004608 0.00468740
#define ONE_SPACESHIP_PAYOUT 3 // 0.110592 0.11064389
#define TWO_SYMBOL_PAYOUT 2 // 0.105984 0.10575249
//
// With these payouts the Volatility Index is 16.43
//
//- Macros
#define ClearBit(x,y) x &= ~y
#define SetBit(x,y) x |= y
#define ClearBitNo(x,y) x &= ~_BV(y)
#define SetState(x) SetBit(machineState, x)
//- Defines
#define DEBUG 1 // turns on (1) and off (0) output from debug* functions
#define BAUD_RATE 38400 // Baud rate for the Serial monitor
#define NUMFRAMES 25 // Number of symbols in each "reel" or "slot". e.g three reels: |7|7|7|
#define LINESPERFRAME 8 // each line corresponds to one row on the 8x8 dot matrix LED
#define FRAME_DELAY 100 // milliseconds, controls the speed of the spinning reels
#define NUMREELS 3 // the hardware (8x8 matricies) accomodates 4 reels, we're only using three now
#define DEBOUNCE_TIME 1000 // microseconds (changed from 500 to 1000 to cut down on double press problem)
#define BUTTON_DDR DDRD // this accomodates the button that starts the reels spinning
#define BUTTON_PORT PORTD
#define BUTTON_PIN PIND
#define PCMSK_BUTTON PCMSK2
#define PCIE_BUTTON PCIE2
#define BUTTON_SPIN_PIN DDD2 // the actual spin button
#define BUTTON_SPIN_INT PCINT18
#define BUTTON_SPIN_PORT PORTD2
#define NAV_DDR DDRC // this is for the buttons that control menu navigation on the 20x4 LCD
#define NAV_PORT PORTC
#define NAV_PIN PINC
#define PCMSK_NAV PCMSK1
#define PCIE_NAV PCIE1
#define NAV_UP_PIN DDC1 // Navigate up button
#define NAV_UP_INT PCINT9
#define NAV_UP_PORT PORTC1
#define NAV_DOWN_PIN DDC2 // Navigate down button
#define NAV_DOWN_INT PCINT10
#define NAV_DOWN_PORT PORTC2
#define SELECT_PIN DDC3 // Select current menu item button
#define SELECT_INT PCINT11
#define SELECT_PORT PORTC3
#define BUZZER_DDR DDRB // This is for the slot machines piezo buzzer
#define BUZZER_PORT PORTB
#define BUZZER_PIN DDB3
#define TONE_PIN 11 // Pin you have speaker/piezo connected to (TODO: be sure to include a 100ohm resistor).
#define EVENT_NONE 0 // These are all of the various events that can occur in the machine
#define EVENT_SPIN 1
#define EVENT_SHOW_MENU 2
#define EVENT_SELECT 3
#define EVENT_NAV_UP 4
#define EVENT_NAV_DOWN 5
#define EVENT_BACK 6
#define EVENT_PLAY 10
#define EVENT_BET 11
#define EVENT_SETTINGS 12
#define EVENT_VIEW_METRICS 13
#define EVENT_RESET 14
#define EVENT_HOLD 15
#define STATE_IDLE B00000001 // These are the various states the machine can be in, not all are
#define STATE_SPINNING B00000010 // mutually exclusive.
#define STATE_AUTO B00000100 // This state is for automatically running the program to gather metrics.
#define STATE_SHOW_MENU B00001000 // State we're in when showing the menu. Note you can spin and show menu
// concurrently.
#define MINIMUM_WAGER 5 // TODO: consider this something that can be changed via settings
#define WAGER_INCREMENT 5 // TODO: consider this something that can be changed via settings
#define ONE_SECOND 1000 // # milliseconds in one second. Used to control how long the siren sounds.
#define SHIP_LOC 144 // Location of various symbols in the array of symbols maintained in SlotMachine.h
#define ALIEN_1_LOC 152 // needed for animation
#define ALIEN_2_LOC 160
#define EEPROM_FREQ 10000 // Write to EEPROM every Nth play
#define AUTO_MODE_MAX 1000000 // stop after this many plays in auto mode
#define RED 1 // TODO: should we use an enum here? Must be a better way...
#define GREEN 2
#define BLUE 3
#define PURPLE 4
#define WHITE 5
#define OFF 6
#define MAX_NOTE 4978 // Maximum high tone in hertz. Used for siren.
#define MIN_NOTE 31 // Minimum low tone in hertz. Used for siren.
#define STARTING_CREDIT_BALANCE 500 // Number of credits you have at "factory reset".
#define DEFAULT_HOLD 0 // default hold is zero, over time the machine pays out whatever is wagered
#define NUM_LED_DATAIN 7
#define NUM_LED_CLK 6
#define NUM_LED_LOAD 5
#define NUM_CHIP_COUNT 1
#define MATRIX_LED_DATAIN 8
#define MATRIX_LED_CLK 13
#define MATRIX_LED_LOAD 12
#define MATRIX_CHIP_COUNT 4
#define LOW_INTENSITY 1 // dim
#define HIGH_INTENSITY 10 // bright
#define SIREN_FLASHES 1
#define LCD_SCREEN_WIDTH 20
#define LCD_SCREEN_HEIGHT 4
#define CREDITS_I2C_SLAVE_ADDR 0x10 // I2C addresses
#define LCD_I2C_ADDR 0x3F // LCD display w/ 4 lines
#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
#define MENU_SIZE 17
#define MAIN_MENU_NUMBER 0
#define MAIN_MENU_ELEMENTS 6
char *mainMenu[] = { "Play",
"Bet",
"Settings",
"Metrics",
"Reset",
"Hold",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" " };
#define BET_MENU_NUMBER 1
#define BET_MENU_ELEMENTS 3
char *betMenu[] = { "+5 credits: ", // TODO: make this dynamic based on WAGER_INCREMENT
"-5 credits: ",
"Back",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" " };
#define SETTINGS_MENU_NUMBER 2
#define SETTINGS_MENU_ELEMENTS 3
#define SETTINGS_BACK_ITEM 2
char *settingsMenu[] = { "Auto/Manual", // TODO: fill out this menu with more cool options
"Toggle Sound ",
"Back ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" " };
#define METRICS_MENU_NUMBER 3
#define METRICS_MENU_ELEMENTS 15
char *metricsMenu[METRICS_MENU_ELEMENTS];
#define HOLD_MENU_NUMBER 4
#define HOLD_MENU_ELEMENTS 3
char *holdMenu[] = { "+1 percent: ",
"-1 percent: ",
"Back",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" " };
int selectPos = 0;
int menuNumber = MAIN_MENU_NUMBER;
int elements = MAIN_MENU_ELEMENTS;
char *currentMenu[MENU_SIZE];
LiquidCrystal_I2C lcd( LCD_I2C_ADDR, // Create the LCD display object for the 20x4 display
En_pin,
Rw_pin,
Rs_pin,
D4_pin,
D5_pin,
D6_pin,
D7_pin );
LedControl lc=LedControl( MATRIX_LED_DATAIN, // Create the LED display object for the 8x8 matrix
MATRIX_LED_CLK,
MATRIX_LED_LOAD,
MATRIX_CHIP_COUNT ); // Pins: DIN,CLK,CS, # of chips connected
volatile int reelArrayPos[NUMREELS];
volatile byte machineState;
volatile byte event = EVENT_NONE;
volatile byte color = RED;
#define ADC_READ_PIN 0 // we read the voltage from this floating pin to seed the random number generator
#define RED_PIN 9 // Pin locations for the RGB LED
#define GREEN_PIN 10
#define BLUE_PIN 3
#define NUM_NOTES 5 // The number of notes in the melody
// EEProm address locations
#define PAYEDOUT_ADDR 0x00 // 4 bytes
#define WAGERED_ADDR 0x04 // 4 bytes
#define PLAYED_ADDR 0x08 // 4 bytes
#define TWO_MATCH_ADDR 0x12 // 4 bytes
#define THREE_MATCH_ADDR 0x16 // 2 bytes
#define SHIP_ONE_MATCH_ADDR 0x18 // 4 bytes
#define SHIP_TWO_MATCH_ADDR 0x22 // 2 bytes
#define SHIP_THREE_MATCH_ADDR 0x24 // 2 bytes
#define EEPROM_WRITES_ADDR 0x34 // 4 bytes
#define RESET_FLAG_ADDR 0x38 // 4 bytes
#define CREDIT_BALANCE_ADDR 0x42 // 4 bytes
#define HOLD_ADDR 0x46 // 2 bytes
boolean sound = true;
byte reelMatches = 0; // per play variables
byte shipMatches = 0;
unsigned long wagered = 0; // amount wagered on a single spin
double owedExcess = 0; // change, need to track this so hold is accurate
unsigned long twoMatchCount = 0; // 1 if two symbols match
unsigned int threeMatchCount = 0; // 1 if three symbols match
unsigned long shipOneMatchCount = 0; // 1 if there's one ship present
unsigned int shipTwoMatchCount = 0; // 1 if there are two ships present
unsigned int shipThreeMatchCount = 0; // 1 if there are three ships present (Jackpot!)
unsigned long totalCalcs = 0; // total plays only relavent in auto mode
signed long startingCreditBalance; // the credit balance before spinning
int increment = WAGER_INCREMENT;
#define DISP_CREDIT_INCREMENT 1 // on the seven segment display, increment/decrement the balance by this value until the final value is reached.
// lifetime variables (stored in EEprom) Reset sets most back to zero
unsigned long storedPayedOut; // sum of all payouts
unsigned long storedWagered; // sum of all wagers (profit = payouts - wagers)
unsigned long storedPlays; // the number of spins
unsigned long storedTwoMatchCount; // number of times two symbols have matched
unsigned int storedThreeMatchCount; // number of times three symbols have matched
unsigned long storedShipOneMatchCount; // number of times one ship has appeared
unsigned int storedShipTwoMatchCount; // number of time two ships have appeared
unsigned int storedShipThreeMatchCount; // number of times three ships have appeared (Jackpot!)
unsigned long storedEEpromWrites; // number of times we've written to EEprom. 100,000 is the approximate maximum
signed long storedCreditBalance; // the credit balance.
int storedHold = DEFAULT_HOLD; // the house advantage, in percent, usually between 1 and 15, 2 bytes
volatile byte portdhistory = 0b00000100; // default is high because of the pull-up, correct setting
volatile byte portchistory = 0b00001110; // default is high because of the pull-up, correct setting
//- Debugging Routines // These routines are helpful for debugging, I will leave them in for your use.
// For sending output to the serial monitor. Set the baud rate in setup.
void debug(String text) {
if (DEBUG) {
Serial.println(text);
}
}
void debugNoLF(String text) {
if (DEBUG) {
Serial.print(text);
}
}
void debugInt(signed int anInt) {
if (DEBUG) {
char myInt[10];
itoa(anInt,myInt,10);
debug(myInt);
}
}
void debugLong(signed long aLong) {
if (DEBUG) {
char myLong[10];
ltoa(aLong,myLong,10);
debug(myLong);
}
}
void debugDouble(double aDouble) {
if (DEBUG) {
char *myDouble = ftoa(aDouble);
debug(myDouble);
}
}
void debugMetric(const char myString[], signed int anInt) {
if (DEBUG) {
debugNoLF(myString);debugNoLF(F(": "));
debugInt(anInt);
Serial.print(F("rn"));
}
}
void debugMetricLong(const char myString[], signed long aLong) {
if (DEBUG) {
debugNoLF(myString);debugNoLF(F(": "));
debugLong(aLong);
Serial.print(F("rn"));
}
}
void debugStoredMetrics() {
for (int i = 0; i < 11; i++) {
debug(metricsMenu[i]);
}
}
void debugMetricDouble(const char myString[], double aDouble) {
if (DEBUG) {
debugNoLF(myString);debugNoLF(F(": "));
debugDouble(aDouble);
Serial.print(F("rn"));
}
}
// quick and dirty ftoa for legacy code
char *ftoa(double f) // from https://www.microchip.com/forums/m1020134.aspx
{
static char buf[17];
char * cp = buf;
unsigned long l, rem;
if(f < 0) {
*cp++ = '-';
f = -f;
}
l = (unsigned long)f;
f -= (double)l;
rem = (unsigned long)(f * 1e6);
sprintf(cp, "%lu.%10.10lu", l, rem);
return buf;
}
//- All Other Functions
void beep() { // Beep and flash LED green unless STATE_AUTO
setGreen();
if (sound) {
BUZZER_PORT |= (1 << BUZZER_PIN); // turn on buzzer
if (!(STATE_AUTO == (machineState & STATE_AUTO))) {
delay(100);
}
BUZZER_PORT &= ~(1 << BUZZER_PIN); // turn off the buzzer
}
setOff();
}
void beepAuto() { // Beep even during STATE_AUTO, flash LED blue
setBlue();
if (sound) {
BUZZER_PORT |= (1 << BUZZER_PIN); // turn on buzzer
delay(100);
BUZZER_PORT &= ~(1 << BUZZER_PIN); // turn off the buzzer
}
setOff();
}
void beepPurple() { // Beep and flash LED purple unless STATE_AUTO
if (!(STATE_AUTO == (machineState & STATE_AUTO))) {
setPurple();
if (sound) {
BUZZER_PORT |= (1 << BUZZER_PIN); // turn on buzzer
delay(100);
BUZZER_PORT &= ~(1 << BUZZER_PIN); // turn off the buzzer
}
setOff();
}
}
void InitInturrupts() // Initialize interrupts for buttons and switches
{
PCICR |= (1 << PCIE_BUTTON); // Pin Change Interrupt Control Register, set PCIE2 to enable PCMSK2 scan
PCICR |= (1 << PCIE_NAV); // Pin Change Interrupt Control Register, set PCIE1 to enable PCMSK1 scan
// Pin Change Mask Register 2 for port D
PCMSK_BUTTON|=(1<<BUTTON_SPIN_INT); // Set PCINT2 to trigger an interrupt on state change
PCMSK_NAV|=((1<<NAV_UP_INT)|(1<<NAV_DOWN_INT)|(1<<SELECT_INT)); // Set PCINT1 to trigger an interrupt on state change
sei(); // enable interrupts
}
ISR (PCINT1_vect)
{
byte changeddbits;
changeddbits = NAV_PIN ^ portchistory;
ClearBitNo(changeddbits,PORTC0); // not a switch, ignore it
ClearBitNo(changeddbits,PORTC4); // not a switch, ignore it
ClearBitNo(changeddbits,PORTC5); // not a switch, ignore it
ClearBitNo(changeddbits,PORTC6); // not a switch, ignore it
portchistory = NAV_PIN;
ClearBitNo(portchistory,PORTC0); // not a switch, ignore it
ClearBitNo(portchistory,PORTC4); // not a switch, ignore it
ClearBitNo(portchistory,PORTC5); // not a switch, ignore it
ClearBitNo(portchistory,PORTC6); // not a switch, ignore it
if(changeddbits & (1 << NAV_UP_PIN))
{
if( (portchistory & (1 << NAV_UP_PIN)) == (1 << NAV_UP_PIN) ) // TODO: test using this instead of 16
{
_delay_us(DEBOUNCE_TIME);
if (bit_is_set(NAV_PIN, NAV_UP_PIN)) { // LOW to HIGH pin change (button released)
// ADD CODE HERE
int x = 0;
}
}
else
{
_delay_us(DEBOUNCE_TIME);
if (bit_is_clear(NAV_PIN, NAV_UP_PIN)) { // button pressed
// HIGH to LOW pin change (spin switch button pressed)
event = EVENT_NAV_UP;
}
}
}
if(changeddbits & (1 << NAV_DOWN_PIN))
{
if( (portchistory & (1 << NAV_DOWN_PIN)) == (1 << NAV_DOWN_PIN) ) // TODO: test using this instead of 16
{
_delay_us(DEBOUNCE_TIME);
if (bit_is_set(NAV_PIN, NAV_DOWN_PIN)) { // LOW to HIGH pin change (button released)
// ADD CODE HERE
int x = 0;
}
}
else
{
_delay_us(DEBOUNCE_TIME);
if (bit_is_clear(NAV_PIN, NAV_DOWN_PIN)) { // button pressed
// HIGH to LOW pin change (spin switch button pressed)
event = EVENT_NAV_DOWN;
}
}
}
if(changeddbits & (1 << SELECT_PIN))
{
if( (portchistory & (1 << SELECT_PIN)) == (1 << SELECT_PIN) ) // TODO: test using this instead of 16
{
_delay_us(DEBOUNCE_TIME);
if (bit_is_set(NAV_PIN, SELECT_PIN)) { // LOW to HIGH pin change (button released)
int x = 0;
}
}
else
{
_delay_us(DEBOUNCE_TIME);
if (bit_is_clear(NAV_PIN, SELECT_PIN)) { // button pressed
// HIGH to LOW pin change (spin switch button pressed)
event = EVENT_SELECT;
}
}
}
}
ISR (PCINT2_vect)
{
byte changeddbits; // Will have bit corresponding to button pressed flipped on
changeddbits = BUTTON_PIN ^ portdhistory; // flip the bit corresponding to the button that was pressed
ClearBitNo(changeddbits,PORTD0); // not a switch, ignore it
ClearBitNo(changeddbits,PORTD1); // not a switch, ignore it
ClearBitNo(changeddbits,PORTD3); // not a switch, ignore it
ClearBitNo(changeddbits,PORTD4); // not a switch, ignore it
ClearBitNo(changeddbits,PORTD5); // not a switch, ignore it
ClearBitNo(changeddbits,PORTD6); // not a switch, ignore it
ClearBitNo(changeddbits,PORTD7); // not a switch, ignore it
portdhistory = BUTTON_PIN; // set history = to the current state of input
ClearBitNo(portdhistory,PORTD0); // not a switch, ignore it
ClearBitNo(portdhistory,PORTD1); // not a switch, ignore it
ClearBitNo(portdhistory,PORTD3); // not a switch, ignore it
ClearBitNo(portdhistory,PORTD4); // not a switch, ignore it
ClearBitNo(portdhistory,PORTD5); // not a switch, ignore it
ClearBitNo(portdhistory,PORTD6); // not a switch, ignore it
ClearBitNo(portdhistory,PORTD7); // not a switch, ignore it
if(changeddbits & (1 << BUTTON_SPIN_PIN))
{
if( (portdhistory & (1 << BUTTON_SPIN_PIN)) == (1 << BUTTON_SPIN_PIN) ) // TODO: test using this instead of 16
{
_delay_us(DEBOUNCE_TIME);
if (bit_is_set(BUTTON_PIN, BUTTON_SPIN_PIN)) { // LOW to HIGH pin change (button released)
}
}
else
{
_delay_us(DEBOUNCE_TIME);
if (bit_is_clear(BUTTON_PIN, BUTTON_SPIN_PIN)){ // button pressed
// HIGH to LOW pin change (spin switch button pressed)
if(STATE_SPINNING == (machineState & STATE_SPINNING)) {
SetState(STATE_IDLE);
event = EVENT_NONE;
} else if (STATE_IDLE == (machineState & STATE_IDLE)) {
if (STATE_AUTO == (machineState & STATE_AUTO)) {
ClearBit(machineState, STATE_AUTO);
}
event = EVENT_SPIN;
}
}
}
}
}
void spinAndEvaluate() { // runs when the spin button is pressed or we 'Play' from the main menu
//debug("spinAndEvaluate()");
spin();
checkForWin();
signed long winnings = calcWinnings();
calcStored(winnings);
if (!(STATE_AUTO == (machineState & STATE_AUTO))) { // if we're not in auto mode display the credits
storeMetrics();
displayCredits();
if (reelMatches > 0) {
celebrateWin(reelMatches);
}
setupMetricsMenu();
} else if ((totalCalcs++%EEPROM_FREQ) == 0) { // EEPROM can be written ~100,000 times,
storeMetrics();
displayCredits(); // displayCredits takes care of the sign on increment
setupMetricsMenu();
debugStoredMetrics();
debugMetricDouble("owedExcess",owedExcess); // don't want to put owedExcess in metricsMenu because of global var space shortage
if (totalCalcs >= AUTO_MODE_MAX) { // drop out of auto mode when threshold exceeded
ClearBit(machineState, STATE_AUTO);
SetState(STATE_IDLE);
event = EVENT_NONE;
}
}
ClearBit(machineState, STATE_SPINNING);
}
void spin() {
//debug("spin()");
SetState(STATE_SPINNING);
if (!(STATE_AUTO == (machineState & STATE_AUTO))) {
beep();
}
zeroAllBalances();
byte reelsStopped[NUMREELS] = {0,0,0};
byte stopArrayPos[NUMREELS];
for (int reelNum = 0; reelNum < NUMREELS; reelNum++) {
if (!(STATE_AUTO == (machineState & STATE_AUTO))) {
lc.setIntensity(reelNum,LOW_INTENSITY); // Set intensity levels
}
stopArrayPos[reelNum] = random(0,(NUMFRAMES)) * LINESPERFRAME;
while (stopArrayPos[reelNum] == reelArrayPos[reelNum]) { // keep picking a stop array position until it's not equal to the current position
stopArrayPos[reelNum] = random(0,(NUMFRAMES)) * LINESPERFRAME;
}
}
while (!allReelsStopped(reelsStopped)) {
for (int reelNum = 0; reelNum < NUMREELS; reelNum++) {
if (reelArrayPos[reelNum] == ((NUMFRAMES * LINESPERFRAME) + 1)) {
reelArrayPos[reelNum] = 0; // go back to top of reel
}
if(reelArrayPos[reelNum] != (stopArrayPos[reelNum]+1)) {
if (!(STATE_AUTO == (machineState & STATE_AUTO))) {
for (int row = 0; row < LINESPERFRAME; row++) { // simulate a spinning reel
lc.setRow(reelNum,row,reel[reelArrayPos[reelNum] + row]); // output to 8x8x3 matrix
}
}
//delay(FRAME_DELAY);
//reelArrayPos[reelNum] += LINESPERFRAME; // uncomment for fast play
reelArrayPos[reelNum] += 1;
} else {
if (!(STATE_AUTO == (machineState & STATE_AUTO))) {
lc.setIntensity(reelNum,HIGH_INTENSITY); // Set intensity levels
}
reelsStopped[reelNum] = 1;
}
}
}
}
void checkForWin() { // this only works if NUMREELS == 3 ! If you change NUMREELS you must do so programming!
//debug("checkForWin()");
for (int reelNum=0; reelNum < NUMREELS; reelNum++) { // see if ships appeared
if ((reelArrayPos[reelNum] - 1) == SHIP_LOC) {
shipMatches += 1;
}
}
for (int i = 0; i < NUMREELS; i++) { // check to see if other symbols matched
for (int j = 0; j < NUMREELS; j++) {
if (reelArrayPos[i] - 1 == reelArrayPos[j] - 1) {
reelMatches += 1;
}
}
}
if (reelMatches == 9) { // code from the block above sets reelMatches to 9 if 3 symbols match
reelMatches = 3;
threeMatchCount++;
} else if (reelMatches == 5) { // etc...
reelMatches = 2;
twoMatchCount++;
} else if (reelMatches == 3) {
reelMatches = 0;
} else {
reelMatches = -1; // never used
}
if (shipMatches == 3) {
shipThreeMatchCount++;
} else if (shipMatches == 2) {
shipTwoMatchCount++;
} else if (shipMatches == 1) {
shipOneMatchCount++;
}
if (shipThreeMatchCount) { // Wins are mutually exclusive, subsequent code assumes that!
threeMatchCount = 0; // TODO: make this a switch statement
shipTwoMatchCount = 0;
shipOneMatchCount = 0;
twoMatchCount = 0;
reelMatches = 0;
} else if (threeMatchCount) {
shipTwoMatchCount = 0;
shipOneMatchCount = 0;
twoMatchCount = 0;
reelMatches = 0;
} else if (shipTwoMatchCount) {
shipOneMatchCount = 0;
twoMatchCount = 0;
reelMatches = 0;
} else if (shipOneMatchCount) {
twoMatchCount = 0;
reelMatches = 0;
} else if (twoMatchCount) {
reelMatches = 0;
}
}
signed long calcWinnings() {
double winnings = 0;
//debugMetric("storedHold",storedHold);
if(shipThreeMatchCount > 0) {
winnings = wagered * (THREE_SPACESHIP_PAYOUT - (THREE_SPACESHIP_PAYOUT * (storedHold/100.0))); // winnings are the amount wagered times the payout minus the hold.
} else if (threeMatchCount > 0) {
winnings = wagered * (THREE_SYMBOL_PAYOUT - (THREE_SYMBOL_PAYOUT * (storedHold/100.0)));
} else if (shipTwoMatchCount > 0) {
winnings = wagered * (TWO_SPACESHIP_PAYOUT - (TWO_SPACESHIP_PAYOUT * (storedHold/100.0)));
} else if (shipOneMatchCount > 0) {
winnings = wagered * (ONE_SPACESHIP_PAYOUT - (ONE_SPACESHIP_PAYOUT * (storedHold/100.0)));
} else if (twoMatchCount > 0) {
winnings = wagered * (TWO_SYMBOL_PAYOUT - (TWO_SYMBOL_PAYOUT * (storedHold/100.0)));
} else {
winnings = 0;
}
signed long roundWinnings = (signed long) round(winnings);
owedExcess += winnings - roundWinnings; // owedExcess is the change; credits between -1 and 1.
if (owedExcess >= 1 || owedExcess <= -1) { // if we can pay out some excess
int roundOwedExcess = (int) round(owedExcess);
roundWinnings += roundOwedExcess; // add the rounded portion to the winnings
owedExcess -= roundOwedExcess; // subtract out what we added to continue to track the excess
}
roundWinnings -= wagered; // you pay for your bet whether you won or not!
// winnings -= wagered;
return roundWinnings;
// return((signed long) round(winnings));
}
void calcStored(signed long winnings) {
storedPayedOut += winnings;
storedWagered += wagered;
startingCreditBalance = storedCreditBalance;
storedCreditBalance += winnings;
storedPlays += 1; // calcStored is called one time per play
storedTwoMatchCount += twoMatchCount;
storedThreeMatchCount += threeMatchCount;
storedShipOneMatchCount += shipOneMatchCount;
storedShipTwoMatchCount += shipTwoMatchCount;
storedShipThreeMatchCount += shipThreeMatchCount;
}
void storeMetrics() {
beepAuto(); // so we know we're not hung in auto mode.
updateStoredPayedOut();
updateStoredWagered();
updateStoredPlays();
updateStoredTwoMatchCount();
updateStoredThreeMatchCount();
updateStoredShipOneMatchCount();
updateStoredShipTwoMatchCount();
updateStoredShipThreeMatchCount();
storedEEpromWrites++;
updateStoredEEpromWrites();
updateStoredCreditBalance();
updateStoredHold();
}
void displayCredits() {
//debug("displayCredits()");
int xmitIncrement;
if ((STATE_AUTO == (machineState & STATE_AUTO))) { // display the credits here if we're in auto mode.
xmitIncrement = abs(startingCreditBalance - storedCreditBalance); // we don't want the display slave to count up/down
} else {
xmitIncrement = DISP_CREDIT_INCREMENT; // set increment back to what it should be during manual play
}
Wire.beginTransmission(CREDITS_I2C_SLAVE_ADDR);
Wire.write( startingCreditBalance & 0xFF);
Wire.write((startingCreditBalance & 0xFF00) >> 8);
Wire.write((startingCreditBalance & 0xFF0000) >> 16);
Wire.write((startingCreditBalance & 0xFF000000) >> 24); // most sigificant byte sent last
if (startingCreditBalance > storedCreditBalance) { // if the player lost,
xmitIncrement *= -1; // flip the sign on increment so we count down
}
Wire.write( xmitIncrement & 0xFF);
Wire.write((xmitIncrement & 0xFF00) >> 8);
Wire.write( storedCreditBalance & 0xFF);
Wire.write((storedCreditBalance & 0xFF00) >> 8);
Wire.write((storedCreditBalance & 0xFF0000) >> 16);
Wire.write((storedCreditBalance & 0xFF000000) >> 24); // most sigificant byte sent last
byte error = Wire.endTransmission();
if (error==4)
{
debug(F("Unknown error at address")); // I've never seen this happen.
}
}
bool allReelsStopped(byte reelsStopped[]) {
byte sumStopped = 0;
for (int i; i < NUMREELS; i++) {
if (reelsStopped[i] == 1) {
sumStopped += 1;
}
}
if (sumStopped == NUMREELS) { // all reels stopped
return 1;
}
return 0;
}
void celebrateWin(byte matches) { // we can probably do better than this. I've never seen it run for a three ship match...
//debug("celebrateWin()");
for (int i = 0; i < (matches - 1); i++) {
playSiren();
delay(ONE_SECOND);
}
}
void playSiren() { // play siren and toggle the RGB LED blue and red
//debug("playSiren()");
for (int j = 1; j <= SIREN_FLASHES; j++){
setBlue();
for (int note = MIN_NOTE; note <= MAX_NOTE; note+=5){ // 5 = # notes to step over. Necessary only w/ TimerFreeTone library.
if (note%1236==0) { // at the top of the range change RGB color.
if (color == RED) {
for (int reelNum = 0; reelNum < NUMREELS; reelNum++) {
lc.setIntensity(reelNum, LOW_INTENSITY); // this doesn't seem to be working...
}
setBlue();
}
if (color == BLUE) {
for (int reelNum = 0; reelNum < NUMREELS; reelNum++) {
lc.setIntensity(reelNum, HIGH_INTENSITY); // this doesn't seem to be working...
}
setRed();
}
}
if (sound) {
TimerFreeTone(TONE_PIN, note, 1); // third parameter is duration
}
}
}
setOff();
for (int reelNum = 0; reelNum < NUMREELS; reelNum++) {
lc.setIntensity(reelNum, HIGH_INTENSITY);
}
}
void setPurple() {
//debug("setPurple()");
setColor(170, 0, 255); // Purple Color
color = PURPLE;
}
void setRed(){
//debug("setRed()");
setColor(255, 0, 0); // Red Color
color = RED;
}
void setGreen(){
//debug("setGreen()");
setColor(0, 255, 0); // Green Color
color = GREEN;
}
void setBlue(){
//debug("setBlue()");
setColor(0, 0, 255); // Blue Color
color = BLUE;
}
void setWhite(){
//debug("setWhite()");
setColor(255, 255, 255); // White Color
color = WHITE;
}
void setOff(){
//debug("setOff()");
setColor(0,0,0); // Off
color = OFF;
}
void setColor(int redValue, int greenValue, int blueValue) {
//debug("setColor()");
analogWrite(RED_PIN, redValue);
analogWrite(GREEN_PIN, greenValue);
analogWrite(BLUE_PIN, blueValue);
}
void showColor(int color) { // There's got to be a better way to do this...
switch(color) {
case RED :
setRed();
break;
case GREEN :
setGreen();
break;
case BLUE :
setBlue();
break;
...
This file has been truncated, please download it to see its full contents.
如果您对此项目有任何想法、意见或问题,请在下方留言。
以上内容翻译自网络,原作者:Dan Murphy ,如涉及侵权,可联系删除。