View unanswered posts | View active topics It is currently Tue Jul 22, 2014 5:43 pm






Reply to topic  [ 3 posts ] 
Tetris style game for the NXT 
Author Message
Rookie

Joined: Wed Jun 25, 2008 6:07 pm
Posts: 46
Post Tetris style game for the NXT
I have to say that using RobotC is a liberating experience. I've been fiddling with both the NXT-G language and also the NXT toolkit for LabView. They both have their place, but there's just nothing quite like getting back to good ol' C.

Thanks to the RobotC team for making such a wonderful alternative available. This was my first project, which I wrote this weekend to get my feet wet with your product. As with all things, there are some things that I wish were different, but overall, I'd say that this is by far my favorite way to program for the NXT.

Enjoy, everyone:

Use the left and right buttons to move the piece. The OK button will rotate the piece. The exit button causes the piece to drop. Pressing the exit button three times in a row will abort the program.

NOTE: Make sure to use the 1.38 Beta version or newer. When I was trying to do this with version 1.10 it caused some pretty nasty problems.


Code:
/*****************************************************************
**  Types and Structures
*****************************************************************/
typedef bool BlockPattern[4][4];

typedef struct
{
   int maxWidth, maxHeight;
   int curRow, curCol;
   BlockPattern pattern;
} PieceInfo;

typedef enum
{
   eNothing,
   eDoneWithPiece,
   eGameOver,
} DropResult;

/*****************************************************************
**  Constants and Configuation Info
*****************************************************************/
const TTimers KEYPRESS_TIMER  = T1;

const int BOX_DIM  = 4;
const int NUM_ROWS = 16;
const int NUM_COLS = 10;

const int SCREEN_WIDTH  = 100;
const int SCREEN_HEIGHT = 64;
const int TEXT_HEIGHT   = 8;
const int TEXT_WIDTH    = 6;

const int BOARD_WIDTH = NUM_COLS * BOX_DIM;
const int BOARD_LEFT  = (SCREEN_WIDTH - BOARD_WIDTH) / 4;
const int BOARD_RIGHT = BOARD_LEFT + BOARD_WIDTH - 1;

const int NEXT_AREA_DIM  = BOX_DIM * 5;
const int NEXT_AREA_LEFT = BOARD_RIGHT + (SCREEN_WIDTH - BOARD_RIGHT - NEXT_AREA_DIM) / 2;
const int NEXT_AREA_TOP  = SCREEN_HEIGHT - 1 - (TEXT_HEIGHT * 2);

const int STD_SCORES[4]  = {1, 3, 6, 10};

const int NUM_PATTERNS = 7;

const BlockPattern ptnSquare =
{
   {true , true , false, false},
   {true , true , false, false},
   {false, false, false, false},
   {false, false, false, false}
};

const BlockPattern ptnRightL =
{
   {true , false, false, false},
   {true , false, false, false},
   {true , true , false, false},
   {false, false, false, false}
};

const BlockPattern ptnLeftL =
{
   {false, true , false, false},
   {false, true , false, false},
   {true , true , false, false},
   {false, false, false, false}
};

const BlockPattern ptnStraight =
{
   {true , false, false, false},
   {true , false, false, false},
   {true , false, false, false},
   {true , false, false, false}
};

const BlockPattern ptnSnakeR =
{
   {false, true , true , false},
   {true , true , false, false},
   {false, false, false, false},
   {false, false, false, false}
};

const BlockPattern ptnSnakeL =
{
   {true , true , false, false},
   {false, true , true , false},
   {false, false, false, false},
   {false, false, false, false}
};

const BlockPattern ptnTee =
{
   {false, true , false, false},
   {true , true , true , false},
   {false, false, false, false},
   {false, false, false, false}
};

/*****************************************************************
**  Global Variables
*****************************************************************/
bool gameBoard[NUM_ROWS][NUM_COLS];

PieceInfo curPiece, nextPiece;
int score = 0;
int lines = 0;
int level = 1;
int latency;

/*****************************************************************
**  Functions
*****************************************************************/
void updateScore()
{
      nxtDisplayStringAt(BOARD_RIGHT + 8, SCREEN_HEIGHT - 1 - (TEXT_HEIGHT * 6.5), "%5d", score);
}

