packages feed

tetris-0.27177: Player.hs

module Player where

import Graphics.UI.GLUT hiding (Red, Green, Blue, rotate)
import Data.Bits ((.&.))

import Pad
import Board
import Util

--------------------------------
-- constant definition

-- フレームレート
frameRate,cellWidth,cellHeight,defaultFallSpeed,blockFallCount,fixedTimer :: Int
frameRate = 40

-- セルの表示サイズ
cellWidth  = 16
cellHeight = 16

-- デフォルトの落下速度
defaultFallSpeed = 1

-- 落ちるカウンタ
blockFallCount = 40

-- ブロックが着地してから固定されるまでの時間
fixedTimer = frameRate `div` 2

--------------------------------
-- render util

scrx x = 2 * x / 320.0 - 1.0
scry y = 1.0 - 2 * y / 400.0

vertex2f :: Float -> Float -> IO ()
vertex2f x y = vertex (Vertex3 (scrx x) (scry y) (0 :: GLfloat))

color3i r g b = color (Color3 (r/255) (g/255) (b/255 :: GLfloat))

scaleColor s (r,g,b) = (s*r, s*g, s*b)

-- 矩形領域塗りつぶし
fill x y w h (r,g,b) = do
	color3i r g b
	renderPrimitive TriangleStrip $ do
		vertex2f ix1 iy1
		vertex2f ix2 iy1
		vertex2f ix1 iy2
		vertex2f ix2 iy2
	where
		ix1 = fromInteger $ toInteger $ x
		iy1 = fromInteger $ toInteger $ y
		ix2 = fromInteger $ toInteger $ x + w
		iy2 = fromInteger $ toInteger $ y + h

-- セルを描く(明暗あり)
renderCell col@(r,g,b) ix iy = do
	fill x y (cellWidth-1) (cellHeight-1) col

	color3i (r + 0.5*(255-r)) (g + 0.5*(255-g)) (b + 0.5*(255-b))
	renderPrimitive LineStrip $ do
		vertex2f (fromInteger $ toInteger $ x+cellWidth-1) (fromInteger $ toInteger $ y)
		vertex2f (fromInteger $ toInteger $ x) (fromInteger $ toInteger $ y)
		vertex2f (fromInteger $ toInteger $ x) (fromInteger $ toInteger $ y+cellHeight-1)

	color3i (0.5*r) (0.5*g) (0.5*b)
	renderPrimitive LineStrip $ do
		vertex2f (fromInteger $ toInteger $ x) (fromInteger $ toInteger $ y+cellHeight-1)
		vertex2f (fromInteger $ toInteger $ x+cellWidth-1) (fromInteger $ toInteger $ y+cellHeight-1)
		vertex2f (fromInteger $ toInteger $ x+cellWidth-1) (fromInteger $ toInteger $ y)

	where
		x = ix * cellWidth
		y = iy * cellHeight

-- フィールド描画
renderBoard board = mapM_ lineProc $ zip [0..] board
	where
		lineProc (iy, line) = mapM_ (cellProc iy) $ zip [0..] line
		cellProc _ (_, Empty) = return ()
		cellProc iy (ix, cell)  = renderCell (cellColor cell) ix iy

-- ブロックを色つきで描画
renderBlockTypeCol col blktype ix iy rote = do
	sequence_ $ concat $ idxmap2 proc pat
	where
		pat = rotate rote $ blockPattern blktype
		proc (dx,dy) 1 = renderCell col (ix+dx) (iy+dy)
		proc (_dx,_dy) _ = return ()

renderBlockType blktype = renderBlockTypeCol (cellColor $ blockCell blktype) blktype

--------------------------------
-- Block

data Block = Block {
	blktype_of :: BlockType,
	x :: Int,
	y :: Int,
	rot :: Int,
	fallSpeed :: Int,
	ycnt :: Int,
	fixedcnt :: Int
}

newBlock :: BlockType -> Int -> Block
newBlock blktype spd = Block {
	blktype_of = blktype,
	x = (boardWidth - length (head (blockPattern blktype))) `div` 2,
	y = 0,
	rot = 0,
	fallSpeed = spd,
	ycnt = 0,
	fixedcnt = 0
}

