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

时间:2013-10-23 20:17:47

标签: c arrays function

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

我正处于这个阶段,我需要在程序开始时在棋盘中心(board[5][5])上显示我自制的简单标记(*由'|'和'_'*组成的框)执行。

以下是当前代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//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]指定了它?

这可能是什么问题?

2 个答案:

答案 0 :(得分:0)

所以在你的标记功能中你需要传递你想要打印的棋盘和坐标

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

我的代码有点偏,但这是逻辑,除了你需要检查标记位于电路板边缘的情况

换句话说,你需要保持一块板,并且每当你改变它时,清除屏幕并再次打印整个板。

答案 1 :(得分:0)

您执行此操作的方式没有持久性绘图。您只是直接打印到shell /命令提示符。你尝试做事的方式是行不通的。在绘制完成后,您无法编辑绘制到提示的内容,您需要基本清除屏幕,然后再次使用指定的制作者进行绘制。

我不知道你是否能够在作业中使用图书馆,但是一个非常好的图书馆可以让你做ncurses

编辑 完全重写答案


在CMD中将事物叠加在一起

好吧,我在工作中遇到了一些停机时间,所以我写了一个项目来做你需要的事情,我将发布代码并解释它的作用以及为什么你需要它。

首先,你需要一个基本上是渲染缓冲区或渲染上下文。无论何时使用OpenGL等图形API进行编程,都不能直接渲染到屏幕上,而是将每个对象渲染到一个缓冲区中,该缓冲区会对内容进行光栅化并将其转换为像素。一旦它以该形式出现,API就会将渲染的图片推到屏幕上。我们将采用类似的方法,而不是绘制到GPU上的像素缓冲区,我们将绘制到字符缓冲区。将每个角色视为屏幕上的一个像素。

以下是完整来源的代码: Complete Source of Project


<强> 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 是对象调用以填充&#34;像素&#34;值。为了使它更灵活,我们的类使用指向字符数组的指针而不是直数组(甚至是二维数组)。这允许我们在运行时设置画布的大小。因此,我们使用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 是实际绘制到提示的内容。它所做的就是迭代所有的像素&#34;在它的缓冲区中,将它们放在屏幕上,然后移动到下一行。

// 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的对象签订合同。它是纯虚拟的,因为我们不想实际能够实例化它,我们只想从它派生。它唯一的功能是 draw ,它接受RenderContext。派生类使用此调用来接收RenderContext,然后使用RenderContext的setContentAt来放置&#34;像素&#34;进入缓冲区。

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();
};

它的关键功能是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的原因。总而言之,它遍历棋盘上的每个元素,并在画布上绘制正确的位置,将元素放置在正确的&#34;像素&#34;之下。这是我们合并间距的地方。另外,请注意我们在此函数中渲染光标。

这是一个选择问题,但你可以在GameBoard类之外存储一个标记并在主循环中自己渲染(这将是一个很好的选择,因为它放松了GameBoard类和标记类。然而,由于它们 相当耦合,我选择让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的许多逻辑,因为它不需要担心电路板上的一堆元素。重要的是绘制功能。

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时,我们都会画出一个&#39; =&#39;每个元素之间。如果我们先将光标拉到板上,那么GameBoard会将光标拉过来使其不可见。

如果这是一个更复杂的场景,我们可能不得不做一些奇特的事情,比如使用一个记录元素z-index的深度缓冲区。然后每当我们绘制时,我们都会检查并查看新元素的z-index是否比RenderContext缓冲区中已有的更接近或更远。根据这一点,我们可能会跳过绘制&#34;像素&#34;共。

我们不是,所以请注意订购您的抽奖电话!

// 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();
};

每个功能都很重要。构造函数获取当前命令提示符的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 有点欺骗。您会认为它会清除提示中的文本。据我所知,它没有。我很确定它实际上将所有内容都移动了,然后在提示符中写入了大量字符,使其看起来像屏幕已被清除。

这个功能带来的一个重要概念是缓冲渲染的想法。同样,如果这是一个更强大的系统,我们希望实现双缓冲的概念,这意味着渲染到一个不可见的缓冲区并等待所有绘图完成,然后将不可见缓冲区与可见缓冲区交换。这样可以更清晰地看到渲染,因为我们在渲染时看不到东西。我们在这里做事的方式,我们看到渲染过程发生在我们面前。这不是一个主要的问题,它有时看起来很丑陋。

// 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
  5. 这个程序没有什么不同。它首先要做的是创建一个GameBoard和一个RenderContext。它还创建了一个CmdPromptHelper,它允许与命令提示符进行交互。之后,它开始循环并让循环继续,直到我们达到退出条件(对于我们按下逃生)。我们可以有一个单独的类或函数do dispatch输入,但由于我们只是将输入分派给另一个输入处理程序,我将它保存在主循环中。在您获得输入后,您将发送到GameBoard,相应地改变它自己。下一步是清除RenderContext和屏幕/提示。如果没有按下逃生,则重新运行循环。

    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然后绘制标记。至少没有命令提示符。我希望这有帮助。这无疑减轻了工作中的停工时间。