How do I create a timer like with UN/AP votes?

Joined
Jun 27, 2007
Messages
2,248
Location
Hamilton, Ontario
The way it currently is, global warming is worse on slower speed games because there are more turns for it to occur. I could divide the GW chance by the speed (0.66 for quick, 1.5 for epic, 3 for marathon) but that's not really the same. Three 33% chances aren't the same as having one 100% chance, so it would be better to have it run the code exactly once every tree turns in that example.
So I found this code:
Code:
int CvGame::getSecretaryGeneralTimer(VoteSourceTypes eVoteSource) const
{
	FAssert(eVoteSource >= 0);
	FAssert(eVoteSource < GC.getNumVoteSourceInfos());
	return m_aiSecretaryGeneralTimer[eVoteSource];
}


void CvGame::setSecretaryGeneralTimer(VoteSourceTypes eVoteSource, int iNewValue)
{
	FAssert(eVoteSource >= 0);
	FAssert(eVoteSource < GC.getNumVoteSourceInfos());
	m_aiSecretaryGeneralTimer[eVoteSource] = iNewValue;
	FAssert(getSecretaryGeneralTimer(eVoteSource) >= 0);
}


void CvGame::changeSecretaryGeneralTimer(VoteSourceTypes eVoteSource, int iChange)
{
	setSecretaryGeneralTimer(eVoteSource, getSecretaryGeneralTimer(eVoteSource) + iChange);
}
and it could be adapted a global warming timer, but some parts like eVoteSource I don't think would apply. So could someone show me how to cut this down to the parts I need?
 
CvGame.h

Code:
public:
	int getGlobalWarmingTimer() const;
	void setGlobalWarmingTimer(int iValue);
	void changeGlobalWarmingTimer(int iChange);
	void resetGlobalWarmingTimer();

private:
	m_iGlobalWarmingTimer;

CvGame.cpp

Code:
CvGame::CvGame() :
	...
	m_iGlobalWarmingTimer(0)

// if this function exists
void CvGame::reset()
{
	...
	m_iGlobalWarmingTimer = 0;
}

int CvGame::getGlobalWarmingTimer() const
{
	return m_iGlobalWarmingTimer;
}

void CvGame::setGlobalWarmingTimer(int iValue)
{
	m_iGlobalWarmingTimer = iValue;
}

void CvGame::changeGlobalWarmingTimer(int iChange)
{
	setGlobalWarmingTimer(getGlobalWarmingTimer() + iChange);
}

void CvGame::resetGlobalWarmingTimer()
{
	setGlobalWarmingTimer(... calculate based on game speed ...);
}

void CvGame::doGlobalWarming()
{
	if (getGlobalWarmingTimer() == 0)
	{
		resetGlobalWarmingTimer();
	}
	changeGlobalWarmingTimer(-1);
	if (getGlobalWarmingTimer() == 0)
	{
		... burn!!! ...
	}
}

You also need to add m_iGlobalWarmingTimer to read() and write() and call doGlobalWarming() in doTurn() where it has the current GW code.
 
Since the desired ratio for quick is 0.66, I am not sure an integer timer would work. You really would like this checked 3 times in two turns, right? It seems much simpler to just modify the percentage by the game speed instead of introducing a timer. Since it is random anyway, I doubt the user would notice any difference between scaling the random number and introducing a timer.
 
I agree with davidallen. Plus it would be nicer to have GW strike at random turn intervals rather than every 3 turns (sometimes, sometimes not). I think the small loss of precision due to scaling is acceptable. If you wanted to get nuts, you could figure out a non-linear formula that would account for the scaling better.
 
As an example, Marathon speed has 3 times as many turns, so the likelihood of GW striking in the same amount of time is

1 - (1 - p)3

Taking p (chance of GW striking each turn) to be 50%, that gives the chance of GW striking at least once in every 3 turns of Marathon to be 7/8 or about 87%. That includes it striking twice and three times as well. If you want to have it strike at least once at the same p value (still allows multiple strikes per 3 turn set), solve

1 - (1 - f(p))3 = p​

for f(p) which will most likely be non-linear. Of course, this will probably be a different function for each game speed. I didn't say it would be easy. :mischief:

(1 - f(p))3 = (1 - p)​

1 - f(p) = (1 - p)1/3

f(p) = 1 - (1 - p)1/3

I think you can substitute n for 3 above where n is the inverse of the game speed, and it will still hold. This gives you a simple function to code in C to calculate the new p based on the XML p and the game speed.

It still allows multiple strikes, but the expected chance of no strikes would be the same. You could do the same thing using E(gw) = 1 to set the chances of exactly 1 strike to be the same, allowing for 0, 2, 3, ... strikes with smaller probabilities. It really depends on what you define as "equal chances of GW."
 
