feat: 全量同步 254 个常用的 Arduino 扩展库文件
This commit is contained in:
72
arduino-libs/arduino-cli/libraries/MD_MAXPanel/.gitignore
vendored
Normal file
72
arduino-libs/arduino-cli/libraries/MD_MAXPanel/.gitignore
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
#OS junk files
|
||||
[Tt]humbs.db
|
||||
*.DS_Store
|
||||
#.gitignore
|
||||
|
||||
#dOxygen project documentation and related files
|
||||
#[Dd]oc/html/
|
||||
#[Mm]edia/photo/
|
||||
[Mm]edia/music/
|
||||
[Mm]edia/video/
|
||||
|
||||
#Stuff
|
||||
*.zip
|
||||
|
||||
#Visual Studio files
|
||||
*.sln
|
||||
*.vcxproj
|
||||
*.filters
|
||||
*.vsarduino.h
|
||||
*.[Oo]bj
|
||||
*.user
|
||||
*.aps
|
||||
*.pch
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ncb
|
||||
*.suo
|
||||
*.tlb
|
||||
*.tlh
|
||||
*.bak
|
||||
*.[Cc]ache
|
||||
*.ilk
|
||||
*.log
|
||||
*.lib
|
||||
*.sbr
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.unsuccessfulbuild
|
||||
*.vmps.xml
|
||||
ipch/
|
||||
[Oo]bj/
|
||||
[Bb]in
|
||||
[Dd]ebug*/
|
||||
[Rr]elease*/
|
||||
Ankh.NoLoad
|
||||
|
||||
#Video files
|
||||
*.mov
|
||||
*.mp3
|
||||
*.mp4
|
||||
*.wlmp
|
||||
|
||||
#Pelles C files
|
||||
*.ppj
|
||||
*.tag
|
||||
*.ppx
|
||||
|
||||
#Additional Atmel Studio files
|
||||
*.atsln
|
||||
*.atsuo
|
||||
*.cppproj
|
||||
|
||||
#Project files
|
||||
[Bb]uild/
|
||||
|
||||
# Office Temp Files
|
||||
~$*
|
||||
|
||||
# visual studio database projects
|
||||
*.dbmdl
|
||||
502
arduino-libs/arduino-cli/libraries/MD_MAXPanel/LICENSE
Normal file
502
arduino-libs/arduino-cli/libraries/MD_MAXPanel/LICENSE
Normal file
@@ -0,0 +1,502 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
9
arduino-libs/arduino-cli/libraries/MD_MAXPanel/README.md
Normal file
9
arduino-libs/arduino-cli/libraries/MD_MAXPanel/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# MD_MAXPanel LED Matrix Panel Display Library
|
||||
|
||||
[Video of the library features](https://www.youtube.com/watch?v=DUwtmrIVH58)
|
||||
|
||||
The library implements functions that allow a panel of MAX72xx modules to be used as a pixel display device. The library provides support for supports elements such as points, lines, rectangles, triangles circles and text. Polygons can also be displayed as filled polygons.
|
||||
|
||||
If you like and use this library please consider making a small donation using [PayPal](https://paypal.me/MajicDesigns/4USD)
|
||||
|
||||
[Library Documentation](https://majicdesigns.github.io/MD_MAXPanel/)
|
||||
@@ -0,0 +1,99 @@
|
||||
MD_MAX72XX::fontType_t _Fixed_5x3[] PROGMEM = {
|
||||
'F', 1, 32, 127, 5,
|
||||
2, 0, 0, // 32 - 'Space'
|
||||
1, 23, // 33 - '!'
|
||||
3, 3, 0, 3, // 34 - '"'
|
||||
3, 31, 10, 31, // 35 - '#'
|
||||
3, 22, 31, 13, // 36 - '$'
|
||||
3, 9, 4, 18, // 37 - '%'
|
||||
3, 10, 21, 26, // 38 - '&'
|
||||
1, 3, // 39
|
||||
2, 14, 17, // 40 - '('
|
||||
2, 17, 14, // 41 - ')'
|
||||
3, 10, 4, 10, // 42 - '*'
|
||||
3, 4, 14, 4, // 43 - '+'
|
||||
2, 16, 8, // 44 - ','
|
||||
3, 4, 4, 4, // 45 - '-'
|
||||
1, 16, // 46 - '.'
|
||||
3, 8, 4, 2, // 47 - '/'
|
||||
3, 31, 17, 31, // 48 - '0'
|
||||
2, 0, 31, // 49 - '1'
|
||||
3, 29, 21, 23, // 50 - '2'
|
||||
3, 17, 21, 31, // 51 - '3'
|
||||
3, 7, 4, 31, // 52 - '4'
|
||||
3, 23, 21, 29, // 53 - '5'
|
||||
3, 31, 21, 29, // 54 - '6'
|
||||
3, 1, 1, 31, // 55 - '7'
|
||||
3, 31, 21, 31, // 56 - '8'
|
||||
3, 23, 21, 31, // 57 - '9'
|
||||
1, 10, // 58 - ':'
|
||||
2, 16, 10, // 59 - ';'
|
||||
3, 4, 10, 17, // 60 - '<'
|
||||
3, 10, 10, 10, // 61 - '='
|
||||
3, 17, 10, 4, // 62 - '>'
|
||||
3, 1, 21, 3, // 63 - '?'
|
||||
3, 14, 21, 22, // 64 - '@'
|
||||
3, 30, 5, 30, // 65 - 'A'
|
||||
3, 31, 21, 10, // 66 - 'B'
|
||||
3, 14, 17, 17, // 67 - 'C'
|
||||
3, 31, 17, 14, // 68 - 'D'
|
||||
3, 31, 21, 17, // 69 - 'E'
|
||||
3, 31, 5, 1, // 70 - 'F'
|
||||
3, 14, 17, 29, // 71 - 'G'
|
||||
3, 31, 4, 31, // 72 - 'H'
|
||||
3, 17, 31, 17, // 73 - 'I'
|
||||
3, 8, 16, 15, // 74 - 'J'
|
||||
3, 31, 4, 27, // 75 - 'K'
|
||||
3, 31, 16, 16, // 76 - 'L'
|
||||
3, 31, 2, 31, // 77 - 'M'
|
||||
3, 31, 14, 31, // 78 - 'N'
|
||||
3, 14, 17, 14, // 79 - 'O'
|
||||
3, 31, 5, 2, // 80 - 'P'
|
||||
3, 14, 25, 30, // 81 - 'Q'
|
||||
3, 31, 5, 26, // 82 - 'R'
|
||||
3, 18, 21, 9, // 83 - 'S'
|
||||
3, 1, 31, 1, // 84 - 'T'
|
||||
3, 15, 16, 15, // 85 - 'U'
|
||||
3, 7, 24, 7, // 86 - 'V'
|
||||
3, 15, 28, 15, // 87 - 'W'
|
||||
3, 27, 4, 27, // 88 - 'X'
|
||||
3, 3, 28, 3, // 89 - 'Y'
|
||||
3, 25, 21, 19, // 90 - 'Z'
|
||||
2, 31, 17, // 91 - '['
|
||||
3, 2, 4, 8, // 92 - '\'
|
||||
2, 17, 31, // 93 - ']'
|
||||
3, 2, 1, 2, // 94 - '^'
|
||||
3, 16, 16, 16, // 95 - '_'
|
||||
2, 1, 2, // 96 - '`'
|
||||
3, 12, 18, 28, // 97 - 'a'
|
||||
3, 31, 18, 12, // 98 - 'b'
|
||||
3, 12, 18, 18, // 99 - 'c'
|
||||
3, 12, 18, 31, // 100 - 'd'
|
||||
3, 12, 26, 20, // 101 - 'e'
|
||||
3, 4, 31, 5, // 102 - 'f'
|
||||
3, 20, 26, 12, // 103 - 'g'
|
||||
3, 31, 2, 28, // 104 - 'h'
|
||||
1, 29, // 105 - 'i'
|
||||
2, 16, 13, // 106 - 'j'
|
||||
3, 31, 8, 20, // 107 - 'k'
|
||||
1, 31, // 108 - 'l'
|
||||
3, 30, 6, 30, // 109 - 'm'
|
||||
3, 30, 2, 28, // 110 - 'n'
|
||||
3, 12, 18, 12, // 111 - 'o'
|
||||
3, 30, 10, 4, // 112 - 'p'
|
||||
3, 4, 10, 30, // 113 - 'q'
|
||||
2, 30, 4, // 114 - 'r'
|
||||
3, 20, 30, 10, // 115 - 's'
|
||||
3, 4, 30, 4, // 116 - 't'
|
||||
3, 14, 16, 30, // 117 - 'u'
|
||||
3, 14, 16, 14, // 118 - 'v'
|
||||
3, 14, 24, 14, // 119 - 'w'
|
||||
3, 18, 12, 18, // 120 - 'x'
|
||||
3, 22, 24, 14, // 121 - 'y'
|
||||
3, 26, 30, 22, // 122 - 'z'
|
||||
3, 4, 27, 17, // 123 - '{'
|
||||
1, 27, // 124 - '|'
|
||||
3, 17, 27, 4, // 125 - '}'
|
||||
3, 6, 2, 3, // 126 - '~'
|
||||
3, 31, 31, 31, // 127 - 'Full Block'
|
||||
};
|
||||
@@ -0,0 +1,652 @@
|
||||
// Implements the game of Bricks using MD_MAXPanel library
|
||||
//
|
||||
// Hardware used
|
||||
// =============
|
||||
// LEFT_PIN - bat left switch, INPUT_PULLUP
|
||||
// RIGHT_PIN - bat right switch, INPUT_PULLUP
|
||||
// UP_PIN - unused
|
||||
// DOWN_PIN - unused
|
||||
// SELECT_PIN - unused
|
||||
// ENTER_PIN - unused
|
||||
// BEEPER_PIN - piezo speaker
|
||||
// CLK_PIN, DATA_PIN, CS_PIN - LED matrix display connections
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
// Rules of the Game
|
||||
// =================
|
||||
// Player must keep the ball in play by batting it away. Each 'brick' that
|
||||
// is hit scores points. Once all the bricks are cleared the game continues
|
||||
// with a new screen. Game end when the ball is let out of bounds.
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
#include "Font5x3.h"
|
||||
#include "score.h"
|
||||
#include "sound.h"
|
||||
|
||||
// Turn on debug statements to the serial output
|
||||
#define DEBUG 0
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
|
||||
#define PRINTS(x) { Serial.print(F(x)); }
|
||||
#define PRINTD(x) { Serial.print(x, DEC); }
|
||||
#define PRINTXY(s,x,y) { Serial.print(F(s)); Serial.print(F("(")); Serial.print(x); Serial.print(F(",")); Serial.print(y); Serial.print(")"); }
|
||||
#define PRINTSTATE(s) { Serial.print("\n++>"); Serial.print(s); }
|
||||
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTD(x)
|
||||
#define PRINTXY(s,x,y)
|
||||
#define PRINTSTATE(s)
|
||||
|
||||
#endif
|
||||
|
||||
// Hardware pin definitions.
|
||||
// All momentary on switches are initialized INPUT_PULLUP
|
||||
const uint8_t UP_PIN = 2;
|
||||
const uint8_t RIGHT_PIN = 3;
|
||||
const uint8_t DOWN_PIN = 4;
|
||||
const uint8_t LEFT_PIN = 5;
|
||||
const uint8_t SELECT_PIN = 6;
|
||||
const uint8_t ENTER_PIN = 7;
|
||||
const uint8_t BEEPER_PIN = 9;
|
||||
|
||||
// Define the number of devices in the chain and the SPI hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 12; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// SPI hardware interface
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
// Arbitrary pins
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
|
||||
uint16_t FIELD_TOP, FIELD_RIGHT; // needs to be initialised in setup()
|
||||
const uint16_t FIELD_LEFT = 1;
|
||||
|
||||
const uint8_t BRICK_SIZE_DEFAULT = 3;
|
||||
const uint8_t BAT_SIZE_DEFAULT = 3; // must be an odd number
|
||||
const uint8_t BAT_EDGE_OFFSET = 1;
|
||||
|
||||
const char TITLE_TEXT[] = "BRICKS";
|
||||
const uint16_t SPLASH_DELAY =3000; // in milliseconds
|
||||
|
||||
const char GAME_TEXT[] = "GAME";
|
||||
const char OVER_TEXT[] = "OVER";
|
||||
const uint16_t GAME_OVER_DELAY = 3000; // in milliseconds
|
||||
|
||||
const uint8_t FONT_NUM_WIDTH = 3;
|
||||
const uint8_t MAX_LIVES = 9;
|
||||
const uint16_t MAX_SCORE = 999;
|
||||
|
||||
// A class to encapsulate the bricks bat
|
||||
// Bat runs at the bottom of the display to keep the ball in play
|
||||
class cBrickBat
|
||||
{
|
||||
private:
|
||||
uint16_t _x, _y; // the position of the center of the bat
|
||||
uint16_t _xmin, _xmax; // the max and min bat boundaries
|
||||
int8_t _vel; // the velocity of the bat (+1 for moving up, -1 moving down)
|
||||
uint8_t _size; // the size in pixels for the bat (odd number)
|
||||
uint8_t _pinLeft; // the pin for the up switch
|
||||
uint8_t _pinRight; // the pin for th down switch
|
||||
uint16_t _batDelay; // the delay between possible moves of the bat in milliseconds
|
||||
uint32_t _timeLastMove; // the millis() value for the last time we moved the bat
|
||||
|
||||
public:
|
||||
enum hitType_t { NO_HIT, CORNER_HIT, FLAT_HIT };
|
||||
|
||||
void begin(uint16_t x, uint16_t y, uint16_t xmin, uint16_t xmax, uint8_t size, uint8_t pinL, uint8_t pinR)
|
||||
{
|
||||
_x = x;
|
||||
_y = y;
|
||||
_xmin = xmin;
|
||||
_xmax = xmax;
|
||||
_vel = 0;
|
||||
_size = size;
|
||||
_pinLeft = pinL;
|
||||
_pinRight = pinR;
|
||||
_batDelay = 40;
|
||||
pinMode(_pinLeft, INPUT_PULLUP);
|
||||
pinMode(_pinRight, INPUT_PULLUP);
|
||||
PRINTXY("\nbat @", _x, _y);
|
||||
PRINTXY(" limits", _xmin, _xmax);
|
||||
}
|
||||
|
||||
uint16_t getX(void) { return (_x); }
|
||||
uint16_t getY(void) { return (_y); }
|
||||
|
||||
int8_t getVelocity(void) { return(_vel); }
|
||||
void draw(void) { mp.drawHLine(_y, _x - (_size / 2), _x + (_size / 2), true); }
|
||||
void erase(void) { mp.drawHLine(_y, _x - (_size / 2), _x + (_size / 2), false); }
|
||||
bool anyKey(void) { return(digitalRead(_pinLeft) == LOW || digitalRead(_pinRight) == LOW); }
|
||||
|
||||
hitType_t hit(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
|
||||
{
|
||||
int16_t dx = x1 - x0;
|
||||
|
||||
// if we are not in the same y plane as the bat there can be no hit
|
||||
if (y1 != _y) return(NO_HIT);
|
||||
|
||||
// if the ball is at
|
||||
// - the left of the bat and travelling left to right, or
|
||||
// - the right of the bat and travelling right to left
|
||||
// then it is a corner hit
|
||||
if ((x1 == _x + (_size / 2) && dx < 0) || (x1 == _x - (_size / 2) && dx>0))
|
||||
return(CORNER_HIT);
|
||||
|
||||
// the ball is between the left and right bat boundaries inclusive
|
||||
// then it is a flat hit
|
||||
if (x1 <= _x + (_size / 2) && x1 >= _x - (_size / 2))
|
||||
return(FLAT_HIT);
|
||||
|
||||
// and in case we missed something, a default no hit
|
||||
return(NO_HIT);
|
||||
}
|
||||
|
||||
bool move(void)
|
||||
{
|
||||
bool moveLeft = (digitalRead(_pinLeft) == LOW);
|
||||
bool moveRight = (digitalRead(_pinRight) == LOW);
|
||||
|
||||
// if this is the time for a move?
|
||||
if (millis() - _timeLastMove <= _batDelay)
|
||||
return(false);
|
||||
_timeLastMove = millis();
|
||||
|
||||
mp.update(false);
|
||||
|
||||
if (moveRight)
|
||||
{
|
||||
PRINTS("\n-- BAT move Left");
|
||||
erase();
|
||||
_vel = 1;
|
||||
_x++;
|
||||
if (_x + (_size/2) > _xmax) _x--; // keep within top boundary
|
||||
draw();
|
||||
}
|
||||
else if (moveLeft)
|
||||
{
|
||||
PRINTS("\n-- BAT move right");
|
||||
erase();
|
||||
_vel = -1;
|
||||
_x--;
|
||||
if (_x - (_size/2) < _xmin) _x++; // keep within bottom boundary
|
||||
draw();
|
||||
}
|
||||
else
|
||||
_vel = 0;
|
||||
|
||||
mp.update(true);
|
||||
return((moveLeft || moveRight));
|
||||
}
|
||||
};
|
||||
|
||||
// A class to encapsulate the bricks ball
|
||||
// Ball bounces off the bat, edges and bricks
|
||||
class cBrickBall
|
||||
{
|
||||
private:
|
||||
uint16_t _x, _y; // the position of the center of the ball
|
||||
int8_t _dx, _dy; // the offsets for the x and y direction
|
||||
uint16_t _xmin, _ymin; // minimum bounds for the ball
|
||||
uint16_t _xmax, _ymax; // maximum bounds for the ball
|
||||
uint32_t _timeLastMove; // last time the ball was moved
|
||||
uint16_t _ballDelay; // the delay between ball moves in milliseconds
|
||||
bool _run; // ball is running when true
|
||||
|
||||
public:
|
||||
enum bounce_t { BOUNCE_NONE, BOUNCE_BACK, BOUNCE_TOP, BOUNCE_BOTTOM, BOUNCE_LEFT, BOUNCE_RIGHT };
|
||||
|
||||
void begin(uint16_t x, uint16_t y, uint16_t xmin, uint16_t ymin, uint16_t xmax, uint16_t ymax)
|
||||
{
|
||||
_dx = _dy = 1;
|
||||
_timeLastMove = 0;
|
||||
_ballDelay = 100;
|
||||
_xmin = xmin;
|
||||
_xmax = xmax;
|
||||
_ymin = ymin;
|
||||
_ymax = ymax;
|
||||
_run = false;
|
||||
reset(x, y);
|
||||
}
|
||||
|
||||
uint16_t getX(void) { return (_x); }
|
||||
uint16_t getY(void) { return (_y); }
|
||||
uint16_t getNextX(void) { return (_x + _dx); }
|
||||
uint16_t getNextY(void) { return (_y + _dy); }
|
||||
uint16_t getDelay(void) { return (_ballDelay); }
|
||||
void setDelay(uint16_t delay) { if (delay > 10) _ballDelay = delay; }
|
||||
|
||||
void start(void) { _run = true; }
|
||||
void stop(void) { _run = false; }
|
||||
void draw(void) { mp.setPoint(_x, _y, true); } // PRINTXY("\nball@", _x, _y); }
|
||||
void erase(void) { mp.setPoint(_x, _y, false); }
|
||||
void reset(uint16_t x, uint16_t y) { _x = x; _y = y; _dy = 1; }
|
||||
|
||||
bool move(void)
|
||||
{
|
||||
// if this is the time for a move?
|
||||
if (_run && millis() - _timeLastMove <= _ballDelay)
|
||||
return(false);
|
||||
_timeLastMove = millis();
|
||||
|
||||
// do the animation
|
||||
mp.update(false);
|
||||
erase();
|
||||
_x = _x + _dx;
|
||||
_y = _y + _dy;
|
||||
|
||||
// ensure it always stays in bounds
|
||||
if (_x < _xmin) { _x = _xmin; _dx = -_dx; }
|
||||
if (_x > _xmax) { _x = _xmax; _dx = -_dx; }
|
||||
if (_y < _ymin) { _y = _ymin; _dy = -_dy; }
|
||||
if (_y > _ymax) { _y = _ymax; _dy = -_dy; }
|
||||
|
||||
// now update
|
||||
draw();
|
||||
mp.update(true);
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
void bounce(bounce_t b)
|
||||
{
|
||||
switch (b)
|
||||
{
|
||||
case BOUNCE_TOP:
|
||||
case BOUNCE_BOTTOM: _dy = -_dy; break;
|
||||
case BOUNCE_LEFT:
|
||||
case BOUNCE_RIGHT: _dx = -_dx; break;
|
||||
case BOUNCE_BACK: _dx = -_dx; _dy = -_dy; break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// A class to encapsulate the entire field of bricks
|
||||
// All the bricks are contained in this object.
|
||||
class cBrickField
|
||||
{
|
||||
private:
|
||||
struct brick_t
|
||||
{
|
||||
uint16_t x, y; // leftmost coordinate for this brick
|
||||
brick_t *next; // the next in the linked list
|
||||
};
|
||||
|
||||
uint8_t _size; // size of the bricks
|
||||
brick_t *_bricks; // start of the list
|
||||
brick_t *_deleted; // the deleted list
|
||||
|
||||
void draw(brick_t *pb) { mp.drawHLine(pb->y, pb->x, pb->x + _size-1, true); }
|
||||
void erase(brick_t *pb) { mp.drawHLine(pb->y, pb->x, pb->x + _size-1, false); }
|
||||
|
||||
void add(uint16_t x, uint16_t y)
|
||||
// add a brick to the start of the list
|
||||
{
|
||||
brick_t *pb;
|
||||
|
||||
if (_deleted == nullptr)
|
||||
pb = new brick_t;
|
||||
else
|
||||
{
|
||||
// take from the front of the deleted list
|
||||
pb = _deleted;
|
||||
_deleted = _deleted->next;
|
||||
}
|
||||
|
||||
pb->x = x;
|
||||
pb->y = y;
|
||||
pb->next = _bricks;
|
||||
_bricks = pb;
|
||||
|
||||
return(pb);
|
||||
}
|
||||
|
||||
void del(brick_t *pbPrev, brick_t *pb)
|
||||
// delete the specified brick from the list
|
||||
{
|
||||
if (pbPrev == nullptr)
|
||||
_bricks = pb->next;
|
||||
else
|
||||
pbPrev->next = pb->next;
|
||||
|
||||
// save to the front of the deleted list
|
||||
pb->next = _deleted;
|
||||
_deleted = pb;
|
||||
}
|
||||
|
||||
void dumpList(void)
|
||||
{
|
||||
PRINTS("\nDUMP List ===");
|
||||
for (brick_t *pb = _bricks; pb != nullptr; pb = pb->next)
|
||||
{
|
||||
PRINTXY("\n", pb->x, pb->y);
|
||||
draw(pb);
|
||||
}
|
||||
PRINTS("\n===");
|
||||
}
|
||||
|
||||
public:
|
||||
enum bounce_t { BOUNCE_NONE, BOUNCE_BACK, BOUNCE_UP, BOUNCE_DOWN };
|
||||
|
||||
void begin(uint16_t xmin, uint16_t ymin, uint16_t xmax, uint16_t ymax, uint8_t size)
|
||||
{
|
||||
// work out how many bricks we can put in the space
|
||||
const uint8_t gapX = 2;
|
||||
const uint8_t gapY = 3;
|
||||
uint8_t marginSide = 2;
|
||||
const uint8_t marginTop = gapY;
|
||||
const uint8_t numAcross = (xmax - xmin - marginSide - marginSide + size) / (gapX + size);
|
||||
const uint8_t numDown = (ymax - ymin - marginTop) / gapY;
|
||||
|
||||
// now adjust the side margin to center the display as much as possible
|
||||
marginSide = (1 + (xmax - xmin) - ((numAcross - 1)*gapX) - (numAcross*size))/2;
|
||||
|
||||
PRINT("\nBricks size=", size);
|
||||
PRINTXY(" field = ", xmin, ymin);
|
||||
PRINTXY(" ", xmax, ymax);
|
||||
PRINT(" -> Across=", numAcross);
|
||||
PRINT(" Down=", numDown);
|
||||
PRINT(" Adj margin=", marginSide);
|
||||
|
||||
_size = size;
|
||||
_bricks = _deleted = nullptr;
|
||||
|
||||
// create all the bricks
|
||||
uint16_t x = xmin + marginSide;
|
||||
for (uint8_t i = 0; i < numAcross; i++)
|
||||
{
|
||||
uint16_t y = ymax - marginTop;
|
||||
|
||||
PRINT("\nin column ", i);
|
||||
for (uint8_t j = 0; j < numDown; j++)
|
||||
{
|
||||
add(x, y);
|
||||
PRINTXY(" - ", x, y);
|
||||
y -= gapY;
|
||||
}
|
||||
x += (gapX + size);
|
||||
}
|
||||
|
||||
//dumpList(); // debug to verify the list is created properly
|
||||
}
|
||||
|
||||
bool emptyField(void) { return(_bricks == nullptr); }
|
||||
void drawField(void) { for (brick_t *pb = _bricks; pb != nullptr; pb = pb->next) draw(pb); }
|
||||
void eraseField(void) { while (_bricks != nullptr) { erase(_bricks); del(nullptr, _bricks); } }
|
||||
|
||||
bounce_t checkHits(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
|
||||
{
|
||||
brick_t *pb = _bricks;
|
||||
brick_t *pbPrev = nullptr;
|
||||
int16_t dx = x2 - x1;
|
||||
int16_t dy = y2 - y1;
|
||||
bounce_t b = BOUNCE_NONE;
|
||||
|
||||
while (pb != nullptr)
|
||||
{
|
||||
if (y2 == pb->y) // at the same height as this brick
|
||||
{
|
||||
// check one of the corners with sideways approach
|
||||
if ((x2 == pb->x && dx > 0) || (x2 == pb->x - _size - 1 && dx < 0))
|
||||
{
|
||||
PRINTS("\n-! BRICK hit on corner");
|
||||
b = BOUNCE_BACK;
|
||||
}
|
||||
// check the whole flat surface with any approach
|
||||
else if (x2 >= pb->x && x2 <= pb->x + _size - 1)
|
||||
{
|
||||
if (dy < 0)
|
||||
{
|
||||
b = BOUNCE_UP;
|
||||
PRINTS("\n-! BRICK hit on top");
|
||||
}
|
||||
else
|
||||
{
|
||||
b = BOUNCE_DOWN;
|
||||
PRINTS("\n-! BRICK hit on bottom");
|
||||
}
|
||||
}
|
||||
|
||||
// eliminate this brick
|
||||
if (b != BOUNCE_NONE)
|
||||
{
|
||||
PRINTXY("\n-! Ball @", x1, y1);
|
||||
PRINTXY(" next@", x2, y2);
|
||||
PRINTXY(" brick ", pb->x, pb->y);
|
||||
PRINTXY("-", pb->x+_size-1, pb->y);
|
||||
PRINTS(" - deleting");
|
||||
erase(pb);
|
||||
del(pbPrev, pb);
|
||||
break; // no point looping further - only one brick per hit
|
||||
}
|
||||
}
|
||||
// advance the pointers
|
||||
pbPrev = pb;
|
||||
pb = pb->next;
|
||||
}
|
||||
|
||||
return(b);
|
||||
}
|
||||
};
|
||||
|
||||
// main objects coordinated by the code logic
|
||||
cScore lives, score;
|
||||
cBrickBall ball;
|
||||
cBrickBat bat;
|
||||
cBrickField bricks;
|
||||
cSound sound;
|
||||
|
||||
void setupField(void)
|
||||
// Draw the playing field at the start of the game.
|
||||
{
|
||||
mp.clear();
|
||||
mp.drawHLine(FIELD_TOP, FIELD_LEFT, FIELD_RIGHT);
|
||||
mp.drawVLine(FIELD_LEFT, 0, FIELD_TOP);
|
||||
mp.drawVLine(FIELD_RIGHT, 0, FIELD_TOP);
|
||||
bat.draw();
|
||||
lives.draw();
|
||||
score.draw();
|
||||
ball.draw();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
#if DEBUG
|
||||
Serial.begin(57600);
|
||||
#endif
|
||||
PRINTS("\n[MD_MAXPanel_Bricks]");
|
||||
|
||||
mp.begin();
|
||||
mp.setFont(_Fixed_5x3);
|
||||
mp.setIntensity(4);
|
||||
mp.setRotation(MD_MAXPanel::ROT_90);
|
||||
|
||||
// one time initialization
|
||||
FIELD_TOP = mp.getYMax() - mp.getFontHeight() - 2;
|
||||
FIELD_RIGHT = mp.getXMax() - 1;
|
||||
|
||||
bat.begin((FIELD_RIGHT - FIELD_LEFT) / 2, BAT_EDGE_OFFSET, FIELD_LEFT + 1, FIELD_RIGHT - 1, BAT_SIZE_DEFAULT, LEFT_PIN, RIGHT_PIN);
|
||||
ball.begin(bat.getX(), bat.getY() + 1, FIELD_LEFT+1, 0, FIELD_RIGHT-1, FIELD_TOP-1);
|
||||
lives.begin(&mp, FIELD_LEFT + 1, FIELD_TOP + 1 + mp.getFontHeight(), MAX_LIVES);
|
||||
score.limit(MAX_SCORE); // set width() so we can used it below
|
||||
score.begin(&mp, FIELD_RIGHT - (score.width() * (FONT_NUM_WIDTH + mp.getCharSpacing())) + mp.getCharSpacing(), FIELD_TOP + 1 + mp.getFontHeight(), MAX_SCORE);
|
||||
sound.begin(BEEPER_PIN);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static enum { S_SPLASH, S_INIT, S_WAIT_START, S_POINT_PLAY, S_BALL_OUT, S_BRICKS_EMPTY, S_POINT_RESET, S_GAME_OVER } runState = S_SPLASH;
|
||||
|
||||
switch (runState)
|
||||
{
|
||||
case S_SPLASH: // show splash screen at start
|
||||
PRINTSTATE("SPLASH");
|
||||
{
|
||||
const uint16_t border = 2;
|
||||
|
||||
mp.clear();
|
||||
mp.drawRectangle(border, border, mp.getXMax() - border, mp.getYMax() - border);
|
||||
mp.drawLine(0, 0, border, border);
|
||||
mp.drawLine(0, mp.getYMax(), border, mp.getYMax()-border);
|
||||
mp.drawLine(mp.getXMax(), 0, mp.getXMax()-border, border);
|
||||
mp.drawLine(mp.getXMax(), mp.getYMax(), mp.getXMax()-border, mp.getYMax()-border);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(TITLE_TEXT)) / 2, (mp.getYMax() + mp.getFontHeight())/2 , TITLE_TEXT);
|
||||
sound.splash();
|
||||
delay(SPLASH_DELAY);
|
||||
|
||||
runState = S_INIT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_INIT: // initialize for a new game
|
||||
PRINTSTATE("INIT");
|
||||
ball.reset(bat.getX(), bat.getY() + 1);
|
||||
lives.set(MAX_LIVES);
|
||||
score.reset();
|
||||
setupField();
|
||||
bricks.begin(FIELD_LEFT + 1, FIELD_TOP / 3, FIELD_RIGHT - 1, FIELD_TOP - 1, BRICK_SIZE_DEFAULT);
|
||||
bricks.drawField();
|
||||
|
||||
runState = S_WAIT_START;
|
||||
PRINTSTATE("WAIT_START");
|
||||
break;
|
||||
|
||||
case S_WAIT_START: // waiting for the start of a new game
|
||||
if (bat.anyKey())
|
||||
{
|
||||
PRINTS("\n-- Starting Game");
|
||||
sound.start();
|
||||
ball.start();
|
||||
runState = S_POINT_PLAY;
|
||||
PRINTSTATE("POINT_PLAY");
|
||||
}
|
||||
break;
|
||||
|
||||
case S_POINT_PLAY: // playing a point
|
||||
// handle the bat animation first
|
||||
bat.move();
|
||||
|
||||
// now move the ball and check what this means
|
||||
if (ball.move())
|
||||
{
|
||||
cBrickBat::hitType_t lastHit;
|
||||
|
||||
// check for top/bottom edge collisions
|
||||
if (ball.getX() <= FIELD_LEFT + 1)
|
||||
{
|
||||
PRINTS("\n-- COLLISION left edge");
|
||||
ball.bounce(cBrickBall::BOUNCE_LEFT);
|
||||
sound.bounce();
|
||||
}
|
||||
else if (ball.getX() >= FIELD_RIGHT - 1)
|
||||
{
|
||||
PRINTS("\n-- COLLISION right edge");
|
||||
ball.bounce(cBrickBall::BOUNCE_RIGHT);
|
||||
sound.bounce();
|
||||
}
|
||||
if (ball.getY() >= FIELD_TOP - 1)
|
||||
{
|
||||
PRINTS("\n-- COLLISION top edge");
|
||||
ball.bounce(cBrickBall::BOUNCE_TOP);
|
||||
sound.bounce();
|
||||
}
|
||||
|
||||
// check for bat collisions
|
||||
if ((lastHit = bat.hit(ball.getX(), ball.getY(), ball.getNextX(), ball.getNextY())) != cBrickBat::NO_HIT)
|
||||
{
|
||||
PRINTS("\n-- COLLISION bat");
|
||||
ball.bounce(lastHit == cBrickBat::CORNER_HIT ? cBrickBall::BOUNCE_BACK : cBrickBall::BOUNCE_BOTTOM);
|
||||
sound.hit();
|
||||
}
|
||||
|
||||
// check for out of bounds at the bottom
|
||||
if (ball.getY() < BAT_EDGE_OFFSET)
|
||||
{
|
||||
PRINTS("\n-- OUT!");
|
||||
runState = S_BALL_OUT;
|
||||
}
|
||||
|
||||
// check for any hits to bricks
|
||||
cBrickField::bounce_t b;
|
||||
if ((b = bricks.checkHits(ball.getX(), ball.getY(), ball.getNextX(), ball.getNextY())) != cBrickField::BOUNCE_NONE)
|
||||
{
|
||||
score.increment();
|
||||
sound.bounce();
|
||||
// check if this is the end of all the bricks
|
||||
if (bricks.emptyField())
|
||||
{
|
||||
PRINTS("\n== BRICKS empty");
|
||||
runState = S_BRICKS_EMPTY;
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise we need to bounce the ball in the right direction
|
||||
switch (b)
|
||||
{
|
||||
case cBrickField::BOUNCE_UP: ball.bounce(cBrickBall::BOUNCE_BOTTOM); break;
|
||||
case cBrickField::BOUNCE_DOWN: ball.bounce(cBrickBall::BOUNCE_TOP); break;
|
||||
case cBrickField::BOUNCE_BACK: ball.bounce(cBrickBall::BOUNCE_BACK); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case S_BRICKS_EMPTY: // handle the bricks being completed
|
||||
PRINTSTATE("BRICKS_EMPTY");
|
||||
ball.stop();
|
||||
ball.erase();
|
||||
lives.increment();
|
||||
ball.setDelay(ball.getDelay() - 5);
|
||||
sound.point();
|
||||
bricks.begin(FIELD_LEFT + 1, FIELD_TOP / 3, FIELD_RIGHT - 1, FIELD_TOP - 1, BRICK_SIZE_DEFAULT);
|
||||
bricks.drawField();
|
||||
runState = S_POINT_RESET;
|
||||
break;
|
||||
|
||||
case S_BALL_OUT: // handle the ball going out
|
||||
PRINTSTATE("BALL_OUT");
|
||||
ball.stop();
|
||||
ball.erase();
|
||||
lives.decrement();
|
||||
sound.point();
|
||||
if (lives.score() != 0)
|
||||
runState = S_POINT_RESET;
|
||||
else
|
||||
runState = S_GAME_OVER;
|
||||
break;
|
||||
|
||||
case S_POINT_RESET:
|
||||
PRINTSTATE("POINT_RESET");
|
||||
bat.draw();
|
||||
ball.reset(bat.getX(), bat.getY() + 1);
|
||||
ball.draw();
|
||||
delay(500);
|
||||
runState = S_WAIT_START;
|
||||
PRINTSTATE("WAIT_START");
|
||||
break;
|
||||
|
||||
case S_GAME_OVER:
|
||||
PRINTSTATE("GAME_OVER");
|
||||
bricks.eraseField();
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(GAME_TEXT)) / 2, FIELD_TOP / 2 + mp.getFontHeight() + 1, GAME_TEXT);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(OVER_TEXT)) / 2, FIELD_TOP / 2 - 1, OVER_TEXT);
|
||||
|
||||
sound.over();
|
||||
delay(GAME_OVER_DELAY);
|
||||
runState = S_INIT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
|
||||
// A class to encapsulate the score display
|
||||
class cScore
|
||||
{
|
||||
private:
|
||||
MD_MAXPanel *_mp; // the MAXPanel object for drawing the score
|
||||
uint16_t _score; // the score
|
||||
uint16_t _x, _y; // coordinate of top left for display
|
||||
uint8_t _width; // number of digits wide
|
||||
uint16_t _limit; // maximum value allowed
|
||||
|
||||
public:
|
||||
void begin(MD_MAXPanel *mp, uint16_t x, uint16_t y, uint16_t maxScore) { _mp = mp; _x = x, _y = y; limit(maxScore); reset(); }
|
||||
void reset(void) { erase(); _score = 0; draw(); }
|
||||
void set(uint16_t s) { if (s <= _limit) { erase(); _score = s; draw(); } }
|
||||
void increment(uint16_t inc = 1) { if (_score + inc <= _limit) { erase(); _score += inc; draw(); } }
|
||||
void decrement(uint16_t dec = 1) { if (_score >= dec) { erase(); _score -= dec; draw(); } }
|
||||
uint16_t score(void) { return(_score); }
|
||||
void erase(void) { draw(false); }
|
||||
uint16_t width(void) { return(_width); }
|
||||
|
||||
void limit(uint16_t m)
|
||||
{
|
||||
erase(); // width may change, so delete with the curret parameters
|
||||
|
||||
_limit = m;
|
||||
// work out how many digits this is
|
||||
_width = 0;
|
||||
do
|
||||
{
|
||||
_width++;
|
||||
m /= 10;
|
||||
} while (m != 0);
|
||||
}
|
||||
|
||||
void draw(bool state = true)
|
||||
{
|
||||
char sz[_width + 1];
|
||||
uint16_t s = _score;
|
||||
|
||||
// PRINT("\n-- SCORE: ", _score);
|
||||
sz[_width] = '\0';
|
||||
for (int i = _width - 1; i >= 0; --i)
|
||||
{
|
||||
sz[i] = (s % 10) + '0';
|
||||
s /= 10;
|
||||
}
|
||||
|
||||
_mp->drawText(_x, _y, sz, MD_MAXPanel::ROT_0, state);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
// A class to encapsulate primitive sound effects
|
||||
class cSound
|
||||
{
|
||||
private:
|
||||
const uint16_t EOD = 0; // End Of Data marker
|
||||
uint8_t _pinBeep; // the pin to use for beeping
|
||||
|
||||
// Sound data - frequency followed by duration in pairs.
|
||||
// Data ends in End Of Data marker EOD.
|
||||
const uint16_t soundSplash[1] PROGMEM = { EOD };
|
||||
const uint16_t soundHit[3] PROGMEM = { 1000, 50, EOD };
|
||||
const uint16_t soundBounce[3] PROGMEM = { 500, 50, EOD };
|
||||
const uint16_t soundPoint[3] PROGMEM = { 150, 150, EOD };
|
||||
const uint16_t soundStart[7] PROGMEM = { 250, 100, 500, 100, 1000, 100, EOD };
|
||||
const uint16_t soundOver[7] PROGMEM = { 1000, 100, 500, 100, 250, 100, EOD };
|
||||
|
||||
void playSound(const uint16_t *table)
|
||||
// Play sound table data. Data table must end in EOD marker.
|
||||
{
|
||||
uint8_t idx = 0;
|
||||
|
||||
//PRINTS("\nTone Data ");
|
||||
while (table[idx] != EOD)
|
||||
{
|
||||
uint16_t t = table[idx++];
|
||||
uint16_t d = table[idx++];
|
||||
|
||||
//PRINTXY("-", t, d);
|
||||
tone(_pinBeep, t);
|
||||
delay(d);
|
||||
}
|
||||
//PRINTS("-EOD");
|
||||
noTone(_pinBeep); // be quiet now!
|
||||
}
|
||||
|
||||
public:
|
||||
void begin(uint8_t pinBeep) { _pinBeep = pinBeep; }
|
||||
void splash(void) { playSound(soundSplash); }
|
||||
void start(void) { playSound(soundStart); }
|
||||
void hit(void) { playSound(soundHit); }
|
||||
void bounce(void) { playSound(soundBounce); }
|
||||
void point(void) { playSound(soundPoint); }
|
||||
void over(void) { playSound(soundOver); }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// Program to exercise the MD_MAXPanel library
|
||||
//
|
||||
// Displays a rotating 3D cube, shamelessly adapted from the
|
||||
// Microview library example "MicroViewCube.ino"
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
#include <SPI.h>
|
||||
|
||||
// Define the number of devices we have in the chain and the hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 13; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// SPI hardware interface
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
// Arbitrary pins
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
|
||||
// We may wait a bit between updates of the display
|
||||
const uint16_t ROTATION_DELAY = 0; // in milliseconds
|
||||
|
||||
float d = 3;
|
||||
float px[] = { -d, d, d, -d, -d, d, d, -d };
|
||||
float py[] = { -d, -d, d, d, -d, -d, d, d };
|
||||
float pz[] = { -d, -d, -d, -d, d, d, d, d };
|
||||
|
||||
float p2x[] = {0,0,0,0,0,0,0,0};
|
||||
float p2y[] = {0,0,0,0,0,0,0,0};
|
||||
|
||||
float r[] = {0,0,0};
|
||||
|
||||
void setup()
|
||||
{
|
||||
mp.begin();
|
||||
mp.clear();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
drawCube();
|
||||
delay(ROTATION_DELAY);
|
||||
}
|
||||
|
||||
void drawCube()
|
||||
{
|
||||
const uint16_t SHAPE_SIZE = 40 * (min(mp.getXMax(), mp.getYMax()) / 3);
|
||||
const uint16_t SCREEN_WIDTH = mp.getXMax();
|
||||
const uint16_t SCREEN_HEIGHT = mp.getYMax();
|
||||
|
||||
r[0]=r[0]+PI/180.0; // Add a degree
|
||||
r[1]=r[1]+PI/180.0; // Add a degree
|
||||
r[2]=r[2]+PI/180.0; // Add a degree
|
||||
if (r[0] >= 360.0*PI/180.0) r[0] = 0;
|
||||
if (r[1] >= 360.0*PI/180.0) r[1] = 0;
|
||||
if (r[2] >= 360.0*PI/180.0) r[2] = 0;
|
||||
|
||||
for (uint8_t i = 0; i < 8; i++)
|
||||
{
|
||||
float px2 = px[i];
|
||||
float py2 = cos(r[0])*py[i] - sin(r[0])*pz[i];
|
||||
float pz2 = sin(r[0])*py[i] + cos(r[0])*pz[i];
|
||||
|
||||
float px3 = cos(r[1])*px2 + sin(r[1])*pz2;
|
||||
float py3 = py2;
|
||||
float pz3 = -sin(r[1])*px2 + cos(r[1])*pz2;
|
||||
|
||||
float ax = cos(r[2])*px3 - sin(r[2])*py3;
|
||||
float ay = sin(r[2])*px3 + cos(r[2])*py3;
|
||||
float az = pz3-150;
|
||||
|
||||
p2x[i] = SCREEN_WIDTH/2+ax*SHAPE_SIZE/az;
|
||||
p2y[i] = SCREEN_HEIGHT/2+ay*SHAPE_SIZE/az;
|
||||
}
|
||||
|
||||
mp.clear();
|
||||
mp.update(false);
|
||||
for (uint8_t i = 0; i < 3; i++)
|
||||
{
|
||||
mp.drawLine(p2x[i],p2y[i],p2x[i+1],p2y[i+1]);
|
||||
mp.drawLine(p2x[i+4],p2y[i+4],p2x[i+5],p2y[i+5]);
|
||||
mp.drawLine(p2x[i],p2y[i],p2x[i+4],p2y[i+4]);
|
||||
}
|
||||
mp.drawLine(p2x[3],p2y[3],p2x[0],p2y[0]);
|
||||
mp.drawLine(p2x[7],p2y[7],p2x[4],p2y[4]);
|
||||
mp.drawLine(p2x[3],p2y[3],p2x[7],p2y[7]);
|
||||
mp.update(true);
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
// Implements Conway's Game of Life using MD_MAXPanel library
|
||||
//
|
||||
// Hardware used
|
||||
// =============
|
||||
// Momentary On push switch on SWITCH_PIN to start a new game. The digital I/O
|
||||
// will be initialize INPUT_PULLUP
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
// Rules of the Game
|
||||
// =================
|
||||
// The universe of the Game of Life is an infinite, two-dimensional orthogonal
|
||||
// grid of square cells, each of which is in one of two possible states,
|
||||
// alive or dead (or populated and unpopulated). Every cell interacts with
|
||||
// its eight neighbors, which are the cells that are horizontally, vertically,
|
||||
// or diagonally adjacent. At each step in time, the following transitions occur:
|
||||
//
|
||||
// 1. Any live cell with fewer than two live neighbors dies, as if by under population.
|
||||
// 2. Any live cell with two or three live neighbors lives on to the next generation.
|
||||
// 3. Any live cell with more than three live neighbors dies, as if by overpopulation.
|
||||
// 4. Any dead cell with exactly three live neighbors becomes a live cell, as if
|
||||
// by reproduction.
|
||||
//
|
||||
// The initial pattern constitutes the seed of the system. The first generation is
|
||||
// created by applying the above rules simultaneously to every cell in the seed;
|
||||
// births and deaths occur simultaneously, and the discrete moment at which this
|
||||
// happens is sometimes called a tick. Each generation is a pure function of the
|
||||
// preceding one. The rules continue to be applied repeatedly to create further
|
||||
// generations.
|
||||
//
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
#include "randomseed.h"
|
||||
|
||||
// Turn on debug statements to the serial output
|
||||
#define DEBUG 0
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
|
||||
#define PRINTS(x) { Serial.print(F(x)); }
|
||||
#define PRINTD(x) { Serial.print(x, DEC); }
|
||||
#define PRINTXY(s, x, y) { Serial.print(s); Serial.print(F("(")); Serial.print(x); Serial.print(F(",")); Serial.print(y); Serial.print(F(")")); }
|
||||
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTD(x)
|
||||
#define PRINTXY(s, x, y)
|
||||
|
||||
#endif
|
||||
|
||||
// Define the number of devices we have in the chain and the hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 13; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// SPI hardware interface
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
// Arbitrary pins
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
|
||||
#define SWITCH_PIN 6
|
||||
|
||||
// We always wait a bit between updates of the display
|
||||
#define TICK_TIME 150 // in milliseconds
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
#if DEBUG
|
||||
Serial.begin(57600);
|
||||
#endif
|
||||
PRINTS("\n[MD_MAXPanel Game of Life]");
|
||||
|
||||
pinMode(SWITCH_PIN, INPUT_PULLUP);
|
||||
|
||||
mp.begin();
|
||||
mp.clear();
|
||||
randomSeed(seedOut(31, RANDOM_SEED_PORT));
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static uint32_t timeLastRun = 0;
|
||||
static uint8_t sameCount = 10;
|
||||
static uint32_t lastCount = 0;
|
||||
uint32_t count = countCells();
|
||||
|
||||
if (lastCount == count) sameCount++; else sameCount = 0;
|
||||
|
||||
if (digitalRead(SWITCH_PIN) == LOW || sameCount >= 10)
|
||||
{
|
||||
mp.clear(); // mark the end of the display ...
|
||||
delay(1000); // ... with a minor pause!
|
||||
firstGeneration();
|
||||
sameCount = 0;
|
||||
}
|
||||
lastCount = count;
|
||||
|
||||
// Check if next generation time
|
||||
if (millis() - timeLastRun >= TICK_TIME)
|
||||
{
|
||||
timeLastRun = millis();
|
||||
nextGeneration();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t countCells(void)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
|
||||
for (uint16_t x = 0; x <= mp.getXMax(); x++)
|
||||
for (uint16_t y = 0; y <= mp.getYMax(); y++)
|
||||
count += (mp.getPoint(x,y) ? 1:0);
|
||||
|
||||
return(count);
|
||||
}
|
||||
|
||||
void firstGeneration(void)
|
||||
// Create a 4-way symmetric random setup
|
||||
{
|
||||
mp.update(false);
|
||||
|
||||
PRINTS("\n-- FIRST Generation");
|
||||
PRINTXY("\n-- Field size (1,1) - ", mp.getXMax() - 1, mp.getYMax() - 1);
|
||||
mp.clear();
|
||||
for (uint16_t x=1; x<(mp.getXMax()+1) / 2; x++)
|
||||
for (uint16_t y = 1; y < (mp.getYMax()+1) / 2; y++)
|
||||
{
|
||||
bool b = (random(101) > 50);
|
||||
|
||||
mp.setPoint(x, y, b);
|
||||
mp.setPoint(mp.getXMax() - x, y, b);
|
||||
mp.setPoint(x, mp.getYMax() - y, b);
|
||||
mp.setPoint(mp.getXMax() - x, mp.getYMax() - y, b);
|
||||
}
|
||||
|
||||
mp.update(true);
|
||||
}
|
||||
|
||||
void nextGeneration(void)
|
||||
// Apply the rules
|
||||
{
|
||||
bool rowBuf[2][mp.getXMax()+2];
|
||||
uint16_t count;
|
||||
bool newCell;
|
||||
|
||||
PRINTS("\n-- NEW generation");
|
||||
// clear out the row buffers
|
||||
memset(rowBuf, 0, sizeof(rowBuf));
|
||||
mp.update(false);
|
||||
|
||||
for (uint16_t y=mp.getYMax()-1; y>=1; y--)
|
||||
{
|
||||
// copy the current row to the buffer
|
||||
for (uint16_t x=0; x<mp.getXMax(); x++)
|
||||
rowBuf[1][x] = mp.getPoint(x, y);
|
||||
|
||||
// work out a 'new' current row
|
||||
for (uint16_t x=1; x<mp.getXMax(); x++)
|
||||
{
|
||||
// count the number of neighbours
|
||||
count = rowBuf[0][x-1] ? 1:0;
|
||||
count += rowBuf[0][x] ? 1:0;
|
||||
count += rowBuf[0][x+1]? 1:0;
|
||||
count += rowBuf[1][x-1]? 1:0;
|
||||
count += rowBuf[1][x+1]? 1:0;
|
||||
count += mp.getPoint(x-1, y-1)? 1:0;
|
||||
count += mp.getPoint(x, y-1) ? 1:0;
|
||||
count += mp.getPoint(x+1, y-1)? 1:0;
|
||||
|
||||
PRINTXY("\n@", x, y);
|
||||
PRINT(" count=", count);
|
||||
PRINTS(" ->");
|
||||
|
||||
if (count < 2)
|
||||
{
|
||||
// A live cell with fewer than two neighbors dies.
|
||||
newCell = false;
|
||||
PRINTS("dies.");
|
||||
}
|
||||
else if ((count == 2 || count == 3) && mp.getPoint(x, y))
|
||||
{
|
||||
// A live cell with two or three neighbors lives on.
|
||||
newCell = true;
|
||||
PRINTS("stays.");
|
||||
}
|
||||
else if (count == 3 && !mp.getPoint(x, y))
|
||||
{
|
||||
// A dead cell with exactly three neighbors becomes live.
|
||||
newCell = true;
|
||||
PRINTS("born.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// A live cell with more than three neighbors dies.
|
||||
newCell = false;
|
||||
PRINTS("dies.");
|
||||
}
|
||||
|
||||
mp.setPoint(x, y, newCell);
|
||||
}
|
||||
|
||||
// update the saved row buffers
|
||||
for (uint16_t x=0; x<=mp.getXMax(); x++)
|
||||
rowBuf[0][x] = rowBuf[1][x];
|
||||
}
|
||||
|
||||
mp.update(true);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
// Random seed creation --------------------------
|
||||
// Adapted from http://www.utopiamechanicus.com/article/arduino-better-random-numbers/
|
||||
|
||||
const uint8_t RANDOM_SEED_PORT = A3; // port read for random seed
|
||||
|
||||
uint16_t bitOut(uint8_t port)
|
||||
{
|
||||
static bool firstTime = true;
|
||||
uint32_t prev = 0;
|
||||
uint32_t bit1 = 0, bit0 = 0;
|
||||
uint32_t x = 0, limit = 99;
|
||||
|
||||
if (firstTime)
|
||||
{
|
||||
firstTime = false;
|
||||
prev = analogRead(port);
|
||||
}
|
||||
|
||||
while (limit--)
|
||||
{
|
||||
x = analogRead(port);
|
||||
bit1 = (prev != x ? 1 : 0);
|
||||
prev = x;
|
||||
x = analogRead(port);
|
||||
bit0 = (prev != x ? 1 : 0);
|
||||
prev = x;
|
||||
if (bit1 != bit0)
|
||||
break;
|
||||
}
|
||||
|
||||
return(bit1);
|
||||
}
|
||||
|
||||
uint32_t seedOut(uint16_t noOfBits, uint8_t port)
|
||||
{
|
||||
// return value with 'noOfBits' random bits set
|
||||
uint32_t seed = 0;
|
||||
|
||||
for (int i = 0; i<noOfBits; ++i)
|
||||
seed = (seed << 1) | bitOut(port);
|
||||
|
||||
return(seed);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,99 @@
|
||||
MD_MAX72XX::fontType_t _Fixed_5x3[] PROGMEM = {
|
||||
'F', 1, 32, 127, 5,
|
||||
2, 0, 0, // 32 - 'Space'
|
||||
1, 23, // 33 - '!'
|
||||
3, 3, 0, 3, // 34 - '"'
|
||||
3, 31, 10, 31, // 35 - '#'
|
||||
3, 22, 31, 13, // 36 - '$'
|
||||
3, 9, 4, 18, // 37 - '%'
|
||||
3, 10, 21, 26, // 38 - '&'
|
||||
1, 3, // 39
|
||||
2, 14, 17, // 40 - '('
|
||||
2, 17, 14, // 41 - ')'
|
||||
3, 10, 4, 10, // 42 - '*'
|
||||
3, 4, 14, 4, // 43 - '+'
|
||||
2, 16, 8, // 44 - ','
|
||||
3, 4, 4, 4, // 45 - '-'
|
||||
1, 16, // 46 - '.'
|
||||
3, 8, 4, 2, // 47 - '/'
|
||||
3, 31, 17, 31, // 48 - '0'
|
||||
2, 0, 31, // 49 - '1'
|
||||
3, 29, 21, 23, // 50 - '2'
|
||||
3, 17, 21, 31, // 51 - '3'
|
||||
3, 7, 4, 31, // 52 - '4'
|
||||
3, 23, 21, 29, // 53 - '5'
|
||||
3, 31, 21, 29, // 54 - '6'
|
||||
3, 1, 1, 31, // 55 - '7'
|
||||
3, 31, 21, 31, // 56 - '8'
|
||||
3, 23, 21, 31, // 57 - '9'
|
||||
1, 10, // 58 - ':'
|
||||
2, 16, 10, // 59 - ';'
|
||||
3, 4, 10, 17, // 60 - '<'
|
||||
3, 10, 10, 10, // 61 - '='
|
||||
3, 17, 10, 4, // 62 - '>'
|
||||
3, 1, 21, 3, // 63 - '?'
|
||||
3, 14, 21, 22, // 64 - '@'
|
||||
3, 30, 5, 30, // 65 - 'A'
|
||||
3, 31, 21, 10, // 66 - 'B'
|
||||
3, 14, 17, 17, // 67 - 'C'
|
||||
3, 31, 17, 14, // 68 - 'D'
|
||||
3, 31, 21, 17, // 69 - 'E'
|
||||
3, 31, 5, 1, // 70 - 'F'
|
||||
3, 14, 17, 29, // 71 - 'G'
|
||||
3, 31, 4, 31, // 72 - 'H'
|
||||
3, 17, 31, 17, // 73 - 'I'
|
||||
3, 8, 16, 15, // 74 - 'J'
|
||||
3, 31, 4, 27, // 75 - 'K'
|
||||
3, 31, 16, 16, // 76 - 'L'
|
||||
3, 31, 2, 31, // 77 - 'M'
|
||||
3, 31, 14, 31, // 78 - 'N'
|
||||
3, 14, 17, 14, // 79 - 'O'
|
||||
3, 31, 5, 2, // 80 - 'P'
|
||||
3, 14, 25, 30, // 81 - 'Q'
|
||||
3, 31, 5, 26, // 82 - 'R'
|
||||
3, 18, 21, 9, // 83 - 'S'
|
||||
3, 1, 31, 1, // 84 - 'T'
|
||||
3, 15, 16, 15, // 85 - 'U'
|
||||
3, 7, 24, 7, // 86 - 'V'
|
||||
3, 15, 28, 15, // 87 - 'W'
|
||||
3, 27, 4, 27, // 88 - 'X'
|
||||
3, 3, 28, 3, // 89 - 'Y'
|
||||
3, 25, 21, 19, // 90 - 'Z'
|
||||
2, 31, 17, // 91 - '['
|
||||
3, 2, 4, 8, // 92 - '\'
|
||||
2, 17, 31, // 93 - ']'
|
||||
3, 2, 1, 2, // 94 - '^'
|
||||
3, 16, 16, 16, // 95 - '_'
|
||||
2, 1, 2, // 96 - '`'
|
||||
3, 12, 18, 28, // 97 - 'a'
|
||||
3, 31, 18, 12, // 98 - 'b'
|
||||
3, 12, 18, 18, // 99 - 'c'
|
||||
3, 12, 18, 31, // 100 - 'd'
|
||||
3, 12, 26, 20, // 101 - 'e'
|
||||
3, 4, 31, 5, // 102 - 'f'
|
||||
3, 20, 26, 12, // 103 - 'g'
|
||||
3, 31, 2, 28, // 104 - 'h'
|
||||
1, 29, // 105 - 'i'
|
||||
2, 16, 13, // 106 - 'j'
|
||||
3, 31, 8, 20, // 107 - 'k'
|
||||
1, 31, // 108 - 'l'
|
||||
3, 30, 6, 30, // 109 - 'm'
|
||||
3, 30, 2, 28, // 110 - 'n'
|
||||
3, 12, 18, 12, // 111 - 'o'
|
||||
3, 30, 10, 4, // 112 - 'p'
|
||||
3, 4, 10, 30, // 113 - 'q'
|
||||
2, 30, 4, // 114 - 'r'
|
||||
3, 20, 30, 10, // 115 - 's'
|
||||
3, 4, 30, 4, // 116 - 't'
|
||||
3, 14, 16, 30, // 117 - 'u'
|
||||
3, 14, 16, 14, // 118 - 'v'
|
||||
3, 14, 24, 14, // 119 - 'w'
|
||||
3, 18, 12, 18, // 120 - 'x'
|
||||
3, 22, 24, 14, // 121 - 'y'
|
||||
3, 26, 30, 22, // 122 - 'z'
|
||||
3, 4, 27, 17, // 123 - '{'
|
||||
1, 27, // 124 - '|'
|
||||
3, 17, 27, 4, // 125 - '}'
|
||||
3, 6, 2, 3, // 126 - '~'
|
||||
3, 31, 31, 31, // 127 - 'Full Block'
|
||||
};
|
||||
@@ -0,0 +1,824 @@
|
||||
// Implements the game of Meteors using MD_MAXPanel library
|
||||
//
|
||||
// Hardware used
|
||||
// =============
|
||||
// LEFT_PIN - bat left switch, INPUT_PULLUP
|
||||
// RIGHT_PIN - bat right switch, INPUT_PULLUP
|
||||
// UP_PIN - unused
|
||||
// DOWN_PIN - unused
|
||||
// SELECT_PIN - shooting bullets switch, INPUT_PULLUP
|
||||
// ENTER_PIN - unused
|
||||
// BEEPER_PIN - piezo speaker
|
||||
// CLK_PIN, DATA_PIN, CS_PIN - LED matrix display connections
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
// Rules of the Game
|
||||
// =================
|
||||
// The player controls a spaceship in an meteor field. The object of the
|
||||
// game is to shoot and destroy meteors while not colliding with any meteor
|
||||
// fragments. Meteors start as a large size and become smaller and move
|
||||
// faster as they fragment when they are hit. Smaller, faster meteors
|
||||
// score more points that large slow ones. The game ends when a meteor
|
||||
// hits the spaceship, destroying it.
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
#include "Font5x3.h"
|
||||
#include "score.h"
|
||||
#include "sound.h"
|
||||
#include "randomseed.h"
|
||||
|
||||
// Turn on debug statements to the serial output
|
||||
#define DEBUG 0
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
|
||||
#define PRINTS(x) { Serial.print(F(x)); }
|
||||
#define PRINTD(x) { Serial.print(x, DEC); }
|
||||
#define PRINTXY(s,x,y) { Serial.print(F(s)); Serial.print(F("(")); Serial.print(x); Serial.print(F(",")); Serial.print(y); Serial.print(")"); }
|
||||
#define PRINTSTATE(s) { Serial.print("\n++>"); Serial.print(s); }
|
||||
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTD(x)
|
||||
#define PRINTXY(s,x,y)
|
||||
#define PRINTSTATE(s)
|
||||
|
||||
#endif
|
||||
|
||||
// Hardware pin definitions.
|
||||
// All momentary on switches are initialized PULLUP
|
||||
const uint8_t UP_PIN = 2;
|
||||
const uint8_t RIGHT_PIN = 3;
|
||||
const uint8_t DOWN_PIN = 4;
|
||||
const uint8_t LEFT_PIN = 5;
|
||||
const uint8_t SELECT_PIN = 6;
|
||||
const uint8_t ENTER_PIN = 7;
|
||||
const uint8_t BEEPER_PIN = 9;
|
||||
|
||||
// Define the number of devices in the chain and the SPI hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 12; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// SPI hardware interface
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
// Arbitrary pins
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
|
||||
uint16_t FIELD_TOP, FIELD_RIGHT; // needs to be initialized in setup()
|
||||
const uint16_t FIELD_LEFT = 0;
|
||||
|
||||
const uint8_t BRICK_SIZE_DEFAULT = 3;
|
||||
const uint8_t BAT_SIZE_DEFAULT = 3; // must be an odd number
|
||||
const uint8_t BAT_EDGE_OFFSET = 1;
|
||||
|
||||
const char TITLE_TEXT[] = "METEOR";
|
||||
const uint16_t SPLASH_DELAY = 3000; // in milliseconds
|
||||
|
||||
const char GAME_TEXT[] = "GAME";
|
||||
const char OVER_TEXT[] = "OVER";
|
||||
const uint16_t GAME_OVER_DELAY = 3000; // in milliseconds
|
||||
|
||||
const uint8_t FONT_NUM_WIDTH = 3;
|
||||
const uint16_t MAX_SCORE = 999;
|
||||
const uint16_t START_SCORE = 25;
|
||||
|
||||
const uint8_t ROID_BIG_SIZE = 4;
|
||||
const uint8_t ROID_MID_SIZE = 2;
|
||||
const uint8_t ROID_SML_SIZE = 1;
|
||||
|
||||
const uint8_t MAX_BULLETS = 5;
|
||||
|
||||
// A class to encapsulate the shooter
|
||||
// Shooter is at the bottom of the display shooting bullets upwards
|
||||
class cGun
|
||||
{
|
||||
private:
|
||||
uint16_t _x, _y; // the position of the tip of the gun
|
||||
uint16_t _xmin, _xmax; // the max and min gun boundaries
|
||||
uint8_t _pinLeft; // the pin for the left switch
|
||||
uint8_t _pinRight; // the pin for the right switch
|
||||
uint16_t _moveDelay; // the delay between possible moves of the gun in milliseconds
|
||||
uint32_t _timeLastMove; // the millis() value for the last time we moved the gun
|
||||
|
||||
public:
|
||||
void begin(uint16_t x, uint16_t y, uint16_t xmin, uint16_t xmax, uint8_t pinL, uint8_t pinR)
|
||||
{
|
||||
_x = x;
|
||||
_y = y;
|
||||
_xmin = xmin;
|
||||
_xmax = xmax;
|
||||
_pinLeft = pinL;
|
||||
_pinRight = pinR;
|
||||
_moveDelay = 25;
|
||||
pinMode(_pinLeft, INPUT_PULLUP);
|
||||
pinMode(_pinRight, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
uint16_t getX(void) { return (_x); }
|
||||
uint16_t getY(void) { return (_y); }
|
||||
|
||||
void draw(void) { mp.setPoint(_x, _y, true); mp.drawHLine(_y-1, _x-1, _x+1, true); }
|
||||
void erase(void) { mp.setPoint(_x, _y, false); mp.drawHLine(_y-1, _x-1, _x+1, false); }
|
||||
|
||||
bool anyKey(void) { return(digitalRead(_pinLeft) == LOW || digitalRead(_pinRight) == LOW); }
|
||||
|
||||
bool move(void)
|
||||
{
|
||||
bool moveLeft = (digitalRead(_pinLeft) == LOW);
|
||||
bool moveRight = (digitalRead(_pinRight) == LOW);
|
||||
|
||||
// if this is the time for a move?
|
||||
if (millis() - _timeLastMove <= _moveDelay)
|
||||
return(false);
|
||||
_timeLastMove = millis();
|
||||
|
||||
mp.update(false);
|
||||
|
||||
if (moveRight)
|
||||
{
|
||||
//PRINTS("\n-- GUN move right");
|
||||
erase();
|
||||
_x++;
|
||||
if (_x + 1 > _xmax) _x--; // keep within right boundary
|
||||
draw();
|
||||
}
|
||||
else if (moveLeft)
|
||||
{
|
||||
//PRINTS("\n-- GUN move left");
|
||||
erase();
|
||||
_x--;
|
||||
if (_x - 1 < _xmin) _x++; // keep within left boundary
|
||||
draw();
|
||||
}
|
||||
|
||||
mp.update(true);
|
||||
|
||||
return((moveLeft || moveRight));
|
||||
}
|
||||
|
||||
bool checkHits(uint16_t x, uint16_t y)
|
||||
{
|
||||
bool b = false;
|
||||
|
||||
if (_y >= y)
|
||||
b = (_x == x) || ((y == _y - 1) && (x == _x - 1 || x == _x + 1));
|
||||
|
||||
return(b);
|
||||
}
|
||||
};
|
||||
|
||||
// A class to encapsulate all the bullets
|
||||
// Bullets are fired from the gun and move straight upwards
|
||||
class cBullet
|
||||
{
|
||||
private:
|
||||
typedef struct bullet_t
|
||||
{
|
||||
uint16_t x, y; // the position of the center of the bullet
|
||||
uint32_t timeLastMove; // time bullet last moved
|
||||
bullet_t *next; // next in the list
|
||||
};
|
||||
|
||||
uint8_t _pinShoot; // the shooting switch pin
|
||||
uint16_t _ymin, _ymax; // maximum bounds for the bullet
|
||||
uint32_t _moveDelay; // delay between bullets moves in milliseconds
|
||||
uint16_t _shootDelay; // the delay between shots in milliseconds
|
||||
uint32_t _timeLastShoot; // the last time the gun was shot in milliseconds
|
||||
bullet_t *_bullets; // the queue of bullets being fired
|
||||
bullet_t *_deleted; // delete bullets list
|
||||
bullet_t *_pScan; // for getFirst(), getNext()
|
||||
uint8_t _count; // count the number of bullets
|
||||
|
||||
bullet_t *add(uint16_t x, uint16_t y)
|
||||
{
|
||||
bullet_t* pb = nullptr;
|
||||
|
||||
if (_count < MAX_BULLETS)
|
||||
{
|
||||
if (_deleted == nullptr)
|
||||
{
|
||||
pb = new bullet_t;
|
||||
PRINTS(" new");
|
||||
}
|
||||
else
|
||||
{
|
||||
// take one from the front of the deleted queue
|
||||
pb = _deleted;
|
||||
_deleted = _deleted->next;
|
||||
PRINTS(" recyle");
|
||||
}
|
||||
|
||||
// save the data
|
||||
if (pb != nullptr)
|
||||
{
|
||||
_count++;
|
||||
pb->x = x;
|
||||
pb->y = y;
|
||||
pb->timeLastMove = 0;
|
||||
pb->next = _bullets;
|
||||
_bullets = pb;
|
||||
}
|
||||
}
|
||||
return(pb);
|
||||
}
|
||||
|
||||
void del(bullet_t *pbPrev, bullet_t * pb)
|
||||
{
|
||||
if (pbPrev == nullptr)
|
||||
_bullets = pb->next;
|
||||
else
|
||||
pbPrev->next = pb->next;
|
||||
_count--;
|
||||
|
||||
// save to the front of the deleted list
|
||||
pb->next = _deleted;
|
||||
_deleted = pb;
|
||||
}
|
||||
|
||||
public:
|
||||
void begin(uint16_t ymin, uint16_t ymax, uint8_t pinShoot)
|
||||
{
|
||||
_moveDelay = 50;
|
||||
_shootDelay = _moveDelay * 3;
|
||||
_ymin = ymin;
|
||||
_ymax = ymax;
|
||||
_deleted = _bullets = nullptr;
|
||||
_pinShoot = pinShoot;
|
||||
_count = 0;
|
||||
|
||||
pinMode(_pinShoot, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
bool getFirstXY(uint16_t &x, uint16_t &y)
|
||||
{
|
||||
_pScan = _bullets;
|
||||
if (_pScan == nullptr)
|
||||
return(false);
|
||||
|
||||
x = _pScan->x;
|
||||
y = _pScan->y;
|
||||
_pScan = _pScan->next;
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool getNextXY(uint16_t &x, uint16_t &y)
|
||||
{
|
||||
if (_pScan == nullptr)
|
||||
return(false);
|
||||
|
||||
x = _pScan->x;
|
||||
y = _pScan->y;
|
||||
_pScan = _pScan->next;
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
void draw(bullet_t *pb) { if (pb != nullptr) mp.setPoint(pb->x, pb->y, true); } //PRINTXY("\nbullet @", pb->x, pb->y); }
|
||||
void erase(bullet_t *pb) { if (pb != nullptr) mp.setPoint(pb->x, pb->y, false); }
|
||||
|
||||
void reset(void) { while (_bullets != nullptr) { erase(_bullets); del(nullptr, _bullets); }}
|
||||
bool empty(void) { return (_bullets == nullptr); }
|
||||
|
||||
void kill(uint16_t x, uint16_t y)
|
||||
// search for and kill the bullet at (x, y)
|
||||
{
|
||||
bullet_t *pb = _bullets;
|
||||
bullet_t *pbPrev = nullptr;
|
||||
|
||||
while (pb!= nullptr)
|
||||
{
|
||||
if (pb->x == x && pb->y == y)
|
||||
{
|
||||
erase(pb);
|
||||
del(pbPrev, pb);
|
||||
break; // found it, no need to look further
|
||||
}
|
||||
// advance pointers
|
||||
pbPrev = pb;
|
||||
pb = pb->next;
|
||||
}
|
||||
}
|
||||
|
||||
void move(void)
|
||||
{
|
||||
bullet_t *pb = _bullets;
|
||||
bullet_t *pbPrev = nullptr;
|
||||
|
||||
mp.update(false);
|
||||
while (pb != nullptr)
|
||||
{
|
||||
// is this the time for a move?
|
||||
if (millis() - pb->timeLastMove >= _moveDelay)
|
||||
{
|
||||
pb->timeLastMove = millis();
|
||||
|
||||
// do the animation
|
||||
erase(pb);
|
||||
pb->y++;
|
||||
|
||||
if (pb->y >= FIELD_TOP)
|
||||
{
|
||||
PRINTS("\n-- BULLET ending");
|
||||
del(pbPrev, pb);
|
||||
pb = pbPrev; // we have just deleted pb!
|
||||
}
|
||||
else
|
||||
draw(pb);
|
||||
}
|
||||
// advance pointers
|
||||
pbPrev = pb;
|
||||
if (pb != nullptr) pb = pb->next;
|
||||
}
|
||||
mp.update(true);
|
||||
}
|
||||
|
||||
bool shoot(uint16_t x, uint16_t y)
|
||||
// shoot the next bullet if switch is pressed
|
||||
{
|
||||
bool b = false;
|
||||
|
||||
// if this is the time for a move?
|
||||
if (millis() - _timeLastShoot <= _shootDelay)
|
||||
return(b);
|
||||
_timeLastShoot = millis();
|
||||
|
||||
// now check if the switch is pressed
|
||||
if (digitalRead(_pinShoot) == LOW)
|
||||
{
|
||||
PRINTS("\n-- BULLET shoot");
|
||||
b = (add(x, y) != nullptr);
|
||||
}
|
||||
|
||||
return(b);
|
||||
}
|
||||
};
|
||||
|
||||
// A class to encapsulate all the meteors
|
||||
class cMeteors
|
||||
{
|
||||
private:
|
||||
typedef struct meteor_t
|
||||
{
|
||||
// NOTE - these are uint8_t to save RAM
|
||||
uint8_t x, y; // lower tip coordinate for this meteor
|
||||
uint8_t dx, dy; // speed in the x, y direction
|
||||
uint8_t size; // size of the meteor (BIG, MID, SML)
|
||||
uint32_t timeLastMove;// when this meteor last moved
|
||||
meteor_t *next; // the next in the linked list
|
||||
};
|
||||
|
||||
uint32_t _timeTick; // base time multiplied by size
|
||||
uint32_t _timeLastBorn; // last time an meteor was born
|
||||
uint32_t _timeGestation; // time between meteors being born
|
||||
meteor_t *_meteors; // start of the list
|
||||
meteor_t *_deleted; // the deleted list
|
||||
meteor_t *_pScan; // for getFirst(), getNext()
|
||||
|
||||
void drawBig(meteor_t *pa, bool b)
|
||||
{
|
||||
mp.setPoint(pa->x, pa->y, b);
|
||||
if ((pa->y + 1) < FIELD_TOP)
|
||||
{
|
||||
mp.drawHLine(pa->y + 1, pa->x - 1, pa->x + 1, b);
|
||||
if ((pa->y + 2) < FIELD_TOP)
|
||||
mp.setPoint(pa->x, pa->y + 2, b);
|
||||
}
|
||||
}
|
||||
|
||||
void drawMid(meteor_t *pa, bool b)
|
||||
{
|
||||
mp.setPoint(pa->x, pa->y, b);
|
||||
if ((pa->y + 1) < FIELD_TOP)
|
||||
{
|
||||
mp.setPoint(pa->x, pa->y + 1, b);
|
||||
}
|
||||
}
|
||||
|
||||
void drawSml(meteor_t *pa, bool b)
|
||||
{
|
||||
mp.setPoint(pa->x, pa->y, b);
|
||||
}
|
||||
|
||||
void draw(meteor_t *pa)
|
||||
{
|
||||
//PRINTXY("\nmeteor @", pa->x, pa->y);
|
||||
if (pa == nullptr) return;
|
||||
|
||||
switch (pa->size)
|
||||
{
|
||||
case ROID_BIG_SIZE: drawBig(pa, true); break;
|
||||
case ROID_MID_SIZE: drawMid(pa, true); break;
|
||||
case ROID_SML_SIZE: drawSml(pa, true); break;
|
||||
}
|
||||
}
|
||||
|
||||
void erase(meteor_t *pa)
|
||||
{
|
||||
if (pa == nullptr) return;
|
||||
|
||||
switch (pa->size)
|
||||
{
|
||||
case ROID_BIG_SIZE: drawBig(pa, false); break;
|
||||
case ROID_MID_SIZE: drawMid(pa, false); break;
|
||||
case ROID_SML_SIZE: drawSml(pa, false); break;
|
||||
}
|
||||
}
|
||||
|
||||
meteor_t *add(uint16_t x, uint16_t y)
|
||||
// add a brick to the start of the list
|
||||
{
|
||||
meteor_t *pa = nullptr;
|
||||
|
||||
if (_deleted == nullptr)
|
||||
{
|
||||
pa = new meteor_t;
|
||||
PRINTS("new");
|
||||
}
|
||||
else
|
||||
{
|
||||
// take from the front of the deleted list
|
||||
pa = _deleted;
|
||||
_deleted = _deleted->next;
|
||||
PRINTS("recyle");
|
||||
}
|
||||
|
||||
if (pa != nullptr)
|
||||
{
|
||||
pa->x = x;
|
||||
pa->y = y;
|
||||
pa->dx = 0;
|
||||
pa->dy = -1;
|
||||
pa->size = ROID_BIG_SIZE;
|
||||
pa->timeLastMove = 0;
|
||||
|
||||
pa->next = _meteors;
|
||||
_meteors = pa;
|
||||
}
|
||||
|
||||
return(pa);
|
||||
}
|
||||
|
||||
void del(meteor_t *paPrev, meteor_t *pa)
|
||||
// delete the specified brick from the list
|
||||
{
|
||||
if (paPrev == nullptr)
|
||||
_meteors = pa->next;
|
||||
else
|
||||
paPrev->next = pa->next;
|
||||
|
||||
// save to the front of the deleted list
|
||||
pa->next = _deleted;
|
||||
_deleted = pa;
|
||||
}
|
||||
|
||||
bool generate(void)
|
||||
{
|
||||
if (millis() - _timeLastBorn < _timeGestation)
|
||||
return(false);;
|
||||
_timeLastBorn = millis();
|
||||
|
||||
if (_timeGestation > 500) _timeGestation -= 10; // speed up the number born over the game duration
|
||||
|
||||
{
|
||||
uint16_t x = FIELD_LEFT + 2 + random(FIELD_RIGHT - FIELD_LEFT - 4);
|
||||
uint16_t y = FIELD_TOP - 1;
|
||||
PRINTXY("\n-- ASTEROID create @", x, y);
|
||||
add(x, y);
|
||||
}
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
public:
|
||||
void begin()
|
||||
{
|
||||
_meteors = _deleted = nullptr;
|
||||
_timeTick = 250;
|
||||
_timeGestation = 5000;
|
||||
}
|
||||
|
||||
bool emptyField(void) { return(_meteors == nullptr); }
|
||||
void drawField(void) { for (meteor_t *pa = _meteors; pa != nullptr; pa = pa->next) draw(pa); }
|
||||
void eraseField(void) { while (_meteors != nullptr) { erase(_meteors); del(nullptr, _meteors); } }
|
||||
|
||||
bool getFirstXY(uint16_t &x, uint16_t &y)
|
||||
{
|
||||
_pScan = _meteors;
|
||||
if (_pScan == nullptr)
|
||||
return(false);
|
||||
|
||||
x = _pScan->x;
|
||||
y = _pScan->y;
|
||||
_pScan = _pScan->next;
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool getNextXY(uint16_t &x, uint16_t &y)
|
||||
{
|
||||
if (_pScan == nullptr)
|
||||
return(false);
|
||||
|
||||
x = _pScan->x;
|
||||
y = _pScan->y;
|
||||
_pScan = _pScan->next;
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
void move(void)
|
||||
{
|
||||
meteor_t *pa = _meteors;
|
||||
meteor_t *paPrev = nullptr;
|
||||
|
||||
mp.update(false);
|
||||
|
||||
generate(); // create a new one if time to do so
|
||||
|
||||
while (pa != nullptr)
|
||||
{
|
||||
// is this the time for a move?
|
||||
if (millis() - pa->timeLastMove >= _timeTick * pa->size)
|
||||
{
|
||||
pa->timeLastMove = millis();
|
||||
|
||||
// do the animation
|
||||
erase(pa);
|
||||
|
||||
// y==0 neede to persist after the increment so the collision code can find it.
|
||||
// Time to deletw it now before calculating moves.
|
||||
if (pa->y == 0)
|
||||
{
|
||||
PRINTS("\n-- ASTEROID ending bottom");
|
||||
del(paPrev, pa);
|
||||
pa = paPrev; // we have just deleted pa!
|
||||
}
|
||||
else
|
||||
{
|
||||
pa->y += pa->dy;
|
||||
pa->x += pa->dx;
|
||||
|
||||
if (pa->x <= FIELD_LEFT || pa->x >= FIELD_RIGHT)
|
||||
{
|
||||
PRINTS("\n-- ASTEROID ending side");
|
||||
del(paPrev, pa);
|
||||
pa = paPrev; // we have just deleted pa!
|
||||
}
|
||||
else
|
||||
draw(pa);
|
||||
}
|
||||
}
|
||||
// advance pointers
|
||||
paPrev = pa;
|
||||
if (pa != nullptr) pa = pa->next;
|
||||
}
|
||||
|
||||
mp.update(true);
|
||||
}
|
||||
|
||||
uint8_t checkHits(uint16_t x, uint16_t y)
|
||||
// Returns the points for the meteor hit
|
||||
{
|
||||
meteor_t *pa = _meteors;
|
||||
meteor_t *paPrev = nullptr;
|
||||
meteor_t *paNew;
|
||||
uint8_t points = 0;
|
||||
bool notFound = true;
|
||||
|
||||
//PRINTXY("\n--- CHECKHITS for ", x, y);
|
||||
|
||||
while (pa != nullptr && notFound)
|
||||
{
|
||||
switch (pa->size)
|
||||
{
|
||||
case ROID_SML_SIZE: // front on point hit only
|
||||
if (pa->x == x && pa->y == y)
|
||||
{
|
||||
//PRINTXY("\n-- ASTEROID SML hit @", pa->x, pa->y);
|
||||
// smallest one just gets deleted
|
||||
erase(pa);
|
||||
del(paPrev, pa);
|
||||
points = 4;
|
||||
notFound = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ROID_MID_SIZE: // front on point hit only
|
||||
if (pa->x == x && pa->y == y)
|
||||
{
|
||||
//PRINTXY("\n-- ASTEROID MID hit @", pa->x, pa->y);
|
||||
// needs to be split
|
||||
erase(pa);
|
||||
paNew = add(pa->x, pa->y);
|
||||
pa->dx = -1;
|
||||
paNew->dx = 1;
|
||||
pa->size = paNew->size = ROID_SML_SIZE;
|
||||
draw(pa);
|
||||
draw(paNew);
|
||||
points = 2;
|
||||
notFound = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ROID_BIG_SIZE: // front on point and either side hits
|
||||
if ((pa->x == x && pa->y == y) ||
|
||||
((pa->x - 1 == x || pa->x + 1 == x) && (pa->y + 1 == y)))
|
||||
{
|
||||
//PRINTXY("\n-- ASTEROID BIG hit @", pa->x, pa->y);
|
||||
// needs to be split
|
||||
erase(pa);
|
||||
paNew = add(pa->x, pa->y);
|
||||
pa->dx = -1;
|
||||
paNew->dx = 1;
|
||||
pa->size = paNew->size = ROID_MID_SIZE;
|
||||
draw(pa);
|
||||
draw(paNew);
|
||||
points = 1;
|
||||
notFound = false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
PRINT("\n--- CHECKHIT UNKNOWN size=", pa->size);
|
||||
break;
|
||||
}
|
||||
|
||||
// advance the pointers
|
||||
paPrev = pa;
|
||||
if (pa != nullptr) pa = pa->next;
|
||||
}
|
||||
|
||||
return(points);
|
||||
}
|
||||
};
|
||||
|
||||
// main objects coordinated by the code logic
|
||||
cScore score;
|
||||
cGun gun;
|
||||
cBullet bullets;
|
||||
cMeteors meteors;
|
||||
cSound sound;
|
||||
|
||||
void setupField(void)
|
||||
// Draw the playing field at the start of the game.
|
||||
{
|
||||
mp.clear();
|
||||
mp.drawHLine(FIELD_TOP, FIELD_LEFT, FIELD_RIGHT);
|
||||
mp.drawVLine(FIELD_LEFT, 0, FIELD_TOP);
|
||||
mp.drawVLine(FIELD_RIGHT, 0, FIELD_TOP);
|
||||
gun.draw();
|
||||
score.draw();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
#if DEBUG
|
||||
Serial.begin(57600);
|
||||
#endif
|
||||
PRINTS("\n[MD_MAXPanel_Bricks]");
|
||||
|
||||
randomSeed(seedOut(31, RANDOM_SEED_PORT));
|
||||
|
||||
mp.begin();
|
||||
mp.setFont(_Fixed_5x3);
|
||||
mp.setIntensity(4);
|
||||
//mp.setRotation(MD_MAXPanel::ROT_90);
|
||||
|
||||
// one time initialization
|
||||
FIELD_TOP = mp.getYMax() - mp.getFontHeight() - 2;
|
||||
FIELD_RIGHT = mp.getXMax();
|
||||
|
||||
gun.begin((FIELD_RIGHT - FIELD_LEFT) / 2, BAT_EDGE_OFFSET, FIELD_LEFT + 1, FIELD_RIGHT - 1, LEFT_PIN, RIGHT_PIN);
|
||||
bullets.begin(1, FIELD_TOP-1, SELECT_PIN);
|
||||
score.limit(MAX_SCORE); // set width() so we can use it below
|
||||
score.begin(&mp, FIELD_RIGHT - (score.width() * (FONT_NUM_WIDTH + mp.getCharSpacing())) + mp.getCharSpacing(), FIELD_TOP + 1 + mp.getFontHeight(), MAX_SCORE);
|
||||
sound.begin(BEEPER_PIN);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static enum { S_SPLASH, S_INIT, S_WAIT_START, S_GAME_PLAY, S_GAME_OVER } runState = S_SPLASH;
|
||||
|
||||
switch (runState)
|
||||
{
|
||||
case S_SPLASH: // show splash screen at start
|
||||
PRINTSTATE("SPLASH");
|
||||
{
|
||||
const uint16_t border = 2;
|
||||
|
||||
mp.clear();
|
||||
mp.drawRectangle(border, border, mp.getXMax() - border, mp.getYMax() - border);
|
||||
mp.drawLine(0, 0, border, border);
|
||||
mp.drawLine(0, mp.getYMax(), border, mp.getYMax() - border);
|
||||
mp.drawLine(mp.getXMax(), 0, mp.getXMax() - border, border);
|
||||
mp.drawLine(mp.getXMax(), mp.getYMax(), mp.getXMax() - border, mp.getYMax() - border);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(TITLE_TEXT)) / 2, (mp.getYMax() + mp.getFontHeight()) / 2, TITLE_TEXT);
|
||||
sound.splash();
|
||||
delay(SPLASH_DELAY);
|
||||
|
||||
runState = S_INIT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_INIT: // initialize for a new game
|
||||
PRINTSTATE("INIT");
|
||||
bullets.reset();
|
||||
score.set(START_SCORE);
|
||||
setupField();
|
||||
meteors.begin();
|
||||
|
||||
runState = S_WAIT_START;
|
||||
PRINTSTATE("WAIT_START");
|
||||
break;
|
||||
|
||||
case S_WAIT_START: // waiting for the start of a new game
|
||||
if (gun.anyKey())
|
||||
{
|
||||
PRINTS("\n-- Starting Game");
|
||||
sound.start();
|
||||
runState = S_GAME_PLAY;
|
||||
PRINTSTATE("GAME_PLAY");
|
||||
}
|
||||
break;
|
||||
|
||||
case S_GAME_PLAY: // playing a point
|
||||
// handle shooting first
|
||||
if (score.score() != 0) // we have some bullets ...
|
||||
if (bullets.shoot(gun.getX(), gun.getY() + 1)) // ... and we can shoot ...
|
||||
{
|
||||
score.decrement(); // ... then we have one bullet less
|
||||
sound.hit();
|
||||
}
|
||||
|
||||
// then run animations
|
||||
gun.move();
|
||||
meteors.move();
|
||||
bullets.move();
|
||||
|
||||
// now check for bullet collisions
|
||||
{
|
||||
uint16_t x, y;
|
||||
|
||||
//PRINTS("\n--- CHECK bullets collisions");
|
||||
if (bullets.getFirstXY(x, y))
|
||||
{
|
||||
do
|
||||
{
|
||||
uint8_t points = meteors.checkHits(x, y);
|
||||
|
||||
if (points != 0)
|
||||
{
|
||||
//PRINT(", hit=", points);
|
||||
bullets.kill(x, y); // bullet is now dead
|
||||
//PRINTS(", kill");
|
||||
sound.bounce();
|
||||
//PRINTS(", sound");
|
||||
score.increment(points);
|
||||
//PRINTS(", score");
|
||||
}
|
||||
} while (bullets.getNextXY(x, y));
|
||||
}
|
||||
|
||||
// now check for gun collisions
|
||||
//PRINTS("\n--- CHECK guns collisions");
|
||||
if (meteors.getFirstXY(x, y))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (gun.checkHits(x, y))
|
||||
runState = S_GAME_OVER;
|
||||
} while (meteors.getNextXY(x, y) && runState != S_GAME_OVER);
|
||||
}
|
||||
}
|
||||
//PRINTS("\n--- CHECK collisions done");
|
||||
|
||||
// finally, have we reached the end of the line?
|
||||
if (bullets.empty() && score.score() == 0)
|
||||
runState = S_GAME_OVER;
|
||||
break;
|
||||
|
||||
case S_GAME_OVER:
|
||||
PRINTSTATE("GAME_OVER");
|
||||
meteors.eraseField();
|
||||
bullets.reset();
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(GAME_TEXT)) / 2, FIELD_TOP / 2 + mp.getFontHeight() + 1, GAME_TEXT);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(OVER_TEXT)) / 2, FIELD_TOP / 2 - 1, OVER_TEXT);
|
||||
|
||||
sound.over();
|
||||
delay(GAME_OVER_DELAY);
|
||||
runState = S_INIT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
// Random seed creation --------------------------
|
||||
// Adapted from http://www.utopiamechanicus.com/article/arduino-better-random-numbers/
|
||||
|
||||
const uint8_t RANDOM_SEED_PORT = A3; // port read for random seed
|
||||
|
||||
uint16_t bitOut(uint8_t port)
|
||||
{
|
||||
static bool firstTime = true;
|
||||
uint32_t prev = 0;
|
||||
uint32_t bit1 = 0, bit0 = 0;
|
||||
uint32_t x = 0, limit = 99;
|
||||
|
||||
if (firstTime)
|
||||
{
|
||||
firstTime = false;
|
||||
prev = analogRead(port);
|
||||
}
|
||||
|
||||
while (limit--)
|
||||
{
|
||||
x = analogRead(port);
|
||||
bit1 = (prev != x ? 1 : 0);
|
||||
prev = x;
|
||||
x = analogRead(port);
|
||||
bit0 = (prev != x ? 1 : 0);
|
||||
prev = x;
|
||||
if (bit1 != bit0)
|
||||
break;
|
||||
}
|
||||
|
||||
return(bit1);
|
||||
}
|
||||
|
||||
uint32_t seedOut(uint16_t noOfBits, uint8_t port)
|
||||
{
|
||||
// return value with 'noOfBits' random bits set
|
||||
uint32_t seed = 0;
|
||||
|
||||
for (int i = 0; i<noOfBits; ++i)
|
||||
seed = (seed << 1) | bitOut(port);
|
||||
|
||||
return(seed);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
|
||||
// A class to encapsulate the score display
|
||||
class cScore
|
||||
{
|
||||
private:
|
||||
MD_MAXPanel *_mp; // the MAXPanel object for drawing the score
|
||||
uint16_t _score; // the score
|
||||
uint16_t _x, _y; // coordinate of top left for display
|
||||
uint8_t _width; // number of digits wide
|
||||
uint16_t _limit; // maximum value allowed
|
||||
|
||||
public:
|
||||
void begin(MD_MAXPanel *mp, uint16_t x, uint16_t y, uint16_t maxScore) { _mp = mp; _x = x, _y = y; limit(maxScore); reset(); }
|
||||
void reset(void) { erase(); _score = 0; draw(); }
|
||||
void set(uint16_t s) { if (s <= _limit) { erase(); _score = s; draw(); } }
|
||||
void increment(uint16_t inc = 1) { if (_score + inc <= _limit) { erase(); _score += inc; draw(); } }
|
||||
void decrement(uint16_t dec = 1) { if (_score >= dec) { erase(); _score -= dec; draw(); } }
|
||||
uint16_t score(void) { return(_score); }
|
||||
void erase(void) { draw(false); }
|
||||
uint16_t width(void) { return(_width); }
|
||||
|
||||
void limit(uint16_t m)
|
||||
{
|
||||
erase(); // width may change, so delete with the curret parameters
|
||||
|
||||
_limit = m;
|
||||
// work out how many digits this is
|
||||
_width = 0;
|
||||
do
|
||||
{
|
||||
_width++;
|
||||
m /= 10;
|
||||
} while (m != 0);
|
||||
}
|
||||
|
||||
void draw(bool state = true)
|
||||
{
|
||||
char sz[_width + 1];
|
||||
uint16_t s = _score;
|
||||
|
||||
// PRINT("\n-- SCORE: ", _score);
|
||||
sz[_width] = '\0';
|
||||
for (int i = _width - 1; i >= 0; --i)
|
||||
{
|
||||
sz[i] = (s % 10) + '0';
|
||||
s /= 10;
|
||||
}
|
||||
|
||||
_mp->drawText(_x, _y, sz, MD_MAXPanel::ROT_0, state);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
// A class to encapsulate primitive sound effects
|
||||
class cSound
|
||||
{
|
||||
private:
|
||||
const uint16_t EOD = 0; // End Of Data marker
|
||||
uint8_t _pinBeep; // the pin to use for beeping
|
||||
|
||||
// Sound data - frequency followed by duration in pairs.
|
||||
// Data ends in End Of Data marker EOD.
|
||||
const uint16_t soundSplash[1] PROGMEM = { EOD };
|
||||
const uint16_t soundHit[3] PROGMEM = { 1000, 50, EOD };
|
||||
const uint16_t soundBounce[3] PROGMEM = { 500, 50, EOD };
|
||||
const uint16_t soundPoint[3] PROGMEM = { 150, 150, EOD };
|
||||
const uint16_t soundStart[7] PROGMEM = { 250, 100, 500, 100, 1000, 100, EOD };
|
||||
const uint16_t soundOver[7] PROGMEM = { 1000, 100, 500, 100, 250, 100, EOD };
|
||||
|
||||
void playSound(const uint16_t *table)
|
||||
// Play sound table data. Data table must end in EOD marker.
|
||||
{
|
||||
uint8_t idx = 0;
|
||||
|
||||
//PRINTS("\nTone Data ");
|
||||
while (table[idx] != EOD)
|
||||
{
|
||||
uint16_t t = table[idx++];
|
||||
uint16_t d = table[idx++];
|
||||
|
||||
//PRINTXY("-", t, d);
|
||||
tone(_pinBeep, t);
|
||||
delay(d);
|
||||
}
|
||||
//PRINTS("-EOD");
|
||||
noTone(_pinBeep); // be quiet now!
|
||||
}
|
||||
|
||||
public:
|
||||
void begin(uint8_t pinBeep) { _pinBeep = pinBeep; }
|
||||
void splash(void) { playSound(soundSplash); }
|
||||
void start(void) { playSound(soundStart); }
|
||||
void hit(void) { playSound(soundHit); }
|
||||
void bounce(void) { playSound(soundBounce); }
|
||||
void point(void) { playSound(soundPoint); }
|
||||
void over(void) { playSound(soundOver); }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
MD_MAX72XX::fontType_t _Fixed_5x3[] PROGMEM = {
|
||||
'F', 1, 32, 127, 5,
|
||||
2, 0, 0, // 32 - 'Space'
|
||||
1, 23, // 33 - '!'
|
||||
3, 3, 0, 3, // 34 - '"'
|
||||
3, 31, 10, 31, // 35 - '#'
|
||||
3, 22, 31, 13, // 36 - '$'
|
||||
3, 9, 4, 18, // 37 - '%'
|
||||
3, 10, 21, 26, // 38 - '&'
|
||||
1, 3, // 39
|
||||
2, 14, 17, // 40 - '('
|
||||
2, 17, 14, // 41 - ')'
|
||||
3, 10, 4, 10, // 42 - '*'
|
||||
3, 4, 14, 4, // 43 - '+'
|
||||
2, 16, 8, // 44 - ','
|
||||
3, 4, 4, 4, // 45 - '-'
|
||||
1, 16, // 46 - '.'
|
||||
3, 8, 4, 2, // 47 - '/'
|
||||
3, 31, 17, 31, // 48 - '0'
|
||||
2, 0, 31, // 49 - '1'
|
||||
3, 29, 21, 23, // 50 - '2'
|
||||
3, 17, 21, 31, // 51 - '3'
|
||||
3, 7, 4, 31, // 52 - '4'
|
||||
3, 23, 21, 29, // 53 - '5'
|
||||
3, 31, 21, 29, // 54 - '6'
|
||||
3, 1, 1, 31, // 55 - '7'
|
||||
3, 31, 21, 31, // 56 - '8'
|
||||
3, 23, 21, 31, // 57 - '9'
|
||||
1, 10, // 58 - ':'
|
||||
2, 16, 10, // 59 - ';'
|
||||
3, 4, 10, 17, // 60 - '<'
|
||||
3, 10, 10, 10, // 61 - '='
|
||||
3, 17, 10, 4, // 62 - '>'
|
||||
3, 1, 21, 3, // 63 - '?'
|
||||
3, 14, 21, 22, // 64 - '@'
|
||||
3, 30, 5, 30, // 65 - 'A'
|
||||
3, 31, 21, 10, // 66 - 'B'
|
||||
3, 14, 17, 17, // 67 - 'C'
|
||||
3, 31, 17, 14, // 68 - 'D'
|
||||
3, 31, 21, 17, // 69 - 'E'
|
||||
3, 31, 5, 1, // 70 - 'F'
|
||||
3, 14, 17, 29, // 71 - 'G'
|
||||
3, 31, 4, 31, // 72 - 'H'
|
||||
3, 17, 31, 17, // 73 - 'I'
|
||||
3, 8, 16, 15, // 74 - 'J'
|
||||
3, 31, 4, 27, // 75 - 'K'
|
||||
3, 31, 16, 16, // 76 - 'L'
|
||||
3, 31, 2, 31, // 77 - 'M'
|
||||
3, 31, 14, 31, // 78 - 'N'
|
||||
3, 14, 17, 14, // 79 - 'O'
|
||||
3, 31, 5, 2, // 80 - 'P'
|
||||
3, 14, 25, 30, // 81 - 'Q'
|
||||
3, 31, 5, 26, // 82 - 'R'
|
||||
3, 18, 21, 9, // 83 - 'S'
|
||||
3, 1, 31, 1, // 84 - 'T'
|
||||
3, 15, 16, 15, // 85 - 'U'
|
||||
3, 7, 24, 7, // 86 - 'V'
|
||||
3, 15, 28, 15, // 87 - 'W'
|
||||
3, 27, 4, 27, // 88 - 'X'
|
||||
3, 3, 28, 3, // 89 - 'Y'
|
||||
3, 25, 21, 19, // 90 - 'Z'
|
||||
2, 31, 17, // 91 - '['
|
||||
3, 2, 4, 8, // 92 - '\'
|
||||
2, 17, 31, // 93 - ']'
|
||||
3, 2, 1, 2, // 94 - '^'
|
||||
3, 16, 16, 16, // 95 - '_'
|
||||
2, 1, 2, // 96 - '`'
|
||||
3, 12, 18, 28, // 97 - 'a'
|
||||
3, 31, 18, 12, // 98 - 'b'
|
||||
3, 12, 18, 18, // 99 - 'c'
|
||||
3, 12, 18, 31, // 100 - 'd'
|
||||
3, 12, 26, 20, // 101 - 'e'
|
||||
3, 4, 31, 5, // 102 - 'f'
|
||||
3, 20, 26, 12, // 103 - 'g'
|
||||
3, 31, 2, 28, // 104 - 'h'
|
||||
1, 29, // 105 - 'i'
|
||||
2, 16, 13, // 106 - 'j'
|
||||
3, 31, 8, 20, // 107 - 'k'
|
||||
1, 31, // 108 - 'l'
|
||||
3, 30, 6, 30, // 109 - 'm'
|
||||
3, 30, 2, 28, // 110 - 'n'
|
||||
3, 12, 18, 12, // 111 - 'o'
|
||||
3, 30, 10, 4, // 112 - 'p'
|
||||
3, 4, 10, 30, // 113 - 'q'
|
||||
2, 30, 4, // 114 - 'r'
|
||||
3, 20, 30, 10, // 115 - 's'
|
||||
3, 4, 30, 4, // 116 - 't'
|
||||
3, 14, 16, 30, // 117 - 'u'
|
||||
3, 14, 16, 14, // 118 - 'v'
|
||||
3, 14, 24, 14, // 119 - 'w'
|
||||
3, 18, 12, 18, // 120 - 'x'
|
||||
3, 22, 24, 14, // 121 - 'y'
|
||||
3, 26, 30, 22, // 122 - 'z'
|
||||
3, 4, 27, 17, // 123 - '{'
|
||||
1, 27, // 124 - '|'
|
||||
3, 17, 27, 4, // 125 - '}'
|
||||
3, 6, 2, 3, // 126 - '~'
|
||||
3, 31, 31, 31, // 127 - 'Full Block'
|
||||
};
|
||||
@@ -0,0 +1,466 @@
|
||||
// Implements the game of Pong using MD_MAXPanel library
|
||||
//
|
||||
// Hardware Used
|
||||
// =============
|
||||
// UP_PIN - left bat up switch, INPUT_PULLUP
|
||||
// RIGHT_PIN - right bat up switch, INPUT_PULLUP
|
||||
// RIGHT_PIN - left bat up switch, INPUT_PULLUP
|
||||
// DOWN_PIN - right bat up switch, INPUT_PULLUP
|
||||
// SELECT_PIN - unused
|
||||
// ENTER_PIN - unused
|
||||
// BEEPER_PIN - piezo speaker
|
||||
// CLK_PIN, DATA_PIN, CS_PIN - LED matrix display connections
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
// Rules of the Game
|
||||
// =================
|
||||
// Simple game like table tennis where each player has to bat the ball to keep
|
||||
// it in play. Points awarded when the ball goes out at the opponents side. First
|
||||
// to reach MAX_SCORE is the winner.
|
||||
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
#include "Font5x3.h"
|
||||
#include "score.h"
|
||||
#include "sound.h"
|
||||
|
||||
// Turn on debug statements to the serial output
|
||||
#define DEBUG 0
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
|
||||
#define PRINTS(x) { Serial.print(F(x)); }
|
||||
#define PRINTD(x) { Serial.print(x, DEC); }
|
||||
#define PRINTXY(s, x, y) { Serial.print(s); Serial.print(F("(")); Serial.print(x); Serial.print(F(",")); Serial.print(y); Serial.print(")"); }
|
||||
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTD(x)
|
||||
#define PRINTXY(s, x, y)
|
||||
|
||||
#endif
|
||||
|
||||
// Hardware pin definitions.
|
||||
// All momentary on switches are initialised INPUT_PULLUP
|
||||
const uint8_t UP_PIN = 2;
|
||||
const uint8_t RIGHT_PIN = 3;
|
||||
const uint8_t DOWN_PIN = 4;
|
||||
const uint8_t LEFT_PIN = 5;
|
||||
const uint8_t SELECT_PIN = 6;
|
||||
const uint8_t ENTER_PIN = 7;
|
||||
const uint8_t BEEPER_PIN = 9;
|
||||
|
||||
// Define the number of devices in the chain and the SPI hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 13; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// SPI hardware interface
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
// Arbitrary pins
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
|
||||
uint16_t FIELD_TOP; // needs to be initialised in setup()
|
||||
const uint16_t FIELD_BOTTOM = 0;
|
||||
|
||||
const uint8_t BAT_SIZE_DEFAULT = 3;
|
||||
const uint8_t BAT_EDGE_OFFSET = 1;
|
||||
|
||||
const char TITLE_TEXT[] = "PONG";
|
||||
const uint16_t SPLASH_DELAY = 5000; // in milliseconds
|
||||
|
||||
const char GAME_TEXT[] = "GAME";
|
||||
const char OVER_TEXT[] = "OVER";
|
||||
const uint16_t GAME_OVER_DELAY = 3000; // in milliseconds
|
||||
|
||||
const uint8_t FONT_NUM_WIDTH = 3;
|
||||
const uint8_t MAX_SCORE = 11;
|
||||
|
||||
// A class to encapsulate the pong bat
|
||||
// Bats are used either side of the display, moving up and down
|
||||
class cPongBat
|
||||
{
|
||||
private:
|
||||
uint16_t _x, _y; // the position of the center of the bat
|
||||
uint16_t _ymin, _ymax; // the max and min bat boundaries
|
||||
int8_t _vel; // the velocity of the bat (+1 for moving up, -1 moving down)
|
||||
uint8_t _size; // the size in pixels for the bat (odd number)
|
||||
uint8_t _pinUp; // the pin for the up switch
|
||||
uint8_t _pinDown; // the pin for th down switch
|
||||
uint16_t _batDelay; // the delay between possible moves of the bat in milliseconds
|
||||
uint32_t _timeLastMove; // the millis() value for the last time we moved the bat
|
||||
|
||||
public:
|
||||
enum hitType_t { NO_HIT, CORNER_HIT, FLAT_HIT };
|
||||
|
||||
void begin(uint16_t x, uint16_t y, uint16_t ymin, uint16_t ymax, uint8_t size, uint8_t pinU, uint8_t pinD)
|
||||
{
|
||||
_x = x;
|
||||
_y = y;
|
||||
_ymin = ymin;
|
||||
_ymax = ymax;
|
||||
_vel = 0;
|
||||
_size = size;
|
||||
_pinUp = pinU;
|
||||
_pinDown = pinD;
|
||||
_batDelay = 40;
|
||||
pinMode(_pinUp, INPUT_PULLUP);
|
||||
pinMode(_pinDown, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
uint16_t getX(void) { return (_x); }
|
||||
uint16_t getY(void) { return (_y); }
|
||||
|
||||
int8_t getVelocity(void) { return(_vel); }
|
||||
void draw(void) { mp.drawVLine(_x, _y - (_size / 2), _y + (_size / 2), true); }
|
||||
void erase(void) { mp.drawVLine(_x, _y - (_size / 2), _y + (_size / 2), false); }
|
||||
bool anyKey(void) { return((digitalRead(_pinUp) == LOW) || (digitalRead(_pinDown) == LOW)); }
|
||||
|
||||
hitType_t hit(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
|
||||
{
|
||||
int16_t dy = y1 - y0;
|
||||
|
||||
// if we are not in the same x plane as the bat there can be no hit
|
||||
if (x1 != _x) return(NO_HIT);
|
||||
|
||||
// if the ball is at
|
||||
// - the top of the bat and traveling down, or
|
||||
// - the bottom of the bat and traveling up
|
||||
// then it is a corner hit
|
||||
if ((y1 == _y + (_size / 2) && dy < 0) || (y1 == _y - (_size / 2) && dy>0))
|
||||
return(CORNER_HIT);
|
||||
|
||||
// the ball is between the top and bottom of the bat boundaries inclusive
|
||||
// then it is a flat hit
|
||||
if (y1 <= _y + (_size / 2) && y1 >= _y - (_size / 2))
|
||||
return(FLAT_HIT);
|
||||
|
||||
// and in case we missed something, a default no hit
|
||||
return(NO_HIT);
|
||||
}
|
||||
|
||||
void move(void)
|
||||
{
|
||||
bool moveUp = (digitalRead(_pinUp) == LOW);
|
||||
bool moveDown = (digitalRead(_pinDown) == LOW);
|
||||
|
||||
// if this is the time for a move?
|
||||
if (millis() - _timeLastMove <= _batDelay)
|
||||
return;
|
||||
_timeLastMove = millis();
|
||||
|
||||
mp.update(false);
|
||||
|
||||
if (moveUp)
|
||||
{
|
||||
PRINTS("\n-- BAT move up");
|
||||
erase();
|
||||
_vel = 1;
|
||||
_y++;
|
||||
if (_y + (_size/2) > _ymax) _y--; // keep within top boundary
|
||||
draw();
|
||||
}
|
||||
else if (moveDown)
|
||||
{
|
||||
PRINTS("\n-- BAT move down");
|
||||
erase();
|
||||
_vel = -1;
|
||||
_y--;
|
||||
if (_y - (_size/2) < _ymin) _y++; // keep within bottom boundary
|
||||
draw();
|
||||
}
|
||||
else
|
||||
_vel = 0;
|
||||
|
||||
mp.update(true);
|
||||
}
|
||||
};
|
||||
|
||||
// A class to encapsulate the pong ball
|
||||
// Ball will bounce around. Bounces off the edges and bats.
|
||||
class cPongBall
|
||||
{
|
||||
private:
|
||||
uint16_t _x, _y; // the position of the center of the ball
|
||||
int8_t _dx, _dy; // the offsets for the x and y direction
|
||||
uint32_t _timeLastMove; // last time the ball was moved
|
||||
uint16_t _ballDelay; // the delay between ball moves in milliseconds
|
||||
bool _run; // ball is running when true
|
||||
|
||||
public:
|
||||
enum bounce_t { BOUNCE_NONE, BOUNCE_BACK, BOUNCE_TOP, BOUNCE_BOTTOM, BOUNCE_LEFT, BOUNCE_RIGHT };
|
||||
|
||||
void begin(uint16_t x, uint16_t y)
|
||||
{
|
||||
reset(x, y);
|
||||
_dx = _dy = 1;
|
||||
_timeLastMove = 0;
|
||||
_ballDelay = 100;
|
||||
_run = false;
|
||||
}
|
||||
|
||||
uint16_t getX(void) { return (_x); }
|
||||
uint16_t getY(void) { return (_y); }
|
||||
uint16_t getNextX(void) { return (_x + _dx); }
|
||||
uint16_t getNextY(void) { return (_y + _dy); }
|
||||
|
||||
void start(void) { _run = true; }
|
||||
void stop(void) { _run = false; }
|
||||
void draw(void) { mp.setPoint(_x, _y, true); } //PRINTS("\nball@"); PRINTXY(_x, _y); }
|
||||
void erase(void) { mp.setPoint(_x, _y, false); }
|
||||
void reset(uint16_t x, uint16_t y) { _x = x; _y = y; }
|
||||
|
||||
bool move(void)
|
||||
{
|
||||
// if this is the time for a move?
|
||||
if (_run && millis() - _timeLastMove <= _ballDelay)
|
||||
return(false);
|
||||
_timeLastMove = millis();
|
||||
|
||||
// do the animation
|
||||
mp.update(false);
|
||||
erase();
|
||||
_x = _x + _dx;
|
||||
_y = _y + _dy;
|
||||
draw();
|
||||
mp.update(true);
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
void bounce(bounce_t b)
|
||||
{
|
||||
switch (b)
|
||||
{
|
||||
case BOUNCE_TOP:
|
||||
case BOUNCE_BOTTOM: _dy = -_dy; break;
|
||||
case BOUNCE_LEFT:
|
||||
case BOUNCE_RIGHT: _dx = -_dx; break;
|
||||
case BOUNCE_BACK: _dx = -_dx; _dy = -_dy; break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// main objects coordinated by the code logic
|
||||
cPongBat batL, batR;
|
||||
cScore scoreL, scoreR;
|
||||
cPongBall ball;
|
||||
cSound sound;
|
||||
|
||||
void centerLine(void)
|
||||
// Dotted line down the middle
|
||||
{
|
||||
bool bOn = true;
|
||||
|
||||
mp.update(false);
|
||||
for (uint16_t y = FIELD_BOTTOM + 1; y < FIELD_TOP; y++)
|
||||
{
|
||||
mp.setPoint(mp.getXMax() / 2, y, bOn);
|
||||
mp.setPoint((mp.getXMax() + 1) / 2, y, bOn);
|
||||
bOn = !bOn;
|
||||
}
|
||||
mp.update(true);
|
||||
}
|
||||
|
||||
void setupField(void)
|
||||
// Draw the playing field at the start of the game.
|
||||
{
|
||||
mp.clear();
|
||||
mp.drawHLine(FIELD_BOTTOM, 0, mp.getXMax());
|
||||
mp.drawHLine(FIELD_TOP, 0, mp.getXMax());
|
||||
centerLine();
|
||||
batL.draw();
|
||||
batR.draw();
|
||||
scoreL.draw();
|
||||
scoreR.draw();
|
||||
ball.draw();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
#if DEBUG
|
||||
Serial.begin(57600);
|
||||
#endif
|
||||
PRINTS("\n[MD_MAXPanel_Pong]");
|
||||
|
||||
mp.begin();
|
||||
mp.setFont(_Fixed_5x3);
|
||||
mp.setIntensity(4);
|
||||
mp.setRotation(MD_MAXPanel::ROT_90);
|
||||
|
||||
sound.begin(BEEPER_PIN);
|
||||
|
||||
FIELD_TOP = mp.getYMax() - mp.getFontHeight() - 2;
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static enum { S_SPLASH, S_INIT, S_GAME_START, S_POINT_PLAY, S_POINT_END, S_WAIT_LSTART, S_WAIT_RSTART, S_GAME_OVER } runState = S_SPLASH;
|
||||
|
||||
switch (runState)
|
||||
{
|
||||
case S_SPLASH: // show splash screen at start
|
||||
{
|
||||
const uint16_t border = 2;
|
||||
|
||||
mp.clear();
|
||||
mp.drawRectangle(border, border, mp.getXMax() - border, mp.getYMax() - border);
|
||||
mp.drawLine(0, 0, border, border);
|
||||
mp.drawLine(0, mp.getYMax(), border, mp.getYMax()-border);
|
||||
mp.drawLine(mp.getXMax(), 0, mp.getXMax()-border, border);
|
||||
mp.drawLine(mp.getXMax(), mp.getYMax(), mp.getXMax()-border, mp.getYMax()-border);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(TITLE_TEXT)) / 2, (mp.getYMax() + mp.getFontHeight())/2 , TITLE_TEXT);
|
||||
sound.splash();
|
||||
delay(SPLASH_DELAY);
|
||||
runState = S_INIT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_INIT: // initialise for a new game
|
||||
batL.begin(BAT_EDGE_OFFSET, (FIELD_TOP - FIELD_BOTTOM) / 2, FIELD_BOTTOM + 1, FIELD_TOP - 1, BAT_SIZE_DEFAULT, UP_PIN, LEFT_PIN);
|
||||
batR.begin(mp.getXMax() - BAT_EDGE_OFFSET, (FIELD_TOP - FIELD_BOTTOM) / 2, FIELD_BOTTOM + 1, FIELD_TOP - 1, BAT_SIZE_DEFAULT, RIGHT_PIN, DOWN_PIN);
|
||||
|
||||
scoreL.begin(&mp, BAT_EDGE_OFFSET, FIELD_TOP + 1 + mp.getFontHeight(), MAX_SCORE);
|
||||
scoreR.limit(MAX_SCORE); // set width() used below
|
||||
scoreR.begin(&mp, mp.getXMax() - (scoreR.width() * (FONT_NUM_WIDTH + mp.getCharSpacing())) + mp.getCharSpacing(), FIELD_TOP + 1 + mp.getFontHeight(), MAX_SCORE);
|
||||
|
||||
ball.begin((mp.getXMax() / 2) - BAT_EDGE_OFFSET - 1, (FIELD_TOP - FIELD_BOTTOM) / 3);
|
||||
|
||||
setupField();
|
||||
|
||||
runState = S_GAME_START;
|
||||
break;
|
||||
|
||||
case S_GAME_START: // waiting for the start of a new game
|
||||
if (batL.anyKey() || batR.anyKey())
|
||||
{
|
||||
PRINTS("\n-- Starting Game");
|
||||
scoreL.reset();
|
||||
scoreR.reset();
|
||||
sound.start();
|
||||
ball.start();
|
||||
runState = S_POINT_PLAY;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_POINT_PLAY: // playing a point
|
||||
// handle the bat animation first
|
||||
batL.move();
|
||||
batR.move();
|
||||
|
||||
// now move the ball and check what this means
|
||||
if (ball.move())
|
||||
{
|
||||
cPongBat::hitType_t lastHit;
|
||||
|
||||
// redraw the centerline if the ball is near it
|
||||
if (ball.getX() >= (mp.getXMax() / 2) - 1 || ball.getX() >= (mp.getXMax() / 2) + 2)
|
||||
{
|
||||
centerLine();
|
||||
ball.draw();
|
||||
}
|
||||
|
||||
// check for top/bottom edge collisions
|
||||
if (ball.getY() == FIELD_TOP - 1)
|
||||
{
|
||||
PRINTS("\n-- COLLISION top edge");
|
||||
ball.bounce(cPongBall::BOUNCE_TOP);
|
||||
sound.bounce();
|
||||
}
|
||||
else if (ball.getY() == FIELD_BOTTOM + 1)
|
||||
{
|
||||
PRINTS("\n-- COLLISION bottom edge");
|
||||
ball.bounce(cPongBall::BOUNCE_BOTTOM);
|
||||
sound.bounce();
|
||||
}
|
||||
|
||||
// check for bat collisions
|
||||
if ((lastHit = batL.hit(ball.getX(), ball.getY(), ball.getNextX(), ball.getNextY())) != cPongBat::NO_HIT)
|
||||
{
|
||||
PRINTS("\n-- COLLISION left bat");
|
||||
ball.bounce(lastHit == cPongBat::CORNER_HIT ? cPongBall::BOUNCE_BACK: cPongBall::BOUNCE_LEFT);
|
||||
sound.hit();
|
||||
}
|
||||
else if ((lastHit = batR.hit(ball.getX(), ball.getY(), ball.getNextX(), ball.getNextY())) != cPongBat::NO_HIT)
|
||||
{
|
||||
PRINTS("\n-- COLLISION right bat");
|
||||
ball.bounce(lastHit == cPongBat::CORNER_HIT ? cPongBall::BOUNCE_BACK : cPongBall::BOUNCE_RIGHT);
|
||||
sound.hit();
|
||||
}
|
||||
|
||||
// check for out of bounds
|
||||
if ((ball.getX() < BAT_EDGE_OFFSET) || (ball.getX() > mp.getXMax() - BAT_EDGE_OFFSET))
|
||||
{
|
||||
PRINTS("\n-- OUT!");
|
||||
runState = S_POINT_END;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case S_POINT_END: // handle the ball going out
|
||||
ball.stop();
|
||||
batL.draw();
|
||||
batR.draw();
|
||||
sound.point();
|
||||
delay(500);
|
||||
ball.erase();
|
||||
if (ball.getX() < BAT_EDGE_OFFSET) // out on the left side
|
||||
{
|
||||
PRINTS("\n--- LEFT side");
|
||||
ball.reset(BAT_EDGE_OFFSET + 1, batL.getY());
|
||||
ball.bounce(cPongBall::BOUNCE_LEFT);
|
||||
scoreR.increment();
|
||||
if (scoreR.score() == MAX_SCORE)
|
||||
runState = S_GAME_OVER;
|
||||
else
|
||||
runState = S_WAIT_LSTART;
|
||||
}
|
||||
else // out on the right side
|
||||
{
|
||||
PRINTS("\n--- RIGHT side");
|
||||
ball.reset(mp.getXMax() - BAT_EDGE_OFFSET - 1, batR.getY());
|
||||
ball.bounce(cPongBall::BOUNCE_RIGHT);
|
||||
scoreL.increment();
|
||||
if (scoreL.score() == MAX_SCORE)
|
||||
runState = S_GAME_OVER;
|
||||
else
|
||||
runState = S_WAIT_RSTART;
|
||||
}
|
||||
ball.draw();
|
||||
break;
|
||||
|
||||
case S_WAIT_LSTART: // waiting for left playter to restart the game
|
||||
if (batL.anyKey())
|
||||
{
|
||||
ball.start();
|
||||
runState = S_POINT_PLAY;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_WAIT_RSTART: // waiting fo the right player to restrt the game
|
||||
if (batR.anyKey())
|
||||
{
|
||||
ball.start();
|
||||
runState = S_POINT_PLAY;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_GAME_OVER:
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(GAME_TEXT))/2, (FIELD_TOP - FIELD_BOTTOM)/2 + mp.getFontHeight() + 1, GAME_TEXT);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(OVER_TEXT)) / 2, (FIELD_TOP - FIELD_BOTTOM)/2 - 1, OVER_TEXT);
|
||||
sound.over();
|
||||
delay(GAME_OVER_DELAY);
|
||||
runState = S_INIT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
|
||||
// A class to encapsulate the score display
|
||||
class cScore
|
||||
{
|
||||
private:
|
||||
MD_MAXPanel *_mp; // the MAXPanel object for drawing the score
|
||||
uint16_t _score; // the score
|
||||
uint16_t _x, _y; // coordinate of top left for display
|
||||
uint8_t _width; // number of digits wide
|
||||
uint16_t _limit; // maximum value allowed
|
||||
|
||||
public:
|
||||
void begin(MD_MAXPanel *mp, uint16_t x, uint16_t y, uint16_t maxScore) { _mp = mp; _x = x, _y = y; limit(maxScore); reset(); }
|
||||
void reset(void) { erase(); _score = 0; draw(); }
|
||||
void set(uint16_t s) { if (s <= _limit) { erase(); _score = s; draw(); } }
|
||||
void increment(uint16_t inc = 1) { if (_score + inc <= _limit) { erase(); _score += inc; draw(); } }
|
||||
void decrement(uint16_t dec = 1) { if (_score >= dec) { erase(); _score -= dec; draw(); } }
|
||||
uint16_t score(void) { return(_score); }
|
||||
void erase(void) { draw(false); }
|
||||
uint16_t width(void) { return(_width); }
|
||||
|
||||
void limit(uint16_t m)
|
||||
{
|
||||
erase(); // width may change, so delete with the curret parameters
|
||||
|
||||
_limit = m;
|
||||
// work out how many digits this is
|
||||
_width = 0;
|
||||
do
|
||||
{
|
||||
_width++;
|
||||
m /= 10;
|
||||
} while (m != 0);
|
||||
}
|
||||
|
||||
void draw(bool state = true)
|
||||
{
|
||||
char sz[_width + 1];
|
||||
uint16_t s = _score;
|
||||
|
||||
// PRINT("\n-- SCORE: ", _score);
|
||||
sz[_width] = '\0';
|
||||
for (int i = _width - 1; i >= 0; --i)
|
||||
{
|
||||
sz[i] = (s % 10) + '0';
|
||||
s /= 10;
|
||||
}
|
||||
|
||||
_mp->drawText(_x, _y, sz, MD_MAXPanel::ROT_0, state);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
// A class to encapsulate primitive sound effects
|
||||
class cSound
|
||||
{
|
||||
private:
|
||||
const uint16_t EOD = 0; // End Of Data marker
|
||||
uint8_t _pinBeep; // the pin to use for beeping
|
||||
|
||||
// Sound data - frequency followed by duration in pairs.
|
||||
// Data ends in End Of Data marker EOD.
|
||||
const uint16_t soundSplash[1] PROGMEM = { EOD };
|
||||
const uint16_t soundHit[3] PROGMEM = { 1000, 50, EOD };
|
||||
const uint16_t soundBounce[3] PROGMEM = { 500, 50, EOD };
|
||||
const uint16_t soundPoint[3] PROGMEM = { 150, 150, EOD };
|
||||
const uint16_t soundStart[7] PROGMEM = { 250, 100, 500, 100, 1000, 100, EOD };
|
||||
const uint16_t soundOver[7] PROGMEM = { 1000, 100, 500, 100, 250, 100, EOD };
|
||||
|
||||
void playSound(const uint16_t *table)
|
||||
// Play sound table data. Data table must end in EOD marker.
|
||||
{
|
||||
uint8_t idx = 0;
|
||||
|
||||
//PRINTS("\nTone Data ");
|
||||
while (table[idx] != EOD)
|
||||
{
|
||||
uint16_t t = table[idx++];
|
||||
uint16_t d = table[idx++];
|
||||
|
||||
//PRINTXY("-", t, d);
|
||||
tone(_pinBeep, t);
|
||||
delay(d);
|
||||
}
|
||||
//PRINTS("-EOD");
|
||||
noTone(_pinBeep); // be quiet now!
|
||||
}
|
||||
|
||||
public:
|
||||
void begin(uint8_t pinBeep) { _pinBeep = pinBeep; }
|
||||
void splash(void) { playSound(soundSplash); }
|
||||
void start(void) { playSound(soundStart); }
|
||||
void hit(void) { playSound(soundHit); }
|
||||
void bounce(void) { playSound(soundBounce); }
|
||||
void point(void) { playSound(soundPoint); }
|
||||
void over(void) { playSound(soundOver); }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
MD_MAX72XX::fontType_t _Fixed_5x3[] PROGMEM = {
|
||||
'F', 1, 32, 127, 5,
|
||||
2, 0, 0, // 32 - 'Space'
|
||||
1, 23, // 33 - '!'
|
||||
3, 3, 0, 3, // 34 - '"'
|
||||
3, 31, 10, 31, // 35 - '#'
|
||||
3, 22, 31, 13, // 36 - '$'
|
||||
3, 9, 4, 18, // 37 - '%'
|
||||
3, 10, 21, 26, // 38 - '&'
|
||||
1, 3, // 39
|
||||
2, 14, 17, // 40 - '('
|
||||
2, 17, 14, // 41 - ')'
|
||||
3, 10, 4, 10, // 42 - '*'
|
||||
3, 4, 14, 4, // 43 - '+'
|
||||
2, 16, 8, // 44 - ','
|
||||
3, 4, 4, 4, // 45 - '-'
|
||||
1, 16, // 46 - '.'
|
||||
3, 8, 4, 2, // 47 - '/'
|
||||
3, 31, 17, 31, // 48 - '0'
|
||||
2, 0, 31, // 49 - '1'
|
||||
3, 29, 21, 23, // 50 - '2'
|
||||
3, 17, 21, 31, // 51 - '3'
|
||||
3, 7, 4, 31, // 52 - '4'
|
||||
3, 23, 21, 29, // 53 - '5'
|
||||
3, 31, 21, 29, // 54 - '6'
|
||||
3, 1, 1, 31, // 55 - '7'
|
||||
3, 31, 21, 31, // 56 - '8'
|
||||
3, 23, 21, 31, // 57 - '9'
|
||||
1, 10, // 58 - ':'
|
||||
2, 16, 10, // 59 - ';'
|
||||
3, 4, 10, 17, // 60 - '<'
|
||||
3, 10, 10, 10, // 61 - '='
|
||||
3, 17, 10, 4, // 62 - '>'
|
||||
3, 1, 21, 3, // 63 - '?'
|
||||
3, 14, 21, 22, // 64 - '@'
|
||||
3, 30, 5, 30, // 65 - 'A'
|
||||
3, 31, 21, 10, // 66 - 'B'
|
||||
3, 14, 17, 17, // 67 - 'C'
|
||||
3, 31, 17, 14, // 68 - 'D'
|
||||
3, 31, 21, 17, // 69 - 'E'
|
||||
3, 31, 5, 1, // 70 - 'F'
|
||||
3, 14, 17, 29, // 71 - 'G'
|
||||
3, 31, 4, 31, // 72 - 'H'
|
||||
3, 17, 31, 17, // 73 - 'I'
|
||||
3, 8, 16, 15, // 74 - 'J'
|
||||
3, 31, 4, 27, // 75 - 'K'
|
||||
3, 31, 16, 16, // 76 - 'L'
|
||||
3, 31, 2, 31, // 77 - 'M'
|
||||
3, 31, 14, 31, // 78 - 'N'
|
||||
3, 14, 17, 14, // 79 - 'O'
|
||||
3, 31, 5, 2, // 80 - 'P'
|
||||
3, 14, 25, 30, // 81 - 'Q'
|
||||
3, 31, 5, 26, // 82 - 'R'
|
||||
3, 18, 21, 9, // 83 - 'S'
|
||||
3, 1, 31, 1, // 84 - 'T'
|
||||
3, 15, 16, 15, // 85 - 'U'
|
||||
3, 7, 24, 7, // 86 - 'V'
|
||||
3, 15, 28, 15, // 87 - 'W'
|
||||
3, 27, 4, 27, // 88 - 'X'
|
||||
3, 3, 28, 3, // 89 - 'Y'
|
||||
3, 25, 21, 19, // 90 - 'Z'
|
||||
2, 31, 17, // 91 - '['
|
||||
3, 2, 4, 8, // 92 - '\'
|
||||
2, 17, 31, // 93 - ']'
|
||||
3, 2, 1, 2, // 94 - '^'
|
||||
3, 16, 16, 16, // 95 - '_'
|
||||
2, 1, 2, // 96 - '`'
|
||||
3, 12, 18, 28, // 97 - 'a'
|
||||
3, 31, 18, 12, // 98 - 'b'
|
||||
3, 12, 18, 18, // 99 - 'c'
|
||||
3, 12, 18, 31, // 100 - 'd'
|
||||
3, 12, 26, 20, // 101 - 'e'
|
||||
3, 4, 31, 5, // 102 - 'f'
|
||||
3, 20, 26, 12, // 103 - 'g'
|
||||
3, 31, 2, 28, // 104 - 'h'
|
||||
1, 29, // 105 - 'i'
|
||||
2, 16, 13, // 106 - 'j'
|
||||
3, 31, 8, 20, // 107 - 'k'
|
||||
1, 31, // 108 - 'l'
|
||||
3, 30, 6, 30, // 109 - 'm'
|
||||
3, 30, 2, 28, // 110 - 'n'
|
||||
3, 12, 18, 12, // 111 - 'o'
|
||||
3, 30, 10, 4, // 112 - 'p'
|
||||
3, 4, 10, 30, // 113 - 'q'
|
||||
2, 30, 4, // 114 - 'r'
|
||||
3, 20, 30, 10, // 115 - 's'
|
||||
3, 4, 30, 4, // 116 - 't'
|
||||
3, 14, 16, 30, // 117 - 'u'
|
||||
3, 14, 16, 14, // 118 - 'v'
|
||||
3, 14, 24, 14, // 119 - 'w'
|
||||
3, 18, 12, 18, // 120 - 'x'
|
||||
3, 22, 24, 14, // 121 - 'y'
|
||||
3, 26, 30, 22, // 122 - 'z'
|
||||
3, 4, 27, 17, // 123 - '{'
|
||||
1, 27, // 124 - '|'
|
||||
3, 17, 27, 4, // 125 - '}'
|
||||
3, 6, 2, 3, // 126 - '~'
|
||||
3, 31, 31, 31, // 127 - 'Full Block'
|
||||
};
|
||||
@@ -0,0 +1,507 @@
|
||||
// Implements the game of Snake using MD_MAXPanel library
|
||||
//
|
||||
// Hardware used
|
||||
// =============
|
||||
// LEFT_PIN - left move switch, INPUT_PULLUP
|
||||
// UP_PIN - up move switch, INPUT_PULLUP
|
||||
// DOWN_PIN - down move switch, INPUT_PULLUP
|
||||
// RIGHT_PIN - right move switch, INPUT_PULLUP
|
||||
// SELECT_PIN - unused
|
||||
// ENTER_PIN - unused
|
||||
// BEEPER_PIN - piezo speaker
|
||||
// CLK_PIN, DATA_PIN, CS_PIN - LED matrix display connections
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
// Rules of the Game
|
||||
// =================
|
||||
// Guide the snake around the screen using the direction keys. Running into
|
||||
// a pill will increase the length of the snake. Running into the sides or
|
||||
// the snake will end the game.
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
#include "Font5x3.h"
|
||||
#include "score.h"
|
||||
#include "sound.h"
|
||||
#include "randomseed.h"
|
||||
|
||||
// Turn on debug statements to the serial output
|
||||
#define DEBUG 1
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
|
||||
#define PRINTS(x) { Serial.print(F(x)); }
|
||||
#define PRINTD(x) { Serial.print(x, DEC); }
|
||||
#define PRINTXY(s,x,y) { Serial.print(F(s)); Serial.print(F("(")); Serial.print(x); Serial.print(F(",")); Serial.print(y); Serial.print(")"); }
|
||||
#define PRINTSTATE(s) { Serial.print("\n++>"); Serial.print(s); }
|
||||
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTD(x)
|
||||
#define PRINTXY(s,x,y)
|
||||
#define PRINTSTATE(s)
|
||||
|
||||
#endif
|
||||
|
||||
// Hardware pin definitions.
|
||||
// All momentary on switches are initialized PULLUP
|
||||
const uint8_t UP_PIN = 2;
|
||||
const uint8_t RIGHT_PIN = 3;
|
||||
const uint8_t DOWN_PIN = 4;
|
||||
const uint8_t LEFT_PIN = 5;
|
||||
const uint8_t SELECT_PIN = 6;
|
||||
const uint8_t ENTER_PIN = 7;
|
||||
const uint8_t BEEPER_PIN = 9;
|
||||
|
||||
// Define the number of devices in the chain and the SPI hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 12; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// SPI hardware interface
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
// Arbitrary pins
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
|
||||
uint16_t FIELD_TOP, FIELD_RIGHT; // needs to be initialised in setup()
|
||||
const uint16_t FIELD_LEFT = 0;
|
||||
const uint16_t FIELD_BOTTOM = 0;
|
||||
|
||||
const uint8_t SNAKE_SIZE_DEFAULT = 2;
|
||||
|
||||
const char TITLE_TEXT[] = "SNAKE";
|
||||
const uint16_t SPLASH_DELAY =3000; // in milliseconds
|
||||
|
||||
const char GAME_TEXT[] = "GAME";
|
||||
const char OVER_TEXT[] = "OVER";
|
||||
const uint16_t GAME_OVER_DELAY = 3000; // in milliseconds
|
||||
|
||||
const uint8_t MAX_LENGTH = 999;
|
||||
|
||||
const uint8_t FONT_NUM_WIDTH = 3;
|
||||
const uint16_t MAX_SCORE = MAX_LENGTH;
|
||||
const uint16_t MAX_FOOD = 99;
|
||||
|
||||
// A class to encapsulate the snake direction switches
|
||||
// Can move up, down, left, right
|
||||
class cMoveSW
|
||||
{
|
||||
private:
|
||||
uint8_t _pinLeft;
|
||||
uint8_t _pinRight;
|
||||
uint8_t _pinUp;
|
||||
uint8_t _pinDown;
|
||||
|
||||
public:
|
||||
enum moveType_t { MOVE_NONE, MOVE_UP, MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT };
|
||||
|
||||
void begin(uint8_t pinL, uint8_t pinR, uint8_t pinU, uint8_t pinD)
|
||||
{
|
||||
_pinLeft = pinL;
|
||||
_pinRight = pinR;
|
||||
_pinUp = pinU;
|
||||
_pinDown = pinD;
|
||||
pinMode(_pinLeft, INPUT_PULLUP);
|
||||
pinMode(_pinRight, INPUT_PULLUP);
|
||||
pinMode(_pinUp, INPUT_PULLUP);
|
||||
pinMode(_pinDown, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
bool anyKey(void) { return (move() != MOVE_NONE);}
|
||||
|
||||
moveType_t move(void)
|
||||
{
|
||||
if (digitalRead(_pinLeft) == LOW)
|
||||
return(MOVE_LEFT);
|
||||
else if (digitalRead(_pinRight) == LOW)
|
||||
return(MOVE_RIGHT);
|
||||
else if (digitalRead(_pinUp) == LOW)
|
||||
return(MOVE_UP);
|
||||
else if (digitalRead(_pinDown) == LOW)
|
||||
return(MOVE_DOWN);
|
||||
|
||||
return(MOVE_NONE);
|
||||
}
|
||||
};
|
||||
|
||||
// A class to encapsulate the food pill
|
||||
class cPill
|
||||
{
|
||||
private:
|
||||
uint16_t _x, _y; // pill coordinates
|
||||
uint16_t _xmin, _xmax; // boundaries for the creation of the pill
|
||||
uint16_t _ymin, _ymax;
|
||||
uint8_t _value; // value of the pill
|
||||
|
||||
public:
|
||||
void begin(uint16_t xmin, uint16_t ymin, uint16_t xmax, uint16_t ymax)
|
||||
{
|
||||
_value = 0;
|
||||
_xmin = xmin;
|
||||
_xmax = xmax;
|
||||
_ymin = ymin;
|
||||
_ymax = ymax;
|
||||
PRINTXY("\n-- PILL boundaries ", _xmin, _ymin);
|
||||
PRINTXY(" to ", _xmax, _ymax);
|
||||
}
|
||||
|
||||
uint16_t getX(void) { return(_x); }
|
||||
uint16_t getY(void) { return(_y); }
|
||||
uint8_t getValue(void) { return(_value); }
|
||||
|
||||
void reset(void)
|
||||
{
|
||||
do // search for an unused space
|
||||
{
|
||||
_x = _xmin + (random(_xmax - _xmin + 1));
|
||||
_y = _ymin + (random(_ymax - _ymin + 1));
|
||||
//PRINTXY("\n--- PILL TEST ", _x, _y);
|
||||
} while (mp.getPoint(_x, _y));
|
||||
|
||||
_value = random(10);
|
||||
PRINTXY("\n-- PILL @", _x, _y);
|
||||
PRINT(" worth ", _value);
|
||||
|
||||
mp.setPoint(_x, _y, true);
|
||||
}
|
||||
};
|
||||
|
||||
// A class to encapsulate the snake
|
||||
class cSnake
|
||||
{
|
||||
private:
|
||||
struct snakeNode_t
|
||||
{
|
||||
uint8_t x, y; // coordinate for this body segment. This is UINT*_T to save
|
||||
// RAM, could overflow with larger displays
|
||||
snakeNode_t *next; // the next in the linked list
|
||||
};
|
||||
|
||||
snakeNode_t *_head; // the head segment of the snake
|
||||
snakeNode_t *_tail; // the tail segment of the snake
|
||||
snakeNode_t *_deleted; // deleted blocks list
|
||||
int8_t _dx, _dy; // the movement offsets for the x and y direction
|
||||
uint32_t _timeLastMove; // last time the snake was moved
|
||||
uint16_t _moveDelay; // the delay between moves in milliseconds
|
||||
bool _run; // snake is moving when true
|
||||
cScore *_pFoodCount; // the food counter
|
||||
|
||||
void draw(uint16_t x, uint16_t y) { mp.setPoint(x, y, true); }
|
||||
void erase(uint16_t x, uint16_t y) { mp.setPoint(x, y, false); }
|
||||
|
||||
snakeNode_t *add(uint16_t x, uint16_t y)
|
||||
// add a body segment to the tail
|
||||
// the list is stored from tail to head
|
||||
{
|
||||
snakeNode_t *psn;
|
||||
|
||||
if (_deleted == nullptr)
|
||||
psn = new snakeNode_t;
|
||||
else
|
||||
{
|
||||
// take one from the head of the deleted list
|
||||
psn = _deleted;
|
||||
_deleted = _deleted->next;
|
||||
}
|
||||
|
||||
if (psn != nullptr)
|
||||
{
|
||||
psn->x = x;
|
||||
psn->y = y;
|
||||
if (_tail == nullptr)
|
||||
{
|
||||
psn->next = nullptr;
|
||||
_head = _tail = psn; // the first segment
|
||||
PRINTS("\n-- SNAKE added first element");
|
||||
}
|
||||
else
|
||||
{
|
||||
psn->next = _tail;
|
||||
_tail = psn;
|
||||
PRINTS("\n-- SNAKE added element");
|
||||
}
|
||||
}
|
||||
return(psn);
|
||||
}
|
||||
|
||||
void deleteAll()
|
||||
// delete all the body segments in the snake
|
||||
{
|
||||
snakeNode_t *psn;
|
||||
uint16_t count = 0;
|
||||
|
||||
while (_tail != nullptr)
|
||||
{
|
||||
// adjust the linked list by consuming from the tail
|
||||
psn = _tail;
|
||||
_tail = _tail->next;
|
||||
|
||||
// put the block in the deleted list
|
||||
psn->next = _deleted;
|
||||
_deleted = psn;
|
||||
|
||||
count++;
|
||||
}
|
||||
_head = nullptr;
|
||||
|
||||
PRINT("\n-- SNAKE nodes deleted: ", count);
|
||||
}
|
||||
|
||||
public:
|
||||
enum moveType_t { MOVE_UP, MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT };
|
||||
|
||||
uint16_t getHeadX(void) { return (_head->x); }
|
||||
uint16_t getHeadY(void) { return (_head->y); }
|
||||
|
||||
uint16_t getDelay(void) { return (_moveDelay); }
|
||||
void setDelay(uint16_t delay) { if (delay > 10) _moveDelay = delay; }
|
||||
|
||||
void start(void) { _run = true; }
|
||||
void stop(void) { _run = false; }
|
||||
|
||||
void setDirection(moveType_t d)
|
||||
{
|
||||
switch (d)
|
||||
{
|
||||
case MOVE_UP: _dx = 0; _dy = 1; break;
|
||||
case MOVE_DOWN: _dx = 0; _dy = -1; break;
|
||||
case MOVE_LEFT: _dx = -1; _dy = 0; break;
|
||||
case MOVE_RIGHT: _dx = 1; _dy = 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
void reset(uint16_t x, uint16_t y)
|
||||
{
|
||||
deleteAll();
|
||||
for (uint8_t i = 0; i < SNAKE_SIZE_DEFAULT; i++)
|
||||
{
|
||||
snakeNode_t *psn = add(x - i, y);
|
||||
if (psn != nullptr) draw(psn->x, psn->y);
|
||||
}
|
||||
setDirection(MOVE_RIGHT);
|
||||
_pFoodCount->reset();
|
||||
}
|
||||
|
||||
void begin(cScore *pFoodCount)
|
||||
{
|
||||
_dx = 1;
|
||||
_dy = 0;
|
||||
_timeLastMove = 0;
|
||||
_moveDelay = 100;
|
||||
_run = false;
|
||||
_pFoodCount = pFoodCount;
|
||||
_pFoodCount->reset();
|
||||
_deleted = nullptr;
|
||||
}
|
||||
|
||||
bool move(void)
|
||||
// return true if something was hit
|
||||
{
|
||||
// if this is the time for a move?
|
||||
if (_run && millis() - _timeLastMove <= _moveDelay)
|
||||
return(false);
|
||||
_timeLastMove = millis();
|
||||
|
||||
// do the animation
|
||||
snakeNode_t *psn = _tail;
|
||||
bool hitSomething = false;
|
||||
|
||||
// check if we would be colliding with the snake body
|
||||
while (psn != nullptr && !hitSomething)
|
||||
{
|
||||
hitSomething = (psn->x == _head->x + _dx && psn->y == _head->y + _dy);
|
||||
psn = psn->next;
|
||||
}
|
||||
if (hitSomething) PRINTS("\n-! COLLIDE snake");
|
||||
|
||||
if (!hitSomething)
|
||||
{
|
||||
psn = _tail; // reset back to the start of the data
|
||||
|
||||
// do we need to add one?
|
||||
if (_pFoodCount->score() != 0)
|
||||
{
|
||||
add(_tail->x, _tail->y); // make a copy of the tail
|
||||
_pFoodCount->decrement();
|
||||
}
|
||||
else // delete the tail on the display
|
||||
erase(_tail->x, _tail->y);
|
||||
|
||||
while (psn != nullptr)
|
||||
{
|
||||
if (psn->next != nullptr) // main body segment
|
||||
{
|
||||
psn->x = psn->next->x;
|
||||
psn->y = psn->next->y;
|
||||
}
|
||||
else // the head
|
||||
{
|
||||
psn->x += _dx;
|
||||
psn->y += _dy;
|
||||
// check if this is already used
|
||||
hitSomething = (mp.getPoint(psn->x, psn->y));
|
||||
draw(psn->x, psn->y);
|
||||
}
|
||||
psn = psn->next;
|
||||
}
|
||||
|
||||
if (hitSomething) PRINTS("\n-! COLLIDE not snake");
|
||||
}
|
||||
|
||||
return(hitSomething);
|
||||
}
|
||||
};
|
||||
|
||||
// main objects coordinated by the code logic
|
||||
cScore score, food;
|
||||
cPill pill;
|
||||
cSnake snake;
|
||||
cMoveSW moveSW;
|
||||
cSound sound;
|
||||
|
||||
void setupField(void)
|
||||
// Draw the playing field at the start of the game.
|
||||
{
|
||||
mp.clear();
|
||||
mp.drawHLine(FIELD_TOP, FIELD_LEFT, FIELD_RIGHT);
|
||||
mp.drawHLine(FIELD_BOTTOM, FIELD_LEFT, FIELD_RIGHT);
|
||||
mp.drawVLine(FIELD_LEFT, 0, FIELD_TOP);
|
||||
mp.drawVLine(FIELD_RIGHT, 0, FIELD_TOP);
|
||||
score.draw();
|
||||
food.draw();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
#if DEBUG
|
||||
Serial.begin(57600);
|
||||
#endif
|
||||
PRINTS("\n[MD_MAXPanel_Snake]");
|
||||
|
||||
mp.begin();
|
||||
mp.setFont(_Fixed_5x3);
|
||||
mp.setIntensity(4);
|
||||
mp.setRotation(MD_MAXPanel::ROT_90);
|
||||
|
||||
randomSeed(seedOut(31, RANDOM_SEED_PORT));
|
||||
|
||||
// one time initialization
|
||||
FIELD_TOP = mp.getYMax() - mp.getFontHeight() - 2;
|
||||
FIELD_RIGHT = mp.getXMax();
|
||||
pill.begin(FIELD_LEFT + 1, FIELD_BOTTOM + 1, FIELD_RIGHT - 1, FIELD_TOP - 1);
|
||||
food.begin(&mp, FIELD_LEFT + 1, FIELD_TOP + 1 + mp.getFontHeight(), MAX_FOOD);
|
||||
|
||||
sound.begin(BEEPER_PIN);
|
||||
score.limit(MAX_SCORE); // set the width so we can use it below
|
||||
score.begin(&mp, FIELD_RIGHT - ((score.width() * (FONT_NUM_WIDTH) + mp.getCharSpacing())) - mp.getCharSpacing(), FIELD_TOP + 1 + mp.getFontHeight(), MAX_SCORE);
|
||||
|
||||
moveSW.begin(LEFT_PIN, RIGHT_PIN, UP_PIN, DOWN_PIN);
|
||||
snake.begin(&food);
|
||||
}
|
||||
|
||||
bool doSwitches(void)
|
||||
{
|
||||
bool b = true;
|
||||
|
||||
switch (moveSW.move())
|
||||
{
|
||||
case cMoveSW::MOVE_UP: snake.setDirection(cSnake::MOVE_UP); break;
|
||||
case cMoveSW::MOVE_DOWN: snake.setDirection(cSnake::MOVE_DOWN); break;
|
||||
case cMoveSW::MOVE_LEFT: snake.setDirection(cSnake::MOVE_LEFT); break;
|
||||
case cMoveSW::MOVE_RIGHT: snake.setDirection(cSnake::MOVE_RIGHT); break;
|
||||
case cMoveSW::MOVE_NONE: b = false; break;
|
||||
}
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static enum { S_SPLASH, S_INIT, S_WAIT_START, S_POINT_PLAY, S_GAME_OVER } runState = S_SPLASH;
|
||||
|
||||
switch (runState)
|
||||
{
|
||||
case S_SPLASH: // show splash screen at start
|
||||
PRINTSTATE("SPLASH");
|
||||
{
|
||||
const uint16_t border = 2;
|
||||
|
||||
mp.clear();
|
||||
mp.drawRectangle(border, border, mp.getXMax() - border, mp.getYMax() - border);
|
||||
mp.drawLine(0, 0, border, border);
|
||||
mp.drawLine(0, mp.getYMax(), border, mp.getYMax()-border);
|
||||
mp.drawLine(mp.getXMax(), 0, mp.getXMax()-border, border);
|
||||
mp.drawLine(mp.getXMax(), mp.getYMax(), mp.getXMax()-border, mp.getYMax()-border);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(TITLE_TEXT)) / 2, (mp.getYMax() + mp.getFontHeight())/2 , TITLE_TEXT);
|
||||
sound.splash();
|
||||
delay(SPLASH_DELAY);
|
||||
|
||||
runState = S_INIT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_INIT: // initialize for a new game
|
||||
PRINTSTATE("INIT");
|
||||
score.reset();
|
||||
food.reset();
|
||||
setupField();
|
||||
snake.reset((FIELD_RIGHT - FIELD_LEFT) / 2, (FIELD_TOP - FIELD_BOTTOM) / 2);
|
||||
pill.reset();
|
||||
|
||||
runState = S_WAIT_START;
|
||||
PRINTSTATE("WAIT_START");
|
||||
break;
|
||||
|
||||
case S_WAIT_START: // waiting for the start of a new game
|
||||
if (doSwitches())
|
||||
{
|
||||
PRINTS("\n-- Starting Game");
|
||||
sound.start();
|
||||
snake.start();
|
||||
runState = S_POINT_PLAY;
|
||||
PRINTSTATE("POINT_PLAY");
|
||||
}
|
||||
break;
|
||||
|
||||
case S_POINT_PLAY: // playing a point
|
||||
// handle the switches
|
||||
doSwitches();
|
||||
|
||||
// move snake and check what this means
|
||||
if (snake.move())
|
||||
{
|
||||
if (snake.getHeadX() == pill.getX() && snake.getHeadY() == pill.getY()) // have we hit the pill?
|
||||
{
|
||||
sound.hit();
|
||||
score.increment(pill.getValue());
|
||||
food.increment(pill.getValue());
|
||||
pill.reset();
|
||||
}
|
||||
else // we have hit the wall or ourselves
|
||||
{
|
||||
snake.stop();
|
||||
runState = S_GAME_OVER;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case S_GAME_OVER:
|
||||
PRINTSTATE("GAME_OVER");
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(GAME_TEXT)) / 2, FIELD_TOP / 2 + mp.getFontHeight() + 1, GAME_TEXT);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(OVER_TEXT)) / 2, FIELD_TOP / 2 - 1, OVER_TEXT);
|
||||
|
||||
sound.over();
|
||||
delay(GAME_OVER_DELAY);
|
||||
runState = S_INIT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
// Random seed creation --------------------------
|
||||
// Adapted from http://www.utopiamechanicus.com/article/arduino-better-random-numbers/
|
||||
|
||||
const uint8_t RANDOM_SEED_PORT = A3; // port read for random seed
|
||||
|
||||
uint16_t bitOut(uint8_t port)
|
||||
{
|
||||
static bool firstTime = true;
|
||||
uint32_t prev = 0;
|
||||
uint32_t bit1 = 0, bit0 = 0;
|
||||
uint32_t x = 0, limit = 99;
|
||||
|
||||
if (firstTime)
|
||||
{
|
||||
firstTime = false;
|
||||
prev = analogRead(port);
|
||||
}
|
||||
|
||||
while (limit--)
|
||||
{
|
||||
x = analogRead(port);
|
||||
bit1 = (prev != x ? 1 : 0);
|
||||
prev = x;
|
||||
x = analogRead(port);
|
||||
bit0 = (prev != x ? 1 : 0);
|
||||
prev = x;
|
||||
if (bit1 != bit0)
|
||||
break;
|
||||
}
|
||||
|
||||
return(bit1);
|
||||
}
|
||||
|
||||
uint32_t seedOut(uint16_t noOfBits, uint8_t port)
|
||||
{
|
||||
// return value with 'noOfBits' random bits set
|
||||
uint32_t seed = 0;
|
||||
|
||||
for (int i = 0; i<noOfBits; ++i)
|
||||
seed = (seed << 1) | bitOut(port);
|
||||
|
||||
return(seed);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
|
||||
// A class to encapsulate the score display
|
||||
class cScore
|
||||
{
|
||||
private:
|
||||
MD_MAXPanel *_mp; // the MAXPanel object for drawing the score
|
||||
uint16_t _score; // the score
|
||||
uint16_t _x, _y; // coordinate of top left for display
|
||||
uint8_t _width; // number of digits wide
|
||||
uint16_t _limit; // maximum value allowed
|
||||
|
||||
public:
|
||||
void begin(MD_MAXPanel *mp, uint16_t x, uint16_t y, uint16_t maxScore) { _mp = mp; _x = x, _y = y; limit(maxScore); reset(); }
|
||||
void reset(void) { erase(); _score = 0; draw(); }
|
||||
void set(uint16_t s) { if (s <= _limit) { erase(); _score = s; draw(); } }
|
||||
void increment(uint16_t inc = 1) { if (_score + inc <= _limit) { erase(); _score += inc; draw(); } }
|
||||
void decrement(uint16_t dec = 1) { if (_score >= dec) { erase(); _score -= dec; draw(); } }
|
||||
uint16_t score(void) { return(_score); }
|
||||
void erase(void) { draw(false); }
|
||||
uint16_t width(void) { return(_width); }
|
||||
|
||||
void limit(uint16_t m)
|
||||
{
|
||||
erase(); // width may change, so delete with the curret parameters
|
||||
|
||||
_limit = m;
|
||||
// work out how many digits this is
|
||||
_width = 0;
|
||||
do
|
||||
{
|
||||
_width++;
|
||||
m /= 10;
|
||||
} while (m != 0);
|
||||
}
|
||||
|
||||
void draw(bool state = true)
|
||||
{
|
||||
char sz[_width + 1];
|
||||
uint16_t s = _score;
|
||||
|
||||
// PRINT("\n-- SCORE: ", _score);
|
||||
sz[_width] = '\0';
|
||||
for (int i = _width - 1; i >= 0; --i)
|
||||
{
|
||||
sz[i] = (s % 10) + '0';
|
||||
s /= 10;
|
||||
}
|
||||
|
||||
_mp->drawText(_x, _y, sz, MD_MAXPanel::ROT_0, state);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
// A class to encapsulate primitive sound effects
|
||||
class cSound
|
||||
{
|
||||
private:
|
||||
const uint16_t EOD = 0; // End Of Data marker
|
||||
uint8_t _pinBeep; // the pin to use for beeping
|
||||
|
||||
// Sound data - frequency followed by duration in pairs.
|
||||
// Data ends in End Of Data marker EOD.
|
||||
const uint16_t soundSplash[1] PROGMEM = { EOD };
|
||||
const uint16_t soundHit[3] PROGMEM = { 1000, 50, EOD };
|
||||
const uint16_t soundBounce[3] PROGMEM = { 500, 50, EOD };
|
||||
const uint16_t soundPoint[3] PROGMEM = { 150, 150, EOD };
|
||||
const uint16_t soundStart[7] PROGMEM = { 250, 100, 500, 100, 1000, 100, EOD };
|
||||
const uint16_t soundOver[7] PROGMEM = { 1000, 100, 500, 100, 250, 100, EOD };
|
||||
|
||||
void playSound(const uint16_t *table)
|
||||
// Play sound table data. Data table must end in EOD marker.
|
||||
{
|
||||
uint8_t idx = 0;
|
||||
|
||||
//PRINTS("\nTone Data ");
|
||||
while (table[idx] != EOD)
|
||||
{
|
||||
uint16_t t = table[idx++];
|
||||
uint16_t d = table[idx++];
|
||||
|
||||
//PRINTXY("-", t, d);
|
||||
tone(_pinBeep, t);
|
||||
delay(d);
|
||||
}
|
||||
//PRINTS("-EOD");
|
||||
noTone(_pinBeep); // be quiet now!
|
||||
}
|
||||
|
||||
public:
|
||||
void begin(uint8_t pinBeep) { _pinBeep = pinBeep; }
|
||||
void splash(void) { playSound(soundSplash); }
|
||||
void start(void) { playSound(soundStart); }
|
||||
void hit(void) { playSound(soundHit); }
|
||||
void bounce(void) { playSound(soundBounce); }
|
||||
void point(void) { playSound(soundPoint); }
|
||||
void over(void) { playSound(soundOver); }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
MD_MAX72XX::fontType_t _Fixed_5x3[] PROGMEM = {
|
||||
'F', 1, 32, 127, 5,
|
||||
2, 0, 0, // 32 - 'Space'
|
||||
1, 23, // 33 - '!'
|
||||
3, 3, 0, 3, // 34 - '"'
|
||||
3, 31, 10, 31, // 35 - '#'
|
||||
3, 22, 31, 13, // 36 - '$'
|
||||
3, 9, 4, 18, // 37 - '%'
|
||||
3, 10, 21, 26, // 38 - '&'
|
||||
1, 3, // 39
|
||||
2, 14, 17, // 40 - '('
|
||||
2, 17, 14, // 41 - ')'
|
||||
3, 10, 4, 10, // 42 - '*'
|
||||
3, 4, 14, 4, // 43 - '+'
|
||||
2, 16, 8, // 44 - ','
|
||||
3, 4, 4, 4, // 45 - '-'
|
||||
1, 16, // 46 - '.'
|
||||
3, 8, 4, 2, // 47 - '/'
|
||||
3, 31, 17, 31, // 48 - '0'
|
||||
2, 0, 31, // 49 - '1'
|
||||
3, 29, 21, 23, // 50 - '2'
|
||||
3, 17, 21, 31, // 51 - '3'
|
||||
3, 7, 4, 31, // 52 - '4'
|
||||
3, 23, 21, 29, // 53 - '5'
|
||||
3, 31, 21, 29, // 54 - '6'
|
||||
3, 1, 1, 31, // 55 - '7'
|
||||
3, 31, 21, 31, // 56 - '8'
|
||||
3, 23, 21, 31, // 57 - '9'
|
||||
1, 10, // 58 - ':'
|
||||
2, 16, 10, // 59 - ';'
|
||||
3, 4, 10, 17, // 60 - '<'
|
||||
3, 10, 10, 10, // 61 - '='
|
||||
3, 17, 10, 4, // 62 - '>'
|
||||
3, 1, 21, 3, // 63 - '?'
|
||||
3, 14, 21, 22, // 64 - '@'
|
||||
3, 30, 5, 30, // 65 - 'A'
|
||||
3, 31, 21, 10, // 66 - 'B'
|
||||
3, 14, 17, 17, // 67 - 'C'
|
||||
3, 31, 17, 14, // 68 - 'D'
|
||||
3, 31, 21, 17, // 69 - 'E'
|
||||
3, 31, 5, 1, // 70 - 'F'
|
||||
3, 14, 17, 29, // 71 - 'G'
|
||||
3, 31, 4, 31, // 72 - 'H'
|
||||
3, 17, 31, 17, // 73 - 'I'
|
||||
3, 8, 16, 15, // 74 - 'J'
|
||||
3, 31, 4, 27, // 75 - 'K'
|
||||
3, 31, 16, 16, // 76 - 'L'
|
||||
3, 31, 2, 31, // 77 - 'M'
|
||||
3, 31, 14, 31, // 78 - 'N'
|
||||
3, 14, 17, 14, // 79 - 'O'
|
||||
3, 31, 5, 2, // 80 - 'P'
|
||||
3, 14, 25, 30, // 81 - 'Q'
|
||||
3, 31, 5, 26, // 82 - 'R'
|
||||
3, 18, 21, 9, // 83 - 'S'
|
||||
3, 1, 31, 1, // 84 - 'T'
|
||||
3, 15, 16, 15, // 85 - 'U'
|
||||
3, 7, 24, 7, // 86 - 'V'
|
||||
3, 15, 28, 15, // 87 - 'W'
|
||||
3, 27, 4, 27, // 88 - 'X'
|
||||
3, 3, 28, 3, // 89 - 'Y'
|
||||
3, 25, 21, 19, // 90 - 'Z'
|
||||
2, 31, 17, // 91 - '['
|
||||
3, 2, 4, 8, // 92 - '\'
|
||||
2, 17, 31, // 93 - ']'
|
||||
3, 2, 1, 2, // 94 - '^'
|
||||
3, 16, 16, 16, // 95 - '_'
|
||||
2, 1, 2, // 96 - '`'
|
||||
3, 12, 18, 28, // 97 - 'a'
|
||||
3, 31, 18, 12, // 98 - 'b'
|
||||
3, 12, 18, 18, // 99 - 'c'
|
||||
3, 12, 18, 31, // 100 - 'd'
|
||||
3, 12, 26, 20, // 101 - 'e'
|
||||
3, 4, 31, 5, // 102 - 'f'
|
||||
3, 20, 26, 12, // 103 - 'g'
|
||||
3, 31, 2, 28, // 104 - 'h'
|
||||
1, 29, // 105 - 'i'
|
||||
2, 16, 13, // 106 - 'j'
|
||||
3, 31, 8, 20, // 107 - 'k'
|
||||
1, 31, // 108 - 'l'
|
||||
3, 30, 6, 30, // 109 - 'm'
|
||||
3, 30, 2, 28, // 110 - 'n'
|
||||
3, 12, 18, 12, // 111 - 'o'
|
||||
3, 30, 10, 4, // 112 - 'p'
|
||||
3, 4, 10, 30, // 113 - 'q'
|
||||
2, 30, 4, // 114 - 'r'
|
||||
3, 20, 30, 10, // 115 - 's'
|
||||
3, 4, 30, 4, // 116 - 't'
|
||||
3, 14, 16, 30, // 117 - 'u'
|
||||
3, 14, 16, 14, // 118 - 'v'
|
||||
3, 14, 24, 14, // 119 - 'w'
|
||||
3, 18, 12, 18, // 120 - 'x'
|
||||
3, 22, 24, 14, // 121 - 'y'
|
||||
3, 26, 30, 22, // 122 - 'z'
|
||||
3, 4, 27, 17, // 123 - '{'
|
||||
1, 27, // 124 - '|'
|
||||
3, 17, 27, 4, // 125 - '}'
|
||||
3, 6, 2, 3, // 126 - '~'
|
||||
3, 31, 31, 31, // 127 - 'Full Block'
|
||||
};
|
||||
@@ -0,0 +1,427 @@
|
||||
// Implements the game of TTT using MD_MAXPanel library
|
||||
// Adapted from the LCD example in the MD_TTT library
|
||||
//
|
||||
// Hardware used
|
||||
// =============
|
||||
// LEFT_PIN - unused
|
||||
// UP_PIN - unused
|
||||
// DOWN_PIN - unused
|
||||
// RIGHT_PIN - unused
|
||||
// SELECT_PIN - switch moves from one selection to another,INPUT_PULLUP
|
||||
// ENTER_PIN - accepts the current selection, INPUT_PULLUP
|
||||
// BEEPER_PIN - piezo speaker
|
||||
// CLK_PIN, DATA_PIN, CS_PIN - LED matrix display connections
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
// MD_TTT available from https://github.com/MajicDesigns/MD_TTT
|
||||
//
|
||||
// Rules of the Game
|
||||
// =================
|
||||
// Tic-tac-toe (also known as noughts and crosses or Xs and Os) is a
|
||||
// paper-and-pencil game for two players, X and O, who take turns marking
|
||||
// the spaces in a 3×3 grid. The player who succeeds in placing three
|
||||
// of their marks in a horizontal, vertical, or diagonal row wins the
|
||||
// game. This version has the computer as one of the players.
|
||||
//
|
||||
|
||||
#include <MD_TTT.h>
|
||||
#include <MD_MAXPanel.h>
|
||||
#include "Font5x3.h"
|
||||
#include "MD_MAXPanel_TTT_Types.h"
|
||||
|
||||
// Turn on debug statements to the serial output
|
||||
#define DEBUG 0
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
|
||||
#define PRINTS(x) { Serial.print(F(x)); }
|
||||
#define PRINTD(x) { Serial.print(x, DEC); }
|
||||
#define PRINTXY(s, x, y) { Serial.print(s); Serial.print(F("(")); Serial.print(x); Serial.print(F(",")); Serial.print(y); Serial.print(")"); }
|
||||
#define PRINTSTATE(s) { Serial.print("\n++>"); Serial.print(s); }
|
||||
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTD(x)
|
||||
#define PRINTXY(s, x, y)
|
||||
#define PRINTSTATE(s)
|
||||
|
||||
#endif
|
||||
|
||||
// function prototype
|
||||
void tttCallback(uint8_t position, int8_t player);
|
||||
|
||||
// User switches for gameplay
|
||||
const uint8_t UP_PIN = 2;
|
||||
const uint8_t RIGHT_PIN = 3;
|
||||
const uint8_t DOWN_PIN = 4;
|
||||
const uint8_t LEFT_PIN = 5;
|
||||
const uint8_t SELECT_PIN = 6;
|
||||
const uint8_t ENTER_PIN = 7;
|
||||
const uint8_t BEEPER_PIN = 9;
|
||||
|
||||
// Define the number of devices in the chain and the SPI hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 13; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// Miscellaneous defines
|
||||
const uint16_t FLASH_DELAY = 500; // milliseconds
|
||||
const uint8_t FLASH_REPEAT = 5; // number of flashes
|
||||
|
||||
const char TITLE_TEXT[] = "TTT";
|
||||
const uint16_t SPLASH_DELAY = 3000; // in milliseconds
|
||||
|
||||
// Coordinates for a board position
|
||||
boardCoord_t movePos[TTT_BOARD_SIZE] = { 0 };
|
||||
|
||||
// Handling for switch states (User Input)
|
||||
swState_t swAccept = { ENTER_PIN, false, 0 };
|
||||
swState_t swSelect = { SELECT_PIN, false, 0 };
|
||||
|
||||
// Main objects used defined here
|
||||
MD_TTT TTT(tttCallback);
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES); // SPI hardware interface
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES); // Arbitrary pins
|
||||
|
||||
int8_t curPlayer = TTT_P2;
|
||||
bool inGamePlay = false;
|
||||
|
||||
// variables and constants used for the display
|
||||
uint16_t USER_MESG, GRID_BOTTOM, GRID_RIGHT, GRID_TOP;
|
||||
uint8_t squareSize, offsetX, offsetY;
|
||||
|
||||
void calcGridSize(void)
|
||||
{
|
||||
uint16_t dx = (GRID_RIGHT - 2) / 3;
|
||||
uint16_t dy = (GRID_TOP - GRID_BOTTOM - 2) / 3;
|
||||
squareSize = min(dx, dy);
|
||||
offsetX = ((GRID_RIGHT - 2) - (squareSize * 3)) / 2; // offset from vert edge
|
||||
offsetY = ((GRID_TOP - GRID_BOTTOM - 2) - (squareSize * 3)) / 2; // offset from horiz edge
|
||||
GRID_BOTTOM = GRID_TOP - offsetY - (3 * squareSize) - 2;
|
||||
|
||||
PRINT("\nsquareSize = ", squareSize);
|
||||
PRINT(", offsetX = ", offsetX);
|
||||
PRINT(", offsetY = ", offsetY);
|
||||
|
||||
// set up the board square coordinates
|
||||
for (uint8_t j = 0; j < TTT_BOARD_SIZE / 3; j++)
|
||||
for (uint8_t i = 0; i < TTT_BOARD_SIZE / 3; i++)
|
||||
{
|
||||
uint8_t idx = (j * 3) + i;
|
||||
|
||||
movePos[idx].x = offsetX + (i*squareSize) + i; // last element is for lines
|
||||
movePos[idx].y = GRID_TOP - offsetY - (j*squareSize) - j; // last element is for lines
|
||||
movePos[idx].size = squareSize-1;
|
||||
|
||||
PRINT("\nmovePos[", idx);
|
||||
PRINTXY("] - ", movePos[idx].x, movePos[idx].y);
|
||||
PRINT(" size=", movePos[idx].size);
|
||||
}
|
||||
}
|
||||
|
||||
void displayGrid(void)
|
||||
{
|
||||
mp.update(false);
|
||||
mp.clear();
|
||||
|
||||
mp.drawVLine(offsetX + squareSize, GRID_BOTTOM + offsetY, GRID_TOP - offsetY);
|
||||
mp.drawVLine(offsetX + (2*squareSize) + 1, GRID_BOTTOM + offsetY, GRID_TOP - offsetY);
|
||||
mp.drawHLine(GRID_TOP - offsetY - squareSize, offsetX, GRID_RIGHT - offsetX);
|
||||
mp.drawHLine(GRID_TOP - offsetY - (2*squareSize) - 1, offsetX, GRID_RIGHT - offsetX);
|
||||
|
||||
mp.update(true);
|
||||
}
|
||||
|
||||
void highlightCell(uint8_t cell, bool b = true)
|
||||
{
|
||||
mp.drawRectangle(movePos[cell].x, movePos[cell].y,
|
||||
movePos[cell].x + movePos[cell].size, movePos[cell].y - movePos[cell].size, b);
|
||||
}
|
||||
|
||||
bool detectSwitch(swState_t *ss)
|
||||
// detects the HIGH to LOW transition of a switch
|
||||
// returns true if a transition has occurred
|
||||
// only check if a period of time has expired to debounce
|
||||
{
|
||||
boolean b = false;
|
||||
|
||||
if ((millis() - ss->lastCheckTime) > 50)
|
||||
{
|
||||
bool curState = (digitalRead(ss->pin) == LOW);
|
||||
|
||||
ss->lastCheckTime = millis();
|
||||
b = (curState && !ss->lastState);
|
||||
ss->lastState = curState;
|
||||
}
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
void userMessage(char *psz)
|
||||
{
|
||||
mp.clear(1, 0, mp.getXMax(), USER_MESG);
|
||||
mp.drawText(1, USER_MESG, psz, MD_MAXPanel::ROT_0, true);
|
||||
}
|
||||
|
||||
uint8_t getMove(void)
|
||||
// get the next move from the player
|
||||
// there may not be a move there so we need to split the
|
||||
// function into a prompting and then checking phase
|
||||
// return 0xff of no move entered
|
||||
{
|
||||
static enum { UI_START, UI_HILIGHT, UI_SELECT, UI_NEXT_HILIGHT, UI_ACCEPT } promptMode = UI_START;
|
||||
static uint8_t curPosUI = 0;
|
||||
|
||||
uint8_t m = 0xff;
|
||||
|
||||
switch (promptMode)
|
||||
{
|
||||
case UI_START: // print the message
|
||||
PRINTS("\n- START");
|
||||
userMessage("You move");
|
||||
promptMode = UI_HILIGHT;
|
||||
break;
|
||||
|
||||
case UI_HILIGHT: // find the first empty cell and highlight it
|
||||
PRINTS("\n- HILIGHT");
|
||||
//highlightCell(curPosUI, false); // unhighlight current selection
|
||||
for (curPosUI = 0; curPosUI<TTT_BOARD_SIZE; curPosUI++)
|
||||
{
|
||||
PRINT(" ", curPosUI);
|
||||
if (TTT.getBoardPosition(curPosUI) == TTT_P0)
|
||||
{
|
||||
PRINTS(": FOUND");
|
||||
highlightCell(curPosUI); // highlight new selection
|
||||
break;
|
||||
}
|
||||
}
|
||||
promptMode = UI_SELECT;
|
||||
break;
|
||||
|
||||
case UI_SELECT: // highlight the cell we are on and handle switches
|
||||
if (detectSwitch(&swSelect))
|
||||
promptMode = UI_NEXT_HILIGHT;
|
||||
else if (detectSwitch(&swAccept))
|
||||
promptMode = UI_ACCEPT;
|
||||
break;
|
||||
|
||||
case UI_NEXT_HILIGHT: // user selected next cell, find it and highlight it
|
||||
PRINTS("\n- NEXT HILIGHT");
|
||||
if (curPosUI < TTT_BOARD_SIZE)
|
||||
highlightCell(curPosUI, false); // unhighlight current selection
|
||||
|
||||
for (curPosUI=curPosUI+1; curPosUI<TTT_BOARD_SIZE; curPosUI++)
|
||||
{
|
||||
PRINT(" ", curPosUI);
|
||||
if (TTT.getBoardPosition(curPosUI) == TTT_P0)
|
||||
{
|
||||
PRINTS(": FOUND");
|
||||
highlightCell(curPosUI); // highlight new selection
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (curPosUI == TTT_BOARD_SIZE) // oops - none there
|
||||
{
|
||||
PRINTS(": NOT FOUND");
|
||||
promptMode = UI_HILIGHT; // start again
|
||||
}
|
||||
else
|
||||
promptMode = UI_SELECT; // wait for a switch again
|
||||
break;
|
||||
|
||||
case UI_ACCEPT: // we have a selection, return the appropriate move
|
||||
PRINTS("\n- ACCEPT");
|
||||
highlightCell(curPosUI, false); // unhighlight current selection
|
||||
m = curPosUI;
|
||||
promptMode = UI_START; // set up for next time
|
||||
break;
|
||||
|
||||
default: // catch all - reset
|
||||
promptMode = UI_START;
|
||||
break;
|
||||
}
|
||||
|
||||
return(m);
|
||||
}
|
||||
|
||||
void tttCallback(uint8_t position, int8_t player)
|
||||
// update the board position with the player token
|
||||
{
|
||||
displayPosition(position, player);
|
||||
}
|
||||
|
||||
void displayPosition(uint8_t pos, int8_t player)
|
||||
{
|
||||
// update the position on the grid
|
||||
switch (player)
|
||||
{
|
||||
case TTT_P0: // clear the space
|
||||
mp.clear(movePos[pos].x, movePos[pos].y,
|
||||
movePos[pos].x+movePos[pos].size, movePos[pos].y-movePos[pos].size);
|
||||
break;
|
||||
|
||||
case TTT_P1: // draw an X
|
||||
mp.drawLine(movePos[pos].x+1, movePos[pos].y-1,
|
||||
movePos[pos].x-1+movePos[pos].size, movePos[pos].y+1-movePos[pos].size);
|
||||
mp.drawLine(movePos[pos].x+1, movePos[pos].y+1-movePos[pos].size,
|
||||
movePos[pos].x-1+movePos[pos].size, movePos[pos].y-1);
|
||||
break;
|
||||
|
||||
case TTT_P2: // draw an O
|
||||
mp.drawCircle(movePos[pos].x+(movePos[pos].size / 2), movePos[pos].y-(movePos[pos].size / 2),
|
||||
(movePos[pos].size / 2) - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void flashLine(uint8_t line)
|
||||
// note this is blocking as it uses delay();
|
||||
{
|
||||
uint8_t l[3];
|
||||
|
||||
// work out the cells for this line
|
||||
switch (line)
|
||||
{
|
||||
case TTT_WL_D1: l[0]=0; l[1]=4; l[2]=8; break;
|
||||
case TTT_WL_D2: l[0]=2; l[1]=4; l[2]=6; break;
|
||||
case TTT_WL_H1: l[0]=0; l[1]=1; l[2]=2; break;
|
||||
case TTT_WL_H2: l[0]=3; l[1]=4; l[2]=5; break;
|
||||
case TTT_WL_H3: l[0]=6; l[1]=7; l[2]=8; break;
|
||||
case TTT_WL_V1: l[0]=0; l[1]=3; l[2]=6; break;
|
||||
case TTT_WL_V2: l[0]=1; l[1]=4; l[2]=7; break;
|
||||
case TTT_WL_V3: l[0]=2; l[1]=5; l[2]=8; break;
|
||||
}
|
||||
|
||||
// turn them off and on a number of times (flash!)
|
||||
for (uint8_t i=0; i<FLASH_REPEAT; i++)
|
||||
{
|
||||
mp.update(false);
|
||||
for (uint8_t j=0; j<ARRAY_SIZE(l); j++)
|
||||
displayPosition(l[j], TTT_P0);
|
||||
mp.update(true);
|
||||
delay(FLASH_DELAY);
|
||||
|
||||
mp.update(false);
|
||||
for (uint8_t j=0; j<ARRAY_SIZE(l); j++)
|
||||
displayPosition(l[j], TTT.getBoardPosition(l[j]));
|
||||
mp.update(true);
|
||||
delay(FLASH_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
#if DEBUG
|
||||
Serial.begin(57600);
|
||||
#endif
|
||||
PRINTS("\n[MD_MAXPanel_TTT]");
|
||||
|
||||
mp.begin();
|
||||
mp.setFont(_Fixed_5x3);
|
||||
mp.setIntensity(4);
|
||||
//mp.setRotation(MD_MAXPanel::ROT_90);
|
||||
|
||||
// initialize switch pins for input
|
||||
pinMode(swAccept.pin, INPUT_PULLUP);
|
||||
pinMode(swSelect.pin, INPUT_PULLUP);
|
||||
|
||||
// set up global constants
|
||||
USER_MESG = mp.getFontHeight() + 1;
|
||||
GRID_BOTTOM = USER_MESG + 1;
|
||||
GRID_RIGHT = mp.getXMax();
|
||||
GRID_TOP = mp.getYMax();
|
||||
|
||||
// initialize display
|
||||
calcGridSize();
|
||||
TTT.setAutoPlayer(curPlayer);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static enum { S_SPLASH, S_START, S_GET_MOVE, S_CHECK_END } curState = S_SPLASH; // current state
|
||||
|
||||
switch (curState)
|
||||
{
|
||||
case S_SPLASH: // show splash screen at start
|
||||
{
|
||||
const uint16_t border = 2;
|
||||
|
||||
PRINTSTATE("S_SPLASH");
|
||||
mp.clear();
|
||||
mp.drawRectangle(border, border, mp.getXMax() - border, mp.getYMax() - border);
|
||||
mp.drawLine(0, 0, border, border);
|
||||
mp.drawLine(0, mp.getYMax(), border, mp.getYMax() - border);
|
||||
mp.drawLine(mp.getXMax(), 0, mp.getXMax() - border, border);
|
||||
mp.drawLine(mp.getXMax(), mp.getYMax(), mp.getXMax() - border, mp.getYMax() - border);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(TITLE_TEXT)) / 2, (mp.getYMax() + mp.getFontHeight()) / 2, TITLE_TEXT);
|
||||
delay(SPLASH_DELAY);
|
||||
curState = S_START;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_START: // initialize for a new game
|
||||
PRINTSTATE("S_START");
|
||||
displayGrid();
|
||||
inGamePlay = TTT.start();
|
||||
curState = S_GET_MOVE;
|
||||
break;
|
||||
|
||||
case S_GET_MOVE: // get and make player move - this section is non-blocking
|
||||
{
|
||||
uint8_t m = 0;
|
||||
|
||||
if (TTT.getAutoPlayer() != curPlayer)
|
||||
m = getMove();
|
||||
|
||||
if (m != 0xff)
|
||||
{
|
||||
TTT.doMove(m, curPlayer);
|
||||
curState = S_CHECK_END;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case S_CHECK_END: // switch players and check if this is the end of the game
|
||||
PRINTSTATE("S_CHECK_END");
|
||||
if (TTT.isGameOver())
|
||||
{
|
||||
inGamePlay = false;
|
||||
|
||||
if (TTT.getGameWinner() == TTT_P0)
|
||||
{
|
||||
userMessage("A draw");
|
||||
delay(FLASH_REPEAT * 2 * FLASH_DELAY); // yes this blocks - so does flashLine()
|
||||
}
|
||||
else if (TTT.getGameWinner() == TTT.getAutoPlayer())
|
||||
userMessage("I win!");
|
||||
else
|
||||
userMessage("You win!");
|
||||
|
||||
if (TTT.getGameWinner() != TTT_P0) // not a draw
|
||||
flashLine(TTT.getWinLine());
|
||||
|
||||
curState = S_START; // restart
|
||||
}
|
||||
else
|
||||
curState = S_GET_MOVE; // get or make next move
|
||||
|
||||
// switch turns for players
|
||||
curPlayer = (curPlayer == TTT_P1 ? TTT_P2 : TTT_P1);
|
||||
break;
|
||||
|
||||
default:
|
||||
PRINTSTATE("DEFAULT!");
|
||||
curState = S_START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Coordinates for the top left corner of the board cells on the display and size (square)
|
||||
typedef struct
|
||||
{
|
||||
uint16_t x, y;
|
||||
uint8_t size;
|
||||
} boardCoord_t;
|
||||
|
||||
// Switch status
|
||||
typedef struct
|
||||
{
|
||||
uint8_t pin;
|
||||
bool lastState;
|
||||
uint32_t lastCheckTime;
|
||||
} swState_t;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
MD_MAX72XX::fontType_t _Fixed_5x3[] PROGMEM = {
|
||||
'F', 1, 32, 127, 5,
|
||||
2, 0, 0, // 32 - 'Space'
|
||||
1, 23, // 33 - '!'
|
||||
3, 3, 0, 3, // 34 - '"'
|
||||
3, 31, 10, 31, // 35 - '#'
|
||||
3, 22, 31, 13, // 36 - '$'
|
||||
3, 9, 4, 18, // 37 - '%'
|
||||
3, 10, 21, 26, // 38 - '&'
|
||||
1, 3, // 39
|
||||
2, 14, 17, // 40 - '('
|
||||
2, 17, 14, // 41 - ')'
|
||||
3, 10, 4, 10, // 42 - '*'
|
||||
3, 4, 14, 4, // 43 - '+'
|
||||
2, 16, 8, // 44 - ','
|
||||
3, 4, 4, 4, // 45 - '-'
|
||||
1, 16, // 46 - '.'
|
||||
3, 8, 4, 2, // 47 - '/'
|
||||
3, 31, 17, 31, // 48 - '0'
|
||||
2, 0, 31, // 49 - '1'
|
||||
3, 29, 21, 23, // 50 - '2'
|
||||
3, 17, 21, 31, // 51 - '3'
|
||||
3, 7, 4, 31, // 52 - '4'
|
||||
3, 23, 21, 29, // 53 - '5'
|
||||
3, 31, 21, 29, // 54 - '6'
|
||||
3, 1, 1, 31, // 55 - '7'
|
||||
3, 31, 21, 31, // 56 - '8'
|
||||
3, 23, 21, 31, // 57 - '9'
|
||||
1, 10, // 58 - ':'
|
||||
2, 16, 10, // 59 - ';'
|
||||
3, 4, 10, 17, // 60 - '<'
|
||||
3, 10, 10, 10, // 61 - '='
|
||||
3, 17, 10, 4, // 62 - '>'
|
||||
3, 1, 21, 3, // 63 - '?'
|
||||
3, 14, 21, 22, // 64 - '@'
|
||||
3, 30, 5, 30, // 65 - 'A'
|
||||
3, 31, 21, 10, // 66 - 'B'
|
||||
3, 14, 17, 17, // 67 - 'C'
|
||||
3, 31, 17, 14, // 68 - 'D'
|
||||
3, 31, 21, 17, // 69 - 'E'
|
||||
3, 31, 5, 1, // 70 - 'F'
|
||||
3, 14, 17, 29, // 71 - 'G'
|
||||
3, 31, 4, 31, // 72 - 'H'
|
||||
3, 17, 31, 17, // 73 - 'I'
|
||||
3, 8, 16, 15, // 74 - 'J'
|
||||
3, 31, 4, 27, // 75 - 'K'
|
||||
3, 31, 16, 16, // 76 - 'L'
|
||||
3, 31, 2, 31, // 77 - 'M'
|
||||
3, 31, 14, 31, // 78 - 'N'
|
||||
3, 14, 17, 14, // 79 - 'O'
|
||||
3, 31, 5, 2, // 80 - 'P'
|
||||
3, 14, 25, 30, // 81 - 'Q'
|
||||
3, 31, 5, 26, // 82 - 'R'
|
||||
3, 18, 21, 9, // 83 - 'S'
|
||||
3, 1, 31, 1, // 84 - 'T'
|
||||
3, 15, 16, 15, // 85 - 'U'
|
||||
3, 7, 24, 7, // 86 - 'V'
|
||||
3, 15, 28, 15, // 87 - 'W'
|
||||
3, 27, 4, 27, // 88 - 'X'
|
||||
3, 3, 28, 3, // 89 - 'Y'
|
||||
3, 25, 21, 19, // 90 - 'Z'
|
||||
2, 31, 17, // 91 - '['
|
||||
3, 2, 4, 8, // 92 - '\'
|
||||
2, 17, 31, // 93 - ']'
|
||||
3, 2, 1, 2, // 94 - '^'
|
||||
3, 16, 16, 16, // 95 - '_'
|
||||
2, 1, 2, // 96 - '`'
|
||||
3, 12, 18, 28, // 97 - 'a'
|
||||
3, 31, 18, 12, // 98 - 'b'
|
||||
3, 12, 18, 18, // 99 - 'c'
|
||||
3, 12, 18, 31, // 100 - 'd'
|
||||
3, 12, 26, 20, // 101 - 'e'
|
||||
3, 4, 31, 5, // 102 - 'f'
|
||||
3, 20, 26, 12, // 103 - 'g'
|
||||
3, 31, 2, 28, // 104 - 'h'
|
||||
1, 29, // 105 - 'i'
|
||||
2, 16, 13, // 106 - 'j'
|
||||
3, 31, 8, 20, // 107 - 'k'
|
||||
1, 31, // 108 - 'l'
|
||||
3, 30, 6, 30, // 109 - 'm'
|
||||
3, 30, 2, 28, // 110 - 'n'
|
||||
3, 12, 18, 12, // 111 - 'o'
|
||||
3, 30, 10, 4, // 112 - 'p'
|
||||
3, 4, 10, 30, // 113 - 'q'
|
||||
2, 30, 4, // 114 - 'r'
|
||||
3, 20, 30, 10, // 115 - 's'
|
||||
3, 4, 30, 4, // 116 - 't'
|
||||
3, 14, 16, 30, // 117 - 'u'
|
||||
3, 14, 16, 14, // 118 - 'v'
|
||||
3, 14, 24, 14, // 119 - 'w'
|
||||
3, 18, 12, 18, // 120 - 'x'
|
||||
3, 22, 24, 14, // 121 - 'y'
|
||||
3, 26, 30, 22, // 122 - 'z'
|
||||
3, 4, 27, 17, // 123 - '{'
|
||||
1, 27, // 124 - '|'
|
||||
3, 17, 27, 4, // 125 - '}'
|
||||
3, 6, 2, 3, // 126 - '~'
|
||||
3, 31, 31, 31, // 127 - 'Full Block'
|
||||
};
|
||||
@@ -0,0 +1,442 @@
|
||||
// Program to exercise the MD_MAXPanel library
|
||||
//
|
||||
// Uses most of the functions in the library
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
#include "Font5x3.h"
|
||||
|
||||
// Turn on debug statements to the serial output
|
||||
#define DEBUG 1
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
|
||||
#define PRINTS(x) Serial.print(F(x))
|
||||
#define PRINTD(x) Serial.print(x, DEC)
|
||||
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTD(x)
|
||||
|
||||
#endif
|
||||
|
||||
// Define the number of devices we have in the chain and the hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 13; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// SPI hardware interface
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
// Arbitrary pins
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
|
||||
// We always wait a bit between updates of the display
|
||||
#define DELAYTIME 100 // in milliseconds
|
||||
|
||||
void zeroPointer(void)
|
||||
// Demonstrates the use of setPoint and
|
||||
// show where the zero point is in the display
|
||||
{
|
||||
PRINTS("\nZero point highlight");
|
||||
mp.clear();
|
||||
|
||||
for (uint8_t i=0; i<=max(mp.getXMax(), mp.getYMax()); i++)
|
||||
{
|
||||
mp.setPoint(i, i, true);
|
||||
mp.setPoint(0, i, true);
|
||||
mp.setPoint(i, 0, true);
|
||||
delay(DELAYTIME/mp.getXMax());
|
||||
}
|
||||
|
||||
delay(DELAYTIME*6);
|
||||
}
|
||||
|
||||
void showUp(void)
|
||||
// Triangle pointing to the of the display as currently rotated
|
||||
{
|
||||
PRINTS("\nTop of display");
|
||||
char sz[] = "UP";
|
||||
uint8_t w, h;
|
||||
|
||||
mp.setFont(_Fixed_5x3);
|
||||
w = mp.getTextWidth(sz);
|
||||
h = 5;
|
||||
|
||||
mp.clear();
|
||||
mp.drawTriangle(0, mp.getYMax() / 2, (mp.getXMax() + 1) / 2, mp.getYMax(), mp.getXMax(), mp.getYMax() / 2, true);
|
||||
mp.drawText((mp.getXMax() - w) / 2, (mp.getYMax() / 2) + 2 + h, sz);
|
||||
mp.setFont(nullptr);
|
||||
|
||||
delay(DELAYTIME * 10);
|
||||
}
|
||||
|
||||
void brightness(void)
|
||||
// Demonstrate the use of setBrightness()
|
||||
// Striped display so as not to overload power supply
|
||||
{
|
||||
const uint8_t SKIP_COUNT = 3;
|
||||
uint8_t c = 0;
|
||||
|
||||
PRINTS("\nBrigtness");
|
||||
mp.clear();
|
||||
|
||||
// set up diagonal dots on the display to avoid using too
|
||||
// much power in big displays
|
||||
for (uint16_t i = 0; i <= mp.getXMax(); i++)
|
||||
{
|
||||
uint8_t ctr = c;
|
||||
|
||||
c++;
|
||||
if (c > SKIP_COUNT) c = 0;
|
||||
|
||||
for (uint16_t j = 0; j <= mp.getYMax(); j++)
|
||||
{
|
||||
mp.setPoint(i, j, (ctr == 0));
|
||||
ctr++;
|
||||
if (ctr > SKIP_COUNT) ctr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// now show the brightness
|
||||
for (uint8_t i = 0; i < MAX_INTENSITY; i++)
|
||||
{
|
||||
mp.setIntensity(i);
|
||||
PRINT(" ", i);
|
||||
delay(DELAYTIME * 5);
|
||||
}
|
||||
|
||||
// reset to a sensible value
|
||||
mp.setIntensity(7);
|
||||
}
|
||||
|
||||
void lines(void)
|
||||
// Demonstrate the use of drawLine().
|
||||
// Stepped fan out lines from each corner
|
||||
{
|
||||
PRINTS("\nLines");
|
||||
const uint8_t stepSize = 4;
|
||||
|
||||
mp.clear();
|
||||
|
||||
for (uint16_t x = 0; x<=mp.getXMax(); x += stepSize)
|
||||
{
|
||||
mp.drawLine(0, 0, x, mp.getYMax(), true);
|
||||
delay(DELAYTIME);
|
||||
mp.clear();
|
||||
}
|
||||
|
||||
for (uint16_t x = 0; x<=mp.getXMax(); x += stepSize)
|
||||
{
|
||||
mp.drawLine(0, mp.getYMax(), x, 0, true);
|
||||
delay(DELAYTIME);
|
||||
mp.clear();
|
||||
}
|
||||
|
||||
for (uint16_t x = 0; x<=mp.getXMax(); x += stepSize)
|
||||
{
|
||||
mp.drawLine(mp.getXMax(), mp.getYMax(), mp.getXMax() - x, 0, true);
|
||||
delay(DELAYTIME);
|
||||
mp.clear();
|
||||
}
|
||||
|
||||
for (uint16_t x = 0; x<=mp.getXMax(); x += stepSize)
|
||||
{
|
||||
mp.drawLine(mp.getXMax(), 0, mp.getXMax() - x, mp.getYMax(), true);
|
||||
delay(DELAYTIME);
|
||||
mp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void hLines(void)
|
||||
// Demonstrate the use of drawHLine()
|
||||
// Draw a sawtooth across the display (depends on aspect ratio)
|
||||
{
|
||||
PRINTS("\nHorizontal Lines");
|
||||
|
||||
mp.clear();
|
||||
for (uint8_t y = 0; y < mp.getYMax(); y++)
|
||||
{
|
||||
mp.drawHLine(y, 0, y % mp.getXMax(), true);
|
||||
delay(DELAYTIME/4);
|
||||
}
|
||||
}
|
||||
|
||||
void vLines(void)
|
||||
// Demonstrate the use of drawVLine()
|
||||
// Stepped increment lines from zero point
|
||||
{
|
||||
PRINTS("\nVertical Lines");
|
||||
|
||||
mp.clear();
|
||||
for (uint16_t x = 0; x < mp.getXMax(); x++)
|
||||
{
|
||||
mp.drawVLine(x, 0, x % mp.getYMax(), true);
|
||||
delay(DELAYTIME/4);
|
||||
}
|
||||
}
|
||||
|
||||
void rectanglesFill(void)
|
||||
// Demonstrate the use of drawFillRectangle()
|
||||
// Various sized rectangles spanning the entire display
|
||||
{
|
||||
PRINTS("\nRectangles Fill");
|
||||
mp.clear();
|
||||
|
||||
for (uint8_t stepSize = 2; stepSize < 6; stepSize++)
|
||||
{
|
||||
for (uint16_t i = 0; i < min(mp.getXMax(), mp.getYMax()) / 2; i += stepSize)
|
||||
{
|
||||
mp.drawFillRectangle(i, i, mp.getXMax() - i, mp.getYMax() - i, true);
|
||||
delay(2 * DELAYTIME);
|
||||
mp.drawFillRectangle(i, i, mp.getXMax() - i, mp.getYMax() - i, false);
|
||||
delay(2 * DELAYTIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rectangles(void)
|
||||
// Demonstrate the use of drawRectangle()
|
||||
// Nested rectangles spanning the entire display
|
||||
{
|
||||
PRINTS("\nRectangles");
|
||||
mp.clear();
|
||||
|
||||
for (uint8_t stepSize = 2; stepSize < 6; stepSize++)
|
||||
{
|
||||
for (uint16_t i = 0; i < min(mp.getXMax(), mp.getYMax()) / 2; i += stepSize)
|
||||
{
|
||||
mp.drawRectangle(i, i, mp.getXMax() - i, mp.getYMax() - i, true);
|
||||
delay(2 * DELAYTIME);
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < min(mp.getXMax(), mp.getYMax()) / 2; i += stepSize)
|
||||
{
|
||||
mp.drawRectangle(i, i, mp.getXMax() - i, mp.getYMax() - i, false);
|
||||
delay(2 * DELAYTIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void quadrilaterals(void)
|
||||
// Demonstrate the use of drawQuadrilateral()
|
||||
// Rubber band quadrilaterals anchored to the display edge
|
||||
{
|
||||
const uint8_t numQuadri = min(mp.getXMax(), mp.getYMax())/2;
|
||||
const uint8_t stepSizeX = mp.getXMax() / numQuadri;
|
||||
const uint8_t stepSizeY = mp.getYMax() / numQuadri;
|
||||
const uint8_t offset = 2;
|
||||
|
||||
PRINTS("\nQuadrilaterals");
|
||||
mp.clear();
|
||||
|
||||
for (uint8_t j = 0; j < 4; j++)
|
||||
{
|
||||
for (uint16_t i = 0; i < numQuadri; i++)
|
||||
{
|
||||
uint16_t x = stepSizeX * i;
|
||||
uint16_t y = stepSizeY * i;
|
||||
|
||||
mp.drawQuadrilateral(x+offset, 0+offset, 0+offset, mp.getYMax() - y - offset, mp.getXMax() - x - offset, mp.getYMax() - offset, mp.getXMax() - offset, y + offset, true);
|
||||
delay(DELAYTIME/2);
|
||||
mp.drawQuadrilateral(x + offset, 0 + offset, 0 + offset, mp.getYMax() - y - offset, mp.getXMax() - x - offset, mp.getYMax() - offset, mp.getXMax() - offset, y + offset, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void trianglesFill(void)
|
||||
// Demonstrate the use of drawFillTriangle()
|
||||
// Random triangles.
|
||||
{
|
||||
const uint8_t NUM_TRIANGLE = 25;
|
||||
const uint8_t NUM_VERTEX = 3;
|
||||
|
||||
uint16_t x[NUM_VERTEX], y[NUM_VERTEX];
|
||||
|
||||
PRINTS("\nTriangles Fill");
|
||||
mp.clear();
|
||||
|
||||
for (uint8_t j = 0; j < NUM_TRIANGLE; j++)
|
||||
{
|
||||
for (uint16_t i = 0; i < NUM_VERTEX; i++)
|
||||
{
|
||||
x[i] = random(mp.getXMax() + 1);
|
||||
y[i] = random(mp.getYMax() + 1);
|
||||
}
|
||||
|
||||
mp.drawFillTriangle(x[0], y[0], x[1], y[1], x[2], y[2], true);
|
||||
delay(2 * DELAYTIME);
|
||||
mp.drawFillTriangle(x[0], y[0], x[1], y[1], x[2], y[2], false);
|
||||
delay(2 * DELAYTIME);
|
||||
}
|
||||
}
|
||||
|
||||
void triangles(void)
|
||||
// Demonstrate the use of drawTriangle()
|
||||
// Random triangles.
|
||||
{
|
||||
const uint8_t NUM_TRIANGLE = 25;
|
||||
const uint8_t NUM_VERTEX = 3;
|
||||
|
||||
uint16_t x[NUM_VERTEX], y[NUM_VERTEX];
|
||||
|
||||
PRINTS("\nTriangles");
|
||||
mp.clear();
|
||||
|
||||
for (uint8_t j = 0; j < NUM_TRIANGLE; j++)
|
||||
{
|
||||
for (uint16_t i = 0; i < NUM_VERTEX; i++)
|
||||
{
|
||||
x[i] = random(mp.getXMax() + 1);
|
||||
y[i] = random(mp.getYMax() + 1);
|
||||
}
|
||||
|
||||
mp.drawTriangle(x[0], y[0], x[1], y[1], x[2], y[2], true);
|
||||
delay(2 * DELAYTIME);
|
||||
mp.drawTriangle(x[0], y[0], x[1], y[1], x[2], y[2], false);
|
||||
delay(2 * DELAYTIME);
|
||||
}
|
||||
}
|
||||
|
||||
void circlesFill(void)
|
||||
// Demonstrate the use of drawFillCircle()
|
||||
// Nested circles spanning the entire display
|
||||
{
|
||||
PRINTS("\nCircles Fill");
|
||||
mp.clear();
|
||||
|
||||
for (uint8_t stepSize = 2; stepSize < 6; stepSize++)
|
||||
{
|
||||
for (uint16_t i = stepSize; i < min(mp.getXMax(), mp.getYMax()) / 2; i += stepSize)
|
||||
{
|
||||
mp.drawFillCircle(mp.getXMax()/2, mp.getYMax()/2, i, true);
|
||||
delay(2 * DELAYTIME);
|
||||
mp.drawFillCircle(mp.getXMax() / 2, mp.getYMax() / 2, i, false);
|
||||
delay(2 * DELAYTIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void circles(void)
|
||||
// Demonstrate the use of drawCircle()
|
||||
// Nested circles spanning the entire display
|
||||
{
|
||||
PRINTS("\nCircles");
|
||||
mp.clear();
|
||||
|
||||
for (uint8_t stepSize = 2; stepSize < 6; stepSize++)
|
||||
{
|
||||
for (uint16_t i = stepSize; i < min(mp.getXMax(), mp.getYMax()) / 2; i += stepSize)
|
||||
{
|
||||
mp.drawCircle(mp.getXMax() / 2, mp.getYMax() / 2, i, true);
|
||||
delay(2 * DELAYTIME);
|
||||
}
|
||||
|
||||
for (uint16_t i = stepSize; i < min(mp.getXMax(), mp.getYMax()) / 2; i += stepSize)
|
||||
{
|
||||
mp.drawCircle(mp.getXMax() / 2, mp.getYMax() / 2, i, false);
|
||||
delay(2 * DELAYTIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bounce(void)
|
||||
// Animation of a bouncing ball
|
||||
{
|
||||
const uint16_t minX = 0;
|
||||
const uint16_t maxX = mp.getXMax();
|
||||
const uint16_t minY = 0;
|
||||
const uint16_t maxY = mp.getYMax();
|
||||
|
||||
uint16_t nCounter = 0;
|
||||
|
||||
uint16_t y = random(maxY/2) + maxY/2, x = random(maxX/2) + maxX/2;
|
||||
int8_t dy = 1, dx = 1; // delta row and column
|
||||
|
||||
PRINTS("\nBouncing ball");
|
||||
mp.clear();
|
||||
|
||||
while (nCounter++ < 200)
|
||||
{
|
||||
mp.setPoint(x, y, false);
|
||||
x += dx;
|
||||
y += dy;
|
||||
mp.setPoint(x, y, true);
|
||||
delay(DELAYTIME/2);
|
||||
|
||||
if ((x == minX) || (x == maxX))
|
||||
dx = -dx;
|
||||
if ((y == minY) || (y == maxY))
|
||||
dy = -dy;
|
||||
}
|
||||
}
|
||||
|
||||
void text(MD_MAX72XX::fontType_t *fontData)
|
||||
// Demonstrate the use of drawText()
|
||||
// Display text in different orientations and fonts
|
||||
{
|
||||
PRINTS("\nText");
|
||||
|
||||
mp.setFont(fontData);
|
||||
PRINT(" - Font height: ", mp.getFontHeight());
|
||||
|
||||
mp.clear();
|
||||
mp.drawText(0, mp.getYMax(), "R_0", MD_MAXPanel::ROT_0);
|
||||
delay(5 * DELAYTIME);
|
||||
|
||||
mp.clear();
|
||||
mp.drawText(0, 0, "R_90", MD_MAXPanel::ROT_90);
|
||||
delay(5 * DELAYTIME);
|
||||
|
||||
mp.clear();
|
||||
mp.drawText(mp.getXMax(), 0, "R_180", MD_MAXPanel::ROT_180);
|
||||
delay(5 * DELAYTIME);
|
||||
|
||||
mp.clear();
|
||||
mp.drawText(mp.getXMax(), mp.getYMax(), "R_270", MD_MAXPanel::ROT_270);
|
||||
delay(5 * DELAYTIME);
|
||||
}
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
mp.begin();
|
||||
|
||||
#if DEBUG
|
||||
Serial.begin(57600);
|
||||
#endif
|
||||
PRINTS("\n[MD_MAXPanel Test & Demo]");
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
zeroPointer();
|
||||
showUp();
|
||||
brightness();
|
||||
lines();
|
||||
hLines();
|
||||
vLines();
|
||||
rectangles();
|
||||
rectanglesFill();
|
||||
circles();
|
||||
circlesFill();
|
||||
triangles();
|
||||
trianglesFill();
|
||||
quadrilaterals();
|
||||
bounce();
|
||||
text(_Fixed_5x3);
|
||||
text(nullptr);
|
||||
|
||||
// rotate the display and do it all again
|
||||
mp.setRotation(mp.getRotation() == MD_MAXPanel::ROT_0 ? MD_MAXPanel::ROT_90 : MD_MAXPanel::ROT_0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
MD_MAX72XX::fontType_t _Fixed_5x3[] PROGMEM = {
|
||||
'F', 1, 32, 127, 5,
|
||||
2, 0, 0, // 32 - 'Space'
|
||||
1, 23, // 33 - '!'
|
||||
3, 3, 0, 3, // 34 - '"'
|
||||
3, 31, 10, 31, // 35 - '#'
|
||||
3, 22, 31, 13, // 36 - '$'
|
||||
3, 9, 4, 18, // 37 - '%'
|
||||
3, 10, 21, 26, // 38 - '&'
|
||||
1, 3, // 39
|
||||
2, 14, 17, // 40 - '('
|
||||
2, 17, 14, // 41 - ')'
|
||||
3, 10, 4, 10, // 42 - '*'
|
||||
3, 4, 14, 4, // 43 - '+'
|
||||
2, 16, 8, // 44 - ','
|
||||
3, 4, 4, 4, // 45 - '-'
|
||||
1, 16, // 46 - '.'
|
||||
3, 8, 4, 2, // 47 - '/'
|
||||
3, 31, 17, 31, // 48 - '0'
|
||||
2, 0, 31, // 49 - '1'
|
||||
3, 29, 21, 23, // 50 - '2'
|
||||
3, 17, 21, 31, // 51 - '3'
|
||||
3, 7, 4, 31, // 52 - '4'
|
||||
3, 23, 21, 29, // 53 - '5'
|
||||
3, 31, 21, 29, // 54 - '6'
|
||||
3, 1, 1, 31, // 55 - '7'
|
||||
3, 31, 21, 31, // 56 - '8'
|
||||
3, 23, 21, 31, // 57 - '9'
|
||||
1, 10, // 58 - ':'
|
||||
2, 16, 10, // 59 - ';'
|
||||
3, 4, 10, 17, // 60 - '<'
|
||||
3, 10, 10, 10, // 61 - '='
|
||||
3, 17, 10, 4, // 62 - '>'
|
||||
3, 1, 21, 3, // 63 - '?'
|
||||
3, 14, 21, 22, // 64 - '@'
|
||||
3, 30, 5, 30, // 65 - 'A'
|
||||
3, 31, 21, 10, // 66 - 'B'
|
||||
3, 14, 17, 17, // 67 - 'C'
|
||||
3, 31, 17, 14, // 68 - 'D'
|
||||
3, 31, 21, 17, // 69 - 'E'
|
||||
3, 31, 5, 1, // 70 - 'F'
|
||||
3, 14, 17, 29, // 71 - 'G'
|
||||
3, 31, 4, 31, // 72 - 'H'
|
||||
3, 17, 31, 17, // 73 - 'I'
|
||||
3, 8, 16, 15, // 74 - 'J'
|
||||
3, 31, 4, 27, // 75 - 'K'
|
||||
3, 31, 16, 16, // 76 - 'L'
|
||||
3, 31, 2, 31, // 77 - 'M'
|
||||
3, 31, 14, 31, // 78 - 'N'
|
||||
3, 14, 17, 14, // 79 - 'O'
|
||||
3, 31, 5, 2, // 80 - 'P'
|
||||
3, 14, 25, 30, // 81 - 'Q'
|
||||
3, 31, 5, 26, // 82 - 'R'
|
||||
3, 18, 21, 9, // 83 - 'S'
|
||||
3, 1, 31, 1, // 84 - 'T'
|
||||
3, 15, 16, 15, // 85 - 'U'
|
||||
3, 7, 24, 7, // 86 - 'V'
|
||||
3, 15, 28, 15, // 87 - 'W'
|
||||
3, 27, 4, 27, // 88 - 'X'
|
||||
3, 3, 28, 3, // 89 - 'Y'
|
||||
3, 25, 21, 19, // 90 - 'Z'
|
||||
2, 31, 17, // 91 - '['
|
||||
3, 2, 4, 8, // 92 - '\'
|
||||
2, 17, 31, // 93 - ']'
|
||||
3, 2, 1, 2, // 94 - '^'
|
||||
3, 16, 16, 16, // 95 - '_'
|
||||
2, 1, 2, // 96 - '`'
|
||||
3, 12, 18, 28, // 97 - 'a'
|
||||
3, 31, 18, 12, // 98 - 'b'
|
||||
3, 12, 18, 18, // 99 - 'c'
|
||||
3, 12, 18, 31, // 100 - 'd'
|
||||
3, 12, 26, 20, // 101 - 'e'
|
||||
3, 4, 31, 5, // 102 - 'f'
|
||||
3, 20, 26, 12, // 103 - 'g'
|
||||
3, 31, 2, 28, // 104 - 'h'
|
||||
1, 29, // 105 - 'i'
|
||||
2, 16, 13, // 106 - 'j'
|
||||
3, 31, 8, 20, // 107 - 'k'
|
||||
1, 31, // 108 - 'l'
|
||||
3, 30, 6, 30, // 109 - 'm'
|
||||
3, 30, 2, 28, // 110 - 'n'
|
||||
3, 12, 18, 12, // 111 - 'o'
|
||||
3, 30, 10, 4, // 112 - 'p'
|
||||
3, 4, 10, 30, // 113 - 'q'
|
||||
2, 30, 4, // 114 - 'r'
|
||||
3, 20, 30, 10, // 115 - 's'
|
||||
3, 4, 30, 4, // 116 - 't'
|
||||
3, 14, 16, 30, // 117 - 'u'
|
||||
3, 14, 16, 14, // 118 - 'v'
|
||||
3, 14, 24, 14, // 119 - 'w'
|
||||
3, 18, 12, 18, // 120 - 'x'
|
||||
3, 22, 24, 14, // 121 - 'y'
|
||||
3, 26, 30, 22, // 122 - 'z'
|
||||
3, 4, 27, 17, // 123 - '{'
|
||||
1, 27, // 124 - '|'
|
||||
3, 17, 27, 4, // 125 - '}'
|
||||
3, 6, 2, 3, // 126 - '~'
|
||||
3, 31, 31, 31, // 127 - 'Full Block'
|
||||
};
|
||||
@@ -0,0 +1,694 @@
|
||||
// Implements the game of Tetris using MD_MAXPanel library
|
||||
// Tips for coding this taken from https://www.youtube.com/watch?v=8OK8_tHeCIA
|
||||
//
|
||||
// Hardware used
|
||||
// =============
|
||||
//
|
||||
// LEFT_PIN - move left switch, INPUT_PULLUP
|
||||
// RIGHT_PIN - move right switch, INPUT_PULLUP
|
||||
// UP_PIN - unused
|
||||
// DOWN_PIN - drop the piece switch, INPUT_PULLUP
|
||||
// SELECT_PIN - rotate the piece switch, INPUT_PULLUP
|
||||
// ENTER_PIN - unused
|
||||
// BEEPER_PIN - piezo speaker
|
||||
// CLK_PIN, DATA_PIN, CS_PIN - LED matrix display connections
|
||||
//
|
||||
// Libraries used
|
||||
// ==============
|
||||
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
// Rules of the Game
|
||||
// =================
|
||||
// Game pieces are shaped like tetrominoes, geometric shapes composed of four
|
||||
// square blocks each. A random sequence of tetrominoes fall down the playing
|
||||
// field (a rectangular vertical shaft, called the "well" or "matrix"). The
|
||||
// objective of the game is to manipulate these tetrominoes, by moving each one
|
||||
// sideways (if the player feels the need) and rotating it by 90 degree units,
|
||||
// with the aim of creating a horizontal line of ten units without gaps. When
|
||||
// such a line is created, it gets destroyed, and any block above the deleted
|
||||
// line will fall. When a certain number of lines are cleared, the game enters
|
||||
// a new level. As the game progresses, each level causes the tetrominoes to fall
|
||||
// faster, and the game ends when the stack of tetrominoes reaches the top of
|
||||
// the playing field and no new tetrominos are able to enter.
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
#include "Font5x3.h"
|
||||
#include "score.h"
|
||||
#include "sound.h"
|
||||
#include "randomseed.h"
|
||||
|
||||
// Turn on debug statements to the serial output
|
||||
#define DEBUG 0
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
|
||||
#define PRINTS(x) { Serial.print(F(x)); }
|
||||
#define PRINTD(x) { Serial.print(x, DEC); }
|
||||
#define PRINTXY(s,x,y) { Serial.print(F(s)); Serial.print(F("(")); Serial.print(x); Serial.print(F(",")); Serial.print(y); Serial.print(")"); }
|
||||
#define PRINTSTATE(s) { Serial.print("\n++>"); Serial.print(s); }
|
||||
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTD(x)
|
||||
#define PRINTXY(s,x,y)
|
||||
#define PRINTSTATE(s)
|
||||
|
||||
#endif
|
||||
|
||||
// Hardware pin definitions.
|
||||
// All momentary on switches are initialized PULLUP
|
||||
const uint8_t UP_PIN = 2;
|
||||
const uint8_t RIGHT_PIN = 3;
|
||||
const uint8_t DOWN_PIN = 4;
|
||||
const uint8_t LEFT_PIN = 5;
|
||||
const uint8_t SELECT_PIN = 6;
|
||||
const uint8_t ENTER_PIN = 7;
|
||||
const uint8_t BEEPER_PIN = 9;
|
||||
|
||||
// Define the number of devices in the chain and the SPI hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
|
||||
const uint8_t X_DEVICES = 4;
|
||||
const uint8_t Y_DEVICES = 5;
|
||||
|
||||
const uint8_t CLK_PIN = 12; // or SCK
|
||||
const uint8_t DATA_PIN = 11; // or MOSI
|
||||
const uint8_t CS_PIN = 10; // or SS
|
||||
|
||||
// SPI hardware interface
|
||||
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
// Arbitrary pins
|
||||
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
|
||||
|
||||
const uint16_t FIELD_WIDTH = 10; // playable area width inside 'bucket'
|
||||
const uint16_t FIELD_HEIGHT = 21; // playable area depth inside 'bucket'
|
||||
|
||||
const uint16_t FIELD_BOTTOM = 3; // y coord bottom wall
|
||||
const uint16_t FIELD_LEFT = 5; // x coord left wall
|
||||
const uint16_t FIELD_TOP = FIELD_BOTTOM + FIELD_HEIGHT; // y coord top opening of the 'bucket'
|
||||
const uint16_t FIELD_RIGHT = FIELD_LEFT + FIELD_WIDTH + 1; // x coord right wall
|
||||
|
||||
const char TITLE_TEXT[] = "TETRIS";
|
||||
const uint16_t SPLASH_DELAY = 3000; // in milliseconds
|
||||
|
||||
const char GAME_TEXT[] = "GAME";
|
||||
const char OVER_TEXT[] = "OVER";
|
||||
const uint16_t GAME_OVER_DELAY = 3000; // in milliseconds
|
||||
|
||||
const uint8_t FONT_NUM_WIDTH = 3;
|
||||
const uint16_t MAX_SCORE = 60000;
|
||||
const uint16_t PIECE_SCORE = 5;
|
||||
const uint16_t LINE_SCORE = 20;
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
|
||||
|
||||
// A class to encapsulate the snake direction switches
|
||||
// Can move up, down, rotate, drop
|
||||
class cMoveSW
|
||||
{
|
||||
private:
|
||||
uint8_t _pinLeft;
|
||||
uint8_t _pinRight;
|
||||
uint8_t _pinRotate;
|
||||
uint8_t _pinDrop;
|
||||
uint16_t _timeDelay; // the delay between switch detection
|
||||
uint32_t _timeLastCheck; // the millis() value for the last time we checked
|
||||
|
||||
public:
|
||||
enum moveType_t { MOVE_NONE, MOVE_LEFT, MOVE_RIGHT, MOVE_DROP, MOVE_ROTATE };
|
||||
|
||||
void begin(uint8_t pinL, uint8_t pinR, uint8_t pinRot, uint8_t pinD)
|
||||
{
|
||||
_timeDelay = 100;
|
||||
_pinLeft = pinL;
|
||||
_pinRight = pinR;
|
||||
_pinRotate = pinRot;
|
||||
_pinDrop = pinD;
|
||||
_timeLastCheck = 0;
|
||||
pinMode(_pinLeft, INPUT_PULLUP);
|
||||
pinMode(_pinRight, INPUT_PULLUP);
|
||||
pinMode(_pinRotate, INPUT_PULLUP);
|
||||
pinMode(_pinDrop, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
bool anyKey(void) { return (move() != MOVE_NONE);}
|
||||
|
||||
moveType_t move(void)
|
||||
{
|
||||
if (millis() - _timeLastCheck < _timeDelay)
|
||||
return(MOVE_NONE);
|
||||
_timeLastCheck = millis();
|
||||
|
||||
if (digitalRead(_pinLeft) == LOW)
|
||||
return(MOVE_LEFT);
|
||||
else if (digitalRead(_pinRight) == LOW)
|
||||
return(MOVE_RIGHT);
|
||||
else if (digitalRead(_pinRotate) == LOW)
|
||||
return(MOVE_ROTATE);
|
||||
else if (digitalRead(_pinDrop) == LOW)
|
||||
return(MOVE_DROP);
|
||||
|
||||
return(MOVE_NONE);
|
||||
}
|
||||
};
|
||||
|
||||
// A class to encapsulate the Tetris field
|
||||
class cTetris
|
||||
{
|
||||
private:
|
||||
const uint8_t OMINO_SIZE = 4; // 4x4 field flattened out into 16 bits
|
||||
|
||||
const uint8_t MAX_ROTATE = 4; // maximum number of rotations
|
||||
|
||||
uint16_t _tetromino[7]; // tetronimoes, values initialized in the constructor
|
||||
|
||||
bool _field[FIELD_WIDTH][FIELD_HEIGHT];
|
||||
|
||||
uint32_t _timeLastMove; // last time the snake was moved
|
||||
uint16_t _moveDelay; // the delay between moves in milliseconds
|
||||
bool _run; // game is playing when true
|
||||
int8_t _pieceCount; // number of pieces completed
|
||||
|
||||
uint8_t _curOmino; // current tetronimo
|
||||
uint8_t _nxtOmino; // the next tetronimo
|
||||
uint8_t _curRotation; // current rotated orientation
|
||||
uint16_t _x, _y; // _field coordinates for the piece
|
||||
cScore *_pScore; // for keeping score
|
||||
cSound *_pSound; // for making noise
|
||||
|
||||
void dumpOmino(uint8_t omino, uint8_t rot)
|
||||
// for debugging only
|
||||
{
|
||||
PRINT("\n -- DUMPOMINO ", omino); PRINT(" R:", rot);
|
||||
for (int8_t j = 0; j < OMINO_SIZE; j++)
|
||||
{
|
||||
PRINT("\n L", j); PRINTS(": ");
|
||||
for (int8_t i = 0; i < OMINO_SIZE; i++)
|
||||
{
|
||||
// Get index into piece
|
||||
int8_t idx = rotate(i, j, rot);
|
||||
|
||||
// draw the point
|
||||
if ((_tetromino[omino] >> idx) & 1) { PRINTS(" 1") }
|
||||
else { PRINTS(" 0") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void showOmino(uint8_t omino, uint8_t rot, int16_t x, int16_t y, bool b)
|
||||
// show the tetronimo on the actual display
|
||||
// x and y need to be display coordinates
|
||||
{
|
||||
//PRINT("\n-- SHOW ", omino);
|
||||
//PRINTXY(" @", x, y);
|
||||
//PRINT(" b=", b);
|
||||
mp.update(false);
|
||||
//dumpOmino(omino, rot);
|
||||
for (int8_t j = 0; j < OMINO_SIZE; j++)
|
||||
for (int8_t i = 0; i < OMINO_SIZE; i++)
|
||||
{
|
||||
// Get index into piece
|
||||
int8_t idx = rotate(i, j, rot);
|
||||
|
||||
// draw the point
|
||||
//PRINTXY(" ", FIELD_LEFT + x + i, FIELD_TOP - y - j);
|
||||
//PRINT(" i", idx);
|
||||
//PRINT(" v", (_tetromino[omino] >> idx) & 1);
|
||||
if ((_tetromino[omino] >> idx) & 1)
|
||||
mp.setPoint(x + i, y - j, b);
|
||||
}
|
||||
mp.update(true);
|
||||
}
|
||||
|
||||
void drawNxtOmino(void) { showOmino(_nxtOmino, 0, FIELD_RIGHT + OMINO_SIZE, FIELD_TOP - (2*OMINO_SIZE), true); }
|
||||
void eraseNxtOmino(void) { showOmino(_nxtOmino, 0, FIELD_RIGHT + OMINO_SIZE, FIELD_TOP - (2*OMINO_SIZE), false); }
|
||||
void drawOmino(uint8_t omino, uint8_t rot, int16_t x, int16_t y) { showOmino(omino, rot, FIELD_LEFT + x + 1, FIELD_TOP - y, true); }
|
||||
void eraseOmino(uint8_t omino, uint8_t rot, int16_t x, int16_t y) { showOmino(omino, rot, FIELD_LEFT + x + 1, FIELD_TOP - y, false); }
|
||||
|
||||
void omino2Field(uint8_t omino, uint8_t rot, int16_t x, int16_t y)
|
||||
// copy the tetronimo to the field, leaving the rest untouched
|
||||
{
|
||||
for (int8_t j = 0; j < OMINO_SIZE; j++)
|
||||
{
|
||||
for (int8_t i = 0; i < OMINO_SIZE; i++)
|
||||
{
|
||||
// Get index into piece
|
||||
int8_t idx = rotate(i, j, rot);
|
||||
|
||||
// set in field if set in tetronimo
|
||||
if ((_tetromino[omino] >> idx) & 1)
|
||||
_field[x + i][y + j] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void displayField(void)
|
||||
// display the field, mindful of offsets in displayable field
|
||||
{
|
||||
mp.update(false);
|
||||
for (uint8_t j = 0; j < FIELD_HEIGHT; j++)
|
||||
for (uint8_t i = 0; i < FIELD_WIDTH; i++)
|
||||
mp.setPoint(FIELD_LEFT+i+1, FIELD_TOP-j, _field[i][j]);
|
||||
mp.update(true);
|
||||
}
|
||||
|
||||
void clearField(void)
|
||||
// clear the playing field
|
||||
{
|
||||
memset(_field, 0, sizeof(bool)*FIELD_HEIGHT*FIELD_WIDTH);
|
||||
displayField();
|
||||
}
|
||||
|
||||
uint8_t rotate(uint8_t x, uint8_t y, uint8_t r)
|
||||
// return the linear index for from the x, y coords and the
|
||||
// current rotation
|
||||
{
|
||||
uint8_t idx = 0;
|
||||
|
||||
switch (r) // Rotation effect
|
||||
{
|
||||
case 0: // 0 degrees // 0 1 2 3
|
||||
idx = y * 4 + x; // 4 5 6 7
|
||||
break; // 8 9 10 11
|
||||
//12 13 14 15
|
||||
|
||||
case 1: // 90 degrees //12 8 4 0
|
||||
idx = 12 + y - (x * 4); //13 9 5 1
|
||||
break; //14 10 6 2
|
||||
//15 11 7 3
|
||||
|
||||
case 2: // 180 degrees //15 14 13 12
|
||||
idx = 15 - (y * 4) - x; //11 10 9 8
|
||||
break; // 7 6 5 4
|
||||
// 3 2 1 0
|
||||
|
||||
case 3: // 270 degrees // 3 7 11 15
|
||||
idx = 3 - y + (x * 4); // 2 6 10 14
|
||||
break; // 1 5 9 13
|
||||
} // 0 4 8 12
|
||||
|
||||
return(idx);
|
||||
}
|
||||
|
||||
bool checkPieceFit(uint8_t omino, uint8_t rot, int16_t x, int16_t y)
|
||||
// does the piece fit in the _field array?
|
||||
{
|
||||
/*
|
||||
PRINTXY("\n-- CHKFIT @", x, y);
|
||||
PRINT(" T:", omino);
|
||||
PRINT(" R:", rot);
|
||||
PRINT(" H:", FIELD_HEIGHT);
|
||||
PRINT(" W:", FIELD_WIDTH);
|
||||
*/
|
||||
for (int8_t j = 0; j < OMINO_SIZE; j++)
|
||||
{
|
||||
for (int8_t i = 0; i < OMINO_SIZE; i++)
|
||||
{
|
||||
// Get index into piece
|
||||
int8_t idx = rotate(i, j, rot);
|
||||
bool bCell = (_tetromino[omino] >> idx) & 1;
|
||||
|
||||
if (bCell) // we have an occupied cell in the tetromino
|
||||
{
|
||||
// PRINTXY("\n", i, j); PRINTXY("=>", x + i, y + j); PRINT(" i:", idx); PRINT(" cell: ", bCell);
|
||||
// check it is in bounds
|
||||
if (x + i < 0 || x + i >= FIELD_WIDTH)
|
||||
{
|
||||
//PRINTS(" fail x");
|
||||
return(false); // out of side bounds
|
||||
}
|
||||
else if (y + j < 0 || y + j >= FIELD_HEIGHT)
|
||||
{
|
||||
//PRINTS(" fail y");
|
||||
return(false); // out of bounds
|
||||
}
|
||||
else if (_field[x + i][y + j]) // in bounds - do a collision check
|
||||
{
|
||||
//PRINTS(" fail field");
|
||||
return(false); // occupied field cell
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//PRINTS(" pass");
|
||||
return(true);
|
||||
}
|
||||
|
||||
public:
|
||||
enum moveType_t { M_LEFT, M_RIGHT, M_DROP, M_ROTATE };
|
||||
|
||||
cTetris(void)
|
||||
{
|
||||
// straight block
|
||||
// 0010
|
||||
// 0010
|
||||
// 0010
|
||||
// 0010
|
||||
_tetromino[0] = 0x2222;
|
||||
|
||||
// T block
|
||||
// 0010
|
||||
// 0110
|
||||
// 0010
|
||||
// 0000
|
||||
_tetromino[1] = 0x2620;
|
||||
|
||||
// square block
|
||||
// 0000
|
||||
// 0110
|
||||
// 0110
|
||||
// 0000
|
||||
_tetromino[2] = 0x0660;
|
||||
|
||||
// normal Z block
|
||||
// 0010
|
||||
// 0110
|
||||
// 0100
|
||||
// 0000
|
||||
_tetromino[3] = 0x2640;
|
||||
|
||||
// reversed Z block
|
||||
// 0100
|
||||
// 0110
|
||||
// 0010
|
||||
// 0000
|
||||
_tetromino[4] = 0x4620;
|
||||
|
||||
// normal L block
|
||||
// 0100
|
||||
// 0100
|
||||
// 0110
|
||||
_tetromino[5] = 0x4460;
|
||||
|
||||
// reversed L block
|
||||
// 0010
|
||||
// 0010
|
||||
// 0110
|
||||
_tetromino[6] = 0x2260;
|
||||
}
|
||||
|
||||
uint16_t getDelay(void) { return (_moveDelay); }
|
||||
void setDelay(uint16_t delay) { if (delay > 10) _moveDelay = delay; }
|
||||
|
||||
void start(void) { _run = true; }
|
||||
void stop(void) { _run = false; }
|
||||
|
||||
void begin(cScore *pScore, cSound *pSound)
|
||||
{
|
||||
_timeLastMove = 0;
|
||||
_moveDelay = 1000;
|
||||
_pieceCount = 0;
|
||||
_run = false;
|
||||
|
||||
// set up the next tetronimo
|
||||
_curOmino = 0;
|
||||
_nxtOmino = random(ARRAY_SIZE(_tetromino));
|
||||
_curRotation = 0;
|
||||
|
||||
// save and reset the score
|
||||
_pScore = pScore;
|
||||
_pScore->reset();
|
||||
|
||||
// save the sound object
|
||||
_pSound = pSound;
|
||||
|
||||
// clear and display the field
|
||||
clearField();
|
||||
}
|
||||
|
||||
bool nextOmino(void)
|
||||
{
|
||||
_x = (FIELD_WIDTH - OMINO_SIZE) / 2;
|
||||
_y = 0;
|
||||
_curRotation = 0;
|
||||
_curOmino = _nxtOmino;
|
||||
if (!checkPieceFit(_curOmino, _curRotation, _x, _y))
|
||||
return(false); // can't fit it it, pass the message back!
|
||||
drawOmino(_curOmino, _curRotation, _x, _y);
|
||||
_pSound->hit();
|
||||
|
||||
// work out and display the next omino
|
||||
eraseNxtOmino();
|
||||
_nxtOmino = random(ARRAY_SIZE(_tetromino));
|
||||
drawNxtOmino();
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool move(moveType_t moveType)
|
||||
// user defined action to do something
|
||||
{
|
||||
bool b = false;
|
||||
|
||||
eraseOmino(_curOmino, _curRotation, _x, _y);
|
||||
|
||||
switch (moveType)
|
||||
{
|
||||
case M_LEFT:
|
||||
if (b = checkPieceFit(_curOmino, _curRotation, _x - 1, _y))
|
||||
_x--;
|
||||
break;
|
||||
|
||||
case M_RIGHT:
|
||||
if (b = checkPieceFit(_curOmino, _curRotation, _x + 1, _y))
|
||||
_x++;
|
||||
break;
|
||||
|
||||
case M_ROTATE:
|
||||
if (b = checkPieceFit(_curOmino, (_curRotation + 1) % MAX_ROTATE, _x, _y))
|
||||
_curRotation = (_curRotation + 1) % MAX_ROTATE;
|
||||
break;
|
||||
|
||||
case M_DROP:
|
||||
if (b = checkPieceFit(_curOmino, _curRotation, _x, _y + 1))
|
||||
_y++;
|
||||
break;
|
||||
}
|
||||
|
||||
drawOmino(_curOmino, _curRotation, _x, _y);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool run(void)
|
||||
// return false if the game has ended
|
||||
{
|
||||
bool bReturn = true;
|
||||
|
||||
// if this is the time for a move?
|
||||
if (_run && millis() - _timeLastMove <= _moveDelay)
|
||||
return(true);
|
||||
_timeLastMove = millis();
|
||||
|
||||
// check if the piece can be moved down
|
||||
if (checkPieceFit(_curOmino, _curRotation, _x, _y + 1))
|
||||
{
|
||||
// yes - do it and animate the display
|
||||
eraseOmino(_curOmino, _curRotation, _x, _y);
|
||||
_y++;
|
||||
drawOmino(_curOmino, _curRotation, _x, _y);
|
||||
|
||||
// now adjust the seed if we have reached the threshold
|
||||
if (_pieceCount >= 10)
|
||||
{
|
||||
// cap how fast we make it!
|
||||
if (_moveDelay >= 200) _moveDelay -= 50;
|
||||
_pieceCount = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pieceCount++; // just landed another one
|
||||
|
||||
// insert the current piece in the field, show it and update the score
|
||||
omino2Field(_curOmino, _curRotation, _x, _y);
|
||||
displayField();
|
||||
_pScore->increment(PIECE_SCORE);
|
||||
|
||||
// Check for lines to delete
|
||||
// There can only be lines in the span of the last block, so
|
||||
// check the 4 lines it takes up
|
||||
uint16_t lines = 0;
|
||||
|
||||
for (int16_t y = _y + OMINO_SIZE; (y >= _y) && (y >= 0); y--)
|
||||
{
|
||||
int16_t count = 0;
|
||||
|
||||
for (int16_t x = 0; x < FIELD_WIDTH; x++)
|
||||
count += (_field[x][y] ? 1 : 0);
|
||||
|
||||
if (count == FIELD_WIDTH)
|
||||
{
|
||||
lines++; // keep count of lines completed
|
||||
|
||||
// delete the line - blank it out, pause for effect,
|
||||
// make a sound then collapse the lines!
|
||||
for (int16_t x = 0; x < FIELD_WIDTH; x++)
|
||||
_field[x][y] = false;
|
||||
|
||||
displayField();
|
||||
delay(200);
|
||||
_pSound->bounce();
|
||||
|
||||
for (int16_t j = y; j >= 1; j--)
|
||||
for (int16_t x = 0; x < FIELD_WIDTH; x++)
|
||||
_field[x][j] = _field[x][j-1];
|
||||
displayField();
|
||||
|
||||
// roll back the index as we have just changed the lines
|
||||
y++;
|
||||
}
|
||||
}
|
||||
|
||||
// update the score if deleted lines
|
||||
if (lines != 0) _pScore->increment((1 << lines)*LINE_SCORE);
|
||||
|
||||
// create the next omino and return status
|
||||
bReturn = nextOmino();
|
||||
}
|
||||
|
||||
return(bReturn);
|
||||
}
|
||||
};
|
||||
|
||||
// main objects coordinated by the code logic
|
||||
cScore score;
|
||||
cMoveSW moveSW;
|
||||
cSound sound;
|
||||
cTetris tetris;
|
||||
|
||||
void setupField(void)
|
||||
// Draw the playing field at the start of the game.
|
||||
{
|
||||
mp.clear();
|
||||
mp.drawHLine(FIELD_BOTTOM, FIELD_LEFT, FIELD_RIGHT);
|
||||
mp.drawVLine(FIELD_LEFT, FIELD_TOP, FIELD_BOTTOM);
|
||||
mp.drawVLine(FIELD_RIGHT, FIELD_TOP, FIELD_BOTTOM);
|
||||
score.draw();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
#if DEBUG
|
||||
Serial.begin(57600);
|
||||
#endif
|
||||
PRINTS("\n[MD_MAXPanel_Tetris]");
|
||||
|
||||
mp.begin();
|
||||
mp.setFont(_Fixed_5x3);
|
||||
mp.setIntensity(4);
|
||||
// mp.setRotation(MD_MAXPanel::ROT_90);
|
||||
|
||||
sound.begin(BEEPER_PIN);
|
||||
score.limit(MAX_SCORE); // so we can use width() below
|
||||
score.begin(&mp, mp.getXMax() - (score.width() * (FONT_NUM_WIDTH + mp.getCharSpacing())) + mp.getCharSpacing(), mp.getYMax() - 1, MAX_SCORE);
|
||||
|
||||
moveSW.begin(LEFT_PIN, RIGHT_PIN, SELECT_PIN, DOWN_PIN);
|
||||
|
||||
randomSeed(seedOut(31, RANDOM_SEED_PORT));
|
||||
}
|
||||
|
||||
bool handleUI(void)
|
||||
{
|
||||
bool b = true;
|
||||
|
||||
switch (moveSW.move())
|
||||
{
|
||||
case cMoveSW::MOVE_ROTATE:tetris.move(cTetris::M_ROTATE); break;
|
||||
case cMoveSW::MOVE_DROP: tetris.move(cTetris::M_DROP); break;
|
||||
case cMoveSW::MOVE_LEFT: tetris.move(cTetris::M_LEFT); break;
|
||||
case cMoveSW::MOVE_RIGHT: tetris.move(cTetris::M_RIGHT); break;
|
||||
case cMoveSW::MOVE_NONE: b = false; break;
|
||||
}
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static enum { S_SPLASH, S_INIT, S_WAIT_START, S_PLAY, S_GAME_OVER } runState = S_SPLASH;
|
||||
|
||||
switch (runState)
|
||||
{
|
||||
case S_SPLASH: // show splash screen at start
|
||||
PRINTSTATE("SPLASH");
|
||||
{
|
||||
const uint16_t border = 2;
|
||||
|
||||
mp.clear();
|
||||
mp.drawRectangle(border, border, mp.getXMax() - border, mp.getYMax() - border);
|
||||
mp.drawLine(0, 0, border, border);
|
||||
mp.drawLine(0, mp.getYMax(), border, mp.getYMax()-border);
|
||||
mp.drawLine(mp.getXMax(), 0, mp.getXMax()-border, border);
|
||||
mp.drawLine(mp.getXMax(), mp.getYMax(), mp.getXMax()-border, mp.getYMax()-border);
|
||||
mp.drawText((mp.getXMax() - mp.getTextWidth(TITLE_TEXT)) / 2, (mp.getYMax() + mp.getFontHeight())/2 , TITLE_TEXT);
|
||||
sound.splash();
|
||||
delay(SPLASH_DELAY);
|
||||
|
||||
runState = S_INIT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_INIT: // initialize for a new game
|
||||
PRINTSTATE("INIT");
|
||||
setupField();
|
||||
tetris.begin(&score, &sound);
|
||||
|
||||
runState = S_WAIT_START;
|
||||
PRINTSTATE("WAIT_START");
|
||||
break;
|
||||
|
||||
case S_WAIT_START: // waiting for the start of a new game
|
||||
if (moveSW.anyKey())
|
||||
{
|
||||
PRINTS("\n-- Starting Game");
|
||||
sound.start();
|
||||
tetris.start();
|
||||
if (tetris.nextOmino())
|
||||
{
|
||||
runState = S_PLAY;
|
||||
PRINTSTATE("PLAY");
|
||||
}
|
||||
else
|
||||
runState = S_GAME_OVER;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_PLAY: // playing a point
|
||||
// handle the switches
|
||||
handleUI();
|
||||
|
||||
// move and finish the game if false
|
||||
if (!tetris.run())
|
||||
{
|
||||
tetris.stop();
|
||||
runState = S_GAME_OVER;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_GAME_OVER:
|
||||
{
|
||||
uint16_t w, x, y;
|
||||
|
||||
PRINTSTATE("GAME_OVER");
|
||||
|
||||
w = mp.getTextWidth(GAME_TEXT);
|
||||
x = ((mp.getXMax() - w) / 2) - 1;
|
||||
y = (mp.getYMax() / 2) + mp.getFontHeight() + 2;
|
||||
mp.clear(x, y, x + w + 2, y - mp.getFontHeight() - 2);
|
||||
mp.drawText(x + 1, y - 1, GAME_TEXT);
|
||||
|
||||
w = mp.getTextWidth(OVER_TEXT);
|
||||
x = ((mp.getXMax() - w) / 2) - 1;
|
||||
y = (mp.getYMax() / 2) - 1 + 2;
|
||||
mp.clear(x, y, x + w + 2, y - mp.getFontHeight() - 1);
|
||||
mp.drawText(x + 1, y - 1, OVER_TEXT);
|
||||
|
||||
sound.over();
|
||||
delay(GAME_OVER_DELAY);
|
||||
runState = S_INIT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
// Random seed creation --------------------------
|
||||
// Adapted from http://www.utopiamechanicus.com/article/arduino-better-random-numbers/
|
||||
|
||||
const uint8_t RANDOM_SEED_PORT = A3; // port read for random seed
|
||||
|
||||
uint16_t bitOut(uint8_t port)
|
||||
{
|
||||
static bool firstTime = true;
|
||||
uint32_t prev = 0;
|
||||
uint32_t bit1 = 0, bit0 = 0;
|
||||
uint32_t x = 0, limit = 99;
|
||||
|
||||
if (firstTime)
|
||||
{
|
||||
firstTime = false;
|
||||
prev = analogRead(port);
|
||||
}
|
||||
|
||||
while (limit--)
|
||||
{
|
||||
x = analogRead(port);
|
||||
bit1 = (prev != x ? 1 : 0);
|
||||
prev = x;
|
||||
x = analogRead(port);
|
||||
bit0 = (prev != x ? 1 : 0);
|
||||
prev = x;
|
||||
if (bit1 != bit0)
|
||||
break;
|
||||
}
|
||||
|
||||
return(bit1);
|
||||
}
|
||||
|
||||
uint32_t seedOut(uint16_t noOfBits, uint8_t port)
|
||||
{
|
||||
// return value with 'noOfBits' random bits set
|
||||
uint32_t seed = 0;
|
||||
|
||||
for (int i = 0; i<noOfBits; ++i)
|
||||
seed = (seed << 1) | bitOut(port);
|
||||
|
||||
return(seed);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <MD_MAXPanel.h>
|
||||
|
||||
// A class to encapsulate the score display
|
||||
class cScore
|
||||
{
|
||||
private:
|
||||
MD_MAXPanel *_mp; // the MAXPanel object for drawing the score
|
||||
uint16_t _score; // the score
|
||||
uint16_t _x, _y; // coordinate of top left for display
|
||||
uint8_t _width; // number of digits wide
|
||||
uint16_t _limit; // maximum value allowed
|
||||
|
||||
public:
|
||||
void begin(MD_MAXPanel *mp, uint16_t x, uint16_t y, uint16_t maxScore) { _mp = mp; _x = x, _y = y; limit(maxScore); reset(); }
|
||||
void reset(void) { erase(); _score = 0; draw(); }
|
||||
void set(uint16_t s) { if (s <= _limit) { erase(); _score = s; draw(); } }
|
||||
void increment(uint16_t inc = 1) { if (_score + inc <= _limit) { erase(); _score += inc; draw(); } }
|
||||
void decrement(uint16_t dec = 1) { if (_score >= dec) { erase(); _score -= dec; draw(); } }
|
||||
uint16_t score(void) { return(_score); }
|
||||
void erase(void) { draw(false); }
|
||||
uint16_t width(void) { return(_width); }
|
||||
|
||||
void limit(uint16_t m)
|
||||
{
|
||||
erase(); // width may change, so delete with the curret parameters
|
||||
|
||||
_limit = m;
|
||||
// work out how many digits this is
|
||||
_width = 0;
|
||||
do
|
||||
{
|
||||
_width++;
|
||||
m /= 10;
|
||||
} while (m != 0);
|
||||
}
|
||||
|
||||
void draw(bool state = true)
|
||||
{
|
||||
char sz[_width + 1];
|
||||
uint16_t s = _score;
|
||||
|
||||
// PRINT("\n-- SCORE: ", _score);
|
||||
sz[_width] = '\0';
|
||||
for (int i = _width - 1; i >= 0; --i)
|
||||
{
|
||||
sz[i] = (s % 10) + '0';
|
||||
s /= 10;
|
||||
}
|
||||
|
||||
_mp->drawText(_x, _y, sz, MD_MAXPanel::ROT_0, state);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
// A class to encapsulate primitive sound effects
|
||||
class cSound
|
||||
{
|
||||
private:
|
||||
const uint16_t EOD = 0; // End Of Data marker
|
||||
uint8_t _pinBeep; // the pin to use for beeping
|
||||
|
||||
// Sound data - frequency followed by duration in pairs.
|
||||
// Data ends in End Of Data marker EOD.
|
||||
const uint16_t soundSplash[1] PROGMEM = { EOD };
|
||||
const uint16_t soundHit[3] PROGMEM = { 1000, 50, EOD };
|
||||
const uint16_t soundBounce[3] PROGMEM = { 500, 50, EOD };
|
||||
const uint16_t soundPoint[3] PROGMEM = { 150, 150, EOD };
|
||||
const uint16_t soundStart[7] PROGMEM = { 250, 100, 500, 100, 1000, 100, EOD };
|
||||
const uint16_t soundOver[7] PROGMEM = { 1000, 100, 500, 100, 250, 100, EOD };
|
||||
|
||||
void playSound(const uint16_t *table)
|
||||
// Play sound table data. Data table must end in EOD marker.
|
||||
{
|
||||
uint8_t idx = 0;
|
||||
|
||||
//PRINTS("\nTone Data ");
|
||||
while (table[idx] != EOD)
|
||||
{
|
||||
uint16_t t = table[idx++];
|
||||
uint16_t d = table[idx++];
|
||||
|
||||
//PRINTXY("-", t, d);
|
||||
tone(_pinBeep, t);
|
||||
delay(d);
|
||||
}
|
||||
//PRINTS("-EOD");
|
||||
noTone(_pinBeep); // be quiet now!
|
||||
}
|
||||
|
||||
public:
|
||||
void begin(uint8_t pinBeep) { _pinBeep = pinBeep; }
|
||||
void splash(void) { playSound(soundSplash); }
|
||||
void start(void) { playSound(soundStart); }
|
||||
void hit(void) { playSound(soundHit); }
|
||||
void bounce(void) { playSound(soundBounce); }
|
||||
void point(void) { playSound(soundPoint); }
|
||||
void over(void) { playSound(soundOver); }
|
||||
};
|
||||
|
||||
51
arduino-libs/arduino-cli/libraries/MD_MAXPanel/keywords.txt
Normal file
51
arduino-libs/arduino-cli/libraries/MD_MAXPanel/keywords.txt
Normal file
@@ -0,0 +1,51 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
MD_MAXPanel KEYWORD1
|
||||
rotation_t KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
clear KEYWORD2
|
||||
setPoint KEYWORD2
|
||||
getPoint KEYWORD2
|
||||
drawLine KEYWORD2
|
||||
drawHLine KEYWORD2
|
||||
drawVLine KEYWORD2
|
||||
drawRecangle KEYWORD2
|
||||
drawFillRectangle KEYWORD2
|
||||
drawTriangle KEYWORD2
|
||||
drawFillTriangle KEYWORD2
|
||||
drawCircle KEYWORD2
|
||||
drawFillCircle KEYWORD2
|
||||
drawQuadrilateral KEYWORD2
|
||||
getXMax KEYWORD2
|
||||
getYMax KEYWORD2
|
||||
getGraphicObject KEYWORD2
|
||||
update KEYWORD2
|
||||
setIntensity KEYWORD2
|
||||
setFont KEYWORD2
|
||||
setCharSpacing KEYWORD2
|
||||
getCharSpacing KEYWORD2
|
||||
setRotation KEYWORD2
|
||||
getRotation KEYWORD2
|
||||
getTextWidth KEYWORD2
|
||||
getFontHeight KEYWORD2
|
||||
drawText KEYWORD2
|
||||
|
||||
######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
ROT_0 LITERAL1
|
||||
ROT_90 LITERAL1
|
||||
ROT_180 LITERAL1
|
||||
ROT_270 LITERAL1
|
||||
@@ -0,0 +1,11 @@
|
||||
name=MD_MAXPanel
|
||||
version=1.2.3
|
||||
author=majicDesigns
|
||||
maintainer=marco_c <8136821@gmail.com>
|
||||
sentence=Implements functions to manage a panel of MAX72xx based LED modules
|
||||
paragraph=Allows the programmer to use the LED matrix panel as a pixel addressable display for graphics and text.
|
||||
category=Device Control
|
||||
url=https://github.com/MajicDesigns/MD_MAXPanel
|
||||
architectures=*
|
||||
includes=MD_MAXPanel.h
|
||||
license=LGPL-2.1
|
||||
@@ -0,0 +1,660 @@
|
||||
/*
|
||||
MD_MAXPanel - Library for MAX7219/7221 LED Panel
|
||||
|
||||
See header file for comments
|
||||
|
||||
This file contains class and hardware related methods.
|
||||
|
||||
Copyright (C) 2018 Marco Colli. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "MD_MAXPanel.h"
|
||||
#include "MD_MAXPanel_lib.h"
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Implements class graphics methods
|
||||
*/
|
||||
|
||||
MD_MAXPanel::MD_MAXPanel(MD_MAX72XX::moduleType_t mod, uint8_t dataPin, uint8_t clkPin, uint8_t csPin, uint8_t xDevices, uint8_t yDevices) :
|
||||
_xDevices(xDevices), _yDevices(yDevices), _rotatedDisplay(false)
|
||||
{
|
||||
_D = new MD_MAX72XX(mod, dataPin, clkPin, csPin, xDevices*yDevices);
|
||||
_killOnDestruct = true;
|
||||
}
|
||||
|
||||
MD_MAXPanel::MD_MAXPanel(MD_MAX72XX::moduleType_t mod, uint8_t csPin, uint8_t xDevices, uint8_t yDevices) :
|
||||
_xDevices(xDevices), _yDevices(yDevices), _rotatedDisplay(false)
|
||||
{
|
||||
_D = new MD_MAX72XX(mod, csPin, xDevices*yDevices);
|
||||
_killOnDestruct = true;
|
||||
}
|
||||
|
||||
MD_MAXPanel::MD_MAXPanel(MD_MAX72XX *D, uint8_t xDevices, uint8_t yDevices) :
|
||||
_xDevices(xDevices), _yDevices(yDevices), _rotatedDisplay(false)
|
||||
{
|
||||
_D = D;
|
||||
_killOnDestruct = false;
|
||||
}
|
||||
|
||||
void MD_MAXPanel::begin(void)
|
||||
{
|
||||
_D->begin();
|
||||
_charSpacing = CHAR_SPACING_DEFAULT;
|
||||
_updateEnabled = true;
|
||||
}
|
||||
|
||||
MD_MAXPanel::~MD_MAXPanel(void)
|
||||
{
|
||||
if (_killOnDestruct) delete _D;
|
||||
}
|
||||
|
||||
uint16_t MD_MAXPanel::getXMax(void)
|
||||
{
|
||||
uint16_t m;
|
||||
|
||||
if (_rotatedDisplay)
|
||||
m = (_yDevices * ROW_SIZE) - 1;
|
||||
else
|
||||
m = (_xDevices * COL_SIZE) - 1;
|
||||
|
||||
return(m);
|
||||
}
|
||||
|
||||
uint16_t MD_MAXPanel::getYMax(void)
|
||||
{
|
||||
uint16_t m;
|
||||
|
||||
if (_rotatedDisplay)
|
||||
m = (_xDevices * COL_SIZE) - 1;
|
||||
else
|
||||
m = (_yDevices * ROW_SIZE) - 1;
|
||||
|
||||
return(m);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawHLine(uint16_t y, uint16_t x1, uint16_t x2, bool state)
|
||||
// draw a horizontal line at row y between columns x1 and x2 inclusive
|
||||
{
|
||||
bool b = true;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
|
||||
if (x1 > x2) // swap x1/x2
|
||||
{
|
||||
uint16_t t = x1;
|
||||
x1 = x2;
|
||||
x2 = t;
|
||||
}
|
||||
|
||||
for (uint16_t i = x1; i <= x2; i++)
|
||||
b &= setPoint(i, y, state);
|
||||
|
||||
update(_updateEnabled);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawVLine(uint16_t x, uint16_t y1, uint16_t y2, bool state)
|
||||
// draw a vertical line at column x between rows y1 and y2 inclusive
|
||||
{
|
||||
bool b = true;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
|
||||
if (y1 > y2) // swap y1/y2
|
||||
{
|
||||
uint8_t t = y1;
|
||||
y1 = y2;
|
||||
y2 = t;
|
||||
}
|
||||
|
||||
for (uint8_t i = y1; i <= y2; i++)
|
||||
b &= setPoint(x, i, state);
|
||||
|
||||
update(_updateEnabled);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, bool state)
|
||||
// draw an arbitrary line between two points using Bresentham's line algorithm
|
||||
// Bresentham's line algorithm at https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C
|
||||
{
|
||||
bool b = true;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
|
||||
PRINT("\n\nLine from ", x1); PRINT(",", y1);
|
||||
PRINT(" to ", x2); PRINT(",", y2);
|
||||
|
||||
if (x1 > x2) // swap direction for line
|
||||
{
|
||||
uint16_t t;
|
||||
|
||||
t = x1; x1 = x2; x2 = t;
|
||||
t = y1; y1 = y2; y2 = t;
|
||||
// PRINTS(" SWAP X");
|
||||
}
|
||||
|
||||
// PRINT("\nPlotting from ", x1); PRINT(",", y1);
|
||||
// PRINT(" to ", x2); PRINT(",", y2);
|
||||
|
||||
int16_t dx = x2 - x1;
|
||||
int16_t sx = 1;
|
||||
int16_t dy = y2 - y1;
|
||||
if (dy < 0) dy = -dy;
|
||||
int16_t sy = y1 < y2 ? 1 : -1;
|
||||
int16_t err = (dx > dy ? dx : -dy) / 2;
|
||||
int16_t e2;
|
||||
|
||||
// PRINT("\ndx=", dx);
|
||||
// PRINT(" dy=", dy);
|
||||
// PRINT(" ystep=", sy);
|
||||
// PRINT(" xstep=", sx);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// PRINT("\nerror=", err);
|
||||
// PRINT(" [", x1); PRINT(",", y1); PRINTS("]");
|
||||
|
||||
b &= setPoint(x1, y1, state);
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
e2 = err;
|
||||
if (e2 >-dx) { err -= dy; x1 += sx; }
|
||||
if (e2 < dy) { err += dx; y1 += sy; }
|
||||
}
|
||||
|
||||
update(_updateEnabled);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, bool state)
|
||||
// draw a rectangle given the 2 diagonal vertices
|
||||
{
|
||||
bool b = true;
|
||||
bool u = _updateEnabled;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
update(false);
|
||||
|
||||
b &= drawHLine(y1, x1, x2, state);
|
||||
b &= drawHLine(y2, x1, x2, state);
|
||||
b &= drawVLine(x1, y1, y2, state);
|
||||
b &= drawVLine(x2, y1, y2, state);
|
||||
|
||||
update(u);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawFillRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, bool state)
|
||||
{
|
||||
bool b = true;
|
||||
bool u = _updateEnabled;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
update(false);
|
||||
|
||||
for (uint8_t i = x1; i <= x2; i++)
|
||||
drawVLine(i, y1, y2, state);
|
||||
|
||||
update(u);
|
||||
|
||||
return(b);
|
||||
};
|
||||
|
||||
bool MD_MAXPanel::drawQuadrilateral(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t x4, uint16_t y4, bool state)
|
||||
// draw a arbitrary quadrilateral given the 4 corner vertices
|
||||
{
|
||||
bool b = true;
|
||||
bool u = _updateEnabled;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
update(false);
|
||||
|
||||
b &= drawLine(x1, y1, x2, y2, state);
|
||||
b &= drawLine(x2, y2, x3, y3, state);
|
||||
b &= drawLine(x3, y3, x4, y4, state);
|
||||
b &= drawLine(x4, y4, x1, y1, state);
|
||||
|
||||
update(u);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, bool state)
|
||||
// draw an arbitrary triangle given the 3 corner vertices
|
||||
{
|
||||
bool b = true;
|
||||
bool u = _updateEnabled;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
update(false);
|
||||
|
||||
b &= drawLine(x1, y1, x2, y2, state);
|
||||
b &= drawLine(x2, y2, x3, y3, state);
|
||||
b &= drawLine(x3, y3, x1, y1, state);
|
||||
|
||||
update(u);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
/*
|
||||
bool MD_MAXPanel::drawFillTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, bool state)
|
||||
// draw an arbitrary filled triangle given the 3 corner vertices
|
||||
// Uses slope method, originally from Adafruit Industries
|
||||
{
|
||||
#define SWAP(a, b) { uint16_t t = a; a = b; b = t; }
|
||||
|
||||
uint8_t a, b, y, last;
|
||||
bool r = true;
|
||||
bool u = _updateEnabled;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
update(false);
|
||||
|
||||
// Sort coordinates by Y order (y3 >= y2 >= y1)
|
||||
if (y1 > y2) { SWAP(y1, y2); SWAP(x1, x2); }
|
||||
if (y2 > y3) { SWAP(y3, y2); SWAP(x3, x2); }
|
||||
if (y1 > y2) { SWAP(y1, y2); SWAP(x1, x2); }
|
||||
|
||||
if (y1 == y3) // All in same line
|
||||
{
|
||||
a = b = x1;
|
||||
if (x2 < a) a = x2;
|
||||
else if (x2 > b) b = x2;
|
||||
|
||||
if (x3 < a) a = x3;
|
||||
else if (x3 > b) b = x3;
|
||||
|
||||
r &= drawHLine(y1, a, b, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
int8_t dx12 = x2 - x1;
|
||||
int8_t dy12 = y2 - y1;
|
||||
int8_t dx13 = x3 - x1;
|
||||
int8_t dy13 = y3 - y1;
|
||||
int8_t dx23 = x3 - x2;
|
||||
int8_t dy23 = y3 - y2;
|
||||
int16_t sa = 0, sb = 0;
|
||||
|
||||
// For upper part of triangle, find scanline crossings for segment
|
||||
// 1-2 and 1-3. If y2=y3 (flat-bottomed triangle), the scanline y
|
||||
// is included here (and second loop will be skipped, avoiding a /0
|
||||
// error there), otherwise scanline y2 is skipped here and handle
|
||||
// in the second loop...which also avoids a /0 error here if y1=y
|
||||
// (flat-topped triangle)
|
||||
if (y2 == y3) last = y2; // Include y2 scanline
|
||||
else last = y2 - 1; // Skip it
|
||||
|
||||
for (y = y1; y <= last; y++)
|
||||
{
|
||||
a = x1 + sa / dy12;
|
||||
b = x1 + sb / dy13;
|
||||
sa += dx12;
|
||||
sb += dx13;
|
||||
// longhand a = x1 + (x2 - x1) * (y - y1) / (y2 - y1)
|
||||
// b = x0 + (x3 - x1) * (y - y1) / (y3 - y1)
|
||||
r &= drawHLine(y, a, b, state);
|
||||
}
|
||||
|
||||
// For lower part of triangle, find scanline crossings for segment
|
||||
// 1-3 and 2-3. This loop is skipped if y2=y3
|
||||
sa = dx23 * (y - y2);
|
||||
sb = dx13 * (y - y1);
|
||||
for (; y <= y3; y++)
|
||||
{
|
||||
a = x2 + sa / dy23;
|
||||
b = x1 + sb / dy13;
|
||||
sa += dx23;
|
||||
sb += dx13;
|
||||
// longhand a = x2 + (x3 - x2) * (y - y2) / (y3 - y2)
|
||||
// b = x1 + (x2 - x1) * (y - y1) / (y3 - y1)
|
||||
r &= drawHLine(y, a, b, state);
|
||||
}
|
||||
}
|
||||
|
||||
update(u);
|
||||
|
||||
return(r);
|
||||
}
|
||||
*/
|
||||
|
||||
bool MD_MAXPanel::drawFillTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, bool state)
|
||||
// Fill a triangle - Bresenham method
|
||||
// Original from http://www.sunshine2k.de/coding/java/TriangleRasterization/TriangleRasterization.html
|
||||
{
|
||||
#define SWAP(a, b) { uint16_t t = a; a = b; b = t; }
|
||||
|
||||
uint8_t t1x, t2x, y, minx, maxx, t1xp, t2xp;
|
||||
bool changed1 = false;
|
||||
bool changed2 = false;
|
||||
int8_t signx1, signx2, dx1, dy1, dx2, dy2;
|
||||
uint8_t e1, e2;
|
||||
bool b = true;
|
||||
bool u = _updateEnabled;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
update(false);
|
||||
|
||||
// Sort vertices
|
||||
if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); }
|
||||
if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); }
|
||||
if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); }
|
||||
|
||||
t1x = t2x = x1; y = y1; // Starting points
|
||||
|
||||
dx1 = (int8_t)(x2 - x1);
|
||||
if (dx1<0) { dx1 = -dx1; signx1 = -1; }
|
||||
else signx1 = 1;
|
||||
dy1 = (int8_t)(y2 - y1);
|
||||
|
||||
dx2 = (int8_t)(x3 - x1);
|
||||
if (dx2<0) { dx2 = -dx2; signx2 = -1; }
|
||||
else signx2 = 1;
|
||||
dy2 = (int8_t)(y3 - y1);
|
||||
|
||||
if (dy1 > dx1) // swap values
|
||||
{
|
||||
SWAP(dx1, dy1);
|
||||
changed1 = true;
|
||||
}
|
||||
if (dy2 > dx2) // swap values
|
||||
{
|
||||
SWAP(dy2, dx2);
|
||||
changed2 = true;
|
||||
}
|
||||
|
||||
e2 = (uint8_t)(dx2 >> 1);
|
||||
// Flat top, just process the second half
|
||||
if (y1 == y2) goto next;
|
||||
e1 = (uint8_t)(dx1 >> 1);
|
||||
|
||||
for (uint8_t i = 0; i < dx1;)
|
||||
{
|
||||
t1xp = 0; t2xp = 0;
|
||||
if (t1x<t2x) { minx = t1x; maxx = t2x; }
|
||||
else { minx = t2x; maxx = t1x; }
|
||||
// process first line until y value is about to change
|
||||
while (i<dx1)
|
||||
{
|
||||
i++;
|
||||
e1 += dy1;
|
||||
while (e1 >= dx1)
|
||||
{
|
||||
e1 -= dx1;
|
||||
if (changed1) t1xp = signx1;//t1x += signx1;
|
||||
else goto next1;
|
||||
}
|
||||
if (changed1) break;
|
||||
else t1x += signx1;
|
||||
}
|
||||
// Move line
|
||||
next1:
|
||||
// process second line until y value is about to change
|
||||
while (1)
|
||||
{
|
||||
e2 += dy2;
|
||||
while (e2 >= dx2)
|
||||
{
|
||||
e2 -= dx2;
|
||||
if (changed2) t2xp = signx2;//t2x += signx2;
|
||||
else goto next2;
|
||||
}
|
||||
if (changed2) break;
|
||||
else t2x += signx2;
|
||||
}
|
||||
next2:
|
||||
if (minx>t1x) minx = t1x;
|
||||
if (minx>t2x) minx = t2x;
|
||||
if (maxx<t1x) maxx = t1x;
|
||||
if (maxx<t2x) maxx = t2x;
|
||||
b &= drawHLine(y, minx, maxx, state); // Draw line from min to max points found on the y
|
||||
// Now increase y
|
||||
if (!changed1) t1x += signx1;
|
||||
t1x += t1xp;
|
||||
if (!changed2) t2x += signx2;
|
||||
t2x += t2xp;
|
||||
y += 1;
|
||||
if (y == y2) break;
|
||||
|
||||
}
|
||||
next:
|
||||
// Second half
|
||||
dx1 = (int8_t)(x3 - x2);
|
||||
if (dx1<0) { dx1 = -dx1; signx1 = -1; }
|
||||
else signx1 = 1;
|
||||
dy1 = (int8_t)(y3 - y2);
|
||||
t1x = x2;
|
||||
|
||||
if (dy1 > dx1)
|
||||
{ // swap values
|
||||
SWAP(dy1, dx1);
|
||||
changed1 = true;
|
||||
}
|
||||
else changed1 = false;
|
||||
|
||||
e1 = (uint8_t)(dx1 >> 1);
|
||||
|
||||
for (uint8_t i = 0; i <= dx1; i++)
|
||||
{
|
||||
t1xp = 0; t2xp = 0;
|
||||
if (t1x<t2x) { minx = t1x; maxx = t2x; }
|
||||
else { minx = t2x; maxx = t1x; }
|
||||
// process first line until y value is about to change
|
||||
while (i<dx1)
|
||||
{
|
||||
e1 += dy1;
|
||||
while (e1 >= dx1)
|
||||
{
|
||||
e1 -= dx1;
|
||||
if (changed1) { t1xp = signx1; break; }//t1x += signx1;
|
||||
else goto next3;
|
||||
}
|
||||
if (changed1) break;
|
||||
else t1x += signx1;
|
||||
if (i<dx1) i++;
|
||||
}
|
||||
next3:
|
||||
// process second line until y value is about to change
|
||||
while (t2x != x3)
|
||||
{
|
||||
e2 += dy2;
|
||||
while (e2 >= dx2)
|
||||
{
|
||||
e2 -= dx2;
|
||||
if (changed2) t2xp = signx2;
|
||||
else goto next4;
|
||||
}
|
||||
if (changed2) break;
|
||||
else t2x += signx2;
|
||||
}
|
||||
next4:
|
||||
|
||||
if (minx>t1x) minx = t1x;
|
||||
if (minx>t2x) minx = t2x;
|
||||
if (maxx<t1x) maxx = t1x;
|
||||
if (maxx<t2x) maxx = t2x;
|
||||
b &= drawHLine(y, minx, maxx, state); // Draw line from min to max points found on the y
|
||||
// Now increase y
|
||||
if (!changed1) t1x += signx1;
|
||||
t1x += t1xp;
|
||||
if (!changed2) t2x += signx2;
|
||||
t2x += t2xp;
|
||||
y += 1;
|
||||
if (y>y3) goto outtahere;
|
||||
}
|
||||
|
||||
outtahere:
|
||||
update(u);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
|
||||
bool MD_MAXPanel::drawCirclePoints(uint16_t xc, uint16_t yc, uint16_t x, uint16_t y, bool state)
|
||||
// draw symmetrical circle points
|
||||
{
|
||||
bool b = true;
|
||||
|
||||
b &= setPoint(xc + x, yc + y, state);
|
||||
b &= setPoint(xc - x, yc + y, state);
|
||||
b &= setPoint(xc + x, yc - y, state);
|
||||
b &= setPoint(xc - x, yc - y, state);
|
||||
b &= setPoint(xc + y, yc + x, state);
|
||||
b &= setPoint(xc - y, yc + x, state);
|
||||
b &= setPoint(xc + y, yc - x, state);
|
||||
b &= setPoint(xc - y, yc - x, state);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawCircle(uint16_t xc, uint16_t yc, uint16_t r, bool state)
|
||||
// draw a circle given center and radius
|
||||
// Bresenhams Algorith from http://www.pracspedia.com/CG/bresenhamcircle.html
|
||||
{
|
||||
int x = 0, y = r;
|
||||
int pk = 3 - (2 * r);
|
||||
bool b = false;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
|
||||
PRINT("\n\nCircle center ", xc); PRINT(",", yc); PRINT(" radius ", r);
|
||||
|
||||
b &= drawCirclePoints(xc, yc, x, y, state);
|
||||
|
||||
while (x < y)
|
||||
{
|
||||
// check for decision parameter and correspondingly update pk, x, y
|
||||
if (pk <= 0)
|
||||
{
|
||||
pk = pk + (4 * x) + 6;
|
||||
b &= drawCirclePoints(xc, yc, ++x, y, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
pk = pk + (4 * (x - y)) + 10;
|
||||
b &= drawCirclePoints(xc, yc, ++x, --y, state);
|
||||
}
|
||||
}
|
||||
|
||||
update(_updateEnabled);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawCircleLines(uint16_t xc, uint16_t yc, uint16_t x, uint16_t y, bool state)
|
||||
// draw symmetrical circle lines for the filled circle
|
||||
{
|
||||
bool b = true;
|
||||
|
||||
b &= drawHLine(yc - y, xc - x, xc + x, state);
|
||||
b &= drawHLine(yc + y, xc - x, xc + x, state);
|
||||
b &= drawHLine(yc - x, xc - y, xc + y, state);
|
||||
b &= drawHLine(yc + x, xc - y, xc + y, state);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::drawFillCircle(uint16_t xc, uint16_t yc, uint16_t r, bool state)
|
||||
// Draw a filled circle given center and radius
|
||||
// Bresenhams Algorith from http://www.pracspedia.com/CG/bresenhamcircle.html
|
||||
{
|
||||
int x = 0, y = r;
|
||||
int pk = 3 - (2 * r);
|
||||
bool b = false;
|
||||
bool u = _updateEnabled;
|
||||
|
||||
_D->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
|
||||
update(false);
|
||||
|
||||
PRINT("\n\nFilled Circle center ", xc); PRINT(",", yc); PRINT(" radius ", r);
|
||||
b &= drawCircleLines(xc, yc, x, y, state);
|
||||
while (x < y)
|
||||
{
|
||||
// check for decision parameter and correspondingly update pk, x, y
|
||||
if (pk <= 0)
|
||||
{
|
||||
pk = pk + (4 * x) + 6;
|
||||
b &= drawCircleLines(xc, yc, ++x, y, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
pk = pk + (4 * (x - y)) + 10;
|
||||
b &= drawCircleLines(xc, yc, ++x, --y, state);
|
||||
}
|
||||
}
|
||||
|
||||
update(u);
|
||||
|
||||
return(b);
|
||||
}
|
||||
|
||||
uint16_t MD_MAXPanel::Y2Row(uint16_t x, uint16_t y)
|
||||
// Convert y coord to linear coord
|
||||
{
|
||||
uint16_t Y;
|
||||
|
||||
if (_rotatedDisplay)
|
||||
{
|
||||
x = getXMax() - x;
|
||||
Y = (ROW_SIZE - (x % ROW_SIZE) - 1);
|
||||
}
|
||||
else
|
||||
Y = (ROW_SIZE - (y % ROW_SIZE) - 1);
|
||||
|
||||
return(Y);
|
||||
}
|
||||
|
||||
uint16_t MD_MAXPanel::X2Col(uint16_t x, uint16_t y)
|
||||
// Convert x coord to linear coord
|
||||
{
|
||||
uint16_t X;
|
||||
|
||||
if (_rotatedDisplay)
|
||||
{
|
||||
x = getXMax() - x;
|
||||
X = ((x / ROW_SIZE) * (_xDevices * COL_SIZE) + (_xDevices * COL_SIZE) - 1 - (y % (_xDevices * COL_SIZE)));
|
||||
}
|
||||
else
|
||||
X = ((y / ROW_SIZE) * (_xDevices * COL_SIZE) + (_xDevices * COL_SIZE) - 1 - (x % (_xDevices * COL_SIZE)));
|
||||
|
||||
return(X);
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::getPoint(uint16_t x, uint16_t y)
|
||||
{
|
||||
if (x > getXMax() || y > getYMax())
|
||||
return(false);
|
||||
|
||||
return(_D->getPoint(Y2Row(x,y), X2Col(x,y)));
|
||||
}
|
||||
|
||||
bool MD_MAXPanel::setPoint(uint16_t x, uint16_t y, bool state)
|
||||
{
|
||||
if (x > getXMax() || y > getYMax())
|
||||
return(false);
|
||||
|
||||
//PRINT("[", x); PRINT(",", y); PRINTS("]");
|
||||
|
||||
return(_D->setPoint(Y2Row(x,y), X2Col(x,y), state));
|
||||
}
|
||||
|
||||
618
arduino-libs/arduino-cli/libraries/MD_MAXPanel/src/MD_MAXPanel.h
Normal file
618
arduino-libs/arduino-cli/libraries/MD_MAXPanel/src/MD_MAXPanel.h
Normal file
@@ -0,0 +1,618 @@
|
||||
#ifndef MD_MAXPANEL_h
|
||||
#define MD_MAXPANEL_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MD_MAX72xx.h>
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Main header file for the MD_MAXPanel library
|
||||
*/
|
||||
|
||||
/**
|
||||
\mainpage Arduino LED Matrix Panel Library
|
||||
The MD_MAXPanel Library
|
||||
-----------------------
|
||||
This library implements functions that allows cascaded MAX72xx LED modules
|
||||
(64 individual LEDs)to be used for LED matrix panels, allowing the programmer
|
||||
to use the LED matrix as an pixel addressable display device, as shown in the
|
||||
photo below.
|
||||
|
||||
![MD_MAXPanel Display Panel] (MAXPanel_Display.jpg "MD_MAXPanel Display Panel")
|
||||
|
||||
The MAX7219/MAX7221 are compact, serial input/output display drivers that
|
||||
interface microprocessors to 7-segment numeric LED displays of up to 8 digits,
|
||||
bar-graph displays, or 64 individual LEDs.
|
||||
|
||||
A 4-wire serial interface (SPI) allows the devices to be cascaded, with
|
||||
communications passed through the first device in the chain to all others.
|
||||
Individual elements may be addressed and updated without rewriting the entire
|
||||
display.
|
||||
|
||||
In order for this library to work, the MD_MAX72xx library must be installed and
|
||||
the correct LED module type selected. The individual LED modules must also be
|
||||
arranged in a zig-zag fashion, as shown in the figure below. The number of modules
|
||||
per row and the number of rows may vary, but the arrangement of the modules must
|
||||
follow the example.
|
||||
|
||||
![MD_MAXPanel Module Arrangement] (MAXPanel_Diagram.jpg "MD_MAXPanel Module Arrangement")
|
||||
|
||||
The wiring for the modules can be simplified as the only signal that needs to be truly
|
||||
cascaded is the MD_MAX72xx IC Data Out to the next IC Data In. The rest can be wired in
|
||||
parallel, as shown in the photo.
|
||||
|
||||
![MD_MAXPanel Module Wiring] (MAXPanel_Wiring.jpg "MD_MAXPanel Module Wiring")
|
||||
|
||||
Topics
|
||||
------
|
||||
- \subpage pageSoftware
|
||||
- \subpage pageRevisionHistory
|
||||
- \subpage pageCopyright
|
||||
- \subpage pageDonation
|
||||
|
||||
\page pageDonation Support the Library
|
||||
If you like and use this library please consider making a small donation using [PayPal](https://paypal.me/MajicDesigns/4USD)
|
||||
|
||||
\page pageCopyright Copyright
|
||||
Copyright (C) 2018 Marco Colli. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
\page pageRevisionHistory Revision History
|
||||
Nov 2018 version 1.2.3
|
||||
- Corrected default parameters for WEMOS compiler
|
||||
|
||||
Oct 2018 version 1.2.2
|
||||
- Corrected some documentation mistakes
|
||||
|
||||
Oct 2018 version 1.2.1
|
||||
- Updated library.properties and READ.MD files
|
||||
|
||||
Sep 2018 version 1.2.0
|
||||
- Fixed reported WEMOS D1 compiler issues
|
||||
- Added polygon Fill functions contributed by AndreasPercher
|
||||
|
||||
Jul 2018 version 1.1.1
|
||||
- Finalized preferred orientation for examples
|
||||
|
||||
Jul 2018 version 1.1.0
|
||||
- Renamed textRotation_t to rotation_t
|
||||
- Allow rotation of display (eg, landscape to portrait)
|
||||
- Restored missing bricks example
|
||||
|
||||
Jun 2018 version 1.0.1
|
||||
- Extracted common elements of examples in .h file
|
||||
|
||||
Jun 2018 version 1.0.0
|
||||
- First Release
|
||||
|
||||
\page pageSoftware Software Library
|
||||
The Library
|
||||
-----------
|
||||
The library implements functions that allow the MAX72xx matrix modules
|
||||
to be used cascaded and built up into LED matrix panels. This allows the
|
||||
programmer to control the individual panel LEDs using cartesian coordinates.
|
||||
The library provides support for standard graphics elements (such as lines,
|
||||
triangles, rectangles, circles) and text.
|
||||
|
||||
The origin for the coordinate system is always in the lowest left hand corner.
|
||||
- X coordinate increase to the right.
|
||||
- Y coordinate increase to upwards.
|
||||
|
||||
In trigonometric terms, the display is located in the first quadrant.
|
||||
|
||||
The library is relies on the related MD_MAX72xx library to provide the
|
||||
device control elements.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core object for the MD_MAXPanel library
|
||||
*/
|
||||
class MD_MAXPanel
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Rotation enumerated type specification.
|
||||
*
|
||||
* Used to define rotation orientation (eg, text or display).
|
||||
* For text the normal rotation is the standard Latin language left to right
|
||||
* orientation. Rotation is specified anchored to the first character of the
|
||||
* string - 0 points >, 90 ^, 180 < and 270 v.
|
||||
* For the display rotation 0 and 180 and are identical and 90 and 270 are identical.
|
||||
* The rotation will shift the display between landscape and portrait mode.
|
||||
*/
|
||||
enum rotation_t
|
||||
{
|
||||
ROT_0, ///< Rotation 0 degrees
|
||||
ROT_90, ///< Rotation 90 degrees
|
||||
ROT_180, ///< Rotation 180 degrees
|
||||
ROT_270, ///< Rotation 270 degrees
|
||||
};
|
||||
|
||||
/**
|
||||
* Class Constructor - arbitrary digital interface.
|
||||
*
|
||||
* Instantiate a new instance of the class. The parameters passed are used to
|
||||
* connect the software to the hardware. Multiple instances may co-exist
|
||||
* but they should not share the same hardware CS pin (SPI interface).
|
||||
*
|
||||
* \param mod the hardware module being used, one of the MD_MAX72XX::moduleType_t values
|
||||
* \param dataPin output on the Arduino where data gets shifted out.
|
||||
* \param clkPin output for the clock signal.
|
||||
* \param csPin output for selecting the device.
|
||||
* \param xDevices number of LED matrix modules for the width of the panel.
|
||||
* \param yDevices number of LED matrix modules for the height of the panel.
|
||||
*/
|
||||
MD_MAXPanel(MD_MAX72XX::moduleType_t mod, uint8_t dataPin, uint8_t clkPin, uint8_t csPin, uint8_t xDevices, uint8_t yDevices);
|
||||
|
||||
/**
|
||||
* Class Constructor - SPI hardware interface.
|
||||
*
|
||||
* Instantiate a new instance of the class. The parameters passed are used to
|
||||
* connect the software to the hardware. Multiple instances may co-exist
|
||||
* but they should not share the same hardware CS pin (SPI interface).
|
||||
* The dataPin and the clockPin are defined by the Arduino hardware definition
|
||||
* (SPI MOSI and SCK signals).
|
||||
*
|
||||
* \param mod the hardware module being used, one of the MD_MAX72XX::moduleType_t values
|
||||
* \param csPin output for selecting the device.
|
||||
* \param xDevices number of LED matrix modules for the width of the panel.
|
||||
* \param yDevices number of LED matrix modules for the height of the panel.
|
||||
*/
|
||||
MD_MAXPanel(MD_MAX72XX::moduleType_t mod, uint8_t csPin, uint8_t xDevices, uint8_t yDevices);
|
||||
|
||||
/**
|
||||
* Class Constructor - Existing MD_MAX72XX object.
|
||||
*
|
||||
* Instantiate a new instance of the class. The parameters passed are used to
|
||||
* connect the software to the hardware. The MD_MAX72X object has been created
|
||||
* outside this object and it is up to the programmer to ensure that the correct
|
||||
* parameters are used when this object is created.
|
||||
*
|
||||
* \param D pointer to the existing MD_MAX72XX object.
|
||||
* \param xDevices number of LED matrix modules for the width of the panel.
|
||||
* \param yDevices number of LED matrix modules for the height of the panel.
|
||||
*/
|
||||
MD_MAXPanel(MD_MAX72XX *D, uint8_t xDevices, uint8_t yDevices);
|
||||
|
||||
/**
|
||||
* Initialize the object.
|
||||
*
|
||||
* Initialize the object data. This needs to be called during setup() to initialize
|
||||
* new data for the class that cannot be done during the object creation.
|
||||
*/
|
||||
void begin(void);
|
||||
|
||||
/**
|
||||
* Class Destructor.
|
||||
*
|
||||
* Released allocated memory and does the necessary to clean up once the object is
|
||||
* no longer required.
|
||||
*/
|
||||
~MD_MAXPanel();
|
||||
|
||||
//--------------------------------------------------------------
|
||||
/** \name Methods for object management.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clear all the display data on all the display devices.
|
||||
*
|
||||
* \return No return value.
|
||||
*/
|
||||
void clear(void) { _D->clear(0, _xDevices*_yDevices); };
|
||||
|
||||
/**
|
||||
* Clear the specified display area.
|
||||
*
|
||||
* Clear the rectangular area specified by the coordinates by setting the pixels
|
||||
* to the state specified (default is off).
|
||||
*
|
||||
* \param x1 the upper left x coordinate of the window
|
||||
* \param y1 the upper left y coordinate of the window
|
||||
* \param x2 the lower right x coordinate of the window
|
||||
* \param y2 the upper lower right y coordinate of the window
|
||||
* \param state true - switch pixels on; false - switch pixels off. If omitted, default to false.
|
||||
* \return No return value.
|
||||
*/
|
||||
void clear(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, bool state = false) { for (uint8_t i=x1; i<=x2; i++) drawVLine(i, y1, y2, state); };
|
||||
|
||||
/**
|
||||
* Get a pointer to the instantiated graphics object.
|
||||
*
|
||||
* Provides a pointer to the MD_MAX72XX object to allow access to
|
||||
* the display graphics functions.
|
||||
*
|
||||
* \return Pointer to the MD_MAX72xx object used by the library.
|
||||
*/
|
||||
MD_MAX72XX *getGraphicObject(void) { return(_D); }
|
||||
|
||||
/**
|
||||
* Gets the maximum X coordinate.
|
||||
*
|
||||
* Depends on the rotation status of the display.
|
||||
*
|
||||
* \return uint8_t the maximum X coordinate.
|
||||
*/
|
||||
uint16_t getXMax(void);
|
||||
|
||||
/**
|
||||
* Gets the maximum Y coordinate.
|
||||
*
|
||||
* Depends on the rotation status of the display.
|
||||
*
|
||||
* \return uint16_t representing the number of columns.
|
||||
*/
|
||||
uint16_t getYMax(void);
|
||||
|
||||
/**
|
||||
* Get the rotation status of the display
|
||||
*
|
||||
* \return rotation_t value for the current rotation (ROT_0 or ROT_90)
|
||||
*/
|
||||
rotation_t getRotation(void) { return(_rotatedDisplay ? ROT_90 : ROT_0); }
|
||||
|
||||
/**
|
||||
* Set rotation status of the display
|
||||
*
|
||||
* Set the display to be rotated or not. ROT_90 and ROT_270 rotate the display
|
||||
* by 90 degrees in the same direction. ROT_O and ROT_180 are an unrotated display.
|
||||
* The default is ROT_O (unrotated).
|
||||
*
|
||||
* \param r rotation_t value for the current rotation (ROT_0 or ROT_90);
|
||||
* \return No return value
|
||||
*/
|
||||
void setRotation(rotation_t r) { _rotatedDisplay = (r == ROT_90) || (r == ROT_270); }
|
||||
|
||||
/**
|
||||
* Turn auto display updates on or off.
|
||||
*
|
||||
* Turn auto updates on and off, as required. When auto updates are turned OFF the
|
||||
* display will not update after each operation. Display updates can be forced at any
|
||||
* time using using a call to update() with no parameters.
|
||||
*
|
||||
* \param state true to enable update, false to suspend updates.
|
||||
* \return No return value.
|
||||
*/
|
||||
void update(bool state) { _updateEnabled = state; _D->control(MD_MAX72XX::UPDATE, state ? MD_MAX72XX::ON : MD_MAX72XX::OFF); };
|
||||
|
||||
/**
|
||||
* Force a display update.
|
||||
*
|
||||
* Force a display update of any changes since the last update. This overrides the
|
||||
* current setting for display updates.
|
||||
*
|
||||
* \return No return value.
|
||||
*/
|
||||
void update() { _D->update(); };
|
||||
|
||||
/**
|
||||
* Set the display intensity.
|
||||
*
|
||||
* Set the intensity (brightness) of the display.
|
||||
*
|
||||
* \param intensity the intensity to set the display (0-15).
|
||||
* \return No return value.
|
||||
*/
|
||||
void setIntensity(uint8_t intensity) { _D->control(MD_MAX72XX::INTENSITY, intensity); }
|
||||
|
||||
/** @} */
|
||||
|
||||
//--------------------------------------------------------------
|
||||
/** \name Methods for drawing graphics.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Draw a horizontal line between two points on the display
|
||||
*
|
||||
* Draw a horizontal line between the specified points. The LED will be turned on or
|
||||
* off depending on the value supplied. The column number will be dereferenced
|
||||
* into the device and column within the device, allowing the LEDs to be treated
|
||||
* as a continuous pixel field.
|
||||
*
|
||||
* \param y y coordinate for the line [0..getYMax()].
|
||||
* \param x1 starting x coordinate for the point [0..getXMax()].
|
||||
* \param x2 ending x coordinate for the point [0..getXMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawHLine(uint16_t y, uint16_t x1, uint16_t x2, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw an arbitrary line between two points on the display
|
||||
*
|
||||
* Draw a line between the specified points using Bresenham's algorithm.
|
||||
* The LED will be turned on or off depending on the value supplied. The
|
||||
* column number will be dereferenced into the device and column within
|
||||
* the device, allowing the LEDs to be treated as a continuous pixel field.
|
||||
*
|
||||
* \param x1 starting x coordinate for the point [0..getXMax()].
|
||||
* \param y1 starting y coordinate for the point [0..getYMax()].
|
||||
* \param x2 ending x coordinate for the point [0..getXMax()].
|
||||
* \param y2 ending y coordinate for the point [0..getYMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw a vertical line between two points on the display
|
||||
*
|
||||
* Draw a horizontal line between the specified points. The LED will be turned on or
|
||||
* off depending on the value supplied. The column number will be dereferenced
|
||||
* into the device and column within the device, allowing the LEDs to be treated
|
||||
* as a continuous pixel field.
|
||||
*
|
||||
* \param x x coordinate for the line [0..getXMax()].
|
||||
* \param y1 starting y coordinate for the point [0..getYMax()].
|
||||
* \param y2 ending y coordinate for the point [0..getYMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawVLine(uint16_t x, uint16_t y1, uint16_t y2, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw a rectangle given two diagonal vertices
|
||||
*
|
||||
* Draw a rectangle given the points across the diagonal. The LED will be turned on or
|
||||
* off depending on the value supplied. The coordinates will be dereferenced
|
||||
* into the device and column within the device, allowing the LEDs to be treated
|
||||
* as a continuous pixel field.
|
||||
*
|
||||
* \param x1 starting x coordinate for the point [0..getXMax()].
|
||||
* \param y1 starting y coordinate for the point [0..getYMax()].
|
||||
* \param x2 ending x coordinate for the point [0..getXMax()].
|
||||
* \param y2 ending y coordinate for the point [0..getYMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw a filled rectangle given two diagonal vertices
|
||||
*
|
||||
* Draw a filled rectangle given the points across the diagonal. The LEDs inside
|
||||
* and on the border will be turned on or off depending on the value supplied.
|
||||
* The coordinates will be dereferenced into the device and column within the
|
||||
* device, allowing the LEDs to be treated as a continuous pixel field.
|
||||
*
|
||||
* \param x1 starting x coordinate for the point [0..getXMax()].
|
||||
* \param y1 starting y coordinate for the point [0..getYMax()].
|
||||
* \param x2 ending x coordinate for the point [0..getXMax()].
|
||||
* \param y2 ending y coordinate for the point [0..getYMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawFillRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw a triangle given 3 vertices
|
||||
*
|
||||
* Draw a triangle given the all the corner points. The LED will be turned on or
|
||||
* off depending on the value supplied. The coordinates will be dereferenced
|
||||
* into the device and column within the device, allowing the LEDs to be treated
|
||||
* as a continuous pixel field.
|
||||
*
|
||||
* \param x1 first x coordinate for the point [0..getXMax()].
|
||||
* \param y1 first y coordinate for the point [0..getYMax()].
|
||||
* \param x2 second x coordinate for the point [0..getXMax()].
|
||||
* \param y2 second y coordinate for the point [0..getYMax()].
|
||||
* \param x3 third x coordinate for the point [0..getXMax()].
|
||||
* \param y3 third y coordinate for the point [0..getYMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw a filled triangle given 3 vertices
|
||||
*
|
||||
* Draw a filled triangle given the all the corner points. The LED for
|
||||
* the border and fill will be turned on or off depending on the value
|
||||
* supplied. The coordinates will be dereferenced into the device and
|
||||
* column within the device, allowing the LEDs to be treated as a
|
||||
* continuous pixel field.
|
||||
*
|
||||
* \param x1 first x coordinate for the point [0..getXMax()].
|
||||
* \param y1 first y coordinate for the point [0..getYMax()].
|
||||
* \param x2 second x coordinate for the point [0..getXMax()].
|
||||
* \param y2 second y coordinate for the point [0..getYMax()].
|
||||
* \param x3 third x coordinate for the point [0..getXMax()].
|
||||
* \param y3 third y coordinate for the point [0..getYMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawFillTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw a quadrilateral given 4 vertices
|
||||
*
|
||||
* Draw a quadrilateral given the all the corner points. The LED will be turned on or
|
||||
* off depending on the value supplied. The coordinates will be dereferenced
|
||||
* into the device and column within the device, allowing the LEDs to be treated
|
||||
* as a continuous pixel field.
|
||||
*
|
||||
* \param x1 first x coordinate for the point [0..getXMax()].
|
||||
* \param y1 first y coordinate for the point [0..getYMax()].
|
||||
* \param x2 second x coordinate for the point [0..getXMax()].
|
||||
* \param y2 second y coordinate for the point [0..getYMax()].
|
||||
* \param x3 third x coordinate for the point [0..getXMax()].
|
||||
* \param y3 third y coordinate for the point [0..getYMax()].
|
||||
* \param x4 fourth x coordinate for the point [0..getXMax()].
|
||||
* \param y4 fourth y coordinate for the point [0..getYMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawQuadrilateral(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t x4, uint16_t y4, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw a circle given center and radius
|
||||
*
|
||||
* Draw a circle given center and radius. The LEDs will be turned
|
||||
* on or off depending on the value supplied. The coordinates will
|
||||
* be dereferenced into the device and column within the device,
|
||||
* allowing the LEDs to be treated as a continuous pixel field.
|
||||
*
|
||||
* \param xc x coordinate for the center point [0..getXMax()].
|
||||
* \param yc y coordinate for the center point [0..getYMax()].
|
||||
* \param r radius of the circle.
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawCircle(uint16_t xc, uint16_t yc, uint16_t r, bool state = true);
|
||||
|
||||
/**
|
||||
* Draw a filled circle given center and radius
|
||||
*
|
||||
* Draw a circle given center and radius. The LEDs for the border
|
||||
* and fill will be turned on or off depending on the value supplied.
|
||||
* The coordinates will be dereferenced into the device and column
|
||||
* within the device, allowing the LEDs to be treated as a continuous
|
||||
* pixel field.
|
||||
*
|
||||
* \param xc x coordinate for the center point [0..getXMax()].
|
||||
* \param yc y coordinate for the center point [0..getYMax()].
|
||||
* \param r radius of the circle.
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if any point is drawn outside the display, true otherwise.
|
||||
*/
|
||||
bool drawFillCircle(uint16_t xc, uint16_t yc, uint16_t r, bool state = true);
|
||||
|
||||
/**
|
||||
* Get the status of a single LED, addressed as a pixel.
|
||||
*
|
||||
* The method will get the status of a specific LED element based on its
|
||||
* coordinate position. The column number is dereferenced into the device
|
||||
* and column within the device, allowing the LEDs to be treated as a
|
||||
* planar pixel field.
|
||||
*
|
||||
* \param x x coordinate [0..getXMax()].
|
||||
* \param y y coordinate [0..getYMax()].
|
||||
* \return true if LED is on, false if off or parameter errors.
|
||||
*/
|
||||
bool getPoint(uint16_t x, uint16_t y);
|
||||
|
||||
/**
|
||||
* Set the status of a single LED, addressed as a pixel.
|
||||
*
|
||||
* The method will set the value of a specific LED element based on its
|
||||
* coordinate position. The LED will be turned on or off depending on the
|
||||
* value supplied. The column number is dereferenced into the device and
|
||||
* column within the device, allowing the LEDs to be treated as a
|
||||
* planar pixel field.
|
||||
*
|
||||
* \param x x coordinate [0..getXMax()].
|
||||
* \param y y coordinate [0..getYMax()].
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return false if parameter errors, true otherwise.
|
||||
*/
|
||||
bool setPoint(uint16_t x, uint16_t y, bool state = true);
|
||||
|
||||
/** @} */
|
||||
|
||||
//--------------------------------------------------------------
|
||||
/** \name Methods for Fonts and text.
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Set the display font.
|
||||
*
|
||||
* Set the display font to a user defined font table. This can be created using the
|
||||
* MD_MAX72xx font builder (refer to documentation for the tool and the MD_MAX72xx library).
|
||||
* Passing nullptr resets to the library default font.
|
||||
*
|
||||
* \param fontDef Pointer to the font definition to be used.
|
||||
* \return No return value.
|
||||
*/
|
||||
void setFont(MD_MAX72XX::fontType_t *fontDef) { _D->setFont(fontDef); }
|
||||
|
||||
/**
|
||||
* Set the spacing between characters.
|
||||
*
|
||||
* Set number of pixel columns between each character in a displayed text.
|
||||
*
|
||||
* \param spacing the spacing between characters.
|
||||
* \return No return value.
|
||||
*/
|
||||
void setCharSpacing(uint8_t spacing) { _charSpacing = spacing; }
|
||||
|
||||
/**
|
||||
* Get the spacing between characters.
|
||||
*
|
||||
* Get number of pixel columns between each character in a displayed text.
|
||||
*
|
||||
* \return the spacing between characters.
|
||||
*/
|
||||
uint8_t getCharSpacing(void) { return(_charSpacing); }
|
||||
|
||||
/**
|
||||
* Get the length of a text string in pixels.
|
||||
*
|
||||
* Get the length of a string in pixels. The text is a nul terminated characters array.
|
||||
* The returned length will include all inter-character Set number of pixel columns between each character in a displayed text.
|
||||
*
|
||||
* \param psz the text string as a nul terminated character array.
|
||||
* \return the length in pixels.
|
||||
*/
|
||||
uint16_t getTextWidth(const char *psz);
|
||||
|
||||
/**
|
||||
* Get the height of the current font in pixels.
|
||||
*
|
||||
* Get the specified of height of the current font in pixels. All the characters of the
|
||||
* font will fit with this height.
|
||||
*
|
||||
* \return the height in pixels.
|
||||
*/
|
||||
uint16_t getFontHeight(void) { return(_D->getFontHeight()); }
|
||||
|
||||
/**
|
||||
* Draw text on the display.
|
||||
*
|
||||
* Get the specified of height of the current font in pixels. All the characters of the
|
||||
* font will fit with this height.
|
||||
*
|
||||
* \param x the x coordinate for the top left corner of the first character.
|
||||
* \param y the Y coordinate for the top left corner of the first character.
|
||||
* \param psz the text to be displayed as a nul terminated character array.
|
||||
* \param rot the required rotation orientation for the text as described in textRotation_t. Default is ROT_0.
|
||||
* \param state true - switch on; false - switch off. If omitted, default to true.
|
||||
* \return the length of the text in pixels.
|
||||
*/
|
||||
uint16_t drawText(uint16_t x, uint16_t y, const char *psz, rotation_t rot = ROT_0, bool state = true);
|
||||
|
||||
/** @} */
|
||||
|
||||
private:
|
||||
// Device buffer data
|
||||
uint8_t _xDevices; // number of devices in the width of the panel
|
||||
uint8_t _yDevices; // number of devices in the height of the panel
|
||||
|
||||
MD_MAX72XX *_D; // hardware driver
|
||||
bool _killOnDestruct; // true if we have allocated the MD_MAX72XX object
|
||||
|
||||
bool _updateEnabled; // true if display updates are suspended
|
||||
uint8_t _charSpacing; // number of pixel columns between characters
|
||||
bool _rotatedDisplay; // true if the display is rotated
|
||||
|
||||
bool drawCirclePoints(uint16_t xc, uint16_t yc, uint16_t x, uint16_t y, bool state);
|
||||
bool drawCircleLines(uint16_t xc, uint16_t yc, uint16_t x, uint16_t y, bool state);
|
||||
uint16_t Y2Row(uint16_t x, uint16_t y); // Convert y coord to linear coord
|
||||
uint16_t X2Col(uint16_t x, uint16_t y); // Convert x coord to linear coord
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
MD_MAXPanel - Library for MAX7219/7221 LED Panel
|
||||
|
||||
See header file for comments
|
||||
|
||||
This file contains font and text related methods.
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "MD_MAXPanel.h"
|
||||
#include "MD_MAXPanel_lib.h"
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Implements font and text methods
|
||||
*/
|
||||
|
||||
uint16_t MD_MAXPanel::getTextWidth(const char *psz)
|
||||
{
|
||||
uint16_t sum = 0;
|
||||
uint8_t bufSize = _D->getMaxFontWidth();
|
||||
uint8_t buf[bufSize];
|
||||
|
||||
while (*psz != '\0')
|
||||
{
|
||||
sum += _D->getChar(*psz++, bufSize, buf);
|
||||
if (*psz) sum += _charSpacing; // next character is not nul, so add inter-character spacing
|
||||
}
|
||||
|
||||
return(sum);
|
||||
}
|
||||
|
||||
uint16_t MD_MAXPanel::drawText(uint16_t x, uint16_t y, const char *psz, rotation_t rot, bool state)
|
||||
{
|
||||
uint8_t height = _D->getFontHeight();
|
||||
uint8_t bufSize = _D->getMaxFontWidth() + _charSpacing;
|
||||
uint8_t buf[bufSize];
|
||||
uint8_t size;
|
||||
uint16_t sum = 0;
|
||||
|
||||
int16_t nextPos;
|
||||
|
||||
PRINT("\ndrawText: ", psz);
|
||||
PRINT(" height ", height);
|
||||
while (*psz != '\0')
|
||||
{
|
||||
PRINT("\nChar ", *psz);
|
||||
memset(buf, '\0', bufSize*sizeof(uint8_t));
|
||||
size = _D->getChar(*psz, bufSize, buf);
|
||||
psz++;
|
||||
if (*psz != '\0') size += _charSpacing; // add in the blank columns
|
||||
sum += size;
|
||||
|
||||
// now display the char depending on what the orientation is
|
||||
switch (rot)
|
||||
{
|
||||
case ROT_0:
|
||||
for (uint16_t i = 0; i < size; i++)
|
||||
for (uint16_t j = 0; j < height; j++)
|
||||
setPoint(x + i, y - j, (buf[i] & (1 << j) ? state : !state));
|
||||
x += size;
|
||||
break;
|
||||
|
||||
case ROT_90:
|
||||
for (uint16_t j = 0; j < size; j++)
|
||||
for (uint16_t i = 0; i < height; i++)
|
||||
setPoint(x + i, y + j, (buf[j] & (1 << i) ? state : !state));
|
||||
y += size;
|
||||
break;
|
||||
|
||||
case ROT_180:
|
||||
for (uint16_t i = 0; i < size; i++)
|
||||
for (uint16_t j = 0; j < height; j++)
|
||||
setPoint(x - i, y + j, (buf[i] & (1 << j) ? state : !state));
|
||||
x -= size;
|
||||
break;
|
||||
|
||||
case ROT_270:
|
||||
for (uint16_t j = 0; j < size; j++)
|
||||
for (uint16_t i = 0; i < height; i++)
|
||||
setPoint(x - i, y - j, (buf[j] & (1 << i) ? state : !state));
|
||||
y -= size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return(sum);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
MD_MAXPanel - Library for panels of MAX72xx LED matrix controllers
|
||||
|
||||
See header file for comments
|
||||
|
||||
This file contains library related definitions and is not visible
|
||||
to user code.
|
||||
|
||||
Copyright (C) 2018 Marco Colli. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Includes library only definitions
|
||||
*/
|
||||
|
||||
#define MP_DEBUG 0 ///< Enable or disable (default) debugging output from the MD_MAX72xx library
|
||||
|
||||
#if MP_DEBUG
|
||||
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); } ///< Print a string followed by a value (decimal)
|
||||
#define PRINTX(s, v) { Serial.print(F(s)); Serial.print(v, HEX); } ///< Print a string followed by a value (hex)
|
||||
#define PRINTB(s, v) { Serial.print(F(s)); Serial.print(v, BIN); } ///< Print a string followed by a value (binary)
|
||||
#define PRINTS(s) { Serial.print(F(s)); } ///< Print a string
|
||||
#else
|
||||
#define PRINT(s, v) ///< Print a string followed by a value (decimal)
|
||||
#define PRINTX(s, v) ///< Print a string followed by a value (hex)
|
||||
#define PRINTB(s, v) ///< Print a string followed by a value (binary)
|
||||
#define PRINTS(s) ///< Print a string
|
||||
#endif
|
||||
|
||||
#define X2COL(x, y) (((y / ROW_SIZE) * (getXMax() + 1)) + (getXMax() - (x % (getXMax() + 1)))) ///< Convert x coord to linear coord
|
||||
#define Y2ROW(x, y) (ROW_SIZE - (y % ROW_SIZE) - 1) ///< Convert y coord to linear coord
|
||||
|
||||
#define CHAR_SPACING_DEFAULT 1 ///< Default number of pixels between characters
|
||||
Reference in New Issue
Block a user