From 5fe97b6aade2bb047b7a51429c0c11637e5e9480 Mon Sep 17 00:00:00 2001 From: Benny Tsai Date: Thu, 6 Oct 2011 19:59:59 -0600 Subject: [PATCH] convert README to Markdown; update endline characters --- README | 11 --- README.md | 9 +++ engine.clj | 174 ++++++++++++++++++++++++------------------------ gui.clj | 150 ++++++++++++++++++++--------------------- light_cycle.clj | 10 +-- win_fns.clj | 14 ++-- 6 files changed, 183 insertions(+), 185 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index dfb8909..0000000 --- a/README +++ /dev/null @@ -1,11 +0,0 @@ -A simple Tron light cycle game in Clojure, inspired by the Snake example in Programming Clojure and Google's Tron AI Challenge: -http://pragprog.com/titles/shcloj/programming-clojure -http://csclub.uwaterloo.ca/contest/ - -Current gameplay is Free-For-All between 2 humans and 2 bots: -- Player 1 controls the yellow light cycle using WASD. -- Player 2 controls the blue light cycle using the arrow keys. -- Right now, the bots are not very smart. They simply choose a random open adjacent square, with a bias for going straight if possible. - -To start the game, assuming . is on the class path, just run light_cycle.clj: -clj light_cycle.clj diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c3588e --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +A simple Tron light cycle game in Clojure, inspired by the Snake example in [Programming Clojure](http://pragprog.com/titles/shcloj/programming-clojure) and [Google's Tron AI Challenge](http://csclub.uwaterloo.ca/contest/). + +Current gameplay is Free-For-All between 2 humans and 2 bots: +* Player 1 controls the yellow light cycle using WASD. +* Player 2 controls the blue light cycle using the arrow keys. +* Right now, the bots are not very smart. They simply choose a random open adjacent square, with a bias for going straight if possible. + +To start the game, run light_cycle.clj: + clj light_cycle.clj diff --git a/engine.clj b/engine.clj index d75d960..d8395b4 100644 --- a/engine.clj +++ b/engine.clj @@ -1,87 +1,87 @@ -(ns engine - (:import (java.awt Color))) - -(def width 75) -(def height 50) -(def point-size 10) -(def turn-millis 75) - -(def black (Color. 0 0 0)) -(def blue (Color. 0 0 255)) -(def red (Color. 255 0 0)) -(def yellow (Color. 255 255 0)) -(def white (Color. 255 255 255)) - -(defn add-points [& pts] - (vec (apply map + pts))) - -(defn create-cycle [name pt dir color move-fn] - {:name name - :trail [pt] - :dir dir - :color color - :move-fn move-fn}) - -(defn hit-wall? [{[[x y] & _] :trail}] - (or - (neg? x) - (neg? y) - (> x width) - (> y height))) - -(defn hit-trail? [{[pt & trail] :trail :as cycle} cycles] - (let [other-cycles (disj (set cycles) cycle) - other-trails (mapcat :trail other-cycles) - trails (concat trail other-trails)] - (some #{pt} trails))) - -(defn dead? [cycle cycles] - (or - (hit-wall? cycle) - (hit-trail? cycle cycles))) - -(defn turn [{[cur-pt prev-pt & _] :trail :as cycle} new-dir] - (let [new-pt (add-points new-dir cur-pt)] - (if (= new-pt prev-pt) cycle - (assoc cycle :dir new-dir)))) - -(defn move [{:keys [trail dir] :as cycle} cycles] - (let [new-pt (add-points dir (first trail)) - new-trail (cons new-pt trail)] - (assoc cycle :trail new-trail))) - -;; AI bot move function -(defn move-random [cycle cycles] - (let [dirs [[0 -1] [-1 0] [0 1] [1 0]] - biased-dirs (concat dirs (repeat 8 (:dir cycle))) - make-move #(move (turn cycle %) cycles) - next-moves (map make-move biased-dirs) - valid-moves (remove #(dead? % cycles) next-moves)] - (if-not (empty? valid-moves) (rand-nth valid-moves) - (move cycle cycles)))) - -(defn create-cycles [] - [(create-cycle "Player 1" [37 50] [0 -1] yellow move) - (create-cycle "Player 2" [37 0] [0 1] blue move) - (create-cycle "Rand 1" [0 24] [1 0] red move-random) - (create-cycle "Rand 2" [75 24] [-1 0] red move-random)]) - -;; mutable state ahead -(defn reset-game [cycles-ref] - (dosync (ref-set cycles-ref (create-cycles)))) - -(defn update-direction [cycles-ref name new-dir] - (let [old-cycle (first (filter #(= name (:name %)) @cycles-ref)) - new-cycle (turn old-cycle new-dir) - replace-cycle (partial replace {old-cycle new-cycle})] - (dosync (alter cycles-ref replace-cycle)))) - -(defn update-positions [cycles-ref] - (let [cycles @cycles-ref - apply-move-fns (partial map #((:move-fn %) % cycles))] - (dosync (alter cycles-ref apply-move-fns)))) - -(defn clear-dead [cycles-ref] - (let [cycles @cycles-ref - remove-dead (partial remove #(dead? % cycles))] - (dosync (alter cycles-ref remove-dead)))) +(ns engine + (:import (java.awt Color))) + +(def width 75) +(def height 50) +(def point-size 10) +(def turn-millis 75) + +(def black (Color. 0 0 0)) +(def blue (Color. 0 0 255)) +(def red (Color. 255 0 0)) +(def yellow (Color. 255 255 0)) +(def white (Color. 255 255 255)) + +(defn add-points [& pts] + (vec (apply map + pts))) + +(defn create-cycle [name pt dir color move-fn] + {:name name + :trail [pt] + :dir dir + :color color + :move-fn move-fn}) + +(defn hit-wall? [{[[x y] & _] :trail}] + (or + (neg? x) + (neg? y) + (> x width) + (> y height))) + +(defn hit-trail? [{[pt & trail] :trail :as cycle} cycles] + (let [other-cycles (disj (set cycles) cycle) + other-trails (mapcat :trail other-cycles) + trails (concat trail other-trails)] + (some #{pt} trails))) + +(defn dead? [cycle cycles] + (or + (hit-wall? cycle) + (hit-trail? cycle cycles))) + +(defn turn [{[cur-pt prev-pt & _] :trail :as cycle} new-dir] + (let [new-pt (add-points new-dir cur-pt)] + (if (= new-pt prev-pt) cycle + (assoc cycle :dir new-dir)))) + +(defn move [{:keys [trail dir] :as cycle} cycles] + (let [new-pt (add-points dir (first trail)) + new-trail (cons new-pt trail)] + (assoc cycle :trail new-trail))) + +;; AI bot move function +(defn move-random [cycle cycles] + (let [dirs [[0 -1] [-1 0] [0 1] [1 0]] + biased-dirs (concat dirs (repeat 8 (:dir cycle))) + make-move #(move (turn cycle %) cycles) + next-moves (map make-move biased-dirs) + valid-moves (remove #(dead? % cycles) next-moves)] + (if-not (empty? valid-moves) (rand-nth valid-moves) + (move cycle cycles)))) + +(defn create-cycles [] + [(create-cycle "Player 1" [37 50] [0 -1] yellow move) + (create-cycle "Player 2" [37 0] [0 1] blue move) + (create-cycle "Rand 1" [0 24] [1 0] red move-random) + (create-cycle "Rand 2" [75 24] [-1 0] red move-random)]) + +;; mutable state ahead +(defn reset-game [cycles-ref] + (dosync (ref-set cycles-ref (create-cycles)))) + +(defn update-direction [cycles-ref name new-dir] + (let [old-cycle (first (filter #(= name (:name %)) @cycles-ref)) + new-cycle (turn old-cycle new-dir) + replace-cycle (partial replace {old-cycle new-cycle})] + (dosync (alter cycles-ref replace-cycle)))) + +(defn update-positions [cycles-ref] + (let [cycles @cycles-ref + apply-move-fns (partial map #((:move-fn %) % cycles))] + (dosync (alter cycles-ref apply-move-fns)))) + +(defn clear-dead [cycles-ref] + (let [cycles @cycles-ref + remove-dead (partial remove #(dead? % cycles))] + (dosync (alter cycles-ref remove-dead)))) diff --git a/gui.clj b/gui.clj index 30bf15c..ea1b660 100644 --- a/gui.clj +++ b/gui.clj @@ -1,75 +1,75 @@ -(ns gui - (:use [engine]) - (:import (java.awt Dimension) - (java.awt.event ActionListener KeyEvent KeyListener) - (javax.swing JFrame JOptionPane JPanel Timer))) - -(def p1-dirs {KeyEvent/VK_W [ 0 -1] - KeyEvent/VK_A [-1 0] - KeyEvent/VK_S [ 0 1] - KeyEvent/VK_D [ 1 0]}) - -(def p2-dirs {KeyEvent/VK_UP [ 0 -1] - KeyEvent/VK_LEFT [-1 0] - KeyEvent/VK_DOWN [ 0 1] - KeyEvent/VK_RIGHT [ 1 0]}) - -(defn point-to-screen-rect [[x y]] - (map #(* point-size %) [x y 1 1])) - -(defn fill-point [g pt color] - (let [[x y width height] (point-to-screen-rect pt)] - (.setColor g color) - (.fillRect g x y width height))) - -(defn paint-cycles [g cycles] - (doseq [{:keys [trail color]} cycles] - (doseq [point trail] - (fill-point g point color)))) - -(defn paint-hits [g cycles] - (doseq [{[pt & _] :trail :as cycle} cycles] - (if (hit-trail? cycle cycles) (fill-point g pt white)))) - -(defn game-panel [frame win-fn cycles-ref] - (proxy [JPanel ActionListener KeyListener] [] - (paintComponent [g] - (proxy-super paintComponent g) - (paint-cycles g @cycles-ref) - (paint-hits g @cycles-ref)) - (actionPerformed [e] - (clear-dead cycles-ref) - (update-positions cycles-ref) - (let [win-msg (win-fn @cycles-ref)] - (when win-msg - (reset-game cycles-ref) - (JOptionPane/showMessageDialog frame win-msg))) - (.repaint this)) - (keyPressed [e] - (let [p1-dir (p1-dirs (.getKeyCode e)) - p2-dir (p2-dirs (.getKeyCode e))] - (cond - p1-dir (update-direction cycles-ref "Player 1" p1-dir) - p2-dir (update-direction cycles-ref "Player 2" p2-dir)))) - (getPreferredSize [] - (Dimension. (* (inc width) point-size) - (* (inc height) point-size))) - (keyReleased [e]) - (keyTyped [e]))) - -(defn game [win-fn] - (let [cycles-ref (ref (create-cycles)) - frame (JFrame. "Light Cycle") - panel (game-panel frame win-fn cycles-ref) - timer (Timer. turn-millis panel)] - (doto panel - (.addKeyListener panel) - (.setBackground black) - (.setFocusable true)) - (doto frame - (.add panel) - (.pack) - (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) - (.setVisible true)) - (.start timer) - [cycles-ref timer])) +(ns gui + (:use [engine]) + (:import (java.awt Dimension) + (java.awt.event ActionListener KeyEvent KeyListener) + (javax.swing JFrame JOptionPane JPanel Timer))) + +(def p1-dirs {KeyEvent/VK_W [ 0 -1] + KeyEvent/VK_A [-1 0] + KeyEvent/VK_S [ 0 1] + KeyEvent/VK_D [ 1 0]}) + +(def p2-dirs {KeyEvent/VK_UP [ 0 -1] + KeyEvent/VK_LEFT [-1 0] + KeyEvent/VK_DOWN [ 0 1] + KeyEvent/VK_RIGHT [ 1 0]}) + +(defn point-to-screen-rect [[x y]] + (map #(* point-size %) [x y 1 1])) + +(defn fill-point [g pt color] + (let [[x y width height] (point-to-screen-rect pt)] + (.setColor g color) + (.fillRect g x y width height))) + +(defn paint-cycles [g cycles] + (doseq [{:keys [trail color]} cycles] + (doseq [point trail] + (fill-point g point color)))) + +(defn paint-hits [g cycles] + (doseq [{[pt & _] :trail :as cycle} cycles] + (if (hit-trail? cycle cycles) (fill-point g pt white)))) + +(defn game-panel [frame win-fn cycles-ref] + (proxy [JPanel ActionListener KeyListener] [] + (paintComponent [g] + (proxy-super paintComponent g) + (paint-cycles g @cycles-ref) + (paint-hits g @cycles-ref)) + (actionPerformed [e] + (clear-dead cycles-ref) + (update-positions cycles-ref) + (let [win-msg (win-fn @cycles-ref)] + (when win-msg + (reset-game cycles-ref) + (JOptionPane/showMessageDialog frame win-msg))) + (.repaint this)) + (keyPressed [e] + (let [p1-dir (p1-dirs (.getKeyCode e)) + p2-dir (p2-dirs (.getKeyCode e))] + (cond + p1-dir (update-direction cycles-ref "Player 1" p1-dir) + p2-dir (update-direction cycles-ref "Player 2" p2-dir)))) + (getPreferredSize [] + (Dimension. (* (inc width) point-size) + (* (inc height) point-size))) + (keyReleased [e]) + (keyTyped [e]))) + +(defn game [win-fn] + (let [cycles-ref (ref (create-cycles)) + frame (JFrame. "Light Cycle") + panel (game-panel frame win-fn cycles-ref) + timer (Timer. turn-millis panel)] + (doto panel + (.addKeyListener panel) + (.setBackground black) + (.setFocusable true)) + (doto frame + (.add panel) + (.pack) + (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) + (.setVisible true)) + (.start timer) + [cycles-ref timer])) diff --git a/light_cycle.clj b/light_cycle.clj index adbae18..5a6914a 100644 --- a/light_cycle.clj +++ b/light_cycle.clj @@ -1,5 +1,5 @@ -(ns light-cycle - (:use [gui :only (game)]) - (:use [win-fns :only (ffa-win-fn)])) - -(game ffa-win-fn) +(ns light-cycle + (:use [gui :only (game)]) + (:use [win-fns :only (ffa-win-fn)])) + +(game ffa-win-fn) diff --git a/win_fns.clj b/win_fns.clj index eed5766..e73551c 100644 --- a/win_fns.clj +++ b/win_fns.clj @@ -1,7 +1,7 @@ -(ns win-fns) - -(defn ffa-win-fn [cycles] - (case (count cycles) - 1 (str (:name (first cycles)) " wins!") - 0 "Tie!" - nil)) +(ns win-fns) + +(defn ffa-win-fn [cycles] + (case (count cycles) + 1 (str (:name (first cycles)) " wins!") + 0 "Tie!" + nil))