Skip to content

Commit 041a4d5

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 72ce25c + 118b72d commit 041a4d5

File tree

8 files changed

+248
-17
lines changed

8 files changed

+248
-17
lines changed

package.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ executables:
4747

4848
tests:
4949
icfpc2018-test:
50-
main: Spec.hs
50+
main: Test.hs
5151
source-dirs: test
5252
ghc-options:
5353
- -threaded

src/Algorithms.hs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import Control.Monad.State
66
import Data.Word
77
import Data.Binary (decodeFile)
88
import Text.Printf
9+
import qualified Data.Array.BitArray as BA
10+
import qualified Data.Set as S
911

1012
import Trace
1113
import Sim
@@ -44,9 +46,30 @@ fill bid dir p@(x,y,z) = do
4446
(neighbour, diff) <- findFreeNeighbour p
4547
let diff' = negateNear diff
4648
move bid neighbour
49+
grounded <- willBeGrounded p
50+
unless grounded $
51+
setHarmonics bid High
4752
issueFill bid diff'
53+
setHarmonics bid Low
4854
step
4955

56+
-- This would be too slow
57+
-- isGrounded :: P3 -> Generator Bool
58+
-- isGrounded p = do
59+
-- matrix <- gets gsFilled
60+
-- return $ go matrix S.empty p
61+
-- where
62+
-- go _ _ (_,0,_) = True
63+
-- go m visited p@(x,y,z) =
64+
-- case m BA.!? p of
65+
-- Nothing -> return False
66+
-- Just False -> return False
67+
-- Just True ->
68+
-- let neighbours = [(x+1, y, z), (x, y+1, z), (x, y, z+1),
69+
-- (x-1, y, z), (x, y-1, z), (x, y, z-1)]
70+
-- nonVisited = [neighbour | neighbour `S.notMember` visited]
71+
-- in or [go m (S.insert neighbour visited) neighbour | neighbour <- nonVisited]
72+
5073
makeLine :: Direction -> Resolution -> Word8 -> Word8 -> [P3]
5174
makeLine dir r y z =
5275
case dir of
@@ -98,10 +121,11 @@ dumbHighSolver modelPath tracePath = do
98121
model <- decodeFile modelPath
99122
let trace = makeTrace model $ do
100123
let bid = 0
101-
issue bid Flip
124+
-- issueFlip bid
102125
dumbFill bid
103126
move bid (0,0,0)
104-
issue bid Flip
127+
-- issueFlip bid
105128
issue bid Halt
129+
print trace
106130
writeTrace tracePath trace
107131

src/Generator.hs

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ type P3d = (Int16, Int16, Int16)
3939

