Skip to content

Instantly share code, notes, and snippets.

@jmhdez
Created July 18, 2016 20:04
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jmhdez/ab1daa90e5b79e501480c9a69d95f0d4 to your computer and use it in GitHub Desktop.
RPG Kata
(ns rpg-kata-clj.core
(:require [clojure.set :refer [intersection]]))
(defrecord Entity [health level attack factions type])
(defn new-character []
(->Entity 1000 1 :melee #{} :character))
(defn new-prop [health]
(->Entity health 1 :melee #{} :property))
(defn damage-multiplier [source-level target-level]
(let [delta (- source-level target-level)]
(cond (>= delta 5) 2.0
(<= delta -5) 0.5
:else 1.0)))
(defn in-range? [attack distance]
(<= distance ({:melee 2 :ranged 20} attack)))
(defn allies? [source-factions target-factions]
(boolean (not-empty (intersection source-factions target-factions))))
(defn attack [source target amount distance]
(assert (not (identical? source target)) "a character cannot deal damage to himself")
(assert (in-range? (:attack source) distance) "target is not in range")
(assert (not (allies? (:factions source) (:factions target))) "cannot attack allies")
(assert (not= :property (:type source)) "properties cannot attack")
(let [damage (* amount (damage-multiplier (:level source) (:level target)))
health (int (max 0 (- (:health target) damage)))]
(assoc target :health health)))
(defn heal [source target amount]
(assert (not= :property (:type source)) "properties cannot heal")
(assert (not= :property (:type target)) "properties cannot be healed")
(assert (or (identical? source target)
(allies? (:factions source) (:factions target)))
"a character can only heal himself or allies")
(if (zero? (:health target))
target
(assoc target :health (min 1000 (+ amount (:health target))))))
(ns rpg-kata-clj.core-test
(:require [clojure.test :refer :all]
[rpg-kata-clj.core :refer :all]))
(defn a-char
([] (a-char {}))
([options]
(map->Entity
(into {:level 1 :health 1000 :attack :melee :factions #{} :type :character}
options))))
(defn a-prop
([] (a-prop {:health 1000}))
([options]
(map->Entity
(into {:level 1 :health 1000 :attack :melee :factions #{} :type :property}
options))))
(deftest when-a-new-character-is-created
(let [c (new-character)]
(testing "it starts with health 1000"
(is (= 1000 (:health c))))
(testing "it-starts with level 1"
(is (= 1 (:level c))))))
(deftest when-calculating-damage-multiplier
(testing "it is 2 when source level is at least 5 above target level"
(is (= 2.0 (damage-multiplier 10 5)))
(is (= 2.0 (damage-multiplier 11 0))))
(testing "it is 0.5 when target level is at least 5 above source level"
(is (= 0.5 (damage-multiplier 5 10)))
(is (= 0.5 (damage-multiplier 0 11))))
(testing "it is 1 when source and target level are closer than 5"
(is (= 1.0 (damage-multiplier 1 4)))
(is (= 1.0 (damage-multiplier 4 1)))))
(deftest when-calculating-if-an-attack-is-in-range
(testing "a melee attack is in range when distance is at most 2"
(is (true? (in-range? :melee 1)))
(is (true? (in-range? :melee 2)))
(is (false? (in-range? :melee 3))))
(testing "a ranged attack is in range when distance is at most 20"
(is (true? (in-range? :ranged 10)))
(is (true? (in-range? :ranged 20)))
(is (false? (in-range? :ranged 21)))))
(deftest when-detecting-allies
(testing "two characters are allies when have a common faction"
(is (true? (allies? #{:green} #{:red :green}))))
(testing "two characters are not allies when have no common factions"
(is (false? (allies? #{:red} #{:blue}))))
(testing "two characters are not allies when any of them have no factions"
(is (false? (allies? #{} #{:green})))
(is (false? (allies? #{:green} #{})))
(is (false? (allies? #{} #{})))))
(deftest when-attacking-a-character
(let [source (a-char {:level 1})
target (a-char {:level 3})
level-10 (a-char {:level 10})
level-1 (a-char {:level 1})
door (a-prop {:health 1000})]
(testing "it cannot attack himself"
(is (thrown? AssertionError (attack source source 100 1))))
(testing "it cannot attack targets out of range"
(is (thrown? AssertionError
(attack (a-char {:attack :melee}) (a-char) 100 3)))
(is (thrown? AssertionError
(attack (a-char {:attack :ranged}) (a-char) 100 21))))
(testing "it cannot attack allies"
(is (thrown? AssertionError
(attack (a-char {:factions #{:red}})
(a-char {:factions #{:red}})
100 1))))
(testing "its health is decreased"
(is (= 5 (:health (attack source target 995 1)))))
(testing "the damage is adjusted depending on character levels"
(is (= 0 (:health (attack level-10 level-1 500 1))))
(is (= 750 (:health (attack level-1 level-10 500 1)))))
(testing "its health cannot be less than zero"
(is (= 0 (:health (attack source target 1500 1)))))
(testing "properties cannot attack"
(is (thrown? AssertionError (attack door source 100 1))))
(testing "properties can be attacked"
(is (= 500 (:health (attack source door 500 1)))))))
(deftest when-healing-a-caracter
(let [wounded (a-char {:health 800 :level 3 :factions #{:red}})
wounded-ally (a-char {:health 800 :level 5 :factions #{:red}})
dead (a-char {:health 0})
door (a-prop)]
(testing "it cannot heal enemies"
(is (thrown? AssertionError (heal wounded (new-character) 100))))
(testing "it can health allies"
(is (= 900 (:health (heal wounded wounded-ally 100)))))
(testing "its health is increased"
(is (= 900 (:health (heal wounded wounded 100)))))
(testing "its health cannot be greater than 1000"
(is (= 1000 (:health (heal wounded wounded 1500)))))
(testing "its health does not change if it is dead"
(is (= 0 (:health (heal dead dead 100)))))
(testing "properties cannot heal"
(is (thrown? AssertionError (heal door wounded 100))))
(testing "properties cannot be healed"
(is (thrown? AssertionError (heal wounded door 100))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment