To solve this optimization problem, we need to define the decision variables, objective function, and constraints. Let's denote:

- $x_e$ as the number of easy puzzles solved,
- $x_h$ as the number of hard puzzles solved.

The objective is to maximize the total points earned from solving these puzzles. Each easy puzzle is worth 5 points, and each hard puzzle is worth 8 points. Thus, the objective function can be written as:

\[ \text{Maximize: } 5x_e + 8x_h \]

We have several constraints based on the problem description:

1. Solve at least 3 easy puzzles: $x_e \geq 3$
2. Solve at least 1 hard puzzle: $x_h \geq 1$
3. Solve at most 10 easy puzzles: $x_e \leq 10$
4. Solve at most 5 hard puzzles: $x_h \leq 5$
5. The total number of puzzles solved cannot exceed 10: $x_e + x_h \leq 10$

All variables are non-negative since you can't solve a negative number of puzzles.

Now, let's translate this into Gurobi code in Python:

```python
from gurobipy import *

# Create a model
m = Model("Puzzle Optimization")

# Define the decision variables
x_e = m.addVar(name='easy_puzzles', vtype=GRB.INTEGER, lb=0)
x_h = m.addVar(name='hard_puzzles', vtype=GRB.INTEGER, lb=0)

# Set the objective function
m.setObjective(5*x_e + 8*x_h, GRB.MAXIMIZE)

# Add constraints
m.addConstr(x_e >= 3, name='at_least_3_easy')
m.addConstr(x_h >= 1, name='at_least_1_hard')
m.addConstr(x_e <= 10, name='at_most_10_easy')
m.addConstr(x_h <= 5, name='at_most_5_hard')
m.addConstr(x_e + x_h <= 10, name='total_puzzles_limit')

# Optimize the model
m.optimize()

# Print the solution
if m.status == GRB.OPTIMAL:
    print("Optimal solution found:")
    print(f"Easy puzzles: {x_e.x}")
    print(f"Hard puzzles: {x_h.x}")
    print(f"Total points: {5*x_e.x + 8*x_h.x}")
else:
    print("No optimal solution found")
```