-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ttt with a cli #2
base: main
Are you sure you want to change the base?
Changes from 36 commits
9a202f8
47282af
4af5bdf
968e4f2
5a52ec0
3e945df
942c476
789841d
b8db7d0
91e7b60
2cc4ee1
35f7d5e
98166db
e96f0f5
b27f9c1
bece888
6fe4ee2
b6d0e81
bca4fc6
8ff0477
1d2814a
ddecdd2
9174f36
5dbdb5b
5ff08f8
eb3d0d8
db983f8
f9b9f4e
16d6b1b
65e7a5c
33e2764
420ff2a
70733e5
49a4e0b
d18ad12
c1d4fc6
0f38dc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Tic Tac Toe | ||
### Cli interface | ||
|
||
To start the game use `lein run` | ||
|
||
Possible positions: | ||
|
||
| 1 | 2 | 3 | | ||
|-----|-----|-----| | ||
| 4 | 5 | 6 | | ||
| 7 | 8 | 9 | | ||
|
||
=> lein run: | ||
|
||
Enter a number between 1-9 to make a move | ||
|
||
Enter h to view position board | ||
|
||
Enter q to quit | ||
|
||
Current board: | ||
|
||
| e | e | e | | ||
|-----|-----|-----| | ||
| e | e | e | | ||
| e | e | e | | ||
|
||
Player is o | ||
=> 1 | ||
|
||
Current board: | ||
|
||
| o | e | e | | ||
|-----|-----|-----| | ||
| e | e | e | | ||
| e | e | e | | ||
|
||
Player is x | ||
|
||
=> q | ||
|
||
Bye bye! | ||
|
||
### Developer notes | ||
|
||
Run tests using `lein test` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
(ns ttt.board | ||
(:refer-clojure :exclude [update]) | ||
(:require | ||
[clojure.set] | ||
[ttt.matrix-operations :as mo])) | ||
|
||
|
||
(def size 3) | ||
|
||
|
||
(def position-to-coordinate | ||
(->> (for [x (range size) | ||
y (range size)] | ||
[x y]) | ||
(map-indexed (fn [i coord] [(inc i) coord])) | ||
(into {}))) | ||
|
||
|
||
(def empty-board | ||
(mapv vec (partition size (repeat (* size size) :e)))) | ||
|
||
|
||
(def valid-positions | ||
(keys position-to-coordinate)) | ||
|
||
|
||
(def positions-string | ||
(mo/matrix->string (partition size (sort valid-positions)))) | ||
|
||
|
||
(def valid-game-pieces-set #{:o :x :e}) | ||
|
||
(def game-pieces-set #{:o :x}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Should we consider |
||
|
||
|
||
(defn to-string | ||
[board] | ||
(mo/matrix->string (mapv #(mapv name %) board))) | ||
|
||
|
||
(defn empty-coordinate? | ||
[board coordinate] | ||
(= :e (get-in board coordinate))) | ||
|
||
|
||
(defn valid? | ||
[board] | ||
(and (mo/square-matrix? board) | ||
(clojure.set/subset? (set (flatten board)) valid-game-pieces-set))) | ||
|
||
|
||
(defn update | ||
[board coordinate game-piece] | ||
(when (valid? board) | ||
(assoc-in board coordinate game-piece))) | ||
|
||
|
||
(defn winner-of-collection | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: What does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the function can find the winner of any collection as opposed to a 3x3 board. Used the word collection to generalize |
||
[coll] | ||
(let [unique-val (set coll)] | ||
(when (and | ||
(= 1 (count unique-val)) | ||
(not (:e unique-val))) | ||
(first unique-val)))) | ||
|
||
|
||
(defn winners-of-seqs | ||
[matrix] | ||
(->> matrix | ||
(mo/get-all-possible-seqs) | ||
(map winner-of-collection) | ||
(remove nil?))) | ||
|
||
|
||
(defn winning-game-piece | ||
[board] | ||
(when (= 1 (count (winners-of-seqs board))) | ||
(first (winners-of-seqs board)))) | ||
|
||
|
||
(defn winner | ||
[board] | ||
(when (valid? board) | ||
(winning-game-piece board))) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
(ns ttt.core | ||
(:gen-class) | ||
(:require [ttt.game :as game] | ||
[ttt.board :as board])) | ||
|
||
(defn -main | ||
[] | ||
(game/play board/empty-board)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
(ns ttt.game | ||
(:require | ||
[failjure.core :as f] | ||
[ttt.board :as board] | ||
[ttt.user-input :as user-input])) | ||
|
||
|
||
(def player-order | ||
(take 9 (cycle board/game-pieces-set))) | ||
|
||
|
||
(defn over? | ||
[board player-sequence] | ||
(or (board/winner board) | ||
(empty? player-sequence))) | ||
|
||
|
||
(defn process-parsed-command | ||
[parsed-command board player-sequence] | ||
(case parsed-command | ||
"h" (do | ||
(println "\nPosition board" board/positions-string "\n") | ||
[board player-sequence]) | ||
|
||
"q" [nil player-sequence] | ||
|
||
(let [player (first player-sequence)] | ||
(if (board/empty-coordinate? board parsed-command) | ||
[(board/update board parsed-command player) (rest player-sequence)] | ||
(do (println "Enter an empty position") | ||
[board player-sequence]))))) | ||
|
||
|
||
(defn process-command | ||
[command board player-sequence] | ||
(f/attempt-all [valid-command (user-input/valid-command? command) | ||
parsed-command (user-input/parse-command valid-command)] | ||
(process-parsed-command parsed-command board player-sequence) | ||
(f/when-failed [e] | ||
(println (f/message e)) | ||
[board player-sequence]))) | ||
|
||
|
||
(defn play | ||
[board] | ||
(println "Enter a number between 1-9 to make a move \nEnter h to view position board \nEnter q to quit") | ||
(loop [board board | ||
player-sequence player-order] | ||
(cond | ||
(or (nil? board)) | ||
(println "Bye bye!") | ||
|
||
(over? board player-sequence) | ||
(if-let [winner (board/winner board)] | ||
(println "Winner is" (name winner)) | ||
(println "It's a draw")) | ||
|
||
:else | ||
(do (println "Current board:" (board/to-string board) | ||
"\nPlayer is" (name (first player-sequence))) | ||
(let [command (read-line) | ||
[new-board new-player-sequence] (process-command command board player-sequence)] | ||
(recur new-board new-player-sequence)))))) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,3 +47,11 @@ | |
(transpose-matrix matrix) | ||
(get-diagonals-of-matrix matrix))) | ||
|
||
|
||
(defn matrix->string | ||
[matrix] | ||
(apply str (for [row matrix] | ||
(str | ||
(apply str "\n" | ||
(interpose " " row)))))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: Using a thread last macro might make the section more readable: (->> row
(interpose " ")
(apply str "\n")
str))) Also, do we need to |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
(ns board-test | ||
(:require | ||
[clojure.test :refer :all] | ||
[ttt.board :refer :all])) | ||
|
||
|
||
(def test-empty-board | ||
[[:e :e :e] | ||
[:e :e :e] | ||
[:e :e :e]]) | ||
|
||
|
||
(deftest empty-coordinate-test | ||
(testing "Returns true" | ||
(testing "when position is empty" | ||
(is (= true (empty-coordinate? test-empty-board [1 1]))))) | ||
(testing "Returns false" | ||
(testing "when position is not empty" | ||
(is (= false (empty-coordinate? [[:e :e :e] | ||
[:e :o :e] | ||
[:e :e :e]] [1 1])))))) | ||
|
||
|
||
(deftest valid-test | ||
(testing "Returns true" | ||
(testing "when board is a square matrix and all pieces are valid" | ||
(is (= true (valid? test-empty-board))))) | ||
(testing "Return false" | ||
(testing "when board is not square matrix and all pieces are valid" | ||
(is (= false (valid? [[:e :e :e]])))) | ||
(testing "when board is square matrix and all pieces are not valid" | ||
(is (= false (valid? [[:e :e :e] | ||
[:e :e :e] | ||
[:e :e 1]])))))) | ||
|
||
|
||
(deftest update-test | ||
(testing "Returns correctly updated board" | ||
(testing "when board is valid" | ||
(is (= [[:e :e :e] | ||
[:e :o :e] | ||
[:e :e :e]] (update test-empty-board [1 1] :o)))))) | ||
|
||
|
||
(deftest winner-of-collection-test | ||
(testing "With all :e elements" | ||
(is (= nil (winner-of-collection [:e :e :e])))) | ||
|
||
(testing "With all :x elements " | ||
(is (= :x (winner-of-collection [:x :x :x])))) | ||
|
||
(testing "With 2 :x elements" | ||
(is (= nil (winner-of-collection [:x :x :o]))))) | ||
|
||
|
||
(deftest winning-game-piece-test | ||
|
||
(testing "Returns winning game piece " | ||
(testing "when winner is along a row" | ||
(is (= :o (winning-game-piece [[:e :x :e] | ||
[:o :o :o] | ||
[:x :e :x]])))) | ||
(testing "when winner is along column" | ||
(is (= :x (winning-game-piece [[:x :e :o] | ||
[:x :e :e] | ||
[:x :e :o]])))) | ||
(testing "when winner is across diagonal" | ||
(is (= :x (winning-game-piece [[:x :e :e] | ||
[:o :x :e] | ||
[:o :e :x]]))))) | ||
|
||
(testing "Returns nil " | ||
(testing "when there is a draw" | ||
(is (= nil (winning-game-piece [[:x :e :o] | ||
[:x :x :e] | ||
[:o :x :o]])))))) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
(ns game-test | ||
(:require | ||
[clojure.set] | ||
[clojure.test :refer :all] | ||
[ttt.game :refer :all])) | ||
|
||
|
||
(def test-player-sequence (cycle #{:o :x})) | ||
|
||
|
||
(deftest over-test | ||
(testing "Returns winner " | ||
(testing "when there is a winner in board" | ||
(is (= :o (over? [[:e :x :e] | ||
[:o :o :o] | ||
[:x :e :x]] (take 2 test-player-sequence)))))) | ||
(testing "Returns true" | ||
(testing "when there is a draw" | ||
(is (= true (over? [[:x :o :x] | ||
[:x :o :o] | ||
[:o :x :x]] (take 0 test-player-sequence)))))) | ||
(testing "Returns false" | ||
(testing "when there no winner with moves left" | ||
(is (= false (over? [[:x :e :o] | ||
[:e :x :e] | ||
[:o :x :o]] (take 3 test-player-sequence))))) | ||
(testing "when the board is empty" | ||
(is (= false (over? [[:e :e :e] | ||
[:e :e :e] | ||
[:e :e :e]] (take 9 test-player-sequence))))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Try to think of the README as the first thing a potential user might be looking at. What would that person want to see here?
Some potential questions:
Thinking from this perspective will help pen down a more thoughtful README.