Compare commits

...

2 Commits

Author SHA1 Message Date
blek a56e33b9f2
add "Create a game" button 2023-09-03 22:38:05 +10:00
blek 40eac5d01f
ensure that targets are in dictionary 2023-09-03 22:37:52 +10:00
10 changed files with 162 additions and 3 deletions

View File

@ -2,5 +2,5 @@
<p align="center"> <p align="center">
<img src="https://jenkins.blek.codes/job/bWordle/badge/icon?style=plastic"></img> <img src="https://jenkins.blek.codes/job/bWordle/badge/icon?style=plastic"></img>
</p> </p>
OpenWordle is a completely open source and lightweight wordle game with no runtime dependencies OpenWordle is a completely open source and lightweight wordle game with very minimal set of runtime dependencies

View File

@ -19,5 +19,9 @@
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.4.5", "vite": "^4.4.5",
"vite-plugin-compression": "^0.5.1" "vite-plugin-compression": "^0.5.1"
},
"dependencies": {
"copy-to-clipboard": "^3.3.3",
"qr-creator": "^1.0.0"
} }
} }

View File

@ -10,6 +10,8 @@
import { isIn, loadDict } from "./dictionary"; import { isIn, loadDict } from "./dictionary";
import { onMount } from "svelte"; import { onMount } from "svelte";
import Keyboard from "./Keyboard.svelte"; import Keyboard from "./Keyboard.svelte";
import GameCreator from "./GameCreator.svelte";
import { decode } from "./lib/cipher";
let targets = getForNWord(5); let targets = getForNWord(5);
@ -23,12 +25,22 @@
let loading = true; let loading = true;
let not_a_word = false; let not_a_word = false;
let game_creator = false;
let green_letters: string[] = []; let green_letters: string[] = [];
let yellow_letters: string[] = []; let yellow_letters: string[] = [];
let unfit_letters: string[] = []; let unfit_letters: string[] = [];
console.log(word); (
function() {
const urlprops = new URLSearchParams(window.location.search);
const challenge = urlprops.get('challenge');
if (challenge !== null) {
word = decode(atob(decodeURIComponent(challenge)));
console.log(word)
}
}
)()
onMount(async () => { onMount(async () => {
await loadDict(); await loadDict();
@ -130,6 +142,8 @@
} }
</script> </script>
<GameCreator bind:show={game_creator} />
{#if not_a_word} {#if not_a_word}
<Modal show animate={false}> <Modal show animate={false}>
<ModalTitle> <ModalTitle>
@ -174,6 +188,10 @@
Give up Give up
</button> </button>
<button on:click={() => {game_creator = true}}>
Create a game
</button>
<p style='line-height:0;margin-top:48px'> <p style='line-height:0;margin-top:48px'>
{#each guessed as g1, i} {#each guessed as g1, i}
{#each g1 as letter, ii} {#each g1 as letter, ii}

69
src/GameCreator.svelte Normal file
View File

@ -0,0 +1,69 @@
<script lang='ts'>
import { onMount } from "svelte";
import Modal from "./modal/Modal.svelte";
import ModalButton from "./modal/ModalButton.svelte";
import ModalContent from "./modal/ModalContent.svelte";
import ModalFooter from "./modal/ModalFooter.svelte";
import ModalTitle from "./modal/ModalTitle.svelte";
import { encode } from "./lib/cipher";
import { getRandom } from "./targets";
export let show = false;
let word = getRandom(5);
let qrurl: string | false = false;
let wordurl = '';
let qrcanvas: HTMLCanvasElement;
async function upd_qr() {
if (word == '') return;
if (!qrcanvas) return;
wordurl = `${window.location.protocol}//${window.location.host}?challenge=${encodeURIComponent(btoa(encode(word)))}`;
const QrCreator = (await import('qr-creator')).default;
QrCreator.render({
text: wordurl,
radius: 0.5,
ecLevel: 'L',
fill: '#000000',
background: '#ffffff',
size: 120
}, qrcanvas);
}
async function copylink() {
const copy = (await import('copy-to-clipboard')).default;
copy(wordurl);
}
onMount(async () => {
await upd_qr();
})
</script>
{#if show}
<Modal show>
<ModalTitle>
Create a game
</ModalTitle>
<ModalContent>
<p>
Word:
<input type='text' bind:value={word} on:input={upd_qr}>
</p>
<p style='min-height:160px'>
<span style='border-radius:12px;padding:20px;background:white;display:inline-block'>
<canvas bind:this={qrcanvas} width="120px" height="122px" style='border-radius:4px' />
</span>
</p>
</ModalContent>
<ModalFooter>
<ModalButton style='width:50%;border-right:0;border-radius:0 0 0 16px' onclick={() => {show = false; word = getRandom(5); upd_qr()}}>
Close
</ModalButton>
<ModalButton style='width:50%;border-radius:0 0 16px 0' onclick={copylink}>
Copy link
</ModalButton>
</ModalFooter>
</Modal>
{/if}

View File

@ -43,6 +43,18 @@ button:active {
background: #161616; background: #161616;
} }
input[type=text] {
background: #181818;
border: 1px solid #343434;
border-radius: 6px;
margin: 0 4px;
padding: 4px 6px;
outline: none;
min-width: 80px;
width: 80px;
max-width: 100%;
}
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {
color: #213547; color: #213547;

View File

@ -1,7 +1,12 @@
import targets from "./targets";
let dict: string[] = []; let dict: string[] = [];
let dict_loaded = false; let dict_loaded = false;
export async function loadDict(onprogress?: {(data: { loaded: number, total: number }): void}) { export async function loadDict(onprogress?: {(data: { loaded: number, total: number }): void}) {
if (dict_loaded) return dict;
const req = await fetch('/dictionary.csv', { const req = await fetch('/dictionary.csv', {
cache: 'force-cache' cache: 'force-cache'
}); });
@ -24,6 +29,7 @@ export async function loadDict(onprogress?: {(data: { loaded: number, total: num
} }
})) }))
dict = data.split(','); dict = data.split(',');
dict = [ ...targets, ...dict ];
dict_loaded = true; dict_loaded = true;
} }

22
src/lib/cipher.ts Normal file
View File

@ -0,0 +1,22 @@
import { random } from "./random";
export function encode(word: string) {
let out = '';
for (const letter of word) {
out += String.fromCharCode(random(32, 128)) + letter;
}
return out
}
export function decode(ciphertext: string) {
let out = '';
for (let i = 0; i != ciphertext.length; i++) {
if (i % 2 == 1) {
out += ciphertext[i];
}
}
return out;
}
globalThis.encode = encode;
globalThis.decode = decode;

View File

@ -11,4 +11,14 @@ export function getForNWord(len: number) {
return out return out
} }
export function getRandom(len: number | null = null) {
const dataset = len ? getForNWord(len) : targets;
for (const target of dataset) {
if (random(0, Math.floor(dataset.length * 0.5)) == 0) {
return target;
}
}
return dataset[0];
}
export default targets export default targets

3
src/vite-env.d.ts vendored
View File

@ -1,3 +1,4 @@
/// <reference types="svelte" /> /// <reference types="svelte" />
/// <reference types="vite/client" /> /// <reference types="vite/client" />
const TARGETS: string const TARGETS: string;
declare module 'qr-creator';

View File

@ -333,6 +333,13 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
copy-to-clipboard@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==
dependencies:
toggle-selection "^1.0.6"
css-tree@^2.3.1: css-tree@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
@ -670,6 +677,11 @@ postcss@^8.4.27:
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.0.2" source-map-js "^1.0.2"
qr-creator@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/qr-creator/-/qr-creator-1.0.0.tgz#f350a8f0b5be02bd1fc1ef133a038a06ef8bc5ef"
integrity sha512-C0cqfbS1P5hfqN4NhsYsUXePlk9BO+a45bAQ3xLYjBL3bOIFzoVEjs79Fado9u9BPBD3buHi3+vY+C8tHh4qMQ==
queue-microtask@^1.2.2: queue-microtask@^1.2.2:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@ -815,6 +827,11 @@ to-regex-range@^5.0.1:
dependencies: dependencies:
is-number "^7.0.0" is-number "^7.0.0"
toggle-selection@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
tslib@^2.6.0: tslib@^2.6.0:
version "2.6.2" version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"