Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
Moderator: Campaign Series Matrix Edition Development Group
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
At the first stopping point, Turn 5, Side A phase, we see this:
The 5th Parachute Battalion (and other VNA forces) has landed.
After a restart, at the second stopping point, Turn 10, Side A phase, the situation:
Interesting. The erstwhile enemy defenders of OBJECTIVES[13] have quickly abandoned the fight and are fleeing to the south. The NVA have taken OBJECTIVES[13] without too much effort.
After a restart, at the third stopping point, Turn 15, Side A phase, the situation:
Oh. Evidently _2ND_5TH_PARACHUTE_COY_55_A_447 has been (randomly) assigned _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER 3, is moving through the swamp southward to attack the western bunker.
Meanwhile, the 1st/5th is attacking OBJECTIVES[8] to the northwest, while the 3rd/5th and the Weapons Company are getting into position to attack OBJECTIVES[16] to the northeast.
After a restart, at the fourth stopping point, Turn 20, Side A phase, the situation:
The 1st/5th continues attacking OBJECTIVES[8]. The other companies draw nearer their objectives.
After a restart, at the fifth stopping point, Turn 25, Side A phase, the situation:
The attacks have everywhere begun in earnest.
After a restart, at the sixth stopping point, Turn 30, Side A phase, the situation:
Surprise! BX Army units have appeared from out of nowhere to attack OBJECTIVES[13]. The 1st/5th is abandoning the attack on OBJECTIVES[8] and is moving to the rescue.
Meanwhile, down south, the 2nd/5th has succeeded in taking that bunker. The other companies continue their dogged assault on OBJECTIVES[16].
After a restart, at the seventh stopping point, Turn 35, Side A phase, the situation:
Oh my! OBJECTIVES[13] has fallen! _1ST_5TH_PARACHUTE_COY_55_A_443 is trying to help take it back. The other companies, no change.
After a restart, at the eight and final stopping point, Turn 40, Side A phase, the situation:
To the northeast, 3rd/5th and the Weapons Company keep banging away, but no joy. To the south, 2nd/5th is content to rest on its laurels.
After running several of these auto-tests, it is clear to me that 5th Parachute Battalion has been given too many assignments. In retrospect, it makes little sense to send the 2nd/5th southward through the swamps in that fashion, far away from its companion companies, its parent HQ, and its sources of supply. Better I think to have them join in the northeast attacks, and they be the ones to backtrack to defend OBJECTIVES[13], if it comes to that.
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
And so it goes, and has been going for the past four years and more. Basing off the rudimentary CSME (Campaign Series Middle East) implementation, development of the CSVN (Campaign Series Vietnam) CSEE/SAI began in earnest in early 2018. Early on especially, it was painfully slow going. I likened it to stepping through a minefield of "gotchas!". I would add a feature, extend the CSEE function collection, try something new. And sooner or later, mostly sooner, it would blow up in my face. Gotcha! An unexpected constraint in the (C++) game engine code, a misplaced tie-in to the CSEE, a missing piece or two, or three, or ... Endless cycles of script revisions, mind numbingly boring and repetitive auto-tests, looking for needles (bugs) in two separate haystacks, the game engine and the CSEE. Now, over four years later, the CSEE is more or less fully developed, and bug free.
[cue the laugh track, <hahahaha!>]
Okay, I lied. CSEE development will never cease. There Will Be Bugs. There will be new functions, both core CSEE and unofficial "uber" (in user.lua). In quest of the "perfect" battle plans and scenario SAI, revising the Lua code will be never ending ...
... Unless we face reality. No plan is ever perfect. No code is ever perfectly bug free. Sooner or later, as a practical matter, we need to say: "Good enough!" And move on to the next scenario (or whatever).
But with the VN_550921_Rung_Sat scenario and its Lua file, we're not there yet. Lots more work still to do, many more units and map areas to consider, much more explanation and many more examples to follow.
To resume, and to recap, the following screenshot depicts the general situation:
The VNA 5th Parachute Battalion (green circle) has paradropped into the Rung Sat Swamp. (Ignore the other VNA units. They will be scripted later.) After recovering from Disruption, the 5th Btn as a whole is to attack and capture OBJECTIVES[13] (center turquoise circle). The 1st/5th Company is then to move up the road to the northwest and attack OBJECTIVES[8]. The 3rd/5th Coy and the Weapons Coy are to move northward, attack and capture OBJECTIVES[16], then attack OBJECTIVES[17] if they still have the strength to do so. The 2nd/5th Company is tasked with defending OBJECTIVES[13], and in part possibly assisting the 3rd/5th etc. in their attacks.
Given the earlier auto-testing results, we have abandoned any option for the 2nd/5th to venture far southward. It will remain in close proximity to the rest of the Battalion.
The BX Army has units defending OBJECTIVES[13] (lower large yellow circle), and OBJECTIVES[8], OBJECTIVES[16], and OBJECTIVES[17] (smaller yellow circles). A large BX Army force lurks to the north (upper large yellow circle), awaiting the opportunity to surprise attack/defend OBJECTIVES[13] as circumstances warrant.
Our first revisions:
Code: Select all
if OBJECTIVES13_CAPTURED then
-- if turn >= 8 then
-- erstwhile OBJECTIVES[13] defenders
local units = join({_1ST_VM_RIFLE_48_105, _3RD_VM_RIFLE_48_107, _MEDIUM_MACHINE_GUN_116, _DAC_CONG_127})
if counter_exists(_SUPPLY_CACHE_484) then
defend_strong(units, counter_hex(_SUPPLY_CACHE_484))
else
defend_strong(units)
end
end
Before, the BX Army OBJECTIVES[13] force was coded to depart southward to defend _SUPPLY_CACHE_484, if it exists (quite likely). This was hard-coded to occur at Turn 8. Triggering things to happen at hard-coded, fixed times is in general a bad idea. If the VNA does not attack OBJECTIVES[13] for some reason, or if their attack is unexpectedly weak and the BX Army defense continues to hold, why not stay put? Or if the BX Army force is to leave, why at Turn 8? Why not Turn 9, or 10, or <fill in the blank>? Why not leave immediately, at scenario start?
No, better to trigger events according to circumstances. Here, the new circumstance is: Only depart the area and retreat southward if OBJECTIVES[13] has been lost to the VNA.
The change has far reaching repercussions. After the change, in auto-tests, the battle for OBJECTIVES[13] persisted often well beyond Turn 8, the fight was much bloodier. When the BX Army force was finally pushed away/retreated, the 5th Parachute Btn was much reduced compared to before. At reduced strength, taking the other objectives, OBJECTIVES[8] etc., was thereby much more difficult, bordering on the impossible even. Virtually impossible with early version, unimproved code. Until I made it better!
Another revision:
Code: Select all
-- _FIRE_SUPPORT_COY_50_82MM_128 -- Fire Support Company 50 - 82mm
-- _RECONNAISSANCE_COY_50_133 -- Reconnaissance Company 50
do local units = join({_FIRE_SUPPORT_COY_50_82MM_128, _RECONNAISSANCE_COY_50_133})
defend_normal(units)
_FIRE_SUPPORT_COY_50_82MM_128_ATTACK_TURN = _FIRE_SUPPORT_COY_50_82MM_128_ATTACK_TURN or random_pick({21,22,23,24,25})
if turn >= _FIRE_SUPPORT_COY_50_82MM_128_ATTACK_TURN then
-- if turn >= 25 then
if objective_owner(OBJECTIVES[13]) == ARVN_SIDE then
attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
else
-- attack any verified ARVN _3RD_ARTILLERY_BTN_55_105MM_413 units
attack_org(units, _3RD_ARTILLERY_BTN_55_105MM_413, NODIR, 1, 100, ATTACK_NORMAL, true, false)
end
end
end
Before, those BX Army "lurkers" (upper large yellow circle) were halted in place, only to be released at a fixed Turn 25. I have changed that release turn to any time from Turn 21 to Turn 25. This too has significant repercussions, as this might force the VNA to break off its attacks on OBJECTIVES[8] etc. sooner rather than later.
So, the release turn is no longer a hard-coded 25, rather it varies. (Note: In formal game terms, that force is not truly Fixed. Rather, it is free to move at any time. The SAI coding applies the "fix". Of course a human player is under no such constriction and can move those units at any time, in any direction, for whatever purpose.) Varying the release turn is an improvement, but could we do better? Could we release those units based on circumstances, on unexpectedly novel human game play, not on raw timing? Yes, we could. We could dwell on this one corner of the game map, we could consider and reconsider again and again ... and never finish the script. At some point -- we are at that point -- we put down our pen, as it were. It is a reasonably good SAI strategy for those BX Army units (a) to lie in wait, and (b) move to attack/defend OBJECTIVES[13] roughly halfway through the scenario. We won't code any variations on that (other than the release timing). Good enough!
At scenario start, the 5th Parachute Btn, all of it, is to attack OBJECTIVES[13]. After it (inevitably) falls, a portion of the 5th is to break off the attack and head off to take other nearby objectives. While the remaining portion is to stick around for a while and drive away the BX Army Side B defenders.
The revised 1st/5th Company orders code:
Code: Select all
-- _1ST_5TH_PARACHUTE_COY_55_A_443 -- 1st/5th Parachute Company 55 - A
do local units = _1ST_5TH_PARACHUTE_COY_55_A_443
if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
if not _1ST_5TH_PARACHUTE_COY_55_A_443_ORDER and
obj13_threatened then
attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
else
if not _1ST_5TH_PARACHUTE_COY_55_A_443_ORDER then -- set one time only
_1ST_5TH_PARACHUTE_COY_55_A_443_ORDER = 1
end
if loss_rate(units) > 65 then
if not _1ST_5TH_PARACHUTE_COY_55_A_443_RETIRE_PT then
local cch = counters_center_hex(units)
local hno = hexes_not_occupied (hexes_habitat(), BX_SIDE)
_1ST_5TH_PARACHUTE_COY_55_A_443_RETIRE_PT = random_pick(hexes_nearest (cch, hno))
end
defend_weak(units, _1ST_5TH_PARACHUTE_COY_55_A_443_RETIRE_PT, NODIR, 1)
elseif not attack_nearest_arc(units, UPLEFTDIR, 2, ATTACK_NORMAL, true, true) and
(objective_owner(OBJECTIVES[8]) == BX_SIDE) then
attack_way_point(units, {"59,12", "50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_STRONG)
end
end
end
end
Before, we assessed whether OBJECTIVES[13] was threatened by means of
units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0
Rather than repeating that verbosity at several places in the code, earlier in the battle_plan_a() function, we have
Code: Select all
-------------------------------------------------------------------
-- _5TH_PARACHUTE_BTN_55_A_441 -- 5th Parachute Battalion 55 - A --
-------------------------------------------------------------------
...
local obj13_threatened = (units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0)
...
Thereafter, we can refer to the boolean (true/false) value of obj13_threatened in our 'if <condition>' tests, as we have done in the 1st/5th orders code above.
Strictly speaking, we don't need to specify the _1ST_5TH_PARACHUTE_COY_55_A_443_ORDER, which is invariably set to 1. We do it that way to conform with how the other 5th Btn companies are scripted, each having their own _ORDER #.
If the 1st/5th losses exceed 65%, they are to break off any active operations and weakly defend; else they are to head off to strong attack OBJECTIVES[8], pausing to attack any enemy units encountered along the way.
This is new:
Code: Select all
if loss_rate(units) > 65 then
if not _1ST_5TH_PARACHUTE_COY_55_A_443_RETIRE_PT then
local cch = counters_center_hex(units)
local hno = hexes_not_occupied (hexes_habitat(), BX_SIDE)
_1ST_5TH_PARACHUTE_COY_55_A_443_RETIRE_PT = random_pick(hexes_nearest (cch, hno))
end
defend_weak(units, _1ST_5TH_PARACHUTE_COY_55_A_443_RETIRE_PT, NODIR, 1)
This says to:
- Accounting for force dispersal, find the "weighted" (by strength) units center of "gravity", i.e., its virtual center point, cch.
- Among all "habitat" hexes -- Village, Suburb, City -- create a list of all such hexes, hno, not occupied by the enemy BX_SIDE. (hexes_habitat() is a new CSEE function to be released with the imminent CSVN 1.22 patch.)
- Determine free (of the enemy) habitat hexes nearest to the 1st/5th center. Since there might be a tie, since there might be multiple such hexes, select one randomly (using random_pick(). Assign that hex (one time only) to _1ST_5TH_PARACHUTE_COY_55_A_443_RETIRE_PT.
- The surviving (and weakened) are ordered to weak defend at that _1ST_5TH_PARACHUTE_COY_55_A_443_RETIRE_PT.
Before, we had simply
Code: Select all
if loss_rate(units) > 65 then
defend_weak(units)
But then we noticed where the units were stopping to recover out in some Wet Paddy or Scrub hex. More plausibly, bloody, weakened units might seek the aid and comfort and shelter of a nearby habitat hex. Hence this change.
Is the change worth it? Might we make this even more nuanced and "plausible"? Yes, we might. Another example of: You can fiddle with SAI'ing endlessly. How far do you want to take it? When will it (ever) be "good enough"? For us, and based off auto-test results, we think this is now good enough.
The revised orders code for the 3rd/5th Company and Weapons Company:
Code: Select all
-- _3RD_5TH_PARACHUTE_COY_55_A_451 -- 3rd/5th Parachute Company 55 - A
-- _5TH_PARACHUTE_WEAPONS_COY_55_455 -- 5th Parachute Weapons Company 55
do local units = join({_3RD_5TH_PARACHUTE_COY_55_A_451, _5TH_PARACHUTE_WEAPONS_COY_55_455})
if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
if not _3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_ORDER and
objective_owner(OBJECTIVES[13]) == BX_SIDE then
attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
else
if not _3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_ORDER then -- set one time only
_3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_ORDER = 1
end
if loss_rate(units) > 65 then
if not _3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_RETIRE_PT then
local cch = counters_center_hex(units)
local hno = hexes_not_occupied (hexes_habitat(), BX_SIDE)
_3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_RETIRE_PT = random_pick(hexes_nearest (cch, hno))
end
defend_weak(units, _3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_RETIRE_PT, NODIR, 1)
elseif not attack_nearest_arc(units, UPRIGHTDIR, 2, ATTACK_NORMAL, true, true) then
if objective_owner(OBJECTIVES[16]) == BX_SIDE then
move_way_point(units, {"67,7", "66,2"})
if within(counters_active(difference(units, _MMG_457)), OBJECTIVES[16], NODIR, 4) then
attack_strong(units, OBJECTIVES[16], {DOWNDIR, DOWNLEFTDIR, UPLEFTDIR, UPDIR, UPRIGHTDIR}, 1)
end
elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
attack_strong(units, OBJECTIVES[17], NODIR, 2)
end
end
end
end
end
Note whereas for the 1st/5th (also later the 2nd/5th) they make use of
Code: Select all
if not _1ST_5TH_PARACHUTE_COY_55_A_443_ORDER and
obj13_threatened then
attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
the 3rd/5th & co. have
Code: Select all
if not _3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_ORDER and
objective_owner(OBJECTIVES[13]) == BX_SIDE then
attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
If the BX_SIDE owns OBJECTIVES[13], the VNA have not yet taken it.
OBJECTIVES[13] might still be "threatened" (by the nearby presence of enemy units) vs. OBJECTIVES[13] is taken (but enemy units are still nearby). For the former, _1ST_5TH_PARACHUTE_COY_55_A_443 sticks around to attack any nearby enemy units. For the latter, _3RD_5TH_PARACHUTE_COY_55_A_451 and _5TH_PARACHUTE_WEAPONS_COY_55_455 are free to leave and proceed to attacking OBJECTIVES[16] etc.
Much like the 1st/5th, the 3rd/5th and Weapons are to retire to the nearest free habitat hex if their total losses exceed 65%.
When the 3rd/5th and Weapons depart to attack OBJECTIVES[16] etc., they are to attack any enemy units encountered along the way.
If the BX_SIDE still owns OBJECTIVES[16], the 3rd/5th and Weapons are to
Code: Select all
move_way_point(units, {"67,7", "66,2"})
if within(counters_active(difference(units, _MMG_457)), OBJECTIVES[16], NODIR, 4) then
attack_strong(units, OBJECTIVES[16], {DOWNDIR, DOWNLEFTDIR, UPLEFTDIR, UPDIR, UPRIGHTDIR}, 1)
end
Why way point move to "66,2" and waiting there until all units are assembled in that vicinity? It is because otherwise forces were committing to the attack on OBJECTIVES[16] piecemeal, as they arrived, in dribs and drabs. Not good. No, we want the units in full to attack. Hence the careful coding.
Another example of polishing, deemed rather necessary. Is it enough? Might we polish still more? Yes, we might. But for now, good enough!
Here is special treatment for the Weapons Coy _81MM_MORTAR_459 (immediately following the above combined units orders code):
Code: Select all
-- _81MM_MORTAR_459 -- Parachute M1 81mm Mortars
if not _3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_ORDER and
obj13_threatened then
fire_indirect_nearest_to_hex(_81MM_MORTAR_459, OBJECTIVES[13], 100)
else
if owned(OBJECTIVES[16], BX_SIDE) and
not at(_81MM_MORTAR_459, "69,5") then
move_way_point(_81MM_MORTAR_459, {"67,7", "69,5"}, NODIR, 0, 100)
elseif owned(OBJECTIVES[16], ARVN_SIDE) and
not at (_81MM_MORTAR_459, OBJECTIVES[16]) then
move_rush(_81MM_MORTAR_459, OBJECTIVES[16])
else
fire_indirect_nearest_to_hex(_81MM_MORTAR_459, "71,3", 100)
end
if _3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_RETIRE_PT then
defend_weak(_81MM_MORTAR_459, _3RD_5TH_PARACHUTE_COY_5TH_PARACHUTE_WEAPONS_COY_RETIRE_PT, NODIR, 1)
end
end
_81MM_MORTAR_459 is to:
- Fire at the nearest enemy to OBJECTIVES[13], if that objective is threatened (and if the 3rd/5th and Weapons have not been latter assigned a general attack order).
- If OBJECTIVES[16] is enemy owned, and if not at hex 69,5, then way point move to hex 69,5.
- Else if the ARVN_SIDE (the VNA) have taken OBJECTIVES[16], rush move there.
- Else, wherever _81MM_MORTAR_459 happens to be, indirect fire at enemy units closest to hex 71,3 (mid way between OBJECTIVES[16] and OBJECTIVES[17]).
- If the rest of the task force has casualties exceeding 65%, _81MM_MORTAR_459 too is to withdraw to their assigned RETIRE_PT.
For the 2nd/5th orders code, rather more complicated:
Code: Select all
-- _2ND_5TH_PARACHUTE_COY_55_A_447 -- 2nd/5th Parachute Company 55 - A
do local units = _2ND_5TH_PARACHUTE_COY_55_A_447
if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
if obj13_threatened then
attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
else
if not _2ND_5TH_PARACHUTE_COY_55_A_447_ORDER then -- set one time only
_2ND_5TH_PARACHUTE_COY_55_A_447_ORDER = 1 -- random_pick({1,2})
end
if _2ND_5TH_PARACHUTE_COY_55_A_447_ORDER == 1 then
if loss_rate(units) > 65 then
if not _2ND_5TH_PARACHUTE_COY_55_A_447_RETIRE_PT then
local cch = counters_center_hex(units)
local hno = hexes_not_occupied (hexes_habitat(), BX_SIDE)
_2ND_5TH_PARACHUTE_COY_55_A_447_RETIRE_PT = random_pick(hexes_nearest (cch, hno))
end
defend_weak(units, _2ND_5TH_PARACHUTE_COY_55_A_447_RETIRE_PT, NODIR, 1)
else
if objective_owner(OBJECTIVES[16]) == BX_SIDE then
move_way_point(units, {"65,8", "66,4"})
if within(difference(join({_3RD_5TH_PARACHUTE_COY_55_A_451, _5TH_PARACHUTE_WEAPONS_COY_55_455}), _MMG_457), OBJECTIVES[16], NODIR, 4) then
attack_strong(units, OBJECTIVES[16], {DOWNDIR, DOWNLEFTDIR, UPLEFTDIR, UPDIR, UPRIGHTDIR}, 1)
end
elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
attack_strong(units, OBJECTIVES[17], NODIR, 2)
end
end
elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ORDER == 2 then
defend_normal(units, OBJECTIVES[13], NODIR, 1)
end
end
end
end
Much like the other companies' orders code, except if _2ND_5TH_PARACHUTE_COY_55_A_447_ORDER
- is 1, then the 2nd/5th is to participate in the attacks on OBJECTIVES[16] and OBJECTIVES[17].
- is 2, then the 2nd/5th is to remain defending OBJECTIVES[13].
In the latter case, 2nd/5th is well placed to join with other VNA defenders in defending OBJECTIVES[13] from that mid scenario BX Army surprise attack (refer to earlier). There is a 50/50 chance the 2nd/5th will defend OBJECTIVES[13] vs. moving away to help attack OBJECTIVES[16] etc. Which is the right bet, stay or leave? On the other hand, what if that lurking BX Army force were, not to attack southward towards OBJECTIVES[13], rather move eastward to help defend OBJECTIVES[16] etc.? Or even move westward to help defend OBJECTIVES[8]! All cases considered, all bets are off! Whatever happens, happens. We need to get on with things. Good enough!
Rounding out the 5th Parachute Btn orders code, at the very beginning there is:
Code: Select all
-------------------------------------------------------------------
-- _5TH_PARACHUTE_BTN_55_A_441 -- 5th Parachute Battalion 55 - A --
-------------------------------------------------------------------
if not _5TH_PARACHUTE_BTN_55_A_441_READY_TURN and -- set one time only
are_not_disrupted(_5TH_PARACHUTE_BTN_55_A_441) then
_5TH_PARACHUTE_BTN_55_A_441_READY_TURN = turn
end
local obj13_threatened = (units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0)
-- _HQ_442 -- VNAF Airborne Battalion HQ (foot)
-- _5TH_PARACHUTE_462 -- Airborne Commander 2
do local units = join({_HQ_442, _5TH_PARACHUTE_462})
if obj13_threatened then
defend_way_point(units, {"65,15", OBJECTIVES[15]})
elseif objective_owner(OBJECTIVES[13]) == ARVN_SIDE then
defend_weak(units, OBJECTIVES[13])
end
end
The Battalion HQ and commanding officer are to withdraw to the safety of OBJECTIVES[15] (lower right turquoise circle, screenshot above) if OBJECTIVES[13] is threatened. Otherwise, they are to move to OBJECTIVES[13] and weak defend at that place.
At the end of the 5th Parachute Btn orders code, there is:
Code: Select all
if objective_owner(OBJECTIVES[13]) == ARVN_SIDE then
-- if 2/5 not assigned to defend OBJECTIVES[13]
if _2ND_5TH_PARACHUTE_COY_55_A_447_ORDER and
_2ND_5TH_PARACHUTE_COY_55_A_447_ORDER ~= 2 then
-- assign _MMG_457 and the weakest unit among 2/5 to garrison and normal defend OBJECTIVES[13]
if not _ARVN_OBJECTIVES13_GARRISON then -- one time only selection
_ARVN_OBJECTIVES13_GARRISON = join({_MMG_457, {random_pick(counters_weakest(_2ND_5TH_PARACHUTE_COY_55_A_447))}})
end
defend_normal(_ARVN_OBJECTIVES13_GARRISON, OBJECTIVES[13], NODIR, 0, 100)
-- else _2ND_5TH_PARACHUTE_COY_55_A_447_ORDER is 2, and that company as a whole is defending OBJECTIVES[13]
end
end
_MMG_457, detached from _5TH_PARACHUTE_WEAPONS_COY_55_455, is to join with the (then) weakest _2ND_5TH_PARACHUTE_COY_55_A_447 platoon to defend normal OBJECTIVES[13]. With luck (?), they will be joined by the rest of the 2nd/5th. There is a 50/50 chance though (see above) they will be defeated in that BX Army surprise attack. (Unless Side B is human played, and the human player does something quite different.)
No screenshot sequences of auto-tests to show this time. I will just note that the results of the SAI playing itself are quite varied. Sometimes the VNA Side A does very well. Other times, the BX Army Side B beats the VNA Side A handily. Much depends on how the initial fight for OBJECTIVES[13] plays out, and how many losses the VNA incur when taking that objective.
Whew! That's a lot of code for just one VNA battalion. There are several more to code in this scenario. Is every possibility considered? Absolutely not. Is this already over engineered? Maybe. There is a continuum between a minimalist SAI
Code: Select all
function battle_plan_a (turn, side)
unleash(ALLA)
end
where the computer player AI is freed from any SAI clutches to follow the raw EAI (Engine AI) directives exclusively.
A continuum between that and refining and auto-testing and refining and auto-testing and ... until we achieve the "perfect" SAI battle plan. Ha!
Where somewhere in the middle of that continuum we have what we now have. Is it "good enough"? When everything is "finished", auto-testing will hint at an answer. But the real test is SAI vs. human play. We will get there, eventually.
There is, simply, playing the game in conventional fashion. Then there is the "meta game" of SAI'ing the computer player. Anybody care to "play" the latter?
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
(Remember to, from time to time, visit the companion thread
https://www.matrixgames.com/forums/view ... 0&t=383969
for discussions and explanations of a more general nature.)
With the NVA 5th Parachute Battalion orders now "good enough", more or less, it is time to consider the next unit, the 6th Parachute Battalion, _6TH_PARACHUTE_BTN_55_A_226.
When we get to auto-testing the _6TH_PARACHUTE_BTN_55_A_226 orders code, we don't want to be distracted by sideshow _5TH_PARACHUTE_BTN_55_A_441 activity. We want to freeze the 5th Btn, and only give active orders to the 6th. We do that by commenting out the 5th orders code, and uncommenting the 6th orders code, as in:
Code: Select all
function battle_plan_a (turn, side)
-- standard orders, uncomment as necessary:
if turn >= 1 then
-- all Side A units halt initially (or maybe use hold())
halt(ALLA)
end
--[[
[other units' orders code]
...
-------------------------------------------------------------------
-- _5TH_PARACHUTE_BTN_55_A_441 -- 5th Parachute Battalion 55 - A --
-------------------------------------------------------------------
[5th Btn orders code]
--]]
-------------------------------------------------------------------
-- _6TH_PARACHUTE_BTN_55_A_226 -- 6th Parachute Battalion 55 - A --
-------------------------------------------------------------------
[6th Btn orders code]
--[[
[other units' orders code]
--]]
end
Note where, apart from the initial halt() general order, only the 6th Btn orders code is active; all other units' orders code is commented out, hence will not be executed, hence such units will remain halt()'ed.
We will, however, leave the opposing BX Army's orders code uncommented. The other side's (Side B) units will remain active.
Like the 5th Btn, the 6th Parachute Battalion is paradropped just north of the Rung Sat Swamp within the scenario's first few turns. Also like the 5th, the 6th Battalion is not ready to move etc. unless/until all units in the battalion are undisrupted (have recovered from the disruption of the paradrop):
Code: Select all
-------------------------------------------------------------------
-- _6TH_PARACHUTE_BTN_55_A_226 -- 6th Parachute Battalion 55 - A --
-------------------------------------------------------------------
if not _6TH_PARACHUTE_BTN_55_A_226_READY_TURN and -- set one time only
are_not_disrupted(_6TH_PARACHUTE_BTN_55_A_226) then
_6TH_PARACHUTE_BTN_55_A_226_READY_TURN = turn
end
Similar to the 5th Btn, the 6th Parachute Battalion orders code shows, for example:
Code: Select all
-- _1ST_6TH_PARACHUTE_COY_55_A_228 -- 1st/6th Parachute Company 55 - A
do local units = _1ST_6TH_PARACHUTE_COY_55_A_228
if _6TH_PARACHUTE_BTN_55_A_226_READY_TURN then
if units_within_count(OBJECTIVES[24], NODIR, 2, BX_SIDE) > 0 then
attack_nearest_to_hex(units, OBJECTIVES[24], NODIR, 1, 100, ATTACK_STRONG)
elseif units_within_count(OBJECTIVES[27], NODIR, 2, BX_SIDE) > 0 then
attack_nearest_to_hex(units, OBJECTIVES[27], NODIR, 1, 100, ATTACK_STRONG)
elseif units_within_count(OBJECTIVES[25], NODIR, 2, BX_SIDE) > 0 then
attack_way_point(units, {"86,12", OBJECTIVES[25]}, NODIR, 1, 100, ATTACK_STRONG)
elseif units_within_count(OBJECTIVES[29], NODIR, 2, BX_SIDE) > 0 then
attack_nearest_to_hex(units, OBJECTIVES[29], NODIR, 1, 100, ATTACK_STRONG)
end
end
end
With some abridgement, that is to say:
Code: Select all
-- _1ST_6TH_PARACHUTE_COY_55_A_228 -- 1st/6th Parachute Company 55 - A
do local units = _1ST_6TH_PARACHUTE_COY_55_A_228
if _6TH_PARACHUTE_BTN_55_A_226_READY_TURN then
[take action, do stuff...]
end
end
I ran an auto-test and was surprised to see where 6th Parachute Battalion units were springing to action, even though (a) not all of the 6th Btn had arrived, and (b) clearly some of the battalion's units were still disrupted.
Curious, I uncommented the 5th Btn's orders code, ran another auto-test, and saw the same result: No waiting, individual platoons from either battalion were moving, firing etc. the turn following their paradrops.
Following the techniques described here
https://www.matrixgames.com/forums/view ... 6#p5015416
I observed:
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ egrep "^l " 'VN_550921_Rung_Sat TEST TRIAL.btl'
...
l _6TH_PARACHUTE_BTN_55_A_226_READY_TURN:1
l _5TH_PARACHUTE_BTN_55_A_441_READY_TURN:1
...
Each battalion was being marked ready at scenario start, on Turn 1, even though neither had arrived on map yet. WTF?!
After running several more auto-tests, digging into the code, pondering things, I discovered a scripting error. In the code
Code: Select all
if not _6TH_PARACHUTE_BTN_55_A_226_READY_TURN and -- set one time only
are_not_disrupted(_6TH_PARACHUTE_BTN_55_A_226) then
_6TH_PARACHUTE_BTN_55_A_226_READY_TURN = turn
end
the are_not_disrupted() does not take into account whether the units in question, in this case _6TH_PARACHUTE_BTN_55_A_226, are on map yet. On Turn 1, no paradrops, so no 6th Btn presence. They are awaiting paradop; the landing has not disrupted them yet. So the are_not_disrupted() resolves to true, the body of the 'if ... end' is entered, and by '_6TH_PARACHUTE_BTN_55_A_226_READY_TURN = turn', with the turn being 1, that explains it.
Oops!
We forgot to check for on-map status. The fixed code:
Code: Select all
if not _6TH_PARACHUTE_BTN_55_A_226_READY_TURN and -- set one time only
(member_count(counters_active(_6TH_PARACHUTE_BTN_55_A_226)) > 0) and
are_not_disrupted(_6TH_PARACHUTE_BTN_55_A_226) then
_6TH_PARACHUTE_BTN_55_A_226_READY_TURN = turn
end
In that second line, we determine which of the _6TH_PARACHUTE_BTN_55_A_226 are 'counters_active()', which by default returns a list of on-map counters only. We take a member_count() of the list, and only if it is greater than zero, meaning to say units of the 6th have landed, only then do we proceed to consider the next question, are they are_not_disrupted().
I ran another auto-test, and ... delay, delay, delay, the 6th Btn did not come to life until Turn 19! Evidently, by a string of bad luck, there was a single platoon of the 6th that did not undisrupt until a dozen turns or so after landing. Blocking any battalion operations until all units battalion-wide are undisrupted is too restrictive.
Oops!
Back to the drawing board. Rather than determine this all battalion-wide, let's do it company by company, as in:
Code: Select all
-- _1ST_6TH_PARACHUTE_COY_55_A_228 -- 1st/6th Parachute Company 55 - A
do local units = _1ST_6TH_PARACHUTE_COY_55_A_228
if not _1ST_6TH_PARACHUTE_COY_55_A_228_READY_TURN then
if (member_count(counters_active(units)) > 0) and
are_not_disrupted(units) then
_1ST_6TH_PARACHUTE_COY_55_A_228_READY_TURN = turn
end
if _1ST_6TH_PARACHUTE_COY_55_A_228_READY_TURN then
[take action, do stuff...]
end
end
Meaning to say, the 1st/6th Company should spring to action if all of the company's units have landed and recovered from disruption, never mind the rest of the battalion.
With that new code, I ran another auto-test or two, and ... things appeared to be WAD. Companies in both the 5th Btn and the 6th Btn were "taking action, doing stuff" (a) only after landing, and (b) only after undisrupting, individually considered by company.
Good enough! ... Except it wasn't quite so. Repeating this sort of thing
Code: Select all
if not _1ST_6TH_PARACHUTE_COY_55_A_228_READY_TURN then
if (member_count(counters_active(units)) > 0) and
are_not_disrupted(units) then
_1ST_6TH_PARACHUTE_COY_55_A_228_READY_TURN = turn
end
for each and every company was, IMO, bloating the code. Too many lines, too many characters. Bloated code is hard to read, hard to decipher. Can we do better? Yes we can!
At the end of the VN_550921_Rung_Sat.lua file, let's add a new custom function:
Code: Select all
------------------------------------------------------------------------------------------------------------------------
function ready (trackids)
local ca = counters_active(trackids)
if (member_count(ca) > 0) and
are_not_disrupted(ca) then
return true
else
return false
end
end
------------------------------------------------------------------------------------------------------------------------
You will observe where this function returns true or false. After pondering all of this, there is no good reason to keep track of the turn when each company is ready; a simple true or false will do.
With that function defined, the code is more simply (fewer lines):
Code: Select all
-- _1ST_6TH_PARACHUTE_COY_55_A_228 -- 1st/6th Parachute Company 55 - A
do local units = _1ST_6TH_PARACHUTE_COY_55_A_228
_1ST_6TH_PARACHUTE_COY_55_A_228_READY = _1ST_6TH_PARACHUTE_COY_55_A_228_READY or ready(units)
if _1ST_6TH_PARACHUTE_COY_55_A_228_READY then
[take action, do stuff...]
end
end
Note use of the
Code: Select all
_1ST_6TH_PARACHUTE_COY_55_A_228_READY = _1ST_6TH_PARACHUTE_COY_55_A_228_READY or ready(units)
if _1ST_6TH_PARACHUTE_COY_55_A_228_READY was earlier determined, is non nil (in this context, is not false), then reassign its earlier value. Or if this is the first time through, hence _1ST_6TH_PARACHUTE_COY_55_A_228_READY is nil, then proceed to resolve the expression after the 'or', resolve the expression 'ready(units)', and assign its return value to _1ST_6TH_PARACHUTE_COY_55_A_228_READY.
The net effect of this is, sooner or later, to set _1ST_6TH_PARACHUTE_COY_55_A_228_READY to true, and thereafter it remains true.
This 'x = x or ...' code is a common CSEE (and Lua) construct. Overall, it saves many lines of code, helps to combat code bloat. Be sure you understand the technique.
After watching so many auto-test, another thing was bothering me. There was code like
Code: Select all
if loss_rate(units) > 65 then
if not _1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT then
local cch = counters_center_hex(units)
local hno = hexes_not_occupied (hexes_habitat(), BX_SIDE)
_1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT = random_pick(hexes_nearest (cch, hno))
end
defend_weak(units, _1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT, NODIR, 1)
else ...
[take action, do stuff...]
end
Repeating that over and over, there is lots of code bloat in that. Can we do better? Yes we can!
Another custom function we add to the end of the VN_550921_Rung_Sat.lua file:
Code: Select all
------------------------------------------------------------------------------------------------------------------------
function retire_point (trackids)
local ca = counters_active(trackids)
if #ca > 0 then
local side = counter_side(ca[1])
local oside = other_side(side)
local cch = counters_center_hex(ca)
local hno = hexes_not_occupied (hexes_habitat(), oside)
local hn = hexes_nearest (cch, hno)
if #hn > 0 then
return random_pick(hn)
end
end
return HEXUNKNOWN
end
------------------------------------------------------------------------------------------------------------------------
Note where, here too, we only consider counters_active(), so as not to have not-yet-arrived or killed units skewing the results.
With that new retire_point() function, we can simplify the above code to
Code: Select all
if loss_rate(units) > 65 then
_1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT = _1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT or retire_point(units)
defend_weak(units, _1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT, NODIR, 1)
else ...
[take action, do stuff...]
end
Compare with the code above. This latter version is shorter, easier to read still.
Are we done? In order to enhance scenario replayability, rather than hard coding that 65 loss_rate() threshold, why don't we
Code: Select all
function init_variables ()
-- initialize values possibly varying through the course of the scenario
-- called once only, in on_startup()
...
VNA_LOSS_RATE_TRIGGER = random_index(65,75) -- random integer from 65 thru 75
...
end
Then we can have
Code: Select all
if loss_rate(units) > VNA_LOSS_RATE_TRIGGER then
_1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT = _1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT or retire_point(units)
defend_weak(units, _1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT, NODIR, 1)
else ...
[take action, do stuff...]
end
That way, sometimes the VNA will retire early, some other scenario playthrough they will retire late. Sometimes they will play weaker, other times more aggressively.
ready() and/or retire_point() are functions we might want to use over and over again in other scenarios. Maybe generalize them and add to user.lua as uber functions? Something to think about.
Again, I ask: Are we done? Rather than set the threshold VNA side, why don't we set individual loss thresholds at the battalion or even company level? Since that would entail managing a new set of variables, one for every battalion or company, um, maybe not. Good enough.
There are many ways to mix it up, to enhance scenario replayability. Which is a good thing. Be on the lookout for creative ways to do just that.
Note that the above VNA_LOSS_RATE_TRIGGER uses the random_index() function. We could instead do its equivalent
Code: Select all
VNA_LOSS_RATE_TRIGGER = random_pick({65,66,67,68,69,70,71,72,73,74,75}) -- random integer from 65 thru 75
but using random_index() is more concise. Strive to be concise!
Whether you use random_index() or random_pick() or some other means, as one of my college economics professors was fond of saying, "There's more than one way to skin a cat." Do what suits you best. But in general, the less code bloat the better.
Are we now "good enough"? Okay, maybe now! ... But you can never be certain, you might discover a bug in some future auto-test run. There's no end to the sophistication and the nuance and the code polishing. Time to fish, or forever cut bait? (Or if you prefer: Keep discovering more and more ways to "skin the cat"?)
Time to fish! [show and tell some "finished" code, and auto-tests...]
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
I'm also interested because I've been plodding away at making a simple turn-based game myself, and I've never had any training in creating an AI. I did make a chess engine a couple years ago, and it actually played decently, but I borrowed heavily from others' algorithms for it.
Anyway, I hope you'll keep updating this thread. Thanks!

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
Things have been quiet here recently. Does that mean I have ceased working on this scenario's SAI, and developing and improving the CSEE/SAI (and EAI) in general? No! I have been working in this area quite avidly in the recent past, in fact. Why then the posting slowdown?
It's because, despite my pronouncements that "this is now good enough", it never really is. The more you analyze a situation, the more you code (script), the more you auto-test, the more things you see, the more ideas that pop into your head.
In the past month, I have run many, many dozens of auto-tests of this one scenario, VN_550921_Rung_Sat. Thus far, it is never good enough!
In doing this CSEE/SAI dev AAR, I am serving several purposes:
- Finishing this one scenario's VN_550921_Rung_Sat.lua file. It is only about 90% "finished" in the official public release. A few (VNA) units are not yet scripted. And the VNA aircraft are not scripted at all. (The new Enhanced Air Support system was added after this scenario was scripted sometime in late 2020.) Also, development of the CSEE/SAI continued right up to CSVN's official public release, and thereafter. VN_550921_Rung_Sat.lua should be reviewed and upgraded to latest standards.
- To document the CSEE/SAI's inner workings, its uses. This thread (and the companion thread https://www.matrixgames.com/forums/view ... 0&t=383969) will form the basis for a formal CSEE/SAI How-To manual eventually.
- To demonstrate best technique. Anything I post here should be bug-free, WAD, effective, done in good and proper style. Because I know it will be copied. I had better get it right.
- In the course of updating this one scenario's Lua file, I discover glitches and outright bugs. And better ways of doing things. (Recent additions to the CSEE toolkit, the functions: attack_sequential(), index(), ready(), retire_point(), counter_is_without_orders(), are_without_orders(), etc. Also fixed attack_way_point() and defend_way_point(). None of this released publicly yet.)
So, no posting through the month of August. No abandonment of this thread (and its companion thread). Far from it. To be continued...
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
No! Time to cut still more bait!

Just before my recent vacation, I thought: A sequence of attacks is something we often code in CSEE scripts. Can we write an uber function for that? But of course! You will see the new CSEE uber function attack_sequential() in the updated user.lua attached in a later post. (Requires also the updated, and attached, init.lua.)
Earlier, we have had something like this, for example:
Code: Select all
if obj24_threatened then
attack_nearest_to_hex(units, OBJECTIVES[24], NODIR, 1, 100, ATTACK_STRONG)
elseif obj27_threatened then
attack_way_point(units, {"84,17", OBJECTIVES[27]}, NODIR, 1, 100, ATTACK_STRONG)
elseif objective_owner(OBJECTIVES[31]) == BX_SIDE then
attack_way_point(units, {"92,19", OBJECTIVES[31]}, NODIR, 1, 50, ATTACK_STRONG)
elseif objective_owner(OBJECTIVES[33]) == BX_SIDE then
attack_strong(units, OBJECTIVES[33])
elseif objective_owner(OBJECTIVES[34]) == BX_SIDE then
attack_strong(units, OBJECTIVES[34])
else
defend_normal(units, OBJECTIVES[31])
end
We now have, more simply, this:
Code: Select all
if not attack_sequential(units, {OBJECTIVES[24], OBJECTIVES[27], OBJECTIVES[31], OBJECTIVES[33], OBJECTIVES[34]}, ATTACK_STRONG, true) then
defend_normal(units, OBJECTIVES[31])
end
The former "wall of text" code is wordy, is rather hard to follow (and to maintain). The latter example is more concise, more readable (and easier to maintain), is clearly better.
Or is it? In the former case, we have the attack triggers:
Code: Select all
...
elseif obj27_threatened then
[attack Objective 27]
...
elseif objective_owner(OBJECTIVES[31]) == BX_SIDE then
[attack Objective 31]
...
where obj27_threatened is earlier defined as
Code: Select all
local obj27_threatened = (units_within_count(OBJECTIVES[27], NODIR, 3, BX_SIDE) > 0)
In other words, if an Objective is enemy owned or "threatened" (whether owned, occupied, or not), then trigger attacks.
In the new attack_sequential() uber function, the attack triggers are, more generically and unvaryingly
Code: Select all
if (member(hc, OBJECTIVES) and owned(hc, oside)) or occupied(hc, oside) then
[attack the hc]
In other words, if the hc is an Objective hex and is enemy owned, else if the hc (whether Objective or not) is enemy occupied, that triggers an attack on the hc.
Those are some good, all-purpose triggers, but there might be others, if the hc is "threatened", however that is defined, or by whatever other considerations come to bear. I could complexify attack_sequential(), adding more and more esoteric attack conditions (and probably more and more function input parameters, to activate/deactivate those attack conditions). But no. I have KISSed attack_sequential() (shorter, more readable, easier to maintain -- the usual reasons).
So we lose some finesse and nuance by using attack_sequential(). But we gain by making the scenario Lua code briefer, thereby more readable, and more easily maintainable (if we want to change the attack sequence, for instance; or add alternative attack sequences heading off in difference directions, different attack "vectors").
When faced with the choice between verbosity vs. brevity, brevity wins every time. At least in my code. YMMV.
For the VNA 1st/6th Company, we have this orders code:
Code: Select all
-- _1ST_6TH_PARACHUTE_COY_55_A_228 -- 1st/6th Parachute Company 55 - A
do local units = _1ST_6TH_PARACHUTE_COY_55_A_228
_1ST_6TH_PARACHUTE_COY_55_A_228_READY = _1ST_6TH_PARACHUTE_COY_55_A_228_READY or ready(units)
if _1ST_6TH_PARACHUTE_COY_55_A_228_READY then
if not attack_sequential(units, {OBJECTIVES[24], OBJECTIVES[25], OBJECTIVES[27]}, ATTACK_STRONG, true) then
defend_normal(units, OBJECTIVES[25])
end
if loss_rate(units) >= VNA_LOSS_RATE_TRIGGER then
_1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT = _1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT or retire_point(units)
defend_weak(units, _1ST_6TH_PARACHUTE_COY_55_A_228_RETIRE_PT, NODIR, 1)
end
end
end
If _1ST_6TH_PARACHUTE_COY_55_A_228 is "ready" (no disruptions; see the scenario ready() function above), they go on to attack, strongly attacking OBJECTIVES[24], OBJECTIVES[25], and OBJECTIVES[27] in that order.
In the attack_sequential() function call, the last parameter value, 'true', says: if later in the sequence, in all cases (always true) return to attack earlier Objectives in the sequence if they become enemy owned again (were initially enemy owned, were taken, then fell back into the hands of the enemy again). (There is more nuance to this parameter. We will return to this later.)
attack_sequential() returns a 'true' value, if an attack is indicated. If no attack is indicated -- because none of the listed Objectives is in enemy hands -- then the function returns a 'false' value. In that case, we have _1ST_6TH_PARACHUTE_COY_55_A_228 defend normal OBJECTIVES[25].
As before, if the 1st/6th's loss rate exceeds the VNA_LOSS_RATE_TRIGGER, we have them retire to the nearest safe haven, the retire_point() (see the earlier post), there to weakly defend.
For the 6th Parachute Battalion Weapons Company, we have:
Code: Select all
-- _6TH_PARACHUTE_WEAPONS_COY_55_240 -- 6th Parachute Weapons Company 55
do local units = _6TH_PARACHUTE_WEAPONS_COY_55_240
_6TH_PARACHUTE_WEAPONS_COY_55_240_READY = _6TH_PARACHUTE_WEAPONS_COY_55_240_READY or ready(units)
if _6TH_PARACHUTE_WEAPONS_COY_55_240_READY then
if not attack_sequential(units, {OBJECTIVES[24], OBJECTIVES[25], OBJECTIVES[27]}, ATTACK_STRONG, true) then
defend_normal(units, OBJECTIVES[27])
end
-- _81MM_MORTAR_244 -- Parachute M1 81mm Mortars
if obj24_threatened then
fire_indirect_nearest_to_hex(_81MM_MORTAR_244, OBJECTIVES[24], 100)
elseif not at(_81MM_MORTAR_244, "82,15") then
move_norush(_81MM_MORTAR_244, "82,15")
elseif objective_owner(OBJECTIVES[25]) == BX_SIDE then
fire_indirect_nearest_to_hex(_81MM_MORTAR_244, OBJECTIVES[25], 100)
elseif objective_owner(OBJECTIVES[27]) == BX_SIDE then
fire_indirect_nearest_to_hex(_81MM_MORTAR_244, OBJECTIVES[27], 100)
end
if loss_rate(units) >= VNA_LOSS_RATE_TRIGGER then
_6TH_PARACHUTE_WEAPONS_COY_55_240_RETIRE_PT = _6TH_PARACHUTE_WEAPONS_COY_55_240_RETIRE_PT or retire_point(units)
defend_weak(units, _6TH_PARACHUTE_WEAPONS_COY_55_240_RETIRE_PT, NODIR, 1)
end
end
end
If the Weapons Company is ready for action, have it attack strongly in sequence the same Objectives as the 1st/6th. If no further attacks (all Objectives are won, and now VNA owned), have _6TH_PARACHUTE_WEAPONS_COY_55_240 defend normal OBJECTIVES[27].
In the middle section of that code block, we special handle _81MM_MORTAR_244. While OBJECTIVES[24] is "threatened", as in
Code: Select all
local obj24_threatened = (units_within_count(OBJECTIVES[24], NODIR, 3, BX_SIDE) > 0)
If OBJECTIVES[24] is threatened, have the mortars indirect fire at any enemy units nearest to OBJECTIVES[24].
Else if OBJECTIVES[24] is not threatened, move _81MM_MORTAR_244 to hex 82,15. Once there, indirect fire at OBJECTIVES[25] until the VNA takes it, after which indirect fire at OBJECTIVES[27]. (After which, no further orders for _81MM_MORTAR_244.)
We further have
Code: Select all
do local units = difference(join({_1ST_6TH_PARACHUTE_COY_55_A_228,_6TH_PARACHUTE_WEAPONS_COY_55_240}), _81MM_MORTAR_244)
if objective_owner(OBJECTIVES[24]) == VNA_SIDE then
-- have the weakest unit among the 1st/6th & Weapons join in garrisoning and defending OBJECTIVES[24]
_VNA_OBJECTIVES24_GARRISON = _VNA_OBJECTIVES24_GARRISON or random_pick(counters_weakest(units))
defend_normal(_VNA_OBJECTIVES24_GARRISON, OBJECTIVES[24], NODIR, 0, 100)
if objective_owner(OBJECTIVES[25]) == VNA_SIDE then
-- have the second weakest unit among the 1st/6th & Weapons join in garrisoning and defending OBJECTIVES[25]
_VNA_OBJECTIVES25_GARRISON = _VNA_OBJECTIVES25_GARRISON or random_pick(counters_weakest(difference(units,_VNA_OBJECTIVES24_GARRISON)))
defend_normal(_VNA_OBJECTIVES25_GARRISON, OBJECTIVES[25], NODIR, 0, 100)
end
end
end
See the '--' comments in that code block for explanation.
For the 2nd/6th Company, the orders are
Code: Select all
-- _2ND_6TH_PARACHUTE_COY_55_A_232 -- 2nd/6th Parachute Company 55 - A
do local units = _2ND_6TH_PARACHUTE_COY_55_A_232
_2ND_6TH_PARACHUTE_COY_55_A_232_READY = _2ND_6TH_PARACHUTE_COY_55_A_232_READY or ready(units)
if _2ND_6TH_PARACHUTE_COY_55_A_232_READY then
_2ND_6TH_PARACHUTE_COY_3RD_6TH_PARACHUTE_COY_ORDER = _2ND_6TH_PARACHUTE_COY_3RD_6TH_PARACHUTE_COY_ORDER or random_pick({1,1,2})
if _2ND_6TH_PARACHUTE_COY_3RD_6TH_PARACHUTE_COY_ORDER == 1 then
if not attack_sequential(units, {OBJECTIVES[24], OBJECTIVES[25], OBJECTIVES[27], OBJECTIVES[31], OBJECTIVES[33], OBJECTIVES[34]}, ATTACK_STRONG, true) then
defend_normal(units, OBJECTIVES[31])
end
else
if not attack_sequential(units, {OBJECTIVES[24], OBJECTIVES[27], OBJECTIVES[31], OBJECTIVES[33], OBJECTIVES[34]}, ATTACK_STRONG, true) then
defend_normal(units, OBJECTIVES[31])
end
end
if loss_rate(units) >= VNA_LOSS_RATE_TRIGGER then
_2ND_6TH_PARACHUTE_COY_55_A_232_RETIRE_PT = _2ND_6TH_PARACHUTE_COY_55_A_232_RETIRE_PT or retire_point(units)
defend_weak(units, _2ND_6TH_PARACHUTE_COY_55_A_232_RETIRE_PT, NODIR, 1)
end
end
end
When ready, _2ND_6TH_PARACHUTE_COY_55_A_232 is assigned order #1 (2/3 chance) or #2 (1/3 chance).
If order #1, then the 2nd/6th will join the 1st/6th & Weapons Companies in attacking OBJECTIVES[25]. If order #2, after OBJECTIVES[24], they instead proceed to attacking OBJECTIVES[27].
In either case (either order #1 or #2), after OBJECTIVES[27] is in the VNA's possession, they attack sequentially, and strongly, OBJECTIVES[31], OBJECTIVES[33] & OBJECTIVES[34].
And so on.
For the 3rd/6th Company, their orders are much the same as the 2nd/6th's. I have them in their own orders code block, although I could (for the sake of brevity!) combine the two companies into the same code block via
Code: Select all
-- _2ND_6TH_PARACHUTE_COY_55_A_232 -- 2nd/6th Parachute Company 55 - A
-- _3RD_6TH_PARACHUTE_COY_55_A_236 -- 3rd/6th Parachute Company 55 - A
do local units = join({_2ND_6TH_PARACHUTE_COY_55_A_232, _3RD_6TH_PARACHUTE_COY_55_A_236})
...
end
To conclude the 6th Parachute Battalion's code, I have
Code: Select all
if objective_owner(OBJECTIVES[34]) == VNA_SIDE then
-- have the weakest unit among the 2nd/6th & 3rd/6th join in garrisoning and defending OBJECTIVES[34]
_VNA_OBJECTIVES34_GARRISON = _VNA_OBJECTIVES34_GARRISON or random_pick(counters_weakest(join({_2ND_6TH_PARACHUTE_COY_55_A_232,_3RD_6TH_PARACHUTE_COY_55_A_236})))
defend_normal(_VNA_OBJECTIVES34_GARRISON, OBJECTIVES[34], NODIR, 0, 100)
end
Again, read the comment in that code block for explanation.
NOTE: Although it's good to write readable code as is, add explanatory comments where helpful! (Jason adds comments in abundance. At the risk of bloating his code, IMO. But His Mileage Varies. <wink> To each his own.)
I have made similar use of attack_sequential() etc. to revise the VNA 5th Parachute Battalion's orders code. In the interest of forum post brevity (hah!), I won't detail those revisions here. For details, see the attached VN_550921_Rung_Sat.lua eventually to follow.)
Let's go to the videotape (see some auto-test screenshots)...
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
At the first stopping point, Turn 6, Side A phase, we see this:
The eagles (the 5th, blue highlights, and 6th, yellow highlights) have landed! (Also all other VNA forces.)
After a restart, at the second stopping point, Turn 10, Side A phase, the situation:
For the 5th Btn, the fight for OBJECTIVES[13] is just getting started.
For the 6th Btn, the unoccupied OBJECTIVES[24] is quickly taken. The fight for OBJECTIVES[24] is already finished. That was too easy!
After a restart, at the third stopping point, Turn 15, Side A phase, the situation:
The fight for OBJECTIVES[13] is nearing its climax.
The 6th begins encircling OBJECTIVES[25].
In the meantime, the 3rd/6th bides its time at OBJECTIVES[24].
After a restart, at the fourth stopping point, Turn 20, Side A phase, the situation:
With OBJECTIVES[13] taken, the 1st/5th moves up the road to attack OBJECTIVES[8].
Other 5th Btn forces move off to attack OBJECTIVES[16] & OBJECTIVES[17] to the northeast.
The 6th Btn has had similar success. OBJECTIVES[25] has fallen!
After a restart, at the fifth stopping point, Turn 25, Side A phase, the situation:
The 1st/5th has captured OBJECTIVES[8]. Good job!
The 2nd/5th remains behind defending OBJECTIVES[13].
To the northeast, the fight for OBJECTIVES[16] (and OBJECTIVES[17]) begins.
Leaving behind some forces to garrison OBJECTIVES[24] and OBJECTIVES[25], the rest of the 6th Btn has encircled and taken OBJECTIVES[27]. Excellent!
After a restart, at the sixth stopping point, Turn 30, Side A phase, the situation:
Surprise! BX Army units have appeared from out of nowhere to attack OBJECTIVES[13]. The 1st/5th leaves OBJECTIVES[8] ungarrisoned and moves to the rescue.
The attack on OBJECTIVES[16] is sputtering.
To the southeast, with OBJECTIVES[27] taken, and ignoring the BX Army forces lurking nearby, the 2nd/6th & 3rd/6th move down the road approaching their other objectives.
After a restart, at the seventh stopping point, Turn 35, Side A phase, the situation:
The second battle of OBJECTIVES[13] is touch and go.
The remainder of the 5th Btn break off their attacks.
The 2nd/6th & 3rd/6th have bunched up at the southeast corner of the screenshot.
After a restart, at the eight and final stopping point, Turn 40, Side A phase, the situation:
OBJECTIVES[13] remains in enemy hands. Bad show!
Due to heavy casualties, the 3rd/5th and Weapons companies have broken off the attack on OBJECTIVES[16] and are licking their wounds in the village hexes to the west.
The 2nd/6th & 3rd/6th remain bunched up, all stacked together. Why is that? Hmm.
These are fairly typical, middling auto-test results. I have witnessed auto-tests where the VNA have taken both OBJECTIVES[16] & OBJECTIVES[17]. Have not taken OBJECTIVES[8]. Have struggled to take OBJECTIVES[25] & OBJECTIVES[27]. OBJECTIVES[13] is always taken, then depending on the random orders and random combat results, sometimes falls, then sometimes is taken back. There is a pleasing variety of outcomes. Much depends on the random order #s (from those random_pick() calls.)
But what about that 2nd/6th & 3rd/6th stalling?
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
I look in the engine.log for some clues to confirm my suspicions. The engine.log is quite large
rober@Rob10rto /cygdrive/r/Temp/Logs
$ ls -lh engine.log
-rwxrwxrwx 1 rober rober 906M Aug 29 05:28 engine.log
So by filtering and capturing to a junk file only the items of special interest (note: I have changed the logging ID #s in the latest EXEs, available in the next release(s))
rober@Rob10rto /cygdrive/r/Temp/Logs
$ egrep "ID 300[0-9]|ID 4000|attack_sequential" engine.log > junk
I have a much smaller file to analyze
rober@Rob10rto /cygdrive/r/Temp/Logs
$ ls -lh junk
-rw-r--r-- 1 rober rober 59M Aug 29 05:28 junk
One of the units in that 2nd/6th & 3rd/6th stack has trackid 238. Using the Cygwin/Linux egrep pattern matching utility, I look for 'MakeGoal' log entries for that trackid:
rober@Rob10rto /cygdrive/r/Temp/Logs
$ egrep "trackid 238" junk | egrep MakeGoal
2022-08-28 20:06:00 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 5, side 0, trackid 238, strackid 236, xai -1, yai -1, hexai -1,-1, aidir -1, airadius -1, aiactprob 100, aiorder 2
2022-08-28 20:06:58 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 6, side 0, trackid 238, strackid 236, xai -1, yai -1, hexai -1,-1, aidir -1, airadius -1, aiactprob 100, aiorder 2
...
2022-08-28 20:08:48 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 8, side 0, trackid 238, strackid 236, xai 77, yai 15, hexai 77,15, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:08:48 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 8, side 0, unit Parachute Platoon 55 A, hex 71,11, trackid 238, strackid 236, xai 77, yai 15, hexai 77,15, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:10:47 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 9, side 0, trackid 238, strackid 236, xai 77, yai 15, hexai 77,15, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:10:47 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 9, side 0, unit Parachute Platoon 55 A, hex 72,12, trackid 238, strackid 236, xai 77, yai 15, hexai 77,15, aidir -1, airadius -1, aiactprob 100, aiorder 13
...
2022-08-28 20:24:06 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 16, side 0, trackid 238, strackid 236, xai 85, yai 18, hexai 85,18, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:24:06 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 16, side 0, unit Parachute Platoon 55 A, hex 77,15, trackid 238, strackid 236, xai 85, yai 18, hexai 85,18, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:25:31 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 17, side 0, trackid 238, strackid 236, xai 85, yai 18, hexai 85,18, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:25:31 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 17, side 0, unit Parachute Platoon 55 A, hex 79,16, trackid 238, strackid 236, xai 85, yai 18, hexai 85,18, aidir -1, airadius -1, aiactprob 100, aiorder 13
...
2022-08-28 20:37:51 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 24, side 0, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:37:51 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 24, side 0, unit Parachute Platoon 55 A, hex 84,16, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:39:46 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 25, side 0, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 20:39:46 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 25, side 0, unit Parachute Platoon 55 A, hex 85,18, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
...
2022-08-28 21:00:27 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 31, side 0, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 21:00:27 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 31, side 0, unit Parachute Platoon 55 A, hex 91,20, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 21:01:43 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 32, side 0, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 21:01:43 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 32, side 0, unit Parachute Platoon 55 A, hex 91,20, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
...
2022-08-28 21:24:32 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 39, side 0, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 21:24:32 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 39, side 0, unit Parachute Platoon 55 A, hex 91,20, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 21:30:43 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1641, Control::MakeGoal()) in Control::MakeGoal(), turn 40, side 0, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
2022-08-28 21:30:43 vnengine.exe: [DEBUG ID 4000] (control.cpp, line 1644, Control::MakeGoal()) in Control::MakeGoal(), setting goal, assigning order, turn 40, side 0, unit Parachute Platoon 55 A, hex 91,20, trackid 238, strackid 236, xai 93, yai 24, hexai 93,24, aidir -1, airadius -1, aiactprob 100, aiorder 13
A review of the OBJECTIVES[] (from the VN_550921_Rung_Sat.lua file init_constants()):
...
OBJECTIVES[24] = "77,15" -- 1-40[2/2] 21
...
OBJECTIVES[27] = "85,18" -- 1-40[2/2] 21
...
OBJECTIVES[31] = "93,24" -- 1-40[2/2] 21
...
And a review of the aiorder #s (from init.lua):
...
HALT = 2
...
ATTACK_STRONG = 13
...
On Turn 5, after landing, arriving on map, _2ND_PLT_238 is given the HALT aiorder 2, while it awaits it and rest of its company to undisrupt.
On Turn 8, _2ND_PLT_238 (& co.) now undisrupted, the unit receives its first attack order, ATTACK_STRONG, aiorder 13, with hexai 77,15 -- OBJECTIVES[24] -- as its target hex.
On Turn 16, with OBJECTIVES[24] by now taken, _2ND_PLT_238 receives the ATTACK_STRONG, aiorder 13, with hexai 85,18 -- OBJECTIVES[27] -- as its target.
On Turn 24, with OBJECTIVES[27] by now taken, _2ND_PLT_238 receives the ATTACK_STRONG, aiorder 13, with hexai 93,24 -- OBJECTIVES[31] -- as its target.
On Turn 31, _2ND_PLT_238 arrives at hex 91,20 ... where it remains stuck for the remainder of the scenario.
At Turn 40:
Note where hex 91,20 abuts a Minor River. That right there is the problem. The game engine path finding doesn't "see" across waterways very well, or at all. From the starting point hex 91,20, the pathfinding cannot determine, cannot find, a path across the Minor River to hex 93,24 (OBJECTIVES[31]).
Without devoting more time to single step through the game engine code execution (the programmer can do that; you can't), I am pretty darned sure this is the crux of the problem. Carefully inspecting the game engine.log as I have just done was not really necessary. Sometimes there is no other way than digging deep and deeper still to pinpoint the cause of failure. Maybe not this time.
This is the latest in a long line of SAI "gotchas", mysterious glitches, where the SAI doesn't do exactly what you would like it to do. Improving the game engine pathfinding is a long-term aim, but for now it is what it is. (Sometimes the issue is a deeper game engine bug, but that is outside the current discussion.)
What to do? attack_way_point() is the answer. Rather than fret about the E[ngine]AI and its shortcomings, we instead use the S[cripted]AI tools we have at hand to address these sorts of glitches.
The attack_sequential() function only encompasses attack_strong(), not attack_way_point(..., ATTACK_STRONG, ...).
Code: Select all
-- attack_sequential() -- attack the indicated hexes in sequence
function attack_sequential (trackids, hcs, attype, backtrack)
...
if at == ATTACK_WEAK then
attack_weak(ca, targethex)
elseif at == ATTACK_NORMAL then
attack_normal(ca, targethex)
elseif at == ATTACK_STRONG then
attack_strong(ca, targethex)
elseif at == ATTACK_BANZAI or
at == ATTACK_FANATICAL then
attack_banzai(ca, targethex)
else
return false
end
...
end
Again, what to do? I might add still more bells & whistles and capabilities and special cases and complexity and additional input parameters ... but I prefer not to do that. The attack_sequential() function is already quite complex. We want using it to be fairly easy and straightforward. I don't want it to become bloated to the point of using it being rocket science. KISS it, and all that.
For _3RD_6TH_PARACHUTE_COY_55_A_236, there is a possible solution. Abandon use of attack_sequential() altogether:
Code: Select all
if obj24_threatened then
attack_nearest_to_hex(units, OBJECTIVES[24], NODIR, 1, 100, ATTACK_STRONG)
elseif obj27_threatened then
attack_way_point(units, {"84,17", OBJECTIVES[27]}, NODIR, 1, 100, ATTACK_STRONG)
elseif objective_owner(OBJECTIVES[31]) == BX_SIDE then
attack_way_point(units, {"92,19", OBJECTIVES[31]}, NODIR, 1, 100, ATTACK_STRONG)
elseif objective_owner(OBJECTIVES[33]) == BX_SIDE then
attack_strong(units, OBJECTIVES[33])
elseif objective_owner(OBJECTIVES[34]) == BX_SIDE then
attack_strong(units, OBJECTIVES[34])
else
defend_normal(units, OBJECTIVES[31])
end
In the earlier formulation above, note where attack_way_point() was used for attacks on both OBJECTIVES[27] and OBJECTIVES[31].
But that puts us back to square one. Bloated code, and unreadability (and code maintenance difficulty).
One possible fix would be
Code: Select all
if not attack_sequential(units, {OBJECTIVES[24], OBJECTIVES[27]}, ATTACK_STRONG, true) then
if objective_owner(OBJECTIVES[31]) == BX_SIDE then
attack_way_point(units, {"92,19", OBJECTIVES[31]}, NODIR, 1, 100, ATTACK_STRONG)
elseif not attack_sequential(units, {OBJECTIVES[33], OBJECTIVES[34]}, ATTACK_STRONG, true) then
defend_normal(units, "97,22")
end
end
that is, sandwich the attack_way_point() on OBJECTIVES[31] between the outer attack_sequential() calls to attack OBJECTIVES[24] & OBJECTIVES[27], and OBJECTIVES[33] & OBJECTIVES[34]. But I dunno. I have a bad feeling about that code, not sure it would be WAD.
This is the approach I have adopted:
Code: Select all
-- _3RD_6TH_PARACHUTE_COY_55_A_236 -- 3rd/6th Parachute Company 55 - A
do local units = _3RD_6TH_PARACHUTE_COY_55_A_236
_3RD_6TH_PARACHUTE_COY_55_A_236_READY = _3RD_6TH_PARACHUTE_COY_55_A_236_READY or ready(units)
if _3RD_6TH_PARACHUTE_COY_55_A_236_READY then
_2ND_6TH_PARACHUTE_COY_3RD_6TH_PARACHUTE_COY_ORDER = _2ND_6TH_PARACHUTE_COY_3RD_6TH_PARACHUTE_COY_ORDER or 1 -- random_pick({1,1,2})
if _2ND_6TH_PARACHUTE_COY_3RD_6TH_PARACHUTE_COY_ORDER == 1 then
attack_strong(units, OBJECTIVES[24])
if owned(OBJECTIVES[25], VNA_SIDE) then
if not attack_sequential(units, {OBJECTIVES[27], OBJECTIVES[31], OBJECTIVES[33], OBJECTIVES[34]}, ATTACK_STRONG, true) then
defend_normal(units, "97,22")
end
end
else
if not attack_sequential(units, {OBJECTIVES[24], OBJECTIVES[27], OBJECTIVES[31], OBJECTIVES[33], OBJECTIVES[34]}, ATTACK_STRONG, true) then
defend_normal(units, "97,22")
end
end
-- if stuck at the major river at 91,20, unable to proceed due to pathfinding failure...
if at(units, "91,20") and
(hexai(units[1]) == OBJECTIVES[31]) and
(aiorder(units[1]) == ATTACK_STRONG) then
attack_way_point(units, {"92,19", OBJECTIVES[31]}, NODIR, 1, 100, ATTACK_STRONG)
end
if loss_rate(units) >= VNA_LOSS_RATE_TRIGGER then
_3RD_6TH_PARACHUTE_COY_55_A_236_RETIRE_PT = _3RD_6TH_PARACHUTE_COY_55_A_236_RETIRE_PT or retire_point(units)
defend_weak(units, _3RD_6TH_PARACHUTE_COY_55_A_236_RETIRE_PT, NODIR, 1)
end
end
end
With this, I make use of the CSEE/SAI rule: later orders override earlier orders in the command sequence. After the preceding attack_sequential() calls, we next consider the OBJECTIVES[31] special case:
Code: Select all
-- if stuck at the major river at 91,20, unable to proceed due to pathfinding failure...
if at(units, "91,20") and
(hexai(units[1]) == OBJECTIVES[31]) and
(aiorder(units[1]) == ATTACK_STRONG) then
attack_way_point(units, {"92,19", OBJECTIVES[31]}, NODIR, 1, 100, ATTACK_STRONG)
end
We first test whether all units are at hex 91,20. We then test whether the units are under the standing order to ATTACK_STRONG OBJECTIVES[31]. This is precisely the situation where _2ND_PLT_238 & co. are stuck at the Minor River, unable to figure out how to proceed across the Minor River to the target destination. In this situation, we override the earlier order with the later order
Code: Select all
attack_way_point(units, {"92,19", OBJECTIVES[31]}, NODIR, 1, 100, ATTACK_STRONG)
Problem solved, I think. The proof is in the testing.
(Note: I apply this coding solution to orders for both _2ND_6TH_PARACHUTE_COY_55_A_232 and _3RD_6TH_PARACHUTE_COY_55_A_236.)
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
- Jason Petho
- Posts: 17398
- Joined: Tue Jun 22, 2004 10:31 am
- Location: Terrace, BC, Canada
- Contact:
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
... success! All units are unstuck and have crossed over the Minor River to hex 92,19. Yay!
I let the auto-test run all the way to the end. At Turn 40:
Looking good. _2ND_6TH_PARACHUTE_COY_55_A_232 and _3RD_6TH_PARACHUTE_COY_55_A_236 succeeded in capturing both OBJECTIVES[31] and OBJECTIVES[33]. Only OBJECTIVES[34] remains in enemy hands.
The overall situation at scenario auto-test's end:
These are perhaps the best results I have ever seen for this auto-test. With the exception of OBJECTIVES[34], also the lately contested OBJECTIVES[13], all other Objectives in this sector are VNA owned.
Is this now good enough? Am I now satisfied with the _5TH_PARACHUTE_BTN_55_A_441 and _6TH_PARACHUTE_BTN_55_A_226 orders coding? I would say so. Time to move on!
Attached are the latest versions of the pertinent files:
$ unzip -l VN_550921_Rung_Sat_20220829.zip
Archive: VN_550921_Rung_Sat_20220829.zip
Length Date Time Name
--------- ---------- ----- ----
441471 08-23-2022 13:20 init.lua
109511 08-28-2022 19:59 user.lua
110289 08-29-2022 10:30 Scenarios/VN_550921_Rung_Sat.lua
--------- -------
661271 3 files
For you to have a look-see, and perhaps to run your own auto-tests with (good luck).
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
Recall that "I Lua scripted Rung Sat a couple of years ago, before many CSEE functions and coding/debugging techniques were available or fully developed". Let me second guess myself.
The VNA 1st Parachute Battalion paradrops to the southwest of the 5th & 6th Btns on Turns 1 & 2. At the beginning of Turn 3, we have:
Initially, the 1st Btn is to attack and capture OBJECTIVES[9] & OBJECTIVES[10]. If the attacks go well, and there is sufficient time remaining in the scenario, the 1st Btn is to pursue other missions and objectives later, hopefully.
Removing (by commenting out) some detail, here is the earlier orders scripting for the VNA 1st Parachute Battalion from a couple of years ago:
Code: Select all
-------------------------------------------------------------------
-- _1ST_PARACHUTE_BTN_55_A_169 -- 1st Parachute Battalion 55 - A --
-------------------------------------------------------------------
if not _1ST_PARACHUTE_BTN_55_A_169_READY_TURN and -- set one time only
are_not_disrupted(_1ST_PARACHUTE_BTN_55_A_169) then
_1ST_PARACHUTE_BTN_55_A_169_READY_TURN = turn
end
-- _HQ_170 -- VNAF Airborne Battalion HQ (foot)
-- _1ST_PARACHUTE_461 -- Airborne Commander 2
do local units = join({_HQ_170, _1ST_PARACHUTE_461})
if _1ST_PARACHUTE_BTN_55_A_169_READY_TURN and
turn >= (_1ST_PARACHUTE_BTN_55_A_169_READY_TURN + 2) then
move_way_point(units, {"50,21", "52,23"})
if objective_owner(OBJECTIVES[10]) == VNA_SIDE then
defend_way_point(units, {"51,25", "52,27", OBJECTIVES[10]}, NODIR, 1, 100, DEFEND_NORMAL)
elseif objective_owner(OBJECTIVES[9]) == VNA_SIDE then
defend_normal(units, OBJECTIVES[9], NODIR, 0, 100)
end
end
end
do local units = _1ST_PARACHUTE_461
end
-- _1ST_1ST_PARACHUTE_COY_55_A_171 -- 1st/1st Parachute Company 55 - A
do local units = _1ST_1ST_PARACHUTE_COY_55_A_171
if _1ST_PARACHUTE_BTN_55_A_169_READY_TURN then
if objective_owner(OBJECTIVES[9]) == BX_SIDE then
attack_way_point(units, {"50,21", "52,23", OBJECTIVES[9]}, NODIR, 2, 50, ATTACK_NORMAL)
elseif objective_owner(OBJECTIVES[10]) == BX_SIDE then
attack_way_point(units, {"52,27", OBJECTIVES[10]}, NODIR, 2, 50, ATTACK_NORMAL)
--[[
...
--]]
end
end
end
-- _2ND_1ST_PARACHUTE_COY_55_A_175 -- 2nd/1st Parachute Company 55 - A
do local units = _2ND_1ST_PARACHUTE_COY_55_A_175
if _1ST_PARACHUTE_BTN_55_A_169_READY_TURN then
if not OBJECTIVES10_CAPTURED then
if objective_owner(OBJECTIVES[9]) == BX_SIDE then
attack_way_point(units, {"50,21", "52,23", OBJECTIVES[9]}, NODIR, 2, 50, ATTACK_NORMAL)
elseif objective_owner(OBJECTIVES[10]) == BX_SIDE then
attack_way_point(units, {"52,27", OBJECTIVES[10]}, NODIR, 2, 50, ATTACK_NORMAL)
end
--[[
...
--]]
end
end
::next::
end
-- _3RD_1ST_PARACHUTE_COY_55_A_179 -- 3rd/1st Parachute Company 55 - A
do local units = _3RD_1ST_PARACHUTE_COY_55_A_179
if _1ST_PARACHUTE_BTN_55_A_169_READY_TURN then
if not OBJECTIVES10_CAPTURED then
if objective_owner(OBJECTIVES[9]) == BX_SIDE then
attack_way_point(units, {"50,21", "52,23", OBJECTIVES[9]}, NODIR, 2, 50, ATTACK_NORMAL)
elseif objective_owner(OBJECTIVES[10]) == BX_SIDE then
attack_way_point(units, {"52,27", OBJECTIVES[10]}, NODIR, 2, 50, ATTACK_NORMAL)
end
--[[
...
--]]
end
end
::next::
end
-- _1ST_PARACHUTE_WEAPONS_COY_55_183 -- 1st Parachute Weapons Company 55
do local units = _1ST_PARACHUTE_WEAPONS_COY_55_183
if _1ST_PARACHUTE_BTN_55_A_169_READY_TURN then
if objective_owner(OBJECTIVES[9]) == BX_SIDE then
attack_way_point(units, {"50,21", "52,23", OBJECTIVES[9]}, NODIR, 2, 50, ATTACK_NORMAL)
elseif objective_owner(OBJECTIVES[10]) == BX_SIDE then
attack_way_point(units, {"52,27", OBJECTIVES[10]}, NODIR, 2, 50, ATTACK_NORMAL)
--[[
...
--]]
end
end
end
-- _81MM_MORTAR_187 -- Parachute M1 81mm Mortars
The
--[[
...
--]]
we will get to later. For now we want to focus on the initial attacks on OBJECTIVES[9] & OBJECTIVES[10] (the uncommented code displayed above).
Without first carefully analyzing that code, let's instead go straight to the auto-tests ...
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "3a" > pass
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "6a" > pass
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "9a" > pass
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "12a" > pass
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "15a" > pass
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "18a" > pass
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "21a" > pass
...
At the beginning of Turn 6
elements of the _1ST_PARACHUTE_BTN_55_A_169 have started their initial attack_way_point() on the nearest objective, OBJECTIVES[9] (hex 49,28).
At the beginning of Turn 9
all platoons are in motion. The battalion HQ stays behind. So far so good.
At Turn 12
the forward elements are poised to launch their attacks.
At Turn 15, a surprise:
OBJECTIVES[9] has already fallen! In fact, I could see where the very first assault, on Turn 10, succeeded in taking the objective. That was too easy!
With OBJECTIVES[9] captured, the _1ST_PARACHUTE_BTN_55_A_169 way point moves on to attack OBJECTIVES[10].
At Turn 18
OBJECTIVES[11] also has fallen! Way too easy!
At Turn 21
with the follow-up ... orders commented out, there is nothing more for _1ST_PARACHUTE_BTN_55_A_169 to do. The scenario is only half over. There is plenty of time to assign the 1st Btn additional missions.
But before I do (uncomment and thereby activate the ... code), let's look at the earlier, two-year-old code critically:
- The initial 'are_not_disrupted(_1ST_PARACHUTE_BTN_55_A_169)' is in fact broken, does not do as intended. I will revise the code to activate the individual companies on a company by company basis much like I did in the orders code for the 5th & 6th Btns (see earlier posts).
- The battalion _HQ_170 initially stayed behind with no combat unit(s) assigned to protect it. Not smart.
- After OBJECTIVES[9] was captured, no units were assigned to garrison it. What if there are BX Army enemy lurking about ready to retake the Objective?
- The attack method chosen for both OBJECTIVES[9] & OBJECTIVES[10] was ATTACK_NORMAL. Again, not smart. I have learned from experience that it is usually better to ATTACK_STRONG Objectives. This is because ATTACK_STRONG is more likely to have the attacking units assault the target hex, while with ATTACK_NORMAL the chance for firing vs. assaulting is more like 50/50. In the earlier 5th & 6th Btn situations, if ATTACK_NORMAL were used, the VNA casualties would pile up: Attacking units out in the open vs. defending units in fortified positions are not going to win many firefights. No, better to move in close and assault. If you review the 5th & 6th Btn orders code, you will see where in all or nearly all cases, ATTACK_STRONG is used. For the 1st Btn, it should be likewise. (ATTACK_NORMAL is more appropriate for attacks against less important, unfortified positions.)
- There are multiple pathways to OBJECTIVES[9].
Why have every company of the 1st Btn take the easternmost route only? Better to mix it up, because
- We don't know beforehand where the enemy might be lurking. Who's to say the easternmost pathway is the only right way?
- By allowing for the westernmost pathway along the river, units attacking from that direction will approach OBJECTIVES[9] from the west, better to encircle OBJECTIVES[9], better to block the path of enemy units maybe retreating from a failed defense of the Objective.
- In addition to taking Objectives, we know (the VNA knows) that BX Army supply depots are scattered about. Maybe by spreading out the attack among the three pathways to OBJECTIVES[9] the VNA will stumble upon, and capture, some VP rich enemy supply dumps/depots/factories?
- For greater scenario replayability, if nothing else.
IMPORTANT: Yes, in the first auto-test (and all such auto-tests?), the 1st Btn had great success. But how can we be sure that would always be so? When we SAI, we should not script with perfect foreknowledge of the situation, foreknowledge gained from previous auto-tests, perhaps a good many of them. In other words, with an orders rewrite, if most of the units take the westernmost or middle pathways which delays somewhat their assaults and capture (?) of OBJECTIVES[9], that is okay. How would the VNA pre-ordain that the eastern pathway is the best? In RL, they wouldn't know. So in scripting, we should not give them that cheat!
With all that said, an orders revamp to follow...
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
Although it looks like there is a Ford at hex 47,22 (magenta circle), in fact there is not (that is a River coursing through a Swamp hex), as we can plainly see by toggling ON the Map Hints:
At hex 47,22 (magenta circle), there is no FD (Ford) hint. There being no Ford at hex 47,22, there is no way to cross the River at that point!
Haha, the joke's on me!
I devoted an hour to some fancy coding randomly selecting any of the three alternate pathways shown two screenshots above. Time wasted, all for nothing. (Well, not quite useless. I will remember the technique in future, when and if needed.)
I might make use of some VNA Marines riverine transport assets nearby (just off screen to the west). If I were to park a riverine transport at hex 47,22, it could ferry the VNA Airborne across the River. But no, the nearby riverine transports are fully loaded; the Marines have other missions and concerns.
In the map editor, I might change hex 47,22 from a River hex to a Shallow River hex, making it crossable. But no, the terrain is what it is (in Real Life). We don't go changing (RL) terrain just to suit the needs of the scripting moment.
Nice idea, that providing for three different pathways to OBJECTIVES[9]. Anyway, with just the one pathway available, it sure simplifies the coding.
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
Code: Select all
-- _1ST_1ST_PARACHUTE_COY_55_A_171 -- 1st/1st Parachute Company 55 - A
do local units = _1ST_1ST_PARACHUTE_COY_55_A_171
_1ST_1ST_PARACHUTE_COY_55_A_171_READY = _1ST_1ST_PARACHUTE_COY_55_A_171_READY or ready(units)
if _1ST_1ST_PARACHUTE_COY_55_A_171_READY then
if objective_owner(OBJECTIVES[9]) == BX_SIDE then
attack_way_point(units, {"50,21", "52,23", OBJECTIVES[9]}, NODIR, 0, 100, ATTACK_STRONG)
elseif objective_owner(OBJECTIVES[10]) == BX_SIDE then
attack_way_point(units, {"52,27", OBJECTIVES[10]}, NODIR, 0, 100, ATTACK_STRONG)
--[[
...
--]]
end
if loss_rate(units) >= VNA_LOSS_RATE_TRIGGER then
_1ST_1ST_PARACHUTE_COY_55_A_171_RETIRE_PT = _1ST_1ST_PARACHUTE_COY_55_A_171_RETIRE_PT or retire_point(units)
defend_weak(units, _1ST_1ST_PARACHUTE_COY_55_A_171_RETIRE_PT, NODIR, 1)
end
end
end
By now, this is pretty standard stuff, much like earlier orders coding (see detailed explanation in previous posts). In brief: When "ready" (all platoons undisrupted, after their para landing), way point attack OBJECTIVES[9], then OBJECTIVES[10]. Retire to the nearest habitat hex if the loss rate is too high.
The commented out '--[[ ... --]]' is a place holder for code to be written later, for when after both OBJECTIVES[9] & OBJECTIVES[10] are secured.
The orders code for the other 1st Para Btn companies are much the same, and I won't be showing them here.
For the one Weapons Company mortar unit, we have
Code: Select all
-- _81MM_MORTAR_187 -- Parachute M1 81mm Mortars
if objective_owner(OBJECTIVES[10]) == VNA_SIDE then
if not at(_81MM_MORTAR_187, OBJECTIVES[10]) then
move_norush(_81MM_MORTAR_187, OBJECTIVES[10])
else
fire_indirect_nearest(_81MM_MORTAR_187, 100)
end
end
When OBJECTIVES[10] is taken, move _81MM_MORTAR_187 to that place. And once there, no more movement, have them indirect fire at the nearest (verified only, by default) enemy units.
The orders code to garrison the captured objectives:
Code: Select all
do local units = join({_1ST_1ST_PARACHUTE_COY_55_A_171, _2ND_1ST_PARACHUTE_COY_55_A_175, _3RD_1ST_PARACHUTE_COY_55_A_179})
if objective_owner(OBJECTIVES[9]) == VNA_SIDE then
-- have the weakest unit among the 1st/1st, 2nd/1st & 3rd/1st join in garrisoning and defending OBJECTIVES[9]
_VNA_OBJECTIVES9_GARRISON = _VNA_OBJECTIVES9_GARRISON or random_pick(counters_weakest(units))
defend_normal(_VNA_OBJECTIVES9_GARRISON, OBJECTIVES[9], NODIR, 0, 100)
if objective_owner(OBJECTIVES[10]) == VNA_SIDE then
-- have the second weakest unit among the 1st/1st, 2nd/1st & 3rd/1st join in garrisoning and defending OBJECTIVES[10]
_VNA_OBJECTIVES10_GARRISON = _VNA_OBJECTIVES10_GARRISON or random_pick(counters_weakest(difference(units,_VNA_OBJECTIVES9_GARRISON)))
defend_normal(_VNA_OBJECTIVES10_GARRISON, OBJECTIVES[10], NODIR, 0, 100)
end
end
end
Note where we select garrison units from among the three airborne infantry companies, excluding the weapons company (not included in the join()). Note too where we exclude _VNA_OBJECTIVES9_GARRISON when determining the _VNA_OBJECTIVES10_GARRISON.
The HQ and battalion commander orders code:
Code: Select all
-- _HQ_170 -- VNAF Airborne Battalion HQ (foot)
-- _1ST_PARACHUTE_461 -- Airborne Commander 2
do local units = join({_HQ_170, _1ST_PARACHUTE_461})
if _1ST_1ST_PARACHUTE_COY_55_A_171_READY and _2ND_1ST_PARACHUTE_COY_55_A_175_READY and _3RD_1ST_PARACHUTE_COY_55_A_179_READY then
move_way_point(units, {"50,21", "52,23"})
if objective_owner(OBJECTIVES[10]) == VNA_SIDE then
defend_way_point(units, {"51,25", "52,27", OBJECTIVES[10]}, NODIR, 1, 100, DEFEND_NORMAL)
elseif objective_owner(OBJECTIVES[9]) == VNA_SIDE then
defend_normal(units, OBJECTIVES[9], NODIR, 0, 100)
end
end
end
do local units = _1ST_PARACHUTE_461
-- no special orders, stay with HQ
end
Only if the three airborne infantry companies are "ready" and have begun moving forward, only then (way point) move the HQ and commander to hex 52,23. Wait there until OBJECTIVES[9] and/or OBJECTIVES[10] are secured, at which time move to one or the other. If both objectives are VNA owned, the HQ and Commander will finish deploying at OBJECTIVES[10] (it being the first condition in the if-then-else).
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "3a" > pass
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "6a" > pass
...
At the beginning of Turn 6
elements of the _1ST_PARACHUTE_BTN_55_A_169 have started their initial attack_way_point() on the nearest objective, OBJECTIVES[9] (hex 49,28).
At the beginning of Turn 9
some platoons are slow to move, because their companies are late to being "ready" (undisrupted).
At Turn 12
every unit is in motion, including the HQ and Battalion Commander.
And OBJECTIVES[9] has fallen. That was quick!
At Turn 15
equally quick, OBJECTIVES[10] too has fallen.
Skipping the Turn 18 display, at Turn 21
we see where the OBJECTIVES[9] garrison unit is not very "weak", at 4 SPs. This reflects the ease (low casualties) of taking that objective.
Note where, as coded, both the Battalion HQ and its Commander have settled at OBJECTIVES[10].
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
Where to go next? What to do?
Should the 1st Parachute Battalion perhaps head northward to assist the 5th and 6th Parachute Battalions in their operations?
We might consider having the 1st Parachute Battalion attempt to capture OBJECTIVES[22] & OBJECTIVES[23] at the east edge of the screenshot. But
- Those objectives are far away. There are only ~20 turns remaining in the scenario. Apart from movement time, what else -- e.g., encounters with the enemy -- might delay the 1st Btn along the way?
- The overland pathways to those objectives (same goes for heading northward) are tortured at best, impossible at worst. How are those ground units supposed to cross Rivers where there are no Fords or shallow water hexes allowing them to pass?
The 1st Btn might lower its ambitions, might wander about generally eastward hoping to find, and destroy, BX Army supply depots and factories it encounters by chance.
Or the 1st Btn might pause, awaiting maybe for some VNA Marines riverine transports to, after delivering the Marines to their intended targets, be freed to transport the paratroopers over water, down the Song Dong Tranh, dropping off the airborne troops near OBJECTIVES[22] & OBJECTIVES[23], if they can survive the inevitable enemy direct fire from those two Bunkers in the passage. At least, the riverine transports might park themselves at the River hexes (magenta circles) to ferry the airborne across the waterways at those points.
At this juncture, we will hit the pause button, leave the '--[[ ... -]]' temporarily unfinished. See what develops with the other VNA units. Then circle back to finish the 1st Parachute Battalion scripting later.
Who says scenario battle plan development needs to be straightforward and linear?
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
A reminder (quoting the OP in this forum thread):
Why this scenario?
- It is big, rich with possibilities. The map is huge, the forces considerable.
- It is varied. Ground combat, of course, but also paratroop drops, riverine assaults, airstrikes, etc. [emphasis added]
- The Side A VNA (Vietnamese National Army) and Side B BX Army -- both sides are scripted. [emphasis added]
- ...
The easy way out would be to designate this scenario as Side A Only, meaning to say: the defending Side B alone is scripted, intended for Side A human play only. But what's the fun in giving up? Scripting Side A also: we accept the challenge!
Diving in even deeper...
In the following screenshot
in the Find Organization Dialog, we have selected, thereby highlighted on the game map, the 11th Naval Assault Group. Their mission is to sail down the Song Long Tau, take the right fork and proceed along the Nga Ba Dong Tranh, disembark northwest of Ap Do Hop (at the green circles), circle around and attack from the south the Military Post there.
Seems simple enough. Is it?
Leading the 11th NAG flotilla are three LCVPs each loaded with a platoon of VNA Marines. Their orders code (much revised from the original code from two years ago):
Code: Select all
-- _11TH_NAVAL_ASSAULT_GROUP_390 -- 11th Naval Assault Group
-- _1ST_PLT_391, _2ND_PLT_392, _3RD_PLT_393
do local units = counters_active(join({_1ST_PLT_391, _2ND_PLT_392, _3RD_PLT_393}))
local transports = counters_active(join({_LCVP_396,_LCVP_397,_LCVP_398}))
local flotilla = join({units, transports})
_11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED = _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED or are_ground(units) -- signals release transports for possible use by _2ND_1ST_PARACHUTE_COY_55_A_175
if not _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED then -- water transport to landing point
if not at(units, "63,36") then
if chance(50) then
move_way_point(flotilla, {"42,23", "49,35", "58,37", "63,36"}, NODIR, 0, 100)
else
move_fire_way_point(flotilla, {"42,23", "49,35", "58,37", "63,36"}, NODIR, 2, 100)
end
else
disembark(flotilla, "63,36", UPRIGHTDIR, 100)
end
else -- only if disembarked, on land
attack_way_point(units, {"66,35", "68,37", OBJECTIVES[14]}, DOWNRIGHTDIR, 0, 100, ATTACK_STRONG)
if within(units, OBJECTIVES[14], NODIR, 1) then
if not at(transports, "64,37") then
move_norush(transports, "64,37")
else
fire_direct_nearest_to_hex (transports, OBJECTIVES[14])
end
end
end
end
We begin by defining three local variables (local to the current do-end block): units (the usual combat units); transports (carrying the combat units); flotilla (a combination (join()) of the first two groupings).
Note the use of counters_active(), thereby weeding out any units or transports possibly earlier destroyed.
Each time around, each turn, we check to see if the surviving combat units have disembarked onto land (ground):
Code: Select all
_11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED = _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED or are_ground(units) -- signals release transports for possible use by _2ND_1ST_PARACHUTE_COY_55_A_175
To review:
The first time around, _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED is undefined, has a value nil. The left side of the 'or' being nil, the right side of the 'or' is evaluated. The first time around, the units are still on-board the transports out in the water, 'are_ground(units)' is therefore false. The whole '... or ...' expression evaluates to false, so _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED is assigned the value 'false'.
On the second turn, and for a time, the '... or ...' evaluates to false, and _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED continues to have the assigned value 'false'.
Only once the units have disembarked is the 'are_ground(units)' true. The '... or ...' is 'false or true', that is, 'true'. _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED is assigned the value 'true'. It remains that value (the '... or ...' is now 'true or true', that is, 'true') for the remainder of the scenario.
Make sure you understand the last three paragraphs. Look to previous posts in this AAR thread for other examples of this technique. (One-time only global variable non-false value assignment.)
If the combat units are still embarked
Code: Select all
if not _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED then -- water transport to landing point
if not at(units, "63,36") then
if chance(50) then
move_way_point(flotilla, {"42,23", "49,35", "58,37", "63,36"}, NODIR, 0, 100)
else
move_fire_way_point(flotilla, {"42,23", "49,35", "58,37", "63,36"}, NODIR, 2, 100)
end
else
disembark(flotilla, "63,36", UPRIGHTDIR, 100)
end
if the combat units are not at the disembarkation hex 63,36, then move them there; else if there, have them disembark.
Ignore for now the 50-50 chance of move_way_point() vs. move_fire_way_point().
Why not this?
Code: Select all
move(flotilla, "63,36", NODIR, 0, 100)
Having the flotilla move directly to the disembarkation point (the right green circle, hex 63,36) in one simple, straightforward move() command? Unfortunately, the EAI path finding might get confused, and detour the flotilla off to a side waterway. (This was observed in auto-testing.) No, in order to ensure that the flotilla does not stray off course, we need to use move_way_point(). (To see the indicated {"42,23", "49,35", "58,37", "63,36"} way points, view the first screenshot in the post following this.)
TIP: If ordinary move (and attack ...) commands don't work quite right, way point move (and attack ...) might do the trick.
Else if the combat units have disembarked
Code: Select all
else -- only if disembarked, on land
attack_way_point(units, {"66,35", "68,37", OBJECTIVES[14]}, DOWNRIGHTDIR, 0, 100, ATTACK_STRONG)
if within(units, OBJECTIVES[14], NODIR, 1) then
if not at(transports, "64,37") then
move_norush(transports, "64,37")
else
fire_direct_nearest_to_hex (transports, OBJECTIVES[14])
end
end
end
have them way point strong attack OBJECTIVES[14]. The series of way points has them move under cover of swamp to the ford east of Ap Do Hop, where they cross the Minor River, then circle back around to attack the Military Post from the south. (See the green arrows in the screen shot above.)
If the combat units are within one hex of OBJECTIVES[14], if the transports are not at hex 64,37, move them there. Being out in the water, the LCVPs are easy targets. We only want to move them closer if the combat units are also close(r), thereby maybe drawing enemy opfire away from the more vulnerable transports.
Why move the transports closer, why not keep them at a distance? Here is the LCVP Range chart:
At three or four hexes away, the LCVP weaponry is too weak. The LCVP firepower is vital to taking the Military Post. So we move them closer, at two or three hexes away, despite the increased risk.
After having moved closer, to hex 64,37, from there the LCVPs direct fire at the enemy nearest to and including OBJECTIVES[14].
What about the 11th NAG support units?
Code: Select all
-- _HQ_378 -- VNMC Battalion HQ (foot)
-- _MMG_48_394
-- _81MM_MORTARS_395 -- Marine M1 81mm Mortars
do local units = counters_active(join({_HQ_378, _MMG_48_394, _81MM_MORTARS_395}))
local transports = counters_active(join({_LCA_399,_LCA_400,_LCP_412}))
local flotilla = join({units, transports})
_11TH_NAVAL_ASSAULT_GROUP_390_SUPPORT_DISEMBARKED = _11TH_NAVAL_ASSAULT_GROUP_390_SUPPORT_DISEMBARKED or are_ground(units) -- signals release transports for possible use by _2ND_1ST_PARACHUTE_COY_55_A_175
if not _11TH_NAVAL_ASSAULT_GROUP_390_SUPPORT_DISEMBARKED then -- water transport to landing point
if not at(units, "62,35") then
if chance(50) then
move_way_point(flotilla, {"42,23", "49,35", "58,37", "62,35"}, NODIR, 0, 100)
else
move_fire_way_point(flotilla, {"42,23", "49,35", "58,37", "62,35"}, NODIR, 2, 100)
end
else
disembark(flotilla, "62,35", UPRIGHTDIR, 100)
end
else -- only if disembarked, on land
attack_way_point(_MMG_48_394, {"66,35", "68,37", OBJECTIVES[14]}, DOWNRIGHTDIR, 0, 100, ATTACK_STRONG)
if not at(_81MM_MORTARS_395, "64,35") then
move_norush(join({_HQ_378, _81MM_MORTARS_395}), "64,35")
else
fire_indirect_nearest_to_hex(_81MM_MORTARS_395, OBJECTIVES[14], 100, true)
defend_weak(_HQ_378)
end
if within(_MMG_48_394, OBJECTIVES[14], NODIR, 1) then
if not at(transports, "64,36") then
move_norush(transports, "64,36")
else
fire_direct_nearest_to_hex (transports, OBJECTIVES[14])
end
end
end
end
The above code is similar to the combat units', but with these differences:
- Only the machine gun platoon, the _MMG_48_394, moves like the combat units around to the ford, crosses the Minor River, then attacks from the south.
- The mortars, the _81MM_MORTARS_395, together with the Battalion HQ, move forward one hex from their landing point, again because closer means more effective firepower.
And as before, the support units' LCA & LCP riverine transports also move forward for enhanced firepower effect, but only if the _MMG_48_394 is within one hex of OBJECTIVES[14] and is therefore likely to draw opfire away from the advancing transports.
The above code is very concise. Much details are behind the scenes, hidden away in the uber functions (in user.lua) and core CSEE functions (in init.lua). You don't need to understand the inner workings. You just need to know about the higher level CSEE functions, and when and how best to use them. "Just" he said?

Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
Note that for this auto-test evaluation, we have effectively halt()'ed and hold()'ed every other unit, especially the enemy BX Army units. (We did this by employing the usual technique of '--[[ ... --]]' the other units' blocks of battle plan orders code.) In this auto-test, only the _11TH_NAVAL_ASSAULT_GROUP_390 will move and fight.
We set the auto-test to stop at the beginning of Turn 4 by means of
rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "4a" > pass
At the start of Turn 4, Side A Phase, we see
The _11TH_NAVAL_ASSAULT_GROUP_390 has moved halfway down the Song Long Tau, and is temporarily halted at hex 48,33 (magenta circle). (The on-map hex coordinates are the waypoint stops.) Why the stop? It's because the flotilla has hit some underwater mines there. See the Strength Dialog. The mines have inflicted considerable damage: 2 SPs of LCVP destroyed, 4 Marine SPs lost. Ouch! (In several auto-tests, I have seen the luck go either way, sometimes good, no losses; sometimes bad, like here; at other times, even worse. You never know.)
I could cheat by setting the way points so as to have the LCVPs avoid the mines. But those mines are supposed to be hidden. Must. Not. Cheat.
Three turns later, at the beginning of Turn 7, we see
The flotilla has moved farther down the river. But what about that trailing LCVP (magenta circle)? At the beginning of Turn 8, we look
Oh! The passengers are disrupted; and because of that, the riverine has not moved. More bad luck. Bummer!
Meanwhile, the flotilla leads have arrived at the disembarkation hexes. The EAI path finding had them bypass the underwater mines in the vicinity (red circle). At last, some good fortune!
(You might be wondering: This being the Side A VNA turn, how is it that we can see the Side B BX Army bunkers, IEDs, etc.? But not the enemy units. This is by design. In auto-test mode, we see all hidden terrain, including fortifications, mines, IEDs, but no enemy units. It would be hard to script orders code and to evaluate the efficacy of battle plans were it not so. In ordinary game play, the Side A VNA player would see neither enemy units nor its hidden bunkers etc.)
At the beginning of Turn 10
that Marine Platoon (turquoise circle, to the left) has finally recovered from disruption. Three or more turns to undisrupt. Such bad luck!
Meanwhile, to the east, the support units have disembarked (green circle, to the right). The other combat units remain embarked waiting for the laggard _3RD_PLT_393.
At the beginning of Turn 11
the laggard LCVPs (and undisrupted _3RD_PLT_393) have indeed gotten moving again (left green circle).
To the east, the _81MM_MORTARS_395 and _HQ_378 have moved closer (turquoise circle) to OBJECTIVES[14] as directed.
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
have the laggard LCVPs caught up with the rest of the combat units flotilla (green circle). All combat units should now be prepared to disembark. Why didn't the lead combat units disembark earlier? It's because we didn't bother to code it that way. Maybe we should? Or maybe we should KISS it?
At Turn 15
yes, the combat units have all disembarked (green circle)!
Meanwhile, the _MMG_48_394 (turquoise circle) is far out ahead, too much so. Should we micromanage better coordination with the rest of the 11th NAG combat units?
Three turns later, at Turn 18
with _MMG_48_394 (turquoise circle) now within one hex of OBJECTIVES[14], the support units' LCAs & LCPs have moved forward in preparation to fire.
At Turn 20
the LCAs & LCPs (turquoise circle) have indeed begun firing. Meanwhile, all combat units are adjacent to OBJECTIVES[14] and are now poised to assault (green circle).
At the start of Turn 22
with now too the combat units within one hex of OBJECTIVES[14], as planned, the other three LCVPs move closer also.
At the start of Turn 24
a most pleasant surprise! Even though ordered to strong attack OBJECTIVES[14], the VNA units have assaulted and captured the Village/IP to their left (green circle). Unplanned, not explicitly programmed to do so, the EAI took the initiative. Smart!
I let the auto-test run for another five turns or so. At the beginning of Turn 30
the VNA have failed to capture the Military Post, OBJECTIVES[14]. They are unlikely to succeed in doing so. Look at the Unit List. See the two disrupted units. See the SP reductions. (The Marine Platoons all started at 6 SPs. Compare their losses from Turn 24 to Turn 30 in the last two screenshots.) The 11th NAG is by now a spent, worn out force. (I should probably code a triggered withdrawal.) OBJECTIVES[14] is a very hard nut to crack!
Despite the difficulty in taking OBJECTIVES[14], the _11TH_NAVAL_ASSAULT_GROUP_390 orders code has played out according to plan, all seems to be WAD.
BUT, there is a huge caveat to all of this. Can you guess what it is?
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)
Note that for this [earlier] auto-test evaluation, we have effectively halt()'ed and hold()'ed every other unit, especially the enemy BX Army units...
you win (another) kewpie doll!
We remove the '--[[ ... --]]' commenting out the BX Army battle_plan_b() orders code.
We also fix this issue:
Meanwhile, the _MMG_48_394 (turquoise circle) is far out ahead, too much so. Should we micromanage better coordination with the rest of the 11th NAG combat units?
Code: Select all
if _11TH_NAVAL_ASSAULT_GROUP_390_MARINES_DISEMBARKED then -- so the MMGs don't prematurely get out too far ahead
attack_way_point(_MMG_48_394, {"66,35", "68,37", OBJECTIVES[14]}, DOWNRIGHTDIR, 0, 100, ATTACK_STRONG)
end
Only have the MMGs (way point) move to attack if the Marines are disembarked and ready to advance to attack also.
We launch a new auto-test.
At the beginning of Turn 4, the flotilla lead has again hit those underwater mines:
Bad damage. 2 LCVP SPs lost, 4 SPs of Marines destroyed. Could be worse.
The lead LCVPs move farther down the river, then ...
BAM! More Marine casualties.
The Marine LCVPs return the fire
with unknown effect.
Over the next couple of turns, the rest of the Marine flotilla sails past the BX Army Military Post, trading fire and opfire back and forth, thankfully without any further VNA loss.
The 11th NAG ran the gauntlet and survived!
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com