Created
April 30, 2018 12:40
-
-
Save taylorwood/e9a37b3f1f0cb0098cf0fbe5cf95f4e6 to your computer and use it in GitHub Desktop.
CodinGame Battle Royale Bot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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