diff --git a/src/rekrvn/modules/resistance.clj b/src/rekrvn/modules/resistance.clj deleted file mode 100644 index 602eaa0..0000000 --- a/src/rekrvn/modules/resistance.clj +++ /dev/null @@ -1,407 +0,0 @@ -(ns rekrvn.modules.resistance - (:require [rekrvn.hub :as hub]) - (:require [clojure.string :as s]) - (:require [rekrvn.config :as c]) - (:require [rekrvn.modules.irc.client :as irc])) - -(def mod-name "resistance") - -;; Initial game state - -(def initial-game-state - "The defaults for the game state." - {:state :inactive - :players [] - :missions [] - :leader nil - :current-team [] - :score {:resistance 0 - :spies 0}}) - -;; Team balancing. Resistance / Spies. - -(defn gen-teams [r s] - "Generate a team with resistance members and - spies." - (concat - (repeat r :resistance) - (repeat s :spies))) - -(def team-balances - "The balances of resistance vs spies for valid game sizes." - {5 (gen-teams 3 2) - 6 (gen-teams 4 2) - 7 (gen-teams 4 3) - 8 (gen-teams 5 3) - 9 (gen-teams 6 3) - 10 (gen-teams 6 4)}) - -(def mission-team-sizes - "The mission info. Each entry in the vector corresponds - to the number of players required in each mission. If - the entry is a number, the mission requires one (default) - negative to fail. If the entry is a vector, the first - item is the number of players and the second item is the - number of negatives required for failure." - {5 [2 3 2 3 3] - 6 [2 3 4 3 4] - 7 [2 3 3 [4 2] 4] - 8 [3 4 4 [5 2] 5] - 9 [3 4 4 [5 2] 5] - 10 [3 4 4 [5 2] 5]}) - -(defn expand-mission-team-info [info] - "Expands based on the protocol for defining team sizes." - (cond - (number? info) [info 1] - (vector? info) info)) - -;; Global game state -(def game-state - (ref initial-game-state)) - -(defn get-mission-team-sizes [size] - (let [mission-team-size (get mission-team-sizes size)] - (map expand-mission-team-info mission-team-size))) - -(defn get-current-mission-num [] - (let [missions (:missions @game-state)] - (- 5 (count missions)))) - -(defn get-num-players [] - (count (:players @game-state))) - -(defn get-current-mission [] - (first (:missions @game-state))) - -(defn set-teams [players] - "Given a list of players, return the players with proper - assignments, returning nil when the number of players - is too large or too small." - (when-let [initial-teams (get team-balances (count players))] - (let [assignments (shuffle initial-teams)] - (map #(conj %1 {:team %2}) players assignments)))) - -;; Utility Fns - -(defmacro in-state-sync [state & forms] - "When the game is in the given state, evaluate the forms. - Otherwise, return nil." - `(dosync - (when (= (:state @game-state) ~state) - (do ~@forms)))) - -;; User actions - -(defn is-player? [name] - "Verify the existence of a player with the name " - (let [players (:players @game-state)] - (some #(= (:name %) name) players))) - -(def resistance-network (:network c/resistance-config)) -(def resistance-nick (:nick c/resistance-config)) -(def resistance-channel (:channel c/resistance-config)) - -(defn enable-user-messages [name] - (irc/modAllow resistance-network name mod-name)) - -(defn disable-user-messages [name] - (irc/modDeny resistance-network name mod-name)) - -(defn join-game [name] - "Adds a user to the game if the game has not started. - Returns true on success and nil on failure." - (in-state-sync - :inactive - (let [players (:players @game-state) - new-players (cons {:name name - :team nil - :current-vote nil} - players)] - (if (< (count players) 10) - (if (not (is-player? name)) - (do - (alter game-state conj {:players new-players}) - (enable-user-messages name) - :player-joined) - :already-joined) - :max-players)))) - -(declare private-message) -(defn send-team-message [player] - (let [name (:name player) - team (:team player)] - (private-message name (str "You're on the " team)))) - -(defn start-game [] - "Initializes a game. Sets state to active, chooses teams, - notifies the players of their teams and starts the game." - (in-state-sync - :inactive - (let [players (:players @game-state) - num-players (count players)] - (if (>= num-players 5) - (let [new-state {:players (set-teams players) - :missions (get-mission-team-sizes num-players) - :state :pick-team - :leader (rand-int num-players)}] - (alter game-state conj new-state) - (doseq [p (:players new-state)] - (send-team-message p)) - :game-started) - :not-enough-players)))) - -(defn is-on-team? [name] - "Verify is on the current name." - (let [team (:current-team @game-state)] - (some #(= name %) team))) - -(defn get-player [name] - "Retrieve the given player from the list." - (let [players (:players @game-state)] - (some #(= (:name %) name) players))) - -(defn leader? [name game-state] - (let [players (:players game-state) - leader-idx (:leader game-state) - leader (nth players leader-idx)] - (= (:name leader) name))) - -(defn pick-team [name team] - "Allow the current leader to choose their team." - (in-state-sync - :pick-team - (if (leader? name @game-state) - (if (= (first (get-current-mission)) (count team)) - (when (every? is-player? team) - (alter game-state - conj - {:current-team team - :state :voting}) - :team-ready) - :wrong-team-size) - :not-leader))) - -(defn update-player [name vals] - "Update the player with the values in vals." - (let [players (:players @game-state)] - (map (fn [p] - (if (= name (:name p)) - (conj p vals) - p)) - players))) - -(defn valid-vote? [vote] - "Verify the vote is for or against." - (#{"for" "against"} vote)) - -(defn votes-cast [] - "Return the number of votes already cast." - (let [players (:players @game-state) - reduce-fn (fn [l r] - (+ l (if (nil? (:current-vote r)) 0 1)))] - (reduce reduce-fn 0 players))) - -(defn mission-ready? [] - "Return whether or not the requisite votes have been placed." - (let [current-mission (get-current-mission) - mission-count (first current-mission) - votes-cast (votes-cast)] - (= votes-cast mission-count))) - -(defn get-player-vote [name] - (let [player (get-player name)] - (:current-vote player))) - -(defn cast-vote [name vote] - "Cast a user's vote." - (in-state-sync - :voting - (when (and (do - (println "got here one") - (valid-vote? vote)) - (do - (println "got here two") - (is-on-team? name)) - (do - (println "got here three") - (nil? (get-player-vote name)))) - (alter game-state - conj - {:players (update-player name {:current-vote vote})}) - (if (mission-ready?) - :mission-ready - :vote-cast)))) - -(defn tabulate-votes [[for against] vote] - (if (= vote "for") - [(inc for) against] - [for (inc against)])) - -(defn erase-votes [players] - (map #(conj % {:current-vote nil}) players)) - -(defn game-over? [game-state] - (let [score (:score game-state) - resistance-score (:resistance score) - spies-score (:spies score)] - (or (when (>= spies-score 3) :spies) - (when (>= resistance-score 3) :resistance)))) - -(defn unregister-players [players] - (doseq [p players] - (disable-user-messages (:name p))) - []) - -(defn evaluate-mission-helper [game-state] - (let [players (:players game-state) - num-players (count players) - [_ negs-required] (get-current-mission) - remaining-missions (rest (:missions game-state)) - votes (map :current-vote - (filter #(string? (:current-vote %)) players)) - [pass against] (reduce tabulate-votes [0 0] votes) - score (:score game-state) - winner (if (>= against negs-required) - :spies - :resistance) - new-score (conj score {winner inc}) - leader (:leader game-state) - new-leader (mod (inc leader) num-players) - new-state (if (game-over? game-state) - :inactive - :pick-team) - new-players (if (= new-state :inactive) - (unregister-players players) - (erase-votes players))] - [[winner [pass against]] {:missions remaining-missions - :score new-score - :state new-state - :leader new-leader - :players new-players}])) - -(def sample-state-end-of-mission - {:state :mission-ready - :missions [[4 1]] - :players [{:name "foo" - :current-vote "for"} - {:name "bar" - :current-vote "for"} - {:name "baz" - :current-vote "against"} - {:name "qux" - :current-vote "for"}] - :score {:resistance 1 - :spies 0} - :leader 3}) - -(defn evaluate-mission [] - "Determines who won the mission and updates state accordingly." - (in-state-sync - :mission-ready - (let [[winner updates] (evaluate-mission-helper @game-state)] - (alter game-state conj updates) - (println (str "Made updates: " updates)) - (println (str "Winner: " winner)) - winner))) - -(defn handle-join [[name] reply] - (let [result (join-game name)] - (cond - (= result :player-joined) (dosync - (let [players (:players @game-state) - names (map :name players) - name-str (s/join ", " names) - response (str name " joined the game. [" name-str "]")] - (reply mod-name response))) - (= result :already-joined) (reply mod-name "Player already joined.") - (= result :max-players) (reply mod-name "Too many players already.") - :else (reply mod-name "Game active. Wait until the game is over to join.")))) - -(defn resistance? [player] - (= (:team player) :resistance)) - -(defn spy? [player] - (= (:team player) :spies)) - -(defn private-message [nick msg] - (let [msg (str mod-name " forirc " resistance-network "#" nick " " msg)] - (hub/broadcast msg))) - -(defn current-leader [game-state] - (let [players (:players game-state) - leader-idx (:leader game-state)] - (nth players leader-idx))) - -(declare mission-prompt) -(defn initial-game-message [reply] - (reply mod-name "Starting the game. Your team will be sent to you in a private message.") - (dosync - (let [players (:players @game-state) - resistance (filter resistance? players) - spies (filter spy? players) - spies-str (fn [s] - (let [cohort (filter #(not (= (:name %) s)) spies)] - (s/join ", " cohort)))] - (doseq [r resistance] - (private-message r "You are on the resistance.")) - (doseq [s spies] - (private-message s (str "You are a spy. Fellow spies are " (spies-str s) "."))) - (reply mod-name (mission-prompt))))) - -(defn handle-start [_ reply] - (let [result (start-game)] - (cond - (= result :game-started) (initial-game-message reply) - (= result :not-enough-players) (reply mod-name "You need a minimum of 5 players (and maximum of 10) to start.") - :else (reply mod-name "Game already started.")))) - -(defn mission-prompt [] - "Determines the parameters of the next mission base on the current game-state. Returns a prompt for that mission." - (let [mission-num (inc (get-current-mission-num)) - team-size (first (get-current-mission)) - leader (current-leader @game-state) - leader-name (:name leader)] - (str leader-name " is the leader for mission " mission-num ". Choose a team of " team-size " to complete it."))) - -(defn start-mission [reply] - "Begins the voting after a team has been picked." - (let [mission-num-readable (inc (get-current-mission-num)) - total-players (count (:players @game-state)) - [num-players win-threshold] (get-current-mission) - votes-to-win (- num-players win-threshold) - mission-str (str "Mission " mission-num-readable " requires " votes-to-win " votes to pass.") - team (:current-team @game-state) - team-str (str "The team for this mission is: " (s/join ", " team) ".")] - (reply mod-name (s/join " " [mission-str team-str "Deliberate! Be scandalous! Vote!"])))) - -(defn handle-choose-team [[name team-str] reply] - (let [team (-> team-str - (s/trim) - (#(s/split % #" "))) - leader (current-leader @game-state) - leader-name (:name leader) - result (pick-team name team)] - (cond (= result :team-ready) (start-mission reply) - (= result :wrong-team-size) (reply mod-name (str "That team is the wrong size.")) - (= result :not-leader) (reply mod-name (str "The leader (" leader-name ") must choose the team.")) - :else (reply mod-name "Invalid game state. The team must be chosen at the beginning of a round.")))) - -(defn handle-vote [[name vote] reply] - (let [result (cast-vote name vote)] - (cond (= result :mission-ready) (let [[winner [pass fail]] (evaluate-mission) - winner-str (if (= winner :resistance) "resistance" "spies") - winner-line (str "The " winner-str " won the round " pass " passes to " fail " fails!")] - (println winner-line) - (private-message resistance-channel winner-line) - (private-message resistance-channel (mission-prompt))) - (= result :vote-cast) (private-message name "Vote cast.") - :else (private-message name "You must be part of an active team to vote.")))) - -(def vote-pattern - (re-pattern (str "^irc :(\\S+)!\\S+ PRIVMSG " resistance-nick " :\\.rvote (for|against)"))) - -(hub/addListener mod-name #"^irc :(\S+)!\S+ PRIVMSG \S+ :\.rjoin" handle-join) -(hub/addListener mod-name #"^irc.*PRIVMSG \S+ :\.rstart" handle-start) -(hub/addListener mod-name #"^irc :(\S+)!\S+ PRIVMSG \S+ :\.rteam (.+)" handle-choose-team) -(hub/addListener mod-name vote-pattern handle-vote) diff --git a/src/rekrvn/modules/voveri/engine.clj b/src/rekrvn/modules/resistance/engine.clj similarity index 99% rename from src/rekrvn/modules/voveri/engine.clj rename to src/rekrvn/modules/resistance/engine.clj index fb511fa..eaf25ab 100644 --- a/src/rekrvn/modules/voveri/engine.clj +++ b/src/rekrvn/modules/resistance/engine.clj @@ -1,4 +1,4 @@ -(ns rekrvn.modules.voveri.engine +(ns rekrvn.modules.resistance.engine (:require [clojure.string :as s])) ;; purely functional game engine diff --git a/src/rekrvn/modules/voveri/interface.clj b/src/rekrvn/modules/resistance/interface.clj similarity index 92% rename from src/rekrvn/modules/voveri/interface.clj rename to src/rekrvn/modules/resistance/interface.clj index ba502db..624b599 100644 --- a/src/rekrvn/modules/voveri/interface.clj +++ b/src/rekrvn/modules/resistance/interface.clj @@ -1,11 +1,11 @@ -(ns rekrvn.modules.voveri.interface +(ns rekrvn.modules.resistance.interface (:require [rekrvn.hub :as hub]) (:require [clojure.string :as s]) (:require [rekrvn.config :only resistance]) (:require [rekrvn.modules.irc.client :as irc]) - (:require [rekrvn.modules.voveri.engine :as e])) + (:require [rekrvn.modules.resistance.engine :as e])) -(def mod-name "voveri.interface") +(def mod-name "resistance.interface") ;; chat stuff (def irc-network (:network rekrvn.config/resistance)) diff --git a/test/rekrvn/modules/voveri/engine_test.clj b/test/rekrvn/modules/resistance/engine_test.clj similarity index 98% rename from test/rekrvn/modules/voveri/engine_test.clj rename to test/rekrvn/modules/resistance/engine_test.clj index 1a52c19..6d43cd6 100644 --- a/test/rekrvn/modules/voveri/engine_test.clj +++ b/test/rekrvn/modules/resistance/engine_test.clj @@ -1,6 +1,6 @@ -(ns rekrvn.modules.voveri.engine-test +(ns rekrvn.modules.resistance.engine-test (:require [clojure.test :refer :all]) - (:require [rekrvn.modules.voveri.engine :refer :all])) + (:require [rekrvn.modules.resistance.engine :refer :all])) (defn- noop [])