Queen Attack in Clojure: Complete Solution & Deep Dive Guide

a close up of a computer screen with code on it

The Definitive Guide to Solving Queen Attack in Clojure

Solving the Queen Attack problem in Clojure is a classic algorithmic challenge that involves checking if two queens on a chessboard share the same row, column, or diagonal. This is elegantly achieved by comparing their coordinates and verifying if the absolute difference of their row and column positions are equal.

You’ve seen the chessboard, a field of 64 squares, a battleground of logic and strategy. The Queen, the most powerful piece, glides effortlessly across rows, columns, and diagonals. Translating this powerful, visual movement into code can feel like a daunting task, especially within the functional paradigm of a language like Clojure. You might be wondering how to capture these geometric rules with immutable data and pure functions.

Fear not. This comprehensive guide will walk you through the entire process, from understanding the core logic to implementing a clean, idiomatic, and efficient Clojure solution. We will deconstruct the problem, explore the underlying mathematical principles, and build a solution step-by-step, solidifying your understanding of algorithmic thinking in a functional context.


What is the Queen Attack Problem?

The Queen Attack problem is a foundational challenge derived from the game of chess. The goal is simple: given the coordinates of two queens on a standard 8x8 chessboard, determine whether they can attack each other in a single move.

In chess, a queen's power lies in her unrestricted movement. She can move any number of squares horizontally, vertically, or diagonally. This translates into three specific conditions for an attack:

  • Same Row: The queens are on the same horizontal line.
  • Same Column: The queens are on the same vertical line.
  • Same Diagonal: The queens share a diagonal path.

Our task is to create a function that accepts two coordinate pairs—one for the white queen (W) and one for the black queen (B)—and returns true if any of these conditions are met, and false otherwise. For this problem, we will use a zero-indexed coordinate system, where both rows and columns are numbered from 0 to 7.


   // Coordinate System (column, row) or (x, y)
   // Top-left corner is (0, 0)
   // Bottom-right corner is (7, 7)

   (0,0) (1,0) (2,0) ... (7,0)
   (0,1) (1,1) (2,1) ... (7,1)
   ...
   (0,7) (1,7) (2,7) ... (7,7)

For example, if the white queen is at [2 3] (column 2, row 3) and the black queen is at [5 6], we need to determine if they are on the same row, column, or diagonal.


Why Use Clojure for Algorithmic Problems?

Clojure, a modern Lisp dialect that runs on the JVM, might seem like an esoteric choice for algorithms at first glance, but its design philosophy offers significant advantages for solving problems like Queen Attack. Its functional-first approach encourages a clear and declarative style that often mirrors the problem's logical definition.

Key Clojure Features

  • Immutability by Default: All data structures in Clojure are immutable. This eliminates entire classes of bugs related to state modification. For a problem like Queen Attack, where we are simply checking a condition based on static input, immutability ensures our logic is predictable and free of side effects.
  • Pure Functions: The core of our solution will be a pure function. It will take the queen positions as input and produce a boolean output, without changing any external state. This makes the code easy to reason about, test, and debug.
  • Powerful Data Destructuring: Clojure's ability to destructure data structures like vectors and maps is incredibly expressive. We can elegantly pull out the x and y coordinates from our input without boilerplate code, making the logic cleaner and more readable.
  • REPL-Driven Development: The interactive Read-Eval-Print Loop (REPL) allows for rapid experimentation. You can test each part of the attack logic—the row check, the column check, the diagonal check—in isolation before combining them, leading to a more robust final solution.

By leveraging these features, we can write a solution that is not only correct but also concise, elegant, and highly reflective of the mathematical rules governing the problem.


How to Implement the Queen Attack Logic in Clojure

Now, let's dive into the implementation. Our goal is to create a function, can-attack?, that takes the positions of the white and black queens and returns true or false. We'll represent the positions as two-element vectors, like [x y].

The Complete Clojure Solution

Here is the full, idiomatic Clojure code from the exclusive kodikra.com curriculum. We'll define the core logic in the queen-attack namespace.


(ns queen-attack
  (:require [clojure.string :as str]))

