我正在为我的第一年工作做一个简单的糖果粉碎游戏。
我正处于这个阶段,一旦程序执行,我需要在板的中心( board[5][5]
)上显示我自制的简单标记(*框由’|’和’_’*组成) 。
这是当前的代码:
#include #include #include //FUNCTION: Draw the Board int drawBoard() { //Declare array size int board[9][9]; //initialize variables int rows, columns, randomNumber, flag; //random number seed generator srand(time(NULL)); for ( rows = 0 ; rows < 9 ; rows++ ) { for ( columns = 0 ; columns < 9 ; columns++ ) { flag = 0; do { //generate random numbers from 2 - 8 randomNumber = rand() %7 + 2; board[rows][columns] = randomNumber; //Checks for 2 adjacent numbers. if ( board[rows][columns] == board[rows - 1][columns] || board[rows][columns] == board[rows][columns - 1] ) { flag = 0; continue; } else { flag = 1; printf( " %d ", board[rows][columns] ); } } while ( flag == 0 ); }//end inner for-loop printf("nn"); }//end outer for-loop //call FUNCTION marker() to display marker around board[5][5] marker( board[5][5] ); }//end FUNCTION drawBoard //FUNCTION: Mark the surrounding of the number with "|" and "_" at board[5][5] void marker( int a ) { printf( " _ n" ); printf( "|%c|n", a ); printf( " _ n" ); } int main() { drawBoard(); }
在函数drawBoard()
的末尾,我放置了代码marker( board[5][5] )
。
这应该在坐标board[5][5]
打印的数字周围打印标记..但由于某种原因,它会在打印完成后立即显示。
那么为什么不在那个坐标上打印,尽管我在board[5][5]
指定了它?
这可能是什么问题?
因此,在您的标记function中,您需要传递电路板和要打印的坐标
void marker( int x, int y, int** board ) { board[x][y-1]="_"; board[x-1][y]="|"; board[x+1][y]="|"; board[x][y+1]="_"; }
然后在调用marker(5,5,board)之后再次调用drawboard
我的代码有点偏,但这是逻辑,除了你需要检查标记位于电路板边缘的情况
换句话说,你需要保持板周围,任何时候你改变它,清除屏幕并再次打印整个板。
你正在这样做的方式没有持久的绘图。 您只是直接打印到shell /命令提示符。 你尝试做事的方式是行不通的。 在绘制完成后,您无法编辑绘制到提示的内容,您需要基本清除屏幕然后再次绘制,但需要使用指定的制造商。
我不知道你是否能够在你的任务中使用库,但是一个非常好的库可以让你做的就是ncurses
编辑 完全重写答案
在CMD中绘制彼此之上的东西
好吧,我在工作中遇到了一些停机时间,所以我写了一个项目来做你需要的事情,我将发布代码并解释它的作用以及为什么你需要它。
首先,您需要基本上是渲染缓冲区或渲染上下文。 无论何时使用OpenGL等图形API进行编程,您都不能直接渲染到屏幕上,而是将每个对象渲染到缓冲区,栅格化内容并将其转换为像素。 一旦它处于该forms,API就会将渲染的图片推到屏幕上。 我们将采用类似的方法,而不是绘制到GPU上的像素缓冲区,我们将绘制到字符缓冲区。 将每个角色视为屏幕上的像素。
这是一个完整来源的pastebin: 完整的项目源
RenderContext
我们这样做的类将是RenderContext
类。 它有包含宽度和高度的字段以及一个chars
数组和一个特殊字符,只要我们清除它就会填充缓冲区。
这个类只包含一个数组和函数,让我们渲染它。 它确保当我们绘制它时,我们在界限内。 对象可能尝试在剪切空间之外绘制(在屏幕外)。 但是,无论如何绘制都会被丢弃。
class RenderContext { private: int m_width, m_height; // Width and Height of this canvas char* m_renderBuffer; // Array to hold "pixels" of canvas char m_clearChar; // What to clear the array to public: RenderContext() : m_width(50), m_height(20), m_clearChar(' ') { m_renderBuffer = new char[m_width * m_height]; } RenderContext(int width, int height) : m_width(width), m_height(height), m_clearChar(' ') { m_renderBuffer = new char[m_width * m_height]; } ~RenderContext(); char getContentAt(int x, int y); void setContentAt(int x, int y, char val); void setClearChar(char clearChar); void render(); void clear(); };
这个类的两个最重要的函数是setContentAt
和render
setContentAt
是对象调用以填充“像素”值的内容。 为了使它更灵活,我们的类使用指向字符数组的指针而不是直数组(甚至是二维数组)。 这允许我们在运行时设置canvas的大小。 因此,我们用x + (y * m_width)
访问这个数组的元素,它取代了二维解引用,例如arr[i][j]
// Fill a specific "pixel" on the canvas void RenderContext::setContentAt(int x, int y, char val) { if (((0 <= x) && (x < m_width)) && ((0 <= y) && (y < m_height))) { m_renderBuffer[(x + (y * m_width))] = val; } }
render
是实际绘制到提示的内容。 它所做的只是迭代它缓冲区中的所有“像素”并将它们放在屏幕上然后移动到下一行。
// Paint the canvas to the shell void RenderContext::render() { int row, column; for (row = 0; row < m_height; row++) { for (column = 0; column < m_width; column++) { printf("%c", getContentAt(column, row)); } printf("n"); } }
I_Drawable
我们的下一个类是一个Interface
,它允许我们与他们可以绘制到RenderContext的对象签订合同。 它是纯虚拟的,因为我们不希望实际能够实例化它,我们只想从中派生它。 它的唯一function是draw
,它接受RenderContext。 派生类使用此调用来接收RenderContext,然后使用RenderContext的setContentAt将“像素”放入缓冲区。
class I_Drawable { public: virtual void draw(RenderContext&) = 0; };
GameBoard
实现I_Drawable的第一个类是GameBoard类,因此能够呈现给我们的RenderContext。 这是大多数逻辑的用武之地。它有宽度,高度和整数数组的字段,用于保存电路板上元素的值。 它还有两个其他的间距字段。 因为当您使用代码绘制电路板时,每个元素之间都有空格。 我们不需要将它结合到电路板的底层结构中,我们只需要在绘制时使用它们。
class GameBoard : public I_Drawable { private: int m_width, m_height; // Width and height of the board int m_verticalSpacing, m_horizontalSpacing; // Spaces between each element on the board Marker m_marker; // The cursor that will draw on this board int* m_board; // Array of elements on this board void setAtPos(int x, int y, int val); void generateBoard(); public: GameBoard() : m_width(10), m_height(10), m_verticalSpacing(5), m_horizontalSpacing(3), m_marker(Marker()) { m_board = new int[m_width * m_height]; generateBoard(); } GameBoard(int width, int height) : m_width(width), m_height(height), m_verticalSpacing(5), m_horizontalSpacing(3), m_marker(Marker()) { m_board = new int[m_width * m_height]; generateBoard(); } ~GameBoard(); int getAtPos(int x, int y); void draw(RenderContext& renderTarget); void handleInput(MoveDirection moveDirection); int getWidth(); int getHeight(); };
它的关键function是generateBoard
, handleInput
和派生的虚拟函数draw
。 但是,请注意在其构造函数中它创建一个新的int数组并将其赋予其指针。 然后,每当电路板消失时,它的析构函数会自动删除分配的内存。
generateBoard
是我们用来实际创建电路板并用数字填充它的方法。 它将迭代板上的每个位置。 每次,它将直接查看左侧和上方的元素并存储它们。 然后它将生成一个随机数,直到它生成的数字与存储的任何一个元素都不匹配,然后它将数字存储在数组中。 我重写了这个以摆脱标志用法。 在构造类时调用此函数。
// Actually create the board void GameBoard::generateBoard() { int row, column, randomNumber, valToLeft, valToTop; // Iterate over all rows and columns for (row = 0; row < m_height; row++) { for (column = 0; column < m_width; column++) { // Get the previous elements valToLeft = getAtPos(column - 1, row); valToTop = getAtPos(column, row - 1); // Generate random numbers until we have one // that is not the same as an adjacent element do { randomNumber = (2 + (rand() % 7)); } while ((valToLeft == randomNumber) || (valToTop == randomNumber)); setAtPos(column, row, randomNumber); } } }
handleInput
是处理在板上移动光标的原因。 它基本上是一个免费赠品和你的下一步,让光标画在板上。 我需要一种方法来测试绘图。 它接受我们打开的枚举,知道将光标移动到下一个位置。 如果您想要在到达边缘时将光标环绕在电路板上,您可能希望在此处执行此操作。
void GameBoard::handleInput(MoveDirection moveDirection) { switch (moveDirection) { case MD_UP: if (m_marker.getYPos() > 0) m_marker.setYPos(m_marker.getYPos() - 1); break; case MD_DOWN: if (m_marker.getYPos() < m_height - 1) m_marker.setYPos(m_marker.getYPos() + 1); break; case MD_LEFT: if (m_marker.getXPos() > 0) m_marker.setXPos(m_marker.getXPos() - 1); break; case MD_RIGHT: if (m_marker.getXPos() < m_width - 1) m_marker.setXPos(m_marker.getXPos() + 1); break; } }
draw
非常重要,因为它是将数字输入RenderContext的原因。 总而言之,它迭代板上的每个元素,并在canvas上绘制正确的位置,将元素放在正确的“像素”下。 这是我们合并间距的地方。 另外,请注意我们在此函数中渲染光标。
这是一个选择问题,但你可以在GameBoard类之外存储一个标记并在主循环中自己渲染它(这将是一个很好的选择,因为它放松了GameBoard类和Marker类之间的耦合。但是,从那以后它们是相当耦合的,我选择让GameBoard渲染它。如果我们使用场景图,就像我们可能会使用更复杂的场景/游戏,Marker可能会成为GameBoard的子节点,所以它会与此类似通过不在GameBoard类中存储显式标记,实现但更通用。
// Function to draw to the canvas void GameBoard::draw(RenderContext& renderTarget) { int row, column; char buffer[8]; // Iterate over every element for (row = 0; row < m_height; row++) { for (column = 0; column < m_width; column++) { // Convert the integer to a char sprintf(buffer, "%d", getAtPos(column, row)); // Set the canvas "pixel" to the char at the // desired position including the padding renderTarget.setContentAt( ((column * m_verticalSpacing) + 1), ((row * m_horizontalSpacing) + 1), buffer[0]); } } // Draw the marker m_marker.draw(renderTarget); }
Marker
说到Marker
课程,现在让我们来看看。 Marker类实际上与GameBoard类非常相似。 然而,它缺乏GameBoard的许多逻辑,因为它不需要担心板上的一堆元素。 重要的是绘制function。
class Marker : public I_Drawable { private: int m_xPos, m_yPos; // Position of cursor public: Marker() : m_xPos(0), m_yPos(0) { } Marker(int xPos, int yPos) : m_xPos(xPos), m_yPos(yPos) { } void draw(RenderContext& renderTarget); int getXPos(); int getYPos(); void setXPos(int xPos); void setYPos(int yPos); };
draw
简单地将四个符号放在RenderContext上,以勾勒出板上所选的元素。 请注意,Marker对GameBoard类没有任何线索。 它没有引用它,它不知道它有多大,或它拥有什么元素。 你应该注意到,我懒得并没有取出那些依赖于GameBoard所具有的填充的硬编码偏移。 您应该为此实现更好的解决方案,因为如果更改GameBoard类中的填充,则光标将关闭。
除此之外,无论何时绘制符号,它们都会覆盖ContextBuffer中的任何内容。 这很重要,因为问题的关键是如何将光标绘制在GameBoard之上。 这也涉及绘制顺序的重要性。 让我们说每当我们绘制GameBoard时,我们在每个元素之间画一个'='。 如果我们首先绘制光标然后再绘制板,GameBoard会将光标拉过来使其不可见。
如果这是一个更复杂的场景,我们可能不得不做一些像使用深度缓冲区那样记录元素的z-index
的东西。 然后每当我们绘制时,我们都会检查并查看新元素的z-index是否比RenderContext缓冲区中已有的更接近或更远。 根据这一点,我们可能会完全跳过“像素”。
但是我们不这样做,所以请注意订购您的抽奖电话!
// Draw the cursor to the canvas void Marker::draw(RenderContext& renderTarget) { // Adjust marker by board spacing // (This is kind of a hack and should be changed) int tmpX, tmpY; tmpX = ((m_xPos * 5) + 1); tmpY = ((m_yPos * 3) + 1); // Set surrounding elements renderTarget.setContentAt(tmpX - 0, tmpY - 1, '-'); renderTarget.setContentAt(tmpX - 1, tmpY - 0, '|'); renderTarget.setContentAt(tmpX - 0, tmpY + 1, '-'); renderTarget.setContentAt(tmpX + 1, tmpY - 0, '|'); }
CmdPromptHelper
我要谈的最后一堂课是CmdPromptHelper。 你原来的问题中没有这样的东西。 但是,您需要尽快担心。 这个类在Windows上也很有用,所以如果你在linux / unix上,你需要担心自己处理shell的绘图。
class CmdPromptHelper { private: DWORD inMode; // Attributes of std::in before we change them DWORD outMode; // Attributes of std::out before we change them HANDLE hstdin; // Handle to std::in HANDLE hstdout; // Handle to std::out public: CmdPromptHelper(); void reset(); WORD getKeyPress(); void clearScreen(); };
每个function都很重要。 构造函数获取当前命令提示符的std::in
和std::out
的句柄。 getKeyPress
函数返回用户按下的键(忽略键入事件)。 clearScreen
函数清除提示符(实际上,它实际上移动了提示中已有的任何内容)。
getKeyPress
只是确保你有一个句柄,然后读取已输入控制台的内容。 它确保无论它是什么,它都是一把钥匙,它正在被压下来。 然后它返回密钥代码作为Windows特定的枚举,通常以VK_
开头。
// See what key is pressed by the user and return it WORD CmdPromptHelper::getKeyPress() { if (hstdin != INVALID_HANDLE_VALUE) { DWORD count; INPUT_RECORD inrec; // Get Key Press ReadConsoleInput(hstdin, &inrec, 1, &count); // Return key only if it is key down if (inrec.Event.KeyEvent.bKeyDown) { return inrec.Event.KeyEvent.wVirtualKeyCode; } else { return 0; } // Flush input FlushConsoleInputBuffer(hstdin); } else { return 0; } }
clearScreen
有点欺骗。 您会认为它会清除提示中的文本。 据我所知,它没有。 我很确定它实际上将所有内容都移动了,然后将大量字符写入提示,使其看起来像屏幕被清除。
这个function带来的一个重要概念是缓冲渲染的想法。 同样,如果这是一个更强大的系统,我们希望实现双缓冲的概念,这意味着渲染到一个不可见的缓冲区并等待所有绘图完成,然后将不可见缓冲区与可见缓冲区交换。 这样可以更清晰地看到渲染,因为我们在渲染时看不到东西。 我们在这里做事的方式,我们看到渲染过程就在我们面前。 这不是一个主要问题,它有时看起来很丑陋。
// Flood the console with empty space so that we can // simulate single buffering (I have no idea how to double buffer this) void CmdPromptHelper::clearScreen() { if (hstdout != INVALID_HANDLE_VALUE) { CONSOLE_SCREEN_BUFFER_INFO csbi; DWORD cellCount; // How many cells to paint DWORD count; // How many we painted COORD homeCoord = {0, 0}; // Where to put the cursor to clear // Get console info if (!GetConsoleScreenBufferInfo(hstdout, &csbi)) { return; } // Get cell count cellCount = csbi.dwSize.X * csbi.dwSize.Y; // Fill the screen with spaces FillConsoleOutputCharacter( hstdout, (TCHAR) ' ', cellCount, homeCoord, &count ); // Set cursor position SetConsoleCursorPosition(hstdout, homeCoord); } }
main
你需要担心的最后一件事是如何使用所有这些东西。 这就是主要进入的地方。你需要一个游戏循环。 游戏循环可能是任何游戏中最重要的事情。 你看的任何游戏都会有游戏循环。
这个想法是:
这个计划也不例外。 它首先要做的是创建一个GameBoard和一个RenderContext。 它还创建了一个CmdPromptHelper,它允许与命令提示符进行交互。 之后,它开始循环并让循环继续,直到我们达到退出条件(对于我们正在按下转义)。 我们可以有一个单独的类或函数do dispatch输入,但由于我们只是将输入分派给另一个输入处理程序,我将它保存在主循环中。 在您获得输入后,您将发送到GameBoard,相应地改变自己。 下一步是清除RenderContext和屏幕/提示。 如果没有按下escape,则重新运行循环。
int main() { WORD key; GameBoard gb(5, 5); RenderContext rc(25, 15); CmdPromptHelper cph; do { gb.draw(rc); rc.render(); key = cph.getKeyPress(); switch (key) { case VK_UP: gb.handleInput(MD_UP); break; case VK_DOWN: gb.handleInput(MD_DOWN); break; case VK_LEFT: gb.handleInput(MD_LEFT); break; case VK_RIGHT: gb.handleInput(MD_RIGHT); break; } rc.clear(); cph.clearScreen(); } while (key != VK_ESCAPE); }
在考虑了所有这些事情后,您就会明白为什么以及在哪里需要绘制光标。 这不是一个接一个地调用函数的问题,你需要合成你的绘制。 你不能只绘制GameBoard然后绘制标记。 至少没有命令提示符。 我希望这有帮助。 它肯定减轻了工作中的停机时间。
以上就是c/c++开发分享尽管指定,打印也不会在打印数组中显示相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/523187.html