Kolega mnie wyzwał i musiałem zrobić Timbermana w c++ w ASCII. Pod koniec aż się zaskoczyłem, bo wyszło 440 linijek kodu (plus 60 na animacje i postać drwala), ale w sumie starałem się to zrobić jak najprościej, a nie jak najkrócej. Dodałem nawet "animacje" spadania gałęzi oraz oldschoolowy spadający napis "game over".
Główna pętla gry troche brzydka, bo pisałem ją bylejak. Z bugów i niedociągnięć to zauważyłem, że spadające gałęzie nie nadążają i są doganiane przez gałęzie na drzewie gdy przytrzymuje jakiś przycisk oraz zapomniałem wyczyścić bufora, którego używa getch() z conio.h przez co na ekranie wyników jak się pospami trochę A i D to wszystkie te zaległe wciśnięcia zostaną natychmiostowo przetworzone i prawdopodobnie zginiemy.
Zasady gry: unikaj gałęzi oraz zetnij jak najwiecej drzewa zanim skończy sie czas (każde ścięcie go troche dodaje)
Screen: https://ibb.co/C5dKzcX
Kod:
Main.cpp
#include <Windows.h>
#include <iostream>
#include <conio.h>
#include <string>
#include <random>
#include <ctime>
#include "Graphics.h"
std::random_device rd;
std::mt19937 mt(rd());
int Random(int min, int max)
{
std::uniform_int_distribution<int> distibution(min, max);
return distibution(mt);
}
//====================================
const HANDLE CONSOLE_HANDLE = GetStdHandle(STD_OUTPUT_HANDLE);
void ResizeConsole(short width, short height, short fontWidth, short fontHeight)
{
COORD size = { width, height };
SMALL_RECT bounds = { 0, 0, width - 1, height - 1 };
SMALL_RECT smallRect = { 0,0,1,1 };
SetConsoleWindowInfo(CONSOLE_HANDLE, TRUE, &smallRect);
SetConsoleScreenBufferSize(CONSOLE_HANDLE, size);
SetConsoleActiveScreenBuffer(CONSOLE_HANDLE);
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof(cfi);
cfi.nFont = 0;
cfi.dwFontSize.X = fontWidth;
cfi.dwFontSize.Y = fontHeight;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_NORMAL;
wcscpy_s(cfi.FaceName, L"Lucida Console");
SetCurrentConsoleFontEx(CONSOLE_HANDLE, false, &cfi);
SetConsoleWindowInfo(CONSOLE_HANDLE, TRUE, &bounds);
}
void SetConsoleCursorVisible(bool state)
{
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(CONSOLE_HANDLE, &cursorInfo);
cursorInfo.bVisible = state;
SetConsoleCursorInfo(CONSOLE_HANDLE, &cursorInfo);
}
char GetInput()
{
while (!_kbhit())
return NULL;
return _getch();
}
char GetChoice()
{
while (!_kbhit())
return _getch();
return _getch();
}
//====================================
const unsigned SCREEN_SIZE = 42;
char buffer[SCREEN_SIZE * SCREEN_SIZE];
char screen[SCREEN_SIZE * SCREEN_SIZE];
void ClearBuffer()
{
std::memset(&buffer, NULL, SCREEN_SIZE * SCREEN_SIZE * sizeof(char));
}
void ClearScreen()
{
std::memset(&screen, NULL, SCREEN_SIZE * SCREEN_SIZE * sizeof(char));
}
void Draw(int x, int y, char ascii)
{
buffer[y * SCREEN_SIZE + x] = ascii;
}
void Draw(int x, int y, const char* text, int size)
{
for (int i = 0; i < size; i++, x++)
buffer[y * SCREEN_SIZE + x] = text[i];
}
void DrawCharArray(const char array[], int x, int y)
{
int posX = x, posY = y;
for (int i = 0; i < strlen(array);)
{
while (array[i] != '\n')
{
Draw(posX++, posY, array[i++]);
}
posX = x;
posY++;
i++;
}
}
void DrawScreen()
{
for (short y = 0; y < SCREEN_SIZE; y++)
{
for (short x = 0; x < SCREEN_SIZE; x++)
{
unsigned index = y * SCREEN_SIZE + x;
if (buffer[index] != screen[index])
{
screen[index] = buffer[index];
COORD position = { x, y };
SetConsoleCursorPosition(CONSOLE_HANDLE, position);
putchar(buffer[index]);
}
}
}
ClearBuffer();
}
//====================================
enum Side
{
Left,
Right
};
struct Branch
{
Side side;
unsigned height;
struct Graphics
{
unsigned x, y;
char ascii;
};
std::vector<Graphics> graphic;
};
const float TIME_PER_SWING = 0.20;
const float STARTING_TIME= 5.0;
const float MAX_TIME = 10;
const unsigned TREE_WIDTH = 2;
const unsigned MIN_BRANCH_SIZE = 3;
const unsigned MAX_BRANCH_SIZE = 6;
const unsigned BRANCH_PROBABILITY = 60;
const unsigned ANIMATION_TIME_MS = 100;
const unsigned ANIMATION_FALLING_BRANCH_TIME_MS = 50;
int animationTimer = 0;
Side currentSide = Left;
std::vector<Branch> branches;
std::vector<Branch> fallingBranches;
std::vector<int> fallingBranchesTimers;
void DrawTree()
{
unsigned x = (SCREEN_SIZE / 2) - (TREE_WIDTH / 2);
for (unsigned i = 0; i < SCREEN_SIZE; i++)
for (unsigned j = 1; j <= TREE_WIDTH; j++)
Draw(x + j, i, '|');
}
void GenerateBranch(unsigned height)
{
Side side = Random(0, 1) ? Left : Right;
for (int i = branches.size() - 1; i >= 0; i--)
{
if (branches[i].side == side)
{
if (height + 4 >= branches[i].height) //check if last branch on the same side wont collide with our branch (i assume that we are generating from bottom to top)
return;
}
else
if (height == branches[i].height)
return;
}
if (Random(0, 100) <= BRANCH_PROBABILITY)
{
Branch branch;
unsigned branchLenght = Random(MIN_BRANCH_SIZE, MAX_BRANCH_SIZE);
branch.height = height;
if (side == Left)
{
branch.side = side;
unsigned x = (SCREEN_SIZE / 2) - (TREE_WIDTH / 2); //tree is drawn from left
for (unsigned i = 0; i < branchLenght; i++)
branch.graphic.push_back({ x - i, height, '=' });
branch.graphic.push_back({ x - Random(0, branchLenght - 1), height - 1, '|' });
branch.graphic.push_back({ x - Random(0, branchLenght - 1), height + 1, '|' });
}
else
{
branch.side = side;
unsigned x = (SCREEN_SIZE / 2) + TREE_WIDTH;
for (unsigned i = 0; i < branchLenght; i++)
branch.graphic.push_back({ x + i, height, '=' });
branch.graphic.push_back({ x + Random(0, branchLenght - 1), height - 1, '|' });
branch.graphic.push_back({ x + Random(0, branchLenght - 1), height + 1, '|' });
}
branches.push_back(branch);
}
}
void DrawBranches()
{
for (const auto& branch : branches)
for (const auto& graphic : branch.graphic)
Draw(graphic.x, graphic.y, graphic.ascii);
for (const auto& fallingBranch : fallingBranches)
for (const auto& graphic : fallingBranch.graphic)
Draw(graphic.x, graphic.y, graphic.ascii);
}
void MoveBranches()
{
for (auto& branch : branches)
{
branch.height++;
for (auto& graphic : branch.graphic)
graphic.y++;
}
}
void DrawLumberjack()
{
if (currentSide == Left)
{
if (clock() - animationTimer >= ANIMATION_TIME_MS)
{
DrawCharArray(LUMBERJACK_STAYING_RIGHT, (SCREEN_SIZE / 2) - (TREE_WIDTH / 2) - LUMBERJACK_WIDTH, SCREEN_SIZE - LUMBERJACK_HEIGHT);
}
else
{
DrawCharArray(LUMBERJACK_SWING_RIGHT, (SCREEN_SIZE / 2) - (TREE_WIDTH / 2) - LUMBERJACK_WIDTH, SCREEN_SIZE - LUMBERJACK_HEIGHT);
}
}
else
{
if (clock() - animationTimer >= ANIMATION_TIME_MS)
{
DrawCharArray(LUMBERJACK_STAYING_LEFT, (SCREEN_SIZE / 2) + TREE_WIDTH, SCREEN_SIZE - LUMBERJACK_HEIGHT);
}
else
{
DrawCharArray(LUMBERJACK_SWING_LEFT, (SCREEN_SIZE / 2) + TREE_WIDTH, SCREEN_SIZE - LUMBERJACK_HEIGHT);
}
}
}
void UpdateFallingBranches()
{
for (int i = 0; i < fallingBranches.size(); i++)
{
if (fallingBranches[i].graphic.size())
{
if (clock() - fallingBranchesTimers[i] >= ANIMATION_FALLING_BRANCH_TIME_MS)
{
for (int j = 0; j < fallingBranches[i].graphic.size(); j++)
{
if (fallingBranches[i].graphic[j].y >= SCREEN_SIZE - 1)
{
fallingBranches[i].graphic.erase(fallingBranches[i].graphic.begin() + j);
}
else
{
fallingBranches[i].graphic[j].y++;
}
}
fallingBranchesTimers[i] = clock();
}
}
else
{
fallingBranches.erase(fallingBranches.begin() + i);
}
}
}
int main()
{
ResizeConsole(SCREEN_SIZE, SCREEN_SIZE, 16, 16);
SetConsoleCursorVisible(false);
ClearBuffer();
ClearScreen();
float gameTimer = 0;
float timeLeft = STARTING_TIME;
unsigned score = 0;
bool gameOver = false;
for (int i = SCREEN_SIZE - LUMBERJACK_HEIGHT - 5; i >= 1; i--)
GenerateBranch(i);
while (!gameOver)
{
char input = GetInput();
if (input == 'a' || input == 'd')
{
currentSide = (input == 'a' ? Left : Right);
MoveBranches();
if (branches.size())
{
if (branches.front().height >= SCREEN_SIZE - LUMBERJACK_HEIGHT)
{
if (currentSide == branches.front().side)
{
gameOver = true;
}
else
{
fallingBranches.push_back(branches.front());
fallingBranchesTimers.push_back(clock());
branches.erase(branches.begin());
}
}
}
if (!gameOver)
{
if (timeLeft + TIME_PER_SWING >= MAX_TIME)
timeLeft = MAX_TIME;
else
timeLeft += TIME_PER_SWING;
score++;
animationTimer = clock();
GenerateBranch(1);
}
}
if (clock() - gameTimer >= 250)
{
timeLeft -= 0.250;
gameTimer = clock();
}
if (gameOver || timeLeft <= 0)
{
std::string gameOverString = "GAME OVER";
int x = SCREEN_SIZE / 2 - gameOverString.size() / 2, y = 0;
int timer = clock();
while (y < SCREEN_SIZE / 2)
{
if (clock() - timer >= 40)
{
y++;
timer = clock();
}
Draw(x, y, gameOverString.c_str(), gameOverString.size());
DrawScreen();
}
Sleep(1250);
system("cls");
std::cout << "Your score: " << score << "\nTry again? (Y/N)";
char choice = tolower(GetChoice());
while (choice != 'y' && choice != 'n')
choice = tolower(GetChoice());
if (choice == 'y')
{
branches.clear();
fallingBranches.clear();
fallingBranchesTimers.clear();
gameOver = false;
gameTimer = 0;
score = 0;
timeLeft = STARTING_TIME;
for (int i = SCREEN_SIZE - LUMBERJACK_HEIGHT - 5; i >= 1; i--)
GenerateBranch(i);
system("cls");
ClearScreen();
ClearBuffer();
}
}
UpdateFallingBranches();
Draw(0, 1, "Time left:", std::string("Time left:").size());
Draw(std::string("Time left: ").size(), 1, std::to_string(timeLeft).c_str(), 4);
Draw(0, 0, "Score: ", 7);
Draw(7, 0, std::to_string(score).c_str(), std::to_string(score).size());
DrawTree();
DrawBranches();
DrawLumberjack();
DrawScreen();
}
return 0;
}
Graphics.h
#pragma once
const unsigned LUMBERJACK_HEIGHT = 10;
const unsigned LUMBERJACK_WIDTH = 11;
const char LUMBERJACK_STAYING_RIGHT[] =
"\
___ [ ]\n\
/---\\ ||\n\
|O O| ||\n\
| U | ||\n\
\\_/ /\n\
/| \\/\n\
|| |\n\
|===|\n\
|| ||\n\
|| ||\n\
";
const char LUMBERJACK_SWING_RIGHT[] =
"\
___ \n\
/---\\ [ ]\n\
|O O| //\n\
| U | //\n\
\\_/ /\n\
/| \\/\n\
|| |\n\
|===|\n\
|| ||\n\
|| ||\n\
";
const char LUMBERJACK_STAYING_LEFT[] =
"\
[ ] ___ \n\
|| /---\\\n\
|| |O O|\n\
|| | U |\n\
\\ \\_/\n\
\\/ |\\\n\
| ||\n\
|===|\n\
|| ||\n\
|| ||\n\
";
const char LUMBERJACK_SWING_LEFT[] =
"\
___\n\
[ ] /---\\\n\
\\\\ |O O|\n\
\\\\ | U |\n\
\\ \\_/\n\
\\/ |\\\n\
| ||\n\
|===|\n\
|| ||\n\
|| ||\n\
";
Download(exe + kod): https://drive.google.com/file/d/1AMVtIE8Q93LSo8SkInwe1Gqj8iagEGE2/view