(defn board-string
  "Creates a string representation of the 8x8 board with queen positions.
  This is a helper function for visualization and not part of the core attack logic."
  [{:keys [w b]}]
  (let [board-size 8
        ;; Create an 8x8 grid filled with underscores
        board (vec (repeat board-size (vec (repeat board-size "_"))))]
    (if (or (nil? w) (nil? b))
      (throw (Exception. "Queen positions must be provided."))
      (-> board
          ;; Place the White queen
          (assoc-in w "W")
          ;; Place the Black queen
          (assoc-in b "B")
          ;; Convert each row vector into a space-separated string
          (->> (map #(str/join " " %))
               ;; Join all row strings with newlines
               (str/join "\n"))))))

(defn can-attack?
  "Determines if two queens at given positions can attack each other.
  Positions are given as a map, e.g., {:w [x1 y1] :b [x2 y2]}"
  [{:keys [w b]}]
  (if (or (nil? w) (nil? b) (= w b))
    ;; According to the problem constraints, queens cannot occupy the same square.
    ;; We return false or could throw an error for invalid input.
    (throw (Exception. "Queens must be on different squares."))
    
    (let [[wx wy] w  ; Destructure white queen's coordinates
          [bx by] b] ; Destructure black queen's coordinates
      (or
        ;; Condition 1: Are they on the same row?
        (= wy by)

        ;; Condition 2: Are they on the same column?
        (= wx bx)

        ;; Condition 3: Are they on the same diagonal?
        ;; This is true if the absolute difference of their x-coordinates
        ;; equals the absolute difference of their y-coordinates.
        (= (Math/abs (- wx bx))
           (Math/abs (- wy by)))))))

Logic Flow Diagram

This diagram illustrates the decision-making process inside our can-attack? function. The logic flows downwards, checking each condition until one is met or all have been exhausted.

    ● Start with Queen Positions {:w [wx wy], :b [bx by]}
    │
    ▼
  ┌──────────────────┐
  │ Destructure Coords │
  │ wx, wy, bx, by   │
  └────────┬─────────┘
           │
           ▼
    ◆ Same Row? (wy == by)
   ╱           ╲
  Yes (true)    No
  │              │
  └───────────┐  ▼
              │  ◆ Same Column? (wx == bx)
              │ ╱           ╲
              Yes (true)    No
              │              │
              └───────────┐  ▼
                          │  ◆ Same Diagonal? (|Δx| == |Δy|)
                          │ ╱           ╲
                          Yes (true)    No (false)
                          │              │
                          └──────┬───────┘
                                 │
                                 ▼
                             ● Return Result

Detailed Code Walkthrough

Let's break down the can-attack? function line by line to understand how it works.

1. Function Signature and Input Validation


(defn can-attack?
  [{:keys [w b]}]
  (if (or (nil? w) (nil? b) (= w b))
    (throw (Exception. "Queens must be on different squares."))
    ...
  • (defn can-attack? [{:keys [w b]}]): We define a function named can-attack?. It takes a single argument: a map. We use map destructuring {:keys [w b]} to immediately bind the values associated with the :w and :b keys to local variables named w and b. This is a highly idiomatic way to handle named parameters in Clojure.
  • Input Validation: The if statement is a guard clause. It checks for invalid input scenarios: if either queen's position is missing (nil) or if they are on the same square ((= w b)). In such cases, it throws an exception, as this represents an impossible board state according to the rules. This makes our function more robust.

2. The let Binding and Coordinate Destructuring


(let [[wx wy] w
      [bx by] b]
  ...
  • The let block creates local bindings. Here, we use vector destructuring.
  • [wx wy] w: This takes the vector bound to w (e.g., [2 3]) and binds its first element to wx (2) and its second element to wy (3).
  • Similarly, [bx by] b does the same for the black queen's coordinates. After this block, we have four simple variables—wx, wy, bx, by—which makes the subsequent logic much cleaner.

3. The Core Attack Logic with or


(or
  ;; Condition 1: Same Row
  (= wy by)

  ;; Condition 2: Same Column
  (= wx bx)

  ;; Condition 3: Same Diagonal
  (= (Math/abs (- wx bx))
     (Math/abs (- wy by))))
  • We use the or macro, which is perfect for this scenario. or evaluates its arguments one by one and returns the first "truthy" value it finds. If all arguments evaluate to false or nil, it returns the last value. This is a short-circuiting operation: as soon as it finds a true condition, it stops and returns true.
  • (= wy by): This checks if the y-coordinates are identical. If they are, the queens are on the same row, this expression is true, and the or macro immediately returns true.
  • (= wx bx): If the row check fails, this expression checks if the x-coordinates are identical for a column attack.
  • (= (Math/abs (- wx bx)) (Math/abs (- wy by))): This is the most crucial part—the diagonal check.

Understanding the Diagonal Check

How do we know if two points are on the same diagonal on a grid? The key insight is that for any two points on a shared diagonal line, the horizontal distance between them is always equal to the vertical distance.

Let's consider the change in x (delta-x or Δx) and the change in y (delta-y or Δy).

  • Δx = wx - bx
  • Δy = wy - by

If the queens are on a diagonal, the slope of the line connecting them will be either 1 or -1. This means that the absolute value of the change in x must equal the absolute value of the change in y: |Δx| = |Δy|.

We use Math/abs to get the absolute value, ensuring the check works for all four diagonal directions. For example, if W is at [2 2] and B is at [5 5]:

  • |2 - 5| = |-3| = 3
  • |2 - 5| = |-3| = 3
  • Since 3 equals 3, they are on a diagonal.

Diagonal Logic Visualization

This diagram shows how the absolute difference in coordinates remains constant along a diagonal path.

    ● Queen W at [wx, wy]
    │
    ├─► Δx = wx - bx
    │
    ▼
    ● Queen B at [bx, by]
    │
    ├─► Δy = wy - by
    │
    ▼
  ┌───────────────────────────┐
  │ Check Diagonal Condition  │
  └─────────────┬─────────────┘
                │
                ▼
      ◆ Is |Δx| equal to |Δy|?
     ╱                         ╲
    Yes                         No
    │                           │
    ▼                           ▼
  ┌───────────┐               ┌──────────┐
  │ On Diagonal │               │ Not on   │
  │ (Attack!)   │               │ Diagonal │
  └───────────┘               └──────────┘

Alternative Approaches and Refinements

While the solution using or is highly readable and idiomatic, there are other ways to structure the logic in Clojure. Exploring them can deepen your understanding of the language.

Using a cond Macro

The cond macro is another excellent choice, which can sometimes be more explicit if you have many conditions or want to return different values for each case (though not needed here).


(defn can-attack-cond?
  [{:keys [w b]}]
  (if (= w b)
    (throw (Exception. "Queens must be on different squares."))
    (let [[wx wy] w
          [bx by] b]
      (cond
        (= wy by) true
        (= wx bx) true
        (= (Math/abs (- wx bx)) (Math/abs (- wy by))) true
        :else false))))

This version is slightly more verbose but makes each condition an explicit test-expression pair. The final :else false clause acts as a default case if no other conditions are met.

Creating Predicate Helper Functions

For maximum clarity and reusability, you could break down each condition into its own small helper function (a predicate). This approach shines in more complex scenarios.


(defn- same-row? [[_ wy] [_ by]]
  (= wy by))

(defn- same-col? [[wx _] [bx _]]
  (= wx bx))

(defn- same-diag? [[wx wy] [bx by]]
  (= (Math/abs (- wx bx)) (Math/abs (- wy by))))

(defn can-attack-helpers?
  [{:keys [w b]}]
  (if (= w b)
    (throw (Exception. "Queens must be on different squares."))
    (or (same-row? w b)
        (same-col? w b)
        (same-diag? w b))))

This style is self-documenting. The function names—same-row?, same-col?, same-diag?—clearly state their purpose. The main can-attack-helpers? function then reads almost like plain English.

Pros and Cons of the Main Approach

Here's a balanced look at the primary solution we developed.

Pros Cons / Considerations
Idiomatic & Concise: The use of let with destructuring and the or macro is a hallmark of clean, functional Clojure code. Implicit Logic: For a developer new to Clojure, the short-circuiting behavior of or might be less obvious than an explicit if/else or cond chain.
Efficient: The or macro short-circuits, meaning it stops evaluation as soon as an attack condition is found. This is computationally efficient. Single Return Type: This structure is optimized for returning a boolean. If you needed to return *why* an attack is possible (e.g., :row, :col), a cond would be a better fit.
Highly Readable: Once familiar with Clojure syntax, the code is very easy to read as it maps directly to the problem's logical disjunction (row OR column OR diagonal). Java Interop: The call to Math/abs is a Java interop call. While seamless in Clojure, it's a point of awareness that you are leveraging the underlying host platform.

Frequently Asked Questions (FAQ)

1. How can this solution be extended for an N x N board?

The beauty of this solution is that it's completely independent of board size. The logic is based on coordinate geometry, not on a fixed 8x8 grid. The same can-attack? function would work perfectly for a 20x20 or a 1000x1000 board without any changes. The only modification would be in any validation logic that checks if coordinates are within bounds.

2. What is the time complexity of this algorithm?

The time complexity is O(1), or constant time. The function performs a fixed number of arithmetic operations and comparisons regardless of the input coordinates or board size. It does not involve any loops or recursion, making it extremely efficient.

3. Why is using the absolute difference (Math/abs) for the diagonal check so important?

The absolute difference ensures the check works for both types of diagonals (top-left to bottom-right and top-right to bottom-left). For example, from [1 1] to [3 3], Δx=2 and Δy=2. From [3 1] to [1 3], Δx=-2 and Δy=2. Without abs(), -2 would not equal 2. By taking the absolute value, |-2| = 2, we correctly identify both as diagonal attacks.

4. How does this problem relate to the more complex N-Queens problem?

The Queen Attack problem is the fundamental building block for solving the N-Queens puzzle. The N-Queens problem asks you to place N queens on an N×N chessboard so that no two queens threaten each other. To solve it, you would use a function like our can-attack? repeatedly to check if each new queen placement is valid against all previously placed queens.

5. Can I use a different data structure to represent the queen positions?

Absolutely. While vectors (e.g., [x y]) are common, you could also use maps, such as {:x 2 :y 3}. If you used maps, you would adjust the destructuring accordingly: (let [{wx :x wy :y} w {bx :x by :y} b] ...). The core logic remains identical; only the method of accessing the coordinate values changes.

6. What happens if the input coordinates are outside the 0-7 range?

Our current function does not validate if the coordinates are on the board. It only checks the geometric relationship between them. For a production-ready system, you would add a validation step at the beginning to ensure that wx, wy, bx, and by are all within the valid range (e.g., 0 to 7 for a standard board).

7. Is there a way to solve this without Java interop (Math/abs)?

Yes, you could write a pure Clojure absolute value function, although it's less common since Math/abs is highly optimized. A simple version would be: (defn abs [n] (if (neg? n) (- n) n)). You could then use this function in your diagonal check: (= (abs (- wx bx)) (abs (- wy by))).


Conclusion: From Chess Rules to Functional Code

We have successfully translated the movement of a chess queen into a clean, efficient, and idiomatic Clojure function. By breaking the problem down into three simple conditions—same row, same column, and same diagonal—we were able to construct a robust solution. The journey highlighted the strengths of Clojure's functional paradigm, including immutability, pure functions, and expressive data destructuring.

The core takeaway is the mathematical principle for diagonal detection: two points share a diagonal if the absolute difference of their x-coordinates equals the absolute difference of their y-coordinates. This small piece of logic is the key that unlocks the entire problem and is applicable to many other grid-based challenges, from game development to pathfinding algorithms.

This kodikra module serves as a perfect example of how abstract rules can be elegantly modeled with code. As you continue your programming journey, you'll find that this pattern of deconstructing a problem into its fundamental logical components is a universally valuable skill.

Disclaimer: The code and concepts in this article are based on modern Clojure (version 1.11+). The principles are timeless, but syntax and library functions may evolve in future language versions.

Ready to tackle the next challenge? Continue your progress on the Clojure 5 Learning Path or explore our complete Clojure curriculum to master the language.


Published by Kodikra — Your trusted Clojure learning resource.