void initScreen()
{
   eraseDisplay();

   // Separate the game board from the rest of the screen
   nxtDrawLine(BOARD_LEFT - 2, 0, BOARD_LEFT - 2, SCREEN_HEIGHT - 1);
   nxtDrawLine(BOARD_LEFT - 1, 0, BOARD_LEFT - 1, SCREEN_HEIGHT - 1);
   nxtDrawLine(BOARD_RIGHT + 1, 0, BOARD_RIGHT + 1, SCREEN_HEIGHT - 1);
   nxtDrawLine(BOARD_RIGHT + 2, 0, BOARD_RIGHT + 2, SCREEN_HEIGHT - 1);

   nxtDisplayStringAt(BOARD_RIGHT + 8, SCREEN_HEIGHT - 1 - (TEXT_HEIGHT / 2), "Next:");
   nxtDisplayStringAt(BOARD_RIGHT + 8, SCREEN_HEIGHT - 1 - (TEXT_HEIGHT * 5), "Score:");

   // Draw the 'next block' area
   nxtDrawRect(NEXT_AREA_LEFT, NEXT_AREA_TOP, NEXT_AREA_LEFT + NEXT_AREA_DIM, NEXT_AREA_TOP - NEXT_AREA_DIM);
  updateScore();
}

void drawBox(int row, int col)
{
   int x1 = BOARD_LEFT + col * BOX_DIM;
   int y1 = (SCREEN_HEIGHT - 1) - row * BOX_DIM;
   int x2 = x1 + BOX_DIM - 1;
   int y2 = y1 - BOX_DIM + 1;
   nxtDrawRect(x1, y1, x2, y2);
}

void eraseBox(int row, int col)
{
   int x1 = BOARD_LEFT + col * BOX_DIM;
   int y1 = (SCREEN_HEIGHT - 1) - row * BOX_DIM;
   int x2 = x1 + BOX_DIM - 1;
   int y2 = y1 - BOX_DIM + 1;
   nxtEraseRect(x1, y1, x2, y2);
}

void drawNextPiece(PieceInfo &piece)
{
  int xPos = NEXT_AREA_LEFT;
  int yPos = NEXT_AREA_TOP;

  xPos += (NEXT_AREA_DIM - (piece.maxWidth  * BOX_DIM)) / 2;
  yPos -= (NEXT_AREA_DIM - (piece.maxHeight * BOX_DIM)) / 2;

  for (int i = 0; i < piece.maxHeight; i++)
    for (int j = 0; j < piece.maxWidth; j++)
      if (piece.pattern[i][j])
        nxtDrawRect(xPos + j * BOX_DIM, yPos - i * BOX_DIM, xPos + (j + 1) * BOX_DIM - 1, yPos - (i + 1) * BOX_DIM + 1);
}

void eraseNextPiece(PieceInfo &piece)
{
  int xPos = NEXT_AREA_LEFT;
  int yPos = NEXT_AREA_TOP;

  xPos += (NEXT_AREA_DIM - (piece.maxWidth  * BOX_DIM)) / 2;
  yPos -= (NEXT_AREA_DIM - (piece.maxHeight * BOX_DIM)) / 2;

  for (int i = 0; i < piece.maxHeight; i++)
    for (int j = 0; j < piece.maxWidth; j++)
      if (piece.pattern[i][j])
        nxtEraseRect(xPos + j * BOX_DIM, yPos - i * BOX_DIM, xPos + (j + 1) * BOX_DIM - 1, yPos - (i + 1) * BOX_DIM + 1);
}

void nextRandomPiece(PieceInfo &nextPiece)
{
   int nextIndex = random(NUM_PATTERNS);

   switch(nextIndex)
   {
      case 0: memcpy(nextPiece.pattern, ptnSquare,   sizeof(BlockPattern)); break;
      case 1: memcpy(nextPiece.pattern, ptnRightL,   sizeof(BlockPattern)); break;
      case 2: memcpy(nextPiece.pattern, ptnLeftL,    sizeof(BlockPattern)); break;
      case 3: memcpy(nextPiece.pattern, ptnStraight, sizeof(BlockPattern)); break;
      case 4: memcpy(nextPiece.pattern, ptnSnakeL,   sizeof(BlockPattern)); break;
      case 5: memcpy(nextPiece.pattern, ptnSnakeR,   sizeof(BlockPattern)); break;
      case 6: memcpy(nextPiece.pattern, ptnTee,      sizeof(BlockPattern)); break;
   }

   nextPiece.maxWidth  = 0;
   nextPiece.maxHeight = 0;
   for (int i = 0; i < 4; i++)
      for (int j = 0; j < 4; j++)
      {
         if (nextPiece.pattern[i][j])
         {
            if (i >= nextPiece.maxHeight) nextPiece.maxHeight = i + 1;
            if (j >= nextPiece.maxWidth)  nextPiece.maxWidth  = j + 1;
         }
      }
   nextPiece.curRow = 0;
   nextPiece.curCol = (NUM_COLS - nextPiece.maxWidth) / 2;
}

void drawPiece(PieceInfo &piece)
{
  for (int i = 0; i < piece.maxHeight; i++)
    for (int j = 0; j < piece.maxWidth; j++)
      if (piece.pattern[i][j])
        drawBox(piece.curRow + i, piece.curCol + j);
}

