title: Soccer simulation for a game
body: <p>I would like to build a simulation engine which can simulate a soccer (association football) match. It would be cool great if you could help me. What is important to me is to decide which actions happen. The event listeners for each action can be implemented later easily. The function should only simulate the game results and comments to happening actions. There is no 2D/3D graphics needed. We're talking about games like <a href="http://www.hattrick.org/" rel="noreferrer">Hattrick</a>.</p>
  
  <hr>
  
  <p>I would propose that you have an array of minutes with actions at first.</p>
  
  <p>$minutes = array(1, 3, 4, 7, 11, 13, ..., 90, 92);</p>
  
  <p>For each of these minutes, you could then simulate an attack.</p>
  
  <p>The attacking team is determined by dice before: $attacking = mt_rand(1, 2);</p>
  
  <p>So the part which is most important to me is the attack function.</p>
  
  <p>Please edit my approach or use it as a sample. Can you help me to improve this? The function should be complex so that the results are as realistic as possible. But you need to find something between high predictability and too random results. I only want to improve this function.</p>
  
  <p><strong>My approach:</strong></p>
  
  <pre><code>&lt;?php
  function Chance_Percent($chance, $universe = 100) {
      $chance = abs(intval($chance));
      $universe = abs(intval($universe));
      if (mt_rand(1, $universe) &lt;= $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, $schuesse, $taktiken;
      // 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
      // ADJUSTMENT START
      switch ($taktiken[$teamname_att][0]) {
          case 1: $strength_att['defenders'] *= 1.1; $strength_att['forwards'] *= 0.9; break;
          case 3: $strength_att['defenders'] *= 0.9; $strength_att['forwards'] *= 1.1; break;
      }
      switch ($taktiken[$teamname_def][0]) {
          case 1: $strength_def['defenders'] *= 1.1; $strength_def['forwards'] *= 0.9; break;
          case 3: $strength_def['defenders'] *= 0.9; $strength_def['forwards'] *= 1.1; break;
      }
      // ADJUSTMENT END
      $matchReport .= '&lt;p&gt;'.$minute.'\': '.comment($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*($taktiken[$teamname_att][2]/2)*($taktiken[$teamname_att][3]/2))) {
          // attacking team passes 1st third of opponent's field side
          $matchReport .= ' '.comment($teamname_def, 'attack_advance');
          if (Chance_Percent(25*($taktiken[$teamname_def][4]/2))) {
              // the defending team fouls the attacking team
              $fouls[$teamname_def]++;
              $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul');
              if (Chance_Percent(43)) {
                  // yellow card for the defending team
                  // chance is correct for my purpose
                  $yellowCards[$teamname_def]++;
                  $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_yellow');
              }
              elseif (Chance_Percent(3)) {
                  // red card for the defending team
                  // chance is correct for my purpose (only 1.43% because it's an alternative way)
                  $redCards[$teamname_def]++;
                  $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_red');
              }
              // indirect free kick
              // only 58.23% because it's an alternative way
              $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick');
              if (Chance_Percent(25)) {
                  // shot at the goal
                  $schuesse[$teamname_att]++;
                  $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick_shot');
                  if (Chance_Percent(25)) {
                      // attacking team scores (6.25% chance)
                      $goals[$teamname_att]++;
                      $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick_shot_score');
                  }
                  else {
                      // defending goalkeeper saves
                      // only 18.75% because it's an alternative way
                      $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick_shot_save');
                  }
              }
              else {
                  // defending team cleares the ball
                  // only 75% because it's an alternative way
                  $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick_clear');
              }
          }
          elseif (Chance_Percent(17)) {
              // attacking team is caught offside
              // only 4.25% because it's an alternative way
              $offsides[$teamname_att]++;
              $matchReport .= ' '.comment($teamname_def, 'attack_advance_offside');
          }
          else {
              if (Chance_Percent(25*($taktiken[$teamname_def][5]/2))) {
                  // the defending team fouls the attacking team
                  $fouls[$teamname_def]++;
                  $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul');
                  if (Chance_Percent(43)) {
                      // yellow card for the defending team
                      // chance is correct for my purpose
                      $yellowCards[$teamname_def]++;
                      $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_yellow');
                  }
                  elseif (Chance_Percent(3)) {
                      // red card for the defending team
                      // chance is correct for my purpose (only 1.43% because it's an alternative way)
                      $redCards[$teamname_def]++;
                      $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_red');
                  }
                  if (Chance_Percent(19)) {
                      // penalty for the attacking team
                      $schuesse[$teamname_att]++;
                      $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_penalty');
                      if (Chance_Percent(77)) {
                          // attacking team scores (77% chance according to Wikipedia)
                          $goals[$teamname_att]++;
                          $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_penalty_score');
                      }
                      elseif (Chance_Percent(50)) {
                          // shot misses the goal
                          // only 11.5% because it's an alternative way
                          $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_penalty_miss');
                      }
                      else {
                          // defending goalkeeper saves
                          // only 11.5% because it's an alternative way
                          $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_penalty_save');
                      }
                  }
                  elseif (Chance_Percent(28)) {
                      // direct free kick
                      // only 22.68% because it's an alternative way
                      $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_dFreeKick');
                      if (Chance_Percent(33)) {
                          // shot at the goal
                          $schuesse[$teamname_att]++;
                          $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_dFreeKick_shot');
                          if (Chance_Percent(33)) {
                              // attacking team scores (10.89% chance)
                              $goals[$teamname_att]++;
                              $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_dFreeKick_shot_score');
                          }
                          else {
                              // defending goalkeeper saves
                              $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_dFreeKick_shot_save');
                          }
                      }
                      else {
                          // defending team cleares the ball
                          // only 77% because it's an alternative way
                          $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_dFreeKick_clear');
                      }
                  }
                  else {
                      // indirect free kick
                      // only 58.23% because it's an alternative way
                      $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick');
                      if (Chance_Percent(25)) {
                          // shot at the goal
                          $schuesse[$teamname_att]++;
                          $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick_shot');
                          if (Chance_Percent(25)) {
                              // attacking team scores (6.25% chance)
                              $goals[$teamname_att]++;
                              $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick_shot_score');
                          }
                          else {
                              // defending goalkeeper saves
                              // only 18.75% because it's an alternative way
                              $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick_shot_save');
                          }
                      }
                      else {
                          // defending team cleares the ball
                          // only 75% because it's an alternative way
                          $matchReport .= ' '.comment($teamname_def, 'attack_advance_foul_iFreeKick_clear');
                      }
                  }
              }
              else {
                  // attack passes the 2nd third of the opponent's field side - good chance
                  $matchReport .= ' '.comment($teamname_def, 'attack_advance_advance');
                  if (Chance_Percent(62*($taktiken[$teamname_att][6]/2)*($taktiken[$teamname_att][7]/2)/($taktiken[$teamname_att][8]/2)*($taktiken[$teamname_att][9]/2)/($taktiken[$teamname_def][10]/2))) {
                      // shot at the goal
                      $schuesse[$teamname_att]++;
                      $matchReport .= ' '.comment($teamname_def, 'attack_advance_advance_shot');
                      if (Chance_Percent(30*$strength_def['goalkeeper']/7/($taktiken[$teamname_att][11]/2))) {
                          // the attacking team scores
                          // only 8.78% because it's an alternative way
                          // if goalkeeper has strenth 7 then chance is 8.78% otherwise lower/higher
                          $goals[$teamname_att]++;
                          $matchReport .= ' '.comment($teamname_def, 'attack_advance_advance_shot_score');
                      }
                      else {
                          if (Chance_Percent(50)) {
                              // the defending defenders block the shot
                              $matchReport .= ' '.comment($teamname_def, 'attack_advance_advance_shot_block');
                          }
                          else {
                              // the defending goalkeeper saves
                              $matchReport .= ' '.comment($teamname_def, 'attack_advance_advance_shot_save');
                          }
                      }
                  }
              }
          }
      }
      // attacking team doesn't pass 1st third of opponent's field side
      elseif (Chance_Percent(15*$defense_strength*($taktiken[$teamname_att][12]/2)*($taktiken[$teamname_att][13]/2))) {
          // quick counter attack - playing on the break
          // only 7.5% because it's an alternative way
          // if defense has strength 7 then chance is 7.5% otherwise lower/higher
          $strength_att['defenders'] = $strength_att['defenders']*0.8; // weaken the current attacking team's defense
          $matchReport .= ' '.comment($teamname_def, 'attack_quickCounterAttack');
          $matchReport .= ' ['.$goals[$_POST['team1']].':'.$goals[$_POST['team2']].']&lt;/p&gt;'; // 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($teamname_def, 'attack_throwIn');
          if (Chance_Percent(33)) {
              // if a new chance is created
              if (Chance_Percent(50)) {
                  // throw-in for the attacking team
                  $matchReport .= ' '.comment($teamname_def, 'attack_throwIn_att');
                  $matchReport .= ' ['.$goals[$_POST['team1']].':'.$goals[$_POST['team2']].']&lt;/p&gt;'; // 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($teamname_def, 'attack_throwIn_def');
                  $matchReport .= ' ['.$goals[$_POST['team1']].':'.$goals[$_POST['team2']].']&lt;/p&gt;'; // 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']].']&lt;/p&gt;'; // close comment line
      return TRUE; // finish the attack
  }
  ?&gt;
  </code></pre>
  
  <p><strong>Tactical settings which should have an influence on the randomness:</strong></p>
  
  <ul>
  <li>adjustment (1=defensive, 2=neutral, 3=offensive): the higher the value is the weaker is the defense and the stronger is the offense</li>
  <li>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</li>
  <li>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</li>
  <li>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</li>
  <li>pressure in defense (1=low, 2=medium, 3=high): the higher the value is the more quick counter attacks you will have</li>
  <li>aggressivity (1=low, 2=medium, 3=high): the higher the value is the more attacks you will stop by fouls</li>
  </ul>
  
  <p><strong>Integration of the tactical settings:</strong></p>
  
  <p>All the tactical settings have a value which can be "1", "2" or "3". "2" is always neutral/medium. So I divide the values by 2. I get a ratio which is 0.5 or 1 or 1.5. I thought that I could then easily multiply the chances by this in order to integrate tactical influence. But one problem has evolved: If I multiply a chance by 2 or more tactical values, it can be higher than 100% (60 x 1.5 x 1.5 for example). So I can't integrate the tactics this way. What else can I do?</p>
  
  <hr>
  
  <p>Thank you very much!</p>
  
  <p><strong>Update (2014):</strong> A few years later, I have now released the full code base of the game as open-source <a href="https://github.com/delight-im/Ballmanager" rel="noreferrer">on GitHub</a>. You'll find the specific implementation of this simulation <a href="https://github.com/delight-im/Ballmanager/blob/master/Website/aa_spieltag_simulation.php" rel="noreferrer">in this file</a>, if anyone is interested.</p>