| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- MODULE pong;
- (* Classic Pong using the tigr Modula-2 binding. No assets: everything
- is drawn with tigrFillRect and the built-in font.
- Controls (two-player capable):
- W / S move the left paddle
- UP / DOWN move the right paddle (arrow keys take over from
- the computer opponent)
- SPACE serve / restart after a point or game over
- ESC quit
- First to 5 points wins the match. The ball speeds up with every
- paddle hit; angle bounces off the paddle depending on where it hits.
- Compilation (from modula2/):
- gcc -c ../tigr-master/tigr.c -o tigr.o
- gm2 -fiso -c helper.mod
- gm2 -fiso -I. tigr.o helper.o examples/pong/pong.mod \
- -o examples/pong/pong -lGL -lX11
- *)
- FROM tigr IMPORT TigrPtr, tigrWindow, tigrFree, tigrClosed, tigrClear,
- tigrUpdate, TPixelType, tigrFillRect, tigrPrint, tfont,
- tigrKeyDown, tigrKeyHeld, tigrTime, tigrTextWidth,
- TK_ESCAPE, TK_SPACE, TK_UP, TK_DOWN;
- FROM helper IMPORT tigrRGB;
- CONST
- winW = 640;
- winH = 400;
- paddleW = 8;
- paddleH = 60;
- paddleMargin = 12; (* gap between wall and paddle *)
- ballS = 8;
- centerX = 320;
- winScore = 5;
- ballSpeed0 = 220.0; (* px/s to begin each rally *)
- ballSpeedMax = 520.0;
- speedIncr = 1.08; (* multiplier per paddle hit *)
- paddleSpeedV = 380.0; (* player paddle speed, px/s *)
- cpuSpeedV = 330.0; (* computer paddle speed, px/s *)
- playerStartX = 12;
- cpuStartX = 620;
- TYPE
- State = (s_serve, s_play, s_over);
- VAR
- win : TigrPtr;
- state : State;
- playerY, cpuY : SHORTREAL; (* paddle top-left y *)
- bx, by : SHORTREAL; (* ball top-left *)
- bvx, bvy : SHORTREAL;
- ballSpeed : SHORTREAL;
- pScore, cScore : CARDINAL;
- seed : CARDINAL; (* tiny LCG *)
- dt : SHORTREAL; (* tigrTime: delta since last call *)
- black, white, green, red, cyan, yellow : TPixelType;
- i, n : CARDINAL;
- msgbuf : ARRAY [0..60] OF CHAR;
- tmp : ARRAY [0..10] OF CHAR;
- PROCEDURE CardToText(v : CARDINAL; VAR s : ARRAY OF CHAR);
- (* Converts v to a NUL-terminated decimal string in s. *)
- VAR
- m, j : CARDINAL;
- t : ARRAY [0..10] OF CHAR;
- BEGIN
- IF v = 0 THEN
- s[0] := '0';
- s[1] := 0C;
- RETURN
- END;
- m := 0;
- WHILE v > 0 DO
- t[m] := CHR(ORD('0') + (v MOD 10));
- m := m + 1;
- v := v DIV 10
- END;
- j := 0;
- WHILE m > 0 DO
- m := m - 1;
- s[j] := t[m];
- j := j + 1
- END;
- s[j] := 0C
- END CardToText;
- PROCEDURE Append(VAR dst : ARRAY OF CHAR; VAR len : CARDINAL;
- src : ARRAY OF CHAR);
- (* Appends the NUL-terminated src to dst, tracking the length in len. *)
- VAR
- k : CARDINAL;
- BEGIN
- k := 0;
- WHILE src[k] # 0C DO
- dst[len] := src[k];
- len := len + 1;
- k := k + 1
- END
- END Append;
- PROCEDURE Serve(toward : BOOLEAN);
- (* Places the ball at the center with a randomized angle. The ball
- travels towards the right (TRUE) or the left (FALSE) at the
- current ballSpeed. *)
- VAR
- r : SHORTREAL;
- BEGIN
- seed := (seed * 1103515245 + 12345) MOD 2147483648;
- r := (SHORTREAL(seed MOD 1000) - 500.0) / 1000.0; (* -0.5..0.5 *)
- IF r < 0.0 THEN
- IF r > -0.15 THEN
- r := -0.15
- END
- ELSIF r < 0.15 THEN
- r := 0.15
- END;
- bx := 316.0;
- by := 196.0;
- IF toward THEN
- bvx := ballSpeed * 0.62
- ELSE
- bvx := -ballSpeed * 0.62
- END;
- bvy := ballSpeed * 0.78 * r;
- state := s_serve
- END Serve;
- PROCEDURE StartGame;
- BEGIN
- seed := 12345;
- playerY := 170.0;
- cpuY := playerY;
- pScore := 0;
- cScore := 0;
- ballSpeed := ballSpeed0;
- seed := (seed * 1103515245 + 12345) MOD 2147483648;
- IF (seed MOD 2) = 0 THEN
- Serve(TRUE)
- ELSE
- Serve(FALSE)
- END
- END StartGame;
- PROCEDURE MovePaddle(VAR py : SHORTREAL; down : BOOLEAN;
- spd : SHORTREAL);
- (* Moves a paddle by spd * dt pixels down (TRUE) or up (FALSE),
- clamped to the playfield. *)
- VAR
- next : SHORTREAL;
- BEGIN
- IF down THEN
- next := py + spd * dt
- ELSE
- next := py - spd * dt
- END;
- IF next < 0.0 THEN
- next := 0.0
- END;
- IF next > 340.0 THEN
- next := 340.0
- END;
- py := next
- END MovePaddle;
- PROCEDURE CpuMove;
- (* Simple AI: chases the ball once it moves past center and heads
- towards the computer's side. *)
- VAR
- tgt, diff, step : SHORTREAL;
- BEGIN
- IF (bvx > 0.0) AND (bx > 320.0) THEN
- tgt := by - 26.0;
- diff := tgt - cpuY;
- IF diff > 0.0 THEN
- step := cpuSpeedV * dt;
- IF diff < step THEN
- cpuY := tgt
- ELSE
- cpuY := cpuY + step
- END
- ELSIF diff < 0.0 THEN
- step := cpuSpeedV * dt;
- IF -diff < step THEN
- cpuY := tgt
- ELSE
- cpuY := cpuY - step
- END
- END;
- IF cpuY < 0.0 THEN
- cpuY := 0.0
- ELSIF cpuY > 340.0 THEN
- cpuY := 340.0
- END
- END
- END CpuMove;
- PROCEDURE BumpSpeed;
- BEGIN
- ballSpeed := ballSpeed * speedIncr;
- IF ballSpeed > ballSpeedMax THEN
- ballSpeed := ballSpeedMax
- END
- END BumpSpeed;
- PROCEDURE BallVsPad;
- (* Reflects the ball off either paddle, steering it by where it
- hits the paddle surface. *)
- VAR
- rel, half : SHORTREAL;
- BEGIN
- IF (bvx < 0.0) AND (bx <= 20.0)
- AND (bx + 8.0 >= 12.0)
- AND (by + 8.0 > playerY)
- AND (by < playerY + 60.0) THEN
- half := 30.0;
- rel := (by + 4.0 - (playerY + half)) / half;
- IF rel < -1.0 THEN
- rel := -1.0
- ELSIF rel > 1.0 THEN
- rel := 1.0
- END;
- bx := 20.0;
- bvx := ballSpeed * 0.62;
- bvy := rel * ballSpeed * 0.78;
- BumpSpeed
- ELSIF (bvx > 0.0) AND (bx + 8.0 >= 620.0)
- AND (bx <= 628.0)
- AND (by + 8.0 > cpuY)
- AND (by < cpuY + 60.0) THEN
- half := 30.0;
- rel := (by + 4.0 - (cpuY + half)) / half;
- IF rel < -1.0 THEN
- rel := -1.0
- ELSIF rel > 1.0 THEN
- rel := 1.0
- END;
- bx := 612.0;
- bvx := -ballSpeed * 0.62;
- bvy := rel * ballSpeed * 0.78;
- BumpSpeed
- END
- END BallVsPad;
- PROCEDURE Score;
- (* Awards a point when the ball leaves the playfield and serves
- towards the conceding player. *)
- BEGIN
- IF bx < -8.0 THEN
- cScore := cScore + 1;
- IF cScore >= winScore THEN
- state := s_over
- ELSE
- ballSpeed := ballSpeed0;
- Serve(FALSE)
- END
- ELSIF bx > 640.0 THEN
- pScore := pScore + 1;
- IF pScore >= winScore THEN
- state := s_over
- ELSE
- ballSpeed := ballSpeed0;
- Serve(TRUE)
- END
- END
- END Score;
- PROCEDURE MoveBall;
- BEGIN
- bx := bx + bvx * dt;
- by := by + bvy * dt;
- IF by <= 0.0 THEN
- by := 0.0;
- IF bvy < 0.0 THEN
- bvy := -bvy
- END
- ELSIF by + 8.0 >= 400.0 THEN
- by := 392.0;
- IF bvy > 0.0 THEN
- bvy := -bvy
- END
- END;
- BallVsPad;
- Score
- END MoveBall;
- PROCEDURE BuildScoreText;
- BEGIN
- n := 0;
- Append(msgbuf, n, "YOU ");
- CardToText(pScore, tmp);
- Append(msgbuf, n, tmp);
- Append(msgbuf, n, " ");
- CardToText(cScore, tmp);
- Append(msgbuf, n, tmp);
- Append(msgbuf, n, " CPU");
- msgbuf[n] := 0C
- END BuildScoreText;
- PROCEDURE CenterPrint(y : CARDINAL; msg : ARRAY OF CHAR;
- col : TPixelType);
- VAR
- tw : INTEGER;
- x : CARDINAL;
- BEGIN
- tw := tigrTextWidth(tfont, msg);
- IF tw < INTEGER(winW) THEN
- x := (winW - CARDINAL(tw)) / 2
- ELSE
- x := 0
- END;
- tigrPrint(win, tfont, x, y, col, msg)
- END CenterPrint;
- PROCEDURE Draw;
- BEGIN
- tigrClear(win, black);
- (* dashed center line *)
- i := 4;
- WHILE i < winH DO
- tigrFillRect(win, 319, i, 2, 8, white);
- i := i + 16
- END;
- tigrFillRect(win, playerStartX, TRUNC(playerY), paddleW, paddleH,
- green);
- tigrFillRect(win, cpuStartX, TRUNC(cpuY), paddleW, paddleH, red);
- IF state # s_over THEN
- tigrFillRect(win, TRUNC(bx), TRUNC(by), ballS, ballS, white)
- END;
- tigrPrint(win, tfont, 6, 4, white, msgbuf);
- tigrPrint(win, tfont, 6, 16, yellow,
- "W/S move UP/DOWN CPU SPACE serve ESC quit");
- IF state = s_serve THEN
- CenterPrint(372, "SPACE to serve", white)
- ELSIF state = s_over THEN
- IF pScore > cScore THEN
- CenterPrint(180, "YOU WIN!", green)
- ELSE
- CenterPrint(180, "CPU WINS", red)
- END;
- CenterPrint(204, "SPACE to restart", white)
- END
- END Draw;
- BEGIN
- win := tigrWindow(winW, winH, "Pong", 0);
- black := tigrRGB(0, 0, 0);
- white := tigrRGB(255, 255, 255);
- green := tigrRGB(60, 230, 60);
- red := tigrRGB(255, 60, 60);
- cyan := tigrRGB(60, 220, 220);
- yellow := tigrRGB(255, 220, 60);
- StartGame;
- WHILE (NOT (tigrClosed(win) > 0)) AND
- (NOT (tigrKeyDown(win, TK_ESCAPE) > 0)) DO
- dt := tigrTime(); (* seconds since the previous call *)
- IF state = s_over THEN
- IF tigrKeyDown(win, TK_SPACE) > 0 THEN
- StartGame
- END
- ELSE
- (* player & manual second paddle *)
- IF tigrKeyHeld(win, ORD('W')) > 0 THEN
- MovePaddle(playerY, FALSE, paddleSpeedV)
- END;
- IF tigrKeyHeld(win, ORD('S')) > 0 THEN
- MovePaddle(playerY, TRUE, paddleSpeedV)
- END;
- IF (tigrKeyHeld(win, TK_UP) > 0) OR
- (tigrKeyHeld(win, TK_DOWN) > 0) THEN
- IF tigrKeyHeld(win, TK_UP) > 0 THEN
- MovePaddle(cpuY, FALSE, paddleSpeedV)
- END;
- IF tigrKeyHeld(win, TK_DOWN) > 0 THEN
- MovePaddle(cpuY, TRUE, paddleSpeedV)
- END
- ELSE
- CpuMove
- END;
- IF state = s_serve THEN
- IF tigrKeyDown(win, TK_SPACE) > 0 THEN
- state := s_play
- END
- ELSE
- MoveBall
- END
- END;
- BuildScoreText;
- Draw;
- tigrUpdate(win)
- END;
- tigrFree(win)
- END pong.
|