tetris.hcc


// Spring 2006: Tetris Project
// by David Edell

#define PAL_TARGET_CLOCK_RATE PAL_PREFERRED_VIDEO_CLOCK_RATE
#include "pal_master.hch"
#include "pxs.hch"
#include <stdlib.hch>


// Game Constants and Settings
    // Grid Position
    macro expr Xstart    = 192; // Start of Board.  Compile-Time Constant
    macro expr Ystart    = 96; // Should be defined to center board in screen at current resolution

    // Active Block Position (the moving block)
    macro expr BlockXstart = 292; // Start of Block (needs to be aligned with game grid co-ord Row0,Col8)
    macro expr BlockYstart = 96;

    // Next Block Position
    macro expr BlockNextX = 75; // Preview Location of next Block
    macro expr BlockNextY = 75;

    // Bonus/Score Values (not yet implemented)
    macro expr ClearFourBonus = 32;
    macro expr ClearThreeBonus = 16;
    macro expr LineClearScore = 8;
    macro expr LevelBonusScore = 4; // Level Number * Bonus Points * lines cleared
    macro expr BlockAddScore = 2;

    // Game Speed Constants
    macro expr GameInterval = 5; // Level-1 Speed.  Resets at level 5, with extra modifier added

/*
 * Forward declarations
 */
static macro proc RunConsole (ConsolePtr);
macro proc RunGame(VGASync, Console, Block_X, Block_Y, activeBlock, nextBlock);
macro proc PxsCustom_Rect( In, Out, X0, Y0, X1, Y1, Color1);
macro proc PxsCust_SevenSeg( In, Out, X, Y, type, Color1, H, W);
macro proc PxsCust_TetBlock( In, Out, X, Y, block, Color1);
macro proc PxsCust_TetGrid( In, Out, Xstart, Ystart, TetrisGrid, color );
macro proc sleep(msec);
macro proc RunUI(mode);
macro proc RunBlockMovement(mode,VGASync,randType);
macro proc TryHorMove(row, col, direction, Block_X,activeBlock);
macro proc TryVertMovement(row,col,rowsClear,resetBlock, activeBlock);
macro proc UpdateGameAndScore(rowsCleared);
static macro expr TetrisBlocks;

// UI
macro expr TouchScreen = PalTouchScreenCT (0);
macro expr VideoIn  = PalVideoInCT (0);

/*
 * Mode macros
 */
macro expr ClockRate = PAL_ACTUAL_CLOCK_RATE;
macro expr Mode      = SyncGen2GetOptimalModeCT   (ClockRate);
macro expr Width     = SyncGen2GetHActivePixelsCT (Mode);
macro expr Height    = SyncGen2GetVActiveLinesCT  (Mode);


macro expr defRow = 0b1110000000000111; // Default Row Values: Edges must be set = 1
macro expr bufRow = 0b1111111111111111; // Filled Row: For Buffer Zone on bottom

/* The game Grid: We initialize an MPROM to use as the game grid.  The grid is accessed
 *   as a RAM in the game's main methods where the contents are read and modified to
 *   compute the game actions.  A ROM is used within the custom PixelStreams filter for
 *   displaying the contents of the game board.  The grid is defined globally in this 
 *   program as it is accessed throughout the program, and MPROMs cannot be easily passed
 *   as parameters.
 */
    mpram TheGrid {
      ram unsigned 16 game[24];
      rom unsigned 16 disp[24];
    } TetrisGrid = {defRow, defRow, defRow, defRow, defRow, defRow, defRow, defRow,
              defRow, defRow, defRow, defRow, defRow, defRow, defRow, defRow,       // DEBUG
              defRow, defRow, defRow, defRow, defRow, bufRow, bufRow, bufRow,
      }; // Initialize all 24 Rows with this value


// Get current Col in grid based on coordinate and pre-defined game constants
macro expr getRow(X,Y) = ((unsigned)(Y - Ystart)) >> 4;


