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_MAINNETin.env 
.env
RPC_URL_MAINNET=""- Write solution + run script
 
forge script <PATH_TO_PUZZLE> -f mainnet -vvvThis is still WIP.