FaaS Wars: Serverless & Virtual Robot Competition
Content
copied
Learn serverless quickly, build your FaaS Fighter and win up to $1400 in cash prizes
FaaS Wars is a programming competition organized by Nimbella and supported by several members of the Apache OpenWhisk community. In this competition, you'll learn "to serverless", have fun, compete and maybe win!
The game’s concept is simple: you write code to control a robot (faasfighter) moving in a battlefield and fire lasers to battle another robot, built by other participants. The goal of the game is to defeat the "enemy" robot, hitting it 5 times, and of course, avoiding lasers it will be firing at your robot. The time limit for each match is one minute.
A Serverless API For Your Robot
The way you control the movements of the faasfighter is to create a serverless API using Nimbella. The API receives an event that describes the state of the game, and responds with orders for the robot fighter to execute. Below is a tutorial that explains how to create your own fighter, along with battle techniques that get progressively "smarter".
The examples below use JavaScript as the programming language to implement the API. You may however use any other programming language available that Nimbella provides, including Python and Go.
A serverless implementation of an API is called a Nimbella action. The action is a function that receives as input a JSON object encoding the state of the game ("the event"). The action returns the output ("the orders") in JSON as well. The examples that follow are simple enough to be readily translated into the language of your choice.
Hello Robot
You control your fighter by writing some code using the FaaS Wars built-in editor. The code you enter in the editor is a function which has the following format.
function main(args) {
return { "body": [] }
}
The function must return a result as a JSON dictionary with the property "body"
containing an array of orders for your fighter (more on this later). This robot does not execute any moves. It sits idle, waiting for the enemy to destroy it.
Commanding Your FaaS Fighter
This next function sends a single order to the robot at every step of the game. It returns an array containing the order to "yell" the given message on the battlefield. Here, the message is the time of day when the order is issued.
function main(args) {
let msg = new Date().toLocaleTimeString()
return { "body": [ { "yell": msg } ] }
}
If you start the battle with this action, you can see the robot emitting the time of day... until it is eventually destroyed by a superior fighter (or time expires). This example is not very useful, battle-wise, but nonetheless, we see our robot is doing something now!
Moving Your Robot
Next, we'll learn how to instruct the robot fighter to move around the battlefield. As we already pointed out, the action returns an array of commands, so you can give multiple orders in one function execution. The action below instructs the robot to move forward, then turn to the left.
function main(args) {
return { "body": [
{ "move_forwards": 50 },
{ "turn_left": 90 }
]}
}
Run this code and you will note that the robot moves around in a square pattern. The orders are to repeatedly move forward 50 pixels and then turn 90 degrees to left. If the robot is hit, it may change its orientation randomly.
Reacting To Events
At every step of the game, the robot actually receives information about its environment in the args
parameter. The most important value to check is args.event
. There are four events our robot can react to.
idle
: when the robot is running out of commands and has nothing to do.enemy-spot
: when the robot sees the enemy right in front of the turret.hit
: when an enemy laser strikes the robot.wall-collide
: when the robot hits the wall and cannot move forward any more.
Now let’s add the ability to "shoot" at the enemy. For this purpose, we introduce a switch on the event. Also, we use an array to accumulate the orders we want to execute. Our revised code follows.
function main(args) {
let orders = []
switch (args.event) {
case "idle":
orders.push({ "move_forwards": 50 })
orders.push({ "turn_left": 90 })
break;
case "enemy-spot":
orders.push({ "yell": "Fire!" })
orders.push({ "shoot": true })
break;
}
return { "body": orders }
}
You will note that this function returns an array of orders. Each order is a map, where the key is some command to execute.
There are two types of orders. The first type are movement orders, and they execute sequentially; meaning your robot cannot move forward and backward at the same time, or move and turn simultaneously.
The movement orders are:
move_forwards <number>
: move forward by the given number of pixels.move_backwards <number>
: move backwards by the given number of pixels.move_opposite <number>
: move in the opposite direction, useful when you hit a wall.turn_left <degrees>
: turn the robot to the left by the given number of degrees.turn_right <degrees>
: turn the robot right by the given number of degrees.
The second type of orders are attack orders and these execute concurrently. For example, your robot can yell and shoot at the same time. In addition, you can also move the turret.
The attack orders are:
yell <message>
show a message.shot: true
: order to shoot when the value is true.turn_turret_left <degrees>
: turn the turret to the left by the given number of degrees.turn_turret_right <degrees>
: turn the turret to the right by the given number of degrees.data: <object>
: store the object and provide it in future events.
A Better Fighter
Now let's put it all together, handling also the cases of the robot colliding with the wall or being hit. What follows is the control program that is the default fighter when you create a new robot.
function main(args){
let orders = []
switch (args.event) {
case "idle":
orders.push({ "turn_turret_left": 45, "move_forwards": 50})
orders.push({ "turn_left": 45})
break;
case "wall-collide":
orders.push({ "move_opposite": 10})
orders.push({ "turn_left": 90})
break;
case "hit":
orders.push({ "yell": "Ooops!"})
break
case "enemy-spot":
orders.push({ "yell": "Fire!", "shoot": true})
break
default:
console.log(args)
}
return { "body": orders }
}
A Stateful Fighter
So far, we’ve developed a simple robot that moves around a square and shoots when it sees the enemy. Let's try to make the robot a bit smarter, using also its ability to "remember" the current state of the game, and considering more information about the environment.
For the examples that follow, we will use this skeleton for the action. You will replace the place holder // insert here
with the code snippets that are introduced later.
function main(args){
let orders = []
switch (args.event) {
// insert here
default:
break;
}
return { "body": orders }
}
The Input Event
The function parameter, called args
in the examples, is the event received by your serverless API. It includes information about the game, your position and that of the enemy robot. The event schema is as follows.
{
"event": [ "idle" | "enemy-spot" | "hit" | "wall-collide" ],
"x": number,
"y": number,
"energy": number,
"tank_angle": number,
"turret_angle": number,
"angle": number,
"enemy_spot": {},
"data": {}
}
The event provides the faasfighter's position (x
and y
), the number of strikes remaining before defeat (energy
), the angle of the tank (tank_angle
) and of the turret (turrent_angle
), and the total angle
which is essentially the angle of your laser beam.
Additionally, when your robot spots the enemy, details are provided in the enemy_spot
object, and you may persist this and other information about the state of the game in data
. But first, let's learn how to search for the enemy. A simple approach is to rotate the turret 360 degrees, with this code:
case "idle":
orders.push({ "turn_turret_left": 360 })
break;
Now, if you start the battle, enable Debug
and repeatedly click the Trace
, you will eventually observe the event enemy-spot
, when the turret is aligned with the enemy. An example enemy-spot
object is illustrated below.
{
"event": "enemy-spot",
"x": 166,
"y": 250,
"energy": 5,
"tank_angle": 195,
"turret_angle": 152,
"angle": 348,
"enemy_spot": {
"id": 1,
"x": 368,
"y": 201,
"angle": 346,
"distance": 207,
"energy": 5
},
"data": {}
}
Save and Use History
In the previously shared example, you can notice that there is an enemy at a distance of 207 pixels with an angle of 346 degrees. What can we do with this information? One idea is to remember where we last saw the enemy and use this information to align the turret. So let's save the angle
. We can do this with the data
command, as follows:
case "enemy-spot":
orders.push({ "data": { "angle": args.enemy_spot.angle } })
break;
Now that the robot is preserving some information, we can use it to align our turret and aim at the spotted enemy robot. In general, if our angle is greater than the angle of the enemy we want to move the turret to the left. Otherwise, to the right, and then shoot when we are aligned with the enemy. After we fire a shot, we want to forget about the angle we recorded until we see the enemy again. Also, we want to avoid being an easy target so it's a good idea to move when we are idle, and keep rotating the turret to spot the enemy. Putting it all together in code:
case "idle":
let data = args.data
if (data.angle) {
let me = args.angle
let it = data.angle
if (me > it) {
orders.push({ "turn_turret_left": me - it})
} else {
orders.push({ "turn_turret_right": it - me})
}
orders.push({ "shoot": true, "data": {}})
} else {
orders.push({ "move_forwards": 100, "turn_turret_left": 360})
orders.push({ "turn_left": 30 + 150 * Math.random() })
}
break;
Final Touches
The robot is now pretty good, but it can get stuck when it hits a wall, so we have to adapt. In our case, we are going to move in the opposite direction and then turn right 90 degrees.
case "wall-collide":
orders.push({ "move_opposite": 10})
orders.push({ "turn_left": 90})
break;
Also, when we are hit, it is better to move away to avoid repeated hits from the enemy. Our choice is to move backwards by a random amount to get to safety and away from enemy fire.
case "hit":
orders.push({ "move_backwards": 10 + 90 * Math.random() })
break;
Conclusion
You now know the basics of the game and how to control your faasfighter: move around, spot the enemy, persist state, aim and fire. Can you devise a winning strategy? Can you take into account the distance and the velocity of the enemy? Can you anticipate its moves? Can you think of better ways to move away from enemy fire?
The fun is endless and we invite you to have fun, compete, and learn about serverless programming at the same time. This is FAAS WARS. Good luck, may the best faasfighter win!
Author: Michele Sciabarrà
Recent Posts
- Results and Feedback of FaaS Wars - May the FaaS Be with You!
- 28 Serverless Gurus and experts One Must Follow in 2021
- The Faas Wars Alert!
- CI/CD pipeline with GitHub Actions
- How to deploy Node.js functions on Nimbella
- Kick-Start Your Serverless Journey
- AWS re:Invent Serverless Highlights
- Opportunities in the Wake of the AWS Juggernaut
- FaaS Wars: Serverless & Virtual Robot Competition
- #DeveloperIPL Online Hackathon Results & Feedback on Nimbella's Integration for Postman
- How to connect to the 3rd party database such as MySQL at Nimbella (example in Java)
- What can you do with the Nimbella Workbench?
- Deploy your Shopify Storefront to Nimbella
- Not All Serverless Platforms Are Created Equal
- Nimbella + Netlify: Uplevel Your Development Speed
- How we learned to Jamstack, Our Caputron Story.
- Commander for Microsoft Teams - Your Custom Bot that runs on your Command!
- How to Build a Stateful Cloud App on Nimbella vs. AWS
- Starter Kit and Resources to Build a Serverless Cloud Application
- How to Build Serverless Slack Apps and Commands
- How to Set up your Serverless Environment and Get Started in Less than 2 Minutes!
- How to Quickly Deploy Stateful Serverless Apps with Nimbella?
- What is Serverless Computing? 3 reasons to start now
- How to Build a Serverless Slack App in Minutes.
- How to Manage your Netlify Website from Slack?
- How to Build a Serverless Slack Command in minutes
- How to Build a Stateful Serverless Cloud Web Application?
- How to Create an Optical Character Recognition (OCR) Application?
- Development at the Speed of Innovation – Nimbella, the Serverless Cloud
- Software Security Features on Enterprise Serverless Slack Apps Enabled by Nimbella Commander
- Coronathon India’s first demo day has 18 projects to help fight COVID-19
- See the time in different cities on Slack with Nimbella Commander
- Greet your friends in their native language in Slack with Nimbella Commander
- Install Commander on your Mattermost Instance
- How to Fetch your Digital Ocean Billing Info on Slack?
- How to Stay Updated with Coronavirus Statistics on Slack?
- Create BlueJeans meetings on Mattermost using Commander
- How to Fetch your AWS Billing Info on Slack?
- Get your Datadog billing info in Slack with Nimbella Commander
- Serverless Slack Apps and Slash Commands
- How to use Slack Effectively with Nimbella Commander?
- How to Create a multi-user Chatroom Hosted on Serverless Cloud?
- Using Docker actions, running Golang, and other fun things with OpenWhisk
- The duality between serverless functions and APIs
- Serverless HTTP handlers with OpenWhisk
- Serverless functions in your favorite language with OpenWhisk
- Run Swiftly: precompiled Swift actions
- Performance debugging for serverless functions using the Cloud Shell
- Locally debugging OpenWhisk actions
- Composing functions into applications
- A Serverless Composition of Functions
- The Serverless Contract
- The dawn of the Cloud Computer
- Security and Serverless Functions