算是突发奇想吧,在玩一个篮球教练游戏的时候对立面的各种设定无力吐槽,所以想自己写一个。
但是写起来发现真的很难,刚开始本来想模拟全场比赛的整个过程,虽然无法做到有画面呈现,但是详细的文字直播“应该是”“不成问题的”。
但是没有想到……就连这个都没有实现,本着简单的原则,只是对一个回合可能产生的结果和数据进行了模拟和记录,运用了来自nba官方的基础数据和一些高阶数据,就这样已经一千多行代码了……而且具体的文字直播还是没有思路。并且由于收集数据太过麻烦,我只收集了国王队和勇士队五个先发球员的数据,轮换、战术、体力、状态等等的东西统统没有……
虽然这样但是做出来的还是有一定参考价值的。(毕竟是NBA官方的数据,也有用到一些高阶数据)
放一下一些截图(英文省事用英文写的文字直播):
不管,先说说我demo的初版关键代码关键思路:
首先,一场比赛由一个函数实现,这个函数会传递两支球队的信息,然后比赛的具体信息则是通过一个结构体掌控:
void Game(Teams home, Teams away)
{
bool homejumped = false;
GameInformation GI;
GI.quarter = 1;
GI.sec = 720;
cout << "Welcome to the game against " << home.name << " and " << away.name << endl;
system("pause");
cout << "Let's see the starting lineup of both team\n";
system("pause");
cout << "Hometeam " << home.name << ":\n";
for (register int i = 1; i <= home.PlayersCount; ++i)
{
PlayerInGame_Clear(NBA[home.PlayersId[i]].game);
}
for (register int i = 1; i <= 5; ++i)
{
cout << NBA[home.PlayersId[i]].name << endl;
GI.homein[i] = NBA[home.PlayersId[i]].id;
}
system("pause");
cout << "Awayteam " << away.name << ":\n";
for (register int i = 1; i <= away.PlayersCount; ++i)
{
PlayerInGame_Clear(NBA[away.PlayersId[i]].game);
}
for (register int i = 1; i <= 5; ++i)
{
cout << NBA[away.PlayersId[i]].name << endl;
GI.awayin[i] = NBA[away.PlayersId[i]].id;
}
system("pause");
cout << "Now, it's time to jump ball!\n";
struct GameInformation
{
int quarter;
float sec;
bool isballhome;
int homein[6], awayin[6];
};
可以看出来这个比赛信息的结构体有一个homein和awayin的数组记录双方上场人员,和一个isballhome的变量记录球权。事实证明这是一个很逆天的错误,因为这样导致每次球权转换想要找出进攻方还得判断一次isballhome再决定调用那些数组,导致了代码量极大的增长(还是没经验啊),其实我应该直接把两个球队结构体放到里面,调取进攻方人员信息的时候就可以专门写一个函数很方便的进行内部调用……但是现在改的话……代码量大的有点不太情愿。
跳球的话我实在找不到成功率的数据,只能双方五五开……:
bool justjump = true;
if (randnum(1, 100) <= 50)
{
cout << "Hometeam is going to have the ball first\n";
GI.isballhome = true;
homejumped = true;
}
else
{
cout << "Awayteam is going to have the ball first\n";
GI.isballhome = false;
}
bool turnover = false;
看看我的justjump和turnover变量,记住,等下打补丁要考(依然是非常坑爹的决定和写法)
while (GI.quarter <= 4)
{
bool CantQuickOffense = true;
while (GI.sec > 0.00)
{
float homescore = 0.0, awayscore = 0.0;
for (register int i = 1; i <= home.PlayersCount; ++i)
homescore += NBA[home.PlayersId[i]].game.Score;
for (register int i = 1; i <= away.PlayersCount; ++i)
awayscore += NBA[away.PlayersId[i]].game.Score;
cout << "[GameInformation: quarter:" << GI.quarter << " time:" << getminute(GI.sec) << ":";
cout << fixed << setprecision(1) << float(GI.sec - int(getminute(GI.sec) * 60));
cout << " " << "Home " << int(homescore) << " : " << int(awayscore) << " Away]\n";
system("pause");
//ShowInformation
cout << "[Now Ball is holding at ";
if (GI.isballhome) cout << home.name;
else cout << away.name;
cout << "]\n";
接下来属于真正的主循环,显示展示记分牌和当节情况,没什么好说的(还有一个打补丁变量等会就知道了)
int off[6], def[6];
for (register int i = 0; i < 6; ++i)
{
off[i] = 0;
def[i] = 0;
}
if (GI.isballhome)
{
for (register int i = 1; i <= 5; ++i)
{
off[i] = GI.homein[i];
def[i] = GI.awayin[i];
}
}
else
{
for (register int i = 1; i <= 5; ++i)
{
def[i] = GI.homein[i];
off[i] = GI.awayin[i];
}
}
这段代码完美展示了刚才说的那个缺陷,要分清楚哪个队在进攻就要用一次这个循环,也导致了写在前面的无数个判断球员具体行为的函数都要用到这段代码,因为要分清进攻方和防守方(嘤嘤嘤)
int Offenser = TurnoverJudge(GI);
if (Offenser != -1)
{
DataChange(Turnover, Offenser);
int Stealer = StealerJudge(GI);
if (Stealer != -1)
{
DataChange(Steal, Stealer);
}
if (GI.isballhome == true) GI.sec -= home.QuickSecond + float(randnum(-200, 200)) / 100.0;
else GI.sec -= away.QuickSecond + float(randnum(-200, 200)) / 100.0;
GI.isballhome = !GI.isballhome;
CantQuickOffense = false;
turnover = true;
continue;
}
Offenser = FoulJudge(GI);
if (Offenser != -1)
{
DataChange(Foul, Offenser);
cout << NBA[Offenser].name << " have a defensive foul. " << "He have " << NBA[Offenser].game.Foul << " now\n";
if (GI.isballhome == true) GI.sec -= (home.QuickSecond + float(randnum(-200, 200)) / 100.0) / 2.00 + 1.00;
else GI.sec -= (away.QuickSecond + float(randnum(-200, 200)) / 100.0) / 2.00 + 1.00;
CantQuickOffense = true;
turnover = false;
continue;
}
Offenser = OffensiveFoulJudge(GI);
if (Offenser != -1)
{
DataChange(Foul, off[Offenser]);
DataChange(Turnover, off[Offenser]);
cout << NBA[off[Offenser]].name << " have a offensive foul. " << "He have " << NBA[off[Offenser]].game.Foul << " foul now\n";
if (GI.isballhome == true) GI.sec -= home.QuickSecond + float(randnum(-200, 200)) / 100.0;
else GI.sec -= away.QuickSecond + float(randnum(-200, 200)) / 100.0;
GI.isballhome = !GI.isballhome;
CantQuickOffense = false;
turnover = false;
continue;
}
这段代码就是判断这个回合有没有普通犯规、进攻犯规、失误和抢断的,Offenser其实就是进攻方的产生这些举动的行为人,而对于抢断的判断则是建立在对方失误的基础上在判断是否抢断(因为有不是抢断的失误)
如果有发生则视为一个回合结束,进行时间加减(没有24秒的概念),球权转换(如果需要的话),还要打补丁,turnover用于失误回合(因为在这里设定中凡是失误必然会导致快攻)后下一个回合快攻的判断,而CantQuickOffense则是限制下一个回合不能发生快攻(比如球权没有转换的防守犯规、进攻篮板等等)
接下来,如果上面这些都没有发生,就实打实的会发生一次投篮了(不管进不进),区别在于这次进攻是快攻还是半转换进攻还是阵地战(其实主要区别在于时间,命中率方面也有一些小差异)
bool QuickOffense = true;
if (!CantQuickOffense)
{
Offenser = QuickOffenseJudge(GI);
}
if (Offenser == -1)
{
QuickOffense = false;
Offenser