invaders.mod 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. MODULE invaders;
  2. (* Simple "Space Invaders" using the tigr Modula-2 binding.
  3. No assets: everything is drawn with tigrFillRect. Built-in font.
  4. Controls: LEFT/RIGHT move, SPACE fire, R restart (after game over),
  5. ESC quit.
  6. Compilation (from modula2/):
  7. gcc -c tigr.c
  8. gm2 -fiso -c helper.mod
  9. gm2 -fiso -I. tigr.o helper.o examples/invaders/invaders.mod \
  10. -o examples/invaders/invaders -lGL -lX11
  11. *)
  12. FROM tigr IMPORT TigrPtr, tigrWindow, tigrFree, tigrClosed, tigrClear,
  13. tigrUpdate, TPixelType, tigrFillRect, tigrPrint, tfont,
  14. tigrKeyDown, tigrKeyHeld, tigrTime, tigrTextWidth,
  15. TK_ESCAPE, TK_SPACE, TK_LEFT, TK_RIGHT;
  16. FROM helper IMPORT tigrRGB;
  17. CONST
  18. winW = 320;
  19. winH = 240;
  20. invRows = 4;
  21. invCols = 8;
  22. cell = 16; (* invader sprite box *)
  23. playerW = 24;
  24. playerH = 6;
  25. bulletW = 3;
  26. bulletH = 9;
  27. bombW = 3;
  28. bombH = 8;
  29. bulletSpeed = 6;
  30. bombSpeed = 3;
  31. moveStep = 4;
  32. dropStep = 10;
  33. border = 4;
  34. TYPE
  35. State = (s_play, s_win, s_lose);
  36. VAR
  37. win : TigrPtr;
  38. alive : ARRAY [0..invRows - 1] OF ARRAY [0..invCols - 1] OF BOOLEAN;
  39. gx, gy : CARDINAL; (* invader grid origin *)
  40. dir : INTEGER; (* -1 left, +1 right *)
  41. moveAcc, moveInt : SHORTREAL;
  42. playerX, playerY : CARDINAL;
  43. bulletActive : BOOLEAN;
  44. bulletX, bulletY : CARDINAL;
  45. bombActive : BOOLEAN;
  46. bombX, bombY : CARDINAL;
  47. score, lives : CARDINAL;
  48. seed : CARDINAL; (* tiny LCG *)
  49. state : State;
  50. lastTime, now : SHORTREAL;
  51. dt : SHORTREAL;
  52. black, white, red, green, yellow : TPixelType;
  53. palette : ARRAY [0..invRows - 1] OF TPixelType;
  54. row, col, n : CARDINAL;
  55. scoreText : ARRAY [0..20] OF CHAR;
  56. msgbuf : ARRAY [0..60] OF CHAR;
  57. PROCEDURE CardToText(n : CARDINAL; VAR s : ARRAY OF CHAR);
  58. (* Converts n to a NUL-terminated decimal string in s. *)
  59. VAR
  60. k : CARDINAL;
  61. tmp : ARRAY [0..10] OF CHAR;
  62. j : CARDINAL;
  63. BEGIN
  64. IF n = 0 THEN
  65. s[0] := '0';
  66. s[1] := 0C;
  67. RETURN
  68. END;
  69. k := 0;
  70. WHILE n > 0 DO
  71. tmp[k] := CHR(ORD('0') + (n MOD 10));
  72. k := k + 1;
  73. n := n DIV 10
  74. END;
  75. j := 0;
  76. WHILE k > 0 DO
  77. k := k - 1;
  78. s[j] := tmp[k];
  79. j := j + 1
  80. END;
  81. s[j] := 0C
  82. END CardToText;
  83. PROCEDURE StartGame;
  84. VAR
  85. r, c : CARDINAL;
  86. BEGIN
  87. gx := 32;
  88. gy := 20;
  89. dir := 1;
  90. moveAcc := 0.0;
  91. moveInt := 0.55;
  92. score := 0;
  93. lives := 3;
  94. playerX := (winW - playerW) / 2;
  95. bulletActive := FALSE;
  96. bombActive := FALSE;
  97. seed := 1;
  98. state := s_play;
  99. FOR r := 0 TO invRows - 1 DO
  100. FOR c := 0 TO invCols - 1 DO
  101. alive[r][c] := TRUE
  102. END
  103. END
  104. END StartGame;
  105. PROCEDURE CountAlive() : CARDINAL;
  106. VAR
  107. r, c, cnt : CARDINAL;
  108. BEGIN
  109. cnt := 0;
  110. FOR r := 0 TO invRows - 1 DO
  111. FOR c := 0 TO invCols - 1 DO
  112. IF alive[r][c] THEN
  113. cnt := cnt + 1
  114. END
  115. END
  116. END;
  117. RETURN cnt
  118. END CountAlive;
  119. PROCEDURE MoveInvaders;
  120. VAR
  121. hitBound : BOOLEAN;
  122. cnt : CARDINAL;
  123. BEGIN
  124. IF dir < 0 THEN
  125. hitBound := gx < (border + moveStep)
  126. ELSE
  127. hitBound := gx + moveStep + invCols * cell > winW - border
  128. END;
  129. IF hitBound THEN
  130. gy := gy + dropStep;
  131. dir := -dir;
  132. IF gy + invRows * cell + 8 >= winH THEN
  133. state := s_lose
  134. END
  135. ELSE
  136. IF dir < 0 THEN
  137. gx := gx - moveStep
  138. ELSE
  139. gx := gx + moveStep
  140. END
  141. END;
  142. (* Speeds up as invaders are destroyed, and may drop a bomb. *)
  143. cnt := CountAlive();
  144. moveInt := 0.55 - (SHORTREAL(CountAlive() - 1)
  145. * 0.35) / SHORTREAL(invRows * invCols - 1);
  146. seed := (seed * 1103515245 + 12345) MOD 2147483648;
  147. IF (NOT bombActive) AND (cnt > 0) AND (seed MOD 3 = 0) THEN
  148. col := seed MOD invCols;
  149. row := 0;
  150. WHILE (row < invRows) AND (NOT alive[row][col]) DO
  151. row := row + 1
  152. END;
  153. IF row < invRows THEN
  154. bombActive := TRUE;
  155. bombX := gx + col * cell + (cell - bombW) / 2;
  156. bombY := gy + row * cell + cell
  157. END
  158. END
  159. END MoveInvaders;
  160. PROCEDURE Shoot;
  161. BEGIN
  162. IF NOT bulletActive THEN
  163. bulletActive := TRUE;
  164. bulletX := playerX + playerW / 2 - bulletW / 2;
  165. bulletY := playerY - bulletH
  166. END
  167. END Shoot;
  168. PROCEDURE MoveBullet;
  169. VAR
  170. r, c : CARDINAL;
  171. ix, iy : CARDINAL;
  172. overlaps : BOOLEAN;
  173. BEGIN
  174. IF bulletActive THEN
  175. IF bulletY > border THEN
  176. bulletY := bulletY - bulletSpeed
  177. ELSE
  178. bulletActive := FALSE
  179. END;
  180. overlaps := FALSE;
  181. r := 0;
  182. WHILE (r < invRows) AND (NOT overlaps) DO
  183. c := 0;
  184. WHILE (c < invCols) AND (NOT overlaps) DO
  185. IF alive[r][c] THEN
  186. ix := gx + c * cell;
  187. iy := gy + r * cell;
  188. IF (bulletX + bulletW > ix) AND (bulletX < ix + cell)
  189. AND (bulletY < iy + cell) AND (bulletY + bulletH > iy) THEN
  190. alive[r][c] := FALSE;
  191. score := score + 10;
  192. overlaps := TRUE
  193. END
  194. END;
  195. c := c + 1
  196. END;
  197. r := r + 1
  198. END;
  199. IF overlaps THEN
  200. bulletActive := FALSE
  201. END;
  202. IF CountAlive() = 0 THEN
  203. state := s_win
  204. END
  205. END
  206. END MoveBullet;
  207. PROCEDURE MoveBomb;
  208. BEGIN
  209. IF bombActive THEN
  210. bombY := bombY + bombSpeed;
  211. IF bombY >= winH THEN
  212. bombActive := FALSE
  213. ELSIF (bombX + bombW > playerX) AND (bombX < playerX + playerW)
  214. AND (bombY + bombH > playerY) AND (bombY < playerY + playerH) THEN
  215. bombActive := FALSE;
  216. IF lives > 1 THEN
  217. lives := lives - 1;
  218. playerX := (winW - playerW) / 2
  219. ELSE
  220. lives := 0;
  221. state := s_lose
  222. END
  223. END
  224. END
  225. END MoveBomb;
  226. PROCEDURE DrawInvader(x, y : CARDINAL; color : TPixelType);
  227. BEGIN
  228. tigrFillRect(win, x + 3, y, cell - 6, 3, color); (* antenna *)
  229. tigrFillRect(win, x + 1, y + 3, cell - 2, 8, color); (* body *)
  230. tigrFillRect(win, x + 4, y + 5, 2, 3, black); (* left eye *)
  231. tigrFillRect(win, x + 10, y + 5, 2, 3, black); (* right eye *)
  232. tigrFillRect(win, x, y + 11, 4, 3, color); (* feet *)
  233. tigrFillRect(win, x + 12, y + 11, 4, 3, color)
  234. END DrawInvader;
  235. PROCEDURE Draw;
  236. VAR
  237. r, c : CARDINAL;
  238. ix, iy : CARDINAL;
  239. tw : INTEGER;
  240. msgX : CARDINAL;
  241. BEGIN
  242. tigrClear(win, black);
  243. FOR r := 0 TO invRows - 1 DO
  244. FOR c := 0 TO invCols - 1 DO
  245. IF alive[r][c] THEN
  246. ix := gx + c * cell;
  247. iy := gy + r * cell;
  248. DrawInvader(ix, iy, palette[r])
  249. END
  250. END
  251. END;
  252. tigrFillRect(win, playerX, playerY, playerW, playerH, green);
  253. tigrFillRect(win, playerX + (playerW - 4) / 2, playerY - 5, 4, 5, green);
  254. IF bulletActive THEN
  255. tigrFillRect(win, bulletX, bulletY, bulletW, bulletH, white)
  256. END;
  257. IF bombActive THEN
  258. tigrFillRect(win, bombX, bombY, bombW, bombH, red)
  259. END;
  260. scoreText := "SCORE ";
  261. CardToText(score * 10, msgbuf);
  262. n := 6;
  263. WHILE msgbuf[n - 6] # 0C DO
  264. scoreText[n] := msgbuf[n - 6];
  265. n := n + 1
  266. END;
  267. scoreText[n] := 0C;
  268. tigrPrint(win, tfont, 6, 4, white, scoreText);
  269. tigrPrint(win, tfont, 6, 14, yellow, "L/R move SPACE fire ESC quit");
  270. IF state = s_win THEN
  271. tw := tigrTextWidth(tfont, "YOU WIN");
  272. IF tw < INTEGER(winW) THEN
  273. msgX := (winW - CARDINAL(tw)) / 2
  274. ELSE
  275. msgX := 0
  276. END;
  277. tigrPrint(win, tfont, msgX, 110, green, "YOU WIN");
  278. tw := tigrTextWidth(tfont, "SPACE to restart");
  279. IF tw < INTEGER(winW) THEN
  280. msgX := (winW - CARDINAL(tw)) / 2
  281. ELSE
  282. msgX := 0
  283. END;
  284. tigrPrint(win, tfont, msgX, 130, white, "SPACE to restart")
  285. ELSIF state = s_lose THEN
  286. tw := tigrTextWidth(tfont, "GAME OVER");
  287. IF tw < INTEGER(winW) THEN
  288. msgX := (winW - CARDINAL(tw)) / 2
  289. ELSE
  290. msgX := 0
  291. END;
  292. tigrPrint(win, tfont, msgX, 110, red, "GAME OVER");
  293. tw := tigrTextWidth(tfont, "SPACE to restart");
  294. IF tw < INTEGER(winW) THEN
  295. msgX := (winW - CARDINAL(tw)) / 2
  296. ELSE
  297. msgX := 0
  298. END;
  299. tigrPrint(win, tfont, msgX, 130, white, "SPACE to restart")
  300. END
  301. END Draw;
  302. BEGIN
  303. win := tigrWindow(winW, winH, "Space Invaders", 0);
  304. black := tigrRGB(0, 0, 0);
  305. white := tigrRGB(255, 255, 255);
  306. red := tigrRGB(255, 60, 60);
  307. green := tigrRGB(60, 230, 60);
  308. yellow := tigrRGB(255, 220, 60);
  309. palette[0] := yellow;
  310. palette[1] := green;
  311. palette[2] := red;
  312. palette[3] := white;
  313. playerY := winH - playerH - 4;
  314. StartGame;
  315. lastTime := tigrTime();
  316. WHILE (NOT (tigrClosed(win) > 0)) AND (NOT (tigrKeyDown(win, TK_ESCAPE) > 0)) DO
  317. now := tigrTime();
  318. dt := now - lastTime;
  319. lastTime := now;
  320. IF state = s_play THEN
  321. (* Player movement *)
  322. IF (tigrKeyHeld(win, TK_LEFT) > 0) AND (playerX > border) THEN
  323. playerX := playerX - 3
  324. END;
  325. IF (tigrKeyHeld(win, TK_RIGHT) > 0) AND
  326. (playerX + playerW < winW - border) THEN
  327. playerX := playerX + 3
  328. END;
  329. IF tigrKeyDown(win, TK_SPACE) > 0 THEN
  330. Shoot
  331. END;
  332. (* Invader ticks *)
  333. moveAcc := moveAcc + dt;
  334. WHILE moveAcc >= moveInt DO
  335. moveAcc := moveAcc - moveInt;
  336. MoveInvaders
  337. END;
  338. MoveBullet;
  339. MoveBomb
  340. ELSIF tigrKeyDown(win, TK_SPACE) > 0 THEN
  341. StartGame
  342. END;
  343. Draw;
  344. tigrUpdate(win)
  345. END;
  346. tigrFree(win)
  347. END invaders.