Halo 5 — Building a Streamlit App to Get More Info on Your Competitors — Part 2

Johnny D
7 min readJul 29, 2021

--

In Part 1, we built multiple functions utilizing API calls to build a single dataframe containing skill metrics for all players in any Halo 5: Guardians arena match. We then plotted those stats using Plotly, with results looking something like this:

If you missed Part 1, click here for an in-depth guide on how we generated this graph by combining multiple calls to the Halo Public API with functions in Python.

In Part 2, we’ll focus on building a Streamlit app, which is a great web app prototyping tool that allows you to get up and running pretty quickly.

What is Streamlit?

Streamlit allows anyone to turn data into shareable web apps. Click here to check out their gallery, which shows a variety of fun and useful web apps built using their infrastructure.

Streamlit has a pretty straightforwad installation process.

1. Open up your terminal of choice (I’ll be using Git Bash) and enter the following code:

pip install streamlit

2. Feel free to test the installation with the following code:

streamlit hello

That code should show something like this in the terminal:

And open up a browser page with showing this:

3. Create a new .py file with the name of your choice and place it at the root file of your repository.

Feel free to title your app however you want, but for this example we’ll be using “halo.py.”

If you haven’t created a .py file before, all you need to do is open a blank notepad file and change the extension from .txt to .py.

If you followed along in Pt. 1 of this blog post, you can paste the code from there into the .py file. If you run into any issue, or just want to start from this step, copy and paste the full code for the .py file found here.

4. Boot up the halo.py file using the terminal of your choice.

Input this command into the terminal:

streamlit run halo.py

The terminal should confirm that it’s running, and a new browser window or tab will open up with a blank screen.

5. Streamlit coding

Now we’ll start the Streamlit code. Full documentation for working with Streamlit can be found here, and a handy cheat sheet can be found here.

First we’ll add a title and markdown for the Streamlit app. This and all subsequent code below should be added at the bottom of halo.py (below all of the API calls and functions):

######################################## Title and markdownst.title('Welcome to Halo 5 Last Match!')
st.markdown('Halo 5 Last Match allows you to view performance stats for players in a recent Halo 5 Arena match. Start by entering your gamertag below. The second box will allow you to specify how many matches back you would like to go in your match history.')

Once this has been pasted, save the halo.py file and go back to the browser window with the Streamlit app and press ‘R’ on the keyboard or hit Rerun in the top right hand corner. You should see something like this:

6. Side bar and buttons

Next we’ll set up the side bar. We’ll be using the st.sidebar.title() and st.sidebar.button() methods. All you need to do to instantiate the buttons is set them equal to a variable of your choice.

######################################## Sidebar and buttonsst.sidebar.title("Additional Stats")
xp_stats = st.sidebar.button('XP / Time Played')
win_loss_stats = st.sidebar.button('Total Wins / Losses')
kd_stats = st.sidebar.button('K/D')
accuracy_stats = st.sidebar.button('Accuracy')
grenades = st.sidebar.button('Grenades')
weapon_damage = st.sidebar.button('Weapon Damage')
power_weapon_kills = st.sidebar.button('Power Weapon Kills')
power_weapon_grabs = st.sidebar.button('Power Weapon Grabs')
melee = st.sidebar.button('Melee')
assassinations = st.sidebar.button('Assassinations')
ground_pound = st.sidebar.button('Ground Pound')
shoulder_bash = st.sidebar.button('Shoulder Bash')

Once you save the halo.py file and rerun the Streamlit app, it should look like this:

7. User input

Next we’ll add areas for user input and include some markdown for explanation.

For input, we’ll set variables equal to st.text_input(). When we set these to a variable, Streamlit knows to save the user input as whatever variable we assign to it. Also, st.text_input() allows us to set the default value for the input box, which we’ll set to my gamertag, ‘Drymander.’

We’ll also set the default back_count variable to the number 0 (not the string ‘0’), which tells the functions how far to go back in the player’s match history.

########################################### Inputgamertag = st.text_input("Type in your Gamertag", 'Drymander')
back_count = st.text_input("How many matches would you like to go back? Enter 0 for most recent match, 1 to go 1 match back, 2 to go 2 matches back, etc.", 0)
st.markdown("Each stat is calculated by Game Base Variant (e.g. Slayer, Capture the Flag, Oddball, Strongholds, etc). Below, you'll see Total Hours Played, Win Rate, and average K/D.")st.markdown("You can also find additional stats on the sidebar.")

Once we rerun, it should look like this:

8. Create match dataframe

Next, we’ll create the dataframe for whatever gamertag is entered using the recent_match_stats function. Streamlit will use the inputs from the user to pull with the gamertag and back_count variables we established with the first st.text_input() method.

df = recent_match_stats(gamertag, back_count=back_count)

9. General match information

Now we’ll use the dataframe we just created to display some general information about the match. We’ll determine the Game Base Variant (Slayer, Capture the Flag, Oddball, etc), Playlist (Super Fiesta Party for most of my matches), the Map name, and whether the player won, lost, or tied the match.

First we’ll assign the variables:

