
How to Play Tango¶
Figure 1:Example of a Tango minigame board. Source: LinkedIn Tango
The Tango’s board is a 6 6 game board with some squares already filled by moons 🌙 and suns ☀️ symbols, and which can have some pairs of squares with an = sign or × sign in-between.
- Objective
- Fill all the squares on the board with moons 🌙 and suns ☀️.
- Rules
- The number of moons and suns in each row and column must be the same;
- There cannot be more than 2 moons or 2 suns in a row, either in a row or column;
- Squares separated by the
=sign must contain the same symbol; - Squares separated by the
×sign must contain opposite symbols.
Problem Modeling¶
In order to model our LOP, it will be necessary to define the following elements:
Ranges
Sets
Decision variables
Parameters
Objective function
Constraints
Ranges¶
- The row range, where is the total number of rows in the grid.
- The column range, where is the total number of columns in the grid.
Sets¶
In addition to the row and column ranges, it will be necessary to consider the sets of pairs of squares with = and × signs, plus the set of squares already filled with a sun or moon symbol. Therefore, there will be:
- Set of all board squares , which is simply the Cartesian product of the ranges and .
- Set of pairs of squares separated by
=. - Set of pairs of squares separated by
×. - Set of squares with an already filled symbol.
Decision Variables¶
The decision variables will be binary, since Tango only uses two symbols. Therefore, the LOP is actually a BLOP:
- , if the square in row and column will be filled with a 🌙;
- , if the same square will be filled with a ☀️.
Parameters¶
In Tango, there may be some cells that already come with a predefined symbol: sun or moon. As the set of parameters in our model, there will be:
- , if the square in row and column is already filled with a 🌙;
- , if the same square is already filled with a ☀️.
Objective Function¶
Again, our objective is not to optimize a function, but rather to find a solution that fulfills all the rules of the game. Therefore, the Tango BLOP is not an optimization problem, but a feasibility problem. Thus, the objective function will be to minimize (or maximize, it doesn’t matter) an arbitrary constant .
Constraints¶
With the previous elements well defined, we can translate the rules of the game to our BLOP as follows:
- Binarity Constraints
- First, we must define that all decision variables in our problem are binary variables, that is, they only accept 0 and 1 as the only possible values.
- Equal-Moons-Suns-Per-Row Constraints
- Since the number of suns and moons for each row must be equal, the sum of belonging to a row must equal the half of total columns, therefore 3. Since there are 6 rows, the model will have 6 of these constraints.
- Equal-Moons-Suns-Per-Column Constraints
- The same logic applies to the columns. Therefore, there are 6 more constraints.
- No-Three-Consecutive-Moons-Per-Row Constraints
- Since the 3 moons in a row cannot be next to each other, it means that the sum of three consecutive in a row must be less than or equal to 2.
- No-Three-Consecutive-Moons-Per-Column Constraints
- And the same logic applies to the columns.
- No-Three-Consecutive-Suns-Per-Row Constraints
- Similarly, since there cannot be 3 consecutive suns in a row, the sum of 3 subsequent in a row i must be greater than or equal to 1.
- No-Three-Consecutive-Suns-Per-Column Constraints
- And the same logic applies to the columns.
- Already-Filled-squares Constraints
- For each square that already has a pre-established figure, it is necessary to impose that is equal to 1 or 0, depending on whether the figure is a moon or a sun, respectively.
- Matching-Pairs Constraints
- For each pair of adjacent cells that contains the
=sign between them, it is necessary to impose the constraint that the values of that pair are equal, which is the same as saying that the difference between the values of that pair must be equal to 0. - Opposite-Pairs Constraints
- Finally, for each pair of adjacent cells that contains the sign
×between them, it is necessary to impose the constraint that the values of this pair are different, which is the same as saying that the sum of the values of this pair must be equal to 1.
Abstract Model¶
Up to this point, the formulas created have been based on the premise that every Tango minigame will have 6 rows and 6 columns, which is indeed the case. However, in order to create a more generalized abstract model, we can admit that this will not always be a certainty, and therefore there is a possibility that the number of rows and columns will vary from game to game. Due to the restriction that the number of moons and suns for each row and column must be equal, it must be assumed that the total number of rows and columns are even numbers, that is, and .
Thus, having raised these points, the abstract model of the Tango minigame is formulated as follows:
Concrete Model¶
The concrete model was built based on the Tango minigame No. 151

Figure 2:Tango No. 151, March 7th, 2025. Source: LinkedIn Tango
S.t.:
- Equal-Moons-Suns-Per-Row Constraints
- (Row 1)
- (Row 2)
- (Row 3)
- (Row 4)
- (Row 5)
- (Row 6)
- Equal-Moons-Suns-Per-Column Constraints
- (Column 1)
- (Column 2)
- (Column 3)
- (Column 4)
- (Column 5)
- (Column 6)
- No-Three-Consecutive-Moons-Per-Row Constraints
- (Row 1)
- (Row 1)
- (Row 1)
- (Row 1)
- (Row 2)
- (Row 2)
- (Row 2)
- (Row 2)
- (Row 3)
- (Row 3)
- (Row 3)
- (Row 3)
- (Row 4)
- (Row 4)
- (Row 4)
- (Row 4)
- (Row 5)
- (Row 5)
- (Row 5)
- (Row 5)
- (Row 6)
- (Row 6)
- (Row 6)
- (Row 6)
- No-Three-Consecutive-Moons-Per-Column Constraints
- (Column 1)
- (Column 1)
- (Column 1)
- (Column 1)
- (Column 2)
- (Column 2)
- (Column 2)
- (Column 2)
- (Column 3)
- (Column 3)
- (Column 3)
- (Column 3)
- (Column 4)
- (Column 4)
- (Column 4)
- (Column 4)
- (Column 5)
- (Column 5)
- (Column 5)
- (Column 5)
- (Column 6)
- (Column 6)
- (Column 6)
- (Column 6)
- No-Three-Consecutive-Suns-Per-Row Constraints
- (Row 1)
- (Row 1)
- (Row 1)
- (Row 1)
- (Row 2)
- (Row 2)
- (Row 2)
- (Row 2)
- (Row 3)
- (Row 3)
- (Row 3)
- (Row 3)
- (Row 4)
- (Row 4)
- (Row 4)
- (Row 4)
- (Row 5)
- (Row 5)
- (Row 5)
- (Row 5)
- (Row 6)
- (Row 6)
- (Row 6)
- (Row 6)
- No-Three-Consecutive-Suns-Per-Column Constraints
- (Column 1)
- (Column 1)
- (Column 1)
- (Column 1)
- (Column 2)
- (Column 2)
- (Column 2)
- (Column 2)
- (Column 3)
- (Column 3)
- (Column 3)
- (Column 3)
- (Column 4)
- (Column 4)
- (Column 4)
- (Column 4)
- (Column 5)
- (Column 5)
- (Column 5)
- (Column 5)
- (Column 6)
- (Column 6)
- (Column 6)
- (Column 6)
- Matching-Pairs Constraints
- Opposite-Pairs Constraints
- Already-Filled-squares Constraints
- Binarity Constraints
Solving Tango¶
In order to solve the presented game, the linkedin-games library counts on Tango class, that implements the Tango game and its constraints, as well as methods to solve it and visualize the solution.
To instantiate the game, it is necessary to provide three sets of inputs regarding the game in question:
filled_squares- A dictionary of
(row, column): 0 | 1representing the pre-filled squares. matching_pairs- A tuple of pairs of squares separated by the
=symbol. opposite_pairs- A tuple of pairs of squares separated by the
×symbol.
from linkedin_games import Tango
# Solving Tango No. 151
# Matching pairs of squares (=), each element is ((i,j),(r,s))
matching_pairs = [
((2,3), (2,4)), ((2,1), (3,1)), ((2,3), (3,3)),
((2,6), (3,6)), ((4,1), (4,2)), ((6,3), (6,4)),
]
# Opposite pairs of squares (x)
opposite_pairs = [((2,4), (3,4)), ((3,1), (4,1)), ((3,3), (3,4)), ((3,6), (4,6)), ((4,5), (4,6))]
# Already filled squares: (i,j) -> kij
filled_squares = {(1, 2): 1, (1, 5): 1, (5, 2): 0, (5, 5): 1}
tango = Tango(filled_squares, matching_pairs, opposite_pairs)The Tango class features the model attribute, which implements the TangoModel object to structure the Linear Optimization logic behind the Tango’s game.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81import pyomo.environ as pyo class TangoModel(pyo.ConcreteModel): """The Linear Optimization Model for Tango game.""" def __init__(self, grid_dims: tuple[int, int], filled_squares: dict[tuple[int, int], int], matching_pairs: list[tuple[tuple[int, int], tuple[int, int]]] | None = None, opposite_pairs: list[tuple[tuple[int, int], tuple[int, int]]] | None = None ) -> None: """ Args: grid_dims: Grid dimensions as a `(row, column)` tuple. filled_squares: Starting filled squares as a dictionary of `(row, column): 0 | 1` items. matching_pairs: Pairs of matching squares (separated by a `=` sign) as a set of `((row1, column1), (row2, column2))` edges. opposite_pairs: Pairs of opposite squares (separated by a `×` sign) as a set of `((row1, column1), (row2, column2))` edges. """ super().__init__() # BOARD DIMENSIONS m, n = grid_dims self.m = pyo.Param(initialize=m, domain=pyo.PositiveIntegers) self.n = pyo.Param(initialize=n, domain=pyo.PositiveIntegers) # RANGE SETS I = self.I = pyo.RangeSet(n) # Rows J = self.J = pyo.RangeSet(m) # Columns # COMPOSITE SETS S = self.S = pyo.Set(initialize=lambda model: [(i,j) for i in I for j in J]) # Grid Squares K = self.K = pyo.Set(initialize=filled_squares.keys(), dimen=2) M = self.M = pyo.Set(initialize=matching_pairs) O = self.O = pyo.Set(initialize=opposite_pairs) # DECISION VARIABLES x = self.x = pyo.Var(S, domain=pyo.Binary, initialize=0) # PARAMETERS m = self.m # Total number of rows n = self.n # Total number of columns k = self.k = pyo.Param(K, initialize=filled_squares, domain=pyo.Binary) # Filled values # OBJECTIVE FUNCTION self.obj = pyo.Objective(expr=0) # feasibility problem # CONSTRAINTS self.equal_moons_suns_per_row_constraints = pyo.Constraint( I, rule=lambda model, i: pyo.quicksum(x[i,j] for j in J) == n / 2 ) self.equal_moons_suns_per_column_constraints = pyo.Constraint( J, rule=lambda model, j: pyo.quicksum(x[i,j] for i in I) == m / 2 ) self.no_three_consecutive_moons_per_row_constraints = pyo.Constraint( I, pyo.RangeSet(n-2), rule=lambda model, i, j: x[i,j] + x[i, j+1] + x[i, j+2] <= 2 ) self.no_three_consecutive_suns_per_row_constraints = pyo.Constraint( I, pyo.RangeSet(n-2), rule=lambda model, i, j: x[i,j] + x[i, j+1] + x[i, j+2] >= 1 ) self.no_three_consecutive_moons_per_column_constraints = pyo.Constraint( pyo.RangeSet(m-2), J, rule=lambda model, i, j: x[i,j] + x[i+1, j] + x[i+2, j] <= 2 ) self.no_three_consecutive_suns_per_column_constraints = pyo.Constraint( pyo.RangeSet(m-2), J, rule=lambda model, i, j: x[i,j] + x[i+1, j] + x[i+2, j] >= 1 ) self.already_filled_squares_constraints = pyo.Constraint( K, rule=lambda model, i, j: x[i,j] == k[i,j] ) self.matching_pairs_constraints = pyo.Constraint( M, rule=lambda model, i, j, r, s: x[i,j] - x[r, s] == 0 ) self.opposite_pairs_constraints = pyo.Constraint( O, rule=lambda model, i, j, r, s: x[i,j] + x[r, s] == 1 )
Program 1:Creating the model with Pyomo components
After the model is built, the public method solve() calls the restricted method _set_solution() to save the solution to _board property, which is a NetworkX Graph representing the game’s board.
1 2 3 4 5 6 7 8 9 10 11 12invalid_items = [ square for pair in values for square in pair for coord in square if not isinstance(coord, int) or coord < 1 ] if invalid_items: msg = f"Coordinates must be positive integers. Invalid squares: {invalid_items!r}." raise ValueError(msg) invalid_items = [pair for pair in values if self._taxicab_distance(*pair) != 1] if invalid_items:
Program 2:Saving the game’s solution to _board attribute.
With the solution obtained, the method show() can be called to plot the Tango’s board.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56msg = f"Squares in a pair must be consecutive ones. Invalid pairs: {invalid_items!r}." raise ValueError(msg) self.__opposite_pairs = list(set(values)) def _set_solution(self, verbose:bool=False) -> None: S= self.model.S x = self.model.x nx.set_node_attributes( self._grid, name="value", values={(i-1, j-1): round(pyo.value(x[i,j])) for i, j in S} ) if verbose: print("Tango solution:") pprint(self.grid_squares) def show(self) -> None: """Show Tango's grid.""" O = self.model.O M = self.model.M plt.figure(figsize=(3, 3)) pos = {(i, j): (j, -i) for i, j in self.grid.nodes()} nx.draw( self.grid, pos= pos, arrows=False, with_labels= True, labels= nx.get_node_attributes(self.grid, "value"), node_size= 1100, node_color= [ "#EEEAE7" if (i+1,j+1) in self.filled_squares else "white" for (i, j) in self.grid.nodes() ], node_shape="s", edgecolors="#EEEAE7", linewidths=1, width=0, edgelist= [((i-1, j-1), (r-1, s-1)) for i,j,r,s in O] + [((i-1, j-1), (r-1, s-1)) for i,j,r,s in M] ) nx.draw_networkx_edge_labels( self._grid, pos= pos, edge_labels= {((i-1, j-1), (r-1, s-1)): "×" for i,j,r,s in O} | {((i-1, j-1), (r-1, s-1)): "=" for i,j,r,s in M}, font_color="#887658" ) plt.show()
Program 3:Implementation of show() function.
So to solve the game and display its results, just call the public methods solve() and show(), at this order.
tango.solve()
tango.show()
As expected, the image result matches the solution of Tango No. 151

Figure 3:Solution of Tango No. 151, March 7th, 2025. Source: LinkedIn Tango