Contestants in the example programming contest create their solutions as web services, called players, that make one move at a time. A questioner is a program that has those players play the game.

The game

Since the point of this tutorial is learning how to create and deploy a serverless solution, 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.

A questioner's job is to play a game against a specified player, according to the questioner's preferred approach. For example, one questioner might provide a small gap between the minimum and maximum values so that a mediocre player could use a poor strategy yet still guess right in a small number of moves. Another might provide an enormous interval so that a player would have to use a good strategy to find the answer in an acceptable number of moves. A programming contest would likely use several questioners that each set up games differently to test players under a variety of conditions.

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 questioner is responsible for keeping track of the state of the game and creating this JSON object. 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 questioner needs to know the player's URL in order to play the game. In addition, the questioner needs a way to report the result of its game play, which will be by sending an HTTP POST request to a provided URL. The contest system needs to know which submitted player solution the result is for, so the questioner needs to be given an identifier for this contest round. Finally, reported results need to be authenticated as coming from a questioner that the system invoked, and not from a malicious third party, so the questioner needs a shared secret it can provide with the result to prove it is valid. This needed information is given to the questioner in a JSON-formatted message sent to a Pub/Sub topic. Example:

{
  "player_url": "the player function url",
  "result_url": "the manager web service url",
  "contest_round": "a0eb1674-b36a-42fa-a0c3-0e503bb8dd3e",
  "secret": "1951fbd5-87ab-4c6a-97cd-b1713d7a3901"
}

The player's 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

The questioner will report its results via an HTTP POST to the given result_url, in a JSON object of the form below:

{
  "questioner": "simple strategy questioner",
  "contest_round": "a0eb1674-b36a-42fa-a0c3-0e503bb8dd3e",
  "secret": "1951fbd5-87ab-4c6a-97cd-b1713d7a3901",
  "outcome": "won",
  "moves": 17
}

What you will build

In this tutorial, you're going to build a computer program that will act as a questioner. The program will set up an initial game state, then invoke the player to make a move and update the game state based on the move. The questioner will continue asking the player for moves until the player:

There may be many different questioners exercising a specific player at the same time. For example, one may specify very easy conditions ("guess a number between 1 and 10"), and another may specify harder ones ("guess a number between 1 and 1,000,000,000,000"). Questioners will be invoked by sending a message to a Pub/Sub topic; one topic may have many questioners subscribing to it, each playing a separate game with separate strategies.

Your app will:

What you'll learn

What you'll need

All software deployed on GCP will be part of a GCP Project. The questioners and the manager must each access a common Pub/Sub topic, which will be easiest if they are in the same GCP Project. For the purposes of this workshop, you can use the same project you have already set up for a player, or you can create a new project at console.cloud.google.com.

You will 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.

All commands in this codelab will be executed in the console UI or 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

If you have not previously retrieved the code in another codelab, use the following command from the Cloud Shell command line:

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.

Questioners will be triggered by publishing a message to a Pub/Sub topic. You will first set up the topic for this, and any other future, questioners to subscribe to. Each message published to this topic will contain the URL of a player and a second URL to post the game result to plus an identifier of the contest round and a secret needed for reporting results. One or more questioners will be triggered by subscribing to this topic, and each of them will play the game against the player described in the message.

  1. Use the menu at the top left of the cloud console page to select Pub/Sub in the Big Data section.
  2. If this is the first time using Pub/Sub in this project, you may immediately see a dialog to create a new topic. Otherwise click the Create Topic button on the top of this page. Click Create Topic.
  3. Enter a new topic name. Note that the name already includes your project ID. Enter play-a-game as the topic name, similar to the following:

  1. Click CREATE TOPIC. A new Pub/Sub topic is created and shown.

Each questioner will have a specific set of conditions they use to set up play. For the simple game in this tutorial, that is just the selection of minimum, maximum, and target values and the maximum number of guesses permitted. We will create two questioners, one with easy minimum, maximum, and target values of 1, 10, 7, and a harder one with values of 1, 1,000,000,000 and 1,000,000. Each of those questioners is going to be a Cloud Function triggered by receiving a message from the Pub/Sub topic created above.

This step creates the first, easy, questioner.

  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 a message that the Cloud Functions API is not enabled. Click the Enable API button to proceed.
  3. The page displays a Create function button. Click it to create a new Cloud Function.

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

  1. Ensure the Environment is set to 1st gen.
  2. Fill in the Function name box with easy-questioner.
  3. Leave the default region selected.
  4. We will use a Cloud Pub/Sub trigger. Select the Topic play-a-game.
  5. Select Save in the Trigger section and then Next at the bottom of the screen.
  6. Change the Runtime to Python 3.9. You'll change the Entry point later.
  7. You may receive a message that Cloud Build API is required. Click Enable API to enable it.

