orc game
This commit is contained in:
parent
ca8c21f43c
commit
49a62fd285
9
collects/realm/chapter8/readme.txt
Normal file
9
collects/realm/chapter8/readme.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
This chapter implements a graphical version of the "orc" game from Barski's
|
||||
"Land of Lisp". To play or to experiment, open the file
|
||||
|
||||
source.rkt
|
||||
|
||||
in DrRacket. The instructions for playing are at the top of the file.
|
||||
Our tests are at the bottom of the file in a separate 'test' submodule.
|
||||
|
885
collects/realm/chapter8/source.rkt
Normal file
885
collects/realm/chapter8/source.rkt
Normal file
|
@ -0,0 +1,885 @@
|
|||
#lang racket
|
||||
|
||||
#|
|
||||
The Orc game
|
||||
-------------
|
||||
|
||||
The Orc game is a turn-based battle game between monsters and the player.
|
||||
|
||||
The player encounters a room full of monsters of all kinds, including
|
||||
orcs, hydras, slimes, and brigands. They are ready to attack. It is
|
||||
the player's task to get rid of the monsters.
|
||||
|
||||
When the game starts up, it is the player's turn, meaning she is given
|
||||
permission to attack a (randomly chosen number) of times. The player uses
|
||||
nine keys to play
|
||||
-- With the four arrow keys the player navigates among the twelve monsters.
|
||||
-- With "s", "f", and "h",
|
||||
-- the player can 's'tab a specific monster,
|
||||
-- the player may 'f'lail at several monsters;
|
||||
-- the player may 'h'eal herself.
|
||||
When the player runs out of attacks, all live monsters attack the player.
|
||||
After that, it is the player's turn again.
|
||||
|
||||
Just in case, the player can end a turn prematurely with "e".
|
||||
|
||||
Finally, "n" starts a new game.
|
||||
|
||||
|
||||
Play a Orc game
|
||||
-----------------
|
||||
|
||||
Run program and evaluate
|
||||
(start-game)
|
||||
This will pop up a window that displays the player's vitals, the orcs and
|
||||
their basic state, and the game instructions.
|
||||
|#
|
||||
|
||||
|
||||
(require 2htdp/image 2htdp/universe)
|
||||
|
||||
;
|
||||
;
|
||||
;
|
||||
; ;;; ;;; ;;; ;; ;;
|
||||
; ; ; ; ; ; ;
|
||||
; ; ; ;; ;;; ;;; ; ; ; ;;;; ;; ;;; ; ;;; ;
|
||||
; ; ; ;; ;; ;; ; ; ; ; ; ;; ; ; ;;
|
||||
; ; ; ; ; ; ; ; ; ; ; ; ; ;
|
||||
; ; ; ; ; ; ; ; ; ; ; ; ; ;
|
||||
; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;
|
||||
; ;;; ;;;;; ;;;; ; ; ;;;; ;;;;; ;;;;; ;;; ;;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
|
||||
;; The OrcWorld as Data:
|
||||
(struct orc-world (player lom attack# target) #:transparent #:mutable)
|
||||
;; A OrcWorld is a (orc-world Player [listof Monster] Nat Nat)
|
||||
;; The third field of the world refers to the number of attacks left.
|
||||
;; The fourth field refers to the position of the next attack target.
|
||||
|
||||
(struct player (health agility strength) #:transparent #:mutable)
|
||||
;; A Player is a (player Nat Nat Nat)
|
||||
;; The player's fields correspond to hit points, strength, agility.
|
||||
|
||||
(struct monster (image [health #:mutable]) #:transparent)
|
||||
(struct orc monster (club) #:transparent)
|
||||
(struct hydra monster () #:transparent)
|
||||
(struct slime monster (sliminess) #:transparent)
|
||||
(struct brigand monster () #:transparent)
|
||||
;; A Monster is a (monster Image Nat)
|
||||
;; (moster i h) is a monster at position i in the list with health h
|
||||
;; Each monster is equipped with the index number,
|
||||
;; which is used to identify the current target.
|
||||
;;
|
||||
;; An Orc is an (orc Nat Nat Nat)
|
||||
;; A Slime is a (slime Nat Nat Nat)
|
||||
;; A Brigrand is a (brigand Nat Nat)
|
||||
;; A Hydra is a (hydra Nat Nat)
|
||||
;;
|
||||
;; The four monster types all inherit the id and health fields from monster.
|
||||
;; Two have additional attributes:
|
||||
;; -- (orc i h c) means the orc's club has strength c
|
||||
;; -- (slime i h a) means the slime can reduce the player's agility by a
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; THE CONSTANTS IN THE WORLD
|
||||
|
||||
;; player attributes
|
||||
(define MAX-HEALTH 35)
|
||||
(define MAX-AGILITY 35)
|
||||
(define MAX-STRENGTH 35)
|
||||
|
||||
;; depending on other player attributes,
|
||||
;; the game picks the number of attacks, flailing and stabbing damage
|
||||
(define ATTACKS# 4)
|
||||
(define STAB-DAMAGE 2)
|
||||
(define FLAIL-DAMAGE 3)
|
||||
(define HEALING 8)
|
||||
|
||||
;; monster attributes
|
||||
(define MONSTER# 12)
|
||||
(define PER-ROW 4)
|
||||
(unless (zero? (remainder MONSTER# PER-ROW))
|
||||
(error 'constraint "PER-ROW must divide MONSTER# evenly into rows"))
|
||||
|
||||
(define MONSTER-HEALTH0 9)
|
||||
(define CLUB-STRENGTH 8)
|
||||
(define SLIMINESS 5)
|
||||
|
||||
(define HEALTH-DAMAGE -2)
|
||||
(define AGILITY-DAMAGE -3)
|
||||
(define STRENGTH-DAMAGE -4)
|
||||
|
||||
;; string constants
|
||||
(define STRENGTH "strength")
|
||||
(define AGILITY "agility")
|
||||
(define HEALTH "health")
|
||||
(define LOSE "YOU LOSE")
|
||||
(define WIN "YOU WIN")
|
||||
(define DEAD "DEAD")
|
||||
(define REMAINING "Remaining attacks ")
|
||||
(define INSTRUCTIONS-2 "Select a monster using the arrow keys")
|
||||
(define INSTRUCTIONS-1
|
||||
"Press S to stab a monster | Press F to Flail wildly | Press H to Heal")
|
||||
|
||||
;; graphical constants
|
||||
(define HEALTH-BAR-HEIGHT 12)
|
||||
(define HEALTH-BAR-WIDTH 50)
|
||||
|
||||
;; compute constants for image frames
|
||||
(define ORC (bitmap "graphics/orc.png"))
|
||||
(define HYDRA (bitmap "graphics/hydra.png"))
|
||||
(define SLIME (bitmap "graphics/slime.bmp"))
|
||||
(define BRIGAND (bitmap "graphics/brigand.bmp"))
|
||||
|
||||
(define PIC-LIST (list ORC HYDRA SLIME BRIGAND))
|
||||
(define w (apply max (map image-width PIC-LIST)))
|
||||
(define h (apply max (map image-height PIC-LIST)))
|
||||
|
||||
;; images: player, monsters, constant texts
|
||||
(define PLAYER-IMAGE (bitmap "graphics/player.bmp"))
|
||||
|
||||
(define FRAME (rectangle w h 'outline 'white))
|
||||
(define TARGET (circle (- (/ w 2) 2) 'outline 'blue))
|
||||
|
||||
(define ORC-IMAGE (overlay ORC FRAME))
|
||||
(define HYDRA-IMAGE (overlay HYDRA FRAME))
|
||||
(define SLIME-IMAGE (overlay SLIME FRAME))
|
||||
(define BRIGAND-IMAGE (overlay BRIGAND FRAME))
|
||||
|
||||
(define V-SPACER (rectangle 0 10 "solid" "white"))
|
||||
(define H-SPACER (rectangle 10 0 "solid" "white"))
|
||||
|
||||
;; fonts & texts & colors
|
||||
(define AGILITY-COLOR "blue")
|
||||
(define HEALTH-COLOR "crimson")
|
||||
(define STRENGTH-COLOR "forest green")
|
||||
(define MONSTER-COLOR "crimson")
|
||||
(define MESSAGE-COLOR "black")
|
||||
(define ATTACK-COLOR "crimson")
|
||||
|
||||
(define HEALTH-SIZE (- HEALTH-BAR-HEIGHT 4))
|
||||
(define DEAD-TEXT-SIZE (- HEALTH-BAR-HEIGHT 2))
|
||||
(define INSTRUCTION-TEXT-SIZE 16)
|
||||
(define MESSAGES-SIZE 40)
|
||||
|
||||
(define INSTRUCTION-TEXT
|
||||
(above
|
||||
(text INSTRUCTIONS-2 (- INSTRUCTION-TEXT-SIZE 2) "blue")
|
||||
(text INSTRUCTIONS-1 (- INSTRUCTION-TEXT-SIZE 4) "blue")))
|
||||
|
||||
(define DEAD-TEXT (text DEAD DEAD-TEXT-SIZE "crimson"))
|
||||
|
||||
;
|
||||
;
|
||||
;
|
||||
; ;;; ;;; ;
|
||||
; ;; ;;
|
||||
; ;; ;; ;;;; ;;; ;; ;;
|
||||
; ; ; ; ; ; ; ;; ;
|
||||
; ; ; ; ;;;;; ; ; ;
|
||||
; ; ; ; ; ; ; ;
|
||||
; ; ; ; ;; ; ; ;
|
||||
; ;;; ;;; ;;; ;; ;;;;; ;;; ;;;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
|
||||
;; Start the game
|
||||
(define (start-game)
|
||||
(big-bang (initialize-orc-world)
|
||||
(on-key player-acts-on-monsters)
|
||||
(to-draw render-orc-battle)
|
||||
(stop-when end-of-orc-battle? render-the-end)))
|
||||
|
||||
;; -> OrcWorld
|
||||
;; creates an orc-world ready for battling orcs
|
||||
(define (initialize-orc-world)
|
||||
(define player0 (initialize-player))
|
||||
(define lom0 (initialize-monsters))
|
||||
(orc-world player0 lom0 (random-number-of-attacks player0) 0))
|
||||
|
||||
;; OrcWorld Key-Event -> OrcWorld
|
||||
;; act on key events by the player, if the player has attacks left
|
||||
(define (player-acts-on-monsters w k)
|
||||
(cond
|
||||
[(zero? (orc-world-attack# w)) w]
|
||||
|
||||
[(key=? "s" k) (stab w)]
|
||||
[(key=? "h" k) (heal w)]
|
||||
[(key=? "f" k) (flail w)]
|
||||
|
||||
[(key=? "right" k) (move-target w +1)]
|
||||
[(key=? "left" k) (move-target w -1)]
|
||||
[(key=? "down" k) (move-target w (+ PER-ROW))]
|
||||
[(key=? "up" k) (move-target w (- PER-ROW))]
|
||||
|
||||
[(key=? "e" k) (end-turn w)]
|
||||
[(key=? "n" k) (initialize-orc-world)]
|
||||
|
||||
[else w])
|
||||
(give-monster-turn-if-attack#=0 w)
|
||||
w)
|
||||
|
||||
;; OrcWorld -> Image
|
||||
;; renders the orc world
|
||||
(define (render-orc-battle w)
|
||||
(render-orc-world w (orc-world-target w) (instructions w)))
|
||||
|
||||
;; OrcWorld -> Boolean
|
||||
;; is the battle over? i.e., the player lost or all monsters are dead
|
||||
(define (end-of-orc-battle? w)
|
||||
(or (win? w) (lose? w)))
|
||||
|
||||
;; OrcWorld -> Image
|
||||
;; render the final orc world
|
||||
(define (render-the-end w)
|
||||
(render-orc-world w #f (message (if (lose? w) LOSE WIN))))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
|
||||
;; WORLD MANAGEMENT
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;
|
||||
;
|
||||
;
|
||||
; ;;;;; ;
|
||||
; ; ;
|
||||
; ; ;; ;; ;;; ;;;;;
|
||||
; ; ;; ; ; ;
|
||||
; ; ; ; ; ;
|
||||
; ; ; ; ; ;
|
||||
; ; ; ; ; ; ;
|
||||
; ;;;;; ;;; ;;; ;;;;; ;;;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
|
||||
;; -> Player
|
||||
;; create a player with maximal capabilities
|
||||
(define (initialize-player)
|
||||
(player MAX-HEALTH MAX-AGILITY MAX-STRENGTH))
|
||||
|
||||
;; -> [Listof Monster]
|
||||
;; create a list of random monsters of length MONSTER-NUM,
|
||||
(define (initialize-monsters)
|
||||
;; Nat -> Monster
|
||||
;; makes a random monster
|
||||
(define (create-monster _)
|
||||
(define health (random+ MONSTER-HEALTH0))
|
||||
(case (random 4)
|
||||
[(0) (orc ORC-IMAGE health (random+ CLUB-STRENGTH))]
|
||||
[(1) (hydra HYDRA-IMAGE health)]
|
||||
[(2) (slime SLIME-IMAGE health (random+ SLIMINESS))]
|
||||
[(3) (brigand BRIGAND-IMAGE health)]
|
||||
[else (error "can't happen")]))
|
||||
(build-list MONSTER# create-monster))
|
||||
|
||||
;; Player -> Nat
|
||||
;; compute a feasible number of attacks the player may execute
|
||||
(define (random-number-of-attacks p)
|
||||
(random-quotient (player-agility p)
|
||||
ATTACKS#))
|
||||
|
||||
;
|
||||
;
|
||||
;
|
||||
; ;;; ;;; ;;;;;;
|
||||
; ; ; ; ; ;
|
||||
; ; ; ;;;; ;;; ;;; ; ; ;;; ;;; ;;;; ;; ;; ;;;;; ;;;;;
|
||||
; ; ; ; ; ; ; ;;; ; ; ; ; ;; ; ; ; ;
|
||||
; ;;; ;;;;;; ; ; ; ; ; ; ;;;;;; ; ; ; ;;;;
|
||||
; ; ; ; ; ; ; ; ; ; ; ; ; ;
|
||||
; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ;
|
||||
; ;;; ;; ;;;;; ; ;;;;;; ;; ;;;;; ;;; ;;; ;;; ;;;;;
|
||||
; ;
|
||||
; ;;;
|
||||
;
|
||||
;
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; player actions
|
||||
|
||||
;; OrcWorld Nat -> Void
|
||||
;; Effect: reduces the target by a given amount
|
||||
;; > (move-target
|
||||
;; (orc-world (player 5 5 5) (list (monster 0 2) (monster 1 3)) 1 0)
|
||||
;; 1)
|
||||
;; (orc-world (player 5 5 5) (list (monster 0 2) (monster 1 3)) 1 1)
|
||||
(define (move-target w n)
|
||||
(set-orc-world-target! w (modulo (+ n (orc-world-target w)) MONSTER#)))
|
||||
|
||||
;; OrcWorld -> Void
|
||||
;; Effect: ends the player's turn by setting the number of attacks to 0
|
||||
(define (end-turn w)
|
||||
(set-orc-world-attack#! w 0))
|
||||
|
||||
;; OrcWorld -> Void
|
||||
;; Effect: reduces the number of remaining attacks for this turn
|
||||
;; and increases the player's health level
|
||||
(define (heal w)
|
||||
(decrease-attack# w)
|
||||
(player-health+ (orc-world-player w) HEALING))
|
||||
|
||||
;; OrcWorld -> Void
|
||||
;; Effect: reduces a targeted monster's health
|
||||
(define (stab w)
|
||||
(decrease-attack# w)
|
||||
(define target (current-target w))
|
||||
(define damage
|
||||
(random-quotient (player-strength (orc-world-player w))
|
||||
STAB-DAMAGE))
|
||||
(damage-monster target damage))
|
||||
|
||||
;; OrcWorld -> Void
|
||||
;; Effect: damages a random number of live monsters,
|
||||
;; determined by strength of the player
|
||||
;; starting with the currently targeted monster
|
||||
(define (flail w)
|
||||
(decrease-attack# w)
|
||||
(define target (current-target w))
|
||||
(define alive (filter monster-alive? (orc-world-lom w)))
|
||||
(define pick#
|
||||
(min
|
||||
(random-quotient (player-strength (orc-world-player w))
|
||||
FLAIL-DAMAGE)
|
||||
(length alive)))
|
||||
(define getem (cons target (take alive pick#)))
|
||||
(for-each (lambda (m) (damage-monster m 1)) getem))
|
||||
|
||||
;; OrcWorld -> Void
|
||||
;; Effect: decrease number of remaining attacks
|
||||
(define (decrease-attack# w)
|
||||
(set-orc-world-attack#! w (sub1 (orc-world-attack# w))))
|
||||
|
||||
;; Monster Nat -> Void
|
||||
;; Effect: reduces the hit-strength of a monster
|
||||
(define (damage-monster m delta)
|
||||
(set-monster-health! m (interval- (monster-health m) delta)))
|
||||
|
||||
;; World -> Monster
|
||||
(define (current-target w)
|
||||
(list-ref (orc-world-lom w) (orc-world-target w)))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; monster action
|
||||
|
||||
;; OrcWorld -> Void
|
||||
;; if it is the monsters turn, they attack
|
||||
;; > (orc-world (player 4 4 4) empty 3 3)
|
||||
;; (orc-world (player 4 4 4) empty 3 3)
|
||||
(define (give-monster-turn-if-attack#=0 w)
|
||||
(when (zero? (orc-world-attack# w))
|
||||
(define player (orc-world-player w))
|
||||
(all-monsters-attack-player player (orc-world-lom w))
|
||||
(set-orc-world-attack#! w (random-number-of-attacks player))))
|
||||
|
||||
;; Player [Listof Monster] -> Void
|
||||
;; Each monster attacks the player
|
||||
(define (all-monsters-attack-player player lom)
|
||||
;; Monster -> Void
|
||||
(define (one-monster-attacks-player monster)
|
||||
(cond
|
||||
[(orc? monster)
|
||||
(player-health+ player (random- (orc-club monster)))]
|
||||
[(hydra? monster)
|
||||
(player-health+ player (random- (monster-health monster)))]
|
||||
[(slime? monster)
|
||||
(player-health+ player -1)
|
||||
(player-agility+ player (random- (slime-sliminess monster)))]
|
||||
[(brigand? monster)
|
||||
(case (random 3)
|
||||
[(0) (player-health+ player HEALTH-DAMAGE)]
|
||||
[(1) (player-agility+ player AGILITY-DAMAGE)]
|
||||
[(2) (player-strength+ player STRENGTH-DAMAGE)])]))
|
||||
;; -- IN --
|
||||
(for-each one-monster-attacks-player (filter monster-alive? lom)))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; actions on player
|
||||
|
||||
;; [Player -> Nat] [Player Nat -> Void] Nat -> Player Nat -> Void
|
||||
;; effect: change player's selector attribute by adding delta, but max out
|
||||
(define (player-update! setter selector max-value)
|
||||
(lambda (player delta)
|
||||
(setter player
|
||||
(interval+ (selector player) delta max-value))))
|
||||
|
||||
;; Player Nat -> Void
|
||||
(define player-health+
|
||||
(player-update! set-player-health! player-health MAX-HEALTH))
|
||||
|
||||
;; Player Nat -> Void
|
||||
(define player-agility+
|
||||
(player-update! set-player-agility! player-agility MAX-AGILITY))
|
||||
|
||||
;; Player Nat -> Void
|
||||
(define player-strength+
|
||||
(player-update! set-player-strength! player-strength MAX-STRENGTH))
|
||||
|
||||
;
|
||||
;
|
||||
;
|
||||
; ;;;;; ;; ;
|
||||
; ; ; ;
|
||||
; ; ; ;;;; ;; ;; ;;; ; ;;;; ;; ;;; ;;; ;; ;; ;;; ;;
|
||||
; ; ; ; ; ;; ; ; ;; ; ; ;; ; ;; ; ; ;;
|
||||
; ;;;; ;;;;;; ; ; ; ; ;;;;;; ; ; ; ; ; ;
|
||||
; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
|
||||
; ; ; ; ; ; ; ;; ; ; ; ; ; ; ;;
|
||||
; ;;; ; ;;;;; ;;; ;;; ;;; ;; ;;;;; ;;;;; ;;;;; ;;; ;;; ;;; ;
|
||||
; ;
|
||||
; ;;;;
|
||||
;
|
||||
;
|
||||
|
||||
;; OrcWorld Boolean Image -> Image
|
||||
;; draws all the monsters and the player, then adds message
|
||||
(define (render-orc-world w with-target additional-text)
|
||||
(define i-player (render-player (orc-world-player w)))
|
||||
(define i-monster (render-monsters (orc-world-lom w) with-target))
|
||||
(above V-SPACER
|
||||
(beside H-SPACER
|
||||
i-player
|
||||
H-SPACER H-SPACER H-SPACER
|
||||
(above i-monster
|
||||
V-SPACER V-SPACER V-SPACER
|
||||
additional-text)
|
||||
H-SPACER)
|
||||
V-SPACER))
|
||||
|
||||
;; Player -> Image
|
||||
;; render player with three status bars
|
||||
(define (render-player p)
|
||||
(above/align
|
||||
"left"
|
||||
(status-bar (player-strength p) MAX-STRENGTH STRENGTH-COLOR STRENGTH)
|
||||
V-SPACER
|
||||
(status-bar (player-agility p) MAX-AGILITY AGILITY-COLOR AGILITY)
|
||||
V-SPACER
|
||||
(status-bar (player-health p) MAX-HEALTH HEALTH-COLOR HEALTH)
|
||||
V-SPACER V-SPACER V-SPACER
|
||||
PLAYER-IMAGE))
|
||||
|
||||
;; Nat Nat Color String -> Image
|
||||
;; creates a labeled rectangle of width/max proportions
|
||||
;; assume: (<= width max)
|
||||
(define (status-bar v-current v-max color label)
|
||||
(define w (* (/ v-current v-max) HEALTH-BAR-WIDTH))
|
||||
(define f (rectangle w HEALTH-BAR-HEIGHT 'solid color))
|
||||
(define b (rectangle HEALTH-BAR-WIDTH HEALTH-BAR-HEIGHT 'outline color))
|
||||
(define bar (overlay/align 'left 'top f b))
|
||||
(beside bar H-SPACER (text label HEALTH-SIZE color)))
|
||||
|
||||
;; String -> Image
|
||||
(define (message str)
|
||||
(text str MESSAGES-SIZE MESSAGE-COLOR))
|
||||
|
||||
;; OrcWorld -> Image
|
||||
(define (instructions w)
|
||||
(define na (number->string (orc-world-attack# w)))
|
||||
(define ra (string-append REMAINING na))
|
||||
(above (text ra INSTRUCTION-TEXT-SIZE ATTACK-COLOR) INSTRUCTION-TEXT))
|
||||
|
||||
;; [Listof Monster] [Opt Nat] -> Image
|
||||
;; add all monsters on lom, including status bar
|
||||
;; label the target unless it isn't called for
|
||||
(define (render-monsters lom with-target)
|
||||
;; the currently targeted monster (if needed)
|
||||
(define target
|
||||
(if (number? with-target)
|
||||
(list-ref lom with-target)
|
||||
'a-silly-symbol-that-cannot-be-eq-to-an-orc))
|
||||
|
||||
;; Monster -> Image
|
||||
(define (render-one-monster m)
|
||||
(define image
|
||||
(if (eq? m target)
|
||||
(overlay TARGET (monster-image m))
|
||||
(monster-image m)))
|
||||
(define health (monster-health m))
|
||||
(define health-bar
|
||||
(if (= health 0)
|
||||
(overlay DEAD-TEXT (status-bar 0 1 'white ""))
|
||||
(status-bar health MONSTER-HEALTH0 MONSTER-COLOR "")))
|
||||
(above health-bar image))
|
||||
|
||||
(arrange (map render-one-monster lom)))
|
||||
|
||||
;; [Listof Image] -> Image
|
||||
;; break a list of images into rows of PER-ROW
|
||||
(define (arrange lom)
|
||||
(cond
|
||||
[(empty? lom) empty-image]
|
||||
[else (define row-image (apply beside (take lom PER-ROW)))
|
||||
(above row-image (arrange (drop lom PER-ROW)))]))
|
||||
|
||||
|
||||
;
|
||||
;
|
||||
;
|
||||
; ;;;;;; ;; ;;;
|
||||
; ; ; ; ; ;
|
||||
; ; ; ;; ;; ;;; ; ;
|
||||
; ;;; ;; ; ; ;; ;
|
||||
; ; ; ; ; ; ; ;
|
||||
; ; ; ; ; ; ;
|
||||
; ; ; ; ; ; ;;
|
||||
; ;;;;;; ;;; ;;; ;;; ;; ;;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
|
||||
;; OrcWorld -> Boolean
|
||||
;; Has the player won?
|
||||
;; > (orc-world (player 1 1 1) (list (monster 0 0)) 0 0)
|
||||
;; #t
|
||||
(define (win? w)
|
||||
(all-dead? (orc-world-lom w)))
|
||||
|
||||
;; OrcWorld -> Boolean
|
||||
;; Has the player lost?
|
||||
;; > (lose? (orc-world (player 0 2 2) empty 0 0))
|
||||
;; #t
|
||||
(define (lose? w)
|
||||
(player-dead? (orc-world-player w)))
|
||||
|
||||
;; Player -> Boolean
|
||||
;; Is the player dead?
|
||||
;; > (orc-world (player 1 0 1) empty 0 0)
|
||||
;; #t
|
||||
(define (player-dead? p)
|
||||
(or (= (player-health p) 0)
|
||||
(= (player-agility p) 0)
|
||||
(= (player-strength p) 0)))
|
||||
|
||||
;; [Listof Monster] -> Boolean
|
||||
;; Are all the monsters in the list dead?s
|
||||
;; > (all-dead? (orc-world (player 5 5 5) (list (monster 1 0)) 0 1))
|
||||
;; #t
|
||||
(define (all-dead? lom)
|
||||
(not (ormap monster-alive? lom)))
|
||||
|
||||
;; Monster -> Boolean
|
||||
;; Is the monster alive?
|
||||
(define (monster-alive? m)
|
||||
(> (monster-health m) 0))
|
||||
|
||||
|
||||
;
|
||||
;
|
||||
;
|
||||
; ;;
|
||||
; ;
|
||||
; ; ; ;; ;; ;; ;; ;;;;;
|
||||
; ; ; ; ; ; ; ; ;
|
||||
; ; ; ; ; ;; ;;;;
|
||||
; ;;; ; ; ;; ;
|
||||
; ; ; ; ;; ; ; ; ;
|
||||
; ;;; ;;; ;; ;; ;; ;; ;;;;;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
|
||||
;; Nat Nat -> Nat
|
||||
;; a random number between 1 and the (quotient x y)
|
||||
(define (random-quotient x y)
|
||||
(define div (quotient x y))
|
||||
(if (> 0 div) 0 (random+ (add1 div))))
|
||||
|
||||
;; Nat -> Nat
|
||||
;; (random+ n) creates a random number in [1,n]
|
||||
(define (random+ n)
|
||||
(add1 (random n)))
|
||||
|
||||
;; Nat -> Nat
|
||||
;; (random+ n) creates a random number in [-n,-1]
|
||||
(define (random- n)
|
||||
(- (add1 (random n))))
|
||||
|
||||
;; Nat Nat [Nat] -> Nat
|
||||
;; subtract n from m but stay in [0,max-value]
|
||||
(define (interval- n m (max-value 100))
|
||||
(min (max 0 (- n m)) max-value))
|
||||
|
||||
;; Nat Nat [Nat] -> Nat
|
||||
;; subtract n from m but stay in [0,max-value]
|
||||
(define (interval+ n m (max-value 100))
|
||||
(interval- n (- m) max-value))
|
||||
|
||||
;
|
||||
;
|
||||
;
|
||||
; ;;;;;;
|
||||
; ; ; ;
|
||||
; ; ;;;; ;;;;; ;;;;; ;;;;;
|
||||
; ; ; ; ; ; ; ; ;
|
||||
; ; ;;;;;; ;;;; ; ;;;;
|
||||
; ; ; ; ; ;
|
||||
; ; ; ; ; ; ; ; ;
|
||||
; ;;; ;;;;; ;;;;; ;;; ;;;;;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
|
||||
(module+ test
|
||||
|
||||
(require rackunit rackunit/text-ui)
|
||||
|
||||
;; Test structs
|
||||
(define WORLD0 (orc-world (initialize-player) empty 0 0))
|
||||
(define WORLD1 (struct-copy orc-world (initialize-orc-world) [attack# 5]))
|
||||
(define (WORLD2) (struct-copy orc-world (initialize-orc-world) [attack# 0]))
|
||||
;; these are random worlds
|
||||
(define AN-ORC (orc 'image 0 5))
|
||||
(define A-SLIME (slime 'image 1 6))
|
||||
(define A-HYDRA (hydra 'image 2))
|
||||
(define A-BRIGAND (brigand 'image 3))
|
||||
|
||||
;; testing move-target
|
||||
|
||||
(check-equal? (let ([w (orc-world 'dummy 'dummy 'dummy 0)])
|
||||
(move-target w +1)
|
||||
w)
|
||||
(orc-world 'dummy 'dummy 'dummy 1))
|
||||
(check-equal? (let ([w (orc-world 'dummy 'dummy 'dummy 0)])
|
||||
(move-target w -1)
|
||||
w)
|
||||
(orc-world 'dummy 'dummy 'dummy (- MONSTER# 1)))
|
||||
(check-equal? (let ([w (orc-world 'dummy 'dummy 'dummy 0)])
|
||||
(move-target w (- PER-ROW))
|
||||
w)
|
||||
(orc-world 'dummy 'dummy 'dummy (- MONSTER# PER-ROW)))
|
||||
(check-equal? (let ([w (orc-world 'dummy 'dummy 'dummy 1)])
|
||||
(move-target w (+ PER-ROW))
|
||||
w)
|
||||
(orc-world 'dummy 'dummy 'dummy (+ PER-ROW 1)))
|
||||
(check-equal? (begin
|
||||
(move-target WORLD1 0)
|
||||
WORLD1)
|
||||
WORLD1)
|
||||
(check-equal? (let ()
|
||||
(define w (struct-copy orc-world WORLD1))
|
||||
(move-target w 4)
|
||||
w)
|
||||
(struct-copy orc-world WORLD1 [target (+ 4 (orc-world-target WORLD1))]))
|
||||
(check-equal? (current-target WORLD1)
|
||||
(first (orc-world-lom WORLD1)))
|
||||
|
||||
;; testing basic player manipulations
|
||||
|
||||
(check-equal? (let ([p (player 1 0 0)])
|
||||
(player-health+ p 5)
|
||||
p)
|
||||
(player 6 0 0))
|
||||
(check-equal? (let ([p (player 0 1 0)])
|
||||
(player-agility+ p 5)
|
||||
p)
|
||||
(player 0 6 0))
|
||||
|
||||
(check-equal? (let ([p (player 0 0 1)])
|
||||
(player-strength+ p 5)
|
||||
p)
|
||||
(player 0 0 6))
|
||||
|
||||
(check-equal? (let ([p (player 5 5 5)])
|
||||
(all-monsters-attack-player p (list (orc 'image 1 1)))
|
||||
p)
|
||||
(player 4 5 5))
|
||||
|
||||
(check-equal? (let ([p (player 5 5 5)])
|
||||
(all-monsters-attack-player p (list (hydra 'image 1)))
|
||||
p)
|
||||
(player 4 5 5))
|
||||
|
||||
(check-equal? (let ([p (player 5 5 5)])
|
||||
(all-monsters-attack-player p (list (slime 'image 1 1)))
|
||||
p)
|
||||
(player 4 4 5))
|
||||
|
||||
(check member
|
||||
(let ([p (player 5 5 5)])
|
||||
(all-monsters-attack-player p (list (brigand 'image 1)))
|
||||
p)
|
||||
(list (player 3 5 5)
|
||||
(player 5 2 5)
|
||||
(player 5 5 1)))
|
||||
|
||||
;; Properties
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Property:
|
||||
;; the output will always be in [1, (/ X Y)]
|
||||
(define (prop:rand-frac-range i)
|
||||
(test-begin
|
||||
(for ([i (in-range i)])
|
||||
(define x (random 4294967087))
|
||||
(define y (random 4294967087))
|
||||
(check-true (<= 1 (random-quotient x y) (add1 (/ x y)))))))
|
||||
|
||||
;; Property:
|
||||
;; The number of the monsters in the list is equal to
|
||||
;; MONSTER-NUM
|
||||
(define (prop:monster-init-length i)
|
||||
(test-begin
|
||||
(for ([i (in-range i)])
|
||||
(check-true (= MONSTER#
|
||||
(length (initialize-monsters)))))))
|
||||
|
||||
;; Property:
|
||||
;; the player will have less points in at least one of its
|
||||
;; fields
|
||||
(define (prop:monster-attack-player-dec i)
|
||||
(test-begin
|
||||
(for ([i (in-range i)])
|
||||
(define pl (player MAX-HEALTH MAX-AGILITY MAX-STRENGTH))
|
||||
(define mon (first (initialize-monsters)))
|
||||
(begin
|
||||
(all-monsters-attack-player pl (list mon))
|
||||
(check-true (or (< (player-health pl) MAX-HEALTH)
|
||||
(< (player-agility pl) MAX-AGILITY)
|
||||
(< (player-strength pl) MAX-STRENGTH)))))))
|
||||
|
||||
;; Property:
|
||||
;; If there are monster, then the player will
|
||||
;; have less points in at least one of its fields
|
||||
(define (prop:monsters-attack-player-dec i)
|
||||
(test-begin
|
||||
(for ([i (in-range i)])
|
||||
(define pl (player MAX-HEALTH MAX-AGILITY MAX-STRENGTH))
|
||||
(define monsters (initialize-monsters))
|
||||
(define wor (orc-world pl monsters 0 0))
|
||||
(begin
|
||||
(all-monsters-attack-player pl monsters)
|
||||
(check-true (or (< (player-health pl) MAX-HEALTH)
|
||||
(< (player-agility pl) MAX-AGILITY)
|
||||
(< (player-strength pl) MAX-STRENGTH)))))))
|
||||
|
||||
;; Property: The health of the targeted monster, m,
|
||||
;; is less than what it was. and
|
||||
;; [(sub1 (monster-health m)),
|
||||
;; (- (monster-health m)
|
||||
;; (/ (player-strength (orc-world-player w)) 2))]
|
||||
(define (prop:stab!-health i)
|
||||
(test-begin
|
||||
(for ([i (in-range i)])
|
||||
(begin (define mon (first(initialize-monsters)))
|
||||
(define ht (monster-health mon))
|
||||
(define pl (random-player))
|
||||
(define w (orc-world pl (list mon) 2 0))
|
||||
(stab w)
|
||||
(check-true (> ht (monster-health (first (orc-world-lom w)))))))))
|
||||
|
||||
;; random-player: -> Player
|
||||
;; creates a random player
|
||||
(define (random-player)
|
||||
(player (add1 (random MAX-HEALTH))
|
||||
(add1 (random MAX-AGILITY))
|
||||
(add1 (random MAX-STRENGTH))))
|
||||
|
||||
;; testing initializers
|
||||
(prop:monster-init-length 1000)
|
||||
(check-true (monster? (first (initialize-monsters))))
|
||||
(check-true (> 10 (monster-health (first (initialize-monsters)))))
|
||||
(check-equal? (length (initialize-monsters)) MONSTER#)
|
||||
(check-equal? (length (orc-world-lom WORLD1)) MONSTER#)
|
||||
(check-true (>= (let ([p (initialize-player)])
|
||||
(player-health p))
|
||||
(let ([p (initialize-player)])
|
||||
(all-monsters-attack-player p (list AN-ORC))
|
||||
(player-health p))))
|
||||
(check-true (> (player-health (initialize-player))
|
||||
(let ([p (initialize-player)])
|
||||
(all-monsters-attack-player p (list A-HYDRA))
|
||||
(player-health p))))
|
||||
(check-true (< (let ([p (initialize-player)])
|
||||
(all-monsters-attack-player p (list A-SLIME))
|
||||
(player-agility p))
|
||||
(let ([p (initialize-player)])
|
||||
(player-agility p))))
|
||||
(check-true (let ([p (initialize-player)])
|
||||
(all-monsters-attack-player p (list A-BRIGAND))
|
||||
(or (= (player-health p)
|
||||
(- (player-health (initialize-player)) 2))
|
||||
(= (player-agility p)
|
||||
(- (player-agility (initialize-player)) 3))
|
||||
(= (player-strength p)
|
||||
(- (player-strength (initialize-player)) 4)))))
|
||||
(check-equal? (length (orc-world-lom WORLD1)) MONSTER#)
|
||||
(check-equal? (orc-world-player WORLD1) (orc-world-player WORLD1))
|
||||
|
||||
;; testing the-monster's attacks
|
||||
|
||||
(prop:monster-attack-player-dec 1000)
|
||||
(prop:monsters-attack-player-dec 1000)
|
||||
(check-true (or (> (player-health (orc-world-player (WORLD2)))
|
||||
(player-health (orc-world-player
|
||||
(let ([w (WORLD2)])
|
||||
(all-monsters-attack-player (orc-world-player w) (orc-world-lom w))
|
||||
w))))
|
||||
(> (player-strength (orc-world-player (WORLD2)))
|
||||
(player-strength (orc-world-player
|
||||
(let ([w (WORLD2)])
|
||||
(all-monsters-attack-player (orc-world-player w) (orc-world-lom w))
|
||||
w))))
|
||||
(> (player-agility (orc-world-player (WORLD2)))
|
||||
(player-agility (orc-world-player
|
||||
(let ([w (WORLD2)])
|
||||
(all-monsters-attack-player (orc-world-player w) (orc-world-lom w))
|
||||
w))))))
|
||||
|
||||
;; testing the player's actions
|
||||
|
||||
(prop:stab!-health 1000)
|
||||
(test-begin (define o (orc 'image 0 5))
|
||||
(damage-monster o 5)
|
||||
(check-equal? o (orc 'image 0 5)))
|
||||
(test-begin (define o (orc 'image 0 5))
|
||||
(damage-monster o 0)
|
||||
(check-equal? o (orc 'image 0 5)))
|
||||
(check-equal? (player-health (orc-world-player
|
||||
(let ()
|
||||
(define w (struct-copy orc-world WORLD1))
|
||||
(heal w)
|
||||
w)))
|
||||
(min MAX-HEALTH
|
||||
(+ 8 (player-health (orc-world-player WORLD1)))))
|
||||
|
||||
(check-equal? (length (orc-world-lom
|
||||
(let ()
|
||||
(define w (struct-copy orc-world WORLD1))
|
||||
(stab w)
|
||||
w)))
|
||||
MONSTER#)
|
||||
|
||||
;; testing game predicates
|
||||
|
||||
(check-false (lose? WORLD0))
|
||||
(check-true (lose? (orc-world (player 0 30 30) empty 0 0)))
|
||||
(check-true (all-dead? (list (orc 'image 0 0) (hydra 'image 0))))
|
||||
(check-true (all-dead? (list AN-ORC)))
|
||||
(check-true (win? (orc-world (initialize-player) (list (orc 'image 0 0)) 0 0)))
|
||||
(check-true (win? (orc-world (initialize-player) (list AN-ORC) 0 0)))
|
||||
(check-true (end-of-orc-battle? (orc-world (initialize-player) (list (orc 'image 0 0)) 0 0)))
|
||||
(check-true (end-of-orc-battle? (orc-world (initialize-player) (list AN-ORC) 0 0)))
|
||||
(check-true (end-of-orc-battle? (orc-world (player 0 30 30) empty 0 0)))
|
||||
(check-true (player-dead? (player 0 2 5)))
|
||||
(check-false (player-dead? (initialize-player)))
|
||||
(check-false (not (monster-alive? A-HYDRA)))
|
||||
(check-true (monster-alive? (monster 'image 1)))
|
||||
(check-false (monster-alive? (orc 'image 0 0)))
|
||||
|
||||
;; testing utilities
|
||||
|
||||
(prop:rand-frac-range 1000)
|
||||
|
||||
"all tests run")
|
Loading…
Reference in New Issue
Block a user