/* OneLoneCoder.com - Command Line Tetris "Put Your Money Where Your Mouth Is" - @Javidx9 License ~~~~~~~ Copyright (C) 2018 Javidx9 This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions; See license for details. Original works located at: https://www.github.com/onelonecoder https://www.onelonecoder.com https://www.youtube.com/javidx9 GNU GPLv3 https://github.com/OneLoneCoder/videos/blob/master/LICENSE From Javidx9 :) ~~~~~~~~~~~~~~~ Hello! Ultimately I don't care what you use this for. It's intended to be educational, and perhaps to the oddly minded - a little bit of fun. Please hack this, change it and use it in any way you see fit. You acknowledge that I am not responsible for anything bad that happens as a result of your actions. However this code is protected by GNU GPLv3, see the license in the github repo. This means you must attribute me if you use it. You can view this license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE Cheers! Background ~~~~~~~~~~ I made a video "8-Bits of advice for new programmers" (https://youtu.be/vVRCJ52g5m4) and suggested that building a tetris clone instead of Dark Sould IV might be a better approach to learning to code. Tetris is nice as it makes you think about algorithms. Controls are Arrow keys Left, Right & Down. Use Z to rotate the piece. You score 25pts per tetronimo, and 2^(number of lines)*100 when you get lines. Future Modifications ~~~~~~~~~~~~~~~~~~~~ 1) Show next block and line counter Author ~~~~~~ Twitter: @javidx9 Blog: www.onelonecoder.com Video: ~~~~~~ https://youtu.be/8OK8_tHeCIA Last Updated: 30/03/2017 */ #include #include #include using namespace std; #include #include int nScreenWidth = 80; // Console Screen Size X (columns) int nScreenHeight = 30; // Console Screen Size Y (rows) wstring tetromino[7]; int nFieldWidth = 12; int nFieldHeight = 18; unsigned char *pField = nullptr; int Rotate(int px, int py, int r) { int pi = 0; switch (r % 4) { case 0: // 0 degrees // 0 1 2 3 pi = py * 4 + px; // 4 5 6 7 break; // 8 9 10 11 //12 13 14 15 case 1: // 90 degrees //12 8 4 0 pi = 12 + py - (px * 4); //13 9 5 1 break; //14 10 6 2 //15 11 7 3 case 2: // 180 degrees //15 14 13 12 pi = 15 - (py * 4) - px; //11 10 9 8 break; // 7 6 5 4 // 3 2 1 0 case 3: // 270 degrees // 3 7 11 15 pi = 3 - py + (px * 4); // 2 6 10 14 break; // 1 5 9 13 } // 0 4 8 12 return pi; } bool DoesPieceFit(int nTetromino, int nRotation, int nPosX, int nPosY) { // All Field cells >0 are occupied for (int px = 0; px < 4; px++) for (int py = 0; py < 4; py++) { // Get index into piece int pi = Rotate(px, py, nRotation); // Get index into field int fi = (nPosY + py) * nFieldWidth + (nPosX + px); // Check that test is in bounds. Note out of bounds does // not necessarily mean a fail, as the long vertical piece // can have cells that lie outside the boundary, so we'll // just ignore them if (nPosX + px >= 0 && nPosX + px < nFieldWidth) { if (nPosY + py >= 0 && nPosY + py < nFieldHeight) { // In Bounds so do collision check if (tetromino[nTetromino][pi] != L'.' && pField[fi] != 0) return false; // fail on first hit } } } return true; } int main() { // Create Screen Buffer wchar_t *screen = new wchar_t[nScreenWidth*nScreenHeight]; for (int i = 0; i < nScreenWidth*nScreenHeight; i++) screen[i] = L' '; HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL); SetConsoleActiveScreenBuffer(hConsole); DWORD dwBytesWritten = 0; tetromino[0].append(L"..X...X...X...X."); // Tetronimos 4x4 tetromino[1].append(L"..X..XX...X....."); tetromino[2].append(L".....XX..XX....."); tetromino[3].append(L"..X..XX..X......"); tetromino[4].append(L".X...XX...X....."); tetromino[5].append(L".X...X...XX....."); tetromino[6].append(L"..X...X..XX....."); pField = new unsigned char[nFieldWidth*nFieldHeight]; // Create play field buffer for (int x = 0; x < nFieldWidth; x++) // Board Boundary for (int y = 0; y < nFieldHeight; y++) pField[y*nFieldWidth + x] = (x == 0 || x == nFieldWidth - 1 || y == nFieldHeight - 1) ? 9 : 0; // Game Logic bool bKey[4]; int nCurrentPiece = 0; int nCurrentRotation = 0; int nCurrentX = nFieldWidth / 2; int nCurrentY = 0; int nSpeed = 20; int nSpeedCount = 0; bool bForceDown = false; bool bRotateHold = true; int nPieceCount = 0; int nScore = 0; vector vLines; bool bGameOver = false; while (!bGameOver) // Main Loop { // Timing ======================= this_thread::sleep_for(50ms); // Small Step = 1 Game Tick nSpeedCount++; bForceDown = (nSpeedCount == nSpeed); // Input ======================== for (int k = 0; k < 4; k++) // R L D Z bKey[k] = (0x8000 & GetAsyncKeyState((unsigned char)("\x27\x25\x28Z"[k]))) != 0; // Game Logic =================== // Handle player movement nCurrentX += (bKey[0] && DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX + 1, nCurrentY)) ? 1 : 0; nCurrentX -= (bKey[1] && DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX - 1, nCurrentY)) ? 1 : 0; nCurrentY += (bKey[2] && DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY + 1)) ? 1 : 0; // Rotate, but latch to stop wild spinning if (bKey[3]) { nCurrentRotation += (bRotateHold && DoesPieceFit(nCurrentPiece, nCurrentRotation + 1, nCurrentX, nCurrentY)) ? 1 : 0; bRotateHold = false; } else bRotateHold = true; // Force the piece down the playfield if it's time if (bForceDown) { // Update difficulty every 50 pieces nSpeedCount = 0; nPieceCount++; if (nPieceCount % 50 == 0) if (nSpeed >= 10) nSpeed--; // Test if piece can be moved down if (DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY + 1)) nCurrentY++; // It can, so do it! else { // It can't! Lock the piece in place for (int px = 0; px < 4; px++) for (int py = 0; py < 4; py++) if (tetromino[nCurrentPiece][Rotate(px, py, nCurrentRotation)] != L'.') pField[(nCurrentY + py) * nFieldWidth + (nCurrentX + px)] = nCurrentPiece + 1; // Check for lines for (int py = 0; py < 4; py++) if(nCurrentY + py < nFieldHeight - 1) { bool bLine = true; for (int px = 1; px < nFieldWidth - 1; px++) bLine &= (pField[(nCurrentY + py) * nFieldWidth + px]) != 0; if (bLine) { // Remove Line, set to = for (int px = 1; px < nFieldWidth - 1; px++) pField[(nCurrentY + py) * nFieldWidth + px] = 8; vLines.push_back(nCurrentY + py); } } nScore += 25; if(!vLines.empty()) nScore += (1 << vLines.size()) * 100; // Pick New Piece nCurrentX = nFieldWidth / 2; nCurrentY = 0; nCurrentRotation = 0; nCurrentPiece = rand() % 7; // If piece does not fit straight away, game over! bGameOver = !DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY); } } // Display ====================== // Draw Field for (int x = 0; x < nFieldWidth; x++) for (int y = 0; y < nFieldHeight; y++) screen[(y + 2)*nScreenWidth + (x + 2)] = L" ABCDEFG=#"[pField[y*nFieldWidth + x]]; // Draw Current Piece for (int px = 0; px < 4; px++) for (int py = 0; py < 4; py++) if (tetromino[nCurrentPiece][Rotate(px, py, nCurrentRotation)] != L'.') screen[(nCurrentY + py + 2)*nScreenWidth + (nCurrentX + px + 2)] = nCurrentPiece + 65; // Draw Score swprintf_s(&screen[2 * nScreenWidth + nFieldWidth + 6], 16, L"SCORE: %8d", nScore); // Animate Line Completion if (!vLines.empty()) { // Display Frame (cheekily to draw lines) WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten); this_thread::sleep_for(400ms); // Delay a bit for (auto &v : vLines) for (int px = 1; px < nFieldWidth - 1; px++) { for (int py = v; py > 0; py--) pField[py * nFieldWidth + px] = pField[(py - 1) * nFieldWidth + px]; pField[px] = 0; } vLines.clear(); } // Display Frame WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten); } // Oh Dear CloseHandle(hConsole); cout << "Game Over!! Score:" << nScore << endl; system("pause"); return 0; }