void erasePiece(PieceInfo &piece)
{
  for (int i = 0; i < piece.maxHeight; i++)
    for (int j = 0; j < piece.maxWidth; j++)
      if (piece.pattern[i][j])
        eraseBox(piece.curRow + i, piece.curCol + j);
}

void init()
{
   srand(nSysTime);

   // "Hi-jack" buttons for user program control.
   nNxtButtonTask = -2;
   nNxtExitClicks =  3;

   initScreen();

   score = 0;
   lines = 0;
   level = 1;
  latency = 1000;

   memset(gameBoard, 0, sizeof(gameBoard));
  nextRandomPiece(curPiece);
   nextRandomPiece(nextPiece);

   drawNextPiece(nextPiece);
   drawPiece(curPiece);
}

int waitForKey(int &howLong)
{
   int buttonPressed = kNoButton;
  bool cleanKey = (nNxtButtonPressed == kNoButton);

   if (howLong == 0) return kNoButton;

   ClearTimer(KEYPRESS_TIMER);

   do
   {
    if (nNxtButtonPressed == kNoButton) cleanKey = true;
    if (cleanKey) buttonPressed = nNxtButtonPressed;
      wait1Msec(1);
   } while((buttonPressed == kNoButton) && ((howLong < 0) || ((howLong >= 0) && (time1[KEYPRESS_TIMER] < howLong))));

   if (howLong >= 0)
   {
     howLong -= time1[KEYPRESS_TIMER];
     if (howLong < 0) howLong = 0;
   }

   return buttonPressed;
}

bool movePiece(PieceInfo &piece, int direction)
{
   // Don't let them move off the board
  if ((piece.curCol + direction) < 0) return false;
  if ((piece.curCol + piece.maxWidth + direction) > NUM_COLS) return false;

   // Check to see if moving the piece causes a collision
   for (int i = 0; i < piece.maxHeight; i++)
     for (int j = 0; j < piece.maxWidth; j++)
       if (piece.pattern[i][j] && gameBoard[curPiece.curRow + i][curPiece.curCol + j + direction])
        return false;

  // Allow the piece to move
  erasePiece(piece);
  piece.curCol += direction;
  drawPiece(piece);
  return true;
}

bool rotatePiece(PieceInfo &piece)
{
  BlockPattern possiblePattern;
  int temp;

  memset(possiblePattern, 0, sizeof(BlockPattern));

  // Rotate the pattern and determine if we can use it when rotated
  for (int i = 0; i < piece.maxWidth; i++)
    for (int j = 0; j < piece.maxHeight; j++)
    {
       if ((piece.curRow + i >= NUM_ROWS) || (piece.curCol + j >= NUM_COLS)) return false;
      possiblePattern[i][j] = piece.pattern[piece.maxHeight - 1 - j][i];
      if (possiblePattern[i][j] && gameBoard[piece.curRow + i][piece.curCol + j])
        return false;
    }

  // If we get here the new pattern is fine.  Go ahead and modify the piece info
  erasePiece(piece);
  memcpy(piece.pattern, possiblePattern, sizeof(BlockPattern));
  temp = piece.maxHeight;
  piece.maxHeight = piece.maxWidth;
  piece.maxWidth = temp;
  drawPiece(piece);

  return true;
}

DropResult letPieceFall(PieceInfo &piece)
{
  // See if we should actually let it move, or if it's going to get stuck here
   if (piece.curRow + piece.maxHeight >= NUM_ROWS) return eDoneWithPiece;

   for (int i = 0; i < piece.maxHeight; i++)
     for (int j = 0; j < piece.maxWidth; j++)
       if (piece.pattern[i][j] && gameBoard[curPiece.curRow + i + 1][curPiece.curCol + j])
        return eDoneWithPiece;

   // Actually let the piece drop
  erasePiece(piece);
   piece.curRow++;
   drawPiece(piece);

   return eNothing;
}

void letPieceFallCompletely(PieceInfo &piece)
{
   DropResult result;
  do
  {
    result = letPieceFall(piece);
  } while(result == eNothing);
}

void animateEliminatedRows(int &eliminatedCount, int e1, int e2, int e3, int e4)
{
   int blinkSpeed = latency / 5;
  int eliminatedRows[4] = {e1, e2, e3, e4};

   // Make the eliminated rows flash, then replace them
  for (int iteration = 0; iteration < 5; iteration++)
  {
    for (int rowIndex = 0; rowIndex < eliminatedCount; rowIndex++)
       for (int col = 0; col < NUM_COLS; col++)
         if (iteration & 0x01)
           drawBox(eliminatedRows[rowIndex], col);
         else
           eraseBox(eliminatedRows[rowIndex], col);
    wait1Msec(blinkSpeed);
  }

  // Now actually remove the rows and update the screen
  for (int rowIndex = 0; rowIndex < eliminatedCount; rowIndex++)
    for (int fallingRow = eliminatedRows[rowIndex]; fallingRow > 0; fallingRow--)
      for (int col = 0; col < NUM_COLS; col++)
      {
        gameBoard[fallingRow][col] = gameBoard[fallingRow - 1][col];
         if (gameBoard[fallingRow][col])
           drawBox(fallingRow, col);
         else
           eraseBox(fallingRow, col);

         // make sure to clear out the top row as well
         if (fallingRow == 1)
         {
            gameBoard[0][col] = false;
            eraseBox(0, col);
         }
      }
}

