From 835c3b8b7ed0c6bfd6722cbe3fbff54405e6e011 Mon Sep 17 00:00:00 2001 From: rsiddharth Date: Mon, 28 Jan 2019 19:19:01 -0500 Subject: net: Add eight/orc.rkt --- net/ricketyspace/ror/eight/graphics/README.txt | 3 + net/ricketyspace/ror/eight/graphics/brigand.bmp | Bin 0 -> 6960 bytes net/ricketyspace/ror/eight/graphics/hydra.png | Bin 0 -> 11948 bytes net/ricketyspace/ror/eight/graphics/orc.gif | Bin 0 -> 1980 bytes net/ricketyspace/ror/eight/graphics/orc.png | Bin 0 -> 10504 bytes net/ricketyspace/ror/eight/graphics/orcSprite.png | Bin 0 -> 7949 bytes net/ricketyspace/ror/eight/graphics/player.bmp | Bin 0 -> 92856 bytes net/ricketyspace/ror/eight/graphics/slime.bmp | Bin 0 -> 21056 bytes net/ricketyspace/ror/eight/orc.rkt | 889 ++++++++++++++++++++++ 9 files changed, 892 insertions(+) create mode 100644 net/ricketyspace/ror/eight/graphics/README.txt create mode 100644 net/ricketyspace/ror/eight/graphics/brigand.bmp create mode 100644 net/ricketyspace/ror/eight/graphics/hydra.png create mode 100644 net/ricketyspace/ror/eight/graphics/orc.gif create mode 100644 net/ricketyspace/ror/eight/graphics/orc.png create mode 100644 net/ricketyspace/ror/eight/graphics/orcSprite.png create mode 100644 net/ricketyspace/ror/eight/graphics/player.bmp create mode 100644 net/ricketyspace/ror/eight/graphics/slime.bmp create mode 100644 net/ricketyspace/ror/eight/orc.rkt 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 Binary files /dev/null and b/net/ricketyspace/ror/eight/graphics/brigand.bmp 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 Binary files /dev/null and b/net/ricketyspace/ror/eight/graphics/hydra.png 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 Binary files /dev/null and b/net/ricketyspace/ror/eight/graphics/orc.gif 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 Binary files /dev/null and b/net/ricketyspace/ror/eight/graphics/orc.png 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 Binary files /dev/null and b/net/ricketyspace/ror/eight/graphics/orcSprite.png 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 Binary files /dev/null and b/net/ricketyspace/ror/eight/graphics/player.bmp 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 Binary files /dev/null and b/net/ricketyspace/ror/eight/graphics/slime.bmp 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") -- cgit v1.2.3