cursesgames: Add a directory for curses based games - and space invaders
authorAlan Cox <alan@linux.intel.com>
Thu, 10 May 2018 22:56:41 +0000 (23:56 +0100)
committerAlan Cox <alan@linux.intel.com>
Thu, 10 May 2018 22:56:41 +0000 (23:56 +0100)
Applications/Makefile
Applications/cursesgames/Makefile.6502 [new file with mode: 0644]
Applications/cursesgames/Makefile.68000 [new file with mode: 0644]
Applications/cursesgames/Makefile.6809 [new file with mode: 0644]
Applications/cursesgames/Makefile.r2k [new file with mode: 0644]
Applications/cursesgames/Makefile.z80 [new file with mode: 0644]
Applications/cursesgames/invaders.c [new file with mode: 0644]
Applications/cursesgames/invaders.h [new file with mode: 0644]

index 20eab97..8c8fbe2 100644 (file)
@@ -1,5 +1,5 @@
 #
-APPS = util cmd sh games cave cpm v7games games\
+APPS = util cmd sh games cave cpm v7games games cursesgames \
        as09 ld09 netd SmallC MWC asz80 flashrom
 
 .PHONY: $(APPS)
@@ -27,6 +27,9 @@ cpm:
 games:
        +(cd games; $(MAKE) -f Makefile.$(USERCPU))
 
+cursesgames:
+       +(cd cursesgames; $(MAKE) -f Makefile.$(USERCPU))
+
 ld09:
        +(cd ld09; $(MAKE) -f Makefile.$(USERCPU))
 