void clearCompletedRows()
{
   int oldLevel = level;
   int eliminatedRows[4];
  int eliminatedCount = 0;

   for (int row = 0; row < NUM_ROWS; row++)
  {
     int rowCount = 0;

     for (int col = 0; col < NUM_COLS; col++)
       if (gameBoard[row][col]) rowCount++;

     if (rowCount == NUM_COLS)
     {
      eliminatedRows[eliminatedCount] = row;
      eliminatedCount++;
     }
  }

  if (eliminatedCount > 0)
  {
    score += STD_SCORES[eliminatedCount - 1] * level;
    updateScore();
    lines += eliminatedCount;
    level  = lines / 10 + 1;
    if (level != oldLevel)
    {
       latency = (latency * 9) / 10;
    }
    animateEliminatedRows(eliminatedCount, eliminatedRows[0], eliminatedRows[1], eliminatedRows[2], eliminatedRows[3]);
  }
}

void animateEndOfGame()
{
  for (int row = NUM_ROWS - 1; row >= 0; row--)
  {
     for (int col = 0; col < NUM_COLS; col++)
       drawBox(row, col);
    wait1Msec(100);
  }
}

DropResult finishPiece()
{
  // Place the piece on the game board
   for (int i = 0; i < curPiece.maxHeight; i++)
     for (int j = 0; j < curPiece.maxWidth; j++)
      if (curPiece.pattern[i][j])
         gameBoard[curPiece.curRow + i][curPiece.curCol + j] = true;

  // Did we complete any rows?
  clearCompletedRows();

  // Make the 'next' piece the current piece
  eraseNextPiece(nextPiece);
  memcpy(curPiece.pattern, nextPiece.pattern, sizeof(BlockPattern));
  curPiece.curCol = nextPiece.curCol;
  curPiece.curRow = nextPiece.curRow;
  curPiece.maxHeight = nextPiece.maxHeight;
  curPiece.maxWidth = nextPiece.maxWidth;
  nextRandomPiece(nextPiece);
  drawNextPiece(nextPiece);
  drawPiece(curPiece);

  // See if the game is over now
  for (int i = 0; i < curPiece.maxHeight; i++)
    for (int j = 0; j < curPiece.maxWidth; j++)
      if (curPiece.pattern[i][j] && gameBoard[curPiece.curRow + i][curPiece.curCol + j])
        return eGameOver;

  return eNothing;
}

void playGame()
{
   bool done = false;
   int  timeUntilDrop;
  int  nextKey;
  bool doneWithPiece = false;

  timeUntilDrop = latency;

  while (!done)
   {
    switch(waitForKey(timeUntilDrop))
    {
       case kLeftButton : movePiece(curPiece, -1); break;
       case kRightButton: movePiece(curPiece,  1); break;
       case kEnterButton: rotatePiece(curPiece);   break;
       case kExitButton :
         letPieceFallCompletely(curPiece);
         doneWithPiece = true;
         break;

       default:  // timeout
         timeUntilDrop = latency;
         if (letPieceFall(curPiece) == eDoneWithPiece)
           doneWithPiece = true;
         break;
    }
    if (doneWithPiece)
    {
       timeUntilDrop = latency;
      if (finishPiece() == eGameOver)
        done = true;
      doneWithPiece = false;
      updateScore();
    }
   }
   animateEndOfGame();

  // Wait for a keypress (infinite timeout)
   timeUntilDrop = -1;
   waitForKey(timeUntilDrop);
}

task main()
{
   init();
   playGame();
}


Sun Jun 29, 2008 9:46 pm
Profile
Rookie

Joined: Sat Jan 19, 2008 1:35 pm
Posts: 5
Post 
Why isnt the beta version on the robotc site?


Tue Jul 01, 2008 4:57 pm
Profile
Rookie

Joined: Wed Jun 25, 2008 6:07 pm
Posts: 46
Post 
Actually, if you click the link I provided above, you are downloading it from the RobotC site.

The main RobotC homepage doesn't have direct links to the beta releases, but you can find posts here in the forum (like this one) that announce the beta releases and show you where to download them.


Tue Jul 01, 2008 11:35 pm
Profile
Display posts from previous:  Sort by  
Reply to topic   [ 3 posts ] 

Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  



Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by ST Software for PTF.