summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrsiddharth <s@ricketyspace.net>2019-01-28 19:19:01 -0500
committerrsiddharth <s@ricketyspace.net>2019-01-28 19:19:01 -0500
commit835c3b8b7ed0c6bfd6722cbe3fbff54405e6e011 (patch)
tree082a312cbdc11ecb3af4b6fe7603b960e0300925
parent127260da372fa6131833f43b3cd032332aa04607 (diff)
net: Add eight/orc.rkt
-rw-r--r--net/ricketyspace/ror/eight/graphics/README.txt3
-rw-r--r--net/ricketyspace/ror/eight/graphics/brigand.bmpbin0 -> 6960 bytes
-rw-r--r--net/ricketyspace/ror/eight/graphics/hydra.pngbin0 -> 11948 bytes
-rw-r--r--net/ricketyspace/ror/eight/graphics/orc.gifbin0 -> 1980 bytes
-rw-r--r--net/ricketyspace/ror/eight/graphics/orc.pngbin0 -> 10504 bytes
-rw-r--r--net/ricketyspace/ror/eight/graphics/orcSprite.pngbin0 -> 7949 bytes
-rw-r--r--net/ricketyspace/ror/eight/graphics/player.bmpbin0 -> 92856 bytes
-rw-r--r--net/ricketyspace/ror/eight/graphics/slime.bmpbin0 -> 21056 bytes
-rw-r--r--net/ricketyspace/ror/eight/orc.rkt889
9 files changed, 892 insertions, 0 deletions
diff --git a/net/ricketyspace/ror/eight/graphics/README.txt b/net/ricketyspace/ror/eight/graphics/README.txt
new file mode 100644
index 0000000..5895c77
--- /dev/null
+++ b/net/ricketyspace/ror/eight/graphics/README.txt
@@ -0,0 +1,3 @@
+Graphics from github.com/racket/realm
+
+Commit: 973041cb6a5c696b99b79a
diff --git a/net/ricketyspace/ror/eight/graphics/brigand.bmp b/net/ricketyspace/ror/eight/graphics/brigand.bmp
new file mode 100644
index 0000000..7bbd3c0
--- /dev/null
+++ b/net/ricketyspace/ror/eight/graphics/brigand.bmp
Binary files differ
diff --git a/net/ricketyspace/ror/eight/graphics/hydra.png b/net/ricketyspace/ror/eight/graphics/hydra.png
new file mode 100644
index 0000000..b495920
--- /dev/null
+++ b/net/ricketyspace/ror/eight/graphics/hydra.png
Binary files differ
diff --git a/net/ricketyspace/ror/eight/graphics/orc.gif b/net/ricketyspace/ror/eight/graphics/orc.gif
new file mode 100644
index 0000000..5553322
--- /dev/null
+++ b/net/ricketyspace/ror/eight/graphics/orc.gif
Binary files differ
diff --git a/net/ricketyspace/ror/eight/graphics/orc.png b/net/ricketyspace/ror/eight/graphics/orc.png
new file mode 100644
index 0000000..d269b6a
--- /dev/null
+++ b/net/ricketyspace/ror/eight/graphics/orc.png
Binary files differ
diff --git a/net/ricketyspace/ror/eight/graphics/orcSprite.png b/net/ricketyspace/ror/eight/graphics/orcSprite.png
new file mode 100644
index 0000000..cdc223c
--- /dev/null
+++ b/net/ricketyspace/ror/eight/graphics/orcSprite.png
Binary files differ
diff --git a/net/ricketyspace/ror/eight/graphics/player.bmp b/net/ricketyspace/ror/eight/graphics/player.bmp
new file mode 100644
index 0000000..c6a04a0
--- /dev/null
+++ b/net/ricketyspace/ror/eight/graphics/player.bmp
Binary files differ
diff --git a/net/ricketyspace/ror/eight/graphics/slime.bmp b/net/ricketyspace/ror/eight/graphics/slime.bmp
new file mode 100644
index 0000000..208c188
--- /dev/null
+++ b/net/ricketyspace/ror/eight/graphics/slime.bmp
Binary files differ
diff --git a/net/ricketyspace/ror/eight/orc.rkt b/net/ricketyspace/ror/eight/orc.rkt
new file mode 100644
index 0000000..5a7aaad
--- /dev/null
+++ b/net/ricketyspace/ror/eight/orc.rkt
@@ -0,0 +1,889 @@
+#lang racket
+
+#|
+
+ From github.com/racket/realm
+
+ Commit: 973041cb6a5c696b99b79a
+|#
+
+#|
+ 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".
+
+ Play
+ ----
+
+ Run 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")