提高足球模拟算法

7
在另一个问题中,您帮助我构建了一个足球模拟算法。我在那里得到了一些非常好的答案。再次感谢!现在我已经编写了这个算法。我想改进它,并找出可能存在的小错误。我不想讨论如何解决它 - 就像我们在上一个问题中所做的那样。现在我只想改进它。请问您能再次帮助我吗?
  1. 是否有任何错误?
  2. 嵌套的if条件结构是否正确?它可以被改进吗?
  3. 根据我的说明,战术是否被正确整合?

应该对随机性产生影响的战术设置:

  • $tactics[x][0] 调整(1=防守,2=中立,3=进攻):数值越高,防御越弱,进攻越强
  • $tacticsx 比赛节奏(1=慢,2=中等,3=快):数值越高,机会越好,但遭受快速反击的风险也越高
  • $tacticsx 传球距离(1=短,2=中等,3=长):数值越高,获得的机会越少但更好,越容易越位
  • $tacticsx 创造机会(1=安全,2=中等,3=冒险):数值越高,你的机会越好,但遭受快速反击的风险也越高
  • $tactics[x][4] 防守压力(1=低,2=中等,3=高):数值越高,你会有更多的快速反击
  • $tactics[x][5] 侵略性(1=低,2=中等,3=高):数值越高,你会通过犯规停止更多的进攻
