mirror of
https://github.com/swarm-game/swarm.git
synced 2024-09-11 14:46:33 +03:00
recognize structures with rotation (#1678)
Closes #1644. The `"recognize"` property in scenario `.yaml` files is changed from a boolean to a list of "up" directions. The structure recognizer adds a rotated copy of each supported orientation to its automaton. Rotational symmetry is accounted for to avoid duplicate work in the recognizer. Also in this PR: * Add cardinal directions to the JSON schema * Tetromino packing challenge scenario ## Demos scripts/run-tests.sh --test-arguments '--pattern "1644-rotated"' ### Structures dialog ![Screenshot from 2023-12-10 18-47-01](https://github.com/swarm-game/swarm/assets/261693/3904b66e-dd22-455b-8b68-5913021f806a) ### Tetromino packing scripts/play.sh -i data/scenarios/Challenges/pack-tetrominoes.yaml --autoplay ![Screenshot from 2023-12-09 23-11-00](https://github.com/swarm-game/swarm/assets/261693/0ad7c0ce-3553-4ad5-a927-82bbfdbe63d8)
This commit is contained in:
parent
e227230bbb
commit
13ae996306
@ -16,6 +16,7 @@ wave.yaml
|
||||
wolf-goat-cabbage.yaml
|
||||
blender.yaml
|
||||
friend.yaml
|
||||
pack-tetrominoes.yaml
|
||||
Mazes
|
||||
Ranching
|
||||
Sokoban
|
||||
|
@ -125,7 +125,7 @@ solution: |
|
||||
run "scenarios/Challenges/Ranching/_beekeeping/solution.sw"
|
||||
structures:
|
||||
- name: beehive
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'-': [dirt, honey frame]
|
||||
@ -137,7 +137,7 @@ structures:
|
||||
b---b
|
||||
bbbbb
|
||||
- name: mead hall
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'w': [dirt, wall]
|
||||
|
67
data/scenarios/Challenges/_pack-tetrominoes/solution.sw
Normal file
67
data/scenarios/Challenges/_pack-tetrominoes/solution.sw
Normal file
@ -0,0 +1,67 @@
|
||||
move;
|
||||
place "j-tile";
|
||||
move;
|
||||
place "j-tile";
|
||||
turn left;
|
||||
move;
|
||||
place "j-tile";
|
||||
move;
|
||||
place "j-tile";
|
||||
|
||||
turn back;
|
||||
move; move;
|
||||
turn left;
|
||||
move;
|
||||
|
||||
place "t-tile";
|
||||
move;
|
||||
place "t-tile";
|
||||
turn left;
|
||||
move;
|
||||
place "t-tile";
|
||||
turn back;
|
||||
move;
|
||||
turn left;
|
||||
move;
|
||||
place "t-tile";
|
||||
|
||||
turn back;
|
||||
move;
|
||||
move;
|
||||
turn right;
|
||||
move;
|
||||
|
||||
place "z-tile";
|
||||
move;
|
||||
place "z-tile";
|
||||
turn right;
|
||||
move;
|
||||
place "z-tile";
|
||||
turn left;
|
||||
move;
|
||||
place "z-tile";
|
||||
|
||||
turn left;
|
||||
move;
|
||||
|
||||
place "i-tile";
|
||||
move;
|
||||
place "i-tile";
|
||||
move;
|
||||
place "i-tile";
|
||||
move;
|
||||
place "i-tile";
|
||||
|
||||
turn left;
|
||||
move;
|
||||
|
||||
place "q-tile";
|
||||
move;
|
||||
turn left;
|
||||
place "q-tile";
|
||||
move;
|
||||
turn left;
|
||||
place "q-tile";
|
||||
move;
|
||||
turn left;
|
||||
place "q-tile";
|
147
data/scenarios/Challenges/pack-tetrominoes.yaml
Normal file
147
data/scenarios/Challenges/pack-tetrominoes.yaml
Normal file
@ -0,0 +1,147 @@
|
||||
version: 1
|
||||
name: Tetrominoes
|
||||
description: |
|
||||
Pack tetrominoes into a rectangle
|
||||
creative: false
|
||||
attrs:
|
||||
- name: q-tile
|
||||
fg: "#ffff00"
|
||||
- name: z-tile
|
||||
fg: "#ff0000"
|
||||
- name: i-tile
|
||||
fg: "#00ffff"
|
||||
- name: j-tile
|
||||
fg: "#0000ff"
|
||||
- name: t-tile
|
||||
fg: "#ff00ff"
|
||||
objectives:
|
||||
- teaser: Place all
|
||||
goal:
|
||||
- |
|
||||
Place all five tetrominoes.
|
||||
condition: |
|
||||
def found = \s.
|
||||
fs <- structure s 0;
|
||||
return $ case fs (\_. false) (\_. true);
|
||||
end;
|
||||
|
||||
foundT <- found "tee";
|
||||
foundJ <- found "jay";
|
||||
foundI <- found "line";
|
||||
foundZ <- found "zee";
|
||||
foundQ <- found "square";
|
||||
return $ foundT && foundJ && foundI && foundZ && foundQ;
|
||||
robots:
|
||||
- name: base
|
||||
dir: [1, 0]
|
||||
devices:
|
||||
- grabber
|
||||
- treads
|
||||
inventory:
|
||||
- [4, z-tile]
|
||||
- [4, j-tile]
|
||||
- [4, i-tile]
|
||||
- [4, q-tile]
|
||||
- [4, t-tile]
|
||||
entities:
|
||||
- name: q-tile
|
||||
display:
|
||||
char: 'q'
|
||||
attr: q-tile
|
||||
description:
|
||||
- q-tile
|
||||
properties: [known, portable]
|
||||
- name: j-tile
|
||||
display:
|
||||
char: 'j'
|
||||
attr: j-tile
|
||||
description:
|
||||
- j-tile
|
||||
properties: [known, portable]
|
||||
- name: i-tile
|
||||
display:
|
||||
char: 'i'
|
||||
attr: i-tile
|
||||
description:
|
||||
- i-tile
|
||||
properties: [known, portable]
|
||||
- name: t-tile
|
||||
display:
|
||||
char: 't'
|
||||
attr: t-tile
|
||||
description:
|
||||
- t-tile
|
||||
properties: [known, portable]
|
||||
- name: z-tile
|
||||
display:
|
||||
char: 'z'
|
||||
attr: z-tile
|
||||
description:
|
||||
- z-tile
|
||||
properties: [known, portable]
|
||||
solution: |
|
||||
run "scenarios/Challenges/_pack-tetrominoes/solution.sw"
|
||||
structures:
|
||||
- name: tee
|
||||
recognize: [north, south, east, west]
|
||||
description: "Tee tetromino"
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
'x': [stone, t-tile]
|
||||
map: |
|
||||
.x.
|
||||
xxx
|
||||
- name: square
|
||||
recognize: [north]
|
||||
description: "Square tetromino"
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
'x': [stone, q-tile]
|
||||
map: |
|
||||
xx
|
||||
xx
|
||||
- name: line
|
||||
recognize: [north, east]
|
||||
description: "Line tetromino"
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
'x': [stone, i-tile]
|
||||
map: |
|
||||
xxxx
|
||||
- name: jay
|
||||
recognize: [north, east, south, west]
|
||||
description: "Jay tetromino"
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
'x': [stone, j-tile]
|
||||
map: |
|
||||
xxx
|
||||
..x
|
||||
- name: zee
|
||||
recognize: [north, east]
|
||||
description: "Zee tetromino"
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
'x': [stone, z-tile]
|
||||
map: |
|
||||
xx.
|
||||
.xx
|
||||
known: [boulder]
|
||||
world:
|
||||
name: root
|
||||
dsl: |
|
||||
{boulder}
|
||||
palette:
|
||||
'.': [grass, erase]
|
||||
'B': [grass, erase, base]
|
||||
upperleft: [0, 0]
|
||||
map: |
|
||||
......
|
||||
......
|
||||
......
|
||||
B.....
|
@ -12,3 +12,5 @@
|
||||
1575-interior-entity-placement.yaml
|
||||
1575-floorplan-command.yaml
|
||||
1575-bounding-box-overlap.yaml
|
||||
1644-rotated-recognition.yaml
|
||||
1644-rotated-preplacement-recognition.yaml
|
||||
|
@ -53,7 +53,7 @@ solution: |
|
||||
doN 3 (place "boulder"; move;);
|
||||
structures:
|
||||
- name: chevron
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'g': [stone, boulder]
|
||||
|
@ -4,7 +4,7 @@ description: |
|
||||
Hit *F6* to view the recognizable structures.
|
||||
|
||||
Only the subset of the structures marked with
|
||||
*recognize: true* are browseable.
|
||||
*recognize: [north]* are browseable.
|
||||
In particular, the `donut`{=structure} structure is placed
|
||||
in the map but not displayed in the *F6* dialog.
|
||||
creative: false
|
||||
@ -53,7 +53,7 @@ structures:
|
||||
@@@@@
|
||||
.@@@.
|
||||
- name: diamond
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
description: "A diamond pattern of flowers"
|
||||
structure:
|
||||
mask: '.'
|
||||
@ -68,7 +68,7 @@ structures:
|
||||
..xxx..
|
||||
...x...
|
||||
- name: contraption
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
description: "A device for assembling useful widgets"
|
||||
structure:
|
||||
mask: '.'
|
||||
@ -83,7 +83,7 @@ structures:
|
||||
lIIIgg
|
||||
rlllgg
|
||||
- name: precious
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
@ -96,7 +96,7 @@ structures:
|
||||
gsq
|
||||
qqm
|
||||
- name: smallish
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -37,7 +37,7 @@ solution: |
|
||||
);
|
||||
structures:
|
||||
- name: green_jewel
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'g': [stone, pixel (G)]
|
||||
|
@ -54,7 +54,7 @@ solution: |
|
||||
place "silver";
|
||||
structures:
|
||||
- name: chessboard
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -51,7 +51,7 @@ solution: |
|
||||
place "gold";
|
||||
structures:
|
||||
- name: chessboard
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -47,7 +47,7 @@ solution: |
|
||||
mkRows height width;
|
||||
structures:
|
||||
- name: wooden box
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'b': [stone, board]
|
||||
|
@ -33,7 +33,7 @@ solution: |
|
||||
place "mithril";
|
||||
structures:
|
||||
- name: precious
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
@ -46,7 +46,7 @@ structures:
|
||||
gsq
|
||||
qqm
|
||||
- name: smallish
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -76,7 +76,7 @@ solution: |
|
||||
place x;
|
||||
structures:
|
||||
- name: pigpen
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'b': [stone, board]
|
||||
|
@ -46,7 +46,7 @@ robots:
|
||||
- treads
|
||||
structures:
|
||||
- name: double ring
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
's': [ice, tree]
|
||||
@ -68,7 +68,7 @@ structures:
|
||||
.s.
|
||||
...
|
||||
- name: flowerbox
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'f': [ice, flower]
|
||||
|
@ -36,7 +36,7 @@ solution: |
|
||||
place "gold";
|
||||
structures:
|
||||
- name: large
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
@ -47,7 +47,7 @@ structures:
|
||||
ggs
|
||||
ggs
|
||||
- name: small
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -37,7 +37,7 @@ solution: |
|
||||
place "gold";
|
||||
structures:
|
||||
- name: topleft
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
@ -48,7 +48,7 @@ structures:
|
||||
gg
|
||||
gg
|
||||
- name: bottomright
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -40,7 +40,7 @@ solution: |
|
||||
noop;
|
||||
structures:
|
||||
- name: red_jewel
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'r': [stone, pixel (R)]
|
||||
@ -49,7 +49,7 @@ structures:
|
||||
rrr
|
||||
rrr
|
||||
- name: green_jewel
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
palette:
|
||||
'g': [stone, pixel (G)]
|
||||
|
@ -36,7 +36,7 @@ solution: |
|
||||
grab;
|
||||
structures:
|
||||
- name: chessboard
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -54,7 +54,7 @@ solution: |
|
||||
swap "pixel (B)";
|
||||
structures:
|
||||
- name: red_jewel
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
@ -68,7 +68,7 @@ structures:
|
||||
gsssg
|
||||
ggggg
|
||||
- name: green_jewel
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
@ -82,7 +82,7 @@ structures:
|
||||
gsssg
|
||||
ggggg
|
||||
- name: blue_jewel
|
||||
recognize: true
|
||||
recognize: [north]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -0,0 +1,47 @@
|
||||
version: 1
|
||||
name: Rotated pre-placed structure recognition
|
||||
description: |
|
||||
Pre-placed structure recognition with rotation
|
||||
creative: false
|
||||
objectives:
|
||||
- teaser: Have structure
|
||||
goal:
|
||||
- |
|
||||
Have a `tee`{=structure} structure
|
||||
condition: |
|
||||
foundStructure <- structure "tee" 0;
|
||||
return $ case foundStructure (\_. false) (\_. true);
|
||||
robots:
|
||||
- name: base
|
||||
dir: [1, 0]
|
||||
solution: |
|
||||
noop;
|
||||
structures:
|
||||
- name: tee
|
||||
recognize: [north, south, east, west]
|
||||
description: "A tee pattern of flowers"
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
'x': [stone, flower]
|
||||
map: |
|
||||
.x.
|
||||
xxx
|
||||
known: [flower]
|
||||
world:
|
||||
name: root
|
||||
dsl: |
|
||||
{blank}
|
||||
palette:
|
||||
'.': [grass]
|
||||
'B': [grass, null, base]
|
||||
upperleft: [0, 0]
|
||||
placements:
|
||||
- src: tee
|
||||
offset: [2, 0]
|
||||
orient:
|
||||
up: east
|
||||
map: |
|
||||
B....
|
||||
.....
|
||||
.....
|
@ -0,0 +1,51 @@
|
||||
version: 1
|
||||
name: Rotated structure recognition
|
||||
description: |
|
||||
Structure recognition with rotation
|
||||
creative: false
|
||||
objectives:
|
||||
- teaser: Build structure
|
||||
goal:
|
||||
- |
|
||||
Build a `tee`{=structure} structure
|
||||
condition: |
|
||||
foundStructure <- structure "tee" 0;
|
||||
return $ case foundStructure (\_. false) (\_. true);
|
||||
robots:
|
||||
- name: base
|
||||
dir: [1, 0]
|
||||
devices:
|
||||
- grabber
|
||||
- treads
|
||||
inventory:
|
||||
- [4, flower]
|
||||
solution: |
|
||||
move; move;
|
||||
turn right;
|
||||
move; move;
|
||||
place "flower";
|
||||
structures:
|
||||
- name: tee
|
||||
recognize: [north, south, east, west]
|
||||
description: "A tee pattern of flowers"
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
'x': [stone, flower]
|
||||
map: |
|
||||
.x.
|
||||
xxx
|
||||
known: [flower]
|
||||
world:
|
||||
name: root
|
||||
dsl: |
|
||||
{blank}
|
||||
palette:
|
||||
'.': [grass]
|
||||
'x': [stone, flower]
|
||||
'B': [grass, null, base]
|
||||
upperleft: [0, 0]
|
||||
map: |
|
||||
B.x..
|
||||
..xx.
|
||||
.....
|
@ -10,7 +10,7 @@ robots:
|
||||
- treads
|
||||
structures:
|
||||
- name: red_jewel
|
||||
recognize: true
|
||||
recognize: [east]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
|
@ -0,0 +1,29 @@
|
||||
version: 1
|
||||
name: Structure recognizer - redundant symmetries
|
||||
description: |
|
||||
Prevent orientations from being supplied
|
||||
for recognition when they are redundant by
|
||||
rotational symmetry
|
||||
creative: false
|
||||
robots:
|
||||
- name: base
|
||||
dir: [1, 0]
|
||||
structures:
|
||||
- name: domino
|
||||
recognize: [east, north, west]
|
||||
structure:
|
||||
mask: '.'
|
||||
palette:
|
||||
'r': [stone, tree]
|
||||
map: |
|
||||
r
|
||||
r
|
||||
known: [tree]
|
||||
world:
|
||||
name: root
|
||||
palette:
|
||||
'.': [grass]
|
||||
'B': [grass, null, base]
|
||||
upperleft: [0, 0]
|
||||
map: |
|
||||
B
|
6
data/schema/directions.json
Normal file
6
data/schema/directions.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/directions.json",
|
||||
"title": "Directions",
|
||||
"enum": ["north", "west", "south", "east"]
|
||||
}
|
@ -15,8 +15,11 @@
|
||||
"description": "Description of this substructure"
|
||||
},
|
||||
"recognize": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this structure participates in automatic recognition when constructed"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "directions.json"
|
||||
},
|
||||
"description": "Orientations for which this structure participates in automatic recognition when constructed"
|
||||
},
|
||||
"structure": {
|
||||
"$ref": "structure.json"
|
||||
|
@ -7,7 +7,7 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"up": {
|
||||
"type": "string"
|
||||
"$ref": "directions.json"
|
||||
},
|
||||
"flip": {
|
||||
"type": "boolean"
|
||||
|
@ -84,6 +84,8 @@ import Swarm.Game.Scenario.Topography.Cell
|
||||
import Swarm.Game.Scenario.Topography.Navigation.Portal
|
||||
import Swarm.Game.Scenario.Topography.Navigation.Waypoint (Parentage (..))
|
||||
import Swarm.Game.Scenario.Topography.Structure qualified as Structure
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Symmetry
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (SymmetryAnnotatedGrid (..))
|
||||
import Swarm.Game.Scenario.Topography.WorldDescription
|
||||
import Swarm.Game.Universe
|
||||
import Swarm.Game.World.Load (loadWorlds)
|
||||
@ -100,7 +102,7 @@ import System.Directory (doesFileExist)
|
||||
import System.FilePath ((<.>), (</>))
|
||||
|
||||
data StaticStructureInfo = StaticStructureInfo
|
||||
{ _structureDefs :: [Structure.NamedGrid (Maybe Cell)]
|
||||
{ _structureDefs :: [SymmetryAnnotatedGrid (Maybe Cell)]
|
||||
, _staticPlacements :: M.Map SubworldName [Structure.LocatedStructure]
|
||||
}
|
||||
deriving (Show)
|
||||
@ -109,7 +111,7 @@ makeLensesNoSigs ''StaticStructureInfo
|
||||
|
||||
-- | Structure templates that may be auto-recognized when constructed
|
||||
-- by a robot
|
||||
structureDefs :: Lens' StaticStructureInfo [Structure.NamedGrid (Maybe Cell)]
|
||||
structureDefs :: Lens' StaticStructureInfo [SymmetryAnnotatedGrid (Maybe Cell)]
|
||||
|
||||
-- | A record of the static placements of structures, so that they can be
|
||||
-- added to the "recognized" list upon scenario initialization
|
||||
@ -226,8 +228,12 @@ instance FromJSONE (EntityMap, WorldMap) Scenario where
|
||||
$ NE.toList allWorlds
|
||||
|
||||
let mergedNavigation = Navigation mergedWaypoints mergedPortals
|
||||
structureInfo =
|
||||
StaticStructureInfo (filter Structure.recognize namedGrids)
|
||||
recognizableGrids = filter Structure.isRecognizable namedGrids
|
||||
|
||||
symmetryAnnotatedGrids <- mapM checkSymmetry recognizableGrids
|
||||
|
||||
let structureInfo =
|
||||
StaticStructureInfo symmetryAnnotatedGrids
|
||||
. M.fromList
|
||||
. NE.toList
|
||||
$ NE.map (worldName &&& placedStructures) allWorlds
|
||||
|
@ -17,6 +17,8 @@ import Data.Either.Extra (maybeToEither)
|
||||
import Data.Foldable (foldrM)
|
||||
import Data.Map qualified as M
|
||||
import Data.Maybe (catMaybes)
|
||||
import Data.Set (Set)
|
||||
import Data.Set qualified as Set
|
||||
import Data.Text (Text)
|
||||
import Data.Text qualified as T
|
||||
import Data.Yaml as Y
|
||||
@ -28,7 +30,8 @@ import Swarm.Game.Scenario.Topography.Cell
|
||||
import Swarm.Game.Scenario.Topography.Navigation.Waypoint
|
||||
import Swarm.Game.Scenario.Topography.Placement
|
||||
import Swarm.Game.Scenario.Topography.WorldPalette
|
||||
import Swarm.Util (failT, quote, showT)
|
||||
import Swarm.Language.Direction (AbsoluteDir, directionJsonModifier)
|
||||
import Swarm.Util (commaList, failT, quote, showT)
|
||||
import Swarm.Util.Yaml
|
||||
import Witch (into)
|
||||
|
||||
@ -39,14 +42,21 @@ newtype Grid c = Grid
|
||||
|
||||
data NamedArea a = NamedArea
|
||||
{ name :: StructureName
|
||||
, recognize :: Bool
|
||||
, recognize :: Set AbsoluteDir
|
||||
-- ^ whether this structure should be registered for automatic recognition
|
||||
-- and which orientations shall be recognized.
|
||||
-- The supplied direction indicates which cardinal direction the
|
||||
-- original map's "North" has been re-oriented to.
|
||||
-- E.g., 'DWest' represents a rotation of 90 degrees counter-clockwise.
|
||||
, description :: Maybe Text
|
||||
-- ^ will be UI-facing only if this is a recognizable structure
|
||||
, structure :: a
|
||||
}
|
||||
deriving (Eq, Show, Functor)
|
||||
|
||||
isRecognizable :: NamedArea a -> Bool
|
||||
isRecognizable = not . null . recognize
|
||||
|
||||
type NamedGrid c = NamedArea (Grid c)
|
||||
|
||||
type NamedStructure c = NamedArea (PStructure c)
|
||||
@ -57,7 +67,7 @@ instance FromJSONE (EntityMap, RobotMap) (NamedArea (PStructure (Maybe Cell))) w
|
||||
parseJSONE = withObjectE "named structure" $ \v -> do
|
||||
NamedArea
|
||||
<$> liftE (v .: "name")
|
||||
<*> liftE (v .:? "recognize" .!= False)
|
||||
<*> liftE (v .:? "recognize" .!= mempty)
|
||||
<*> liftE (v .:? "description")
|
||||
<*> v
|
||||
..: "structure"
|
||||
@ -78,13 +88,14 @@ data Placed c = Placed Placement (NamedStructure c)
|
||||
-- | For use in registering recognizable pre-placed structures
|
||||
data LocatedStructure = LocatedStructure
|
||||
{ placedName :: StructureName
|
||||
, upDirection :: AbsoluteDir
|
||||
, cornerLoc :: Location
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
instance HasLocation LocatedStructure where
|
||||
modifyLoc f (LocatedStructure x originalLoc) =
|
||||
LocatedStructure x $ f originalLoc
|
||||
modifyLoc f (LocatedStructure x y originalLoc) =
|
||||
LocatedStructure x y $ f originalLoc
|
||||
|
||||
data MergedStructure c = MergedStructure [[c]] [LocatedStructure] [Originated Waypoint]
|
||||
|
||||
@ -159,8 +170,8 @@ mergeStructures ::
|
||||
Either Text (MergedStructure (Maybe a))
|
||||
mergeStructures inheritedStrucDefs parentPlacement (Structure origArea subStructures subPlacements subWaypoints) = do
|
||||
overlays <- elaboratePlacement parentPlacement $ mapM g subPlacements
|
||||
let wrapPlacement (Placed z ns) = LocatedStructure (name ns) $ offset z
|
||||
wrappedOverlays = map wrapPlacement $ filter (\(Placed _ ns) -> recognize ns) overlays
|
||||
let wrapPlacement (Placed z ns) = LocatedStructure (name ns) (up $ orient z) $ offset z
|
||||
wrappedOverlays = map wrapPlacement $ filter (\(Placed _ ns) -> isRecognizable ns) overlays
|
||||
foldrM
|
||||
(overlaySingleStructure structureMap)
|
||||
(MergedStructure origArea wrappedOverlays originatedWaypoints)
|
||||
@ -176,14 +187,34 @@ mergeStructures inheritedStrucDefs parentPlacement (Structure origArea subStruct
|
||||
maybeToEither
|
||||
(T.unwords ["Could not look up structure", quote n])
|
||||
$ sequenceA (placement, M.lookup sName structureMap)
|
||||
when (recognize ns && orientation /= defaultOrientation) $
|
||||
Left $
|
||||
T.unwords
|
||||
[ "Recognizable structure"
|
||||
, quote n
|
||||
, "must use default orientation."
|
||||
]
|
||||
let placementDirection = up orientation
|
||||
recognizedOrientations = recognize ns
|
||||
when (isRecognizable ns) $ do
|
||||
when (flipped orientation) $
|
||||
Left $
|
||||
T.unwords
|
||||
[ "Placing recognizable structure"
|
||||
, quote n
|
||||
, "with flipped orientation is not supported."
|
||||
]
|
||||
|
||||
-- Redundant orientations by rotational symmetry are accounted
|
||||
-- for at scenario parse time
|
||||
when (Set.notMember placementDirection recognizedOrientations) $
|
||||
Left $
|
||||
T.unwords
|
||||
[ "Placing recognizable structure"
|
||||
, quote n
|
||||
, "with"
|
||||
, renderDir placementDirection
|
||||
, "orientation is not supported."
|
||||
, "Try"
|
||||
, commaList $ map renderDir $ Set.toList recognizedOrientations
|
||||
, "instead."
|
||||
]
|
||||
return $ uncurry Placed t
|
||||
where
|
||||
renderDir = quote . T.pack . directionJsonModifier . show
|
||||
|
||||
instance FromJSONE (EntityMap, RobotMap) (PStructure (Maybe Cell)) where
|
||||
parseJSONE = withObjectE "structure definition" $ \v -> do
|
||||
|
@ -46,12 +46,19 @@ data ParticipatingEntity = ParticipatingEntity
|
||||
}
|
||||
deriving (Generic, ToJSON)
|
||||
|
||||
data IntactPlacementLog = IntactPlacementLog
|
||||
{ isIntact :: Bool
|
||||
, sName :: StructureName
|
||||
, locUpperLeft :: Cosmic Location
|
||||
}
|
||||
deriving (Generic, ToJSON)
|
||||
|
||||
data SearchLog
|
||||
= FoundParticipatingEntity ParticipatingEntity
|
||||
| StructureRemoved StructureName
|
||||
| FoundRowCandidates [FoundRowCandidate]
|
||||
| FoundCompleteStructureCandidates [StructureName]
|
||||
| IntactStaticPlacement [(Bool, StructureName, Cosmic Location)]
|
||||
| IntactStaticPlacement [IntactPlacementLog]
|
||||
deriving (Generic)
|
||||
|
||||
instance ToJSON SearchLog where
|
||||
|
@ -37,7 +37,7 @@ module Swarm.Game.Scenario.Topography.Structure.Recognition.Precompute (
|
||||
-- * Helper functions
|
||||
populateStaticFoundStructures,
|
||||
getEntityGrid,
|
||||
extractGrid,
|
||||
extractGrids,
|
||||
lookupStaticPlacements,
|
||||
) where
|
||||
|
||||
@ -49,14 +49,17 @@ import Data.Map qualified as M
|
||||
import Data.Maybe (catMaybes, mapMaybe)
|
||||
import Data.Semigroup (sconcat)
|
||||
import Data.Set qualified as S
|
||||
import Data.Set qualified as Set
|
||||
import Data.Tuple (swap)
|
||||
import Swarm.Game.Entity (Entity, entityName)
|
||||
import Swarm.Game.Scenario (StaticStructureInfo (..))
|
||||
import Swarm.Game.Scenario.Topography.Cell
|
||||
import Swarm.Game.Scenario.Topography.Placement (Orientation (..), applyOrientationTransform)
|
||||
import Swarm.Game.Scenario.Topography.Structure
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type
|
||||
import Swarm.Game.Universe (Cosmic (..))
|
||||
import Swarm.Language.Direction (AbsoluteDir)
|
||||
import Swarm.Util (binTuples, histogram)
|
||||
import Swarm.Util.Erasable (erasableToMaybe)
|
||||
import Text.AhoCorasick
|
||||
@ -96,7 +99,7 @@ mkRowLookup neList =
|
||||
concatMap (concatMap catMaybes . fst) tuples
|
||||
|
||||
deriveRowOffsets :: StructureRow -> InspectionOffsets
|
||||
deriveRowOffsets (StructureRow (StructureWithGrid _ g) rwIdx _) =
|
||||
deriveRowOffsets (StructureRow (StructureWithGrid _ _ g) rwIdx _) =
|
||||
mkOffsets rwIdx g
|
||||
|
||||
bounds = sconcat $ NE.map deriveRowOffsets neList
|
||||
@ -167,28 +170,42 @@ mkEntityLookup grids =
|
||||
catMaybes $
|
||||
zipWith (\idx -> fmap (PositionWithinRow idx r,)) [0 ..] rowMembers
|
||||
|
||||
mkAutomatons :: [NamedGrid (Maybe Cell)] -> RecognizerAutomatons
|
||||
-- | Create Aho-Corasick matchers that will recognize all of the
|
||||
-- provided structure definitions
|
||||
mkAutomatons :: [SymmetryAnnotatedGrid (Maybe Cell)] -> RecognizerAutomatons
|
||||
mkAutomatons xs =
|
||||
RecognizerAutomatons
|
||||
infos
|
||||
(mkEntityLookup grids)
|
||||
(mkEntityLookup rotatedGrids)
|
||||
where
|
||||
grids = map extractGrid xs
|
||||
rotatedGrids = concatMap (extractGrids . namedGrid) xs
|
||||
|
||||
process g = StructureInfo g . histogram . concatMap catMaybes $ entityGrid g
|
||||
infos = M.fromList $ map (name . originalDefinition &&& process) grids
|
||||
process g = StructureInfo g (getEntityGrid $ structure $ namedGrid g) . histogram . concatMap catMaybes . getEntityGrid . structure $ namedGrid g
|
||||
infos = M.fromList $ map (name . namedGrid &&& process) xs
|
||||
|
||||
extractGrid :: NamedGrid (Maybe Cell) -> StructureWithGrid
|
||||
extractGrid x = StructureWithGrid x $ getEntityGrid $ structure x
|
||||
extractOrientedGrid :: NamedGrid (Maybe Cell) -> AbsoluteDir -> StructureWithGrid
|
||||
extractOrientedGrid x d = StructureWithGrid x d $ getEntityGrid g'
|
||||
where
|
||||
Grid rows = structure x
|
||||
g' = Grid $ applyOrientationTransform (Orientation d False) rows
|
||||
|
||||
-- | At this point, we have already ensured that orientations
|
||||
-- redundant by rotational symmetry have been excluded
|
||||
-- (i.e. at Scenario validation time).
|
||||
extractGrids :: NamedGrid (Maybe Cell) -> [StructureWithGrid]
|
||||
extractGrids x = map (extractOrientedGrid x) $ Set.toList $ recognize x
|
||||
|
||||
-- | The output list of 'FoundStructure' records is not yet
|
||||
-- vetted; the 'ensureStructureIntact' function will subsequently
|
||||
-- filter this list.
|
||||
lookupStaticPlacements :: StaticStructureInfo -> [FoundStructure]
|
||||
lookupStaticPlacements (StaticStructureInfo structDefs thePlacements) =
|
||||
concatMap f $ M.toList thePlacements
|
||||
where
|
||||
definitionMap = M.fromList $ map (name &&& id) structDefs
|
||||
definitionMap = M.fromList $ map ((name &&& id) . namedGrid) structDefs
|
||||
|
||||
f (subworldName, locatedList) = mapMaybe g locatedList
|
||||
where
|
||||
g (LocatedStructure theName loc) = do
|
||||
g (LocatedStructure theName d loc) = do
|
||||
sGrid <- M.lookup theName definitionMap
|
||||
return $ FoundStructure (extractGrid sGrid) $ Cosmic subworldName loc
|
||||
return $ FoundStructure (extractOrientedGrid sGrid d) $ Cosmic subworldName loc
|
||||
|
@ -0,0 +1,69 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
-- |
|
||||
-- SPDX-License-Identifier: BSD-3-Clause
|
||||
--
|
||||
-- Symmetry analysis for structure recognizer.
|
||||
module Swarm.Game.Scenario.Topography.Structure.Recognition.Symmetry where
|
||||
|
||||
import Control.Monad (unless, when)
|
||||
import Data.Map qualified as M
|
||||
import Data.Set qualified as Set
|
||||
import Data.Text qualified as T
|
||||
import Swarm.Game.Scenario.Topography.Placement (Orientation (..), applyOrientationTransform)
|
||||
import Swarm.Game.Scenario.Topography.Structure qualified as Structure
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (RotationalSymmetry (..), SymmetryAnnotatedGrid (..))
|
||||
import Swarm.Language.Direction (AbsoluteDir (DSouth, DWest), getCoordinateOrientation)
|
||||
import Swarm.Util (commaList, failT, histogram, showT)
|
||||
|
||||
-- | Warns if any recognition orientations are redundant
|
||||
-- by rotational symmetry.
|
||||
-- We can accomplish this by testing only two rotations:
|
||||
--
|
||||
-- 1. Rotate 90 degrees. If identical to the original
|
||||
-- orientation, then has 4-fold symmetry and we don't
|
||||
-- need to check any other orientations.
|
||||
-- Warn if more than one recognition orientation was supplied.
|
||||
-- 2. Rotate 180 degrees. At best, we may now have
|
||||
-- 2-fold symmetry.
|
||||
-- Warn if two opposite orientations were supplied.
|
||||
checkSymmetry ::
|
||||
(MonadFail m, Eq a) => Structure.NamedGrid a -> m (SymmetryAnnotatedGrid a)
|
||||
checkSymmetry ng = do
|
||||
case symmetryType of
|
||||
FourFold ->
|
||||
when (Set.size suppliedOrientations > 1)
|
||||
. failT
|
||||
. pure
|
||||
$ T.unwords ["Redundant orientations supplied; with four-fold symmetry, just supply 'north'."]
|
||||
TwoFold ->
|
||||
unless (null redundantOrientations)
|
||||
. failT
|
||||
. pure
|
||||
$ T.unwords
|
||||
[ "Redundant"
|
||||
, commaList $ map showT redundantOrientations
|
||||
, "orientations supplied with two-fold symmetry."
|
||||
]
|
||||
where
|
||||
redundantOrientations =
|
||||
map fst
|
||||
. filter ((> 1) . snd)
|
||||
. M.toList
|
||||
. histogram
|
||||
. map getCoordinateOrientation
|
||||
$ Set.toList suppliedOrientations
|
||||
_ -> return ()
|
||||
|
||||
return $ SymmetryAnnotatedGrid ng symmetryType
|
||||
where
|
||||
symmetryType
|
||||
| quarterTurnRows == originalRows = FourFold
|
||||
| halfTurnRows == originalRows = TwoFold
|
||||
| otherwise = NoSymmetry
|
||||
|
||||
quarterTurnRows = applyOrientationTransform (Orientation DWest False) originalRows
|
||||
halfTurnRows = applyOrientationTransform (Orientation DSouth False) originalRows
|
||||
|
||||
suppliedOrientations = Structure.recognize ng
|
||||
Structure.Grid originalRows = Structure.structure ng
|
@ -38,6 +38,7 @@ import Swarm.Game.Scenario.Topography.Cell
|
||||
import Swarm.Game.Scenario.Topography.Placement (StructureName)
|
||||
import Swarm.Game.Scenario.Topography.Structure (NamedGrid)
|
||||
import Swarm.Game.Universe (Cosmic, offsetBy)
|
||||
import Swarm.Language.Syntax (AbsoluteDir)
|
||||
import Text.AhoCorasick (StateMachine)
|
||||
|
||||
-- | A "needle" consisting of a single cell within
|
||||
@ -129,13 +130,30 @@ data StructureRow = StructureRow
|
||||
-- with its grid of cells having been extracted for convenience.
|
||||
data StructureWithGrid = StructureWithGrid
|
||||
{ originalDefinition :: NamedGrid (Maybe Cell)
|
||||
, rotatedTo :: AbsoluteDir
|
||||
, entityGrid :: [SymbolSequence]
|
||||
}
|
||||
deriving (Eq)
|
||||
|
||||
data RotationalSymmetry
|
||||
= -- | Aka 1-fold symmetry
|
||||
NoSymmetry
|
||||
| -- | Equivalent under rotation by 180 degrees
|
||||
TwoFold
|
||||
| -- | Equivalent under rotation by 90 degrees
|
||||
FourFold
|
||||
deriving (Show, Eq)
|
||||
|
||||
data SymmetryAnnotatedGrid a = SymmetryAnnotatedGrid
|
||||
{ namedGrid :: NamedGrid a
|
||||
, symmetry :: RotationalSymmetry
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
-- | Structure definitions with precomputed metadata for consumption by the UI
|
||||
data StructureInfo = StructureInfo
|
||||
{ withGrid :: StructureWithGrid
|
||||
{ annotatedGrid :: SymmetryAnnotatedGrid (Maybe Cell)
|
||||
, entityProcessedGrid :: [SymbolSequence]
|
||||
, entityCounts :: Map Entity Int
|
||||
}
|
||||
|
||||
@ -181,9 +199,9 @@ makeLenses ''AutomatonInfo
|
||||
-- | The complete set of data needed to identify applicable
|
||||
-- structures, based on a just-placed entity.
|
||||
data RecognizerAutomatons = RecognizerAutomatons
|
||||
{ _definitions :: Map StructureName StructureInfo
|
||||
{ _originalStructureDefinitions :: Map StructureName StructureInfo
|
||||
-- ^ all of the structures that shall participate in automatic recognition.
|
||||
-- This list is used only by the UI.
|
||||
-- This list is used only by the UI and by the 'Floorplan' command.
|
||||
, _automatonsByEntity :: Map Entity (AutomatonInfo AtomicKeySymbol StructureSearcher)
|
||||
}
|
||||
deriving (Generic)
|
||||
|
@ -577,7 +577,7 @@ ensureStructureIntact ::
|
||||
(Has (State GameState) sig m) =>
|
||||
FoundStructure ->
|
||||
m Bool
|
||||
ensureStructureIntact (FoundStructure (StructureWithGrid _ grid) upperLeft) =
|
||||
ensureStructureIntact (FoundStructure (StructureWithGrid _ _ grid) upperLeft) =
|
||||
allM outer $ zip [0 ..] grid
|
||||
where
|
||||
outer (y, row) = allM (inner y) $ zip [0 ..] row
|
||||
@ -595,12 +595,18 @@ mkRecognizer ::
|
||||
mkRecognizer structInfo@(StaticStructureInfo structDefs _) = do
|
||||
foundIntact <- mapM (sequenceA . (id &&& ensureStructureIntact)) allPlaced
|
||||
let fs = populateStaticFoundStructures . map fst . filter snd $ foundIntact
|
||||
foundIntactLog =
|
||||
IntactStaticPlacement $
|
||||
map (\(x, isIntact) -> (isIntact, (Structure.name . originalDefinition . structureWithGrid) x, upperLeftCorner x)) foundIntact
|
||||
return $ StructureRecognizer (mkAutomatons structDefs) fs [foundIntactLog]
|
||||
return $
|
||||
StructureRecognizer
|
||||
(mkAutomatons structDefs)
|
||||
fs
|
||||
[IntactStaticPlacement $ map mkLogEntry foundIntact]
|
||||
where
|
||||
allPlaced = lookupStaticPlacements structInfo
|
||||
mkLogEntry (x, isIntact) =
|
||||
IntactPlacementLog
|
||||
isIntact
|
||||
((Structure.name . originalDefinition . structureWithGrid) x)
|
||||
(upperLeftCorner x)
|
||||
|
||||
buildTagMap :: EntityMap -> Map Text (NonEmpty EntityName)
|
||||
buildTagMap em =
|
||||
|
@ -511,12 +511,12 @@ execConst runChildProg c vs s k = do
|
||||
_ -> badConst
|
||||
Floorplan -> case vs of
|
||||
[VText name] -> do
|
||||
structureTemplates <- use $ discovery . structureRecognition . automatons . definitions
|
||||
structureTemplates <- use $ discovery . structureRecognition . automatons . originalStructureDefinitions
|
||||
let maybeStructure = M.lookup (StructureName name) structureTemplates
|
||||
structureDef <-
|
||||
maybeStructure
|
||||
`isJustOr` cmdExn Floorplan (pure $ T.unwords ["Unknown structure", quote name])
|
||||
return . mkReturn . getAreaDimensions . entityGrid $ withGrid structureDef
|
||||
return . mkReturn . getAreaDimensions $ entityProcessedGrid structureDef
|
||||
_ -> badConst
|
||||
HasTag -> case vs of
|
||||
[VText eName, VText tName] -> do
|
||||
|
@ -16,6 +16,8 @@ module Swarm.Language.Direction (
|
||||
directionSyntax,
|
||||
isCardinal,
|
||||
allDirs,
|
||||
directionJsonModifier,
|
||||
getCoordinateOrientation,
|
||||
) where
|
||||
|
||||
import Data.Aeson.Types hiding (Key)
|
||||
@ -51,6 +53,18 @@ data AbsoluteDir = DEast | DNorth | DWest | DSouth
|
||||
directionJsonModifier :: String -> String
|
||||
directionJsonModifier = map C.toLower . L.tail
|
||||
|
||||
data CoordinateOrientation
|
||||
= Latitudinal
|
||||
| Longitudinal
|
||||
deriving (Show, Eq, Ord)
|
||||
|
||||
getCoordinateOrientation :: AbsoluteDir -> CoordinateOrientation
|
||||
getCoordinateOrientation = \case
|
||||
DEast -> Longitudinal
|
||||
DWest -> Longitudinal
|
||||
DNorth -> Latitudinal
|
||||
DSouth -> Latitudinal
|
||||
|
||||
directionJsonOptions :: Options
|
||||
directionJsonOptions =
|
||||
defaultOptions
|
||||
|
@ -79,7 +79,7 @@ import Swarm.Game.Location
|
||||
import Swarm.Game.ResourceLoading (getSwarmHistoryPath)
|
||||
import Swarm.Game.Robot
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition (automatons)
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (definitions)
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (originalStructureDefinitions)
|
||||
import Swarm.Game.ScenarioInfo
|
||||
import Swarm.Game.State
|
||||
import Swarm.Game.State.Robot
|
||||
@ -338,7 +338,7 @@ handleMainEvent ev = do
|
||||
FKey 5 | not (null (s ^. gameState . messageNotifications . notificationsContent)) -> do
|
||||
toggleModal MessagesModal
|
||||
gameState . messageInfo . lastSeenMessageTime .= s ^. gameState . temporal . ticks
|
||||
FKey 6 | not (null $ s ^. gameState . discovery . structureRecognition . automatons . definitions) -> toggleModal StructuresModal
|
||||
FKey 6 | not (null $ s ^. gameState . discovery . structureRecognition . automatons . originalStructureDefinitions) -> toggleModal StructuresModal
|
||||
-- show goal
|
||||
ControlChar 'g' ->
|
||||
if hasAnythingToShow $ s ^. uiState . uiGoal . goalsContent
|
||||
|
@ -51,7 +51,7 @@ import Swarm.Game.Scenario.Scoring.ConcreteMetrics
|
||||
import Swarm.Game.Scenario.Scoring.GenericMetrics
|
||||
import Swarm.Game.Scenario.Status
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition (automatons)
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (definitions)
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (originalStructureDefinitions)
|
||||
import Swarm.Game.ScenarioInfo (
|
||||
loadScenarioInfo,
|
||||
normalizeScenarioPath,
|
||||
@ -268,7 +268,7 @@ scenarioToUIState isAutoplaying siPair@(scenario, _) gs u = do
|
||||
& uiWorldEditor . EM.editingBounds . EM.boundsRect %~ setNewBounds
|
||||
& uiStructure
|
||||
.~ StructureDisplay
|
||||
(SR.makeListWidget . M.elems $ gs ^. discovery . structureRecognition . automatons . definitions)
|
||||
(SR.makeListWidget . M.elems $ gs ^. discovery . structureRecognition . automatons . originalStructureDefinitions)
|
||||
(focusSetCurrent (StructureWidgets StructuresList) $ focusRing $ map StructureWidgets listEnums)
|
||||
where
|
||||
entityList = EU.getEntitiesForList $ gs ^. landscape . entityMap
|
||||
|
@ -931,7 +931,7 @@ drawModalMenu s = vLimit 1 . hBox $ map (padLeftRight 1 . drawKeyCmd) globalKeyC
|
||||
|
||||
-- Hides this key if the recognizable structure list is empty
|
||||
structuresKey =
|
||||
if null $ s ^. gameState . discovery . structureRecognition . automatons . definitions
|
||||
if null $ s ^. gameState . discovery . structureRecognition . automatons . originalStructureDefinitions
|
||||
then Nothing
|
||||
else Just (NoHighlight, "F6", "Structures")
|
||||
|
||||
|
@ -16,6 +16,7 @@ import Brick.Widgets.List qualified as BL
|
||||
import Control.Lens hiding (Const, from)
|
||||
import Data.Map.NonEmpty qualified as NEM
|
||||
import Data.Map.Strict qualified as M
|
||||
import Data.Set qualified as Set
|
||||
import Data.Text qualified as T
|
||||
import Data.Vector qualified as V
|
||||
import Swarm.Game.Entity (entityDisplay)
|
||||
@ -28,11 +29,13 @@ import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry (foundByNam
|
||||
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type
|
||||
import Swarm.Game.State
|
||||
import Swarm.Game.State.Substate
|
||||
import Swarm.Language.Direction (directionJsonModifier)
|
||||
import Swarm.TUI.Model.Name
|
||||
import Swarm.TUI.Model.Structure
|
||||
import Swarm.TUI.View.Attribute.Attr
|
||||
import Swarm.TUI.View.CellDisplay
|
||||
import Swarm.TUI.View.Util
|
||||
import Swarm.Util (commaList)
|
||||
|
||||
-- | Render a two-pane widget with structure selection on the left
|
||||
-- and single-structure details on the right.
|
||||
@ -46,10 +49,10 @@ structureWidget gs s =
|
||||
. T.pack
|
||||
. renderRectDimensions
|
||||
. getAreaDimensions
|
||||
. entityGrid
|
||||
$ withGrid s
|
||||
$ entityProcessedGrid s
|
||||
, occurrenceCountSuffix
|
||||
]
|
||||
, reorientabilityWidget
|
||||
, maybeDescriptionWidget
|
||||
, padTop (Pad 1) $
|
||||
hBox
|
||||
@ -64,7 +67,29 @@ structureWidget gs s =
|
||||
, withAttr boldAttr $ txt content
|
||||
]
|
||||
|
||||
maybeDescriptionWidget = maybe emptyWidget txtWrap $ Structure.description . originalDefinition . withGrid $ s
|
||||
annotatedStructureGrid = annotatedGrid s
|
||||
|
||||
supportedOrientations = Set.toList . Structure.recognize . namedGrid $ annotatedStructureGrid
|
||||
|
||||
renderSymmetry = \case
|
||||
NoSymmetry -> "no"
|
||||
TwoFold -> "2-fold"
|
||||
FourFold -> "4-fold"
|
||||
|
||||
reorientabilityWidget =
|
||||
txt $
|
||||
T.unwords
|
||||
[ "Orientable:"
|
||||
, commaList $ map (T.pack . directionJsonModifier . show) supportedOrientations
|
||||
, "with"
|
||||
, renderSymmetry $ symmetry annotatedStructureGrid
|
||||
, "symmetry."
|
||||
]
|
||||
|
||||
maybeDescriptionWidget =
|
||||
maybe emptyWidget (padTop (Pad 1) . withAttr italicAttr . txtWrap) $
|
||||
Structure.description . namedGrid . annotatedGrid $
|
||||
s
|
||||
|
||||
registry = gs ^. discovery . structureRecognition . foundStructures
|
||||
occurrenceCountSuffix = case M.lookup sName $ foundByName registry of
|
||||
@ -72,7 +97,7 @@ structureWidget gs s =
|
||||
Just inner -> padLeft (Pad 2) . headerItem "Count" . T.pack . show $ NEM.size inner
|
||||
|
||||
structureIllustration = vBox $ map (hBox . map renderOneCell) cells
|
||||
d = originalDefinition $ withGrid s
|
||||
d = namedGrid $ annotatedGrid s
|
||||
|
||||
ingredientsBox =
|
||||
vBox
|
||||
@ -97,8 +122,8 @@ structureWidget gs s =
|
||||
renderOneCell = maybe (txt " ") (renderDisplay . view entityDisplay)
|
||||
|
||||
makeListWidget :: [StructureInfo] -> BL.List Name StructureInfo
|
||||
makeListWidget structureDefs =
|
||||
BL.listMoveTo 0 $ BL.list (StructureWidgets StructuresList) (V.fromList structureDefs) 1
|
||||
makeListWidget structureDefinitions =
|
||||
BL.listMoveTo 0 $ BL.list (StructureWidgets StructuresList) (V.fromList structureDefinitions) 1
|
||||
|
||||
renderStructuresDisplay :: GameState -> StructureDisplay -> Widget Name
|
||||
renderStructuresDisplay gs structureDisplay =
|
||||
@ -140,5 +165,5 @@ drawSidebarListItem ::
|
||||
Bool ->
|
||||
StructureInfo ->
|
||||
Widget Name
|
||||
drawSidebarListItem _isSelected (StructureInfo swg _) =
|
||||
txt . getStructureName . Structure.name $ originalDefinition swg
|
||||
drawSidebarListItem _isSelected (StructureInfo annotated _ _) =
|
||||
txt . getStructureName . Structure.name $ namedGrid annotated
|
||||
|
@ -192,6 +192,7 @@ library
|
||||
Swarm.Game.Scenario.Topography.Structure.Recognition.Log
|
||||
Swarm.Game.Scenario.Topography.Structure.Recognition.Precompute
|
||||
Swarm.Game.Scenario.Topography.Structure.Recognition.Registry
|
||||
Swarm.Game.Scenario.Topography.Structure.Recognition.Symmetry
|
||||
Swarm.Game.Scenario.Topography.Structure.Recognition.Tracking
|
||||
Swarm.Game.Scenario.Topography.Structure.Recognition.Type
|
||||
Swarm.Game.Scenario.Topography.WorldDescription
|
||||
|
@ -236,6 +236,7 @@ testScenarioSolutions rs ui =
|
||||
, testSolution (Sec 3) "Challenges/lights-out"
|
||||
, testSolution (Sec 10) "Challenges/Sliding Puzzles/3x3"
|
||||
, testSolution Default "Challenges/friend"
|
||||
, testSolution Default "Challenges/pack-tetrominoes"
|
||||
, testGroup
|
||||
"Mazes"
|
||||
[ testSolution Default "Challenges/Mazes/easy_cave_maze"
|
||||
@ -428,6 +429,8 @@ testScenarioSolutions rs ui =
|
||||
, testSolution Default "Testing/1575-structure-recognizer/1575-interior-entity-placement"
|
||||
, testSolution Default "Testing/1575-structure-recognizer/1575-floorplan-command"
|
||||
, testSolution Default "Testing/1575-structure-recognizer/1575-bounding-box-overlap"
|
||||
, testSolution Default "Testing/1575-structure-recognizer/1644-rotated-recognition"
|
||||
, testSolution Default "Testing/1575-structure-recognizer/1644-rotated-preplacement-recognition"
|
||||
]
|
||||
]
|
||||
, testSolution' Default "Testing/1430-built-robot-ownership" CheckForBadErrors $ \g -> do
|
||||
|
Loading…
Reference in New Issue
Block a user