
How I built a spelling game with AWS Serverless and GenAI
This blog post explains the process of building a simple spelling game with AWS Serverless and GenAI.
Final application: https://spelling-game.pubudu.dev/
- Players can select a language to play the game. As of now, only English (US) and Dutch are the available languages.
- When a player selects a language, there will be a maximum 5 words generated. This includes an audio, brief meaning of the word and the number of characters for each word.
- Then the player needs to fill the word in the text box given.
- There is an indicator next to the text box how many characters have been entered in the text box and how many characters are required for the word. This indicator will be red until the required number of characters are filled, then it turns into green.
- There is a timer that starts as soon as the words are generated. This is based on the number of words generated.
- When the remaining time is less than 30 seconds, the background of the page as well as the background of the timer turns into red.
- Players can submit the game any time. However, if the player was unable to submit it until the timer runs out, the game will be automatically submitted.
- Then, the answers are evaluated and based on the number of correct answers, there is a pop up visible.
- If all the answers are correct, there will be a "Confetti" effect appearing on the page.
- Clicking on the 'show results' button on the pop up, the player can see the correct/incorrect answers and the correct word (in case of an incorrect answer).
* Backend - where words are generated and APIs are available.
* Frontend - Vue.js application for players to interact.
npm run dev
in the dev mode. Or you can build the frontend app using npm run build
.- Words generator component - to generate and save words in the database.
- API component - to serve frontend.
"Generate 5 unique words that have a random number of characters more than 4 and less than 10 in Dutch language. For each word, provide a brief description of its meaning in English with more than a couple of words. Produce output only in a minified JSON array with the keys word and description. Word must always be in lowercase."
StringToJson
. 1
2
3
{
"words.$": "States.StringToJson($.Body.content[0].text)"
}
StartSpeechSynthesisTask
.StartSpeechSynthesisTask
is an async operation. So, there is an immediate step to check if the synthesis task is completed using Polly's GetSpeechSynthesisTask
.GetSpeechSynthesisTask
, it waits and retries the status check. PutItem
api to save the generated data to the table. One record consists of below data:1
2
3
4
5
6
7
pk - Primary key in the format of Word#{LanguageCode}. ex: Word#en-US
sk - MD5 hash of the word - here, intrinsic function States.Hash($.word, 'MD5')is in use.
word - The word generated by Bedrock.
description - The description generated by Bedrock.
s3file - Mp3 file location provided by Polly synthesis task.
charcount - Character count of the word. This is retrieved from Polly synthesis task.
updated_at - Update timestamp.
POST /questions
- To generate questions using Step Functions to appear on the frontend.2.
POST /answers
- To validate the answers submitted by the player.start_sync_execution
SDK call.1
2
3
4
5
6
7
8
9
10
{
"language":"en-US",
"iterate":[
1,
2,
3,
4,
5
]
}
ExclusiveStartKey
is in use with the help of the intrinsic function UUID()
. Also, FilterExpression
is used to filter the records applicable only for the given language code.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"TableName": "arn:aws:dynamodb:****",
"Limit": 50,
"ExclusiveStartKey": {
"pk": {
"S.$": "States.Format('Word#{}', $$.Execution.Input.language)"
},
"sk": {
"S.$": "States.UUID()"
}
},
"FilterExpression": "pk = :pk",
"ExpressionAttributeValues": {
":pk": {
"S.$": "States.Format('Word#{}', $$.Execution.Input.language)"
}
},
"ReturnConsumedCapacity": "TOTAL"
}
ArrayGetItem
, MathRandom
and ArrayLength
.1
2
3
{
"item.$": "States.ArrayGetItem($.items,States.MathRandom(0, States.ArrayLength($.items)))"
}
s3file
path of the record. So, from the frontend the mp3 file can be played using this pre-signed url. Also there is a transformation of data within this Lambda function. The expiry of the pre-signed url is set to minimum because it is only required within the session of the game.1
2
3
4
5
6
7
{
"id":"XXXX",
"description":"Description of the word",
"charcount":8,
"language":"en-US",
"url":"Presigned-url"
}
/questions
end point.POST /answers
API is responsible for validating the answers submitted in the frontend.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"language":"en-US",
"answers":[
{
"id":"312e8b6583d4b65b",
"word":"dsfsda"
},
{
"id":"99b788c54c1a8265",
"word":"fdsafasd"
},
...
...
]
}
batch_get_item
SDK call to fetch words per ids and match with the word provided in the API.1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"id": "312e8b6583d4b65b",
"original_word": "upshot",
"correct": false
},
{
"id": "99b788c54c1a8265",
"original_word": "perilous",
"correct": true
},
...
...
]
correct
flag, frontend will calculate the results.OutputS3BucketName
where the generated audio will be stored. However, we cannot specify a path to save the object. Instead, I have used the OutputS3KeyPrefix
parameter to provide the path with the language code so, the audio is saved in s3://bucket_name/language_code/file_name.mp3
ExclusiveStartKey
and fetch maximum 50 items and select one random item.