#include #include #include #pragma semicolon 1 //---------------------------------------------------------------------------------------------------------------------- public Plugin:myinfo = { name = "Videogames -> Tetris", author = "mukunda", description = "Tetris!!", version = "1.0.0", url = "www.mukunda.com" }; //---------------------------------------------------------------------------------------------------------------------- // assets #define MODEL_BLOCKCELL "models/videogames/tetris/blockcell.mdl" #define MODEL_CARTRIDGE "models/videogames/tetris/cartridge.mdl" #define MODEL_TETROMINOES "models/videogames/tetris/tetrominoes.mdl" new String:model_list[][] = { MODEL_BLOCKCELL, MODEL_CARTRIDGE, MODEL_TETROMINOES }; new String:downloads[][] = {""}; // fill in for release build! new field_position[2][2]; // position of play fields in pixels on screen new tetris_field[2][12*32]; // data for tetris fields (1=filled,0=cleared) // includes extra space to fill with play border (to ease collision testing!) // actual on-screen field is X 1-10 and Y 11-30 #define FIELD_BASEY 10 #define FIELD_DATAWIDTH 12 #define FIELD_BASEX 1 #define FIELD_DATAHEIGHT 32 #define FIELD_WIDTH 10 #define FIELD_HEIGHT 20 new String:piece_forms[][] = { " # # ", "#### # # ", // I " # #### # ", " # # ", "# ## # ", "### # ### # ", // J " # # ## ", " ", " # # ## ", "### # ### # ", // L " ## # # ", " ", " ## ## ## ## ", " ## ## ## ## ", // O " ", " ", "## # # ", " ## ## ## ## ", // Z " # ## # ", " ", " ## # # ", "## ## ## ## ", // S " # ## # ", " ", " # # # ", "### ## ### ## ", // T " # # # ", " " }; #define FORM_EMPTY ' ' #define FORM_FILLED '#' enum { PIECE_I, PIECE_J, PIECE_L, PIECE_O, PIECE_Z, PIECE_S, PIECE_T }; enum { IG_TEXT_PLAYFIELDS=0, // playfield display 25 x 2 players IG_TEXT_NEXTPIECE=IG_TEXT_PLAYFIELDS+50, // next pieces 3 x 2 players IG_TEXT_HOLD=IG_TEXT_NEXTPIECE+6, // held piece 1 x 2 players IG_TEXT_TIME=IG_TEXT_HOLD+2, // 1p time counter "xxx.xx" (6 ents) IG_TEXT_LINES=IG_TEXT_TIME+6, // 1p line counter "xx" (2 ents) IG_TEXT_PIECE=IG_TEXT_LINES+2 }; // global game state switcher new game_state; enum { STATE_TITLE, STATE_INGAME }; // ingame variables new game_players; new game_gravity; #define FORCE_GRAVITY 180 new piece_type[2]; // current type of piece a player is using new piece_position[2][2]; // internal position of piece on field new actual_piece_position[2][2]; // actual position of piece on screen, this shifts towards internal position quickly new piece_rotation[2]; new piece_lock_time[2]; new player_throw_dir[2]; new player_throw_time[2]; #define THROW_TIME 13 #define LOCK_TIME 30 #define LOCK_TIME_HELD 15 new player_state[2]; new player_timer[2]; // generic tiemr for state stuff #define LOCK_DELAY_NEXT 10 enum { PS_CONTROL, // controlling a piece PS_LOCKED, // a piece was just locked - mainly for a brief delay before next piece PS_LINECLEAR, // a piece was locked and lines are being cleared - slightly more delay than a normal piece locking PS_READY, // ready signal state - maybe have a countdown? PS_DROPPING, // player is dropping a piece PS_LOSE, // the player has topped out PS_WINNING, // the player has won a duel PS_CLEARED // the player has cleared a sprint }; new game_time; Abs( a ) { return a < 0 ? -a : a; } //---------------------------------------------------------------------------------------------------------------------- public OnAllPluginsLoaded() { VG_Register( "tetris", "Tetris" ); } //---------------------------------------------------------------------------------------------------------------------- public OnPluginStart() { } //---------------------------------------------------------------------------------------------------------------------- public OnMapStart() { for( new i = 0; i < sizeof(model_list); i++ ) { PrecacheModel( model_list[i] ); } for( new i = 0; i < sizeof(downloads); i++ ) { AddFileToDownloadsTable( downloads[i] ); } } //---------------------------------------------------------------------------------------------------------------------- public VG_OnEntry() { game_time = 0; VG_SetFramerate( 60.0 ); VG_SetUpdateTime( 100 ); VG_SetBlanking( false ); game_players = 1; game_gravity = 4; game_state = STATE_INGAME; SetupPlayfield(); } //---------------------------------------------------------------------------------------------------------------------- public VG_OnFrame() { game_time++; switch( game_state ) { case STATE_INGAME: { UpdateIngame(); } case STATE_TITLE: { } } } //---------------------------------------------------------------------------------------------------------------------- SetupPlayfield() { if( game_players == 1 ) { field_position[0][0] = 88; field_position[0][1] = 0; VG_Text_SetPositionGrid( IG_TEXT_PLAYFIELDS, 25, 88,0, 5, 16, 32 ); } else { field_position[0][0] = 24; field_position[0][1] = 0; field_position[1][0] = 152; field_position[1][1] = 0; VG_Text_SetPositionGrid( IG_TEXT_PLAYFIELDS, 25, 24 ,0, 5, 16, 32 ); VG_Text_SetPositionGrid( IG_TEXT_PLAYFIELDS+25,25, 152,0, 5, 16, 32 ); } VG_Text_SetModelBatch( IG_TEXT_PLAYFIELDS, 50, MODEL_BLOCKCELL ); VG_Text_SetOnBatch( IG_TEXT_PLAYFIELDS, 50, true ); VG_Text_SetFrameBatch( IG_TEXT_PLAYFIELDS, 50, 0 ); VG_Text_SetSizeBatch( IG_TEXT_PLAYFIELDS, 50, 1 ); VG_Text_SetOffsetBatch( IG_TEXT_PLAYFIELDS, 50, 0 ); VG_Text_SetOffsetParam( 0, 0, 0, 1 ); VG_Text_SetOffsetParam( 1, 0, 0, 2 ); VG_Text_SetOffsetParam( 3, 0, 0, 0 ); SpawnPiece( 0, 0 ); DrawPiece(0); for( new i = 0; i < FIELD_DATAWIDTH*FIELD_DATAHEIGHT; i++ ) { tetris_field[0][i] = 0; tetris_field[1][i] = 0; } // debug: // for( new i = 20*FIELD_DATAWIDTH; i < FIELD_DATAWIDTH*FIELD_DATAHEIGHT; i++ ) { // tetris_field[0][i] = GetRandomInt(0,1); // tetris_field[1][i] = GetRandomInt(0,1); // } for( new i = 0; i < FIELD_DATAHEIGHT; i++ ) { for( new p = 0; p < 2; p++ ) { tetris_field[p][FIELD_BASEX-1+i*FIELD_DATAWIDTH] = 1; tetris_field[p][FIELD_BASEX+FIELD_WIDTH+i*FIELD_DATAWIDTH] = 1; } } for( new i = 0; i < FIELD_DATAWIDTH; i++ ) { for( new p = 0; p < 2; p++ ) { tetris_field[p][(FIELD_BASEY+FIELD_HEIGHT)*FIELD_DATAWIDTH+i] = 1; } } UpdatePlayfield(0); } //---------------------------------------------------------------------------------------------------------------------- UpdatePlayfield( player ) { for( new i = 0; i < 25; i++ ) { new y = i / 5; new x = i - y*5; new offset = FIELD_BASEX + x * 2 + (FIELD_BASEY+y*4)*FIELD_DATAWIDTH; new index = (tetris_field[player][offset+0])+ (tetris_field[player][offset+1]<<1)+ (tetris_field[player][offset+FIELD_DATAWIDTH]<<2)+ (tetris_field[player][offset+FIELD_DATAWIDTH+1]<<3)+ (tetris_field[player][offset+FIELD_DATAWIDTH*2]<<4)+ (tetris_field[player][offset+FIELD_DATAWIDTH*2+1]<<5)+ (tetris_field[player][offset+FIELD_DATAWIDTH*3]<<6)+ (tetris_field[player][offset+FIELD_DATAWIDTH*3+1]<<7); VG_Text_SetFrame( IG_TEXT_PLAYFIELDS + player*25 + i, index ); } } //---------------------------------------------------------------------------------------------------------------------- DrawPiece( player ) { new index = IG_TEXT_PIECE+player; if( player_state[player] == PS_CONTROL ) { VG_Text_SetPosition( index, field_position[player][0] + ((actual_piece_position[player][0]+128)>>8), field_position[player][1] + ((actual_piece_position[player][1]+128)>>8) ); VG_Text_SetFrame( index, piece_type[player] * 4 + piece_rotation[player] ); VG_Text_SetOn( index, true ); VG_Text_SetSize( index, 1 ); VG_Text_SetModel( index, MODEL_TETROMINOES ); VG_Text_SetOffset( index, 1 ); } else { VG_Text_SetOffset( index, 3 ); } } //---------------------------------------------------------------------------------------------------------------------- SpawnPiece( player, type ) { piece_type[player] = type; piece_rotation[player] = 1; piece_position[player][0] = 3; piece_position[player][1] = -1*256; actual_piece_position[player][0] = piece_position[player][0] * 8*256; actual_piece_position[player][1] = piece_position[player][1] * 8; } bool:PieceCollision( player, offsetx=0,offsety=0 ) { return PieceCollisionEx( player, piece_type[player], piece_rotation[player], piece_position[player][0]+offsetx, ((piece_position[player][1]+255+offsety)>>8) ); } bool:PieceCollisionEx( player, type, rot, px, py ) { new field_offset = FIELD_BASEX + px + (FIELD_BASEY + py)*FIELD_DATAWIDTH; new form_offset = rot*4; new form_slice = type*(4*4*4); for( new y = 0; y < 4; y++ ) { for( new x = 0; x < 4; x++ ) { if( piece_forms[form_slice+y][form_offset+x] == FORM_EMPTY ) continue; if( tetris_field[player][field_offset+x+y*FIELD_DATAWIDTH ] ) { // a collision occurred. // return true; } } } return false; } //---------------------------------------------------------------------------------------------------------------------- SlidePiece( player, direction ) { if( direction == 0 ) return; // direction must be -1 or +1 // collision test: //new field_offset = FIELD_BASEX + piece_position[player][0] + (FIELD_BASEY + ((piece_position[player][1]+255)>>8))*FIELD_DATAWIDTH; //new form_offset = piece_rotation[player]*4; //new form_slice = piece_type[player]*(4*4*4); if( PieceCollision( player, direction ) ) { // return; } /* for( new y = 0; y < 4; y++ ) { for( new x = 0; x < 4; x++ ) { if( piece_forms[form_slice+y][form_offset+x] == FORM_EMPTY ) continue; if( tetris_field[player][field_offset+x+y*FIELD_DATAWIDTH +direction] ) { // a collision occurred. // return; } } }*/ piece_position[player][0] += direction; } //---------------------------------------------------------------------------------------------------------------------- ThrowPiece( player, direction ) { if( direction == 0 ) return; // direction must be -1 or +1 // collision test: new field_offset = FIELD_BASEX + piece_position[player][0] + (FIELD_BASEY + ((piece_position[player][1]+255)>>8))*FIELD_DATAWIDTH; new form_offset = piece_rotation[player]*4; new form_slice = piece_type[player]*(4*4*4); new shortest_stride = FIELD_WIDTH; for( new y = 0; y < 4; y++ ) { for( new x = 0; x < 4; x++ ) { if( piece_forms[form_slice+y][form_offset+x] == FORM_EMPTY ) continue; // get stride length new offset2 = field_offset+x+y*FIELD_DATAWIDTH; offset2 += direction; new stride = 0; while( !tetris_field[player][offset2] ) { stride++; offset2 += direction; } if( stride == 0 ) { // piece is touching something already // return; } if( stride < shortest_stride ) { shortest_stride = stride; } } } if( shortest_stride > 3 ) shortest_stride = 3; if( direction < 0 ) { piece_position[player][0] -= shortest_stride; } else { piece_position[player][0] += shortest_stride; } } //---------------------------------------------------------------------------------------------------------------------- DropPiece( player ) { } //---------------------------------------------------------------------------------------------------------------------- RotatePiece( player, direction ) { new newrot = (piece_rotation[player] + direction) & 3; // todo // should also slide piece if rotating against a wall and there is space next to it // } //---------------------------------------------------------------------------------------------------------------------- BakePieceEx( player, type, rot, px, py ) { new field_offset = FIELD_BASEX + px + (FIELD_BASEY + py)*FIELD_DATAWIDTH; new form_offset = rot*4; new form_slice = type*(4*4*4); for( new y = 0; y < 4; y++ ) { for( new x = 0; x < 4; x++ ) { if( piece_forms[form_slice+y][form_offset+x] == FORM_EMPTY ) continue; tetris_field[player][field_offset+x+y*FIELD_DATAWIDTH ] = 1; } } } //---------------------------------------------------------------------------------------------------------------------- BakePiece( player ) { BakePieceEx( player, piece_type[player], piece_rotation[player], piece_position[player][0], ((piece_position[player][1]+255)>>8) ); } //---------------------------------------------------------------------------------------------------------------------- PP_Control( player ) { new gravity = game_gravity; if( VG_Joypad_Held( player+1, VG_INPUT_DOWN ) ) { gravity = FORCE_GRAVITY; } else if( VG_Joypad_Clicks( player+1, VG_INPUT_UP_INDEX ) ) { DropPiece( player ); } piece_position[player][1] += gravity; if( PieceCollision( player, 0,1 ) ) { piece_position[player][1] &= ~0xFF; // fixed point truncate piece_lock_time[player]++; } else { piece_lock_time[player] = 0; } if( piece_lock_time[player] >= (VG_Joypad_Held( player+1, VG_INPUT_DOWN ) ? LOCK_TIME_HELD : LOCK_TIME) ) { // LOCK PIECE piece_lock_time[player] = 0; BakePiece( player ); UpdatePlayfield(player); player_state[player] = PS_LOCKED; player_timer[player] = 0; DrawPiece(player); return; } if( VG_Joypad_Clicks( player+1, VG_INPUT_LEFT_INDEX ) ) { SlidePiece(player,-1); player_throw_dir[player] = -1; player_throw_time[player] = game_time; } else if( VG_Joypad_Clicks( player+1, VG_INPUT_RIGHT_INDEX ) ) { SlidePiece(player,1); player_throw_dir[player] = 1; player_throw_time[player] = game_time; } new throw_key = player_throw_dir[player] == 1 ? VG_INPUT_RIGHT : VG_INPUT_LEFT; if( !VG_Joypad_Held( player+1, throw_key ) ) { player_throw_time[player] = game_time; } else { if( game_time >= player_throw_time[player] + THROW_TIME ) { ThrowPiece( player, player_throw_dir[player] ); } } if( VG_Joypad_Clicks( player+1, VG_INPUT_JUMP_INDEX ) ) { RotatePiece( player, 1 ); } new desired_x = (piece_position[player][0]<<11); new diff = desired_x - actual_piece_position[player][0]; if( diff != 0 ) { if( Abs( diff ) < 300 ) { actual_piece_position[player][0] = desired_x; } else { actual_piece_position[player][0] = (actual_piece_position[player][0]*4 + desired_x*12) >> 4; } } new desired_y = (piece_position[player][1]<<3); diff = desired_y - actual_piece_position[player][1]; if( diff != 0 ) { if( Abs( diff) < 300 ) { actual_piece_position[player][1] = desired_y; } else { actual_piece_position[player][1] = (actual_piece_position[player][1]*6 + desired_y*10) >> 4; } } DrawPiece(player); } //---------------------------------------------------------------------------------------------------------------------- PP_Locked(player) { player_timer[player]++; if( player_timer[player] >= LOCK_DELAY_NEXT ) { SpawnPiece(player, 0); player_state[player] = PS_CONTROL; } } //---------------------------------------------------------------------------------------------------------------------- ProcessPlayer( player ) { switch( player_state[player] ) { case PS_CONTROL: { PP_Control(player); } case PS_LOCKED: { PP_Locked(player); } } } //---------------------------------------------------------------------------------------------------------------------- UpdateIngame() { ProcessPlayer(0); }