Skip to content

Instantly share code, notes, and snippets.

@taylorwood
Created April 30, 2018 12:40
Show Gist options
  • Save taylorwood/e9a37b3f1f0cb0098cf0fbe5cf95f4e6 to your computer and use it in GitHub Desktop.
Save taylorwood/e9a37b3f1f0cb0098cf0fbe5cf95f4e6 to your computer and use it in GitHub Desktop.
CodinGame Battle Royale Bot
(ns Player
(:require [clojure.pprint :refer [print-table pprint]]
[clojure.set :as set])
(:gen-class))
(defmacro debug [& body] `(binding [*out* *err*] (do ~@body)))
(def split-by (juxt filter remove))
(defn read-map [ks]
(let [vs (repeatedly (count ks) read)]
(zipmap ks vs)))
(defn read-input-maps [ks n] (doall (repeatedly n #(read-map ks))))
(def read-sites (partial read-input-maps [:site-id :x :y :radius]))
(def structure-type-kw {-1 nil, 0 :mine, 1 :tower, 2 :barracks})
(def unit-type-kw {-1 :queen, 0 :knight, 1 :archer, 2 :giant})
(def unit-type-cost {0 80, 1 100, 2 140})
(defn fix-site [s]
(let [site (-> s
(update :owner #(when-not (neg? %) %))
(update :structure-type structure-type-kw))]
(case (:structure-type site)
:barracks (-> site
(assoc
:can-train? (zero? (:param-1 site))
:build-turns-rem (:param-1 site)
:unit-type (unit-type-kw (:param-2 site))
:train-cost (unit-type-cost (:param-2 site)))
(dissoc :param-1 :param-2))
:tower (set/rename-keys site {:param-1 :health, :param-2 :attack-radius})
:mine (-> site
(assoc :mine-rate (Math/abs ^int (:param-1 site)))
(dissoc :param-2))
site)))
(def read-turn-sites (comp #(map fix-site %) (partial read-input-maps [:site-id :gold :max-mine-rate :structure-type :owner :param-1 :param-2])))
(defn fix-unit-type [u] (update u :unit-type unit-type-kw))
(def read-units (comp #(map fix-unit-type %) (partial read-input-maps [:x :y :owner :unit-type :health])))
(defn distance* [{x1 :x y1 :y} {x2 :x y2 :y}]
(if (and x1 y1 x2 y2)
(Math/sqrt (+ (Math/pow (- x1 x2) 2) (Math/pow (- y1 y2) 2)))
Double/POSITIVE_INFINITY))
(def distance (memoize (fn [a b] (distance* (select-keys a [:x :y]) (select-keys b [:x :y])))))
(defn travel* [{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2} ratio]
(let [d (distance p1 p2)]
(if (zero? d)
p1
(let [t (/ (* d ratio) d)]
{:x (int (+ (* x1 (- 1 t)) (* t x2)))
:y (int (+ (* y1 (- 1 t)) (* t y2)))}))))
(def travel (memoize (fn [a b r] (travel* (select-keys a [:x :y]) (select-keys b [:x :y]) r))))
(defn travel-dist* [{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2} dist]
(if (or (zero? dist)
(and (= x1 x2) (= y1 y2)))
p1
(let [d (distance p1 p2)
ratio (/ dist d)]
(if (zero? d)
p1
(let [t (/ (* d ratio) d)]
{:x (int (+ (* x1 (- 1 t)) (* t x2)))
:y (int (+ (* y1 (- 1 t)) (* t y2)))})))))
(def travel-dist (memoize (fn [a b r] (travel-dist* (select-keys a [:x :y]) (select-keys b [:x :y]) r))))
(def corners [{:x 0 :y 0}
{:x 0 :y 1000}
{:x 1920 :y 0}
{:x 1920 :y 1000}])
(defn box-center [points]
(when (seq points)
(let [x-bounds (apply (juxt max min) (map :x points))
y-bounds (apply (juxt max min) (map :y points))
midpoint #(int (/ (apply + %) 2))]
{:x (midpoint x-bounds)
:y (midpoint y-bounds)})))
(defn nearest [pos coll] (first (sort-by #(distance pos %) coll)))
(defn remove-touching [units sites]
(remove (fn [site] (->> units
(some (fn [unit] (<= (distance unit site)
(+ 30 (:radius site)))))))
sites))
(defn best-mine-site [state]
(when (seq (:unbuilt-sites state))
(let [sites (:unbuilt-sites state)
my-queen (get-in state [:friendly :queen])
their-queen (get-in state [:enemy :queen])
target (travel-dist my-queen their-queen -120)]
(->> sites
(filter #(pos? (:gold %)))
(remove-touching (get-in state [:enemy :units]))
(nearest target)))))
(defn best-tower-site [state]
(when (seq (:unbuilt-sites state))
(let [sites (:unbuilt-sites state)
my-queen (get-in state [:friendly :queen])
nearest-enemy-barracks (nearest my-queen (get-in state [:enemy :barracks]))
target (if nearest-enemy-barracks
(travel my-queen nearest-enemy-barracks 1/4)
(travel (box-center corners) my-queen 2/3))]
(->> sites
(remove-touching (get-in state [:enemy :units]))
(nearest target)))))
(def best-barracks-site best-tower-site)
(defn hurting? [state]
(let [my-queen (get-in state [:friendly :queen])
enemy-units (get-in state [:enemy :units])
enemy-towers (get-in state [:enemy :towers])
in-range (filter #(< (distance my-queen %) 250) enemy-units)]
(or (<= 3 (count in-range))
(some #(<= (distance my-queen %) (:attack-radius %)) enemy-towers))))
(defn mine [state]
(when-not (hurting? state)
(if-let [upgrade-mine (->> (get-in state [:friendly :mines])
(remove #(or (zero? (:gold %))
(= (:max-mine-rate %) (:mine-rate %))))
(sort-by #(distance (get-in state [:enemy :queen]) %))
(last))]
(format "BUILD %s MINE" (:site-id upgrade-mine))
(when (< (count (get-in state [:friendly :mines]))
(max 2 (count (get-in state [:enemy :mines]))))
(when-let [site (best-mine-site state)]
(format "BUILD %s MINE" (:site-id site)))))))
(defn build-rear-tower [state]
(when (seq (:unbuilt-sites state))
(let [my-queen (get-in state [:friendly :queen])
their-queen (get-in state [:enemy :queen])]
(when-let [site (or (->> (:unbuilt-sites state)
(filter #(<= (distance my-queen %) (- (:radius %) 119)))
(nearest my-queen))
(and (not (hurting? state))
(->> (:unbuilt-sites state)
(sort-by #(+ (distance my-queen %)
(- (distance their-queen %))))
(first))))]
(format "BUILD %s %s" (:site-id site) "TOWER")))))
(defn build-tower [state]
(when (seq (get-in state [:friendly :barracks]))
(cond
(<= (count (get-in state [:friendly :towers]))
(count (get-in state [:enemy :towers])))
(when-let [site (nearest (get-in state [:friendly :queen]) (:unbuilt-sites state))]
(format "BUILD %s %s" (:site-id site) "TOWER"))
:else
(when-let [site (->> (get-in state [:friendly :towers])
(remove #(< 400 (:attack-radius %)))
(nearest (get-in state [:friendly :queen])))]
(format "BUILD %s %s" (:site-id site) "TOWER")))))
(defn build-barracks [state]
(cond
(empty? (get-in state [:friendly :barracks]))
(let [my-queen (get-in state [:friendly :queen])
site (->> (:unbuilt-sites state)
(sort-by #(distance my-queen %))
(take 2)
(sort-by :gold)
(first))]
(when site (format "BUILD %s BARRACKS-KNIGHT" (:site-id site))))
(or (< (count (get-in state [:friendly :barracks]))
(count (get-in state [:enemy :barracks])))
(and (not (hurting? state))
(<= (count (get-in state [:friendly :barracks]))
(count (get-in state [:enemy :barracks])))))
(when-let [site (best-barracks-site state)]
(format "BUILD %s BARRACKS-%s" (:site-id site) "KNIGHT"))))
(defn destroy-enemy-site [state]
(cond
(and (< 4 (count (get-in state [:enemy :towers])))
(not-any? #(= :giant (:unit-type %))
(get-in state [:friendly :barracks])))
(when-let [site (best-barracks-site state)]
(format "BUILD %s BARRACKS-GIANT" (:site-id site)))
(not (hurting? state))
(let [my-queen (get-in state [:friendly :queen])
nearest-enemy-barracks (->> (get-in state [:enemy :barracks])
(sort-by #(distance my-queen %))
(first))]
(when nearest-enemy-barracks
(format "MOVE %s %s" (:x nearest-enemy-barracks) (:y nearest-enemy-barracks))))))
(defn barracks->tower [state]
(when (< 2 (count (get-in state [:friendly :barracks])))
(let [my-queen (get-in state [:friendly :queen])
furthest-idle-barracks (->> (get-in state [:friendly :barracks])
(filter :can-train?)
(sort-by #(distance my-queen %))
(last))]
(when furthest-idle-barracks
(format "BUILD %s TOWER" (:site-id furthest-idle-barracks))))))
(defn retreat [state]
(when (hurting? state)
(let [my-queen (get-in state [:friendly :queen])
dest (nearest my-queen corners)]
(when (and dest (<= 120 (distance my-queen dest)))
(format "MOVE %s %s" (:x dest) (:y dest))))))
(def strategies
{:rush {:actions [:build-barracks] :step-fn #(when (seq (get-in % [:friendly :barracks]))
:establish)}
:establish {:actions [:mine :build-tower :build-barracks]
:step-fn (fn [state]
(cond
(hurting? state)
:turtle
(and (< (get-in state [:enemy :mine-rate])
(get-in state [:friendly :mine-rate]))
(< (count (get-in state [:enemy :barracks]))
(count (get-in state [:friendly :barracks]))))
:fight))}
:turtle {:actions [:mine :build-rear-tower :destroy-enemy-site :retreat :barracks->tower]
:step-fn (fn [state]
(cond
(not (hurting? state))
:establish))}
:fight {:actions [:build-tower :destroy-enemy-site :build-barracks :retreat]
:step-fn (fn [state]
(cond
(hurting? state)
:turtle
(< (get-in state [:friendly :queen :health])
(get-in state [:enemy :queen :health]))
:turtle
(< (get-in state [:friendly :mine-rate])
(get-in state [:enemy :mine-rate]))
:establish))}})
(def actions {:mine mine
:build-rear-tower build-rear-tower
:build-tower build-tower
:build-barracks build-barracks
:destroy-enemy-site destroy-enemy-site
:barracks->tower barracks->tower
:retreat retreat})
(defn get-train-cmd [state]
(let [gold (get-in state [:friendly :gold])
their-queen (get-in state [:enemy :queen])
idle-barracks (filter :can-train? (get-in state [:friendly :barracks]))
desirable-barracks (->> idle-barracks
(sort-by #(distance % their-queen)))
trainable-barracks (loop [barracks desirable-barracks
to-train []
rem-gold gold]
(if-let [barrack (first barracks)]
(if (< (:train-cost barrack) rem-gold)
(recur (rest barracks)
(conj to-train barrack)
(- rem-gold (:train-cost barrack)))
to-train)
to-train))]
(when (seq trainable-barracks)
(cond
(and (seq (filter #(= :giant (:unit-type %)) idle-barracks))
(not-any? #(= :giant (:unit-type %)) (get-in state [:friendly :units])))
(some->> idle-barracks
(filter #(= :giant (:unit-type %)))
(first)
(:site-id)
(str "TRAIN "))
:else
(str "TRAIN " (clojure.string/join " " (map :site-id trainable-barracks)))))))
(defn get-unit-composition [units sites]
(let [queen (first (filter #(= :queen (:unit-type %)) units))
structures (group-by :structure-type sites)]
{:queen queen :units units :sites sites
:barracks (structures :barracks) :mines (structures :mine) :towers (structures :tower)
:mine-rate (apply + (map :mine-rate (structures :mine)))}))
(defn mk-game-state [units sites]
(let [[my-units their-units] (split-by #(= 0 (:owner %)) units)
[my-sites their-sites] (split-by #(= 0 (:owner %)) sites)
my-comp (get-unit-composition my-units my-sites)
their-comp (get-unit-composition their-units their-sites)]
{:friendly my-comp
:enemy their-comp
:unbuilt-sites (remove :owner sites)}))
(defn merge-seen-sites [seen-sites sites]
(let [seen-by-id (into {} (map (juxt :site-id identity) seen-sites))]
(for [site sites
:let [seen (seen-by-id (:site-id site))]]
(if (and (not= -1 (:gold site)) (not= -1 (:mine-rate site)))
site
(merge site (select-keys seen [:gold :mine-rate]))))))
(defn -main [& _args]
(let [num-sites (read)
sites (read-sites num-sites)
sites-by-id (into {} (map (juxt :site-id identity) sites))
curr-strat (atom :rush)
last-seen-sites (atom [])]
(while true
(let [gold (read)
_touched-site-id (read)
turn-sites (merge-seen-sites
@last-seen-sites
(map
#(merge % (sites-by-id (:site-id %)))
(read-turn-sites num-sites)))]
; touched-site-id: -1 if none
(let [num-units (read)
units (read-units num-units)
state (-> (mk-game-state units turn-sites)
(assoc-in [:friendly :gold] gold))
{acts :actions} (let [{f :step-fn} (strategies @curr-strat)
next-strat (f state)]
(when next-strat
(debug (println "transitioning" @curr-strat "to" next-strat))
(reset! curr-strat next-strat))
(strategies @curr-strat))
act (or (some (fn [a]
(debug (println "trying" a))
((actions a) state))
acts)
"WAIT")
train-cmd (or (get-train-cmd state) "TRAIN")]
(debug (println "curr strat" @curr-strat))
(println act)
(println train-cmd)
(reset! last-seen-sites turn-sites))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment