
Continuing with the challenge of solving LinkedIn games by using LO, just like I did with Queens and Tango, the next one I’ve chosen is Zip, which turned out to be the most challenging so far.
How to Play Zip¶
Figure 1:Example of a Zip game board (Source: LinkedIn)
The Zip game board consists of a grid with some numbered squares. Both the grid’s dimensions and the total of numbered squares can vary from game to game. The objective is to trace a path that runs through all the squares on the board, moving through numbered squares in ascending order, starting from square number 1 to the one with the highest number.
Problem Modeling¶
The best way I thought of to solve the game is to consider the squares as nodes in a directed graph (digraph), where each node has a set of pairs of edges that arrive at and depart from the nodes, as shown in the following figure.

Figure 2:Zip game modeled on a directed graph. Source: created by the author himself
Considering this model, enacted the Abstract LO Model by defining its following components:
Ranges
Sets
Decision Variables
Parameter
Objective Function
Constraints
Ranges¶
To properly formulate the LO model, we will start from the premise that the squares on the board will be the Cartesian product of the ranges of rows and of columns present on the board. In addition, it is necessary to have defined an interval for the set of numbered squares.
- The row range, where is the total number of rows (in this case, )
- The column range, where is the total number of columns (in this case, as well).
Sets¶
This time, the definition of sets will be more than fundamental to reducing the complexity of the model, as we will avoid having to deal with numerous exceptions. From the ranges determined previously, we can define the set of vertices () and edges () existing in the network logic. In addition to these, it is also necessary to consider the subsets of numbered vertices () and blocked edges (), since, in some instances of Zip game, there may be walls between two squares that prevent the path from being traced between them.
- Set of all squares (vertices). There must be at least 2 vertices in the grid, as it is only possible to trace a path with an initial and a final square. Therefore,
- Set of all edges present in the graph. Its definition is a bit more complex as it is necessary to clarify that, for each vertex, there will only be edges between neighbors immediately to the left, immediately to the right, immediately above, and immediately below it, if they exist.
- Set of numbered squares, which will consist of a set of all vertices indexed by a number on board game.
- Set of blocked edges (walls) that may appear on the grid.
Decision Variables¶
By using the network logic already presented, it is possible to define the edges as binary decision variables , which again makes our LOP a BLOP. In addition to , variables will also be considered, representing the absolute ordinal position of the square within the path (these variables will be better explained later).
- , if the edge that starts from square and arrives at square is part of the path
- , otherwise
- Absolute ordinal positions of the squares within the path.
Parameters¶
- For each numbered board square in set, it’ll be defined a integer that matches its number displayed on game board.
Objective Function¶
Once again, just as in the Queens and Tango games, our problem does not have a function to be optimized, since we only want to find a solution that fulfills all the rules of the game. Therefore, Zip’s BLOP is a feasibility problem, and the objective function of the model consists of maximizing (or minimizing) an arbitrary constant.
Constraints¶
- Decision variables domain constraints
First, let’s not forget to define our variables as binary and the variables as non-negative real numbers. To simplify, whenever possible, we can replace the notation with and with just , since the sequence always refers to some vertex belonging to set.
- Blocked edges constraints
If there is a wall between a pair of squares, then the sum of the edges connecting those ones must be equal to 0.
- Continuity constraints
Since it is only possible to enter and exit a node once (except for the initial and destination nodes), then we will have the following:
- Outgoing-Edges Constraints
- The sum of the edges emanating from a node must equal 1 (except for the destination square).
- Incoming-Edges Constraints
- The sum of the edges arriving at a node must equal 1 (except for the initial square).
- Arrival Square Constraint
- The sum of the edges emanating from the destination node must be 0.
- Starting Square Constraint
- The sum of the edges arriving at the initial node must be 0.
- Subroute Elimination Constraints
The advantage of the MTZ formulation is that the number of constraints to be included to guarantee subroute elimination is equal to , that is, the number of edges in the graph. Thus, the number of constraints grows linearly as a function of the number of edges, instead of exponentially as a function of the number of vertices. For it to work, it requires the addition of more decision variables (the previously mentioned ), which represent the absolute ordinal position of square within the path.
- Position of Starting Square Constraint
- First, the position of the starting square must be equal to 1, since it will always be the first position on the path.
- MTZ Constraints
- Then, the positions of the remaining squares on the board should be given as follows.
- Positon of Arrival Square Constraint
- Although not necessary, one can restrict the position of the arrival square to be equal to the number of squares on the board, just to make the range from 1 to 36.
- Order of Precedence Constraints
- In order for the path to traverse the numbered squares in ascending numerical order, it is necessary to impose the constraint that the position of a numbered square must be at least equal to the position subsequent to the last numbered square.
Abstract Model¶
With all the formulas elaborated, we have the complete abstract model for the Zip minigame.
Concrete Model¶
The game selected to be solved is the Zip No. 166, published on LinkedIn on August 29th, 2025

