Просмотр исходного кода

Add a Space Invaders example (no assets)

Pure Modula-2 using only tigrFillRect and the built-in font: player,
invaders, bullets, bombs, score and win/lose states. LEFT/RIGHT move,
SPACE fires, R restarts, ESC quits.
Eric Streit 3 недель назад
Родитель
Сommit
ed1de8e723
3 измененных файлов с 391 добавлено и 1 удалено
  1. 4 1
      modula2/ANALYSIS.md
  2. BIN
      modula2/examples/invaders/invaders
  3. 387 0
      modula2/examples/invaders/invaders.mod

+ 4 - 1
modula2/ANALYSIS.md

@@ -23,7 +23,10 @@ Verified by building and running on this machine (X11 session):
   all compile, link and run as *windows*; `headless` runs without a window and
   writes a correct 320x240 PNG; `opengl` opens two windows and drives the
   raw GL path (magenta `glClearColor`/`glClear` visible in `Window2`).
-  `Essai2` (qualified imports) and `Essai3` build. `essai4.mod` is
+  `Essai2` (qualified imports) and `Essai3` build. `invaders` (new, no
+  assets) is a playable Space Invaders game using only `tigrFillRect` +
+  the built-in font; verified rendering all palette colors and autonomous
+  invader motion. `essai4.mod` is
   not a tigr example: it is a standalone PIM-style bit-twiddling test
   (`SHIFT`/`AND`/`OR` on `CARDINAL`) that the ISO dialect does not accept -
   left as is.

BIN
modula2/examples/invaders/invaders


+ 387 - 0
modula2/examples/invaders/invaders.mod