4040
data GeneratorState = GS {
4141
gsModel :: ModelFile,
42-
gsFilled :: BA.BitArray P3,
42+
gsHarmonics :: Harmonics,
43+
gsFilled :: BA.BitArray P3, -- voxels that are already filled by generator
44+
gsGrounded :: BA.BitArray P3, -- grounded voxels
4345
gsStepNumber :: Step,
4446
gsAliveBots :: [(Step, [AliveBot])], -- which bots are alive. Record is to be added when set of bots is changed.
4547
gsBots :: Array BID BotState,
@@ -50,13 +52,14 @@ maxBID :: BID
5052
maxBID = 20
5153

5254
initState :: ModelFile -> GeneratorState
53-
initState model = GS model filled 0 [(0,[bid])] bots traces
55+
initState model = GS model Low filled grounded 0 [(0,[bid])] bots traces
5456
where
5557
bid = 0
5658
bots = array (0, maxBID) [(bid, Bot bid (0,0,0) []) | bid <- [0 .. maxBID]]
5759
traces = array (0, maxBID) [(bid, Seq.empty) | bid <- [0 .. maxBID]]
5860
r = mfResolution model
5961
filled = BA.array ((0,0,0), (r-1,r-1,r-1)) [((x,y,z), False) | x <- [0..r-1], y <- [0..r-1], z <- [0..r-1]]
62+
grounded = BA.array ((0,0,0), (r-1,r-1,r-1)) [((x,y,z), False) | x <- [0..r-1], y <- [0..r-1], z <- [0..r-1]]
6063

6164
type Generator a = State GeneratorState a
6265

@@ -89,6 +92,23 @@ issue bid cmd = do
8992
let trace' = trace |> cmd
9093
modify $ \st -> st {gsTraces = gsTraces st // [(bid, trace')]}
9194

95+
flipH :: Harmonics -> Harmonics
96+
flipH Low = High
97+
flipH High = Low
98+
99+
-- | Issue Flip command and remember resulting harmonics.
100+
issueFlip :: BID -> Generator ()
101+
issueFlip bid = do
102+
modify $ \st -> st {gsHarmonics = flipH (gsHarmonics st)}
103+
issue bid Flip
104+
105+
-- | Set harmonics to target value
106+
setHarmonics :: BID -> Harmonics -> Generator ()
107+
setHarmonics bid target = do
108+
current <- gets gsHarmonics
109+
when (current /= target) $
110+
issueFlip bid
111+
92112
nearPlus :: P3 -> NearDiff -> P3
93113
nearPlus (x,y,z) (NearDiff dx dy dz) = (x+fromIntegral dx, y+fromIntegral dy, z+fromIntegral dz)
94114

@@ -99,12 +119,47 @@ negateNear (NearDiff dx dy dz) = NearDiff (-dx) (-dy) (-dz)
99119
-- This will mark the voxel as filled in generator's state
100120
issueFill :: BID -> NearDiff -> Generator ()
101121
issueFill bid nd = do
102-
bot <- getBot bid
103-
let c' = nearPlus (_pos bot) nd
104-
filled <- isFilled c'
105-
if filled
106-
then fail $ "Voxel is already filled: " ++ show c'
107-
else issue bid $ Fill nd
122+
bot <- getBot bid
123+
let c' = nearPlus (_pos bot) nd
124+
filled <- isFilled c'
125+
if filled
126+
then fail $ "Voxel is already filled: " ++ show c'
127+
else do
128+
issue bid $ Fill nd
129+
modify $ \st -> st {gsFilled = gsFilled st BA.// [(c', True)]}
130+
updateGrounded c'
131+
where
132+
updateGrounded :: P3 -> Generator ()
133+
updateGrounded p@(x,y,z) = do
134+
filled <- gets gsFilled
135+
grounded <- gets gsGrounded
136+
let result = check filled grounded p
137+
modify $ \st -> st {gsGrounded = gsGrounded st BA.// [(p, result)]}
138+
where
139+
check _ _ (_,0,_) = True
140+
check filled grounded p@(x,y,z) =
141+
if filled BA.! p
142+
then
143+
let neighbours = [(x+1, y, z), (x, y+1, z), (x, y, z+1),
144+
(x-1, y, z), (x, y-1, z), (x, y, z-1)]
145+
in or [grounded BA.! neighbour | neighbour <- neighbours]
146+
else False
147+
148+
-- | Is voxel grounded?
149+
-- This works by definition, i.e. always returns False for non-filled voxels.
150+
isGrounded :: P3 -> Generator Bool
151+
isGrounded p = do
152+
grounded <- gets gsGrounded
153+
return $ grounded BA.! p
154+
155+
-- | Will voxel become grounded if we fill it?
156+
-- This checks if any neighbour voxel is grounded.
157+
willBeGrounded :: P3 -> Generator Bool
158+
willBeGrounded (x,y,z) = do
159+
grounded <- gets gsGrounded
160+
let neighbours = [(x+1, y, z), (x, y+1, z), (x, y, z+1),
161+
(x-1, y, z), (x, y-1, z), (x, y, z-1)]
162+
return $ or [fromMaybe False (grounded BA.!? neighbour) | neighbour <- neighbours]
108163

109164
-- | Switch to the next step.
110165
-- If we did not issue commands for some bots on current steps,

src/Optimizations.hs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
-- | Optimizations for sequences of commands.
2+
--
3+
-- Each optimizations replaces first occurrence of a pattern with an optimized
4+
-- version. It's up to you to figure out if the result is better than the
5+
-- input, and if you should re-run optimizations to find new occurrences of
6+
-- patterns.
7+
--
8+
-- WARNING: For now, these functions assume that there is only one nanobot!
9+
module Optimizations where
10+
11+
import Control.Monad (forM)
12+
13+
import Trace
14+
15+
import Debug.Trace
16+
17+
-- | Returns all possible optimizations of a given command sequence.
18+
optimize :: [Command] -> [[Command]]
19+
optimize [] = []
20+
optimize cmds = go [cmds] [cmds]
21+
where
22+
go :: [[Command]] -> [[Command]] -> [[Command]]
23+
go [] optimized = optimized
24+
go (t:stack) optimized =
25+
let
26+
new = map (\f -> f t) optimizers
27+
new' = filter (/= t) new
28+
in go (new' ++ stack) (new' ++ optimized)
29+
30+
optimizers :: [[Command] -> [Command]]
31+
optimizers = [splitLMove, mergeSMoves]
32+
33+
-- | Turns [LMove a b, Wait] into [SMove a, SMove b]
34+
--
35+
-- The latter consumes 4 less energy points than the former.
36+
--
37+
-- Note: it doesn't make sense to split [LMove a b] into two SMoves because the
38+
-- extra step will consume 3 R³ energy points, which is detrimental for any
39+
-- R greater than one.
40+
splitLMove :: [Command] -> [Command]
41+
splitLMove [] = []
42+
splitLMove ((LMove a b):Wait:cmds) =
43+
(SMove (fromShortLinDiff a)) : (SMove (fromShortLinDiff b)) : cmds
44+
splitLMove (cmd:cmds) = cmd : (splitLMove cmds)
45+
46+
-- | Turns [SMove a, SMove b] into [SMove x] if a and b are on the same axis
47+
-- and can be combined.
48+
mergeSMoves :: [Command] -> [Command]
49+
mergeSMoves [] = []
50+
mergeSMoves (m1@(SMove (LongLinDiff a1 d1)):m2@(SMove (LongLinDiff a2 d2)):cmds)
51+
| (a1 /= a2) || (d1 + d2 < -15) || (d1 + d2 > 15)
52+
= m1 : (mergeSMoves (m2:cmds))
53+
| otherwise =
54+
let d = d1 + d2
55+
in if d == 0
56+
then cmds
57+
else (SMove (LongLinDiff a1 d)) : cmds
58+
mergeSMoves (cmd:cmds) = cmd : (mergeSMoves cmds)

src/Trace.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ instance Coded Command where
167167

168168
| otherwise = error "Command.decode: invalid input"
169169

170+
-- | Converts short linear difference into a long linear difference.
171+
--
172+
-- This conversion is valid since any short linear difference satisfies
173+
-- mlen(ld) ≤ 5, which also satisfies LongLinDiff's predicate of mlen(ld) ≤ 15.
174+
fromShortLinDiff :: ShortLinDiff -> LongLinDiff
175+
fromShortLinDiff (ShortLinDiff axis diff) = LongLinDiff axis diff
170176

171177
encodeL :: Coded a => a -> L.ByteString
172178
encodeL x = runPutL . runEncode $ encode x >> flush

test/Test.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Test.Tasty
2+
3+
import TestSpec
4+
import TestOptimizations
5+
6+
main :: IO ()
7+
main = defaultMain tests
8+
9+
tests :: TestTree
10+
tests = testGroup "Tests" [specExamples, codec, optimizations]

test/TestOptimizations.hs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
module TestOptimizations where
2+
3+
import Test.Tasty
4+
import Test.Tasty.HUnit
5+
6+
import Optimizations
7+
import Trace
8+
9+
optimizations :: TestTree
10+
optimizations = testGroup "Optimizations"
11+
[ testSplitLMove
12+
, testMergeSMoves
13+
]
14+
15+
testSplitLMove :: TestTree
16+
testSplitLMove = testGroup "splitLMove"
17+
[
18+
testCase "Empty input" $ splitLMove [] @?= []
19+
20+
, testCase "Unoptimal input" $
21+
let
22+
a = ShortLinDiff X 3
23+
a' = fromShortLinDiff a
24+
b = ShortLinDiff Z (-1)
25+
b' = fromShortLinDiff b
26+
in splitLMove [LMove a b, Wait] @?= [SMove a', SMove b']
27+
28+
, testCase "Only first occurrence is optimized" $
29+
let
30+
a = ShortLinDiff X 3
31+
a' = fromShortLinDiff a
32+
b = ShortLinDiff Z (-1)
33+
b' = fromShortLinDiff b
34+
in
35+
splitLMove [LMove a b, Wait, LMove a b, Wait]
36+
@?= [SMove a', SMove b', LMove a b, Wait]
37+
]
38+
39+
testMergeSMoves :: TestTree
40+
testMergeSMoves = testGroup "mergeSMoves"
41+
[ testCase "Empty input" $ mergeSMoves [] @?= []
42+
43+
, testCase "SMoves on different axis" $
44+
let
45+
m1 = SMove (LongLinDiff X 13)
46+
m2 = SMove (LongLinDiff Y (-11))
47+
in mergeSMoves [m1, m2] @?= [m1, m2]
48+
49+
, testCase "Long SMoves can't be combined" $
50+
let
51+
m1 = SMove (LongLinDiff X 13)
52+
m2 = SMove (LongLinDiff X 11)
53+
in mergeSMoves [m1, m2] @?= [m1, m2]
54+
55+
, testCase "Unoptimal input.1" $
56+
let
57+
m1 = SMove (LongLinDiff X 4)
58+
m2 = SMove (LongLinDiff X 3)
59+
result = SMove (LongLinDiff X 7)
60+
in mergeSMoves [m1, m2] @?= [result]
61+
, testCase "Unoptimal input.2" $
62+
let
63+
m1 = SMove (LongLinDiff Z (-11))
64+
m2 = SMove (LongLinDiff Z (-4))
65+
result = SMove (LongLinDiff Z (-15))
66+
in mergeSMoves [m1, m2] @?= [result]
67+
, testCase "Unoptimal input.3" $
68+
let
69+
m1 = SMove (LongLinDiff Y (-3))
70+
m2 = SMove (LongLinDiff Y 3)
71+
in mergeSMoves [m1, m2] @?= []
72+
73+
, testCase "Only first occurrence is optimized" $
74+
let
75+
m1 = SMove (LongLinDiff X 4)
76+
m2 = SMove (LongLinDiff X 3)
77+
combined = SMove (LongLinDiff X 7)
78+
in mergeSMoves [m1, m2, m1, m2] @?= [combined, m1, m2]
79+
]

test/Spec.hs renamed to test/TestSpec.hs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
{-# LANGUAGE BinaryLiterals #-}
22

3+
module TestSpec (
4+
specExamples,
5+
codec
6+
) where
7+
38
import Test.Tasty
49
import Test.Tasty.HUnit
510

@@ -15,12 +20,6 @@ assert :: String -> Bool -> IO ()
1520
assert _ True = return ()
1621
assert message False = fail message
1722

18-
main :: IO ()
19-
main = defaultMain tests
20-
21-
tests :: TestTree
22-
tests = testGroup "Tests" [specExamples, codec]
23-
2423
specExamples :: TestTree
2524
specExamples = testGroup "Specification examples"
2625
[

0 commit comments

Comments
 (0)