first commit, working game, some work to do yet
This commit is contained in:
commit
3bc2b0fe18
17 changed files with 15694 additions and 0 deletions
17
.eslintrc.js
Normal file
17
.eslintrc.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
};
|
||||
113
.gitignore
vendored
Normal file
113
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# intellij
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# apple
|
||||
.DS_Store
|
||||
4
README.md
Normal file
4
README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
* remake of word collect (app) without the ads
|
||||
* no demo yet
|
||||
* static html/javascript
|
||||
* pretty hard!
|
||||
25
index.html
Normal file
25
index.html
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<meta name="viewport" content="minimal-ui, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WordCollector</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<canvas id="canvas" draggable="false">
|
||||
Your browser does not support the canvas element.
|
||||
</canvas>
|
||||
|
||||
<div class="points"><img class="medal" src="src/img/medal.png"><span id="points">0</span></div>
|
||||
<div id="slots">
|
||||
</div>
|
||||
<div id="guess" class="guess"></div>
|
||||
<div class="hint"><img id="hint" alt="hint" src="src/img/hint.png" width="50px"></div>
|
||||
<div class="reload"><img id="reload" alt="shuffle" src="src/img/reload.png" width="50px"></div>
|
||||
<div id="characters"></div>
|
||||
|
||||
<script src="bundle.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
7729
package-lock.json
generated
Normal file
7729
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
25
package.json
Normal file
25
package.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "wordcollector",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "src/js/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "webpack",
|
||||
"watch": "webpack --watch",
|
||||
"start": "webpack-dev-server --open --host 0.0.0.0 --port 80"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"css-loader": "^3.4.2",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"fabric": "^4.0.0-beta.6",
|
||||
"file-loader": "^5.0.2",
|
||||
"jquery": "^3.4.1",
|
||||
"style-loader": "^1.1.3",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
}
|
||||
}
|
||||
154
src/css/style.css
Normal file
154
src/css/style.css
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
html {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Quicksand', serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
background: #53a600;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
flex-flow: row-reverse wrap;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.character-tile {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
transform: translate(0, -15%);
|
||||
}
|
||||
|
||||
.medal {
|
||||
height: 1em;
|
||||
transform: translate(-12px, 2px);
|
||||
}
|
||||
|
||||
.points {
|
||||
position: absolute;
|
||||
right: 5%;
|
||||
top: 1%;
|
||||
flex-flow: row-reverse wrap;
|
||||
color: yellow;
|
||||
background: black;
|
||||
border: #dab107 2px solid;
|
||||
border-radius: 4px;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
#slots {
|
||||
top: 35px;
|
||||
width: 90%;
|
||||
margin: 5px;
|
||||
display: flex;
|
||||
flex-flow: row-reverse wrap;
|
||||
border: #f5f1bc solid 15px;
|
||||
border-radius: 15px;
|
||||
background: #f5f1bc;
|
||||
position: absolute;
|
||||
horiz-align: center;
|
||||
}
|
||||
|
||||
.wordslot {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
color: brown;
|
||||
}
|
||||
|
||||
.revealed {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.characterslot {
|
||||
vertical-align: center;
|
||||
text-align: center;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
background: brown;
|
||||
border: black solid .5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.tile {
|
||||
position: absolute;
|
||||
background: url("../img/wood.jpg");
|
||||
width: 18vw;
|
||||
height: 10vh;
|
||||
max-width: 75px;
|
||||
text-align: center;
|
||||
color: rgb(99, 28, 28);
|
||||
border: solid beige 1px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 1), 0 0 40px rgba(255, 255, 0, 0.2) inset;
|
||||
font-size: 10vh;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.guess {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
visibility: hidden;
|
||||
background: #f5f1bc;
|
||||
position: absolute;
|
||||
top: 37%;
|
||||
left: 40%;
|
||||
display: inline-block;
|
||||
height: 11vh;
|
||||
text-align: center;
|
||||
color: black;
|
||||
border: solid beige 2px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 1), 0 0 40px rgba(100,100,100, 0.1) inset;
|
||||
font-size: 7vh;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.shown {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.selected {
|
||||
border-width: 5px;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(../font/quicksand.woff) format('woff');
|
||||
}
|
||||
|
||||
.reload {
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
right: 5%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 5%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
1525
src/data/wordcombis4.json.disabled
Normal file
1525
src/data/wordcombis4.json.disabled
Normal file
File diff suppressed because it is too large
Load diff
2271
src/data/wordcombis5.json.disabled
Normal file
2271
src/data/wordcombis5.json.disabled
Normal file
File diff suppressed because it is too large
Load diff
3511
src/data/wordcombis6.json
Normal file
3511
src/data/wordcombis6.json
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/font/quicksand.woff
Normal file
BIN
src/font/quicksand.woff
Normal file
Binary file not shown.
BIN
src/img/hint.png
Normal file
BIN
src/img/hint.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
src/img/medal.png
Normal file
BIN
src/img/medal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
src/img/reload.png
Normal file
BIN
src/img/reload.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
BIN
src/img/wood.jpg
Normal file
BIN
src/img/wood.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
278
src/js/index.js
Normal file
278
src/js/index.js
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
import $ from 'jquery';
|
||||
import '../css/style.css';
|
||||
import {fabric} from 'fabric';
|
||||
import data from '../data/wordcombis6.json';
|
||||
|
||||
new function () {
|
||||
const characterTiles = document.getElementsByClassName('tile'); //
|
||||
|
||||
let lines = []; //lines connecting the letters
|
||||
let activeLine; //the line that moves with the pointer
|
||||
let screenMiddle;
|
||||
let words, // the words to guess
|
||||
characters, // the letters to choose from
|
||||
guess = "", // the current guess
|
||||
points = 0;
|
||||
const canvas = new fabric.Canvas('canvas', {selection: false});
|
||||
|
||||
resize();
|
||||
registerEvents();
|
||||
start();
|
||||
|
||||
function start() {
|
||||
// get new word
|
||||
let challenge = data[Math.floor(Math.random() * data.length)];
|
||||
words = challenge.words;
|
||||
|
||||
// max ten words to guess
|
||||
// TODO put the rest in the extra-word list for bonus points
|
||||
// TODO make sure words of all lengths are guaranteed to be in the list
|
||||
while (words.length > 10) {
|
||||
words.splice(Math.random() * Math.floor(words.length), 1);
|
||||
}
|
||||
|
||||
showEmptySlots(); // this is where the guessed words appear
|
||||
|
||||
characters = shuffleLetters(challenge.chars); // random order, otherwise the longest word is to easily visible
|
||||
layoutLetterTiles(); // put them on screen
|
||||
}
|
||||
|
||||
// boxes for the guessed words
|
||||
// TODO nice columnar layout
|
||||
function showEmptySlots() {
|
||||
let slots = $("#slots");
|
||||
slots.children().remove();
|
||||
|
||||
for (let i = words.length - 1; i >= 0; i--) {
|
||||
let word = words[i];
|
||||
let wordSlot = $("<div class='wordslot hidden' id='" + word + "'>");
|
||||
slots.append(wordSlot);
|
||||
|
||||
for (let j = 0; j < word.length; j++) {
|
||||
wordSlot.append($("<div class='characterslot'>").append(word.charAt(j).toUpperCase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// put all letter tiles in a nice circle
|
||||
function layoutLetterTiles() {
|
||||
let width = $(window).width();
|
||||
let height = $(window).height();
|
||||
let center_x = width / 2.4;
|
||||
let center_y = height / 2.2;
|
||||
|
||||
let angleBetweenChars = 2 * Math.PI / characters.length;
|
||||
|
||||
//remove any characters that were there before
|
||||
$("#characters div").remove();
|
||||
|
||||
// layout new ones in a circle
|
||||
for (let i = 0; i < characters.length; i++) {
|
||||
let radius_x = Math.min((width / 4), 150); // not too wide on big screens
|
||||
|
||||
let x = center_x - radius_x * Math.cos(i * angleBetweenChars) + Math.random();
|
||||
let y = center_y * 1.5 - (height / 6) * Math.sin(i * angleBetweenChars) + Math.random() * 20 - 20;
|
||||
|
||||
// make visible
|
||||
let span = $("<span class='character-tile' id='" + i + "-" + characters[i] + "'>");
|
||||
span.append(characters[i].toUpperCase());
|
||||
let div = $("<div class='tile' style='top:" + y + "px;left:" + x + "px;transform: rotate(" + (Math.random() * 8 - 4) + "deg)'>");
|
||||
div.append(span);
|
||||
$("#characters").append(div);
|
||||
}
|
||||
}
|
||||
|
||||
function registerEvents() {
|
||||
$(window).resize(function () {
|
||||
layoutLetterTiles();
|
||||
});
|
||||
|
||||
canvas.on('mouse:down', event => {
|
||||
if (!activeLine) {
|
||||
addLineToCanvas(event);
|
||||
updatePointer(event);
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on('mouse:up', () => {
|
||||
checkGuess();
|
||||
reset();
|
||||
});
|
||||
|
||||
canvas.on('mouse:move', updatePointer);
|
||||
|
||||
$("#reload").on("click", () => {
|
||||
characters = shuffleLetters(characters);
|
||||
layoutLetterTiles();
|
||||
});
|
||||
|
||||
$("#hint").on("click", () => {
|
||||
showHint();
|
||||
});
|
||||
}
|
||||
|
||||
function checkGuess() {
|
||||
if (guess.length > 0) {
|
||||
// if the guess is correct, the word will light up
|
||||
$("#" + guess).attr("class", "wordslot revealed");
|
||||
|
||||
// check if all words are found
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
if (guess === words[i]) {
|
||||
|
||||
//update points
|
||||
points += guess.length;
|
||||
$("#points").text(points);
|
||||
|
||||
//remove word from words to guess
|
||||
words.splice(words.indexOf(guess), 1);
|
||||
if (words.length === 0) {
|
||||
start(); //restart
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updatePointer(event) {
|
||||
if (activeLine) {
|
||||
let pointer = canvas.getPointer(event.e, false);
|
||||
|
||||
//move current line to mouse pointer
|
||||
activeLine.set({x2: pointer.x, y2: pointer.y});
|
||||
|
||||
// check for hits
|
||||
for (let ix = 0; ix < characterTiles.length; ix++) {
|
||||
let tile = characterTiles[ix];
|
||||
if (isHit(tile, pointer)) {
|
||||
registerHit(tile, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
canvas.renderAll();
|
||||
}
|
||||
|
||||
function addLineToCanvas(event) {
|
||||
let points = [event.e.layerX, event.e.layerY, event.e.layerX, event.e.layerY];
|
||||
let line = new fabric.Line(points, {
|
||||
strokeWidth: 4,
|
||||
stroke: '#ffffff',
|
||||
opacity: 0.5,
|
||||
hasBorders: false,
|
||||
hasControls: false,
|
||||
selectable: false,
|
||||
evented: false
|
||||
});
|
||||
|
||||
activeLine = line; // the new line becomes the line that moves with the mouse/finger
|
||||
lines.push(line); // register, to be able to remove later
|
||||
|
||||
canvas.add(line); // make visible on canvas
|
||||
}
|
||||
|
||||
function reset() {
|
||||
// remove visible lines
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
canvas.remove(lines[i]);
|
||||
}
|
||||
|
||||
// empty the lines in memory
|
||||
lines = [];
|
||||
|
||||
// reset the activeline, so the gui goes into it's initial state
|
||||
activeLine = null;
|
||||
|
||||
// reset all tiles
|
||||
for (let i = 0; i < characterTiles.length; i++) {
|
||||
characterTiles[i].setAttribute("class", "tile");
|
||||
}
|
||||
|
||||
// reset guess
|
||||
let guessContainer = $("#guess");
|
||||
guessContainer.children().remove();
|
||||
guessContainer.attr("class", "guess");
|
||||
guess = "";
|
||||
}
|
||||
|
||||
// simple collision detection
|
||||
function isHit(tile, pointer) {
|
||||
let tileX = parseInt(tile.style.left.valueOf());
|
||||
let tileY = parseInt(tile.style.top.valueOf());
|
||||
let dx = pointer.x - tileX;
|
||||
let dy = pointer.y + screenMiddle - tileY;
|
||||
|
||||
return dx > 10 && dx < 80 && dy > 10 && dy < 80;
|
||||
}
|
||||
|
||||
function registerHit(tile, event) {
|
||||
if (notHitAlready(tile)) {
|
||||
updateViewToSelected(tile);
|
||||
|
||||
// create a new line from here
|
||||
addLineToCanvas(event);
|
||||
|
||||
// extract the character on the selected tile
|
||||
let character = tile.firstChild.id.substring(2);
|
||||
|
||||
// add it to the current guess
|
||||
guess = guess + character;
|
||||
|
||||
// and update the guess view
|
||||
let guessContainer = $("#guess");
|
||||
let guessCharContainer = $("<span>");
|
||||
guessCharContainer.text(character.toUpperCase());
|
||||
guessCharContainer.appendTo(guessContainer);
|
||||
guessContainer.attr("class", "guess shown");
|
||||
|
||||
// update guess view position on screen
|
||||
let newSize = ($(window).width() - 20) / 2 - guess.length * 17;
|
||||
guessContainer.css("left", newSize + "px");
|
||||
}
|
||||
}
|
||||
|
||||
function updateViewToSelected(tile) {
|
||||
tile.setAttribute("class", "tile selected");
|
||||
}
|
||||
|
||||
function notHitAlready(tile) {
|
||||
return tile.getAttribute("class") === "tile";
|
||||
}
|
||||
|
||||
function resize() {
|
||||
canvas.setWidth($(window).width());
|
||||
screenMiddle = $(window).height() / 2;
|
||||
canvas.setHeight(screenMiddle);
|
||||
}
|
||||
|
||||
function shuffleLetters(array) {
|
||||
let j, x;
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
j = Math.floor(Math.random() * (i + 1));
|
||||
x = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = x;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function showHint() {
|
||||
let found = false;
|
||||
let count = 0; //100 tries
|
||||
while (!found && count++ < 100) {
|
||||
let word = words[Math.floor(Math.random() * words.length)];
|
||||
let letter = word[Math.floor(Math.random() * word.length)].toUpperCase();
|
||||
let slot = $("#" + word).children();
|
||||
|
||||
for (let i = 0; i < word.length; i++) {
|
||||
if (slot[i].firstChild.nodeValue === letter && slot[i].getAttribute("class") === "characterslot") {
|
||||
slot[i].setAttribute("class", "characterslot revealed");
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
42
webpack.config.js
Normal file
42
webpack.config.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: './src/js/index.js',
|
||||
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
emitError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|svg|jpg|gif)$/,
|
||||
use: [
|
||||
'file-loader',
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/,
|
||||
use: [
|
||||
'file-loader',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue