1// SPDX-License-Identifier: Unlicense2pragma solidity ^0.8.17;3
4import "../interfaces/IPuzzle.sol";5
6/// @title 2 × 4 = 87/// @custom:subtitle Sodoku8/// @author fiveoutofnine9/// @notice A modified version of the classic Sodoku puzzle, for an 8 × 8 grid.10/// As usual, each row and column must contain [1, ..., 8] exactly once.11/// However, unlike in regular Sodoku, we now check for 2 × 4 subgrids, rather12/// than 3 × 3 subgrids.13contract TwoTimesFourIsEight is IPuzzle {14 /// @notice A mapping of from indices to which checks must be performed at15 /// that index.16 /// @dev We reserve 3 bits for each check as follows:17 /// * 0th bit is `1`: check subgrid;18 /// * 1st bit is `1`: check column;19 /// * 2nd bit is `1`: check row.20 ///21 /// For clarity, the following table lays out the bitpacked values:22 /// | Index | Row | Column | Subgrid | Value |23 /// |-------+---------+---------+---------+-------|24 /// | 0 | 1 | 1 | 1 | 0b111 |25 /// | 1 | 0 | 1 | 0 | 0b010 |26 /// | 2 | 0 | 1 | 0 | 0b010 |27 /// | 3 | 0 | 1 | 0 | 0b010 |28 /// | 4 | 0 | 1 | 1 | 0b011 |29 /// | 5 | 0 | 1 | 0 | 0b010 |30 /// | 6 | 0 | 1 | 0 | 0b010 |31 /// | 7 | 0 | 1 | 0 | 0b010 |32 /// | 8 | 1 | 0 | 0 | 0b100 |33 /// | 16 | 1 | 0 | 1 | 0b101 |34 /// | 20 | 0 | 0 | 1 | 0b001 |35 /// | 24 | 1 | 0 | 0 | 0b100 |36 /// | 32 | 1 | 0 | 1 | 0b101 |37 /// | 36 | 0 | 0 | 1 | 0b001 |38 /// | 40 | 1 | 0 | 0 | 0b100 |39 /// | 48 | 1 | 0 | 1 | 0b101 |40 /// | 52 | 0 | 0 | 1 | 0b001 |41 /// | 56 | 1 | 0 | 0 | 0b100 |42 uint256 private constant CHECKS =43 0x400010005000000040001000500000004000100050000000422232227;44
45 /// @notice A bitpacked value that indicates how many bits to shift by to46 /// get to the next value in the row.47 /// @dev We reserve 6 bits for each value, and the following are packed48 // left-to-right: `[4, 4, 4, 4, 4, 4, 4, 4]`.49 uint256 private constant ROW_SHIFTS = 0x104104104104;50
51 /// @notice A bitpacked value that indicates how many bits to shift by to52 /// get to the next value in the column.53 /// @dev We reserve 6 bits for each value, and the following are packed54 // left-to-right: `[32, 32, 32, 32, 32, 32, 32, 32]`.55 uint256 private constant COL_SHIFTS = 0x820820820820;56
57 /// @notice A bitpacked value that indicates how many bits to shift by to58 /// get to the next value in the 2 × 4 subgrid.59 /// @dev We reserve 6 bits for each value, and the following are packed60 // left-to-right: `[4, 4, 4, 20, 4, 4, 4, 4]`.61 uint256 private constant SUBGRID_SHIFTS = 0x104104504104;62
63 /// @notice A bitmap to denote that each of [1, ..., 8] has been seen.64 /// @dev Bits 1-8 should be set to 1, with everything else set to 0 (i.e.65 /// `0b111111110 = 0xFE`).66 uint256 private constant FILLED_BITMAP = 0x1FE;67
68 /// @inheritdoc IPuzzle69 function name() external pure returns (string memory) {70 return unicode"2 × 4 = 8";71 }72
73 /// @inheritdoc IPuzzle74 function generate(address _seed) external pure returns (uint256) {75 uint256 seed = uint256(keccak256(abi.encodePacked(_seed)));76 uint256 puzzle;77
78 // We use this to keep track of which indices [0, ..., 63] have been79 // filled. See the next comment for why the value is initialized to80 // `1 << 64`.81 uint256 bitmap = 1 << 64;82 // Note that the bitmap only intends on reserving bits 0-63 to represent83 // the slots that have been filled. Thus, if we set `index` to 64, it84 // is a sentinel value that will always yield 0 when using it to85 // retrieve from the bitmap.86 uint256 index = 64;87 // We fill the puzzle randomly with 1 of [1, ..., 8]. This way, every88 // puzzle is solvable.89 for (uint256 i = 1; i < 9; ) {90 // We have exhausted the seed, so stop iterating.91 if (seed == 0) break;92
93 // Loop through until we find an unfilled index.94 while ((bitmap >> index) & 1 == 1 && seed != 0) {95 // Retrieve 6 random bits from `seed` to determine which index96 // to fill.97 index = seed & 0x3F;98 seed >>= 6;99 }100 // Set the bit in the bitmap to indicate that the index has101 // been filled.102 bitmap |= 1 << index;103
104 // Place the number into the slot that was just filled.105 puzzle |= (i << (index << 2));106 index = 64;107 unchecked {108 ++i;109 }110 }111
112 return puzzle;113 }114
115 /// @inheritdoc IPuzzle116 function verify(uint256 _start, uint256 _solution)117 external118 pure119 returns (bool)120 {121 // Iterate through the puzzle.122 for (uint256 index; index < 256; ) {123 // Check that the starting position is included in the solution.124 if (_start & 0xF != 0 && _start & 0xF != _solution & 0xF)125 return false;126
127 // Retrieve how many checks to perform.128 uint256 checks = (CHECKS >> index) & 7;129 if (checks & 4 == 4 && !check(_solution, ROW_SHIFTS)) return false;130 if (checks & 2 == 2 && !check(_solution, COL_SHIFTS)) return false;131 if (checks & 1 == 1 && !check(_solution, SUBGRID_SHIFTS))132 return false;133
134 _start >>= 4;135 _solution >>= 4;136 unchecked {137 index += 4;138 }139 }140
141 return true;142 }143
144 /// @notice Checks whether a row, column, or box is filled in a valid way.145 /// @param _shifted The puzzle shifted to the index it should start checking146 /// from.147 /// @param _shifts A bitpacked value that indicates how many bits to shift148 /// by after each iteration in the loop.149 /// @return Whether the check is valid.150 function check(uint256 _shifted, uint256 _shifts)151 internal152 pure153 returns (bool)154 {155 uint256 shifted = _shifted;156 // Used to keep track of which numbers [1, ..., 8] have been seen.157 uint256 bitmap;158
159 while (_shifts != 0) {160 // Set the bit in the bitmap to indicate that the number has been161 // seen.162 bitmap |= 1 << (shifted & 0xF); // `shifted & 0xF` reads the number.163 // Retrieve 6 bits from `_shifts` to determine how many bits to164 // shift the puzzle by.165 shifted >>= (_shifts & 0x3F);166 _shifts >>= 6;167 }168
169 return bitmap == FILLED_BITMAP;170 }171}172
Time Left
Solve locally (WIP)
- Clone GitHub repo + install deps
git clone https://github.com/waterfall-mkt/curta-puzzles.git && cd curta-puzzles && forge install
- Set
RPC_URL_MAINNET
in.env
.env
RPC_URL_MAINNET=""
- Write solution + run script
forge script <PATH_TO_PUZZLE> -f mainnet -vvv
This is still WIP.