|
|
@@ -2,8 +2,8 @@ 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.
|
|
|
+ Controls: LEFT/RIGHT move, SPACE hold to autofire, R restart
|
|
|
+ (after game over), ESC quit.
|
|
|
|
|
|
Compilation (from modula2/):
|
|
|
gcc -c tigr.c
|
|
|
@@ -21,20 +21,22 @@ 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;
|
|
|
+ invRows = 4;
|
|
|
+ invCols = 8;
|
|
|
+ cell = 16; (* invader sprite box *)
|
|
|
+ maxBombs = 5;
|
|
|
+ playerW = 24;
|
|
|
+ playerH = 6;
|
|
|
+ bulletW = 3;
|
|
|
+ bulletH = 9;
|
|
|
+ bombW = 3;
|
|
|
+ bombH = 8;
|
|
|
bulletSpeed = 6;
|
|
|
- bombSpeed = 3;
|
|
|
+ bombSpeed = 4;
|
|
|
moveStep = 4;
|
|
|
- dropStep = 10;
|
|
|
+ dropStep = 12;
|
|
|
border = 4;
|
|
|
+ autofire = 0.3;
|
|
|
|
|
|
TYPE
|
|
|
State = (s_play, s_win, s_lose);
|
|
|
@@ -44,20 +46,21 @@ VAR
|
|
|
alive : ARRAY [0..invRows - 1] OF ARRAY [0..invCols - 1] OF BOOLEAN;
|
|
|
gx, gy : CARDINAL; (* invader grid origin *)
|
|
|
dir : INTEGER; (* -1 left, +1 right *)
|
|
|
+ frame : CARDINAL; (* 0/1 invader sprite frame *)
|
|
|
moveAcc, moveInt : SHORTREAL;
|
|
|
+ fireAcc : SHORTREAL;
|
|
|
playerX, playerY : CARDINAL;
|
|
|
bulletActive : BOOLEAN;
|
|
|
bulletX, bulletY : CARDINAL;
|
|
|
- bombActive : BOOLEAN;
|
|
|
- bombX, bombY : CARDINAL;
|
|
|
+ bombFlags : ARRAY [0..maxBombs - 1] OF BOOLEAN;
|
|
|
+ bombX, bombY : ARRAY [0..maxBombs - 1] OF CARDINAL;
|
|
|
score, lives : CARDINAL;
|
|
|
seed : CARDINAL; (* tiny LCG *)
|
|
|
state : State;
|
|
|
- lastTime, now : SHORTREAL;
|
|
|
- dt : SHORTREAL;
|
|
|
- black, white, red, green, yellow : TPixelType;
|
|
|
+ dt : SHORTREAL; (* tigrTime: delta since last call *)
|
|
|
+ black, white, red, green, yellow, bombColor : TPixelType;
|
|
|
palette : ARRAY [0..invRows - 1] OF TPixelType;
|
|
|
- row, col, n : CARDINAL;
|
|
|
+ row, col, n, slot : CARDINAL;
|
|
|
scoreText : ARRAY [0..20] OF CHAR;
|
|
|
msgbuf : ARRAY [0..60] OF CHAR;
|
|
|
|
|
|
@@ -100,13 +103,17 @@ VAR
|
|
|
gx := 32;
|
|
|
gy := 20;
|
|
|
dir := 1;
|
|
|
+ frame := 0;
|
|
|
moveAcc := 0.0;
|
|
|
- moveInt := 0.55;
|
|
|
+ moveInt := 0.5;
|
|
|
+ fireAcc := 0.0;
|
|
|
score := 0;
|
|
|
lives := 3;
|
|
|
playerX := (winW - playerW) / 2;
|
|
|
bulletActive := FALSE;
|
|
|
- bombActive := FALSE;
|
|
|
+ FOR slot := 0 TO maxBombs - 1 DO
|
|
|
+ bombFlags[slot] := FALSE
|
|
|
+ END;
|
|
|
seed := 1;
|
|
|
state := s_play;
|
|
|
FOR r := 0 TO invRows - 1 DO
|
|
|
@@ -158,25 +165,57 @@ VAR
|
|
|
gx := gx + moveStep
|
|
|
END
|
|
|
END;
|
|
|
+ frame := 1 - frame;
|
|
|
|
|
|
- (* Speeds up as invaders are destroyed, and may drop a bomb. *)
|
|
|
+ (* Speeds up as invaders are destroyed. *)
|
|
|
cnt := CountAlive();
|
|
|
- moveInt := 0.55 - (SHORTREAL(CountAlive() - 1)
|
|
|
- * 0.35) / SHORTREAL(invRows * invCols - 1);
|
|
|
+ IF cnt > 0 THEN
|
|
|
+ moveInt := 0.5 - (SHORTREAL(cnt - 1)
|
|
|
+ * 0.38) / SHORTREAL(invRows * invCols - 1)
|
|
|
+ END
|
|
|
+ END MoveInvaders;
|
|
|
+
|
|
|
+ PROCEDURE TrySpawnBomb;
|
|
|
+
|
|
|
+ (* Drops a bomb from a surviving invader, some aimed at the player. *)
|
|
|
+
|
|
|
+ VAR
|
|
|
+ tgtCol, r2 : CARDINAL;
|
|
|
+ tgt : INTEGER;
|
|
|
+ aim : BOOLEAN;
|
|
|
+
|
|
|
+ BEGIN
|
|
|
+ slot := 0;
|
|
|
+ WHILE (slot < maxBombs) AND bombFlags[slot] DO
|
|
|
+ slot := slot + 1
|
|
|
+ END;
|
|
|
+ IF slot = maxBombs THEN
|
|
|
+ RETURN
|
|
|
+ END;
|
|
|
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
|
|
|
+ aim := seed MOD 4 = 0;
|
|
|
+ IF aim THEN
|
|
|
+ tgt := INTEGER(playerX + playerW / 2) - INTEGER(gx);
|
|
|
+ IF tgt < 0 THEN
|
|
|
+ tgtCol := 0
|
|
|
+ ELSIF tgt >= INTEGER(invCols * cell) THEN
|
|
|
+ tgtCol := invCols - 1
|
|
|
+ ELSE
|
|
|
+ tgtCol := tgt / cell
|
|
|
END
|
|
|
+ ELSE
|
|
|
+ tgtCol := seed MOD invCols
|
|
|
+ END;
|
|
|
+ r2 := 0;
|
|
|
+ WHILE (r2 < invRows) AND (NOT alive[r2][tgtCol]) DO
|
|
|
+ r2 := r2 + 1
|
|
|
+ END;
|
|
|
+ IF r2 < invRows THEN
|
|
|
+ bombFlags[slot] := TRUE;
|
|
|
+ bombX[slot] := gx + tgtCol * cell + (cell - bombW) / 2;
|
|
|
+ bombY[slot] := gy + r2 * cell + cell
|
|
|
END
|
|
|
- END MoveInvaders;
|
|
|
+ END TrySpawnBomb;
|
|
|
|
|
|
PROCEDURE Shoot;
|
|
|
|
|
|
@@ -191,9 +230,9 @@ VAR
|
|
|
PROCEDURE MoveBullet;
|
|
|
|
|
|
VAR
|
|
|
- r, c : CARDINAL;
|
|
|
- ix, iy : CARDINAL;
|
|
|
- overlaps : BOOLEAN;
|
|
|
+ r, c, ix, iy : CARDINAL;
|
|
|
+ overlaps : BOOLEAN;
|
|
|
+ slot2 : CARDINAL;
|
|
|
|
|
|
BEGIN
|
|
|
IF bulletActive THEN
|
|
|
@@ -202,6 +241,8 @@ VAR
|
|
|
ELSE
|
|
|
bulletActive := FALSE
|
|
|
END;
|
|
|
+
|
|
|
+ (* Versus invaders. *)
|
|
|
overlaps := FALSE;
|
|
|
r := 0;
|
|
|
WHILE (r < invRows) AND (NOT overlaps) DO
|
|
|
@@ -224,51 +265,87 @@ VAR
|
|
|
IF overlaps THEN
|
|
|
bulletActive := FALSE
|
|
|
END;
|
|
|
+
|
|
|
+ (* Versus bombs (shoot them down for +5). *)
|
|
|
+ IF bulletActive THEN
|
|
|
+ slot2 := 0;
|
|
|
+ WHILE slot2 < maxBombs DO
|
|
|
+ IF bombFlags[slot2]
|
|
|
+ AND (bulletX + bulletW > bombX[slot2])
|
|
|
+ AND (bulletX < bombX[slot2] + bombW)
|
|
|
+ AND (bulletY + bulletH > bombY[slot2])
|
|
|
+ AND (bulletY < bombY[slot2] + bombH) THEN
|
|
|
+ bombFlags[slot2] := FALSE;
|
|
|
+ score := score + 5;
|
|
|
+ bulletActive := FALSE;
|
|
|
+ slot2 := maxBombs
|
|
|
+ ELSE
|
|
|
+ slot2 := slot2 + 1
|
|
|
+ END
|
|
|
+ END
|
|
|
+ END;
|
|
|
+
|
|
|
IF CountAlive() = 0 THEN
|
|
|
state := s_win
|
|
|
END
|
|
|
END
|
|
|
END MoveBullet;
|
|
|
|
|
|
- PROCEDURE MoveBomb;
|
|
|
+ PROCEDURE MoveBombs;
|
|
|
|
|
|
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
|
|
|
+ FOR slot := 0 TO maxBombs - 1 DO
|
|
|
+ IF bombFlags[slot] THEN
|
|
|
+ bombY[slot] := bombY[slot] + bombSpeed;
|
|
|
+ IF bombY[slot] >= winH THEN
|
|
|
+ bombFlags[slot] := FALSE
|
|
|
+ ELSIF (bombX[slot] + bombW > playerX)
|
|
|
+ AND (bombX[slot] < playerX + playerW)
|
|
|
+ AND (bombY[slot] + bombH > playerY)
|
|
|
+ AND (bombY[slot] < playerY + playerH) THEN
|
|
|
+ bombFlags[slot] := FALSE;
|
|
|
+ IF lives > 1 THEN
|
|
|
+ lives := lives - 1;
|
|
|
+ playerX := (winW - playerW) / 2
|
|
|
+ ELSE
|
|
|
+ lives := 0;
|
|
|
+ state := s_lose
|
|
|
+ END
|
|
|
END
|
|
|
END
|
|
|
END
|
|
|
- END MoveBomb;
|
|
|
+ END MoveBombs;
|
|
|
|
|
|
PROCEDURE DrawInvader(x, y : CARDINAL; color : TPixelType);
|
|
|
|
|
|
+ (* Two classic invader frames, toggled on every move step. *)
|
|
|
+
|
|
|
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)
|
|
|
+ IF frame = 0 THEN
|
|
|
+ tigrFillRect(win, x + 3, y, 2, 3, color); (* antenna up *)
|
|
|
+ tigrFillRect(win, x + 11, y, 2, 3, color);
|
|
|
+ tigrFillRect(win, x + 1, y + 3, cell - 2, 8, color); (* body *)
|
|
|
+ tigrFillRect(win, x + 4, y + 5, 2, 3, black); (* eyes *)
|
|
|
+ tigrFillRect(win, x + 10, y + 5, 2, 3, black);
|
|
|
+ tigrFillRect(win, x, y + 11, 4, 3, color); (* feet out *)
|
|
|
+ tigrFillRect(win, x + 12, y + 11, 4, 3, color)
|
|
|
+ ELSE
|
|
|
+ tigrFillRect(win, x + 3, y + 1, 2, 2, color); (* antenna down *)
|
|
|
+ tigrFillRect(win, x + 11, y + 1, 2, 2, color);
|
|
|
+ tigrFillRect(win, x + 1, y + 3, cell - 2, 8, color);
|
|
|
+ tigrFillRect(win, x + 4, y + 5, 2, 3, black);
|
|
|
+ tigrFillRect(win, x + 10, y + 5, 2, 3, black);
|
|
|
+ tigrFillRect(win, x + 2, y + 11, 3, 3, color); (* feet tucked *)
|
|
|
+ tigrFillRect(win, x + 11, y + 11, 3, 3, color)
|
|
|
+ END
|
|
|
END DrawInvader;
|
|
|
|
|
|
PROCEDURE Draw;
|
|
|
|
|
|
VAR
|
|
|
- r, c : CARDINAL;
|
|
|
- ix, iy : CARDINAL;
|
|
|
- tw : INTEGER;
|
|
|
- msgX : CARDINAL;
|
|
|
+ r, c, ix, iy : CARDINAL;
|
|
|
+ tw : INTEGER;
|
|
|
+ msgX : CARDINAL;
|
|
|
|
|
|
BEGIN
|
|
|
tigrClear(win, black);
|
|
|
@@ -286,9 +363,13 @@ VAR
|
|
|
IF bulletActive THEN
|
|
|
tigrFillRect(win, bulletX, bulletY, bulletW, bulletH, white)
|
|
|
END;
|
|
|
- IF bombActive THEN
|
|
|
- tigrFillRect(win, bombX, bombY, bombW, bombH, red)
|
|
|
+ FOR slot := 0 TO maxBombs - 1 DO
|
|
|
+ IF bombFlags[slot] THEN
|
|
|
+ tigrFillRect(win, bombX[slot], bombY[slot], bombW, bombH,
|
|
|
+ bombColor)
|
|
|
+ END
|
|
|
END;
|
|
|
+
|
|
|
scoreText := "SCORE ";
|
|
|
CardToText(score * 10, msgbuf);
|
|
|
n := 6;
|
|
|
@@ -298,7 +379,8 @@ VAR
|
|
|
END;
|
|
|
scoreText[n] := 0C;
|
|
|
tigrPrint(win, tfont, 6, 4, white, scoreText);
|
|
|
- tigrPrint(win, tfont, 6, 14, yellow, "L/R move SPACE fire ESC quit");
|
|
|
+ tigrPrint(win, tfont, 6, 14, yellow,
|
|
|
+ "L/R move SPACE fire ESC quit");
|
|
|
|
|
|
IF state = s_win THEN
|
|
|
tw := tigrTextWidth(tfont, "YOU WIN");
|
|
|
@@ -335,11 +417,12 @@ VAR
|
|
|
|
|
|
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);
|
|
|
+ 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);
|
|
|
+ bombColor := tigrRGB(255, 160, 40);
|
|
|
palette[0] := yellow;
|
|
|
palette[1] := green;
|
|
|
palette[2] := red;
|
|
|
@@ -348,11 +431,8 @@ BEGIN
|
|
|
|
|
|
StartGame;
|
|
|
|
|
|
- lastTime := tigrTime();
|
|
|
WHILE (NOT (tigrClosed(win) > 0)) AND (NOT (tigrKeyDown(win, TK_ESCAPE) > 0)) DO
|
|
|
- now := tigrTime();
|
|
|
- dt := now - lastTime;
|
|
|
- lastTime := now;
|
|
|
+ dt := tigrTime(); (* seconds since the previous call *)
|
|
|
|
|
|
IF state = s_play THEN
|
|
|
(* Player movement *)
|
|
|
@@ -363,19 +443,24 @@ BEGIN
|
|
|
(playerX + playerW < winW - border) THEN
|
|
|
playerX := playerX + 3
|
|
|
END;
|
|
|
- IF tigrKeyDown(win, TK_SPACE) > 0 THEN
|
|
|
- Shoot
|
|
|
+ fireAcc := fireAcc + dt;
|
|
|
+ IF (tigrKeyDown(win, TK_SPACE) > 0)
|
|
|
+ OR ((tigrKeyHeld(win, TK_SPACE) > 0) AND (fireAcc >= autofire)) THEN
|
|
|
+ Shoot;
|
|
|
+ fireAcc := 0.0
|
|
|
END;
|
|
|
|
|
|
- (* Invader ticks *)
|
|
|
+ (* Invader ticks: move, then rain up to two bombs. *)
|
|
|
moveAcc := moveAcc + dt;
|
|
|
WHILE moveAcc >= moveInt DO
|
|
|
moveAcc := moveAcc - moveInt;
|
|
|
- MoveInvaders
|
|
|
+ MoveInvaders;
|
|
|
+ TrySpawnBomb;
|
|
|
+ TrySpawnBomb
|
|
|
END;
|
|
|
|
|
|
MoveBullet;
|
|
|
- MoveBomb
|
|
|
+ MoveBombs
|
|
|
ELSIF tigrKeyDown(win, TK_SPACE) > 0 THEN
|
|
|
StartGame
|
|
|
END;
|