├── score.dat ├── screenshot_1.png ├── project.clj ├── src └── tetris │ ├── score.clj │ ├── collision.clj │ ├── graphics.clj │ ├── rotation.clj │ ├── matrix.clj │ ├── move.clj │ ├── gui.clj │ └── core.clj ├── README.md └── LICENSE /score.dat: -------------------------------------------------------------------------------- 1 | [0 0] 2 | -------------------------------------------------------------------------------- /screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netb258/console-tetris/HEAD/screenshot_1.png -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject tetris "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :dependencies [[org.clojure/clojure "1.8.0"] 8 | 9 | [clojure-lanterna "0.9.7"]] ;; Advanced console library for Clojure. 10 | :main tetris.core 11 | :aot :all) 12 | -------------------------------------------------------------------------------- /src/tetris/score.clj: -------------------------------------------------------------------------------- 1 | ;; Keeps the game's score. 2 | (ns tetris.score) 3 | 4 | (defn read-high-score 5 | "Contract: string -> vector 6 | Reads the players best score from a file" 7 | [fname] 8 | (clojure.edn/read-string (slurp fname))) 9 | 10 | (defn save-high-score 11 | "Contract: string int int -> nil 12 | Saves the players score to a file." 13 | [fname score lines] 14 | (spit fname (with-out-str (pr [score lines])))) 15 | 16 | (defn overwrite-high-score! 17 | "Contract: string string int int -> nil" 18 | [fname score lines] 19 | (let [high-score (read-high-score fname)] 20 | (when (> score (first high-score)) 21 | (save-high-score fname score lines)))) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # console-tetris 2 | A command line implementation of Tetris, written in Clojure. 3 | 4 | Uses clojure-lanterna for rendering. 5 | 6 | ![Alt text](./screenshot_1.png?raw=true "Title") 7 | 8 | # Usage 9 | 10 | 11 | $ lein run 12 | 13 | # Running in system shell 14 | 15 | By default the program is set to run in a swing based console (for portability). 16 | 17 | If you want to run it in the OS shell, then you will need to change src/tetris/gui.clj slightly. 18 | 19 | Notice that line 12 of gui.clj sets the WINDOW var with the :swing keyword. 20 | 21 | Just change that keyword to :unix if you are on a Unix OS. 22 | 23 | Under MS Windows only :cygwin is supported, so you will need to install Cygwin. 24 | 25 | In addition, you will also need to remove lines 164 and 165 of src/tetris/core.clj. 26 | 27 | ## License 28 | 29 | Copyright © 2019 FIXME 30 | 31 | Distributed under the Eclipse Public License either version 1.0 or (at 32 | your option) any later version. 33 | -------------------------------------------------------------------------------- /src/tetris/collision.clj: -------------------------------------------------------------------------------- 1 | ;; As the name suggests, this module handles collision detection. 2 | ;; The functions here are pure, they take matrixes along with x,y coordinates and return any detected collisions. 3 | (ns tetris.collision 4 | (:require [tetris.matrix :as m] 5 | [tetris.graphics :as g])) 6 | 7 | (defn check-bounds 8 | "Contract: int int vector vector -> keyword 9 | Takes the row and col of where the player is trying to move and checks if they are within the matrix." 10 | [move-x move-y piece-graphics matrix] 11 | (let [piece-cleaned (m/clean-piece piece-graphics) 12 | piece-width (count (last (sort-by count piece-cleaned))) 13 | piece-height (count piece-cleaned) 14 | y-limit (- (count g/EMPTY-LINE) piece-width) 15 | x-limit (- (count matrix) piece-height)] 16 | (cond 17 | (< move-y 0) :side-collison 18 | (> move-y y-limit) :side-collison 19 | (> move-x x-limit) :bottom-collison 20 | :else :in-bounds))) 21 | 22 | (defn get-collisions 23 | "Contract: vector vector -> vector 24 | Detects collisions on a single piece row." 25 | [piece-row move-row] 26 | (map #(and (not= "." %1) (not= "." %2)) piece-row move-row)) 27 | 28 | (defn count-collisions 29 | "Contract: vector -> int" 30 | [collision-vector] 31 | (count (filter #(some #{true} %) collision-vector))) 32 | 33 | (defn detect-collision 34 | "Contract: int int vector vector -> keyword 35 | Returns :collision if the active tetris piece will collide with anything in the matrix at the given coordinates." 36 | [move-row move-col rotation-graphics playfield] 37 | (let [piece-height (count (m/clean-piece rotation-graphics)) 38 | portrait (m/insert-piece rotation-graphics (m/get-empty-matrix) move-row move-col) 39 | piece-slice (subvec portrait move-row (+ move-row piece-height)) 40 | move-slice (subvec playfield move-row (+ move-row piece-height)) 41 | collisions (map #(get-collisions %1 %2) piece-slice move-slice)] 42 | (if 43 | (> (count-collisions collisions) 0) :collision 44 | :no-collision))) 45 | -------------------------------------------------------------------------------- /src/tetris/graphics.clj: -------------------------------------------------------------------------------- 1 | ;; All the glorious graphics in our game, represented as data structures: 2 | ;; Needless to say, this module is pure. 3 | (ns tetris.graphics) 4 | 5 | (def EMPTY-LINE ["." "." "." "." "." "." "." "." "." "."]) 6 | 7 | (def PIECES-NORMAL 8 | {"I" [["." "." "." "."] 9 | ["c" "c" "c" "c"] 10 | ["." "." "." "."] 11 | ["." "." "." "."]] 12 | "O" [["y" "y"] 13 | ["y" "y"]] 14 | "Z" [["r" "r" "."] 15 | ["." "r" "r"] 16 | ["." "." "."]] 17 | "S" [["." "g" "g"] 18 | ["g" "g" "."] 19 | ["." "." "."]] 20 | "J" [["b" "." "."] 21 | ["b" "b" "b"] 22 | ["." "." "."]] 23 | "L" [["." "." "o"] 24 | ["o" "o" "o"] 25 | ["." "." "."]] 26 | "T" [["." "m" "."] 27 | ["m" "m" "m"] 28 | ["." "." "."]]}) 29 | 30 | (def PIECES-ROTATED1 31 | {"I" [["." "." "c" "."] 32 | ["." "." "c" "."] 33 | ["." "." "c" "."] 34 | ["." "." "c" "."]] 35 | "O" [["y" "y"] 36 | ["y" "y"]] 37 | "Z" [["." "." "r"] 38 | ["." "r" "r"] 39 | ["." "r" "."]] 40 | "S" [["." "g" "."] 41 | ["." "g" "g"] 42 | ["." "." "g"]] 43 | "J" [["." "b" "b"] 44 | ["." "b" "."] 45 | ["." "b" "."]] 46 | "L" [["." "o" "."] 47 | ["." "o" "."] 48 | ["." "o" "o"]] 49 | "T" [["." "m" "."] 50 | ["." "m" "m"] 51 | ["." "m" "."]]}) 52 | 53 | (def PIECES-ROTATED2 54 | {"I" [["." "." "." "."] 55 | ["." "." "." "."] 56 | ["c" "c" "c" "c"] 57 | ["." "." "." "."]] 58 | "O" [["y" "y"] 59 | ["y" "y"]] 60 | "Z" [["." "." "."] 61 | ["r" "r" "."] 62 | ["." "r" "r"]] 63 | "S" [["." "." "."] 64 | ["." "g" "g"] 65 | ["g" "g" "."]] 66 | "J" [["." "." "."] 67 | ["b" "b" "b"] 68 | ["." "." "b"]] 69 | "L" [["." "." "."] 70 | ["o" "o" "o"] 71 | ["o" "." "."]] 72 | "T" [["." "." "."] 73 | ["m" "m" "m"] 74 | ["." "m" "."]]}) 75 | 76 | (def PIECES-ROTATED3 77 | {"I" [["." "c" "." "."] 78 | ["." "c" "." "."] 79 | ["." "c" "." "."] 80 | ["." "c" "." "."]] 81 | "O" [["y" "y"] 82 | ["y" "y"]] 83 | "Z" [["." "r" "."] 84 | ["r" "r" "."] 85 | ["r" "." "."]] 86 | "S" [["g" "." "."] 87 | ["g" "g" "."] 88 | ["." "g" "."]] 89 | "J" [["." "b" "."] 90 | ["." "b" "."] 91 | ["b" "b" "."]] 92 | "L" [["o" "o" "."] 93 | ["." "o" "."] 94 | ["." "o" "."]] 95 | "T" [["." "m" "."] 96 | ["m" "m" "."] 97 | ["." "m" "."]]}) 98 | 99 | (defn get-graphics 100 | "Contract: string keyword -> vector" 101 | [piece-id rotation] 102 | (cond 103 | (= :CENTER rotation) (get PIECES-NORMAL piece-id) 104 | (= :ROTATE1 rotation) (get PIECES-ROTATED1 piece-id) 105 | (= :ROTATE2 rotation) (get PIECES-ROTATED2 piece-id) 106 | (= :ROTATE3 rotation) (get PIECES-ROTATED3 piece-id))) 107 | -------------------------------------------------------------------------------- /src/tetris/rotation.clj: -------------------------------------------------------------------------------- 1 | ;; In the game of tetris, the player needs to be able to rotate the currently active tetris piece. 2 | ;; Unfortunately, this implies change, so the functions here all deal with atoms. 3 | ;; The atoms we are dealing with here are the playfield and active-piece. 4 | ;; The active-piece looks like this: (atom {:id "" :rotation :CENTER :row 0 :col 0 :anchored false :graphics []}) 5 | ;; The playfield looks like this: 6 | ;; (atom 7 | ;; [["." "." "." "." "." "." "." "." "." "."] 8 | ;; ["." "." "." "." "." "." "." "." "." "."] 9 | ;; ["." "." "." "." "." "." "." "." "." "."] 10 | ;; ["." "." "." "." "." "." "." "." "." "."] 11 | ;; ["." "." "." "." "." "." "." "." "." "."] 12 | ;; ["." "." "." "." "." "." "." "." "." "."] 13 | ;; ["." "." "." "." "." "." "." "." "." "."] 14 | ;; ["." "." "." "." "." "." "." "." "." "."] 15 | ;; ["." "." "." "." "." "." "." "." "." "."] 16 | ;; ["." "." "." "." "." "." "." "." "." "."] 17 | ;; ["." "." "." "." "." "." "." "." "." "."] 18 | ;; ["." "." "." "." "." "." "." "." "." "."] 19 | ;; ["." "." "." "." "." "." "." "." "." "."] 20 | ;; ["." "." "." "." "." "." "." "." "." "."] 21 | ;; ["." "." "." "." "." "." "." "." "." "."] 22 | ;; ["." "." "." "." "." "." "." "." "." "."] 23 | ;; ["." "." "." "." "." "." "." "." "." "."] 24 | ;; ["." "." "." "." "." "." "." "." "." "."] 25 | ;; ["." "." "." "." "." "." "." "." "." "."] 26 | ;; ["." "." "." "." "." "." "." "." "." "."] 27 | ;; ["." "." "." "." "." "." "." "." "." "."] 28 | ;; ["." "." "." "." "." "." "." "." "." "."]]) 29 | 30 | (ns tetris.rotation 31 | (:require [tetris.collision :as c] 32 | [tetris.move :as mv] 33 | [tetris.graphics :as g])) 34 | 35 | (defn rotate-piece! 36 | "Contract: atom atom keyword -> nil 37 | NOTE: There is only one situation when the rotation would put us out of bounds: 38 | When we have moved too far to the right and we are trying to rotate out of the matrix (can't happen for left). 39 | We correct this situation by moving ot the left, until (not= :in-bounds) becomes false (or we :cant-move-there)." 40 | [playfield active-piece rotation] 41 | (let [current-id (:id @active-piece) 42 | current-row (:row @active-piece) 43 | current-col (:col @active-piece) 44 | new-rotation (g/get-graphics current-id rotation)] 45 | (cond 46 | (not= :in-bounds (c/check-bounds current-row current-col new-rotation @playfield)) 47 | (when (not= :cant-move-there (mv/move-left! playfield active-piece)) (recur playfield active-piece rotation)) 48 | (= :no-collision (c/detect-collision current-row current-col new-rotation @playfield)) 49 | (swap! 50 | active-piece 51 | (fn [p] 52 | {:id current-id 53 | :rotation rotation 54 | :row current-row 55 | :col current-col 56 | :anchored false 57 | :graphics new-rotation})) 58 | :else :cant-rotate))) 59 | 60 | (defn rotate-left! 61 | "Contract: atom atom -> nil" 62 | [playfield active-piece] 63 | (let [current-rotation (:rotation @active-piece)] 64 | (cond 65 | (= :CENTER current-rotation) (rotate-piece! playfield active-piece :ROTATE3) 66 | (= :ROTATE3 current-rotation) (rotate-piece! playfield active-piece :ROTATE2) 67 | (= :ROTATE2 current-rotation) (rotate-piece! playfield active-piece :ROTATE1) 68 | :else (rotate-piece! playfield active-piece :CENTER)))) 69 | 70 | (defn rotate-right! 71 | "Contract: atom atom -> nil" 72 | [playfield active-piece] 73 | (let [current-rotation (:rotation @active-piece)] 74 | (cond 75 | (= :CENTER current-rotation) (rotate-piece! playfield active-piece :ROTATE1) 76 | (= :ROTATE1 current-rotation) (rotate-piece! playfield active-piece :ROTATE2) 77 | (= :ROTATE2 current-rotation) (rotate-piece! playfield active-piece :ROTATE3) 78 | :else (rotate-piece! playfield active-piece :CENTER)))) 79 | -------------------------------------------------------------------------------- /src/tetris/matrix.clj: -------------------------------------------------------------------------------- 1 | ;; You could say that the game of tetris is all about matrix manipulations and that is exactly what this module does. 2 | ;; The functions here are pure, mostly they take a matrix and returns a transformed matrix. 3 | (ns tetris.matrix 4 | (:require [clojure.string :as s] 5 | [tetris.graphics :as g])) 6 | 7 | (defn get-empty-matrix 8 | "Contract: nil -> vector" 9 | [] 10 | (into [] (take 22 (repeat g/EMPTY-LINE)))) 11 | 12 | (defn clean-rows 13 | "Contract: vector -> vector 14 | Removes from the piece any rows that contain only empty spaces." 15 | [piece-graphics] 16 | (let [top-empty-rows (take-while (fn [row] (every? #(= "." %) row)) piece-graphics) 17 | full-rows (filter (fn [row] (some #(not= "." %) row)) piece-graphics)] 18 | (into [] (concat top-empty-rows full-rows)))) 19 | 20 | (defn flip-row 21 | "Contract: vector int -> vector 22 | Selects a single row from a piece graphics and transforms it from rows to cols representation." 23 | [piece-graphics col] 24 | (into [] (map #(nth % col) piece-graphics))) 25 | 26 | (defn flip-all-rows 27 | "Contract: vector -> vector" 28 | [piece-graphics] 29 | (vec 30 | (for [i (range (count (first piece-graphics))) 31 | :let [col (flip-row piece-graphics i)]] 32 | col))) 33 | 34 | ;; NOTE: Calling flip-all-rows twice, basically flips the rows/cols representation back into it's original form. 35 | (defn clean-cols 36 | "Contract: vector -> vector 37 | Removes from the piece any cols that contain only empty spaces." 38 | [piece-graphics] 39 | (flip-all-rows 40 | (filter 41 | (fn [row] (some #(not= "." %) row)) 42 | (flip-all-rows piece-graphics)))) 43 | 44 | (defn clean-piece 45 | "Contract: vector -> vector 46 | Removes any empty rows and cols from a piece." 47 | [piece-graphics] 48 | (clean-cols (clean-rows piece-graphics))) 49 | 50 | (defn insert-piece-row 51 | "Contract: vector vector int -> vector" 52 | [piece-row matrix-row position] 53 | (let [row-size (count matrix-row) 54 | end-position (+ position (count piece-row)) 55 | leading-space (take-while #(= "." %) piece-row) 56 | trailing-space (drop-while #(not= "." %) (drop-while #(= "." %) piece-row)) 57 | before-piece (subvec matrix-row 0 (+ position (count leading-space))) 58 | piece (filter #(not= "." %) piece-row) 59 | after-piece (subvec matrix-row (- end-position (count trailing-space)))] 60 | (into [] 61 | (concat 62 | before-piece piece after-piece)))) 63 | 64 | (defn upcase-matrix 65 | "Contract: vector -> vector" 66 | [matrix] 67 | (mapv #(mapv s/upper-case %) matrix)) 68 | 69 | (defn downcase-matrix 70 | "Contract: vector -> vector" 71 | [matrix] 72 | (mapv #(mapv s/lower-case %) matrix)) 73 | 74 | ;; Throws an IndexOutOfBoundsException, if the row/col are outside the matrix. 75 | ;; NOTE: (map #(map s/upper-case %) piece) - Need the double map, since the piece is represented as a vector of vectors. 76 | (defn insert-piece 77 | "Contract: vector vector int int -> vector" 78 | [piece matrix row col] 79 | (let [piece (clean-piece piece) 80 | num-piece-rows (count piece) 81 | rows-before-piece (subvec matrix 0 row) 82 | rows-for-piece (subvec matrix row (+ row num-piece-rows)) 83 | rows-after-piece (subvec matrix (+ num-piece-rows row)) 84 | piece-upcased (upcase-matrix piece) 85 | piece-inserted (map #(insert-piece-row %1 %2 col) piece-upcased rows-for-piece)] 86 | (into [] (concat rows-before-piece piece-inserted rows-after-piece)))) 87 | 88 | (defn get-filled-lines 89 | "Contract: vector -> vector 90 | Returns any lines with no empty spaces in them." 91 | [matrix] 92 | (filter #(not (some #{"."} %)) matrix)) 93 | 94 | (defn clear-filled-lines 95 | "Contract: vector -> vector 96 | Well, if the player has filled any lines, we have to unfill them." 97 | [matrix] 98 | (let [num-cleared-lines (count (get-filled-lines matrix)) 99 | matrix-cleared (into [] (remove #(not (some #{"."} %)) matrix))] 100 | (into [] 101 | (concat 102 | (take num-cleared-lines (repeat g/EMPTY-LINE)) 103 | matrix-cleared)))) 104 | -------------------------------------------------------------------------------- /src/tetris/move.clj: -------------------------------------------------------------------------------- 1 | ;; This module controls motion in the game. 2 | ;; The very act of moving inplies changing state, because of this the functions here all deal with atoms. 3 | ;; The atoms we are dealing with here are the playfield and active-piece. 4 | ;; The active-piece looks like this: (atom {:id "" :rotation :CENTER :row 0 :col 0 :anchored false :graphics []}) 5 | ;; The playfield looks like this: 6 | ;; (atom 7 | ;; [["." "." "." "." "." "." "." "." "." "."] 8 | ;; ["." "." "." "." "." "." "." "." "." "."] 9 | ;; ["." "." "." "." "." "." "." "." "." "."] 10 | ;; ["." "." "." "." "." "." "." "." "." "."] 11 | ;; ["." "." "." "." "." "." "." "." "." "."] 12 | ;; ["." "." "." "." "." "." "." "." "." "."] 13 | ;; ["." "." "." "." "." "." "." "." "." "."] 14 | ;; ["." "." "." "." "." "." "." "." "." "."] 15 | ;; ["." "." "." "." "." "." "." "." "." "."] 16 | ;; ["." "." "." "." "." "." "." "." "." "."] 17 | ;; ["." "." "." "." "." "." "." "." "." "."] 18 | ;; ["." "." "." "." "." "." "." "." "." "."] 19 | ;; ["." "." "." "." "." "." "." "." "." "."] 20 | ;; ["." "." "." "." "." "." "." "." "." "."] 21 | ;; ["." "." "." "." "." "." "." "." "." "."] 22 | ;; ["." "." "." "." "." "." "." "." "." "."] 23 | ;; ["." "." "." "." "." "." "." "." "." "."] 24 | ;; ["." "." "." "." "." "." "." "." "." "."] 25 | ;; ["." "." "." "." "." "." "." "." "." "."] 26 | ;; ["." "." "." "." "." "." "." "." "." "."] 27 | ;; ["." "." "." "." "." "." "." "." "." "."] 28 | ;; ["." "." "." "." "." "." "." "." "." "."]]) 29 | 30 | (ns tetris.move 31 | (:require [tetris.graphics :as g] 32 | [tetris.matrix :as m] 33 | [tetris.collision :as c])) 34 | 35 | ;; All possible tetris pieces and their spawning locations. 36 | (def START-POSITIONS 37 | {"I" [0 3] 38 | "O" [0 4] 39 | "Z" [0 3] 40 | "S" [0 3] 41 | "J" [0 3] 42 | "L" [0 3] 43 | "T" [0 3]}) 44 | 45 | (defn set-active-piece! 46 | ([active-piece id] (set-active-piece! active-piece id :CENTER (START-POSITIONS id))) 47 | ([active-piece id [row col]] (set-active-piece! active-piece id (:rotation @active-piece) [row col])) 48 | ([active-piece id rotation [row col]] ;; Contract: atom string keyword [int int] -> nil 49 | (swap! 50 | active-piece 51 | (fn [p] {:id id 52 | :rotation rotation 53 | :row row 54 | :col col 55 | :anchored false 56 | :graphics (g/get-graphics id rotation)})))) 57 | 58 | (defn update-playfield! 59 | "Contract: atom atom -> nil 60 | Note: Both arguments are atoms that will be swapped." 61 | [playfield active-piece] 62 | (swap! playfield 63 | #(m/downcase-matrix 64 | (m/insert-piece (:graphics @active-piece) % (:row @active-piece) (:col @active-piece)))) 65 | (swap! active-piece #(assoc % :anchored true))) 66 | 67 | (defn move-active-piece! 68 | "Contract: atom atom int int -> nil or error keyword" 69 | [playfield active-piece & {:keys [x y] 70 | :or {x (:row @active-piece) 71 | y (:col @active-piece)}}] 72 | (if (= :in-bounds (c/check-bounds x y (:graphics @active-piece) @playfield)) 73 | (set-active-piece! active-piece (:id @active-piece) [x y]) 74 | :out-of-bounds)) 75 | 76 | (defn move-left! 77 | "Contract: atom atom -> nil or error keyword 78 | Allows the player to move the current active piece to the left." 79 | [playfield active-piece] 80 | (let [new-x (:row @active-piece) 81 | new-y (dec (:col @active-piece))] 82 | (cond 83 | (not= :in-bounds (c/check-bounds new-x new-y (:graphics @active-piece) @playfield)) :out-of-bouns 84 | (= :collision (c/detect-collision new-x new-y (:graphics @active-piece) @playfield)) :cant-move-there 85 | :else (move-active-piece! playfield active-piece :x new-x :y new-y)))) 86 | 87 | (defn move-right! 88 | "Contract: atom atom -> nil or error keyword 89 | Allows the player to move the current active piece to the right." 90 | [playfield active-piece] 91 | (let [new-x (:row @active-piece) 92 | new-y (inc (:col @active-piece))] 93 | (cond 94 | (not= :in-bounds (c/check-bounds new-x new-y (:graphics @active-piece) @playfield)) :out-of-bouns 95 | (= :collision (c/detect-collision new-x new-y (:graphics @active-piece) @playfield)) :cant-move-there 96 | :else (move-active-piece! playfield active-piece :x new-x :y new-y)))) 97 | 98 | (defn move-down! 99 | "Contract: atom atom -> nil or error keyword 100 | Moves to move the current active tetris piece one step down." 101 | [playfield active-piece] 102 | (let [new-x (inc (:row @active-piece)) 103 | new-y (:col @active-piece)] 104 | (cond 105 | (not= :in-bounds (c/check-bounds new-x new-y (:graphics @active-piece) @playfield)) (update-playfield! playfield active-piece) 106 | (= :collision (c/detect-collision new-x new-y (:graphics @active-piece) @playfield)) (update-playfield! playfield active-piece) 107 | :else (move-active-piece! playfield active-piece :x new-x :y new-y)))) 108 | 109 | (defn hard-drop! 110 | "Contract: atom atom -> nil or error keyword 111 | Drop the player to the bottom of the matrix instantly." 112 | [playfield active-piece] 113 | (dotimes [i (count @playfield)] 114 | (move-down! playfield active-piece))) 115 | -------------------------------------------------------------------------------- /src/tetris/gui.clj: -------------------------------------------------------------------------------- 1 | ;; This module renders the game. Our GUI is a command line interface with colors. 2 | (ns tetris.gui 3 | (:require [lanterna.terminal :as t] 4 | [lanterna.screen :as console] 5 | [tetris.graphics :as g] 6 | [tetris.move :as mv] 7 | [tetris.collision :as c] 8 | [tetris.matrix :as m]) 9 | (:import com.googlecode.lanterna.screen.Screen)) 10 | 11 | ;; The window that will hold our game. 12 | (def WINDOW (t/get-terminal :swing {:rows 26 :cols 19 :font-size 20})) 13 | (def DISPLAY (new Screen WINDOW)) 14 | (def REDRAW-PAUSE 20) 15 | 16 | (defn get-color 17 | [ch] 18 | (cond 19 | (or (= \b ch) (= \B ch)) {:fg :blue} 20 | (or (= \r ch) (= \R ch)) {:fg :red} 21 | (or (= \y ch) (= \Y ch)) {:fg :yellow :styles #{:bold}} 22 | (or (= \g ch) (= \G ch)) {:fg :green} 23 | (or (= \m ch) (= \M ch)) {:fg :magenta} 24 | (or (= \c ch) (= \C ch)) {:fg :cyan} 25 | (or (= \o ch) (= \O ch)) {:fg :yellow} 26 | (= \= ch) {:fg :white :styles #{:underline}} 27 | :else {:fg :default :bg :default})) 28 | 29 | (defn right-pad 30 | "Right pad string with spaces, making it at least len long." 31 | [mystr len] 32 | (format (str "%-" len "s") mystr)) 33 | 34 | (defn print-line! 35 | "Contract: string int bool -> nil 36 | A custom printing function for our swing console. 37 | NOTE: Obliously it returns something, since it's a call to map, 38 | but the result is useless so I'm contracting it as -> nil." 39 | [text lnum use-color] 40 | (doall 41 | (map-indexed 42 | (fn [idx ch] 43 | (console/put-string DISPLAY idx lnum (str ch) (when use-color (get-color ch)))) 44 | text))) 45 | 46 | (defn clear-screen! 47 | "Contract: nil -> nil 48 | Clear the console window." 49 | [] 50 | (console/redraw DISPLAY) 51 | (Thread/sleep REDRAW-PAUSE)) ;; We need a slight delay when redrawing or it will consume too much CPU. 52 | 53 | (defn print-matrix! 54 | "Contract: vector int -> nil" 55 | [matrix offset] 56 | (flush) 57 | (if (empty? matrix) (recur (m/get-empty-matrix) offset) 58 | (let [lines (map #(clojure.string/join " " %) matrix)] 59 | (doseq [[line i] (map list lines (range (count lines)))] 60 | (print-line! line (+ i offset) true))))) 61 | 62 | (defn show-next-piece! 63 | "Contract: string -> nil 64 | Displays the next tetris piece that the player will receive." 65 | [next-piece-id] 66 | (print-line! "^^^ NEXT1 PIECE ^^^" 3 false) 67 | (let [next-piece-graphics (g/get-graphics next-piece-id :CENTER) 68 | start-position (mv/START-POSITIONS next-piece-id) 69 | padding (into [] (take 3 (repeat g/EMPTY-LINE))) 70 | x (first start-position) 71 | y (last start-position) 72 | offset 0] 73 | (print-matrix! 74 | (m/insert-piece 75 | next-piece-graphics padding x y) 76 | offset))) 77 | 78 | (defn get-lowest-row 79 | "Contract: vector vector int int -> int 80 | Returns the lowest row that a piece can drop in the matrix." 81 | [matrix piece-graphics current-row current-col] 82 | (let [new-x (inc current-row) 83 | new-y current-col] 84 | (cond 85 | (not= :in-bounds (c/check-bounds new-x new-y piece-graphics matrix)) current-row 86 | (= :collision (c/detect-collision new-x new-y piece-graphics matrix)) current-row 87 | :else (recur matrix piece-graphics new-x new-y)))) 88 | 89 | (defn show-playfield! 90 | "Contract: vector vector -> nil 91 | Renders the playfield along with the current tetris piece and it's shadow. 92 | The shadow is the little preview at the bottom, that tells the player where the current tetris piece is going to land." 93 | [playfield active-piece] 94 | (let [shadow-graphics (map (fn [row] (map #(if (not= "." %) "=" %) row)) (:graphics active-piece)) 95 | shadow-col (:col active-piece) 96 | shadow-row (get-lowest-row playfield shadow-graphics (:row active-piece) shadow-col) 97 | playfield-with-shadow (m/insert-piece shadow-graphics playfield shadow-row shadow-col) 98 | start-row 4] 99 | (print-matrix! 100 | (m/insert-piece 101 | (:graphics active-piece) playfield-with-shadow (:row active-piece) (:col active-piece)) 102 | start-row))) 103 | 104 | (defn get-key 105 | "Contract: nil -> char 106 | Does not block when listening for a keypress." 107 | [] 108 | (console/get-key DISPLAY)) 109 | 110 | (defn get-key-blocking 111 | "Contract: nil -> char 112 | Blocks when listening for a keypress." 113 | [] 114 | (console/get-key-blocking DISPLAY)) 115 | 116 | (defn start-gui 117 | "Contract: nil -> nil" 118 | [] 119 | (console/start DISPLAY)) 120 | 121 | (defn center-gui! 122 | "Contract: nil -> nil" 123 | [] 124 | (-> WINDOW (.getJFrame) (.setLocationRelativeTo nil))) 125 | 126 | (defn set-title! 127 | "Contract: string -> nil" 128 | [title] 129 | (-> WINDOW (.getJFrame) (.setTitle title))) 130 | 131 | (defn show-title-screen! 132 | "Contract: nil -> char" 133 | [] 134 | (print-line! "***** TETRIS *****" 0 false) 135 | (print-line! "PRESS ANY KEY: PLAY" 1 false) 136 | (print-line! "PRESS ESC: QUIT" 2 false) 137 | (print-line! "AROW KEYS: MOVE" 3 false) 138 | (print-line! "PRESS Z: ROTATE L" 4 false) 139 | (print-line! "PRESS X: ROTATE R" 5 false) 140 | (print-line! "PRESS P: PAUSE" 6 false) 141 | (print-line! "PRESS ENTER: PAUSE" 7 false) 142 | (clear-screen!) 143 | (get-key-blocking)) 144 | 145 | (defn show-pause-screen! 146 | "Contract int int -> nil" 147 | [score cleared-lines] 148 | (print-line! "*** GAME PAUSED ***" 0 false) 149 | (print-line! "*ANY KEY: CONTINUE*" 1 false) 150 | (print-line! (right-pad (str "*SCORE: " score) 19) 2 false) 151 | (print-line! (right-pad (str "*LINES: " cleared-lines) 19) 3 false) 152 | (clear-screen!) 153 | (get-key-blocking)) 154 | -------------------------------------------------------------------------------- /src/tetris/core.clj: -------------------------------------------------------------------------------- 1 | ;; The main/core module handles the game loop. 2 | (ns tetris.core 3 | (:require [clojure.string :as s] 4 | [tetris.matrix :as m] 5 | [tetris.rotation :as r] 6 | [tetris.move :as mv] 7 | [tetris.score :as score] 8 | [tetris.gui :as gui]) 9 | (:gen-class)) 10 | 11 | ;; ------------------------------------------------------------------------------------------- 12 | ;; ----------------------------------------- GLOBALS ----------------------------------------- 13 | ;; ------------------------------------------------------------------------------------------- 14 | 15 | ;; The player's score. 16 | (def SCORE (atom 0)) 17 | (def CLEARED-LINES (atom 0)) 18 | (def HIGH-SCORE-FILE "./score.dat") 19 | 20 | ;; Timers. 21 | (def LAST-MOVE-TIME (atom (System/currentTimeMillis))) ;; The exact time of when the game last moved down. 22 | 23 | ;; Our playfield. 24 | (def MATRIX (atom [])) 25 | 26 | ;; The active tetris piece that the player moves. 27 | ;; Possible rotations: CENTER, ROTATE1, ROTATE2, ROTATE3 28 | (def ACTIVE-PIECE (atom {:id "" :rotation :CENTER :row 0 :col 0 :anchored false :graphics []})) 29 | 30 | ;; The number of pieces that the player has received so far in the game. 31 | (def PIECE-COUNT (atom 0)) 32 | 33 | ;; A lazy seq of all pieces that will flow one after the other during the game. 34 | (def NEXT-PIECE 35 | (repeatedly 36 | #(first (shuffle ["I" "O" "Z" "S" "J" "L" "T"])))) 37 | 38 | ;; ------------------------------------------------------------------------------------------- 39 | ;; ------------------------------------- HELPER FUNCTIONS ------------------------------------ 40 | ;; ------------------------------------------------------------------------------------------- 41 | 42 | (defn clear-playfield! 43 | "Contract: nil -> nil" 44 | [] 45 | (swap! MATRIX (fn [m] (m/get-empty-matrix)))) 46 | 47 | (defn choose-new-piece! 48 | "Contract: nil -> string" 49 | [] 50 | (mv/set-active-piece! ACTIVE-PIECE (nth NEXT-PIECE @PIECE-COUNT)) 51 | (swap! PIECE-COUNT #(inc %))) 52 | 53 | (defn game-over? 54 | "Contract: nil -> bool 55 | Returns true if the player has reached the top level of the matrix, thus losing the game." 56 | [] 57 | (some #(not= "." %) (first @MATRIX))) 58 | 59 | (defn quit-game! 60 | "Contract: nil -> nil" 61 | [] 62 | (System/exit 0)) 63 | 64 | (defn restart-game! 65 | "Contract: nil -> nil 66 | Allows the player to start playing the game again after game-over." 67 | [] 68 | (reset! SCORE 0) 69 | (reset! CLEARED-LINES 0) 70 | (clear-playfield!)) 71 | 72 | ;; game-over! needs to call game-loop early, in order to restart the game. 73 | (declare game-loop) 74 | 75 | (defn game-over! 76 | "Contract: nil -> nil 77 | Shows the game over message and exits when the player presses ESC key." 78 | [] 79 | (score/overwrite-high-score! HIGH-SCORE-FILE @SCORE @CLEARED-LINES) 80 | (gui/print-line! "**** GAME OVER ****" 0 false) 81 | (gui/print-line! (gui/right-pad (str "YOUR SCORE - " @SCORE) 19) 1 false) 82 | (gui/print-line! (gui/right-pad (str "YOUR LINES - " @CLEARED-LINES) 19) 2 false) 83 | (gui/print-line! (gui/right-pad (str "HIGH SCORE - " (first (score/read-high-score HIGH-SCORE-FILE))) 19) 3 false) 84 | (gui/print-line! (gui/right-pad (str "HIGH LINES - " (last (score/read-high-score HIGH-SCORE-FILE))) 19) 4 false) 85 | (gui/print-line! (gui/right-pad "ENTER: RESTART" 19) 5 false) 86 | (gui/print-line! (gui/right-pad "ESC: QUIT" 19) 6 false) 87 | (gui/clear-screen!) 88 | (let [input-key (gui/get-key-blocking)] 89 | (cond 90 | (= :escape input-key) (quit-game!) 91 | (= :enter input-key) (do (restart-game!) (game-loop)) 92 | :else (recur)))) 93 | 94 | (defn get-game-speed "Contract: nil -> int" [] 95 | (cond 96 | (> @CLEARED-LINES 100) 60 97 | (> @CLEARED-LINES 75) 80 98 | (> @CLEARED-LINES 60) 120 99 | (> @CLEARED-LINES 40) 250 100 | (> @CLEARED-LINES 30) 330 101 | (> @CLEARED-LINES 20) 400 102 | (> @CLEARED-LINES 10) 500 103 | :else 600)) 104 | 105 | (defn force-down! 106 | "Contract: nil -> nil 107 | Force the current active piece to move down on its own." 108 | [] 109 | (when (> 110 | (- (System/currentTimeMillis) @LAST-MOVE-TIME) 111 | (get-game-speed)) 112 | (swap! LAST-MOVE-TIME (fn [x] (System/currentTimeMillis))) 113 | (mv/move-down! MATRIX ACTIVE-PIECE))) 114 | 115 | ;; ------------------------------------------------------------------------------------------- 116 | ;; ---------------------------------------- GAME LOOP ---------------------------------------- 117 | ;; ------------------------------------------------------------------------------------------- 118 | 119 | (defn read-input 120 | "Contract: nil -> nil" 121 | [] 122 | (let [user-input (gui/get-key)] 123 | (cond 124 | (= :left user-input) (mv/move-left! MATRIX ACTIVE-PIECE) 125 | (= :right user-input) (mv/move-right! MATRIX ACTIVE-PIECE) 126 | (= :down user-input) (mv/move-down! MATRIX ACTIVE-PIECE) 127 | (= :up user-input) (mv/hard-drop! MATRIX ACTIVE-PIECE) 128 | (= :escape user-input) (quit-game!) 129 | (= :enter user-input) (gui/show-pause-screen! @SCORE @CLEARED-LINES) 130 | (= \p user-input) (gui/show-pause-screen! @SCORE @CLEARED-LINES) 131 | (= \z user-input) (r/rotate-left! MATRIX ACTIVE-PIECE) 132 | (= \x user-input) (r/rotate-right! MATRIX ACTIVE-PIECE)))) 133 | 134 | (defn step! 135 | "Contract: nil -> nil 136 | Perform the next step in the game (if the player cleared a line, count the score and stuff)" 137 | [] 138 | (let [num-cleared-lines (count (m/get-filled-lines @MATRIX))] 139 | (when (> num-cleared-lines 0) 140 | (swap! MATRIX #(m/clear-filled-lines %)) 141 | (swap! CLEARED-LINES #(+ num-cleared-lines %)) 142 | (swap! SCORE #(+ (* 100 num-cleared-lines) %))))) 143 | 144 | (defn game-loop 145 | "Contract: nil -> nil" 146 | [] 147 | (when (or (= "" (:id @ACTIVE-PIECE)) 148 | (= true (:anchored @ACTIVE-PIECE))) 149 | (choose-new-piece!)) 150 | (step!) 151 | (gui/clear-screen!) 152 | (gui/show-next-piece! (nth NEXT-PIECE @PIECE-COUNT)) 153 | (gui/show-playfield! @MATRIX @ACTIVE-PIECE) 154 | (read-input) 155 | (force-down!) 156 | (if (game-over?) 157 | (game-over!) 158 | (recur))) 159 | 160 | (defn -main [] 161 | (println "Done!") ;; Signal that we have loaded the program. 162 | (gui/start-gui) 163 | ;; Center the main window before showing the title screen. 164 | (gui/center-gui!) 165 | (gui/set-title! "TETRIS") 166 | (gui/show-title-screen!) 167 | (clear-playfield!) 168 | (game-loop)) 169 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | --------------------------------------------------------------------------------