like a lot of amateur programmers I suffer from having lot's of ideas but hardly ever finishing a project. For quite some time now I had that idea to "force" myself to finish just something by writing minimalistic games. To really enforce that minimalism I figured I should restrict myself to a single source file since that "punishes" me for bloating my code. I.e. it keeps me from writing an engine instead of a game. So today I finally wrote "One File Tetris". It uses SFML and OpenGL and is 444 lines long. Instead of printing points (because displaying text isn't trivial) it has an efficiency meter.
Feel free to criticize/discuss any aspect of the code or the idea of writing single file games.
Edit: on gcc it should compile with:
g++ -o tetris main.cpp -lsfml-system -lsfml-window
Here it is:
#include <SFML/System.hpp>#include <SFML/Window.hpp>#include <SFML/Graphics.hpp>#include <algorithm>#include <cstdlib>//define piecesint t_piece1[16] = {0,0,0,0,1,1,1,0,0,1,0,0,0,0,0,0};int t_piece2[16] = {0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0};int t_piece3[16] = {0,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0};int t_piece4[16] = {0,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0};int l_piece1[16] = {0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0};int l_piece2[16] = {1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0};int l_piece3[16] = {0,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0};int l_piece4[16] = {0,1,0,0,0,1,0,0,0,1,1,0,0,0,0,0};int reverse_l_piece1[16] = {0,0,0,0,1,1,1,0,0,0,1,0,0,0,0,0};int reverse_l_piece2[16] = {0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0};int reverse_l_piece3[16] = {1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0};int reverse_l_piece4[16] = {0,1,1,0,0,1,0,0,0,1,0,0,0,0,0,0};int square_piece1[16] = {0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0};int square_piece2[16] = {0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0};int square_piece3[16] = {0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0};int square_piece4[16] = {0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0};int s_piece1[16] = {0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0};int s_piece2[16] = {1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0};int s_piece3[16] = {0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0};int s_piece4[16] = {1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0};int reverse_s_piece1[16] = {1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0};int reverse_s_piece2[16] = {0,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0};int reverse_s_piece3[16] = {1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0};int reverse_s_piece4[16] = {0,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0};int line_piece1[16] = {0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0};int line_piece2[16] = {0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0};int line_piece3[16] = {0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0};int line_piece4[16] = {0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0};class Tetris {public: Tetris() { init(); } void init(); void step(); void moveLeft(); void moveRight(); void rotateLeft(); void rotateRight(); void draw(); bool isGameOver() { return game_over; } int getPoints() { return points; } int getLines() { return lines; }private: void newPiece(); bool checkBounds(int x, int y); bool isFree(int x, int y); void set(int x, int y, int val); bool checkIfPossible(int x, int y, int piece[16]); void setPiece(int x, int y, int piece[16]); int* getActivePiece(); int* getNextPiece(); void removeLines(); int active_x, active_y; int active_index, active_orientation; bool game_over; int points, lines; int next_piece; int pieces[7][4][16]; int field[200];};void Tetris::init(){ std::fill(field, field+200, 0); std::copy(t_piece1, t_piece1+16, pieces[0][0]); std::copy(t_piece2, t_piece2+16, pieces[0][1]); std::copy(t_piece3, t_piece3+16, pieces[0][2]); std::copy(t_piece4, t_piece4+16, pieces[0][3]); std::copy(l_piece1, l_piece1+16, pieces[1][0]); std::copy(l_piece2, l_piece2+16, pieces[1][1]); std::copy(l_piece3, l_piece3+16, pieces[1][2]); std::copy(l_piece4, l_piece4+16, pieces[1][3]); std::copy(reverse_l_piece1, reverse_l_piece1+16, pieces[2][0]); std::copy(reverse_l_piece2, reverse_l_piece2+16, pieces[2][1]); std::copy(reverse_l_piece3, reverse_l_piece3+16, pieces[2][2]); std::copy(reverse_l_piece4, reverse_l_piece4+16, pieces[2][3]); std::copy(s_piece1, s_piece1+16, pieces[3][0]); std::copy(s_piece2, s_piece2+16, pieces[3][1]); std::copy(s_piece3, s_piece3+16, pieces[3][2]); std::copy(s_piece4, s_piece4+16, pieces[3][3]); std::copy(reverse_s_piece1, reverse_s_piece1+16, pieces[4][0]); std::copy(reverse_s_piece2, reverse_s_piece2+16, pieces[4][1]); std::copy(reverse_s_piece3, reverse_s_piece3+16, pieces[4][2]); std::copy(reverse_s_piece4, reverse_s_piece4+16, pieces[4][3]); std::copy(square_piece1, square_piece1+16, pieces[5][0]); std::copy(square_piece2, square_piece2+16, pieces[5][1]); std::copy(square_piece3, square_piece3+16, pieces[5][2]); std::copy(square_piece4, square_piece4+16, pieces[5][3]); std::copy(line_piece1, line_piece1+16, pieces[6][0]); std::copy(line_piece2, line_piece2+16, pieces[6][1]); std::copy(line_piece3, line_piece3+16, pieces[6][2]); std::copy(line_piece4, line_piece4+16, pieces[6][3]); game_over = false; points = 0; lines = 0; next_piece = std::rand()%7; newPiece();}void Tetris::step(){ //advance position if possible, otherwise set piece and get a new one if(checkIfPossible(active_x, active_y+1, getActivePiece())) active_y++; else { setPiece(active_x, active_y, getActivePiece()); removeLines(); newPiece(); }}void Tetris::moveLeft(){ if(checkIfPossible(active_x+1, active_y, getActivePiece())) active_x++;}void Tetris::moveRight(){ if(checkIfPossible(active_x-1, active_y, getActivePiece())) active_x--;}void Tetris::rotateLeft(){ active_orientation = (active_orientation+1)%4; if(!checkIfPossible(active_x, active_y, getActivePiece())) active_orientation = (active_orientation+3)%4;}void Tetris::rotateRight(){ active_orientation = (active_orientation+3)%4; if(!checkIfPossible(active_x, active_y, getActivePiece())) active_orientation = (active_orientation+1)%4;}void Tetris::draw(){ glBegin(GL_QUADS); glColor3f(0.7,0.7,0.7); for(int x = 0;x<10;++x) for(int y = 0;y<20;++y) if(!isFree(x,y) || game_over) { glVertex2f(x ,y ); glVertex2f(x+1,y ); glVertex2f(x+1,y+1); glVertex2f(x ,y+1); } if(!game_over) //only draw piece and next piece if game is still running { glColor3f(1,1,1); for(int dx = 0;dx<4;++dx) for(int dy = 0;dy<4;++dy) if(getActivePiece()[4*dy+dx]!=0) { int x = active_x + dx; int y = active_y + dy; glVertex2f(x ,y ); glVertex2f(x+1,y ); glVertex2f(x+1,y+1); glVertex2f(x ,y+1); } for(int dx = 0;dx<4;++dx) for(int dy = 0;dy<4;++dy) if(getNextPiece()[4*dy+dx]!=0) { int x = -5 + dx; int y = 1 + dy; glVertex2f(x ,y ); glVertex2f(x+1,y ); glVertex2f(x+1,y+1); glVertex2f(x ,y+1); } } glEnd(); //outline of playing field glBegin(GL_LINE_STRIP); glVertex2f( 0, 0); glVertex2f(10, 0); glVertex2f(10,20); glVertex2f( 0,20); glVertex2f( 0, 0); glEnd();}void Tetris::newPiece(){ active_x = 3; active_y = 0; active_index = next_piece; active_orientation = 0; //game over if new piece cannot be entered without collision if(!checkIfPossible(active_x, active_y, getActivePiece())) { game_over = true; } next_piece = std::rand()%7;}bool Tetris::checkBounds(int x, int y){ return x>=0 && x<10 && y>=0 && y<20;}bool Tetris::isFree(int x, int y){ if(checkBounds(x,y)) return field[y*10+x] == 0; else return false;}void Tetris::set(int x, int y, int val){ if(checkBounds(x,y)) field[y*10+x] = val;}bool Tetris::checkIfPossible(int x, int y, int piece[16]){ for(int dx = 0;dx<4;++dx) for(int dy = 0;dy<4;++dy) if(piece[dy*4+dx] != 0 && !isFree(x+dx, y+dy)) return false; return true;}void Tetris::setPiece(int x, int y, int piece[16]){ for(int dx = 0;dx<4;++dx) for(int dy = 0;dy<4;++dy) if(isFree(x+dx, y+dy)) set(x+dx, y+dy, piece[dy*4+dx]);}int* Tetris::getActivePiece(){ return pieces[active_index][active_orientation];}int* Tetris::getNextPiece(){ return pieces[next_piece][0];}void Tetris::removeLines(){ int removed_lines = 0; for(int y=0;y<20;++y) { bool full = true; for(int x = 0;x<10;++x) { if(isFree(x,y)) { full = false; break; } } if(full) { //push down lines removed_lines++; for(int iy = y;iy>0;--iy) for(int ix = 0;ix<10;++ix) field[iy*10+ix] = field[(iy-1)*10+ix]; //insert empty line at the top for(int ix = 0;ix<10;++ix) field[ix] = 0; } } //award score lines += removed_lines; switch(removed_lines) { case 4: points+=300; //800 case 3: points+=200; //500 case 2: points+=200; //300 case 1: points+=100; //100 }}int main(){ sf::Window app(sf::VideoMode(400, 480, 32), "One File Tetris", sf::Style::Close); glClearColor(0.f, 0.f, 0.f, 0.f); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, 400, 0, 480, 1, 11); Tetris game; sf::Clock clock; while (app.IsOpened()) { //handle input sf::Event Event; while (app.GetEvent(Event)) { switch(Event.Type) { case sf::Event::Closed: app.Close(); break; case sf::Event::KeyPressed: switch(Event.Key.Code) { case sf::Key::Escape: app.Close(); break; case sf::Key::Down: case sf::Key::S: game.step(); break; case sf::Key::Left: case sf::Key::A: game.moveLeft(); break; case sf::Key::Right: case sf::Key::D: game.moveRight(); break; case sf::Key::Up: case sf::Key::E: game.rotateRight(); break; case sf::Key::Q: game.rotateLeft(); break; case sf::Key::Space: if(game.isGameOver()) game.init(); break; } break; } } //move down piece every second if(clock.GetElapsedTime()>=1) { game.step(); clock.Reset(); } glClear(GL_COLOR_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.f, 0.f, -6.f); glPushMatrix(); //draw tetris field glTranslatef(20,20,0); glScalef(22,22,1); glTranslatef(10,20,0); glRotatef(180,0,0,1); game.draw(); glPopMatrix(); //calculate and draw efficiency meter glPushMatrix(); glTranslatef(260,20,0); float efficiency = 0; if(game.getLines() != 0) efficiency = 2*(game.getPoints()/(200.*game.getLines()))-1; glColor3f(0,0,1); glBegin(GL_QUADS); glColor3f(0,0,1); glVertex2d(0,0); glVertex2d(22,0); glColor3f(efficiency,0,1-efficiency); glVertex2d(22,308*efficiency); glVertex2d(0,308*efficiency); glEnd(); glColor3f(1,1,1); glBegin(GL_LINE_STRIP); glVertex2d(0,0); glVertex2d(22,0); glVertex2d(22,308); glVertex2d(0,308); glVertex2d(0,0); glEnd(); glPopMatrix(); app.Display(); } return 0;}
[Edited by - japro on December 27, 2010 2:40:58 PM]