Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
94 lines (82 sloc) 3.58 KB
(ns farkle-ai.core
(:require [farkle-ai.rules :refer [score-roll]])
(:gen-class))
(defn random-dice-roll
"Generate a random rollout. Can also generate a list of random rollouts."
([num-dice]
(repeatedly num-dice #(inc (rand-int 6))))
([num-dice num-rolls]
(repeatedly num-rolls #(random-dice-roll num-dice))))
(defn score-rolls
"Score multiple rolls, and extract out just the score.
Helper function, just calls score-roll."
[rolls]
(map #(or (:score (score-roll %1)) 0) rolls))
(defn average
"Given a list of numbers, returns the average as a float."
[xs]
(float (/ (reduce + xs) (count xs))))
(defn expected-value-threshold
"Given probabality good thing happening, probabality bad thing happening,
and good reward value, caculates threshold at which expected value is worth
the risk."
[p-good p-bad reward-good]
(/ (* reward-good p-good) p-bad))
(defn ceil
"Given a float, round up to closest integer."
[x]
(int (Math/ceil x)))
(defn rollout-analysis
"Given a number of dice, and a number of rollouts, returns an analysis of the
rollouts."
[num-dice num-rolls]
(let [random-rolls (random-dice-roll num-dice num-rolls)
scored-rolls (score-rolls random-rolls)
rolls-grouped (group-by zero? scored-rolls)
num-farkled-rolls (count (get rolls-grouped true))
num-unfarkled-rolls (count (get rolls-grouped false))
avg-unfarkled-score (average (get rolls-grouped false))
farkled-percent (float (/ num-farkled-rolls num-rolls))
unfarkled-percent (- 1 farkled-percent)
roll-threshold (ceil (expected-value-threshold
unfarkled-percent
farkled-percent
avg-unfarkled-score))]
{:num-dice num-dice
:avg-unfarkled-score avg-unfarkled-score
:farkled-percent farkled-percent
:unfarkled-percent unfarkled-percent
:roll-threshold roll-threshold}))
(defn continue-rule
"Convert threshold rule to plain text."
[analysis]
(str "If " (:num-dice analysis) " die and current score less than "
(:roll-threshold analysis) ", then continue to roll."))
(defn hot-dice-rule
"Generate plaintext rules for hot dice."
[max-dice-analysis analysis]
(let [hot-score-threshold (ceil (/ (- (* (:avg-unfarkled-score max-dice-analysis)
(:unfarkled-percent max-dice-analysis))
(* (:avg-unfarkled-score analysis)
(:unfarkled-percent analysis)))
(:unfarkled-percent analysis)))]
(str "If " (:num-dice analysis) " hot die and hot score greater than "
hot-score-threshold ", then roll off hot dice.")))
(defn generate-rules
"Generate a list of rules to follow based on a number of random simulations."
[num-rolls]
(try (let [rollout-analysis-for-dice (map #(rollout-analysis % num-rolls) (range 1 7))
continue-rules (map continue-rule rollout-analysis-for-dice)
max-dice-analysis (last rollout-analysis-for-dice)
hot-dice-rules (map (partial hot-dice-rule max-dice-analysis) rollout-analysis-for-dice)]
(concat continue-rules hot-dice-rules))
(catch Exception _# ["Divide by zero occurred during simulation. You are likely not setting the simulation count high enough."])))
(defn print-list
[xs]
(doseq [x xs]
(println x)))
(defn -main
[& args]
(if (< (count args) 1)
(println "Provide a number of simulations you wish to run.")
(print-list (generate-rules (Integer. (first args))))))