@ -0,0 +1,5 @@
|
||||
# Test tasks from the real job interviews |
||||
|
||||
This folder is used to store test tasks from the real job interviews. The tasks are stored in the `test-tasks` folder. Each task is stored in a separate folder with the name of the company that provided the task. Inside the company folder, there is a `README.md` file with the task description. |
||||
|
||||
The real job interviews prepare students and junior specialists for the real job interviews. The tasks are designed to test the knowledge and skills of the candidates. The tasks are usually taken from the job position interviews and the company's technology stack. |
@ -0,0 +1,38 @@
|
||||
# Site Reliability Engineer - Basic API Hosting Task |
||||
|
||||
## Part 1 – The Web Service |
||||
|
||||
Write a web service in any language that takes in a JSON payload, does |
||||
some basic validation against an expected message format and content, |
||||
and then puts that payload into a queue of your choice or a file. |
||||
|
||||
Example valid payload: |
||||
|
||||
```json |
||||
{ |
||||
"ts": "1530228282", |
||||
"sender": "curler-user", |
||||
"message": { |
||||
"foo": "bar", |
||||
"hash": "bash" |
||||
}, |
||||
"sent-from-ip": "1.2.3.4" |
||||
} |
||||
``` |
||||
|
||||
Validation rules: |
||||
● “ts” must be present and a valid Unix timestamp |
||||
● “sender” must be present and a string |
||||
● “message” must be present, a JSON object, and have at least one |
||||
field set |
||||
● If present, “sent-from-ip” must be a valid IPv4 address |
||||
● All fields not listed in the example above are invalid, and |
||||
should result in the message being rejected. |
||||
|
||||
## Part 2 – Terraform |
||||
|
||||
Deploy this application to your favourite cloud provider using Terraform. |
||||
|
||||
## Part 3 – NewRelic |
||||
|
||||
Implement NewRelic monitoring for this application using Terraform. |
@ -0,0 +1,24 @@
|
||||
# Senior DevOps Engineer - NotBad Header based API |
||||
|
||||
Look at the following tasks and estimate how much time you will spend on them. |
||||
|
||||
## Preconditions |
||||
|
||||
### Technical & Knowledge |
||||
|
||||
You need at least: |
||||
|
||||
- Experience with AWS stack |
||||
- Experience with CI/CD |
||||
- Experience with Bash scripts |
||||
- Experience in at least one programming language (Java, Python, PHP, Perl, etc.) |
||||
- A text editor of your choice |
||||
|
||||
## The tasks |
||||
|
||||
1. We have a Terraform securitygroups.tf file. Every time Terraform runs, it says the security group in that file will be updated in place. Find a way to prevent this. |
||||
|
||||
2. Look into keycloak folder. What can be improved? |
||||
3. Provide infrastructure and create CI/CD with a web app that will listen to 8089 port and return "ReallyNotBad" string when POST request contains header "NotBad" with value "true", eg. `curl -X POST -H "NotBad: true" https://someurl:8089/` should return "ReallyNotBad". |
||||
Use any technology you want to deploy the application to AWS. It can be Ansible, Terraform, etc. or a combination of some of them. |
||||
Hint: https://aws.amazon.com/free/ |
@ -0,0 +1 @@
|
||||
.env |
@ -0,0 +1,7 @@
|
||||
# alpeso-test |
||||
|
||||
alpeso-test |
||||
|
||||
## How-to use |
||||
|
||||
Simple run `./keycloack.sh test test1` where test = keycloak user id and test1 = keycloak client secret. |
@ -0,0 +1,8 @@
|
||||
#!/bin/bash |
||||
|
||||
CLIENTID=$1 |
||||
CLIENTSECRET=$2 |
||||
|
||||
TOKEN=$(curl -k -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Basic $(echo -n ${CLIENTID}:${CLIENTSECRET} | base64 )" --data "grant_type=client_credentials" "http://mydomain.com/auth/realms/myrealm/protocol/openid-connect/token" -s | jq -r .access_token) |
||||
|
||||
echo $TOKEN |
@ -0,0 +1,66 @@
|
||||
## Software Engineer - Game Task |
||||
|
||||
### Tools and technologies used: |
||||
|
||||
1. Go |
||||
2. PostgreSQL |
||||
3. Docker |
||||
4. Makefile |
||||
5. Postman |
||||
|
||||
All database tables are in `migrations` folder. To run them, use `make` command. |
||||
|
||||
1. `make migrate-up` - to run up migrations |
||||
2. `make migrate-down` - to run down migrations |
||||
3. `make migrate-force` - to force run migrations if you have some errors like `error: Dirty database version -1. Fix and force version.` |
||||
|
||||
### Tables: |
||||
|
||||
1. `users` - contains users data |
||||
2. `transaction` - contains transactions data |
||||
|
||||
### Endpoints to test: |
||||
|
||||
1. `GET /users` - to get all users |
||||
2. `GET /users/{user_id}` - to get user by id, check his balance |
||||
3. `GET /transactions/{user_id}` - to get all transactions by user id (check if user has any transactions) |
||||
4. `POST /process-record/{user_id}` - to process record by user id |
||||
|
||||
Process record request body example: |
||||
|
||||
``` |
||||
{ |
||||
"amount": 10, |
||||
"transaction_id": "64338a05-81e5-426b-b01e-927e447c9e33", |
||||
"state": "win" |
||||
} |
||||
``` |
||||
|
||||
Transaction id is unique, so you can't process the same transaction twice, provide UUID v4 format. |
||||
State can be `win` or `lose`. |
||||
Amount is a number should be positive but to have a negative balance you should provide a `lose` state. |
||||
|
||||
### Required header for all endpoints: |
||||
|
||||
1. `Source-Type: game` - available values: `game`, `server`, `payment` |
||||
|
||||
Postman collection is in `postman` folder to test endpoints. |
||||
|
||||
## To run the app locally: |
||||
|
||||
1. Create `.env` file in root folder and add all required variables from `.env.example` file |
||||
2. To run migrations you should have migrate tool installed. You can install it with `brew install golang-migrate` (https://github.com/golang-migrate/migrate/tree/master/cmd/migrate) |
||||
3. To run any `make` command you should have `make` tool installed. You can install it with `sudo apt install make` command (https://linuxhint.com/install-make-ubuntu/) |
||||
4. Run `make migrate-up` command to run migrations and create all tables with test user (user_id: `63e83104-b9a7-4fec-929e-9d08cae3f9b9`) |
||||
5. Run `make run` command to run application |
||||
6. Take a look at `postman` folder to take collection for testing all endpoints |
||||
|
||||
Test user with id `63e83104-b9a7-4fec-929e-9d08cae3f9b9` will be created automatically when you run migrations. |
||||
This user has 50 amount of his balance for testing. |
||||
|
||||
## To run application in docker container: |
||||
|
||||
1. Create `.env` file in root folder and add all required variables from `.env.example` file |
||||
2. To run docker container you should have `docker` and `docker-compose` tools installed (Tested on `Docker version 26.1.3, build b72abbb` and `Docker Compose version v2.27.1`) |
||||
3. `docker-compose up` - to run application in docker container |
||||
4. `docker-compose down` - to stop application in docker container |
@ -1,58 +1,58 @@
|
||||
[ |
||||
{ |
||||
"description": "components variable must be an Array", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n\n// Your code\nif (!Array.isArray(components)) {\n throw Error('Season must be an Array')\n}" |
||||
}, |
||||
{ |
||||
"description": "components first element must be motor", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[0].toLowerCase(), 'motor')\n" |
||||
}, |
||||
{ |
||||
"description": "components second element sensor", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[1].toLowerCase(), 'sensor')\n" |
||||
}, |
||||
{ |
||||
"description": "components third element battery", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[2].toLowerCase(), 'battery')\n" |
||||
}, |
||||
{ |
||||
"description": "components fourth element camera", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[3].toLowerCase(), 'camera')\n" |
||||
}, |
||||
{ |
||||
"description": "components we must not have a fifth element", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[4], undefined)\n" |
||||
}, |
||||
{ |
||||
"description": "firstPart is the value of the first element", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'battery',\n 'camera',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(firstPart, 'motor')\n" |
||||
}, |
||||
{ |
||||
"description": "firstPart is the value of the first element even if we change the list", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'sensor',\n 'motor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(firstPart, 'sensor')\n" |
||||
}, |
||||
{ |
||||
"description": "lastPart is the value of the last element", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'battery',\n 'camera',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(lastPart, 'camera')\n" |
||||
}, |
||||
{ |
||||
"description": "lastPart is the value of the last element even if we change the list", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'sensor',\n 'motor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(lastPart, 'battery')\n" |
||||
}, |
||||
{ |
||||
"description": "comboParts is an array of lastPart and firstPart", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'battery',\n 'camera',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(comboParts, ['camera', 'motor'])\n" |
||||
}, |
||||
{ |
||||
"description": "comboParts is an array of lastPart and firstPart even if we change the list", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'sensor',\n 'motor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(comboParts, ['battery', 'sensor'])\n" |
||||
}, |
||||
{ |
||||
"description": "replaceComponents third element is 'enhanced'", |
||||
"code": "\n\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\n\n// Your code\n\nequal(replaceComponents, ['sensor', 'battery', 'enhanced', 'brain'])\n" |
||||
}, |
||||
{ |
||||
"description": "1st and 2nd elements of swapComponents are swapped pif,paf,pom", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nlet swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(swapComponents, ['battery', 'sensor', 'motor'])\n" |
||||
} |
||||
] |
||||
{ |
||||
"description": "components variable must be an Array", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n\n// Your code\nif (!Array.isArray(components)) {\n throw Error('Components must be an Array')\n}" |
||||
}, |
||||
{ |
||||
"description": "components first element must be motor", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[0].toLowerCase(), 'motor')\n" |
||||
}, |
||||
{ |
||||
"description": "components second element sensor", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[1].toLowerCase(), 'sensor')\n" |
||||
}, |
||||
{ |
||||
"description": "components third element battery", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[2].toLowerCase(), 'battery')\n" |
||||
}, |
||||
{ |
||||
"description": "components fourth element camera", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[3].toLowerCase(), 'camera')\n" |
||||
}, |
||||
{ |
||||
"description": "components we must not have a fifth element", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet swapComponents = ['motor', 'battery']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\n// Your code\nequal(components[4], undefined)\n" |
||||
}, |
||||
{ |
||||
"description": "firstPart is the value of the first element", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'battery',\n 'camera',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(firstPart, 'motor')\n" |
||||
}, |
||||
{ |
||||
"description": "firstPart is the value of the first element even if we change the list", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'sensor',\n 'motor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(firstPart, 'sensor')\n" |
||||
}, |
||||
{ |
||||
"description": "lastPart is the value of the last element", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'battery',\n 'camera',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(lastPart, 'camera')\n" |
||||
}, |
||||
{ |
||||
"description": "lastPart is the value of the last element even if we change the list", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'sensor',\n 'motor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(lastPart, 'battery')\n" |
||||
}, |
||||
{ |
||||
"description": "comboParts is an array of lastPart and firstPart", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'battery',\n 'camera',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(comboParts, ['camera', 'motor'])\n" |
||||
}, |
||||
{ |
||||
"description": "comboParts is an array of lastPart and firstPart even if we change the list", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'sensor',\n 'motor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(comboParts, ['battery', 'sensor'])\n" |
||||
}, |
||||
{ |
||||
"description": "replaceComponents third element is 'enhanced'", |
||||
"code": "\n\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nconst swapComponents = ['sensor', 'battery', 'motor']\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\n\n// Your code\n\nequal(replaceComponents, ['sensor', 'battery', 'enhanced', 'brain'])\n" |
||||
}, |
||||
{ |
||||
"description": "1st and 2nd elements of swapComponents are swapped pif,paf,pom", |
||||
"code": "\n\nconst replaceComponents = ['sensor', 'battery', 'motor', 'brain']\nlet robotParts = [\n 'motor',\n 'sensor',\n 'camera',\n 'battery',\n // 'memory', ??\n]\nlet swapComponents = ['sensor', 'battery', 'motor']\n\n// Your code\n\nequal(swapComponents, ['battery', 'sensor', 'motor'])\n" |
||||
} |
||||
] |
||||
|
@ -1,82 +1,82 @@
|
||||
[ |
||||
{ |
||||
"description": "Test with the falsy value 0", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 0\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value NaN", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = NaN\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value undefined", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = undefined\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value null", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = null\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value ''", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = ''\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value false", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = false\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value 'Sure'", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 'Sure'\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value []", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = []\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value {}", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = {}\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value true", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = true\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value -0.1", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = -0.1\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that can have the promotion", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You cannot benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You can benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that is too old", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: true, age: 33 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You can benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You cannot benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that is too young", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: true, age: 12 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You can benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You cannot benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that doesn't have an active membership", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: false, age: 21 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You can benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You cannot benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that can have the promotion but is just at the limit", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: true, age: 25 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You can benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You can benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a customer that has enough cash", |
||||
"code": "let truth = 0\nlet ticketSold = 8\nlet customer = { cash: 20, hasVoucher: false }\nlet user = { activeMembership: true, age: 22 }\nlet ticket = 'You cannot benefit from our special promotion'\n\n// Your code\n\nequal(ticketSold, 9)" |
||||
}, |
||||
{ |
||||
"description": "Test with a customer that has a voucher", |
||||
"code": "let truth = 0\nlet ticketSold = 5\nlet customer = { cash: 0, hasVoucher: true }\nlet user = { activeMembership: true, age: 22 }\nlet ticket = 'You cannot benefit from our special promotion'\n\n// Your code\n\nequal(ticketSold, 6)" |
||||
}, |
||||
{ |
||||
"description": "Test with a customer that has a voucher and cash", |
||||
"code": "let truth = 0\nlet ticketSold = 6\nlet customer = { cash: 42, hasVoucher: true }\nlet user = { activeMembership: true, age: 22 }\nlet ticket = 'You cannot benefit from our special promotion'\n\n// Your code\n\nequal(ticketSold, 7)" |
||||
}, |
||||
{ |
||||
"description": "Test with a customer that can not afford the ticket", |
||||
"code": "let truth = 0\nlet ticketSold = 3\nlet customer = { cash: 3, hasVoucher: false }\nlet user = { activeMembership: true, age: 22 }\nlet ticket = 'You cannot benefit from our special promotion'\n\n// Your code\n\nequal(ticketSold, 3)" |
||||
} |
||||
] |
||||
{ |
||||
"description": "Test with the falsy value 0", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 0\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value NaN", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = NaN\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value undefined", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = undefined\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value null", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = null\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value ''", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = ''\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the falsy value false", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = false\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'Lies !!!!')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value 'Sure'", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 'Sure'\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value []", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = []\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value {}", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = {}\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value true", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = true\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with the truthy value -0.1", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = -0.1\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(args[0]?.[0], 'The truth was spoken.')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that can have the promotion", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: true, age: 22 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You can benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that is too old", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: true, age: 33 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You can benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You cannot benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that is too young", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: true, age: 12 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You can benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You cannot benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that doesn't have an active membership", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: false, age: 21 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You can benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You cannot benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a user that can have the promotion but is just at the limit", |
||||
"code": "const args = saveArguments(console, 'log')\nlet truth = 1\nlet user = { activeMembership: true, age: 25 }\nlet customer = { cash: 20, hasVoucher: false }\nlet ticket = 'You can benefit from our special promotion'\nlet ticketSold = 3\n\n// Your code\n\nequal(ticket, 'You can benefit from our special promotion')" |
||||
}, |
||||
{ |
||||
"description": "Test with a customer that has enough cash", |
||||
"code": "let truth = 0\nlet ticketSold = 8\nlet customer = { cash: 20, hasVoucher: false }\nlet user = { activeMembership: true, age: 22 }\nlet ticket\n\n// Your code\n\nequal(ticketSold, 9)" |
||||
}, |
||||
{ |
||||
"description": "Test with a customer that has a voucher", |
||||
"code": "let truth = 0\nlet ticketSold = 5\nlet customer = { cash: 0, hasVoucher: true }\nlet user = { activeMembership: true, age: 22 }\nlet ticket\n\n// Your code\n\nequal(ticketSold, 6)" |
||||
}, |
||||
{ |
||||
"description": "Test with a customer that has a voucher and cash", |
||||
"code": "let truth = 0\nlet ticketSold = 6\nlet customer = { cash: 42, hasVoucher: true }\nlet user = { activeMembership: true, age: 22 }\nlet ticket\n\n// Your code\n\nequal(ticketSold, 7)" |
||||
}, |
||||
{ |
||||
"description": "Test with a customer that can not afford the ticket", |
||||
"code": "let truth = 0\nlet ticketSold = 3\nlet customer = { cash: 3, hasVoucher: false }\nlet user = { activeMembership: true, age: 22 }\nlet ticket\n\n// Your code\n\nequal(ticketSold, 3)" |
||||
} |
||||
] |
||||
|
@ -0,0 +1,159 @@
|
||||
## Serverless Payments Reminder |
||||
|
||||
Serverless Payments Reminder is a basic Slack Bot that reminds companies / users to pay their bills. The reminder gets triggered by AWS EventBridge (also known as CloudWatch Events). The bot itself is hosted on a simple AWS Lambda function. |
||||
|
||||
### Requirements for the task |
||||
|
||||
Create a simple Slack bot using: |
||||
|
||||
- [AWS](https://aws.amazon.com/) |
||||
- [Serverless Framework](https://serverless.com/) |
||||
- [Cloudformation](https://aws.amazon.com/cloudformation/) |
||||
- [Terraform](https://www.terraform.io/) |
||||
|
||||
### Task |
||||
|
||||
The task is separated into multiple different levels. The levels are ordered by complexity. You can choose the level that you feel most comfortable with. |
||||
|
||||
## Level 0: Basic Lambda Function hosted via Serverless |
||||
|
||||
**1. Create Slack Bot** |
||||
|
||||
Write a slack bot, that sends a reminder to slack channel with the following message: |
||||
|
||||
```sh |
||||
Dear Board Members! This is a reminder to make the payment for the licenses of the software. The due date is 07.XX.YY, where XX is the month and YY is the year. The amount to be paid is $ZZZ.VV. Please make the payment as soon as possible to <IBAN_NUMBER>. Thank you! |
||||
``` |
||||
|
||||
The environment variables which must be available in the Lambda function are: |
||||
|
||||
- `SLACK_WEBHOOK_URL` - The Slack Webhook URL |
||||
- `AMOUNT` - The amount to be paid |
||||
- `IBAN_NUMBER` - The IBAN number |
||||
|
||||
The due date must be calculated based on the current date for the next month. |
||||
|
||||
Before step two, you need to test that Slack Bot is functioning properly by running an application locally. It should send a message to the Slack channel. |
||||
|
||||
**2. Write tests for the application** |
||||
|
||||
Write integration tests for the application to ensure that each part of the application is functioning properly. |
||||
|
||||
**3. Use [Serverless Framework](https://github.com/serverless/serverless) to host the lambda function** |
||||
|
||||
Serverless framework is an automation tool used to deploy serverless applications which can help with event-driven architecture deployments. |
||||
|
||||
Use the serverless framework to host the lambda function combined with AWS Eventbridge. |
||||
|
||||
**Refs** |
||||
|
||||
- [AWS EventBridge](https://www.serverless.com/framework/docs/providers/aws/events/event-bridge) |
||||
- [AWS Lambda Function](https://www.serverless.com/framework/docs/providers/aws/guide/functions). |
||||
|
||||
AWS Eventbridge should trigger lambda function every month on the 1st day of the month at 10:00 AM. |
||||
|
||||
**Note** Before hosting, you need to ensure that the bot is written in a way that it can be hosted on AWS Lambda. You can look at the example [here](https://github.com/KostLinux/aws-incident-manager-notifier/blob/56d52e90f8a14e689e7d2a1c7ee44590de5af2f5/main.go#L158). |
||||
|
||||
## Level 1: Basic Lambda Function automation via Cloudformation |
||||
|
||||
**1. Repeat the 1st and 2nd step from Level 0** |
||||
|
||||
Create a slack bot, that sends a reminder to slack channel. Write integration tests for the application to ensure that each part of the application is functioning properly. |
||||
|
||||
**Note** Don't forget to add Lambda handler for the application |
||||
|
||||
**2. Use [Cloudformation](https://aws.amazon.com/cloudformation/) to host the lambda function** |
||||
|
||||
Cloudformation is Infrastructure As Code (IaC) tool used to automate the provisioning of AWS resources inside the AWS. It allows you to use a simple yaml syntax to define the resources you want to create. |
||||
|
||||
**2.1 Hosting the Lambda function** |
||||
|
||||
Write cloudformation template with parameters (variables) `SlackWebhookUrl`, `Amount`, `IbanNumber`. The template should automatically provision the Slackbot based on architecture mentioned in Level 0 step 3. |
||||
|
||||
## Level 2: Basic Lambda Function automation via Basic Terraform with Local State |
||||
|
||||
Terraform is an Infrastructure As Code (IaC) tool used to automate the provisioning of resources inside different SaaS solutions and Cloud providers. It uses HCL syntax, which is easy maintainable. The main problem of terraform is that you need to know, how everything works under the hood (e.g. IAM roles, policies, serverless platforms etc). |
||||
|
||||
**1. Repeat the 1st and 2nd step from Level 0** |
||||
|
||||
Create a slack bot, that sends a reminder to slack channel. Write integration tests for the application to ensure that each part of the application is functioning properly. |
||||
|
||||
**Note** Don't forget to add Lambda handler for the application |
||||
|
||||
**2. Automate the hosting via Terraform** |
||||
|
||||
**2.1 Write an IAM role with the least privilege principle for the lambda function and EventBridge.** |
||||
|
||||
**2.2 Automatically pack up the lambda function into a zip file** |
||||
|
||||
**2.3 Build the lambda function based on the zip file** |
||||
|
||||
**2.4 Build AWS Eventbridge, which will trigger the lambda function** |
||||
|
||||
**Note** Lambda function should be triggered every month on the 1st day of the month at 10:00 AM. |
||||
|
||||
At this level you can use local state for terraform, no modules needed to write. |
||||
|
||||
## Level 3: Basic Lambda Function automation via Terraform Module with Remote S3 State (advanced) |
||||
|
||||
In a DevOps world, mostly we use remote state for terraform. The main reason is that we can share the state with other team members and we can easily manage the state of the infrastructure. Although it is a bit more complex to set up, it allows you to avoid issues when someone even removes the state file, cause S3 has versioning turned on. |
||||
|
||||
This level is more advanced, but more near to the real-world scenario. As a DevOps Engineer / SRE mostly all the terraform code is written in modules (or using community pre-built modules). Modules help us to maintain the codebase and reuse the code in different projects, so you don't need to write the same code again and again. |
||||
|
||||
**1. Repeat the steps from Level 2** |
||||
|
||||
Repeat all the steps from Level 2, but now you need to write a terraform module for the lambda function and EventBridge. Which means that instead of writing values directly into main.tf file, everything should be written using variables. For example: |
||||
|
||||
```tf |
||||
resource "aws_lambda_function" "slack_bot" { |
||||
function_name = var.function_name |
||||
role = var.role |
||||
handler = var.handler |
||||
runtime = var.runtime |
||||
timeout = var.timeout |
||||
} |
||||
``` |
||||
|
||||
We're using variables for each value, so we can reuse the module in different projects. |
||||
|
||||
**2. Use remote state for terraform** |
||||
|
||||
In this step you need to configure IAM User with least privilege principle for terraform to access S3 bucket, provision resources in Eventbridge and Lambda function. |
||||
|
||||
**Note** We would recommend using [Cloudformation](https://aws.amazon.com/cloudformation/) to create an IAM User with the least privilege principle + S3 Bucket for the remote state. Cloudformation can be applied via AWS CLI which means that everything persists to be AS CODE. |
||||
|
||||
**3. Use the S3 bucket for the remote state and module to provision the resources** |
||||
|
||||
Write your backend configuration to backend.tf file: |
||||
|
||||
```tf |
||||
terraform { |
||||
backend "s3" { |
||||
bucket = "" |
||||
key = "" |
||||
region = "" |
||||
encrypt = "" |
||||
} |
||||
} |
||||
``` |
||||
|
||||
Use the module written at step 1 to provision the resources |
||||
|
||||
```tf |
||||
module "slack_bot" { |
||||
source = "./modules/slack_bot" |
||||
function_name = "slack_bot" |
||||
role = "arn:aws:iam::123456789012:role/lambda-role" |
||||
handler = "main.handler" |
||||
runtime = "nodejs14.x" |
||||
timeout = 10 |
||||
} |
||||
``` |
||||
|
||||
## Helpful commands |
||||
|
||||
- `serverless deploy` - Deploy the serverless application |
||||
- `aws cloudformation deploy --template-file template.yaml --stack-name my-stack` - Deploy the cloudformation stack |
||||
- `terraform init` - Initialize the terraform project and remote state. |
||||
- `terraform plan` - Plan the resources you're going to provision. It will show you the changes that will be made. |
||||
- `terraform apply` - Apply the terraform project only if you're sure that everything is correct during the plan. |
@ -0,0 +1,3 @@
|
||||
module "aws_slack_bot" { |
||||
source = "./modules/aws_slack_bot" |
||||
} |
@ -0,0 +1,148 @@
|
||||
## 2.5D Adventure |
||||
|
||||
### Overview |
||||
|
||||
This project involves creating a simple 2.5D side-scroller game designed to introduce you to the fundamentals of Unreal Engine 5 and game development, without the complexity of advanced mechanics. It focuses on the essential skills and concepts needed to start your journey as a game developer. |
||||
|
||||
<center> |
||||
<img src="./resources/1.jpg?raw=true" style = "width: 500px !important; height: 350px !important;"/> |
||||
</center> |
||||
|
||||
### Role play |
||||
|
||||
You've just embarked on your journey into game development, choosing `Unreal Engine 5` as your primary tool. Excited by the possibilities, you decide to create a simple 2.5D game as your first project. However, as you dive into the engine, you realize there's a vast array of tools and features you've never encountered before. Now, you're tasked with navigating this uncharted territory, learning new skills in level design, scripting, and asset management to bring your game to life. |
||||
|
||||
### Learning Objective |
||||
|
||||
At the end of this project you will have learned: |
||||
|
||||
- Setting up a basic player character (input handling, movement, camera, etc.) |
||||
- Basic scripting (`Blueprints` and/or `C++`). |
||||
- Collision detection. |
||||
- Object creation and `instancing`. |
||||
- Basic UI/HUD. |
||||
- Basic level design |
||||
- Game logic and flow (Game Loops). |
||||
|
||||
### Instructions |
||||
|
||||
#### General |
||||
|
||||
The use of `event dispatchers` is mandatory throughout the project, any code coupling will be disqualified. |
||||
|
||||
> Tip: look for the observer pattern. |
||||
|
||||
Your game should follow a coherent `theme` of your choice, whether it's a dark fantasy, sci-fi, or something entirely different. Let your imagination guide you. |
||||
|
||||
> Note: the resources section will list some places where you can browse assets. |
||||
|
||||
> Tip: For maximum learning, it is recommended to start this project with a blank Unreal project and implement everything from scratch. |
||||
|
||||
#### Main Menu |
||||
|
||||
The main menu `widget` should: |
||||
|
||||
- Be on a separate `level/map` from the `main game` map. |
||||
- Contain two buttons: |
||||
- Start game button. |
||||
- Exit game button. |
||||
|
||||
#### Player Character |
||||
|
||||
The player `character` should: |
||||
|
||||
- Have a skeletal mesh. |
||||
- Move only along two axes: left-right and up-down. |
||||
- Have a basic locomotion system with the following animations: |
||||
- `Idle`. |
||||
- `Walking`. |
||||
- `Running`. |
||||
- `Jumping`. |
||||
- `Falling`. |
||||
- `Landing`. |
||||
- Transition smoothly between a walking and running animations based on their speed. |
||||
<center> |
||||
<img src="./resources/locomotion.gif?raw=true" style = "width: 500px !important; height: 350px !important;"/> |
||||
</center> |
||||
|
||||
> Tip: Look into the best practices for creating a locomotion system, Animation blueprints and Blend spaces. |
||||
|
||||
#### Collectible |
||||
|
||||
The collectible `Actor` should: |
||||
|
||||
- Have a static mesh. |
||||
- Rotate around an axis of your choice. |
||||
- Have a box collider that acts like a trigger. |
||||
- Be collected when the player enters its box trigger. |
||||
- Play a sound when collected |
||||
<center> |
||||
<img src="./resources/collectible.gif?raw=true" style = "width: 500px !important; height: 350px !important;"/> |
||||
</center> |
||||
|
||||
#### HUD |
||||
|
||||
The HUD `widget` should: |
||||
|
||||
- Display a value related to the `collectible`. |
||||
- Be updated each time a `collectible` is picked up. |
||||
<center> |
||||
<img src="./resources/hud.gif?raw=true" style = "width: 500px !important; height: 350px !important;"/> |
||||
</center> |
||||
|
||||
#### Enemy |
||||
|
||||
The enemy `character` should: |
||||
|
||||
- Have a skeletal mesh. |
||||
- Have at least a walking cycle. |
||||
- Have a simple AI that patrols between two set points. |
||||
- Kill the player on collision. |
||||
- Be killed if the player lands on top of it. |
||||
- Play a sound when killed. |
||||
- create an instance of `Collectible` at the place of death. |
||||
> Note: Make sure the instance of collectible spawned from killing an enemy also updates the HUD. |
||||
|
||||
<center> |
||||
<div style="display: flex; justify-content: center;"> |
||||
<img src="./resources/enemywalk.gif?raw=true" style="width: 500px !important; height: 350px !important;" /> |
||||
<img src="./resources/enemydie.gif?raw=true" style="width: 500px !important; height: 350px !important;" /> |
||||
</div> |
||||
</center> |
||||
|
||||
#### Game Loop Logic |
||||
|
||||
The game loop consists of a spawn point where the player `starts` and `respawns` when dead. |
||||
And an end point that defines the player goal to finish the level. |
||||
When the player reaches the end point: |
||||
|
||||
- a menu with options to `restart` or `quit` the game should be displayed. |
||||
|
||||
#### Level design |
||||
|
||||
Your level design shouldn't consist of just a long, empty run to the finish point. However, since creating a level is a creative process, you have the freedom to design it as you see fit. For example, you can include moving platforms or skill-based platforming elements. The choice is yours to provide the player with a fun and challenging experience. |
||||
|
||||
> Tip: look at other games from a similar genre for inspiration. |
||||
> <img src="./resources/platformer.jpg?raw=true" style="width: 600px; height: 350px;" /> |
||||
|
||||
<!-- Expected results video link to be added later when available --> |
||||
|
||||
### Bonus |
||||
|
||||
- Add a health system to both the player character and the enemy character. (look into `Components`) |
||||
- Add a simple crouch to your locomotion system. |
||||
- more mechanics as you see fit for your game. |
||||
- Create a simple `Game Design Document` where you describe your mechanics and how are you planning to implement them. |
||||
|
||||
### Submission |
||||
|
||||
- In your repository there should be a zip file of a build of your game for your target platform. |
||||
> If it is not possible to upload files to Gitea due to their size, use GitHub instead and have a look at [Git LSF](https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github) |
||||
|
||||
### Resources |
||||
|
||||
- [Animation Blueprints](https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-blueprints-in-unreal-engine) |
||||
- [Actor Components](https://dev.epicgames.com/documentation/en-us/unreal-engine/components-in-unreal-engine) |
||||
- [Mixamo for animations and character models](https://www.mixamo.com/) |
||||
- [SketchFab for 3D models](https://sketchfab.com/) |
||||
- [itch.io for more assets](https://itch.io/) |
@ -0,0 +1,89 @@
|
||||
> Due to file size reason, the solution might be uploaded on GitHub instead of Gitea! |
||||
|
||||
#### General |
||||
|
||||
##### If, at any point during the audit, coupled code is detected that could have been resolved using the Observer pattern (`event dispatchers`), the audit will stop, and the student will fail the project. |
||||
|
||||
###### Is the game following a coherent theme ? |
||||
|
||||
#### Main Menu |
||||
|
||||
###### Is the main menu widget on a separate level/map from the main game map? |
||||
|
||||
###### Does the main menu contain a Start game button? |
||||
|
||||
###### Does the main menu contain an Exit game button? |
||||
|
||||
#### Player Character |
||||
|
||||
###### Does the player character have a skeletal mesh? |
||||
|
||||
###### Can the player character move only along two axes: left-right and up-down? |
||||
|
||||
###### Does the player character have a basic locomotion system with all the required animations? |
||||
|
||||
###### Does the player character transition smoothly between walking and running animations based on their speed? |
||||
|
||||
#### Collectible |
||||
|
||||
###### Does the collectible Actor have a static mesh? |
||||
|
||||
###### Does the collectible rotate around an axis? |
||||
|
||||
###### Does the collectible have a box collider that acts as a trigger? |
||||
|
||||
###### Is the collectible collected when the player enters its box trigger? |
||||
|
||||
###### Does the collectible play a sound when collected? |
||||
|
||||
#### HUD |
||||
|
||||
###### Does the HUD widget display a value related to the `collectible`? |
||||
|
||||
###### Is the HUD updated each time a `collectible` is picked up? |
||||
|
||||
###### Is the HUD also updated when picking a `collectible` that spawned from killing an enemy? |
||||
|
||||
#### Enemy |
||||
|
||||
###### Does the enemy character have a skeletal mesh? |
||||
|
||||
###### Does the enemy character have at least a walking cycle? |
||||
|
||||
###### Does the enemy character have a simple AI that patrols between two set points? |
||||
|
||||
###### Does the enemy character kill the player on collision? |
||||
|
||||
###### Is the enemy character killed if the player lands on top of it? |
||||
|
||||
###### Does the enemy character play a sound when killed? |
||||
|
||||
###### Does the enemy character create an instance of a `Collectible` at the place of death? |
||||
|
||||
#### Game loop |
||||
|
||||
###### Is there a spawn point where the player starts? |
||||
|
||||
###### Does the player respawn at the spawn point when dead? |
||||
|
||||
###### Is there an end point that defines the player’s goal to finish the level? |
||||
|
||||
###### When the player reaches the end point, is a menu displayed with options to restart or quit the game? |
||||
|
||||
#### Level design |
||||
|
||||
###### Does the level design avoid being just a long, empty run to the finish point? |
||||
|
||||
###### Does the level include mechanics chosen by the student? `(e.g. moving platforms skill-based platforming)` |
||||
|
||||
###### Does the level provide a fun and challenging experience for the player? |
||||
|
||||
#### Bonus |
||||
|
||||
###### +Has a health system been added to both the player character and the enemy character using Components? |
||||
|
||||
###### +Has a simple crouch been added to the player character's locomotion system? |
||||
|
||||
###### +Have additional mechanics been implemented to fit the overall game design? |
||||
|
||||
###### +Has a simple Game Design Document been created that describes the mechanics and outlines the plan for implementing them? |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 5.4 MiB |
After Width: | Height: | Size: 5.4 MiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 5.2 MiB |
After Width: | Height: | Size: 15 MiB |
After Width: | Height: | Size: 149 KiB |
@ -1,39 +1,125 @@
|
||||
## FiringRange |
||||
## Firing-range |
||||
|
||||
In this exercise, you will learn to use the Unreal Engine and Blueprints to script functionalities of a basic FPS game. The theme is to reproduce a firing range. And remember, weapons are allowed only inside the shooting area!! |
||||
### Overview |
||||
|
||||
This project involves creating a fully functional firing range in Unreal Engine 5 (UE5). You will focus on implementing shooting mechanics, a weapon system, player input handling, physics for projectiles, and basic AI to control moving targets. Your task is to build a simple environment where the player can shoot targets, and all systems should work seamlessly together. |
||||
|
||||
<center> |
||||
<img src="./resources/fgm.jpg?raw=true" style="width: 500px !important; height: 350px !important;"/> |
||||
</center> |
||||
|
||||
### Role Play |
||||
|
||||
You are developing a firing range training level for a first-person shooter game. You are required to set up a working shooting system, integrate a functional weapon with reload mechanics, implement a physics system for projectiles, and design basic AI for moving targets. Additionally, you will create a player HUD that tracks their score, accuracy, and remaining ammunition. |
||||
|
||||
### Learning Objective |
||||
|
||||
By the end of this project, you will have implemented: |
||||
|
||||
- A complete shooting mechanic (with accurate aiming, projectiles, and hit detection). |
||||
- A weapon system with at least one firearm, including shooting and reload mechanics. |
||||
- Basic AI to control target movement. |
||||
- Physics-based projectiles that interact with the environment. |
||||
- A simple UI displaying relevant gameplay information (accuracy, ammo count, etc.). |
||||
- A simple firing range level design that incorporates moving and stationary targets. |
||||
|
||||
### Instructions |
||||
|
||||
The map of this project should be composed of a cube with dimensions of X = 35, Y = 40, Z = 1. It should simulate the floor and other cubes as walls around the floor. In the map there should be a zone where the character is able to walk around and shoot the targets and another zone where the player can not go and where the targets are present. |
||||
#### General Requirements |
||||
|
||||
- Use `Blueprints` or `C++` to implement the game mechanics. |
||||
- Implement a firing system where the player can aim downsights and shoot. |
||||
- Implement a weapon system that supports shooting, reloading, and proper hit detection. |
||||
- Create a firing range level that includes both stationary and moving targets. |
||||
- Implement a physics system that governs projectile behavior (trajectory and collisions). |
||||
- Implement a HUD that shows the player's score, accuracy, and remaining ammunition. |
||||
|
||||
#### Main Menu |
||||
|
||||
The game's main menu must: |
||||
|
||||
- Be a separate `level/map`. |
||||
- Include the following options: |
||||
- **Start Game**: Transitions to the firing range gameplay. |
||||
- **Settings**: Allows the player to adjust mouse sensitivity. |
||||
- **Quit**: Closes the game. |
||||
|
||||
#### Game Screen (HUD) |
||||
|
||||
The game screen must include a HUD that displays: |
||||
|
||||
- A precise crosshair that accurately indicates the bullet's point of impact. |
||||
- The player's current accuracy (hits devided by total shots fired). |
||||
- Remaining ammunition and reload status for the current weapon. |
||||
|
||||
#### Player Character |
||||
|
||||
The player character must have the following functionalities: |
||||
|
||||
- Basic movement around the firing range. |
||||
- Proper input handling for aiming, shooting, and reloading. |
||||
- Interact with ammo pickups to replenish ammo. |
||||
|
||||
#### Weapons |
||||
|
||||
You must create at least one firearm (e.g., a pistol) that includes: |
||||
|
||||
- Recoil mechanics. |
||||
- Reloading the weapon with an appropriate animation. |
||||
- Projectile physics that simulate bullet trajectories. |
||||
- Bullet hit detection (with an effect on impact, e.g., sound and visual feedback). |
||||
|
||||
#### Targets and AI |
||||
|
||||
You must implement the following for the targets: |
||||
|
||||
- Create a stationary target. |
||||
- Create a moving target with basic AI that follows a predefined path or pattern. |
||||
- Implement hit detection for the targets. |
||||
- Ensure the targets respawn after a set interval once they are hit. |
||||
<center> |
||||
<div style="display: flex; justify-content: center;"> |
||||
<img src="./resources/example1.jpg?raw=true" style="width: 500px !important; height: 350px !important;" /> |
||||
<img src="./resources/example2.jpg?raw=true" style="width: 500px !important; height: 350px !important;" /> |
||||
</div> |
||||
</center> |
||||
|
||||
For this project you will have to create a Blueprint Class target, that will have some of these characteristics. The target should : |
||||
#### Game Loop Logic |
||||
|
||||
- have associated to it the previous created material. |
||||
- either be moving from side to side or be stationary. |
||||
- be dynamic, using the timeline node. |
||||
- use a public variable to set or unset the movement animation of the target. |
||||
- rise again after x seconds after being hit it with a projectile, and behave like before it was hit. |
||||
The game loop must be continuous and consist of: |
||||
|
||||
Only one class of target is allowed for the whole project. |
||||
- The player starts with a loaded weapon and can shoot at targets. |
||||
- The player can open a pause menu at any time, with options to: |
||||
- Restart the game (reset score, targets, and ammo). |
||||
- Return to the main menu. |
||||
|
||||
The previously mentioned projectile should: |
||||
#### Level Design |
||||
|
||||
- have a size of X = Y = Z = 0,5. |
||||
- have a speed of 10000. |
||||
You must design a firing range that includes: |
||||
|
||||
After downloading and unzipping this [file](https://assets.01-edu.org/Unreal-Engine-Piscine/FiringRange.zip), you can copy its content to your project Content folder. |
||||
- A coherent theme to ensure a consistent and immersive experience. |
||||
- Proper lighting and environment setup to ensure the range is clear and visually appealing. |
||||
- A section with stationary targets. |
||||
- A section with moving targets. |
||||
- Ammo pickups where the player can restock. |
||||
|
||||
When finished, your project should look like the executable example on the folder or the ["Expected Result" video](https://youtu.be/EBibaN-dh_0). |
||||
### Bonus |
||||
|
||||
> Do not forget to zip up the project compile and save everything for peer correction. |
||||
> If it is not possible to upload files to Gitea due to their size, use GitHub instead and have a look at [Git LSF](https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github) |
||||
- Add multiple weapons (e.g., a shotgun, sniper rifle) with unique shooting and reload mechanics. |
||||
- Implement more advanced target AI, such as random movement patterns or different difficulty levels. |
||||
- Add different hit zones for targets, with headshots awarding more accuracy points. |
||||
- Design a timed challenge mode where the player must hit all targets within a set time frame. |
||||
|
||||
#### Bonus |
||||
### Submission |
||||
|
||||
Here are some ideas for improving the game: |
||||
- You must upload a zip file of your game build in your repository. |
||||
- Ensure that the build works on your platform. |
||||
- If file size is an issue, use GitHub with [Git LFS](https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github). |
||||
|
||||
- Targets with different speeds. |
||||
- Textures on the walls and ground. |
||||
- Adding obstacles in front of the targets. |
||||
### Resources |
||||
|
||||
[Here](https://www.youtube.com/playlist?list=PLHyAJ_GrRtf9sxZqgfPVM06PrLk8_CWA-) you can find an instructional playlist on Unreal Engine. |
||||
- [Unreal Engine's AI Documentation](https://dev.epicgames.com/documentation/en-us/unreal-engine/ai-system-settings-in-the-unreal-engine-project-settings?application_version=5.5) |
||||
- [Projectile Movement Component](https://dev.epicgames.com/documentation/en-us/unreal-engine/BlueprintAPI/Game/Components/ProjectileMovement?application_version=5.5) |
||||
- [Mixamo for character animations and models](https://www.mixamo.com/) |
||||
- [SketchFab for 3D models](https://sketchfab.com/) |
||||
- [itch.io for additional assets](https://itch.io/) |
||||
|
@ -1,27 +1,79 @@
|
||||
> Due to file size reason, the solution might be uploaded on GitHub instead of Gitea! |
||||
|
||||
#### Functional |
||||
#### Main menu |
||||
|
||||
###### Are the map size (X = 35, Y = 40, Z = 1) and composition (using cubes) being respected? |
||||
###### Is the main menu widget on a separate level/map? |
||||
|
||||
###### Are the projectile size (X = Y = Z = 0,5) and speed (10000) being respected? |
||||
###### Does the main menu contain a Start Game button that transitions to the firing range gameplay? |
||||
|
||||
###### If you try to move the target to the target zone, is the player forbidden from trespassing to that zone by an invisible wall? |
||||
###### Does the main menu contain a Settings button for adjusting mouse sensitivity? |
||||
|
||||
###### When you shoot a target, does the bullet go through the invisible wall? |
||||
###### Does the main menu contain an Exit Game button? |
||||
|
||||
###### Did the targets correctly lay on the floor when hit by the projectile? |
||||
#### HUD |
||||
|
||||
###### Did the target rotate from the bottom and not from the center? |
||||
###### Does the HUD display an accurate crosshair that indicates the bullet's point of impact? |
||||
|
||||
###### Is there only one class “BP_Target” present on the project? |
||||
###### Does the HUD display the player's accuracy? |
||||
|
||||
###### Do all the public variables have a tooltip? (Mouse over it to check if a description is set or check if the variable have a little green eye on the right) |
||||
###### Does the HUD display the remaining ammunition and the reload status? |
||||
|
||||
#### Player Character |
||||
|
||||
###### Can the player character move around the firing range? |
||||
|
||||
###### Is input handling for aiming, shooting, and reloading functional? |
||||
|
||||
###### Can the player interact with ammo pickups to replenish ammunition? |
||||
|
||||
#### Weapons |
||||
|
||||
###### Does the weapon have recoil mechanics implemented? |
||||
|
||||
###### Can the weapon be reloaded and has a proper reloading animation? |
||||
|
||||
###### Are projectile physics implemented for bullets, simulating accurate bullet trajectories? |
||||
|
||||
###### Is hit detection for bullets functional, with audio and visual feedback upon impact? |
||||
|
||||
#### Targets and AI |
||||
|
||||
###### Is there a stationary target present in the firing range? |
||||
|
||||
###### Is there a moving target with basic AI that follows a predefined path or pattern? |
||||
|
||||
###### Is hit detection implemented for both stationary and moving targets? |
||||
|
||||
###### Do the targets respawn after being hit, within a set interval? |
||||
|
||||
#### Level Design |
||||
|
||||
###### Does the firing range follow a coherent and immersive theme? |
||||
|
||||
###### Is the level lighting and environment properly set up for clarity and appeal? |
||||
|
||||
###### Does the level include a section with stationary targets? |
||||
|
||||
###### Does the level include a section with moving targets? |
||||
|
||||
###### Are there ammo pickups available for the player to replenish ammunition? |
||||
|
||||
#### Game Loop Logic |
||||
|
||||
###### Does the game loop start with a loaded weapon, ready for the player to shoot? |
||||
|
||||
###### Can the player open a pause menu? |
||||
|
||||
###### Does the pause menu include a restart button (reset score, targets, and ammo)? |
||||
|
||||
###### Does the pause menu include a return to the main menu button? |
||||
|
||||
#### Bonus |
||||
|
||||
###### +Do different targets have different speeds? |
||||
###### +Have additional weapons (e.g., shotgun, sniper rifle) with unique shooting and reload mechanics been added? |
||||
|
||||
###### +Has more advanced target AI with random movement patterns or difficulty levels been implemented? |
||||
|
||||
###### +Do the walls and floor have texture? |
||||
###### +Are there hit zones for targets (e.g., headshots) that award more points or accuracy? |
||||
|
||||
###### +Are there obstacles in front of targets? |
||||
###### +Has a timed challenge mode been added where the player must hit all targets within a set time frame? |
||||
|
After Width: | Height: | Size: 205 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 56 KiB |