求c语言大神帮忙ps编一道程序,第二题

  上次配置好了linux+vim+gcc以及写了一个HelloWorld级别的示例程序,这次写一个稍微有意义的程序,在写这个小程序的过程中,我们快速的对C语言有一个大致的了解,SICP里指出,要学一门语言,要注意3个方面,一是这个语言提供了哪些Primitive,如数据类型,表达式,语句;二是提供了哪些组合规则,三是提供了哪些抽象机制,我们学C的时候也有意识的留意一下。
  同事们中午一般都一起出去吃午饭,AA制,但每次吃饭都现场算钱的话,比较麻烦,不如一人付一次,轮换着付钱,最终付的钱还是均匀的。但有的时候今天吃的多,明天吃的少,而且有的人今天来了,明天没来,所以要有个记账的软件,要记录下哪天都有谁去吃饭了,花了多少钱,打了多少折扣,当天是谁付的款,然后程序能自动算出来,谁付款付的多,谁付款付的少,付款付的最少的今天就主动付款。(大家可以了解下)
  我定义了一个文件格式,每个字段用"|"分隔,从左到右每列一次是吃饭日期,总消费金额,折扣,吃饭的人,付款人和付款金额。其中吃饭的人用逗号分隔,付款记录也用逗号分隔,每个付款记录用冒号分隔开付款人和付款金额。
|83|0.8|a,b,c,d|a:100,b:100|102|0.8|a,b,c,d,e|b:100,c:50
比如以上的输入文件input.txt,9月10日花了83块钱,打了0.8折是66.4元,有4个人吃饭,分别是a,b,c,d,人均消费是66.4/4=16.6元,当天a和b各充了100元,那么今天a和b的余额就是100-16.6=83.4元,而c和d没付钱,余额就是-16.6元,下次就应该让他俩出钱。
数据结构定义
  我们先进行数据结构的定义,在C里定义数据一般用struct来定义,c的struct不能定义函数(能定义函数指针),只能定义数据成员,而且不是原生支持的数据类型,使用类型的时候要加struct前缀。
  我们定义两个常量,MAX_RECORD_COUNT定义input.txt里最大的记录数(一行一个记录),因为C里要自己管理内存,分配数据等要考虑个最大值,不像c#里有ArrayList这样自动扩大的类,所以我们声明列表类型的数据一般用数组,数组要给定一个最大长度。MAX_ARRAY_COUNT,这个定义普通字符串的最大长度,如输入文件里各个字段的长度都不能超过这个长度。
