C#及OOP方式编写石头剪刀布的猜拳游戏
概述
OOP(面向对象)较OPP(面向过程)开发效率更高。笔者使用C#编写了一个石头剪刀布的猜拳小游戏供读者学习。
方案原理
UI界面
winform拖拽。。。不解释。。。不会的读者可以参考这2篇。。。图片需要PS协作。
C#及visual studio入门
面向对象
笔者使用了玩家类(play)及计算类(Calculator),玩家类主要存储信息,计算类主要提供方法。
算法
电脑人使用随机数确定出拳顺序,石头剪刀布的概率大体都是1/3。比较及统计后输出结果。
具体实现
玩家类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShitouJiandaoBu
{
class Player
{
//定义变量
private int Counter_shitou ;
private int Counter_jiandao;
private int Counter_bu;
private int Counter_sum;
private int Counter_success;
private int Counter_equal;
private int Counter_fail;
private int execulation;
private double Percent_success;
private double Percent_equal;
private double Percent_fail;
private double Percent_shitou;
private double Percent_jiandao;
private double Percent_bu;
//构造方法
public Player() {
Counter_shitou = 1;
Counter_jiandao = 1;
Counter_bu = 1;
Counter_sum = 3;
Counter_success = 0;
Counter_equal = 3;
Counter_fail = 0;
execulation = 0;
Percent_success = 0.0;
Percent_equal = 1.0;
Percent_fail = 0.0;
Percent_shitou = 1/3;
Percent_jiandao = 1/3;
Percent_bu = 1/3;
}
//封装
//set
public void shitou() {
this.Counter_shitou++;
this.Counter_sum++;
}
public void jiandao() {
this.Counter_jiandao++;
this.Counter_sum++;
}
public void bu() {
this.Counter_bu++;
this.Counter_sum++;
}
public void success() {
this.Counter_success++;
}
public void equal() {
this.Counter_equal++;
}
public void fail() {
this.Counter_fail++;
}
public void Percent() {
this.Percent_success = (double)Counter_success / (double)Counter_sum;
this.Percent_equal = (double)Counter_equal / (double)Counter_sum;
this.Percent_fail = 1.0 - Percent_equal - Percent_success;
this.Percent_shitou = (double)Counter_shitou / (double)Counter_sum;
this.Percent_jiandao = (double)Counter_jiandao / (double)Counter_sum;
this.Percent_bu = 1.0 - Percent_shitou - Percent_jiandao;
}
//设置动作:0为石头,1为剪刀,2为布
public void SetExe(int execulation) {
this.execulation = execulation;
switch (this.execulation) {
case 0:
shitou();
break;
case 1:
jiandao();
break;
case 2:
bu();
break;
default:
break;
}
}
//get
public int Getjiandao() {
return Counter_jiandao;
}
public int Getshitou() {
return Counter_shitou;
}
public int Getbu() {
return Counter_bu;
}
public int GetSc() {
return Counter_success;
}
public int Geteq() {
return Counter_equal;
}
public int GetFl() {
return Counter_fail;
}
public int GetSum() {
return this.Counter_sum;
}
public double GetPctSuccess() {
return this.Percent_success;
}
public double GetPctEqual() {
return this.Percent_equal;
}
public double GetPctFail() {
return this.Percent_fail;
}
public int GetExe() {
return this.execulation;
}
public double GetPctShitou() {
return this.Percent_shitou;
}
public double GetPctJiandao() {
return this.Percent_jiandao;
}
public double GetPctBu() {
return this.Percent_bu;
}
}
}
标准的OOP编写类时需要先定义私有变量,写构造方法(用于初始化类的成员变量),使用get与set封装类确保类内部的安全,编写普通方法。
该类的主要功能是存储数据(并且操作自己的数据)。
定义私有变量
写构造方法
封装类
写普通方法
计算类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShitouJiandaoBu
{
class Calculator
{
//定义变量
Random Random1 = new Random();
private double xishu ;
private int execulation;
private int judge;//判断输赢,1vs2输为0,平为1,赢为2
private double Percent_shitou;
private double Percent_jiandao;
private double Percent_bu;
//构造方法
public Calculator() {
this.xishu = 0.0;
this.judge = 1;
}
//封装
//set
public void SetPctShitou(double Percent_shitou) {
this.Percent_shitou = Percent_shitou;
}
public void SetPctJiandao(double Percent_jiandao) {
this.Percent_jiandao = Percent_jiandao;
}
public void SetPctBu(double Percent_bu) {
this.Percent_bu = Percent_bu;
}
//get
public int GetExe() {
return this.execulation;
}
public int GetJudge() {
return this.judge;
}
//类的普通方法
public void CalRandom() { //用于计算0~1之间的平均数并确定出拳
this.xishu = Random1.NextDouble();
}
public void CalExeculation1() {//带简单机器学习算法
if (this.xishu >= 0.0 && this.xishu < Percent_jiandao)
{
this.execulation = 0;//出石头
}
else if (this.xishu >= Percent_jiandao && this.xishu < (Percent_jiandao+Percent_bu))
{
this.execulation = 1;//出剪刀
}
else
{
this.execulation = 2;//出布
}
}
public void CalExeculation2(){//不带简单机器学习算法
if (this.xishu >= 0.0 && this.xishu < 0.33)
{
this.execulation = 0;//出石头
}
else if (this.xishu >= 0.33 && this.xishu < 0.66)
{
this.execulation = 1;//出剪刀
}
else
{
this.execulation = 2;//出布
}
}
public void judge1(int execulation1,int execulation2) { //判断胜负算法1
if (execulation1 == execulation2) {
this.judge = 1;
} else
if (execulation1 == 0 && execulation2 == 1) { //左石头右剪刀
this.judge = 0;
} else
if (execulation1 == 0 && execulation2 == 2) {//左石头右布
this.judge = 2;
} else
if (execulation1 == 1 && execulation2 == 0){ //左剪刀右石头
this.judge = 2;
} else
if (execulation1 == 1 && execulation2 == 2){//左剪刀右布
this.judge = 0;
} else
if (execulation1 == 2 && execulation2 == 0){ //左布右石头
this.judge = 0;
} else
if (execulation1 == 2 && execulation2 == 1){//左布右剪刀
this.judge = 2;
}
}
}
}
同样的套路,写出计算类。该类的主要功能就是计算。
定义私有变量
写构造方法
封装类
写普通方法
form事件
初始化
//定义变量
public int sum;
public int exe1;
public int exe2;
//public int
public Image image1_wenhao = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\左红问号.png");
public Image image1_shitou = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\左红拳头.png");
public Image image1_jiandao = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\左红剪刀.png");
public Image image1_bu = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\左红手掌.png");
public Image image2_wenhao = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\右绿问号.png");
public Image image2_shitou = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\右绿拳头.png");
public Image image2_jiandao = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\右绿剪刀.png");
public Image image2_bu = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\右绿手掌.png");
public int num_suanfa = 0;
//构造函数初始化
public MainForm()
{
InitializeComponent();
//this.lb_pct_sc1.Text = 25.ToString() + "%";
//Image image1_wenhao = new Bitmap(@"E:\工作文件夹\2021.3.25\素材\左红问号.png");
//ra_bu.Enabled = false;
//ra_jiandao.Enabled = false;
//ra_shitou.Enabled = false;
button1.Text = "已经开始第一局";
button1.Enabled = false;
//MessageBox.Show("初始化完成");
open_off.Select();
ra_shitou.Select();
ready();
pb1.Image = image1_wenhao;
pb2.Image = image2_wenhao;
}
创造类的实体对象
//创建类的实体对象
Player player1 = new Player();
Player player2 = new Player();
Calculator Calculator = new Calculator();
随机算法切换
private void open_on_CheckedChanged(object sender, EventArgs e)
{
if (open_on.Checked) {
num_suanfa = 1;
MessageBox.Show("已经打开机器学习模式,玩家的胜率将会降低");
}
}
private void open_off_CheckedChanged(object sender, EventArgs e)
{
if (open_off.Checked)
{
num_suanfa = 2;
MessageBox.Show("已经关闭机器学习,玩家的胜率将会提高到1/3附近");
}
}
最简单的机器学习算法。。。利用统计结果动态调整随机的概率。。。
玩家图片切换
private void ra_shitou_CheckedChanged(object sender, EventArgs e)
{
if (ra_shitou.Checked) {
pb1.Image = image1_shitou;
exe1 = 0;
}
}
private void ra_jiandao_CheckedChanged(object sender, EventArgs e)
{
if (ra_jiandao.Checked)
{
pb1.Image = image1_jiandao;
exe1 = 1;
}
}
private void ra_bu_CheckedChanged(object sender, EventArgs e)
{
if (ra_bu.Checked)
{
pb1.Image = image1_bu;
exe1 = 2;
}
}
电脑人准备
#region 电脑人准备
public void ready() {
Calculator.CalRandom();
Calculator.SetPctBu(player1.GetPctBu());
Calculator.SetPctJiandao(player1.GetPctJiandao());
Calculator.SetPctShitou(player1.GetPctShitou());
if (num_suanfa == 1) {//使用机器学习算法
Calculator.CalExeculation1();
} else
if (num_suanfa == 2) { //使用1/3算法
Calculator.CalExeculation2();
}
exe2 = Calculator.GetExe();
player2.SetExe(exe2);
//MessageBox.Show(player2.GetSum().ToString());
}
#endregion
电脑需要在玩家之前做好准备。
玩家动作
#region 玩家动作
public void action(){
player1.SetExe(exe1);
Calculator.judge1(exe1, exe2);
switch (Calculator.GetJudge())
{
case 0:
player1.success();
player2.fail();
label4.Text = "左边胜利";
break;
case 1:
player1.equal();
player2.equal();
label4.Text = "平局";
break;
case 2:
player1.fail();
player2.success();
label4.Text = "右边胜利";
break;
default:
break;
}
player1.Percent();
player2.Percent();
}
#endregion
这部分是玩家的动作对游戏的影响,涉及到判断胜负及数据存储等操作。
刷新界面
#region 刷新界面
public void flush() {
//更新文字
lb_sc1.Text = player1.GetSc().ToString();
lb_eq1.Text = player1.Geteq().ToString();
lb_fl1.Text = player1.GetFl().ToString();
lb_pct_sc1.Text = (player1.GetPctSuccess()*100).ToString()+"%";
lb_pct_eq1.Text = (player1.GetPctEqual()*100).ToString()+"%";
lb_pct_fl1.Text = (player1.GetPctFail()*100).ToString()+"%";
lb_sc2.Text = player2.GetSc().ToString();
lb_eq2.Text = player2.Geteq().ToString();
lb_fl2.Text = player2.GetFl().ToString();
lb_pct_sc2.Text = (player1.GetPctFail() * 100).ToString() + "%";
lb_pct_eq2.Text = (player2.GetPctEqual() * 100).ToString() + "%";
lb_pct_fl2.Text = (player1.GetPctSuccess() * 100).ToString() + "%";
//更新右侧图
if (exe2 == 0) {
pb2.Image = image2_shitou;
} else
if (exe2 == 1) {
pb2.Image = image2_jiandao;
} else
if (exe2 == 2) {
pb2.Image = image2_bu;
}
//更新左侧图
if (exe1 == 0)
{
pb1.Image = image1_shitou;
}
else
if (exe1 == 1)
{
pb1.Image = image1_jiandao;
}
else
if (exe1 == 2)
{
pb1.Image = image1_bu;
}
}
#endregion
这部分主要是刷新展示的内容,故笔者进行了封装。
调用
#region 准备出拳
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
ra_jiandao.Enabled = true;
ra_shitou.Enabled = true;
ra_bu.Enabled = true;
button1.Text = "请出拳";
ready();
button2.Enabled = true;
pb1.Image = image1_wenhao;
pb2.Image = image2_wenhao;
label4.Text = "请出拳";
}
#endregion
#region 确定出拳
private void button2_Click(object sender, EventArgs e)
{
//MessageBox.Show(player2.GetSc().ToString());
button1.Text = "不服,再来一局!";
button1.Enabled = true;
button2.Enabled = false;
ra_jiandao.Enabled = false;
ra_shitou.Enabled = false;
ra_bu.Enabled = false;
action();
flush();
}
#endregion
对封装好的方法进行调用,这是主要功能。
效果展示
开启最简单的机器学习算法后,胜率较之前会降低,尤其是玩家无脑出同一个拳的时候尤为明显。
尾言
算法中判断输赢采用了枚举的方式,事实上,由于只存在0,1,2这3种情况,按照共模的基本原理,还可以判断player1.ex==(player2.ex+2)%3
为胜利,player1.ex==(player2.ex+1)%3
为失败。
0 0 平
0 1 胜
0 2 负
1 0 负
1 1 平
1 2 胜
2 0 胜
2 1 负
2 2 平
没错吧?
至于像样的机器学习算法。。。已经留好了接口,日后有机会学习和验证。