rem Plain Old T*tris rem COPYRIGHT (C) 2004 TONI RONKKO rem rem Mathew Carr's note: readme.txt is appended to the end rem of this source file. rem rem Permission is hereby granted, free of charge, to any rem person obtaining a copy of this software and rem associated documentation files (the 'Software'), to rem deal in the Software without restriction, including rem without limitation the rights to use, copy, modify, rem merge, publish, distribute, sublicense, and/or sell rem copies of the Software, and to permit persons to whom rem the Software is furnished to do so, subject to the rem following conditions: rem rem The above copyright notice and this permission rem notice shall be included in all copies or rem substantial portions of the Software. rem rem THE SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY OF rem ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT rem LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS rem FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO rem EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, rem DAMAGES OR OTHER LIABILITY, WHETHER IN ACTION OF rem CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR rem IN CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER rem DEALINGS IN THE SOFTWARE. rem rem rem Aug 2006, Toni Ronkko rem New home page. rem rem September 2006, Toni Ronkko rem Several fixes for the emulator: main screen is visible rem on startup, mini-keyboard is visible, synchronize rem game play on system clock instead of assuming 50 rem frames per second. rem rem March 2004, Toni Ronkko rem Fixed off by one error in the shadowed grid lines. rem The keyboard cursor warps from the edge. Shortened rem the delay between changing levels. Increased the rem delay between completion of a row and the next block rem on the very last levels. rem rem February 2004, Toni Ronkko rem The keyboard works on the emulator. Using light rem colors to suit computer screen. Added 3D-look to the rem background grid. rem rem January 2004, Toni Ronkko rem First version. rem rem Initial high score table. If you would like to have rem your score here, insert your name and score to the rem proper place in the following table. (Scores must be rem sorted.) Then, save this very code, and the next time rem your score will be already in the table. Yabasic does rem not have any functionality to save files, so the high rem score table cannot be saved programmatically. label high_score_table data "JASU", 214 data "TUKE", 211 data "JH", 202 data "TONI", 180 data "TR", 155 data "TUKE", 107 data "ZZ TOP", 44 data "TEPA", 0 data "EOF" rem Initial name of the player. default_player$ = "" rem POTRIS version. ver$ = "1.0.3" rem Set up graphics window. WINDOWWIDTH=640 WINDOWHEIGHT=512 open window WINDOWWIDTH,WINDOWHEIGHT setrgb 0, 0,0,60 clear window setrgb 1, 255,255,255 text WINDOWWIDTH/2,WINDOWHEIGHT/2-10, "INITIALIZING POTRIS,", "cc" text WINDOWWIDTH/2,WINDOWHEIGHT/2+10, "PLEASE WAIT...", "cc" rem Maximum number of fields. Currently one field is used rem for the main game field and another is used for the rem preview window. MAXFIELDS=3 rem Maximum dimensions of a field. FIELDMAXWIDTH=40 FIELDMAXHEIGHT=40 rem Width and height of the main gaming area. FIELDWIDTH=11 FIELDHEIGHT=21 rem Dot types. Types 1 -- 255 are reserved for coloured rem blocks. Type 1 is coloured using the very first color rem of the color_data. Variable colors specifies the rem number of colors and the number of block types at rem the same time. DOTMASK is and-mask that can be used to rem filter out some special flags. TMPDOT is a special rem flag saying that the dot is a temporary, and should rem not be consider for hit testing. BLINK means that the rem corresponding dot should change it colors from white rem to normal. Dots marked with BLINKONCE should flash. rem Dots marked with DESTROY are removed from the game rem as a result of completing a row. EMPTYDOT=0 DOTMASK=255 TMPDOT=256 BLINK=512 BLINKONCE=1024 BLINKMASK=BLINKONCE+BLINK DESTROY=2048 rem Default size of a dot in pixels. DOTSIZE=22 rem Size of dot in the preview window. PREVIESDOTSIZE=DOTSIZE rem Position of the game field in screen. FIELDX=WINDOWWIDTH/2-FIELDWIDTH*DOTSIZE/2 FIELDY=WINDOWHEIGHT/2-FIELDHEIGHT*DOTSIZE/2 rem Width of the border of a dot. DOTBORDER=4 rem Width of the border around the game main field. MAINFIELDBORDER=10 rem Width of the border around the preview windpw. PREVIEWBORDER=MAINFIELDBORDER rem Max width and height of a block. (Block is a set of rem dots that is controlled by the user.) BLOCKSIZE=5 rem Number of times each block can be rotated. BLOCKVERSIONS=4 rem Maximum number of different kind of blocks. MAXBLOCKS=10 rem Max number of colours for the blocks. MAXCOLORS=16 rem Controller buttons. PRESS=65536: rem used with get_event RELEASE=0: rem used with get_event SQUARE=32768 CROSS=16384 CIRC=8192: rem circle TRI=4096: rem triangle R1=2048 L1=1024 R2=512 L2=256 LEFT=128 DOWN=64 RIGHT=32 UP=16 START=8 R3=4 L3=2 SELECT=1 rem Mask covering all the buttons. BUTTONMASK=65535: rem dec("ffff") rem Mask filtering out excess button events in combo. rem The length of the mask determinates how many events rem can be combined. For example, 255 means that at most rem two buttons are to be combined, 65535 means that four. COMBOMASK=16777215: rem dec("ffffff") rem Time that is as far in the future as possible. NEVER=2147483647 rem Repeat button press if a button is held down long rem enough. The first time should be longer than the rem second one. AUTOREPEATFIRST=14: rem frames AUTOREPEAT=4: rem frames rem The maximum number of game ports. PORTS=1 rem Number of buttons in controller. BUTTONS=16 rem The maximum number of past button events to remember. EVENTS=20 rem The frequency of the active key blinking in the rem mini-keyboard. (The time is expressed in frames assuming rem 50 frames/sec.) KEYBLINKFREQ=50 rem Number of buttons in the keyboard. KEYBOARDW=7 KEYBOARDH=7 rem Width and height of the keyboard buttons (in pixels). KEYW=25 KEYH=25 rem Number of slots allocated for the big keys. Value of rem 2 means that big key is as wide as two normal keys. BIGKEYW=2 rem Number of frames to wait when changing level. LEVELCHANGEDELAY=50 rem Maximum number of levels. LEVELS=10 rem Blinking frequency (10 = 10/50 = 5 times per second) BLINKFREQ=20 rem Length of shadow. SHADOW=8 rem Number of timeouts at the beginning of the game. rem Timeouts allow the player to pause the game. INITIAL_TIMEOUTS=2 rem Color of the background. BACKGROUNDR=0 BACKGROUNDG=0 BACKGROUNDB=130 rem Color of the background at the lower right corner. BACKGROUND2R=30 BACKGROUND2G=80 BACKGROUND2B=200 rem Color of the normal text. TEXTR=120 TEXTG=250 TEXTB=250 rem How quickly to change pages when the game has not rem started. (Times given in frames.) The user delay rem specifies the delay after user has changed the page rem using L1 and R1 buttons. If the users switches pages rem manually, the game must not switch the page soon rem again. PAGESWITCHDELAY=20*50 USERPAGESWITCHDELAY=60*50 rem Number of pages in the left-hand window. PAGES=4 rem The maximum number of players in the high score table. HIGHSCORES=16 rem How long should the text "GAME OVER" stay visible. rem This measurement is not very precise since it may take rem a couple of frames to redraw the display. GAMEOVERDELAY=50*2 rem Number of frames/second. This will be 50 for a rem genuine PS2, but may change from 30 to 400 in rem yabasic emulator. The exact number of frames rem per second will be computed further below so changing rem this variable here typically achieves very little framespersec = 50 rem The number of frames since the program start. In PS2, this rem is equal to the number of page flips since the program rem start. In yabasic emulator, this counter is synchronized rem with the system clock such that the value can be a floating rem point number. time = 0 rem The total page flips since the program start. This value rem depends on the emulator version and hardware so do not use rem this variable for measuring time! Instead, see variable rem time, which is calibrated to the system clock. rawtime = 0 rem For calibrating delay. startframes = rawtime startseconds = val(mid$(time$,10)) rem Show debug information? debug = 0 rem Initialize game. gosub init_ports gosub init_keyboard gosub init_dirty gosub init_levels gosub init_field gosub init_blocks gosub init_star gosub init_help gosub init_score rem Currently visible buffer. curbuffer = 0 rem Allocate the main game field. x = FIELDX y = FIELDY w = FIELDWIDTH h = FIELDHEIGHT d = DOTSIZE gosub new_field game_arena = f rem Read image of "POTRIS" to the field f. restore potris_data gosub read_field rem Allocate field for the preview window (next block). x = FIELDX + FIELDWIDTH*DOTSIZE + MAINFIELDBORDER*4 y = FIELDY + 30 w = BLOCKSIZE h = BLOCKSIZE d = PREVIESDOTSIZE gosub new_field next_arena = f rem Reset various counters to freeze the main display. gosub halt_game level = 1 timeouts = INITIAL_TIMEOUTS rem Show the welcome screen. page = 1 page_time = PAGESWITCHDELAY rem The game loop. We enter this loop when the game rem has either just been run, or the previous game has rem ended. label the_game gosub redraw gosub reset_ports rem For Emulator: paint the other buffer to show rem the main screen. gosub switch_buffers gosub redraw rem Show information screen automatically or let the rem user switch pages. start_game = 0 label begin_loop gosub switch_buffers rem Switch pages automatically. page_time = page_time - 50/framespersec if (page_time < 0) then page_time = PAGESWITCHDELAY page = page + 1 if (page > PAGES) then page = 1 endif x1 = 0 y1 = 0 x2 = field_x(game_arena) - MAINFIELDBORDER - 1 y2 = WINDOWHEIGHT gosub make_dirty endif rem Read game port 1. p = 1 gosub read_port gosub save_events rem Read one event from the game port. Here, we assume rem that at most one event can be triggered per frame. rem This is not necessarily true if the user holds down rem all the buttons at once. gosub get_event while (event <> 0) rem Start button begins new game. if (event = PRESS+START) then start_game = 1 endif rem R1 and L1 change pages. if (event = PRESS+R1) then page_time = USERPAGESWITCHDELAY page = page + 1 if (page > PAGES) page = PAGES x1 = 0 y1 = 0 x2 = field_x(game_arena) - MAINFIELDBORDER - 1 y2 = WINDOWHEIGHT gosub make_dirty endif if (event = PRESS+L1) then page_time = USERPAGESWITCHDELAY page = page - 1 if (page < 1) page = 1 x1 = 0 y1 = 0 x2 = field_x(game_arena) - MAINFIELDBORDER - 1 y2 = WINDOWHEIGHT gosub make_dirty endif p = 1 gosub get_event wend rem Refresh changed parts of the screen. gosub refresh if (start_game = 0) goto begin_loop rem ##################### rem # Start a new game. # rem ##################### gosub reset_game gosub redraw rem Give the player his first block. gosub generate_next f = game_arena gosub new_block falling = 1 rem The game loop. game_over = 0 label loop gosub switch_buffers rem Read game port 1. p = 1 gosub read_port gosub save_events gosub get_event rem Pause game if the user has some time-outs left. rem Note that holding down the start button does not rem consume multiple timeouts, and therefore we are not rem intercepting the START event without first checking rem if the button was held down earlier. if(event=PRESS+START and and(port_prev(1),START)=0) then if (game_paused = 0) then rem Pause game. if (timeouts > 0) then timeouts = timeouts - 1 game_paused = 1 gosub redraw else rem No more timeouts. beep endif else rem Continue game. game_paused = 0 gosub redraw endif endif rem Move a block if we have one. if (curtype <> 0 and game_paused = 0) then rem Copy the position and the angle of the block so rem that we can try to move it. newx = curx newy = cury newa = curangle rem Deal with the main arena from now on. f = game_arena rem Rotate the block if the user wants to. if (event = PRESS+CROSS) then x = newx y = newy a = mod(newa,BLOCKVERSIONS)+1 t = curtype gosub hit_test if (r = 1) then rem There is enough room to rotate the block. Make rem the new angle permanent. newa = a else rem Not enough room; try to move the block left rem or right to make it fit. We end up here when rem the users tries to rotate the L shaped block rem near walls, for example. x = newx-1 gosub hit_test if (r = 1) then newa = a newx = x else rem Try moving right. x = newx+1 gosub hit_test if (r = 1) then newa = a newx = x else rem Try to move the block down to fit. x = newx y = newy+1 gosub hit_test if (r = 1) then newa = a newy = y else rem Try to move further down. This happens rem when rotating the I shaped block at the rem top of the screen. x = newx y = newy+2 gosub hit_test if (r = 1) then newa = a newy = y endif endif endif endif endif endif rem Move the block according to user's instructions. if (event = PRESS+LEFT) then x = newx-1 y = newy a = newa t = curtype gosub hit_test if (r = 1) newx = x elsif (event = PRESS+RIGHT) then x = newx+1 y = newy a = newa t = curtype gosub hit_test if (r = 1) newx = x endif rem Let the user move the block down or move it rem automatically. Variable falling is set to false rem if the block reaches the floor. fall_delay = fall_delay - 50/framespersec if (and(port_cur(1),DOWN) > 0) then rem Expedite fall by factor 20. fall_delay = fall_delay - 20*50/framespersec endif if (fall_delay < 0) then rem The block moves down automatically or by users rem command. fall_delay = level_fall(level) x = newx y = newy+1 a = newa t = curtype gosub hit_test if (r <> 0) then rem There is room below the block so accept the rem new position. newy = y else rem The block has reached the floor. falling = 0 endif endif rem Now that the new coordinates are set, move the rem image on screen. if (newx<>curx or newy<>cury or newa<>curangle) then rem Remove the existing block from the field. t = curtype a = curangle x = curx y = cury d = EMPTYDOT gosub paint_block rem Draw the new block. If the block has reached rem the floor, the block will be left there. t = curtype a = newa x = newx y = newy d = or(curdot, TMPDOT) gosub paint_block rem Remember the new position of the block. curx = newx cury = newy curangle = newa endif rem Freeze the block when it hits the bottom. if (falling = 0) then gosub freeze endif elsif (game_paused = 0) then rem There is no block to move. Let the user rotate the rem block in the preview window. if (event = PRESS+CROSS) then nextangle = mod(nextangle,BLOCKVERSIONS)+1 f = next_arena gosub paint_next endif endif rem Update the game arena to reflect the changes. gosub refresh rem Get a new block if the previous one reached the rem floor. if ((falling = 0 or curtype = 0) and game_paused=0) then next_delay = next_delay - 50/framespersec if (next_delay < 0) then rem It is time to give the user a new block. next_delay = level_next(level) falling = 1 f = game_arena gosub new_block endif endif if (game_over = 0) goto loop rem ###################### rem # THE GAME HAS ENDED # rem ###################### f = game_arena gosub reset_blinking game_paused = 0 rem Blink the game field to mark the end of the game. f = game_arena for j = 1 to field_height(f) for i = 1 to field_width(f) if (and(field(f,i,j),DOTMASK) <> 0) then field(f,i,j) = or(field(f,i,j),BLINKONCE) endif next i field_row(f,j) = or(field_row(f,j),BLINKONCE) next j blink_time = 0 blink_state = 1 destroy_time = NEVER blink_destroy_time = GAMEOVERDELAY-1 rem Let the dots flash for a while. game_over = GAMEOVERDELAY while (game_over >= 0) gosub switch_buffers gosub refresh rem Tell the user not to expect any more blocks. a$ = "GAME OVER" x = WINDOWWIDTH/2 y = WINDOWHEIGHT/2 - 10 gosub draw_cstring gosub draw_cstring game_over = game_over - 50/framespersec wend rem Reset blinking and other counters. gosub halt_game rem See if the user makes it into the high score table. i = 1 pos = 0 while (i <= HIGHSCORES) if (score_rows(i) < rows_completed) then rem User makes it into the high score table. pos = i i = HIGHSCORES+1 endif i = i + 1 wend rem Ask initials if the player makes it to the high rem score table. if (pos <> 0) then rem Make room to the high score table. a$ = "nobody" gosub insert_score rem Switch to high score list and redraw the display. page = 3 gosub redraw gosub refresh rem For the emulator: update the other buffer also rem so that we may swap buffers without re-painting rem the whole screen. (See input_string function rem for more info.) gosub switch_buffers gosub refresh rem Compute the position of the editable text rem such that the mini-keyboard does not overlap rem the score. If possible, the text area is placed rem to proper position in the high-score list. x = 8 y = 314 text_h = 25 text_x = x - 5 text_w = field_x(game_arena) - text_x - MAINFIELDBORDER if (pos <= 9) then text_y = 85 + 25*(pos-1) else rem The real position would be hidden by the keyboard. text_y = 85 + 25*8 endif rem Ask user's initials. text$ = default_player$ maxlen = 8 gosub input_string default_player$ = text$ if (text$ = "") text$ = "POTRIS" rem Save score to the high score table. score_lvl(pos) = level score_player$(pos) = text$ score_rows(pos) = rows_completed endif rem Show the high score table. page = 3 page_time = PAGESWITCHDELAY*2 gosub redraw gosub refresh goto the_game rem Redraw changed parts of the screen. Drawing just the rem changed parts is important for running the game at rem 50 fps. The yabasic is not powerful enough to redraw rem the whole screen in every frame. rem BEWARE: overwrites global variables at will. label refresh rem Do blinkig in the main arena. if (game_paused = 0) then f = game_arena gosub update_blinking endif rem Draw borders of the main arena if necessary. x1 = field_x(game_arena) y1 = 0 x2 = x1 + field_dot(game_arena)*field_width(game_arena) y2 = WINDOWHEIGHT x1 = x1 - MAINFIELDBORDER x2 = x2 + MAINFIELDBORDER gosub is_dirty if (r <> 0) then rem Reset background. setrgb 1, BACKGROUNDR*.8,BACKGROUNDG*.8,BACKGROUNDB*.8 setrgb 2, BACKGROUNDR*.9,BACKGROUNDG*.9,BACKGROUNDB*.9 setrgb 3, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB gtriangle x1,y1 to x1,field_y(game_arena) to x2,y1 setrgb 1, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB gtriangle x2,field_y(game_arena) to x1,field_y(game_arena) to x2,y1 setrgb 1, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB fill rectangle x1,field_y(game_arena)+field_dot(game_arena)*field_height(game_arena) to x2,WINDOWHEIGHT rem Draw border of the main game arena. f = game_arena b = MAINFIELDBORDER gosub draw_field_border endif rem Always refresh main arena. f = game_arena gosub refresh_field rem Draw the preview window. f = next_arena x1 = field_x(game_arena) + field_dot(game_arena)*field_width(game_arena) + MAINFIELDBORDER y1 = 0 x2 = WINDOWWIDTH y2 = field_y(f) + field_dot(f)*field_height(f) + PREVIEWBORDER gosub is_dirty if (r <> 0) then rem Reset background. setrgb 1, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB fill rectangle x1,y1 to x2,field_y(f) fill rectangle x1,field_y(f) to field_x(f),y2 rem Part left of the field showing the next block. x = field_x(f)+field_dot(f)*field_width(f) fill triangle x,field_y(f) to WINDOWWIDTH,field_y(f) to x,y2 setrgb 2, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB setrgb 3, BACKGROUNDR+20,BACKGROUNDG+20,BACKGROUNDB+20 gtriangle WINDOWWIDTH,field_y(f) to x,y2 to WINDOWWIDTH,y2 rem Draw border of the main game arena. f = next_arena b = PREVIEWBORDER gosub draw_field_border rem Paint the field itself. gosub refresh_field rem Draw the text above the preview window. setrgb 1, TEXTR,TEXTG,TEXTB x1 = field_x(game_arena) + field_dot(game_arena)*field_width(game_arena) + MAINFIELDBORDER + 15 y1 = field_y(f) - PREVIEWBORDER - 10 text x1,y1, "Next:", "lb" endif rem Draw the help/high-score/credits window. x1 = 0 y1 = 0 x2 = field_x(game_arena) - MAINFIELDBORDER y2 = WINDOWHEIGHT gosub is_dirty if (r <> 0) then rem Reset background. y = field_y(game_arena) setrgb 1, BACKGROUNDR*.5,BACKGROUNDG*.5,BACKGROUNDB*.5 setrgb 2, BACKGROUNDR*.9,BACKGROUNDG*.9,BACKGROUNDB*.9 setrgb 3, BACKGROUNDR*.8,BACKGROUNDG*.8,BACKGROUNDB*.8 gtriangle x1,y1 to x2,y to x2,y1 setrgb 3, BACKGROUNDR*.7,BACKGROUNDG*.7,BACKGROUNDB*.7 gtriangle x1,y1 to x2,y to x1,y2 setrgb 1, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB gtriangle x2,y2 to x2,y to x1,y2 setrgb 1, TEXTR,TEXTG,TEXTB x = x1 + 8 y = y1 + 22 if (page = 1) then for i = 1 to info_strings text x,y, info$(i), "lb" y = y + 20 next i elsif (page = 2) then for i = 1 to help_strings text x,y, help$(i), "lb" y = y + 20 next i elsif (page = 3) then gosub draw_scores elsif (page = 4) then for i = 1 to credit_strings text x,y, credit$(i), "lb" y = y + 20 next i endif endif rem Draw the score window showing the current level etc. x1 = field_x(game_arena) + field_dot(game_arena)*field_width(game_arena) + MAINFIELDBORDER y1 = field_y(next_arena) + field_dot(next_arena)*field_height(next_arena) + PREVIEWBORDER x2 = WINDOWWIDTH y2 = WINDOWHEIGHT gosub is_dirty if (r <> 0) then rem Reset background. f = next_arena x = field_x(f)+field_dot(f)*field_width(f) x3 = WINDOWWIDTH - 60 y3 = WINDOWHEIGHT - 100 setrgb 1, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB fill triangle x1,y1 to x,y1 to x1,y2 setrgb 2, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB setrgb 3, BACKGROUNDR+20,BACKGROUNDG+20,BACKGROUNDB+20 gtriangle x1,y2 to x,y1 to x3,y3 setrgb 2, BACKGROUNDR+20,BACKGROUNDG+20,BACKGROUNDB+20 gtriangle x,y1 to x3,y3 to x2,y1 setrgb 3, BACKGROUND2R,BACKGROUND2G,BACKGROUND2B gtriangle x1,y2 to x3,y3 to x2,y2 setrgb 1, BACKGROUNDR+20,BACKGROUNDG+20,BACKGROUNDB+20 gtriangle x2,y1 to x3,y3 to x2,y2 rem Draw the texts. setrgb 1, TEXTR,TEXTG,TEXTB text x1 + 15, y1 + 40, "Level:", "lb" text x1 + 15, y1 + 80, "Rows left:", "lb" text x1 + 15, y1 + 120, "Time-outs:", "lb" text x1 + 15, y1 + 185, "Rows:", "lb" x = x1 + 30 y = y1 + 144 for k = 1 to 5 if (k <= timeouts) then setrgb 1, 200,200,0 gosub draw_star x = x - 1 gosub draw_star x = x + 1 else setrgb 1, 0,30,170 x = x - 2 y = y - 2 gosub draw_star setrgb 1, 0,10,60 x = x + 4 y = y + 4 gosub draw_star setrgb 1, BACKGROUNDR,BACKGROUNDG,BACKGROUNDB x = x - 2 y = y - 2 gosub draw_star endif x = x + 30 next k x = x1 + 160 y = y1 + 40 a$ = str$(level) gosub draw_rstring rem Rows to complete before advancing to next level. k = level_rows(level) - rows_completed if (k > 0) then a$ = str$(k) else rem No more levels. a$ = "-" endif x = x1 + 160 y = y1 + 80 gosub draw_rstring x = x1 + 160 y = y1 + 185 a$ = str$(rows_completed) gosub draw_rstring rem Draw the copyright text at the lower right corner. rem First draw the dark backdrop that makes the text rem visible in the light background. x = x1 + 10 setrgb 1, 0,30,100 text x+2,y2-70+2, "Plain Old T*tris", "lb" text x+2,y2-50+2, "version " + ver$, "lb" text x+2,y2-30+2, "Copyright", "lb" text x+2,y2-10+2, "Toni Ronkko 2004", "lb" rem Draw the light copyright text at the lower right rem corner. setrgb 1, 0,80,230 text x,y2-70, "Plain Old T*tris", "lb" text x,y2-50, "version " + ver$, "lb" text x,y2-30, "Copyright", "lb" text x,y2-10, "Toni Ronkko 2004", "lb" endif rem Show the paused game text. Note that we may draw rem freely during the paused game since the screen rem will be completely re-drawn when we either exit rem or enter the pause mode. if (game_paused <> 0) then x1 = 0 y1 = 0 x2 = WINDOWWIDTH y2 = WINDOWHEIGHT gosub is_dirty if (r <> 0) then rem Dim screen by clearing out every other pixel. setrgb 1, 0,0,0 for x = 2 to WINDOWHEIGHT+WINDOWWIDTH step 2 line x,0 to x-WINDOWHEIGHT,WINDOWHEIGHT next x rem Show the user how to continue the game. a$ = "PRESS START TO CONTINUE" x = WINDOWWIDTH/2 y = WINDOWHEIGHT/2 - 10 gosub draw_cstring endif endif rem Tell user about changing level. if (level_change_time < LEVELCHANGEDELAY/2) then a$ = "LEVEL " + str$(level) x = WINDOWWIDTH/2 y = WINDOWHEIGHT/2 + 10 gosub draw_cstring endif rem Mark the dirty rectangle clean. gosub reset_dirty rem Erase level text, Note that this has to be after rem reset_dirty or else the other buffer is not redrawn rem leaving the text flashing at the middle of the rem screen. level_change_time = level_change_time - 50/framespersec if (level_change_time < 0) then rem Continue game. level_change_time = NEVER next_delay = level_next(level) rem Re-draw area with the text. x1 = field_x(game_arena) y1 = WINDOWHEIGHT/2 - 30 x2 = x1+field_dot(game_arena)*field_width(game_arena) y2 = y1 + 30 gosub make_dirty endif return rem Switch back and front buffers. rem BEWARE: overwrites variables x, now label switch_buffers rem Switch buffers. setdrawbuf curbuffer curbuffer = 1-curbuffer setdispbuf curbuffer rem Advance time so that event functions work OK. rawtime = rawtime + 1 time = time + 50 / framespersec rem Update the number of frames/second. now = val(mid$(time$,10)) if now <> startseconds then framespersec = rawtime/(now-startseconds) endif rem Show the number of frames/second. if (debug <> 0) then setrgb 1, 0,0,100 fill rectangle 0,440 to 50,512 setrgb 1, 0,0,200 text 0,465, str$(now-startseconds), "lb" text 0,485, str$(int(time)), "lb" text 0,505, str$(int(framespersec)), "lb" endif return rem ###### BLINKING ###################################### rem Update the blinking dots in field f. Blinking dots are rem often associated with blocks being destroyed, and, rem as such, the function also deals with destroyable rem dots. rem BEWARE: overwrites variables x1, y1, x2, y2, f, i, rem row_flags, still_blinking, k, and j. rem BEWARE: overwrites variable c. label update_blinking rem Reset blinking dots. blink_destroy_time = blink_destroy_time - 50/framespersec if (blink_destroy_time < 0) then gosub reset_blinking endif rem Redraw dots that are blinking. blink_time = blink_time - 50/framespersec if (blink_time < 0) then rem Switch blinking dots on or off. blink_state = 1 - blink_state rem Find the blinking dots and mark the area occupied rem by the dots dirty. still_blinking = 0 y1 = field_y(f) y2 = y1 + field_dot(f) - 1 for j = 1 to field_height(f) row_flags = field_row(f,j) if (and(row_flags,BLINKMASK) <> 0) then rem There are some dots that need repainting. row_flags = and(row_flags,65535-BLINKMASK) x1 = field_x(f) x2 = x1 + field_dot(f) - 1 for i = 1 to field_width(f) k = and(field(f,i,j),BLINKMASK) rem Mark the blinking dot for repaint. if (k <> 0) then row_flags = or(row_flags,k) still_blinking = 1 gosub make_dirty endif x1 = x2 + 1 x2 = x2 + field_dot(f) next i rem Reset the row flag if there are no more rem blinking dots. field_row(f,j) = row_flags endif y1 = y2 + 1 y2 = y2 + field_dot(f) next j rem Compute when to blink the dots next time. if (still_blinking <> 0) then rem Reset blink counter. blink_time = BLINKFREQ else rem Stop blinking the dots. blink_time = NEVER endif endif rem Destroy dots marked for destruction. destroy_time = destroy_time - 50/framespersec if (destroy_time < 0) then rem Scan the game area from bottom up destoying the rem dots marked for destruction. Dots above the rem destroyed ones will be moved down. Note that we rem do this column by column. It is not necessary to rem destroy a full row. f = game_arena for i = 1 to field_width(f) rem Scan column from bottom up. k = field_height(f) for j = k to 1 step -1 if (and(field(f,i,j),DESTROY) <> 0) then rem Dot at (i,j) is marked for destuction. field(f,i,j) = EMPTYDOT elsif (and(field(f,i,j),DOTMASK) <> EMPTYDOT) then rem Try to move the dot down. if (j <> k) then field(f,i,k) = field(f,i,j) field(f,i,j) = EMPTYDOT endif rem Remove blinking. field(f,i,k) = and(field(f,i,k),65535-BLINKMASK) k = k - 1 else rem Leave an empty dot to mark the spot. Not rem doing this would remove holes from the field rem which in turn would eventually fill the game rem field with indestructible rows. (To fix rem that, we would have to prepare for rem destroying some more rows at this point.) k = k - 1 endif next j next i rem Fix row flags and compute the number of rows rem destroyed. for j = 1 to field_height(f) rem Count the rows completed. if (and(field_row(f,j),DESTROY) <> 0) then rows_completed = rows_completed + 1 rem Earn timeouts every 50th row. if (mod(rows_completed,50) = 0) then timeouts = timeouts + 1 endif endif rem Reset row flags. field_row(f,j) = 0 next j rem Stop destruction counter. destroy_time = NEVER rem Advance to next level if enough rows completed. if (level 0) then rem Reset blinking of each dot. x1 = field_x(f) x2 = x1 + field_dot(f) - 1 for i = 1 to field_width(f) k = field(f,i,j) if (and(k,BLINKMASK) <> 0) then rem Mark the dot for repaint. field(f,i,j) = and(k,65535-BLINKMASK) gosub make_dirty endif x1 = x2 + 1 x2 = x2 + field_dot(f) next i field_row(f,j)=and(field_row(f,j),65535-BLINKMASK) endif y1 = y2 + 1 y2 = y2 + field_dot(f) next j rem Stop the counters. blink_destroy_time = NEVER blink_time = NEVER return rem ###### FIELDS ######################################## rem Draw field f completely. rem BEWARE: overwrites variables x1, y1, x2, y2, b, i, t rem k, m, n, and j. label draw_field i = 1 j = 1 m = field_width(f) n = field_height(f) goto _draw_field rem Refresh changed parts of field f. The dirty rectangle rem identifies the parts that should be drawn. rem BEWARE: overwrites variables x1, y1, x2, y2, b, i, t rem k, m, n, and j. label refresh_field rem Compute the top-left and the bottom-right dot that rem must be drawn. Note that we must include two excess rem dots at right and below. One excess dot ensures that rem a full dot is draw even though the dirty rectangle rem does not completely include the dot. One more excess rem dot must be redraw because of the shadows. i = int((dirty_x1(curbuffer)-field_x(f))/field_dot(f)) j = int((dirty_y1(curbuffer)-field_y(f))/field_dot(f)) m = int((dirty_x2(curbuffer)-field_x(f))/field_dot(f))+2 n = int((dirty_y2(curbuffer)-field_y(f))/field_dot(f))+2 label _draw_field b = DOTBORDER rem Refuse to draw outside the field. if (i < 1) i = 1 if (j < 1) j = 1 if (i > field_width(f)) return if (j > field_height(f)) return if (m < 1) return if (n < 1) return if (m > field_width(f)) m = field_width(f) if (n > field_height(f)) n = field_height(f) rem Redraw the dirty dots. y2 = j*field_dot(f) + field_y(f) y1 = y2 - field_dot(f) while (j <= n) k = i x2 = k*field_dot(f) + field_x(f) x1 = x2 - field_dot(f) while (k <= m) rem Extract dot type from the field. t = and(field(f,k,j),DOTMASK) rem Clear dot. if (t = EMPTYDOT) then rem Fill background. setrgb 1, 0,30,80 fill rectangle x1,y1 to x2,y2 rem Draw the grid. setrgb 1, 0,60,140 line x1,y1+1 to x2-1,y1+1 line x1+1,y1 to x1+1,y2 setrgb 1, 20,20,20 line x1,y1 to x2-1,y1 line x1,y1 to x1,y2 rem Draw shadow of a dot at left. setrgb 1, 0,25,70 if (k <= 1 or field(f,k-1,j) <> EMPTYDOT) then x = x1 + SHADOW fill triangle x1,y1 to x,y1+SHADOW to x,y2 fill triangle x1,y1 to x,y2 to x1,y2 endif rem Shadow of a dot at top. if (j <= 1 or field(f,k,j-1) <> EMPTYDOT) then y = y1 + SHADOW fill triangle x1,y1 to x1+SHADOW,y to x2,y fill triangle x1,y1 to x2,y to x2,y1 endif rem Shadow of a dot at upper left corner. if(k<=1 or j<=1 or field(f,k-1,j-1)<>EMPTYDOT)then fill rectangle x1,y1 to x1+SHADOW,y1+SHADOW rem Dim the grid lines on the shaded area. setrgb 1, 0,30,70 if(j > 1) line x1,y1+1 to x1+SHADOW,y1+1 if(k > 1) line x1+1,y1 to x1+1,y1+SHADOW endif rem Draw coloured dot. elsif (t <= colors) then if (and(field(f,k,j),BLINKMASK)=0 or blink_state<>0) then rem Draw normal dot. setrgb 1, base_r(t),base_g(t),base_b(t) fill rectangle x1,y1 to x2,y2 setrgb 1,highlight_r(t),highlight_g(t),highlight_b(t) fill rectangle x1,y1 to x2,y1+b fill rectangle x1,y1 to x1+b,y2 setrgb 1, shadow_r(t),shadow_g(t),shadow_b(t) fill rectangle x1+b,y2-b to x2,y2 fill rectangle x2-b,y1+b to x2,y2 fill triangle x1,y2 to x1+b,y2-b to x1+b,y2 fill triangle x2,y1 to x2-b,y1+b to x2,y1+b elsif (and(field(f,k,j),BLINK)<>0) then rem Draw blinking dot. setrgb 1, 215,215,215 fill rectangle x1,y1 to x2,y2 setrgb 1, 255,255,255 fill rectangle x1,y1 to x2,y1+b fill rectangle x1,y1 to x1+b,y2 setrgb 1, 180,180,180 fill rectangle x1+b,y2-b to x2,y2 fill rectangle x2-b,y1+b to x2,y2 fill triangle x1,y2 to x1+b,y2-b to x1+b,y2 fill triangle x2,y1 to x2-b,y1+b to x2,y1+b else rem Draw dot that is blinking once (flashing). x=1.5 setrgb 1, base_r(t)*x,base_g(t)*x,base_b(t)*x fill rectangle x1,y1 to x2,y2 setrgb 1,highlight_r(t)*x,highlight_g(t)*x,highlight_b(t)*x fill rectangle x1,y1 to x2,y1+b fill rectangle x1,y1 to x1+b,y2 setrgb 1, shadow_r(t)*x,shadow_g(t)*x,shadow_b(t)*x fill rectangle x1+b,y2-b to x2,y2 fill rectangle x2-b,y1+b to x2,y2 fill triangle x1,y2 to x1+b,y2-b to x1+b,y2 fill triangle x2,y1 to x2-b,y1+b to x2,y1+b endif else print "unknown dot type at draw_field" end endif k = k + 1 x1 = x2 x2 = x2 + field_dot(f) wend j = j + 1 y1 = y2 y2 = y2 + field_dot(f) wend return rem Draw b pixels wide border around field f. rem BEWARE: overwrites x1, y1, x2, y2 label draw_field_border rem Compute the location of the field. x1 = field_x(f) y1 = field_y(f) x2 = field_x(f) + field_width(f)*field_dot(f) y2 = field_y(f) + field_height(f)*field_dot(f) rem Draw lowered frame around the field. setrgb 1, 0,40,100 fill rectangle x1-b,y1-b to x2+b,y1 fill rectangle x1-b,y1-b to x1,y2+b setrgb 1, 0,120,200 fill rectangle x1,y2 to x2+b,y2+b fill rectangle x2,y1 to x2+b,y2+b fill triangle x1-b,y2+b to x1,y2 to x1,y2+b fill triangle x2+b,y1+b to x2,y1 to x2+b,y1-b return rem Read field from data area. rem BEWARE: overwrites d, k, j and i. label read_field d = EMPTYDOT gosub fill_field j = 1 read a$ while (a$ <> "EOF") k = len(a$) for i = 1 to k c$ = mid$(a$,i,1) if (c$ = "x") then rem Paint the dots marked with x yellow. The rem number refers to the color_data. field(f,i,j) = 3 endif next i read a$ j = j + 1 wend return rem Allocate w dots wide and h dots tall field at (x,y) rem having dotsize d. Returns the new field in variable rem f. Does not return in error. label new_field rem Find a free field. f = 1 while (f <= MAXFIELDS) if (field_allocated(f) = 0) then rem Found a free field. field_allocated(f) = 1 field_width(f) = w field_height(f) = h field_x(f) = x field_y(f) = y field_dot(f) = d return endif f = f + 1 wend print "out of fields" end rem Release field allocated by new_field label delete_field if (field_allocated(f) <> 0) then field_allocated(f) = 0 else print "field already deleted" end endif return rem Fill field f with dot type d. rem BEWARE: does not prepare the field for drawing. rem BEWARE: overwrites variables i and j. label fill_field for j = 1 to FIELDHEIGHT for i = 1 to FIELDWIDTH field(f,i,j) = d next i rem Reset row flags. field_row(f,j) = 0 next j return rem Initialize fielf subsystem. rem BEWARE: overwrites f. label init_field dim field(MAXFIELDS,FIELDMAXWIDTH,FIELDMAXHEIGHT) dim field_row(MAXFIELDS,FIELDMAXHEIGHT) dim field_allocated(MAXFIELDS) dim field_width(MAXFIELDS) dim field_height(MAXFIELDS) dim field_x(MAXFIELDS) dim field_y(MAXFIELDS) dim field_dot(MAXFIELDS) rem Mark all field available. for f = 1 to MAXFIELDS field_allocated(f) = 0 next f return rem ###### DIRTY RECTANGLES ############################## rem Check if rectangle (x1,y1)-(x2,y2) is within dirty rem rectangle. Returns r=1 if the rectangle should be rem redrawn. label is_dirty r = 1 rem Is dirty rectangle empty? if (dirty_x2(curbuffer) < dirty_x1(curbuffer)) r = 0 rem Does the rectangle (x1,y1)-(x2,y2) overlap the rem dirty rectangle? if (x2 < dirty_x1(curbuffer)) r = 0 if (dirty_x2(curbuffer) < x1) r = 0 if (y2 < dirty_y1(curbuffer)) r = 0 if (dirty_y2(curbuffer) < y1) r = 0 return rem Make rectangle (x1,y1)-(x2,y2) dirty so that the rem region will be repaint later. rem BEWARE: overwrites variable c. label make_dirty for c = 0 to 1 if (dirty_x1(c) > x1) dirty_x1(c) = x1 if (dirty_y1(c) > y1) dirty_y1(c) = y1 if (dirty_x2(c) < x2) dirty_x2(c) = x2 if (dirty_y2(c) < y2) dirty_y2(c) = y2 next c return rem Mark the whole screen dirty so that it will be drawn rem again. label redraw dirty_x1(0) = 0 dirty_y1(0) = 0 dirty_x2(0) = WINDOWWIDTH dirty_y2(0) = WINDOWHEIGHT dirty_x1(1) = 0 dirty_y1(1) = 0 dirty_x2(1) = WINDOWWIDTH dirty_y2(1) = WINDOWHEIGHT return rem Reset the current buffer's dirty rectangle. The rem function is used to mark the dirty rectangle drawn. rem BEWARE: uses global variable curbuffer. label reset_dirty dirty_x1(curbuffer) = WINDOWWIDTH dirty_y1(curbuffer) = WINDOWHEIGHT dirty_x2(curbuffer) = 0 dirty_y2(curbuffer) = 0 return rem Initialize dirty rectangles. label init_dirty dim dirty_x1(1) dim dirty_y1(1) dim dirty_x2(1) dim dirty_y2(1) return rem ###### BLOCKS ######################################## rem Generate new block at random in field f. Sets rem variable game_over to 1 if the block does not fit to rem the field. The following variables describe the block: rem curangle - angle of the block rem curtype - type of the block (L, I or some other) rem curx,cury - position of the bloc rem curdot - color of the block rem BEWARE: overwrites variables f, r, x, y, a and t. rem BEWARE: overwrites variables k, i and j. label new_block rem Move the block from the next area to the main field. curangle = nextangle curtype = nexttype curdot = nextdot curx = int(field_width(f)/2) - int(BLOCKSIZE/2) cury = -block_y1(curtype,curangle)+1 fall_delay = level_fall(level) rem Check if the block fits to screen. There must be rem space below the insertion area before the game can rem continue. x = curx y = cury+1 a = curangle t = curtype gosub hit_test if (r <> 0) then rem The block fits. Draw the block to the field. y = cury d = or(curdot,TMPDOT) gosub paint_block rem Generate new block to the preview window. gosub generate_next else rem There is not enough room in the game arena. game_over = 1 endif return rem Generate the next block, and update the screen. rem BEWARE: overwrites f, d, i, j, x, y, t, a. label generate_next rem Generate next block. nextangle = 1 + int(ran(BLOCKVERSIONS)) nexttype = 1 + int(ran(blocks)) nextdot = 1 + int(ran(colors)) goto paint_next rem Paint the image of the next block to the preview rem window. rem BEWARE: overwrites f, d, i, j, x, y, t, a. label paint_next rem Clear the image of the current block. f = next_arena d = EMPTYDOT gosub fill_field rem Paint the next block to the field. x = 0 y = 0 d = nextdot a = nextangle t = nexttype f = next_arena gosub _paint_block rem Ensure that the whole field gets updated. This is rem necessary since fill_field does not update rem dirty region. x1 = field_x(f) y1 = field_y(f) x2 = x1 + (field_width(f)-1) * field_dot(f) y2 = y1 + (field_height(f)-1) * field_dot(f) gosub make_dirty return rem Draw block (t,a) at (x,y) in field f with dot type d, rem and mark the area dirty so that the game arena can rem be later cleared. Set d to EMPTYDOT to remove a block rem from the screen. rem BEWARE: does not update row flags. If you want to rem draw blinking dots, please set row flags manually. rem BEWARE: overwrites variables x1, y1, x2, y2, i and j. rem BEWARE: overwrites variable c. label paint_block rem Mark the modified area so that we know to draw it. x1 = field_x(f) + (x + block_x1(t,a)) * field_dot(f) y1 = field_y(f) + (y + block_y1(t,a)) * field_dot(f) x2 = field_x(f) + (x + block_x2(t,a)) * field_dot(f)-1 y2 = field_y(f) + (y + block_y2(t,a)) * field_dot(f)-1 gosub make_dirty rem FALLTHROUGH label _paint_block rem Paint the block to the field. for j = block_y1(t,a) to block_y2(t,a) for i = block_x1(t,a) to block_x2(t,a) if (block(t,a,i,j) > 0) then field(f,x+i,y+j) = d endif next i next j return rem See if block (t,a) at (x,y) in field f hits any rem reserved dots. Dots having TMPDOT attribute are not rem considered for a hit. rem BEWARE: overwrites variables k, i and j. label hit_test j = block_y1(t,a) while (j <= block_y2(t,a)) i = block_x1(t,a) while (i <= block_x2(t,a)) if (block(t,a,i,j) > 0) then rem The dot (i,j) in block t is set. Now, see if rem the dot is within the game field. if (x+i < 1) goto _hit_test_fail if (y+j < 1) goto _hit_test_fail if (x+i > field_width(f)) goto _hit_test_fail if (y+j > field_height(f)) goto _hit_test_fail rem Retrieve the dot at (x+i,y+j) from the field rem and check it. The current block is marked rem with the temporary flag so the block cannot rem test against itself. k = field(f,x+i,y+j) if (and(k,DOTMASK)<>EMPTYDOT and and(k,TMPDOT)=0) goto _hit_test_fail endif i = i + 1 wend j = j + 1 wend r = 1 return label _hit_test_fail r = 0 return rem Initialize the blocks that fall from the top of the rem screen. rem BEWARE: overwrites variable i, j, k, a$ and c$. label init_blocks rem Block arrays. dim block(MAXBLOCKS,BLOCKVERSIONS,BLOCKSIZE,BLOCKSIZE) dim block_x1(MAXBLOCKS,BLOCKVERSIONS) dim block_y1(MAXBLOCKS,BLOCKVERSIONS) dim block_x2(MAXBLOCKS,BLOCKVERSIONS) dim block_y2(MAXBLOCKS,BLOCKVERSIONS) blocks = 0 rem Read blocks from the data area. restore block_data read a$ while (a$ <> "EOF") rem Clear the bounding box of the block. blocks = blocks + 1 for k = 1 to BLOCKVERSIONS block_x1(blocks,k) = BLOCKSIZE block_y1(blocks,k) = BLOCKSIZE block_x2(blocks,k) = 1 block_y2(blocks,k) = 1 next k rem Read four versions of a block. for j = 1 to BLOCKSIZE for k = 1 to BLOCKVERSIONS for i = 1 to BLOCKSIZE c$ = mid$(a$,i,1) if (c$ = "x") then block(blocks,k,i,j) = 1 rem Update the bounding box. if (iblock_x2(blocks,k)) block_x2(blocks,k)=i if (j>block_y2(blocks,k)) block_y2(blocks,k)=j else block(blocks,k,i,j) = 0 endif next i read a$ next k next j wend rem Colors for the blocks. dim highlight_r(MAXCOLORS) dim highlight_g(MAXCOLORS) dim highlight_b(MAXCOLORS) dim base_r(MAXCOLORS) dim base_g(MAXCOLORS) dim base_b(MAXCOLORS) dim shadow_r(MAXCOLORS) dim shadow_g(MAXCOLORS) dim shadow_b(MAXCOLORS) rem Read colors. i = 0 read r1 while (r1 >= 0) read g1,b1, r2,g2,b2, r3,g3,b3 i = i + 1 highlight_r(i) = r1 highlight_g(i) = g1 highlight_b(i) = b1 base_r(i) = r2 base_g(i) = g2 base_b(i) = b2 shadow_r(i) = r3 shadow_g(i) = g3 shadow_b(i) = b3 read r1 wend colors = i return label block_data data ".....", "..x..", ".....", "..x.." data ".....", "..x..", ".....", "..x.." data "xxxxx", "..x..", "xxxxx", "..x.." data ".....", "..x..", ".....", "..x.." data ".....", "..x..", ".....", "..x.." data ".....", ".....", ".....", "....." data ".....", ".....", ".....", "....." data "..xx.", "..xx.", "..xx.", "..xx." data "..xx.", "..xx.", "..xx.", "..xx." data ".....", ".....", ".....", "....." data ".....", ".....", ".....", "....." data ".x...", ".....", ".x...", "....." data ".xx..", "..xx.", ".xx..", "..xx." data "..x..", ".xx..", "..x..", ".xx.." data ".....", ".....", ".....", "....." data ".....", ".....", ".....", "....." data "..x..", "..x..", ".....", "..x.." data ".xxx.", ".xx..", ".xxx.", "..xx." data ".....", "..x..", "..x..", "..x.." data ".....", ".....", ".....", "....." data ".....", ".....", ".....", "....." data ".x...", "..x..", ".....", "..xx." data ".xxx.", "..x..", ".xxx.", "..x.." data ".....", ".xx..", "...x.", "..x.." data ".....", ".....", ".....", "....." data ".....", ".....", ".....", "....." data ".....", "..x..", "...x.", ".xx.." data ".xxx.", "..x..", ".xxx.", "..x.." data ".x...", "..xx.", ".....", "..x.." data ".....", ".....", ".....", "....." data "EOF" label color_data rem highlight base shadow rem R G B R G B R G B data 255,120,000, 200,060,000, 160,000,000 data 000,200,255, 000,120,255, 000,060,200 data 255,255,000, 200,200,000, 120,120,000 data 040,255,040, 020,200,020, 000,120,000 data -1 rem ###### LEVELS ####################################### rem Initialize game levels. rem BEWARE: overwrites i. label init_levels rem Delay (in frames) of blocks falling and user getting rem his new block. dim level_fall(LEVELS) dim level_next(LEVELS) dim level_destroy(LEVELS) dim level_rows(LEVELS) rem Read levels from data. restore level_data for i = 1 to LEVELS read fall,nextd,dest,rows level_fall(i) = fall level_next(i) = nextd level_destroy(i) = dest level_rows(i) = rows next return rem fall: frames to wait before moving block automatically rem next: frames to wait before giving the user new block rem destr: frames to wait after completing row rem rows: advance to next level after the total number rem of completed rows reaches the given value. The number rem of rows must be set to 0 for the final level. label level_data rem fall next destr rows data 28, 64, 170, 20 data 23, 60, 160, 40 data 18, 56, 150, 60 data 14, 54, 145, 80 data 11, 50, 140, 100 data 9, 46, 135, 125 data 7, 42, 130, 150 data 5, 38, 125, 175 data 4, 34, 120, 200 data 3, 30, 115, 0 rem ###### GAME PLAY ##################################### rem Reset all game variables for a new game. rem BEWARE: overwrites globals at will. label reset_game gosub halt_game rem Reset level. fall_delay = 0 next_delay = level_next(1) level = 1 rem Reset game ports. gosub reset_ports rem Reset score. rows_completed = 0 blocks_frozen = 0 rem Clear all fields. f = next_arena d = EMPTYDOT gosub fill_field f = game_arena d = EMPTYDOT gosub fill_field rem Show high scores. page = 3 rem Reset timeouts and pause. timeouts = INITIAL_TIMEOUTS game_paused = 0 return rem Reset various counters after end-game to freeze the rem situation. label halt_game rem Fix any dots blinking. f = game_arena gosub reset_blinking rem Disable blinking and auto-destruction. blink_state = 1 blink_time = NEVER destroy_time = NEVER blink_destroy_time = NEVER level_change_time = NEVER return rem Freeze the block when it hits the floor. rem BEWARE: overwrites global variables at will. label freeze rem Draw the block without the TMPDOT attribute to make rem the block permanent. f = game_arena t = curtype a = curangle x = curx y = cury d = curdot + BLINKONCE gosub paint_block rem Re-draw the blinking dots as soon as possible. This rem makes the frozen block visible but this is also rem very important when a row becomes full; we do not rem mark the flashing rows dirty here so they will not rem be drawn until we blink the dots for the very rem first time. blink_time = 0 blink_state = 1 destroy_time = NEVER blink_destroy_time = BLINKFREQ rem See if the user completes any rows. j = cury + block_y1(curtype,curangle) k = cury + block_y2(curtype,curangle) if (j < 1) j = 1 if (k > field_height(f)) k = field_height(f) while (j <= k) rem Set BLINKONCE flag for the row. We only deal with rem rows that can be completed by the frozen block, rem and there will be flashing dots in every row. field_row(f,j) = or(field_row(f,j),BLINKONCE) rem Scan row to see if there are any holes. i = 1 full = 1 while (i <= field_width(f)) if (field(f,i,j) = EMPTYDOT) then rem Hole found; the row is not full. full = 0 i = field_width(f) endif i = i + 1 wend rem Initialize the destruction of the row if we found rem it completed. if (full <> 0) then rem Make the row blink. for i = 1 to field_width(f) field(f,i,j)=or(field(f,i,j),BLINK+DESTROY) next i field_row(f,j) = or(field_row(f,j),BLINK+DESTROY) rem Give user a breathing moment after completing rem a row. The destruction time is synchronized rem with the blinking so that the blinking does rem not end in the middle of the phase. Destroying rem the row at the and of the phase looks a bit more rem beautiful. Moreover, destroying the block during rem the flashing period could leave some dots rem highlighted. next_delay = level_destroy(level) + 2 destroy_time=int(next_delay*0.7/BLINKFREQ/2)*BLINKFREQ*2 blink_destroy_time=destroy_time endif j = j + 1 wend rem There is no moving block anymore. curtype = 0 return rem ###### STARS ######################################### rem Initialize data structures for drawing stars. label init_star dim star_x(10) dim star_y(10) rem Radius of the inner and the outer ring of the star. outer = 12 inner = 3 offs = PI + PI/2 rem Compute the location of the star's spikes. The rem number 5 is the number of spikes in a star. i = 1 for a = offs to 2*PI-0.001+offs step 2*PI/5 star_x(i) = cos(a)*outer star_y(i) = sin(a)*outer star_x(i+1) = cos(a+2*PI/(2*5))*inner star_y(i+1) = sin(a+2*PI/(2*5))*inner i = i + 2 next a return rem Draw one star to (x,y). rem BEWARE: overwrites i, _x1, _y1, _x2, _y2, _x3, _y3 label draw_star _x3 = x + star_x(10) _y3 = y + star_y(10) for i = 1 to 10 step 2 _x1 = x + star_x(i) _y1 = y + star_y(i) _x2 = x + star_x(i+1) _y2 = y + star_y(i+1) fill triangle _x1,_y1 to _x2,_y2 to _x3,_y3 fill triangle _x2,_y2 to _x3,_y3 to x,y _x3 = _x2 _y3 = _y2 next i return rem ###### STRINGS ####################################### rem Draw right-aligned text string a$ at (x,y) with rem black highlight. label draw_rstring setrgb 1, 0,0,0 text x+2, y, a$, "rb" text x, y+2, a$, "rb" text x-2, y, a$, "rb" text x, y-2, a$, "rb" setrgb 1, 255,255,255 text x, y, a$, "rb" return rem Draw centered text string a$ at (x,y) with rem black highlight. label draw_cstring setrgb 1, 0,0,0 text x+2, y, a$, "cb" text x, y+2, a$, "cb" text x-2, y, a$, "cb" text x, y-2, a$, "cb" setrgb 1, 255,255,255 text x, y, a$, "cb" return rem ###### HELP, INFO AND DEMO ########################### rem Read help strings from data area to help$ array. rem BEWARE: overwrites a$, k. label init_help dim help$(25) dim info$(25) dim credit$(25) rem Read the instructions. restore help_data read a$ help_strings = 0 while (a$ <> "EOF") help_strings = help_strings + 1 help$(help_strings) = a$ read a$ wend rem Read the welcome page. restore info_data read a$ info_strings = 0 while (a$ <> "EOF") rem Replace "ver$" with the value of the ver variable. k = instr(a$, "ver$") if (k > 0) then a$ = left$(a$,k-1) + ver$ + mid$(a$,k+4) endif rem Save the modified info string. info_strings = info_strings + 1 info$(info_strings) = a$ read a$ wend rem Read the credits screen. restore credit_data read a$ credit_strings = 0 while (a$ <> "EOF") credit_strings = credit_strings + 1 credit$(credit_strings) = a$ read a$ wend return label help_data data "" data "INSTRUCTIONS" data "Your mission is" data "to pack blocks as" data "tightly as you " data "can. If you fill" data "a row, the row" data "will be removed" data "from the game" data "giving you more" data "space. The game" data "ends when a block" data "does not fit the" data "well." data "" data "CONTROLS" data "LEFT & RIFHT - " data "move horizontally" data "" data "DOWN - drop" data "" data "X - rotate" data "" data "START - timeout" data "EOF" label info_data data "" data "WELCOME TO POTRIS" data "" data "Plain Old T*tris" data "Version ver$" data "" data "Copyright" data "Toni Ronkko 2004" data "" data "Press START to" data "begin new game." data "" data "Switch between" data "instructions and" data "high scores" data "using L1 and R1" data "buttons." data "EOF" label credit_data data "" data "CREDITS" data "" data "Programming:" data "Toni Ronkko" data "" data "Testing:" data "Tuukka Ronkko" data "Jari Hyvonen" data "" data "Please see http://" data "softagalleria.net/" data "potris" data "for copyright and" data "updates." data "" data "Greetings to" data "members of yabasic" data "discussion forum" data "at http://pub51." data "ezboard.com/byaba" data "sicprogramming" data "EOF" rem The initial contents of the game arena. label potris_data data "..x..xxx..." data "..x.xx..x.." data "...xx...x.." data "..........." data "..xxxxxxx.." data "..........." data "...xx.xxx.." data "..x..x....." data "..xxxxxxx.." data "..........." data "..x........" data "..xxxxxxx.." data "..x........" data "..........." data "...xxxxx..." data "..x.....x.." data "...xxxxx..." data "..........." data "...xxx....." data "..x..x....." data "..xxxxxxx.." data "EOF" rem ###### HIGH SCORE #################################### rem Initialize high-score tables. rem BEWARE: overwrites i, a$, j, level, rows. label init_score dim score_player$(HIGHSCORES) dim score_lvl(HIGHSCORES) dim score_rows(HIGHSCORES) rem Clear high scores. for i = 1 to HIGHSCORES score_player$(i) = "" score_lvl(i) = -1 score_rows(i) = -1 next i rem The most recent standing in the high score list. rem At the beginning of the game the topmost entry is rem highlighted. last_standing = 1 rem Read initial scores. restore high_score_table read a$ i = 1 while (a$ <> "EOF" and i <= HIGHSCORES) read rows rem Compute level according to player's score. j = 1 level = LEVELS while (j <= LEVELS-1) if (rows < level_rows(j)) then level = j j = LEVELS endif j = j + 1 wend rem Save entry to high-score table. score_player$(i) = a$ score_lvl(i) = level score_rows(i) = rows i = i + 1 read a$ wend return rem Draw high-score table at coordinate (x,y). rem BEWARE: overwrites i. rem BEWARE: modifies coordinates x and y. label draw_scores setrgb 1, TEXTR,TEXTG,TEXTB y = y + 20 text x,y, "HIGH SCORES", "lb" y = y + 40 text x,y, "Player Lvl Rows", "lb" y = y + 20 for i = 1 to HIGHSCORES rem Highlight most recent standing. if (last_standing = i) then setrgb 1, 255,255,0 else setrgb 1, TEXTR,TEXTG,TEXTB endif text x,y, score_player$(i), "lb" if (score_lvl(i) >= 0) then text x+118,y, str$(score_lvl(i)), "rb" endif if (score_rows(i) >= 0) then text x+168,y, str$(score_rows(i)), "rb" endif y = y + 25 next i return rem Insert player a$ into high score table at position rem pos. rem BEWARE: overwrites i. label insert_score rem Make room for the player dropping the dullest player rem from the high score table. if (pos < HIGHSCORES) then for i = HIGHSCORES to pos step -1 score_rows(i) = score_rows(i-1) score_lvl(i) = score_lvl(i-1) score_player$(i) = score_player$(i-1) next i endif rem Insert player. score_lvl(pos) = level score_player$(pos) = a$ score_rows(pos) = rows_completed last_standing = pos return rem ###### KEYBOARD ###################################### rem Let the user modify string text$ using controller and rem a small keyboard. Variables x and y define the rem location of the keyboard. Variable maxlen set the rem maximum length of the text, while variables text_x rem and text_y define the location of the text display. rem Variables text_w and text_h define the length and the rem height of the text display area. rem rem BEWARE: In order to be compatible with the emulator, rem the function flips buffers after every screen update. rem However, the parts of the screen not affected by the rem text input are not redrawn automatically. Hence, it is rem important to ensure that both the front and the back rem buffers contain exactly the same image when entering rem this very function. Otherwise, flickering will rem occur. label input_string rem Discard old input events. gosub reset_ports rem Draw the selected key. key_state = 1 key_blinking = KEYBLINKFREQ rem Select return key by default. keyx = keyboard_retx keyy = keyboard_rety rem Remember the keyboard location. keyboardx = x keyboardy = y rem Set to 1 to exit the loop. done_editing = 0 label xloop rem Delay 1/50th of a second. gosub switch_buffers rem Read game port 1 and save the events. p = 1 gosub read_port gosub save_events rem Read event from the controller. gosub get_event if (event <> 0) then rem Always highlight the current key when the user rem does something. key_blinking = KEYBLINKFREQ key_state = 1 endif rem Read and respond to events. while (event <> 0) if (event = PRESS+RIGHT) then rem Move the cursor right. keyx = keyx + 1 rem If a big key is selected, skip the following rem dummy key. This has to be done before warping rem the cursor so that the warping works OK if a rem big key is located at the right edge. rem if (keyx <= KEYBOARDW) then if (keyboard$(keyx,keyy) = "") keyx = keyx + 1 endif rem Warp to the left edge. if (keyx > KEYBOARDW) keyx = 1 elsif (event = PRESS+LEFT) then keyx = keyx - 1 if (keyx < 1) keyx = KEYBOARDW if (keyboard$(keyx,keyy) = "") keyx = keyx - 1 elsif (event = PRESS+DOWN) then keyy = keyy + 1 if (keyy > KEYBOARDH) keyy = 1 if (keyboard$(keyx,keyy) = "") then keyx = keyx - 1 endif elsif (event = PRESS+UP) then keyy = keyy - 1 if (keyy < 1) keyy = KEYBOARDH if (keyboard$(keyx,keyy) = "") then keyx = keyx - 1 endif elsif (event = PRESS+TRI) then rem Accept text (shorthand for ret key) done_editing = 1 elsif (event = PRESS+SQUARE) then rem Erase character (as in yabasic editor). if (text$ <> "") then text$ = left$(text$,len(text$) - 1) else rem No character to delete! beep endif elsif (event = PRESS+CROSS) then rem Find out the key under the cursor. a$ = keyboard$(keyx,keyy) if (len(a$) = 1) then rem Append plain character. if (len(text$) < maxlen) then text$ = text$ + a$ else beep endif elsif (a$ = "ret") then rem Done editing. done_editing = 1 elsif (a$ = "del") then rem Erase character. if (text$ <> "") then text$ = left$(text$,len(text$) - 1) else rem No more characters to delete! beep endif elsif (a$ = "clr") then if (text$ <> "") then text$ = "" else rem The text has been already cleared! beep endif endif endif gosub get_event wend rem Update the text on screen after each event. x = keyboardx y = keyboardy gosub draw_keyboard gosub draw_keyboard_text rem Make the selected key blink. key_blinking = key_blinking - 50/framespersec if (key_blinking < 0) then key_state = 1-key_state key_blinking = KEYBLINKFREQ endif i = keyx j = keyy gosub draw_key if (done_editing = 0) goto xloop rem Remove highlight from screen. key_state = 0 i = keyx j = keyy gosub draw_key return rem Draw keyboard at (x,y) to both front and back rem buffers. rem BEWARE: overwrites i, j, x1, y1, x2, y2, a$ label draw_keyboard for j = 1 to KEYBOARDH for i = 1 to KEYBOARDW gosub draw_key next i next j return rem Draw key (i,j) of keyboard at (x,y). rem BEWARE: overwrites x1, y1, x2, y2, a$ label draw_key a$ = keyboard$(i,j) if (a$ = "") return rem Compute location of the key in screen coordinates. x1 = x + i*KEYW - KEYW y1 = y + j*KEYH - KEYH if (len(a$) > 1) then x2 = x1 + KEYW*BIGKEYW y2 = y1 + KEYH else x2 = x1 + KEYW y2 = y1 + KEYH endif rem Draw the background of the key. if (keyx <> i or keyy <> j or key_state = 0) then rem The key is not selected although it may be active. setrgb 1, 0,0,80 fill rectangle x1,y1 to x2,y2 else rem The key is selected; draw the background of the rem key. The special keys are painted with different rem background. if (a$ = "ret") then setrgb 1, 120,255,120 elsif (a$ = "clr") then setrgb 1, 255,120,0 elsif (a$ = "del") then setrgb 1, 255,255,0 else setrgb 1, 0,120,255 endif fill rectangle x1,y1 to x2,y2 endif rem Rectangle around the key. setrgb 1, 120,120,120 rectangle x1,y1 to x2,y2 rem Compute the location of the letter inside the rem key. The letters seem to fall a few pixels too far rem rightin the emulator, but the difference is very rem small. x1 = (x1+x2)/2+4 y1 = y2 - 7 rem Draw the label of the key. if (keyx <> i or keyy <> j or key_state = 0) then setrgb 1, 255,255,255 else rem The key is selected. setrgb 1, 0,0,0 endif text x1,y1, a$, "cb" return rem Draw the text string being edited. label draw_keyboard_text x1 = text_x y1 = text_y x2 = text_x + text_w y2 = text_y + text_h setrgb 1, 0,0,255 fill rectangle x1,y1 to x2,y2-1 setrgb 1, 255,255,255 if (len(text$) < maxlen) then rem There is room to insert more chars. text x1+5,y2-8, text$ + "_", "lb" else rem The string cannot be any longer. text x1+5,y2-8, text$, "lb" endif return rem Initialize keyboard. rem BEWARE: overwrites i, j, a$. label init_keyboard rem Array with key descriptions. The keyboard is a two rem dimensional grid where big keys take up two or more rem slots. The slots reserved for the big keys are rem empty, and should be skipped when moving the cursor. dim keyboard$(KEYBOARDW,KEYBOARDH) for j = 1 to KEYBOARDH for i = 1 to KEYBOARDW keyboard$(i,j) = "" next i next j rem Read the keys from the data area. i = 1 j = 1 restore keyboard_data read a$ while (a$ <> "EOF") rem Save keyboard key. keyboard$(i,j) = a$ rem Remember the location of the return key so that rem we can pre-select it. if (a$ = "ret") then keyboard_retx = i keyboard_rety = j endif rem Take the width of the key in account. if (len(a$) > 1) then i = i + BIGKEYW else i = i + 1 endif if (i > KEYBOARDW) then rem Place following keys to next line. j = j + 1 i = 1 endif read a$ wend return label keyboard_data data "A", "B", "C", "D", "E", "F", "G" data "H", "I", "J", "K", "L", "M", "N" data "O", "P", "Q", "R", "S", "T", "U" data "V", "W", "X", "Y", "Z", "0", "1" data "2", "3", "4", "5", "6", "7", "8" data "9", "!", "$", "*", "+", "-", "/" data " ", "del", "clr", "ret" data "EOF" rem ###### GAME PORTS ################################### rem Read button event from port p, and return the event rem in variable event. The event contains the button rem pressed or released. For example, upon return event rem may have been set to PRESS+R1 or RELEASE+SELECT rem meaning that r1 has been pressed or select has been rem released. The subroutine correctly keeps track of rem all the controller buttons so pressing and releasing rem multiple buttons at once works OK. However, you must rem call subroutine save_events after reading ports. rem BEWARE: overwrites variable i. label get_event if (port_first(p) <> port_last(p)) then rem Get button event. event = port_event(p,port_first(p)) rem See whether the button was pressed or released. if (port_press(p,port_first(p)) > 0) then event = event + PRESS else event = event + RELEASE endif rem Mark the event read. port_first(p) = mod(port_first(p)+1,EVENTS) else rem No events in the queue. event = 0 endif return rem Read game port p saving the current status of buttons rem to variables port_cur(p) and port_prev(p). To collect rem button events for processing them later, use rem subroutine save_events too. You can also collect rem combos that way. label read_port port_prev(p) = port_cur(p) port_cur(p) = peek("port1") return rem Save previously read button events for processing rem them later. This is used in conjunction with the rem read_port subroutine that actually reads the game rem port. The save_events subroutine is used to save the rem events for later processing when there is no time to rem process the events just yet. When the time allows, rem the saved events can be retrieved one by one using rem subroutine get_event. The subroutine also saves rem combos and creates synthetic events for repeating rem buttons. rem BEWARE: overwrites variables i and j. rem BEWARE: the subroutine depends on variable time. label save_events rem Create event if button is pressed, released or held rem down long enough. if (port_cur(p) > 0 or port_prev(p) > 0) then rem Check every button. i = 1 j = 1 while (i < BUTTONMASK) if (and(port_prev(p),i) <> and(port_cur(p),i) or (and(port_cur(p),i) > 0 and port_repeat(p,j) < time)) then rem Save event to our FIFO list. if (and(port_cur(p),i) <> 0) then port_press(p,port_last(p)) = 1 else port_press(p,port_last(p)) = 0 endif port_event(p,port_last(p)) = i rem Update combo if this is a button press. (For rem more information on combos, please see rem comments at init_ports subroutine.) Note rem that holding down a button has no effect on rem the combo. if (and(port_cur(p),i) <> 0 and and(port_prev(p),i) = 0) then port_combo(p) = and(port_combo(p)*16 + j-1, COMBOMASK) endif rem Advance output pointer. port_next = mod(port_last(p)+1,EVENTS) if (port_next <> port_first(p)) then rem There is still room in the buffer. port_last(p) = port_next else rem Buffer too small to keep the event. beep endif rem Set time for autorepeat. If the button is rem still pressed after time has passed, we will rem create a synthetic event for the button. if (and(port_prev(p),i) = 0) then rem The button was just pressed. port_repeat(p,j) = time + AUTOREPEATFIRST else rem The button has been held down for some time rem so we can speed up the rate. port_repeat(p,j) = time + AUTOREPEAT endif endif i = i * 2 j = j + 1 wend endif return rem Discard saved button events on all game ports. Note rem that buttons held down during the subroutine call rem will not generate further button press events. That rem is, autorepeat will not work for those buttons. rem However, button release events do get received rem although there will be no corresponding button press rem events. rem BEWARE: overwrites variables i and j (but not p). rem BEWARE: depends on variable time label reset_ports for i = 1 to PORTS if (i = 1) port_cur(i) = peek("port1") if (i > 1) port_cur(i) = peek("port2") port_prev(i) = port_cur(i) port_first(i) = 0 port_last(i) = 0 port_combo(i) = 0 rem Reset auto repeat counters. It is important that rem we set the counters to NEVER; if there are buttons rem depressed at the time of this subroutine call, rem those buttons will be considered for autorepeat rem although we have not seen them pressed. We have rem to set repeat counters to flag the system that rem we have seen them. for j = 1 to BUTTONS port_repeat(i,j) = NEVER next j next i return rem Initialize game ports. rem BEWARE: overwrites variables i and p. label init_ports rem Buttons pressed just now and a short moment ago. dim port_prev(PORTS) dim port_cur(PORTS) rem List of recent buttons presses. Each button press is rem coded with a hexadecimal digit so that 0=SELECT, rem 1=L3, 2=R3, 3=START, 4=UP, 5=RIGHT, 6=DOWN, 7=LEFT, rem 8=L2, 9=R2, a=L1, b=R1, c=TRI, d=CIRC, e=CROSS and rem f=SQUARE. The variable is initially filled with rem zeroes so please do not use SELECT as a part of the rem combo. The button presses are stored so that the rem very latest button press lies on the least rem significant bits. For example, read the variable rem using expression rem if (dec$("cba") = and(port_combo(1),dec("fff"))) rem The expression checks if the user has pressed L1, rem R1 and TRIANGLE in that order. This can be used to rem unlock some secret, for example. The variable holds rem at most six button presses. dim port_combo(PORTS) rem List of recent events in first-in first-out list. dim port_event(PORTS,EVENTS) dim port_press(PORTS,EVENTS) dim port_first(PORTS) dim port_last(PORTS) rem Autorepeat dim port_repeat(PORTS,BUTTONS) rem Initialize ports. gosub reset_ports return REM Introduction REM ============ REM REM Plain Old T*tris (POTRIS) is a Tetris(TM)-like game for PlayStation 2 REM Yabasic. PS2 Yabasic was once distributed with the European REM PlayStation 2 units. Nowadays, you can run Potris and other Yabasic REM games in MS-Windows using the Yabasic emulator. REM REM In order to play Potris, download the latest Yabasic emulator first at REM REM http://members.iinet.net.au/~jimshaw/Yabasic/ps2yabasic1.6b3.zip REM REM Run the emulator and select file open. Open file "potris.txt" and REM press the large start button on the left to run the game. REM REM REM Objective REM ========= REM REM Your objective in Potris is to pack falling blocks as tightly as you REM can into the bottom of the well. If you pack a row completely, that REM row will be removed from the well giving you more space to fit new REM blocks. As the game progresses, the blocks will start falling more REM rapidly giving you less and less time to think your strategy. The REM game ends when a block does not fit into the well. REM REM REM Controls REM ======== REM REM To start playing Potris, press Page Down key in your keyboard REM (emulator only) or the Start button in your Dualshock controller (PS2 REM only). While playing the game, you can temporarily pause the game by REM pressing Page Down (or Start button in the Dualshock controller). REM REM In order to move a block to a suitable position, move the block REM horizontally by the direction buttons. The block will then slowly REM fall to the bottom of the well and freeze when it reaches the bottom. REM A new block will appear for you to position after the previous block REM has freezed at the bottom of the well. If you prefer not to wait for REM the next block, press the down button to accelerate the fall. REM REM To fit a block tightly, it may be necessary to rotate the block. To REM rotate a block, move the block to free area and press the Delete key REM (X button). You can rotate a block repeatedly to fit it. After REM positioning a block you can also rotate the next block before it REM appears in the well. REM REM When outside the game, Control and Caps Lock keys (L1 and R1 buttons) REM change between instructions, credits and high-score listings. REM REM REM High-Score Table REM ================ REM REM If your score makes it into the high-score table, you can type your REM initials using a miniature keyboard that appears at the left side of REM the display. To use this miniature keyboard, press the direction REM buttons to move the cursor over a button and press Delete key (X REM button) to operate the button. Pressing Delete key (X button) over a REM character inserts the corresponding character to the high-score table. REM To delete a character, move the cursor over the del button and press REM Delete key (X button). To clear the initials completely, move the REM cursor over the clr button and press the Delete key (X button). When REM you are satisfied with the initials, move the cursor over the ret REM button and press Delete key (X button). REM REM Note that the high-score table is not automatically saved to disk when REM you exit Potris. In fact, the PS2 Yabasic does not even have a method REM to read or write files in your disk. Thus, the only way to save the REM high-score table is to modify the potris.txt source file. If you want REM to save your score to the high-score table permanently, please read REM the instructions at the top of the potrix.txt file. You can use the REM Emulator to read and modify this file. REM REM REM Updates REM ======= REM REM Please see www.softagalleria.net for update and additional information REM on Potris. REM REM REM Copying Potris REM ============== REM REM Copyright (C) 2004 Toni Ronkko REM REM Permission is hereby granted, free of charge, to any person obtaining REM a copy of this software and associated documentation files (the REM 'Software'), to deal in the Software without restriction, including REM without limitation the rights to use, copy, modify, merge, publish, REM distribute, sublicense, and/or sell copies of the Software, and to REM permit persons to whom the Software is furnished to do so, subject to REM the following conditions: REM REM The above copyright notice and this permission notice shall be REM included in all copies or substantial portions of the Software. REM REM THE SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EXPRESS REM OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND REM NONINFRINGEMENT. IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY REM CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN ACTION OF CONTRACT, TORT REM OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE REM OR THE USE OF OTHER DEALINGS IN THE SOFTWARE. REM REM Tetris (TM) is a registered trademark of Tetris Company LLC. REM