The blog of a juvenile Geekus biologicus

Tent Game in Answer Set Programming

The Tent game is a logic puzzle on a square or rectangular grid. Each grid cell can have a tree, a tent or stay empty. <Nourry (2024)>

The following rules must be respected:

The aim of this article is to propose a implementation of the Tent game in Answer Set Programming.

Tent game in Answer Set Programming

Let’s begin with some constraint on the game grid.

% The size of the grid:
#const width=6.
#const height=6.

% Some emoji
#const tent="⛺".
#const tree="🌳".
#const herb="🌿".

% A cell on the grid at row R and column C is represented by the predicate cell(R, C, V) and contain the value V.

% A cell cannot contain more than one value
V1 = V2 :- cell(R, C, V1), cell(R, C, V2).

% We check the border of the grid. We start indicing by 1.
R > 0 , R <= width , C > 0 , R <= height :- cell(R, C, _).

% Each empty cell can remain empty, or accept a tent
position(R, C) :- R = 1..width , C = 1..height.
occupied(R, C) :- cell(R, C, herb) , position(R, C).
occupied(R, C) :- cell(R, C, tree) , position(R, C).
empty(R, C) :- not occupied(R, C) , position(R, C).

symbol(tent;"").
1 { cell(R, C, V) : symbol(V) } 1 :- empty(R, C).

How to encode the tent neighborhood constraint?

An approach could be to use an ASP rule for each of the four von Neumann neighbors.

neighbor(R + 1, C) :- cell(R, C).
neighbor(R - 1, C) :- cell(R, C).
neighbor(R, C + 1) :- cell(R, C).
neighbor(R, C - 1) :- cell(R, C).

cell(1, 1).
clingo version 5.8.0
Reading from /tmp/babel-MfTgfu/clingo-KAEfOE
Solving...
Answer: 1 (Time: 0.000s)
cell(1,1) neighbor(1,0) neighbor(1,2) neighbor(0,1) neighbor(2,1)
SATISFIABLE

Models       : 1
Calls        : 1
Time         : 0.000s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time     : 0.000s

However, this way, each atom neighbor/2 are considered true in the answer set, though in our case, we muse choose a tree neighbor cell to put a tent on it. We have to make a choice. In ASP, we can write it as:

% We first define all position delta for von Neumann neighborhood
neumann((-1, 0); (0, -1); (1, 0); (0, 1)).

% Then, we chose one neighbor
1 = { neighbor(R + R_delta, C + C_delta) : neumann((R_delta, C_delta)) } :- cell(R, C).

cell(1, 1).

#show neighbor/2.
clingo version 5.8.0
Reading from /tmp/babel-MfTgfu/clingo-UY6vfe
Solving...
Answer: 1 (Time: 0.000s)
neighbor(1,0)
Answer: 2 (Time: 0.000s)
neighbor(2,1)
Answer: 3 (Time: 0.000s)
neighbor(1,2)
Answer: 4 (Time: 0.000s)
neighbor(0,1)
SATISFIABLE

Models       : 4
Calls        : 1
Time         : 0.000s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time     : 0.000s

We can now implement the adjacency rule for the tents and the trees:


% von Neumann neighborhood
neumann((-1, 0); (0, -1); (1, 0); (0, 1)).

% A neighboring cell of a tent
1 = { neighbor_tent_tree(R_tent, C_tent, R_tent + R_delta, C_tent + C_delta): neumann((R_delta, C_delta)) } :- cell(R_tent, C_tent, tent).

% We check it is indeed a tree
:- neighbor_tent_tree(_, _, R_tree, C_tree) , not cell(R_tree, C_tree, tree).

A tree is attached to a single tent

% Similar to the rule checking the uniqueness of the cell value,
% We check the uniqueness of the tree neighbor tent, if any.
R_cell1 = R_cell2 , C_cell1 = C_cell2 :- neighbor_tent_tree(R_cell1, C_cell1, R_tree, C_tree) , neighbor_tent_tree(R_cell2, C_cell2, R_tree, C_tree).