This is now completely tangential to the OP, but it seems linear to me. A 100 turn marathon game should have the same number of GW events as a 300 turn normal speed game, right? So the probability of it happening in any one turn should be 3x more. This is no longer true if the probability at normal speed ever exceeds 33%, and I have not read through the code to prove that. But one GW event every three turns in normal would feel awfully fast to me.
 
That last function I wrote is a polynomial function of p--certainly not linear--since there is a cube root involved. A linear function of p would be of the form

f(p) = ap/b​

Just scaling by the game speed doesn't produce the same rate of GW. As my example shows, it goes from 50% to 87.5% (for at least 1 in 3, sometimes more). The average (expected) number of strikes per 3 turns is (if I remember my probability classes)

E(GW) = 1 * p(1) + 2 * p(2) + 3 * p(3)​

p(1) is 3 * 12.5% = 3/8 (50%3)
p(2) is 3 * 12.5% = 3/8
p(3) is 12.5% = 1/8

I chose 50% for a reason. ;) It makes the math easy but probably muddies the example.

That means

E(GW) = 1 * 3/8 + 2 * 3/8 + 3 * 1/8 = 12/8 = 1.5​

Thus 50% chance of a strike at Normal gives the average of 0.5 strikes per turn whereas at Marathon it gives the average of 1.5 strikes for every three turns--3 times the average number of strikes for the same time period.
 
So, with your probabilities, you have determined that if normal has 0.5 strikes per turn, then marathon should have 1.5 strikes per turn. I agree. This is linear.
 
No, what I'm saying is that using an unaltered GW probability p will produce skewed GW strikes. It will result in 3 times as many strikes in a Marathon game in the same amount of calendar time as in Normal games. That seems undesirable. That means in 20 years you'd get 10 strikes on Normal (assuming 1 yr/turn) and 30 strikes on Marathon (3 yr/turn).

If you use a linear scaling of p by n (the game speed), it won't produce the same linear result in E(GW). Using again 50% as p and 3 for n (Marathon), let's scale it to 50%/3 ~ 16.7%. E(GW) would be

P(0) = (1 - p)3
P(1) = 3 * p * (1 - p)2
P(2) = 3 * p2 * (1 - p)
P(3) = p3

and so

E(GW) = 0.5​

So scaling it linearly does produce the same expected number of strikes. The chance of at least 1 strike is slightly lower (50% versus 42.1%), but some of those cases involve 2 or 3 strikes.

Of course, applying the same logic to Quick speed means you set must have 1 strike every turn to provide the same effect. This is for 50%. What is the actual GW chance per turn at Normal speed?
 
So scaling it linearly does produce the same expected number of strikes ... What is the actual GW chance per turn at Normal speed?

I am glad we agree. The percent chance of GW is given by some complex formula involving the number of previous strikes, the number of forest plots, and some other factors.
 
Recalling MA's initial description of the algorithm, it won't be that simple. I had hoped MA had done some back-of-the-envelope calculations on a few common settings.
 
I haven't done much work on this so far, but my current thinking is it would be too complicated to try and use probability to determine if it will be checked, so I'd keep the timer and maybe have a random factor added into the timer so it could shift by a few turns.
Although it was my original plan to have normal speed stay relatively the same, I'd have to make slower speeds use the timer, but have quick multiply the GW checks. Easier to have it do the same thing for all speeds. The number I've come up with for the total GW checks to happen at any speed is 150. That seems small though considering there will not be that many turns left by the industrial era, there might just be 20 checks in the whole game. It does come out as close to even as I'm likely to get:
Quick, 330 turns, 2 turn timer, 165 GW checks
Normal, 500 turns, 3 turn timer, 166 GW checks
Epic, 750 turns, 5 turn timer, 150 GW checks
Marathon, 1500 turns, 10 turn timer, 150 GW checks
Instead I could have quick be 1 turn and have normal and epic randomly add 1 or 0 essentially making them 1.5 and 2.5, with 5 for marathon. So the fastest and slowest would be set and the same every game, but the two middle have variation. Maybe that's better.

EDIT: One other idea is to have it check every turn, but when it successfully hits, start the times so it can't happen again for x turns. Might be a little less predictable, but then again it might be harder to program and not have a very noticeable difference.
 
Another option is to completely redo the calculation so that it's always in a known range. You pick the min and max chance for a GW strike that you want each check and then have various calculations slide the actual value within that. Maybe that's what it's like now, but I got the impression that it just pegs off the scale for certain map types, leading me to believe it simply adds a bunch of points and caps that.

Instead, say assign 100 points for forest/jungle defense--the % coverage over all land. Another 100 for land as % of map size. Etc. These values will never lie outside [0, 100] because they are percentages. These are the easy ones.

Ones based on population . . . yeah those get tougher to code up. How do you pick what should be the sustainable rate for the map size and land area? Oh well.
 
Top Bottom