Compare commits
No commits in common. "725b7bdf9456707f7ff2da4afb60a7909151caa5" and "3fd342c69f8a8f305dad12ed150d92f27de60ea5" have entirely different histories.
725b7bdf94
...
3fd342c69f
8 changed files with 155 additions and 450 deletions
138
.github/workflows/release.yaml
vendored
138
.github/workflows/release.yaml
vendored
|
@ -1,138 +0,0 @@
|
||||||
# This is copied from the stan project
|
|
||||||
#
|
|
||||||
# Note [environment variables]
|
|
||||||
#
|
|
||||||
# It seems absurd, but the syntax for creating environment variables
|
|
||||||
# differs between Windows and Linux/MacOS. See
|
|
||||||
#
|
|
||||||
# https://docs.github.com/en/actions/learn-github-actions/variables
|
|
||||||
#
|
|
||||||
# In Linux/MacOS we have to use
|
|
||||||
#
|
|
||||||
# run: echo "VARNAME=content" >> "$GITHUB_ENV"
|
|
||||||
#
|
|
||||||
# whereas in Windows we have to use
|
|
||||||
#
|
|
||||||
# run: echo "VARNAME=content" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Trigger the workflow on the new 'v*' tag created
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
create_release:
|
|
||||||
name: Create Github Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@v1.1.4
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: Release ${{ github.ref }}
|
|
||||||
draft: true
|
|
||||||
prerelease: false
|
|
||||||
|
|
||||||
- name: Output Release URL File
|
|
||||||
run: echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt
|
|
||||||
- name: Save Release URL File for publish
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: release_url
|
|
||||||
path: release_url.txt
|
|
||||||
|
|
||||||
build_artifact:
|
|
||||||
needs: [create_release]
|
|
||||||
name: ${{ matrix.os }}/${{ github.ref }}
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set tag name
|
|
||||||
uses: olegtarasov/get-tag@v2.1.2
|
|
||||||
id: tag
|
|
||||||
with:
|
|
||||||
tagRegex: "v(.*)"
|
|
||||||
tagRegexGroup: 1
|
|
||||||
|
|
||||||
- name: Setup Haskell
|
|
||||||
uses: haskell-actions/setup@v2
|
|
||||||
id: setup-haskell-cabal
|
|
||||||
with:
|
|
||||||
ghc-version: "latest"
|
|
||||||
cabal-version: "latest"
|
|
||||||
|
|
||||||
- name: Freeze
|
|
||||||
run: |
|
|
||||||
cabal freeze
|
|
||||||
|
|
||||||
- name: Cache ~/.cabal/store
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.setup-haskell-cabal.outputs.cabal-store }}
|
|
||||||
key: ${{ runner.os }}-${{ hashFiles('cabal.project.freeze') }}
|
|
||||||
|
|
||||||
- name: Build binary
|
|
||||||
run: |
|
|
||||||
mkdir dist
|
|
||||||
cabal install exe:tetris --install-method=copy --overwrite-policy=always --installdir=dist
|
|
||||||
|
|
||||||
# See Note [environment variables]
|
|
||||||
- if: matrix.os == 'windows-latest'
|
|
||||||
name: Set binary path name on Windows
|
|
||||||
run: echo "BINARY_PATH=./dist/tetris.exe" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
# See Note [environment variables]
|
|
||||||
- if: matrix.os != 'windows-latest'
|
|
||||||
name: Set binary path name not on Windows
|
|
||||||
run: echo "BINARY_PATH=./dist/tetris" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- if: matrix.os != 'macOS-latest'
|
|
||||||
name: Compress binary
|
|
||||||
uses: svenstaro/upx-action@2.3.0
|
|
||||||
with:
|
|
||||||
file: ${{ env.BINARY_PATH }}
|
|
||||||
|
|
||||||
- name: Load Release URL File from release job
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: release_url
|
|
||||||
path: release_url
|
|
||||||
|
|
||||||
# See Note [environment variables]
|
|
||||||
- if: matrix.os == 'windows-latest'
|
|
||||||
name: Get Release File Name & Upload URL on Widows
|
|
||||||
run: |
|
|
||||||
echo "upload_url=$(cat release_url/release_url.txt)" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
# See Note [environment variables]
|
|
||||||
- if: matrix.os != 'windows-latest'
|
|
||||||
name: Get Release File Name & Upload URL not on Widows
|
|
||||||
run: |
|
|
||||||
echo "upload_url=$(cat release_url/release_url.txt)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Upload Release Asset
|
|
||||||
id: upload-release-asset
|
|
||||||
uses: actions/upload-release-asset@v1.0.2
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ env.upload_url }}
|
|
||||||
asset_path: ${{ env.BINARY_PATH }}
|
|
||||||
asset_name: tetris-${{ steps.tag.outputs.tag }}-${{ runner.os }}
|
|
||||||
asset_content_type: application/octet-stream
|
|
12
app/Main.hs
12
app/Main.hs
|
@ -9,7 +9,7 @@ import qualified System.Directory as D
|
||||||
import System.FilePath ((</>))
|
import System.FilePath ((</>))
|
||||||
|
|
||||||
import Tetris (Game(..))
|
import Tetris (Game(..))
|
||||||
import UI.PickLevel (pickLevel, LevelConfig(..))
|
import UI.PickLevel (pickLevel)
|
||||||
import UI.Game (playGame)
|
import UI.Game (playGame)
|
||||||
|
|
||||||
data Opts = Opts
|
data Opts = Opts
|
||||||
|
@ -70,11 +70,11 @@ hdOptStr (CustomChars s) = Just s
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
(Opts hd ml hs) <- execParser fullopts
|
(Opts hd ml hs) <- execParser fullopts -- get CLI opts/args
|
||||||
when hs (getHighScore >>= printM >> exitSuccess)
|
when hs (getHighScore >>= printM >> exitSuccess) -- show high score and exit
|
||||||
levelConfig <- maybe pickLevel (\l -> return $ LevelConfig l False) ml
|
l <- maybe pickLevel return ml -- pick level prompt if necessary
|
||||||
g <- playGame (levelNumber levelConfig) (hdOptStr hd) (progression levelConfig)
|
g <- playGame l (hdOptStr hd) -- play game
|
||||||
handleEndGame (_score g)
|
handleEndGame (_score g) -- save & print score
|
||||||
|
|
||||||
handleEndGame :: Int -> IO ()
|
handleEndGame :: Int -> IO ()
|
||||||
handleEndGame s = do
|
handleEndGame s = do
|
||||||
|
|
106
src/Tetris.hs
106
src/Tetris.hs
|
@ -1,4 +1,3 @@
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE RankNTypes #-}
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
@ -12,6 +11,7 @@ module Tetris
|
||||||
, rotate
|
, rotate
|
||||||
, hardDrop
|
, hardDrop
|
||||||
-- Game state handlers
|
-- Game state handlers
|
||||||
|
, execTetris
|
||||||
, evalTetris
|
, evalTetris
|
||||||
-- Game state queries
|
-- Game state queries
|
||||||
, isGameOver
|
, isGameOver
|
||||||
|
@ -25,7 +25,7 @@ module Tetris
|
||||||
, Tetrimino(..)
|
, Tetrimino(..)
|
||||||
, Tetris
|
, Tetris
|
||||||
-- Lenses
|
-- Lenses
|
||||||
, block, board, level, nextShape, score, shape, linesCleared, progression
|
, block, board, level, nextShape, score, shape
|
||||||
-- Constants
|
-- Constants
|
||||||
, boardHeight, boardWidth, relCells
|
, boardHeight, boardWidth, relCells
|
||||||
) where
|
) where
|
||||||
|
@ -33,10 +33,9 @@ module Tetris
|
||||||
import Prelude hiding (Left, Right)
|
import Prelude hiding (Left, Right)
|
||||||
import Control.Applicative ((<|>))
|
import Control.Applicative ((<|>))
|
||||||
import Control.Monad (forM_, mfilter, when, (<=<))
|
import Control.Monad (forM_, mfilter, when, (<=<))
|
||||||
|
|
||||||
import Control.Monad.IO.Class (MonadIO(..), liftIO)
|
import Control.Monad.IO.Class (MonadIO(..), liftIO)
|
||||||
import Control.Monad.State.Class (MonadState, gets, put)
|
|
||||||
import Control.Monad.Trans.State (evalStateT)
|
import Control.Monad.Trans.State (StateT(..), gets, evalStateT, execStateT)
|
||||||
import Data.Map (Map)
|
import Data.Map (Map)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Data.Sequence (Seq(..), (><))
|
import Data.Sequence (Seq(..), (><))
|
||||||
|
@ -45,7 +44,6 @@ import Control.Lens hiding (Empty)
|
||||||
import Linear.V2 (V2(..), _y)
|
import Linear.V2 (V2(..), _y)
|
||||||
import qualified Linear.V2 as LV
|
import qualified Linear.V2 as LV
|
||||||
import System.Random (getStdRandom, randomR)
|
import System.Random (getStdRandom, randomR)
|
||||||
|
|
||||||
-- Types and instances
|
-- Types and instances
|
||||||
|
|
||||||
-- | Tetris shape types
|
-- | Tetris shape types
|
||||||
|
@ -79,17 +77,20 @@ data Game = Game
|
||||||
, _block :: Block
|
, _block :: Block
|
||||||
, _nextShape :: Tetrimino
|
, _nextShape :: Tetrimino
|
||||||
, _nextShapeBag :: Seq.Seq Tetrimino
|
, _nextShapeBag :: Seq.Seq Tetrimino
|
||||||
, _linesCleared :: Int
|
, _rowClears :: Seq.Seq Int
|
||||||
, _score :: Int
|
, _score :: Int
|
||||||
, _board :: Board
|
, _board :: Board
|
||||||
, _progression :: Bool
|
} deriving (Eq, Show)
|
||||||
} deriving (Eq)
|
|
||||||
makeLenses ''Game
|
makeLenses ''Game
|
||||||
|
|
||||||
|
type TetrisT = StateT Game
|
||||||
|
type Tetris a = forall m. (Monad m) => TetrisT m a
|
||||||
|
|
||||||
evalTetris :: Tetris a -> Game -> a
|
evalTetris :: Tetris a -> Game -> a
|
||||||
evalTetris m = runIdentity . evalStateT m
|
evalTetris m = runIdentity . evalStateT m
|
||||||
|
|
||||||
type Tetris a = forall m. MonadState Game m => m a
|
execTetris :: Tetris a -> Game -> Game
|
||||||
|
execTetris m = runIdentity . execStateT m
|
||||||
|
|
||||||
-- Translate class for direct translations, without concern for boundaries
|
-- Translate class for direct translations, without concern for boundaries
|
||||||
-- 'shift' concerns safe translations with boundaries
|
-- 'shift' concerns safe translations with boundaries
|
||||||
|
@ -162,8 +163,8 @@ bagFourTetriminoEach Empty =
|
||||||
bagFourTetriminoEach <=< shuffle . Seq.fromList . take 28 $ cycle [I ..]
|
bagFourTetriminoEach <=< shuffle . Seq.fromList . take 28 $ cycle [I ..]
|
||||||
|
|
||||||
-- | Initialize a game with a given level
|
-- | Initialize a game with a given level
|
||||||
initGame :: Int -> Bool -> IO Game -- Updated signature
|
initGame :: Int -> IO Game
|
||||||
initGame lvl prog = do
|
initGame lvl = do
|
||||||
(s1, bag1) <- bagFourTetriminoEach mempty
|
(s1, bag1) <- bagFourTetriminoEach mempty
|
||||||
(s2, bag2) <- bagFourTetriminoEach bag1
|
(s2, bag2) <- bagFourTetriminoEach bag1
|
||||||
pure $ Game
|
pure $ Game
|
||||||
|
@ -172,40 +173,31 @@ initGame lvl prog = do
|
||||||
, _nextShape = s2
|
, _nextShape = s2
|
||||||
, _nextShapeBag = bag2
|
, _nextShapeBag = bag2
|
||||||
, _score = 0
|
, _score = 0
|
||||||
, _linesCleared = 0
|
, _rowClears = mempty
|
||||||
, _board = mempty
|
, _board = mempty
|
||||||
, _progression = prog -- Added prog parameter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Increment level
|
|
||||||
nextLevel :: (MonadIO m, MonadState Game m) => m ()
|
|
||||||
nextLevel = do
|
|
||||||
level %= (+ 1)
|
|
||||||
|
|
||||||
isGameOver :: Game -> Bool
|
isGameOver :: Game -> Bool
|
||||||
isGameOver g = blockStopped g && g ^. (block . origin) == startOrigin
|
isGameOver g = blockStopped g && g ^. (block . origin) == startOrigin
|
||||||
|
|
||||||
-- | The main game execution, this is executed at each discrete time step
|
-- | The main game execution, this is executed at each discrete time step
|
||||||
timeStep :: (MonadIO m, MonadState Game m) => m ()
|
timeStep :: MonadIO m => TetrisT m ()
|
||||||
timeStep = do
|
timeStep = do
|
||||||
gets blockStopped >>= \case
|
gets blockStopped >>= \case
|
||||||
False -> gravitate
|
False -> gravitate
|
||||||
True -> do
|
True -> do
|
||||||
freezeBlock
|
freezeBlock
|
||||||
clearFullRows >>= updateScore
|
n <- clearFullRows
|
||||||
prog <- use progression
|
addToRowClears n
|
||||||
when prog $ do
|
updateScore
|
||||||
levelFinished >>= \case
|
|
||||||
True -> nextLevel
|
|
||||||
False -> pure ()
|
|
||||||
nextBlock
|
nextBlock
|
||||||
|
|
||||||
-- | Gravitate current block, i.e. shift down
|
-- | Gravitate current block, i.e. shift down
|
||||||
gravitate :: MonadState Game m => m ()
|
gravitate :: Tetris ()
|
||||||
gravitate = shift Down
|
gravitate = shift Down
|
||||||
|
|
||||||
-- | If necessary: clear full rows and return the count
|
-- | If necessary: clear full rows and return the count
|
||||||
clearFullRows :: MonadState Game m => m Int
|
clearFullRows :: Tetris Int
|
||||||
clearFullRows = do
|
clearFullRows = do
|
||||||
brd <- use board
|
brd <- use board
|
||||||
let rowSize r = length $ M.filterWithKey (\(V2 _ y) _ -> r == y) brd
|
let rowSize r = length $ M.filterWithKey (\(V2 _ y) _ -> r == y) brd
|
||||||
|
@ -215,42 +207,40 @@ clearFullRows = do
|
||||||
-- Shift cells above full rows
|
-- Shift cells above full rows
|
||||||
modifying board $ M.mapKeysMonotonic $ over _y $ \y ->
|
modifying board $ M.mapKeysMonotonic $ over _y $ \y ->
|
||||||
y - length (filter (< y) fullRows)
|
y - length (filter (< y) fullRows)
|
||||||
let clearedLines = length fullRows
|
return $ length fullRows
|
||||||
linesCleared %= (+ clearedLines)
|
|
||||||
pure clearedLines
|
|
||||||
|
|
||||||
-- | This updates game points with respect to the provided number of cleared
|
-- | Empties row on 0, otherwise appends value (just keeps consecutive information)
|
||||||
-- lines.
|
addToRowClears :: Int -> Tetris ()
|
||||||
|
addToRowClears 0 = rowClears .= mempty
|
||||||
|
addToRowClears n = rowClears %= (|> n)
|
||||||
|
|
||||||
|
-- | This updates game points with respect to the current
|
||||||
|
-- _rowClears value (thus should only be used ONCE per step)
|
||||||
--
|
--
|
||||||
-- See https://tetris.fandom.com/wiki/Scoring
|
-- Note I'm keeping rowClears as a sequence in case I want to award
|
||||||
updateScore :: (MonadState Game m, MonadIO m) => Int -> m ()
|
-- more points for back to back clears, right now the scoring is more simple,
|
||||||
updateScore 0 = pure ()
|
-- but you do get more points for more rows cleared at once.
|
||||||
updateScore lines = do
|
updateScore :: Tetris ()
|
||||||
lvl <- use level
|
updateScore = do
|
||||||
let newPoints = (lvl + 1) * points lines
|
multiplier <- (1 +) <$> use level
|
||||||
|
clears <- latestOrZero <$> use rowClears
|
||||||
|
let newPoints = multiplier * points clears
|
||||||
score %= (+ newPoints)
|
score %= (+ newPoints)
|
||||||
where
|
where
|
||||||
-- Translate row line clears to points
|
-- Translate row clears to points
|
||||||
points 0 = 0
|
points 0 = 0
|
||||||
points 1 = 40
|
points 1 = 40
|
||||||
points 2 = 100
|
points 2 = 100
|
||||||
points 3 = 300
|
points 3 = 300
|
||||||
points _ = 1200
|
points _ = 800
|
||||||
|
-- | Get last value of sequence or 0 if empty
|
||||||
-- | Using the fixed-goal system described here: https://tetris.wiki/Marathon
|
latestOrZero :: Seq.Seq Int -> Int
|
||||||
levelFinished :: (MonadState Game m, MonadIO m) => m Bool
|
latestOrZero Empty = 0
|
||||||
levelFinished = do
|
latestOrZero (_ :|> n) = n
|
||||||
prog <- use progression
|
|
||||||
if not prog
|
|
||||||
then pure False
|
|
||||||
else do
|
|
||||||
lvl <- use level
|
|
||||||
lc <- use linesCleared
|
|
||||||
pure $ lvl < 15 && lc >= 10 * (lvl + 1)
|
|
||||||
|
|
||||||
-- | Handle counterclockwise block rotation (if possible)
|
-- | Handle counterclockwise block rotation (if possible)
|
||||||
-- Allows wallkicks: http://tetris.wikia.com/wiki/TGM_rotation
|
-- Allows wallkicks: http://tetris.wikia.com/wiki/TGM_rotation
|
||||||
rotate :: MonadState Game m => m ()
|
rotate :: Tetris ()
|
||||||
rotate = do
|
rotate = do
|
||||||
blk <- use block
|
blk <- use block
|
||||||
brd <- use board
|
brd <- use board
|
||||||
|
@ -274,10 +264,10 @@ isStopped brd = any stopped . coords
|
||||||
stopped = (||) <$> atBottom <*> (`M.member` brd) . translate Down
|
stopped = (||) <$> atBottom <*> (`M.member` brd) . translate Down
|
||||||
atBottom = (== 1) . view _y
|
atBottom = (== 1) . view _y
|
||||||
|
|
||||||
hardDrop :: MonadState Game m => m ()
|
hardDrop :: Tetris ()
|
||||||
hardDrop = hardDroppedBlock >>= assign block
|
hardDrop = hardDroppedBlock >>= assign block
|
||||||
|
|
||||||
hardDroppedBlock :: MonadState Game m => m Block
|
hardDroppedBlock :: Tetris Block
|
||||||
hardDroppedBlock = do
|
hardDroppedBlock = do
|
||||||
boardCoords <- M.keys <$> use board
|
boardCoords <- M.keys <$> use board
|
||||||
blockCoords <- coords <$> use block
|
blockCoords <- coords <$> use block
|
||||||
|
@ -293,13 +283,13 @@ hardDroppedBlock = do
|
||||||
translateBy dist Down <$> use block
|
translateBy dist Down <$> use block
|
||||||
|
|
||||||
-- | Freeze current block
|
-- | Freeze current block
|
||||||
freezeBlock :: MonadState Game m => m ()
|
freezeBlock :: Tetris ()
|
||||||
freezeBlock = do
|
freezeBlock = do
|
||||||
blk <- use block
|
blk <- use block
|
||||||
modifying board $ M.union $ M.fromList [ (c, _shape blk) | c <- coords blk ]
|
modifying board $ M.union $ M.fromList [ (c, _shape blk) | c <- coords blk ]
|
||||||
|
|
||||||
-- | Replace block with next block
|
-- | Replace block with next block
|
||||||
nextBlock :: (MonadIO m, MonadState Game m) => m ()
|
nextBlock :: MonadIO m => TetrisT m ()
|
||||||
nextBlock = do
|
nextBlock = do
|
||||||
bag <- use nextShapeBag
|
bag <- use nextShapeBag
|
||||||
(t, ts) <- liftIO $ bagFourTetriminoEach bag
|
(t, ts) <- liftIO $ bagFourTetriminoEach bag
|
||||||
|
@ -308,7 +298,7 @@ nextBlock = do
|
||||||
nextShapeBag .= ts
|
nextShapeBag .= ts
|
||||||
|
|
||||||
-- | Try to shift current block; if shifting not possible, leave block where it is
|
-- | Try to shift current block; if shifting not possible, leave block where it is
|
||||||
shift :: MonadState Game m => Direction -> m ()
|
shift :: Direction -> Tetris ()
|
||||||
shift dir = do
|
shift dir = do
|
||||||
brd <- use board
|
brd <- use board
|
||||||
blk <- use block
|
blk <- use block
|
||||||
|
|
160
src/UI/Game.hs
160
src/UI/Game.hs
|
@ -1,4 +1,3 @@
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE RankNTypes #-}
|
{-# LANGUAGE RankNTypes #-}
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
@ -8,8 +7,9 @@ module UI.Game
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Concurrent (threadDelay, forkIO)
|
import Control.Concurrent (threadDelay, forkIO)
|
||||||
import Control.Concurrent.STM (TVar, newTVarIO, readTVarIO, writeTVar, atomically)
|
import Control.Monad (void, forever, when, unless)
|
||||||
import Control.Monad (void, forever)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Control.Monad.Trans.State (execStateT)
|
||||||
import Prelude hiding (Left, Right)
|
import Prelude hiding (Left, Right)
|
||||||
|
|
||||||
import Brick hiding (Down)
|
import Brick hiding (Down)
|
||||||
|
@ -18,11 +18,7 @@ import qualified Brick.Widgets.Border as B
|
||||||
import qualified Brick.Widgets.Border.Style as BS
|
import qualified Brick.Widgets.Border.Style as BS
|
||||||
import qualified Brick.Widgets.Center as C
|
import qualified Brick.Widgets.Center as C
|
||||||
import Control.Lens hiding (preview, op, zoom)
|
import Control.Lens hiding (preview, op, zoom)
|
||||||
import Control.Monad.Extra (orM, unlessM)
|
|
||||||
import Control.Monad.IO.Class (liftIO)
|
|
||||||
import qualified Graphics.Vty as V
|
import qualified Graphics.Vty as V
|
||||||
import qualified Graphics.Vty.CrossPlatform
|
|
||||||
import qualified Graphics.Vty.Config
|
|
||||||
import Data.Map (Map)
|
import Data.Map (Map)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Linear.V2 (V2(..))
|
import Linear.V2 (V2(..))
|
||||||
|
@ -30,12 +26,10 @@ import Linear.V2 (V2(..))
|
||||||
import Tetris
|
import Tetris
|
||||||
|
|
||||||
data UI = UI
|
data UI = UI
|
||||||
{ _game :: Game
|
{ _game :: Game -- ^ tetris game
|
||||||
, _initLevel :: Int
|
, _preview :: Maybe String -- ^ hard drop preview cell
|
||||||
, _currLevel :: TVar Int
|
, _locked :: Bool -- ^ lock after hard drop before time step
|
||||||
, _preview :: Maybe String
|
, _paused :: Bool -- ^ game paused
|
||||||
, _locked :: Bool
|
|
||||||
, _paused :: Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
makeLenses ''UI
|
makeLenses ''UI
|
||||||
|
@ -61,27 +55,24 @@ app = App
|
||||||
, appAttrMap = const theMap
|
, appAttrMap = const theMap
|
||||||
}
|
}
|
||||||
|
|
||||||
playGame :: Int -- ^ Starting level
|
playGame
|
||||||
-> Maybe String -- ^ Preview cell (Nothing == no preview)
|
:: Int -- ^ Starting level
|
||||||
-> Bool -- ^ Enable level progression
|
-> Maybe String -- ^ Preview cell (Nothing == no preview)
|
||||||
-> IO Game
|
-> IO Game
|
||||||
playGame lvl mp prog = do
|
playGame lvl mp = do
|
||||||
|
let delay = levelToDelay lvl
|
||||||
chan <- newBChan 10
|
chan <- newBChan 10
|
||||||
tv <- newTVarIO lvl
|
|
||||||
void . forkIO $ forever $ do
|
void . forkIO $ forever $ do
|
||||||
writeBChan chan Tick
|
writeBChan chan Tick
|
||||||
lvl <- readTVarIO tv
|
threadDelay delay
|
||||||
threadDelay $ levelToDelay lvl
|
initialGame <- initGame lvl
|
||||||
initialGame <- initGame lvl prog -- Pass the progression parameter
|
let builder = V.mkVty V.defaultConfig
|
||||||
let buildVty = Graphics.Vty.CrossPlatform.mkVty Graphics.Vty.Config.defaultConfig
|
initialVty <- builder
|
||||||
initialVty <- buildVty
|
ui <- customMain initialVty builder (Just chan) app $ UI
|
||||||
ui <- customMain initialVty buildVty (Just chan) app $ UI
|
{ _game = initialGame
|
||||||
{ _game = initialGame
|
, _preview = mp
|
||||||
, _initLevel = lvl
|
, _locked = False
|
||||||
, _currLevel = tv
|
, _paused = False
|
||||||
, _preview = mp
|
|
||||||
, _locked = False
|
|
||||||
, _paused = False
|
|
||||||
}
|
}
|
||||||
return $ ui ^. game
|
return $ ui ^. game
|
||||||
|
|
||||||
|
@ -91,9 +82,7 @@ levelToDelay n = floor $ 400000 * (0.85 :: Double) ^ (2 * n)
|
||||||
-- Handling events
|
-- Handling events
|
||||||
|
|
||||||
handleEvent :: BrickEvent Name Tick -> EventM Name UI ()
|
handleEvent :: BrickEvent Name Tick -> EventM Name UI ()
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar 'r') [])) = restart
|
handleEvent (AppEvent Tick ) = handleTick
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar 'q') [])) = halt
|
|
||||||
handleEvent (VtyEvent (V.EvKey V.KEsc [])) = halt
|
|
||||||
handleEvent (VtyEvent (V.EvKey V.KRight [])) = exec (shift Right)
|
handleEvent (VtyEvent (V.EvKey V.KRight [])) = exec (shift Right)
|
||||||
handleEvent (VtyEvent (V.EvKey V.KLeft [])) = exec (shift Left)
|
handleEvent (VtyEvent (V.EvKey V.KLeft [])) = exec (shift Left)
|
||||||
handleEvent (VtyEvent (V.EvKey V.KDown [])) = exec (shift Down)
|
handleEvent (VtyEvent (V.EvKey V.KDown [])) = exec (shift Down)
|
||||||
|
@ -103,36 +92,53 @@ handleEvent (VtyEvent (V.EvKey (V.KChar 'j') [])) = exec (shift Down)
|
||||||
handleEvent (VtyEvent (V.EvKey V.KUp [])) = exec rotate
|
handleEvent (VtyEvent (V.EvKey V.KUp [])) = exec rotate
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar 'k') [])) = exec rotate
|
handleEvent (VtyEvent (V.EvKey (V.KChar 'k') [])) = exec rotate
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar ' ') [])) =
|
handleEvent (VtyEvent (V.EvKey (V.KChar ' ') [])) =
|
||||||
unlessM (orM [use paused, use (game . to isGameOver)]) $ do
|
guarded
|
||||||
zoom game hardDrop
|
(not . view paused)
|
||||||
assign locked True
|
(over game (execTetris hardDrop) . set locked True)
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar 'p') [])) =
|
handleEvent (VtyEvent (V.EvKey (V.KChar 'p') [])) =
|
||||||
unlessM (orM [use locked, use (game . to isGameOver)]) $ do
|
guarded
|
||||||
modifying paused not
|
(not . view locked)
|
||||||
handleEvent (AppEvent Tick ) =
|
(over paused not)
|
||||||
unlessM (orM [use paused, use (game . to isGameOver)]) $ do
|
handleEvent (VtyEvent (V.EvKey (V.KChar 'r') [])) = restart
|
||||||
zoom game timeStep
|
handleEvent (VtyEvent (V.EvKey (V.KChar 'q') [])) = halt
|
||||||
-- Keep level in sync with ticker (gross)
|
handleEvent (VtyEvent (V.EvKey V.KEsc [])) = halt
|
||||||
lvl <- use $ game . level
|
|
||||||
tv <- use $ currLevel
|
|
||||||
liftIO . atomically $ writeTVar tv lvl
|
|
||||||
assign locked False
|
|
||||||
handleEvent _ = pure ()
|
handleEvent _ = pure ()
|
||||||
|
|
||||||
-- | This common execution function is used for all game user input except hard
|
-- | This common execution function is used for all game user input except hard
|
||||||
-- drop and pause. If paused or locked (from hard drop) do nothing, else
|
-- drop and pause. If paused or locked (from hard drop) do nothing, else
|
||||||
-- execute the state computation.
|
-- execute the state computation.
|
||||||
exec :: Tetris () -> EventM Name UI ()
|
exec :: Tetris () -> EventM Name UI ()
|
||||||
exec = unlessM (orM [use paused, use locked, use (game . to isGameOver)]) . zoom game
|
exec op =
|
||||||
|
guarded
|
||||||
|
(not . \ui -> ui ^. paused || ui ^. locked)
|
||||||
|
(game %~ execTetris op)
|
||||||
|
|
||||||
-- | Restart game at the initially chosen level
|
-- | This base execution function takes a predicate and only issues UI
|
||||||
|
-- modification when predicate passes and game is not over.
|
||||||
|
guarded :: (UI -> Bool) -> (UI -> UI) -> EventM Name UI ()
|
||||||
|
guarded p f = do
|
||||||
|
ui <- get
|
||||||
|
when (p ui && not (ui ^. game . to isGameOver)) $
|
||||||
|
modify f
|
||||||
|
|
||||||
|
-- | Handles time steps, does nothing if game is over or paused
|
||||||
|
handleTick :: EventM Name UI ()
|
||||||
|
handleTick = do
|
||||||
|
ui <- get
|
||||||
|
unless (ui ^. paused || ui ^. game . to isGameOver) $ do
|
||||||
|
-- awkward, should just mutate the inner state
|
||||||
|
--zoom game timeStep
|
||||||
|
g' <- execStateT timeStep $ ui ^. game
|
||||||
|
game .= g'
|
||||||
|
locked .= False
|
||||||
|
|
||||||
|
-- | Restart game at the same level
|
||||||
restart :: EventM Name UI ()
|
restart :: EventM Name UI ()
|
||||||
restart = do
|
restart = do
|
||||||
lvl <- use initLevel
|
lvl <- use $ game . level
|
||||||
prog <- use (game . progression) -- Get current progression setting
|
g <- liftIO $ initGame lvl
|
||||||
g <- liftIO $ initGame lvl prog -- Use it when restarting
|
game .= g
|
||||||
assign game g
|
locked .= False
|
||||||
assign locked False
|
|
||||||
|
|
||||||
-- Drawing
|
-- Drawing
|
||||||
|
|
||||||
|
@ -212,20 +218,10 @@ drawStats g =
|
||||||
$ B.borderWithLabel (str "Stats")
|
$ B.borderWithLabel (str "Stats")
|
||||||
$ vBox
|
$ vBox
|
||||||
[ drawStat "Score" $ g ^. score
|
[ drawStat "Score" $ g ^. score
|
||||||
, padTop (Pad 1) $ drawStat "Lines" $ g ^. linesCleared
|
|
||||||
, padTop (Pad 1) $ drawStat "Level" $ g ^. level
|
, padTop (Pad 1) $ drawStat "Level" $ g ^. level
|
||||||
, padTop (Pad 1) $ drawProgression (g ^. progression)
|
|
||||||
, drawLeaderBoard g
|
, drawLeaderBoard g
|
||||||
]
|
]
|
||||||
|
|
||||||
drawProgression :: Bool -> Widget Name
|
|
||||||
drawProgression True =
|
|
||||||
padLeftRight 1 $ str "Level Mode: " <+>
|
|
||||||
withAttr progressionAttr (padLeft Max $ str "ON")
|
|
||||||
drawProgression False =
|
|
||||||
padLeftRight 1 $ str "Level Mode: " <+>
|
|
||||||
withAttr fixedAttr (padLeft Max $ str "Fixed")
|
|
||||||
|
|
||||||
drawStat :: String -> Int -> Widget Name
|
drawStat :: String -> Int -> Widget Name
|
||||||
drawStat s n = padLeftRight 1 $ str s <+> padLeft Max (str $ show n)
|
drawStat s n = padLeftRight 1 $ str s <+> padLeft Max (str $ show n)
|
||||||
|
|
||||||
|
@ -287,23 +283,21 @@ drawGameOver g =
|
||||||
theMap :: AttrMap
|
theMap :: AttrMap
|
||||||
theMap = attrMap
|
theMap = attrMap
|
||||||
V.defAttr
|
V.defAttr
|
||||||
[ (iAttr , tToColor I `on` tToColor I)
|
[ (iAttr , tToColor I `on` tToColor I)
|
||||||
, (oAttr , tToColor O `on` tToColor O)
|
, (oAttr , tToColor O `on` tToColor O)
|
||||||
, (tAttr , tToColor T `on` tToColor T)
|
, (tAttr , tToColor T `on` tToColor T)
|
||||||
, (sAttr , tToColor S `on` tToColor S)
|
, (sAttr , tToColor S `on` tToColor S)
|
||||||
, (zAttr , tToColor Z `on` tToColor Z)
|
, (zAttr , tToColor Z `on` tToColor Z)
|
||||||
, (jAttr , tToColor J `on` tToColor J)
|
, (jAttr , tToColor J `on` tToColor J)
|
||||||
, (lAttr , tToColor L `on` tToColor L)
|
, (lAttr , tToColor L `on` tToColor L)
|
||||||
, (ihAttr , fg $ tToColor I)
|
, (ihAttr , fg $ tToColor I)
|
||||||
, (ohAttr , fg $ tToColor O)
|
, (ohAttr , fg $ tToColor O)
|
||||||
, (thAttr , fg $ tToColor T)
|
, (thAttr , fg $ tToColor T)
|
||||||
, (shAttr , fg $ tToColor S)
|
, (shAttr , fg $ tToColor S)
|
||||||
, (zhAttr , fg $ tToColor Z)
|
, (zhAttr , fg $ tToColor Z)
|
||||||
, (jhAttr , fg $ tToColor J)
|
, (jhAttr , fg $ tToColor J)
|
||||||
, (lhAttr , fg $ tToColor L)
|
, (lhAttr , fg $ tToColor L)
|
||||||
, (gameOverAttr , fg V.red `V.withStyle` V.bold)
|
, (gameOverAttr, fg V.red `V.withStyle` V.bold)
|
||||||
, (progressionAttr, fg V.green `V.withStyle` V.bold)
|
|
||||||
, (fixedAttr , fg V.blue `V.withStyle` V.bold)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
tToColor :: Tetrimino -> V.Color
|
tToColor :: Tetrimino -> V.Color
|
||||||
|
@ -338,7 +332,3 @@ emptyAttr = attrName "empty"
|
||||||
|
|
||||||
gameOverAttr :: AttrName
|
gameOverAttr :: AttrName
|
||||||
gameOverAttr = attrName "gameOver"
|
gameOverAttr = attrName "gameOver"
|
||||||
|
|
||||||
progressionAttr, fixedAttr :: AttrName
|
|
||||||
progressionAttr = attrName "progression"
|
|
||||||
fixedAttr = attrName "fixed"
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
module UI.PickLevel
|
module UI.PickLevel
|
||||||
( pickLevel
|
( pickLevel
|
||||||
, LevelConfig(..)
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import System.Exit (exitSuccess)
|
import System.Exit (exitSuccess)
|
||||||
|
@ -12,39 +11,17 @@ import qualified Brick.Widgets.Border.Style as BS
|
||||||
import qualified Brick.Widgets.Center as C
|
import qualified Brick.Widgets.Center as C
|
||||||
import qualified Graphics.Vty as V
|
import qualified Graphics.Vty as V
|
||||||
|
|
||||||
data LevelConfig = LevelConfig
|
app :: App (Maybe Int) e ()
|
||||||
{ levelNumber :: Int
|
|
||||||
, progression :: Bool
|
|
||||||
} deriving (Show, Eq)
|
|
||||||
|
|
||||||
data MenuOption = YesOption | NoOption deriving (Eq)
|
|
||||||
|
|
||||||
data PickState = PickState
|
|
||||||
{ currentLevel :: Maybe Int
|
|
||||||
, showProgression :: Bool
|
|
||||||
, pickingLevel :: Bool
|
|
||||||
, selectedOption :: MenuOption
|
|
||||||
}
|
|
||||||
|
|
||||||
app :: App PickState e ()
|
|
||||||
app = App
|
app = App
|
||||||
{ appDraw = drawUI
|
{ appDraw = const [ui]
|
||||||
, appHandleEvent = handleEvent
|
, appHandleEvent = handleEvent
|
||||||
, appStartEvent = pure ()
|
, appStartEvent = pure ()
|
||||||
, appAttrMap = const $ attrMap V.defAttr
|
, appAttrMap = const $ attrMap V.defAttr []
|
||||||
[ (selectedAttr, V.black `on` V.white)
|
|
||||||
]
|
|
||||||
, appChooseCursor = neverShowCursor
|
, appChooseCursor = neverShowCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedAttr :: AttrName
|
ui :: Widget ()
|
||||||
selectedAttr = attrName "selected"
|
ui =
|
||||||
|
|
||||||
drawUI :: PickState -> [Widget ()]
|
|
||||||
drawUI ps = [ui ps]
|
|
||||||
|
|
||||||
ui :: PickState -> Widget ()
|
|
||||||
ui ps =
|
|
||||||
padLeft (Pad 19)
|
padLeft (Pad 19)
|
||||||
$ padRight (Pad 21)
|
$ padRight (Pad 21)
|
||||||
$ C.center
|
$ C.center
|
||||||
|
@ -53,69 +30,17 @@ ui ps =
|
||||||
$ withBorderStyle BS.unicodeBold
|
$ withBorderStyle BS.unicodeBold
|
||||||
$ B.borderWithLabel (str "Tetris")
|
$ B.borderWithLabel (str "Tetris")
|
||||||
$ C.center
|
$ C.center
|
||||||
$ vBox
|
$ str " Choose Level (0-9)"
|
||||||
[ if pickingLevel ps
|
|
||||||
then str "Choose Level (0-9)"
|
|
||||||
else vBox
|
|
||||||
[ str "Level Progression?"
|
|
||||||
, str ""
|
|
||||||
, C.hCenter $ drawOption "YES" YesOption (selectedOption ps)
|
|
||||||
, C.hCenter $ drawOption "NO" NoOption (selectedOption ps)
|
|
||||||
, str ""
|
|
||||||
, C.hCenter $ str "Use ↑↓ or j/k"
|
|
||||||
, C.hCenter $ str "to Select."
|
|
||||||
, str ""
|
|
||||||
, C.hCenter $ str "Press Enter"
|
|
||||||
, C.hCenter $ str "to Continue."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
drawOption :: String -> MenuOption -> MenuOption -> Widget ()
|
handleEvent :: BrickEvent () e -> EventM () (Maybe Int) ()
|
||||||
drawOption label opt current =
|
|
||||||
withAttr (if opt == current then selectedAttr else attrName "")
|
|
||||||
$ str $ " " ++ label ++ " "
|
|
||||||
|
|
||||||
handleEvent :: BrickEvent () e -> EventM () PickState ()
|
|
||||||
handleEvent (VtyEvent (V.EvKey V.KEsc _)) = halt
|
handleEvent (VtyEvent (V.EvKey V.KEsc _)) = halt
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar 'q') _)) = halt
|
handleEvent (VtyEvent (V.EvKey (V.KChar 'q') _)) = halt
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar 'Q') _)) = halt
|
handleEvent (VtyEvent (V.EvKey (V.KChar 'Q') _)) = halt
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar d) [])) =
|
handleEvent (VtyEvent (V.EvKey (V.KChar d) [])) =
|
||||||
whenPickingLevel $ when (d `elem` ['0' .. '9']) $ do
|
when (d `elem` ['0' .. '9']) $ do
|
||||||
modify $ \s -> s { currentLevel = Just $ read [d], pickingLevel = False }
|
put $ Just $ read [d]
|
||||||
handleEvent (VtyEvent (V.EvKey V.KEnter [])) = do
|
halt
|
||||||
s <- get
|
|
||||||
when (not $ pickingLevel s) $ do
|
|
||||||
case currentLevel s of
|
|
||||||
Just l -> do
|
|
||||||
put $ PickState (Just l) (selectedOption s == YesOption) True YesOption
|
|
||||||
halt
|
|
||||||
Nothing -> pure ()
|
|
||||||
handleEvent (VtyEvent (V.EvKey V.KUp [])) =
|
|
||||||
whenNotPickingLevel $ modify $ \s -> s { selectedOption = YesOption }
|
|
||||||
handleEvent (VtyEvent (V.EvKey V.KDown [])) =
|
|
||||||
whenNotPickingLevel $ modify $ \s -> s { selectedOption = NoOption }
|
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar 'j') [])) =
|
|
||||||
whenNotPickingLevel $ modify $ \s -> s { selectedOption = YesOption }
|
|
||||||
handleEvent (VtyEvent (V.EvKey (V.KChar 'k') [])) =
|
|
||||||
whenNotPickingLevel $ modify $ \s -> s { selectedOption = NoOption }
|
|
||||||
handleEvent _ = pure ()
|
handleEvent _ = pure ()
|
||||||
|
|
||||||
whenPickingLevel :: EventM () PickState () -> EventM () PickState ()
|
pickLevel :: IO Int
|
||||||
whenPickingLevel action = do
|
pickLevel = defaultMain app Nothing >>= maybe exitSuccess return
|
||||||
picking <- gets pickingLevel
|
|
||||||
when picking action
|
|
||||||
|
|
||||||
whenNotPickingLevel :: EventM () PickState () -> EventM () PickState ()
|
|
||||||
whenNotPickingLevel action = do
|
|
||||||
picking <- gets pickingLevel
|
|
||||||
when (not picking) action
|
|
||||||
|
|
||||||
initialState :: PickState
|
|
||||||
initialState = PickState Nothing True True YesOption
|
|
||||||
|
|
||||||
pickLevel :: IO LevelConfig
|
|
||||||
pickLevel = do
|
|
||||||
result <- defaultMain app initialState
|
|
||||||
case currentLevel result of
|
|
||||||
Nothing -> exitSuccess
|
|
||||||
Just l -> return $ LevelConfig l (showProgression result)
|
|
||||||
|
|
75
stack.yaml
75
stack.yaml
|
@ -1,67 +1,10 @@
|
||||||
# This file was automatically generated by 'stack init'
|
flags: {}
|
||||||
#
|
extra-package-dbs: []
|
||||||
# Some commonly used options have been documented as comments in this file.
|
|
||||||
# For advanced use and comprehensive documentation of the format, please see:
|
|
||||||
# https://docs.haskellstack.org/en/stable/yaml_configuration/
|
|
||||||
|
|
||||||
# A 'specific' Stackage snapshot or a compiler version.
|
|
||||||
# A snapshot resolver dictates the compiler version and the set of packages
|
|
||||||
# to be used for project dependencies. For example:
|
|
||||||
#
|
|
||||||
# snapshot: lts-22.28
|
|
||||||
# snapshot: nightly-2024-07-05
|
|
||||||
# snapshot: ghc-9.6.6
|
|
||||||
#
|
|
||||||
# The location of a snapshot can be provided as a file or url. Stack assumes
|
|
||||||
# a snapshot provided as a file might change, whereas a url resource does not.
|
|
||||||
#
|
|
||||||
# snapshot: ./custom-snapshot.yaml
|
|
||||||
# snapshot: https://example.com/snapshots/2024-01-01.yaml
|
|
||||||
snapshot:
|
|
||||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/43.yaml
|
|
||||||
|
|
||||||
# User packages to be built.
|
|
||||||
# Various formats can be used as shown in the example below.
|
|
||||||
#
|
|
||||||
# packages:
|
|
||||||
# - some-directory
|
|
||||||
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
|
|
||||||
# subdirs:
|
|
||||||
# - auto-update
|
|
||||||
# - wai
|
|
||||||
packages:
|
packages:
|
||||||
- .
|
- '.'
|
||||||
# Dependency packages to be pulled from upstream that are not in the snapshot.
|
extra-deps: []
|
||||||
# These entries can reference officially published versions as well as
|
resolver: lts-20.1
|
||||||
# forks / in-progress versions pinned to a git hash. For example:
|
nix:
|
||||||
#
|
packages:
|
||||||
# extra-deps:
|
- gcc
|
||||||
# - acme-missiles-0.3
|
- ncurses
|
||||||
# - git: https://github.com/commercialhaskell/stack.git
|
|
||||||
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
|
|
||||||
#
|
|
||||||
# extra-deps: []
|
|
||||||
|
|
||||||
# Override default flag values for project packages and extra-deps
|
|
||||||
# flags: {}
|
|
||||||
|
|
||||||
# Extra package databases containing global packages
|
|
||||||
# extra-package-dbs: []
|
|
||||||
|
|
||||||
# Control whether we use the GHC we find on the path
|
|
||||||
# system-ghc: true
|
|
||||||
#
|
|
||||||
# Require a specific version of Stack, using version ranges
|
|
||||||
# require-stack-version: -any # Default
|
|
||||||
# require-stack-version: ">=3.1"
|
|
||||||
#
|
|
||||||
# Override the architecture used by Stack, especially useful on Windows
|
|
||||||
# arch: i386
|
|
||||||
# arch: x86_64
|
|
||||||
#
|
|
||||||
# Extra directories used by Stack for building
|
|
||||||
# extra-include-dirs: [/path/to/dir]
|
|
||||||
# extra-lib-dirs: [/path/to/dir]
|
|
||||||
#
|
|
||||||
# Allow a newer minor version of GHC than the snapshot specifies
|
|
||||||
# compiler-check: newer-minor
|
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
packages: []
|
packages: []
|
||||||
snapshots:
|
snapshots:
|
||||||
- completed:
|
- completed:
|
||||||
sha256: 08bd13ce621b41a8f5e51456b38d5b46d7783ce114a50ab604d6bbab0d002146
|
sha256: b73b2b116143aea728c70e65c3239188998bac5bc3be56465813dacd74215dc5
|
||||||
size: 720271
|
size: 648424
|
||||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/43.yaml
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/1.yaml
|
||||||
original:
|
original: lts-20.1
|
||||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/43.yaml
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: tetris
|
name: tetris
|
||||||
version: 0.1.6
|
version: 0.1.5
|
||||||
homepage: https://github.com/samtay/tetris#readme
|
homepage: https://github.com/samtay/tetris#readme
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
|
@ -19,15 +19,11 @@ library
|
||||||
build-depends: base >= 4.7 && < 5
|
build-depends: base >= 4.7 && < 5
|
||||||
, brick
|
, brick
|
||||||
, containers
|
, containers
|
||||||
, extra
|
|
||||||
, lens
|
, lens
|
||||||
, linear
|
, linear
|
||||||
, mtl
|
|
||||||
, random
|
, random
|
||||||
, stm
|
|
||||||
, transformers
|
, transformers
|
||||||
, vty
|
, vty
|
||||||
, vty-crossplatform
|
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
executable tetris
|
executable tetris
|
||||||
|
|
Loading…
Add table
Reference in a new issue