Two tent cannot be located one next to the other

We can use the same principle as before, this time using the Moore neighborhood.

moore((-1,-1); (-1,0); (-1,1); (0,-1); (0,1); (1,-1); (1,0); (1,1)).
clingo version 5.8.0
Reading from /tmp/babel-MfTgfu/clingo-7ekHqS
Solving...
Answer: 1 (Time: 0.000s)
moore((-1,-1)) moore((-1,0)) moore((-1,1)) moore((0,-1)) moore((0,1)) moore((1,-1)) moore((1,0)) moore((1,1))
SATISFIABLE

Models       : 1
Calls        : 1
Time         : 0.000s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time     : 0.000s

:- moore((R_delta, C_delta))
        , cell(R, C, tent)
        , cell(R + R_delta, C + C_delta, tent).

Constraint on the number of tent per row and column

% We introduce two new predicates:
% tent_column(C, N). that counts the number of tent in column C.
% and tent_row(R, N). for rows.

% We begin by counting the number of tent per row and column
row_count(R, N) :- N = { cell(R, _, tent) } , R = 1..width.
column_count(C, N) :- N = { cell(_, C, tent) } , C = 1..height.

% Then we ensure the count respect the input
N1 = N2 :- row_count(R, N1) , tent_row(R, N2).
N1 = N2 :- column_count(C, N1) , tent_column(C, N2).

All rules together

% The size of the grid:
#const width=6.
#const height=6.

% Some emoji
#const tent="⛺".
#const tree="🌳".
#const herb="🌿".

% A cell on the grid at row R and column C is represented by the predicate cell(R, C, V) and contain the value V.

% A cell cannot contain more than one value
V1 = V2 :- cell(R, C, V1), cell(R, C, V2).

% We check the border of the grid. We start indicing by 1.
R > 0 , R <= width , C > 0 , R <= height :- cell(R, C, _).

% Each empty cell can remain empty, or accept a tent
position(R, C) :- R = 1..width , C = 1..height.
occupied(R, C) :- cell(R, C, herb) , position(R, C).
occupied(R, C) :- cell(R, C, tree) , position(R, C).
empty(R, C) :- not occupied(R, C) , position(R, C).

symbol(tent;"").
1 { cell(R, C, V) : symbol(V) } 1 :- empty(R, C).

% Similar to the rule checking the uniqueness of the cell value,
% We check the uniqueness of the tree neighbor tent, if any.
R_cell1 = R_cell2 , C_cell1 = C_cell2 :- neighbor_tent_tree(R_cell1, C_cell1, R_tree, C_tree) , neighbor_tent_tree(R_cell2, C_cell2, R_tree, C_tree).
moore((-1,-1); (-1,0); (-1,1); (0,-1); (0,1); (1,-1); (1,0); (1,1)).
:- moore((R_delta, C_delta))
        , cell(R, C, tent)
        , cell(R + R_delta, C + C_delta, tent).

% We introduce two new predicates:
% tent_column(C, N). that counts the number of tent in column C.
% and tent_row(R, N). for rows.

% We begin by counting the number of tent per row and column
row_count(R, N) :- N = { cell(R, _, tent) } , R = 1..width.
column_count(C, N) :- N = { cell(_, C, tent) } , C = 1..height.

% Then we ensure the count respect the input
N1 = N2 :- row_count(R, N1) , tent_row(R, N2).
N1 = N2 :- column_count(C, N1) , tent_column(C, N2).

An example


% Problem input

cell(R, C, tree) :- tree(R, C).
tree(1, 4).
tree(2, 2).
tree(2, 6).
tree(3, 3).
tree(3, 5).
tree(4, 4).
tree(5, 2).
tree(6, 5).

% Columns
tent_column(1, 1).
tent_column(2, 2).
tent_column(3, 0).
tent_column(4, 2).
tent_column(5, 1).
tent_column(6, 2).

