views:

932

answers:

4

Hello!

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
  • $tactics[x][2] 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
  • $tactics[x][3] 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
}
A: 

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
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.
Why consider optimizing? He asked for improvements for his algorithm.
Jasper Bekkers
And an optimization isn't an improvement?
BraedenP
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.
+7  A: 

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.

Christopher W. Allen-Poole
Thank you very much! Your tips speed up the script and make it clearer. I will implement all of your proposals I think.
+3  A: 

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
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.
"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?
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
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
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
A: 

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
:D Very funny, maybe I'll implement this as an easter egg or for fun games. ;)