data_structure.h
#define MAX_RECORD_COUNT 10#define MAX_ARRAY_COUNT 15struct people{
char name[MAX_ARRAY_COUNT];};struct pay_record{
double};struct account_record{
char date[MAX_ARRAY_COUNT];
struct people person[MAX_ARRAY_COUNT];
int people_
struct pay_record payrecord[MAX_ARRAY_COUNT];
int pay_record_
int total_};struct account_record_list{
struct account_record records[MAX_RECORD_COUNT];
int};struct person_consumption{
char name[MAX_ARRAY_COUNT];
double};struct person_consumption_list{
struct person_consumption persons[MAX_ARRAY_COUNT];
  如上,我们用struct account_record来表示一天的记账记录,account_record_list表示多条这样的记录,我们的命名规则就是表示多条数据类的结构后缀名加_list,并有一个count的成员表示有效数据的长度。struct account_record里各个成员分别对应输入文件里的各个字段,比如struct people其实就是一个长度为15的字符数组,person_consumption表示每个人的余额。这里尽量不用typeof是因为那样有些乱。
  在这里我们用到了各种数据类型的定义,如单个值int,double,一维数组,结构定义等。
  定义好了数据,就该定义操作这些数据的函数了,我们先从上层来分析都需要哪些模块,模块之间的依赖关系,以及模块里有哪些操作。首先因为我们定义了一个输入文件,就应该有一个模块来读取这个文件,并构建成内存里的消费记录,付款记录等对象,该模块就叫readinput吧。另外内存里有了消费记录,付款记录这些对象,就需要处理它们,计算出每个人的余额,某天的人均消费等,我们把这个模块叫record_handler,最后我们要有个主模块调用这两个模块,组合成最终的业务逻辑,并显示给用户,这个模块就叫main吧。
readinput.h
struct account_record_list read_input();
  该模块对外只提供一个方法read_input,返回一个消费记录列表类,其内部实现的私有函数不需要写在头文件里,因为没人用它,这也算起到了封装的作用,因为具体该函数的实现类是readinput.c,该文件最终会编译成一个.o文件,别人要想用该模块的功能的话,只要有readinput.o和readinput.h就行了,一般会把.o放到lib目录下,.h放到include目录下。
record_handler.h
void edit_person_consumption(struct person_consumption_list *list,
const char *name,double money);void print_person_consumption_list(const struct person_consumption_list list);double calc_avg_consumption(double total, int person_count, double discount);
  该模块定义了对消费记录的处理,edit_person_consumption用来修改消费记录,比如某人吃饭消费了多少钱,某人付了多少钱,都调用它来计算出各个人的余额。print_person_consumption_list用来打印出每个人的余额,谁是正的余额,谁是负的余额,calc_avg_consumption用来根据总金额,折扣数和吃饭的人数计算出人均消费数。
  我们在设计模块时要尽量让模块的职责清晰,做到高内聚,尽量少的使用别的模块的功能,并尽量让很多的模块使用自己,还要考虑清楚模块之间的调用关系。
  Main模块不需要.h头,它是一个驱动模块,用来调用其它两个模块,完成整体的功能,不对外提供接口,但要实现一个main的入口函数。
主函数的实现
  每个可执行程序都要有一个main的方法,我们在main模块里定义,在使用前,先要用include来声明你都依赖哪些模块,只需要包含该模块的头文件就可以,尖括号括的是系统的头文件,会在/usr/include/下查找,引号括住的是自己的头文件,会在当前目录下查找。
#include &stdio.h&#include "data_structure.h"#include "readinput.h"#include "record_handler.h"void print_account_record_list(const struct account_record_list list);struct person_consumption_list handler_account_record_list(
const struct account_record_list list);int main(){
struct account_record_
list = read_input();
print_account_record_list(list);
struct person_consumption_list consumption_list =
handler_account_record_list(list);
print_person_consumption_list(consumption_list);
return 0;}
  接下来我们声明两个main函数要用到的两个私有函数,因为c里要使用函数要先声明,否则你就只能用你这个函数上面定义的函数,我们在这里先声明两个私有函数的原型,print_account_record_list来打印出每条消费记录的细节,handler_account_record_list用来处理整个记录列表。在这里看到list参数有个const的修饰,该关键字可以保证调用的函数不会修改你的传入的变量,因为这两个方法一个用来打印,一个用来当作输入源计算一些值,从语义上来说就不应该会去修改该参数,所以我们加了const。c里使用并深入理解const关键字是老鸟和新手的一个标志,大家可以查查相关资料。
  main主函数一般都返回int,其中函数定义里可以省略掉int,默认就是int,里面的逻辑也很简单,读取消费记录,打印消费记录,处理消费记录得到每个人的余额状况,打印每个人的余额状况,逻辑非常清晰,下面就是每个子函数的具体实现了。
  下面这个私有函数用来处理消费记录,遍历每天的消费和充值记录,并修改每人的余额记录,逻辑也很清晰,很好的调用了record_handler模块提供的功能,使该函数的简单明了,职责明确。
struct person_consumption_list handler_account_record_list(
const struct account_record_list list) {
struct person_consumption_list consumption_
consumption_list.count = 0;
int i = 0, j = 0;
for(i = 0; i & list. i++)
struct account_record record = list.records[i];
double average_consumption =
calc_avg_consumption(
record.total_consumption,
record.people_count,
record.discount);
for(j = 0; j & record.people_ j++)
edit_person_consumption(&consumption_list,
record.person[j].name,
-average_consumption);
for(j = 0; j& record.pay_record_ j++)
edit_person_consumption(&consumption_list,
record.payrecord[j].person.name,
record.payrecord[j].amount);
return consumption_ }
读取记账文件
  我们会用到IO,字符串以及一些字符串和数值转换的函数,所以先包含这些头文件。
