Introduction
Beyond CommandEvents
(see <<Chapter 5 - Handling Command Events>>), another potential indicator that I can use to profile the players' tactical play (i.e. their micro-game) is their selection behaviour. For instance, in StarCraft II, players can select units in two ways; they can use their mouse in conjunction with some hotkeys like ctrl
or use control groups to assemble sets of units for custom and fast selection.
This chapter explores how I can extract information about this behaviour with sc2reader's SelectionEvents
and ControlGroupEvents
. Using these events, I define a set of functions to quantify this characteristic of the players' play.
Exported Functions:
Extracting the Control Group Compositions
Before examining the structure of the ControlGroupEvents
and the SelectionEvents
, I must explain how to load Replays
to include enough information to quantify the abovementioned indicators. The issue is that loading Replays
with sc2reader
as I have in previous modules does not record the composition of the players' control groups over time. This can be remediated to an extent using the sc2reader
plug-in SelectionTracker
, which is similar to the APMTracker
I use in <<Chapter 5 -Handling Command Events>>. However, in contrast with APMTracker
, SelectionTracker
is meant as an input for user-defined plug-ins that specify its behaviour.
selection_parser
, which exports the class CtrlGroupTracker
that can be used as an sc2reader
plug-in. This plug-in adds the ctrl_grp_trk
attribute to the Replay
objects upon load. This attribute stores the composition of the players’ control groups each time the player triggers a ControlGroupEvent
during the match. See the notebook or the module’s source code for implementation details. Selection Behaviour Events
As stated above, in this chapter, I take a look into two kinds of GameEvents
that I can use to get a notion of the players' selection behaviours, i.e. ControlGroupEvents
and SelectonEvents
.
Handling ControlGroupEvents
I will start by loading the sample Replays
I use in this notebook to analyse this type of event.
CtrlGroupTracker
plug-in I define above.# Register CtrlGroupTracker plug-in
sc2reader.engine.register_plugin(CtrlGroupTracker())
# Load sample replays
RPS_PATH = Path("./test_replays")
game_path = str(RPS_PATH/"Jagannatha LE.SC2Replay")
single_replay = sc2reader.load_replay(game_path)
ctrl_grp_test = sc2reader.load_replay(str(RPS_PATH/'ctrl_grp_t.SC2Replay'))
ctrl_grp_test_2 = sc2reader.load_replay(str(RPS_PATH/'ctrl_grp_t_2.SC2Replay'))
ta_test = sc2reader.load_replay(str(RPS_PATH/'Terran_abilities.SC2Replay'))
pa_test = sc2reader.load_replay(str(RPS_PATH/'ProtossAbilities.SC2Replay'))
tms_test = sc2reader.load_replay(str(RPS_PATH/'TMovesSelect.SC2Replay'))
pms_test = sc2reader.load_replay(str(RPS_PATH/'p_move_test.SC2Replay'))
With Replays
loaded, one can see that they now have the ctrl_grp_trk
attribute. Calling this attribute, I can choose to look at each of the players' control group compositions using their pid
as an index. Afterwards, I can look at each composition independently using the value of the second
attribute of the ControlGroupEvent
that generated it as an index.
pid
). Meanwhile, the number 221, which I use to extract a sample composition, refers to the time index of the event that triggered its recording.TEST_MATCH = single_replay
TEST_PID = 2
sample_ctrlg_comp = TEST_MATCH.ctrl_grp_trk[TEST_PID][221]
print('LOAD RESULTS:')
print(f'Ctrl group compositions: {sample_ctrlg_comp}')
print('---------------------------')
print('Sample Ctrl group composition:')
pprint(TEST_MATCH.ctrl_grp_trk[TEST_PID][221])
print('----------------------------')
sample_event = [e for e in TEST_MATCH.events
if isinstance(e, sc2reader.events.game.ControlGroupEvent)
and e.pid == (TEST_PID - 1)
and e.second == 221]
print(f'Sample {sample_event[0].name}')
print(f'Generated by: {sample_event[0].player}')
print(f'Recorded time: {sample_event[0].second}second')
Beyond the control group compositions, I can use simple list comprehensions to extract the ControlGroupEvents
from the Replay's
event list.
With this technique, I can also segregate these events into three sub-types:
SetControlGroupEvent
: created each time a player assigns a control group.GetControlGroupEvent
: registered each time a player summons, i.e. uses a hot-key to select a control group.AddToControlGroupEvent
: registered when a player uses a hot-key to assign selected units to an existing control group.
This classification is helpful to distinguish between events that select units and those that do not.
#List all ControlGroupEvents
ctrl_grp_e = [e for e in TEST_MATCH.events
if isinstance(e, sc2reader.events.game.ControlGroupEvent)
and e.pid == (TEST_PID - 1)]
#List ControlGroupEvents sub-types
set_ctrl_grp = [e for e in ctrl_grp_e
if isinstance(e, sc2reader.events.game.SetControlGroupEvent)]
get_ctrl_grp = [e for e in ctrl_grp_e
if isinstance(e, sc2reader.events.game.GetControlGroupEvent)]
add_ctrl_grp = [e for e in ctrl_grp_e
if isinstance(e,
sc2reader.events.game.AddToControlGroupEvent)]
print(f'ControlGroupEvents: {len(ctrl_grp_e)}')
print(f'SetControlGroupEvents: {len(set_ctrl_grp)}')
print(f'GetControlGroupEvents: {len(get_ctrl_grp)}')
print(f'AddControlGroupEvents: {len(add_ctrl_grp)}')
Meanwhile, with a simple loop I can go through a control group composition group-lists to count how many of them actually have units assign to them as shown in the code bellow.
count = 0
for l in sample_ctrlg_comp.values():
if l:
count += 1
count
In this module, I define an internal function called count_active_groups
that carries out this task.
# count_active_groups sample run
count_active_groups(sample_ctrlg_comp)
Handling SelectionEvents
As explained in sc2reader's
documentation,
:"
SelectionEvents
are generated when ever the active selection of the player is updated. Unlike other game events, these events can also be generated by non-player actions like unit deaths or transformations. [...] selection events targetting control group buffers are also generated when control group selections are modified by non-player actions. When a player action updates a control group aControlGroupEvent
is generated." (Kim, 2015, p. 48) Hence,SelectionEvents
events can refer to game situations that also triggerControlGroupEvents
, but they are essentially different.
Below, I extract the sample match's SelectionEvents
. I also use their control_group
attribute to see what control groups they are linked to. Note that there is a control group number ten according to the list of control groups linked to the selections. This group refers to the player's current active selection, whatever it was at the moment. In other words, if the player selects units just by clicking, it would trigger a SelectionEvent
but not a ControlGroupEvent
. Said SelectionEvent
would be linked to the current selection instead of a control group.
select_e = [e for e in TEST_MATCH.events
if isinstance(e, sc2reader.events.game.SelectionEvent)
and e.pid == (TEST_PID - 1)]
print(f'SelectionEvents in sample replay: {len(select_e)}')
print(f'Groups referenced by the Selection Events:')
print(set([s.control_group for s in select_e]))
Similarly, the following code exposes the intersection between the two sets. It shows how many SelectionEvents
and ControlGroupEvents
sc2reader
registered independently and how many were triggered by the same game event.
Recognizing this link is crucial if I want to count the two types of events together. In this case, I need to make sure that I only count each game event once. The following code shows how the sets of events intersect, how simply adding the sets miss-counts the elements, and how I can use set-union to count them correctly.
sel_e_times = set([s.second for s in select_e])
ctrl_grp_times = set([c.second for c in ctrl_grp_e])
print(f'select_e_times_indexes: {len(sel_e_times)}')
print(f'ctrl_grp_times_indexes: {len(ctrl_grp_times)}')
intersect = sel_e_times.intersection(ctrl_grp_times)
not_intersect_set = sel_e_times.symmetric_difference(ctrl_grp_times)
print(f'Number of elements that intersect: {len(intersect)}')
print(f'Number of elements that do not intersect: {len(not_intersect_set)}')
print(f'Sum of set counts: {len(sel_e_times) + len(ctrl_grp_times)}')
print(f'Union of select and ctrl_group: {len(sel_e_times | ctrl_grp_times)}')
Functions
Helper functions
Internally the module defines the helper functions:
build_ctrlg_df
that builds a DataFrame with theReplay's
list ofControlGroupEvents
.
The table results from a sample run of build_ctrlg_df
called on the notebooks TEST_MATCH
.
real_time | second | pid | name | control_group | |
---|---|---|---|---|---|
0 | 4.28571 | 6 | 2 | SetControlGroupEvent | 1 |
1 | 4.28571 | 6 | 2 | SetControlGroupEvent | 2 |
2 | 4.28571 | 6 | 2 | SetControlGroupEvent | 3 |
3 | 5 | 7 | 2 | SetControlGroupEvent | 4 |
4 | 5 | 7 | 2 | SetControlGroupEvent | 5 |
count_max_active_groups
sample run:
count_max_active_groups(TEST_MATCH, TEST_PID)
calc_ctrlg_ratio(TEST_MATCH, TEST_PID)
The next two functions use the union between GetControlGroupEvent
and SelectEvent
sets to quantify if the player preferes to select units based on mouse clicks or using control groups.
calc_get_ctrl_grp_ratio
sample run:
calc_get_ctrl_grp_ratio(TEST_MATCH, TEST_PID)
calc_select_ratio
sample run:
calc_select_ratio(TEST_MATCH, TEST_PID)
References
- Kim, G. (2015) 'sc2reader Documentation'. Available at: https://sc2reader.readthedocs.io/_/downloads/en/latest/pdf/.