Skip to content
Assessment Information

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 your REQUEST_INFO and REQUEST_CHUNK messages 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
  • 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:

TierGuaranteeAvailable Sizes
CustomNo guarantees5x5 – 32x32 (indices 0–15)
EasySolvable by propagation alone (no search/backtracking)5x5 – 10x10 (indices 0–4)
MediumNo guarantees10x10 – 16x16 (indices 4–7)
HardRequires search - propagation alone will not solve16x16 – 20x20 (indices 7–9)
  • In order to get a guaranteed easy or hard puzzle, you must set seed = 0.
  • When seed = 0 the 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 = 0 and select Medium, or Custom then the server will give you a random seed with no difficulty guarantee.
  • If seed != 0 then 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 NameWidth (bytes)Description
Message ID10x01 (MSG_REQUEST_INFO)
Seed4A 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.
Difficulty1Composite 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 NameWidth (bytes)Description
Message ID10x02 (MSG_PUZZLE_INFO)
Seed4The seed of the puzzle provided.
Difficulty1The difficulty byte (tier + size index) of the puzzle.
Width1The width of the puzzle grid in cells.
Height1The height of the puzzle grid in cells.
Num Chunks1The 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 Bytes2The 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 NameWidth (bytes)Description
Message ID10x03 (MSG_REQUEST_CHUNK)
Seed4The seed of the puzzle.
Difficulty1The difficulty byte (tier + size index) of the puzzle.
Chunk ID1The 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 NameWidth (bytes)Description
Message ID10x04 (MSG_CHUNK_DATA)
Seed4The seed of the puzzle.
Difficulty1The difficulty byte (tier + size index) of the puzzle.
Chunk ID1The ID of the chunk being sent.
Num Chunks1The total number of chunks for the puzzle.
Offset2The byte offset of this chunk within the complete clue data.
Data Length2The length of the Chunk Data field in bytes.
Chunk DatavariableA 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:

FieldWidth (bytes)Description
Count1Number of clue blocks in this line (0 for an empty row/column).
BlocksCountOne 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 NameWidth (bytes)Description
Message ID10x05 (MSG_SUBMIT_SOLUTION)
Seed4The seed of the puzzle being solved.
Difficulty1The difficulty byte (tier + size index) of the puzzle.
BitmapvariableThe 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 NameWidth (bytes)Description
Message ID10x06 (MSG_RESULT)
Seed4The seed of the puzzle.
Difficulty1The difficulty byte (tier + size index) of the puzzle.
Status10x00: Incorrect solution.
0x01: Correct solution.
0x02: Error.
Solve Time4The 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 NameWidth (bytes)Description
Message ID10xFF (MSG_ERROR)
Seed4The seed from the original message, if available.
Difficulty1The difficulty byte from the original message, if available.
Original Msg ID1The Message ID of the message that caused the error.
Text Length1The length of the Error Text field.
Error TextvariableA 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 IndexSize (Width & Height)
05x5
16x6
27x7
38x8
410x10
512x12
614x14
716x16
818x18
920x20
1022x22
1124x24
1226x26
1328x28
1430x30
1532x32