// // aitetris.cc // from http://www.liacs.leidenuniv.nl/~kosterswa/AI/ // assignment: https://liacs.leidenuniv.nl/~kosterswa/AI/teet2024.html // February 5, 2024 // Walter Kosters, w.a.kosters@liacs.leidenuniv.nl // // Tetris playing C++ program // // Compile: g++ -Wall -Wextra -O3 -o aitetris aitetris.cc // Usage: ./aitetris // // play number of games on a height by width board // if print is 1, much more output is generated // seed seeds the random number generator (if 0: use time) // play type = 0: random, 1: MC, ... // here Monte Carlo (MC) uses playouts // // Every piece has a unique name (LS, RS, I, Sq, T, LG, RG; see below), // orientation (0/1/2/3), and starting position (0...width of // game board - 1), indicating the column of the leftmost square of // the position of the piece. // Note that possible orientation and position depend on the piece! // As an example: move 7 (out of 34, being 0..33) for piece LG (a Left // Gun), on a width 10 board, corresponds with orientation 0, starting // in column 7. There is perhaps no need to know this. // // The program generates a random series of pieces, and then needs // an orientation and position (random in this version, see the function // playrandomgame (print)); the piece then drops as required. // After that rows are cleared, and the board is displayed. // There is no lookahead. // // The board is of size h (height) times w (width); // maximum values are hMAX and wMAX, respectively; // bottom left is (0,0); the top 3 rows are "outside" the board: // if part of a piece ends here, the game stops; // board[i][j] is true if row i (from below, start at 0), column j // (from left, start at 0) is occupied // // If you have a piece, the function call possibilities (piece) // returns the number of possible moves possib. These moves are // denoted by 0,1,...,possib-1. Given a number n in this range, // the function dothemove (piece,n) then does the corresponding move. // // // The 7 pieces, with orientations: // // LS 0: XX 1: X Left Snake // XX XX // X // // RS 0: XX 1: X Right Snake // XX XX // X // // I 0: XXXX 1: X I // X // X // X // // Sq always 0: XX Square // XX // // T 0: XXX 1: X 2: X 3: X T // X XX XXX XX // X X // // LG 0: XXX 1: XX 2: X 3: X Left Gun // X X XXX X // X XX // // RG 0: XXX 1: X 2: X 3: XX Right Gun // X X XXX X // XX X // #include #include // for time stuff #include // for rand ( ) using namespace std; enum PieceName {Sq,LG,RG,LS,RS,I,T}; const int wMAX = 20; // maximum width of the game board const int hMAX = 25; // maximum total height of the game board class Tetris { private: int h, w; // actual height and width bool board[hMAX][wMAX]; // the game board; board[i][j] true <=> occupied int piececount; // number of pieces that has been used so far int avgpiececount; // total/average number of pieces that has been used int rowscleared; // number of rows cleared so far int avgrowscleared; // total/average number of rows cleared int theplayouts; // used by Monte Carlo void getrandompiece (PieceName & piece); void clearrows ( ); void displayboard ( ); void letitfall (PieceName piece, int orientation, int position); void infothrowpiece (PieceName piece, int orientation, int position); bool endofgame ( ); int possibilities (PieceName piece); void computeorandpos (PieceName piece, int & orientation, int & position, int themove); void dorandommove (PieceName piece); void doMCmove (PieceName piece); void toprow (bool therow[wMAX], int & numberrow, int & empties); int numberempties (int numberrow); void dothemove (PieceName piece, int themove); public: Tetris ( ); Tetris (int height, int width, int playouts); void reset ( ); void statistics (bool print); void averageresults (int numberofgames); void playrandomgame (bool print); void playMCgame (bool print); };//Tetris // default constructor Tetris::Tetris ( ) { int i, j; piececount = avgpiececount = 0; rowscleared = avgrowscleared = 0; h = hMAX; w = wMAX; for ( i = 0; i < hMAX; i++ ) for ( j = 0; j < wMAX; j++ ) board[i][j] = false; }//Tetris::Tetris // constructor Tetris::Tetris (int height, int width, int playouts) { int i, j; piececount = avgpiececount = 0; rowscleared = avgrowscleared = 0; theplayouts = playouts; if ( height < hMAX ) h = height; else h = hMAX; if ( 4 <= width && width < wMAX ) w = width; else w = wMAX; for ( i = 0; i < hMAX; i++ ) for ( j = 0; j < wMAX; j++ ) board[i][j] = false; }//Tetris::Tetris // reset the game void Tetris::reset ( ) { int i, j; piececount = 0; rowscleared = 0; for ( i = 0; i < hMAX; i++ ) for ( j = 0; j < wMAX; j++ ) board[i][j] = false; }//Tetris::reset // some statistics void Tetris::statistics (bool print) { if ( print ) cout << endl << "Done! We have " << rowscleared << " row(s) cleared, using " << piececount << " pieces." << endl; avgpiececount += piececount; avgrowscleared += rowscleared; }//Tetris::statistics // some average statistics void Tetris::averageresults (int numberofgames) { cout << "Average results over " << numberofgames << " games:" << endl << "- number of rows cleared: " << avgrowscleared / static_cast (numberofgames) << endl << "- number of pieces = moves: " << avgpiececount / static_cast (numberofgames) << endl << endl; }//Tetris::averageresults // how many empties has row numberrow? int Tetris::numberempties (int numberrow) { int j, theempties = w; for ( j = 0; j < w; j++ ) if ( board[numberrow][j] ) theempties--; return theempties; }//Tetris::numberempties // gives number of empties in heighest non-empty row, // and copies this row into therow; its row index being numberrow // if this is -1, the whole field is empty void Tetris::toprow (bool therow[wMAX], int & numberrow, int & empties) { int i, j, theempties; numberrow = -1; empties = w; for ( i = 0; i < h; i++ ) { theempties = numberempties (i); if ( theempties < w ) { for ( j = 0; j < w; j++ ) therow[j] = board[i][j]; empties = theempties; numberrow = i; }//if }//for }//Tetris::toprow // checks for full rows --- and removes them void Tetris::clearrows ( ) { int i, j, k; bool full; for ( i = h-2; i >= 0; i-- ) { full = true; j = 0; while ( full && j < w ) if ( !board[i][j] ) full = false; else j++; if ( full ) { //cout << "Row cleared ..." << endl; rowscleared++; for ( k = i; k < h-1; k++ ) for ( j = 0; j < w; j++ ) board[k][j] = board[k+1][j]; for ( j = 0; j < w; j++ ) board[h-1][j] = false; }//if }//for }//Tetris::clearrows // displays current board on the screen void Tetris::displayboard ( ) { int i, j; for ( i = h-1; i >= 0; i-- ) { if ( i < h-3 ) cout << "|"; else cout << " "; for ( j = 0; j < w; j++ ) if ( board[i][j] ) cout << "X"; else cout << " "; if ( i < h-3 ) cout << "|" << endl; else cout << endl; }//for for ( j = 0; j < w+2; j++ ) cout << "-"; cout << endl; cout << " "; for ( j = 0; j < w; j++ ) cout << j % 10; cout << endl; cout << "This was move "<< piececount << "." << endl; }//Tetris::displayboard // let piece fall in position and orientation given // assume it still fits in top rows void Tetris::letitfall (PieceName piece, int orientation, int position) { int x[4] = {0}; int y[4] = {0}; int i; piececount++; switch (piece) { case Sq: x[0] = position; y[0] = h-2; x[1] = position; y[1] = h-1; x[2] = position+1; y[2] = h-2; x[3] = position+1; y[3] = h-1; break; case LG: switch (orientation) { case 0: x[0] = position+2; y[0] = h-2; x[1] = position+2; y[1] = h-1; x[2] = position+1; y[2] = h-1; x[3] = position; y[3] = h-1; break; case 1: x[0] = position; y[0] = h-3; x[1] = position; y[1] = h-2; x[2] = position; y[2] = h-1; x[3] = position+1; y[3] = h-1; break; case 2: x[0] = position; y[0] = h-2; x[1] = position+1; y[1] = h-2; x[2] = position+2; y[2] = h-2; x[3] = position; y[3] = h-1; break; case 3: x[0] = position; y[0] = h-3; x[1] = position+1; y[1] = h-1; x[2] = position+1; y[2] = h-2; x[3] = position+1; y[3] = h-3; break; }//switch break; case RG: switch (orientation) { case 0: x[0] = position; y[0] = h-2; x[1] = position+2; y[1] = h-1; x[2] = position+1; y[2] = h-1; x[3] = position; y[3] = h-1; break; case 1: x[0] = position; y[0] = h-3; x[1] = position; y[1] = h-2; x[2] = position; y[2] = h-1; x[3] = position+1; y[3] = h-3; break; case 2: x[0] = position; y[0] = h-2; x[1] = position+1; y[1] = h-2; x[2] = position+2; y[2] = h-2; x[3] = position+2; y[3] = h-1; break; case 3: x[0] = position+1; y[0] = h-3; x[1] = position+1; y[1] = h-1; x[2] = position+1; y[2] = h-2; x[3] = position; y[3] = h-1; break; }//switch break; case LS: switch (orientation) { case 0: x[0] = position+1; y[0] = h-2; x[1] = position+1; y[1] = h-1; x[2] = position+2; y[2] = h-2; x[3] = position; y[3] = h-1; break; case 1: x[0] = position; y[0] = h-3; x[1] = position; y[1] = h-2; x[2] = position+1; y[2] = h-1; x[3] = position+1; y[3] = h-2; break; }//switch break; case RS: switch (orientation) { case 0: x[0] = position+1; y[0] = h-2; x[1] = position+1; y[1] = h-1; x[2] = position+2; y[2] = h-1; x[3] = position; y[3] = h-2; break; case 1: x[0] = position+1; y[0] = h-3; x[1] = position; y[1] = h-2; x[2] = position+1; y[2] = h-2; x[3] = position; y[3] = h-1; break; }//switch break; case I : switch (orientation) { case 0: x[0] = position; y[0] = h-1; x[1] = position+1; y[1] = h-1; x[2] = position+2; y[2] = h-1; x[3] = position+3; y[3] = h-1; break; case 1: x[0] = position; y[0] = h-4; x[1] = position; y[1] = h-3; x[2] = position; y[2] = h-2; x[3] = position; y[3] = h-1; break; }//switch break; case T : switch (orientation) { case 0: x[0] = position+1; y[0] = h-2; x[1] = position; y[1] = h-1; x[2] = position+1; y[2] = h-1; x[3] = position+2; y[3] = h-1; break; case 1: x[0] = position; y[0] = h-3; x[1] = position; y[1] = h-2; x[2] = position; y[2] = h-1; x[3] = position+1; y[3] = h-2; break; case 2: x[0] = position; y[0] = h-2; x[1] = position+1; y[1] = h-2; x[2] = position+2; y[2] = h-2; x[3] = position+1; y[3] = h-1; break; case 3: x[0] = position+1; y[0] = h-3; x[1] = position+1; y[1] = h-2; x[2] = position+1; y[2] = h-1; x[3] = position; y[3] = h-2; break; }//switch break; }//switch while ( y[0] > 0 && !board[y[0]-1][x[0]] && !board[y[1]-1][x[1]] && !board[y[2]-1][x[2]] && !board[y[3]-1][x[3]] ) for ( i = 0; i < 4; i++ ) y[i]--; for ( i = 0; i < 4; i++ ) board[y[i]][x[i]] = true; clearrows ( ); }//Tetris::letitfall // give piece a chance: info to the screen void Tetris::infothrowpiece (PieceName piece, int orientation, int position) { int j; cout << endl; for ( j = 0; j < w+5; j++ ) cout << "="; if ( piececount < 10 ) cout << " "; else if ( piececount < 100 ) cout << " "; else cout << " "; cout << piececount << ": "; switch ( piece ) { case Sq: cout << "Square "; break; case LG: cout << "Left gun "; break; case RG: cout << "Right gun "; break; case LS: cout << "Left snake "; break; case RS: cout << "Right snake "; break; case I: cout << "I "; break; case T: cout << "T "; break; }//switch cout << orientation << " " << position << endl; }//Tetris::infothrowpiece // check whether top 3 rows are somewhat occupied (so game has ended?) bool Tetris::endofgame ( ) { int j; for ( j = 0; j < w; j++ ) if ( board[h-3][j] ) return true; return false; }//Tetris::endofgame // how many possibilities has piece? int Tetris::possibilities (PieceName piece){ if ( piece == Sq ) return (w-1); if ( ( piece == LS ) || ( piece == RS ) || ( piece == I ) ) return (2*w-3); return (4*w-6); }//Tetris::possibilities // compute orientation and position for move themove from piece void Tetris::computeorandpos (PieceName piece, int & orientation, int & position, int themove) { orientation = 0; position = themove; switch ( piece ) { case LS: case RS: if ( themove > w-3 ) { orientation = 1; position = themove - (w-2); }//if break; case I: if ( themove > w-4 ) { orientation = 1; position = themove - (w-3); }//if break; case Sq: break; case T: case LG: case RG: if ( themove > 3*w-6 ) { orientation = 3; position = themove - (3*w-5); }//if else if ( themove > 2*w-4 ) { orientation = 2; position = themove - (2*w-3); }//if else if ( themove > w-3 ) { orientation = 1; position = themove - (w-2); }//if break; }//switch }//Tetris::computeorandpos // generate a random piece void Tetris::getrandompiece (PieceName & piece) { int intpiece = rand ( ) % 7; switch (intpiece) { case 0: piece = LS; break; case 1: piece = RS; break; case 2: piece = I; break; case 3: piece = Sq; break; case 4: piece = T; break; case 5: piece = LG; break; case 6: piece = RG; break; }//switch }//Tetris::getrandompiece // do themove for the given piece // assume thet 0 <= themove < possibilities (piece) void Tetris::dothemove (PieceName piece, int themove) { int orientation; int position; computeorandpos (piece,orientation,position,themove); letitfall (piece,orientation,position); }//Tetris::dothemove // do a random move for piece void Tetris::dorandommove (PieceName piece) { int themove = rand ( ) % possibilities (piece); dothemove (piece,themove); }//Tetris::dorandommove // do a MC move for piece void Tetris::doMCmove (PieceName piece) { int bestmove = 0; // TODO dothemove (piece,bestmove); }//Tetris::doMCmove // play a random game void Tetris::playrandomgame (bool print) { PieceName piece = Sq; int nr, emp; bool therow[wMAX]; if ( print ) displayboard ( ); while ( ! endofgame ( ) ) { getrandompiece (piece); // obtain random piece dorandommove (piece); // and drop it randomly if ( print ) { displayboard ( ); // print the board toprow (therow,nr,emp); // how is top row? if ( nr != -1 ) cout << "Top row " << nr << " has " << emp << " empties" << "." << endl; }//if }//while }//Tetris::playrandomgame // play a MC game void Tetris::playMCgame (bool print) { PieceName piece = Sq; int nr, emp; bool therow[wMAX]; if ( print ) displayboard ( ); while ( ! endofgame ( ) ) { getrandompiece (piece); // obtain random piece doMCmove (piece); // and drop it using MC if ( print ) { displayboard ( ); // print the board toprow (therow,nr,emp); // how is top row? if ( nr != -1 ) cout << "Top row " << nr << " has " << emp << " empties" << "." << endl; }//if }//while }//Tetris::playMCgame int main (int argc, char* argv[ ]) { if ( argc != 8 ) { cout << "Usage: " << argv[0] << " " << " " << endl; return 1; }//if int h = atoi (argv[1]); int w = atoi (argv[2]); bool print = ( atoi (argv[3]) == 1 ); int numberofgames = atoi (argv[4]); int playouts = atoi (argv[5]); int type = atoi (argv[6]); Tetris board (h,w,playouts); int i; if ( atoi (argv[7]) == 0 ) { srand (time (NULL)); }//if else { srand (atoi (argv[7])); }//else for ( i = 1; i <= numberofgames; i++ ) { if ( print ) { cout << "Game "<< i << ":" << endl; }//if board.reset ( ); if ( type == 0 ) { board.playrandomgame (print); }//if else if ( type == 1 ) { board.playMCgame (print); }//else board.statistics (print); }//if board.averageresults (numberofgames); return 0; }//main