Logic Puzzles

Takuzu

Embed a fully native Takuzu (Binairo) binary puzzle into your mobile app with minimal code.

@State private var model = TakuzuModel.example

TakuzuGameView(model: $model)
1
0
0
1
0
1
1
0
1
0
1
0
0
1
0
1
0
1
1
0
1
0
0
1
0
1
0
1
1
0
1
0
1
0
1
0
0
1
0
1
1
1
0
0
0
0
1
1

What's included

Quick integration

Drop in a TakuzuModel and TakuzuGameView — the entire game loop (tap cycling, violation detection, completion) is handled for you.

Live rule checking

model.violations returns a Set<TakuzuCoord> updated on every move. Balance, no-triples, and uniqueness violations are all detected automatically.

Any grid size

Works with any even-sized square grid. Use the included 6×6 example or supply your own 4×4, 8×8, 10×10, or larger puzzle.

Customise cells

Swap in your own cell view via the .grid(spacing:cell:) modifier. Receive value, isFixed, and isViolation for each cell to drive any visual design.

Tap-to-cycle input

Tapping an editable cell cycles: empty → 1 → 0 → empty. No number pad needed — the input model perfectly mirrors how Takuzu is played on paper.

State & reset

All live state lives in model.state. Read it any time, pass it back to restore a saved game, or call model.reset() to restart the puzzle.


The rules

Takuzu has exactly three constraints. The SDK checks all of them live on every move via model.violations.

Balance

Each row and each column must contain exactly half true ("1") and half false ("0") values. On a 6×6 grid, that means 3 of each per row and column.

No triples

No more than two consecutive identical values in any row or column. "1 1 0 0 1 1" is valid; "0 0 0 1 1 0" is not.

Uniqueness

All rows must be distinct from each other. All columns must also be distinct from each other. No two rows or columns can share the same sequence.

// Takuzu has three rules — all checked live inside model.violations:

// 1. Balance — each row and column must have equal numbers of true and false values.
//    On a 6×6 grid: exactly 3 "1"s and 3 "0"s per row and per column.

// 2. No triples — no more than two consecutive identical values in any row or column.
//    ✅  1 0 0 1 1 0   (two adjacent pairs — ok)
//    ❌  1 0 0 0 1 1   (three consecutive 0s)

// 3. Uniqueness — all rows are distinct, and all columns are distinct.

// model.violations returns a Set<TakuzuCoord> highlighting every offending cell.
let violations = model.violations
if violations.isEmpty {
    print("No violations")
}

Quick integration

Pass a model into the view to render a fully playable Takuzu grid. The view owns the tap-cycle interaction — no input controls needed.

@State private var model = TakuzuModel.example

var body: some View {
    // Pass a $Binding — every tap is written back into model.state
    TakuzuGameView(model: $model)
}

The model

TakuzuModel describes the puzzle and owns all live player state — the initial grid, the complete solution, the player's current entries, and computed helpers for completion and violation detection. Pass it as a @Binding so the view writes mutations back to your state layer.

// Supply a size, a cells grid (nil = editable, non-nil = fixed given), and the complete solution.
let model = TakuzuModel(
    size: 6,
    cells: [
        [true,  nil,   nil,   false, nil,   nil  ],
        [nil,   nil,   true,  nil,   nil,   false],
        // …
    ],
    solution: [
        [true,  true,  false, false, true,  false],
        [true,  false, true,  true,  false, false],
        // …
    ]
)

// ── Configuration ────────────────────────────────────────────────────
// model.size             — side length of the square grid (must be even)
// model.cells            — initial puzzle: nil = blank/editable, true/false = fixed given
// model.solution         — complete correct answer grid

// ── Live state (mutated during play) ─────────────────────────────────
// model.state            — player's current grid ([[Bool?]]), updated on every tap

// ── Computed ─────────────────────────────────────────────────────────
// model.isComplete       — true when every cell in state is non-nil
// model.isCorrect        — true when state matches solution exactly
// model.violations       — Set<TakuzuCoord> of cells breaking a rule

// ── Methods ───────────────────────────────────────────────────────────
// model.reset()          — restores state to the original cells grid
// model.isFixed(row:col:) — returns true for given (non-editable) cells

Providing a puzzle

Supply a cells grid with nil for empty cells and true/false for fixed givens, plus a solution grid for answer validation. TakuzuModel.example is a ready-to-use 6×6 puzzle you can drop in immediately.

// Supply a cells grid and solution — the SDK validates nothing at init time;
// rule checking happens lazily via model.violations and model.isCorrect.
let model = TakuzuModel(
    size: 6,
    cells: [
        [true,  nil,   nil,   false, nil,   nil  ],
        [nil,   nil,   true,  nil,   nil,   false],
        [nil,   false, nil,   nil,   true,  nil  ],
        [false, nil,   nil,   true,  nil,   nil  ],
        [nil,   nil,   false, nil,   true,  nil  ],
        [nil,   true,  nil,   nil,   false, nil  ],
    ],
    solution: [
        [true,  true,  false, false, true,  false],
        [true,  false, true,  true,  false, false],
        [false, false, true,  false, true,  true ],
        [false, true,  false, true,  false, true ],
        [true,  false, false, true,  true,  false],
        [false, true,  true,  false, false, true ],
    ]
)

// Use TakuzuModel.example for the same ready-to-use 6×6 puzzle.
let example = TakuzuModel.example

Tap-to-cycle input

Tapping an editable cell cycles through three states: empty → 1 → 0 → empty. Fixed givens are inert. Use .onCellTap(_:) to react to each tap in your own UI.