@@ -0,0 +1,387 @@
+MODULE invaders;
+
+(* Simple "Space Invaders" using the tigr Modula-2 binding.
+   No assets: everything is drawn with tigrFillRect. Built-in font.
+   Controls: LEFT/RIGHT move, SPACE fire, R restart (after game over),
+   ESC quit.
+
+   Compilation (from modula2/):
+       gcc -c tigr.c
+       gm2 -fiso -c helper.mod
+       gm2 -fiso -I. tigr.o helper.o examples/invaders/invaders.mod \
+           -o examples/invaders/invaders -lGL -lX11
+*)
+
+FROM tigr IMPORT TigrPtr, tigrWindow, tigrFree, tigrClosed, tigrClear,
+                tigrUpdate, TPixelType, tigrFillRect, tigrPrint, tfont,
+                tigrKeyDown, tigrKeyHeld, tigrTime, tigrTextWidth,
+                TK_ESCAPE, TK_SPACE, TK_LEFT, TK_RIGHT;
+FROM helper IMPORT tigrRGB;
+
+CONST
+    winW = 320;
+    winH = 240;
+    invRows = 4;
+    invCols = 8;
+    cell    = 16;          (* invader sprite box *)
+    playerW = 24;
+    playerH = 6;
+    bulletW = 3;
+    bulletH = 9;
+    bombW   = 3;
+    bombH   = 8;
+    bulletSpeed = 6;
+    bombSpeed   = 3;
+    moveStep    = 4;
+    dropStep    = 10;
+    border      = 4;
+
+TYPE
+    State = (s_play, s_win, s_lose);
+
+VAR
+    win             : TigrPtr;
+    alive           : ARRAY [0..invRows - 1] OF ARRAY [0..invCols - 1] OF BOOLEAN;
+    gx, gy          : CARDINAL;          (* invader grid origin *)
+    dir             : INTEGER;           (* -1 left, +1 right *)
+    moveAcc, moveInt : SHORTREAL;
+    playerX, playerY : CARDINAL;
+    bulletActive    : BOOLEAN;
+    bulletX, bulletY : CARDINAL;
+    bombActive      : BOOLEAN;
+    bombX, bombY    : CARDINAL;
+    score, lives    : CARDINAL;
+    seed            : CARDINAL;          (* tiny LCG *)
+    state           : State;
+    lastTime, now   : SHORTREAL;
+    dt              : SHORTREAL;
+    black, white, red, green, yellow : TPixelType;
+    palette         : ARRAY [0..invRows - 1] OF TPixelType;
+    row, col, n     : CARDINAL;
+    scoreText       : ARRAY [0..20] OF CHAR;
+    msgbuf          : ARRAY [0..60] OF CHAR;
+
+    PROCEDURE CardToText(n : CARDINAL; VAR s : ARRAY OF CHAR);
+
+    (* Converts n to a NUL-terminated decimal string in s. *)
+
+        VAR
+            k    : CARDINAL;
+            tmp  : ARRAY [0..10] OF CHAR;
+            j    : CARDINAL;
+
+    BEGIN
+        IF n = 0 THEN
+            s[0] := '0';
+            s[1] := 0C;
+            RETURN
+        END;
+        k := 0;
+        WHILE n > 0 DO
+            tmp[k] := CHR(ORD('0') + (n MOD 10));
+            k := k + 1;
+            n := n DIV 10
+        END;
+        j := 0;
+        WHILE k > 0 DO
+            k := k - 1;
+            s[j] := tmp[k];
+            j := j + 1
+        END;
+        s[j] := 0C
+    END CardToText;
+
+    PROCEDURE StartGame;
+
+        VAR
+            r, c : CARDINAL;
+
+    BEGIN
+        gx := 32;
+        gy := 20;
+        dir := 1;
+        moveAcc := 0.0;
+        moveInt := 0.55;
+        score := 0;
+        lives := 3;
+        playerX := (winW - playerW) / 2;
+        bulletActive := FALSE;
+        bombActive := FALSE;
+        seed := 1;
+        state := s_play;
+        FOR r := 0 TO invRows - 1 DO
+            FOR c := 0 TO invCols - 1 DO
+                alive[r][c] := TRUE
+            END
+        END
+    END StartGame;
+
+    PROCEDURE CountAlive() : CARDINAL;
+
+        VAR
+            r, c, cnt : CARDINAL;
+
+    BEGIN
+        cnt := 0;
+        FOR r := 0 TO invRows - 1 DO
+            FOR c := 0 TO invCols - 1 DO
+                IF alive[r][c] THEN
+                    cnt := cnt + 1
+                END
+            END
+        END;
+        RETURN cnt
+    END CountAlive;
+
+    PROCEDURE MoveInvaders;
+
+        VAR
+            hitBound : BOOLEAN;
+            cnt      : CARDINAL;
+
+    BEGIN
+        IF dir < 0 THEN
+            hitBound := gx < (border + moveStep)
+        ELSE
+            hitBound := gx + moveStep + invCols * cell > winW - border
+        END;
+        IF hitBound THEN
+            gy := gy + dropStep;
+            dir := -dir;
+            IF gy + invRows * cell + 8 >= winH THEN
+                state := s_lose
+            END
+        ELSE
+            IF dir < 0 THEN
+                gx := gx - moveStep
+            ELSE
+                gx := gx + moveStep
+            END
+        END;
+
+        (* Speeds up as invaders are destroyed, and may drop a bomb. *)
+        cnt := CountAlive();
+        moveInt := 0.55 - (SHORTREAL(CountAlive() - 1)
+                 * 0.35) / SHORTREAL(invRows * invCols - 1);
+        seed := (seed * 1103515245 + 12345) MOD 2147483648;
+        IF (NOT bombActive) AND (cnt > 0) AND (seed MOD 3 = 0) THEN
+            col := seed MOD invCols;
+            row := 0;
+            WHILE (row < invRows) AND (NOT alive[row][col]) DO
+                row := row + 1
+            END;
+            IF row < invRows THEN
+                bombActive := TRUE;
+                bombX := gx + col * cell + (cell - bombW) / 2;
+                bombY := gy + row * cell + cell
+            END
+        END
+    END MoveInvaders;
+
+    PROCEDURE Shoot;
+
+    BEGIN
+        IF NOT bulletActive THEN
+            bulletActive := TRUE;
+            bulletX := playerX + playerW / 2 - bulletW / 2;
+            bulletY := playerY - bulletH
+        END
+    END Shoot;
+
+    PROCEDURE MoveBullet;
+
+        VAR
+            r, c        : CARDINAL;
+            ix, iy      : CARDINAL;
+            overlaps    : BOOLEAN;
+
+    BEGIN
+        IF bulletActive THEN
+            IF bulletY > border THEN
+                bulletY := bulletY - bulletSpeed
+            ELSE
+                bulletActive := FALSE
+            END;
+            overlaps := FALSE;
+            r := 0;
+            WHILE (r < invRows) AND (NOT overlaps) DO
+                c := 0;
+                WHILE (c < invCols) AND (NOT overlaps) DO
+                    IF alive[r][c] THEN
+                        ix := gx + c * cell;
+                        iy := gy + r * cell;
+                        IF (bulletX + bulletW > ix) AND (bulletX < ix + cell)
+                           AND (bulletY < iy + cell) AND (bulletY + bulletH > iy) THEN
+                            alive[r][c] := FALSE;
+                            score := score + 10;
+                            overlaps := TRUE
+                        END
+                    END;
+                    c := c + 1
+                END;
+                r := r + 1
+            END;
+            IF overlaps THEN
+                bulletActive := FALSE
+            END;
+            IF CountAlive() = 0 THEN
+                state := s_win
+            END
+        END
+    END MoveBullet;
+
+    PROCEDURE MoveBomb;
+
+    BEGIN
+        IF bombActive THEN
+            bombY := bombY + bombSpeed;
+            IF bombY >= winH THEN
+                bombActive := FALSE
+            ELSIF (bombX + bombW > playerX) AND (bombX < playerX + playerW)
+                  AND (bombY + bombH > playerY) AND (bombY < playerY + playerH) THEN
+                bombActive := FALSE;
+                IF lives > 1 THEN
+                    lives := lives - 1;
+                    playerX := (winW - playerW) / 2
+                ELSE
+                    lives := 0;
+                    state := s_lose
+                END
+            END
+        END
+    END MoveBomb;
+
+    PROCEDURE DrawInvader(x, y : CARDINAL; color : TPixelType);
+
+    BEGIN
+        tigrFillRect(win, x + 3, y, cell - 6, 3, color);     (* antenna *)
+        tigrFillRect(win, x + 1, y + 3, cell - 2, 8, color); (* body *)
+        tigrFillRect(win, x + 4, y + 5, 2, 3, black);        (* left eye *)
+        tigrFillRect(win, x + 10, y + 5, 2, 3, black);       (* right eye *)
+        tigrFillRect(win, x, y + 11, 4, 3, color);           (* feet *)
+        tigrFillRect(win, x + 12, y + 11, 4, 3, color)
+    END DrawInvader;
+
+    PROCEDURE Draw;
+
+        VAR
+            r, c    : CARDINAL;
+            ix, iy  : CARDINAL;
+            tw      : INTEGER;
+            msgX    : CARDINAL;
+
+    BEGIN
+        tigrClear(win, black);
+        FOR r := 0 TO invRows - 1 DO
+            FOR c := 0 TO invCols - 1 DO
+                IF alive[r][c] THEN
+                    ix := gx + c * cell;
+                    iy := gy + r * cell;
+                    DrawInvader(ix, iy, palette[r])
+                END
+            END
+        END;
+        tigrFillRect(win, playerX, playerY, playerW, playerH, green);
+        tigrFillRect(win, playerX + (playerW - 4) / 2, playerY - 5, 4, 5, green);
+        IF bulletActive THEN
+            tigrFillRect(win, bulletX, bulletY, bulletW, bulletH, white)
+        END;
+        IF bombActive THEN
+            tigrFillRect(win, bombX, bombY, bombW, bombH, red)
+        END;
+        scoreText := "SCORE ";
+        CardToText(score * 10, msgbuf);
+        n := 6;
+        WHILE msgbuf[n - 6] # 0C DO
+            scoreText[n] := msgbuf[n - 6];
+            n := n + 1
+        END;
+        scoreText[n] := 0C;
+        tigrPrint(win, tfont, 6, 4, white, scoreText);
+        tigrPrint(win, tfont, 6, 14, yellow, "L/R move SPACE fire ESC quit");
+
+        IF state = s_win THEN
+            tw := tigrTextWidth(tfont, "YOU WIN");
+            IF tw < INTEGER(winW) THEN
+                msgX := (winW - CARDINAL(tw)) / 2
+            ELSE
+                msgX := 0
+            END;
+            tigrPrint(win, tfont, msgX, 110, green, "YOU WIN");
+            tw := tigrTextWidth(tfont, "SPACE to restart");
+            IF tw < INTEGER(winW) THEN
+                msgX := (winW - CARDINAL(tw)) / 2
+            ELSE
+                msgX := 0
+            END;
+            tigrPrint(win, tfont, msgX, 130, white, "SPACE to restart")
+        ELSIF state = s_lose THEN
+            tw := tigrTextWidth(tfont, "GAME OVER");
+            IF tw < INTEGER(winW) THEN
+                msgX := (winW - CARDINAL(tw)) / 2
+            ELSE
+                msgX := 0
+            END;
+            tigrPrint(win, tfont, msgX, 110, red, "GAME OVER");
+            tw := tigrTextWidth(tfont, "SPACE to restart");
+            IF tw < INTEGER(winW) THEN
+                msgX := (winW - CARDINAL(tw)) / 2
+            ELSE
+                msgX := 0
+            END;
+            tigrPrint(win, tfont, msgX, 130, white, "SPACE to restart")
+        END
+    END Draw;
+
+BEGIN
+    win := tigrWindow(winW, winH, "Space Invaders", 0);
+    black  := tigrRGB(0, 0, 0);
+    white  := tigrRGB(255, 255, 255);
+    red    := tigrRGB(255, 60, 60);
+    green  := tigrRGB(60, 230, 60);
+    yellow := tigrRGB(255, 220, 60);
+    palette[0] := yellow;
+    palette[1] := green;
+    palette[2] := red;
+    palette[3] := white;
+    playerY := winH - playerH - 4;
+
+    StartGame;
+
+    lastTime := tigrTime();
+    WHILE (NOT (tigrClosed(win) > 0)) AND (NOT (tigrKeyDown(win, TK_ESCAPE) > 0)) DO
+        now := tigrTime();
+        dt := now - lastTime;
+        lastTime := now;
+
+        IF state = s_play THEN
+            (* Player movement *)
+            IF (tigrKeyHeld(win, TK_LEFT) > 0) AND (playerX > border) THEN
+                playerX := playerX - 3
+            END;
+            IF (tigrKeyHeld(win, TK_RIGHT) > 0) AND
+               (playerX + playerW < winW - border) THEN
+                playerX := playerX + 3
+            END;
+            IF tigrKeyDown(win, TK_SPACE) > 0 THEN
+                Shoot
+            END;
+
+            (* Invader ticks *)
+            moveAcc := moveAcc + dt;
+            WHILE moveAcc >= moveInt DO
+                moveAcc := moveAcc - moveInt;
+                MoveInvaders
+            END;
+
+            MoveBullet;
+            MoveBomb
+        ELSIF tigrKeyDown(win, TK_SPACE) > 0 THEN
+            StartGame
+        END;
+
+        Draw;
+        tigrUpdate(win)
+    END;
+    tigrFree(win)
+END invaders.