// Get current Row in grid based on coordinate and pre-defined game constants
macro expr getCol(X,Y) = ((unsigned)(X - Xstart)) >> 4;

    // Console Handle: - moved here for debug (to allow extra outptu statements), but it should be in main
    PxsConsoleHandle Console;

void main (void)
{

    unsigned 16 NBlock_X, NBlock_Y; // These 2 can be replaced with constants
    unsigned 16 Block_X, Block_Y;
    unsigned 16 activeBlock;
    unsigned 16 nextBlock;

    /*
     * Streams
     */
    PXS_PV_S (VGASync,     PXS_EMPTY);
    PXS_PV_S (VGASync1,    PXS_EMPTY);
    PXS_PV_S (VGASync2,    PXS_EMPTY);
    PXS_PV_S (XOR,         PXS_RGB_U8);
    PXS_PV_S (Checker,     PXS_RGB_U8);
    PXS_PV_S (Background,  PXS_RGB_U8);
    PXS_PV_S (Background2, PXS_RGB_U8);
    PXS_PV_S (Out,         PXS_RGB_U8);
    PXS_PV_S (End,         PXS_RGB_U8);
    PXS_PV_S (Test,        PXS_RGB_U8);
    PXS_PV_S (Tetris,      PXS_RGB_U8);
    PXS_PV_S (Next,        PXS_RGB_U8);
    


    PalTouchScreenRequire (1);
    PalVideoInRequire  (1);
    PalVideoInRequire  (1);

    par
    {
        /*
         * Network of filters
         */
        PxsVGASyncGen       (&VGASync, Mode); 
        PxsSplit2           (&VGASync, &VGASync1, &VGASync2);
        PxsCheckerboard     ( &VGASync2, &Checker, 4);
        PxsXorPattern       (&VGASync1, &XOR);
        PxsScalarRightShift (&XOR, &Background, 2);       // What is the purpose of the shift?
                                  // Scalar Shift Appears to Significantly Reduce Image Brightness
                                  // Without this line, the XorPattern is to bright to be used as a background image
                                  // Divides all Pixel Values by 2^2=4, reducing intensity
        PxsScalarRightShift (&Checker, &Background2, 1);
        PxsAverage          ( &Background, &Background2, &Out );

        PxsConsole          (&Out, &End, &Console, Width, Height);
        PxsCust_TetBlock    (&End, &Next, Block_X, Block_Y, activeBlock, 1); // X, Y, type, rot, Color1, size
        PxsCust_TetBlock    (&Next, &Tetris, NBlock_X, NBlock_Y, nextBlock, 1); // Display next block to drop
        PxsCust_TetGrid     (&Tetris, &Test, Xstart, Ystart, TetrisGrid, 1);
        PxsVGAOut           (&Test, 0, 0, ClockRate);

        PalTouchScreenRun (TouchScreen, ClockRate);
        PalVideoInRun (VideoIn, ClockRate);

      seq {
        PalVideoInEnable (VideoIn);
        RunGame(VGASync, Console, Block_X, Block_Y, activeBlock, nextBlock);
      }
   }
}

/* RunGame: The main method of the game.  This method is divided into three segments.  The first section
 *  increments a counter variable and serves as our random number generator in creating new blocks.  The 
 *  second section provides for the User Interface controls and (will) display various game labels, status,
 *  and scores.  The third section provides the majority of the game's logic and drives the game.  A mode
 *  variable (described below) is the primary means of communication between these two methods.  Because
 *  both sections must run continuously, updating the status using other means (such as channels) is not as
 *  practical.  An additional 'turbo' variable was added that disabled the VGASync, allowing for blocks to
 *  quickly drop down.
 */
macro proc RunGame(VGASync, Console, Block_X, Block_Y, activeBlock, nextBlock) {
  unsigned 3 randType; // Random Block Generator
  unsigned 3 mode; // Current Game Status
  unsigned 3 type, nextType; // Type of current and next block
  unsigned 2 rot; // Rotation Status of current block (default=0)
  unsigned 1 turbo;
  unsigned 32 score;
  
  static rom unsigned 16 tetris_blocks[4 * 7] = TetrisBlocks with {block=1};
  
  // Game Status Modes: (Set to 100 after left/right movement completes or game resets)
    // 000 - No Game in Progress
    // 001 - Game Over
    // 010 - Game Paused
    // 011 - New Game: Set Next block, reset grid, Activate Next Block and generate new next, Reset Status to 100
    // 100 - Game Active, No Horizontal Movement
    // 101 - -Game Active, "Drop Piece" (piece drops in rapid time period)
              // Drop/Turbo Mode simply means we skip the VGASync
             // This mode now re-defined as rotate
             // turbo Changed to variable turbo
    // 110 - Game Active, Move to Left
    // 111 - Game active, Move to Right

  par {
  
    // *** Random Number Generator ***
    while(1) {
      if (randType > 5) { // Keep it within the Random-7
        randType = 0;
      } else {
        randType++;
      }
    }

    //*** RunUI(mode, type, rot, tetris_blocks); ***
    seq {
        unsigned 1 Button0, Button1;
        unsigned Touch;
        unsigned tX, tY;

        // Display Identifiers
        static ram unsigned char Rotate[6]     = "Rotate";
        static ram unsigned char MoveLeft[9]   = "Move Left";
        static ram unsigned char MoveRight[10] = "Move Right";
        static ram unsigned char GameOver[11] = "Game Over  ";
        static ram unsigned char GamePaused[11] = "Game Paused";
        static ram unsigned char GameSolo[11] = "Solo Game  ";

        mode = 0b011; // Start New Game immediately
    
        while(1) { // Process User Input and Set/Reset game Modes:
              // The following input actions to be defined:
                // Move Left
                // Move Right
                // Drop
                // Pause Game
                // Start/End Game

              PalSwitchRead (PalSwitchCT (0), &Button0);
              PalSwitchRead (PalSwitchCT (1), &Button1);
              PalTouchScreenReadScaled (TouchScreen, &tX, &tY, &Touch);
              
    
              if (Button0 && Button1) { // New Game
                mode = 0b011;
              } else if (Button0) { // Turbo Button
                turbo = 1;
              } else if (Button1) { // Pause/Resume
                if (mode == 0b010) {
                  mode = 0b100;
                } else if (mode[2]) {
                  mode = 0b010;
                }
              }

              if (Touch) {
                sleep(5);
                PalTouchScreenReadScaled (TouchScreen, &tX, &tY, &Touch); // And again to be sure
  
                // Rotate Button to Rotate
                if (tY < 300) {
                  mode = 0b101;
                  //rot++;
                  //activeBlock = tetris_blocks[type@rot]; 
                      // Conflict only if executed at exact time as nextBlock is computed.
                      // Odds are low, and worst case might just give us an unusual block configuration
                      // Acceptable: Call this a "cheat" :-)
                      // Alternative: Would need to signal game thread to update
                } else if (tY > 320 && tX < 288) {
                  mode = 0b110;
                } else if (tY > 320 && tX > 288) {
                  mode = 0b111;
                }
                // Bottom and Left of Board to Move Left
                // Bottom and Right of Board to Move Right
               // if tX,tY is at Rotate button, then:
                // Rotate Piece:
              }

            // Update Interface (GUI):
            PxsConsoleSetCursor( &Console, 0); // Turn Cursor Off?

            PxsConsoleMoveCursor( &Console, 60, 9); 
            PxsConsolePutString( &Console, Rotate);
 
            PxsConsoleMoveCursor( &Console, 10, 20); 
            PxsConsolePutString( &Console, MoveLeft);

            PxsConsoleMoveCursor( &Console, 60, 20); 
            PxsConsolePutString( &Console, MoveRight);

            // Add Display for Score and Game Status: In Progress, Paused, No Game Active, Game Over
            if (mode == 0b001) { // Game Over
              PxsConsoleMoveCursor( &Console, 35, 2); 
              PxsConsolePutString( &Console, GameOver);
            } else if (mode == 0b010) { // Game Paused
              PxsConsoleMoveCursor( &Console, 35, 2); 
              PxsConsolePutString( &Console, GamePaused);
            } else {
              PxsConsoleMoveCursor( &Console, 35, 2); 
              PxsConsolePutString( &Console, GameSolo);
            }

              PxsConsoleMoveCursor( &Console, 40, 4); 
              PxsConsolePutUInt32( &Console, score);              


          sleep(100);
        }
    }

    // Block Movement: The main part of the game
    //RunBlockMovement(mode,VGASync,randType, type, rot, nextType, Block_X, Block_Y, activeBlock, nextBlock);
    seq {
      unsigned 3 cycles, interval;
      unsigned 5 col;
      unsigned 5 row; // Variables Used for Block Movement - Initialized at start of game
      unsigned 4 rowsCleared; // When it reaches MAX, increase level
      unsigned 2 addCount; // This and interval together define a level
      
      while(1) {
        par {
          row = getRow(Block_X,Block_Y) <- 5;
          col = getCol(Block_X,Block_Y) <- 5;
        }

        if (mode == 0b011) { // New Game
          unsigned 5 temp;
         par {
          // Reset Score 
            // Score not yet implemented
          // Reset Block Location
            Block_X = BlockXstart; // Defined by macro
            Block_Y = BlockYstart;
            interval = GameInterval;
            addCount = 1;
            mode = 0b100; // Set mode to 100

          // Generate new Next and Active Blocks
            type = randType;
            nextType = randType+2; // Simplest way of initializing new game since we only have 1 random number
            rot = 0;
         } // remaining parts can't be done in par
            activeBlock = tetris_blocks[type@0];
            nextBlock = tetris_blocks[nextType@0]; // Psuedo-Random first next block
          // Reset the game grid
            temp = 0;
            while (temp <= 20) { par { // Reset all visible rows to default
              TetrisGrid.game[temp] = defRow;
              temp++;
            } }
        }

        // If Game is paused, do nothing (delay)
        if (!mode[2]) { // Game is paused unless mode is 1xx
          delay;
        } else {
          // Advance Position
          cycles--;
          if (cycles == 0) { // Move Block Down to next row
            unsigned 5 temp;

            par {
              Block_Y += 0@addCount; // Advance Y position
              cycles = interval; // Reset cycles
              temp = row; // Save current row #
            }
            row = getRow(Block_X,Block_Y) <- 5;// Re-Calculate the Current Row

            
            // Check if We have reached a new level:
            if (temp != row) { // we have Advanced to the next row
              // Check if We can still continue moving, or if we have reached the end
                unsigned 3 rowsClear;
                unsigned 1 resetBlock;
                TryVertMovement(row,col,rowsClear, resetBlock, activeBlock);
                // Does nothing if Movement Succeeds, otherwise adds to grid
           
                if (resetBlock) {
                  // Check for Lines Cleared and Update Score and Game*** 
                  par {
                    turbo = 0; // Disable Turbo for next block
                    activeBlock = nextBlock;
                    nextType = randType;
                    type=nextType;
                    nextBlock = tetris_blocks[randType@0]; // Set new nextBlock
                    Block_X = BlockXstart;
                    Block_Y = BlockYstart;


                    /*** ADD SCORE UPDATE CODE HERE ***/
                    rowsCleared += 0@rowsClear;
                    score += BlockAddScore; // Add minimum score for having a block drop.

                    // Check for Level Advancement: [If rowsCleared variable resets
                    if (rowsCleared > 4 && rowsCleared+0@rowsClear < 4) {
                      interval--;
                      if (interval == 0) { // Reset interval and increase addCount
                         interval = GameInterval;
                         addCount++;
                      }
                    }

                    if (rowsClear == 0) {
                      score += BlockAddScore;
                    } else if (rowsClear == 1) {
                      score += BlockAddScore + LineClearScore;
                    } else if (rowsClear == 2) {
                      score += BlockAddScore + LineClearScore + LineClearScore;
                    } else if (rowsClear == 3) {
                        score += BlockAddScore + LineClearScore + ClearThreeBonus;
                    } else if (rowsClear == 4) {
                        score += BlockAddScore + ClearFourBonus;
                    }

                    // Check for game-over condition
                    if (col < 3) {
                      mode = 0b001; // And Set Game Mode Appropriately
                    }
                  } // End par
                } // End if
                
            }
          }

          if(mode == 0b110 || mode == 0b111) { // Move In Specified Direction
            TryHorMove(row,col,mode[0:0],Block_X,activeBlock);
            mode = 0b100;
            // Movement is atomic (although we will delay a few cycles in input thread before/after setting flag)
            // Resets Status to 100
            // If movement is possible, does it, otherwise does nothing
          } else if (mode == 0b101) { // Try to Rotate
            // Before Committing to the rotation, we must be sure that rotation is in a valid location

            unsigned gridRow0, gridRow1, gridRow2, gridRow3;
            unsigned row0, row1, row2, row3;
            unsigned 16 rotatedBlock;
            rotatedBlock = tetris_blocks[type@(rot+1)];
              // Convert Each row in activeBlock to row to compare against GridRow
              row3 = (rotatedBlock[3:0]@(0b000000000000)) >> col;
              row2 = (rotatedBlock[7:4]@(0b000000000000)) >> col;
              row1 = (rotatedBlock[11:8]@(0b000000000000)) >> col;
              row0 = (rotatedBlock[15:12]@(0b000000000000)) >> col;

              // Only one row can be accessed at a time, so this part can't be in par
              gridRow0 = TetrisGrid.game[row+0];
              gridRow1 = TetrisGrid.game[row+1];
              gridRow2 = TetrisGrid.game[row+2];
              gridRow3 = TetrisGrid.game[row+3];

            // Check if block can be added at row+1 (if & is non-zero, then we can't move it)
            if (   (row0 & gridRow0) != 0
                || (row1 & gridRow1) != 0 
                || (row2 & gridRow2) != 0 
                || (row3 & gridRow3) != 0 ) {
                // Rotation disallowed
                mode = 0b100;
            } else {
              activeBlock = rotatedBlock;
              rot++;
              mode = 0b100;
            }
          }
        }

        if (!turbo) { // In turbo mode, we skip the VGASync
          PxsAwaitVSync (&VGASync); // DEBUG - disable this line for debugging in simulation
        }
      }
    }
  }
}

