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