我正在尝试用C创建井字游戏,并且我具有创建游戏板的功能:
void createBoard(char*** board, int size)
{
int i, j;
*(board) = malloc(size * sizeof( char* ));
for(i = 0; i < size; i++)
{
*(board)[i] = malloc(size * sizeof(char));
}
printf("Default board:\n");
for(i = 0; i < size; i++)
{
for(j = 0; j < size; j++)
{
*(board)[i][j] = '-';
printf("%c", *(board)[i][j]);
printf(" ");
if((j + 1) % size == 0)
printf("\n");
}
}
}
在主函数中,我将一个字符的地址传递给此函数**:
int main()
{
int size;
char** board = NULL;
char userInp;
sizeOfBoard(&size);
createBoard(&board, size);
}
,并且我使用*(board)访问指针的值(首先是NULL),然后将malloc返回的地址分配给它,但是我不知道为什么它不起作用。有人可以帮我解决此问题。
答案 0 :(得分:2)
不要那样做。而是创建一个描述电路板和电路板状态的结构,然后使用该结构。在结构中,使用一维数组表示板状态:
typedef struct {
int rows;
int cols;
char *cell;
} board;
#define BOARD_INIT { 0, 0, NULL }
#define CELL(b, r, c) ((b).cell[(c) + (r)*(b).cols])
要创建木板,您可以使用例如
int create_board(board *b, const int rows, const int cols)
{
const size_t cells = (size_t)rows * (size_t)cols;
const size_t bytes = sizeof (board) + cells * sizeof (b->cell[0]);
if (!b) {
/* No board specified. */
errno = EINVAL;
return -1;
}
b->rows = 0;
b->cols = 0;
b->cell = NULL;
if (rows < 1 || cols < 1) {
/* Invalid size. */
errno = EINVAL;
return -1;
}
if ((size_t)(cells / (size_t)rows) != (size_t)cols ||
(size_t)((bytes - sizeof (board)) / cells) != sizeof (b->cell[0])) {
/* Board is too large. */
errno = ENOMEM;
return -1;
}
b->cell = malloc(bytes);
if (!b->cell) {
/* Board was too large. */
errno = ENOMEM;
return -1;
}
b->rows = rows;
b->cols = cols;
return 0;
}
void free_board(board *b)
{
if (b) {
free(b->cell);
b->rows = 0;
b->cols = 0;
b->cell = NULL;
}
}
您可以使用CELL(board, row, column)
访问宏,也可以使用以下安全的访问器功能:
char get_cell(board *b, const int r, const int c, const char outside)
{
if (!b || r < 0 || c < 0 || r >= b.rows || c >= b.cols)
return outside;
else
return b->cell[r*b->cols + c];
}
void set_cell(board *b, const int r, const int c, const char value)
{
if (b && r >= 0 && c >= 0 && r < b.rows && c < b.cols)
b->cell[r*b->cols + c] = value;
}
get_cell()
或set_cell()
都不会尝试在实际板之外进行访问。 (如果尝试访问无效的板或板外部,则outside
的第四个参数get_cell()
定义结果值。)
说实话,我更喜欢为单元格数组使用C99灵活数组成员。在那种情况下,结构就像
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
typedef struct {
int rows;
int cols;
unsigned char cell[];
} board;
然后,board
类型与FILE
流句柄类似:您永远不会静态声明它们(board b;
或FILE f;
),而要使用指向一个的指针({ {1}},board *b
)。要创建新的木板,您可以使用例如
FILE *f
在C99中,我们可以将访问器函数声明为board *create_board(const int rows, const int cols)
{
board *b;
const size_t cells = (size_t)rows * (size_t)cols;
const size_t bytes = cells * sizeof (b->cell[0]);
const size_t total = bytes + sizeof (board);
/* Check for invalid size */
if (rows < 1 || cols < 1) {
errno = EINVAL;
return NULL;
}
/* Check for size overflow */
if ((size_t)(cells / (size_t)rows) != (size_t)cols ||
(size_t)(bytes / cells) != sizeof (b->cell[0]) ||
total <= bytes) {
errno = ENOMEM;
return NULL;
}
/* Allocate memory for the whole board */
b = malloc(total);
if (!b) {
/* Failed. Too large. */
errno = ENOMEM;
return NULL;
}
/* Optional: Clear the entire structure. */
memset(b, 0, total);
/* Initialize the board fields. */
b->rows = rows;
b->cols = cols;
return b;
}
void free_board(board *b)
{
if (b) {
b->rows = 0;
b->cols = 0;
free(b);
}
}
,这意味着它们仅在相同的转换单元中可见(因此,您可以在声明static inline
结构的头文件中声明这些访问器函数。也是如此),也向C编译器暗示它应该内联这些代码。基本上,board
函数应与预处理器宏一样快,但也应提供类型安全性。
static inline
要打印电路板(使用static inline int get_cell(board *b, const int row, const int col,
const int outside)
{
if (!b || row < 0 || col < 0 || row >= b->rows || col >= b->cols)
return outside;
else
return b->cell[row * b->cols + col];
}
static inline int set_cell(board *b, const int row, const int col,
const int value, const int outside)
{
if (b && row >= 0 && col >= 0 && row < b->rows && col < b->cols)
return b->cell[row * b->cols + col] = value;
else
return outside;
}
,|
和+
作为单元格之间的线条),可以使用例如
-
其中static int board_char(board *b, const int row, const int col)
{
if (b && row >= 0 && col >= 0 &&
row < b->rows && col < b->cols) {
const int value = b->cell[row * b->cols + col];
if (isprint(value))
return value;
else
return ' ';
} else
return ' ';
}
void print_board(FILE *out, board *b)
{
if (out && b && b->rows > 0 && b->cols > 0) {
const int lastrow = b->rows - 1;
const int lastcol = b->cols - 1;
int r, c;
for (r = 0; r < lastrow; r++) {
for (c = 0; c < lastcol; c++) {
fputc(board_char(b, r, c), out);
fputc('|', out);
}
fputc(board_char(b, r, lastcol), out);
fputc('\n', out);
for (c = 0; c < lastcol; c++) {
fputc('-', out);
fputc('+', out);
}
fputc('-', out);
fputc('\n', out);
}
for (c = 0; c < lastcol; c++) {
fputc(board_char(b, lastrow, c), out);
fputc('|', out);
}
fputc(board_char(b, lastrow, lastcol), out);
fputc('\n', out);
}
}
返回与所引用单元格相对应的描述性字符。上面的版本检查数组中的值是否是可打印的字符,如果是,则返回它;否则,返回它。否则,包括面板外的字符,它将返回一个空格。可以将board_char()
与if (isprint(value))
语句一起使用,而不是switch (value) { ... }
来声明用于每个单元格的每个值。
(例如,如果零表示未使用/空闲,1表示第一名玩家(例如case
),2表示第二名玩家(例如X
),则可以将3用作“已阻止”,将某些单元格标记为不可用,从而使游戏更加有趣。)
您可以使用宽泛的Curses库(例如ncursesw)在终端中使您的游戏具有交互性,并使用漂亮的箱形绘制字符绘制游戏板。例如,
O
您可以使用本地化和宽字符支持将上述类型的板打印到终端上,而无需使用Curses库,但是不幸的是Windows中会出现问题。 (截至目前,Microsoft仍在为终端/控制台应用程序提供适当的Unicode支持,而无需使用Microsoft特定的C扩展。)