Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: Iterating over a vector of vectors to find the first vector that meets a certain condition

To learn Clojure I'm working on a little Tic Tac Toe game. After completing the first part of the game with relative ease, I've struggled trying to build an intelligent computer player.

For the test I'm writing to help guide this, I want to check that the computer picks spot 9 if it's the computer's turn and this is the board:

X | O | 3
4 | X | O
7 | 8 | 9

To begin the game, the board is defined like this, as a map with key-value pairs representing the location on the board and the contents of that space:

(def board {1 "1" 2 "2" 3 "3" 4 "4" 5 "5" 6 "6" 7 "7" 8 "8" 9 "9"})

I had a few thoughts about how to solve this problem. One was to define the winning sets like this:

(def winning-sets
    [[(board 1) (board 2) (board 3)],
     [(board 4) (board 5) (board 6)],
     [(board 7) (board 8) (board 9)],
     [(board 1) (board 4) (board 7)],
     [(board 2) (board 5) (board 8)],
     [(board 3) (board 6) (board 9)],
     [(board 1) (board 5) (board 9)],
     [(board 3) (board 5) (board 7)]])

Iterate over each set:

(for [set winning-sets]
    (filter #(= symbol %) set))

But that doesn't seem right...I don't know where I would go from there. The problem I'm trying to solve can be described like this:

Tell the computer to look over the 8 winning sets and find one set that has TWO of your symbols and ONE open spot.

I'm fairly new to Clojure, so I'm just not sure I understand the best way to approach this problem. I've been looking at the ClojureDocs (checking out iterating functions like for and loop and case), but haven't been able to make this work.

What would be the best way to iterate over those winning sets, currently in vector form, and find the set that has two of a specific symbol and one opening? Or would it be best to store the winning sets in a different data structure?

NOTE: I've read the responses to this question, but haven't been able to apply them to mine.

like image 327
rzv Avatar asked Oct 07 '22 04:10

rzv


1 Answers

First of all, I advice you to use this structure for board position:

(def board [[1 1 0] 
            [0 0 0] 
            [1 0 1]])

Where X is 1, O is -1 and empty cell is 0. Board in my example has only X symbols (to simplify). Next,

(def winning-sets
  '([[0 0] [0 1] [0 2]] 
    [[1 0] [1 1] [1 2]] 
    [[2 0] [2 1] [2 2]]
    [[0 0] [1 0] [2 0]] 
    [[0 1] [1 1] [2 1]] 
    [[0 2] [1 2] [2 2]]
    [[0 0] [1 1] [2 2]]
    [[0 2] [1 1] [2 0]]))

This is "winning" sets of coordinates. You can calculate this if necessary, but for 3x3 this list is really not so big. In this terms, the answer for you question is

(defn check 
  [target combo] 
  (= (map #(count (filter (partial = %) combo)) [target 0]) '(2 1)))

(defn extract 
  [coords] 
  (apply vector (map (fn [[f s]] ((board f) s)) coords))) 

(filter #(check 1 (extract %)) winning-sets)

If you execute this code in REPL, you will see

user=> (filter #(check 1 (extract %)) winning-sets)
([[0 0] [0 1] [0 2]] 
 [[2 0] [2 1] [2 2]] 
 [[0 0] [1 0] [2 0]] 
 [[0 0] [1 1] [2 2]])

which looks like right answer (2 horizontal lines, 1 vertical and 1 diagonal). Code is rough and there are few ways to make it more beautiful and reusable. Should I explain what's going on or code is clear enough?

like image 195
Alexey Kachayev Avatar answered Oct 10 '22 02:10

Alexey Kachayev