]> nyquistdev.net Git - galaga.git/commitdiff
first commit main
authorA-Larsen <austin.larsen@mailfence.com>
Thu, 15 Oct 2026 22:30:52 +0000 (18:30 -0400)
committerA-Larsen <austin.larsen@mailfence.com>
Thu, 15 Oct 2026 22:30:52 +0000 (18:30 -0400)
LICENSE [new file with mode: 0644]
README.adoc [new file with mode: 0644]
docinfo.conf [new file with mode: 0644]
galaga [new file with mode: 0755]
galaga.c [new file with mode: 0644]
game.c [new file with mode: 0644]
game.h [new file with mode: 0644]
makefile [new file with mode: 0644]
rendered.html [new file with mode: 0644]
tests.c [new file with mode: 0644]
todo.adoc [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..d159169
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 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.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, 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 or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+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 give any other recipients of the Program a copy of this License
+along with the Program.
+
+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 Program or any portion
+of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+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 Program, 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 Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) 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; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, 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 executable.  However, as a
+special exception, the source code 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.
+
+If distribution of executable or 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 counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program 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.
+
+  5. 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 Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program 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 to
+this License.
+
+  7. 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 Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program 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 Program.
+
+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.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program 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.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the 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 Program
+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 Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, 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
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), 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 Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  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 program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, 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.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.adoc b/README.adoc
new file mode 100644 (file)
index 0000000..ec68d70
--- /dev/null
@@ -0,0 +1,22 @@
+== Galaga
+Recreation of Galaga with SDL2 library in C
+
+== Building
+
+----
+make
+----
+
+== Running tests
+
+[source, sh]
+----
+./test <test name>
+----
+
+example:
+
+[source, sh]
+----
+ ./tests "pickFormationPosition"
+----
diff --git a/docinfo.conf b/docinfo.conf
new file mode 100644 (file)
index 0000000..57fa5b9
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+HOSTNAME=nyquistdev.net
+DOCINFODIR=/usr/local/share/readme-to-html
diff --git a/galaga b/galaga
new file mode 100755 (executable)
index 0000000..9b0e870
Binary files /dev/null and b/galaga differ
diff --git a/galaga.c b/galaga.c
new file mode 100644 (file)
index 0000000..4ce3051
--- /dev/null
+++ b/galaga.c
@@ -0,0 +1,26 @@
+/* 
+ * Copyright (C) 2024  Austin Larsen
+ * 
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "game.h"
+
+int main(void)
+{
+    Game game;
+    GameInit(&game);
+    GameUpdate(&game, 220, 60);
+    GameQuit(&game);
+}
diff --git a/game.c b/game.c
new file mode 100644 (file)
index 0000000..95cc56f
--- /dev/null
+++ b/game.c
@@ -0,0 +1,457 @@
+/* \r
+ * Copyright (C) 2024  Austin Larsen\r
+ * \r
+ * This program is free software; you can redistribute it and/or modify it under\r
+ * the terms of the GNU General Public License as published by the Free Software\r
+ * Foundation; either version 2 of the License, or (at your option) any later\r
+ * version.\r
+ * \r
+ * This program is distributed in the hope that it will be useful, but WITHOUT\r
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\r
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\r
+ * details.\r
+ * \r
+ * You should have received a copy of the GNU General Public License along with\r
+ * this program; if not, write to the Free Software Foundation, Inc., 51\r
+ * Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\r
+ */\r
+#include "game.h"\r
+\r
+void interpolate(float *x, int y, int s, SDL_Point p1, SDL_Point p2) {\r
+    if ((p2.y - p1.y) == 0) return;\r
+    *x = s + (float)((p2.x - p1.x) * (y - p1.y)) / (float)(p2.y - p1.y);\r
+}\r
+\r
+void\r
+setColor(SDL_Renderer *renderer, uint8_t color)\r
+{\r
+    const SDL_Color colors[] = {\r
+        [COLOR_RED] = {.r = 217, .g = 100, .b = 89, .a = 255},\r
+        [COLOR_WHITE] = {.r = 255, .g = 255, .b = 255, .a = 255},\r
+        [COLOR_GREEN] = {.r = 88, .g = 140, .b = 126, .a = 255},\r
+        [COLOR_BLUE] = {.r = 146, .g = 161, .b = 185, .a = 255},\r
+        [COLOR_ORANGE] = {.r = 242, .g = 174, .b = 114, .a = 255},\r
+        [COLOR_GREY] = {.r = 89, .g = 89, .b = 89, .a = 89},\r
+        [COLOR_BLACK] = {.r = 0, .g = 0, .b = 0, .a = 0},\r
+    };\r
+\r
+    SDL_SetRenderDrawColor(renderer, colors[color].r, colors[color].g,\r
+                           colors[color].b, colors[color].a);\r
+}\r
+\r
+void\r
+updateGridPosition(Grid *grid, uint64_t frame)\r
+{\r
+    uint64_t update_rate = 450;\r
+\r
+    grid->space = 50 + sin(((frame % update_rate) /\r
+                           (float)update_rate) * TAU) * 4;\r
+\r
+    float width = (float)grid->space * (float)FORMATION_WIDTH;\r
+    uint8_t height = (float)grid->space * (float)FORMATION_HEIGHT;\r
+\r
+    grid->start.x = ((grid->space - ENEMY_SIZE_PX) / 2.0f) +\r
+              ((float)SCREEN_WIDTH_PX / 2.0f) - (width / 2.0f);\r
+              \r
+\r
+    grid->start.y = 10;\r
+}\r
+\r
+void\r
+getGridPosition(SDL_Point *point, Grid grid,  uint8_t x, uint8_t y)\r
+{\r
+    point->x = grid.start.x + x * grid.space;\r
+    point->y = grid.start.y + y * grid.space;\r
+}\r
+\r
+void\r
+drawFormationGrid(SDL_Renderer *renderer, Grid grid, uint64_t frame)\r
+{\r
+\r
+    for (uint8_t y = 0; y < FORMATION_HEIGHT; ++y) {\r
+        for (uint8_t x = 0; x < FORMATION_WIDTH; ++x) {\r
+            SDL_Point point;\r
+\r
+            getGridPosition(&point, grid, x, y);\r
+\r
+            SDL_Rect rect = {\r
+                .x = point.x,\r
+                .y = point.y,\r
+                .w = ENEMY_SIZE_PX,\r
+                .h = ENEMY_SIZE_PX,\r
+            };\r
+            \r
+            SDL_RenderDrawRect(renderer, &rect);\r
+        }\r
+    }\r
+}\r
+\r
+void\r
+enemyToFormation(FPosition *point, Grid *grid, uint64_t frame, FPosition source,\r
+                 bool *enteredFormation, uint8_t pickedPosition,\r
+                 SDL_Point destination)\r
+{\r
+    SDL_Point a;\r
+\r
+    getGridPosition(&a, *grid, destination.x, destination.y);\r
+\r
+    if (!(*enteredFormation)) {\r
+        SDL_Point b = {\r
+            .x = source.x,\r
+            .y = source.y\r
+        };\r
+\r
+        interpolate(&point->x, point->y, source.x, b, a);\r
+\r
+        point->y -= 1;\r
+\r
+        if (point->y < a.y) {\r
+            *enteredFormation = true;\r
+            grid->formation[pickedPosition] = 1;\r
+        }\r
+        return;\r
+    }\r
+\r
+    point->y = a.y;\r
+    point->x = a.x;\r
+}\r
+\r
+void\r
+drawFighter(SDL_Renderer *renderer, SDL_Point point)\r
+{\r
+    SDL_Rect rect = {\r
+        .x = point.x,\r
+        .y = point.y,\r
+        .w = FIGHTER_WIDTH_PX,\r
+        .h = FIGHTER_HEIGHT_PX\r
+    };\r
+\r
+    setColor(renderer, COLOR_GREEN);\r
+    SDL_RenderFillRect(renderer, &rect);\r
+}\r
+\r
+void\r
+drawEnemy(SDL_Renderer *renderer, FPosition point)\r
+{\r
+    SDL_Rect rect = {\r
+        .x = point.x,\r
+        .y = point.y,\r
+        .w = ENEMY_SIZE_PX,\r
+        .h = ENEMY_SIZE_PX\r
+    };\r
+\r
+    setColor(renderer, COLOR_BLUE);\r
+    SDL_RenderFillRect(renderer, &rect);\r
+}\r
+\r
+void\r
+drawNoiseCircle(SDL_Renderer *renderer, SDL_Point center,\r
+           uint8_t px_size, uint16_t radius, float accuracy)\r
+{\r
+    for(float i = 0; i <= TAU + accuracy; i += accuracy) {\r
+        float n = (float)rand() / (float)RAND_MAX;\r
+\r
+        float x = sinf(n * TAU);\r
+        float y = cosf(n * TAU);\r
+\r
+        // if (!(i > 0)) continue;\r
+\r
+        SDL_Rect rect = {\r
+            .x = x * radius + center.x,\r
+            .y = y * radius + center.y,\r
+            .w = px_size,\r
+            .h = px_size\r
+        };\r
+\r
+        SDL_RenderFillRect(renderer, &rect);\r
+    }\r
+}\r
+\r
+bool\r
+drawExplosion(SDL_Renderer *renderer, uint64_t frame)\r
+{\r
+    static float i = 0;\r
+    SDL_Point center = {.x = 400, .y = 400};\r
+\r
+    if (i >= 55) return false;\r
+\r
+    uint8_t gap = 3;\r
+\r
+    setColor(renderer, (int)i % 4 ? COLOR_WHITE : COLOR_RED);\r
+\r
+    for (uint8_t j = 0; j < i; ++j) {\r
+        drawNoiseCircle(renderer, center, 3, j, 4);\r
+    }\r
+\r
+    i += .9f;\r
+    return true;\r
+}\r
+\r
+\r
+void\r
+enemyMove(FPosition *point, bool invert_x, bool invert_y, float radians)\r
+{\r
+    point->x += sinf(radians) * (invert_x ? -1 : 1);\r
+    point->y += cosf(radians) * (invert_y ? -1 : 1);\r
+}\r
+\r
+bool\r
+enemyEntrance(uint8_t p1, uint8_t p2, uint64_t frame, FPosition *rect)\r
+{\r
+    const int padding = 50;\r
+\r
+    if (rect->init) {\r
+\r
+        switch(p1) {\r
+            case BOTTOM: rect->radians = M_PI_2; break;\r
+            case TOP: rect->radians = 0; break;\r
+        }\r
+\r
+        rect->start_radians = rect->radians;\r
+\r
+        if (p1 == BOTTOM && p2 == LEFT) {\r
+            rect->x = -padding - ENEMY_SIZE_PX;\r
+            rect->y = (float)SCREEN_HEIGHT_PX - ENEMY_SIZE_PX - padding;\r
+        }\r
+\r
+       if (p1 == BOTTOM && p2 == RIGHT) {\r
+            rect->x = SCREEN_WIDTH_PX + padding;\r
+            rect->y = (float)SCREEN_HEIGHT_PX - ENEMY_SIZE_PX - padding;\r
+       }\r
+\r
+       if (p1 == TOP && p2 == CENTER_LEFT) {\r
+            rect->x = ((float)SCREEN_WIDTH_PX / 2.0f) - ENEMY_SIZE_PX - 30;\r
+            rect->y = -padding - ENEMY_SIZE_PX;\r
+       }\r
+\r
+        rect->init = false;\r
+    }\r
+\r
+\r
+    switch(p1) {\r
+        case BOTTOM: {\r
+            enemyMove(rect, p2 == LEFT ? 0 : 1, 0, rect->radians);\r
+\r
+            float r2 = 0;\r
+\r
+            if (rect->radians - rect->start_radians < M_PI_2) {\r
+                rect->radians += .003f;\r
+                r2 = rect->radians;\r
+            } else{\r
+                rect->radians += .016f;\r
+                if (rect->radians - r2 >= M_3PI) {\r
+                    rect->init = true;\r
+                    return false;\r
+                }\r
+            }\r
+            break;\r
+        }\r
+        case TOP: {\r
+            enemyMove(rect, p2 == CENTER_LEFT ? 1 : 0, 0, rect->radians);\r
+\r
+            float r2 = 0;\r
+\r
+            if (rect->radians - rect->start_radians > -M_PI_4) {\r
+                rect->radians -= .002f;\r
+                r2 = rect->radians;\r
+            } else {\r
+                rect->radians -= .016f;\r
+                if (rect->radians - r2 <= -TAU) {\r
+                    rect->init = true;\r
+                    return false;\r
+                }\r
+            }\r
+            break;\r
+        }\r
+    }\r
+\r
+    return true;\r
+}\r
+\r
+uint8_t\r
+pickFormationPosition(uint8_t type, bool *formation)\r
+{\r
+\r
+    switch(type) {\r
+        case ENEMY_BEE: {\r
+            int i = -1;\r
+\r
+            while (true) {\r
+                float r = (float)rand() / (float)RAND_MAX;\r
+                i = r * FORMATION_WIDTH * FORMATION_HEIGHT;\r
+                if (!formation[i]) break;\r
+            }\r
+\r
+            formation[i] = 1;\r
+            return i;\r
+        }\r
+    }\r
+    return 0;\r
+}\r
+\r
+FPosition *\r
+EnemyEnter(uint8_t id, uint8_t type, Grid *grid, uint8_t enter,\r
+           uint64_t frame) {\r
+\r
+    static EnemyInfo info[FORMATION_WIDTH] = {[0 ... FORMATION_WIDTH - 1] = {\r
+        .pickedPosition = 0,\r
+        .enteredFormation = false,\r
+        .entering = true,\r
+        .source = {0},\r
+        .position = {\r
+            .x = 0,\r
+            .y = 0,\r
+            .radians = 0,\r
+            .init = true\r
+        },\r
+        .formation = {0}\r
+    }};\r
+\r
+    EnemyInfo *infop = &info[id];\r
+\r
+    if (infop->entering) {\r
+        infop->entering = enemyEntrance(BOTTOM, enter, frame,\r
+                                        &infop->position);\r
+    } else if (!infop->pickedPosition) {\r
+        SDL_Point p;\r
+        uint8_t i = pickFormationPosition(type, grid->formation);\r
+        infop->formation.x = i % FORMATION_WIDTH;\r
+        infop->formation.y = floor((float)i / (float)FORMATION_WIDTH);\r
+        printf("i: %d\n", i);\r
+        printf("x: %d, y: %d\n", infop->formation.x, infop->formation.y);\r
+        memcpy(&infop->source, &infop->position, sizeof(FPosition));\r
+        infop->pickedPosition = i;\r
+    } else{\r
+        enemyToFormation(&infop->position, grid, frame, infop->source,\r
+                         &infop->enteredFormation, infop->pickedPosition,\r
+                         infop->formation);\r
+    }\r
+    return &infop->position;\r
+}\r
+\r
+uint8_t\r
+updateMain(Game *game, uint64_t frame, SDL_KeyCode key, bool keydown)\r
+{\r
+    static bool init = true;\r
+\r
+    if (init) {\r
+        srand(time(NULL));\r
+        init = false;\r
+    }\r
+\r
+    updateGridPosition(&game->grid, frame);\r
+\r
+    static SDL_Point fighter_pos = {\r
+        .x = 10,\r
+        .y = SCREEN_HEIGHT_PX - FIGHTER_HEIGHT_PX - 10\r
+    };\r
+\r
+    // make sure left and right enemy do not choose the same spot\r
+    FPosition *position1 = EnemyEnter(0, ENEMY_BEE, &game->grid, RIGHT, frame);\r
+    FPosition *position2 = EnemyEnter(1, ENEMY_BEE, &game->grid, LEFT, frame);\r
+\r
+    drawExplosion(game->renderer, frame);\r
+    drawFighter(game->renderer, fighter_pos);\r
+    drawEnemy(game->renderer, *position1);\r
+    drawEnemy(game->renderer, *position2);\r
+    SDL_RenderDrawLine(game->renderer, SCREEN_WIDTH_PX / 2, 0,\r
+                       SCREEN_WIDTH_PX / 2, SCREEN_HEIGHT_PX);\r
+    drawFormationGrid(game->renderer, game->grid, frame);\r
+\r
+    return UPDATE_MAIN;\r
+}\r
+\r
+void\r
+GameInit(Game *game)\r
+{\r
+    memset(game, 0, sizeof(Game));\r
+\r
+    END(SDL_Init(SDL_INIT_VIDEO) != 0, "Could not create texture",\r
+        SDL_GetError());\r
+\r
+    END(TTF_Init() != 0, "Could not initialize TTF", TTF_GetError());\r
+\r
+    END(IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG, "Could not initialize PNG",\r
+                 TTF_GetError());\r
+\r
+    game->window = SDL_CreateWindow("galaga", SDL_WINDOWPOS_UNDEFINED, \r
+                     SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH_PX, \r
+                     SCREEN_HEIGHT_PX, SDL_WINDOW_SHOWN);\r
+\r
+    END(game->window == NULL, "Could not create window", SDL_GetError());\r
+\r
+    game->renderer = SDL_CreateRenderer(game->window, 0,\r
+                                        SDL_RENDERER_SOFTWARE);\r
+\r
+    END(game->renderer == NULL, "Could not create renderer", SDL_GetError());\r
+}\r
+\r
+void\r
+GameUpdate(Game *game, const uint16_t lps, const uint16_t fps)\r
+{\r
+    uint64_t frame = 0;\r
+    bool keydown = false;\r
+    uint8_t update_id = 0;\r
+    Update_callback update;\r
+    float lsp_mspd = (1.0f / (float)lps) * 1000.0f;\r
+\r
+    SDL_Rect background_rect = {\r
+        .x = 0,\r
+        .y = 0,\r
+        .w = SCREEN_WIDTH_PX,\r
+        .h = SCREEN_HEIGHT_PX\r
+    };\r
+\r
+    while (!game->quit) {\r
+        uint32_t start = SDL_GetTicks();\r
+\r
+        switch (update_id) {\r
+            case UPDATE_MAIN: update = updateMain; break;\r
+        }\r
+\r
+        setColor(game->renderer, COLOR_GREY);\r
+        SDL_RenderClear(game->renderer);\r
+        setColor(game->renderer, COLOR_BLACK);\r
+        SDL_RenderFillRect(game->renderer, &background_rect);\r
+\r
+        SDL_Event event;\r
+        SDL_KeyCode key = 0;\r
+\r
+        while (SDL_PollEvent(&event)) {\r
+            switch (event.type) {\r
+                case SDL_KEYDOWN: {\r
+                    if (event.key.repeat == 0) {\r
+                      key = event.key.keysym.sym;\r
+                      keydown = true;\r
+                    }\r
+\r
+                    break;\r
+                }\r
+\r
+                case SDL_KEYUP: keydown = false; break;\r
+                case SDL_QUIT: game->quit = true; break;\r
+            }\r
+        }\r
+\r
+        update_id = update(game, frame, key, keydown);\r
+\r
+        uint32_t end = SDL_GetTicks();\r
+        uint32_t elapsed_time = end - start;\r
+\r
+        if (elapsed_time < lsp_mspd) SDL_Delay(lsp_mspd - elapsed_time);\r
+\r
+        if (frame % (lps / fps) == 0) SDL_RenderPresent(game->renderer);\r
+\r
+        frame++;\r
+    }\r
+}\r
+\r
+void\r
+GameQuit(Game *game)\r
+{\r
+    SDL_DestroyWindow(game->window);\r
+    SDL_DestroyRenderer(game->renderer);\r
+    TTF_Quit();\r
+    IMG_Quit();\r
+    SDL_Quit();\r
+}\r
diff --git a/game.h b/game.h
new file mode 100644 (file)
index 0000000..ef4072e
--- /dev/null
+++ b/game.h
@@ -0,0 +1,120 @@
+/* 
+ * Copyright (C) 2024  Austin Larsen
+ * 
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _GAME_H_
+#define _GAME_H_
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+#include <SDL2/SDL_image.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <time.h>
+
+#define TAU ((float)(M_PI * 2.0f))
+#define M_3PI ((float)(M_PI * 3.0f))
+#define SCREEN_WIDTH_PX 600U
+#define SCREEN_HEIGHT_PX 800U
+#define FIGHTER_WIDTH_PX 45
+#define FIGHTER_HEIGHT_PX 58
+#define ENEMY_SIZE_PX 30
+#define UP TOP
+#define DOWN BOTTOM
+#define FORMATION_WIDTH 10
+#define FORMATION_HEIGHT 5
+#define FORMATION_SIZE (FORMATION_WIDTH * FORMATION_HEIGHT)
+#define ENEMY_BOSS_START 0
+#define ENEMY_BOSS_END 9
+#define ENEMY_BUTTERFLY_START 10
+#define ENEMY_BUTTERFLY_END 29
+#define ENEMY_BEE_START 30
+#define ENEMY_BEE_END 39
+
+#define END(check, str1, str2) \
+    if (check) { \
+        assert(check); \
+        fprintf(stderr, "%s\n%s", str1, str2); \
+        exit(1); \
+    } \
+
+enum /* color */ {COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_ORANGE, COLOR_GREY, COLOR_WHITE, COLOR_BLACK, COLOR_SIZE};
+enum /* cardinal */ {TOP, BOTTOM, LEFT, RIGHT, CENTER_LEFT, CENTER_RIGHT};
+enum /*enemy types */ {ENEMY_BEE, ENEMY_BUTTERFLY, ENEMY_BOSS};
+enum /* update callback ids */ {UPDATE_MAIN};
+
+typedef struct _FPoint {
+    bool init;
+    float x;
+    float y;
+    float radians;
+    float start_radians;
+} FPosition;
+
+typedef struct _Grid {
+    bool formation[FORMATION_SIZE]; // 10 x 5
+    SDL_Point start;
+    uint8_t space;
+    // row 1  (0-9)    boss
+    // row 2  (10-19)  butterfly
+    // row 3  (20-29)  butterfly
+    // row 4  (30-39)  bees
+    // row 5: (40-49)  bees
+} Grid;
+
+typedef struct _Game {
+    bool quit;
+    bool canDraw;
+    uint8_t level;
+    uint64_t score;
+    Grid grid;
+    SDL_Renderer *renderer;
+    SDL_Window *window;
+} Game;
+
+typedef struct _EnemyInfo {
+    bool enteredFormation;
+    bool entering;
+    SDL_Point formation;
+    FPosition source;
+    FPosition position;
+    uint8_t pickedPosition;
+} EnemyInfo;
+
+typedef uint8_t (*Update_callback)(Game *game, uint64_t frame, SDL_KeyCode key, bool keydown);
+
+void interpolate(float *x, int y, int s, SDL_Point p1, SDL_Point p2);
+void setColor(SDL_Renderer *renderer, uint8_t color);
+void updateGridPosition(Grid *grid, uint64_t frame);
+void getGridPosition(SDL_Point *point, Grid grid,  uint8_t x, uint8_t y);
+void drawFormationGrid(SDL_Renderer *renderer, Grid grid, uint64_t frame);
+void enemyToFormation(FPosition *point, Grid *grid, uint64_t frame, FPosition source, bool *enteredFormation, uint8_t pickedPosition, SDL_Point destination);
+void drawFighter(SDL_Renderer *renderer, SDL_Point point);
+void drawEnemy(SDL_Renderer *renderer, FPosition point);
+void drawNoiseCircle(SDL_Renderer *renderer, SDL_Point center, uint8_t px_size, uint16_t radius, float accuracy);
+bool drawExplosion(SDL_Renderer *renderer, uint64_t frame);
+void enemyMove(FPosition *point, bool invert_x, bool invert_y, float radians);
+bool enemyEntrance(uint8_t p1, uint8_t p2, uint64_t frame, FPosition *rect);
+uint8_t pickFormationPosition(uint8_t type, bool *formation);
+FPosition * EnemyEnter(uint8_t id, uint8_t type, Grid *grid, uint8_t enter, uint64_t frame);
+uint8_t updateMain(Game *game, uint64_t frame, SDL_KeyCode key, bool keydown);
+void GameInit(Game *game);
+void GameUpdate(Game *game, const uint16_t lps, const uint16_t fps);
+void GameQuit(Game *game);
+
+#endif // _GAME_H_
diff --git a/makefile b/makefile
new file mode 100644 (file)
index 0000000..a4cb5fe
--- /dev/null
+++ b/makefile
@@ -0,0 +1,14 @@
+LIBS = -lSDL2 -lSDL2_image -lSDL2_ttf -lm\r
+\r
+PROG = galaga\r
+\r
+game: game.c $(PROG).c\r
+       gcc -g -o $(PROG) game.c $(PROG).c $(LIBS)\r
+\r
+tests: game.c tests.c\r
+       gcc -g -o tests game.c tests.c $(LIBS)\r
+\r
+clean:\r
+       rm -rf $(PROG) tests\r
+\r
+.PHONY: clean\r
diff --git a/rendered.html b/rendered.html
new file mode 100644 (file)
index 0000000..ab97e91
--- /dev/null
@@ -0,0 +1,959 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Galaga</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
+<style>
+pre.pygments .hll { background-color: #49483e }
+pre.pygments { background: #272822; color: #F8F8F2 }
+pre.pygments .tok-c { color: #959077 } /* Comment */
+pre.pygments .tok-err { color: #ED007E; background-color: #1E0010 } /* Error */
+pre.pygments .tok-esc { color: #F8F8F2 } /* Escape */
+pre.pygments .tok-g { color: #F8F8F2 } /* Generic */
+pre.pygments .tok-k { color: #66D9EF } /* Keyword */
+pre.pygments .tok-l { color: #AE81FF } /* Literal */
+pre.pygments .tok-n { color: #F8F8F2 } /* Name */
+pre.pygments .tok-o { color: #FF4689 } /* Operator */
+pre.pygments .tok-x { color: #F8F8F2 } /* Other */
+pre.pygments .tok-p { color: #F8F8F2 } /* Punctuation */
+pre.pygments .tok-ch { color: #959077 } /* Comment.Hashbang */
+pre.pygments .tok-cm { color: #959077 } /* Comment.Multiline */
+pre.pygments .tok-cp { color: #959077 } /* Comment.Preproc */
+pre.pygments .tok-cpf { color: #959077 } /* Comment.PreprocFile */
+pre.pygments .tok-c1 { color: #959077 } /* Comment.Single */
+pre.pygments .tok-cs { color: #959077 } /* Comment.Special */
+pre.pygments .tok-gd { color: #FF4689 } /* Generic.Deleted */
+pre.pygments .tok-ge { color: #F8F8F2; font-style: italic } /* Generic.Emph */
+pre.pygments .tok-ges { color: #F8F8F2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+pre.pygments .tok-gr { color: #F8F8F2 } /* Generic.Error */
+pre.pygments .tok-gh { color: #F8F8F2 } /* Generic.Heading */
+pre.pygments .tok-gi { color: #A6E22E } /* Generic.Inserted */
+pre.pygments .tok-go { color: #66D9EF } /* Generic.Output */
+pre.pygments .tok-gp { color: #FF4689; font-weight: bold } /* Generic.Prompt */
+pre.pygments .tok-gs { color: #F8F8F2; font-weight: bold } /* Generic.Strong */
+pre.pygments .tok-gu { color: #959077 } /* Generic.Subheading */
+pre.pygments .tok-gt { color: #F8F8F2 } /* Generic.Traceback */
+pre.pygments .tok-kc { color: #66D9EF } /* Keyword.Constant */
+pre.pygments .tok-kd { color: #66D9EF } /* Keyword.Declaration */
+pre.pygments .tok-kn { color: #FF4689 } /* Keyword.Namespace */
+pre.pygments .tok-kp { color: #66D9EF } /* Keyword.Pseudo */
+pre.pygments .tok-kr { color: #66D9EF } /* Keyword.Reserved */
+pre.pygments .tok-kt { color: #66D9EF } /* Keyword.Type */
+pre.pygments .tok-ld { color: #E6DB74 } /* Literal.Date */
+pre.pygments .tok-m { color: #AE81FF } /* Literal.Number */
+pre.pygments .tok-s { color: #E6DB74 } /* Literal.String */
+pre.pygments .tok-na { color: #A6E22E } /* Name.Attribute */
+pre.pygments .tok-nb { color: #F8F8F2 } /* Name.Builtin */
+pre.pygments .tok-nc { color: #A6E22E } /* Name.Class */
+pre.pygments .tok-no { color: #66D9EF } /* Name.Constant */
+pre.pygments .tok-nd { color: #A6E22E } /* Name.Decorator */
+pre.pygments .tok-ni { color: #F8F8F2 } /* Name.Entity */
+pre.pygments .tok-ne { color: #A6E22E } /* Name.Exception */
+pre.pygments .tok-nf { color: #A6E22E } /* Name.Function */
+pre.pygments .tok-nl { color: #F8F8F2 } /* Name.Label */
+pre.pygments .tok-nn { color: #F8F8F2 } /* Name.Namespace */
+pre.pygments .tok-nx { color: #A6E22E } /* Name.Other */
+pre.pygments .tok-py { color: #F8F8F2 } /* Name.Property */
+pre.pygments .tok-nt { color: #FF4689 } /* Name.Tag */
+pre.pygments .tok-nv { color: #F8F8F2 } /* Name.Variable */
+pre.pygments .tok-ow { color: #FF4689 } /* Operator.Word */
+pre.pygments .tok-pm { color: #F8F8F2 } /* Punctuation.Marker */
+pre.pygments .tok-w { color: #F8F8F2 } /* Text.Whitespace */
+pre.pygments .tok-mb { color: #AE81FF } /* Literal.Number.Bin */
+pre.pygments .tok-mf { color: #AE81FF } /* Literal.Number.Float */
+pre.pygments .tok-mh { color: #AE81FF } /* Literal.Number.Hex */
+pre.pygments .tok-mi { color: #AE81FF } /* Literal.Number.Integer */
+pre.pygments .tok-mo { color: #AE81FF } /* Literal.Number.Oct */
+pre.pygments .tok-sa { color: #E6DB74 } /* Literal.String.Affix */
+pre.pygments .tok-sb { color: #E6DB74 } /* Literal.String.Backtick */
+pre.pygments .tok-sc { color: #E6DB74 } /* Literal.String.Char */
+pre.pygments .tok-dl { color: #E6DB74 } /* Literal.String.Delimiter */
+pre.pygments .tok-sd { color: #E6DB74 } /* Literal.String.Doc */
+pre.pygments .tok-s2 { color: #E6DB74 } /* Literal.String.Double */
+pre.pygments .tok-se { color: #AE81FF } /* Literal.String.Escape */
+pre.pygments .tok-sh { color: #E6DB74 } /* Literal.String.Heredoc */
+pre.pygments .tok-si { color: #E6DB74 } /* Literal.String.Interpol */
+pre.pygments .tok-sx { color: #E6DB74 } /* Literal.String.Other */
+pre.pygments .tok-sr { color: #E6DB74 } /* Literal.String.Regex */
+pre.pygments .tok-s1 { color: #E6DB74 } /* Literal.String.Single */
+pre.pygments .tok-ss { color: #E6DB74 } /* Literal.String.Symbol */
+pre.pygments .tok-bp { color: #F8F8F2 } /* Name.Builtin.Pseudo */
+pre.pygments .tok-fm { color: #A6E22E } /* Name.Function.Magic */
+pre.pygments .tok-vc { color: #F8F8F2 } /* Name.Variable.Class */
+pre.pygments .tok-vg { color: #F8F8F2 } /* Name.Variable.Global */
+pre.pygments .tok-vi { color: #F8F8F2 } /* Name.Variable.Instance */
+pre.pygments .tok-vm { color: #F8F8F2 } /* Name.Variable.Magic */
+pre.pygments .tok-il { color: #AE81FF } /* Literal.Number.Integer.Long */
+</style>
+<head>
+<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=DotGothic16"> -->
+<!-- <link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/A-Larsen/math-notes/refs/heads/main/favicon.png"> -->
+<!-- <link rel="icon" type="image/png" href="N2DE_favicon.png"/> -->
+<!-- <link rel="preconnect" href="https://fonts.googleapis.com"> -->
+<!-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> -->
+<!-- <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap" rel="stylesheet"> -->
+<style>
+
+/*--------------------------------------------------------------*/
+/*use '!important' to override default css used by asciidoctor*/
+/*--------------------------------------------------------------*/
+
+:root {
+    --gruvbox-black: #282828;
+    --gruvbox-black2: #1d2021;
+    --gruvbox-black3: #463f39;
+    --gruvbox-red: #fc7869;
+    --gruvbox-yellow: #fabd2f;
+    --gruvbox-white: #ebdbb2;
+    --gruvbox-white2: #a7a187;
+    --gruvbox-blue: #83a598;
+    --gruvbox-green: #b8bb26;
+    --gruvbox-bg1: #7c6f64;
+    --gruvbox-bg2: #665c54;
+
+}
+
+.title {
+  color: var(--gruvbox-red) !important;
+}
+
+blockquote {
+    color: var(--gruvbox-white) !important;
+}
+
+table {
+    margin: 0 auto !important;
+}
+
+.hdlist1 {
+  font-size: 1.3em;
+}
+
+cite {
+    color: var(--gruvbox-green) !important;
+}
+
+.tableblock {
+    background-color: inherit;
+    color: var(--gruvbox-white);
+    max-width: 95%;
+}
+
+.tableblock th {
+    background-color: var(--gruvbox-black3);
+    color: var(--gruvbox-white);
+    max-width: 97%;
+}
+
+code:not([data-lang]) {
+    background-color: var(--gruvbox-bg2) !important;
+    color: var(--gruvbox-white);
+    text-shadow: none;
+}
+
+.checklist {
+    text-align: left;
+    font-size: .8em;
+}
+
+.checklist li ul { margin-left: 3%;
+}
+
+#content, h1, pre {
+    border-radius: 8px !important;
+}
+
+#header, #content, #footnotes, #footer {
+  padding-left: 0 !important;
+  padding-right: 0 !important;
+}
+
+.api-type a:hover {
+    text-decoration: underline;
+}
+
+.api-type, .api-type a {
+    color: var(--gruvbox-green);
+    text-decoration: none;
+}
+
+pre {
+    border-style: solid !important;
+    border-color: white !important;
+    border-width: 2px !important;
+}
+
+h1 {
+   /* font-family: "DotGothic16", sans-serif !important;*/
+
+
+    background-color: var(--gruvbox-white2) !important;
+    color: var(--gruvbox-black) !important;
+}
+
+.admonitionblock > table td.content {
+    color: var(--gruvbox-white) !important;
+}
+
+/*
+html {
+    margin: 0;
+    height: 100%;
+    width: 100%;
+}
+
+
+*/
+/*html {*/
+
+body {
+    /*background: black url("background-image-space.jpeg") no-repeat fixed center;*/
+    /*background-color: black;*/
+    background-color: var(--gruvbox-black);
+    background-attachment: fixed;
+    background-size: cover;
+    overflow-y: scroll;
+    /*
+    margin: 0;
+    height: 100%;
+    width: 100%;
+    overflow: hidden;
+    */
+}
+
+
+/*
+body {
+    overflow-y: scroll;
+    background: rgba(0,0,0,0);
+    height: 100%;
+    margin: 2%;
+}
+*/
+
+#toc {
+    background-color: var(--gruvbox-black2) !important;
+    /*opacity: .9;*/
+}
+
+#toctitle {
+    color: var(--gruvbox-yellow);
+}
+
+a {
+    color: var(--gruvbox-bg1);
+}
+
+#content {
+    color: #ebdbb2;
+    background: var(--gruvbox-black);
+    /*opacity: .9;*/
+}
+
+#header > h1:nth-last-child(2) {
+    border-bottom: none !important;
+    background-color: rgba(0, 0, 0, .1);
+    padding-left: 2%;
+}
+
+.sect1 + .sect1 {
+  border-top: none !important;
+}
+
+.sect1 h2 {
+    background-size: cover;
+    background-attachment: fixed;
+    /*
+    border-bottom: 1px solid var(--gruvbox-white);
+    border-top: 1px solid var(--gruvbox-white);
+    */
+    padding: 10px;
+    text-align: left;
+    color: var(--gruvbox-red) !important;
+    /*
+    text-shadow: 1px 1px 1px var(--gruvbox-black);
+    text-shadow: 3px 3px 3px var(--gruvbox-black);
+    text-shadow: 5px 5px 5px var(--gruvbox-blue);
+    */
+}
+
+h3, h4, h5 {
+    /*
+    text-shadow: 1px 1px 1px var(--gruvbox-white);
+    */
+}
+
+h3 {
+  color: var(--gruvbox-red) !important;
+}
+
+h4 {
+  color: var(--gruvbox-yellow) !important;
+}
+
+h5 {
+  color: #83a598 !important;
+}
+
+h2 ~ *:not(h1) {
+  margin-left: .5em;
+}
+
+.pygments {
+    max-width: 97% !important;
+    white-space: pre;
+    overflow-x: scroll !important;
+}
+
+::before {
+    /*makes sure that the programming language that show up on hover is within
+      pre tag*/
+    padding-right: 3%;
+}
+
+@media screen and (max-width: 800px) {
+    .checklist li ul {
+        margin-left: 6% !important;
+    }
+    #toc {
+        padding-left: 10px;
+    }
+
+    footer {
+        margin-bottom: 10px !important;
+    }
+    html {
+        margin-bottom: 10px !important;
+    }
+}
+@media screen and (max-width: 535px) {
+    h3 ~ *:not(h2):not(h3) {
+      margin-left: 0 !important;
+    }
+
+    h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+      margin-left: 0 !important;
+    }
+    h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+      margin-left: 0 !important;
+    }
+
+    footer {
+        margin-bottom: 10px !important;
+    }
+    html {
+        margin-bottom: 10px !important;
+    }
+}
+h3 ~ *:not(h2):not(h3) {
+  margin-left: 1.5em;
+}
+
+h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+  margin-left: 2em;
+}
+h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+  margin-left: 2.5em;
+}
+
+
+.sect1 h2 {
+
+    /*
+    background: linear-gradient(
+        to bottom,
+        rgba(251, 189, 47, 0.2), /* yellow */
+        rgba(0, 0, 0, 0.4)       /* black */
+    );
+    */
+    /*opacity: .9;*/
+  }
+
+#preamble p{
+  padding-left: 10px;
+  color: var(--gruvbox-white) !important;
+}
+
+#footer {
+    max-width: 62.5em !important;
+    padding-left: 10px !important;
+    text-align: center;
+}
+
+.logo img {
+    display: block !important;
+    margin-left: auto !important;
+    margin-right: auto !important;
+}
+
+#header > h1:first-child {
+  margin-top: .3em !important;
+  padding-top: .2em !important;
+  text-align: center;
+}
+
+footer {
+    max-width: 62.5em;
+    margin-left: auto;
+    margin-right: auto;
+    display: block;
+
+}
+
+details[open] > summary::before {
+   /* display: none;*/
+    border-top: none;
+    content: "▼";
+    margin-top: -12px;
+}
+
+.example.stemblock {
+    border: solid;
+    font-size: 2vw;
+    max-width: 40%;
+    margin: auto;
+}
+
+.center {
+    max-width: 500px;
+    display: flex;
+    justify-content: center;
+}
+
+.highlight {
+    color: var(--gruvbox-yellow)
+}
+
+.border {
+    padding: 4px;
+    border: 2px solid rgba(255, 0, 0, .3);
+}
+
+.dull {
+    opacity: 40%;
+}
+
+.inline {
+    margin-right: 5%;
+    display: inline-block;
+}
+
+img {
+    margin: auto;
+    width: 100%;
+}
+
+
+</style>
+
+</head>
+
+<script>
+function sendHeight() {
+  const height = document.documentElement.scrollHeight;
+  parent.postMessage({ type: "HEIGHT", height }, "*");
+}
+
+window.addEventListener("load", sendHeight);
+window.addEventListener("resize", sendHeight);
+</script>
+
+
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_galaga">Galaga</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Recreation of Galaga with SDL2 library in C</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_building">Building</h2>
+<div class="sectionbody">
+<div class="listingblock">
+<div class="content">
+<pre>make</pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_running_tests">Running tests</h2>
+<div class="sectionbody">
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="sh"><span></span>./test<span class="tok-w"> </span>&lt;<span class="tok-nb">test</span><span class="tok-w"> </span>name&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>example:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="sh"><span></span><span class="tok-w"> </span>./tests<span class="tok-w"> </span><span class="tok-s2">&quot;pickFormationPosition&quot;</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<script type="text/x-mathjax-config">
+MathJax.Hub.Config({
+  messageStyle: "none",
+  tex2jax: {
+    inlineMath: [["\\(", "\\)"]],
+    displayMath: [["\\[", "\\]"]],
+    ignoreClass: "nostem|nolatexmath"
+  },
+  asciimath2jax: {
+    delimiters: [["\\$", "\\$"]],
+    ignoreClass: "nostem|noasciimath"
+  },
+  TeX: { equationNumbers: { autoNumber: "none" } }
+})
+MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
+  MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
+    if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
+      data.math.root.display = "block"
+    }
+    return data
+  })
+})
+</script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests.c b/tests.c
new file mode 100644 (file)
index 0000000..2aa76e1
--- /dev/null
+++ b/tests.c
@@ -0,0 +1,69 @@
+/* 
+ * Copyright (C) 2024  Austin Larsen
+ * 
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "game.h"
+
+int main(int argc, char **argv)
+{
+
+    if (argc > 1) {
+        if (strcmp(argv[1], "pickFormationPosition") == 0) {
+            Grid grid;
+            srand(time(NULL));
+
+            uint16_t size = 20;
+
+            uint8_t i = 0;
+            uint8_t *picked = malloc(sizeof(uint8_t) * size);
+            bool pass = true;
+
+            while(i < size) {
+                printf("%d: ", i);
+                int a = -1;
+                a = pickFormationPosition(ENEMY_BEE, grid.formation);
+
+                printf("%d\n", a);
+
+                if (picked[a] == 1) {
+                    pass = false;
+                    break;
+                }
+
+                picked[a] = 1;
+
+                i++;
+            }
+
+            if (pass) {
+                printf("SUCCESS\n");
+                return 0;
+
+            } else {
+                printf("FAIL\n");
+                return 1;
+            }
+
+            free(picked);
+        } else {
+            fprintf(stderr, "not an argument");
+            return 1 ;
+        }
+
+    }
+
+    return 0;
+}
diff --git a/todo.adoc b/todo.adoc
new file mode 100644 (file)
index 0000000..0ca36c8
--- /dev/null
+++ b/todo.adoc
@@ -0,0 +1,3 @@
+== TODO
+
+- [ ] make sure left and right enemy do not choose the same spot