#include &stdio.h&#include &string.h&#include &stdlib.h&#include "data_structure.h"
  C的编译器比较傻,有的时候你不包含头文件也能编译,但运行时会给个错误记录,比如atof是在stdlib.h里定义的,你不包含它也能编译,但你printf("%f",atof("0.8"));它会给你显示0.0,你包含了就没事了,这个太无语了,在c#里你不引用dll就使用人家的方法,编译肯定出错,在C里却什么事都可能发生,所以最好把自己以前学的编程知识先扔到一边,当个编程初学者来学习C,感觉c比javascript还诡异。
  struct account_record_list read_input()是一个比较大的函数,我们分开来看,先看变量定义部分,在C的函数里,变量定义要放在最前面,我们这里定义了fp一个文件类型指针,其中文件操作用c的标准库函数fopen,fclose操作,大家看下c手册就知道用法,这里是用只读方式打开,如果不存在则抛错。
FILE *if((fp=fopen("input.txt","rt")) == NULL){
printf("cannot open input.txt");
getchar();
exit(1);}int i = 0;enum read_state {
state_default,
state_date,
state_consumption,
state_discount,
state_person,
state_payrecord}state = state_struct account_record_result.count = 0;struct account_record *p_record = result.char temp_buffer[512];memset(temp_buffer, &\0&, 512);char *p_temp_buffer = temp_char ch = fgetc(fp);
  定义了一个read_state的枚举,在定义枚举的时候一般第一个成员定义成default,表示一种无效或者默认的状态,c里的枚举不能用xxx.yyy来访问,只能用yyy来访问,跟常量一样,所以我们定义成员的时候加上一个state_前缀,这样在使用的时候就知道是个枚举了。
  下面还定义了要返回的account_record_list result,因为在栈上声明的变量没人给初始化,所以result.count我们要人工设置为0,p_record是指向result.records的指针,它是一个指向数组的指针,这样可以用p_record++来依次对每个记录赋值,而不需要像用下标访问那样得知道下标值,再一个就是指针可以提高一点性能。
  temp_buffer是定义的一个临时缓冲区,因为我们解析输入文件,肯定要对原文件进行一些分隔等,所以要用临时缓存区保存临时结果。同理,这里生成的字符数组也没人给初始化,我们用memset来把每个字节都初始化成'\0'。最后也用一个p_temp_buffer指针来指向临时缓冲区,指针我们就以p_做前缀,这样能看出来。
  接下来是对输入文件的解析,我们要尽量保证函数的短小,所以这里的逻辑只是按分隔符找出每个字段,具体每个字段的解析又调用了各个set_xxx的函数。
