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:

type Player = {
FirstName:string
LastName:string
Team: string
Position: string
Rating:int
}

type Team = {
Goalkeeper: Player
Defenders: Player[]
Midfielder: Player[]
Attacker: Player[]
}

type Position = Attacker | Midfielder | Defender | Goalkeeper
type Formation = FourFourTwo | FourThreeThree | ThreeFiveTwo | FourFiveOne

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:

let findBestTeam formation =
createTeam getPlayers formation |> printTeam
let pickTeam (stringFormation:string) =
match stringFormation with
| "442" -> findBestTeam FourFourTwo
| "433" -> findBestTeam FourThreeThree
| "352" -> findBestTeam ThreeFiveTwo
| "451" -> findBestTeam FourFiveOne
| _ -> failwith "Unknown formation, please try again!!"

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 the findBestTeam 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:

let getStartingPlayers players findBest numberToTake =
players
|> Array.sortByDescending findBest
|> Array.take numberToTake
|> Array.map createPlayerFromJson

let createTeam (players:JsonValue[]) (pickedFormation:Formation) =
let numberOfGoalkeepers,numberOfDefenders,numberOfMidfielders,numberOfAttackers = match pickedFormation with
| FourFourTwo -> 1,4,4,2
| FourThreeThree -> 1,4,3,3
| ThreeFiveTwo -> 1,3,5,2
| FourFiveOne -> 1,4,5,1

let goalkeeper = getStartingPlayers (getAllGoalkeepers players) findBestGoalkeeper numberOfGoalkeepers |> Array.head
let defenders = getStartingPlayers (getAllDefenders players) findBestDefenders numberOfDefenders
let midfielders = getStartingPlayers (getAllMidfielders players) findBestMidfielders numberOfMidfielders
let attackers = getStartingPlayers (getAllAttackers players) findBestAttackers numberOfAttackers
{
Goalkeeper = goalkeeper
Defenders = defenders
Midfielder = midfielders
Attacker = attackers
}

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

let removeIconPlayers (jsonPlayer:JsonValue) = jsonPlayer?club?name.AsString() <> "Icons"
let removeDuplicatePlayers (player:JsonValue) = player?firstName.AsString() + " " + player?lastName.AsString()

let getAllPlayersInPosition (position:Position) (players:JsonValue[]) =
players
|> Array.filter (findPositionFromString position)
|> Array.distinctBy removeDuplicatePlayers
|> Array.filter removeIconPlayers

let getAllGoalkeepers = getAllPlayersInPosition Goalkeeper
let getAllDefenders = getAllPlayersInPosition Defender
let getAllMidfielders = getAllPlayersInPosition Midfielder
let getAllAttackers = getAllPlayersInPosition Attacker

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:

let getPlayers () =
let getPage page =
async {
let! data = JsonValue.AsyncLoad (sprintf "https://www.easports.com/fifa/ultimate-team/api/fut/item?page=%i" page)
let items = [| for item in data?items -> item |]
return items
}

let value = JsonValue.Load ("https://www.easports.com/fifa/ultimate-team/api/fut/item")
let totalPages = value?totalPages.AsInteger()

[|1..totalPages|]
|> Array.map getPage
|> Async.Parallel
|> Async.RunSynchronously
|> Array.concat

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:

let findBestMidfielders player =
player?rating.AsInteger() +
player?acceleration.AsInteger() +
player?aggression.AsInteger() +
player?agility.AsInteger() +
player?balance.AsInteger() +
player?ballcontrol.AsInteger() +
player?skillMoves.AsInteger() +
player?crossing.AsInteger() +
player?interceptions.AsInteger() +
player?longpassing.AsInteger() +
player?longshots.AsInteger() +
player?positioning.AsInteger() +
player?shortpassing.AsInteger() +
player?standingtackle.AsInteger() +
player?stamina.AsInteger() +
player?strength.AsInteger() +
player?vision.AsInteger()

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:

let findBestMidfielders player =
player?sprintspeed.AsInteger()

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.

let findPositionFromString (position:Position) (player:JsonValue) =
let stringPosition = player?position.AsString()
let foundPosition = findPosition stringPosition
position = foundPosition

let createPlayerFromJson (jsonPlayer:JsonValue) : Player =
{
FirstName = jsonPlayer?firstName.AsString()
LastName = jsonPlayer?lastName.AsString()
Team = jsonPlayer?club?name.AsString()
Rating = jsonPlayer?rating.AsInteger()
Position = match jsonPlayer?position.AsString() with
| "GK" -> "Goalkeeper"
| "RWB" -> "Right Wing Back"
| "RB" -> "Right Back"
| "LWB" -> "Left Wing Back"
| "LB" -> "Left Back"
| "CB" -> "Centre Back"
| "RW" -> "Right Wing"
| "RM" -> "Right Midfield"
| "LW" -> "Left Wing"
| "LM" -> "Left Midfield"
| "CM" -> "Centre Midfield"
| "CDM" -> "Centre Defensive Midfielder"
| "CAM" -> "Centre Atacking Midfielder"
| "ST" -> "Striker"
| "LF" -> "Left Forward"
| "RF" -> "Right Forward"
| "CF" -> "Centre Forward"
| _ -> "Unknown position!"
}

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!