├── .gitignore ├── images ├── loop.gif ├── intro.gif ├── screen1.gif ├── screen2.gif └── screen3.gif ├── Makefile ├── TODO ├── README.md ├── invaders.h ├── LICENSE └── invaders.c /.gitignore: -------------------------------------------------------------------------------- 1 | ascii_invaders 2 | -------------------------------------------------------------------------------- /images/loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macdice/ascii-invaders/HEAD/images/loop.gif -------------------------------------------------------------------------------- /images/intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macdice/ascii-invaders/HEAD/images/intro.gif -------------------------------------------------------------------------------- /images/screen1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macdice/ascii-invaders/HEAD/images/screen1.gif -------------------------------------------------------------------------------- /images/screen2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macdice/ascii-invaders/HEAD/images/screen2.gif -------------------------------------------------------------------------------- /images/screen3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macdice/ascii-invaders/HEAD/images/screen3.gif -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #CC=gcc 2 | CFLAGS+=-Wall 3 | LIBS=-lncursesw 4 | 5 | all: ascii_invaders 6 | 7 | clean: 8 | rm -f invaders.o ascii_invaders 9 | 10 | ascii_invaders: invaders.o 11 | $(CC) $(CFLAGS) $(LDFLAGS) invaders.o $(LIBS) -o ascii_invaders 12 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | - shoot all three rows of a shield before getting a bullet through 4 | - make alien explosions not last so long 5 | - improve the code that makes the aliens speed up 6 | - improve the code that makes aliens randomly drops bombs 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ascii-invaders 2 | ============== 3 | 4 | An ASCII-art game like Space Invaders using Curses. 5 | 6 | (c) 2001, 2013 Thomas Munro 7 | 8 | This is short bit of C code I wrote back in 2001 and published on my 9 | erstwhile home page: 10 | 11 | http://web.archive.org/web/20100417061809/http://www.ip9.org/munro/invaders/ 12 | 13 | Looking at it more than a decade later, there are two unforgivable flaws! 14 | 15 | 1. It does illegal things inside a signal handler! 16 | 2. It uses global variables in order to do so. 17 | 18 | I really must get around to fixing this. It should probably use select 19 | for timing instead signals. 20 | 21 | I am embarassed to admit that I did not do anything with several patches 22 | that people have sent me by email over the years in the pre-Github era 23 | to make it work on various platforms. Sorry! 24 | 25 | artwork 26 | ======= 27 | 28 | Original vs ASCII: 29 | 30 | ![](images/loop.gif) 31 | 32 | Screen shots (I think this must have been 2001-era Gnome Terminal!): 33 | 34 | ![](images/intro.gif) 35 | 36 | ![](images/screen1.gif) 37 | 38 | ![](images/screen2.gif) 39 | 40 | ![](images/screen3.gif) 41 | 42 | -------------------------------------------------------------------------------- /invaders.h: -------------------------------------------------------------------------------- 1 | /** 2 | * ascii invaders - A curses clone of the classic video game Space Invaders 3 | * (c) 2001 Thomas Munro 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | * 19 | * https://github.com/macdice/ascii-invaders 20 | * Thomas Munro 21 | * 22 | * $Id: invaders.h,v 1.3 2002/07/21 21:52:14 munro Exp $ 23 | */ 24 | 25 | struct Bomb { 26 | int x; 27 | int y; 28 | int anim; 29 | struct Bomb *next; 30 | }; 31 | 32 | typedef struct Sprite { 33 | const char *lines[4]; 34 | } Sprite; 35 | 36 | /* To make this compile under Darwin/BSD I have to comment these, until 37 | I figure out how to fix this problem (may involve not using apple's curses 38 | implementation). */ 39 | #define USE_COLORS 1 40 | #define USE_KEYS 1 41 | 42 | //#define BULLET_PROOF 1 // debug 43 | 44 | #define BOMB_ANIM_SIZE 4 // "frames" in bomb anim 45 | 46 | #define FPS 15 // frames per second 47 | #define PAINT_WAIT 2 // how many frames between row repaints 48 | 49 | #define ASCII 0 50 | #define UNICODE 1 51 | 52 | /* Sprites. */ 53 | #define SHELTER 8 54 | #define ALIEN_EXPLODE 7 55 | #define GUNNER_EXPLODE 6 56 | #define GUNNER 5 57 | #define MA 4 58 | #define ALIEN30 3 59 | #define ALIEN20 2 60 | #define ALIEN10 1 61 | 62 | /* Special alien table values. */ 63 | #define ALIEN_EMPTY 0 64 | #define ALIEN_EXPLODE1 -1 65 | #define ALIEN_EXPLODE2 -2 66 | 67 | #define ALIEN_WIDTH 6 68 | #define ALIEN_HEIGHT 3 69 | #define GUNNER_WIDTH 7 70 | #define GUNNER_HEIGHT 2 71 | #define SHELTER_WIDTH 7 72 | #define SHELTER_HEIGHT 3 73 | #define MA_HEIGHT 2 74 | #define MA_WIDTH 6 75 | 76 | #define GUNNER_ENTRANCE 40 // how many frames before gunner appears 77 | #define MA_ENTRANCE 400 // how many frames before MA comes on the screen 78 | 79 | #define STATE_INTRO 1 80 | #define STATE_PLAY 2 81 | #define STATE_EXPLODE 3 82 | #define STATE_WAIT 4 83 | #define STATE_GAMEOVER 5 84 | 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | An ASCII-art game like Space Invaders 294 | Copyright (C) 2013 Thomas Munro 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /invaders.c: -------------------------------------------------------------------------------- 1 | /** 2 | * ascii invaders - A curses clone of the classic video game Space Invaders 3 | * (c) 2001 Thomas Munro 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | * 19 | * https://github.com/macdice/ascii-invaders 20 | * Thomas Munro 21 | * 22 | * $Id: invaders.c,v 1.20 2002/07/21 21:52:13 munro Exp $ 23 | */ 24 | 25 | #include "invaders.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | static const Sprite sprites[][2][2] = { 37 | [ALIEN30] = { 38 | [ASCII] = { 39 | { 40 | { 41 | " {@@} ", 42 | " /\"\"\\ ", 43 | " " 44 | } 45 | }, 46 | { 47 | { 48 | " {@@} ", 49 | " \\/ ", 50 | " " 51 | } 52 | } 53 | }, 54 | [UNICODE] = { 55 | { 56 | { 57 | "⢀⡴⣿⢦⡀ ", 58 | "⢈⢝⠭⡫⡁ ", 59 | " " 60 | } 61 | }, 62 | { 63 | { 64 | "⢀⡴⣿⢦⡀ ", 65 | "⠨⡋⠛⢙⠅ ", 66 | " " 67 | } 68 | } 69 | } 70 | }, 71 | [ALIEN20] = { 72 | [ASCII] = { 73 | { 74 | { 75 | " dOOb ", 76 | " ^/\\^ ", 77 | " " 78 | } 79 | }, 80 | { 81 | { 82 | " dOOb ", 83 | " ~||~ ", 84 | " " 85 | } 86 | } 87 | }, 88 | [UNICODE] = { 89 | { 90 | { 91 | "⢀⡵⣤⡴⣅ ", 92 | "⠏⢟⡛⣛⠏⠇", 93 | " " 94 | } 95 | }, 96 | { 97 | { 98 | "⣆⡵⣤⡴⣅⡆", 99 | "⢘⠟⠛⠛⢟⠀", 100 | " " 101 | } 102 | } 103 | }, 104 | }, 105 | [ALIEN10] = { 106 | [ASCII] = { 107 | { 108 | { 109 | " /MM\\ ", 110 | " |~~| ", 111 | " " 112 | } 113 | }, 114 | { 115 | { 116 | " /MM\\ ", 117 | " \\~~/ ", 118 | " " 119 | } 120 | } 121 | }, 122 | [UNICODE] = { 123 | { 124 | { 125 | "⣴⡶⢿⡿⢶⣦", 126 | "⠩⣟⠫⠝⣻⠍", 127 | " " 128 | } 129 | }, 130 | { 131 | { 132 | "⣴⡶⢿⡿⢶⣦", 133 | "⣉⠽⠫⠝⠯⣉", 134 | " " 135 | } 136 | } 137 | } 138 | }, 139 | [MA] = { 140 | [ASCII] = { 141 | { 142 | { 143 | "_/MMM\\_", 144 | "qWAVAWp" 145 | } 146 | } 147 | }, 148 | [UNICODE] = { 149 | { 150 | { 151 | "⢀⡴⣾⢿⡿⣷⢦⡀", 152 | "⠉⠻⠋⠙⠋⠙⠟⠉" 153 | } 154 | } 155 | } 156 | }, 157 | [GUNNER] = { 158 | [ASCII] = { 159 | { 160 | { 161 | " mAm ", 162 | " MAZAM " 163 | } 164 | } 165 | }, 166 | [UNICODE] = { 167 | { 168 | { 169 | " ⢀⣀⣾⣷⣀⡀ ", 170 | " ⣿⣿⣿⣿⣿⣿ " 171 | } 172 | } 173 | } 174 | }, 175 | [GUNNER_EXPLODE] = { 176 | [ASCII] = { 177 | { 178 | { 179 | " ,' % ", 180 | " ;&+,! " 181 | } 182 | }, 183 | { 184 | { 185 | " -,+$! ", 186 | " + ^~ " 187 | } 188 | } 189 | }, 190 | [UNICODE] = { 191 | /* TODO */ 192 | { 193 | { 194 | " ,' % ", 195 | " ;&+,! " 196 | } 197 | }, 198 | { 199 | { 200 | " -,+$! ", 201 | " + ^~ " 202 | } 203 | } 204 | } 205 | }, 206 | [ALIEN_EXPLODE] = { 207 | [ASCII] = { 208 | { 209 | { 210 | " \\||/ ", 211 | " /||\\ ", 212 | " " 213 | } 214 | } 215 | }, 216 | [UNICODE] = { 217 | /* TODO */ 218 | { 219 | { 220 | " \\||/ ", 221 | " /||\\ ", 222 | " " 223 | } 224 | } 225 | } 226 | }, 227 | [SHELTER] = { 228 | [ASCII] = { 229 | { 230 | { 231 | "/MMMMM\\", 232 | "MMMMMMM", 233 | "MMM MMM" 234 | } 235 | } 236 | }, 237 | [UNICODE] = { 238 | /* TODO */ 239 | { 240 | { 241 | "/MMMMM\\", 242 | "MMMMMMM", 243 | "MMM MMM" 244 | } 245 | } 246 | } 247 | } 248 | }; 249 | 250 | const char *alienBlank = " "; 251 | 252 | const char *bombAnim = "\\|/-"; 253 | 254 | static int ctype = ASCII; 255 | 256 | // We have to use global variables becase our main loop is driven by 257 | // an alarm signal - so we put them into tidy structures. 258 | // TODO! Define the structs, but declare the game state objects on 259 | // the stack of main. Down with globalisation! 260 | 261 | struct { 262 | int score; 263 | int lives; 264 | int state; 265 | int screenCols; // screen columns 266 | int screenRows; // screen rows 267 | int timer; // timer used to switch between game states 268 | struct itimerval myTimer; // alarm signal timer 269 | } game; 270 | 271 | struct { 272 | int rows, cols; // how many rows and columns of aliens are there? 273 | int x, y; // alien position 274 | int *table; // the table of aliens 275 | // which rows and columns have been cleared? 276 | int emptyLeft, emptyRight, emptyTop, emptyBottom; 277 | int direction; 278 | int paintRow; // cursor for repainting row by row 279 | int paintWait; // counter for repainting row by row 280 | int sideAnim; // staggered sideways movement 281 | int count; // how many aliens are left? 282 | int anim; // used for wiggling 283 | struct Bomb *headBomb; // linked list of bombs 284 | } aliens; 285 | 286 | struct { 287 | int x; // x position 288 | int move; // buffered moves 289 | int explodeTimer; // timer used when gunner explodes 290 | struct { 291 | int x; // x position 292 | int y; // y position 293 | } missile; 294 | char *shields; // pointer to shield table 295 | } gun; 296 | 297 | struct { 298 | int x; // x position 299 | int pointsTimer; // how long to leave the points on the screen 300 | } ma; 301 | 302 | 303 | void paintShelters(); 304 | void resetAliens(); 305 | void resetShields(); 306 | void initGame(); 307 | void cleanUp(int signal); 308 | void handleTimer(int signal); 309 | void paintAlienRow(int row, int clean); 310 | void paintGunner(); 311 | void paintIntro(); 312 | void paintExplodingAlien(int y, int x); 313 | void removeAlien(int y, int x); 314 | void paintScore(); 315 | void trimAliens(); 316 | void addBomb(int x, int y); 317 | int removeBomb(struct Bomb *b); 318 | void freeBombs(); 319 | void moveAliensDown(); 320 | 321 | int main(int argc, char **argv) { 322 | 323 | if (argc > 1 && strcmp(argv[1], "--unicode") == 0) { 324 | setlocale(LC_CTYPE, ""); 325 | ctype = UNICODE; 326 | } 327 | 328 | // set up curses library 329 | initscr(); 330 | cbreak(); 331 | noecho(); 332 | #ifdef USE_COLORS 333 | curs_set(0); // hide cursor 334 | #endif 335 | initscr(); 336 | cbreak(); 337 | noecho(); 338 | #ifdef USE_KEYS 339 | keypad(stdscr, TRUE); 340 | #endif 341 | #ifdef USE_COLORS 342 | if (has_colors()) { 343 | start_color(); 344 | init_pair(0, COLOR_BLACK, COLOR_BLACK); 345 | init_pair(1, COLOR_GREEN, COLOR_BLACK); 346 | init_pair(2, COLOR_RED, COLOR_BLACK); 347 | init_pair(3, COLOR_CYAN, COLOR_BLACK); 348 | init_pair(4, COLOR_WHITE, COLOR_BLACK); 349 | init_pair(5, COLOR_MAGENTA, COLOR_BLACK); 350 | init_pair(6, COLOR_BLUE, COLOR_BLACK); 351 | init_pair(7, COLOR_YELLOW, COLOR_BLACK); 352 | } 353 | #endif 354 | game.screenCols = COLS; 355 | 356 | 357 | initGame(); 358 | paintIntro(); 359 | game.state = STATE_INTRO; 360 | 361 | // TODO! 362 | // Instead of registering handleTime here, we should use select 363 | // to wait for a key with a timeout, and run handleTimer when 364 | // enough time for one frame has elapsed. 365 | 366 | // set up realtime interrupt timer and signals 367 | game.myTimer.it_value.tv_sec = 0; 368 | game.myTimer.it_value.tv_usec = 1000000 / FPS; 369 | game.myTimer.it_interval.tv_sec = 0; 370 | game.myTimer.it_interval.tv_usec = 1000000 / FPS; 371 | setitimer(ITIMER_REAL, &game.myTimer, NULL); 372 | signal(SIGALRM, handleTimer); 373 | 374 | for (;;) { 375 | switch(getch()) { 376 | case 'q': 377 | cleanUp(0); 378 | break; 379 | case 'j': 380 | #ifdef USE_KEYS 381 | case KEY_LEFT: 382 | #endif 383 | gun.move = -2; 384 | break; 385 | case 'k': 386 | #ifdef USE_KEYS 387 | case KEY_RIGHT: 388 | #endif 389 | gun.move = 2; 390 | break; 391 | case ' ': 392 | if (game.state == STATE_INTRO) { 393 | game.lives = 3; 394 | game.score = 0; 395 | game.state = STATE_PLAY; 396 | resetShields(); 397 | 398 | resetAliens(); 399 | game.timer = 0; 400 | clear(); 401 | paintShelters(); 402 | } else if (game.state == STATE_PLAY 403 | && game.timer > GUNNER_ENTRANCE 404 | && gun.missile.y == 0) { 405 | gun.missile.x = gun.x; 406 | gun.missile.y = LINES - GUNNER_HEIGHT - 1; 407 | } 408 | break; 409 | } 410 | } 411 | 412 | return 0; // not reached 413 | } 414 | 415 | void paintShelters() { 416 | int n, y, x; 417 | 418 | #ifdef USE_COLORS 419 | if (has_colors()) attron(COLOR_PAIR(1)); 420 | #endif 421 | n = 0; 422 | for (y = 0; y < SHELTER_HEIGHT; y++) { 423 | move(LINES - 1 - SHELTER_HEIGHT - GUNNER_HEIGHT + y, 0); 424 | for (x = 0; x < game.screenCols; x++) { 425 | addch(gun.shields[n++]); 426 | } 427 | } 428 | refresh(); 429 | } 430 | 431 | /** 432 | * Handle timer signal. This is the logic that moves all the sprites 433 | * around the screen. 434 | */ 435 | void handleTimer(int signal) { 436 | // TODO! This should take a pointer to the game state, which points 437 | // to object on the stack of main, instead of accessing evil 438 | // global variables. 439 | int x; 440 | struct Bomb *b; 441 | 442 | // check which state the game is in 443 | if (game.state == STATE_INTRO) { 444 | // intro anim 445 | return; 446 | } else if (game.state == STATE_WAIT) { 447 | if (game.timer++ == 10) { 448 | game.state = STATE_PLAY; 449 | paintScore(); 450 | } 451 | return; 452 | } else if (game.state == STATE_GAMEOVER) { 453 | if (game.timer++ == 40) { 454 | game.state = STATE_INTRO; 455 | paintIntro(); 456 | refresh(); 457 | } 458 | // game over anim 459 | return; 460 | } else if (game.state == STATE_EXPLODE) { 461 | // explode anim 462 | if (gun.explodeTimer++ % 4 == 0) { 463 | paintGunner(); 464 | } 465 | if (gun.explodeTimer == 40) { 466 | if (game.lives-- == 0) { 467 | // game over 468 | game.state = STATE_GAMEOVER; 469 | game.timer = 0; 470 | mvprintw(LINES / 2, (COLS / 2) - 5, "GAME OVER"); 471 | refresh(); 472 | return; 473 | } else { 474 | // start next life 475 | // if the aliens have reached the shields 476 | // then move them back up to the top of the screen 477 | int lastLine = aliens.y + (ALIEN_HEIGHT * aliens.emptyBottom) - 1; 478 | int shieldTop = LINES - GUNNER_HEIGHT - SHELTER_HEIGHT - 1; 479 | if (lastLine >= shieldTop) aliens.y = 0; 480 | 481 | game.timer = 0; 482 | game.state = STATE_WAIT; 483 | ma.x = 0; // just in case she was there at the time 484 | freeBombs(); // get rid of any bombs 485 | clear(); 486 | paintShelters(); 487 | refresh(); 488 | aliens.paintRow = aliens.rows; 489 | return; 490 | } 491 | } 492 | } 493 | 494 | // otherwise handle game play... 495 | game.timer++; 496 | 497 | if (game.timer == GUNNER_ENTRANCE && game.state == STATE_PLAY) { 498 | paintGunner(); 499 | } 500 | 501 | // decide if it's time to send on ma 502 | if (game.timer % MA_ENTRANCE == MA_ENTRANCE - 1 && aliens.y > 5) { 503 | ma.x = COLS - ALIEN_WIDTH - 1; 504 | } 505 | 506 | // if ma is currently on, display 507 | if (ma.x > 0) { 508 | int i; 509 | 510 | #ifdef USE_COLORS 511 | if (has_colors()) attron(COLOR_PAIR(2)); 512 | #endif 513 | if (ma.pointsTimer != 0) { 514 | // ma has been shot and is now just showing points 515 | if (ma.pointsTimer++ == 20) { 516 | mvprintw(2, ma.x, "%s", alienBlank); 517 | ma.pointsTimer = 0; 518 | ma.x = 0; 519 | } 520 | } else { 521 | // ma is grooving across the top of the screen 522 | ma.x--; 523 | for (i = 0; i < MA_HEIGHT; i++) 524 | mvprintw(2 + i, ma.x, "%s ", sprites[MA][ctype][0].lines[i]); 525 | refresh(); 526 | // if we have reach the edge then remove ma 527 | if (ma.x == 0) { 528 | for (i = 0; i < MA_HEIGHT; i++) 529 | mvprintw(2 + i, ma.x, "%s ", alienBlank); 530 | } 531 | } 532 | } 533 | 534 | // drop bombs 535 | if (game.timer > GUNNER_ENTRANCE) { 536 | for (x = aliens.emptyLeft; x < aliens.emptyRight; x++) { 537 | int y; 538 | // find the first alien from the bottom 539 | for (y = aliens.emptyBottom - 1; y >= 0; y--) { 540 | if (aliens.table[(aliens.cols * y) + x] > ALIEN_EMPTY) { 541 | if (1 == (int) (50.0 * rand() / (RAND_MAX + 1.0))) { 542 | addBomb(aliens.x + (x * ALIEN_WIDTH) + (ALIEN_WIDTH / 2), 543 | aliens.y + (y * ALIEN_HEIGHT) + ALIEN_HEIGHT - 1); 544 | } 545 | break; 546 | } 547 | } 548 | } 549 | } 550 | 551 | // handle gunner movements 552 | if (game.state == STATE_PLAY && game.timer > GUNNER_ENTRANCE) { 553 | if (gun.move < 0 && gun.x > GUNNER_WIDTH / 2) { 554 | gun.move++; 555 | gun.x--; 556 | paintGunner(); 557 | refresh(); 558 | } 559 | if (gun.move > 0 && gun.x < COLS - (GUNNER_WIDTH / 2)) { 560 | gun.move--; 561 | gun.x++; 562 | paintGunner(); 563 | refresh(); 564 | } 565 | } 566 | 567 | // handle alien movements 568 | if (--aliens.paintWait <= 0) { 569 | // time to repaint one row of aliens (speeds up as you shoot aliens) 570 | aliens.paintWait = (int) ((double) PAINT_WAIT * ((double) aliens.count 571 | / (double) (aliens.cols * aliens.rows)));; 572 | aliens.paintRow--; 573 | paintAlienRow(aliens.paintRow, 0); 574 | refresh(); 575 | if (aliens.paintRow <= aliens.emptyTop) { 576 | // time to move the block of aliens 577 | aliens.paintRow = aliens.emptyBottom; // reset counter 578 | aliens.anim = (aliens.anim ? 0 : 1); // wiggle 579 | if (aliens.direction == -1) { 580 | if (--aliens.x + (aliens.emptyLeft * ALIEN_WIDTH) == 0) { 581 | // change direction, clear top line, shuffle down 582 | aliens.direction = 1; 583 | move(aliens.y, 0); 584 | clrtoeol(); 585 | moveAliensDown(); 586 | } 587 | } else if (aliens.direction == 1) { 588 | if (++aliens.x + (aliens.emptyRight * ALIEN_WIDTH) == COLS) { 589 | // change direction, clear top line, shuffle down 590 | aliens.direction = -1; 591 | move(aliens.y, 0); 592 | clrtoeol(); 593 | moveAliensDown(); 594 | } 595 | } 596 | paintScore(); 597 | /* 598 | // see if the aliens have hit the bottom 599 | if (game.state == STATE_PLAY 600 | && aliens.y + (ALIEN_HEIGHT * 601 | (aliens.rows - aliens.emptyBottom)) 602 | > LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT) { 603 | game.state = STATE_EXPLODE; 604 | gun.explodeTimer = 0; 605 | } 606 | */ 607 | } 608 | } 609 | 610 | #ifdef USE_COLORS 611 | // use white for missiles and bombs 612 | if (has_colors()) attron(COLOR_PAIR(4)); 613 | #endif 614 | 615 | // handle bomb movements 616 | for (b = aliens.headBomb; b != NULL; ) { 617 | struct Bomb *next = b->next; 618 | move(b->y, b->x); 619 | addch(' '); 620 | if (++(b->y) < LINES) { 621 | if (gun.missile.y != 0 622 | && abs(b->x - gun.missile.x) < 2 623 | && abs(b->y - gun.missile.y) < 2) { 624 | // collision with missile 625 | removeBomb(b); 626 | move(gun.missile.y, gun.missile.x); 627 | addch(' '); 628 | gun.missile.y = 0; 629 | } else if (game.state == STATE_PLAY 630 | && game.timer > GUNNER_ENTRANCE 631 | && b->y >= LINES - GUNNER_HEIGHT 632 | && b->x > (gun.x - (GUNNER_WIDTH / 2)) 633 | && b->x < (gun.x + (GUNNER_WIDTH / 2))) { 634 | // collision with gunner 635 | removeBomb(b); 636 | #ifndef BULLET_PROOF 637 | game.state = STATE_EXPLODE; 638 | gun.explodeTimer = 0; 639 | #endif 640 | } else if (b->y < LINES - 1 - GUNNER_HEIGHT 641 | && b->y >= LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT 642 | && gun.shields[((b->y - (LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT)) 643 | * game.screenCols) + b->x] != ' ') { 644 | // collision with shield 645 | gun.shields[((b->y - (LINES - 1 - GUNNER_HEIGHT 646 | - SHELTER_HEIGHT)) 647 | * game.screenCols) + b->x] = ' '; 648 | mvaddch(b->y, b->x, ' '); 649 | removeBomb(b); 650 | } else { 651 | // advance bomb 652 | move(b->y, b->x); 653 | addch(bombAnim[b->anim++]); 654 | if (b->anim == BOMB_ANIM_SIZE) { 655 | b->anim = 0; 656 | } 657 | } 658 | } else { 659 | removeBomb(b); 660 | } 661 | 662 | b = next; 663 | refresh(); 664 | } 665 | 666 | // handle missile movements 667 | if (gun.missile.y != 0) { 668 | move(gun.missile.y, gun.missile.x); 669 | addch(' '); 670 | if ((gun.missile.y -= 2) > 0) { 671 | move(gun.missile.y, gun.missile.x); 672 | addch('!'); 673 | } else { 674 | gun.missile.y = 0; 675 | } 676 | 677 | // test for collision with shield 678 | if (gun.missile.y < LINES - 1 - GUNNER_HEIGHT 679 | && gun.missile.y >= LINES - 1 - GUNNER_HEIGHT 680 | - SHELTER_HEIGHT) { 681 | if (gun.shields[((gun.missile.y - (LINES - 1 - GUNNER_HEIGHT 682 | - SHELTER_HEIGHT)) 683 | * game.screenCols) + gun.missile.x] != ' ') { 684 | gun.shields[((gun.missile.y - (LINES - 1 - GUNNER_HEIGHT 685 | - SHELTER_HEIGHT)) 686 | * game.screenCols) + gun.missile.x] = ' '; 687 | 688 | mvaddch(gun.missile.y, gun.missile.x, ' '); 689 | gun.missile.y = 0; 690 | } 691 | } 692 | 693 | // test for collision with aliens 694 | else if (gun.missile.x >= aliens.x && gun.missile.x < aliens.x 695 | + (ALIEN_WIDTH * aliens.cols) 696 | && gun.missile.y < aliens.y + ALIEN_HEIGHT * aliens.rows && 697 | gun.missile.y >= aliens.y) { 698 | int alien; 699 | int x = gun.missile.x - aliens.x; 700 | int y = gun.missile.y - aliens.y; 701 | if (x % ALIEN_WIDTH != 0 && x % ALIEN_WIDTH != ALIEN_WIDTH -1) { 702 | // it didn't sneak between two aliens 703 | x /= ALIEN_WIDTH; 704 | y /= ALIEN_HEIGHT; 705 | alien = aliens.table[(y * aliens.cols) + x]; 706 | if (alien > ALIEN_EMPTY) { 707 | game.score += alien * 10; 708 | paintExplodingAlien(y, x); 709 | paintScore(); 710 | gun.missile.y = 0; // no more missile 711 | aliens.table[(y * aliens.cols) + x] = ALIEN_EXPLODE1; 712 | if (--aliens.count == 0) { 713 | resetAliens(); 714 | game.timer = 0; 715 | clear(); 716 | paintShelters(); 717 | } 718 | refresh(); 719 | trimAliens(); 720 | } 721 | } 722 | } 723 | 724 | // test for collection with ma 725 | else if (ma.x != 0 726 | && gun.missile.y <= 2 + MA_HEIGHT 727 | && gun.missile.x >= ma.x 728 | && gun.missile.x <= ma.x + ALIEN_WIDTH) { 729 | // chose a 'random' number of points, either 50, 100 or 150 730 | int points = ((time(0) % 3) + 1) * 50; 731 | int i; 732 | game.score += points; 733 | // remove ma 734 | for (i = 0; i < MA_HEIGHT; i++) 735 | mvprintw(2 + i, ma.x, "%s ", alienBlank); 736 | // draw the number of points 737 | mvprintw(2, ma.x, " %d", points); 738 | ma.pointsTimer = 1; 739 | paintScore(); 740 | } 741 | 742 | refresh(); 743 | } 744 | } 745 | 746 | /** 747 | * Move aliens down one row. 748 | */ 749 | void moveAliensDown() { 750 | // figure out which screen row the bottom alien is one 751 | // and if it's over the sheilds then clear the line of 752 | // the shields or at the bottom of the screen 753 | int lastLine = ++aliens.y + (ALIEN_HEIGHT * aliens.emptyBottom) - 1; 754 | int topShield = LINES - GUNNER_HEIGHT - SHELTER_HEIGHT - 1; 755 | int gunnerTop = LINES - GUNNER_HEIGHT - 1; 756 | 757 | if (lastLine >= topShield && lastLine < topShield + SHELTER_HEIGHT) { 758 | // clear the shield line 759 | int i = (lastLine - topShield) * game.screenCols; 760 | int j; 761 | for (j = 0; j < game.screenCols; j++) { 762 | gun.shields[i + j] = ' '; // DEBUG 763 | } 764 | paintShelters(); 765 | } 766 | if (lastLine == gunnerTop) { 767 | // blow up gunner if not already blowing up 768 | if (game.state != STATE_EXPLODE) { 769 | game.state = STATE_EXPLODE; 770 | gun.explodeTimer = 0; 771 | } 772 | } 773 | } 774 | 775 | /** 776 | * Adjusts the size of the block of aliens for left and right (the bottom 777 | * adjustment is made by the alien drawing code). 778 | */ 779 | void trimAliens() { 780 | // update empty line pointers 781 | int found = 0; 782 | for (;;) { 783 | int i; 784 | for (i = 0; i < aliens.rows; i++) { 785 | if (aliens.table[(i * aliens.cols) + aliens.emptyLeft] 786 | != ALIEN_EMPTY) { 787 | found = 1; 788 | break; 789 | } 790 | } 791 | if (found) break; 792 | else aliens.emptyLeft++; 793 | } 794 | 795 | found = 0; 796 | for (;;) { 797 | int i; 798 | for (i = 0; i < aliens.rows; i++) { 799 | if (aliens.table[(i * aliens.cols) + aliens.emptyRight - 1] 800 | != ALIEN_EMPTY) { 801 | found = 1; 802 | break; 803 | } 804 | } 805 | if (found) break; 806 | else aliens.emptyRight--; 807 | } 808 | 809 | found = 0; 810 | for (;;) { 811 | int i; 812 | for (i = 0; i < aliens.cols; i++) { 813 | if (aliens.table[((aliens.emptyBottom - 1) * aliens.cols) + i] 814 | != ALIEN_EMPTY) { 815 | found = 1; 816 | break; 817 | } 818 | } 819 | if (found) break; 820 | else aliens.emptyBottom--; 821 | } 822 | } 823 | 824 | /** 825 | * Ends and tidies up. 826 | */ 827 | void cleanUp(int signal) { 828 | if (aliens.table != NULL) free(aliens.table); 829 | if (gun.shields != NULL) free(gun.shields); 830 | endwin(); 831 | exit(EXIT_SUCCESS); 832 | } 833 | 834 | void paintGunner() { 835 | int i; 836 | #ifdef USE_COLORS 837 | if (has_colors()) attron(COLOR_PAIR(1)); 838 | #endif 839 | for (i = 0; i < GUNNER_HEIGHT; i++) { 840 | move(LINES - GUNNER_HEIGHT + i, gun.x - (GUNNER_WIDTH / 2)); 841 | if (game.state == STATE_PLAY) { 842 | printw("%s", sprites[GUNNER][ctype][0].lines[i]); 843 | } else if (game.state == STATE_EXPLODE) { 844 | printw("%s", sprites[GUNNER_EXPLODE][ctype][(game.timer / 4) % 2].lines[i]); 845 | } 846 | } 847 | } 848 | 849 | void paintExplodingAlien(int y, int x) { 850 | int i; 851 | for (i = 0; i < ALIEN_HEIGHT; i++) { 852 | move((y * ALIEN_HEIGHT) + aliens.y + i, (x * ALIEN_WIDTH) + aliens.x 853 | + (y > aliens.paintRow ? aliens.direction : 0)); 854 | // adjustment because of 855 | // alien drawing technique 856 | printw("%s", sprites[ALIEN_EXPLODE][ctype][0].lines[i]); 857 | } 858 | } 859 | 860 | void removeAlien(int y, int x) { 861 | int i; 862 | for (i = 0; i < ALIEN_HEIGHT; i++) { 863 | move((y * ALIEN_HEIGHT) + aliens.y + i, (x * ALIEN_WIDTH) + aliens.x); 864 | printw("%s", alienBlank); 865 | } 866 | } 867 | 868 | void paintScore() { 869 | #ifdef USE_COLORS 870 | if (has_colors()) attron(COLOR_PAIR(4)); 871 | #endif 872 | if (aliens.y > 0) { 873 | move(0, 0); 874 | printw("Ascii-Invaders Score: %d Lives remaining: %d", game.score, 875 | game.lives); 876 | } 877 | } 878 | 879 | /** 880 | * Paints a row of aliens (but doesn't call refresh). 881 | * @param row which row of aliens to draw 882 | * @param clean whether to paint the line above this row of aliens white 883 | */ 884 | void paintAlienRow(int row, int clean) { 885 | int x, i; 886 | 887 | if (clean) { 888 | move((row * ALIEN_HEIGHT) + aliens.y - 1, 0); 889 | deleteln(); 890 | } 891 | // draw the alien space ships 892 | #ifdef USE_COLORS 893 | if (has_colors()) attron(COLOR_PAIR(4)); 894 | #endif 895 | // this is a slight kludge - occasionally bits of explosion were left 896 | // behind when the aliens were moving and exploding at the same time, so: 897 | // if we are not right against the left or right of the screen, 898 | // we delete the column immediately before and after each line 899 | if (aliens.x > 0) { 900 | for (i = 0; i < ALIEN_HEIGHT; i++) { 901 | move((row * ALIEN_HEIGHT) + aliens.y + i, 902 | (aliens.emptyLeft * ALIEN_WIDTH) + aliens.x - 1); 903 | addch(' '); 904 | } 905 | } 906 | if (aliens.x + (ALIEN_WIDTH * aliens.emptyRight) < game.screenCols) { 907 | for (i = 0; i < ALIEN_HEIGHT; i++) { 908 | move((row * ALIEN_HEIGHT) + aliens.y + i, 909 | (aliens.emptyRight * ALIEN_WIDTH) + aliens.x); 910 | addch(' '); 911 | } 912 | } 913 | 914 | // draw the aliens 915 | for (x = aliens.emptyLeft; x < aliens.emptyRight; x++) { 916 | int alien = aliens.table[(row * aliens.cols) + x]; 917 | for (i = 0; i < ALIEN_HEIGHT; i++) { 918 | move((row * ALIEN_HEIGHT) + aliens.y + i, 919 | (x * ALIEN_WIDTH) + aliens.x); 920 | switch (alien) { 921 | case ALIEN10: 922 | case ALIEN20: 923 | case ALIEN30: 924 | printw("%s", sprites[alien][ctype][aliens.anim].lines[i]); 925 | break; 926 | case ALIEN_EXPLODE1: 927 | //printw("%s", alienExplode[i]); 928 | // do nothing, to leave the explosion on the screen 929 | break; 930 | case ALIEN_EXPLODE2: 931 | case ALIEN_EMPTY: 932 | // wipe out 933 | printw("%s", alienBlank); 934 | } 935 | } 936 | // if the alien is exploding then advance its explosion state 937 | if (alien == ALIEN_EXPLODE1) { 938 | aliens.table[(row * aliens.cols) + x] = ALIEN_EXPLODE2; 939 | } else if (alien == ALIEN_EXPLODE2) { 940 | aliens.table[(row * aliens.cols) + x] = ALIEN_EMPTY; 941 | trimAliens(); 942 | } 943 | } 944 | } 945 | 946 | void initGame() { 947 | 948 | // how many aliens? 949 | if (COLS < 80 || LINES < 20) { 950 | fprintf(stderr, "Terminal size too small to play Ascii Invaders.\n"); 951 | cleanUp(0); 952 | } 953 | aliens.cols = (COLS / ALIEN_WIDTH) - 4; 954 | aliens.rows = (LINES / ALIEN_HEIGHT) - 4; 955 | aliens.table = malloc(aliens.cols * aliens.rows * sizeof(int)); 956 | gun.shields = malloc(game.screenCols * SHELTER_HEIGHT); 957 | if (aliens.table == NULL || gun.shields == NULL) { 958 | fprintf(stderr, "Out of memory.\n"); 959 | cleanUp(0); 960 | } 961 | } 962 | 963 | void paintIntro() { 964 | clear(); 965 | 966 | #ifdef USE_COLORS 967 | if (has_colors()) attron(COLOR_PAIR(4)); 968 | #endif 969 | mvprintw(2, (COLS / 2) - 30, " _ _ _ _ "); 970 | mvprintw(3, (COLS / 2) - 30, " __ _ ___ ___(_|_) (_)_ ____ ____ _ __| | ___ _ __ ___ "); 971 | mvprintw(4, (COLS / 2) - 30, " / _` / __|/ __| | | | | '_ \\ \\ / / _` |/ _` |/ _ \\ '__/ __|"); 972 | mvprintw(5, (COLS / 2) - 30, "| (_| \\__ \\ (__| | | | | | | \\ V / (_| | (_| | __/ | \\__ \\"); 973 | mvprintw(6, (COLS / 2) - 30, " \\__,_|___/\\___|_|_| |_|_| |_|\\_/ \\__,_|\\__,_|\\___|_| |___/"); 974 | 975 | 976 | #ifdef USE_COLORS 977 | if (has_colors()) attron(COLOR_PAIR(2)); 978 | #endif 979 | mvprintw( 9, (COLS / 2) - 9, sprites[MA][ctype][0].lines[0]); 980 | mvprintw(10, (COLS / 2) - 9, sprites[MA][ctype][0].lines[1]); 981 | #ifdef USE_COLORS 982 | if (has_colors()) attron(COLOR_PAIR(4)); 983 | #endif 984 | mvprintw( 9, (COLS / 2), "= ? points"); 985 | 986 | mvprintw(12, (COLS / 2) - 8, sprites[ALIEN30][ctype][0].lines[0]); 987 | mvprintw(13, (COLS / 2) - 8, sprites[ALIEN30][ctype][0].lines[1]); 988 | mvprintw(12, (COLS / 2), "= 30 points"); 989 | 990 | mvprintw(15, (COLS / 2) - 8, sprites[ALIEN20][ctype][0].lines[0]); 991 | mvprintw(16, (COLS / 2) - 8, sprites[ALIEN20][ctype][0].lines[1]); 992 | mvprintw(15, (COLS / 2), "= 20 points"); 993 | 994 | mvprintw(18, (COLS / 2) - 8, sprites[ALIEN10][ctype][0].lines[0]); 995 | mvprintw(19, (COLS / 2) - 8, sprites[ALIEN10][ctype][0].lines[1]); 996 | mvprintw(18, (COLS / 2), "= 10 points"); 997 | 998 | #ifdef USE_COLORS 999 | if (has_colors()) attron(COLOR_PAIR(1)); 1000 | #endif 1001 | mvprintw(LINES - 1, (COLS - 41) / 2, "https://github.com/macdice/ascii-invaders"); 1002 | refresh(); 1003 | } 1004 | 1005 | void resetAliens() { 1006 | int x, y = 0; 1007 | 1008 | // we make one row of alien30s... 1009 | for (x = 0; x < aliens.cols; x++) 1010 | aliens.table[x] = ALIEN30; 1011 | 1012 | // next we fill half of the remaining rows with alien20s... 1013 | while (++y < (aliens.rows / 2)) 1014 | for (x = 0; x < aliens.cols; x++) 1015 | aliens.table[(y * aliens.cols) + x] = ALIEN20; 1016 | 1017 | // next we stick in some alien10s... 1018 | do 1019 | for (x = 0; x < aliens.cols; x++) 1020 | aliens.table[(y * aliens.cols) + x] = ALIEN10; 1021 | while (++y < aliens.rows); 1022 | 1023 | aliens.emptyLeft = aliens.emptyTop = 0; 1024 | aliens.emptyRight = aliens.cols; 1025 | aliens.emptyBottom = aliens.rows; 1026 | 1027 | aliens.x = aliens.y = 0; 1028 | aliens.direction = 1; 1029 | aliens.paintWait = PAINT_WAIT; 1030 | aliens.paintRow = aliens.rows; 1031 | aliens.count = aliens.cols * aliens.rows; 1032 | freeBombs(); 1033 | 1034 | ma.x = 0; 1035 | 1036 | gun.x = COLS / 2; 1037 | gun.missile.x = gun.missile.y = 0; 1038 | } 1039 | 1040 | /** 1041 | * Sets up the shields. 1042 | */ 1043 | void resetShields() { 1044 | int x, y; 1045 | for (y = 0; y < SHELTER_HEIGHT * game.screenCols; gun.shields[y++] = ' '); 1046 | for (x = 0; x < game.screenCols - 10; x += 10) { 1047 | for (y = 0; y < SHELTER_HEIGHT; y++) { 1048 | int i = 0; 1049 | const Sprite *sprite = &sprites[SHELTER][ctype][0]; 1050 | while (sprite->lines[y][i] != 0) { 1051 | gun.shields[(y * game.screenCols) + x + i] = sprite->lines[y][i]; 1052 | i++; 1053 | } 1054 | } 1055 | } 1056 | } 1057 | 1058 | /** 1059 | * Add a bomb to the linked list of bombs. 1060 | * @param x the x coordinate 1061 | * @param y the y coordinate 1062 | */ 1063 | void addBomb(int x, int y) { 1064 | struct Bomb *b; 1065 | b = malloc(sizeof(struct Bomb)); 1066 | if (b == NULL) { 1067 | /* If we run out of memory, just don't add a bomb. 1068 | * Well overcommit likely means this never happens, and 1069 | * instead in the event of memory shortage b will not be NULL 1070 | * but will not have physical memory available and we'll just crash 1071 | * when we attempt to access it. But this shuts scan-build up. */ 1072 | return; 1073 | } 1074 | b->x = x; 1075 | b->y = y; 1076 | b->anim = 0; 1077 | b->next = aliens.headBomb; 1078 | aliens.headBomb = b; 1079 | } 1080 | 1081 | /** 1082 | * Remove a bomb from the linked list of bombs. 1083 | * @param b bomb pointer 1084 | * @return whether the bomb was found 1085 | */ 1086 | int removeBomb(struct Bomb *b) { 1087 | struct Bomb *this; 1088 | struct Bomb *last; 1089 | 1090 | // see if it's illegal to search 1091 | if (b == NULL || aliens.headBomb == NULL) 1092 | return 0; 1093 | 1094 | // see if it's the first one 1095 | if (aliens.headBomb == b) { 1096 | aliens.headBomb = b->next; 1097 | free(b); 1098 | return 1; 1099 | } 1100 | 1101 | // no, look for it in the list 1102 | for (this = aliens.headBomb, last = NULL; 1103 | this != NULL; 1104 | last = this, this = this->next) { 1105 | 1106 | if (this == b) { 1107 | last->next = this->next; 1108 | free(this); 1109 | return 1; 1110 | } 1111 | } 1112 | 1113 | // couldn't find it 1114 | return 0; 1115 | } 1116 | 1117 | /** 1118 | * Free all alien bombs. 1119 | */ 1120 | void freeBombs() { 1121 | while (aliens.headBomb) 1122 | removeBomb(aliens.headBomb); 1123 | } 1124 | 1125 | /* mode: C; indent-tabs-mode: nil; tab-width: 4 */ 1126 | --------------------------------------------------------------------------------