diff --git a/src/Grid.elm b/src/Grid.elm index 0c65737..9aa2d11 100644 --- a/src/Grid.elm +++ b/src/Grid.elm @@ -28,8 +28,8 @@ initialize { rows, columns } init = Array.initialize (rows * columns) (\i -> init - { row = i // rows - , column = modBy rows i + { row = i // columns + , column = modBy columns i } ) , width = columns diff --git a/tests/GridTests.elm b/tests/GridTests.elm index 1359774..6c4b8d0 100644 --- a/tests/GridTests.elm +++ b/tests/GridTests.elm @@ -3,6 +3,7 @@ module GridTests exposing (..) import Expect import Fuzz exposing (Fuzzer) import Grid exposing (Grid) +import SlowGrid import Test exposing (..) @@ -60,6 +61,29 @@ windowsTest = ] + +{- Tests for compatibility with the older, slower, but known-good version. -} + + +compatibilityTest : Test +compatibilityTest = + describe "compatibility with the array-of-arrays implementation" + [ fuzz2 (Fuzz.intRange 0 2) (Fuzz.intRange 0 2) "initialize" <| + \rows columns -> + let + initter = + \{ row, column } -> ( row, column ) + + expected = + SlowGrid.toArrays (SlowGrid.initialize { rows = rows, columns = columns } identity) + + actual = + Grid.toArrays (Grid.initialize { rows = rows, columns = columns } identity) + in + Expect.equal expected actual + ] + + blank10x10 : Grid Int blank10x10 = Grid.initialize { rows = 10, columns = 10 } (always 0) diff --git a/tests/SlowGrid.elm b/tests/SlowGrid.elm new file mode 100644 index 0000000..7a1d9d9 --- /dev/null +++ b/tests/SlowGrid.elm @@ -0,0 +1,221 @@ +module SlowGrid exposing (FromRowsAndColumnsProblem, Grid, fromRowsAndColumns, fromRowsAndColumnsArray, get, getWrapping, indexedMap, initialize, map, rotate, set, toArrays, topLeft, update, view, windows) + +import Array exposing (Array) +import Color.Transparent as Color exposing (Color) +import Css +import Html.Styled as Html exposing (Html) +import Html.Styled.Attributes as Attrs exposing (css, style) +import Set + + +type Grid a + = Grid + { -- rows x columns + items : Array (Array a) + , width : Int + , height : Int + } + + +type FromRowsAndColumnsProblem + = MoreThanOneWidth (List Int) + + +initialize : { rows : Int, columns : Int } -> ({ row : Int, column : Int } -> a) -> Grid a +initialize { rows, columns } init = + Grid + { items = Array.initialize rows (\row -> Array.initialize columns (\colNum -> init { row = row, column = colNum })) + , width = columns + , height = rows + } + + +{-| Construct a grid from a list of lists. The outer list is a list of rows, +and the inner lists are values in columns. + +If the sizes of the column arrays (the inner ones) don't match up, you'll get a +`MoreThanOneWidth` error back from this function. + +-} +fromRowsAndColumns : List (List a) -> Grid a +fromRowsAndColumns rowsAndColumns = + Array.fromList (List.map Array.fromList rowsAndColumns) + |> fromRowsAndColumnsArray + + +fromRowsAndColumnsArray : Array (Array a) -> Grid a +fromRowsAndColumnsArray rowsAndColumns = + Grid + { items = rowsAndColumns + , width = + Array.get 0 rowsAndColumns + |> Maybe.map Array.length + |> Maybe.withDefault 0 + , height = Array.length rowsAndColumns + } + + +toArrays : Grid a -> Array (Array a) +toArrays (Grid { items }) = + items + + +{-| Rotate a grid 90° clockwise. +-} +rotate : Grid a -> Grid a +rotate ((Grid { width, height }) as grid) = + let + newItems = + List.range 0 (width - 1) + |> List.map + (\col -> + column col grid + |> Maybe.withDefault Array.empty + |> Array.foldr Array.push Array.empty + ) + |> Array.fromList + in + Grid + { items = newItems + , height = width + , width = height + } + + +column : Int -> Grid a -> Maybe (Array a) +column colNum (Grid { items, height }) = + List.range 0 (height - 1) + |> List.foldl + (\row soFar -> + Maybe.andThen + (\arr -> + items + |> Array.get row + |> Maybe.andThen (Array.get colNum) + |> Maybe.map (\val -> Array.push val arr) + ) + soFar + ) + (Just Array.empty) + + +{-| Get a number of windows over the given grid data. Windows wrap around the +edges of the input grid. We include tuples here, as they're useful as IDs. +-} +windows : { width : Int, height : Int } -> Grid a -> Grid (Grid a) +windows sizes (Grid { width, height, items }) = + let + -- when we reach the edge, we just need to wrap around. + -- Repeating once per axis should do it! + expanded = + Array.initialize (height * 2) + (\i -> + let + row = + items + |> Array.get (modBy height i) + |> Maybe.withDefault Array.empty + in + Array.append row row + ) + in + Grid + { items = + Array.initialize height + (\row -> + Array.initialize width + (\col -> + Grid + { items = + expanded + |> Array.slice row (row + sizes.height) + |> Array.map (Array.slice col (col + sizes.width)) + , width = sizes.width + , height = sizes.height + } + ) + ) + , width = width + , height = height + } + + +get : { row : Int, column : Int } -> Grid a -> Maybe a +get coords (Grid { items }) = + items + |> Array.get coords.row + |> Maybe.andThen (Array.get coords.column) + + +{-| Still a maybe because the grid could be empty +-} +getWrapping : { row : Int, column : Int } -> Grid a -> Maybe a +getWrapping coords ((Grid { width, height }) as grid) = + get + { row = modBy height coords.row + , column = modBy width coords.column + } + grid + + +topLeft : Grid a -> Maybe a +topLeft = + get { row = 0, column = 0 } + + +set : { row : Int, column : Int } -> a -> Grid a -> Grid a +set coords newValue (Grid grid) = + case Array.get coords.row grid.items of + Nothing -> + Grid grid + + Just row -> + Grid + { grid + | items = + Array.set + coords.row + (Array.set coords.column newValue row) + grid.items + } + + +update : (a -> a) -> { row : Int, column : Int } -> Grid a -> Grid a +update fn coords grid = + case get coords grid of + Just item -> + set coords (fn item) grid + + Nothing -> + grid + + +{-| TODO: could probably do this with CSS grids but I'm not sure how. +-} +view : (a -> Html msg) -> Grid a -> Html msg +view viewItem (Grid { items }) = + items + |> Array.map (Array.map viewItem >> Array.toList >> Html.tr []) + |> Array.toList + |> Html.table [ css [ Css.borderCollapse Css.collapse ] ] + + +indexedMap : ({ row : Int, column : Int } -> a -> b) -> Grid a -> Grid b +indexedMap fn (Grid grid) = + Grid + { items = + Array.indexedMap + (\rowNum row -> + Array.indexedMap + (\colNum val -> fn { row = rowNum, column = colNum } val) + row + ) + grid.items + , width = grid.width + , height = grid.height + } + + +map : (a -> b) -> Grid a -> Grid b +map fn = + indexedMap (\_ a -> fn a)