Skip to content

Commit

Permalink
Enhance
Browse files Browse the repository at this point in the history
  • Loading branch information
rstropek committed Apr 24, 2023
1 parent 1ca48be commit 23d1bc0
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 100 deletions.
1 change: 0 additions & 1 deletion wasm-serverless/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
members = [
"word_puzzle_cloudflare",
"word_puzzle_generator",
"word_puzzle_spin",
]
5 changes: 5 additions & 0 deletions wasm-serverless/justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cfdev:
cd word_puzzle_cloudflare; npm run dev

spin:
cd word_puzzle_spin; spin build; spin up
4 changes: 0 additions & 4 deletions wasm-serverless/word_puzzle_cloudflare/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ default = ["console_error_panic_hook"]
[dependencies]
cfg-if = "1.0"
worker = "0.0.14"
serde_json = "1.0.67"
serde = { version = "1", features = ["derive"] }
rand = "0.8"
getrandom = { version = "0.2", features = ["js"] }
word_puzzle_generator = { path = "../word_puzzle_generator" }

# The `console_error_panic_hook` crate provides better debugging of panics by
Expand Down
61 changes: 17 additions & 44 deletions wasm-serverless/word_puzzle_cloudflare/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,32 @@
use serde::Deserialize;
use serde_json::json;
use word_puzzle_generator::place_words;
use word_puzzle_generator::{place_words, GeneratorOptions};
use worker::*;

mod utils;

#[derive(Deserialize)]
struct GeneratorOptions {
size: usize,
words: Vec<String>,
}

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
// Optionally, get more helpful error messages written to the console in the case of a panic.
utils::set_panic_hook();

// Optionally, use the Router to handle matching endpoints, use ":name" placeholders, or "*name"
// catch-alls to match on specific patterns. Alternatively, use `Router::with_data(D)` to
// provide arbitrary data that will be accessible in each route via the `ctx.data()` method.
let router = Router::new();

// Add as many routes as your Worker needs! Each route will get a `Request` for handling HTTP
// functionality and a `RouteContext` which you can use to and get route parameters and
// Environment bindings like KV Stores, Durable Objects, Secrets, and Variables.
router
.get("/", |_, _| Response::ok("Hello from Workers!"))
.get("/worker-version", |_, ctx| {
let version = ctx.var("WORKERS_RS_VERSION")?.to_string();
Response::ok(version)
})
.post_async("/generate", |mut req, _ctx| async move {
match req.json().await {
Err(_) => Response::error("Bad Request", 400),
Ok(options) => {
let options: GeneratorOptions = options;
if options.size > 20 {
return Response::error("Bad Request", 400);
}

match place_words(&options.words, options.size) {
Err(unplaced_words) => Ok(Response::from_json(&json!({
"error": "Unable to place all words",
"unplaced_words": unplaced_words
}))
.unwrap()
.with_status(500)),
Ok(grid) => Response::from_json(&json!(grid
.iter()
.map(|row| row.iter().collect::<String>())
.collect::<Vec<String>>())),
}
}
}
})
.post_async("/generate", generate_puzzle)
.run(req, env)
.await
}

async fn generate_puzzle(mut req: Request, _ctx: RouteContext<()>) -> Result<Response> {
match req.json().await {
Err(_) => Response::error("Bad Request", 400),
Ok(options) => {
let options: GeneratorOptions = options;
if options.size > 20 {
return Response::error("Bad Request", 400);
}

let puzzle = place_words(options);
Response::from_json(&puzzle)
}
}
}
1 change: 1 addition & 0 deletions wasm-serverless/word_puzzle_generator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ edition = "2021"
[dependencies]
rand = "0.8"
getrandom = { version = "0.2", features = ["js"] }
serde = { version = "1", features = ["derive"] }
53 changes: 34 additions & 19 deletions wasm-serverless/word_puzzle_generator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
use std::collections::HashSet;

use rand::Rng;
use serde::{Deserialize, Serialize};

// Naive word puzzle generator

pub fn place_words(words: &[String], size: usize) -> Result<Vec<Vec<char>>, Vec<String>> {
let mut rng = rand::thread_rng();
#[derive(Deserialize)]
pub struct GeneratorOptions {
pub size: usize,
pub words: Vec<String>,
}

// Make sure that all words are shorter than the size of the grid
if words.iter().any(|word| word.len() > size) {
return Err(words
.iter()
.filter(|word| word.len() > size)
.map(|word| word.to_string())
.collect());
}
#[derive(Serialize)]
pub struct Puzzle {
pub grid: Vec<String>,
pub words: Vec<String>,
}

pub fn place_words(mut options: GeneratorOptions) -> Puzzle {
let mut rng = rand::thread_rng();

// Create empty grid
let mut grid = vec![vec!['.'; size]; size];
let mut grid = vec![vec!['.'; options.size]; options.size];

// Remember unplaced words
let mut unplaced_words = Vec::new();
let mut unplaced_words = HashSet::new();

for word in words {
// Sort words by length, process longest first
options.words.sort_by_key(|w| w.len());
for word in options.words.iter().rev() {
let mut placed = false;

// In this naive implementation, we try to place a word 100 times before giving up.
Expand All @@ -39,15 +47,22 @@ pub fn place_words(words: &[String], size: usize) -> Result<Vec<Vec<char>>, Vec<

// If we couldn't place a word, remember it
if !placed {
unplaced_words.push(word.to_string());
unplaced_words.insert(word);
}
}

if unplaced_words.is_empty() {
fill_remaining_spots(&mut grid);
Ok(grid)
} else {
Err(unplaced_words)
fill_remaining_spots(&mut grid);
Puzzle {
grid: grid
.iter()
.map(|row| row.iter().collect::<String>())
.collect(),
words: options
.words
.iter()
.filter(|w| !unplaced_words.contains(w))
.cloned()
.collect(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion wasm-serverless/word_puzzle_spin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ bytes = "1"
http = "0.2"
# The Spin SDK.
spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v1.1.0" }
routefinder = "0.5"
# Crate that generates Rust Wasm bindings from a WebAssembly interface.
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
word_puzzle_generator = { path = "../word_puzzle_generator" }

Expand Down
4 changes: 2 additions & 2 deletions wasm-serverless/word_puzzle_spin/requests.http
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@host = https://word-puzzle-spin-zirmufxv.fermyon.app/
#@host = http://localhost:3000
#@host = https://word-puzzle-spin-zirmufxv.fermyon.app/
@host = http://localhost:3000

###
GET {{host}}/
Expand Down
56 changes: 27 additions & 29 deletions wasm-serverless/word_puzzle_spin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
use anyhow::Result;
use routefinder::Captures;
use spin_sdk::{
http::{Request, Response, Router},
http_component,
};
use serde::Deserialize;
use word_puzzle_generator::place_words;
use word_puzzle_generator::{place_words, GeneratorOptions};

#[derive(Deserialize)]
struct GeneratorOptions {
size: usize,
words: Vec<String>,
}

/// A simple Spin HTTP component.
#[http_component]
fn handle_word_puzzle_spin(req: Request) -> Result<Response> {
let mut router = Router::new();
let mut router = Router::new();

router.get("/", |_req, _params| {
Ok(http::Response::builder()
.status(http::StatusCode::OK)
.body(Some("Hello from spin!".into()))?)
});

router.get("/", |_req, _params| Ok(http::Response::builder()
.status(http::StatusCode::OK)
.body(Some("Hello from spin!".into()))?));
router.post("/generate", |req, _params| {
let body = req.body().as_ref().unwrap();
let options: GeneratorOptions = serde_json::from_str(std::str::from_utf8(body.as_ref()).unwrap()).unwrap();
router.post("/generate", generate_puzzle);

router.handle(req)
}

if options.size > 20 {
return Ok(http::Response::builder().status(http::StatusCode::BAD_REQUEST).body(None)?);
}
fn generate_puzzle(req: Request, _params: Captures) -> Result<Response> {
let body = req.body().as_ref().unwrap();
let options: GeneratorOptions = serde_json::from_str(std::str::from_utf8(body.as_ref())?)?;

let grid = place_words(&options.words, options.size).unwrap();
let response = serde_json::to_string_pretty(&grid
.iter()
.map(|row| row.iter().collect::<String>())
.collect::<Vec<String>>())?.as_bytes().to_vec();
return Ok(http::Response::builder()
.status(http::StatusCode::OK)
.body(Some(response.into()))?)
});
if options.size > 20 {
return Ok(http::Response::builder()
.status(http::StatusCode::BAD_REQUEST)
.body(None)?);
}

router.handle(req)
let puzzle = place_words(options);
let response = serde_json::to_string_pretty(&puzzle)?.as_bytes().to_vec();
Ok(http::Response::builder()
.status(http::StatusCode::OK)
.header("Content-Type", "application/json")
.body(Some(response.into()))?)
}

0 comments on commit 23d1bc0

Please sign in to comment.