Contestants in the example programming contest create their solutions, called players, as web services that make one move at a time. Each move is requested by an external entity sending the service a request with a JSON body specifying the state of the game, and the player responds with the JSON representation of a single move.

The game

Since the point of this tutorial is learning how to create and deploy a serverless solution rather than game-playing strategy, the game to be played is as simple as possible: guess a number. The player is given a minimum and maximum integer and guesses an integer in that range. The player is provided with a history of its prior guesses as well, so it does not need to keep track of them itself.

The same concepts used here can be applied to more sophisticated single or many-player games such as Battleship, hangman, or checkers.

Input and output

The input to the player is an HTTP request sent to a URL chosen by the player, with a JSON body representing the state of the game prior to the move. The JSON object has properties minimum (an integer greater than or equal to zero), maximum (an integer greater than or equal to minimum), and history (an array of guesses, in the order they were made). Each guess is an object with properties guess (an integer between minimum and maximum, inclusive), and result (the string "higher" or "lower"). Example:

{
  "minimum": 1,
  "maximum": 10,
  "history": [
    {"guess": 5, "result": "higher"},
    {"guess": 8, "result": "lower"}
  ]
}

The output represents the game player's move as an HTTP response with a JSON body containing a single integer, the player's guess. Example:

6

A judging system will invoke the player repeatedly, each time with a history that incorporates previous guesses, until the correct guess is returned or some maximum number of guesses or amount of time is exceeded. There may be several "judges" each with a different range to guess from operating.

What you will build

In this tutorial, you're going to build a player implemented as a Cloud Function that responds to HTTP requests. It will play the contest game by making a single move in response to a request describing the prior state of the game. Your app will:

What you'll learn

What you'll need

All software deployed on GCP will be part of a GCP Project. If this is the first part of the game playing system you're working on, create a new project for it at console.cloud.google.com.

You will do some work in the Cloud Shell command line environment. Start by opening that environment and fetching the sample code to it.

Launch the Console and Cloud Shell

Open the Cloud Console at console.cloud.google.com and select your project. If this is the first time you've used the Cloud Shell, you can read the explanation and click the appropriate button to continue to the shell.

All commands in this codelab will use the console UI or be executed within a Cloud Shell. Open the Cloud Shell by clicking the Activate Cloud Shell icon found at the right side of the console page header. The lower half of the page will allow you to enter and run commands.

The commands could be run from your own PC, but you would have to install and configure needed development software first. The Cloud Shell already has all the software tools you need.

Fetch the source code

You can use the following command from the Cloud Shell command line to get the source code to examine. This is not a required step for this codelab, however.

git clone https://github.com/GoogleCloudPlatform/serverless-game-contest.git

You can explore the code in the built-in code editor by clicking the editor icon to launch it.

You will create, test, and invoke a cloud function that plays the guessing game.

Using the Cloud Console

Navigate to the console at console.cloud.google.com. Select or create a project to work in. Create the new function as follows:

  1. Using the menu in the top left corner of the console, select Cloud Functions from the Serverless section.
  2. If this is the first time you are using Cloud Functions in this project, you may see "Cloud Functions is getting ready..." or a message that the Cloud Functions API is not enabled. If you see an Enable API button, click it to proceed.
  3. The next message displays a Create function button. Click it to create a new Cloud Function.

Use the displayed Create function page to specify your new Cloud Function.

  1. Ensure the Environment is set to 1st gen.
  2. Fill in the Function name box with player.
  3. Leave the default region selected.
  4. We will use an HTTP trigger. A URL for the function is displayed, in the form https://region-project_id.cloudfunctions.net/function_name.
  5. Ensure the checkbox Allow unauthenticated invocations is checked, so anyone can invoke the new function.
  6. Select Save in the Trigger section and then Next at the bottom of the screen.
  7. Change the Runtime to Python 3.9. You'll change the Entry point later.
  8. You may receive a message that Cloud Build API is required. Click Enable API to enable it.
  1. This will take you to the Cloud Build API page in another tab. Click Enable.
  2. Return to the tab with Create function.

Use the inline editor to replace the code skeleton with one that plays the game, shown below. You can edit the function body in main.py and the list of required non-standard libraries in requirements.txt. Our example does not use any non-standard libraries, so the requirements.txt file will remain empty.

This function will be sent an HTTP POST request whose body is a JSON object with the smallest and largest possible guesses and a list of prior guesses. The function code will make a guess based on that input and return the guess in JSON format. The function code to enter is in the file player/badplayer.py in the repository, and is shown below.

