Initial DDGuesser. :3
This commit is contained in:
parent
ff6440a418
commit
51b743b858
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -21,4 +21,5 @@ Cargo.lock
|
||||||
/target
|
/target
|
||||||
|
|
||||||
# Added by BurnyLlama
|
# Added by BurnyLlama
|
||||||
.env
|
.env
|
||||||
|
static/location-data
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
rocket = { version = "0.5.0-rc.3", features = ["json"] }
|
rocket = { version = "0.5.0-rc.3", features = ["json"] }
|
||||||
|
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] }
|
||||||
serde = "1.0.183"
|
serde = "1.0.183"
|
||||||
sqlx = { version = "0.7.1", features = [ "runtime-tokio-rustls", "postgres", "chrono", "uuid" ] }
|
sqlx = { version = "0.7.1", features = [ "runtime-tokio-rustls", "postgres", "chrono", "uuid" ] }
|
||||||
tokio-test = "0.4.2"
|
tokio-test = "0.4.2"
|
||||||
|
|
4
Rocket.toml
Normal file
4
Rocket.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[default]
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 12345
|
||||||
|
template_dir = "templates"
|
68
src/app/mod.rs
Normal file
68
src/app/mod.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use rocket::{response::Redirect, Route, State};
|
||||||
|
use rocket_dyn_templates::{context, Template};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::database::{
|
||||||
|
models::{
|
||||||
|
guess::Guess,
|
||||||
|
location::{self, Location},
|
||||||
|
},
|
||||||
|
DatabaseHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn landing() -> Template {
|
||||||
|
Template::render("landing", context! {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/start-game")]
|
||||||
|
async fn start_game(db: &State<DatabaseHandler>) -> Result<Redirect, String> {
|
||||||
|
Location::get_random(db, true)
|
||||||
|
.await
|
||||||
|
.map(|location| Redirect::to(format!("/location/{}", location.id)))
|
||||||
|
.map_err(|_| {
|
||||||
|
"Sorry for this, but the game ran into an error. Please try again!".to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/location/<id>")]
|
||||||
|
async fn location_via_id(db: &State<DatabaseHandler>, id: String) -> Result<Template, String> {
|
||||||
|
let uuid = match Uuid::parse_str(&id) {
|
||||||
|
Ok(uuid) => uuid,
|
||||||
|
Err(_) => return Err("Sorry, that id seems invalid!".to_string()),
|
||||||
|
};
|
||||||
|
let location = match Location::get_by_id(db, &uuid).await {
|
||||||
|
Ok(location) => location,
|
||||||
|
Err(_) => return Err("Sorry, that location seems invalid!".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Template::render("location", context! { location }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/location/<id>?<guess>")]
|
||||||
|
async fn location_guess_via_id(
|
||||||
|
db: &State<DatabaseHandler>,
|
||||||
|
id: String,
|
||||||
|
guess: String,
|
||||||
|
) -> Result<Template, String> {
|
||||||
|
let uuid = match Uuid::parse_str(&id) {
|
||||||
|
Ok(uuid) => uuid,
|
||||||
|
Err(_) => return Err("Sorry, that id seems invalid!".to_string()),
|
||||||
|
};
|
||||||
|
let location = match Location::get_by_id(db, &uuid).await {
|
||||||
|
Ok(location) => location,
|
||||||
|
Err(_) => return Err("Sorry, that location seems invalid!".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_guess_correct = guess == location.map;
|
||||||
|
let guess_with_results = Guess::create(guess, is_guess_correct);
|
||||||
|
|
||||||
|
Ok(Template::render(
|
||||||
|
"location",
|
||||||
|
context! { location, guess: guess_with_results },
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all_routes() -> Vec<Route> {
|
||||||
|
routes![landing, start_game, location_via_id, location_guess_via_id]
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ use std::{env, error::Error};
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
|
||||||
pub struct DatabaseHandler {
|
pub struct DatabaseHandler {
|
||||||
pool: PgPool,
|
pub pool: PgPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseHandler {
|
impl DatabaseHandler {
|
||||||
|
|
19
src/database/models/guess.rs
Normal file
19
src/database/models/guess.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Guess {
|
||||||
|
pub map: String,
|
||||||
|
pub result: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Guess {
|
||||||
|
pub fn create(map: String, is_correct: bool) -> Guess {
|
||||||
|
Guess {
|
||||||
|
map,
|
||||||
|
result: match is_correct {
|
||||||
|
true => "correct".to_string(),
|
||||||
|
false => "incorrect".to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,15 +4,31 @@ use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Location {
|
pub struct Location {
|
||||||
id: Uuid,
|
pub id: Uuid,
|
||||||
map: String,
|
pub map: String,
|
||||||
|
pub has_entities: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Location {
|
impl Location {
|
||||||
pub async fn get_random(db: &DatabaseHandler) -> Result<Location, sqlx::Error> {
|
/// Get a random location. Use the `has_entities` option to specify whether the location should be returned with entities. No entities = harder.
|
||||||
|
pub async fn get_random(
|
||||||
|
db: &DatabaseHandler,
|
||||||
|
has_entities: bool,
|
||||||
|
) -> Result<Location, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Location,
|
Location,
|
||||||
"SELECT id, map FROM locations ORDER BY RANDOM() LIMIT 1"
|
"SELECT id, map, has_entities FROM locations WHERE has_entities = $1 ORDER BY RANDOM() LIMIT 1",
|
||||||
|
has_entities
|
||||||
|
)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_by_id(db: &DatabaseHandler, id: &Uuid) -> Result<Location, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Location,
|
||||||
|
"SELECT id, map, has_entities FROM locations WHERE id = $1",
|
||||||
|
id
|
||||||
)
|
)
|
||||||
.fetch_one(&db.pool)
|
.fetch_one(&db.pool)
|
||||||
.await
|
.await
|
||||||
|
@ -52,7 +68,7 @@ mod tests {
|
||||||
async fn test() {
|
async fn test() {
|
||||||
let db = get_db().await;
|
let db = get_db().await;
|
||||||
|
|
||||||
let location = match Location::get_random(&db).await {
|
let location = match Location::get_random(&db, false).await {
|
||||||
Ok(location) => location,
|
Ok(location) => location,
|
||||||
Err(err) => panic!("Error while getting random location! {:?}", err),
|
Err(err) => panic!("Error while getting random location! {:?}", err),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
pub mod guess;
|
||||||
pub mod location;
|
pub mod location;
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -1,9 +1,13 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
|
mod app;
|
||||||
mod database;
|
mod database;
|
||||||
|
|
||||||
|
use app::get_all_routes;
|
||||||
use database::DatabaseHandler;
|
use database::DatabaseHandler;
|
||||||
|
use rocket::fs::FileServer;
|
||||||
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
#[rocket::main]
|
#[rocket::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -15,7 +19,14 @@ async fn main() {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
match rocket::build().manage(database).launch().await {
|
match rocket::build()
|
||||||
|
.attach(Template::fairing())
|
||||||
|
.manage(database)
|
||||||
|
.mount("/", get_all_routes())
|
||||||
|
.mount("/static", FileServer::from("./static"))
|
||||||
|
.launch()
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => println!("Encountered an error while starting rocket:\n{}", err),
|
Err(err) => println!("Encountered an error while starting rocket:\n{}", err),
|
||||||
}
|
}
|
||||||
|
|
83
static/ddguesser.css
Normal file
83
static/ddguesser.css
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
body.ddguesser {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: 'nav' 'main';
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ddguesser > nav {
|
||||||
|
background-color: #1F1F47;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ddguesser > main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: min(90ch, 90%);
|
||||||
|
place-self: start center;
|
||||||
|
margin-bottom: 5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img#meow {
|
||||||
|
width: min(45ch, 50%);
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
margin: 0 auto 2rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a#start-game {
|
||||||
|
width: max-content;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
form > input[type=text] {
|
||||||
|
color: #FAFAFF;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
/* text-align: center; */
|
||||||
|
}
|
||||||
|
|
||||||
|
span.correct {
|
||||||
|
color: #64DB4F;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.incorrect {
|
||||||
|
color: #E04F53;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woof {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woof > img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woof > p {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1rem;
|
||||||
|
user-select: none;
|
||||||
|
translate: 50%;
|
||||||
|
right: 50%;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
background-color: #0008;
|
||||||
|
}
|
2
static/nebulosa.css
Normal file
2
static/nebulosa.css
Normal file
File diff suppressed because one or more lines are too long
11
templates/landing.html.tera
Normal file
11
templates/landing.html.tera
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "template/app" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<header>
|
||||||
|
<h1>Welcome to DDGuesser!</h1>
|
||||||
|
<p>The game where you guess the DDNet map!</p>
|
||||||
|
</header>
|
||||||
|
<img src="https://cataas.com/cat/says/DDGuesser?type=square" alt="Random image of a cat, with the caption 'DDGuesser'."
|
||||||
|
loading="lazy" id="meow">
|
||||||
|
<a href="/start-game" class="btn primary raised" id="start-game">Play game!</a>
|
||||||
|
{% endblock main %}
|
20
templates/location.html.tera
Normal file
20
templates/location.html.tera
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "template/app" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<header>
|
||||||
|
<h1>Which map is this image from?</h1>
|
||||||
|
</header>
|
||||||
|
{% if guess %}
|
||||||
|
<div class="woof">
|
||||||
|
<img src="/static/location-data/{{ location.id }}.png" class="map" loading="lazy">
|
||||||
|
<p class="guess">Your guess {{ guess.map }} is <span class="{{ guess.result }}">{{ guess.result }}</span>!</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<img src="/static/location-data/{{ location.id }}.png" class="map" loading="lazy">
|
||||||
|
{% endif %}
|
||||||
|
<form action="/location/{{ location.id }}" method="get">
|
||||||
|
<label for="guess">Enter map to guess for:</label>
|
||||||
|
<input type="text" name="guess" id="guess">
|
||||||
|
<button class="btn primary outline" type="submit">Guess!</button>
|
||||||
|
</form>
|
||||||
|
{% endblock main %}
|
27
templates/template/app.html.tera
Normal file
27
templates/template/app.html.tera
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/nebulosa.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/ddguesser.css">
|
||||||
|
{% block head %}
|
||||||
|
<title>DDGuesser</title>
|
||||||
|
{% endblock head %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="ddguesser">
|
||||||
|
<nav>
|
||||||
|
<a class="btn primary textonly" href="/">DDGuesser</a>
|
||||||
|
<a class="btn primary textonly" href="/about">About</a>
|
||||||
|
<a class="btn primary textonly" href="/help">Help</a>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
{% block main %}
|
||||||
|
<p>Hello world!</p>
|
||||||
|
{% endblock main %}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user