C语言,24点发牌小程序。。。。

C语言,24点发牌小程序。。。。,第1张

int _tmain(int argc, _TCHAR* argv[])

{

int i,j,card,tank[54],player[26],computer[26]

bool flag

while (true){

srand(time(NULL))//初始化随机种子

for(i=0i<54i++)

tank[i]=-1//初罩举始物数碧化容器

//printf("%X\n",tank[1])

for(i=0i<52i++)//52张牌

{

flag=1

while(flag)

{

flag=0

card=rand()%52

for(j=0j<=ij++){

if(card==tank[j]) {

flag=1

}

}

}

tank[i]=card

}

printf("\n\n")

for(i=0i<52i++)

printf("毕局%d\t",tank[i])

printf("\n\n")

for(i=0i<52i++)

{

switch((tank[i])/13){

case 0:

if(tank[i]%13==12) printf("红桃K\t")

else if(tank[i]%13==11) printf("红桃Q\t")

else if(tank[i]%13==10) printf("红桃J\t")

else if(tank[i]%13==0 ) printf("红桃A\t")

else {printf("红桃%d\t",tank[i]%13+1)}

break

case 1:

if(tank[i]%13==12) printf("黑桃K\t")

else if(tank[i]%13==11) printf("黑桃Q\t")

else if(tank[i]%13==10) printf("黑桃J\t")

else if(tank[i]%13==0 ) printf("黑桃A\t")

else {printf("黑桃%d\t",tank[i]%13+1)}

break

case 2:

if(tank[i]%13==12) printf("方片K\t")

else if(tank[i]%13==11) printf("方片Q\t")

else if(tank[i]%13==10) printf("方片J\t")

else if(tank[i]%13==0 ) printf("方片A\t")

else {printf("方片%d\t",tank[i]%13+1)}

break

case 3:

if(tank[i]%13==12) printf("梅花K\t")

else if(tank[i]%13==11) printf("梅花Q\t")

else if(tank[i]%13==10) printf("梅花J\t")

else if(tank[i]%13==0 ) printf("梅花A\t")

else {printf("梅花%d\t",tank[i]%13+1)}

break

case 4:

if(tank[i]==52) {printf("小王\t")}else{printf("大王\t")}

break

}

}

//替换成规定的数据列

printf("\n\n")

for(i=0i <4i++)

{

player[i]=tank[i]

printf("%d\t",player[i])

}

printf("\n\n你的牌是:\n\n")

for(i=0i<4i++)

{

switch((player[i])/13){

case 0:

if(player[i]%13==12) printf("红桃K\t")

else if(player[i]%13==11) printf("红桃Q\t")

else if(player[i]%13==10) printf("红桃J\t")

else if(player[i]%13==0 ) printf("红桃A\t")

else {printf("红桃%d\t",player[i]%13+1)}

break

case 1:

if(player[i]%13==12) printf("黑桃K\t")

else if(player[i]%13==11) printf("黑桃Q\t")

else if(player[i]%13==10) printf("黑桃J\t")

else if(player[i]%13==0 ) printf("黑桃A\t")

else {printf("黑桃%d\t",player[i]%13+1)}

break

case 2:

if(player[i]%13==12) printf("方片K\t")

else if(player[i]%13==11) printf("方片Q\t")

else if(player[i]%13==10) printf("方片J\t")

else if(player[i]%13==0 ) printf("方片A\t")

else {printf("方片%d\t",player[i]%13+1)}

break

case 3:

if(player[i]%13==12) printf("梅花K\t")

else if(player[i]%13==11) printf("梅花Q\t")

else if(player[i]%13==10) printf("梅花J\t")

else if(player[i]%13==0 ) printf("梅花A\t")

else {printf("梅花%d\t",player[i]%13+1)}

break

case 4:

if(player[i]==52) {printf("小王\t")}else{printf("大王\t")}

break

}

}

int id

cout<<endl<<endl<<"再来一幅牌嘛(0键退出),任意键"<<endl

id=cin.get()

if(cin.fail()){

cin.clear()

cin.sync()

continue

}else{

if (id=0) break

}

}

getchar()

getchar()

return 0

}

