Data commit

This commit is contained in:
Ingy döt Net 2023-07-01 11:58:00 -04:00
parent 7387c8f97b
commit cb5bb5e222
199093 changed files with 3378972 additions and 0 deletions

View file

@ -0,0 +1,192 @@
{-# LANGUAGE TemplateHaskell #-}
module MineSweeper
( Board
, Cell(..)
, CellState(..)
, Pos
-- lenses / prisms
, pos
, coveredLens
, coveredFlaggedLens
, coveredMinedLens
, xCoordLens
, yCoordLens
-- Functions
, emptyBoard
, groupedByRows
, displayCell
, isLoss
, isWin
, exposeMines
, openCell
, flagCell
, mineBoard
, totalRows
, totalCols )
where
import Control.Lens ((%~), (&), (.~), (^.), (^?), Lens', Traversal', _1, _2,
anyOf, filtered, folded, lengthOf, makeLenses, makePrisms,
preview, to, view)
import Data.List (find, groupBy, nub, delete, sortBy)
import Data.Maybe (isJust)
import System.Random (getStdGen, getStdRandom, randomR, randomRs)
type Pos = (Int, Int)
type Board = [Cell]
data CellState = Covered { _mined :: Bool, _flagged :: Bool }
| UnCovered { _mined :: Bool }
deriving (Show, Eq)
data Cell = Cell
{ _pos :: Pos
, _state :: CellState
, _cellId :: Int
, _adjacentMines :: Int }
deriving (Show, Eq)
makePrisms ''CellState
makeLenses ''CellState
makeLenses ''Cell
-- Re-useable lens.
coveredLens :: Traversal' Cell (Bool, Bool) --'
coveredLens = state . _Covered
coveredMinedLens, coveredFlaggedLens, unCoveredLens :: Traversal' Cell Bool --'
coveredMinedLens = coveredLens . _1
coveredFlaggedLens = coveredLens . _2
unCoveredLens = state . _UnCovered
xCoordLens, yCoordLens :: Lens' Cell Int --'
xCoordLens = pos . _1
yCoordLens = pos . _2
-- Adjust row and column size to your preference.
totalRows, totalCols :: Int
totalRows = 4
totalCols = 6
emptyBoard :: Board
emptyBoard = (\(n, p) -> Cell { _pos = p
, _state = Covered False False
, _adjacentMines = 0
, _cellId = n }) <$> zip [1..] positions
where
positions = (,) <$> [1..totalCols] <*> [1..totalRows]
updateCell :: Cell -> Board -> Board
updateCell cell = fmap (\c -> if cell ^. cellId == c ^. cellId then cell else c)
updateBoard :: Board -> [Cell] -> Board
updateBoard = foldr updateCell
okToOpen :: [Cell] -> [Cell]
okToOpen = filter (\c -> c ^? coveredLens == Just (False, False))
openUnMined :: Cell -> Cell
openUnMined = state .~ UnCovered False
openCell :: Pos -> Board -> Board
openCell p b = f $ find (\c -> c ^. pos == p) b
where
f (Just c)
| c ^? coveredFlaggedLens == Just True = b
| c ^? coveredMinedLens == Just True = updateCell
(c & state .~ UnCovered True)
b
| isCovered c = if c ^. adjacentMines == 0 && not (isFirstMove b)
then updateCell (openUnMined c) $ expandEmptyCells b c
else updateCell (openUnMined c) b
| otherwise = b
f Nothing = b
isCovered = isJust . preview coveredLens
expandEmptyCells :: Board -> Cell -> Board
expandEmptyCells board cell
| null openedCells = board
| otherwise = foldr (flip expandEmptyCells) updatedBoard (zeroAdjacent openedCells)
where
findMore _ [] = []
findMore exclude (c : xs)
| c `elem` exclude = findMore exclude xs
| c ^. adjacentMines == 0 = c : adjacent c <>
findMore (c : exclude <> adjacent c) xs
| otherwise = c : findMore (c : exclude) xs
adjacent = okToOpen . flip adjacentCells board
openedCells = openUnMined <$> nub (findMore [cell] (adjacent cell))
zeroAdjacent = filter (view (adjacentMines . to (== 0)))
updatedBoard = updateBoard board openedCells
flagCell :: Pos -> Board -> Board
flagCell p board = case find ((== p) . view pos) board of
Just c -> updateCell (c & state . flagged %~ not) board
Nothing -> board
adjacentCells :: Cell -> Board -> [Cell]
adjacentCells Cell {_pos = c@(x1, y1)} = filter (\c -> c ^. pos `elem` positions)
where
f n = [pred n, n, succ n]
positions = delete c $ [(x, y) | x <- f x1, x > 0, y <- f y1, y > 0]
isLoss, isWin, allUnMinedOpen, allMinesFlagged, isFirstMove :: Board -> Bool
isLoss = anyOf (traverse . unCoveredLens) (== True)
isWin b = allUnMinedOpen b || allMinesFlagged b
allUnMinedOpen = (== 0) . lengthOf (traverse . coveredMinedLens . filtered (== False))
allMinesFlagged b = minedCount b == flaggedMineCount b
where
minedCount = lengthOf (traverse . coveredMinedLens . filtered (== True))
flaggedMineCount = lengthOf (traverse . coveredLens . filtered (== (True, True)))
isFirstMove = (== totalCols * totalRows) . lengthOf (folded . coveredFlaggedLens . filtered (== False))
groupedByRows :: Board -> [Board]
groupedByRows = groupBy (\c1 c2 -> yAxis c1 == yAxis c2)
. sortBy (\c1 c2 -> yAxis c1 `compare` yAxis c2)
where
yAxis = view yCoordLens
displayCell :: Cell -> String
displayCell c
| c ^? unCoveredLens == Just True = "X"
| c ^? coveredFlaggedLens == Just True = "?"
| c ^? (unCoveredLens . to not) == Just True =
if c ^. adjacentMines > 0 then show $ c ^. adjacentMines else ""
| otherwise = "."
exposeMines :: Board -> Board
exposeMines = fmap (\c -> c & state . filtered (\s -> s ^? _Covered . _1 == Just True) .~ UnCovered True)
updateMineCount :: Board -> Board
updateMineCount b = go b
where
go [] = []
go (x : xs) = (x & adjacentMines .~ totalAdjacentMines b) : go xs
where
totalAdjacentMines =
foldr (\c acc -> if c ^. (state . mined) then succ acc else acc) 0 . adjacentCells x
-- IO
mineBoard :: Pos -> Board -> IO Board
mineBoard p board = do
totalMines <- randomMinedCount
minedBoard totalMines >>= \mb -> pure $ updateMineCount mb
where
mines n = take n <$> randomCellIds
minedBoard n = (\m ->
fmap (\c -> if c ^. cellId `elem` m then c & state . mined .~ True else c)
board) . filter (\c -> openedCell ^. cellId /= c)
<$> mines n
openedCell = head $ filter (\c -> c ^. pos == p) board
randomCellIds :: IO [Int]
randomCellIds = randomRs (1, totalCols * totalRows) <$> getStdGen
randomMinedCount :: IO Int
randomMinedCount = getStdRandom $ randomR (minMinedCells, maxMinedCells)
where
maxMinedCells = floor $ realToFrac (totalCols * totalRows) * 0.2
minMinedCells = floor $ realToFrac (totalCols * totalRows) * 0.1

View file

@ -0,0 +1,80 @@
module Main
where
import MineSweeper (Board, Cell (..), Pos, coveredLens, coveredMinedLens,
coveredFlaggedLens, xCoordLens, yCoordLens, pos, emptyBoard,
groupedByRows, displayCell, isLoss, isWin, exposeMines,
openCell, flagCell, mineBoard, totalRows, totalCols)
import Text.Printf (printf)
import Text.Read (readMaybe)
import System.IO (hSetBuffering, stdout, BufferMode(..))
import Control.Monad (join, guard)
import Control.Lens (lengthOf, filtered, view, preview)
data Command = Open Pos | Flag Pos | Invalid
parseInput :: String -> Command
parseInput s | length input /= 3 = Invalid
| otherwise = maybe Invalid command parsedPos
where
input = words s
parsedPos = do
x <- readMaybe (input !! 1)
y <- readMaybe (input !! 2)
guard (x <= totalCols && y <= totalRows)
pure (x, y)
command p = case head input of
"o" -> Open p
"f" -> Flag p
_ -> Invalid
cheat :: Board -> String
cheat = show . fmap (view pos) . filter ((== Just True) . preview coveredMinedLens)
totalMines, totalFlagged, totalCovered :: Board -> Int
totalMines = lengthOf (traverse . coveredMinedLens . filtered (==True))
totalFlagged = lengthOf (traverse . coveredFlaggedLens . filtered (==True))
totalCovered = lengthOf (traverse . coveredLens)
-- IO
drawBoard :: Board -> IO ()
drawBoard b = do
-- printf "Cheat: %s\n" $ cheat b
printf " Mines: %d Covered: %d Flagged: %d\n\n"
(totalMines b) (totalCovered b) (totalFlagged b)
printf "%3s" ""
mapM_ (printf "%3d") $ view xCoordLens <$> head rows
printf "\n"
mapM_ (\row -> do
printf "%3d" $ yCoord row
mapM_ (printf "%3s" . displayCell) row
printf "\n" ) rows
where
rows = groupedByRows b
yCoord = view yCoordLens . head
gameLoop :: Board -> IO ()
gameLoop b
| isLoss b = drawBoard (exposeMines b) >> printf "\nYou Lose.\n"
| isWin b = drawBoard b >> printf "\nYou Win.\n"
| otherwise = do
drawBoard b
putStr "\nPick a cell: "
c <- getLine
case parseInput c of
Open p -> gameLoop $ openCell p b
Flag p -> gameLoop $ flagCell p b
Invalid -> gameLoop b
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
drawBoard emptyBoard
putStr "\nPick a cell: "
c <- getLine
case parseInput c of
Open p -> join $ startGame p emptyBoard
_ -> main
where
startGame p b = gameLoop . openCell p <$> mineBoard p b