1. Credentials
  2. Puzzles
  3. Writing Puzzles

Writing Puzzles

Learn how to write and add puzzles to Curta.

Introduction

To write a puzzle, inherit IPuzzle and implement its 3 functions:

SoliditySolidity's logo.IPuzzle.sol
interface IPuzzle {
/// @notice Returns the puzzle's name.
/// @return The puzzle's name.
function name() external pure returns (string memory);
/// @notice Generates the puzzle's starting position based on a seed.
/// @dev The seed is intended to be `msg.sender` of some wrapper function or
/// call.
/// @param _seed The seed to use to generate the puzzle.
/// @return The puzzle's starting position.
function generate(address _seed) external returns (uint256);
/// @notice Verifies that a solution is valid for the puzzle.
/// @dev `_start` is intended to be an output from {IPuzzle-generate}.
/// @param _start The puzzle's starting position.
/// @param _solution The solution to the puzzle.
/// @return Whether the solution is valid.
function verify(uint256 _start, uint256 _solution) external returns (bool);
}

Text Example: Sorted List

Consider some puzzle where the solution is to take an unsorted list of randomly generated numbers and sort it. In other words:

  • generate returns a bitpacked uint256 of 32 random numbers [0,255][0, 255].
  • verify iterates through 8-bit words of _solution and verifies that they are sorted, and each number in _start is contained in _solution.

Code Example: Collatz

Consider some puzzle where the solution is to apply the operation from the Collatz Conjecture a random number of times to a randomly generated integer. The code below implements this:

SoliditySolidity's logo.CollatzPuzzle.sol
1
contract CollatzPuzzle is IPuzzle {
2
/// @inheritdoc IPuzzle
3
function name() external pure returns (string memory) {
4
return "Collatz";
5
}
6
/// @inheritdoc IPuzzle
7
function generate(address _seed) external returns (uint256) {
8
// The last 8 bits denote the number of times to apply the operation.
9
// We want to apply it at least once, hence `| 1`.
10
return uint256(keccak256(abi.encodePacked(_seed))) | 1;
11
}
12
/// @inheritdoc IPuzzle
13
function verify(uint256 _start, uint256 _solution) external returns (bool) {
14
// Retrieve the last 8 bits.
15
uint256 iterations = _start & 0xFF;
16
for (uint256 i = 0; i < iterations; ) {
17
unchecked {
18
// Collatz Operation
19
if (_start & 1 == 0) _start >>= 1;
20
else _start = 3 * _start + 1;
21
++i;
22
}
23
}
24
return _start == _solution;
25
}
26
}

Adding a puzzle to Curta

To add a puzzle to Curta, you must first obtain an unused Authorship Token.

After obtaining an Authorship Token and deploying your puzzle contract, you can use it like a ticket to add a puzzle by calling addPuzzle(IPuzzle,uint256) on the Curta contract:

/// @notice Adds a puzzle to the contract. Note that an unused Authorship
/// Token is required to add a puzzle (see {AuthorshipToken}).
/// @param _puzzle The address of the puzzle.
/// @param _id The ID of the Authorship Token to burn.
function addPuzzle(IPuzzle _puzzle, uint256 _id) external;

Customizing art

Authors can customize 5 colors on their puzzle's Flag NFTs art:

NameDescription
BackgroundPortion behind the entire card
FillBackground color of the card
BorderStroke that outlines the card
Primary textMain text elements (header and stats)
Secondary textSecondary text elements (labels)

Each color is represented with 24 bits, and the colors for a puzzle's Flag is stored as a single bitpacked uint256 with the following offsets:

bitpackedUint = (background << 96)
| (fill << 72)
| (border << 48)
| (primaryText << 24)
| (secondaryText )

To set this onchain, call setPuzzleColors(uint32,uint256) on the Curta contract:

/// @notice Set the colors for a puzzle's Flags.
/// @dev Only the author of the puzzle of ID `_puzzleId` may set its token
/// renderer.
/// @param _puzzleId The ID of the puzzle.
/// @param _colors A bitpacked `uint120` of 5 24-bit colors for the puzzle's
/// Flags in the following order (left-most bit to right-most):
/// * Background
/// * Fill
/// * Border
/// * Primary text
/// * Secondary text
function setPuzzleColors(uint32 _puzzleId, uint256 _colors) external;
Waterfall