=head1 TITLE app.pir - a tetris application object B<Note:> The Tetris::App class is implemented as a singleton. =head1 SYNOPSIS app = new "Tetris::App" app."run"() app."shutdown"() end ... # create a new random C<next block> on board 3 app = get_hll_global [ "Tetris::App" ], "app" app."nextBlock"( 3 ) =head1 CLASS INFORMATION This is the main tetris class. Neither has it parent classes nor is it subclassed. =cut .include "timer.pasm" .namespace ["Tetris::App"] .sub __onload :load $P0 = get_class "Tetris::App" unless null $P0 goto END load_bytecode "library/SDL/App.pir" load_bytecode "library/SDL/Color.pir" load_bytecode "library/SDL/Event.pir" load_bytecode "library/SDL/Rect.pir" load_bytecode "examples/sdl/tetris/eventhandler.pir" load_bytecode "examples/sdl/tetris/board.pir" newclass $P0, "Tetris::App" addattribute $P0, "SDL" addattribute $P0, "EventHandler" addattribute $P0, "DebugFlags" addattribute $P0, "Timer" addattribute $P0, "TimerDisableCount" addattribute $P0, "InTimer" addattribute $P0, "Players" addattribute $P0, "Palette" addattribute $P0, "Boards" # set the BUILD method name $P1 = new 'String' $P1 = 'BUILD' setprop $P0, 'BUILD', $P1 END: .end =head1 CONSTRUCTOR =over 4 =item BUILD - called automatically by "new" Initializes the application. It performs the SDL initialization and creates some internal data structures afterwards. This method throws an exception if an error occurs. =cut .sub BUILD :method # create the app object set_hll_global [ "Tetris::App" ], "app", self $P0 = new 'Hash' setattribute self, 'DebugFlags', $P0 # prepare SDL's constructor arguments $P0 = new 'Hash' $P0["height"] = 480 $P0["width"] = 640 $P0["bpp"] = 32 $P0["flags"] = 1 # create the SDL object $P0 = new ['SDL'; 'App'], $P0 # store the SDL object setattribute self, 'SDL', $P0 # generate some data structures self."genPalette"() # create the app timer self."initTimer"() # init the SDL event handler $P0 = new "Tetris::EventHandler", self setattribute self, 'EventHandler', $P0 # create the debug flags hash $P0 = new 'Hash' setattribute self, 'DebugFlags', $P0 # start a new single player game self."newGame"( 1 ) .end =back =head1 METHODS The Tetris::App class provides the following methods: =over 4 =item sdl = app."SDL"() =cut .sub SDL :method getattribute $P0, self, 'SDL' .return ($P0) .end =item app."shutdown"() Shuts SDL down and performs some internal cleanup. B<Note:> The application object is invalid afterwards, you are not allowed to call any other of its methods after this one. This method returns nothing. =cut .sub shutdown :method .local pmc sdl # XXX free data structures # ... # shutdown the SDL system $P0 = self."SDL"() if_null $P0, END $P0."quit"() END: .end =item success = app."run"() The application's main loop. Returns if the user requested a shutdown. An exception is thrown if an error occurs. =cut .sub _app_timer get_hll_global $P0, [ "Tetris::App" ], "app" $P0."timer"() .end .sub run :method .local pmc eh .local pmc loop getattribute eh, self, 'EventHandler' loop = new ['SDL'; 'Event'] self."enableTimer"() loop."process_events"( 0.1, eh, self ) self."disableTimer"() print "done\n" .end =item app."initTimer"() =cut .sub initTimer :method $P0 = new "Array" $P1 = get_hll_global [ "Tetris::App" ], "_app_timer" $P0 = 8 $P0[0] = .PARROT_TIMER_NSEC $P0[1] = 0.1 $P0[2] = .PARROT_TIMER_HANDLER $P0[3] = $P1 $P0[4] = .PARROT_TIMER_REPEAT $P0[5] = -1 $P0[6] = .PARROT_TIMER_RUNNING $P0[7] = 1 $P1 = new 'Timer', $P0 setattribute self, 'Timer', $P1 $P0 = new 'Integer' $P0 = 1 setattribute self, 'TimerDisableCount', $P0 $P0 = new 'Integer' $P0 = 0 setattribute self, 'InTimer', $P0 .end =item app."setTimerStatus"( status ) =cut .sub setTimerStatus :method .param int status getattribute $P0, self, 'Timer' set $P0[.PARROT_TIMER_RUNNING], status .end =item app."enableTimer"() =cut .sub enableTimer :method getattribute $P0, self, 'TimerDisableCount' dec $P0 if $P0 != 0 goto END self."setTimerStatus"( 1 ) END: .end =item app."disableTimer"() =cut .sub disableTimer :method getattribute $P0, self, 'TimerDisableCount' inc $P0 self."setTimerStatus"( 0 ) .end =item color = app."color"( number ) Returns the specified color entry from the palette. =cut .sub color :method .param int number .local pmc palette .local pmc color palette = self."palette"() color = palette[number] .return (color) .end =item palette = app."palette"() Returns the color palette. =cut .sub palette :method .local pmc palette getattribute palette, self, 'Palette' if_null palette, CREATE branch RET CREATE: (palette) = self."genPalette"() branch RET NULL: print "warning: no color palette found!\n" RET: .return (palette) .end =item palette = app."genPalette"() B<(internal)> Creates the color palette. This method returns the created palette. =cut .sub genPalette :method .local pmc palette .local pmc hash .local pmc color .local int i .local int r .local int g .local int b .local int l palette = new 'ResizablePMCArray' hash = new 'Hash' set i, 0 GENLOOP: band l, i, 8 band r, i, 4 band g, i, 2 band b, i, 1 shr r, 2 shr g, 1 mul r, 127 mul g, 127 mul b, 127 unless l, NOT_BRIGHT add r, 64 add g, 64 add b, 64 NOT_BRIGHT: hash["r"] = r hash["g"] = g hash["b"] = b color = new ['SDL'; 'Color'], hash push palette, color inc i if i < 16 goto GENLOOP setattribute self, 'Palette', palette .return (palette) .end =item board = self."board"( boardID ) Lookup a board using its ID. =over 4 =item parameter C<boardID> The ID of the board to return. =back Returns the board object, or NULL if no board with the specified ID exists. =cut .sub board :method .param int boardID .local pmc board getattribute board, self, 'Boards' $I0 = board if boardID < $I0 goto OK ERR: null board print "board " print boardID print " not found!\n" branch END OK: board = board[boardID] defined $I0, board unless $I0 goto ERR END: .return (board) .end =item block = app."currentBlock"( boardID ) Returns the currently falling block of a board. =over 4 =item parameter C<boardID> The ID of the board whose C<current block> should be returned. =back Returns the block object, or NULL if the board was not found. =cut .sub currentBlock :method .param int boardID .local pmc temp temp = self."board"( boardID ) if_null temp, BLOCKISNULL temp = temp."currentBlock"() BLOCKISNULL: .return (temp) .end =item success = app."rotate"( boardID, dir ) Rotates the current block of a board. =over 4 =item parameter C<boardID> The ID of the board whose block should be rotated. =item parameter C<dir> +1 = rotate clockwise -1 = rotate counterclockwise =back Returns 1 if the rotation was possible, 0 otherwise. =cut .sub rotate :method .param int boardID .param int dir .local pmc block .local int ret self."disableTimer"() ret = 0 # lookup the block block = self."currentBlock"( boardID ) if_null block, END # rotate the block ret = block."rotate"( dir ) if ret == 0 goto END # redraw the screen self."drawScreen"( 0 ) END: self."enableTimer"() .return (ret) .end =item success = app."move"( boardID, xval, yval ) Moves the currently falling block of a board. It does not lock the block onto the board in any case, use C<_Board::lockBlock()> if you want this. =over 4 =item parameter C<boardID> The ID of the board whose block should be moved. =item parameter C<xval> Number of units the block should be moved horizontally. Positiv numbers will move the block rightwards, negative numbers leftwards. =item parameter C<xval> Number of units the block should be moved vertically. Positiv numbers will move the block downwards, negative numbers upwards (untested; not recommended). =back Returns 1 if the movement was possible, 0 otherwise. =cut .sub move :method .param int boardID .param int xval .param int yval .local int success .local pmc block # disable the timer self."disableTimer"() block = self."currentBlock"( boardID ) if_null block, END success = block."move"( xval, yval ) unless success goto END self."drawScreen"( 0 ) END: # reenable the timer self."enableTimer"() .return (success) .end =item block = self."nextBlock"( boardID, id ) Activates the C<next block> on the specified board and creates a new C<next block>. The old current block is just overwritten, and not locked onto the board. Use C<_Board::lockBlock()> for this. =over 4 =item parameter C<boardID> The ID of the board where the next block should be activated. =item parameter C<id> B<(optional)> The id of the block to create. The id is an index in the blockdata array. Please refer to <_Tetris::App::blockdata()> for more information. =back This method returns the new falling block. =cut .sub nextBlock :method .param int boardID .param int id :optional .param int got_id :opt_flag .local pmc temp print "nextBlock: (" print boardID print ") " print id print "\n" if got_id goto SKIP_SET_ID # no INT arg => use a random next block id = -1 SKIP_SET_ID: temp = self."board"( boardID ) if_null temp, APP_NEXTBLOCK_END temp = temp."nextBlock"(id ) APP_NEXTBLOCK_END: .return (temp) .end =item app."fall"( boardID ) Lets the current block of the specified board fall down fast. =over 4 =item parameter C<boardID> The ID of the board where the block should fall down fast. =back This method returns 1 if the board was found, 0 otherwise. =cut .sub fall :method .param int boardID .local pmc board .local int ret ret = 0 board = self."board"( boardID ) if_null board, APP_FALL_END board."fall"() ret = 1 APP_FALL_END: .return (ret) .end =item falling = app."falling"( boardID ) Checks if the current block of the specified board is falling down fast. =over 4 =item parameter C<boardID> The ID of the board to check. =back This method returns 1 if the block is falling down fast, 0 otherwise. =cut .sub falling :method .param int boardID .local pmc board .local int ret ret = 0 board = self."board"( boardID ) if_null board, APP_FALLING_END ret = board."falling"() APP_FALLING_END: .return (ret) .end =item interval = app."fallInterval"( boardID ) Checks the board's falling speed. =over 4 =item parameter C<boardID> The ID of the board to check. =back This method returns how many seconds it take for a block to fall down one unit. =cut .sub fallInterval :method .param int boardID .local pmc board .local num ret ret = 0 board = self."board"( boardID ) if_null board, APP_INTERVAL_END ret = board."fallInterval"() APP_INTERVAL_END: .return (ret) .end =item nextfall = app."nextFallTime"( boardID ) Checks when the block on the specified board falls down the next unit. =over 4 =item parameter C<boardID> The ID of the board to check. =back Returns the time when the block falls down the next time. =cut .sub nextFallTime :method .param int boardID .local pmc board .local num ret board = self."board"( boardID ) if_null board, APP_NEXTFALL_END ret = board."nextFallTime"() APP_NEXTFALL_END: .return (ret) .end =item app."setNextFallTime"( boardID, val ) Sets the time when the block on the specified board falls down the next unit. =over 4 =item parameter C<boardID> The ID of the board to modify. =item parameter C<val> The time when the block falls down the next time. =back This method returns nothing. =cut .sub setNextFallTime :method .param int boardID .param num val .local pmc board board = self."board"( boardID ) if_null board, APP_SETFALL_END board."setNextFallTime"( val ) APP_SETFALL_END: .return () .end =item redrawn = app."timer"() Has to be called at frequent intervals. Returns 1 if the screen has been redrawn, 0 otherwise. =cut .sub timer :method .local pmc board .local int redraw .local int ret .local pmc boards .local pmc board .local int i .local pmc inTimer redraw = 0 # check the timer disable count # do nothing if the timer is disabled getattribute $P0, self, 'TimerDisableCount' if $P0 > 0 goto END getattribute inTimer, self, 'InTimer' i = inTimer if i goto END # XXX: fetch exceptions and reset this flag # we are in the timer handler function inc inTimer # get the boards array (boards, i) = self."boards"( self ) LOOP: dec i if i < 0 goto REDRAW board = boards[i] # call the board's timer ret = board."timer"() unless ret goto LOOP redraw = 1 branch LOOP REDRAW: unless redraw goto NOREDRAW self."drawScreen"( 0 ) NOREDRAW: dec inTimer END: .return (redraw) .end =item app."drawScreen"( full ) Redraws the screen. =over 4 =item parameter C<full> 0 = update only modified parts (uses a draw cache) 1 = draw everything =back This method returns nothing. =cut .sub drawScreen :method .param int full .local pmc screen .local pmc temp self."disableTimer"() screen = self."SDL"() screen = screen."surface"() # draw everything? $I0 = self."flag"( "show blocksize" ) if $I0 goto FULL $I0 = self."flag"( "draw full" ) if $I0 goto FULL branch NOT_FULL FULL: full = 1 NOT_FULL: # # draw the main background # # to see the draw cache optimisation $I0 = self."flag"( "show optimisation" ) if $I0 goto FORCE unless full goto NO_MAINBACKGROUND FORCE: .local pmc rect .local pmc screen .local pmc color rect = new 'Hash' rect["width"] = 640 rect["height"] = 480 rect["x"] = 0 rect["y"] = 0 temp = new ['SDL'; 'Rect'], rect color = self."color"( 3 ) screen."fill_rect"( temp, color ) NO_MAINBACKGROUND: # # draw the boards # self."drawBoards"( screen, full ) # # update the screen # # XXX: optimize screen updates rect = new 'Hash' rect["width"] = 640 rect["height"] = 480 rect["x"] = 0 rect["y"] = 0 temp = new ['SDL'; 'Rect'], rect screen."update_rect"( temp ) self."enableTimer"() .end =item app."drawBoards"( screen, full ) =cut .sub drawBoards :method .param pmc screen .param int full .local pmc boards .local pmc board .local int i (boards, i) = self."boards"() LOOP: dec i if i < 0 goto END board = boards[i] board."draw"( screen, full ) branch LOOP END: .return () .end =item (boards, count) = app."boards"() Returns the number of boards registered as well as the boards array. =cut .sub boards :method .local pmc boards .local int count getattribute boards, self, 'Boards' count = 0 if_null boards, END count = boards END: .return (boards, count) .end =item app."registerBoard"( board, id ) B<(internal)> Called by the board constructor to add the board object to the application's board array. Returns the index where the board has been added, which is used as the board ID. =cut .sub registerBoard :method .param pmc board .local pmc boards .local int id (boards, id) = self."boards"() set boards[id], board .return (id) .end =item value = app."flag"( name, value ) Get or sets an integer debug flag. =over 4 =item parameter C<name> The flag's name. =item parameter C<value> B<(optional)> Set the flag to the specified (integer) value. =back Returns the flag's (new) value. =cut .sub flag :method .param string name .param int value :optional .param int got_value :opt_flag .local pmc flag .local int ret # get the flags hash getattribute flag, self, 'DebugFlags' # check the number of INT args unless got_value goto FLAG_GET # set a new value set flag[name], value FLAG_GET: set ret, flag[name] .return (ret) .end =item app."newGame"( boards ) Starts a new game with the specified number of boards. =over 4 =item parameter C<boards> The number of boards to create. =back This method returns nothing. =cut .sub newGame :method .param int players :optional .param int got_players :opt_flag .local pmc temp .local int xpos self."disableTimer"() set xpos, 10 # check the number of INT args if got_players goto SET getattribute temp, self, 'Players' players = 1 if_null temp, SET players = temp branch END_SET SET: # save the number of players new temp, 'Integer' set temp, players setattribute self, 'Players', temp END_SET: print "starting a " print players print " player game...\n" # create the boards array new temp, 'ResizablePMCArray' setattribute self, 'Boards', temp NEWGAME_NEW_BOARD: if players <= 0 goto NEWGAME_END print "new board...\n" temp = new "Tetris::Board", self print "new board done.\n" temp."setPosition"( xpos, 10 ) add xpos, 320 dec players branch NEWGAME_NEW_BOARD NEWGAME_END: self."drawScreen"( 1 ) self."enableTimer"() .return () .end =back =head1 AUTHOR Jens Rieks E<lt>parrot at jensbeimsurfen dot deE<gt> is the author and maintainer. Please send patches and suggestions to the Perl 6 Internals mailing list. =head1 COPYRIGHT Copyright (C) 2004-2008, Parrot Foundation. =cut # Local Variables: # mode: pir # fill-column: 100 # End: # vim: expandtab shiftwidth=4 ft=pir: