Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
convert README to Markdown; update endline characters
- Loading branch information
Showing
6 changed files
with
183 additions
and
185 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)))) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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])) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) |