Download
"""
PyCSP3 Model (see pycsp.org)

Data can come:
 - either directly from a JSON file
 - or from an intermediate parser

Example:
  python SolitaireBattleship.py -data=SolitaireBattleship_sb-12-12-5-0.json
"""

from pycsp3 import *

fleet, hints, rowSums, colSums = data
surfaces = [ship.size * ship.cnt for ship in fleet]
pos, neg = [ship.size for ship in fleet], [-ship.size for ship in fleet]
n, nTypes = len(colSums), len(pos)


def automaton(horizontal):
    q = Automaton.q  # for building state names
    t = [(q(0), 0, q(0)), (q(0), neg if horizontal else pos, "qq"), ("qq", 0, q(0))]
    for i in pos:
        v = i if horizontal else -i
        t.append((q(0), v, q(i, 1)))
        t.extend((q(i, j), v, q(i, j + 1)) for j in range(1, i))
        t.append((q(i, i), 0, q(0)))
    return Automaton(start=q(0), final=q(0), transitions=t)


horizontal_automaton, vertical_automaton = automaton(True), automaton(False)  # automata for ships online

# s[i][j] is 1 iff the cell at row i and col j is occupied by a ship segment
s = VarArray(size=[n + 2, n + 2], dom={0, 1})

# t[i][j] is 0 iff the cell at row i and col j is unoccupied, the type (size) of the ship fragment otherwise,
# when positive, the ship is put horizontally, when negative, the ship is put vertically
t = VarArray(size=[n + 2, n + 2], dom=set(neg) | {0} | set(pos))

# cp[i] is the number of positive ship segments of type i
cp = VarArray(size=nTypes, dom=range(max(surfaces) + 1))

# cn[i] is the number of negative ship segments of type i
cn = VarArray(size=nTypes, dom=lambda i: {0} if fleet[i].size == 1 else range(max(surfaces) + 1))


def hint_ctr(c, i, j):
    if c == 'w':
        return s[i][j] == 0
    if c in {'c', 'l', 'r', 't', 'b'}:
        return [
            s[i][j] == 1,
            s[i - 1][j] == (1 if c == 'b' else 0),
            s[i + 1][j] == (1 if c == 't' else 0),
            s[i][j - 1] == (1 if c == 'r' else 0),
            s[i][j + 1] == (1 if c == 'l' else 0)
        ]
    if c == 'm':
        return [
            s[i][j] == 1,
            t[i][j] not in {-2, -1, 0, 1, 2},
            (s[i - 1][j], s[i + 1][j], s[i][j - 1], s[i][j + 1]) in {(0, 0, 1, 1), (1, 1, 0, 0)}
        ]


satisfy(
    # no ship on borders
    [(s[0][k] == 0, s[-1][k] == 0, s[k][0] == 0, s[k][-1] == 0) for k in range(n + 2)],

    # respecting the specified row tallies
    [Sum(s[i + 1]) == k for i, k in enumerate(rowSums)],

    # respecting the specified column tallies
    [Sum(s[:, j + 1]) == k for j, k in enumerate(colSums)],

    # being careful about cells on diagonals
    [(s[i][j], s[i - 1][j - 1], s[i - 1][j + 1], s[i + 1][j - 1], s[i + 1][j + 1]) in {(0, ANY, ANY, ANY, ANY), (1, 0, 0, 0, 0)}
     for i in range(1, n + 1) for j in range(1, n + 1)],

    # tag(channeling)
    [iff(s[i][j] == 1, t[i][j] != 0) for i in range(n + 2) for j in range(n + 2)],

    # counting the number of occurrences of ship segments of each type
    Cardinality(t[1:n + 1, 1:n + 1], occurrences={pos[i]: cp[i] for i in range(nTypes)} + {neg[i]: cn[i] for i in range(nTypes)}),

    # ensuring the right number of occurrences of ship segments of each type
    [cp[i] + cn[i] == surfaces[i] for i in range(nTypes)],

    # ensuring row connectedness of ship segments
    [t[i + 1] in horizontal_automaton for i in range(n)],

    # ensuring column connectedness of ship segments
    [t[:, j + 1] in vertical_automaton for j in range(n)],

    # tag(clues)
    [hint_ctr(c, i, j) for (c, i, j) in hints] if hints else None
)