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
}

Comments

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.

Written by middus

@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 ...

Written by Marco W.

LOC = Lines of code

Written by lubos hasko

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.

Written by RBarryYoung

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.

Written by Marco W.

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).

Written by RBarryYoung

Accepted Answer

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.

Written by cwallenpoole
This page was build to provide you fast access to the question and the direct accepted answer.
The content is written by members of the stackoverflow.com community.
It is licensed under cc-wiki