1 (*
2  * Hedgewars, a free turn based strategy game
3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 2 of the License
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *)
18 
19 {$INCLUDE "options.inc"}
20 
21 unit uAI;
22 interface
23 uses uFloat;
24 
25 procedure initModule;
26 procedure freeModule;
27 
28 procedure ProcessBot;
29 procedure FreeActionsList;
30 
31 implementation
32 uses uConsts, SDLh, uAIMisc, uAIAmmoTests, uAIActions,
33     uAmmos, uTypes,
34     uVariables, uCommands, uUtils, uDebug, uAILandMarks,
35     uGearsUtils;
36 
37 var BestActions: TActions;
38     CanUseAmmo: array [TAmmoType] of boolean;
39     StopThinking: boolean;
40     StartTicks: Longword;
41     ThreadSem: PSDL_Sem;
42 
43 procedure FreeActionsList;
44 begin
45     AddFileLog('FreeActionsList called');
46 
47     StopThinking:= true;
48     SDL_SemWait(ThreadSem);
49     SDL_SemPost(ThreadSem);
50 
51     if CurrentHedgehog <> nil then
52         with CurrentHedgehog^ do
53             if Gear <> nil then
54                 if BotLevel <> 0 then
55                     StopMessages(Gear^.Message);
56 
57     BestActions.Count:= 0;
58     BestActions.Pos:= 0
59 end;
60 
61 
62 const cBranchStackSize = 12;
63 type TStackEntry = record
64                    MadeActions: TActions;
65                    Hedgehog: TGear;
66                    end;
67 
68 var Stack: record
69            Count: Longword;
70            States: array[0..Pred(cBranchStackSize)] of TStackEntry;
71            end;
72 
Pushnull73 function Push(const Actions: TActions; const Me: TGear; Dir: integer): boolean;
74 var bRes: boolean;
75 begin
76     bRes:= (Stack.Count < cBranchStackSize) and (Actions.Count < MAXACTIONS - 5);
77     if bRes then
78         with Stack.States[Stack.Count] do
79             begin
80             MadeActions:= Actions;
81             Hedgehog:= Me;
82             Hedgehog.Message:= Dir;
83             inc(Stack.Count)
84             end;
85     Push:= bRes
86 end;
87 
88 procedure Pop(var Actions: TActions; var Me: TGear);
89 begin
90     dec(Stack.Count);
91     with Stack.States[Stack.Count] do
92         begin
93         Actions:= MadeActions;
94         Me:= Hedgehog
95         end
96 end;
97 
98 
99 
100 procedure TestAmmos(var Actions: TActions; Me: PGear; rareChecks: boolean);
101 var BotLevel: Byte;
102     ap: TAttackParams;
103     Score, i, t, n, dAngle: LongInt;
104     a, aa: TAmmoType;
105     useThisActions: boolean;
106 begin
107 BotLevel:= Me^.Hedgehog^.BotLevel;
108 windSpeed:= hwFloat2Float(cWindSpeed);
109 useThisActions:= false;
110 Me^.AIHints:= Me^.AIHints and (not aihAmmosChanged);
111 
112 for i:= 0 to Pred(Targets.Count) do
113     if (Targets.ar[i].Score >= 0) and (not StopThinking) then
114         begin
115         with Me^.Hedgehog^ do
116             a:= CurAmmoType;
117         aa:= a;
118         SDL_delay(0); // hint to let the context switch run
119         repeat
120         if (CanUseAmmo[a])
121             and ((not rareChecks) or ((AmmoTests[a].flags and amtest_Rare) = 0))
122             and ((i = 0) or ((AmmoTests[a].flags and amtest_NoTarget) = 0))
123             then
124             begin
125 {$HINTS OFF}
126             Score:= AmmoTests[a].proc(Me, Targets.ar[i], BotLevel, ap);
127 {$HINTS ON}
128             if (Score > BadTurn) and (Actions.Score + Score > BestActions.Score) then
129                 if (BestActions.Score < 0) or (Actions.Score + Score > BestActions.Score + Byte(BotLevel - 1) * 2048) then
130                     begin
131                     if useThisActions then
132                         begin
133                         BestActions.Count:= Actions.Count
134                         end
135                     else
136                         begin
137                         BestActions:= Actions;
138                         BestActions.isWalkingToABetterPlace:= false;
139                         useThisActions:= true
140                         end;
141 
142                     BestActions.Score:= Actions.Score + Score;
143 
144                     // if not between shots, activate invulnerability/vampirism if available
145                     if CurrentHedgehog^.MultiShootAttacks = 0 then
146                         begin
147                         if (HHHasAmmo(Me^.Hedgehog^, amInvulnerable) > 0) and (Me^.Hedgehog^.Effects[heInvulnerable] = 0) then
148                             begin
149                             AddAction(BestActions, aia_Weapon, Longword(amInvulnerable), 80, 0, 0);
150                             AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
151                             AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
152                             end;
153 
154                         if (HHHasAmmo(Me^.Hedgehog^, amExtraDamage) > 0) and (cDamageModifier <> _1_5) then
155                             begin
156                             AddAction(BestActions, aia_Weapon, Longword(amExtraDamage), 80, 0, 0);
157                             AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
158                             AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
159                             end;
160                         if (HHHasAmmo(Me^.Hedgehog^, amVampiric) > 0) and (not cVampiric) then
161                             begin
162                             AddAction(BestActions, aia_Weapon, Longword(amVampiric), 80, 0, 0);
163                             AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
164                             AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
165                             end;
166                         end;
167 
168                     AddAction(BestActions, aia_Weapon, Longword(a), 300 + random(400), 0, 0);
169 
170                     if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
171                         begin
172                         AddAction(BestActions, aia_Put, 0, 8, ap.AttackPutX, ap.AttackPutY)
173                         end;
174 
175                     if (ap.Angle > 0) then
176                         AddAction(BestActions, aia_LookRight, 0, 200, 0, 0)
177                     else if (ap.Angle < 0) then
178                         AddAction(BestActions, aia_LookLeft, 0, 200, 0, 0);
179 
180                     if (Ammoz[a].Ammo.Propz and ammoprop_Timerable) <> 0 then
181                         AddAction(BestActions, aia_Timer, ap.Time div 1000, 400, 0, 0);
182 
183                     if (Ammoz[a].Ammo.Propz and ammoprop_NoCrosshair) = 0 then
184                         begin
185                         dAngle:= LongInt(Me^.Angle) - Abs(ap.Angle);
186                         if dAngle > 0 then
187                             begin
188                             AddAction(BestActions, aia_Up, aim_push, 300 + random(250), 0, 0);
189                             AddAction(BestActions, aia_Up, aim_release, dAngle, 0, 0)
190                             end
191                         else if dAngle < 0 then
192                             begin
193                             AddAction(BestActions, aia_Down, aim_push, 300 + random(250), 0, 0);
194                             AddAction(BestActions, aia_Down, aim_release, -dAngle, 0, 0)
195                             end
196                         end;
197 
198                     if (Ammoz[a].Ammo.Propz and ammoprop_OscAim) <> 0 then
199                         begin
200                         AddAction(BestActions, aia_attack, aim_push, 350 + random(200), 0, 0);
201                         AddAction(BestActions, aia_attack, aim_release, 1, 0, 0);
202 
203                         if abs(ap.Angle) > 32 then
204                            begin
205                            AddAction(BestActions, aia_Down, aim_push, 100 + random(150), 0, 0);
206                            AddAction(BestActions, aia_Down, aim_release, 32, 0, 0);
207                            end;
208 
209                         AddAction(BestActions, aia_waitAngle, ap.Angle, 250, 0, 0);
210                         AddAction(BestActions, aia_attack, aim_push, 1, 0, 0);
211                         AddAction(BestActions, aia_attack, aim_release, 1, 0, 0);
212                         end else
213                         if (Ammoz[a].Ammo.Propz and ammoprop_AttackingPut) = 0 then
214                             begin
215                             if (AmmoTests[a].flags and amtest_MultipleAttacks) = 0 then
216                                 n:= 1 else n:= ap.AttacksNum;
217 
218                             AddAction(BestActions, aia_attack, aim_push, 650 + random(300), 0, 0);
219                             for t:= 2 to n do
220                                 begin
221                                 AddAction(BestActions, aia_attack, aim_push, 150, 0, 0);
222                                 AddAction(BestActions, aia_attack, aim_release, ap.Power, 0, 0);
223                                 end;
224                             AddAction(BestActions, aia_attack, aim_release, ap.Power, 0, 0);
225                             end;
226 
227                     if (Ammoz[a].Ammo.Propz and ammoprop_Track) <> 0 then
228                         begin
229                         AddAction(BestActions, aia_waitAmmoXY, 0, 12, ap.ExplX, ap.ExplY);
230                         AddAction(BestActions, aia_attack, aim_push, 1, 0, 0);
231                         AddAction(BestActions, aia_attack, aim_release, 7, 0, 0);
232                         end;
233 
234                     if ap.ExplR > 0 then
235                         AddAction(BestActions, aia_AwareExpl, ap.ExplR, 10, ap.ExplX, ap.ExplY);
236                     end
237             end;
238         if a = High(TAmmoType) then
239             a:= Low(TAmmoType)
240         else inc(a)
241         until (a = aa) or (CurrentHedgehog^.MultiShootAttacks > 0) {shooting same weapon}
242             or StopThinking
243         end
244 end;
245 
246 procedure Walk(Me: PGear; var Actions: TActions);
247 const FallPixForBranching = cHHRadius;
248 var
249     maxticks, oldticks, steps, tmp: Longword;
250     BaseRate, BestRate, Rate: LongInt;
251     GoInfo: TGoInfo;
252     CanGo: boolean;
253     AltMe: TGear;
254     BotLevel: Byte;
255     a: TAmmoType;
256     isAfterAttack: boolean;
257 begin
258 Actions.ticks:= 0;
259 oldticks:= 0; // avoid compiler hint
260 Stack.Count:= 0;
261 
262 clearAllMarks;
263 
264 for a:= Low(TAmmoType) to High(TAmmoType) do
265     CanUseAmmo[a]:= Assigned(AmmoTests[a].proc) and (HHHasAmmo(Me^.Hedgehog^, a) > 0);
266 
267 BotLevel:= Me^.Hedgehog^.BotLevel;
268 
269 isAfterAttack:= ((Me^.State and gstAttacked) <> 0) and ((GameFlags and gfInfAttack) = 0);
270 if isAfterAttack then
271     maxticks:= Max(0, TurnTimeLeft - 500)
272 else
273     maxticks:= Max(0, TurnTimeLeft - 5000 - LongWord(4000 * BotLevel));
274 
275 if not isAfterAttack then
276     TestAmmos(Actions, Me, false);
277 
278 BestRate:= RatePlace(Me);
279 BaseRate:= Max(BestRate, 0);
280 
281 // switch to 'skip' if we cannot move because of mouse cursor being shown
282 if (Ammoz[Me^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
283     AddAction(Actions, aia_Weapon, Longword(amSkip), 100 + random(200), 0, 0);
284 
285 if ((CurrentHedgehog^.MultiShootAttacks = 0) or ((Ammoz[Me^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NoMoveAfter) = 0))
286     and (CurrentHedgehog^.Effects[heArtillery] = 0) and (cGravityf <> 0) then
287     begin
288     tmp:= random(2) + 1;
289     Push(Actions, Me^, tmp);
290     Push(Actions, Me^, tmp xor 3);
291 
292     while (Stack.Count > 0) and (not StopThinking) do
293         begin
294         Pop(Actions, Me^);
295 
296         AddAction(Actions, Me^.Message, aim_push, 250, 0, 0);
297         if (Me^.Message and gmLeft) <> 0 then
298             AddAction(Actions, aia_WaitXL, hwRound(Me^.X), 0, 0, 0)
299         else
300             AddAction(Actions, aia_WaitXR, hwRound(Me^.X), 0, 0, 0);
301 
302         steps:= 0;
303 
304         while (not StopThinking) do
305             begin
306     {$HINTS OFF}
307             CanGo:= HHGo(Me, @AltMe, GoInfo);
308     {$HINTS ON}
309             oldticks:= Actions.ticks;
310             inc(Actions.ticks, GoInfo.Ticks);
311             if (Actions.ticks > maxticks) or (TurnTimeLeft < BestActions.ticks + 5000) then
312             begin
313                 if (BotLevel < 5)
314                         and (not isAfterAttack)
315                         and (BestActions.Score > 0) // we have a good move
316                         and (TurnTimeLeft < BestActions.ticks + 5000) // we won't have a lot of time after attack
317                         and (HHHasAmmo(Me^.Hedgehog^, amExtraTime) > 0) // but can use extra time
318                 then
319                 begin
320                     BestActions.Count:= 0;
321                     AddAction(BestActions, aia_Weapon, Longword(amExtraTime), 80, 0, 0);
322                     AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
323                     AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
324                 end;
325 
326                 break;
327             end;
328 
329             if (BotLevel < 5)
330                 and (GoInfo.JumpType = jmpHJump)
331                 and (not checkMark(hwRound(Me^.X), hwRound(Me^.Y), markHJumped))
332                 then // hjump support
333                 begin
334                 // check if we could go backwards and maybe ljump over a gap after this hjump
335                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markHJumped);
336                 if Push(Actions, AltMe, Me^.Message xor 3) then
337                     begin
338                     with Stack.States[Pred(Stack.Count)] do
339                         begin
340                         if (Me^.Message and gmLeft) <> 0 then
341                             AddAction(MadeActions, aia_LookRight, 0, 200, 0, 0)
342                         else
343                             AddAction(MadeActions, aia_LookLeft, 0, 200, 0, 0);
344 
345                         AddAction(MadeActions, aia_HJump, 0, 305 + random(50), 0, 0);
346                         AddAction(MadeActions, aia_HJump, 0, 350, 0, 0);
347                         end;
348                     // but first check walking forward
349                     Push(Stack.States[Pred(Stack.Count)].MadeActions, AltMe, Me^.Message)
350                     end;
351                 end;
352             if (BotLevel < 3)
353                 and (GoInfo.JumpType = jmpLJump)
354                 and (not checkMark(hwRound(Me^.X), hwRound(Me^.Y), markLJumped))
355                 then // ljump support
356                 begin
357                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markLJumped);
358                 // at final check where we go after jump walking backward
359                 if Push(Actions, AltMe, Me^.Message xor 3) then
360                     with Stack.States[Pred(Stack.Count)] do
361                         begin
362                         if (Me^.Message and gmLeft) <> 0 then
363                             AddAction(MadeActions, aia_LookLeft, 0, 200, 0, 0)
364                         else
365                             AddAction(MadeActions, aia_LookRight, 0, 200, 0, 0);
366 
367                         AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0);
368                         end;
369 
370                 // push current position so we proceed from it after checking jump+forward walk opportunities
371                 if CanGo then Push(Actions, Me^, Me^.Message);
372 
373                 // first check where we go after jump walking forward
374                 if Push(Actions, AltMe, Me^.Message) then
375                     with Stack.States[Pred(Stack.Count)] do
376                         AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0);
377 
378                 break
379                 end;
380 
381             // 'not CanGO' means we cannot go straight, possible jumps are checked above
382             if not CanGo then
383                 break;
384 
385              inc(steps);
386              Actions.actions[Pred(Actions.Count)].Param:= hwRound(Me^.X);
387              Rate:= RatePlace(Me);
388              if Rate > BestRate then
389                 begin
390                 BestActions:= Actions;
391                 BestActions.isWalkingToABetterPlace:= true;
392                 BestRate:= Rate;
393                 isAfterAttack:= true // we have better place, go there and do not use ammo
394                 end
395             else if Rate < BestRate then
396                 break;
397 
398             if (not isAfterAttack) and ((steps mod 4) = 0) then
399                 begin
400                 if (steps > 4) and checkMark(hwRound(Me^.X), hwRound(Me^.Y), markWalkedHere) then
401                     break;
402                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markWalkedHere);
403 
404                 TestAmmos(Actions, Me, Actions.ticks shr 12 = oldticks shr 12);
405                 end;
406 
407             if GoInfo.FallPix >= FallPixForBranching then
408                 Push(Actions, Me^, Me^.Message xor 3); // aia_Left xor 3 = aia_Right
409             end {while};
410 
411         if BestRate > BaseRate then
412             exit
413         end {while}
414     end {if}
415 end;
416 
417 function Think(Me: PGear): LongInt; cdecl; export;
418 var BackMe, WalkMe: TGear;
419     switchCount: LongInt;
420     currHedgehogIndex, itHedgehog, switchesNum, i: Longword;
421     switchImmediatelyAvailable: boolean;
422     Actions: TActions;
423 begin
424 dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent;
425 StartTicks:= GameTicks;
426 
427 currHedgehogIndex:= CurrentTeam^.CurrHedgehog;
428 itHedgehog:= currHedgehogIndex;
429 switchesNum:= 0;
430 
431 switchImmediatelyAvailable:= (CurAmmoGear <> nil) and (CurAmmoGear^.Kind = gtSwitcher);
432 if Me^.Hedgehog^.BotLevel <> 5 then
433     switchCount:= HHHasAmmo(PGear(Me)^.Hedgehog^, amSwitch)
434 else switchCount:= 0;
435 
436 if ((Me^.State and gstAttacked) = 0) or isInMultiShoot or bonuses.activity or ((Me^.AIHints and aihAmmosChanged) <> 0) then
437     if Targets.Count > 0 then
438         begin
439         // iterate over current team hedgehogs
440         repeat
441             WalkMe:= CurrentTeam^.Hedgehogs[itHedgehog].Gear^;
442 
443             Actions.Count:= 0;
444             Actions.Pos:= 0;
445             Actions.Score:= 0;
446             if switchesNum > 0 then
447                 begin
448                 if (not switchImmediatelyAvailable)  then
449                     begin
450                     // when AI has to use switcher, make it cost smth unless they have a lot of switches
451                     if (switchCount < 10) then Actions.Score:= (-27+switchCount*3)*4000;
452                     AddAction(Actions, aia_Weapon, Longword(amSwitch), 300 + random(200), 0, 0);
453                     AddAction(Actions, aia_attack, aim_push, 300 + random(300), 0, 0);
454                     AddAction(Actions, aia_attack, aim_release, 1, 0, 0);
455                     end;
456                 for i:= 1 to switchesNum do
457                     AddAction(Actions, aia_Switch, 0, 300 + random(200), 0, 0);
458                 end;
459             Walk(@WalkMe, Actions);
460 
461             // find another hog in team
462             repeat
463                 itHedgehog:= Succ(itHedgehog) mod CurrentTeam^.HedgehogsNumber;
464             until (itHedgehog = currHedgehogIndex) or ((CurrentTeam^.Hedgehogs[itHedgehog].Gear <> nil) and (CurrentTeam^.Hedgehogs[itHedgehog].Effects[heFrozen]=0));
465 
466             inc(switchesNum);
467         until (not (switchImmediatelyAvailable or (switchCount > 0)))
468             or StopThinking
469             or (itHedgehog = currHedgehogIndex)
470             or BestActions.isWalkingToABetterPlace;
471 
472         if (StartTicks > GameTicks - 1500) and (not StopThinking) then
473             SDL_Delay(700);
474 
475         if (BestActions.Score < -1023) and (not BestActions.isWalkingToABetterPlace) then
476             begin
477             BestActions.Count:= 0;
478 
479             FillBonuses(false);
480 
481             // Hog has no idea what to do. Use tardis or skip
482             if (not bonuses.activity) and ((Me^.AIHints and aihAmmosChanged) = 0) then
483                 if (((GameFlags and gfInfAttack) <> 0) or (CurrentHedgehog^.MultiShootAttacks = 0)) and (HHHasAmmo(Me^.Hedgehog^, amTardis) > 0) and (CanUseTardis(Me^.Hedgehog^.Gear)) and (random(4) < 3) then
484                     // Tardis brings hog to a random place. Perfect for clueless AI
485                     begin
486                     AddAction(BestActions, aia_Weapon, Longword(amTardis), 80, 0, 0);
487                     AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
488                     AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
489                     end
490                 else
491                     AddAction(BestActions, aia_Skip, 0, 250, 0, 0);
492             Me^.AIHints := ME^.AIHints and (not aihAmmosChanged);
493             end;
494 
495         end else SDL_Delay(100)
496 else
497     begin
498     BackMe:= Me^;
499     i:= 4;
500     while (not StopThinking) and (BestActions.Count = 0) and (i > 0) do
501         begin
502 
503 (*
504         // Maybe this would get a bit of movement out of them? Hopefully not *toward* water. Need to check how often he'd choose that strategy
505         if SuddenDeathDmg and ((hwRound(BackMe.Y)+cWaterRise*2) > cWaterLine) then
506             AddBonus(hwRound(BackMe.X), hwRound(BackMe.Y), 250, -40);
507 *)
508 
509         FillBonuses(true);
510         WalkMe:= BackMe;
511         Actions.Count:= 0;
512         Actions.Pos:= 0;
513         Actions.Score:= 0;
514         Walk(@WalkMe, Actions);
515         if not bonuses.activity then dec(i);
516         if not StopThinking then
517             SDL_Delay(100)
518         end
519     end;
520 
521 Me^.State:= Me^.State and (not gstHHThinking);
522 Think:= 0;
523 SDL_SemPost(ThreadSem);
524 end;
525 
526 procedure StartThink(Me: PGear);
527 var ThinkThread: PSDL_Thread;
528 begin
529 if ((Me^.State and (gstAttacking or gstHHJumping or gstMoving)) <> 0)
530 or isInMultiShoot then
531     exit;
532 
533 SDL_SemWait(ThreadSem);
534 //DeleteCI(Me); // this will break demo/netplay
535 
536 Me^.State:= Me^.State or gstHHThinking;
537 Me^.Message:= 0;
538 
539 BestActions.Count:= 0;
540 BestActions.Pos:= 0;
541 BestActions.Score:= Low(LongInt);
542 BestActions.isWalkingToABetterPlace:= false;
543 
544 StopThinking:= false;
545 ThinkingHH:= Me;
546 
547 FillTargets;
548 if Targets.Count = 0 then
549     begin
550     OutError('AI: no targets!?', false);
551     exit
552     end;
553 
554 FillBonuses(((Me^.State and gstAttacked) <> 0) and (not isInMultiShoot) and ((GameFlags and gfInfAttack) = 0));
555 
556 ThinkThread:= SDL_CreateThread(@Think, PChar('think'), Me);
557 SDL_DetachThread(ThinkThread);
558 end;
559 
560 {$IFDEF DEBUGAI}
561 var scoreShown: boolean = false;
562 {$ENDIF}
563 
564 procedure ProcessBot;
565 const cStopThinkTime = 40;
566 begin
567 with CurrentHedgehog^ do
568     if (Gear <> nil)
569     and ((Gear^.State and gstHHDriven) <> 0)
570     and ((TurnTimeLeft < cHedgehogTurnTime - 50) or (TurnTimeLeft > cHedgehogTurnTime)) then
571         if ((Gear^.State and gstHHThinking) = 0) then
572             if (BestActions.Pos >= BestActions.Count)
573             and (TurnTimeLeft > cStopThinkTime) then
574                 begin
575                 if Gear^.Message <> 0 then
576                     begin
577                     StopMessages(Gear^.Message);
578                     if checkFails((Gear^.Message and gmAllStoppable) = 0, 'Engine bug: AI may break demos playing', true) then exit;
579                     end;
580 
581                 if Gear^.Message <> 0 then
582                     exit;
583 
584 {$IFDEF DEBUGAI}
585                 scoreShown:= false;
586 {$ENDIF}
587                 StartThink(Gear);
588                 StartTicks:= GameTicks
589 
590             end else
591                 begin
592 {$IFDEF DEBUGAI}
593                 if not scoreShown then
594                     begin
595                     if BestActions.Score > 0 then ParseCommand('/say Expected score = ' + inttostr(BestActions.Score div 1024), true);
596                     scoreShown:= true
597                     end;
598 {$ENDIF}
599                 ProcessAction(BestActions, Gear)
600                 end
601         else if ((GameTicks - StartTicks) > cMaxAIThinkTime)
602             or (TurnTimeLeft <= cStopThinkTime) then
603                 StopThinking:= true
604 end;
605 
606 procedure initModule;
607 begin
608     StartTicks:= 0;
609     ThreadSem:= SDL_CreateSemaphore(1);
610 end;
611 
612 procedure freeModule;
613 begin
614     FreeActionsList();
615     SDL_DestroySemaphore(ThreadSem);
616 end;
617 
618 end.
619