########################## Game type, playlist, map, win/lose/tiegamebasevariantid = df_outcome['GameBaseVariantId'].iloc[0]
playlistid = df_outcome['PlaylistId'].iloc[0]
map_name = df_outcome['MapVariantId'].iloc[0]
if df_outcome['Winner'].iloc[0] == 'Victory':
outcome = 'VICTORY'
elif df_outcome['Winner'].iloc[0] == 'Defeat':
outcome = 'DEFEAT'
else:
outcome = 'TIE'

Then, we’ll use st.header() and st.subheader() to distinguish the text from our previous st.markdown():

st.header(f”Showing stats for {gamertag}’s match on {df_outcome[‘Date’].iloc[0]}”)
st.subheader(f’Outcome — {outcome}’)
st.subheader(f’Game Mode — {gamebasevariantid}’)
st.subheader(f’Playlist — {playlistid}’)
st.subheader(f’Map — {map_name}’)

After rerunning, you should see additional information displayed like this:

10. Create show_stat function

To streamline the graphing process, we’ll create a function that will accomplish two things:

  • Create a header for the stat we want to display
  • Create a graph showing the stat for everyone in the match
def show_stat(column_name, df=df, gamebasevariantid=gamebasevariantid):
st.header(f'{column_name} - {gamebasevariantid}')
stat_plot = compare_stat(df, column_name)
st.plotly_chart(stat_plot)

11. Set up plots for sidebar buttons

We need to tell Streamlit which stats to show using if / elif / else statements. The if and elif statements represent the button presses on the sidebar, and the else statement represents what we want Streamlit to show by default.

If you recall from our previously coded Grenades button as an example, we set the variable ‘grenades’ to the Grenades button

grenades = st.sidebar.button(‘Grenades’)

Now, by using ‘if grenades,’ Streamlit knows to execute all code within the if-statement if the Grenades button is pressed by the user.

if grenades:

show_stat('GrenadeKillsPerGame')
show_stat('GrenadeDamagePerGame')
show_stat('TotalGrenadeKills')
show_stat('TotalGrenadeDamage')

The code below ‘if grenades:’ will show graphs for Grenade Kills Per Game, Grenade Damage Per Game, Total Grenade Kills, and Total Grenade Damage.

The same logic will apply to the rest of our buttons:

elif weapon_damage:
show_stat('WeaponDamagePerGame')
show_stat('TotalWeaponDamage')
show_stat('PowerWeaponDamagePerGame')
show_stat('TotalPowerWeaponDamage')
elif power_weapon_kills:
show_stat('PowerWeaponKillsPerGame')
show_stat('TotalPowerWeaponKills')
elif power_weapon_grabs:
show_stat('PowerWeaponGrabsPerGame')
show_stat('TotalPowerWeaponGrabs')
show_stat('PowerWeaponPossessionTimePerGame')
show_stat('TotalPowerWeaponPossessionTime')

elif kd_stats:
show_stat('K/D')
show_stat('KillsPerGame')
show_stat('TotalKills')
show_stat('DeathsPerGame')
show_stat('TotalDeaths')
show_stat('AssistsPerGame')
show_stat('TotalAssists')

elif accuracy_stats:
show_stat('Accuracy')
show_stat('HeadshotsPerGame')
show_stat('TotalHeadshots')
show_stat('ShotsFiredPerGame')
show_stat('ShotsLandedPerGame')
show_stat('TotalShotsLanded')
show_stat('TotalShotsFired')

elif melee:
show_stat('MeleeKillsPerGame')
show_stat('TotalMeleeKills')
show_stat('MeleeDamagePerGame')
show_stat('TotalMeleeDamage')

elif assassinations:
show_stat('AssassinationsPerGame')
show_stat('TotalAssassinations')
elif ground_pound:
show_stat('GroundPoundKillsPerGame')
show_stat('TotalGroundPoundKills')
show_stat('GroundPoundDamagePerGame')
show_stat('TotalGroundPoundDamage')
elif shoulder_bash:
show_stat('ShoulderBashKillsPerGame')
show_stat('TotalShoulderBashKills')
show_stat('ShoulderBashDamagePerGame')
show_stat('TotalShoulderBashDamage')

elif xp_stats:
show_stat('SpartanRank')
show_stat('PrevTotalXP')
show_stat('TotalTimePlayed')
elif win_loss_stats:
show_stat('WinRate')
show_stat('TotalGamesWon')
show_stat('TotalGamesLost')
show_stat('TotalGamesTied')
show_stat('TotalGamesCompleted')

12. Set default graphs

Finally, we’ll set the default graphs using an else-statement.

else:
show_stat('WinRate')
show_stat('TotalTimePlayed')
show_stat('K/D')

Once rerun, you should see three graphs, the first of which should show win rate for the players in the match:

And that’s it! Hopefully this guide was helpful in creating your own Streamlit app.

In case the code doesn’t work for any reason, click here for the full code of the Streamlit app.

I will be following up on this post within the next week once I have been accepted in the Streamlit invite queue, and the app will be live via my Github Repository.

--

--

No responses yet