|
|
@@ -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.
|