7

In another question, you helped me to build a simulation algorithm for soccer. I got some very good answers there. Thanks again!

Now I've coded this algorithm. I would like to improve it and find little mistakes which could be in it. I don't want to discuss how to solve it - as we did in the last question. Now I only want to improve it. Can you help me again please?

  1. Are there any mistakes?
  2. Is the structure of the nested if-clauses ok? Could it be improved?
  3. Are the tactics integrated correctly according to my description?

Tactical settings which should have an influence on the randomness:

  • $tactics[x][0] adjustment (1=defensive, 2=neutral, 3=offensive): the higher the value is the weaker is the defense and the stronger is the offense
  • $tacticsx speed of play (1=slow, 2=medium, 3=fast): the higher the value is the better are the opportunities but the higher is the risk of getting a quick counter attack
  • $tacticsx distance of passes (1=short, 2=medium, 3=long): the higher the value is the less but better opportunities you get and the more often you are offside
  • $tacticsx creation of changes (1=safe, 2=medium, 3=risky): the higher the value is the better are your opportunities but the higher is the risk of getting a quick counter attack
  • $tactics[x][4] pressure in defense (1=low, 2=medium, 3=high): the higher the value is the more quick counter attacks you will have
  • $tactics[x][5] aggressivity (1=low, 2=medium, 3=high): the higher the value is the more attacks you will stop by fouls

Note: Tactic 0 and tactic 4 are partly integrated in the rest of the engine, not needed in this function.

The current algorithm:

<?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
}

Update (2014): A few years later, I have now released the full code base of the game as open-source on GitHub. You'll find the specific implementation of this simulation in this file, if anyone is interested.

Community
  • 1
  • 1
caw
  • 30,999
  • 61
  • 181
  • 291
  • 1
    I'm not sure weather Stackoverflow is the right place to discuss 191 LOC. Especially as you are probably the only one who knows if you're code is 100% semantically correct. Hint: decide for a language, don't mix English and German in your code. – middus Sep 17 '09 at 15:02
  • @middus: I'm sorry. I wrote the code in German but I've translated all important parts to English for you. Maybe stupid question: What is "191 LOC"? I thought someone could help me because all necessary data are in the question. Let's see ... – caw Sep 17 '09 at 15:41
  • 1
    What you show here is a Model of a Soccer match, however, I am not sure if this technically qualifies as a "simulation". Simulation is a specific kind of modeling that models changes of internal state over time. Inherent to this is that the current internal "state" (as opposed to the external "conditions", which are your configuration or attribute settings) partially or entirely determines the possible or probable events at that point in time. I am not proficient in php, however, it do not see any reference to time or a state-change, or a determination of events based on a mutable state. – RBarryYoung Sep 25 '09 at 13:54
  • Yes, of course there are changes of state: goals, offsides, yellowCards, redCards and fouls are increased (++). ;) Furthermore, new comments are added to the match report. The time counter is implemented out of this function. This function is called for every attack. – caw Sep 25 '09 at 14:18
  • But unless those changes effect the events that occur (or that can occur) in the model, then they're really external state, rather than internal state. So for instance, can the accumulation of Red Cards eventually result in a player being taken out of play, with the consequent changes in that team's in-play attributes. If so, then yes, I'd call that a simulation (I don't know, because I don't know php well enough to discern the details of this question in the code). – RBarryYoung Sep 25 '09 at 23:33

4 Answers4

8

In general, it looks like this is a fairly complicated problem, and I'm not sure how efficient you'll get it.

That said, I have seen some things which would decidedly help you.

First I would type the variables in the parameters. This may not necessarily make your code faster, but it would make it easier to read and debug. Next, I would remove the $teamname_att, $teamname_def parameters and simply have those as values in the associative $strength_att, $strength_def arrays. Since this data is always paired up anyway, this will reduce the risk of accidentally using one team's name as a reference to the other team.

This will make it so you will not have to continually look up values in arrays:

// replace all $tactics[$teamname_att] with $attackers
$attackers = $tactics[$teamname_att]; 
$defenders = $tactics[$teamname_def];
// Now do the same with arrays like $_POST[ "team1" ];

You have three helper functions which all have the pattern:

function foo( $arg ){
    $bar = $arg * $value;
    return $bar;
}

Since this means that you have to create an extra variable (something which can be costly) each time you run the function, use these instead:

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);
}

I couldn't help but notice this pattern coming up consistently:

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

