parent
e23fa0fe92
commit
e644618355
@ -0,0 +1,270 @@ |
||||
/*
|
||||
OneLoneCoder.com - Command Line Tetris |
||||
"Put Your Money Where Your Mouth Is" - @Javidx9 |
||||
|
||||
Disclaimer |
||||
~~~~~~~~~~ |
||||
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. BUT, you acknowledge that I am not responsible for anything |
||||
bad that happens as a result of your actions. However, if good stuff happens, I |
||||
would appreciate a shout out, or at least give the blog some publicity for me. |
||||
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: |
||||
~~~~~~ |
||||
xxxxxxx |
||||
|
||||
Last Updated: 30/03/2017 |
||||
*/ |
||||
|
||||
#include <iostream> |
||||
#include <thread> |
||||
#include <vector> |
||||
using namespace std; |
||||
|
||||
#include <stdio.h> |
||||
#include <Windows.h> |
||||
|
||||
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<int> 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; |
||||
} |
Loading…
Reference in new issue