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