diff --git a/Applications/cursesgames/Makefile.6502 b/Applications/cursesgames/Makefile.6502
new file mode 100644 (file)
index 0000000..c7b2a1d
--- /dev/null
@@ -0,0 +1,29 @@
+PLATFORM = 6502
+CC = cl65
+ASM = ca65
+LINKER = cl65
+CFLAGS = -t none -O -D__STDC__ -c -O -I../../Library/include -I../../Library/include/6502
+LINKER_OPT = -L../../Library/libs -C ../../Library/libs/ld65-$(TARGET).cfg
+ASM_OPT = -o
+CRT0 = ../../Library/libs/crt0_6502.o
+CRT0NS = ../../Library/libs/crt0nostdio_6502.o
+
+.SUFFIXES: .c .o
+
+SRCS  = invaders.c
+
+OBJS = $(SRCS:.c=.o)
+APPS = $(OBJS:.o=)
+all: $(APPS) size.report
+
+$(APPS): %: %.o
+       $(LINKER) -o $@ $(LINKER_OPT) $(CRT0) $^ c6502.lib -m $@.map
+
+size.report: $(APPS) $(APPSNS)
+       ls -l $^ > $@
+
+clean:
+       rm -f $(OBJS) $(APPS) $(SRCS:.c=) core *~ *.asm *.lst *.sym *.map *.noi *.lk *.ihx *.tmp *.bin size.report
+
+rmbak:
+       rm -f *~ core
diff --git a/Applications/cursesgames/Makefile.68000 b/Applications/cursesgames/Makefile.68000
new file mode 100644 (file)
index 0000000..7ee3753
--- /dev/null
@@ -0,0 +1,35 @@
+PLATFORM = 68000
+CC = m68k-uclinux-gcc
+ASM = m68k-uclinux-as
+AR = m68k-uclinux-ar
+LINKER = m68k-uclinux-ld
+CFLAGS = -fno-strict-aliasing -fomit-frame-pointer -fno-builtin -msoft-float -Wall -m68000 -Os -I../../Library/include -I../../Library/include/68000
+LINKER_OPT = -L../../Library/libs -lcurses68000 -lc68000
+LIBGCCDIR = $(dir $(shell $(CC) -print-libgcc-file-name))
+LINKER_OPT += --emit-relocs -L$(LIBGCCDIR) -lgcc -T ../../Library/elf2flt.ld
+CRT0 = ../../Library/libs/crt0_68000.o
+# For now while we get going. Really we want to use some kind of elf2zmagic
+# with relocs.
+ELF2FUZIX = elf2flt
+.SUFFIXES: .c .o
+
+SRCS  = invaders.c
+OBJS = $(SRCS:.c=.o)
+APPS = $(OBJS:.o=)
+
+all: $(APPS) size.report
+
+$(APPS): $(CRT0)
+
+$(APPS) : %: %.o
+       $(LINKER) $^ -o $@.bin $(LINKER_OPT)
+       $(ELF2FUZIX) -o $@ $@.bin
+
+size.report: $(APPS) $(APPSNS)
+       ls -l $^ > $@
+
+clean:
+       rm -f $(OBJS) $(APPS) $(SRCS:.c=) core *~ *.asm *.lst *.sym *.map *.noi *.lk *.ihx *.tmp *.bin size.report
+
+rmbak:
+       rm -f *~ core
diff --git a/Applications/cursesgames/Makefile.6809 b/Applications/cursesgames/Makefile.6809
new file mode 100644 (file)
index 0000000..f06d77d
--- /dev/null
@@ -0,0 +1,32 @@
+PLATFORM = 6809
+CC = m6809-unknown-gcc
+ASM = m6809-unknown-as
+AR = m6809-unknown-ar
+LINKER = m6809-unknown-ld
+CFLAGS = -I../../Library/include -I../../Library/include/6809
+LINKER_OPT = --oformat=raw -L../../Library/libs -lcurses6809 -lc6809
+LIBGCCDIR = $(dir $(shell $(CC) -print-libgcc-file-name))
+LINKER_OPT += -L$(LIBGCCDIR) -lgcc
+LINKER_OPT += --script=../util/$(TARGET).link
+ASM_OPT = -o
+CRT0 = ../../Library/libs/crt0_6809.o
+
+.SUFFIXES: .c .o
+
+SRCS  = invaders.c
+OBJS = $(SRCS:.c=.o)
+APPS = $(OBJS:.o=)
+
+all: $(APPS) size.report
+
+$(APPS): $(CRT0)
+
+$(APPS) $(APPSNS): %: %.o
+       $(LINKER) -o $@ $(LINKER_OPT) $^
+
+size.report: $(APPS)
+       ls -l $^ > $@
+
+clean:
+       rm -f $(OBJS) $(APPS) *.lst *.map size.report
+
diff --git a/Applications/cursesgames/Makefile.r2k b/Applications/cursesgames/Makefile.r2k
new file mode 100644 (file)
index 0000000..218ab89
--- /dev/null
@@ -0,0 +1 @@
+       include Makefile.z80
diff --git a/Applications/cursesgames/Makefile.z80 b/Applications/cursesgames/Makefile.z80
new file mode 100644 (file)
index 0000000..9d225ee
--- /dev/null
@@ -0,0 +1,36 @@
+CC = sdcc
+FCC = ../../Library/tools/fcc -O2 -m$(USERCPU)
+PLATFORM =
+#PLATFORM = -tzx128
+PROGLOAD=`(cat ../../Kernel/platform/config.h; echo PROGLOAD) | cpp -E | tail -n1`
+# Used for programs that make sdcc go boom or take hours
+ASM_OPT = -l -o -s
+LINKER_OPT = -mz80 --nostdlib --no-std-crt0 --code-loc $(PROGLOAD) --data-loc  0
+BINMAN = ../../Library/tools/binman
+LIBS = -lcurses -ltermcap
+
+.SUFFIXES: .c .rel
+
+SRCS  = invaders.c
+OBJS = $(SRCS:.c=.rel)
+APPS = $(OBJS:.rel=)
+
+all: $(APPS) sizes
+
+$(OBJS): %.rel: %.c
+
+.c.rel:
+       $(FCC) $(PLATFORM) -c $<
+
+%: %.rel
+       $(FCC) $(PLATFORM) $(LIBS) $< -o $@
+
+sizes: $(APPS)
+       ls -l $(APPS) >size.report
+
+clean:
+       rm -f $(OBJS) $(APPS) $(SRCS:.c=) core *~ *.asm *.lst *.sym *.map *.noi *.lk *.ihx *.tmp *.bin size.report
+
+rmbak:
+       rm -f *~ core
+
diff --git a/Applications/cursesgames/invaders.c b/Applications/cursesgames/invaders.c
new file mode 100644 (file)
index 0000000..0729e5e
--- /dev/null
@@ -0,0 +1,942 @@
+/**
+ * ascii invaders - A curses clone of the classic video game Space Invaders
+ * (c) 2001 Thomas Munro
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * https://github.com/macdice/ascii-invaders
+ * Thomas Munro <munro@ip9.org>
+ *
+ * $Id: invaders.c,v 1.20 2002/07/21 21:52:13 munro Exp $
+ */
+
+#include "invaders.h"
+
+#include <curses.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/signal.h>
+#include <time.h>
+#include <termios.h>
+
+const char *alienBlank = "      ";
+const char *alien30[] = { " {@@} ",
+       " /\"\"\\ ",
+       "      ",
+       " {@@} ",
+       "  \\/  ",
+       "      "
+};
+
+const char *alien20[] = { " dOOb ",
+       " ^/\\^ ",
+       "      ",
+       " dOOb ",
+       " ~||~ ",
+       "      "
+};
+
+const char *alien10[] = { " /MM\\ ",
+       " |~~| ",
+       "      ",
+       " /MM\\ ",
+       " \\~~/ ",
+       "      "
+};
+
+const char *alienMa[] = { "_/MM\\_",
+       "qWAAWp"
+};
+
+const char *gunner[] = { "  mAm  ",
+       " MAZAM "
+};
+
+const char *gunnerExplode[] = {
+       " ,' %  ",
+       " ;&+,! ",
+       " -,+$! ",
+       " +  ^~ "
+};
+
+const char *alienExplode[] = {
+       " \\||/ ",
+       " /||\\ ",
+       "      "
+};
+
+const char *shelter[] = {
+       "/MMMMM\\",
+       "MMMMMMM",
+       "MMM MMM"
+};
+
+const char *bombAnim = "\\|/-";
+
+// We have to use global variables becase our main loop is driven by
+// an alarm signal - so we put them into tidy structures.
+// TODO!  Define the structs, but declare the game state objects on
+// the stack of main.  Down with globalisation!
+
+struct {
+       int score;
+       int lives;
+       int state;
+       int screenCols;         // screen columns
+       int screenRows;         // screen rows
+       int timer;              // timer used to switch between game states
+} game;
+
+struct {
+       int rows, cols;         // how many rows and columns of aliens are there?
+       int x, y;               // alien position
+       int *table;             // the table of aliens
+       // which rows and columns have been cleared?
+       int emptyLeft, emptyRight, emptyTop, emptyBottom;
+       int direction;
+       int paintRow;           // cursor for repainting row by row
+       int paintWait;          // counter for repainting row by row
+       int sideAnim;           // staggered sideways movement
+       int count;              // how many aliens are left?
+       int anim;               // used for wiggling
+       struct Bomb *headBomb;  // linked list of bombs
+} aliens;
+
+struct {
+       int x;                  // x position
+       int move;               // buffered moves
+       int explodeTimer;       // timer used when gunner explodes
+       struct {
+               int x;          // x position
+               int y;          // y position
+       } missile;
+       char *shields;          // pointer to shield table
+} gun;
+
+struct {
+       int x;                  // x position
+       int pointsTimer;        // how long to leave the points on the screen
+} ma;
+
+
+void paintShelters(void);
+void resetAliens(void);
+void resetShields(void);
+void initGame(void);
+void handleTimer(void);
+void paintAlienRow(int row, int clean);
+void paintGunner(void);
+void paintIntro(void);
+void paintExplodingAlien(int y, int x);
+void removeAlien(int y, int x);
+void paintScore(void);
+void trimAliens(void);
+void addBomb(int x, int y);
+int removeBomb(struct Bomb *b);
+void freeBombs(void);
+void moveAliensDown(void);
+
+struct termios save;
+
+static void cleanup(int sig)
+{
+       tcsetattr(0, TCSANOW, &save);
+       exit(0);
+}
+
+int main(int argc, char **argv)
+{
+
+       struct termios t;
+
+       tcgetattr(0, &save);
+
+       signal(SIGINT, cleanup);
+       signal(SIGQUIT, cleanup);
+       signal(SIGTSTP, SIG_IGN);
+
+       // set up curses library
+       initscr();
+       cbreak();
+       noecho();
+#ifdef USE_COLORS
+       curs_set(0);            // hide cursor
+#endif
+       initscr();
+       cbreak();
+       noecho();
+#ifdef USE_KEYS
+       keypad(stdscr, TRUE);
+#endif
+#ifdef USE_COLORS
+       if (has_colors()) {
+               start_color();
+               init_pair(0, COLOR_BLACK, COLOR_BLACK);
+               init_pair(1, COLOR_GREEN, COLOR_BLACK);
+               init_pair(2, COLOR_RED, COLOR_BLACK);
+               init_pair(3, COLOR_CYAN, COLOR_BLACK);
+               init_pair(4, COLOR_WHITE, COLOR_BLACK);
+               init_pair(5, COLOR_MAGENTA, COLOR_BLACK);
+               init_pair(6, COLOR_BLUE, COLOR_BLACK);
+               init_pair(7, COLOR_YELLOW, COLOR_BLACK);
+       }
+#endif
+       game.screenCols = COLS;
+
+       tcgetattr(0, &t);
+       t.c_cc[VMIN] = 0;
+       t.c_cc[VTIME] = 0;
+       tcsetattr(0, TCSANOW, &t);
+
+       initGame();
+       paintIntro();
+       game.state = STATE_INTRO;
+
+
+       for (;;) {
+               usleep(100000);
+               handleTimer();
+               switch (getch()) {
+               case 'q':
+                       cleanup(0);
+                       break;
+               case 'j':
+#ifdef USE_KEYS
+               case KEY_LEFT:
+#endif
+                       gun.move = -2;
+                       break;
+               case 'k':
+#ifdef USE_KEYS
+               case KEY_RIGHT:
+#endif
+                       gun.move = 2;
+                       break;
+               case ' ':
+                       if (game.state == STATE_INTRO) {
+                               game.lives = 3;
+                               game.score = 0;
+                               game.state = STATE_PLAY;
+                               resetShields();
+
+                               resetAliens();
+                               game.timer = 0;
+                               clear();
+                               paintShelters();
+                       } else if (game.state == STATE_PLAY && game.timer > GUNNER_ENTRANCE && gun.missile.y == 0) {
+                               gun.missile.x = gun.x;
+                               gun.missile.y = LINES - GUNNER_HEIGHT - 1;
+                       }
+                       break;
+               }
+       }
+
+       return 0;               // not reached
+}
+
+void paintShelters()
+{
+       int n, y, x;
+
+#ifdef USE_COLORS
+       if (has_colors())
+               attron(COLOR_PAIR(1));
+#endif
+       n = 0;
+       for (y = 0; y < SHELTER_HEIGHT; y++) {
+               move(LINES - 1 - SHELTER_HEIGHT - GUNNER_HEIGHT + y, 0);
+               for (x = 0; x < game.screenCols; x++) {
+                       addch(gun.shields[n++]);
+               }
+       }
+       refresh();
+}
+
+/**
+ * Handle timer.  This is the logic that moves all the sprites
+ * around the screen.
+ */
+void handleTimer(void)
+{
+       int x;
+       int i;
+       struct Bomb *b;
+
+       // check which state the game is in
+       if (game.state == STATE_INTRO) {
+               // intro anim
+               return;
+       } else if (game.state == STATE_WAIT) {
+               if (game.timer++ == 10) {
+                       game.state = STATE_PLAY;
+                       paintScore();
+               }
+               return;
+       } else if (game.state == STATE_GAMEOVER) {
+               if (game.timer++ == 40) {
+                       game.state = STATE_INTRO;
+                       paintIntro();
+                       refresh();
+               }
+               // game over anim
+               return;
+       } else if (game.state == STATE_EXPLODE) {
+               // explode anim
+               if (gun.explodeTimer++ % 4 == 0) {
+                       paintGunner();
+               }
+               if (gun.explodeTimer == 40) {
+                       if (game.lives-- == 0) {
+                               // game over
+                               game.state = STATE_GAMEOVER;
+                               game.timer = 0;
+                               mvprintw(LINES / 2, (COLS / 2) - 5, "GAME OVER");
+                               refresh();
+                               return;
+                       } else {
+                               // start next life
+                               // if the aliens have reached the shields
+                               // then move them back up to the top of the screen
+                               int lastLine = aliens.y + (ALIEN_HEIGHT * aliens.emptyBottom) - 1;
+                               int shieldTop = LINES - GUNNER_HEIGHT - SHELTER_HEIGHT - 1;
+                               if (lastLine >= shieldTop)
+                                       aliens.y = 0;
+
+                               game.timer = 0;
+                               game.state = STATE_WAIT;
+                               ma.x = 0;       // just in case she was there at the time
+                               freeBombs();    // get rid of any bombs
+                               clear();
+                               paintShelters();
+                               refresh();
+                               aliens.paintRow = aliens.rows;
+                               return;
+                       }
+               }
+       }
+       // otherwise handle game play...
+       game.timer++;
+
+       if (game.timer == GUNNER_ENTRANCE && game.state == STATE_PLAY) {
+               paintGunner();
+       }
+       // decide if it's time to send on ma
+       if (game.timer % MA_ENTRANCE == MA_ENTRANCE - 1 && aliens.y > 5) {
+               ma.x = COLS - ALIEN_WIDTH - 1;
+       }
+       // if ma is currently on, display
+       if (ma.x > 0) {
+               int i;
+
+#ifdef USE_COLORS
+               if (has_colors())
+                       attron(COLOR_PAIR(2));
+#endif
+               if (ma.pointsTimer != 0) {
+                       // ma has been shot and is now just showing points
+                       if (ma.pointsTimer++ == 20) {
+                               mvprintw(2, ma.x, "%s", alienBlank);
+                               ma.pointsTimer = 0;
+                               ma.x = 0;
+                       }
+               } else {
+                       // ma is grooving across the top of the screen
+                       ma.x--;
+                       for (i = 0; i < MA_HEIGHT; i++)
+                               mvprintw(2 + i, ma.x, "%s ", alienMa[i]);
+                       refresh();
+                       // if we have reach the edge then remove ma
+                       if (ma.x == 0) {
+                               for (i = 0; i < MA_HEIGHT; i++)
+                                       mvprintw(2 + i, ma.x, "%s ", alienBlank);
+                       }
+               }
+       }
+       // drop bombs
+       if (game.timer > GUNNER_ENTRANCE) {
+               for (x = aliens.emptyLeft; x < aliens.emptyRight; x++) {
+                       int y;
+                       // find the first alien from the bottom
+                       for (y = aliens.emptyBottom - 1; y >= 0; y--) {
+                               if (aliens.table[(aliens.cols * y) + x] > ALIEN_EMPTY) {
+                                       if (1 == (int) (50.0 * rand() / (RAND_MAX + 1.0))) {
+                                               addBomb(aliens.x + (x * ALIEN_WIDTH) + (ALIEN_WIDTH / 2), aliens.y + (y * ALIEN_HEIGHT) + ALIEN_HEIGHT - 1);
+                                       }
+                                       break;
+                               }
+                       }
+               }
+       }
+       // handle gunner movements
+       if (game.state == STATE_PLAY && game.timer > GUNNER_ENTRANCE) {
+               if (gun.move < 0 && gun.x > GUNNER_WIDTH / 2) {
+                       gun.move++;
+                       gun.x--;
+                       paintGunner();
+                       refresh();
+               }
+               if (gun.move > 0 && gun.x < COLS - (GUNNER_WIDTH / 2)) {
+                       gun.move--;
+                       gun.x++;
+                       paintGunner();
+                       refresh();
+               }
+       }
+       // handle alien movements
+       if (--aliens.paintWait <= 0) {
+               // time to repaint one row of aliens (speeds up as you shoot aliens)
+               aliens.paintWait = (int) ((double) PAINT_WAIT * ((double) aliens.count / (double) (aliens.cols * aliens.rows)));;
+               aliens.paintRow--;
+               paintAlienRow(aliens.paintRow, 0);
+               refresh();
+               if (aliens.paintRow <= aliens.emptyTop) {
+                       // time to move the block of aliens
+                       aliens.paintRow = aliens.emptyBottom;   // reset counter
+                       aliens.anim = (aliens.anim ? 0 : 1);    // wiggle
+                       if (aliens.direction == -1) {
+                               if (--aliens.x + (aliens.emptyLeft * ALIEN_WIDTH) == 0) {
+                                       // change direction, clear top line, shuffle down
+                                       aliens.direction = 1;
+                                       move(aliens.y, 0);
+                                       clrtoeol();
+                                       moveAliensDown();
+                               }
+                       } else if (aliens.direction == 1) {
+                               if (++aliens.x + (aliens.emptyRight * ALIEN_WIDTH) == COLS) {
+                                       // change direction, clear top line, shuffle down
+                                       aliens.direction = -1;
+                                       move(aliens.y, 0);
+                                       clrtoeol();
+                                       moveAliensDown();
+                               }
+                       }
+                       paintScore();
+/*
+            // see if the aliens have hit the bottom
+            if (game.state == STATE_PLAY
+                    && aliens.y + (ALIEN_HEIGHT * 
+                            (aliens.rows - aliens.emptyBottom)) 
+                    > LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT) {
+                game.state = STATE_EXPLODE;
+                gun.explodeTimer = 0;
+            }
+*/
+               }
+       }
+#ifdef USE_COLORS
+       // use white for missiles and bombs
+       if (has_colors())
+               attron(COLOR_PAIR(4));
+#endif
+
+       // handle bomb movements
+       for (b = aliens.headBomb; b != NULL;) {
+               struct Bomb *next = b->next;
+               move(b->y, b->x);
+               addch(' ');
+               if (++(b->y) < LINES) {
+                       if (gun.missile.y != 0 && abs(b->x - gun.missile.x) < 2 && abs(b->y - gun.missile.y) < 2) {
+                               // collision with missile
+                               removeBomb(b);
+                               move(gun.missile.y, gun.missile.x);
+                               addch(' ');
+                               gun.missile.y = 0;
+                       } else if (game.state == STATE_PLAY && game.timer > GUNNER_ENTRANCE && b->y >= LINES - GUNNER_HEIGHT && b->x > (gun.x - (GUNNER_WIDTH / 2))
+                                  && b->x < (gun.x + (GUNNER_WIDTH / 2))) {
+                               // collision with gunner
+                               removeBomb(b);
+#ifndef BULLET_PROOF
+                               game.state = STATE_EXPLODE;
+                               gun.explodeTimer = 0;
+#endif
+                       } else if (b->y < LINES - 1 - GUNNER_HEIGHT && b->y >= LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT && gun.shields[((b->y - (LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT))
+                                                                                                                                         * game.screenCols) + b->x] != ' ') {
+                               // collision with shield
+                               gun.shields[((b->y - (LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT))
+                                            * game.screenCols) + b->x] = ' ';
+                               mvaddch(b->y, b->x, ' ');
+                               removeBomb(b);
+                       } else {
+                               // advance bomb
+                               move(b->y, b->x);
+                               addch(bombAnim[b->anim++]);
+                               if (b->anim == BOMB_ANIM_SIZE) {
+                                       b->anim = 0;
+                               }
+                       }
+               } else {
+                       removeBomb(b);
+               }
+
+               b = next;
+       }
+
+       // handle missile movements
+       if (gun.missile.y != 0) {
+
+               for (i = 0; i < 2; i++) {
+                       move(gun.missile.y, gun.missile.x);
+                       addch(' ');
+                       if ((gun.missile.y -= 1) > 0) {
+                               move(gun.missile.y, gun.missile.x);
+                               addch('!');
+                       } else {
+                               gun.missile.y = 0;
+                       }
+                       // test for collision with shield
+                       if (gun.missile.y < LINES - 1 - GUNNER_HEIGHT && gun.missile.y >= LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT) {
+                               if (gun.shields[((gun.missile.y - (LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT))
+                                                * game.screenCols) + gun.missile.x] != ' ') {
+                                       gun.shields[((gun.missile.y - (LINES - 1 - GUNNER_HEIGHT - SHELTER_HEIGHT))
+                                                    * game.screenCols) + gun.missile.x] = ' ';
+
+                                       mvaddch(gun.missile.y, gun.missile.x, ' ');
+                                       gun.missile.y = 0;
+                               }
+                       }
+                       // test for collision with aliens
+                       else if (gun.missile.x >= aliens.x && gun.missile.x < aliens.x + (ALIEN_WIDTH * aliens.cols)
+                                && gun.missile.y < aliens.y + ALIEN_HEIGHT * aliens.rows && gun.missile.y >= aliens.y) {
+                               int alien;
+                               int x = gun.missile.x - aliens.x;
+                               int y = gun.missile.y - aliens.y;
+                               if (x % ALIEN_WIDTH != 0 && x % ALIEN_WIDTH != ALIEN_WIDTH - 1) {
+                                       // it didn't sneak between two aliens
+                                       x /= ALIEN_WIDTH;
+                                       y /= ALIEN_HEIGHT;
+                                       alien = aliens.table[(y * aliens.cols) + x];
+                                       if (alien > ALIEN_EMPTY) {
+                                               game.score += alien * 10;
+                                               paintExplodingAlien(y, x);
+                                               paintScore();
+                                               gun.missile.y = 0;      // no more missile
+                                               aliens.table[(y * aliens.cols) + x] = ALIEN_EXPLODE1;
+                                               if (--aliens.count == 0) {
+                                                       resetAliens();
+                                                       game.timer = 0;
+                                                       clear();
+                                                       paintShelters();
+                                               }
+                                               refresh();
+                                               trimAliens();
+                                       }
+                               }
+                       }
+                       // test for collection with ma
+                       else if (ma.x != 0 && gun.missile.y <= 2 + MA_HEIGHT && gun.missile.x >= ma.x && gun.missile.x <= ma.x + ALIEN_WIDTH) {
+                               // chose a 'random' number of points, either 50, 100 or 150
+                               int points = ((time(0) % 3) + 1) * 50;
+                               int i;
+                               game.score += points;
+                               // remove ma
+                               for (i = 0; i < MA_HEIGHT; i++)
+                                       mvprintw(2 + i, ma.x, "%s ", alienBlank);
+                               // draw the number of points
+                               mvprintw(2, ma.x, "  %d", points);
+                               ma.pointsTimer = 1;
+                               paintScore();
+                       }
+               }
+       }
+       refresh();
+}
+
+/**
+ * Move aliens down one row.
+ */
+void moveAliensDown(void)
+{
+       // figure out which screen row the bottom alien is one
+       // and if it's over the sheilds then clear the line of
+       // the shields or at the bottom of the screen
+       int lastLine = ++aliens.y + (ALIEN_HEIGHT * aliens.emptyBottom) - 1;
+       int topShield = LINES - GUNNER_HEIGHT - SHELTER_HEIGHT - 1;
+       int gunnerTop = LINES - GUNNER_HEIGHT - 1;
+
+       if (lastLine >= topShield && lastLine < topShield + SHELTER_HEIGHT) {
+               // clear the shield line
+               int i = (lastLine - topShield) * game.screenCols;
+               int j;
+               for (j = 0; j < game.screenCols; j++) {
+                       gun.shields[i + j] = ' ';       // DEBUG
+               }
+               paintShelters();
+       }
+       if (lastLine == gunnerTop) {
+               // blow up gunner if not already blowing up
+               if (game.state != STATE_EXPLODE) {
+                       game.state = STATE_EXPLODE;
+                       gun.explodeTimer = 0;
+               }
+       }
+}
+
+/**
+ * Adjusts the size of the block of aliens for left and right (the bottom
+ * adjustment is made by the alien drawing code).
+ */
+void trimAliens(void)
+{
+       // update empty line pointers
+       int found = 0;
+       for (;;) {
+               int i;
+               for (i = 0; i < aliens.rows; i++) {
+                       if (aliens.table[(i * aliens.cols) + aliens.emptyLeft]
+                           != ALIEN_EMPTY) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if (found)
+                       break;
+               else
+                       aliens.emptyLeft++;
+       }
+
+       found = 0;
+       for (;;) {
+               int i;
+               for (i = 0; i < aliens.rows; i++) {
+                       if (aliens.table[(i * aliens.cols) + aliens.emptyRight - 1]
+                           != ALIEN_EMPTY) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if (found)
+                       break;
+               else
+                       aliens.emptyRight--;
+       }
+
+       found = 0;
+       for (;;) {
+               int i;
+               for (i = 0; i < aliens.cols; i++) {
+                       if (aliens.table[((aliens.emptyBottom - 1) * aliens.cols) + i]
+                           != ALIEN_EMPTY) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if (found)
+                       break;
+               else
+                       aliens.emptyBottom--;
+       }
+}
+
+void paintGunner(void)
+{
+       int i;
+#ifdef USE_COLORS
+       if (has_colors())
+               attron(COLOR_PAIR(1));
+#endif
+       for (i = 0; i < GUNNER_HEIGHT; i++) {
+               move(LINES - GUNNER_HEIGHT + i, gun.x - (GUNNER_WIDTH / 2));
+               if (game.state == STATE_PLAY) {
+                       printw("%s", gunner[i]);
+               } else if (game.state == STATE_EXPLODE) {
+                       printw("%s", gunnerExplode[i + (GUNNER_HEIGHT * ((game.timer / 4) % 2))]);
+               }
+       }
+}
+
+void paintExplodingAlien(int y, int x)
+{
+       int i;
+       for (i = 0; i < ALIEN_HEIGHT; i++) {
+               move((y * ALIEN_HEIGHT) + aliens.y + i, (x * ALIEN_WIDTH) + aliens.x + (y > aliens.paintRow ? aliens.direction : 0));
+               // adjustment because of
+               // alien drawing technique
+               printw("%s", alienExplode[i]);
+       }
+}
+
+void removeAlien(int y, int x)
+{
+       int i;
+       for (i = 0; i < ALIEN_HEIGHT; i++) {
+               move((y * ALIEN_HEIGHT) + aliens.y + i, (x * ALIEN_WIDTH) + aliens.x);
+               printw("%s", alienBlank);
+       }
+}
+
+void paintScore(void)
+{
+#ifdef USE_COLORS
+       if (has_colors())
+               attron(COLOR_PAIR(4));
+#endif
+       if (aliens.y > 0) {
+               move(0, 0);
+               printw("Ascii-Invaders  Score: %d   Lives remaining: %d", game.score, game.lives);
+       }
+}
+
+/**
+ * Paints a row of aliens (but doesn't call refresh).
+ * @param row which row of aliens to draw
+ * @param clean whether to paint the line above this row of aliens white
+ */
+void paintAlienRow(int row, int clean)
+{
+       int x, i;
+
+       if (clean) {
+               move((row * ALIEN_HEIGHT) + aliens.y - 1, 0);
+               deleteln();
+       }
+       // draw the alien space ships
+#ifdef USE_COLORS
+       if (has_colors())
+               attron(COLOR_PAIR(4));
+#endif
+       // this is a slight kludge - occasionally bits of explosion were left
+       // behind when the aliens were moving and exploding at the same time, so:
+       // if we are not right against the left or right of the screen,
+       // we delete the column immediately before and after each line
+       if (aliens.x > 0) {
+               for (i = 0; i < ALIEN_HEIGHT; i++) {
+                       move((row * ALIEN_HEIGHT) + aliens.y + i, (aliens.emptyLeft * ALIEN_WIDTH) + aliens.x - 1);
+                       addch(' ');
+               }
+       }
+       if (aliens.x + (ALIEN_WIDTH * aliens.emptyRight) < game.screenCols) {
+               for (i = 0; i < ALIEN_HEIGHT; i++) {
+                       move((row * ALIEN_HEIGHT) + aliens.y + i, (aliens.emptyRight * ALIEN_WIDTH) + aliens.x);
+                       addch(' ');
+               }
+       }
+       // draw the aliens
+       for (x = aliens.emptyLeft; x < aliens.emptyRight; x++) {
+               int line = ALIEN_HEIGHT * aliens.anim;
+               int alien = aliens.table[(row * aliens.cols) + x];
+               for (i = 0; i < ALIEN_HEIGHT; i++) {
+                       move((row * ALIEN_HEIGHT) + aliens.y + i, (x * ALIEN_WIDTH) + aliens.x);
+                       switch (alien) {
+                       case ALIEN10:
+                               printw("%s", alien10[line + i]);
+                               break;
+                       case ALIEN20:
+                               printw("%s", alien20[line + i]);
+                               break;
+                       case ALIEN30:
+                               printw("%s", alien30[line + i]);
+                               break;
+                       case ALIEN_EXPLODE1:
+                               //printw("%s", alienExplode[i]);
+                               // do nothing, to leave the explosion on the screen
+                               break;
+                       case ALIEN_EXPLODE2:
+                       case ALIEN_EMPTY:
+                               // wipe out
+                               printw("%s", alienBlank);
+                       }
+               }
+               // if the alien is exploding then advance its explosion state
+               if (alien == ALIEN_EXPLODE1) {
+                       aliens.table[(row * aliens.cols) + x] = ALIEN_EXPLODE2;
+               } else if (alien == ALIEN_EXPLODE2) {
+                       aliens.table[(row * aliens.cols) + x] = ALIEN_EMPTY;
+                       trimAliens();
+               }
+       }
+}
+
+void initGame(void)
+{
+
+       // how many aliens?
+       if (COLS < 80 || LINES < 20) {
+               fprintf(stderr, "Terminal size too small to play Ascii Invaders.\n");
+               cleanup(0);
+       }
+       aliens.cols = (COLS / ALIEN_WIDTH) - 4;
+       aliens.rows = (LINES / ALIEN_HEIGHT) - 4;
+       aliens.table = malloc(aliens.cols * aliens.rows * sizeof(int));
+       gun.shields = malloc(game.screenCols * SHELTER_HEIGHT);
+       if (aliens.table == NULL || gun.shields == NULL) {
+               fprintf(stderr, "Out of memory.\n");
+               cleanup(0);
+       }
+}
+
+void paintIntro(void)
+{
+       clear();
+
+#ifdef USE_COLORS
+       if (has_colors())
+               attron(COLOR_PAIR(4));
+#endif
+       mvprintw(2, (COLS / 2) - 30, "                _ _   _                     _               ");
+       mvprintw(3, (COLS / 2) - 30, "  __ _ ___  ___(_|_) (_)_ ____   ____ _  __| | ___ _ __ ___ ");
+       mvprintw(4, (COLS / 2) - 30, " / _` / __|/ __| | | | | '_ \\ \\ / / _` |/ _` |/ _ \\ '__/ __|");
+       mvprintw(5, (COLS / 2) - 30, "| (_| \\__ \\ (__| | | | | | | \\ V / (_| | (_| |  __/ |  \\__ \\");
+       mvprintw(6, (COLS / 2) - 30, " \\__,_|___/\\___|_|_| |_|_| |_|\\_/ \\__,_|\\__,_|\\___|_|  |___/");
+
+
+#ifdef USE_COLORS
+       if (has_colors())
+               attron(COLOR_PAIR(2));
+#endif
+       mvprintw(9, (COLS / 2) - 8, alienMa[0]);
+       mvprintw(10, (COLS / 2) - 8, alienMa[1]);
+#ifdef USE_COLORS
+       if (has_colors())
+               attron(COLOR_PAIR(4));
+#endif
+       mvprintw(9, (COLS / 2), "= ?  points");
+
+       mvprintw(12, (COLS / 2) - 8, alien30[0]);
+       mvprintw(13, (COLS / 2) - 8, alien30[1]);
+       mvprintw(12, (COLS / 2), "= 30 points");
+
+       mvprintw(15, (COLS / 2) - 8, alien20[0]);
+       mvprintw(16, (COLS / 2) - 8, alien20[1]);
+       mvprintw(15, (COLS / 2), "= 20 points");
+
+       mvprintw(18, (COLS / 2) - 8, alien10[0]);
+       mvprintw(19, (COLS / 2) - 8, alien10[1]);
+       mvprintw(18, (COLS / 2), "= 10 points");
+
+#ifdef USE_COLORS
+       if (has_colors())
+               attron(COLOR_PAIR(1));
+#endif
+       mvprintw(LINES - 1, (COLS - 41) / 2, "https://github.com/macdice/ascii-invaders");
+       refresh();
+}
+
+void resetAliens(void)
+{
+       int x, y = 0;
+
+       // we make one row of alien30s...
+       for (x = 0; x < aliens.cols; x++)
+               aliens.table[x] = ALIEN30;
+
+       // next we fill half of the remaining rows with alien20s...
+       while (++y < (aliens.rows / 2))
+               for (x = 0; x < aliens.cols; x++)
+                       aliens.table[(y * aliens.cols) + x] = ALIEN20;
+
+       // next we stick in some alien10s...
+       do
+               for (x = 0; x < aliens.cols; x++)
+                       aliens.table[(y * aliens.cols) + x] = ALIEN10;
+       while (++y < aliens.rows);
+
+       aliens.emptyLeft = aliens.emptyTop = 0;
+       aliens.emptyRight = aliens.cols;
+       aliens.emptyBottom = aliens.rows;
+
+       aliens.x = aliens.y = 0;
+       aliens.direction = 1;
+       aliens.paintWait = PAINT_WAIT;
+       aliens.paintRow = aliens.rows;
+       aliens.count = aliens.cols * aliens.rows;
+       freeBombs();
+
+       ma.x = 0;
+
+       gun.x = COLS / 2;
+       gun.missile.x = gun.missile.y = 0;
+}
+
+/**
+ * Sets up the shields.
+ */
+void resetShields(void)
+{
+       int x, y;
+       for (y = 0; y < SHELTER_HEIGHT * game.screenCols; gun.shields[y++] = ' ');
+       for (x = 0; x < game.screenCols - 10; x += 10) {
+               for (y = 0; y < SHELTER_HEIGHT; y++) {
+                       int i = 0;
+                       while (shelter[y][i] != 0) {
+                               gun.shields[(y * game.screenCols) + x + i] = shelter[y][i];
+                               i++;
+                       }
+               }
+       }
+}
+
+/**
+ * Add a bomb to the linked list of bombs.
+ * @param x the x coordinate
+ * @param y the y coordinate
+ */
+void addBomb(int x, int y)
+{
+       struct Bomb *b;
+       b = malloc(sizeof(struct Bomb));
+       if (b == NULL) {
+               // die("Out of memory");
+       }
+       b->x = x;
+       b->y = y;
+       b->anim = 0;
+       b->next = aliens.headBomb;
+       aliens.headBomb = b;
+}
+
+/**
+ * Remove a bomb from the linked list of bombs.
+ * @param b bomb pointer
+ * @return whether the bomb was found
+ */
+int removeBomb(struct Bomb *b)
+{
+       struct Bomb *this;
+       struct Bomb *last;
+
+       // see if it's illegal to search
+       if (b == NULL || aliens.headBomb == NULL)
+               return 0;
+
+       // see if it's the first one
+       if (aliens.headBomb == b) {
+               aliens.headBomb = b->next;
+               free(b);
+               return 1;
+       }
+       // no, look for it in the list
+       for (this = aliens.headBomb, last = NULL; this != NULL; last = this, this = this->next) {
+
+               if (this == b) {
+                       last->next = this->next;
+                       free(this);
+                       return 1;
+               }
+       }
+
+       // couldn't find it
+       return 0;
+}
+
+/**
+ * Free all alien bombs.
+ */
+void freeBombs(void)
+{
+       while (aliens.headBomb)
+               removeBomb(aliens.headBomb);
+}
diff --git a/Applications/cursesgames/invaders.h b/Applications/cursesgames/invaders.h
new file mode 100644 (file)
index 0000000..5ee194d
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ * ascii invaders - A curses clone of the classic video game Space Invaders
+ * (c) 2001 Thomas Munro
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * https://github.com/macdice/ascii-invaders
+ * Thomas Munro <munro@ip9.org>
+ *
+ * $Id: invaders.h,v 1.3 2002/07/21 21:52:14 munro Exp $
+ */
+
+struct Bomb {
+    int x;
+    int y;
+    int anim;
+    struct Bomb *next;
+};
+
+/* To make this compile under Darwin/BSD I have to comment these, until
+   I figure out how to fix this problem (may involve not using apple's curses
+   implementation). */
+//#define USE_COLORS 1 
+//#define USE_KEYS 1
+
+//#define BULLET_PROOF 1  // debug
+
+#define BOMB_ANIM_SIZE 4 // "frames" in bomb anim
+
+#define FPS 10          // frames per second
+#define PAINT_WAIT 2    // how many frames between row repaints
+
+#define ALIEN30 3
+#define ALIEN20 2
+#define ALIEN10 1
+#define ALIEN_EMPTY 0
+#define ALIEN_EXPLODE1 -1
+#define ALIEN_EXPLODE2 -2
+
+#define ALIEN_WIDTH 6
+#define ALIEN_HEIGHT 3
+#define GUNNER_WIDTH 7
+#define GUNNER_HEIGHT 2
+#define SHELTER_WIDTH 7
+#define SHELTER_HEIGHT 3
+#define MA_HEIGHT 2
+#define MA_WIDTH 6
+
+#define GUNNER_ENTRANCE 40 // how many frames before gunner appears
+#define MA_ENTRANCE 400 // how many frames before MA comes on the screen
+
+#define STATE_INTRO 1
+#define STATE_PLAY 2
+#define STATE_EXPLODE 3
+#define STATE_WAIT 4
+#define STATE_GAMEOVER 5
+