## Problem Description and Formulation

The bakery has two main resources: batter and milk, with 1000 grams and 800 grams available, respectively. There are three types of cake-pops: soft, smooth, and crunchy, each requiring different amounts of batter and milk. The profit per cake-pop for each type is also given. The goal is to determine how many of each type of cake-pop the bakery should make to maximize profit.

Let's denote:
- \(S\) as the number of soft cake-pops,
- \(M\) as the number of smooth cake-pops,
- \(C\) as the number of crunchy cake-pops.

The constraints based on the available resources are:
1. Batter: \(20S + 15M + 12C \leq 1000\)
2. Milk: \(10S + 15M + 18C \leq 800\)

Also, \(S \geq 0\), \(M \geq 0\), and \(C \geq 0\) since the number of cake-pops cannot be negative.

The objective function to maximize profit is:
\[ \text{Maximize:} \quad 4S + 6M + 5C \]

## Gurobi Code

```python
import gurobi

def solve_bakery_problem():
    # Create a new model
    model = gurobi.Model()

    # Define variables
    S = model.addVar(lb=0, name="Soft_Cake-pops")
    M = model.addVar(lb=0, name="Smooth_Cake-pops")
    C = model.addVar(lb=0, name="Crunchy_Cake-pops")

    # Objective function: Maximize profit
    model.setObjective(4*S + 6*M + 5*C, gurobi.GRB.MAXIMIZE)

    # Constraints
    model.addConstr(20*S + 15*M + 12*C <= 1000, name="Batter_Constraint")
    model.addConstr(10*S + 15*M + 18*C <= 800, name="Milk_Constraint")

    # Optimize
    model.optimize()

    # Print solution
    if model.status == gurobi.GRB.OPTIMAL:
        print(f"Optimal Solution:")
        print(f"Soft Cake-pops: {S.varValue}")
        print(f"Smooth Cake-pops: {M.varValue}")
        print(f"Crunchy Cake-pops: {C.varValue}")
        print(f"Max Profit: {model.objVal}")
    else:
        print("No optimal solution found.")

if __name__ == "__main__":
    solve_bakery_problem()
```