Assessment Information
Clarifications
If any questions or clarifications are asked which would be useful for everyone I will note them here. Please check this often.
- As a suggestion from one of you, I have recorded a bunch of example responses from the server which you can copy-paste into your code directly to allow you to work on the interesting parts of the assessment without worrying about the networking. They can be found here.
- Instead of creating a new PCB with
udp_new()every time you send an ethernet message, create one and reuse it. Normally this doesn’t matter, but sometimes if you create a new PCB for each message then lwIP will send yourREQUEST_INFOandREQUEST_CHUNKmessages from different ports which confuses the server.- You will know that this issue is affecting you if the server sends you the error
No active session - send REQUEST_INFO first
- You will know that this issue is affecting you if the server sends you the error
- The extension marks discussed in the markscheme are for solving large puzzles (potentially larger than the normal Hard tier) or for solving them particularly quickly etc. Basically they are for me and Steven to reward particularly good/interesting solutions. Most people should not worry about them.
Useful Tips
- You are not required to implement constraint propagation, but it will hugely cut down the search space.
- Similarly, there are many more complex strategies for solving Nonogram puzzles but the basic constraint propagation described in the paper is probably good enough.
- Avoid recursion. It is OK to use on the ARM, but you cannot implement a recursive function in HLS, it must be iterative.
Nonogram Server Protocol
We use a UDP-based protocol for interacting with the Nonogram server. The server is at IP 192.168.10.1 on port 51050. All multi-byte integer values in the protocol are encoded in big-endian format.
Tiers
The server supports four puzzle tiers that provide different difficulty guarantees:
| Tier | Guarantee | Available Sizes |
|---|---|---|
| Custom | No guarantees | 5x5 – 32x32 (indices 0–15) |
| Easy | Solvable by propagation alone (no search/backtracking) | 5x5 – 10x10 (indices 0–4) |
| Medium | No guarantees | 10x10 – 16x16 (indices 4–7) |
| Hard | Requires search - propagation alone will not solve | 16x16 – 20x20 (indices 7–9) |
- In order to get a guaranteed easy or hard puzzle, you must set
seed = 0. - When
seed = 0the server randomly selects from a curated seed list that satisfies the appropriate guarantee. These exist only for the “Available Sizes” listed above. - If you set
seed = 0and select Medium, or Custom then the server will give you a random seed with no difficulty guarantee. - If
seed != 0then tier is ignored entirely and you will get exactly the seed you requested.
Difficulty Field Encoding
The difficulty field in all messages is a single byte that encodes both tier and grid size:
Bit: 7 6 5 4 3 2 1 0
[reserved] [tier] [size index]- Bits [7:6] - Reserved
- Bits [5:4] - Tier:
0b00= Custom,0b01= Easy,0b10= Medium,0b11= Hard - Bits [3:0] - Size index (0–15), maps to grid sizes per the table below
Message Structures
Communication with the server involves a sequence of request and response messages.
REQUEST_INFO
To begin, the client requests information about a puzzle by sending a REQUEST_INFO message. This specifies the desired puzzle seed and difficulty level.
| Field Name | Width (bytes) | Description |
|---|---|---|
| Message ID | 1 | 0x01 (MSG_REQUEST_INFO) |
| Seed | 4 | A 32-bit unsigned integer for the puzzle seed. A seed of 0 requests auto-selection: Easy and Hard tiers pick from curated seed lists; Medium and Custom tiers get a random seed. |
| Difficulty | 1 | Composite byte encoding tier (bits [5:4]) and size index (bits [3:0]). See Difficulty Byte Encoding. |
The server responds with a PUZZLE_INFO message:
PUZZLE_INFO
| Field Name | Width (bytes) | Description |
|---|---|---|
| Message ID | 1 | 0x02 (MSG_PUZZLE_INFO) |
| Seed | 4 | The seed of the puzzle provided. |
| Difficulty | 1 | The difficulty byte (tier + size index) of the puzzle. |
| Width | 1 | The width of the puzzle grid in cells. |
| Height | 1 | The height of the puzzle grid in cells. |
| Num Chunks | 1 | The total number of chunks the puzzle’s clue data is divided into. Currently all puzzles fit within one chunk so this will always be 1. |
| Clue Bytes | 2 | The total size in bytes of the compressed clue data for the entire puzzle. Currently all puzzles are <700 bytes. |
REQUEST_CHUNK
The clue data is downloaded in parts called “chunks”. To request a chunk, the client sends a REQUEST_CHUNK message. Our puzzles currently all fit into a single chunk so we only need to send this once.
| Field Name | Width (bytes) | Description |
|---|---|---|
| Message ID | 1 | 0x03 (MSG_REQUEST_CHUNK) |
| Seed | 4 | The seed of the puzzle. |
| Difficulty | 1 | The difficulty byte (tier + size index) of the puzzle. |
| Chunk ID | 1 | The ID of the chunk being requested (0 to Num Chunks - 1). This should currently always be 0. |
The server responds with a CHUNK_DATA message containing the requested data:
CHUNK_DATA
| Field Name | Width (bytes) | Description |
|---|---|---|
| Message ID | 1 | 0x04 (MSG_CHUNK_DATA) |
| Seed | 4 | The seed of the puzzle. |
| Difficulty | 1 | The difficulty byte (tier + size index) of the puzzle. |
| Chunk ID | 1 | The ID of the chunk being sent. |
| Num Chunks | 1 | The total number of chunks for the puzzle. |
| Offset | 2 | The byte offset of this chunk within the complete clue data. |
| Data Length | 2 | The length of the Chunk Data field in bytes. |
| Chunk Data | variable | A portion of the puzzle’s clue data. The maximum size is 1000 bytes. |
Clue Data Format
The chunk data forms a byte stream encoding all row clues followed by all column clues. Each clue (row or column) is encoded as a length-prefixed list:
| Field | Width (bytes) | Description |
|---|---|---|
| Count | 1 | Number of clue blocks in this line (0 for an empty row/column). |
| Blocks | Count | One byte per block, giving the run length of consecutive filled cells. |
The full clue stream is structured as:
Row 0 clue: [count] [block_0] [block_1] ... [block_{count-1}]
Row 1 clue: [count] [block_0] ...
...
Row H-1 clue: [count] ...
Col 0 clue: [count] [block_0] ...
Col 1 clue: [count] ...
...
Col W-1 clue: [count] ...The total size of this stream is reported in the Clue Bytes field of the PUZZLE_INFO response.
Example: Consider this 5x5 puzzle (seed 13):
Col clues
3 3 1 3 3
+-----------+
- | . . . . . |
2 2 | # # . # # |
5 | # # # # # |
2 2 | # # . # # |
- | . . . . . |
+-----------+The complete clue data stream (20 bytes) encoding all rows then all columns:
00 ← Row 0: count=0 (empty row)
02 02 02 ← Row 1: count=2, blocks=[2, 2]
01 05 ← Row 2: count=1, blocks=[5]
02 02 02 ← Row 3: count=2, blocks=[2, 2]
00 ← Row 4: count=0 (empty row)
01 03 ← Col 0: count=1, blocks=[3]
01 03 ← Col 1: count=1, blocks=[3]
01 01 ← Col 2: count=1, blocks=[1]
01 03 ← Col 3: count=1, blocks=[3]
01 03 ← Col 4: count=1, blocks=[3]Raw hex stream: 00 02 02 02 01 05 02 02 02 00 01 03 01 03 01 01 01 03 01 03
Submitting a Solution
Once the puzzle is solved, the client submits its solution using the SUBMIT_SOLUTION message. The solution is sent as a bitmap, where each bit represents a cell’s state (1 for filled, 0 for empty). The bitmap is packed row by row, with each row padded to a full byte.
SUBMIT_SOLUTION
| Field Name | Width (bytes) | Description |
|---|---|---|
| Message ID | 1 | 0x05 (MSG_SUBMIT_SOLUTION) |
| Seed | 4 | The seed of the puzzle being solved. |
| Difficulty | 1 | The difficulty byte (tier + size index) of the puzzle. |
| Bitmap | variable | The solution grid, encoded as a bitmap. The size is ceil(width / 8) * height bytes. |
The server validates the solution and replies with a RESULT message:
RESULT
| Field Name | Width (bytes) | Description |
|---|---|---|
| Message ID | 1 | 0x06 (MSG_RESULT) |
| Seed | 4 | The seed of the puzzle. |
| Difficulty | 1 | The difficulty byte (tier + size index) of the puzzle. |
| Status | 1 | 0x00: Incorrect solution.0x01: Correct solution.0x02: Error. |
| Solve Time | 4 | The time taken in milliseconds from requesting Chunk 0 to submitting the solution. 0xFFFFFFFF if the time was > 60 sec. |
ERROR
If the client sends a malformed or out-of-sequence message, the server may respond with an ERROR message (assuming my error handling caught it and you didn’t nuke the server…)
| Field Name | Width (bytes) | Description |
|---|---|---|
| Message ID | 1 | 0xFF (MSG_ERROR) |
| Seed | 4 | The seed from the original message, if available. |
| Difficulty | 1 | The difficulty byte from the original message, if available. |
| Original Msg ID | 1 | The Message ID of the message that caused the error. |
| Text Length | 1 | The length of the Error Text field. |
| Error Text | variable | A human-readable ASCII error message (up to 200 bytes). |
Size Index
The lower 4 bits of the difficulty byte (the size index) map to the grid size:
| Size Index | Size (Width & Height) |
|---|---|
| 0 | 5x5 |
| 1 | 6x6 |
| 2 | 7x7 |
| 3 | 8x8 |
| 4 | 10x10 |
| 5 | 12x12 |
| 6 | 14x14 |
| 7 | 16x16 |
| 8 | 18x18 |
| 9 | 20x20 |
| 10 | 22x22 |
| 11 | 24x24 |
| 12 | 26x26 |
| 13 | 28x28 |
| 14 | 30x30 |
| 15 | 32x32 |