Skip to content

Commit

Permalink
convert README to Markdown; update endline characters
Browse files Browse the repository at this point in the history
  • Loading branch information
bitsai committed Oct 7, 2011
1 parent 0594c49 commit 5fe97b6
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 185 deletions.
11 changes: 0 additions & 11 deletions README

This file was deleted.

9 changes: 9 additions & 0 deletions 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
174 changes: 87 additions & 87 deletions 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))))
150 changes: 75 additions & 75 deletions 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]))
10 changes: 5 additions & 5 deletions 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)
14 changes: 7 additions & 7 deletions 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))

0 comments on commit 5fe97b6

Please sign in to comment.