First off, I want to give credit to Martin Andersen and his awesome post from last year's F# Advent Calendar, without which, I wouldn't have found The EA SPORTS FUT Database which I use for this post.
The Idea
What I'm going to do in this blog post, is create a simple program, which, given a valid football formation (442, 451, 352 etc) will query the EA SPORTS FUT Database and return the best 11 players given a set of player characteristics. While this isn't particularly tricky, it does cover a lot of the basic F# types and idioms such as Record Types, Discriminated Unions, Pattern Matching and Partial Application.
The Domain
I have an F# file called Domain.fs
that has my Discriminated Union and Record Types which contains all the information about my players, team, possible formations and player positions:
Main Program
The main logic of this application lives in a file called FantasyFootball.fs
. What we will do is work through the code in logical steps until we have all the pieces needed to put together the program.
I suppose the first step when deciding a fantasy football team is choosing the formation you would like. I'm quite a traditionalist when it comes to football, so for this blog post I'm going to pick 442
.
The first 2 functions that we'll look at are:
Firstly, we call the pickTeam
function passing in a string such as "442" which then uses pattern matching to determine which named case to pass to the findBestTeam
function.
The reason the
pickTeam
function is under thefindBestTeam
function is that in F# functions have to be defined in code before they can be used.
Once we are in the findBestTeam
function, we just call the createTeam
function passing in the result of the getPlayers
function which gets all the players from the database and the formation picked by the user. The result of that is piped to the printTeam
function which is a function in Helper.fs
that just prints the team to the console.
The next step is to look at the createTeam
function:
The first line of the createTeam
function pattern matches over the pickedFormation
which is passed in and then returns a tuple containing the number of players that are needed in each position which is deconstructed using a let binding. Once we have the number of players needed for each position we can call getStartingPlayers
to get the players best suited for our team (based on our chosen characteristics which we'll see later) and then we just return the Team
record type, which is then printed to the console using a method in our Helper.fs
file.
The getStartingPlayers
function then takes in the players in the correct position, a function to sort the players by called findBest
and then the number of players to take: numberToTake
.
The next part of code that we'll look through is in charge of actually finding the best players for each position,
The reason that I'm not using FSharp.Data and the JSON Type Provider is becuase this is a .NET Core project and Type Providers don't currently work in the FSI
The EA SPORTS FUT Database contains retired and classic players which they call "Icon players". As nice as it would be to include Pelé or Maradona into our fantasy team, the Icon players don't play much football nowadays so they wouldn't score us many points!
The removeIconPlayers
function in the section above is very simple and is just passed to Array.filter
to remove Icon players because they are given the club name 'Icons' by FIFA and the removeDuplicatePlayers
function is passed to Array.distinctBy
to remove duplicate players.
The next function: getAllPlayersInPosition
is the function that we are going to use partial application on. To do this we make position:Position
the first argument so that when we call the function with 1 argument (instead of the 2 that it's declared with) we get back a function that requires 1 argument (players:JsonValue[])
but will use the implementation from getAllPlayersInPosition
. I know that can sound a bit much if you've never used partial application before, so I'd really recommend Scott Wlaschin's article to understand this fully.
The next function we'll take a look at is the getPlayers
function below that we use to call the EA SPORTS FUT Database API and retrieve the players:
Inside getPlayers
the first function we define is getPage
which makes an asynchronus call to The EA SPORTS FUT Database to retrieve 1 page of players which it then returns as an array (each page has 24 players). The next 2 lines work out how many pages in total are in the database and then the last expression actually calls the getPage
function the required amount of times in parallel and then concatenates the arrays and returns one array of all players.
As pointed out to me, I could do with throttling and/or batching these requests to the API. There is a library called FSharp.Control.AsyncSeq that I intend to add.
Player Characteristics
The other functions that we've touched on but not looked at yet are the functions that sum up the total of our players characteristics and determine the players that are 'picked' for our team (findBestGoalkeeper
, findBestDefenders
,findBestMidfielders
,findBestAttackers
) which are found in Helper.fs
, one of which looks like this:
All these functions do is sum up the values of the player characteristics that we're interested in and return the total to be sorted by. You can change these characteristics by picking the values for each player that can be found in the database - but these are the characteristics that I chose that I value for each position. The rest of the values that I chose can be found in the Helper.fs
file: https://github.com/iwasdavid/fsharp-fantasy-football/blob/master/Helper.fs
For example if all you were worried about was having quick players, you could change all the of functions to be like this:
The functions below are just a few little functions to help with finding player positions and then the createPlayerFromJson
function takes a JsonValue
type and creates a record type of our Player
type so that it can be added to our Team
type which is what is eventually printed to the console.
The Results
Running the following program gives us the following results which are very interesting, as they were not the players that I would have expected to be returned!
GOALKEEPERS
Name: Manuel Neuer. Team: FC Bayern München. Position: Goalkeeper. Rating: 91
DEFENDERS
Name: Marcelo Vieira da Silva. Team: Real Madrid. Position: Left Back. Rating: 89
Name: Juan Francisco Torres Belén. Team: Atlético Madrid. Position: Right Back. Rating: 87
Name: Sergio Ramos García. Team: Real Madrid. Position: Centre Back. Rating: 92
Name: Alex Nicolao Telles. Team: FC Porto. Position: Left Back. Rating: 86
MIDFIELDERS
Name: Luka Modric. Team: Real Madrid. Position: Centre Midfield. Rating: 92
Name: Kevin De Bruyne. Team: Manchester City. Position: Centre Atacking Midfielder. Rating: 92
Name: Radja Nainggolan. Team: Inter. Position: Centre Midfield. Rating: 86
Name: N'Golo Kanté. Team: Chelsea. Position: Centre Defensive Midfielder. Rating: 89
ATTACKERS
Name: Lionel Messi. Team: FC Barcelona. Position: Centre Forward. Rating: 95
Name: Neymar da Silva Santos Jr.. Team: Paris Saint-Germain. Position: Left Forward. Rating: 93
And that is it. There's a lot of things I could do to improve and add to this script, but I hope it's been helpful for at least 1 person :)
Github
Source code is available on Github: https://github.com/iwasdavid/fsharp-fantasy-football
Thanks
I would like to thank Stuart Lang and Isaac Abraham for taking a look at my code and making some suggestions which improved it.
Merry Christmas and a Happy New Year!