while (ch != EOF){
if(result.count & MAX_RECORD_COUNT)
printf("max record count");
if(ch != '|' && ch != '\n'){
*(p_temp_buffer++) =
*(p_temp_buffer++) = '\0';
switch(state)
case state_date:
set_date(p_record,temp_buffer);
state = state_
case state_consumption:
set_consumption(p_record,temp_buffer);
state = state_
case state_discount:
set_discount(p_record,temp_buffer);
state = state_
case state_person:
set_person(p_record,temp_buffer);
state = state_
case state_payrecord:
set_payrecord(p_record,temp_buffer);
state = state_
printf("state is error");
memset(temp_buffer, 0, 512);
p_temp_buffer = temp_
if(ch == '\n'){
result.count++;
p_record++;
memset(temp_buffer, 0, 512);
p_temp_buffer = temp_
state = state_
putchar(ch);
ch = fgetc(fp);}fclose(fp);return
  这些逻辑性的东西就没什么说的了,逐个读取每个字符,如果遇到分隔符|或者\n就把这段字符放入缓冲区,并传给set_xxx来处理,注意每次set_xxx后要重置缓冲区的内容,以及让缓冲区指针指向起始位置。这里读取完某个字段后要把读取状态修改成下一个状态,这也是简单的状态机的应用,在字符串解析方面用的很广。
  最后记着要fclose文件,否则会资源泄漏,像那些成对出现的api要时刻记着配平资源,比如foepn,fclose,malloc,free这种,少半拉的话,一般就会引起资源泄漏问题。  
  我们在看一个set_xxx方法,对付款记录的解析是最复杂的,我们就看这个,付款记录字段格式是先用逗号分隔每个人的付款记录,再用冒号分隔付款人和付款金额。在c里有个strtok的函数,类似split,可以把一个字符串分隔成多个子串,这里也用到了临时缓冲区,把传入的只读字符串用strncpy拷贝到临时缓冲区里再做处理,strncpy比strcpy安全,因为后者拷贝时会一直拷贝,直到遇到\0为止,前者可以指定最多拷贝多少个字符。
void set_payrecord(struct account_record *record, const char *buff){
char temp_buffer[512];
memset(temp_buffer, 0, 512);
strncpy(temp_buffer, buff, 512*sizeof(char));
char c[MAX_ARRAY_COUNT][2*MAX_ARRAY_COUNT] = {{'\0'}};
char (*pc)[2*MAX_ARRAY_COUNT] =
char *p = strtok(temp_buffer,",");
int paycount= 0;
while(p != NULL)
strncpy(*pc++, p, 2*MAX_ARRAY_COUNT*sizeof(char));
p = strtok(NULL,",");
paycount++;
struct pay_record *payrecord = record -&
int i = 0;
for(i = 0; i & i++)
char *p2 = strtok(c[i],":");
if(p2 == NULL)
printf("error:parse payrecord error");
strncpy(person.name, p2, MAX_ARRAY_COUNT*sizeof(char));
p2 = strtok(NULL,":");
if(p2 == NULL)
printf("error:parse payrecord error");
double amount = atof(p2);
payrecord -& person =
payrecord -& amount =
payrecord++;
record -& pay_record_count++;
  这里需要一个两维数组,声明两维数组就用char [3][4] 就行,c99里只是声明数组时直接初始化,用={{'\0'}}就可以把数组都初始化成'\0',然后虽然这是一个两位的数组,但要用一维的数组指针去指,如char (*pc)[4],然后用*pc就能访问二维数组的每一行了,每一行是个字符数组,可以用strncpy等函数操作。注意strtok不能嵌套使用,所以先用它把逗号分隔的子串放入到二维数组里,然后便利二维数组的每一行,对每一行按冒号分隔取出付款人和付款金额,最后放到内存对象里。
处理记账记录
  这个模块比较小,edit_person_consumption用来处理每一笔消费和付款记录,先看list里有没有这个人,如果有这个人就直接把金额修改掉,如果没有,就在list里添加一个人机器消费记录。这里有个问题折腾了半天,就是我把strcmp写成strcpy了,编译也没问题,但输出结果让人很诡异,赋值都乱了,看来这种编译不出错,运行时给个错误值的问题是最难排查的,拼写错误真是程序员最常见的错误呀。剩下两个函数比较简单,打印没人余额记录和计算人均消费。
#include "data_structure.h"void edit_person_consumption(struct person_consumption_list *list,
const char *name,double money){
int i = 0;
int found = -1;
for(i = 0; i & list -& i++)
if(strcmp(list -& persons[i].name, name) == 0)
list -& persons[i].consumption +=
if(found == -1)
int count = list -&
strncpy(list -& persons[count].name, name, MAX_ARRAY_COUNT);
list -& persons[count].consumption =
list -& count++;
}}void print_person_consumption_list(const struct person_consumption_list list){
printf("\n-----consumption details-------\n");
for(i = 0; i & list. i++)
printf("%s=%0.2f\n",list.persons[i].name,list.persons[i].consumption);
}}double calc_avg_consumption(double total,int person_count,double discount){
return total * discount / person_}
编译及测试
  上篇帖子简单介绍过makefile的编写,以下是该程序的makefile文件,注意换行符和跳格键的使用。
book:readinput.o record_handler.o \
data_structure.h readinput.h record_handler.h\
gcc main.c -o book readinput.o record_handler.o readinput.o: data_structure.h readinput.h readinput.c
gcc -c readinput.c record_handler: data_structure.h record_handler.h record_handler.c
gcc -c record_handler.c
  最后输出一个book的可执行文件,执行./book,输出以下结果,符合预期
  可以看到d负的最多,因为它吃了两顿都没付钱,下次吃饭就该他出钱了,而b正的最多,可以连续一周不用付款吃饭了。
2010-9-10|83|0.8|a,b,c,d|a:100,b:1002010-9-11|102|0.8|a,b,c,d,e|b:100,c:502010-9-10
discount=0.80
consumption=83
pay_record
b:100.002010-9-11
discount=0.80
consumption=102
a,b,c,d,e,
pay_record
c:50.00-----consumption details-------a=67.08b=167.08c=17.08d=-32.92e=-16.32
  其实最终的每人余额可以从小到大排个序,可以练习一下冒泡排序和函数指针的使用,不过这也算是一个比较有意义的下程序了,多写代码,C的入门也就快了。下次可能给大家分享下如何配置VIM能更快的编写C程序,工具的熟练程度会大大影响开发效率。
  语言,工具等在编程里都是次要矛盾,编程的主要要解决的问题是业务逻辑本身的复杂性,所以要经常写一些逻辑比较复杂的小程序来提高编程能力,可以迅速提高思维能力,减少出错的能力,在写代码的过程中所犯的错误都积累起来,以后就可以一次编写,直接执行就通过了,编译和运行都没有错误,推荐下我前段时间写的练习作品:
