Skip to content

Commit

Permalink
Add text notifications from server. Notifications are displayed as ve…
Browse files Browse the repository at this point in the history
…rtically scrolling text at the beginning of level 1. Server components include a DynamoDB table, AWS Lambda Function, and CloudFront distribution rule.
  • Loading branch information
ebarlas committed Sep 3, 2024
1 parent 64d0806 commit 4ebea60
Show file tree
Hide file tree
Showing 114 changed files with 622 additions and 19 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ Testing/
6x/
7x/
9x/
10x/
10x/
tot_chart*.png
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,32 @@ GET /scores/alltime
GET /scores/today
```

# Notifications

At the start of each game, the player is shown a vertically scrolling
text message notification. Notifications are intended to provide
players with news and updates about the game.

The message is obtained from the notification web server in the background
when the application launches in the same way that high scores are obtained.

The notification server protocol allows for a distinct message given a major and minor
version.

```
GET /notifications/{major}/{minor}
```

The notification server is a [Python program](server/notification_lambda_function.py)
that is deployed as an AWS Lambda Function attached to a CloudFront distribution
for www.trippinontubs.com.

Notifications are stored in an Amazon DynamoDB table.

Scores are organized in the database by game version.
The table partition key column is an integer named `major` and the
sort key column is an integer named `minor`.

# Journal Files

Remote logging and high score events outlined above are staged in a local journal
Expand Down
54 changes: 54 additions & 0 deletions server/notification_lambda_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import re

import boto3

client = boto3.client('dynamodb', region_name='us-west-2')


def load_notification(major, minor):
return client.get_item(
TableName='trippin-notifications',
Key={
'major': {'N': str(major)},
'minor': {'N': str(minor)}
}
).get('Item')


def to_response(body):
return {
'status': '200',
'statusDescription': 'OK',
'headers': {
'content-type': [
{
'key': 'Content-Type',
'value': 'text/plain'
}
]
},
'body': body
}


def not_found_response():
return {
'status': '404',
'statusDescription': 'Not Found',
'body': 'Not Found'
}


def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']

print(f'method={request["method"]}, path={request["uri"]}')

rex = '^/notifications/([0-9]+)/([0-9]+)$'

if request['method'] == 'GET' and (m := re.match(rex, request['uri'])):
major, minor = m.group(1), m.group(2)
if item := load_notification(major, minor):
return to_response(item['notification']['S'])

return not_found_response()
Binary file modified sprites/alpha/1x/alpha_39.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha/1x/alpha_40.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha/1x/alpha_41.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha/1x/alpha_42.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha/1x/alpha_43.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified sprites/alpha/8x/alpha_39.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha/8x/alpha_40.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha/8x/alpha_41.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha/8x/alpha_42.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha/8x/alpha_43.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion sprites/alpha/alpha.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"frames": 40,
"frames": 44,
"duration": 80,
"hitBox": {
"x": 0,
Expand Down
Binary file added sprites/alpha_small/1x/alpha_small_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_14.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_15.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_17.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_18.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_19.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_20.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_21.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sprites/alpha_small/1x/alpha_small_22.png
Binary file added sprites/alpha_small/1x/alpha_small_23.png
Binary file added sprites/alpha_small/1x/alpha_small_24.png
Binary file added sprites/alpha_small/1x/alpha_small_25.png
Binary file added sprites/alpha_small/1x/alpha_small_26.png
Binary file added sprites/alpha_small/1x/alpha_small_27.png
Binary file added sprites/alpha_small/1x/alpha_small_28.png
Binary file added sprites/alpha_small/1x/alpha_small_29.png
Binary file added sprites/alpha_small/1x/alpha_small_3.png
Binary file added sprites/alpha_small/1x/alpha_small_30.png
Binary file added sprites/alpha_small/1x/alpha_small_31.png
Binary file added sprites/alpha_small/1x/alpha_small_32.png
Binary file added sprites/alpha_small/1x/alpha_small_33.png
Binary file added sprites/alpha_small/1x/alpha_small_34.png
Binary file added sprites/alpha_small/1x/alpha_small_35.png
Binary file added sprites/alpha_small/1x/alpha_small_36.png
Binary file added sprites/alpha_small/1x/alpha_small_37.png
Binary file added sprites/alpha_small/1x/alpha_small_38.png
Binary file added sprites/alpha_small/1x/alpha_small_39.png
Binary file added sprites/alpha_small/1x/alpha_small_4.png
Binary file added sprites/alpha_small/1x/alpha_small_40.png
Binary file added sprites/alpha_small/1x/alpha_small_41.png
Binary file added sprites/alpha_small/1x/alpha_small_42.png
Binary file added sprites/alpha_small/1x/alpha_small_43.png
Binary file added sprites/alpha_small/1x/alpha_small_5.png
Binary file added sprites/alpha_small/1x/alpha_small_6.png
Binary file added sprites/alpha_small/1x/alpha_small_7.png
Binary file added sprites/alpha_small/1x/alpha_small_8.png
Binary file added sprites/alpha_small/1x/alpha_small_9.png
Binary file added sprites/alpha_small/8x/alpha_small_0.png
Binary file added sprites/alpha_small/8x/alpha_small_1.png
Binary file added sprites/alpha_small/8x/alpha_small_10.png
Binary file added sprites/alpha_small/8x/alpha_small_11.png
Binary file added sprites/alpha_small/8x/alpha_small_12.png
Binary file added sprites/alpha_small/8x/alpha_small_13.png
Binary file added sprites/alpha_small/8x/alpha_small_14.png
Binary file added sprites/alpha_small/8x/alpha_small_15.png
Binary file added sprites/alpha_small/8x/alpha_small_16.png
Binary file added sprites/alpha_small/8x/alpha_small_17.png
Binary file added sprites/alpha_small/8x/alpha_small_18.png
Binary file added sprites/alpha_small/8x/alpha_small_19.png
Binary file added sprites/alpha_small/8x/alpha_small_2.png
Binary file added sprites/alpha_small/8x/alpha_small_20.png
Binary file added sprites/alpha_small/8x/alpha_small_21.png
Binary file added sprites/alpha_small/8x/alpha_small_22.png
Binary file added sprites/alpha_small/8x/alpha_small_23.png
Binary file added sprites/alpha_small/8x/alpha_small_24.png
Binary file added sprites/alpha_small/8x/alpha_small_25.png
Binary file added sprites/alpha_small/8x/alpha_small_26.png
Binary file added sprites/alpha_small/8x/alpha_small_27.png
Binary file added sprites/alpha_small/8x/alpha_small_28.png
Binary file added sprites/alpha_small/8x/alpha_small_29.png
Binary file added sprites/alpha_small/8x/alpha_small_3.png
Binary file added sprites/alpha_small/8x/alpha_small_30.png
Binary file added sprites/alpha_small/8x/alpha_small_31.png
Binary file added sprites/alpha_small/8x/alpha_small_32.png
Binary file added sprites/alpha_small/8x/alpha_small_33.png
Binary file added sprites/alpha_small/8x/alpha_small_34.png
Binary file added sprites/alpha_small/8x/alpha_small_35.png
Binary file added sprites/alpha_small/8x/alpha_small_36.png
Binary file added sprites/alpha_small/8x/alpha_small_37.png
Binary file added sprites/alpha_small/8x/alpha_small_38.png
Binary file added sprites/alpha_small/8x/alpha_small_39.png
Binary file added sprites/alpha_small/8x/alpha_small_4.png
Binary file added sprites/alpha_small/8x/alpha_small_40.png
Binary file added sprites/alpha_small/8x/alpha_small_41.png
Binary file added sprites/alpha_small/8x/alpha_small_42.png
Binary file added sprites/alpha_small/8x/alpha_small_43.png
Binary file added sprites/alpha_small/8x/alpha_small_5.png
Binary file added sprites/alpha_small/8x/alpha_small_6.png
Binary file added sprites/alpha_small/8x/alpha_small_7.png
Binary file added sprites/alpha_small/8x/alpha_small_8.png
Binary file added sprites/alpha_small/8x/alpha_small_9.png
10 changes: 10 additions & 0 deletions sprites/alpha_small/alpha_small.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"frames": 44,
"duration": 80,
"hitBox": {
"x": 0,
"y": 0,
"w": 7,
"h": 9
}
}
26 changes: 20 additions & 6 deletions src/game/Game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,17 @@ namespace trippin {
};

void trippin::Game::initDbSynchronizer() {
int version = configuration.version.major;
int major = configuration.version.major;
int minor = configuration.version.minor;
transport = std::make_unique<Transport>(
configuration.db.host,
configuration.db.port,
version,
major,
minor,
configuration.highScores);
stagingArea = std::make_unique<StagingArea>(*transport);
stagingArea->start();
myScores = std::make_unique<MyScores>(version, 10);
myScores = std::make_unique<MyScores>(major, 10);
auto scoreAddCallback = [m = myScores.get()](const Score &s) {
m->addScore(s);
};
Expand All @@ -106,15 +108,15 @@ void trippin::Game::initDbSynchronizer() {
return result == AddResult::success || result == AddResult::clientError;
};
int mdcs = configuration.maxDispatchChannelSize;
scoreDb = std::make_unique<Db<Score>>("scores", version, mdcs, scoreAddCallback, scoreDispatchFn);
scoreDb = std::make_unique<Db<Score>>("scores", major, mdcs, scoreAddCallback, scoreDispatchFn);
scoreDb->start();
auto logAddCallback = [](const LogEvent &e) {};
auto logDispatchFn = [t = transport.get()](const LogEvent &e) {
auto result = t->addLogEvent(e);
SDL_Log("add log event attempted, index=%d, result=%s", e.index, toString(result));
return result == AddResult::success || result == AddResult::clientError;
};
logDb = std::make_unique<Db<LogEvent>>("logs", version, mdcs, logAddCallback, logDispatchFn);
logDb = std::make_unique<Db<LogEvent>>("logs", major, mdcs, logAddCallback, logDispatchFn);
logDb->start();
}

Expand Down Expand Up @@ -220,6 +222,12 @@ void trippin::Game::initOverlays() {
spriteManager->get("tap_score_exit"), renderClock, 750, 5'000);
speedUpOverlay = std::make_unique<SpeedUpOverlay>(rendererSize, configuration.meterMargin, *spriteManager,
renderClock);
marquee = std::make_unique<Marquee>(
scrollPxPerMs,
windowSize,
spriteManager->get("alpha_small"),
renderClock,
configuration.meterMargin);
}

void trippin::Game::initClock() {
Expand Down Expand Up @@ -430,6 +438,7 @@ void trippin::Game::render() {
trainingCompletedOverlay->render();
} else if (state == State::PLAYING) {
playingExitOverlay->render();
marquee->render();
}

SDL_RenderPresent(sdlSystem->getRenderer());
Expand Down Expand Up @@ -463,12 +472,17 @@ void trippin::Game::handle(UserInput::Event &event) {
inputEvents.clear();
state = State::PLAYING;
playingExitOverlay->reset();
auto notification = stagingArea->getNotification();
if (!notification.empty()) {
marquee->start(notification);
}
logger->log(std::string("op=state_change")
+ ", prev=START_MENU"
+ ", next=PLAYING"
+ ", id=" + std::to_string(gameId)
+ ", tps=" + formatTps()
+ ", fps=" + formatFps());
+ ", fps=" + formatFps()
+ ", notification=" + (notification.empty() ? "no" : "yes"));
advanceLevel();
} else if (titleMenu->contains(1, event.touchPoint)) {
score = 0;
Expand Down
2 changes: 2 additions & 0 deletions src/game/Game.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "ui/SimpleOverlay.h"
#include "ui/ExitOverlay.h"
#include "ui/SpeedUpOverlay.h"
#include "ui/Marquee.h"
#include "net/Transport.h"
#include "net/StagingArea.h"
#include "SdlSystem.h"
Expand Down Expand Up @@ -91,6 +92,7 @@ namespace trippin {
std::unique_ptr<SimpleOverlay> gameOverOverlay;
std::unique_ptr<SimpleOverlay> levelsCompletedOverlay;
std::unique_ptr<SimpleOverlay> trainingCompletedOverlay;
std::unique_ptr<Marquee> marquee;
std::unique_ptr<StagingArea> stagingArea;
std::unique_ptr<MyScores> myScores;
std::unique_ptr<Transport> transport;
Expand Down
10 changes: 10 additions & 0 deletions src/net/StagingArea.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ std::vector<trippin::Score> trippin::StagingArea::getTopScores(int limit) const
return combine(topScores, addedScores, limit);
}

std::string trippin::StagingArea::getNotification() const {
std::lock_guard<std::mutex> lock(mutex);
return notification;
}

std::vector<trippin::Score> trippin::StagingArea::combine(
const std::vector<Score> &sorted,
const std::vector<Score> &unsorted,
Expand Down Expand Up @@ -73,6 +78,11 @@ void trippin::StagingArea::run() {
setTodayScores(today.scores);
SDL_Log("set today scores in staging area, count=%lu", today.scores.size());
}
auto n = transport.getNotification();
if (!n.empty()) {
notification = n;
SDL_Log("set notification in staging area");
}
std::this_thread::sleep_for(std::chrono::minutes(1));
}
}
2 changes: 2 additions & 0 deletions src/net/StagingArea.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace trippin {
void addScore(const Score &score);
std::vector<Score> getTodayScores(int limit) const;
std::vector<Score> getTopScores(int limit) const;
std::string getNotification() const;
bool bothSet() const;
void start();
private:
Expand All @@ -23,6 +24,7 @@ namespace trippin {
std::vector<Score> addedScores;
std::vector<Score> todayScores;
std::vector<Score> topScores;
std::string notification;
bool todaySet{};
bool topSet{};
static std::vector<Score> combine(
Expand Down
67 changes: 62 additions & 5 deletions src/net/Transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#include "Tcp.h"
#include "SDL_net.h"

trippin::Transport::Transport(std::string host, int port, int version, int limit)
: host(std::move(host)), port(port), version(version), limit(limit) {
trippin::Transport::Transport(std::string host, int port, int major, int minor, int limit)
: host(std::move(host)), port(port), major(major), minor(minor), limit(limit) {
}

trippin::Transport::Scores trippin::Transport::topScores() const {
Expand All @@ -24,7 +24,7 @@ trippin::Transport::Scores trippin::Transport::sendRequest(const std::string &ur

std::stringstream req;
req << "GET " << uri
<< "?version=" << version
<< "?version=" << major
<< "&limit=" << limit
<< "&compression=diff"
<< " HTTP/1.0\r\n"
Expand Down Expand Up @@ -62,7 +62,7 @@ trippin::AddResult trippin::Transport::addScore(const Score &score) const {
}

nlohmann::json j = score.to_json();
j["version"] = version;
j["version"] = major;

std::stringstream ss;
ss << score.id << score.game;
Expand Down Expand Up @@ -131,4 +131,61 @@ trippin::AddResult trippin::Transport::classifyResponse(const std::string &respo
return AddResult::clientError;
}
return AddResult::other;
}
}

std::string trippin::Transport::getNotification() {
Tcp tcp(host, port);
auto sock = tcp.get();
if (sock == nullptr) {
return {};
}

std::stringstream req;
req << "GET " << "/notifications/" << major << "/" << minor
<< " HTTP/1.0\r\n"
<< "Host: " << host << "\r\n"
<< "Accept: */*\r\n\r\n";

tcp.send(req.str());
std::string response = tcp.receive(1'024 * 4);

auto f = response.find("\r\n\r\n");
if (f == std::string::npos) {
SDL_Log("response parse delimiter not found");
return {};
}

if (response.find(" 200 OK") == std::string::npos) {
SDL_Log("error response received from notification request");
return {};
}

auto s = response.substr(f + 4);
if (validNotification(s)) {
return s;
}

SDL_Log("invalid characters in notification string");
return {};
}

bool trippin::Transport::validNotification(std::string &notification) {
for (auto &c: notification) {
c = static_cast<char>(std::toupper(c)); // normalize notification string to uppercase
if (!validNotificationChar(c)) {
return false;
}
}
return true;
}

bool trippin::Transport::validNotificationChar(char c) {
return (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '.'
|| c == ','
|| c == '!'
|| c == ' '
|| c == '\''
|| c == '\n';
}
8 changes: 6 additions & 2 deletions src/net/Transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@ namespace trippin {
bool ok{};
};

Transport(std::string host, int port, int version, int limit);
Transport(std::string host, int port, int major, int minor, int limit);
[[nodiscard]] AddResult addScore(const Score &score) const;
[[nodiscard]] AddResult addLogEvent(const LogEvent &event) const;
[[nodiscard]] Scores topScores() const;
[[nodiscard]] Scores todayScores() const;
[[nodiscard]] std::string getNotification();
private:
const std::string host;
const int port;
const int version;
const int major;
const int minor;
const int limit;
static AddResult classifyResponse(const std::string &response);
[[nodiscard]] Scores sendRequest(const std::string &uri) const;
static bool validNotification(std::string &notification);
static bool validNotificationChar(char c);
};
}

Expand Down
Loading

0 comments on commit 4ebea60

Please sign in to comment.