Build out more game mechanics
Typical tetris randomizer, bring in `containers`, swap to absolute cell locations, write game initializer.
This commit is contained in:
		
							parent
							
								
									7cdf59d5aa
								
							
						
					
					
						commit
						1633c4a9c7
					
				
					 5 changed files with 96 additions and 98 deletions
				
			
		
							
								
								
									
										119
									
								
								src/Tetris.hs
									
										
									
									
									
								
							
							
						
						
									
										119
									
								
								src/Tetris.hs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -2,35 +2,32 @@
 | 
			
		|||
{-# LANGUAGE RecordWildCards #-}
 | 
			
		||||
{-# LANGUAGE TypeSynonymInstances #-}
 | 
			
		||||
{-# LANGUAGE FlexibleInstances #-}
 | 
			
		||||
module Tetris
 | 
			
		||||
    (
 | 
			
		||||
    ) where
 | 
			
		||||
module Tetris where
 | 
			
		||||
 | 
			
		||||
import Data.Map (Map)
 | 
			
		||||
import qualified Data.Map as M
 | 
			
		||||
import Data.Sequence (ViewL(..), (<|), (><))
 | 
			
		||||
import qualified Data.Sequence as Seq
 | 
			
		||||
import Lens.Micro
 | 
			
		||||
import Lens.Micro.TH
 | 
			
		||||
import System.Random (getStdRandom, randomR)
 | 
			
		||||
 | 
			
		||||
import Prelude hiding (Left, Right)
 | 
			
		||||
 | 
			
		||||
-- Types and instances
 | 
			
		||||
 | 
			
		||||
-- | Tetris shape types
 | 
			
		||||
data Tetrimino =
 | 
			
		||||
    I
 | 
			
		||||
  | O
 | 
			
		||||
  | T
 | 
			
		||||
  | S
 | 
			
		||||
  | Z
 | 
			
		||||
  | J
 | 
			
		||||
  | L
 | 
			
		||||
data Tetrimino = I | O | T | S | Z | J | L
 | 
			
		||||
  deriving (Eq, Show, Enum)
 | 
			
		||||
 | 
			
		||||
-- | Coordinates
 | 
			
		||||
type Coord = (Int, Int)
 | 
			
		||||
type CoordMap = (Int -> Int, Int -> Int)
 | 
			
		||||
 | 
			
		||||
-- | Tetris shape in coordinate context
 | 
			
		||||
data Block = Block
 | 
			
		||||
  { _shape  :: Tetrimino -- ^ block type
 | 
			
		||||
  , _origin :: Coord -- ^ origin (absolute)
 | 
			
		||||
  , _extra  :: [Coord] -- ^ extraneous cells (relative)
 | 
			
		||||
  , _origin :: Coord -- ^ origin
 | 
			
		||||
  , _extra  :: [Coord] -- ^ extraneous cells
 | 
			
		||||
  } deriving (Eq, Show)
 | 
			
		||||
 | 
			
		||||
makeLenses ''Block
 | 
			
		||||
| 
						 | 
				
			
			@ -43,13 +40,14 @@ data Cell = Filled Tetrimino | Empty
 | 
			
		|||
  deriving (Eq, Show)
 | 
			
		||||
 | 
			
		||||
-- | Board of cells
 | 
			
		||||
type Board = M.Map Coord Cell
 | 
			
		||||
type Board = Map Coord Cell
 | 
			
		||||
 | 
			
		||||
-- | Game state
 | 
			
		||||
data Game = Game
 | 
			
		||||
  { _speed :: Int
 | 
			
		||||
  { _level :: Int
 | 
			
		||||
  , _currBlock :: Block
 | 
			
		||||
  , _nextShape :: Tetrimino
 | 
			
		||||
  , _nextShapeBag :: Seq.Seq Tetrimino
 | 
			
		||||
  , _score :: Int
 | 
			
		||||
  , _board :: Board
 | 
			
		||||
  } deriving (Eq, Show)
 | 
			
		||||
| 
						 | 
				
			
			@ -66,27 +64,84 @@ instance Translatable Coord where
 | 
			
		|||
  translate Down (x,y) = (x, y-1)
 | 
			
		||||
 | 
			
		||||
instance Translatable Block where
 | 
			
		||||
  translate d b = b & origin %~ translate d
 | 
			
		||||
  translate d b =
 | 
			
		||||
    b & origin %~ translate d
 | 
			
		||||
      & extra %~ fmap (translate d)
 | 
			
		||||
 | 
			
		||||
initI, initO, initS, initZ, initL, initJ, initT :: Block
 | 
			
		||||
initI = Block I (0,0) [(-2,0), (-1,0), (1,0)]
 | 
			
		||||
initO = Block O (0,0) [(-1,0), (-1,-1), (0,-1)]
 | 
			
		||||
initS = Block S (0,0) [(-1,-1), (0,-1), (1,0)]
 | 
			
		||||
initZ = Block Z (0,0) [(-1,0), (0,-1), (1,-1)]
 | 
			
		||||
initL = Block L (0,0) [(-1,-1), (-1,0), (1,0)]
 | 
			
		||||
initJ = Block J (0,0) [(-1,0), (1,0), (1,-1)]
 | 
			
		||||
initT = Block T (0,0) [(-1,0), (0,-1), (1,0)]
 | 
			
		||||
-- Low level functions on blocks, cells, and coordinates
 | 
			
		||||
 | 
			
		||||
initBlock :: Tetrimino -> Block
 | 
			
		||||
initBlock I = Block I startOrigin [(-2,0), (-1,0), (1,0)]
 | 
			
		||||
initBlock O = Block O startOrigin [(-1,0), (-1,-1), (0,-1)]
 | 
			
		||||
initBlock S = Block S startOrigin [(-1,-1), (0,-1), (1,0)]
 | 
			
		||||
initBlock Z = Block Z startOrigin [(-1,0), (0,-1), (1,-1)]
 | 
			
		||||
initBlock L = Block L startOrigin [(-1,-1), (-1,0), (1,0)]
 | 
			
		||||
initBlock J = Block J startOrigin [(-1,0), (1,0), (1,-1)]
 | 
			
		||||
initBlock T = Block T startOrigin [(-1,0), (0,-1), (1,0)]
 | 
			
		||||
 | 
			
		||||
-- | Visible, active board size
 | 
			
		||||
boardWidth, boardHeight :: Int
 | 
			
		||||
boardWidth = 10
 | 
			
		||||
boardHeight = 20
 | 
			
		||||
 | 
			
		||||
-- | Starting block origin cell
 | 
			
		||||
startOrigin :: Coord
 | 
			
		||||
startOrigin = (6, 21)
 | 
			
		||||
 | 
			
		||||
-- | Rotate block counter clockwise about origin
 | 
			
		||||
-- *Note*: Strict unsafe rotation not respecting boundaries
 | 
			
		||||
-- Safety can only be assured within Game context
 | 
			
		||||
rotate' :: Block -> Block
 | 
			
		||||
rotate' = (& extra %~ fmap (\(x,y) -> (-y, x)))
 | 
			
		||||
rotate' b@(Block s o@(xo,yo) cs)
 | 
			
		||||
  | s == O = b -- O doesn't need rotation
 | 
			
		||||
  | s == I && (xo,yo+1) `elem` cs = rotateWith clockwise b -- I only has two orientations
 | 
			
		||||
  | otherwise = rotateWith counterclockwise b
 | 
			
		||||
 | 
			
		||||
-- | Get absolute coordinates of extraneous block cells
 | 
			
		||||
absExtra :: Block -> [Coord]
 | 
			
		||||
absExtra (Block _ (xo,yo) cs) = fmap (\(x,y) -> (x+xo, y+yo)) cs
 | 
			
		||||
rotateWith :: (Coord -> Coord -> Coord) -> Block -> Block
 | 
			
		||||
rotateWith dir b = let o = b ^. origin
 | 
			
		||||
                    in b & extra %~ fmap (dir o)
 | 
			
		||||
 | 
			
		||||
-- | Get absolute coordinates of all block cells
 | 
			
		||||
absAll :: Block -> [Coord]
 | 
			
		||||
absAll (Block _ o@(xo,yo) cs) = o : fmap (\(x,y) -> (x+xo, y+yo)) cs
 | 
			
		||||
clockwise :: Coord -- ^ origin
 | 
			
		||||
          -> Coord -- ^ point to rotate around origin
 | 
			
		||||
          -> Coord
 | 
			
		||||
clockwise (xo, yo) (x, y) = (xo + y - yo, xo + y - x)
 | 
			
		||||
 | 
			
		||||
counterclockwise :: Coord -- ^ origin
 | 
			
		||||
                 -> Coord -- ^ point to rotate around origin
 | 
			
		||||
                 -> Coord
 | 
			
		||||
counterclockwise (xo, yo) (x, y) = (xo + yo - y, x + yo - xo)
 | 
			
		||||
 | 
			
		||||
-- | Get coordinates of all block cells
 | 
			
		||||
occupiedCells :: Block -> [Coord]
 | 
			
		||||
occupiedCells b = b ^. origin : b ^. extra
 | 
			
		||||
 | 
			
		||||
-- Higher level functions on game and board
 | 
			
		||||
 | 
			
		||||
bagFourTetriminoEach :: IO (Seq.Seq Tetrimino)
 | 
			
		||||
bagFourTetriminoEach =
 | 
			
		||||
  shuffle $ Seq.cycleTaking 28 $ Seq.fromList [(I)..]
 | 
			
		||||
 | 
			
		||||
-- | Initialize a game with a given level
 | 
			
		||||
initGame :: Int ->  IO Game
 | 
			
		||||
initGame lvl = do
 | 
			
		||||
  initBag <- bagFourTetriminoEach
 | 
			
		||||
  let (fstShape :< fstBag) = Seq.viewl initBag
 | 
			
		||||
      (sndShape :< sndBag) = Seq.viewl fstBag
 | 
			
		||||
  return
 | 
			
		||||
    Game { _level = lvl
 | 
			
		||||
         , _currBlock = initBlock fstShape
 | 
			
		||||
         , _nextShape = sndShape
 | 
			
		||||
         , _nextShapeBag = sndBag
 | 
			
		||||
         , _score = 0
 | 
			
		||||
         , _board = mempty }
 | 
			
		||||
 | 
			
		||||
-- | TODO wallkicks http://tetris.wikia.com/wiki/TGM_rotation
 | 
			
		||||
 | 
			
		||||
shuffle :: Seq.Seq a -> IO (Seq.Seq a)
 | 
			
		||||
shuffle xs
 | 
			
		||||
  | null xs   = mempty
 | 
			
		||||
  | otherwise = do
 | 
			
		||||
      randomPosition <- getStdRandom (randomR (0, length xs - 1))
 | 
			
		||||
      let (left, right) = Seq.splitAt randomPosition xs
 | 
			
		||||
          (y :< ys)     = Seq.viewl right
 | 
			
		||||
      fmap (y <|) (shuffle $ left >< ys)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue