source: trunk/AI Template/AI.pas

Last change on this file was 592, checked in by chronos, 3 months ago
  • Fixed: Avoided more GTK2 chrashes.
  • Fixed: Build StdAI with O1 optimization level to avoid crash.
  • Modified: Code cleanup.
File size: 21.3 KB
Line 
1{$INCLUDE Switches.inc}
2unit AI;
3
4interface
5
6uses
7 {$IFDEF DEBUG}SysUtils,{$ENDIF} // necessary for debug exceptions
8 Protocol, CustomAI, ToolAI;
9
10type
11 UnitRole = (Roam, Defend);
12
13 TAI = class(TToolAI)
14 constructor Create(Nation: integer); override;
15
16 protected
17 procedure DoTurn; override;
18 procedure DoNegotiation; override;
19 function ChooseResearchAdvance: integer; override;
20 function ChooseGovernment: integer; override;
21 function WantNegotiation(Nation: integer; NegoTime: TNegoTime): boolean; override;
22
23 procedure ProcessSettlers;
24 procedure ProcessUnit(uix: integer; Role: UnitRole);
25 procedure SetCityProduction;
26 end;
27
28
29implementation
30
31uses
32 Pile;
33
34const
35 // fine adjustment
36 Aggressive = 40; // 0 = never attacks, 100 = attacks even with heavy losses
37 DestroyBonus = 30; // percent of building cost
38
39
40constructor TAI.Create(Nation: integer);
41begin
42 inherited;
43end;
44
45
46//-------------------------------
47// MY TURN
48//-------------------------------
49
50procedure TAI.DoTurn;
51var
52 uix: integer;
53begin
54 // correct tax rate if necessary
55 if RO.Money > RO.nCity * 16 then
56 ChangeRates(RO.TaxRate - 10, 0)
57 else if RO.Money < RO.nCity * 8 then
58 ChangeRates(RO.TaxRate + 10, 0);
59
60 // better government form available?
61 if RO.Government <> gAnarchy then
62 if IsResearched(adTheRepublic) then
63 begin
64 if RO.Government <> gRepublic then
65 Revolution;
66 end
67 else if IsResearched(adMonarchy) then
68 begin
69 if RO.Government <> gMonarchy then
70 Revolution;
71 end;
72
73 // do combat
74 for uix := 0 to RO.nUn - 1 do
75 if (MyUnit[uix].Loc >= 0) and not (MyModel[MyUnit[uix].mix].Kind in
76 [mkSettler, mkSlaves]) then
77 ProcessUnit(uix, Roam);
78
79 ProcessSettlers;
80
81 // do discover/patrol
82
83 OptimizeCityTiles;
84 SetCityProduction;
85end;
86
87
88// ProcessSettlers: move settlers, do terrain improvement, found cities
89procedure TAI.ProcessSettlers;
90var
91 uix, cix, ecix, Loc, RadiusLoc, TestScore, BestNearCityScore, TerrType, Special, V21: integer;
92 Radius: TVicinity21Loc;
93 ResourceScore, CityScore: array[0..lxmax * lymax - 1] of integer;
94
95 procedure ReserveCityRadius(Loc: integer);
96 var
97 V21, RadiusLoc: integer;
98 Radius: TVicinity21Loc;
99 begin
100 V21_to_Loc(Loc, Radius);
101 for V21 := 1 to 26 do
102 begin
103 RadiusLoc := Radius[V21];
104 if (RadiusLoc >= 0) and (RadiusLoc < MapSize) then
105 ResourceScore[RadiusLoc] := 0;
106 end;
107 end;
108
109begin
110 JobAssignment_Initialize;
111
112 // rate resources of all tiles
113 fillchar(ResourceScore, MapSize * sizeof(integer), 0);
114 for Loc := 0 to MapSize - 1 do
115 if (Map[Loc] and fRare) = 0 then
116 if (Map[Loc] and fTerrain) = fGrass then
117 if (Map[Loc] and fSpecial) <> 0 then
118 ResourceScore[Loc] := 3 // plains, 3 points
119 else
120 ResourceScore[Loc] := 2 // grassland, 2 points
121 else if (Map[Loc] and fSpecial) <> 0 then
122 ResourceScore[Loc] := 4; // special resource, 4 points
123 for cix := 0 to RO.nCity - 1 do
124 if MyCity[cix].Loc >= 0 then
125 ReserveCityRadius(MyCity[cix].Loc); // these resources already have a city
126 for uix := 0 to RO.nUn - 1 do
127 if (MyUnit[uix].Loc >= 0) and (MyUnit[uix].Job = jCity) then
128 ReserveCityRadius(MyUnit[uix].Loc); // these resources almost already have a city
129 for ecix := 0 to RO.nEnemyCity - 1 do
130 if RO.EnemyCity[ecix].Loc >= 0 then
131 ReserveCityRadius(RO.EnemyCity[ecix].Loc);
132 // these resources already have an enemy city
133
134 // rate possible new cities
135 fillchar(CityScore, MapSize * sizeof(integer), 0);
136 for Loc := 0 to MapSize - 1 do
137 if ((Map[Loc] and fTerrain) = fGrass) and ((Map[Loc] and fRare) = 0) and
138 ((RO.Territory[Loc] < 0) or (RO.Territory[Loc] = me)) then
139 // don't consider founding cities in foreign nation territory
140 begin
141 TestScore := 0;
142 BestNearCityScore := 0;
143 V21_to_Loc(Loc, Radius);
144 for V21 := 1 to 26 do
145 begin // sum resource scores in potential city radius
146 RadiusLoc := Radius[V21];
147 if (RadiusLoc >= 0) and (RadiusLoc < MapSize) then
148 begin
149 TestScore := TestScore + ResourceScore[RadiusLoc];
150 if CityScore[RadiusLoc] > BestNearCityScore then
151 BestNearCityScore := CityScore[RadiusLoc];
152 end;
153 end;
154 if TestScore >= 10 then // city is worth founding
155 begin
156 TestScore := TestScore shl 8 + ((loc xor me) * 4567) mod 251;
157 // some unexactness, random but always the same for this tile
158 if TestScore > BestNearCityScore then
159 begin // better than all other sites in radius
160 if BestNearCityScore > 0 then // found no other cities in radius
161 begin
162 for V21 := 1 to 26 do
163 begin
164 RadiusLoc := Radius[V21];
165 if (RadiusLoc >= 0) and (RadiusLoc < MapSize) then
166 CityScore[RadiusLoc] := 0;
167 end;
168 end;
169 CityScore[Loc] := TestScore;
170 end;
171 end;
172 end;
173 for Loc := 0 to MapSize - 1 do
174 if CityScore[Loc] > 0 then
175 JobAssignment_AddJob(Loc, jCity, 10);
176
177 // improve terrain
178 for cix := 0 to RO.nCity - 1 do
179 with MyCity[cix] do
180 if Loc >= 0 then
181 begin
182 V21_to_Loc(Loc, Radius);
183 for V21 := 1 to 26 do
184 if (Tiles and (1 shl V21) and not (1 shl CityOwnTile)) <> 0 then
185 begin // tile is exploited, but not the city own tile -- check if improvable
186 RadiusLoc := Radius[V21];
187 Assert((RadiusLoc >= 0) and (RadiusLoc < MapSize));
188 if (RadiusLoc >= 0) and (RadiusLoc < MapSize) then
189 begin
190 TerrType := Map[RadiusLoc] and fTerrain;
191 Special := Map[RadiusLoc] shr 5 and 3;
192 if TerrType >= fGrass then // can't improve water tiles
193 if (Terrain[TerrType].IrrEff > 0) // terrain is irrigatable
194 and not ((RO.Government = gDespotism) and
195 (Terrain[TerrType].FoodRes[Special] >= 3))
196 // improvement makes no sense when limit is depotism
197 and ((Map[RadiusLoc] and fTerImp) = 0) then // no terrain improvement yet
198 JobAssignment_AddJob(RadiusLoc, jIrr, 50) // irrigate!
199 else if (Terrain[TerrType].MoveCost = 1) // plain terrain
200 and ((Map[RadiusLoc] and (fRoad or fRR or fRiver)) = 0) then
201 // no road or railroad yet, no river
202 JobAssignment_AddJob(RadiusLoc, jRoad, 40);
203 // build road (The Wheel trade benefit)
204 end;
205 end;
206 end;
207
208 // choose all settlers to work
209 for uix := 0 to RO.nUn - 1 do
210 if (MyUnit[uix].Loc >= 0) and (MyModel[MyUnit[uix].mix].Kind in
211 [mkSettler, mkSlaves]) then
212 JobAssignment_AddUnit(uix);
213
214 JobAssignment_Go;
215end; // ProcessSettlers
216
217
218// ProcessUnit: execute attack, capture, discover or patrol task according to unit role
219procedure TAI.ProcessUnit(uix: integer; Role: UnitRole);
220const
221 DistanceScore = 4;
222var
223 BestScore, BestCount, BestLoc, TerrType, TestLoc, NextLoc, TestDistance,
224 Tile, V8, TestScore, euix, MyDamage, EnemyDamage, TerrOwner, StepSize, OldLoc,
225 AttackForecast, MoveResult, AttackResult: integer;
226 Exhausted: boolean;
227 TestTask, BestTask: (utNone, utAttack, utCapture, utDiscover, utPatrol, utGoHome);
228 Adjacent: TVicinity8Loc;
229 AdjacentUnknown: array[0..lxmax * lymax - 1] of integer;
230begin
231 Pile.Create(MapSize);
232 with MyUnit[uix] do
233 repeat
234 BestScore := -999999;
235 BestTask := utNone;
236 FillChar(AdjacentUnknown, SizeOf(AdjacentUnknown), $FF);
237 // -1, indicates tiles not checked yet
238 Pile.Empty;
239 Pile.Put(Loc, 0); // start search for something to do at current location
240 while Pile.Get(TestLoc, TestDistance) do
241 begin
242 TestScore := 0;
243 Tile := Map[TestLoc];
244 AdjacentUnknown[TestLoc] := 0;
245
246 if ((Tile and fUnit) <> 0) and ((Tile and fOwned) = 0) then
247 begin // enemy unit
248 Unit_FindEnemyDefender(TestLoc, euix);
249 if RO.Treaty[RO.EnemyUn[euix].Owner] < trPeace then
250 begin // unfriendly unit -- check attack
251 if Unit_AttackForecast(uix, TestLoc, 100, AttackForecast) then
252 begin // attack possible, but advantageous?
253 if AttackForecast > 0 then
254 begin // enemy unit would be destroyed
255 MyDamage := Health - AttackForecast;
256 EnemyDamage := RO.EnemyUn[euix].Health + DestroyBonus;
257 end
258 else // own unit would be destroyed
259 begin
260 MyDamage := Health + DestroyBonus;
261 EnemyDamage := RO.EnemyUn[euix].Health + AttackForecast;
262 end;
263 TestScore := Aggressive * 2 *
264 (EnemyDamage * RO.EnemyModel[RO.EnemyUn[euix].emix].Cost) div
265 (MyDamage * MyModel[mix].Cost);
266 if TestScore <= 100 then
267 TestScore := 0 // own losses exceed enemy losses, no good
268 else
269 begin
270 TestScore := (TestScore - 100) div 10 + 30;
271 TestTask := utAttack;
272 end;
273 end;
274 end;
275 end // enemy unit
276
277 else if ((Tile and fCity) <> 0) and ((Tile and fOwned) = 0) then
278 begin // enemy city, empty or unobserved
279 if (MyModel[mix].Domain = dGround)
280 // ships of this AI have no long-range guns, so don't try to attack cities with them
281 and ((RO.Territory[TestLoc] <
282 0) // happens only for unobserved cities of extinct tribes, new owner unknown
283 or (RO.Treaty[RO.Territory[TestLoc]] < trPeace)) then
284 begin // unfriendly city -- check attack/capture
285 if (Tile and fObserved) <> 0 then
286 begin // observed and no unit present -- city is undefended, capture!
287 TestScore := 40;
288 TestTask := utCapture;
289 end
290 else if Role = Roam then
291 begin // unobserved city, possibly defended -- go for attack
292 TestScore := 30;
293 TestTask := utPatrol;
294 end;
295 end;
296 end // enemy city, empty or unobserved
297
298 else
299 begin // no enemy city or unit here
300 // add surrounding tiles to queue, but only if there's a chance to beat BestScore
301 if 50 - DistanceScore * (TestDistance + 1) >= BestScore then
302 // assume a score of 50 is the best achievable
303 begin
304 V8_to_Loc(TestLoc, Adjacent);
305 for V8 := 0 to 7 do
306 begin
307 NextLoc := Adjacent[V8];
308 if (NextLoc >= 0) and (NextLoc < MapSize) and
309 (AdjacentUnknown[NextLoc] < 0) then // tile not checked yet
310 begin
311 TerrType := Map[NextLoc] and fTerrain;
312 if TerrType = fUNKNOWN then
313 Inc(AdjacentUnknown[TestLoc])
314 else
315 begin
316 case MyModel[mix].Domain of
317 dGround:
318 begin
319 TerrOwner := RO.Territory[NextLoc];
320 if (TerrType >= fGrass) and
321 (TerrType <> fArctic) // terrain can be walked
322 and ((TerrOwner < 0) or (TerrOwner = me) or
323 (RO.Treaty[TerrOwner] < trPeace)) // no peace treaty violated
324 and (((Map[NextLoc] and (fUnit or fCity)) <>
325 0) or (Map[TestLoc] and Map[NextLoc] and fInEnemyZoC = 0)) then
326 // no ZoC violated
327 begin // yes, consider walking this tile
328 if TerrType = fMountains then
329 StepSize := 2 // mountains cause delay
330 else
331 StepSize := 1;
332 end
333 else
334 StepSize := 0; // no, don't walk here
335 end;
336 dSea:
337 if TerrType = fShore then // ships of this AI can only move along shore
338 StepSize := 1
339 else
340 StepSize := 0;
341 dAir:
342 StepSize := 1;
343 end;
344 if StepSize > 0 then
345 Pile.Put(NextLoc, TestDistance + StepSize);
346 end;
347 end;
348 end;
349 end;
350 if Role = Defend then TestScore := 0 // don't discover/patrol
351 else if AdjacentUnknown[TestLoc] > 0 then
352 begin
353 TestScore := 20 + AdjacentUnknown[TestLoc];
354 TestTask := utDiscover;
355 end
356 else
357 begin
358 TestScore := (RO.Turn - RO.MapObservedLast[TestLoc]) div 10;
359 TestTask := utPatrol;
360 end;
361 end; // no enemy city or unit here
362
363 if TestScore > 0 then
364 begin
365 TestScore := TestScore - DistanceScore * TestDistance;
366 if TestScore > BestScore then
367 BestCount := 0;
368 if TestScore >= BestScore then
369 begin
370 Inc(BestCount);
371 if random(BestCount) = 0 then
372 begin
373 BestScore := TestScore;
374 BestLoc := TestLoc;
375 BestTask := TestTask;
376 end;
377 end;
378 end;
379 end;
380
381 if (BestTask = utNone) and ((Map[Loc] and fCity) = 0) then
382 begin // nothing to do, move home
383 if Home >= 0 then
384 BestLoc := MyCity[Home].Loc
385 else
386 BestLoc := maNextCity;
387 BestTask := utGoHome;
388 end;
389 if BestTask <> utNone then
390 begin // attack/capture/discover/patrol task found, execute it
391 OldLoc := Loc;
392 MoveResult := Unit_Move(uix, BestLoc);
393 Exhausted := (Loc = OldLoc) or
394 ((MoveResult and (rMoreTurns or rUnitRemoved)) <> 0);
395 if (BestTask = utAttack) and ((MoveResult and rLocationReached) <> 0) then
396 if Movement < 100 then
397 Exhausted := True
398 else
399 begin
400 AttackResult := Unit_Attack(uix, BestLoc);
401 Exhausted := ((AttackResult and rExecuted) = 0) or
402 ((AttackResult and rUnitRemoved) <> 0);
403 end;
404 if not Exhausted then
405 Exhausted := (Movement < 100) and
406 ((Map[Loc] and (fRoad or fRR or fRiver or fCity)) = 0);
407 // no road, too few movement points for further movement
408 end
409 else
410 Exhausted := True;
411 until Exhausted;
412 Pile.Free;
413end; // ProcessUnit
414
415
416// SetCityProduction: choose production of each city
417procedure TAI.SetCityProduction;
418var
419 cix, mix, mixSettler, mixShip, mixArmy, V8, NewImprovement, Count, wix, AdjacentLoc: integer;
420 IsPort: boolean;
421 Adjacent: TVicinity8Loc;
422 Report: TCityReport;
423
424 procedure TryBuild(Improvement: integer);
425 begin
426 if (NewImprovement < 0) // already improvement of higher priority found
427 and (MyCity[cix].Built[Improvement] = 0) // not built yet
428 and City_Improvable(cix, Improvement) then
429 NewImprovement := Improvement;
430 end;
431
432begin
433 // only produce newest models
434 mixSettler := -1;
435 mixArmy := -1;
436 mixShip := -1;
437 for mix := 0 to RO.nModel - 1 do
438 with MyModel[mix] do
439 if Kind = mkSettler then
440 mixSettler := mix
441 else if (Domain = dGround) and (Kind < mkSpecial_TownGuard) then
442 mixArmy := mix
443 else if Domain = dSea then
444 mixShip := mix;
445
446 for cix := 0 to RO.nCity - 1 do
447 with MyCity[cix] do
448 if (RO.Turn = 0) or ((Flags and chProduction) <> 0) // city production complete
449 or not City_HasProject(cix) then
450 begin // check production
451 IsPort := False;
452 V8_to_Loc(Loc, Adjacent);
453 for v8 := 0 to 7 do
454 begin
455 AdjacentLoc := Adjacent[V8];
456 if (AdjacentLoc >= 0) and (AdjacentLoc < MapSize) and
457 ((Map[AdjacentLoc] and fTerrain) = fShore) then
458 IsPort := True; // shore tile at adjacent location -- city is port!
459 end;
460 City_GetReport(cix, Report);
461
462 if (Report.Support = 0) or (SupportFree[RO.Government] < 2) and
463 (Report.Support < Report.ProdRep div 2) then
464 begin // enough material to support more units
465 if (RO.Turn > 4) and ((Report.Eaten - Size * 2) div
466 SettlerFood[RO.Government] < Size div 4) then
467 // less than 1 settler per 4 citizens -- produce more!
468 City_StartUnitProduction(cix, mixSettler)
469 else if IsPort and (mixShip >= 0) and (random(2) = 0) then
470 City_StartUnitProduction(cix, mixShip)
471 else
472 City_StartUnitProduction(cix, mixArmy);
473 end
474 else
475 begin // check for building a city improvement
476 NewImprovement := -1;
477 if Built[imPalace] + Built[imCourt] + Built[imTownHall] = 0 then
478 begin
479 TryBuild(imCourt);
480 TryBuild(imTownHall);
481 end;
482 if Report.Trade - Report.Corruption >= 11 then
483 TryBuild(imLibrary);
484 if Report.Trade - Report.Corruption >= 11 then
485 TryBuild(imMarket);
486 if Size >= 9 then
487 TryBuild(imHighways);
488 if (RO.Government <> gDespotism) and (Size >= 4) then
489 TryBuild(imTemple);
490 if (RO.Government <> gDespotism) and (Size >= 6) then
491 TryBuild(imTheater);
492 if (RO.Government <> gDespotism) and (Size >= 8) then
493 TryBuild(imAqueduct);
494 if (Report.ProdRep >= 4) or (RO.nCity = 1) then
495 TryBuild(imBarracks);
496 TryBuild(imWalls);
497 if IsPort then
498 TryBuild(imCoastalFort);
499 if NewImprovement < 0 then
500 begin // nothing to produce -- check for building a wonder
501 Count := 0;
502 for wix := 0 to nImp - 1 do
503 if (Imp[wix].Kind = ikWonder) and (RO.Wonder[wix].CityID = -1) // not built yet
504 and ((Report.ProdRep - Report.Support) * 40 >= Imp[wix].Cost)
505 // takes less than 40 turns to produce
506 and City_Improvable(cix, wix) then
507 begin
508 Inc(Count);
509 if random(Count) = 0 then
510 NewImprovement := wix; // yes, build this wonder!
511 end;
512 end;
513 if NewImprovement >= 0 then
514 City_StartImprovement(cix, NewImprovement)
515 else if City_HasProject(cix) then
516 City_StopProduction(cix); // nothing to produce
517 end;
518 end; // check production
519end; // SetCityProduction
520
521
522function TAI.ChooseResearchAdvance: integer;
523var
524 mix: integer;
525begin
526 if not IsResearched(adWheel) then
527 begin
528 Result := adWheel;
529 exit;
530 end // research the wheel first
531 else if not IsResearched(adWarriorCode) then
532 begin
533 Result := adWarriorCode;
534 exit;
535 end // research warrior code first
536 else if not IsResearched(adHorsebackRiding) then
537 begin
538 Result := adHorsebackRiding;
539 exit;
540 end; // research horseback riding first
541
542 Result := -1; // random advance
543 if random(10) = 0 then
544 begin // check military research
545 Result := adMilitary;
546 if IsResearched(adMapMaking) and (random(2) = 0) then
547 begin // try to develop new ship
548 PrepareNewModel(dSea);
549 SetNewModelFeature(mcDefense, 3);
550 SetNewModelFeature(mcOffense, RO.DevModel.MaxWeight - 3);
551 end
552 else
553 begin // try to develop new ground unit
554 PrepareNewModel(dGround);
555 SetNewModelFeature(mcDefense, 1);
556 SetNewModelFeature(mcOffense, RO.DevModel.MaxWeight - 4);
557 SetNewModelFeature(mcMob, 2);
558 end;
559
560 // don't develop model twice
561 for mix := 0 to RO.nModel - 1 do
562 if (RO.DevModel.Domain = MyModel[mix].Domain) and
563 (RO.DevModel.Attack = MyModel[mix].Attack) and
564 (RO.DevModel.Defense = MyModel[mix].Defense) then
565 Result := -1; // already have this model
566 end;
567end; // ChooseResearchAdvance
568
569
570function TAI.ChooseGovernment: integer;
571begin
572 if IsResearched(adTheRepublic) then
573 Result := gRepublic
574 else if IsResearched(adMonarchy) then
575 Result := gMonarchy
576 else
577 Result := gDespotism;
578end;
579
580
581//-------------------------------
582// DIPLOMACY
583//-------------------------------
584
585function TAI.WantNegotiation(Nation: integer; NegoTime: TNegoTime): boolean;
586begin
587 Result := (NegoTime = EnemyCalled) // always accept contact
588 or (NegoTime = EndOfTurn) and (RO.Turn mod 20 = Nation + me);
589 // ask for contact only once in 20 turns
590end;
591
592procedure TAI.DoNegotiation;
593begin
594 if (RO.Treaty[Opponent] < trPeace) and Odd(me + Opponent) then
595 // make peace with some random nations
596 if (OppoAction = scDipOffer) and (OppoOffer.nCost = 0) and
597 (OppoOffer.nDeliver = 1) and (OppoOffer.Price[0] = opTreaty + trPeace) then
598 MyAction := scDipAccept // accept peace
599 else if OppoAction = scDipStart then
600 begin
601 MyOffer.nCost := 0;
602 MyOffer.nDeliver := 1;
603 MyOffer.Price[0] := opTreaty + trPeace; // offer peace in exchange of nothing
604 MyAction := scDipOffer;
605 end;
606end;
607
608end.
Note: See TracBrowser for help on using the repository browser.