注意:策略0和策略4在引擎的其他部分中部分集成,不需要在此函数中使用。
当前算法:
<?php
function tactics_weight($wert) {
    $neuerWert = $wert*0.1+0.8;
    return $neuerWert;
}
function strengths_weight($wert) {
    $neuerWert = log10($wert+1)+0.35;
    return $neuerWert;
}
function Chance_Percent($chance, $universe = 100) {
    $chance = abs(intval($chance));
    $universe = abs(intval($universe));
    if (mt_rand(1, $universe) <= $chance) {
        return true;
    }
    return false;
}
function simulate_attack($teamname_att, $teamname_def, $strength_att, $strength_def) {
    global $minute, $goals, $_POST, $matchReport, $fouls, $yellowCards, $redCards, $offsides, $shots, $tactics;
    // input values: attacker's name, defender's name, attacker's strength array, defender's strength array
    // players' strength values vary from 0.1 to 9.9
    $matchReport .= '<p>'.$minute.'\': '.comment_action($teamname_att, 'attack');
    $offense_strength = $strength_att['forwards']/$strength_def['defenders'];
    $defense_strength = $strength_def['defenders']/$strength_att['forwards'];
    if (Chance_Percent(50*$offense_strength*tactics_weight($tactics[$teamname_att][1])/tactics_weight($tactics[$teamname_att][2]))) {
        // attacking team passes 1st third of opponent's field side
        $matchReport .= ' '.comment_action($teamname_def, 'advance');
        if (Chance_Percent(25*tactics_weight($tactics[$teamname_def][5]))) {
            // the defending team fouls the attacking team
            $fouls[$teamname_def]++;
            $matchReport .= ' '.comment_action($teamname_def, 'foul');
            if (Chance_Percent(43)) {
                // yellow card for the defending team
                $yellowCards[$teamname_def]++;
                $matchReport .= ' '.comment_action($teamname_def, 'yellow');
            }
            elseif (Chance_Percent(3)) {
                // red card for the defending team
                $redCards[$teamname_def]++;
                $matchReport .= ' '.comment_action($teamname_def, 'red');
            }
            // indirect free kick
            $matchReport .= ' '.comment_action($teamname_att, 'iFreeKick');
            if (Chance_Percent(25*strengths_weight($strength_att['forwards']))) {
                // shot at the goal
                $shots[$teamname_att]++;
                $matchReport .= ' '.comment_action($teamname_att, 'iFreeKick_shot');
                if (Chance_Percent(25/strengths_weight($strength_def['goalkeeper']))) {
                    // attacking team scores
                    $goals[$teamname_att]++;
                    $matchReport .= ' '.comment_action($teamname_att, 'shot_score');
                }
                else {
                    // defending goalkeeper saves
                    $matchReport .= ' '.comment_action($teamname_def, 'iFreeKick_shot_save');
                }
            }
            else {
                // defending team cleares the ball
                $matchReport .= ' '.comment_action($teamname_def, 'iFreeKick_clear');
            }
        }
        elseif (Chance_Percent(17)*tactics_weight($tactics[$teamname_att][2])) {
            // attacking team is caught offside
            $offsides[$teamname_att]++;
            $matchReport .= ' '.comment_action($teamname_def, 'offside');
        }
        else {
            // attack isn't interrupted
            // attack passes the 2nd third of the opponent's field side - good chance
            $matchReport .= ' '.comment_action($teamname_def, 'advance_further');
            if (Chance_Percent(25*tactics_weight($tactics[$teamname_def][5]))) {
                // the defending team fouls the attacking team
                $fouls[$teamname_def]++;
                $matchReport .= ' '.comment_action($teamname_def, 'foul');
                if (Chance_Percent(43)) {
                    // yellow card for the defending team
                    $yellowCards[$teamname_def]++;
                    $matchReport .= ' '.comment_action($teamname_def, 'yellow');
                }
                elseif (Chance_Percent(3)) {
                    // red card for the defending team
                    $redCards[$teamname_def]++;
                    $matchReport .= ' '.comment_action($teamname_def, 'red');
                }
                if (Chance_Percent(19)) {
                    // penalty for the attacking team
                    $shots[$teamname_att]++;
                    $matchReport .= ' '.comment_action($teamname_att, 'penalty');
                    if (Chance_Percent(77)) {
                        // attacking team scores
                        $goals[$teamname_att]++;
                        $matchReport .= ' '.comment_action($teamname_att, 'shot_score');
                    }
                    elseif (Chance_Percent(50)) {
                        // shot misses the goal
                        $matchReport .= ' '.comment_action($teamname_att, 'penalty_miss');
                    }
                    else {
                        // defending goalkeeper saves
                        $matchReport .= ' '.comment_action($teamname_def, 'penalty_save');
                    }
                }
                else {
                    // direct free kick
                    $matchReport .= ' '.comment_action($teamname_att, 'dFreeKick');
                    if (Chance_Percent(33*strengths_weight($strength_att['forwards']))) {
                        // shot at the goal
                        $shots[$teamname_att]++;
                        $matchReport .= ' '.comment_action($teamname_att, 'dFreeKick_shot');
                        if (Chance_Percent(33/strengths_weight($strength_def['goalkeeper']))) {
                            // attacking team scores
                            $goals[$teamname_att]++;
                            $matchReport .= ' '.comment_action($teamname_att, 'shot_score');
                        }
                        else {
                            // defending goalkeeper saves
                            $matchReport .= ' '.comment_action($teamname_def, 'dFreeKick_shot_save');
                        }
                    }
                    else {
                        // defending team cleares the ball
                        $matchReport .= ' '.comment_action($teamname_def, 'dFreeKick_clear');
                    }
                }
            }
            elseif (Chance_Percent(62*strengths_weight($strength_att['forwards'])*tactics_weight($tactics[$teamname_att][2])*tactics_weight($tactics[$teamname_att][3]))) {
                // shot at the goal
                $shots[$teamname_att]++;
                $matchReport .= ' '.comment_action($teamname_att, 'shot');
                if (Chance_Percent(30/strengths_weight($strength_def['goalkeeper']))) {
                    // the attacking team scores
                    $goals[$teamname_att]++;
                    $matchReport .= ' '.comment_action($teamname_att, 'shot_score');
                }
                else {
                    if (Chance_Percent(50)) {
                        // the defending defenders block the shot
                        $matchReport .= ' '.comment_action($teamname_def, 'shot_block');
                    }
                    else {
                        // the defending goalkeeper saves
                        $matchReport .= ' '.comment_action($teamname_def, 'shot_save');
                    }
                }
            }
            else {
                // attack is stopped
                $matchReport .= ' '.comment_action($teamname_def, 'stopped');
                if (Chance_Percent(15*$defense_strength*tactics_weight($tactics[$teamname_att][1])*tactics_weight($tactics[$teamname_att][3])*tactics_weight($tactics[$teamname_def][4]))) {
                    // quick counter attack - playing on the break
                    $strength_att['defenders'] = $strength_att['defenders']*0.8; // weaken the current attacking team's defense
                    $matchReport .= ' '.comment_action($teamname_def, 'quickCounterAttack');
                    $matchReport .= ' ['.$goals[$_POST['team1']].':'.$goals[$_POST['team2']].']</p>'; // close comment line
                    return simulate_attack($teamname_def, $teamname_att, $strength_def, $strength_att); // new attack - this one is finished
                }
            }
        }
    }
    // attacking team doesn't pass 1st third of opponent's field side
    elseif (Chance_Percent(15*$defense_strength*tactics_weight($tactics[$teamname_att][1])*tactics_weight($tactics[$teamname_att][3])*tactics_weight($tactics[$teamname_def][4]))) {
        // attack is stopped
        // quick counter attack - playing on the break
        $matchReport .= ' '.comment_action($teamname_def, 'stopped');
        $strength_att['defenders'] = $strength_att['defenders']*0.8; // weaken the current attacking team's defense
        $matchReport .= ' '.comment_action($teamname_def, 'quickCounterAttack');
        $matchReport .= ' ['.$goals[$_POST['team1']].':'.$goals[$_POST['team2']].']</p>'; // close comment line
        return simulate_attack($teamname_def, $teamname_att, $strength_def, $strength_att); // new attack - this one is finished
    }
    else {
        // ball goes into touch - out of the field
        $matchReport .= ' '.comment_action($teamname_def, 'throwIn');
        if (Chance_Percent(33)) {
            // if a new chance is created
            if (Chance_Percent(50)) {
                // throw-in for the attacking team
                $matchReport .= ' '.comment_action($teamname_def, 'throwIn_att');
                $matchReport .= ' ['.$goals[$_POST['team1']].':'.$goals[$_POST['team2']].']</p>'; // close comment line
                return simulate_attack($teamname_att, $teamname_def, $strength_att, $strength_def); // new attack - this one is finished
            }
            else {
                // throw-in for the defending team
                $matchReport .= ' '.comment_action($teamname_def, 'throwIn_def');
                $matchReport .= ' ['.$goals[$_POST['team1']].':'.$goals[$_POST['team2']].']</p>'; // close comment line
                return simulate_attack($teamname_def, $teamname_att, $strength_def, $strength_att); // new attack - this one is finished
            }
        }
    }
    $matchReport .= ' ['.$goals[$_POST['team1']].':'.$goals[$_POST['team2']].']</p>'; // close comment line
    return TRUE; // finish the attack
}
更新(2014年): 几年后,我现在已经将游戏的完整代码库发布到GitHub上的开源。如果有人感兴趣,可以在此文件中找到该模拟的具体实现。

1
我不确定Stackoverflow是否是讨论191行代码的合适场所,特别是因为你可能是唯一知道自己代码是否100%语义正确的人。 提示:选择一种语言,不要在代码中混用英语和德语。 - middus
@middus:抱歉,我用德语编写了代码,但我已经为您翻译了所有重要部分。也许这是个愚蠢的问题:什么是“191 LOC”?我认为有人可以帮助我,因为问题中包含了所有必要的数据。让我们看看... - caw
1
您展示的是足球比赛的模型,但我不确定这是否在技术上可以被称为"模拟"。模拟是一种特定类型的建模,它在时间上建立内部状态的变化模型。这意味着当前的内部“状态”(与外部的“条件”相对应,即您的配置或属性设置)部分或完全决定了此时可能发生的事件。我不精通PHP,但是我没有看到任何关于时间、状态改变或基于可变状态确定事件的参考。 - RBarryYoung
当然有状态的变化:进球,越位,黄牌,红牌和犯规都会增加(++)。 ;) 此外,比赛报告中还添加了新的评论。时间计数器是在此功能之外实现的。每次进攻都会调用此功能。 - caw
但是,除非这些更改影响模型中发生的事件(或可能发生的事件),否则它们实际上是外部状态,而不是内部状态。例如,红牌的累积是否最终会导致玩家被取出比赛,并随之而来的该团队在比赛中属性的变化。如果是这样,那么我会称之为模拟(我不知道,因为我不太了解php足以辨别代码中这个问题的细节)。 - RBarryYoung
4个回答

8
总的来说,这似乎是一个相当复杂的问题,我不确定你能否高效地解决它。
话虽如此,我看到了一些可以帮助你的东西。
首先,我会在参数中输入变量。这可能不一定会使您的代码更快,但会使其更易于阅读和调试。接下来,我会删除$teamname_att、$teamname_def参数,并将其作为关联数组$strength_att、$strength_def的值。由于这些数据始终成对出现,因此这将减少意外使用一个团队的名称作为对另一个团队的引用的风险。
这将使您无需不断查找数组中的值:
// replace all $tactics[$teamname_att] with $attackers
$attackers = $tactics[$teamname_att]; 
$defenders = $tactics[$teamname_def];
// Now do the same with arrays like $_POST[ "team1" ];

你有三个帮助函数,它们都具有以下模式:
function foo( $arg ){
    $bar = $arg * $value;
    return $bar;
}

由于这意味着每次运行函数都需要创建一个额外的变量(这可能是昂贵的),因此请改用以下方法:

function tactics_weight($wert) {
    return $wert*0.1+0.8;
}

function strengths_weight($wert) {
    return log10($wert+1)+0.35;
}

/*
 Perhaps I missed it, but I never saw Chance_Percent( $num1, $num2 )
 consider using this function instead: (one line instead of four, it also
 functions more intuitively, Chance_Percent is your chance out of 100 
 (or per cent)

 function Chance_Percent( $chance ) {
     return (mt_rand(1, 100) <= $chance);
 }    

*/
function Chance_Percent($chance, $universe = 100) {
    $chance = abs(intval($chance)); // Will you always have a number as $chance?
                                    // consider using only abs( $chance ) here.
    $universe = abs(intval($universe));
    return (mt_rand(1, $universe) <= $chance);
}