-- ブロックをパッドで移動
updateBlock :: Board -> Pad -> Block -> Block
updateBlock board pad block =
	block { x = x', y = y', rot = rot' `mod` 4, ycnt = ycnt', fixedcnt = fixedcnt' }
	where
		x'
			| canMove board blktype (oldx + dx) oldy oldrot	= oldx + dx
			| otherwise										= oldx
		rot'
			| canRot										= oldrot + drot
			| rotPushUp										= oldrot + drot
			| otherwise										= oldrot
		ytmp
			| rotPushUp		= oldy - 1
			| otherwise		= oldy
		y'
			| beFall && canFall		= ytmp + 1
			| otherwise				= ytmp
		ycnt'
			| beFall && canFall			= (oldycnt + fallSpeed block) `mod` blockFallCount
			| beFall && (not canFall)	= blockFallCount
			| otherwise					= oldycnt + fallSpeed block
		fixedcnt' =
			if isLand
				then (fixedcnt block) + 1
				else 0

		trgbtn = trig pad
		rptbtn = rpt pad
		nowbtn = btn pad

		dx = -left + right
		left  = if ((rptbtn .&. padL) /= 0) then 1 else 0
		right = if ((rptbtn .&. padR) /= 0) then 1 else 0

		drot = (rotcw - rotccw + 4) `mod` 4
		rotcw  = if ((trgbtn .&. padA) /= 0) then 1 else 0
		rotccw = if ((trgbtn .&. padB) /= 0) then 1 else 0

		canRot = canMove board blktype x' oldy (oldrot + drot)
		rotPushUp = drot /= 0 && not canRot && canMove board blktype x' (oldy-1) (oldrot + drot)

		beFall = ((nowbtn .&. padD) /= 0) || (oldycnt + fallSpeed block >= blockFallCount)
		canFall = canMove board blktype x' (oldy + 1) rot'
		isLand = beFall && (not canFall)

		blktype = blktype_of block
		oldx = x block
		oldy = y block
		oldrot = rot block
		oldycnt = ycnt block

-- ブロックが地面について固定されたか?
isBlockFixed block = (fixedcnt block) > fixedTimer

-- 操作中のブロック描画
renderBlock block =
	renderBlockType (blktype_of block) (x block) (y block) (rot block)

-- ゴーストブロック描画
renderGhostBlock board block =
	renderBlockTypeCol col (blktype_of block) (x block) landY (rot block)
	where
		landY = landingY board (blktype_of block) (x block) (y block) (rot block)
		col = scaleColor 0.25 (cellColor $ blockCell $ blktype_of block)

--------------------------------
-- Player

data PlayerStat = PlNormal | PlEraseEffect | PlDead
	deriving (Eq)

type PlayerUpdater = Pad -> Player -> IO Player

data Player = Player {
	board_of :: Board,
	block_of :: Block,
	nxtblktype :: BlockType,
	score :: Int,

	stat :: PlayerStat,
	cnt :: Int,

	updater :: PlayerUpdater
}

initialPlayer =
	Player {
		board_of = emptyBoard,
		block_of = newBlock BlockI defaultFallSpeed,
		nxtblktype = BlockI,
		score = 0,
		stat = PlDead,
		cnt = 0,
		updater = updatePlayerNormal
	}

newPlayer = do
	blktype <- randBlockType
	nxt <- randBlockType
	return $ Player {
		board_of = emptyBoard,
		block_of = newBlock blktype defaultFallSpeed,
		nxtblktype = nxt,
		score = 0,
		stat = PlNormal,
		cnt = 0,
		updater = updatePlayerNormal
	}

-- 通常時
updatePlayerNormal pad player
	-- 通常
	| not (isBlockFixed block)	= return $ player { block_of = block' }
	-- 接地したとき:フィールドに格納して次のブロックを出す
	| otherwise	= do
		if null filled
			then setupNextBlock $ player { board_of = storedBoard }
			else do
				let upproc = updatePlayerErase filled
				return $ player { board_of = eraseLines storedBoard filled, stat = PlEraseEffect, updater = upproc, cnt = 0 }
	where
		board = board_of player
		block = block_of player

		block' = updateBlock board pad block

		storedBoard = storeBlock board (blktype_of block) (x block) (y block) (rot block)
		filled = getFilledLines storedBoard

-- そろったラインを消した後の時間待ち
updatePlayerErase filled _pad player =
	if (not $ null filled) && (cnt player) < (frameRate `div` 2)
		then	return $ player { cnt = (cnt player) + 1 }
		else	return $ player { board_of = falledBoard, score = score', updater = updatePlayerErase2, cnt = 0 }
	where
		falledBoard = fallLines (board_of player) filled
		score' = (score player) + 10 * square (length filled)

-- そろったラインを消して下に詰めた後の時間待ち
updatePlayerErase2 _pad player =
	if (cnt player) < (frameRate `div` 2)
		then	return $ player { cnt = (cnt player) + 1 }
		else	setupNextBlock player

-- 死亡
updatePlayerDead _pad player = return player

-- 次のブロックを出す
setupNextBlock player = do
	if canMove board nxtblk (x nxtBlock) (y nxtBlock) (rot nxtBlock)
		then do		-- 登場できる
			nxt' <- randBlockType		-- 次の次のブロックを乱数で選ぶ
			return $ player { block_of = nxtBlock, nxtblktype = nxt', stat = PlNormal, updater = updatePlayerNormal }
		else do		-- 詰まってる:死亡
			let storedBoard = storeBlock board nxtblk (x nxtBlock) (y nxtBlock) (rot nxtBlock)
			return $ player { board_of = storedBoard, stat = PlDead, updater = updatePlayerDead }
	where
		nxtblk = nxtblktype player		-- 次のブロックの種類
		nxtBlock = newBlock nxtblk nxtFallSpd
		nxtFallSpd = if curFallSpd < blockFallCount then curFallSpd + 1 else defaultFallSpeed
		curFallSpd = fallSpeed (block_of player)
		board = board_of player


-- 更新
updatePlayer :: Pad -> Player -> IO Player
updatePlayer pad player = (updater player) pad player

renderNextBlock :: Player -> IO ()
renderNextBlock player = renderBlockType (nxtblktype player) (boardWidth + 2) 5 0

renderPlayer :: Player -> IO ()
renderPlayer player = do
	renderBoard (board_of player)
	if (stat player) == PlNormal
		then do
			renderGhostBlock (board_of player) (block_of player)
			renderBlock (block_of player)
		else return ()
	if (stat player) /= PlDead
		then renderNextBlock player
		else return ()

isDead :: Player -> Bool
isDead player = (stat player) == PlDead