My general experience is that if you move the concatenation of $matchReport into comment_action, then it will be just slightly faster (Generally less than a dozen milliseconds, but since you're calling that function a half-dozen times inside of a recursive function, this could shave a couple tenths of a second per running).

I think that this would flow much better (both from a reader's perspective, and from

Finally, there are several times where you will use the same call to the same function with the same parameter. Make that call up front:

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

Hope this helps.

cwallenpoole
  • 79,954
  • 26
  • 128
  • 166
  • Thank you very much! Your tips speed up the script and make it clearer. I will implement all of your proposals I think. – caw Sep 24 '09 at 19:19
5

I (quickly) read through it and I noticed a couple of things:

  • The percentage a red / yellow card is handed out is the same in all thirds of the field, is this intentional? I'm not a soccer guy, but I'd say that offences are more likely to happen on the last third of the field, than on the first. (Because if you're on the first, you're likely defending)

  • The percentage to determine that a penalty is scored is the same for each team, however some teams, or rather players, are more likely to score a penalty than others.

  • You're not taking into account corner kicks, possible injuries after a foul, or goals scored using the head (which might be worth mentioning in the report).

Apart from that, you'll just need to run this simulation a lot of times and see if the values you chose are correct; tweak the algorithm. The best thing to do is hand tweak it (eg. read all the constants from a file and run a couple of hundred simulations with different values and different teams), the easiest thing to do is probably to implement a Genetic Algorithm to try and find better values.

Basically what you have here is genuine gameplay / ai code, so you might want to read up on techniques used by game studios to manage this type of code. (One thing is to put the variables in a google spreadsheet which you can then share / tweak more easily, for example).

Also, even though you're missing some things that a real soccer match has, there's no point trying to be as realistic as possible because generally in these cases it's more important to provide nice gameplay than it is to provide an accurate simulation.

Jasper Bekkers
  • 6,711
  • 32
  • 46
  • Thank you, very nice suggestions! :) I will have a look at all of them. I think I can improve the quality of the simulation with your tips. – caw Sep 24 '09 at 19:22
  • "run this simulation a lot of times and see if the values you chose are correct" How can I do this? Simply have a look at all the match reports and decide whether they are realistic or not? - "the easiest thing to do is probably to implement a Genetic Algorithm to try and find better values" How should a Genetic Algorithm do that? How to measure the quality/fitness/success of each population? – caw Sep 25 '09 at 14:23
  • What you do is find some values for some set of well known teams (or rather, teams that win most of the time and teams that you'd expect lose all the time). And then you decide which results you like most. Now the problem with this is, of course, that your interpretation of the results can be very subjectively. – Jasper Bekkers Sep 25 '09 at 17:47
  • For the Genetic algorithm, what I'd do is try to mimic an actual soccer competition eg. run the algorithm until the result of the algorithm matches the correct positions in the competition. Base the fitness score, for example, on the Levenshtein distance between the generated competition and the actual competition. Now obviously the values you've assigned to the teams will be subjective; but those values will yield roughly the desired results. There would also be a problem with with the random numbers, but you could probably just mock the PNRG away. – Jasper Bekkers Sep 25 '09 at 17:51
  • Now obviously, you don't really need a perfect implementation, or the perfect algorithm. The only real mission is that the results that the algorithm provide comply with the expectations of your users and are fun to play with. – Jasper Bekkers Sep 25 '09 at 17:59
5

Yous seem to be missing:-

#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;
}
James Anderson
  • 27,109
  • 7
  • 50
  • 78
  • 1
    :D Very funny, maybe I'll implement this as an easter egg or for fun games. ;) – caw Sep 25 '09 at 14:16
0

How often are these values going to be checked? If it's going to be in use by a lot of people and constantly recursing over those if/else statements, I can see you eating up a lot of memory and running quite slowly.

Perhaps you could but a few switches in there to replace some of the if's?

That's all I can see for speed improvement. As for the algorithm itself, I'll have to peruse over that a bit later if no one else does.

BraedenP
  • 7,125
  • 4
  • 33
  • 42
  • Thank you very much, BraedenP! Are switches really faster than if/else statements? I think I can't use switches since all conditional statements are nested. I don't have any idea how to use switches here reasonably. It would be great if you could say something to the algorithm itself later as well. – caw Sep 17 '09 at 15:50
  • Why consider optimizing? He asked for improvements for his algorithm. – Jasper Bekkers Sep 23 '09 at 21:36
  • And an optimization isn't an improvement? – BraedenP Sep 24 '09 at 00:40
  • Of course it is. :) An optimization is an improvement which tries to make something perfect, isn't it? It tries to make the last few weaknesses disappear. – caw Sep 24 '09 at 19:21