Figure 3:Zip No. 166, August 29th, 2025 (Source: LinkedIn)
Based on this specific case, we have the following concrete model:
S.t.:
- Outgoing edges constraints
- (Square (1,1))
- (Square (1,2))
- (Square (1,3))
- (Square (1,4))
- (Square (1,5))
- (Square (1,6))
- (Square (2,1))
- (Square (2,2))
- (Square (2,3))
- (Square (2,4))
- (Square (2,5))
- (Square (2,6))
- (Square (3,1))
- (Square (3,2))
- (Square (3,3))
- (Square (3,4))
- (Square (3,5))
- (Square (3,6))
- (Square (4,1))
- (Square (4,2))
- (Square (4,3))
- (Square (4,4))
- (Square (4,5))
- (Square (4,6))
- (Square (5,1))
- (Square (5,2))
- (Square (5,3))
- (Square (5,4))
- (Square (5,5))
- (Square (5,6))
- (Square (6,1))
- (Square (6,2))
- (Square (6,3))
- (Square (6,4))
- (Square (6,5))
- (Square (6,6))
- Incoming edges cosntraints
- (Square (1,1))
- (Square (1,2))
- (Square (1,3))
- (Square (1,4))
- (Square (1,5))
- (Square (1,6))
- (Square (2,1))
- (Square (2,2))
- (Square (2,3))
- (Square (2,4))
- (Square (2,5))
- (Square (2,6))
- (Square (3,1))
- (Square (3,2))
- (Square (3,3))
- (Square (3,4))
- (Square (3,5))
- (Square (3,6))
- (Square (4,1))
- (Square (4,2))
- (Square (4,3))
- (Square (4,4))
- (Square (4,5))
- (Square (4,6))
- (Square (5,1))
- (Square (5,2))
- (Square (5,3))
- (Square (5,4))
- (Square (5,5))
- (Square (5,6))
- (Square (6,1))
- (Square (6,2))
- (Square (6,3))
- (Square (6,4))
- (Square (6,5))
- (Square (6,6))
- Ordinal position constraints
- (Square 1)
- (Square 2)
- (Square 3)
- (Square 4)
- (Square 5)
- (Square 6)
- (Square 7)
- (Square 8)
- (Square 9)
- (Square 10)
- (Square 11)
- (Square 12)
- (Square 13)
- (Square 14)
- (Square 15)
- (Square 16)
- (Square 16)
- Subroute elimination - MZT - constraints
- Binarity constraints for edge variables
- Non-negativity constraints for positional variables
- (Square (1,1))
- (Square (1,2))
- (Square (1,3))
- (Square (1,4))
- (Square (1,5))
- (Square (1,6))
- (Square (2,1))
- (Square (2,2))
- (Square (2,3))
- (Square (2,4))
- (Square (2,5))
- (Square (2,6))
- (Square (3,1))
- (Square (3,2))
- (Square (3,3))
- (Square (3,4))
- (Square (3,5))
- (Square (3,6))
- (Square (4,1))
- (Square (4,2))
- (Square (4,3))
- (Square (4,4))
- (Square (4,5))
- (Square (4,6))
- (Square (5,1))
- (Square (5,2))
- (Square (5,3))
- (Square (5,4))
- (Square (5,5))
- (Square (5,6))
- (Square (6,1))
- (Square (6,2))
- (Square (6,3))
- (Square (6,4))
- (Square (6,5))
- (Square (6,6))
Solving with Pyomo and NetworkX libraries¶
First of all, let’s import all the required libraries
import matplotlib.pyplot as plt
import networkx as nx
import pyomo.environ as pyoSolving Zip¶
In order to solve a Zip minigame, the solve_zip() function was created, as it follows:
def solve_zip(n:int, m:int, numbered_squares:dict|list, walls:list=None) -> None:
# Input validation
if n < 1 or m < 1:
raise ValueError("The grid dimensions must be equal or greater then 1")
if not isinstance(n, int) or not isinstance(m, int):
raise ValueError("The grid dimensions must be integers")
if len(numbered_squares) > n * m:
raise ValueError("The number of numbered squares exceeds the amount of vertices in the grid.")
if len(numbered_squares) < 2 or n * m < 2:
raise ValueError("The number of numbered squares exceeds the amount of vertices in the grid.")
if isinstance(numbered_squares, list):
numbered_squares = {key: value for key, value in enumerate(numbered_squares)}
elif isinstance(numbered_squares, dict):
pass
else:
raise ValueError("The numbered squares must be a dictionary or a list.")
model = pyo.ConcreteModel()
# Ranges
I = model.I = pyo.RangeSet(n)
J = model.J = pyo.RangeSet(m)
# Sets
V = model.Vertices = pyo.Set(initialize=lambda model: [(i, j) for i in I for j in J])
E = model.Edges = pyo.Set(initialize=lambda model: [
((i, j), (i+1, j)) for i in I for j in J if i+1 in I] + [
((i, j), (i-1, j)) for i in I for j in J if i-1 in I] + [
((i, j), (i, j+1)) for i in I for j in J if j+1 in J] + [
((i, j), (i, j-1)) for i in I for j in J if j-1 in J]
)
K = model.NumberedVertices = pyo.Set(initialize=numbered_squares.keys(), dimen=2)
W = model.Walls = pyo.Set(initialize=walls)
# Decision variables
x = model.x = pyo.Var(E, within=pyo.Binary, initialize=0)
u = model.u = pyo.Var(V, within=pyo.NonNegativeReals)
# Parameters
k = model.k = pyo.Param(V, initialize=numbered_squares, default=0, within=pyo.NonNegativeIntegers)
# Objective function
model.obj = pyo.Objective(expr=0)
# Constraints
model.outgoing_edges_constraints = pyo.Constraint(
V,
rule=lambda model, i, j: \
sum(x[(i,j),w] for w in V if ((i,j),w) in E) == 1 if k[i,j] != len(K) else \
sum(x[(i,j),w] for w in V if ((i,j),w) in E) == 0
)
model.incoming_edges_constraints = pyo.Constraint(
V,
rule=lambda model, i, j: \
sum(x[v,(i,j)] for v in V if (v,(i,j)) in E) == 1 if k[i,j] != 1 else \
sum(x[v,(i,j)] for v in V if (v,(i,j)) in E) == 0
)
if W is not None:
model.wall_constraints = pyo.Constraint(
W,
rule=lambda model, i, j, r, s: x[i,j,r,s] + x[r,s,i,j] == 0
)
M = n * m
model.subroute_elimination_constraints = pyo.Constraint(
E,
rule=lambda model, i, j, r, s: u[r,s] >= u[i,j] + 1 - M*(1 - x[i,j,r,s])
)
model.first_square_position_constraint = pyo.Constraint(
K,
rule= lambda model, i, j: u[i,j] == 1 if k[i,j] == 1 else pyo.Constraint.Skip
)
model.ordinal_position_constraints = pyo.Constraint(
K, K,
rule= lambda model, i, j, r, s: u[i,j] >= u[r,s] + 1 if k[i,j] == k[r,s] + 1 else pyo.Constraint.Skip
)
model.last_square_position_constraint = pyo.Constraint(
K,
rule= lambda model, i, j: u[i,j] == M if k[i,j] == len(K) else pyo.Constraint.Skip
)
# Optmization result
solver = pyo.SolverFactory("gurobi")
results = solver.solve(model)
if str(results.Solver.status) == "ok":
G = nx.grid_2d_graph(n,m).to_directed()
plt.figure(figsize=(3.4, 3.4))
nx.draw(
G,
pos= {(i,j): (j,-i) for i, j in G.nodes()},
with_labels= True,
labels= {(i-1, j-1): k[i,j] for (i,j) in K},
arrows=False,
node_shape="o", # round nodes
node_size= 1000,
node_color= ["white" if (i+1,j+1) in K else "#EE5F12" for (i,j) in G.nodes()],
edge_color= "#EE5F12",
edgecolors='#EE5F12',
linewidths= 3,
width= 35,
edgelist= [((i-1, j-1), (r-1, s-1)) for i, j, r, s in model.Edges if pyo.value(model.x[i,j,r,s]) == 1]
)
plt.show()
else:
print("No valid solution was found!")With the function created, we can pass on the necessary information to solve the game.
numbered_squares = {
(1,1): 9,
(1,2): 10,
(1,3): 11,
(2,1): 8,
(2,4): 13,
(3,1): 7,
(3,4): 14,
(3,5): 12,
(4,2): 6,
(4,3): 15,
(4,6): 16,
(5,3): 5,
(5,6): 3,
(6,4): 4,
(6,5): 1,
(6,6): 2
}
solve_zip(6,6,numbered_squares)
As expected, the displayed path satisfies all the rules of Zip No. 166.

References¶
Zip. LinkedIn. Available at https://
www .linkedin .com /games /zip/. Accessed on August 29th, 2025. Miller-Tucker-Zemlin Formulation. AIMMS. December 13th, 2024. Available at https://
how -to .aimms .com /Articles /332 /332 -Miller -Tucker -Zemlin -formulation .html. Accessed on September 2nd, 2025.