源码下载:
环境:cygwin+gcc3.4.4+vim7.3.3+make3.8.1
Views(...) Comments()当前访客身份:游客 [
当前位置:
我们是一个课程设计,让设计一个五子棋的游戏,让我们写实训报告,源代码调试出来了,可是实训报告不知道怎么写,求大神们指点。
一、需求分析
建立一个简单的五子棋游戏,能够实现人机对战。人人对战的简单功能。
二、概要设计(典型算法)
三、模块设计
四、详细设计
五、调试分析
六、用户使用说明
七、参考文献
八、对所设计的软件进行自我评价,如创新点、未解决的问题等情况说明
九、程序源代码
# include&stdio.h&
# include&string.h&
# include&stdlib.h&
# define SPA 0
# define MAN 1
# define COM 2
/* 空位置设为0 ,玩家下的位置设为1 ,电脑下的位置设为2 */
int qipan[15][15];
/* 15*15的棋盘 */
int a,b,c,d,x;
/* a b为玩家下子坐标 ,c d为电脑下子坐标 x为剩余空位置*/
void start();
/* 程序的主要控制函数 */
void draw();
/* 画棋盘 */
int win(int p,int q);
/* 判断胜利 p q为判断点坐标 */
void AI(int *p,int *q);
/* 电脑下子 p q返回下子坐标 */
int value(int p,int q);
/* 计算空点p q的价值 */
int qixing(int n,int p,int q);
/* 返回空点p q在n方向上的棋型 n为1-8方向 从右顺时针开始数 */
void yiwei(int n,int *i,int *j);
/* 在n方向上对坐标 i j 移位 n为1-8方向 从右顺时针开始数
void main()
printf(&还要再来一把吗?输入y或n:&); getchar(); scanf(&%c&,&k);
while(k!='y'&&k!='n'){ printf(&输入错误,请重新输入\n&); scanf(&%c&,&k); }
system(&cls&);
}while(k=='y');
printf(&谢谢使用!\n&);
void start()
int i,j,a1,b1,c1,d1,
/* a1 b1储存玩家上手坐标 c1 d1储存电脑上手坐标 */
printf(&\t╔═══════════════════════════════╗\n&);
printf(&\t║
printf(&\t║
欢迎使用五子棋对战程序
祝您玩的愉快挑战无极限
printf(&\t║
printf(&\t║
._______________________.
printf(&\t║
| _____________________ |
printf(&\t║
printf(&\t║
printf(&\t║
printf(&\t║
made by XXX
printf(&\t║
| I___________________I |
printf(&\t║
!_______________________!
printf(&\t║
._[__________]_.
printf(&\t║
.___|_______________|___.
printf(&\t║
printf(&\t║
~~~~ [CD-ROM]
printf(&\t║
!_____________________!
printf(&\t║
printf(&\t║
printf(&\t║
寒 星 溪 月 疏 星 首,花 残 二 月 并 白 莲。
printf(&\t║
雨 月 金 星 追 黑 玉,松 丘 新 宵 瑞 山 腥。
printf(&\t║
星 月 长 峡 恒 水 流,白 莲 垂 俏 云 浦 岚。
printf(&\t║
黑 玉 银 月 倚 明 星,斜 月 明 月 堪 称 朋。
printf(&\t║
二 十 六 局 先 弃 二,直 指 游 星 斜 彗 星。
printf(&\t║
printf(&\t║
printf(&\t║
1.人机对战
2.人人对战
printf(&\t║
printf(&\t╚═══════════════════════════════╝\n&);
printf(&\t\t\t请输入1或2:&);
scanf(&%d&,&choice);
/* 选择模式:人机或人人 */
while(choice!=1&&choice!=2) { printf(&输入错误,请重新输入:&); scanf(&%d&,&choice); }
if(choice==1){
/* 人机模式 */
system(&cls&);
printf(&欢迎使用五子棋人机对战!下子请输入坐标(如13 6)。悔棋请输入15 15。\n\n\n&);
for(j=0;j&15;j++)
for(i=0;i&15;i++)
qipan[j][i]=SPA;
/* 置棋盘全为空 */
printf(&先下请按1,后下请按2:&);
scanf(&%d&,&i);
while(i!=1&&i!=2)
{ printf(&输入错误,请重新输入:&); scanf(&%d&,&i); }
if(i==1) {
/* 如果玩家先手下子 */
printf(&请下子:&); scanf(&%d%d&,&a,&b);
while((a&0||a&14)||(b&0||b&14)) { printf(&坐标错误!请重新输入:&); scanf(&%d%d&,&a,&b); }
a1=a; b1=b; x--; qipan[a]=MAN; system(&cls&); draw();
while(x!=0){
if(x==225) { c=7; d=7; qipan[d][c]=COM; x--; system(&cls&); draw(); }
/* 电脑先下就下在7 7 */
else { AI(&c,&d); qipan[d][c]=COM; x--; system(&cls&); draw(); } /* 电脑下子 */
c1=c; d1=d;
/* 储存电脑上手棋型 */
if(win(c,d)){
/* 电脑赢 */
printf(&要悔棋吗?请输入y或n:&); getchar(); scanf(&%c&,&ch);
while(ch!='y'&&ch!='n') { printf(&输入错误,请重新输入:&); scanf(&%c&,&ch); }
if(ch=='n') { printf(&下不过电脑很正常,请不要灰心!!!\n&); }
else { x+=2; qipan[d][c]=SPA; qipan[a1]=SPA; system(&cls&); draw(); } /* 悔棋 */
printf(&电脑下在%d %d\n请输入:&,c,d); scanf(&%d%d&,&a,&b);
/* 玩家下子 */
if(a==15&&b==15) { x+=2; qipan[d][c]=SPA; qipan[a1]=SPA; system(&cls&); draw(); printf(&请输入:&); scanf(&%d%d&,&a,&b); } /* 悔棋 */
while((a&0||a&14)||(b&0||b&14)||qipan[a]!=SPA) { printf(&坐标错误或该位置已有子!请重新输入:&); scanf(&%d%d&,&a,&b); }
a1=a; b1=b; x--; qipan[a]=MAN; system(&cls&); draw();
if(win(a,b)){ printf(&电脑神马的都是浮云!!!\n&); }
/* 玩家赢 */
printf(&和局\n&);
if(choice==2){
system(&cls&);
printf(&欢迎使用五子棋人人对战!下子请输入坐标(如13 6)。悔棋请输入15 15。
for(j=0;j&15;j++)
for(i=0;i&15;i++)
qipan[j][i]=SPA; /* 置棋盘全为空 */
while(x!=0){
printf(&1P请输入:&); scanf(&%d%d&,&a,&b);
if(a==15&&b==15) { x+=2; qipan[d][c]=SPA; qipan[a1]=SPA; system(&cls&); draw(); printf(&1P请输入:&); scanf(&%d%d&,&a,&b); }
while((a&0||a&14)||(b&0||b&14)||qipan[a]!=SPA) { printf(&坐标错误或该位置已有子!请重新输入:&); scanf(&%d%d&,&a,&b); }
a1=a; b1=b; x--; qipan[a]=MAN; system(&cls&); draw();
printf(&1P下在%d %d。\n&,a,b);
if(win(a,b)){ printf(&你真棒!!!\n&); }
/* 玩家1赢 */
printf(&2P请输入:&); scanf(&%d%d&,&c,&d);
if(c==15&&d==15) { x+=2; qipan[a]=SPA; qipan[d1][c1]=SPA; system(&cls&); draw(); printf(&2P请输入:&); scanf(&%d%d&,&c,&d); }
while((c&0||c&14)||(d&0||d&14)||qipan[d][c]!=SPA) { printf(&坐标错误或该位置已有子!请重新输入:&); scanf(&%d%d&,&c,&d); }
c1=c; d1=d; x--; qipan[d][c]=COM; system(&cls&); draw();
printf(&2P下在%d %d。\n&,c,d);
if(win(c,d)){ printf(&你真棒!!!\n&); }
/* 玩家2赢 */
printf(&和局\n&);
void draw() /* 画棋盘 */
char p[15][15][4];
for(j=0;j&15;j++)
for(i=0;i&15;i++){
if(qipan[j][i]==SPA) strcpy(p[j][i],&
if(qipan[j][i]==MAN) strcpy(p[j][i],&●\0&);
if(qipan[j][i]==COM) strcpy(p[j][i],&◎\0&);
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐\n&);
for(i=0,j=0;i&14;i++,j++){
%2d│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%d\n&,j,p[i][0],p[i][1],p[i][2],p[i][3],p[i][4],p[i][5],p[i][6],p[i][7],p[i][8],p[i][9],p[i][10],p[i][11],p[i][12],p[i][13],p[i][14],j);
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤\n&);
14│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│%s│0\n&,p[14][0],p[14][1],p[14][2],p[14][3],p[14][4],p[14][5],p[14][6],p[14][7],p[14][8],p[14][9],p[14][10],p[14][11],p[14][12],p[14][13],p[14][14]);
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘\n&);
int win(int p,int q)
/* 判断胜利 p q为判断点坐标,胜利返回1,否则返回0 */
int k,n=1,m,P,Q;
/* k储存判断点p q的状态COM或MAN。P Q储存判断点坐标。n为判断方向。m为个数。 */
k=qipan[q][p];
while(n!=5){
while(k==qipan[q][p]){
if(m==5) return 1;
yiwei(n,&p,&q); if(p&0||p&14||q&0||q&14)
n+=4; m-=1; p=P; q=Q;
/* 转向判断 */
while(k==qipan[q][p]){
if(m==5) return 1;
yiwei(n,&p,&q); if(p&0||p&14||q&0||q&14)
n-=3; p=P; q=Q;
/* 不成功则判断下一组方向 */
void AI(int *p,int *q)
/* 电脑下子 *p *q返回下子坐标 */
int i,j,k,max=0,I,J;
/* I J为下点坐标 */
for(j=0;j&15;j++)
for(i=0;i&15;i++)
if(qipan[j][i]==SPA){
/* 历遍棋盘,遇到空点则计算价值,取最大价值点下子。 */
k=value(i,j);
if(k&=max) { I=i; J=j; max=k; }
*p=I; *q=J;
int value(int p,int q) /* 计算空点p q的价值 以k返回 */
int n=1,k=0,k1,k2,K1,K2,X1,Y1,Z1,X2,Y2,Z2,
int a[2][4][4]={40,400,,6,10,600,,200,0,6,10,500,0,30,300,,8,300,,0,0,4,20,300,0};
/* 数组a中储存己方和对方共32种棋型的值
己方0对方1
活0冲1空活2空冲3
子数0-3(0表示1个子,3表示4个子) */
while(n!=5){
k1=qixing(n,p,q); n+=4;
/* k1,k2为2个反方向的棋型编号 */
k2=qixing(n,p,q); n-=3;
if(k1&k2) { temp=k1; k1=k2; k2= }
/* 使编号小的为k1,大的为k2 */
K1=k1; K2=k2;
/* K1 K2储存k1 k2的编号 */
Z1=k1%10; Z2=k2%10; k1/=10; k2/=10; Y1=k1%10; Y2=k2%10; k1/=10; k2/=10; X1=k1%10; X2=k2%10;
/* X Y Z分别表示 己方0对方1
活0冲1空活2空冲3
子数0-3(0表示1个子,3表示4个子) */
if(K1==-1) { if(K2&0) { k+=0; } else k+=a[X2][Y2][Z2]+5;
/* 空棋型and其他 */
if(K1==-2) { if(K2&0) { k+=0; } else k+=a[X2][Y2][Z2]/2; };
/* 边界冲棋型and其他 */
if(K1==-3) { if(K2&0) { k+=0; } else k+=a[X2][Y2][Z2]/3; };
/* 边界空冲棋型and其他 */
if(((K1&-1&&K1&4)&&((K2&-1&&K2&4)||(K2&9&&K2&14)))||((K1&99&&K1&104)&&((K2&99&&K2&104)||(K2&109&&K2&114)))){
/* 己活己活 己活己冲 对活对活 对活对冲 的棋型赋值*/
if(Z1+Z2&=2) { k+=a[X2][Y2][3]; }
else { k+=a[X2][Y2][Z1+Z2+1]; }
if(((K1&9&&K1&14)&&(K2&9&&K2&14))||((K1&109&&K1&114)&&(K2&109&&K2&114))){
/* 己冲己冲 对冲对冲 的棋型赋值*/
if(Z1+Z2&=2) { k+=10000; }
else { k+=0; }
if(((K1&-1&&K1&4)&&((K2&99&&K2&104)||(K2&109&&K2&114)))||((K1&9&&K1&14)&&((K2&99&&K2&104)||(K2&109&&K2&114)))){
/* 己活对活 己活对冲 己冲对活 己冲对冲 的棋型赋值*/
if(Z1==3||Z2==3) { k+=10000; }
else { k+=a[X2][Y2][Z2]+a[X1][Y1][Z1]/4; }
{ k+=a[X1][Y1][Z1]+a[X2][Y2][Z2];
/* 其他棋型的赋值 */
int qixing(int n,int p,int q)
/* 返回空点p q在n方向上的棋型号 n为1-8方向 从右顺时针开始数 */
int k,m=0;
/* 棋型号注解:
己活000-003 己冲010-013 对活100-103 对冲110-113 己空活020-023 己空冲030-033 对空活120-123 对空冲130-133 空-1 边界冲-2 边界空冲-3*/
yiwei(n,&p,&q);
if(p&0||p&14||q&0||q&14) k=-2;
/* 边界冲棋型 */
switch(qipan[q][p]){
case COM:{
m++; yiwei(n,&p,&q);
if(p&0||p&14||q&0||q&14) { k=m+9; }
while(qipan[q][p]==COM) { m++; yiwei(n,&p,&q); if(p&0||p&14||q&0||q&14) { k=m+9; } }
if(qipan[q][p]==SPA) k=m-1;
/* 己方活棋型 */
else k=m+9;
/* 己方冲棋型 */
case MAN:{
m++; yiwei(n,&p,&q);
if(p&0||p&14||q&0||q&14) { k=m+109; }
while(qipan[q][p]==MAN) { m++; yiwei(n,&p,&q); if(p&0||p&14||q&0||q&14) { k=m+109; } }
if(qipan[q][p]==SPA) k=m+99;
/* 对方活棋型 */
else k=m+109;
/* 对方冲棋型 */
case SPA:{
yiwei(n,&p,&q);
if(p&0||p&14||q&0||q&14) { k=-3; }
/* 边界空冲棋型 */
switch(qipan[q][p]){
case COM:{
m++; yiwei(n,&p,&q);
if(p&0||p&14||q&0||q&14) { k=m+29; }
while(qipan[q][p]==COM) { m++; yiwei(n,&p,&q); if(p&0||p&14||q&0||q&14) { k=m+29; } }
if(qipan[q][p]==SPA) k=m+19;
/* 己方空活棋型 */
else k=m+29;
/* 己方空冲棋型 */
case MAN:{
m++; yiwei(n,&p,&q);
if(p&0||p&14||q&0||q&14) { k=m+129; }
while(qipan[q][p]==MAN) { m++; yiwei(n,&p,&q); if(p&0||p&14||q&0||q&14) { k=m+129; } }
if(qipan[q][p]==SPA) k=m+119;
/* 对方空活棋型 */
else k=m+129;
/* 对方空冲棋型 */
case SPA: k=-1;
/* 空棋型 */
void yiwei(int n,int *i,int *j)
/* 在n方向上对坐标 i j 移位 n为1-8方向 从右顺时针开始数 */
switch(n){
case 1: *i+=1;
case 2: *i+=1; *j+=1;
case 3: *j+=1;
case 4: *i-=1; *j+=1;
case 5: *i-=1;
case 6: *i-=1; *j-=1;
case 7: *j-=1;
case 8: *i+=1; *j-=1;
共有2个答案
<span class="a_vote_num" id="a_vote_num_
代码有问题,而且代码风格也很糟糕。
--- 共有 1 条评论 ---
他这个估计是OS网站问题
(2年前)&nbsp&
<span class="a_vote_num" id="a_vote_num_
曾经写过简单的AI
采用的是估值法,例如连2是20分,连3是100分,活四是10000分,死四是1000分,差不多这样的思路,这个分值需要你自己来设置,设置的好坏直接影响AI聪明
然后在估值法的基础上可以进行一些阿法贝塔剪枝。。。
更多开发者职位上
有什么技术问题吗?
格子_的其它问题
类似的话题

我要回帖

更多关于 求大神帮忙 的文章

 

随机推荐