1#lang racket 2 3#| 4 The Orc game 5 ------------- 6 7 The Orc game is a turn-based battle game between monsters and the player. 8 9 The player encounters a room full of monsters of all kinds, including 10 orcs, hydras, slimes, and brigands. They are ready to attack. It is 11 the player's task to get rid of the monsters. 12 13 When the game starts up, it is the player's turn, meaning she is given 14 permission to attack a (randomly chosen number) of times. The player uses 15 nine keys to play 16 -- With the four arrow keys the player navigates among the twelve monsters. 17 -- With "s", "f", and "h", 18 -- the player can 's'tab a specific monster, 19 -- the player may 'f'lail at several monsters; 20 -- the player may 'h'eal herself. 21 When the player runs out of attacks, all live monsters attack the player. 22 After that, it is the player's turn again. 23 24 Just in case, the player can end a turn prematurely with "e". 25 26 Play 27 ---- 28 29 Run and evaluate 30 (start-game) 31 This will pop up a window that displays the player's vitals, the orcs and 32 their basic state, and the game instructions. 33|# 34 35 36(require 2htdp/image 2htdp/universe) 37 38; 39; 40; 41; ;;; ;;; ;;; ;; ;; 42; ; ; ; ; ; ; 43; ; ; ;; ;;; ;;; ; ; ; ;;;; ;; ;;; ; ;;; ; 44; ; ; ;; ;; ;; ; ; ; ; ; ;; ; ; ;; 45; ; ; ; ; ; ; ; ; ; ; ; ; ; 46; ; ; ; ; ; ; ; ; ; ; ; ; ; 47; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; 48; ;;; ;;;;; ;;;; ; ; ;;;; ;;;;; ;;;;; ;;; ;; 49; 50; 51; 52; 53 54;; The OrcWorld as Data: 55(struct orc-world (player lom attack# target) #:transparent #:mutable) 56;; A OrcWorld is a (orc-world Player [listof Monster] Nat Nat) 57;; The third field of the world refers to the number of attacks left. 58;; The fourth field refers to the position of the next attack target. 59 60(struct player (health agility strength) #:transparent #:mutable) 61;; A Player is a (player Nat Nat Nat) 62;; The player's fields correspond to hit points, strength, agility. 63 64(struct monster (image [health #:mutable]) #:transparent) 65(struct orc monster (club) #:transparent) 66(struct hydra monster () #:transparent) 67(struct slime monster (sliminess) #:transparent) 68(struct brigand monster () #:transparent) 69;; A Monster is a (monster Image Nat) 70;; (moster i h) is a monster at position i in the list with health h 71;; Each monster is equipped with the index number, 72;; which is used to identify the current target. 73;; 74;; An Orc is an (orc Nat Nat Nat) 75;; A Slime is a (slime Nat Nat Nat) 76;; A Brigrand is a (brigand Nat Nat) 77;; A Hydra is a (hydra Nat Nat) 78;; 79;; The four monster types all inherit the id and health fields from monster. 80;; Two have additional attributes: 81;; -- (orc i h c) means the orc's club has strength c 82;; -- (slime i h a) means the slime can reduce the player's agility by a 83 84;; ----------------------------------------------------------------------------- 85;; THE CONSTANTS IN THE WORLD 86 87;; player attributes 88(define MAX-HEALTH 35) 89(define MAX-AGILITY 35) 90(define MAX-STRENGTH 35) 91 92;; depending on other player attributes, 93;; the game picks the number of attacks, flailing and stabbing damage 94(define ATTACKS# 4) 95(define STAB-DAMAGE 2) 96(define FLAIL-DAMAGE 3) 97(define HEALING 8) 98 99;; monster attributes 100(define MONSTER# 12) 101(define PER-ROW 4) 102(unless (zero? (remainder MONSTER# PER-ROW)) 103 (error 'constraint "PER-ROW must divide MONSTER# evenly into rows")) 104 105(define MONSTER-HEALTH0 9) 106(define CLUB-STRENGTH 8) 107(define SLIMINESS 5) 108 109(define HEALTH-DAMAGE -2) 110(define AGILITY-DAMAGE -3) 111(define STRENGTH-DAMAGE -4) 112 113;; string constants 114(define STRENGTH "strength") 115(define AGILITY "agility") 116(define HEALTH "health") 117(define LOSE "YOU LOSE") 118(define WIN "YOU WIN") 119(define DEAD "DEAD") 120(define REMAINING "Remaining attacks ") 121(define INSTRUCTIONS-2 "Select a monster using the arrow keys") 122(define INSTRUCTIONS-1 123 "Press S to stab a monster | Press F to Flail wildly | Press H to Heal") 124 125;; graphical constants 126(define HEALTH-BAR-HEIGHT 12) 127(define HEALTH-BAR-WIDTH 50) 128 129;; compute constants for image frames 130(define ORC (bitmap "graphics/orc.png")) 131(define HYDRA (bitmap "graphics/hydra.png")) 132(define SLIME (bitmap "graphics/slime.bmp")) 133(define BRIGAND (bitmap "graphics/brigand.bmp")) 134 135(define PIC-LIST (list ORC HYDRA SLIME BRIGAND)) 136(define w (apply max (map image-width PIC-LIST))) 137(define h (apply max (map image-height PIC-LIST))) 138 139;; images: player, monsters, constant texts 140(define PLAYER-IMAGE (bitmap "graphics/player.bmp")) 141 142(define FRAME (rectangle w h 'outline 'white)) 143(define TARGET (circle (- (/ w 2) 2) 'outline 'blue)) 144 145(define ORC-IMAGE (overlay ORC FRAME)) 146(define HYDRA-IMAGE (overlay HYDRA FRAME)) 147(define SLIME-IMAGE (overlay SLIME FRAME)) 148(define BRIGAND-IMAGE (overlay BRIGAND FRAME)) 149 150(define V-SPACER (rectangle 0 10 "solid" "white")) 151(define H-SPACER (rectangle 10 0 "solid" "white")) 152 153;; fonts & texts & colors 154(define AGILITY-COLOR "blue") 155(define HEALTH-COLOR "crimson") 156(define STRENGTH-COLOR "forest green") 157(define MONSTER-COLOR "crimson") 158(define MESSAGE-COLOR "black") 159(define ATTACK-COLOR "crimson") 160 161(define HEALTH-SIZE (- HEALTH-BAR-HEIGHT 4)) 162(define DEAD-TEXT-SIZE (- HEALTH-BAR-HEIGHT 2)) 163(define INSTRUCTION-TEXT-SIZE 16) 164(define MESSAGES-SIZE 40) 165 166(define INSTRUCTION-TEXT 167 (above 168 (text INSTRUCTIONS-2 (- INSTRUCTION-TEXT-SIZE 2) "blue") 169 (text INSTRUCTIONS-1 (- INSTRUCTION-TEXT-SIZE 4) "blue"))) 170 171(define DEAD-TEXT (text DEAD DEAD-TEXT-SIZE "crimson")) 172 173; 174; 175; 176; ;;; ;;; ; 177; ;; ;; 178; ;; ;; ;;;; ;;; ;; ;; 179; ; ; ; ; ; ; ;; ; 180; ; ; ; ;;;;; ; ; ; 181; ; ; ; ; ; ; ; 182; ; ; ; ;; ; ; ; 183; ;;; ;;; ;;; ;; ;;;;; ;;; ;;; 184; 185; 186; 187; 188 189;; Start the game 190(define (start-game) 191 (big-bang (initialize-orc-world) 192 (on-key player-acts-on-monsters) 193 (to-draw render-orc-battle) 194 (stop-when end-of-orc-battle? render-the-end))) 195 196;; -> OrcWorld 197;; creates an orc-world ready for battling orcs 198(define (initialize-orc-world) 199 (define player0 (initialize-player)) 200 (define lom0 (initialize-monsters)) 201 (orc-world player0 lom0 (random-number-of-attacks player0) 0)) 202 203;; OrcWorld Key-Event -> OrcWorld 204;; act on key events by the player, if the player has attacks left 205(define (player-acts-on-monsters w k) 206 (cond 207 [(zero? (orc-world-attack# w)) w] 208 209 [(key=? "s" k) (stab w)] 210 [(key=? "h" k) (heal w)] 211 [(key=? "f" k) (flail w)] 212 213 [(key=? "right" k) (move-target w +1)] 214 [(key=? "left" k) (move-target w -1)] 215 [(key=? "down" k) (move-target w (+ PER-ROW))] 216 [(key=? "up" k) (move-target w (- PER-ROW))] 217 218 [(key=? "e" k) (end-turn w)] 219;; [(key=? "n" k) (initialize-orc-world)] 220 221 [else w]) 222 (give-monster-turn-if-attack#=0 w) 223 w) 224 225;; OrcWorld -> Image 226;; renders the orc world 227(define (render-orc-battle w) 228 (render-orc-world w (orc-world-target w) (instructions w))) 229 230;; OrcWorld -> Boolean 231;; is the battle over? i.e., the player lost or all monsters are dead 232(define (end-of-orc-battle? w) 233 (or (win? w) (lose? w))) 234 235;; OrcWorld -> Image 236;; render the final orc world 237(define (render-the-end w) 238 (render-orc-world w #f (message (if (lose? w) LOSE WIN)))) 239 240;; ----------------------------------------------------------------------------- 241 242;; WORLD MANAGEMENT 243;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 244 245; 246; 247; 248; ;;;;; ; 249; ; ; 250; ; ;; ;; ;;; ;;;;; 251; ; ;; ; ; ; 252; ; ; ; ; ; 253; ; ; ; ; ; 254; ; ; ; ; ; ; 255; ;;;;; ;;; ;;; ;;;;; ;;; 256; 257; 258; 259; 260 261;; -> Player 262;; create a player with maximal capabilities 263(define (initialize-player) 264 (player MAX-HEALTH MAX-AGILITY MAX-STRENGTH)) 265 266;; -> [Listof Monster] 267;; create a list of random monsters of length MONSTER-NUM, 268(define (initialize-monsters) 269 ;; Nat -> Monster 270 ;; makes a random monster 271 (define (create-monster _) 272 (define health (random+ MONSTER-HEALTH0)) 273 (case (random 4) 274 [(0) (orc ORC-IMAGE health (random+ CLUB-STRENGTH))] 275 [(1) (hydra HYDRA-IMAGE health)] 276 [(2) (slime SLIME-IMAGE health (random+ SLIMINESS))] 277 [(3) (brigand BRIGAND-IMAGE health)] 278 [else (error "can't happen")])) 279 (build-list MONSTER# create-monster)) 280 281;; Player -> Nat 282;; compute a feasible number of attacks the player may execute 283(define (random-number-of-attacks p) 284 (random-quotient (player-agility p) 285 ATTACKS#)) 286 287; 288; 289; 290; ;;; ;;; ;;;;;; 291; ; ; ; ; ; 292; ; ; ;;;; ;;; ;;; ; ; ;;; ;;; ;;;; ;; ;; ;;;;; ;;;;; 293; ; ; ; ; ; ; ;;; ; ; ; ; ;; ; ; ; ; 294; ;;; ;;;;;; ; ; ; ; ; ; ;;;;;; ; ; ; ;;;; 295; ; ; ; ; ; ; ; ; ; ; ; ; ; 296; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; 297; ;;; ;; ;;;;; ; ;;;;;; ;; ;;;;; ;;; ;;; ;;; ;;;;; 298; ; 299; ;;; 300; 301; 302 303;; ----------------------------------------------------------------------------- 304;; player actions 305 306;; OrcWorld Nat -> Void 307;; Effect: reduces the target by a given amount 308;; > (move-target 309;; (orc-world (player 5 5 5) (list (monster 0 2) (monster 1 3)) 1 0) 310;; 1) 311;; (orc-world (player 5 5 5) (list (monster 0 2) (monster 1 3)) 1 1) 312(define (move-target w n) 313 (set-orc-world-target! w (modulo (+ n (orc-world-target w)) MONSTER#))) 314 315;; OrcWorld -> Void 316;; Effect: ends the player's turn by setting the number of attacks to 0 317(define (end-turn w) 318 (set-orc-world-attack#! w 0)) 319 320;; OrcWorld -> Void 321;; Effect: reduces the number of remaining attacks for this turn 322;; and increases the player's health level 323(define (heal w) 324 (decrease-attack# w) 325 (player-health+ (orc-world-player w) HEALING)) 326 327;; OrcWorld -> Void 328;; Effect: reduces a targeted monster's health 329(define (stab w) 330 (decrease-attack# w) 331 (define target (current-target w)) 332 (define damage 333 (random-quotient (player-strength (orc-world-player w)) 334 STAB-DAMAGE)) 335 (damage-monster target damage)) 336 337;; OrcWorld -> Void 338;; Effect: damages a random number of live monsters, 339;; determined by strength of the player 340;; starting with the currently targeted monster 341(define (flail w) 342 (decrease-attack# w) 343 (define target (current-target w)) 344 (define alive (filter monster-alive? (orc-world-lom w))) 345 (define pick# 346 (min 347 (random-quotient (player-strength (orc-world-player w)) 348 FLAIL-DAMAGE) 349 (length alive))) 350 (define getem (cons target (take alive pick#))) 351 (for-each (lambda (m) (damage-monster m 1)) getem)) 352 353;; OrcWorld -> Void 354;; Effect: decrease number of remaining attacks 355(define (decrease-attack# w) 356 (set-orc-world-attack#! w (sub1 (orc-world-attack# w)))) 357 358;; Monster Nat -> Void 359;; Effect: reduces the hit-strength of a monster 360(define (damage-monster m delta) 361 (set-monster-health! m (interval- (monster-health m) delta))) 362 363;; World -> Monster 364(define (current-target w) 365 (list-ref (orc-world-lom w) (orc-world-target w))) 366 367;; ----------------------------------------------------------------------------- 368;; monster action 369 370;; OrcWorld -> Void 371;; if it is the monsters turn, they attack 372;; > (orc-world (player 4 4 4) empty 3 3) 373;; (orc-world (player 4 4 4) empty 3 3) 374(define (give-monster-turn-if-attack#=0 w) 375 (when (zero? (orc-world-attack# w)) 376 (define player (orc-world-player w)) 377 (all-monsters-attack-player player (orc-world-lom w)) 378 (set-orc-world-attack#! w (random-number-of-attacks player)))) 379 380;; Player [Listof Monster] -> Void 381;; Each monster attacks the player 382(define (all-monsters-attack-player player lom) 383 ;; Monster -> Void 384 (define (one-monster-attacks-player monster) 385 (cond 386 [(orc? monster) 387 (player-health+ player (random- (orc-club monster)))] 388 [(hydra? monster) 389 (player-health+ player (random- (monster-health monster)))] 390 [(slime? monster) 391 (player-health+ player -1) 392 (player-agility+ player (random- (slime-sliminess monster)))] 393 [(brigand? monster) 394 (case (random 3) 395 [(0) (player-health+ player HEALTH-DAMAGE)] 396 [(1) (player-agility+ player AGILITY-DAMAGE)] 397 [(2) (player-strength+ player STRENGTH-DAMAGE)])])) 398 ;; -- IN -- 399 (for-each one-monster-attacks-player (filter monster-alive? lom))) 400 401;; ----------------------------------------------------------------------------- 402;; actions on player 403 404;; [Player -> Nat] [Player Nat -> Void] Nat -> Player Nat -> Void 405;; effect: change player's selector attribute by adding delta, but max out 406(define (player-update! setter selector max-value) 407 (lambda (player delta) 408 (setter player 409 (interval+ (selector player) delta max-value)))) 410 411;; Player Nat -> Void 412(define player-health+ 413 (player-update! set-player-health! player-health MAX-HEALTH)) 414 415;; Player Nat -> Void 416(define player-agility+ 417 (player-update! set-player-agility! player-agility MAX-AGILITY)) 418 419;; Player Nat -> Void 420(define player-strength+ 421 (player-update! set-player-strength! player-strength MAX-STRENGTH)) 422 423; 424; 425; 426; ;;;;; ;; ; 427; ; ; ; 428; ; ; ;;;; ;; ;; ;;; ; ;;;; ;; ;;; ;;; ;; ;; ;;; ;; 429; ; ; ; ; ;; ; ; ;; ; ; ;; ; ;; ; ; ;; 430; ;;;; ;;;;;; ; ; ; ; ;;;;;; ; ; ; ; ; ; 431; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 432; ; ; ; ; ; ; ;; ; ; ; ; ; ; ;; 433; ;;; ; ;;;;; ;;; ;;; ;;; ;; ;;;;; ;;;;; ;;;;; ;;; ;;; ;;; ; 434; ; 435; ;;;; 436; 437; 438 439;; OrcWorld Boolean Image -> Image 440;; draws all the monsters and the player, then adds message 441(define (render-orc-world w with-target additional-text) 442 (define i-player (render-player (orc-world-player w))) 443 (define i-monster (render-monsters (orc-world-lom w) with-target)) 444 (above V-SPACER 445 (beside H-SPACER 446 i-player 447 H-SPACER H-SPACER H-SPACER 448 (above i-monster 449 V-SPACER V-SPACER V-SPACER 450 additional-text) 451 H-SPACER) 452 V-SPACER)) 453 454;; Player -> Image 455;; render player with three status bars 456(define (render-player p) 457 (above/align 458 "left" 459 (status-bar (player-strength p) MAX-STRENGTH STRENGTH-COLOR STRENGTH) 460 V-SPACER 461 (status-bar (player-agility p) MAX-AGILITY AGILITY-COLOR AGILITY) 462 V-SPACER 463 (status-bar (player-health p) MAX-HEALTH HEALTH-COLOR HEALTH) 464 V-SPACER V-SPACER V-SPACER 465 PLAYER-IMAGE)) 466 467;; Nat Nat Color String -> Image 468;; creates a labeled rectangle of width/max proportions 469;; assume: (<= width max) 470(define (status-bar v-current v-max color label) 471 (define w (* (/ v-current v-max) HEALTH-BAR-WIDTH)) 472 (define f (rectangle w HEALTH-BAR-HEIGHT 'solid color)) 473 (define b (rectangle HEALTH-BAR-WIDTH HEALTH-BAR-HEIGHT 'outline color)) 474 (define bar (overlay/align 'left 'top f b)) 475 (beside bar H-SPACER (text label HEALTH-SIZE color))) 476 477;; String -> Image 478(define (message str) 479 (text str MESSAGES-SIZE MESSAGE-COLOR)) 480 481;; OrcWorld -> Image 482(define (instructions w) 483 (define na (number->string (orc-world-attack# w))) 484 (define ra (string-append REMAINING na)) 485 (above (text ra INSTRUCTION-TEXT-SIZE ATTACK-COLOR) INSTRUCTION-TEXT)) 486 487;; [Listof Monster] [Opt Nat] -> Image 488;; add all monsters on lom, including status bar 489;; label the target unless it isn't called for 490(define (render-monsters lom with-target) 491 ;; the currently targeted monster (if needed) 492 (define target 493 (if (number? with-target) 494 (list-ref lom with-target) 495 'a-silly-symbol-that-cannot-be-eq-to-an-orc)) 496 497 ;; Monster -> Image 498 (define (render-one-monster m) 499 (define image 500 (if (eq? m target) 501 (overlay TARGET (monster-image m)) 502 (monster-image m))) 503 (define health (monster-health m)) 504 (define health-bar 505 (if (= health 0) 506 (overlay DEAD-TEXT (status-bar 0 1 'white "")) 507 (status-bar health MONSTER-HEALTH0 MONSTER-COLOR ""))) 508 (above health-bar image)) 509 510 (arrange (map render-one-monster lom))) 511 512;; [Listof Image] -> Image 513;; break a list of images into rows of PER-ROW 514(define (arrange lom) 515 (cond 516 [(empty? lom) empty-image] 517 [else (define row-image (apply beside (take lom PER-ROW))) 518 (above row-image (arrange (drop lom PER-ROW)))])) 519 520 521; 522; 523; 524; ;;;;;; ;; ;;; 525; ; ; ; ; ; 526; ; ; ;; ;; ;;; ; ; 527; ;;; ;; ; ; ;; ; 528; ; ; ; ; ; ; ; 529; ; ; ; ; ; ; 530; ; ; ; ; ; ;; 531; ;;;;;; ;;; ;;; ;;; ;; ;; 532; 533; 534; 535; 536 537;; OrcWorld -> Boolean 538;; Has the player won? 539;; > (orc-world (player 1 1 1) (list (monster 0 0)) 0 0) 540;; #t 541(define (win? w) 542 (all-dead? (orc-world-lom w))) 543 544;; OrcWorld -> Boolean 545;; Has the player lost? 546;; > (lose? (orc-world (player 0 2 2) empty 0 0)) 547;; #t 548(define (lose? w) 549 (player-dead? (orc-world-player w))) 550 551;; Player -> Boolean 552;; Is the player dead? 553;; > (orc-world (player 1 0 1) empty 0 0) 554;; #t 555(define (player-dead? p) 556 (or (= (player-health p) 0) 557 (= (player-agility p) 0) 558 (= (player-strength p) 0))) 559 560;; [Listof Monster] -> Boolean 561;; Are all the monsters in the list dead?s 562;; > (all-dead? (orc-world (player 5 5 5) (list (monster 1 0)) 0 1)) 563;; #t 564(define (all-dead? lom) 565 (not (ormap monster-alive? lom))) 566 567;; Monster -> Boolean 568;; Is the monster alive? 569(define (monster-alive? m) 570 (> (monster-health m) 0)) 571 572 573; 574; 575; 576; ;; 577; ; 578; ; ; ;; ;; ;; ;; ;;;;; 579; ; ; ; ; ; ; ; ; 580; ; ; ; ; ;; ;;;; 581; ;;; ; ; ;; ; 582; ; ; ; ;; ; ; ; ; 583; ;;; ;;; ;; ;; ;; ;; ;;;;; 584; 585; 586; 587; 588 589;; Nat Nat -> Nat 590;; a random number between 1 and the (quotient x y) 591(define (random-quotient x y) 592 (define div (quotient x y)) 593 (if (> 0 div) 0 (random+ (add1 div)))) 594 595;; Nat -> Nat 596;; (random+ n) creates a random number in [1,n] 597(define (random+ n) 598 (add1 (random n))) 599 600;; Nat -> Nat 601;; (random+ n) creates a random number in [-n,-1] 602(define (random- n) 603 (- (add1 (random n)))) 604 605;; Nat Nat [Nat] -> Nat 606;; subtract n from m but stay in [0,max-value] 607(define (interval- n m (max-value 100)) 608 (min (max 0 (- n m)) max-value)) 609 610;; Nat Nat [Nat] -> Nat 611;; subtract n from m but stay in [0,max-value] 612(define (interval+ n m (max-value 100)) 613 (interval- n (- m) max-value)) 614 615; 616; 617; 618; ;;;;;; 619; ; ; ; 620; ; ;;;; ;;;;; ;;;;; ;;;;; 621; ; ; ; ; ; ; ; ; 622; ; ;;;;;; ;;;; ; ;;;; 623; ; ; ; ; ; 624; ; ; ; ; ; ; ; ; 625; ;;; ;;;;; ;;;;; ;;; ;;;;; 626; 627; 628; 629; 630 631(module+ test 632 633 (require rackunit rackunit/text-ui) 634 635 ;; Test structs 636 (define WORLD0 (orc-world (initialize-player) empty 0 0)) 637 (define WORLD1 (struct-copy orc-world (initialize-orc-world) [attack# 5])) 638 (define (WORLD2) (struct-copy orc-world (initialize-orc-world) [attack# 0])) 639 ;; these are random worlds 640 (define AN-ORC (orc 'image 0 5)) 641 (define A-SLIME (slime 'image 1 6)) 642 (define A-HYDRA (hydra 'image 2)) 643 (define A-BRIGAND (brigand 'image 3)) 644 645 ;; testing move-target 646 647 (check-equal? (let ([w (orc-world 'dummy 'dummy 'dummy 0)]) 648 (move-target w +1) 649 w) 650 (orc-world 'dummy 'dummy 'dummy 1)) 651 (check-equal? (let ([w (orc-world 'dummy 'dummy 'dummy 0)]) 652 (move-target w -1) 653 w) 654 (orc-world 'dummy 'dummy 'dummy (- MONSTER# 1))) 655 (check-equal? (let ([w (orc-world 'dummy 'dummy 'dummy 0)]) 656 (move-target w (- PER-ROW)) 657 w) 658 (orc-world 'dummy 'dummy 'dummy (- MONSTER# PER-ROW))) 659 (check-equal? (let ([w (orc-world 'dummy 'dummy 'dummy 1)]) 660 (move-target w (+ PER-ROW)) 661 w) 662 (orc-world 'dummy 'dummy 'dummy (+ PER-ROW 1))) 663 (check-equal? (begin 664 (move-target WORLD1 0) 665 WORLD1) 666 WORLD1) 667 (check-equal? (let () 668 (define w (struct-copy orc-world WORLD1)) 669 (move-target w 4) 670 w) 671 (struct-copy orc-world WORLD1 [target (+ 4 (orc-world-target WORLD1))])) 672 (check-equal? (current-target WORLD1) 673 (first (orc-world-lom WORLD1))) 674 675 ;; testing basic player manipulations 676 677 (check-equal? (let ([p (player 1 0 0)]) 678 (player-health+ p 5) 679 p) 680 (player 6 0 0)) 681 (check-equal? (let ([p (player 0 1 0)]) 682 (player-agility+ p 5) 683 p) 684 (player 0 6 0)) 685 686 (check-equal? (let ([p (player 0 0 1)]) 687 (player-strength+ p 5) 688 p) 689 (player 0 0 6)) 690 691 (check-equal? (let ([p (player 5 5 5)]) 692 (all-monsters-attack-player p (list (orc 'image 1 1))) 693 p) 694 (player 4 5 5)) 695 696 (check-equal? (let ([p (player 5 5 5)]) 697 (all-monsters-attack-player p (list (hydra 'image 1))) 698 p) 699 (player 4 5 5)) 700 701 (check-equal? (let ([p (player 5 5 5)]) 702 (all-monsters-attack-player p (list (slime 'image 1 1))) 703 p) 704 (player 4 4 5)) 705 706 (check member 707 (let ([p (player 5 5 5)]) 708 (all-monsters-attack-player p (list (brigand 'image 1))) 709 p) 710 (list (player 3 5 5) 711 (player 5 2 5) 712 (player 5 5 1))) 713 714 ;; Properties 715 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 716 717 ;; Property: 718 ;; the output will always be in [1, (/ X Y)] 719 (define (prop:rand-frac-range i) 720 (test-begin 721 (for ([i (in-range i)]) 722 (define x (random 4294967087)) 723 (define y (random 4294967087)) 724 (check-true (<= 1 (random-quotient x y) (add1 (/ x y))))))) 725 726 ;; Property: 727 ;; The number of the monsters in the list is equal to 728 ;; MONSTER-NUM 729 (define (prop:monster-init-length i) 730 (test-begin 731 (for ([i (in-range i)]) 732 (check-true (= MONSTER# 733 (length (initialize-monsters))))))) 734 735 ;; Property: 736 ;; the player will have less points in at least one of its 737 ;; fields 738 (define (prop:monster-attack-player-dec i) 739 (test-begin 740 (for ([i (in-range i)]) 741 (define pl (player MAX-HEALTH MAX-AGILITY MAX-STRENGTH)) 742 (define mon (first (initialize-monsters))) 743 (begin 744 (all-monsters-attack-player pl (list mon)) 745 (check-true (or (< (player-health pl) MAX-HEALTH) 746 (< (player-agility pl) MAX-AGILITY) 747 (< (player-strength pl) MAX-STRENGTH))))))) 748 749 ;; Property: 750 ;; If there are monster, then the player will 751 ;; have less points in at least one of its fields 752 (define (prop:monsters-attack-player-dec i) 753 (test-begin 754 (for ([i (in-range i)]) 755 (define pl (player MAX-HEALTH MAX-AGILITY MAX-STRENGTH)) 756 (define monsters (initialize-monsters)) 757 (define wor (orc-world pl monsters 0 0)) 758 (begin 759 (all-monsters-attack-player pl monsters) 760 (check-true (or (< (player-health pl) MAX-HEALTH) 761 (< (player-agility pl) MAX-AGILITY) 762 (< (player-strength pl) MAX-STRENGTH))))))) 763 764 ;; Property: The health of the targeted monster, m, 765 ;; is less than what it was. and 766 ;; [(sub1 (monster-health m)), 767 ;; (- (monster-health m) 768 ;; (/ (player-strength (orc-world-player w)) 2))] 769 (define (prop:stab!-health i) 770 (test-begin 771 (for ([i (in-range i)]) 772 (begin (define mon (first(initialize-monsters))) 773 (define ht (monster-health mon)) 774 (define pl (random-player)) 775 (define w (orc-world pl (list mon) 2 0)) 776 (stab w) 777 (check-true (> ht (monster-health (first (orc-world-lom w))))))))) 778 779 ;; random-player: -> Player 780 ;; creates a random player 781 (define (random-player) 782 (player (add1 (random MAX-HEALTH)) 783 (add1 (random MAX-AGILITY)) 784 (add1 (random MAX-STRENGTH)))) 785 786 ;; testing initializers 787 (prop:monster-init-length 1000) 788 (check-true (monster? (first (initialize-monsters)))) 789 (check-true (> 10 (monster-health (first (initialize-monsters))))) 790 (check-equal? (length (initialize-monsters)) MONSTER#) 791 (check-equal? (length (orc-world-lom WORLD1)) MONSTER#) 792 (check-true (>= (let ([p (initialize-player)]) 793 (player-health p)) 794 (let ([p (initialize-player)]) 795 (all-monsters-attack-player p (list AN-ORC)) 796 (player-health p)))) 797 (check-true (> (player-health (initialize-player)) 798 (let ([p (initialize-player)]) 799 (all-monsters-attack-player p (list A-HYDRA)) 800 (player-health p)))) 801 (check-true (< (let ([p (initialize-player)]) 802 (all-monsters-attack-player p (list A-SLIME)) 803 (player-agility p)) 804 (let ([p (initialize-player)]) 805 (player-agility p)))) 806 (check-true (let ([p (initialize-player)]) 807 (all-monsters-attack-player p (list A-BRIGAND)) 808 (or (= (player-health p) 809 (- (player-health (initialize-player)) 2)) 810 (= (player-agility p) 811 (- (player-agility (initialize-player)) 3)) 812 (= (player-strength p) 813 (- (player-strength (initialize-player)) 4))))) 814 (check-equal? (length (orc-world-lom WORLD1)) MONSTER#) 815 (check-equal? (orc-world-player WORLD1) (orc-world-player WORLD1)) 816 817 ;; testing the-monster's attacks 818 819 (prop:monster-attack-player-dec 1000) 820 (prop:monsters-attack-player-dec 1000) 821 (check-true (or (> (player-health (orc-world-player (WORLD2))) 822 (player-health (orc-world-player 823 (let ([w (WORLD2)]) 824 (all-monsters-attack-player (orc-world-player w) (orc-world-lom w)) 825 w)))) 826 (> (player-strength (orc-world-player (WORLD2))) 827 (player-strength (orc-world-player 828 (let ([w (WORLD2)]) 829 (all-monsters-attack-player (orc-world-player w) (orc-world-lom w)) 830 w)))) 831 (> (player-agility (orc-world-player (WORLD2))) 832 (player-agility (orc-world-player 833 (let ([w (WORLD2)]) 834 (all-monsters-attack-player (orc-world-player w) (orc-world-lom w)) 835 w)))))) 836 837 ;; testing the player's actions 838 839 (prop:stab!-health 1000) 840 (test-begin (define o (orc 'image 0 5)) 841 (damage-monster o 5) 842 (check-equal? o (orc 'image 0 5))) 843 (test-begin (define o (orc 'image 0 5)) 844 (damage-monster o 0) 845 (check-equal? o (orc 'image 0 5))) 846 (check-equal? (player-health (orc-world-player 847 (let () 848 (define w (struct-copy orc-world WORLD1)) 849 (heal w) 850 w))) 851 (min MAX-HEALTH 852 (+ 8 (player-health (orc-world-player WORLD1))))) 853 854 (check-equal? (length (orc-world-lom 855 (let () 856 (define w (struct-copy orc-world WORLD1)) 857 (stab w) 858 w))) 859 MONSTER#) 860 861 ;; testing game predicates 862 863 (check-false (lose? WORLD0)) 864 (check-true (lose? (orc-world (player 0 30 30) empty 0 0))) 865 (check-true (all-dead? (list (orc 'image 0 0) (hydra 'image 0)))) 866 (check-true (all-dead? (list AN-ORC))) 867 (check-true (win? (orc-world (initialize-player) (list (orc 'image 0 0)) 0 0))) 868 (check-true (win? (orc-world (initialize-player) (list AN-ORC) 0 0))) 869 (check-true (end-of-orc-battle? (orc-world (initialize-player) (list (orc 'image 0 0)) 0 0))) 870 (check-true (end-of-orc-battle? (orc-world (initialize-player) (list AN-ORC) 0 0))) 871 (check-true (end-of-orc-battle? (orc-world (player 0 30 30) empty 0 0))) 872 (check-true (player-dead? (player 0 2 5))) 873 (check-false (player-dead? (initialize-player))) 874 (check-false (not (monster-alive? A-HYDRA))) 875 (check-true (monster-alive? (monster 'image 1))) 876 (check-false (monster-alive? (orc 'image 0 0))) 877 878 ;; testing utilities 879 880 (prop:rand-frac-range 1000) 881 882 "all tests run") 883