import json

def make_guess(request):
    game = request.get_json()
    guess = game['minimum']
    return json.dumps(guess)

Key steps in this function code are:

  1. Import the standard json module for interpreting the request data and formatting the response. If this were a non-standard module we would have had to add it to requirements.txt
  2. The request handler function is named make_guess. Any valid Python name can be used, but it must be filled in the Entry point field.
  3. The body of the function retrieves the minimum property of the JSON request object, uses it as the guess, and returns that guess as a JSON object. (The JSON version of a number is simply that number.) This is a very bad game player because it ignores the list of previous moves that is sent to it and thus does not learn or improve on subsequent moves.

Fill in the name of the Entry point as make_guess and click the Deploy button. A spinner icon will appear next to the function name near the top of the page. After a few minutes, it should change to a green check mark. Hovering over the check mark will show the message Function is active.

If something goes wrong: the spinner icon will show a red exclamation point. Click the function name and look at the details of the Deployment failure messages in the General tab to figure out how to fix it and try again.

Test the function

We are now ready to test the function. Click the function name to open the Function details page, and then click the Testing tab to start.

  1. Fill in the Triggering Event box with a JSON object representing a request to make a move, as shown below.
{
  "minimum": 1,
  "maximum": 10,
  "history": []
}
  1. Click Test the function.
  2. The Output should show 1. There should be no quotation marks. If there are, it means that the function incorrectly returned a string instead of an integer.
  3. In a few minutes, the most recent Logs should be displayed at the bottom of the page.

You can also test with a history of previous guesses to the request, as below.

{
  "minimum": 1,
  "maximum": 10,
  "history": [
    {"guess": 5, "result": "higher"},
    {"guess": 8, "result": "lower"}
  ]
}

Since this is not a very smart game player, it will not learn from the history of prior guesses and will always return 1, even though the history shows the right answer at this point must be 6 or 7.

The function seems to be working as designed.

Call the function

Perform one last check to make sure the function works properly when invoked via an HTTP POST request using the URL assigned to it. Simply using a web browser to view that URL will result in an error response because the browser's request will not contain a valid JSON body. Instead, you will use the curl command line tool installed in the Cloud Shell. You could use Postman instead if you are more familiar with that tool and have previously installed it.

  1. Use the code editor to create a file called game.json with either of the sample input JSON objects from the previous step.
  2. Run the following command in the Cloud Shell, replacing the url parameter with the actual URL of your cloud function (you can find this URL in the Trigger section of the details page for your function):
curl -X POST -H "Content-type: application/json" --data-binary 
@game.json <url>

This command sends an HTTP POST request to the given url. The request has a header that specifies the request body is in application/json form, and the body itself is the contents of the game.json file you created.

  1. The command should output 1 at the beginning of the following line. Because the function does not return an end-of-line marker after the response, the prompt for the next command will be displayed immediately after the 1 instead of beginning a new line, such as shown below:
1user@host$

You have created, deployed, tested, and called a Cloud Function that is a valid player for the competition.

(Optional) Create a function from the Command Line

Instead of using the cloud console, you can create and deploy a cloud function from a command line. You will need to have the gcloud command installed and configured to do this; in this example you will work from the Cloud Shell which already has that in place.

  1. Navigate to the player folder of the repository you cloned earlier:
cd ~/serverless-game-contest/player
  1. There are three programs in this folder, any one of which can be used as a game player. The function you deployed using the console is in the badplayer.py file. Now you will deploy the function in okayplayer.py. The function to deploy must be in a file named main.py, so begin by copying the function to that file:
cp okayplayer.py main.py
  1. Now deploy the function with the following command:
gcloud functions deploy okayplayer \
--runtime=python37 --trigger-http \
--source=. --entry-point=make_guess \
--allow-unauthenticated 
  1. The gcloud functions group of commands manage Cloud Functions, and the deploy command creates or updates a function. The first parameter is the name you want to give to the function, and the rest of the parameters tell what language runtime the function is written in, what kind of event should trigger it, which directory contains the source code (main.py and requirements.txt for python37 runtime functions), the entry-point (function to execute when triggered), and in this case, that anyone should be able to invoke the function without authentication.

The response to the command will start with:

Deploying function (may take a while - up to 2 minutes)...done.

This will be followed by details of the deployed function.

You can now test and invoke this function within the console just as you did for the one deployed there.