% vim: set tw=72: % Part of Hetris \section{The heart of the game} All the modules dealing with the various pieces of the game are now complete leaving only the central policy module, the very heart of the game, left to write. This will serve as \hsmodule{Main} so the header is essentially already fixed for us. \begin{code} module Main (main) where \end{code} We pull together all of the abstract modules here, so we start by importing them all. We also import \hsmodule{Random} as we are going to want to be able to select a random piece to become the new active piece. \begin{code} import Data import Pieces import Board import UI import System.Random \end{code} In this simplified variant the time between ticks is a constant, but in a more sophisticated variant it might be a function on factors such as the score. In either case it makes sense to separate this functionality out into a function so it can easily be tweaked for good playability. \begin{code} start_delay :: Delay start_delay = 1000 \end{code} Similarly we separate out the desired width and height. \begin{code} desired_dimensions :: (Vector, Vector) desired_dimensions = (9, 12) \end{code} We are going to need to be able to get a random new piece both when we create the \hstype{Board} and when we find we need to add a new piece on a \hsconstructor{Tick} event. Thus it makes sense to split the code for doing so off into a separate function. We use the random number generator in the \hstype{IO} monad so we return an \hstype{IO Piece} rather than just a \hstype{Piece}. We pick a random number in the range of the elements of the \hsfunction{pieces} list, exported by \hsmodule{Pieces}, and return the element at that position in the list. \begin{code} get_new_piece :: IO Piece get_new_piece = randomRIO (0, length pieces - 1) >>= (return . (pieces !!)) \end{code} The \hsfunction{main} function first performs an initialisation phase. The first task is to initialise the user interface, noting the maximum width and height board it can cope with. It then gets a new piece and makes a board, as large as possible while not more than the desired dimensions, with this as the initial piece. The user interface is then asked to perform the relevant changes. The next phase is performed by a function roughly equivalent to an event loop. It takes the current representation of the board and the time until the next tick event---in this case the time between ticks---and deals with events as they happen. When the loop finishes we enter the final phase; we tell the user interface to shut down and then the program terminates. \begin{code} main :: IO () main = do (width_ui, height_ui) <- init_ui let (width_des, height_des) = desired_dimensions let width = width_ui `min` width_des height = height_ui `min` height_des make_board width height piece <- get_new_piece let (b, cs) = create_board width height piece do_changes cs event_loop b start_delay shutdown_ui return () \end{code} \begin{code} {- main :: IO () main = do (width, height) <- init_ui make_board width height piece <- get_new_piece let (b, cs) = create_board width height piece do_changes cs event_loop b start_delay shutdown_ui return () -} \end{code} The actual event loop is complicated mainly by special cases. It starts by getting the next event, passing \hsfunction{get\_event} the time until the next tick event is due. The event that occurred and the time that elapsed are returned. If the event was \hsconstructor{Quit} then the loop terminates. Otherwise more complex handling is needed. If the elapsed time is less than the time until the next tick event and the event wasn't \hsconstructor{Tick} then we subtract the elapsed time from the time until the next tick event to get the new time until the next tick and leave the event unchanged. Otherwise we have either overrun our allocated time (XXX could lose events here) or we have received a \hsconstructor{Tick} event; in either case we reset the time until the next tick and continue as if we had received a \hsconstructor{Tick} event. If we are dealing with a \hsconstructor{Tick} event and the current piece can't be moved down we get a new piece and use the \hsfunction{next\_piece} function to add it to the board. If this succeeds then we call ourselves recursively---the next iteration of the event loop. Otherwise the game is over so we leave the loop. Otherwise we can just use \hsfunction{get\_changes} and \hsfunction{do\_changes} to work out and apply the changes needed respectively. Then we continue with the next iteration of the event loop. \begin{code} event_loop :: Board -> Delay -> IO () event_loop b d = do (e, elapsed) <- get_event d if e == Quit then return () else do let (d', e') = if elapsed < d && e /= Tick then (d - elapsed, e) else (start_delay, Tick) if e' == Tick && not (can_down b) then do piece <- get_new_piece let (m_b', cs) = next_piece b piece do_changes cs case m_b' of Just b' -> event_loop b' d' Nothing -> return () else do let (b', cs) = get_changes b e' do_changes cs event_loop b' d' \end{code}