<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Gambhir Sharma</title><description>Personal website and blog of Gambhir Sharma</description><link>https://gambhir.dev/</link><item><title>Building Jardinains using Amazon-q</title><link>https://gambhir.dev//posts/jardinains-amazon-q/</link><guid isPermaLink="true">https://gambhir.dev//posts/jardinains-amazon-q/</guid><description>Reliving childhood memories through code, one brick at a time</description><pubDate>Fri, 11 Jul 2025 00:00:00 GMT</pubDate><content:encoded>![background](/blog-assets/jardinains-cli-game/jardinains.png)

&gt; This is my submition for Amazon Q Build Games Challenge

### The Spark of Nostalgia

Imagine this: It’s 2008, and I’m sitting next to my father at his new Acer Aspire laptop. The room echoes with the satisfying [sounds](https://downloads.khinsider.com/game-soundtracks/album/jardinains-windows-gamerip-2001) of bouncing balls and shattering bricks as we take turns playing [Jardinains](https://www.google.com/search?q=Jardinains&amp;sourceid=chrome&amp;ie=UTF-8)!, each of us determined to beat the other’s high score. Those moments of friendly rivalry and shared laughter became some of my most treasured childhood memories.

Now, as an engineering student competing in the [Amazon Q Build Games Challenge](https://builder.aws.com/content/2y6egGcPAGQs8EwtQUM9KAONojz/build-games-challenge-build-classics-with-amazon-q-developer-cli), I knew exactly what I wanted to bring to life. Not just any game, but that game, the one that brought my father and me closer, and showed me how the simplest ideas can create the most unforgettable experiences.

### Why Jardinains? The Perfect Retro Choice

There&apos;s something beautifully pure about Jardinains! - just a ball, paddle, and colorful bricks, yet endlessly captivating. It&apos;s the kind of game anyone can learn in seconds but takes forever to master.

For this AI challenge, it was the ideal canvas - complex enough to showcase Q Developer CLI&apos;s capabilities, yet simple enough to focus on what really matters: recreating those magical gaming moments that made us fall in love with games in the first place.

### v1.0.0 with Amazon Q

I kicked off this project by installing Amazon Q Developer CLI using brew install --cask amazon-q and following the setup instructions. Once everything was ready, I integrated Amazon Q with my Alacritty terminal. For my first experiment, I simply typed q chat and gave it a straightforward prompt:
&quot;Build a CLI game using pygame like Jardinains.&quot;

To my surprise, Amazon Q delivered an impressive first draft!

![v1 of the game](/blog-assets/jardinains-cli-game/v1.png)

The initial version already included most of the features I wanted, and I was genuinely excited by how quickly things came together. Of course, there were a few areas I wanted to tweak and improve. Let’s take a closer look at the code and the enhancements I made.

##### **Mouse/Trackpad Control**

In the first iteration, the game only supported keyboard controls. I wanted to add mouse and trackpad support for a more intuitive experience. I went back to Amazon Q, requested this feature, and it generated the correct implementation right away:

```python
def update(self):
    if self.mouse_control:
        # Mouse/trackpad control
        mouse_x = pygame.mouse.get_pos()[0]
        self.x = mouse_x - self.width // 2
        self.x = max(0, min(self.x, SCREEN_WIDTH - self.width))
    else:
        # Keyboard control
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and self.x &gt; 0:
            self.x -= self.speed
        if keys[pygame.K_RIGHT] and self.x &lt; SCREEN_WIDTH - self.width:
            self.x += self.speed
```

##### **Bringing Back the Nostalgic Sound**

A huge part of Jardinains’ charm is its classic sound effects—the satisfying clink of breaking bricks and the iconic start and end jingles. To capture that nostalgia, I downloaded the original soundtracks from [this website](https://downloads.khinsider.com/game-soundtracks/album/jardinains-windows-gamerip-2001) and placed them in my project’s assets/sound/ folder.

With Amazon Q’s help, I integrated these sounds using Pygame’s mixer module and a simple SoundManager class. Now, the game plays the authentic sounds at just the right moments:

- Game Start: Plays the classic start jingle.
- Brick Break: Plays the original brick-breaking sound.
- Win or Game Over: Plays the corresponding victory or end tune.

And that’s how we completed the whole project!

---

#### **References**
- Official challenge page [Build Classics with Amazon Q Developer CLI](https://builder.aws.com/content/2y6egGcPAGQs8EwtQUM9KAONojz/build-games-challenge-build-classics-with-amazon-q-developer-cli)
- Source Code [gambhirsharma/jardinains-cli](https://github.com/gambhirsharma/jardinain-cli-game)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>Serverless Scraper with AWS SAM &amp; DynamoDB</title><link>https://gambhir.dev//posts/lazio-disco-serverless-bot/</link><guid isPermaLink="true">https://gambhir.dev//posts/lazio-disco-serverless-bot/</guid><description>Build a serverless web-scraping stack on AWS using SAM, Lambda, and DynamoDB</description><pubDate>Sun, 06 Jul 2025 00:00:00 GMT</pubDate><content:encoded>![Lazio-Serverless Diagram](https://github.com/user-attachments/assets/a34e77c0-17e4-445b-9ea1-281b02c33b82)
&gt; Lazio Serverless Architecture Diagram

## Table of Contents
1. [Context](#context)
2. [Architecture Overview](#architecture-overview)
3. [Project Setup with AWS SAM](#install-and-initialize-aws-sam)
4. [Defining Resources in template.yaml](#define-resources-in-templateyaml)
5. [Scraping Python Code](#the-scraping-python-code-lazioapppy)
6. [Build, Deploy, and Test](#build-deploy-and-test)
7. [References](#references)

### **Context**

Before this project, I manually checked the [Lazio Disco](https://laziodisco.it/) website (which lists scholarships for students in Rome) for updates, a tedious process. I first automated it with a scraper on an EC2 instance, logging updates and sending Telegram notifications. Later, I rebuilt it using AWS SAM: now, the scraper runs as a Lambda function, stores results in DynamoDB, and is triggered automatically by EventBridge. This serverless setup is much more efficient and easier to maintain.

---

### Architecture Overview

- `Trigger`: An AWS EventBridge (formerly CloudWatch Events) rule acts as a scheduler, triggering the workflow every 30 minutes. This eliminates the need for manual intervention or a constantly running server.

- `Compute`:
The Lambda function **LazioBotFunction** is responsible for:
    - **Fetching Credentials**: Securely retrieving login credentials from AWS Secrets Manager.
    - **Web Scraping**: Logging into the LazioDisco website and fetching the target message page using Python’s requests and BeautifulSoup libraries.
    - **Change Detection**: Parsing the HTML content to detect new or updated messages.
    - **Logging**: Recording the outcome of each run (update found, no update, or error) for monitoring and auditing.

- `Storage`: Store output in DynamoDB.

- `Notification`: Send the notifications to the users using [Telegram api](https://core.telegram.org/)

---

### Install and Initialize AWS SAM

AWS [SAM](https://github.com/aws/serverless-application-model) (Serverless Application Model) is an open-source framework that makes it easy to define, build, and deploy serverless applications on AWS using simple YAML templates. It streamlines local development, testing, and deployment of resources like Lambda and DynamoDB.

We use SAM here to quickly set up and manage all the serverless components for our scraper in a repeatable, efficient way.

```bash
sam init --runtime python3.9 --name serverless-scraper
cd serverless-scraper
```

---

### Define Resources in `template.yaml`

- Lambda function `LazioBotFunction`
  -  IAM roles granting Lambda read/write access
  - Events: schedule or API Gateway trigger
  - Log table name as env

- DynamoDB table  `LazioDiscoLogs`

Example snippet:

```yaml
# ...
Globals:
  Function:
    Timeout: 10
    MemorySize: 128

Resources:
  LazioBotFunction:
    Type: AWS::Serverless::Function
    Properties:
      # ...
      Events:
        HelloWorld:
          Type: ScheduleV2
          Properties:
            ScheduleExpression: rate(30 minutes)
    Policies:
    # ...
    Environment:
      Variables:
        LOG_TABLE_NAME: !Ref LazioDiscoLogs

  # properties for the table
  LazioDiscoLogs:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: LazioDiscoLogs
      # ...
```

See the complete [template.yaml](https://github.com/gambhirsharma/lazio-disco-bot/blob/main/lazio-serverless/template.yaml)

---

#### The Scraping Python Code `lazio/app.py`

Now, let’s look at the core Python code running in our Lambda function. It logs into the Lazio Disco site, checks for updates, and sends notifications if anything changes.

**Secure Credential Handling**

Since the site requires login, we use AWS Secrets Manager to securely fetch credentials instead of hardcoding them. The get_secret() function handles this process.

```python
def get_secret():
    secret_name = &quot;Lazio_disco_bot&quot;
    region_name = &quot;eu-south-1&quot;
    session = boto3.session.Session()
    client = session.client(
        service_name=&apos;secretsmanager&apos;,
        region_name=region_name
    )
    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        raise e

    secret = get_secret_value_response[&apos;SecretString&apos;]
    return secret

secret = json.loads(get_secret())
```

`boto3` Integration: We use the boto3 library, the AWS SDK for Python, to interact with AWS services.

`secretsmanager Client`: A client is created for the secretsmanager service in our specified region_name.

&gt; Never hardcode secrets in your code.

**Logging and Notifications**

```python
def send_message(mess):
    url = f&quot;https://api.telegram.org/bot{TOKEN}/sendMessage?chat_id={chat_id}&amp;text={mess}&quot;
    r = requests.get(url)
    # print(r.json())

def save_log(status, timestamp):

    dynamodb = boto3.resource(&apos;dynamodb&apos;)
    table = dynamodb.Table(&apos;LazioDiscoLogs&apos;)

    if not timestamp:
        timestamp = datetime.utcnow().isoformat()

    try:
        table.put_item(
            Item={
                &apos;LogId&apos;: f&quot;log-{datetime.utcnow().strftime(&apos;%Y%m%d%H%M%S&apos;)}&quot;,
                &apos;Status&apos;: status,
                &apos;Timestamp&apos;: timestamp
            }
        )
        print(&quot;Log saved successfully.&quot;)
    except Exception as e:
        print(f&quot;Error saving log: {e}&quot;)
```

To keep track of our scraper&apos;s activity and notify us of any changes, we have two helper functions:

- `send_message(mess):` This function uses the Telegram Bot API to send messages directly to a specified chat. If an update is detected or an error occurs, this function will send a notification.
- `save_log(status, timestamp)`: This is crucial for monitoring. It interacts with our DynamoDB table (LazioDiscoLogs) to store a record of each run. It logs the Status (e.g., &apos;New Update!!&apos;, &apos;No Update&apos;, &apos;Error&apos;) and the Timestamp of when the event occurred. This provides a persistent history of our scraper&apos;s operations.

**The Core Logic `lambda_handler`**

The lambda_handler function is the entry point for our AWS Lambda. When EventBridge triggers our Lambda, this function executes the scraping logic.

```python
def lambda_handler(event, context):
    with requests.session() as s:
        try:
            # 1. Login to the website
            s.post(login_url, data=payload)

            # 2. Access the target message page
            m = s.get(message_url)
            message_page = BeautifulSoup(m.content, &apos;html.parser&apos;)

            # 3. Extract and compare content
            card_titles = message_page.find_all(&quot;h5&quot;, class_=&quot;card-title&quot;, recursive=True)
            card_texts = message_page.find_all(&quot;p&quot;, class_=&quot;card-text&quot;)

            # Iterate through all detected cards to check for new information
            for title, text in zip(card_titles, card_texts):
                card_title_text = title.get_text(strip=True)
                card_text_content = text.get_text(strip=True)

                # Check if the content deviates from known &quot;fixed&quot; content
                if card_title_text != fixed_card_title or fixed_card_text not in card_text_content:
                    print(&quot;Update detected!&quot;)
                    status_message = &apos;New Update!!&apos;
                    save_log(status_message, None)
                    send_message(&quot;Check website there is some update!!&quot;)
                    return {
                        &apos;statusCode&apos;: 200,
                        &apos;body&apos;: status_message,
                    }
            else:
                # If loop completes without finding an update
                print(&quot;No update detected.&quot;)
                status_message = &apos;No Update&apos;
                save_log(status_message, None)
                return {
                    &apos;statusCode&apos;: 200,
                    &apos;body&apos;: status_message
                }

        except Exception as e:
            # Handle any unexpected errors during the scraping process
            print(f&quot;An error occurred: {e}&quot;)
            send_message(&quot;Error in bot&quot;)
            save_log(&quot;Error&quot;, None)
            return {
                &apos;statusCode&apos;: 500,
                &apos;body&apos;: &apos;An error occurred during scraping.&apos;
            }
```

See the complete [lazio/app.py](https://github.com/gambhirsharma/lazio-disco-bot/blob/main/lazio-serverless/lazio/app.py)

---

####  Build, Deploy, and Test

**Install the Prerequisites**
- **AWS CLI**: For configuring your AWS credentials.
- **AWS SAM CLI**: For building and managing your serverless app.
- **Docker** (optional, but recommended): For local testing that closely matches the AWS Lambda environment.

```bash
# validate you template.yaml
sam validate --lint

# build your sam project
sam build
```

To test the function is working correctly you can use invoke the serverless function locally by `sam local invoke` command

```bash
aws lambda invoke --endpoint-url http://127.0.0.1:3001 --function-name LazioBotFunction out.json
```

Before deploying your code to AWS, make sure you have the AWS CLI installed and configure your user with the appropriate IAM roles.
While you can use a .env file to store credentials for local development, always use aws configure to set up your AWS credentials securely for deployment.
The best way to do this is using `.env` to store the credentials and then source them according to the use case.

- You need to `sam build` for every change you make in the code.
- check if `template.yaml` is correct by using `sam validate --lint`
- `sam local` to test everything locally
- `sam local invoke` to test a single function locally
- `sam local start-lambda` to deploy the code to AWS

- Use this command to invoke the function when it&apos;s running locally using `sam local start-lambda`:
```bash
aws lambda invoke --endpoint-url http://127.0.0.1:3001 --function-name LazioBotFunction out.json
```

And, now you have tested you code locally and satisfied by the outputs then you can deploy it to AWS infra using

```bash
# --guided if it&apos;s your first time, this will prompt you for stack name, region, and other settings.
sam deploy --guided

sam deploy
```

#### References

- Project code at [gambhirsharma/lazio-disco-bot](https://github.com/gambhirsharma/lazio-disco-bot)

- AWS SAM  [docs](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html)

- AWS SAM does not support the .env convention. Read this [blog](https://blowstack.com/blog/how-to-use-environmental-variables-in-aws-sam) for more info

Thanks for reading, Ciao Ciao</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>Dev Coffee | CSS</title><link>https://gambhir.dev//posts/notes/dev_coffee/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/dev_coffee/</guid><description>This is a submission for DEV Challenge v24.03.20, CSS Art, Favorite Snack.</description><pubDate>Sat, 30 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [Dev Coffee](https://dev.to/gambhirsharma/dev-coffee-2c8o)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>One Byte Explainer, IndexedDB</title><link>https://gambhir.dev//posts/notes/one_byte_explainer_indexeddb/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/one_byte_explainer_indexeddb/</guid><description>Learn IndexDB</description><pubDate>Sat, 30 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [one-byte-explainer-index DB](https://dev.to/gambhirsharma/one-byte-explainer-indexeddb-40h3)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>5 terminal apps that I can&apos;t live without | Twitter</title><link>https://gambhir.dev//posts/notes/top_5_cli_app/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/top_5_cli_app/</guid><description>I use these apps daily &amp; it has 100x my Productivity 🙌</description><pubDate>Thu, 21 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [5 Terminal app](https://x.com/gambhir_sharma/status/1770669881001755013)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>HTMX the React.js killer</title><link>https://gambhir.dev//posts/talks/post-1/</link><guid isPermaLink="true">https://gambhir.dev//posts/talks/post-1/</guid><description>HTMX talk at GDG Gauhati, India</description><pubDate>Wed, 27 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;img src=&quot;/images/talk-assets/htmx-images-0.jpg&quot; alt=&quot;&quot; width=&quot;640&quot; height=&quot;360&quot; /&gt;

**Presented at GDG Guwahati, India**

&lt;p&gt;
    Download the &lt;a href=&quot;/htmx.pptx&quot; download=&quot;htmx.pptx&quot;&gt;htmx.pptx&lt;/a&gt;
&lt;/p&gt;</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>Generics in TypeScript</title><link>https://gambhir.dev//posts/notes/generics_in_typescript/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/generics_in_typescript/</guid><description>Learn Generics in TypeScript</description><pubDate>Sat, 09 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [Generics in TS](https://x.com/gambhir_sharma/status/1733335875830890755)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>Closures in JavaScript</title><link>https://gambhir.dev//posts/notes/closures_in_javascript/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/closures_in_javascript/</guid><description>Understanding Closures in JavaScript</description><pubDate>Sun, 12 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [Closures in JS](https://x.com/gambhir_sharma/status/1723548841633288345)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>Arrow Functions in JavaScript</title><link>https://gambhir.dev//posts/notes/arrow_functions_in_javascript/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/arrow_functions_in_javascript/</guid><description>Understanding Arrow Functions in JavaScript</description><pubDate>Fri, 10 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [Arrow Functions JS](https://x.com/gambhir_sharma/status/1722824078094930274)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>Interfaces VS Types</title><link>https://gambhir.dev//posts/notes/interfaces_vs_types/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/interfaces_vs_types/</guid><description>Difference between Interfaces &amp; Types in TypeScript</description><pubDate>Sun, 05 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&gt; Original Tweet: [Link](https://x.com/gambhir_sharma/status/1721014576165048449)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>AI is changing the way we write code.</title><link>https://gambhir.dev//posts/notes/ai_is_changing_the_way_we_write_code/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/ai_is_changing_the_way_we_write_code/</guid><description>Will AI replace programmers?</description><pubDate>Sat, 10 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&gt; Original Post [AI is changing the way we write code.](https://gambhir.hashnode.dev/ai-is-changing-the-way-we-write-code)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>JavaScript Topics to Master Before Learning React | Twitter</title><link>https://gambhir.dev//posts/notes/javascript_topics_to_master_before_learning_react/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/javascript_topics_to_master_before_learning_react/</guid><description>Learning JavaScript first lays the foundation for mastering React, providing a solid understanding of core concepts, making the React journey smoother and more rewarding</description><pubDate>Fri, 11 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [JavaScript topics](https://x.com/gambhir_sharma/status/1723186507970646411)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>Modifying data in C++</title><link>https://gambhir.dev//posts/notes/modifying_data_in_cpp/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/modifying_data_in_cpp/</guid><description>modify data in cpp</description><pubDate>Fri, 14 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [Modifying data in C++](https://gambhir.hashnode.dev/modifying-data-in-cpp)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item><item><title>Penguin in CSS 🐧🎨</title><link>https://gambhir.dev//posts/notes/penguin_in_css/</link><guid isPermaLink="true">https://gambhir.dev//posts/notes/penguin_in_css/</guid><description>Learn CSS</description><pubDate>Wed, 15 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&gt; Original post: [Penguin in CSS](https://gambhir.hashnode.dev/penguin-in-css)</content:encoded><author>Gambhir Sharma ⚡ &lt;hey@gambhir.dev&gt;</author></item></channel></rss>