Compare commits
2 Commits
775aa83134
...
a56e33b9f2
Author | SHA1 | Date |
---|---|---|
blek | a56e33b9f2 | |
blek | 40eac5d01f |
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
12
src/app.css
12
src/app.css
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
@ -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
|
|
@ -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';
|
17
yarn.lock
17
yarn.lock
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue