Skip to content

Commit

Permalink
v1.1.0, level searching with filters & ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
sarahkittyy committed Oct 30, 2022
1 parent 1f58712 commit 2325bf3
Show file tree
Hide file tree
Showing 20 changed files with 563 additions and 96 deletions.
Binary file added assets/gifs/loading-gif.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions backend/controllers/Level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
validateOrReject,
} from 'class-validator';

const SortableFields = ['id', 'createdAt', 'updatedAt', 'name'] as const;
const SortableFields = ['id', 'downloads', 'createdAt', 'updatedAt', 'title'] as const;
const SortDirections = ['asc', 'desc'] as const;

/* options for searching through levels */
Expand Down Expand Up @@ -130,7 +130,7 @@ export default class Level {
: [];

const levels = await prisma.level.findMany({
take: opts.limit,
take: opts.limit + 1,
...(opts.cursor && {
skip: opts.cursor < 1 ? 0 : 1,
cursor: {
Expand All @@ -148,7 +148,7 @@ export default class Level {
},
});

const lastLevel = levels?.[levels.length - 1];
const lastLevel = levels?.[levels.length - 2];

const ret: ISearchResponse = {
levels: levels.map(
Expand All @@ -163,7 +163,7 @@ export default class Level {
downloads: lvl.downloads,
})
),
cursor: lastLevel?.id ?? 0,
cursor: lastLevel?.id && levels.length > opts.limit ? lastLevel.id : -1,
};

return res.status(200).send(ret);
Expand Down
2 changes: 1 addition & 1 deletion backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ app.post('/signup', Auth.CreateAccount);
app.post('/login', Auth.Login);

app.post('/level/upload/:confirm?', requireAuth(0), Level.upload);
app.get('/level/search', Level.search);
app.post('/level/search', Level.search);
app.get('/level/:id/ping-download', Level.downloadPing);
app.get('/level/:id', Level.getById);

Expand Down
74 changes: 74 additions & 0 deletions game/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,67 @@ api &api::get() {
return instance;
}

std::future<api::search_response> api::search_levels(api::search_query q) {
return std::async([this, q]() -> api::search_response {
try {
nlohmann::json body;
body["cursor"] = q.cursor;
body["limit"] = q.limit;
if (q.query.length() != 0) body["query"] = q.query;
body["matchTitle"] = q.matchTitle;
body["matchDescription"] = q.matchDescription;
body["sortBy"] = q.sortBy;
body["order"] = q.order;

if (auto res = m_cli.Post("/level/search", body.dump(), "application/json")) {
nlohmann::json result = nlohmann::json::parse(res->body);
if (res->status == 200) {
search_response rsp;
rsp.cursor = result["cursor"].get<int>();
rsp.success = true;
for (auto &level_json : result["levels"]) {
api::level l{
.id = level_json["id"].get<int>(),
.author = level_json["author"],
.code = level_json["code"],
.title = level_json["title"],
.description = level_json["description"],
.createdAt = level_json["createdAt"].get<std::time_t>(),
.updatedAt = level_json["updatedAt"].get<std::time_t>(),
.downloads = level_json["downloads"].get<int>()
};
rsp.levels.push_back(l);
}
return rsp;
} else {
if (result.contains("error")) {
throw std::runtime_error(result["error"]);
} else {
throw "Unknown server error";
}
}
} else {
throw "Could not connect to server";
}
} catch (const char *e) {
return {
.success = false,
.error = e
};
} catch (std::exception &e) {
return {
.success = false,
.error = e.what()
};
} catch (...) {
return {
.success = false,
.error = "Unknown error."
};
}
});
}

void api::ping_download(int id) {
std::thread([this, id]() -> void {
try {
Expand Down Expand Up @@ -157,3 +218,16 @@ std::future<api::response> api::upload_level(::level l, const char *title, const
}
});
}

bool api::search_query::operator==(const search_query &other) const {
return limit == other.limit &&
query == other.query &&
matchTitle == other.matchTitle &&
matchDescription == other.matchDescription &&
sortBy == other.sortBy &&
order == other.order;
}

bool api::search_query::operator!=(const search_query &other) const {
return !(other == *this);
}
22 changes: 22 additions & 0 deletions game/api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,31 @@ class api {
std::optional<api::level> level;
};

struct search_query {
int cursor;
int limit;
std::string query;
bool matchTitle;
bool matchDescription;
std::string sortBy;
std::string order;
bool operator==(const search_query& other) const;
bool operator!=(const search_query& other) const;
};

struct search_response {
bool success;
std::optional<std::string> error;

std::vector<api::level> levels;
int cursor;
};

std::future<response> upload_level(::level l, const char* title, const char* description, bool override = false);
std::future<response> download_level(int id);

std::future<search_response> search_levels(search_query q);

// sends a download ping to be run when we fetch a level
void ping_download(int id);

Expand Down
5 changes: 5 additions & 0 deletions game/gui/gif.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace ImGui {

Gif::Gif(sf::Texture& gif_atlas, int frame_ct, sf::Vector2i frame_sz, int fps)
: m_atlas(gif_atlas),
m_img_sz(frame_sz),
m_frame(0),
m_frame_ct(frame_ct) {
m_per_frame = sf::seconds(1.f / float(fps));
Expand All @@ -26,6 +27,10 @@ void Gif::update() {
}
}

sf::Vector2i Gif::image_size() const {
return m_img_sz;
}

void Gif::draw(sf::Vector2i sz) {
int x = m_frame % m_atlas_sz.x;
int y = m_frame / m_atlas_sz.x;
Expand Down
11 changes: 7 additions & 4 deletions game/gui/gif.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ class Gif {
void update();
void draw(sf::Vector2i sz);

sf::Vector2i image_size() const;

private:
sf::Texture& m_atlas; // texture atlas
sf::Vector2f m_sz; // size of one frame, from 0-1
sf::Clock m_c; // clock for updating frames
sf::Time m_per_frame; // time per frame
sf::Texture& m_atlas; // texture atlas
sf::Vector2f m_sz; // size of one frame, from 0-1
sf::Vector2i m_img_sz; // size of one frame in pixels
sf::Clock m_c; // clock for updating frames
sf::Time m_per_frame; // time per frame

int m_frame;
int m_frame_ct;
Expand Down
49 changes: 49 additions & 0 deletions game/gui/level_card.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "level_card.hpp"

#include "gui/image_text_button.hpp"
#include "resource.hpp"

namespace ImGui {

ApiLevelTile::ApiLevelTile(api::level lvl, sf::Color bg)
: m_bg(bg),
m_lvl(lvl),
m_tmap(resource::get().tex("assets/tiles.png"), 32, 32, 16) {
m_map_tex.create(256, 256);
m_tmap.load(m_lvl.code);
m_map_tex.setView(sf::View(sf::FloatRect(0, 0, 512, 512)));
m_map_tex.setSmooth(true);
m_map_tex.clear(m_bg);
m_map_tex.draw(m_tmap);
m_map_tex.display();
}

void ApiLevelTile::imdraw(bool* download) {
if (download) {
*download = false;
}
ImGui::PushStyleColor(ImGuiCol_Text, 0xFB8CABFF);
ImGui::Text("%s (%s) #%d", m_lvl.title.c_str(), m_lvl.author.c_str(), m_lvl.id);
ImGui::PopStyleColor();
ImGui::Image(m_map_tex);
std::string download_label = "Download (" + std::to_string(m_lvl.downloads) + ")";
if (ImGui::ImageButtonWithText(resource::get().imtex("assets/gui/download.png"), download_label.c_str())) {
if (download) {
*download = true;
}
}
char date_fmt[100];
tm* date_tm = std::localtime(&m_lvl.createdAt);
std::strftime(date_fmt, 100, "%D %r", date_tm);
ImGui::TextWrapped("Created: %s", date_fmt);
if (m_lvl.createdAt != m_lvl.updatedAt) {
date_tm = std::localtime(&m_lvl.updatedAt);
std::strftime(date_fmt, 100, "%D %r", date_tm);
ImGui::TextWrapped("Updated: %s", date_fmt);
}
ImGui::PushStyleColor(ImGuiCol_Text, 0x48c8f0FF);
ImGui::TextWrapped("%s", m_lvl.description.c_str());
ImGui::PopStyleColor();
}

}
28 changes: 28 additions & 0 deletions game/gui/level_card.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <SFML/Graphics.hpp>

#include "imgui-SFML.h"
#include "imgui.h"
#include "imgui_internal.h"

#include "api.hpp"
#include "tilemap.hpp"

namespace ImGui {

/* a frame that stores a small preview of an api-fetched level */
class ApiLevelTile {
public:
ApiLevelTile(api::level lvl, sf::Color bg = sf::Color(0xC8AD7FFF));

void imdraw(bool* download = nullptr);

private:
sf::Color m_bg;
api::level m_lvl;
tilemap m_tmap; // tilemap generated from the level data
sf::RenderTexture m_map_tex;
};

}
16 changes: 3 additions & 13 deletions game/level.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ sf::Vector2i level::mouse_tile(sf::Vector2f mouse_pos) const {
}

void level::load_from_api(api::level data) {
m_metadata = level::metadata(data);
m_metadata = data;
map().load(data.code);
}

Expand All @@ -44,24 +44,14 @@ bool level::has_metadata() const {
return !!m_metadata;
}

level::metadata& level::get_metadata() {
api::level& level::get_metadata() {
return *m_metadata;
}

const level::metadata& level::get_metadata() const {
const api::level& level::get_metadata() const {
return *m_metadata;
}

void level::clear_metadata() {
m_metadata.reset();
}

level::metadata::metadata(api::level data)
: id(data.id),
author(data.author),
title(data.title),
description(data.description),
createdAt(data.createdAt),
updatedAt(data.updatedAt),
downloads(data.downloads) {
}
17 changes: 3 additions & 14 deletions game/level.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,6 @@ class level : public sf::Drawable, public sf::Transformable {
public:
level(int xs = 32, int ys = 32);

struct metadata {
metadata(api::level data);
int id;
std::string author;
std::string title;
std::string description;
std::time_t createdAt;
std::time_t updatedAt;
int downloads;
};

tilemap& map(); // get the map
const tilemap& map() const; // get the map

Expand All @@ -32,8 +21,8 @@ class level : public sf::Drawable, public sf::Transformable {

// does this level have metadata or is it local only
bool has_metadata() const;
metadata& get_metadata();
const metadata& get_metadata() const;
api::level& get_metadata();
const api::level& get_metadata() const;
void clear_metadata();

// load from api data
Expand All @@ -46,5 +35,5 @@ class level : public sf::Drawable, public sf::Transformable {

tilemap m_tmap; // associated tilemap

std::optional<metadata> m_metadata; // optional level metadata
std::optional<api::level> m_metadata; // optional level metadata
};
16 changes: 11 additions & 5 deletions game/states/edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
#include "../gui/editor_tile_button.hpp"
#include "../gui/image_text_button.hpp"

#include "./search.hpp"
#include "states/levels.hpp"
#include "states/search.hpp"

namespace states {

edit::edit(api::level lvl)
: edit() {
m_load_api_level(lvl);
}

edit::edit()
: m_menu_bar(),
m_level(),
Expand Down Expand Up @@ -396,7 +400,7 @@ void edit::m_load_api_level(api::level lvl) {
m_upload_status.reset();
m_download_status.reset();
m_level.load_from_api(lvl);
level::metadata md = m_level.get_metadata();
api::level md = m_level.get_metadata();
if (m_is_current_level_ours()) {
std::strncpy(m_title_buffer, m_level.get_metadata().title.c_str(), 50);
std::strncpy(m_description_buffer, m_level.get_metadata().description.c_str(), 256);
Expand All @@ -420,13 +424,15 @@ void edit::m_gui_menu(fsm* sm) {
if (ImGui::ImageButtonWithText(resource::get().imtex("assets/gui/search.png"), "Search###Search")) {
return sm->swap_state<states::search>();
}
ImGui::BeginDisabled();
if (ImGui::ImageButtonWithText(resource::get().imtex("assets/gui/folder.png"), "Levels###Levels")) {
return sm->swap_state<states::levels>();
//return sm->swap_state<states::search>();
}
ImGui::EndDisabled();
}

void edit::m_gui_level_info(fsm* sm) {
level::metadata md = m_level.get_metadata();
api::level md = m_level.get_metadata();
ImGui::Text("-= Details (ID %d) =-", md.id);
ImGui::TextWrapped("Title: %s", md.title.c_str());
ImGui::TextWrapped("Author: %s", md.author.c_str());
Expand Down
Loading

0 comments on commit 2325bf3

Please sign in to comment.