The topic will be sent a message whose body is a JSON object with four fields: player_url, result_url, contest_round, and secret. The code will play the game with the player at the specified URL, and then send the results to the appropriate URL. The contest_round is an identifier used by the system to track which requested player submission was being exercised, and secret is used to make sure that results are reported only by functions invoked by this Pub/Sub message.

The function code to enter is in the questioners/easy_questioner/main.py file in the repository that was cloned at the beginning of this lab. Copy that code from the Cloud Shell code editor and paste it into the function body into main.py.

Add the line below to requirements.txt.

requests>=2.8.1

Fill in the name of the Entry point as question_player, 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.

How it works

When a message is published to the Pub/Sub topic, the question_player function will be called with an object describing the triggering event and another object describing the context:

def question_player(event, context):

Only the event object is used here. The data of the event is commonly (by convention) encoded in base-64, so the function decodes it and then loads the JSON data into a Python object.

    message = json.loads(base64.b64decode(event['data']).decode('utf-8'))

    player_url = message['player_url']
    result_url = message['result_url']
    contest_round = message['contest_round']
    secret = message['secret']

The game is then played by the play_game function, and the report_score function HTTP POSTs the result back to the given URL.

    outcome, moves = play_game(player_url)
    report_score(result_url, outcome, moves, contest_round, secret)

The Cloud Function can be tested using the Testing tab to simulate receiving an event from the Topic, or it can be tested along with testing its configuration by sending an actual event to the Topic:

  1. Use the menu at the top left of the cloud console page to select Pub/Sub Topics in the Big Data section.
  2. Click on your Topic name play-a-game.
  3. Go to the bottom of the screen, open the Messages tab, and click the Publish Message button.
  4. Fill in the Message body field with the following.
{
  "player_url": "your-player-url",
  "result_url": "https://echoing.uc.r.appspot.com/",
  "contest_round": "a-contest-round-identifier",
  "secret": "shhh - it's a secret!"
}
  1. Click Publish.

You can track the execution of your function in the Cloud Function View Logs page, and see the results at https://echoing.uc.r.appspot.com/. The game should end in a failure after the maximum number of guesses due to the poor player logic.

You will follow essentially the same steps as above to create a second Cloud Function triggered by the same Pub/Sub topic. It will have almost exactly the same contents, except for a different function name and different MINIMUM, MAXIMUM, TARGET, MAX_GUESSES, and QUESTIONER values, which have been changed to the following:

MINIMUM = 0
MAXIMUM = 1000000000
TARGET = 1000000
MAX_GUESSES = 100
QUESTIONER = 'hard-questioner'

This step creates the second, hard, questioner.

  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 a message that the Cloud Functions API is not enabled. Click the Enable API button to proceed.
  3. The page displays a Create function button. Click it to create a new Cloud Function.

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

  1. Ensure the Environment is set to 1st gen.
  2. Fill in the Function name box with hard-questioner.
  3. Leave the default region selected.
  4. We will use a Cloud Pub/Sub trigger. Select the Topic play-a-game.
  5. Select Save in the Trigger section and then Next at the bottom of the screen.
  6. Change the Runtime to Python 3.9. You'll change the Entry point later.
  7. You may receive a message that Cloud Build API is required. Click Enable API to enable it.

The function code to enter is in the questioners/hard_questioner/main.py file in the repository that was cloned at the beginning of this lab. Copy that code from the Cloud Shell code editor and paste it into the function body into main.py.

Add the line below to requirements.txt.

requests>=2.8.1

Fill in the name of the Entry point as question_player, 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.

There are now two questioner Cloud Functions that will run games against a player when a message is published to the play-a-game topic.

Test the functions together, along with testing their configuration, by sending an actual event to the Topic:

  1. Use the menu at the top left of the cloud console page to select Pub/Sub Topics in the Big Data section.
  2. Click on your Topic name play-a-game.
  3. Go to the bottom of the screen, open the Messages tab, and click the Publish Message button.
  4. Fill in the Message body field with the following.
{
  "player_url": "your-player-url",
  "result_url": "https://echoing.uc.r.appspot.com/",
  "contest_round": "a-contest-round-identifier",
  "secret": "shhh - it's a secret!"
}
  1. Click Publish.

You can track the execution of your function in the Cloud Function View Logs page, and see the results at https://echoing.uc.r.appspot.com/. The game should end in a failure after the maximum number of guesses due to the poor player logic.

You have created, deployed, and tested two Cloud Functions that are valid questioners for the competition.