1
2=head1 DESCRIPTION
3
4app.pir - a tetris application object
5
6B<Note:> The Tetris::App class is implemented as a singleton.
7
8=head1 SYNOPSIS
9
10    app = new "Tetris::App"
11
12    app."run"()
13    app."shutdown"()
14    end
15
16    ...
17
18    # create a new random C<next block> on board 3
19    app = get_hll_global [ "Tetris::App" ], "app"
20    app."nextBlock"( 3 )
21
22=head1 CLASS INFORMATION
23
24This is the main tetris class. Neither has
25it parent classes nor is it subclassed.
26
27=cut
28
29
30.include "timer.pasm"
31.namespace ["Tetris::App"]
32
33.sub __onload :load
34    $P0 = get_class "Tetris::App"
35    unless null $P0 goto END
36
37    load_bytecode "SDL/App.pir"
38    load_bytecode "SDL/Color.pir"
39    load_bytecode "SDL/Event.pir"
40    load_bytecode "SDL/Rect.pir"
41
42    load_bytecode "examples/sdl/tetris/eventhandler.pir"
43    load_bytecode "examples/sdl/tetris/board.pir"
44
45    newclass $P0, "Tetris::App"
46
47    addattribute $P0, "SDL"
48    addattribute $P0, "EventHandler"
49    addattribute $P0, "DebugFlags"
50    addattribute $P0, "Timer"
51    addattribute $P0, "TimerDisableCount"
52    addattribute $P0, "InTimer"
53    addattribute $P0, "Players"
54    addattribute $P0, "Palette"
55    addattribute $P0, "Boards"
56
57    # set the BUILD method name
58    $P1 = new 'String'
59    $P1 = 'BUILD'
60    setprop $P0, 'BUILD', $P1
61END:
62.end
63
64=head1 CONSTRUCTOR
65
66=over 4
67
68=item BUILD - called automatically by "new"
69
70Initializes the application.
71
72It performs the SDL initialization and
73creates some internal data structures afterwards.
74
75This method throws an exception if an error occurs.
76
77=cut
78
79.sub BUILD :method
80    # create the app object
81    set_hll_global [ "Tetris::App" ], "app", self
82
83    $P0 = new 'Hash'
84    setattribute self, 'DebugFlags', $P0
85
86    # prepare SDL's constructor arguments
87    $P0           = new 'Hash'
88    $P0["height"] = 480
89    $P0["width"]  = 640
90    $P0["bpp"]    =  32
91    $P0["flags"]  =   1
92
93    # create the SDL object
94    $P0 = new ['SDL'; 'App'], $P0
95
96    # store the SDL object
97    setattribute self, 'SDL', $P0
98
99    # generate some data structures
100    self."genPalette"()
101
102    # create the app timer
103    self."initTimer"()
104
105    # init the SDL event handler
106    $P0 = new "Tetris::EventHandler", self
107    setattribute self, 'EventHandler', $P0
108
109    # create the debug flags hash
110    $P0 = new 'Hash'
111    setattribute self, 'DebugFlags', $P0
112
113    # start a new single player game
114    self."newGame"( 1 )
115.end
116
117=back
118
119=head1 METHODS
120
121The Tetris::App class provides the following methods:
122
123=over 4
124
125=item sdl = app."SDL"()
126
127=cut
128
129.sub SDL :method
130    getattribute $P0, self, 'SDL'
131
132    .return ($P0)
133.end
134
135=item app."shutdown"()
136
137Shuts SDL down and performs some internal cleanup.
138
139B<Note:> The application object is invalid afterwards, you are not allowed to
140call any other of its methods after this one.
141
142This method returns nothing.
143
144=cut
145
146.sub shutdown :method
147    .local pmc sdl
148
149    # XXX free data structures
150    # ...
151
152    # shutdown the SDL system
153    $P0 = self."SDL"()
154    if_null $P0, END
155    $P0."quit"()
156END:
157.end
158
159=item success = app."run"()
160
161The application's main loop.
162
163Returns if the user requested a shutdown.
164An exception is thrown if an error occurs.
165
166=cut
167
168.sub _app_timer
169    get_hll_global $P0, [ "Tetris::App" ], "app"
170    $P0."timer"()
171.end
172
173.sub run :method
174    .local pmc eh
175    .local pmc loop
176
177    getattribute eh, self, 'EventHandler'
178
179    loop = new ['SDL'; 'Event']
180
181    self."enableTimer"()
182    loop."process_events"( 0.1, eh, self )
183    self."disableTimer"()
184    print "done\n"
185.end
186
187=item app."initTimer"()
188
189=cut
190
191.sub initTimer :method
192    $P0 = new "Array"
193    $P1 = get_hll_global [ "Tetris::App" ], "_app_timer"
194    $P0 = 8
195    $P0[0] = .PARROT_TIMER_NSEC
196    $P0[1] = 0.1
197    $P0[2] = .PARROT_TIMER_HANDLER
198    $P0[3] = $P1
199    $P0[4] = .PARROT_TIMER_REPEAT
200    $P0[5] = -1
201    $P0[6] = .PARROT_TIMER_RUNNING
202    $P0[7] = 1
203
204    $P1 = new 'Timer', $P0
205    setattribute self, 'Timer', $P1
206
207    $P0 = new 'Integer'
208    $P0 = 1
209    setattribute self, 'TimerDisableCount', $P0
210
211    $P0 = new 'Integer'
212    $P0 = 0
213    setattribute self, 'InTimer', $P0
214.end
215
216=item app."setTimerStatus"( status )
217
218=cut
219
220.sub setTimerStatus :method
221    .param int status
222
223    getattribute $P0, self, 'Timer'
224    set $P0[.PARROT_TIMER_RUNNING], status
225.end
226
227=item app."enableTimer"()
228
229=cut
230
231.sub enableTimer :method
232    getattribute $P0, self, 'TimerDisableCount'
233    dec $P0
234    if $P0 != 0 goto END
235    self."setTimerStatus"( 1 )
236END:
237.end
238
239=item app."disableTimer"()
240
241=cut
242
243.sub disableTimer :method
244    getattribute $P0, self, 'TimerDisableCount'
245    inc $P0
246    self."setTimerStatus"( 0 )
247.end
248
249=item color = app."color"( number )
250
251Returns the specified color entry from the palette.
252
253=cut
254
255.sub color :method
256    .param int number
257    .local pmc palette
258    .local pmc color
259
260    palette = self."palette"()
261    color = palette[number]
262
263    .return (color)
264.end
265
266=item palette = app."palette"()
267
268Returns the color palette.
269
270=cut
271
272.sub palette :method
273    .local pmc palette
274
275    getattribute palette, self, 'Palette'
276    if_null palette, CREATE
277    branch RET
278CREATE:
279    (palette) = self."genPalette"()
280
281    branch RET
282
283NULL:
284    print "warning: no color palette found!\n"
285
286RET:
287    .return (palette)
288.end
289
290=item palette = app."genPalette"() B<(internal)>
291
292Creates the color palette.
293
294This method returns the created palette.
295
296=cut
297
298.sub genPalette :method
299    .local pmc palette
300    .local pmc hash
301    .local pmc color
302    .local int i
303    .local int r
304    .local int g
305    .local int b
306    .local int l
307
308    palette = new 'ResizablePMCArray'
309    hash = new 'Hash'
310
311    set i, 0
312GENLOOP:
313    band l, i, 8
314    band r, i, 4
315    band g, i, 2
316    band b, i, 1
317    shr r, 2
318    shr g, 1
319    mul r, 127
320    mul g, 127
321    mul b, 127
322    unless l, NOT_BRIGHT
323    add r, 64
324    add g, 64
325    add b, 64
326NOT_BRIGHT:
327
328    hash["r"] = r
329    hash["g"] = g
330    hash["b"] = b
331    color = new ['SDL'; 'Color'], hash
332
333    push palette, color
334    inc i
335    if i < 16 goto GENLOOP
336
337    setattribute self, 'Palette', palette
338
339    .return (palette)
340.end
341
342=item board = self."board"( boardID )
343
344Lookup a board using its ID.
345
346=over 4
347
348=item parameter C<boardID>
349
350The ID of the board to return.
351
352=back
353
354Returns the board object, or NULL if
355no board with the specified ID exists.
356
357=cut
358
359.sub board :method
360    .param int boardID
361    .local pmc board
362
363    getattribute board, self, 'Boards'
364
365    $I0 = board
366    if boardID < $I0 goto OK
367ERR:
368    null board
369    print "board "
370    print boardID
371    print " not found!\n"
372    branch END
373
374OK:
375    board = board[boardID]
376    defined $I0, board
377    unless $I0 goto ERR
378
379END:
380    .return (board)
381.end
382
383=item block = app."currentBlock"( boardID )
384
385Returns the currently falling block of a board.
386
387=over 4
388
389=item parameter C<boardID>
390
391The ID of the board whose C<current block> should be returned.
392
393=back
394
395Returns the block object, or NULL if the board was not found.
396
397=cut
398
399.sub currentBlock :method
400    .param int boardID
401    .local pmc temp
402
403    temp = self."board"( boardID )
404    if_null temp, BLOCKISNULL
405    temp = temp."currentBlock"()
406
407BLOCKISNULL:
408    .return (temp)
409.end
410
411=item success = app."rotate"( boardID, dir )
412
413Rotates the current block of a board.
414
415=over 4
416
417=item parameter C<boardID>
418
419The ID of the board whose block should be rotated.
420
421=item parameter C<dir>
422
423+1 = rotate clockwise
424
425-1 = rotate counterclockwise
426
427=back
428
429Returns 1 if the rotation was possible, 0 otherwise.
430
431=cut
432
433.sub rotate :method
434    .param int boardID
435    .param int dir
436    .local pmc block
437    .local int ret
438
439    self."disableTimer"()
440
441    ret = 0
442
443    # lookup the block
444    block = self."currentBlock"( boardID )
445    if_null block, END
446
447    # rotate the block
448    ret = block."rotate"( dir )
449    if ret == 0 goto END
450
451    # redraw the screen
452    self."drawScreen"( 0 )
453
454END:
455    self."enableTimer"()
456
457    .return (ret)
458.end
459
460=item success = app."move"( boardID, xval, yval )
461
462Moves the currently falling block of a board.
463It does not lock the block onto the board in any case, use
464C<_Board::lockBlock()> if you want this.
465
466=over 4
467
468=item parameter C<boardID>
469
470The ID of the board whose block should be moved.
471
472=item parameter C<xval>
473
474Number of units the block should be moved horizontally.
475
476Positiv numbers will move the block rightwards, negative
477numbers leftwards.
478
479=item parameter C<xval>
480
481Number of units the block should be moved vertically.
482
483Positiv numbers will move the block downwards, negative
484numbers upwards (untested; not recommended).
485
486=back
487
488Returns 1 if the movement was possible, 0 otherwise.
489
490=cut
491
492.sub move :method
493    .param int boardID
494    .param int xval
495    .param int yval
496    .local int success
497    .local pmc block
498
499    # disable the timer
500    self."disableTimer"()
501
502    block = self."currentBlock"( boardID )
503    if_null block, END
504    success = block."move"( xval, yval )
505
506    unless success goto END
507    self."drawScreen"( 0 )
508
509END:
510    # reenable the timer
511    self."enableTimer"()
512
513    .return (success)
514.end
515
516=item block = self."nextBlock"( boardID, id )
517
518Activates the C<next block> on the specified board
519and creates a new C<next block>.
520The old current block is just overwritten, and not locked
521onto the board. Use C<_Board::lockBlock()> for this.
522
523=over 4
524
525=item parameter C<boardID>
526
527The ID of the board where the next block should be activated.
528
529=item parameter C<id> B<(optional)>
530
531The id of the block to create. The id is an index in the
532blockdata array. Please refer to <_Tetris::App::blockdata()>
533for more information.
534
535=back
536
537This method returns the new falling block.
538
539=cut
540
541.sub nextBlock :method
542    .param int boardID
543    .param int id       :optional
544    .param int got_id   :opt_flag
545    .local pmc temp
546
547    print "nextBlock: ("
548    print boardID
549    print ") "
550    print id
551    print "\n"
552
553    if got_id goto SKIP_SET_ID
554    # no INT arg => use a random next block
555    id = -1
556SKIP_SET_ID:
557
558    temp = self."board"( boardID )
559    if_null temp, APP_NEXTBLOCK_END
560    temp = temp."nextBlock"(id )
561
562APP_NEXTBLOCK_END:
563    .return (temp)
564.end
565
566=item app."fall"( boardID )
567
568Lets the current block of the specified board
569fall down fast.
570
571=over 4
572
573=item parameter C<boardID>
574
575The ID of the board where the block should fall down fast.
576
577=back
578
579This method returns 1 if the board was found, 0 otherwise.
580
581=cut
582
583.sub fall :method
584    .param int boardID
585    .local pmc board
586    .local int ret
587
588    ret = 0
589    board = self."board"( boardID )
590    if_null board, APP_FALL_END
591    board."fall"()
592    ret = 1
593
594APP_FALL_END:
595    .return (ret)
596.end
597
598=item falling = app."falling"( boardID )
599
600Checks if the current block of the specified board
601is falling down fast.
602
603=over 4
604
605=item parameter C<boardID>
606
607The ID of the board to check.
608
609=back
610
611This method returns 1 if the block is falling down fast, 0 otherwise.
612
613=cut
614
615.sub falling :method
616    .param int boardID
617    .local pmc board
618    .local int ret
619
620    ret = 0
621    board = self."board"( boardID )
622    if_null board, APP_FALLING_END
623    ret = board."falling"()
624
625APP_FALLING_END:
626    .return (ret)
627.end
628
629=item interval = app."fallInterval"( boardID )
630
631Checks the board's falling speed.
632
633=over 4
634
635=item parameter C<boardID>
636
637The ID of the board to check.
638
639=back
640
641This method returns how many seconds it take for a block
642to fall down one unit.
643
644=cut
645
646.sub fallInterval :method
647    .param int boardID
648    .local pmc board
649    .local num ret
650
651    ret = 0
652    board = self."board"( boardID )
653    if_null board, APP_INTERVAL_END
654    ret = board."fallInterval"()
655
656APP_INTERVAL_END:
657    .return (ret)
658.end
659
660=item nextfall = app."nextFallTime"( boardID )
661
662Checks when the block on the specified board falls down
663the next unit.
664
665=over 4
666
667=item parameter C<boardID>
668
669The ID of the board to check.
670
671=back
672
673Returns the time when the block falls down the next time.
674
675=cut
676
677.sub nextFallTime :method
678    .param int boardID
679    .local pmc board
680    .local num ret
681
682    board = self."board"( boardID )
683    if_null board, APP_NEXTFALL_END
684    ret = board."nextFallTime"()
685
686APP_NEXTFALL_END:
687    .return (ret)
688.end
689
690=item app."setNextFallTime"( boardID, val )
691
692Sets the time when the block on the specified board
693falls down the next unit.
694
695=over 4
696
697=item parameter C<boardID>
698
699The ID of the board to modify.
700
701=item parameter C<val>
702
703The time when the block falls down the next time.
704
705=back
706
707This method returns nothing.
708
709=cut
710
711.sub setNextFallTime :method
712    .param int boardID
713    .param num val
714    .local pmc board
715
716    board = self."board"( boardID )
717    if_null board, APP_SETFALL_END
718    board."setNextFallTime"( val )
719
720APP_SETFALL_END:
721    .return ()
722.end
723
724=item redrawn = app."timer"()
725
726Has to be called at frequent intervals.
727
728Returns 1 if the screen has been redrawn, 0 otherwise.
729
730=cut
731
732.sub timer :method
733    .local pmc board
734    .local int redraw
735    .local int ret
736    .local pmc boards
737    .local pmc board
738    .local int i
739    .local pmc inTimer
740
741    redraw = 0
742
743    # check the timer disable count
744    # do nothing if the timer is disabled
745    getattribute $P0, self, 'TimerDisableCount'
746    if $P0 > 0 goto END
747
748    getattribute inTimer, self, 'InTimer'
749
750    i = inTimer
751    if i goto END
752
753    # XXX: fetch exceptions and reset this flag
754    # we are in the timer handler function
755    inc inTimer
756
757    # get the boards array
758    (boards, i) = self."boards"( self )
759
760LOOP:
761    dec i
762    if i < 0 goto REDRAW
763    board = boards[i]
764
765    # call the board's timer
766    ret = board."timer"()
767
768    unless ret goto LOOP
769    redraw = 1
770    branch LOOP
771
772REDRAW:
773    unless redraw goto NOREDRAW
774    self."drawScreen"( 0 )
775NOREDRAW:
776
777    dec inTimer
778END:
779
780    .return (redraw)
781.end
782
783=item app."drawScreen"( full )
784
785Redraws the screen.
786
787=over 4
788
789=item parameter C<full>
790
7910 = update only modified parts (uses a draw cache)
792
7931 = draw everything
794
795=back
796
797This method returns nothing.
798
799=cut
800
801.sub drawScreen :method
802    .param int full
803    .local pmc screen
804    .local pmc temp
805
806    self."disableTimer"()
807
808    screen = self."SDL"()
809    screen = screen."surface"()
810
811    # draw everything?
812    $I0 = self."flag"( "show blocksize" )
813    if $I0 goto FULL
814    $I0 = self."flag"( "draw full" )
815    if $I0 goto FULL
816    branch NOT_FULL
817FULL:
818    full = 1
819NOT_FULL:
820
821    #
822    # draw the main background
823    #
824
825    # to see the draw cache optimisation
826    $I0 = self."flag"( "show optimisation" )
827    if $I0 goto FORCE
828    unless full goto NO_MAINBACKGROUND
829FORCE:
830    .local pmc rect
831    .local pmc screen
832    .local pmc color
833
834    rect = new 'Hash'
835    rect["width"] = 640
836    rect["height"] = 480
837    rect["x"] = 0
838    rect["y"] = 0
839    temp = new ['SDL'; 'Rect'], rect
840    color = self."color"( 3 )
841
842    screen."fill_rect"( temp, color )
843NO_MAINBACKGROUND:
844    #
845    # draw the boards
846    #
847    self."drawBoards"( screen, full )
848
849    #
850    # update the screen
851    #
852    # XXX: optimize screen updates
853    rect = new 'Hash'
854    rect["width"] = 640
855    rect["height"] = 480
856    rect["x"] = 0
857    rect["y"] = 0
858    temp = new ['SDL'; 'Rect'], rect
859    screen."update_rect"( temp )
860
861    self."enableTimer"()
862.end
863
864=item app."drawBoards"( screen, full )
865
866=cut
867
868.sub drawBoards :method
869    .param pmc screen
870    .param int full
871    .local pmc boards
872    .local pmc board
873    .local int i
874
875    (boards, i) = self."boards"()
876
877LOOP:
878    dec i
879    if i < 0 goto END
880    board = boards[i]
881    board."draw"( screen, full )
882    branch LOOP
883
884END:
885    .return ()
886.end
887
888=item (boards, count) = app."boards"()
889
890Returns the number of boards registered as well as
891the boards array.
892
893=cut
894
895.sub boards :method
896    .local pmc boards
897    .local int count
898
899    getattribute boards, self, 'Boards'
900    count = 0
901    if_null boards, END
902    count = boards
903
904END:
905    .return (boards, count)
906.end
907
908=item app."registerBoard"( board, id ) B<(internal)>
909
910Called by the board constructor to add the board object
911to the application's board array. Returns the index where
912the board has been added, which is used as the board ID.
913
914=cut
915
916.sub registerBoard :method
917    .param pmc board
918    .local pmc boards
919    .local int id
920
921    (boards, id) = self."boards"()
922    set boards[id], board
923
924    .return (id)
925.end
926
927=item value = app."flag"( name, value )
928
929Get or sets an integer debug flag.
930
931=over 4
932
933=item parameter C<name>
934
935The flag's name.
936
937=item parameter C<value> B<(optional)>
938
939Set the flag to the specified (integer) value.
940
941=back
942
943Returns the flag's (new) value.
944
945=cut
946
947.sub flag :method
948    .param string name
949    .param int value        :optional
950    .param int got_value    :opt_flag
951
952    .local pmc flag
953    .local int ret
954
955    # get the flags hash
956    getattribute flag, self, 'DebugFlags'
957
958    # check the number of INT args
959    unless got_value goto FLAG_GET
960    # set a new value
961    set flag[name], value
962
963FLAG_GET:
964    set ret, flag[name]
965
966    .return (ret)
967.end
968
969=item app."newGame"( boards )
970
971Starts a new game with the specified number of boards.
972
973=over 4
974
975=item parameter C<boards>
976
977The number of boards to create.
978
979=back
980
981This method returns nothing.
982
983=cut
984
985.sub newGame :method
986    .param int players     :optional
987    .param int got_players :opt_flag
988    .local pmc temp
989    .local int xpos
990
991    self."disableTimer"()
992
993    set xpos, 10
994
995    # check the number of INT args
996    if got_players goto SET
997
998    getattribute temp, self, 'Players'
999    players = 1
1000    if_null temp, SET
1001    players = temp
1002    branch END_SET
1003
1004SET:
1005    # save the number of players
1006    new temp, 'Integer'
1007    set temp, players
1008
1009    setattribute self, 'Players', temp
1010END_SET:
1011
1012    print "starting a "
1013    print players
1014    print " player game...\n"
1015
1016    # create the boards array
1017    new temp, 'ResizablePMCArray'
1018
1019    setattribute self, 'Boards', temp
1020
1021NEWGAME_NEW_BOARD:
1022    if players <= 0 goto NEWGAME_END
1023    print "new board...\n"
1024    temp = new "Tetris::Board", self
1025    print "new board done.\n"
1026    temp."setPosition"( xpos, 10 )
1027    add xpos, 320
1028    dec players
1029    branch NEWGAME_NEW_BOARD
1030
1031NEWGAME_END:
1032    self."drawScreen"( 1 )
1033
1034    self."enableTimer"()
1035
1036    .return ()
1037.end
1038
1039=back
1040
1041=head1 AUTHOR
1042
1043Jens Rieks E<lt>parrot at jensbeimsurfen dot deE<gt> is the author
1044and maintainer.
1045Please send patches and suggestions to the Perl 6 Internals mailing list.
1046
1047=head1 COPYRIGHT
1048
1049Copyright (C) 2004-2008, Parrot Foundation.
1050
1051=cut
1052
1053# Local Variables:
1054#   mode: pir
1055#   fill-column: 100
1056# End:
1057# vim: expandtab shiftwidth=4 ft=pir:
1058