/***  Try Vertical Movement: This method is called when a block advances to the next row. 
 *  The blocks position si checked against the game grid.  If the block cannot move down 
 *  any further, then the block is saved to its current position and the block is then reset.
 *  This method will also check if any rows are cleared, and if so remove them, shifting all
 *  subsequent rows down.
 ***/
// rowsClear = signed 3 int (-1 is no change, 0+ is # of rows cleared): -1 replaced by resetBlock flag
macro proc TryVertMovement(row, col, rowsClear, resetBlock, activeBlock) {
  // Accessible Variables: TetrisGrid.game[], activeBlock
  unsigned 2 numClear;
  unsigned gridRow0, gridRow1, gridRow2, gridRow3;
  unsigned row0, row1, row2, row3;

    // Convert Each row in activeBlock to row to compare against GridRow
    row3 = (activeBlock[3:0]@(0b000000000000)) >> col;
    row2 = (activeBlock[7:4]@(0b000000000000)) >> col;
    row1 = (activeBlock[11:8]@(0b000000000000)) >> col;
    row0 = (activeBlock[15:12]@(0b000000000000)) >> col;

    // Only one row can be accessed at a time, so this part can't be in par
    gridRow0 = TetrisGrid.game[row+1+0];
    gridRow1 = TetrisGrid.game[row+1+1];
    gridRow2 = TetrisGrid.game[row+1+2];
    gridRow3 = TetrisGrid.game[row+1+3];


  // Check if block can be added at row+1 (if & is non-zero, then we can't move it)
  if (   (row0 & gridRow0) != 0
      || (row1 & gridRow1) != 0 
      || (row2 & gridRow2) != 0 
      || (row3 & gridRow3) != 0 ) {
      // row+1 is occupied, so save block to location row
      unsigned i;
      resetBlock = 1;
      
      
      // Get Row values:
      gridRow0 = TetrisGrid.game[row+0];
      gridRow1 = TetrisGrid.game[row+1];
      gridRow2 = TetrisGrid.game[row+2];
      gridRow3 = TetrisGrid.game[row+3];

      // gridrow = row | gridrow
      TetrisGrid.game[row+0] = (gridRow0 | row0);
      TetrisGrid.game[row+1] = (gridRow1 | row1);
      TetrisGrid.game[row+2] = (gridRow2 | row2);
      TetrisGrid.game[row+3] = (gridRow3 | row3);

      // Check for Lines Cleared
      rowsClear = 0; // Block has been saved

      gridRow1 = TetrisGrid.game[row+0];
      if (gridRow1 == bufRow && row+0 <= 20) {
        i = row+0;
        rowsClear++;
        while(i > 2) {
          gridRow0 = TetrisGrid.game[i-1];
          TetrisGrid.game[i] = gridRow0;
          i--;
        }
        TetrisGrid.game[i] = defRow;
      }

      gridRow1 = TetrisGrid.game[row+1];
      if (gridRow1 == bufRow && row+1 <= 20) {
        i = row+1;
        rowsClear++;
        while(i > 2) {
          gridRow0 = TetrisGrid.game[i-1];
          TetrisGrid.game[i] = gridRow0;
          i--;
        }
        TetrisGrid.game[i] = defRow;
      }

      gridRow1 = TetrisGrid.game[row+2];
      if (gridRow1 == bufRow && row+2 <= 20) {
        i = row+2;
        rowsClear++;
        while(i > 2) {
          gridRow0 = TetrisGrid.game[i-1];
          TetrisGrid.game[i] = gridRow0;
          i--;
        }
        TetrisGrid.game[i] = defRow;
      }

      gridRow1 = TetrisGrid.game[row+3];
      if (gridRow1 == bufRow && row+3 <= 20) {
        i = row+3;
        rowsClear++;
        while(i > 2) {
          gridRow0 = TetrisGrid.game[i-1];
          TetrisGrid.game[i] = gridRow0;
          i--;
        }
        TetrisGrid.game[i] = defRow;
      }

  } // else If clear, we allow movement and do nothing
  else {
    resetBlock=0;// Indicate no success
  }
}

/*** Try Horizontal Movement:
 * This method tries horizontal movement in the specified direction.  If movement in that
 *  direction is not blocked by the game's border or another saved block, then the block
 *  will immediately be moved.  Otherwis, no action is taken.
 */
// direction: 0=left, 1=right
macro proc TryHorMove(row, col, direction, Block_X, activeBlock) {
      unsigned gridRow0, gridRow1, gridRow2, gridRow3;
      unsigned row0, row1, row2, row3;

    if ( (direction == 0 && row < 1) || (direction == 1 && row > 15) ) {
      // At Edge of Board, do nothing
      //return;
      delay;
    } else {
      
      // Compute new Col
      if (direction == 0) {
        col--;
      } else {
        col++;
      }
    
      // Convert Each row in activeBlock to row to compare against GridRow
    par {
      row0 = (activeBlock[3:0]@(0b000000000000)) >> col;
      row1 = (activeBlock[7:4]@(0b000000000000)) >> col;
      row2 = (activeBlock[11:8]@(0b000000000000)) >> col;
      row3 = (activeBlock[15:12]@(0b000000000000)) >> col;
    }

      // Only one row can be accessed at a time, so this part can't be in par
      gridRow0 = TetrisGrid.game[row+0]; // We don't need to check row+1, since its moving atomically
      gridRow1 = TetrisGrid.game[row+1]; // If we have moved partially beyond the row, row should have increased
      gridRow2 = TetrisGrid.game[row+2]; 
      gridRow3 = TetrisGrid.game[row+3];

      // Check if Move is Valid (condition was originally negation of what's below)
      if (   (row0 & gridRow0) == 0
          && (row1 & gridRow1) == 0 
          && (row2 & gridRow2) == 0 
          && (row3 & gridRow3) == 0 ) {
        // Move Block if ti is- Use loop and delay to animate
        Block_X = (direction ? (Block_X+16) : (Block_X-16)); // CONFIRM THIS WORKS: DEBUG***
      } else {
        delay;
      }

    }
}

// Placeholder function
macro proc UpdateGameAndScore(temp) {
        activeBlock = nextBlock;
        nextBlock = tetris_blocks[randType@0]; // Set new nextBlock
        Block_X = BlockXstart;
        Block_Y = BlockYstart;
/* Not yet implemented
        // Update the Score 
        gameScore += temp * LineClearScore + BlockAddScore + level * LevelBonusScore * temp;
        if (temp == 3) { // 3-Line Bonus Score
          gameScore += ClearThreeBonus;
        } else if (temp == 4) { // 4-Line Bonus score
          gameScore += ClearFourBonus;
        }
*/
}

/*********************************** Filters and Utilities **********************************/
macro proc sleep(msec) { // Define sleep() macro function
  macro expr ticks = (PAL_ACTUAL_CLOCK_RATE / 1000) * msec;

  unsigned (log2ceil(ticks)) count;
  count= ticks-1; // -1 to compensate for the time it takes to execute this line

  while (count) {  // Executes for ticks cycles
    count--;  // This line takes exactly 1 clock cycle
  }
}


// TetGrid: Contains grid of Tetris_Blocks only, top of grid is top of board
  // New blocks start offset above the grid, and move down [maybe blocked by overlay rectangle]
// Xstart,Ystart = co-ordinates for top-left corner of TetGrid
// size = dimensions for each block (assumming to be square)
  // Note: border to be printed around all blocks, at edges (where offset=0 or size), giving 2-px border between blocks
// Grid must be reference to a mpram defined with an available rom called "disp"
macro proc PxsCust_TetGrid( In, Out, Xstart, Ystart, Grid, Color1 ) {
      // Calculate if in Border Region
        // Currently is border of grid, needs to be modified to border of viewable grid later
      macro expr inBorderRegion() = ((In->Coord.X == Xstart+48 || In->Coord.X == Xstart+208 ) && (In->Coord.Y >= Ystart && In->Coord.Y <= Ystart+320)) || In->Coord.Y == Ystart+384;
    
  // Grid is an mpram representing the grid.This macro reads only from Grid.disp[]
    PXS_DECLARE_BW (Out);
    PXS_GENERIC (Stage0);
    PXS_GENERIC (Stage1);

    PXS_EXPECT_COORD (In);
    PXS_OVERLAY (In, Out);

    // Constants:
      // numCols = 10, + 3 border on each side
      // numRows = 20 + 4 border on top


   while (1)
    {
        par
        {

            unsigned row; // don't think width size needs to be specified here.-was 16
            unsigned 16 col;
            unsigned 1 enable; // True if we are in the game grid
            unsigned 5 bit_loc; // log2ceil+1 of width(block)=16
            unsigned 5 bit_index; // Index to grid row


          /* Stage 0: Calculate Row/Col and Check if we are in the grid */
          PxsCopyVCSH (In, &Stage0);
          col = (((unsigned)(In->Coord.X - (signed)Xstart)) >> 4);
          row = (((unsigned)(In->Coord.Y - (signed)Ystart)) >> 4);

          /* Stage 1: Select Col from given block type*/
          PxsCopyVCSH (&Stage0, &Stage1);

          // Need to modify to give border between blocks
          if (col < 3 || col > 12 || row < 3 || row >20) { // should be? || row <3 or || row > 20) {//DEBUG
            enable = 0; // Outside viewable playing area
          } else {
            enable = 1;
          }

          // Calculate bit_loc [Number of bits to shift by to get enable]
          bit_loc = col <- 5;
          bit_index = row <- 5;

          /* Stage 2: Output */
          PxsCopyVCSH (&Stage1, Out);
          if ( inBorderRegion() || (enable && ((Grid.disp[bit_index] << bit_loc)[15:15]) ) ) { // Check if col is enabled if box also enabled
                par
                {
                    Out->Pixel = (Color1 ? PxsWhite : PxsBlack);
                    Out->Active = !In->Sync.Blank;
                }
            }
            else
            {
                par
                {
                    Out->Pixel = In->Pixel;
                    Out->Active = In->Active;
                }
            }
        }
    }
}

