Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

A game of riddles to play with your friends

How to play

  1. To start a game, choose a 3-20 letter word. The game generates a riddle based on your word.
  2. Copy the link to share with your friends to see if they can guess the word.
  3. If they guess the word correctly, they get to pick a new word and can send you a new clue back.
  4. If they guess wrong, the next riddle is based on the word they guessed

You can play with 2 or as many people as you want. You can follow a long chain, like the game Telephone or games can branch with multiple players.


TODO

  • Reload after updating the URL to support native share sheets
  • Incorrect state / helpers
  • Trim and lowercase guesses
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource https://esm.sh/react */
import copy from "https://esm.sh/copy-text-to-clipboard";
import leven from "https://esm.sh/fast-levenshtein";
import { useEffect, useState } from "https://esm.sh/react";
import { DataRequest, render } from "https://esm.town/v/jxnblk/ReactStream";
import { useStreamingAPI } from "https://esm.town/v/jxnblk/useStreamingAPI";
// chat and encode/decode
import api, { decode } from "https://esm.town/v/jxnblk/IndirectionAPI";
import { GoogleFonts } from "https://esm.town/v/jxnblk/reactGoogleFonts";
import { Typewriter } from "https://esm.town/v/jxnblk/useTypewriter";
const PERMALINK = "https://www.val.town/v/jxnblk/indirection";
function App(props) {
const [history, setHistory] = useState<Turn[]>(props.data.history || []);
const [word, setWord] = useState<string>("");
const [turn, setTurn] = useState(props.data.turn || emptyTurn);
const [ui, setUI] = useState<UIState>(props.data?.turn?.clue ? UIState.Guessing : UIState.Start);
const { message, submit, loading, error } = useStreamingAPI<APIRequestBody>();
const addHistoryGuess = (guess: string) => {
setHistory([
...history,
{
word: turn.word,
clue: turn.clue,
guess: guess,
},
]);
};
const handleSubmit = e => {
e.preventDefault();
let correct = null;
if (ui === UIState.Guessing) {
// is a guess play
addHistoryGuess(word.toLowerCase());
correct = turn.word === word.toLowerCase();
console.log("levenshtein", leven.get(turn.word, word.toLowerCase()));
setTurn({ ...turn, guess: word.toLowerCase() });
} else if (ui === UIState.Correct) {
// is a new word after correct answer
submit({ word: word.toLowerCase() });
setUI(UIState.Loading);
return;
}
if (correct) {
setUI(UIState.Correct);
setWord("");
} else {
// incorrect words continue the chain
submit({ word: word.toLowerCase() });
setUI(UIState.Loading);
}
};
const copyLink = () => {
if (copy(window.location.href)) {
console.log("copied");
}
};
useEffect(() => {
if (loading) return;
if (!message) return;
console.log({ word, message });
setURL({
history,
turn: { // for the next play / page load
word: word.toLowerCase(),
clue: message,
},
});
setUI(UIState.Sharing);
}, [word, message, loading]);
const showForm = ui === UIState.Start || ui === UIState.Guessing || ui === UIState.Correct;
const urlLength = props.url?.length || -1;
console.log("url.length", urlLength);
return (
<html>
<head>
<OGMeta {...turn} url={props.url.href} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<GoogleFonts family="Lexend" />
<GoogleFonts family="BioRhyme" weight="400" />
<GoogleFonts family="BioRhyme" weight="800" />
<style dangerouslySetInnerHTML={{ __html: css }} />
</head>
<body>
<div className="container card">
{error && (
<div>
Something went wrong. Reload the page and try again.
</div>
)}
<h1 className="h1 slab xbold">Indirection</h1>
<>
jxnblk-indirection.web.val.run
August 13, 2024