% Rows
tent_row(1, 2).
tent_row(2, 0).
tent_row(3, 3).
tent_row(4, 0).
tent_row(5, 2).
tent_row(6, 1).
clingo version 5.8.0
Reading from /tmp/babel-MfTgfu/clingo-6fCwzE
Solving...
Answer: 1 (Time: 0.003s)
cell(1,4,"🌳") cell(2,2,"🌳") cell(2,6,"🌳") cell(3,3,"🌳") cell(3,5,"🌳") cell(4,4,"🌳") cell(5,2,"🌳") cell(6,5,"🌳") position(1,1) position(1,2) position(1,3) position(1,4) position(1,5) position(1,6) position(2,1) position(2,2) position(2,3) position(2,4) position(2,5) position(2,6) position(3,1) position(3,2) position(3,3) position(3,4) position(3,5) position(3,6) position(4,1) position(4,2) position(4,3) position(4,4) position(4,5) position(4,6) position(5,1) position(5,2) position(5,3) position(5,4) position(5,5) position(5,6) position(6,1) position(6,2) position(6,3) position(6,4) position(6,5) position(6,6) occupied(1,4) occupied(2,2) occupied(2,6) occupied(3,3) occupied(3,5) occupied(4,4) occupied(5,2) occupied(6,5) symbol("⛺") symbol("") moore((-1,-1)) moore((-1,0)) moore((-1,1)) moore((0,-1)) moore((0,1)) moore((1,-1)) moore((1,0)) moore((1,1)) tent_row(1,2) tent_row(2,0) tent_row(3,3) tent_row(4,0) tent_row(5,2) tent_row(6,1) tent_column(1,1) tent_column(2,2) tent_column(3,0) tent_column(4,2) tent_column(5,1) tent_column(6,2) tree(1,4) tree(2,2) tree(2,6) tree(3,3) tree(3,5) tree(4,4) tree(5,2) tree(6,5) empty(1,1) empty(1,2) empty(1,3) empty(1,5) empty(1,6) empty(2,1) empty(2,3) empty(2,4) empty(2,5) empty(3,1) empty(3,2) empty(3,4) empty(3,6) empty(4,1) empty(4,2) empty(4,3) empty(4,5) empty(4,6) empty(5,1) empty(5,3) empty(5,4) empty(5,5) empty(5,6) empty(6,1) empty(6,2) empty(6,3) empty(6,4) empty(6,6) cell(1,1,"") cell(1,2,"⛺") cell(1,3,"") cell(1,5,"⛺") cell(1,6,"") cell(2,1,"") cell(2,3,"") cell(2,4,"") cell(2,5,"") cell(3,1,"") cell(3,2,"⛺") cell(3,4,"⛺") cell(3,6,"⛺") cell(4,1,"") cell(4,2,"") cell(4,3,"") cell(4,5,"") cell(4,6,"") cell(5,1,"") cell(5,3,"") cell(5,4,"⛺") cell(5,5,"") cell(5,6,"⛺") cell(6,1,"⛺") cell(6,2,"") cell(6,3,"") cell(6,4,"") cell(6,6,"") column_count(1,1) column_count(2,2) column_count(3,0) column_count(4,2) column_count(5,1) column_count(6,2) row_count(1,2) row_count(2,0) row_count(3,3) row_count(4,0) row_count(5,2) row_count(6,1)
SATISFIABLE

Models       : 1+
Calls        : 1
Time         : 0.003s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time     : 0.003s
1 2 0 2 1 2
2 🌳
0 🌳 🌳
3 🌳 🌳
0 🌳
2 🌳
1 🌳

References

“Fiche: Jeux de Réflexion - Tentes.” n.d. éducmat. Accessed December 11, 2025. https://www.educmat.fr/categories/jeux_reflexion/fiches_jeux/camping/index.php.

Nourry, Célian. 2024. “Rapport de Projet de R´esolution du jeu de Tentes.” Rapport de projet. Université Paris 8. https://code.up8.edu/cnourry/jeu-de-tentes.