/* Tetris Block Custom Filter: Displays a tetris block (16x16 in size) on the screen, starting
 *   at the specified co-ordinates, based on an unsigned 16 block variable, with each bit representing
 *   an enable/disable value for a position in a moving block grid.
 */
macro proc PxsCust_TetBlock( In, Out, X, Y, block, Color1 ) {
    PXS_DECLARE_BW (Out);
    PXS_GENERIC (Stage0);
    PXS_GENERIC (Stage1);

    PXS_EXPECT_COORD (In);
    PXS_OVERLAY (In, Out);

    while (1)
    {
        par
        {
            unsigned 16 row; // don't think width size needs to be specified here.
            unsigned 16 col;
            unsigned 1 enable; // True if we are in the 4x4 grid
            unsigned 5 bit_loc; // log2ceil+1 of width(block)=16

            

          /* Stage 0: Calculate Row/Col and Check if we are in the grid */
          PxsCopyVCSH (In, &Stage0);
          col = ((unsigned)(In->Coord.X - (signed)X)) >> 4;
          row = ((unsigned)(In->Coord.Y - (signed)Y)) >> 4;

          /* Stage 1: Select Row from given block type*/
          PxsCopyVCSH (&Stage0, &Stage1);
          bit_loc = ((row * 4) + col) <- 5; 

          /* Stage 2: Output */
          PxsCopyVCSH (&Stage1, Out);
          if ( row < 4 && col < 4 && ((block << bit_loc)[15:15]) ) { // Check if col is enabled if box also enabled
                par
                {
                    Out->Pixel = (Color1 ? PxsWhite : PxsBlack);
                    Out->Active = !In->Sync.Blank;
                }
            }
            else
            {
                par
                {
                    Out->Pixel = In->Pixel;
                    Out->Active = In->Active;
                }
            }
        }
    }
}

     // 4x4 Block Grid: meaning unsigned 16 for each block
      // 4 forms (rotations) per block, x4 basic blocks
      // ROM Index = type+(unsigned 2 rotation)
      // | block =>  0b1000
          //           1000
          //           1000
          //           1000
static macro expr TetrisBlocks = {
  0b1000100010001000, // The Line
  0b1111000000000000,
  0b1000100010001000,
  0b1111000000000000,

  0b0100111000000000, // |-
  0b0100011001000000,
  0b0000111001000000,
  0b0100110001000000,

  0b0010111000000000, // __|
  0b0100010001100000,
  0b0000111010000000,
  0b1100010001000000,

  0b1000111000000000, // |__
  0b0110010001000000,
  0b0000111000100000,
  0b0100010011000000,

  0b0110011000000000, // ||
  0b0110011000000000,
  0b0110011000000000,
  0b0110011000000000,

  0b1100011000000000, // -_
  0b0100110010000000,
  0b0110001100000000,
  0b0001001100100000,

  0b0110110000000000, // _-
  0b1000110001000000,
  0b0110110000000000,
  0b1000110001000000,

};