pong.mod 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. MODULE pong;
  2. (* Classic Pong using the tigr Modula-2 binding. No assets: everything
  3. is drawn with tigrFillRect and the built-in font.
  4. Controls (two-player capable):
  5. W / S move the left paddle
  6. UP / DOWN move the right paddle (arrow keys take over from
  7. the computer opponent)
  8. SPACE serve / restart after a point or game over
  9. ESC quit
  10. First to 5 points wins the match. The ball speeds up with every
  11. paddle hit; angle bounces off the paddle depending on where it hits.
  12. Compilation (from modula2/):
  13. gcc -c ../tigr-master/tigr.c -o tigr.o
  14. gm2 -fiso -c helper.mod
  15. gm2 -fiso -I. tigr.o helper.o examples/pong/pong.mod \
  16. -o examples/pong/pong -lGL -lX11
  17. *)
  18. FROM tigr IMPORT TigrPtr, tigrWindow, tigrFree, tigrClosed, tigrClear,
  19. tigrUpdate, TPixelType, tigrFillRect, tigrPrint, tfont,
  20. tigrKeyDown, tigrKeyHeld, tigrTime, tigrTextWidth,
  21. TK_ESCAPE, TK_SPACE, TK_UP, TK_DOWN;
  22. FROM helper IMPORT tigrRGB;
  23. CONST
  24. winW = 640;
  25. winH = 400;
  26. paddleW = 8;
  27. paddleH = 60;
  28. paddleMargin = 12; (* gap between wall and paddle *)
  29. ballS = 8;
  30. centerX = 320;
  31. winScore = 5;
  32. ballSpeed0 = 220.0; (* px/s to begin each rally *)
  33. ballSpeedMax = 520.0;
  34. speedIncr = 1.08; (* multiplier per paddle hit *)
  35. paddleSpeedV = 380.0; (* player paddle speed, px/s *)
  36. cpuSpeedV = 330.0; (* computer paddle speed, px/s *)
  37. playerStartX = 12;
  38. cpuStartX = 620;
  39. TYPE
  40. State = (s_serve, s_play, s_over);
  41. VAR
  42. win : TigrPtr;
  43. state : State;
  44. playerY, cpuY : SHORTREAL; (* paddle top-left y *)
  45. bx, by : SHORTREAL; (* ball top-left *)
  46. bvx, bvy : SHORTREAL;
  47. ballSpeed : SHORTREAL;
  48. pScore, cScore : CARDINAL;
  49. seed : CARDINAL; (* tiny LCG *)
  50. dt : SHORTREAL; (* tigrTime: delta since last call *)
  51. black, white, green, red, cyan, yellow : TPixelType;
  52. i, n : CARDINAL;
  53. msgbuf : ARRAY [0..60] OF CHAR;
  54. tmp : ARRAY [0..10] OF CHAR;
  55. PROCEDURE CardToText(v : CARDINAL; VAR s : ARRAY OF CHAR);
  56. (* Converts v to a NUL-terminated decimal string in s. *)
  57. VAR
  58. m, j : CARDINAL;
  59. t : ARRAY [0..10] OF CHAR;
  60. BEGIN
  61. IF v = 0 THEN
  62. s[0] := '0';
  63. s[1] := 0C;
  64. RETURN
  65. END;
  66. m := 0;
  67. WHILE v > 0 DO
  68. t[m] := CHR(ORD('0') + (v MOD 10));
  69. m := m + 1;
  70. v := v DIV 10
  71. END;
  72. j := 0;
  73. WHILE m > 0 DO
  74. m := m - 1;
  75. s[j] := t[m];
  76. j := j + 1
  77. END;
  78. s[j] := 0C
  79. END CardToText;
  80. PROCEDURE Append(VAR dst : ARRAY OF CHAR; VAR len : CARDINAL;
  81. src : ARRAY OF CHAR);
  82. (* Appends the NUL-terminated src to dst, tracking the length in len. *)
  83. VAR
  84. k : CARDINAL;
  85. BEGIN
  86. k := 0;
  87. WHILE src[k] # 0C DO
  88. dst[len] := src[k];
  89. len := len + 1;
  90. k := k + 1
  91. END
  92. END Append;
  93. PROCEDURE Serve(toward : BOOLEAN);
  94. (* Places the ball at the center with a randomized angle. The ball
  95. travels towards the right (TRUE) or the left (FALSE) at the
  96. current ballSpeed. *)
  97. VAR
  98. r : SHORTREAL;
  99. BEGIN
  100. seed := (seed * 1103515245 + 12345) MOD 2147483648;
  101. r := (SHORTREAL(seed MOD 1000) - 500.0) / 1000.0; (* -0.5..0.5 *)
  102. IF r < 0.0 THEN
  103. IF r > -0.15 THEN
  104. r := -0.15
  105. END
  106. ELSIF r < 0.15 THEN
  107. r := 0.15
  108. END;
  109. bx := 316.0;
  110. by := 196.0;
  111. IF toward THEN
  112. bvx := ballSpeed * 0.62
  113. ELSE
  114. bvx := -ballSpeed * 0.62
  115. END;
  116. bvy := ballSpeed * 0.78 * r;
  117. state := s_serve
  118. END Serve;
  119. PROCEDURE StartGame;
  120. BEGIN
  121. seed := 12345;
  122. playerY := 170.0;
  123. cpuY := playerY;
  124. pScore := 0;
  125. cScore := 0;
  126. ballSpeed := ballSpeed0;
  127. seed := (seed * 1103515245 + 12345) MOD 2147483648;
  128. IF (seed MOD 2) = 0 THEN
  129. Serve(TRUE)
  130. ELSE
  131. Serve(FALSE)
  132. END
  133. END StartGame;
  134. PROCEDURE MovePaddle(VAR py : SHORTREAL; down : BOOLEAN;
  135. spd : SHORTREAL);
  136. (* Moves a paddle by spd * dt pixels down (TRUE) or up (FALSE),
  137. clamped to the playfield. *)
  138. VAR
  139. next : SHORTREAL;
  140. BEGIN
  141. IF down THEN
  142. next := py + spd * dt
  143. ELSE
  144. next := py - spd * dt
  145. END;
  146. IF next < 0.0 THEN
  147. next := 0.0
  148. END;
  149. IF next > 340.0 THEN
  150. next := 340.0
  151. END;
  152. py := next
  153. END MovePaddle;
  154. PROCEDURE CpuMove;
  155. (* Simple AI: chases the ball once it moves past center and heads
  156. towards the computer's side. *)
  157. VAR
  158. tgt, diff, step : SHORTREAL;
  159. BEGIN
  160. IF (bvx > 0.0) AND (bx > 320.0) THEN
  161. tgt := by - 26.0;
  162. diff := tgt - cpuY;
  163. IF diff > 0.0 THEN
  164. step := cpuSpeedV * dt;
  165. IF diff < step THEN
  166. cpuY := tgt
  167. ELSE
  168. cpuY := cpuY + step
  169. END
  170. ELSIF diff < 0.0 THEN
  171. step := cpuSpeedV * dt;
  172. IF -diff < step THEN
  173. cpuY := tgt
  174. ELSE
  175. cpuY := cpuY - step
  176. END
  177. END;
  178. IF cpuY < 0.0 THEN
  179. cpuY := 0.0
  180. ELSIF cpuY > 340.0 THEN
  181. cpuY := 340.0
  182. END
  183. END
  184. END CpuMove;
  185. PROCEDURE BumpSpeed;
  186. BEGIN
  187. ballSpeed := ballSpeed * speedIncr;
  188. IF ballSpeed > ballSpeedMax THEN
  189. ballSpeed := ballSpeedMax
  190. END
  191. END BumpSpeed;
  192. PROCEDURE BallVsPad;
  193. (* Reflects the ball off either paddle, steering it by where it
  194. hits the paddle surface. *)
  195. VAR
  196. rel, half : SHORTREAL;
  197. BEGIN
  198. IF (bvx < 0.0) AND (bx <= 20.0)
  199. AND (bx + 8.0 >= 12.0)
  200. AND (by + 8.0 > playerY)
  201. AND (by < playerY + 60.0) THEN
  202. half := 30.0;
  203. rel := (by + 4.0 - (playerY + half)) / half;
  204. IF rel < -1.0 THEN
  205. rel := -1.0
  206. ELSIF rel > 1.0 THEN
  207. rel := 1.0
  208. END;
  209. bx := 20.0;
  210. bvx := ballSpeed * 0.62;
  211. bvy := rel * ballSpeed * 0.78;
  212. BumpSpeed
  213. ELSIF (bvx > 0.0) AND (bx + 8.0 >= 620.0)
  214. AND (bx <= 628.0)
  215. AND (by + 8.0 > cpuY)
  216. AND (by < cpuY + 60.0) THEN
  217. half := 30.0;
  218. rel := (by + 4.0 - (cpuY + half)) / half;
  219. IF rel < -1.0 THEN
  220. rel := -1.0
  221. ELSIF rel > 1.0 THEN
  222. rel := 1.0
  223. END;
  224. bx := 612.0;
  225. bvx := -ballSpeed * 0.62;
  226. bvy := rel * ballSpeed * 0.78;
  227. BumpSpeed
  228. END
  229. END BallVsPad;
  230. PROCEDURE Score;
  231. (* Awards a point when the ball leaves the playfield and serves
  232. towards the conceding player. *)
  233. BEGIN
  234. IF bx < -8.0 THEN
  235. cScore := cScore + 1;
  236. IF cScore >= winScore THEN
  237. state := s_over
  238. ELSE
  239. ballSpeed := ballSpeed0;
  240. Serve(FALSE)
  241. END
  242. ELSIF bx > 640.0 THEN
  243. pScore := pScore + 1;
  244. IF pScore >= winScore THEN
  245. state := s_over
  246. ELSE
  247. ballSpeed := ballSpeed0;
  248. Serve(TRUE)
  249. END
  250. END
  251. END Score;
  252. PROCEDURE MoveBall;
  253. BEGIN
  254. bx := bx + bvx * dt;
  255. by := by + bvy * dt;
  256. IF by <= 0.0 THEN
  257. by := 0.0;
  258. IF bvy < 0.0 THEN
  259. bvy := -bvy
  260. END
  261. ELSIF by + 8.0 >= 400.0 THEN
  262. by := 392.0;
  263. IF bvy > 0.0 THEN
  264. bvy := -bvy
  265. END
  266. END;
  267. BallVsPad;
  268. Score
  269. END MoveBall;
  270. PROCEDURE BuildScoreText;
  271. BEGIN
  272. n := 0;
  273. Append(msgbuf, n, "YOU ");
  274. CardToText(pScore, tmp);
  275. Append(msgbuf, n, tmp);
  276. Append(msgbuf, n, " ");
  277. CardToText(cScore, tmp);
  278. Append(msgbuf, n, tmp);
  279. Append(msgbuf, n, " CPU");
  280. msgbuf[n] := 0C
  281. END BuildScoreText;
  282. PROCEDURE CenterPrint(y : CARDINAL; msg : ARRAY OF CHAR;
  283. col : TPixelType);
  284. VAR
  285. tw : INTEGER;
  286. x : CARDINAL;
  287. BEGIN
  288. tw := tigrTextWidth(tfont, msg);
  289. IF tw < INTEGER(winW) THEN
  290. x := (winW - CARDINAL(tw)) / 2
  291. ELSE
  292. x := 0
  293. END;
  294. tigrPrint(win, tfont, x, y, col, msg)
  295. END CenterPrint;
  296. PROCEDURE Draw;
  297. BEGIN
  298. tigrClear(win, black);
  299. (* dashed center line *)
  300. i := 4;
  301. WHILE i < winH DO
  302. tigrFillRect(win, 319, i, 2, 8, white);
  303. i := i + 16
  304. END;
  305. tigrFillRect(win, playerStartX, TRUNC(playerY), paddleW, paddleH,
  306. green);
  307. tigrFillRect(win, cpuStartX, TRUNC(cpuY), paddleW, paddleH, red);
  308. IF state # s_over THEN
  309. tigrFillRect(win, TRUNC(bx), TRUNC(by), ballS, ballS, white)
  310. END;
  311. tigrPrint(win, tfont, 6, 4, white, msgbuf);
  312. tigrPrint(win, tfont, 6, 16, yellow,
  313. "W/S move UP/DOWN CPU SPACE serve ESC quit");
  314. IF state = s_serve THEN
  315. CenterPrint(372, "SPACE to serve", white)
  316. ELSIF state = s_over THEN
  317. IF pScore > cScore THEN
  318. CenterPrint(180, "YOU WIN!", green)
  319. ELSE
  320. CenterPrint(180, "CPU WINS", red)
  321. END;
  322. CenterPrint(204, "SPACE to restart", white)
  323. END
  324. END Draw;
  325. BEGIN
  326. win := tigrWindow(winW, winH, "Pong", 0);
  327. black := tigrRGB(0, 0, 0);
  328. white := tigrRGB(255, 255, 255);
  329. green := tigrRGB(60, 230, 60);
  330. red := tigrRGB(255, 60, 60);
  331. cyan := tigrRGB(60, 220, 220);
  332. yellow := tigrRGB(255, 220, 60);
  333. StartGame;
  334. WHILE (NOT (tigrClosed(win) > 0)) AND
  335. (NOT (tigrKeyDown(win, TK_ESCAPE) > 0)) DO
  336. dt := tigrTime(); (* seconds since the previous call *)
  337. IF state = s_over THEN
  338. IF tigrKeyDown(win, TK_SPACE) > 0 THEN
  339. StartGame
  340. END
  341. ELSE
  342. (* player & manual second paddle *)
  343. IF tigrKeyHeld(win, ORD('W')) > 0 THEN
  344. MovePaddle(playerY, FALSE, paddleSpeedV)
  345. END;
  346. IF tigrKeyHeld(win, ORD('S')) > 0 THEN
  347. MovePaddle(playerY, TRUE, paddleSpeedV)
  348. END;
  349. IF (tigrKeyHeld(win, TK_UP) > 0) OR
  350. (tigrKeyHeld(win, TK_DOWN) > 0) THEN
  351. IF tigrKeyHeld(win, TK_UP) > 0 THEN
  352. MovePaddle(cpuY, FALSE, paddleSpeedV)
  353. END;
  354. IF tigrKeyHeld(win, TK_DOWN) > 0 THEN
  355. MovePaddle(cpuY, TRUE, paddleSpeedV)
  356. END
  357. ELSE
  358. CpuMove
  359. END;
  360. IF state = s_serve THEN
  361. IF tigrKeyDown(win, TK_SPACE) > 0 THEN
  362. state := s_play
  363. END
  364. ELSE
  365. MoveBall
  366. END
  367. END;
  368. BuildScoreText;
  369. Draw;
  370. tigrUpdate(win)
  371. END;
  372. tigrFree(win)
  373. END pong.