twentyseven 0.0.0 → 1.0.0
raw patch · 15 files changed
+210/−173 lines, 15 filesdep +taggeddep +tastydep +tasty-hunitdep −Cabaldep −HUnit-Plusdep −cabal-test-quickcheckdep ~MonadRandomdep ~containersdep ~deepseq
Dependencies added: tagged, tasty, tasty-hunit, tasty-quickcheck
Dependencies removed: Cabal, HUnit-Plus, cabal-test-quickcheck
Dependency ranges changed: MonadRandom, containers, deepseq, directory, filepath, heap, monad-loops, mtl, newtype, optparse-applicative, primitive, ref-fd, template-haskell, time, transformers, vector
Files
- CHANGELOG.md +7/−0
- README.md +46/−24
- exec-src/twentyseven.hs +18/−34
- src/Data/Tuple/Template.hs +5/−1
- src/Data/Vector/HalfByte.hs +1/−1
- src/Rubik/Cube/Cubie/Internal.hs +28/−14
- src/Rubik/Cube/Facelet/Internal.hs +4/−1
- src/Rubik/Cube/Moves/Internal.hs +0/−1
- src/Rubik/IDA.hs +9/−5
- src/Rubik/Misc.hs +0/−1
- src/Rubik/Solver/TwoPhase.hs +0/−1
- src/Rubik/Tables/Moves.hs +0/−1
- stack.yaml +0/−8
- test/Test.hs +58/−56
- twentyseven.cabal +34/−25
+ CHANGELOG.md view
@@ -0,0 +1,7 @@+# Twentyseven + +## 1.0.0 - 2025-08-01 + +- Update to GHC 9.12 + +## 0.0.0 - 2016-03-16
README.md view
@@ -7,25 +7,20 @@ The main idea is to precompute, for every configuration, the number of moves required to put certain subsets of the 27 cubies composing the 3x3 Rubik's cube-in their right place and/or in the right orientation. This gives lower bounds+in their right place or in the right orientation. This gives lower bounds used for an A⋆-like search in the graph of scrambled cubes. ----+Two algorithms are available in Twentyseven: -By default, a suboptimal "two-phase" solver is used, as it runs rather quickly.-It currently solves 1000 random cubes (uniformly distributed) in about one-minute. The optimal solver is quite slow however, taking between five minutes-and two hours to solve a random cube (18 moves in average).+- the two-phase solver, which is suboptimal but fast: it solves 1000 random cubes (uniformly distributed) in one minute;+- the optimal solver, which is quite slow, taking between five minutes and two hours to solve a random cube (18 moves in average). -The solver must precompute a certain number of lookup tables, which can be-stored in files. These tables take fifteen seconds to compute and weigh 13MB-for the two-phase solver, compare that to about 8 hours and 2GB for the optimal-one!+The lookup tables for the two-phase solver take ten seconds to compute and weigh 13MB,+compare that to about four hours and 2GB for the optimal solver!+(Timed on a i7-10750H.) -You may check the produced files with the checksums in `ts-tables.sha256`.-A compressed archive `ts-tables.zip` (723MB) of all precomputed tables is-available in the branch `fetch-tables` via `git-lfs`. Unzip it in `$HOME/.27/`,-or wherever (see usage below).+A compressed archive `ts-tables.tar.gz` (720MB) of all precomputed tables is+available on Google Drive; see instructions at the bottom. Usage summary -------------@@ -34,11 +29,14 @@ - For the first invocation, use `-p` to precompute nonexistent lookup tables, otherwise an error is thrown when `twentyseven` tries to load them;-- `--strict` loads tables immediately, otherwise they are loaded "by need" (so- you can also send it a cube to solve);+- `--strict` loads tables immediately, otherwise they are loaded "by need"; - `-d DIR` specifies the directory where the tables should be read and written (default: `$HOME/.27/`). +For more options:++ twentyseven --help+ The input is read line by line. Input format@@ -52,7 +50,7 @@ Output: a sequence of moves to unscramble it. - Facelets are numbered in base 9. Faces `0,1,2,3,4,5` correspond to `U,L,F,R,B,D`.+ In the following figure, facelets are numbered in base 9. Faces `0,1,2,3,4,5` correspond to `U,L,F,R,B,D`. 00 01 02 03 04 05@@ -93,7 +91,7 @@ ### Initialization - $ echo quit|twentyseven -p --strict+ $ twentyseven -p --strict < /dev/null ### Example @@ -122,11 +120,10 @@ --- -Detail of current heuristics-----------------------------+Detail of heuristics+-------------------- -The distance estimations are based on cosets corresponding to the following-elements.+The distance estimations are based on the following projections of the cube state. ### Two-phase @@ -135,8 +132,8 @@ - Corner Orientation × UD Slice - Edge Orientation × UD Slice -It is possible to store the actual distances to the goal set in phase 1 but-the current speed seems good enough for now.+It is possible to store the actual distances to the goal set in phase 1 to+make it even faster, but the two phase algorithm is fairly quick already. #### Phase 2 @@ -148,3 +145,28 @@ - Corner Orientation × Edge Orientation × XY Slice Permutation, for XY in {UD, LR, FB} - Corner Orientation × Corner Permutation++Download the tables+-------------------++If you can't wait four hours to precompute the optimal tables,+you can download them from Google Drive.+Pray to the Google gods that the URL still works.+There are checksums for the archive as well as the uncompressed files in `ts-tables.sha256`.++```sh+# 1. This command prints a Google Drive URL (drive.usercontent.google.com) where you can download ts-tables.tar.gz+sh print-ts-tables-url.sh twentyseven++# 2. Download ts-tables.tar.gz++# 3. The checksums should match+sha256sum ts-tables.tar.gz+tail -1 ts-tables.sha256++# 4. Unpack tables into ~/.27+mkdir ~/.27+mv ts-tables.tar.gz ~/.27+cd ~/.27+tar zxf ts-tables.tar.gz+```
exec-src/twentyseven.hs view
@@ -12,12 +12,10 @@ import Data.Time.Clock import Data.Char-import Data.Monoid import Numeric ( showFFloat ) import Options.Applicative hiding ( value )-import qualified Options.Applicative as Opt import System.Exit import System.IO.Error@@ -25,35 +23,35 @@ type Solver = Cube -> Move data Parameters = Parameters {- verbose :: Bool, solve :: Solver,- tsPath :: Maybe FilePath, precompute :: Bool,+ strict :: Bool, overwrite :: Bool, noFiles :: Bool,- strict :: Bool,+ timed :: Bool,+ tsPath :: Maybe FilePath, debug :: Bool } optparse :: Parser Parameters optparse = Parameters- <$> switch ( long "verbose" <> short 'v'- <> help "Print time taken to solve every cube" )- <*> flag TwoPhase.solve Optimal.solve ( long "optimal"- <> help "Use optimal solver (experimental)" )- <*> (optional . strOption) ( long "ts-dir" <> short 'd'- <> metavar "DIR"- <> help "Location of precomputed tables" )+ <$> flag TwoPhase.solve Optimal.solve ( long "optimal"+ <> help "Use optimal solver (slow!)" ) <*> switch ( long "precompute" <> short 'p' <> help "Precompute and store tables \ \(do enable this at the first invocation)" )+ <*> switch ( long "strict"+ <> help "Force loading tables before doing anything else" ) <*> switch ( long "overwrite" <> help "Recompute and overwrite tables even when they exist already" ) <*> switch ( long "no-files" <> help "Do not read or write any files \ \(recompute tables for this session)" )- <*> switch ( long "strict"- <> help "Force loading tables before doing anything else" )+ <*> switch ( long "timed" <> short 't'+ <> help "Print time taken to solve every cube" )+ <*> (optional . strOption) ( long "ts-dir" <> short 'd'+ <> metavar "DIR"+ <> help "Location of precomputed tables" ) <*> switch ( long "debug" ) main :: IO ()@@ -84,13 +82,16 @@ _ -> faceletList s p -- A sequence of moves, e.g., "URF".+moveSequence :: String -> IO () moveSequence s = putStrLn $ case stringToMove s of Left c -> "Unexpected '" ++ [c] ++ "'." Right ms -> stringOfCubeColors . moveToCube . reduceMove $ ms +faceletList :: [Char] -> Parameters -> IO () faceletList = either (const . putStrLn) justSolve . readCube +readCube :: (Eq b, Show b) => [b] -> Either String Cube readCube s = case colorFacelets'' s of Nothing -> Left "Expected string of length 54 of a set of (any) 6 \@@ -111,21 +112,13 @@ justSolve c p = do let solved = solve p c solStr = moveToString solved- flip vPutStrLn p . toString =<< clock (evaluate solved)+ time <- clock (evaluate (solved `seq` ()))+ when (timed p) (putStrLn (showFFloat (Just 2) time "s")) if c <> moveToCube solved == iden then putStrLn solStr else fail $ "Incorrect solver: " ++ solStr- where- toString d = showFFloat (Just 2) d "s" -unlessQuiet' :: IO () -> Parameters -> IO ()-unlessQuiet' a = unlessQuiet (const a) ()---- Strict in its second argument-unlessQuiet :: (a -> IO ()) -> a -> Parameters -> IO ()-unlessQuiet f a p = evaluate a >> when (verbose p) (f a)--clock :: IO a -> IO Double+clock :: IO () -> IO Double clock a = do t <- getCurrentTime a@@ -133,12 +126,3 @@ return (diffTimeToSeconds (diffUTCTime t' t)) where diffTimeToSeconds = fromRational . toRational--listSeq' :: [a] -> [a]-listSeq' s = s `listSeq` s--vPutStrLn :: String -> Parameters -> IO ()-vPutStrLn s = unlessQuiet putStrLn (listSeq' s)--vPutStr :: String -> Parameters -> IO ()-vPutStr s = unlessQuiet putStrLn (listSeq' s)
src/Data/Tuple/Template.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell, ViewPatterns #-}+{-# LANGUAGE CPP, TemplateHaskell, ViewPatterns #-} module Data.Tuple.Template where import Control.Monad@@ -31,7 +31,11 @@ [typeD aas, consInlD, consD, splitInlD, splitD] where typeD aas@(a : as) =+#if MIN_VERSION_template_haskell(2, 15, 0)+ TySynInstD <$> tySynEqn Nothing (conT (mkName ":|") `appT` a `appT` tupleT as) (tupleT aas)+#else TySynInstD (mkName ":|") <$> tySynEqn [a, tupleT as] (tupleT aas)+#endif consInlD = pragInlD (mkName "|:|") Inline FunLike AllPhases splitInlD = pragInlD (mkName "split") Inline FunLike AllPhases consD = do
src/Data/Vector/HalfByte.hs view
@@ -4,7 +4,7 @@ {-# LANGUAGE FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving, MagicHash, MultiParamTypeClasses, RankNTypes, ScopedTypeVariables,- TypeFamilies #-}+ TypeFamilies, UndecidableInstances #-} module Data.Vector.HalfByte where import Control.DeepSeq
src/Rubik/Cube/Cubie/Internal.hs view
@@ -11,7 +11,6 @@ import Data.Function ( on ) import Data.List import Data.Maybe-import Data.Monoid import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as MU @@ -209,20 +208,28 @@ -- +instance Semigroup CornerPermu where+ CornerPermu b <> CornerPermu c = CornerPermu $ composeVector b c+ instance Monoid CornerPermu where mempty = CornerPermu $ idVector numCorners- mappend (CornerPermu b) (CornerPermu c) = CornerPermu $ composeVector b c+ mappend = (<>) instance Group CornerPermu where inverse (CornerPermu a) = CornerPermu $ inverseVector a ++instance Semigroup EdgePermu where+ EdgePermu b <> EdgePermu c = EdgePermu $ composeVector b c+ instance Monoid EdgePermu where mempty = EdgePermu $ idVector numEdges- mappend (EdgePermu b) (EdgePermu c) = EdgePermu $ composeVector b c+ mappend = (<>) instance Group EdgePermu where inverse (EdgePermu a) = EdgePermu $ inverseVector a + instance CubeAction CornerPermu where cubeAction cp_ = (cp_ <>) . fromCube @@ -257,32 +264,36 @@ -- -instance Monoid Corner where- mempty = Corner iden idCornerO- where idCornerO = CornerOrien $ U.replicate numCorners 0-- mappend (Corner bp_ bo_)+instance Semigroup Corner where+ (<>) (Corner bp_ bo_) c@(Corner cp_ co_) = Corner dp_ do_ where dp_ = bp_ <> cp_ do_ = bo_ `actionCorner` c +instance Monoid Corner where+ mempty = Corner iden idCornerO+ where idCornerO = CornerOrien $ U.replicate numCorners 0+ mappend = (<>)+ instance Group Corner where inverse (Corner ap_ (CornerOrien ao)) = Corner ap_' (CornerOrien ao') where ap_'@(CornerPermu ap') = inverse ap_ ao' = U.map oInv . U.backpermute ao $ ap' -instance Monoid Edge where- mempty = Edge iden idEdgeO- where idEdgeO = EdgeOrien $ U.replicate numEdges 0-- mappend (Edge bp_ bo_)+instance Semigroup Edge where+ (<>) (Edge bp_ bo_) c@(Edge cp_ co_) = Edge dp_ do_ where dp_ = bp_ <> cp_ do_ = bo_ `actionEdge` c +instance Monoid Edge where+ mempty = Edge iden idEdgeO+ where idEdgeO = EdgeOrien $ U.replicate numEdges 0+ mappend = (<>)+ instance Group Edge where inverse (Edge ap_ (EdgeOrien ao)) = Edge ap_' (EdgeOrien ao')@@ -291,9 +302,12 @@ -- +instance Semigroup Cube where+ Cube cA eA <> Cube cB eB = Cube (cA <> cB) (eA <> eB)+ instance Monoid Cube where mempty = Cube iden iden- mappend (Cube cA eA) (Cube cB eB) = Cube (cA <> cB) (eA <> eB)+ mappend = (<>) instance Group Cube where inverse (Cube c e) = Cube (inverse c) (inverse e)
src/Rubik/Cube/Facelet/Internal.hs view
@@ -20,9 +20,12 @@ fromFacelets :: Vector Int } deriving (Eq, Show) +instance Semigroup Facelets where+ Facelets b <> Facelets c = Facelets $ composeVector b c+ instance Monoid Facelets where mempty = Facelets $ idVector numFacelets- mappend (Facelets b) (Facelets c) = Facelets $ composeVector b c+ mappend = (<>) instance Group Facelets where inverse (Facelets a) = Facelets $ inverseVector a
src/Rubik/Cube/Moves/Internal.hs view
@@ -16,7 +16,6 @@ import Data.Function ( on ) import Data.List import Data.Maybe-import Data.Monoid import qualified Data.Vector as V import qualified Data.Vector.Unboxed as U
src/Rubik/IDA.hs view
@@ -23,15 +23,19 @@ type Result a l = Maybe [l] data SearchResult a l = Next !a | Found [l] | Stop +instance Ord a => Semigroup (SearchResult a l) where+ {-# INLINE (<>) #-}+ (<>) f@(Found _) _ = f+ (<>) _ f@(Found _) = f+ (<>) (Next a) (Next b) = Next (min a b)+ (<>) Stop x = x+ (<>) x Stop = x+ instance Ord a => Monoid (SearchResult a l) where {-# INLINE mempty #-} mempty = Stop {-# INLINE mappend #-}- mappend f@(Found _) _ = f- mappend _ f@(Found _) = f- mappend (Next a) (Next b) = Next (min a b)- mappend Stop x = x- mappend x Stop = x+ mappend = (<>) -- | Depth-first search up to depth @bound@, -- and reduce results from the leaves.
src/Rubik/Misc.hs view
@@ -9,7 +9,6 @@ import Control.Applicative import Data.Maybe-import Data.Monoid import Data.Proxy (Proxy(..)) import Data.List import qualified Data.Vector.Unboxed as U
src/Rubik/Solver/TwoPhase.hs view
@@ -10,7 +10,6 @@ import Rubik.Tables.Distances import Data.Function ( on )-import Data.Monoid {-# INLINE phase1Proj #-} phase1Proj
src/Rubik/Tables/Moves.hs view
@@ -10,7 +10,6 @@ import Data.Bifunctor import Data.Bits import Data.Maybe-import Data.Monoid import qualified Data.Vector as V import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Storable.Allocated as S
− stack.yaml
@@ -1,8 +0,0 @@-flags: {}-packages:- - '.'-extra-deps: []- # These deps are for tests and the first one needs fixing anyway.- #- HUnit-Plus-0.1.0- #- cabal-test-quickcheck-0.1.6-resolver: lts-5.8
test/Test.hs view
@@ -1,33 +1,50 @@ {-# LANGUAGE LambdaCase, RecordWildCards, ScopedTypeVariables, ViewPatterns #-}-module Test where import Rubik.Cube import Rubik.Cube.Facelet.Internal import Rubik.Cube.Cubie.Internal-import Rubik.Cube.Moves.Internal+import Rubik.Tables.Internal (setPrecompute) import Rubik.Tables.Moves import Rubik.Misc import Rubik.Symmetry import Control.Applicative import Control.Monad-import Data.List import Data.List.Split (chunksOf) import Data.Maybe-import Data.Monoid+import Data.Proxy (Proxy(..))+import Data.Tagged (Tagged(..)) import qualified Data.Vector.Generic as G-import qualified Data.Vector.Primitive.Pinned as P-import Distribution.TestSuite-import Distribution.TestSuite.QuickCheck-import Test.HUnitPlus-import Test.QuickCheck+import qualified Data.Vector.Storable.Allocated as P+import Test.Tasty (TestTree, askOption, defaultIngredients, defaultMainWithIngredients, includingOptions, testGroup)+import Test.Tasty.Options (IsOption(..), OptionDescription(..), flagCLParser, safeReadBool)+import Test.Tasty.Runners (TestTree(SingleTest, TestGroup))+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck import qualified Test.QuickCheck as Gen-import System.Environment +type Test = TestTree++main :: IO ()+main = do+ setPrecompute True+ defaultMainWithIngredients ingredients tests+ where+ ingredients = includingOptions [Option (Proxy :: Proxy EnableOptimal)] : defaultIngredients++newtype EnableOptimal = EnableOptimal Bool++instance IsOption EnableOptimal where+ defaultValue = EnableOptimal False+ parseValue = (fmap . fmap) EnableOptimal safeReadBool+ optionName = Tagged "enable-optimal"+ optionHelp = Tagged "Run tests involving optimal solver"+ optionCLParser = flagCLParser Nothing (EnableOptimal True)+ -- If the test suite receives some command line arguments, only tests whose -- fully qualified name has a prefix among them are run.-tests :: IO [Test]-tests = (filterTests . rename)+tests :: Test+tests = testGroup "twentyseven" [ testGroup "Cube" [ testGroup "Facelets" [ testProperty "permutation-to-facelet" $@@ -149,7 +166,7 @@ move18 move18CornerOrien , testMoveTables "move18EdgeOrien" move18 move18EdgeOrien- , testMoveTables "move18UDSlicePermu"+ , requiresOptimal $ testMoveTables "move18UDSlicePermu" move18 move18UDSlicePermu , testMoveTables "move18UDSlice" move18 move18UDSlice@@ -160,13 +177,26 @@ ] , testUDSlicePermu , testFlipUDSlicePermu- , testRawToSymFlipUDSlicePermu- , testSymReprTable "srFUDSP"+ , requiresOptimal testRawToSymFlipUDSlicePermu+ , requiresOptimal $ testSymReprTable "srFUDSP" reprFlipUDSlicePermu conjugateFlipUDSlicePermu- , testMoveSymTables "msFUDSP" move18 move18SymFlipUDSlicePermu+ , requiresOptimal $ testMoveSymTables "msFUDSP" move18SymFlipUDSlicePermu ] ] +-- Argument must be SingleTest or TestGroup+requiresOptimal :: TestTree -> TestTree+requiresOptimal t =+ askOption $ \(EnableOptimal enable) ->+ if enable then+ t+ else+ let name = case t of+ SingleTest name _ -> name+ TestGroup name _ -> name+ _ -> error "expected named test" in+ testCaseInfo name (pure "Skipped (requires --enable-optimal)")+ -- * Facelets genFacelets = unsafeFacelets' <$> shuffle [0 .. 53]@@ -247,11 +277,11 @@ -- * Moves testMoves :: String -> String -> Test-testMoves moves result = '.' : moves ~:- (stringOfCubeColors . moveToCube <$> stringToMove moves) ~?= Right result+testMoves moves result = testCase ('_' : moves) $+ (stringOfCubeColors . moveToCube <$> stringToMove moves) @?= Right result testCube :: String -> Cube -> String -> Test-testCube name c result = name ~: stringOfCubeColors c ~?= result+testCube name c result = testCase name $ stringOfCubeColors c @?= result -- * Move tables @@ -283,13 +313,13 @@ genCoordFUDSP = RawCoord <$> Gen.choose (0, range ([] :: [FlipUDSlicePermu]) -1) testMoveSymTables :: ()- => String -> MoveTag m [Cube] -> MoveTag m [SymMove UDFix FlipUDSlicePermu]+ => String -> MoveTag m [SymMove UDFix FlipUDSlicePermu] -> Test-testMoveSymTables name (MoveTag cubes) (MoveTag moves)+testMoveSymTables name (MoveTag moves) = testProperty name $- conjoin $ zipWith propMoveSymTable1 cubes moves+ conjoin $ propMoveSymTable1 <$> moves -propMoveSymTable1 c (SymMove m)+propMoveSymTable1 (SymMove m) -- = forAll (Gen.choose (0, P.length m-1)) $ \x -> = case G.find (\x -> x >= 16 * P.length m) m of Nothing -> property True@@ -306,9 +336,8 @@ -- * Typeclass laws testMonoid0 :: (Monoid a, Eq a, Show a) => proxy a -> Test-testMonoid0 proxy =- "mempty-mappend-mempty" ~:- mempty <> mempty ~?= mempty `asProxyTypeOf` proxy+testMonoid0 proxy = testCase "mempty-mappend-mempty" $+ mempty <> mempty @?= mempty `asProxyTypeOf` proxy testMonoid :: (Monoid a, Eq a, Show a) => Gen a -> Test testMonoid gen = testGroup "Monoid"@@ -323,9 +352,8 @@ ] testGroup0 :: (Group a, Eq a, Show a) => proxy a -> Test-testGroup0 proxy =- "inverse-mempty" ~:- inverse mempty ~?= mempty `asProxyTypeOf` proxy+testGroup0 proxy = testCase "inverse-mempty" $+ inverse mempty @?= mempty `asProxyTypeOf` proxy testGroupInstance :: (Group a, Eq a, Show a) => Gen a -> Test testGroupInstance gen = testGroup "Group"@@ -340,7 +368,7 @@ testMonoidMorphism :: (Monoid a, Monoid b, Eq a, Eq b, Show a, Show b) => Gen a -> (a -> b) -> Test testMonoidMorphism gen f = testGroup "MonoidM"- [ "morphism-iden" ~: f mempty ~?= mempty+ [ testCase "morphism-iden" $ f mempty @?= mempty , testProperty "morphism-compose" $ forAll gen $ \x -> forAll gen $ \y -> f (x <> y) === f x <> f y@@ -367,29 +395,3 @@ testGenerator :: (Eq a, Show a) => Gen a -> (a -> Maybe b) -> Test testGenerator gen p = testProperty "generator" $ forAll gen (isJust . p)---- * Utilities---- Qualify test names-rename :: [Test] -> [Test]-rename = fmap (rename' "")--rename' :: String -> Test -> Test-rename' pfx (Test t) = Test t{ name = pfx ++ name t }-rename' pfx (Group name conc tests)- = Group name conc (fmap (rename' (pfx ++ name ++ "/")) tests)-rename' pfx (ExtraOptions opts test) = ExtraOptions opts (rename' pfx test)--filterTests :: [Test] -> IO [Test]-filterTests tests = do- getArgs <&> \case- [] -> tests- pfxs -> filterTests' pfxs tests--filterTests' pfxs = (>>= filterTest pfxs)--filterTest pfxs test@(Test t) = [test | any (`isPrefixOf` name t) pfxs]-filterTest pfxs (Group name conc tests)- = let tests' = filterTests' pfxs tests- in [Group name conc tests' | (not . null) tests']-filterTest pfxs (ExtraOptions opts test) = ExtraOptions opts <$> filterTest pfxs test
twentyseven.cabal view
@@ -1,5 +1,5 @@ name: twentyseven-version: 0.0.0+version: 1.0.0 synopsis: Rubik's cube solver description: Solve 3×3×3 Rubik's cubes in the fewest possible moves. Or, if you can't@@ -8,12 +8,18 @@ license: MIT license-file: LICENSE author: Li-yao Xia-maintainer: li-yao.xia@ens.fr+maintainer: lysxia@gmail.com category: Algorithms build-type: Simple-extra-source-files: README.md stack.yaml-cabal-version: >=1.10+extra-doc-files: README.md, CHANGELOG.md+cabal-version: 2.0+tested-with:+ GHC == 8.6.5, GHC == 9.12.2 +source-repository head+ type: git+ location: https://github.com/lysxia/twentyseven+ library exposed-modules: Data.Binary.Storable@@ -56,19 +62,19 @@ ViewPatterns build-depends: base >=4.8 && <5,- deepseq,- directory,- filepath,- heap >=1.0,- primitive >=0.6,- vector >=0.10,- containers >=0.5,- monad-loops,- MonadRandom,- mtl >= 2.1,- newtype >= 0.2,- ref-fd >=0.4,- template-haskell+ deepseq < 1.7,+ directory < 1.4,+ filepath < 1.6,+ heap >=1.0 && < 1.1,+ primitive >=0.6 && < 0.10,+ vector >=0.10 && < 0.14,+ containers >=0.5 && < 0.9,+ monad-loops >= 0.4 && < 0.5,+ MonadRandom >= 0.4.2.2 && < 0.7,+ mtl >= 2.1 && < 2.4,+ newtype >= 0.2 && < 0.3,+ ref-fd >=0.4 && < 0.6,+ template-haskell < 2.24 hs-source-dirs: src default-language: Haskell2010 ghc-options: -fwarn-unused-imports@@ -81,25 +87,28 @@ RecordWildCards build-depends: base >=4.8 && <5,- optparse-applicative,- time <1.6,- transformers,+ optparse-applicative < 0.20,+ time <1.15,+ transformers < 0.7, twentyseven default-language: Haskell2010+ ghc-options: -Wall Test-Suite test-twentyseven- type: detailed-0.9+ type: exitcode-stdio-1.0 hs-source-dirs: test- test-module: Test+ main-is: Test.hs other-extensions: LambdaCase build-depends: base >=4.8,- Cabal >=1.9.3,- cabal-test-quickcheck >=0.1.6,- HUnit-Plus >=1.1.0, QuickCheck >=2.8,+ tagged >= 0.8.9,+ tasty,+ tasty-quickcheck,+ tasty-hunit, split, vector, twentyseven default-language: Haskell2010+ ghc-options: -Wall -Wno-name-shadowing -Wno-missing-signatures -Wno-unused-top-binds