// Tapping an editable cell cycles: nil (empty) → true ("1") → false ("0") → nil → …
// Fixed (given) cells cannot be tapped.

TakuzuGameView(model: $model)
    .onCellTap { row, col, newValue in
        switch newValue {
        case nil:   print("(\(row),\(col)) cleared")
        case true:  print("(\(row),\(col)) = 1")
        case false: print("(\(row),\(col)) = 0")
        }
    }

Violation highlighting

By default the view passes isViolation: true to any cell that is part of a balance, no-triples, or uniqueness infraction. Toggle the feedback with .showViolations(_:), or read model.violations directly to drive your own error UI.

// Violation highlighting is ON by default.
// Cells that break balance, no-triples, or uniqueness rules are flagged.
TakuzuGameView(model: $model)
    .showViolations(true)   // default — show rule-breaking cells

// Disable to give a cleaner look (no real-time feedback)
TakuzuGameView(model: $model)
    .showViolations(false)

// Read violations directly from the model (Set<TakuzuCoord>)
let badCells = model.violations
print("\(badCells.count) cells are in violation")

State management

Because TakuzuGameView accepts a Binding<TakuzuModel>, all live game state — the player's entries, completion status, and correctness — is written back into the model on every tap. Hold the model in @State, read state directly, and call reset() to restart.

// Hold the model in @State — the view writes every tap back into model.state
struct ContentView: View {
    @State private var model = TakuzuModel.example

    var body: some View {
        VStack {
            TakuzuGameView(model: $model)

            // Read live state directly from the binding's wrapped value
            if model.isComplete {
                Text(model.isCorrect ? "Solved!" : "Incorrect — keep trying.")
            }

            Button("Restart") { model.reset() }
        }
    }
}

Customise cells

Control cell spacing and replace the built-in TakuzuCell with any view that conforms to TakuzuCellProtocol via the .grid(spacing:cell:) modifier.

TakuzuGameView(model: $model)
    .grid(spacing: 4, cell: TakuzuCell.self)

Your custom cell receives five parameters: row, column, the current value (nil / true / false), whether the cell is a fixed given, and whether it is currently in violation of a rule.

struct MyCell: TakuzuCellProtocol {
    let row: Int
    let column: Int
    let value: Bool?          // nil = empty, true = "1", false = "0"
    let isFixed: Bool         // true for given (non-editable) cells
    let isViolation: Bool     // true when this cell breaks a rule

    init(row: Int, column: Int, value: Bool?, isFixed: Bool, isViolation: Bool) {
        self.row = row; self.column = column
        self.value = value; self.isFixed = isFixed; self.isViolation = isViolation
    }

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 8)
                .fill(background)
                .overlay(
                    RoundedRectangle(cornerRadius: 8)
                        .strokeBorder(isViolation ? Color.red : Color.clear, lineWidth: 2)
                )
            if let v = value {
                Text(v ? "1" : "0")
                    .font(.system(size: 18, weight: isFixed ? .heavy : .semibold, design: .rounded))
                    .foregroundStyle(.white)
            }
        }
        .aspectRatio(1, contentMode: .fit)
    }

    private var background: Color {
        switch value {
        case nil:   return Color(.systemGray5)
        case true:  return .accentColor
        case false: return Color(.label)
        }
    }
}

// Register the custom cell
TakuzuGameView(model: $model)
    .grid(spacing: 4, cell: MyCell.self)

Protocols

TakuzuGameView exposes one protocol extension point — the cell view.

TakuzuCellProtocol
.grid(spacing:cell:)

The cell view contract. Implement to control how each grid cell looks. Receives the cell value (nil/true/false), isFixed, and isViolation flags.

TakuzuCoord

A zero-based (row, col) coordinate used in model.violations to identify cells that are breaking a rule.

// Renders each grid cell — implement to customise cell appearance
public protocol TakuzuCellProtocol: View {
    init(
        row: Int,
        column: Int,
        value: Bool?,       // nil = empty, true = "1", false = "0"
        isFixed: Bool,      // given cell that cannot be edited
        isViolation: Bool   // cell is part of a rule violation
    )
}

Hint

Wire up a hint button by passing a Binding<Int> to .hint(trigger:). Each increment activates hint mode — all empty editable cells are highlighted with an orange tint. Tap any highlighted cell to fill it with the correct solution value. Hint mode ends after one tap.

// Hold an integer counter in @State — increment it to request a hint.
@State private var hintCount = 0

TakuzuGameView(model: $model)
    .hint(trigger: $hintCount)

// Each increment activates hint mode — all empty editable cells are highlighted.
// Tap any highlighted cell to fill it with the correct solution value.
// Hint mode ends after one tap. Has no effect when the board is already fully filled.
Button("Hint") { hintCount += 1 }

// Pairs naturally with onGameComplete — if the hint fills the last cell,
// the completion callback fires as normal.
TakuzuGameView(model: $model)
    .hint(trigger: $hintCount)
    .onGameComplete { isCorrect in
        print(isCorrect ? "Solved!" : "Board filled — incorrect.")
    }

Completion callbacks

Get notified when the player fills the last cell. The callback fires once, regardless of whether the solution is correct. Read isCorrect from the argument to distinguish a win from a filled-but-wrong board.

TakuzuGameView(model: $model)
    .grid(spacing: 4, cell: TakuzuCell.self)
    .onCellTap { row, col, value in
        print("Tapped (\(row),\(col))\(value.map { $0 ? \"1\" : \"0\" } ?? \"empty\")")
    }
    .onGameComplete { isCorrect in
        print(isCorrect ? "Puzzle solved!" : "Board filled — try again.")
    }

Explore the examples

Focus

Explore a fully working iOS sample project you can run immediately.