为什么算数纸牌游戏是计算24点而不是别的数?这其实是一个有意思的问题。

最简单的答复:因为24约数多啊!稍微认真点的回答:因为24有8个正约数,1、2、3、4、6、8、12、24,是一个超级合数,容易通过乘法来得到它,而且24本身也不太大,用4张扑克牌(点数1~10或1~13(J、Q、K分别代表11、12、13)),也比较容易通过加法来得到24,总之,通过四则运算算得24的方案数较多,所以随意抽取4张牌,有解的可能性较大,游戏也比较容易顺畅地进行。然而,这样的回答能令人满意吗?我认为不能。“可能性较大”是前缺什么意思?有多大?和别的数比呢?——Talk is cheap, show me your data.要算概率,首先计算4张牌可能出现的组合:如果4个数的范围都是1~10,那么去除重复的情况,不同的组合数为C{10+4-1}^{4}=\frac{13!}{4!\times 9!}=715种。如果4个数的范围都是1~13,那么去除重复的情况,不同的组合数为C{13+4-1}^{4}=\frac{16!}{4!\times 12!}=1820种。当然,由于扑克牌张数的特殊性,每种情况出现的概率实际上并不相等,甚至相差很大(比如实际上出现[3,4,5,6]的可能性是[6,6,6,6]的可能性的4^{4}=256倍),不过为了简化问题,只考虑哪些情况是有解的,并用有解的组合数/总组合数来计算有解的概率。正巧我最近刚开始自学Java,于是顺手编了一个算24的小程序,来计算所有数字组合的24点,写好代码,剩下的就交给计算机了!

嗯,结果出来了:

对于4个数均为1~10的715种情况,有566种有解,概率为79.16%;对于4个数均为1~13的1820种情况,有1362种有解,概率为74.83%。也就是说,如果我们只用数字牌,大约4/5的情况是能算出24点的,如果加上人头牌,这个概率大约是3/4。因此,玩24点游戏,总体来说还是比较顺畅的。当然,问题还远没有结束。对别的数,这个概率是多少呢?于是改一下程序,看看同样的组合,计算1~100的正整数,能算的概率是多少。

结果:1)四张扑克牌均为1~10时,结果如下图所示:横坐标为要计算的数,左边的纵坐标为有解的组合数,而右边纵坐标代表的是有解的概率。

是不是很出人意料!有最多拦派解的计算值并不是24点!而是——2点。4个1~10的数计算2,有解的组简悔贺合数为709组,有解概率高达99.16%24呢?正如红圈所示,虽然有解率很高(明显高于23和25),但也并不是鹤立鸡群!你看,18和20有解的概率就比它高!2)四张扑克牌均为1~13时,结果如下图所示。

有解概率上,依然是2遥遥领先!因此,我的回答是:我们算24点,其实并不仅仅因为24点的有解概率大(虽然24的有解概率确实也不小,所有大于24的数有解概率都比24点小),如果只是为了有解概率大,那么我们应该计算2、3、1、4等小自然数。我们之所以会去算24,乃是因为它在有解概率较大的情况下,比那些小自然数有了更多的变化性(比如38,46,18+6,14+10……),因此计算起来更具有技巧些,因此对思维的训练也更有帮助。其实呢,24点,如果算厌倦了,也可以算算20点、36点等等,它们的有解概率也是很大的哟!另外,对于计算高手而言,算24点似乎简单了些,因为总共就715种或1820种变化,难题也就诸如[1,5,5,5]、[1,3,4,6]、[1,4,5,6]、[2,7,7,10]、[3,3,7,7]、[3,3,8,8]、[4,4,7,7]等寥寥几个,很容易就会厌倦的。

关于二十四点游戏的编程思路与基本算法

漫长的假期对于我来说总是枯燥无味的,闲来无聊便和同学玩起童年时经常玩的二十四点牌游戏来。此游戏说来简单,就是利用加减乘除以及括号将给出的四张牌组成一个值为24的表达式。但是其中却不乏一些有趣的题目,这不,我们刚玩了一会儿,便遇到了一个难题——3、6、6、10(其实后来想想,这也不算是个太难的题,只是当时我们的脑筋都没有转弯而已,呵呵)。

