游戏的所有对象都由一个或多个特定字符来表示
##############
# #
# dv #
# v # #
# v # #
# s >>D # #
# v # #
# *A< * # #
# #
##############
main() {
Read board from file or stdin or create a default board
update game
write updated board to file or stdout
}
typedef struct snake_t {
unsigned int tail_row;
unsigned int tail_col;
unsigned int head_row;
unsigned int head_col;
bool live;
} snake_t;
typedef struct game_t {
unsigned int num_rows;
char **board;
unsigned int num_snakes;
snake_t *snakes;
} game_t;
update_game()board file预设了所有对象的位置:蛇,水果,墙
在加载board之后,一时刻的游戏运行逻辑:
蛇的移动实际上只需要更新头部和尾部的位置,即
update_head(), update_tail()
两个函数根据下一刻的位置是空地,墙还是水果做不同的更新
而这两个函数的实现有许多helper完成,大概来说有:
body_to_tail(), head_to_body()update_tail()需要incident这一信息(对正常移动,撞墙,吃到水果编码),但为了让代码逻辑更精简,不再做与update_head()相同的检查逻辑,而是总是先执行update_head(),其将incident返回,再用if语句控制load_board()一个问题是在不知道文件中的board有多大的情况下,而且在C中无法通过不读取来获取board的大小,因为FILEobject type不包含也不蕴含这一信息
Object type that identifies a stream and contains the information needed to control it, including a pointer to its buffer, its position indicator and all its state indicators.
要向game->board分配多大的内存呢?
答案是动态分配,与一个动态数组类似,当读取的行数超过分配的大小时,原来的大小翻倍,通过realloc()重新分配内存。game->board[r]的内存分配也是如此,和其他语句结合,封装在另一个函数read_line()中,通过fgets()copy string from stream,整体逻辑是
fgets()之后检查读取的字符串最后一个字符是否为\nfgets()被截断,大小翻倍,line重新分配内存line指针不移动,通过pointer arithmetic来计算从哪里开始读取新的num个字符
heuristically, 有两个变量size: 分配的内存大小, len: 实际大小。由此来计算fgets()的参数str, num
自动附加的\0需要被覆写,所以读取的num有+1,len因为不包含\0,所以作为offset,值刚刚好
char *read_line(FILE *fp) {
char *line = malloc(256);
int size = 256;
int len = 0;
while ((fgets(line + len, size - len + 1, fp)) != NULL) {
// check if fgets() is truncated
len = strlen(line);
if (line[len-1] != '\n' && !feof(fp)) {
size *= 2;
line = realloc(line, (size_t) size);
continue;
}
return line;
}
free(line);
return NULL;
}
snake的初始化是与load_board分离的,实现很简单
board找到tail位置snake的各个field到此为止cs61c/proj1 的所有工作已完成,因为不是从头写起的,骨架都搭好了,难度不大,作为C语言初学者的练习再合适不过了。