尽管指定,打印也不会在打印数组中显示

我正在为我的第一年工作做一个简单的糖果粉碎游戏。

我正处于这个阶段,一旦程序执行,我需要在板的中心( 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("\n\n"); }//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(); }; 

这个类的两个最重要的函数是setContentAtrender

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是generateBoardhandleInput和派生的虚拟函数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::instd::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

你需要担心的最后一件事是如何使用所有这些东西。 这就是主要进入的地方。你需要一个游戏循环。 游戏循环可能是任何游戏中最重要的事情。 你看的任何游戏都会有游戏循环。

这个想法是:

  1. 在屏幕上显示内容
  2. 读输入
  3. 处理输入
  4. GOTO 1

这个计划也不例外。 它首先要做的是创建一个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然后绘制标记。 至少没有命令提示符。 我希望这有帮助。 它肯定减轻了工作中的停机时间。