问题既然出现了,我们当然要解决。冥思苦想之际,我的脑中掠过一丝念头——何不编个程序来解决这个问题呢?文曲星中不就有这样的程序吗?所以这个想法应该是可行。想到这里我立刻开始思索这个程序的算法,最先想到的自然是穷举法(后来发现我再也想不到更好的方法了,悲哀呀,呵呵),因为在这学期我曾经写过一个小程序——计算有括号的简单表达式。只要我能编程实现四个数加上运算符号所构成的表达式的穷举,不就可以利用这个计算程序来完成这个计算二十四点的程序吗?确定饥烂段了这个思路之后,我开始想这个问题的细节。

首先穷举的可行性问题。我把表达式如下分成三类—烂誉—

1、 无括号的简单表达式。

2、 有一个括号的简单表达式。

3、 有两个括号的较复4、 杂表达式。

穷举的开始我对给出的四个数进行排列,其可能的种数为4*3*2*1=24。我利用一个嵌套函数实现四个数的排列,算法如下:

/* ans[] 用来存放各种排列组合的数历誉组 */

/* c[] 存放四张牌的数组 */

/* k[] c[]种四张牌的代号,其中k[I]=I+1。

用它来代替c[]做处理,考虑到c[]中有可能出现相同数的情况 */

/* kans[] 暂存生成的排列组合 */

/* j 嵌套循环的次数 */

int fans(c,k,ans,kans,j)

int j,k[],c[]char ans[],kans[]

{ int i,p,q,r,h,flag,s[4],t[4][4]

for(p=0,q=0p<4p++)

{ for(r=0,flag=0r if(k[p]!=kans[r]) flag++

if(flag==j) t[j][q++]=k[p]

}

for(s[j]=0s[j]<4-js[j]++)

{ kans[j]=t[j][s[j]]

if(j==3) { for(h=0h<4h++)

ans[2*h]=c[kans[h]-1]/* 调整生成的排列组合在最终的表

达式中的位置 */

for(h=0h<3h++)

symbol(ans,h)/* 在表达式中添加运算符号 */

}

else { j++

fans(c,k,ans,kans,j)

j--

}

}

}

正如上面函数中提到的,在完成四张牌的排列之后,在表达式中添加运算符号。由于只有四张牌,所以只要添加三个运算符号就可以了。由于每一个运算符号可重复,所以计算出其可能的种数为4*4*4=64种。仍然利用嵌套函数实现添加运算符号的穷举,算法如下:

/* ans[],j同上。sy[]存放四个运算符号。h为表达式形式。*/

int sans(ans,sy,j,h)

char ans[],sy[]int j,h

{ int i,p,k[3],m,nchar ktans[20]

for(k[j]=0k[j]<4k[j]++)

{ ans[2*j+1]=sy[k[j]]/* 刚才的四个数分别存放在0、2、4、6位

这里的三个运算符号分别存放在1、3、5位*/

if(j==2)

{ ans[5]=sy[k[j]]

/* 此处根据不同的表达式形式再进行相应的处理 */

}

else { j++sans(ans,sy,j--,h)}

}

}

好了,接下来我再考虑不同表达式的处理。刚才我已经将表达式分为三类,是因为添加三个括号对于四张牌来说肯定是重复的。对于第一种,无括号自然不用另行处理;而第二种情况由以下代码可以得出其可能性有六种,其中还有一种是多余的。

for(m=0m<=4m+=2)

for(n=m+4n<=8n+=2)

这个for循环给出了添加一个括号的可能性的种数,其中m、n分别为添加在表达式中的左右括号的位置。我所说的多余的是指m=0,n=8,也就是放在表达式的两端。这真是多此一举,呵呵!最后一种情况是添加两个括号,我分析了一下,发现只可能是这种形式才不会是重复的——(a b)(c d)。为什么不会出现嵌套括号的情况呢?因为如果是嵌套括号,那么外面的括号肯定是包含三个数字的(四个没有必要),也就是说这个括号里面包含了两个运算符号,而这两个运算符号是被另外一个括号隔开的。那么如果这两个运算符号是同一优先级的,则肯定可以通过一些转换去掉括号(你不妨举一些例子来试试),也就是说这一个括号没有必要;如果这两个运算符号不是同一优先级,也必然是这种形式((a+-b)*/c)。而*和/在这几个运算符号中优先级最高,自然就没有必要在它的外面添加括号了。

综上所述,所有可能的表达式的种数为24*64*(1+6+1)=12288种。哈哈,只有一万多种可能性(这其中还有重复),这对于电脑来说可是小case哟!所以,对于穷举的可行性分析和实现也就完成了。

接下来的问题就是如何对有符号的简单表达式进行处理。这是栈的一个著名应用,那么什么是栈呢?栈的概念是从日常生活中货物在货栈种的存取过程抽象出来的,即最后存放入栈的货物(堆在靠出口处)先被提取出去,符合“先进后出,后进先出”的原则。这种结构犹如子d夹。

在栈中,元素的插入称为压入(push)或入栈,元素的删除称为d出(pop)或退栈。

栈的基本运算有三种,其中包括入栈运算、退栈运算以及读栈顶元素,这些请参考相关数据结构资料。根据这些基本运算就可以用数组模拟出栈来。

那么作为栈的著名应用,表达式的计算可以有两种方法。

第一种方法——

首先建立两个栈, *** 作数栈OVS和运算符栈OPS。其中, *** 作数栈用来记忆表达式中的 *** 作数,其栈顶指针为topv,初始时为空,即topv=0;运算符栈用来记忆表达式中的运算符,其栈顶指针为topp,初始时,栈中只有一个表达式结束符,即topp=1,且OPS(1)=‘;’。此处的‘;’即表达式结束符。

然后自左至右的扫描待处理的表达式,并假设当前扫描到的符号为W,根据不同的符号W做如下不同的处理:

1、 若W为 *** 作数

2、 则将W压入 *** 作数栈OVS

3、 且继续扫描下一个字符

4、 若W为运算符

5、 则根据运算符的性质做相应的处理:

(1)、若运算符为左括号或者运算符的优先级大于运算符栈栈顶的运算符(即OPS(top)),则将运算符W压入运算符栈OPS,并继续扫描下一个字符。

(2)、若运算符W为表达式结束符‘;’且运算符栈栈顶的运算符也为表达式结束符(即OPS(topp)=’’),则处理过程结束,此时, *** 作数栈栈顶元素(即OVS(topv))即为表达式的值。

(3)、若运算符W为右括号且运算符栈栈顶的运算符为左括号(即OPS(topp)=’(‘),则将左括号从运算符栈谈出,且继续扫描下一个符号。

(4)、若运算符的右不大于运算符栈栈顶的运算符(即OPS(topp)),则从 *** 作数栈OVS中d出两个 *** 作数,设先后d出的 *** 作数为a、b,再从运算符栈OPS中d出一个运算符,设为+,然后作运算a+b,并将运算结果压入 *** 作数栈OVS。本次的运算符下次将重新考虑。

第二种方法——

首先对表达式进行线性化,然后将线性表达式转换成机器指令序列以便进行求值。

那么什么是表达式的线性化呢?人们所习惯的表达式的表达方法称为中缀表示。中缀表示的特点是运算符位于运算对象的中间。但这种表示方式,有时必须借助括号才能将运算顺序表达清楚,而且处理也比较复杂。

1929年,波兰逻辑学家Lukasiewicz提出一种不用括号的逻辑符号体系,后来人们称之为波兰表示法(Polish notation)。波兰表达式的特点是运算符位于运算对象的后面,因此称为后缀表示。在对波兰表达式进行运算,严格按照自左至右的顺序进行。下面给出一些表达式及其相应的波兰表达式。

表达式 波兰表达式

A-B AB-

(A-B)*C+D AB-C*D+

A*(B+C/D)-E*F ABCD/+*EF*-

(B+C)/(A-D) BC+AD-/

OK,所谓表达式的线性化是指将中缀表达的表达式转化为波兰表达式。对于每一个表达式,利用栈可以把表达式变换成波兰表达式,也可以利用栈来计算波兰表达式的值。

至于转换和计算的过程和第一种方法大同小异,这里就不再赘述了。

下面给出转换和计算的具体实现程序——

/* first函数给出各个运算符的优先级,其中=为表达式结束符 */

int first(char c)

{ int p

switch(c)

{ case '*': p=2break

case '/': p=2break

case '+': p=1break

case '-': p=1break

case '(': p=0break

case '=': p=-1break

}

return(p)

}

/* 此函数实现中缀到后缀的转换 */

/* M的值宏定义为20 */

/* sp[]为表达式数组 */

int mid_last()

{ int i=0,j=0char c,sm[M]

c=s[0]sm[0]='='top=0

while(c!='\0')

{ if(islower(c)) sp[j++]=c

else switch(c)

{ case '+':

case '-':

case '*':

case '/': while(first(c)<=first(sm[top]))

sp[j++]=sm[top--]

sm[++top]=cbreak

case '(': sm[++top]=cbreak

case ')': while(sm[top]!='(')

sp[j++]=sm[top--]

top--break

default :return(1)

}

c=s[++i]

}

while(top>0) sp[j++]=sm[top--]

sp[j]='\0'return(0)

}

/* 由后缀表达式来计算表达式的值 */

int calc()

{ int i=0,sm[M],trchar c

c=sp[0]top=-1

while(c!='\0')

{ if(islower(c)) sm[++top]=ver[c-'a']/*在转换过程中用abcd等来代替数,

这样才可以更方便的处理非一位数,

ver数组中存放着这些字母所代替的数*/

else switch(c)

{ case '+': tr=sm[top--]sm[top]+=trbreak

case '-': tr=sm[top--]sm[top]-=trbreak

case '*': tr=sm[top--]sm[top]*=trbreak

case '/': tr=sm[top--]sm[top]/=trbreak

default : return(1)

}

c=sp[++i]

}

if(top>0) return(1)

else { result=sm[top]return(0)}

}

这样这个程序基本上就算解决了,回过头来拿这个程序来算一算文章开始的那个问题。哈哈,算出来了,原来如此简单——(6-3)*10-6=24。

最后我总结了一下这其中容易出错的地方——

1、 排列的时候由于一个数只能出现一次, 所以必然有一个判断语句。但是用什么来判断,用大小显然不行,因为有可能这四个数中有两个或者以上的数是相同的。我的方法是给每一个数设置一个代号,在排列结束时,通过这个代号找到这个数。

2、在应用嵌套函数时,需仔细分析程序的执行过程,并对个别变量进行适当的调整(如j的值),程序才能正确的执行。

3、在分析括号问题的时候要认真仔细,不要错过任何一个可能的机会,也要尽量使程序变得简单一些。不过我的分析可能也有问题,还请高手指点。

4、在用函数对一个数组进行处理的时候,一定要注意如果这个数组还需要再应用,就必须将它先保存起来,否则会出错,而且是很严重的错误。

5、在处理用户输入的表达式时,由于一个十位数或者更高位数是被分解成各位数存放在数组中,所以需对它们进行处理,将它们转化成实际的整型变量。另外,在转化过程中,用一个字母来代替这个数,并将这个数存在一个数组中,且它在数组中的位置和代替它的这个字母有一定的联系,这样才能取回这个数。

6、由于在穷举过程难免会出现计算过程中有除以0的计算,所以我们必须对calc函数种对于除的运算加以处理,否则程序会因为出错而退出(Divide by 0)。

7、最后一个问题,本程序尚未解决。对于一些比较著名的题目,本程序无法解答。比如说5、5、5、1或者8、8、3、3。这是由于这些题目在计算的过程用到了小数,而本程序并没有考虑到小数。

最后,由于此文档并没有在写程序的同时完成,所以难免因为记忆的差错和小弟水平的不足而有不少错误,还望各位批评指正;或者你认为我写得还不够清楚,你也可以给我来信讨论。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/12235250.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-22
下一篇 2023-05-22

发表评论

登录后才能评论

评论列表(0条)

保存