我不禁注意到这种模式一直出现:

$matchReport .= ' ' . comment_action($teamname_att, 'attack');

我的一般经验是,如果将$matchReport的拼接移动到comment_action中,则它会稍微快一些(通常少于十几毫秒,但由于您在递归函数内调用该函数半打次,因此每次运行可能会节省几分之一秒)。

我认为这样会更流畅(从读者的角度和从

最后,有几次您将使用相同的参数对同一函数进行相同的调用。提前调用该函数:

$goalieStrength = strengths_weight($strength_def['goalkeeper']);

希望这能帮到你。

非常感谢您!您的技巧加快了脚本的速度,并使其更清晰。我认为我会实现您的所有建议。 - caw

5

您好,看起来您缺少:

#include oscar.h;
void function dive (int ball_location, int[] opposition, int[] opposition_loc) {
    if (this.location != PenaltyBox || ball_location != PenatlyBox)
       return;
    } else {
       for (x = 0; x < 11; x++) {
           if ( opposition_loc[x] = PenaltyBox ) {
               scream(loudly);
               falldown();
               roll_around();
               cry();
               roll_around();
               scream(patheticaly);
               plead_with_ref();
               return;
            }
     }
     return;
}

1
非常有趣,也许我会将其作为复活节彩蛋或有趣的游戏实现。;) - caw

5
我快速阅读了一下,注意到几个问题:
  • 在场地的三分之一中发出红/黄牌的比例是相同的,这是有意为之吗?虽然我不是足球迷,但我认为犯规更可能发生在场地的最后一个三分之一而不是第一个三分之一。(因为如果你在第一个三分之一,你很可能在防守)

  • 确定罚球得分的百分比对于每个队都是相同的,然而有些队或者说球员比其他人更有可能罚进点球。

  • 你没有考虑到角球、犯规后可能的受伤以及用头球进球(这可能值得在报告中提到)。

除此之外,你需要运行这个模拟器很多次,看看你选择的值是否正确;调整算法。最好的方法是手动调整(例如从文件中读取所有常量,并使用不同的值和不同的团队运行数百次模拟),最简单的方法可能是实现遗传算法来寻找更好的值。

基本上,你所拥有的是真正的游戏玩法/ AI 代码,因此你可能需要阅读游戏工作室用于管理此类代码的技术。(例如将变量放入 Google 电子表格中,然后您可以更轻松地共享/调整)。

此外,即使你缺少了一些真正的足球比赛所具有的东西,也没有必要尽可能地逼真,因为通常在这些情况下,提供良好的游戏玩法比提供准确的模拟更为重要。


谢谢,非常好的建议! :) 我会看看所有的建议。我认为我可以通过你的提示提高模拟的质量。 - caw
运行这个模拟很多次,看看你选择的值是否正确。我该怎么做呢?只需查看所有比赛报告,然后决定它们是否真实即可吗?最简单的方法可能是实现遗传算法来尝试找到更好的值。那么遗传算法应该如何做到这一点呢?如何衡量每个种群的质量/适应性/成功程度? - caw
你所做的是为一些众所周知的团队找到一些值(或者说,大多数时间赢得比赛的团队和你预计输掉所有比赛的团队)。然后你决定哪些结果最符合你的意愿。现在这个问题的问题当然是,你对结果的解释可能非常主观。 - Jasper Bekkers
对于遗传算法,我的做法是尝试模拟一个真实的足球比赛,例如运行算法直到算法结果与比赛中正确的位置相匹配。例如,基于生成的比赛和实际比赛之间的Levenshtein距离来确定适应度分数。现在显然你为团队分配的值将是主观的;但这些值将产生大致预期的结果。还会有随机数的问题,但你可能只需模拟掉 PNRG 即可。 - Jasper Bekkers
显然,你并不需要一个完美的实现或者完美的算法。唯一真正的任务是,算法提供的结果符合用户的期望,并且有趣可玩。 - Jasper Bekkers

0
这些值会被检查多频繁?如果将被许多人使用并不断递归这些if/else语句,我可以看到您会消耗大量内存并运行得相当缓慢。
也许您可以在其中放置一些开关来替换其中的某些if语句?
这就是我能看到的提高速度的全部内容。至于算法本身,如果没有其他人处理,我稍后会仔细研究一下。

非常感谢,BraedenP!开关语句真的比if/else语句更快吗?我认为我不能使用开关语句,因为所有条件语句都是嵌套的。我不知道如何合理地在这里使用开关语句。如果您能稍后对算法本身说些什么那就太好了。 - caw
为什么要考虑优化?他要求改进他的算法。 - Jasper Bekkers
优化不是改进吗? - BraedenP
当然是的。 :) 优化是一种改进,试图使某物完美,不是吗?它试图消除最后几个弱点。 - caw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接