aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE.txt7
-rw-r--r--README.md14
-rw-r--r--examples/helloworld.js8
-rw-r--r--examples/jarvis.js43
-rw-r--r--examples/spam.js8
-rw-r--r--socket.js38
-rw-r--r--space.js93
-rw-r--r--tests/socket_cursor.js8
-rw-r--r--tests/socket_fetch.js8
-rw-r--r--tests/space_comb.js24
-rw-r--r--tests/space_file.js14
-rw-r--r--tests/space_fromfetch.js9
-rw-r--r--tests/space_search.js16
-rw-r--r--tests/space_subsection.js10
-rw-r--r--utils/getdims.js15
-rw-r--r--utils/tilekeys.js2
16 files changed, 259 insertions, 58 deletions
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..64ab5c5
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,7 @@
+Copyright 2019 Holden Rohrer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index 51a3569..88f1cfe 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,20 @@
A reconstructed version of `ywot-bot` built off of clean programming principles and a minimal, but fast API.
-# Features
+## Getting Started
+
+To download and run this library, install node.js and npm, if you haven't already, and run:
+```bash
+git clone https://github.com/feynmansfedora/ywot-clean.git
+cd ywot-clean
+npm i
+node examples/helloworld.js # To try out the hello world (read the code for more detail)
+```
+
+## Features
Currently, `socket.js` provides `Socket` which has a few signals and functions (documented in the code, of course) which allow abstracted interfacing with YWOT. However, certain facilities available in the old version like queuing or fetch division were scrapped in favor of more direct limits due to the unmaintainability of the old style.
`space.js` provides some dense spatial data management with `Space`, which is really good for specific dialogs and tests, but there may be some more work to be done to create a sparse storage device.
-`examples/helloworld.js` can get you started. Everything in that should work pretty well, but the remainder of the repo is probably pretty buggy until I test it.
+`examples/helloworld.js` can get you started. Everything in the core API (`utils`, `socket.js`, `space.js`) should be pretty stable, but please don't hesitate to report a bug.
diff --git a/examples/helloworld.js b/examples/helloworld.js
index cc43666..4984914 100644
--- a/examples/helloworld.js
+++ b/examples/helloworld.js
@@ -1,9 +1,9 @@
-const ywot = require('../socket'); // Import direct library
-const space = require('../space');
+const Socket = require('../socket'); // Import direct library
+const Space = require('../space');
-writes = new space.Space(); //Generate a place to store potential writes
+writes = new Space(); //Generate a place to store potential writes
writes.adhoc('hello\nworld') // A method to fill internal data with inline code.
-main = new ywot.Socket(''); // Open socket
+main = new Socket('helloworld'); // Open socket at yourworldoftext.com/helloworld
main.on('open',()=>{ // When socket opens
main.write(writes.towrite([8,16])); // Tell the server to write the content of `writes` to
});
diff --git a/examples/jarvis.js b/examples/jarvis.js
new file mode 100644
index 0000000..ccf1f80
--- /dev/null
+++ b/examples/jarvis.js
@@ -0,0 +1,43 @@
+// A bot which responds to `jarvis` with a box
+
+const Space = require('../space');
+const Socket = require('../socket');
+const getdims = require('../utils/getdims');
+const tilekeys = require('../utils/tilekeys');
+
+var main = new Socket();
+var read = {};
+
+function equals(arg1,arg2){ // Just takes the specific case argument of each being an int pair
+ return arg1[0] == arg2[0] && arg1[1] == arg2[1]
+}
+
+main.on('open', ()=>{ // Tries to identify itself with a cursor movement
+ let coords = [Math.floor(Math.random()*100000+16),Math.floor(Math.random()*100000+16)];
+ main.cursor(coords);
+ main.on('cursor',detect);
+ function detect(pos, send){
+ if (equals(pos[0],coords)){
+ main.off('cursor', detect);
+ this.emit('identity', send);
+ }
+ }
+})
+
+main.on('identity', (sender) => {
+ console.log('identity activated');
+ main.on('tileUpdate', (send, source, tiles) => {
+ if (send == sender) return;
+ let locs = tilekeys(tiles);
+ for (let i=0; i<locs.length; i++){
+ let loc = locs[i];
+ if (read[loc]) clearTimeout(read[loc].timer);
+ read[loc] = ({
+ "timer":setTimeout(()=>{delete read[dim]},30000),
+ "block":new Space(),
+ });
+ read[loc].block.fromfetch(tiles, [loc[0][0], loc[0][1], loc[1][0], loc[1][1]], conform=false);
+ // TODO : if any pair or quadruplet of blocks are adjacent, SEARCH THEM!
+ }
+ });
+});
diff --git a/examples/spam.js b/examples/spam.js
index 8134c76..53ac2af 100644
--- a/examples/spam.js
+++ b/examples/spam.js
@@ -1,8 +1,8 @@
// Sorry about writing this, but I want to maintain a notif about the API
-const socket = require('../socket');
-const space = require('../space');
+const Socket = require('../socket');
+const Space = require('../space');
-let text = new space.Space();
+let text = new Space();
text.adhoc("\
\n\
For a node.js YWOT api: \n\
@@ -10,7 +10,7 @@ text.adhoc("\
");
let out = text.towrite([-6,20]);
-let main = new socket.Socket();
+let main = new Socket();
function write(){
main.write(out.slice());
diff --git a/socket.js b/socket.js
index 114949b..f2502e8 100644
--- a/socket.js
+++ b/socket.js
@@ -27,7 +27,7 @@ class retryws extends EventEmitter{ // a wrapper on ws that retries on failure
class Socket extends retryws {
constructor(world='') { // Takes the name of the world, which can be an empty string.
- let loc = (world == '') ? '' : `/${world}`;
+ let loc = (world == '') ? '' : `${world}/`;
let addr = `wss://www.yourworldoftext.com/${loc}ws/`;
super(addr);
this.on('message', (msg)=>{
@@ -36,7 +36,7 @@ class Socket extends retryws {
case 'write':
this.emit('write', msg.accepted, msg.rejected); break; // A confirmation message for writes (accepted/rejected updates)
case 'cursor':
- this.emit('cursor', msg.positions, msg.sender); break; // Must be used for self-identification
+ this.emit('cursor', msg.positions.map(pos => [pos.tileY*8+pos.charY,pos.tileX*16+pos.charX]), msg.sender); break; // Must be used for self-identification
case 'tileUpdate':
this.emit('tileUpdate', msg.sender, msg.source, msg.tiles); break; // Any change by another user or possibly oneself
case 'fetch':
@@ -49,28 +49,46 @@ class Socket extends retryws {
for (var i=0; i<coords.length; i++){
coords[i] = {"minY":coords[i][0], "minX":coords[i][1], "maxY":coords[i][2], "maxX":coords[i][3]};
}
- this.send(`{"fetchRectangles":${JSON.stringify(coords)},"kind":"fetch","v":"3"}`);
+ this.send( JSON.stringify({
+ "fetchRectangles": coords,
+ "kind": "fetch",
+ "v": "3"
+ }));
}
- this.write = function(chars){ //chars is an list of triplets [ tile coordinate (y/x), pixel coordinate (y/x < 16), char ]
+ this.write = function(chars){ //chars is an list of pairs [ pixel coordinate (y/x < 16), char ]
if (chars.length > 200){
throw "Too many characters to write";
}
for (var i=0; i<chars.length; i++){
let char = chars[i]
- chars[i] = [char[0][0],char[0][1],char[1][0],char[1][1],char[2]];
+ let coord = char[0];
+ chars[i] = [Math.floor(coord[0]/8),Math.floor(coord[1]/16),(coord[0] % 8),(coord[1] % 16),char[1]];
chars[i].splice(4,0,0);
chars[i].push(i);
}
- this.send(`{"edits":${JSON.stringify(chars)},"kind":"write"}`);
+ this.send( JSON.stringify({
+ "edits": chars,
+ "kind": "write"
+ }));
}
- this.cursor = function(coords){ //coords is just one quadruplet analagous to fetch; I think the api could handle more, but it's unnecessary for now.
- this.send(`"kind":"cursor","positions":[${JSON.stringify(coords)}]}`);
+ this.cursor = function(coords){ //coords is just one pair of char coords; I think the api could handle more, but it's unnecessary for now.
+ this.send( JSON.stringify({
+ "kind": "cursor",
+ "positions":
+ [
+ {
+ "tileY": Math.floor(coords[0]/8),
+ "tileX": Math.floor(coords[1]/16),
+ "charY": coords[0] % 8,
+ "charX": coords[1] % 16
+ }
+ ]
+ }));
}
}
}
-exports.retryws = retryws;
-exports.Socket = Socket;
+module.exports = Socket;
diff --git a/space.js b/space.js
index 58023a9..619b47b 100644
--- a/space.js
+++ b/space.js
@@ -5,10 +5,11 @@
const fs = require('fs')
function chop(string, n){ // chops a string into n-sized chunks. Assumed to be perfect multiple
- arr = []
- while (string.length > 0){
- arr += string.slice(0,16);
+ let arr = [];
+ for (let sec = 0; sec < string.length; sec++){
+ arr.push(string.slice(sec*n,(sec+1)*n));
}
+ return arr;
}
function replace(text, old, repl){ //replaces, in an array `text`, the instances of `old` with `repl`
for (let i=0; i<text.length; i++){
@@ -16,74 +17,86 @@ function replace(text, old, repl){ //replaces, in an array `text`, the instances
}
return text;
}
+function zeroifnull(obj, prop, alt=0){ //If obj is null, return 0; else obj.prop
+ if (typeof(obj) == 'undefined') return alt;
+ else return obj[prop];
+}
function Space(){ // CLASS
this.data = []; //
self = this;
- this.fromfetch = function(tiles, dimension){ //tiles is straight from fetch function, dimension is a quadruplet
+ this.fromfetch = function(tiles, dimension, conform=true){ //tiles is straight from fetch/tileUpdate function, dimension is a quadruplet; conform is false for tileUpdate because the cell_props don't actually mean anything; all data is still included
for (let y=dimension[0]; y<=dimension[2]; y++){
for (let line=0; line<8; line++)
this.data.push([]); // Adds lines
for (let x=dimension[1]; x<=dimension[3]; x++){
- tilein(tiles[[y,x]],y-dimension[0]);
+ if (! tiles[[y,x]]) tilein({"content":' '.repeat(16*8),"properties":{"cell_props":{}}}); // Insert a null tile
+ else tilein(tiles[[y,x]],y-dimension[0]);
}
}
function tilein(tile, tilerow){ //tile is one of the tiles from `tiles`, and tilerow is y-dimension[0]; helper function
- let incl = Object.keys(tile.properties.cell_props).map(linenum => linenum.parseInt()); // list of included lines in the content
+ let incl = Object.keys(tile.properties.cell_props).map(linenum => parseInt(linenum)) // list of included lines in the content
let cont = chop(tile.content,16);
let read = 0; //line of cont to read
for (let line=0; line<8; line++){
curline = line+8*tilerow;
- if (incl.includes(line)){
- this.data[curline] += cont[read].split('');
- read++;
+ if (conform && incl.includes(line)){
+ for (let i=0; i<16; i++) self.data[curline].push(' ');
} else {
- for (let i=0; i<16; i++) this.data[curline].push(' ');
+ Array.prototype.push.apply(self.data[curline],cont[read].split(''));
+ read++;
}
}
}
+ //console.log(this.data);
}
this.towrite = function(charoffset){ // Does no splitting or anything like that. Just returns a list of triplets for the write function
let writes = [];
for (let line = 0; line < this.data.length; line++) for (let chr = 0; chr< this.data[line].length; chr++){
if (this.data[line][chr] == '') continue;
- writes.push([[Math.floor( (charoffset[0]+line)/8 ),Math.floor( (charoffset[1]+chr)/16 )],[ (charoffset[0]+line) % 8, (charoffset[1]+chr) % 16 ],this.data[line][chr]]);
+ writes.push([[charoffset[0]+line,charoffset[1]+chr],this.data[line][chr]]);
}
return writes;
}
this.tofile = function(filename){
- fs.writeFile(filename,this.data.map(row => replace(row,'','&').join('').replace(/\\|&/g, '\\$&')).join('\n')+'\n',(err)=>{console.log(err);});
+ fs.writeFileSync(filename, this.print());
};
this.fromfile = function(filename){ //Reads an external file into internal data
- fs.readFile(filename, (err, data) => {
- if (err) throw err;
- self.adhoc(data);
- });
+ this.adhoc(fs.readFileSync(filename,'utf8'));
}
this.adhoc = function(text){
- this.data = text.split('\n').map(row => replace( row.replace(/\\(.)/g,'$1').split('') ,'&','' ));
+ text = text.split('\n')
+ text = text.map(row => row.split(''));
+ this.data = text.map(row => {
+ for (let i = 0; i<row.length; i++){
+ if (row[i] == '\\') row.splice(i,1);
+ else if (row[i] == '&') row[i] = '';
+ }
+ return row;
+ });
+ }
+ this.print = function(){
+ return this.data.map(row => replace(replace(replace(row,'&','\\&'),'\\','\\\\'),'','&').join('')).join('\n');
}
this.comb = function(other, func, offset){
- offsety = offset[0];
- offsetx = offset[1];
- for (let row = offset[0]; row<Math.max(other.data.length+offset[0],this.data.length); row++) for (let chr = offset[1]; chr<Math.max(otherlens.data[row].length+offset[1],this.data[row].length); chr++){
- if (row < 0)
- this.data.unshift([]);
+ for (let row = offset[0]; row<Math.max(other.data.length+offset[0],this.data.length); row++){
+ if (row < 0) this.data.unshift([]);
+ if (row >= this.data.length) this.data.push([]);
+ for (let chr = offset[1]; chr<Math.max(zeroifnull(other.data[row],'length')+offset[1], zeroifnull(this.data[row],'length')); chr++){
if (chr < 0)
this.data[row].unshift('');
- if (row > this.data.length)
- this.data.push([]);
- if (chr > this.data[row].length)
+ if (chr >= this.data[row].length)
this.data[row].push('');
- this.data[row][chr] = func(this.data[row][chr], other.data[row][chr]);
- }
+ this.data[row][chr] = func(this.data[row][chr], other.data[row][chr] || '');
+ console.log(row,chr,this.data[row][chr])
+ }}
}
- this.search = function(other){
- loc = [];
- for (let line=0; line<=this.data.length-other.data.length; line++) for (let chr=0; chr<=this.data[y].length-other.data[0].length; chr++){
- match = true;
- for (var y=0; y<other.data.length; y++) for (var x=0; x<other[y].data.length; x++){
+ this.search = function(other){ //Returns first instance of a subspace (prioritized vertically then horizontally)
+ let loc = [];
+ for (let line=0; line<=this.data.length-other.data.length; line++){ for (let chr=0; chr<=this.data[line].length-other.data[0].length; chr++){
+ var match = true;
+ for (let y=0; y<other.data.length; y++) for (let x=0; x<other.data[y].length; x++){
if (this.data[line+y][chr+x] != other.data[y][x]){
match = false;
break;
@@ -93,17 +106,21 @@ function Space(){ // CLASS
loc = [line,chr];
break;
}
- }
+ } if (match) break;}
return loc;
}
this.subsection = function(range){ // range is a standard quadruplet
newspace = new Space();
- for (let line=0; line<range[2]-range[0]; line++) for (let chr=0; chr<range[3]-range[1]; chr++){
- newspace.data[line][chr] = this.data[line+range[0]][line+range[1]];
+ for (let line=0; line<=range[2]-range[0]; line++){
+ newspace.data.push([]);
+ for (let chr=0; chr<=range[3]-range[1]; chr++){
+ newspace.data[line].push(
+ this.data[line+range[0]][chr+range[1]] || ''
+ );
+ }
}
+ return newspace;
}
}
-const comb = require('comb'); // A small set of utilities for Space.comb
-exports.Space = Space;
-exports.comb = comb;
+module.exports = Space
diff --git a/tests/socket_cursor.js b/tests/socket_cursor.js
new file mode 100644
index 0000000..e9e4df7
--- /dev/null
+++ b/tests/socket_cursor.js
@@ -0,0 +1,8 @@
+// Tests Socket.cursor and Socket.on('cursor')
+
+Socket = require('../socket');
+
+world = new Socket();
+
+world.on('open', ()=>{world.cursor([22,23])});
+world.on('cursor', (locs, send)=>{console.log(locs,send)});
diff --git a/tests/socket_fetch.js b/tests/socket_fetch.js
new file mode 100644
index 0000000..b558f89
--- /dev/null
+++ b/tests/socket_fetch.js
@@ -0,0 +1,8 @@
+// Tests Socket.fetch and Socket.on('fetch')
+const Socket = require('../socket');
+
+let world = new Socket();
+
+world.on('open', () => {world.fetch([[10,10,10,10]])});
+
+world.on('fetch', (tiles) => {console.log(tiles);})
diff --git a/tests/space_comb.js b/tests/space_comb.js
new file mode 100644
index 0000000..1cde721
--- /dev/null
+++ b/tests/space_comb.js
@@ -0,0 +1,24 @@
+// Tests space.comb()
+
+const Space = require('../space');
+
+newspace = new Space();
+newspace.adhoc('\
+line1\n\
+&&&&transparency&&&&\n\
+\n\
+afterempty\n')
+
+otherspace = new Space();
+otherspace.adhoc('\
+&&&transparency&&&\n\
+testline');
+
+function add(char1,char2){
+ if (char1 == '') return char2;
+ else return char1;
+}
+
+otherspace.comb(newspace, add, [0,0]);
+
+console.log(otherspace.print());
diff --git a/tests/space_file.js b/tests/space_file.js
new file mode 100644
index 0000000..572214e
--- /dev/null
+++ b/tests/space_file.js
@@ -0,0 +1,14 @@
+// Tests Space.{to,from}file
+const Space = require('../space')
+
+text = new Space();
+
+text.adhoc('\
+line of text\n\
+other&&\\\\&&line of text\n\
+\n\
+final\n');
+
+text.tofile('local')
+text.fromfile('local')
+console.log(text.print()); //Should be losslessly transmitted, and local contains a copy
diff --git a/tests/space_fromfetch.js b/tests/space_fromfetch.js
new file mode 100644
index 0000000..67b41cc
--- /dev/null
+++ b/tests/space_fromfetch.js
@@ -0,0 +1,9 @@
+// Tets Space.fromfetch
+const Space = require('../space')
+
+newspace = new Space()
+
+newspace.fromfetch({"4,2": {"content": " hhhhhhhh k hone ns ", "properties": {"writability": null, "cell_props": {}}}},[4,2,4,2],conform=false);
+// Straight from a real tileUpdate
+
+console.log(newspace.data); //Should be an array of arrays of chars, representing the content of "content" above.
diff --git a/tests/space_search.js b/tests/space_search.js
new file mode 100644
index 0000000..14ec174
--- /dev/null
+++ b/tests/space_search.js
@@ -0,0 +1,16 @@
+// Tests space.search
+
+const Space = require('../space')
+
+let text = new Space();
+text.adhoc('\
+\n\
+ jarvis\n\
+ jarvis\n\
+ jarvis \
+');
+
+let line = new Space();
+line.adhoc('jarvis');
+
+console.log(text.search(line))
diff --git a/tests/space_subsection.js b/tests/space_subsection.js
new file mode 100644
index 0000000..b30b7e1
--- /dev/null
+++ b/tests/space_subsection.js
@@ -0,0 +1,10 @@
+// Tests Space.subsection()
+const Space = require('../space');
+
+newspace = new Space();
+newspace.adhoc('\
+line\n\
+\n\
+long long long line\
+');
+console.log(newspace.subsection([1,1,2,10]));
diff --git a/utils/getdims.js b/utils/getdims.js
new file mode 100644
index 0000000..db0dced
--- /dev/null
+++ b/utils/getdims.js
@@ -0,0 +1,15 @@
+// Takes a list of coordinate pairs (like the keys from a fetch block) and returns a dimension
+
+function dims(coords){
+ ext = [[Infinity, Infinity], [-Infinity, -Infinity]]
+ for (let i=0; i<coords.length; i++){
+ let coord = coords[i];
+ if (coord[0] < ext[0][0]) ext[0][0] = coord[0];
+ if (coord[1] < ext[0][1]) ext[0][1] = coord[1];
+ if (coord[0] > ext[1][0]) ext[1][0] = coord[0];
+ if (coord[1] > ext[1][1]) ext[1][1] = coord[1];
+ }
+ return ext;
+}
+
+module.exports = dims
diff --git a/utils/tilekeys.js b/utils/tilekeys.js
new file mode 100644
index 0000000..ffdf168
--- /dev/null
+++ b/utils/tilekeys.js
@@ -0,0 +1,2 @@
+// Gets keys from tiles, like the kind returned from tileUpdate or fetch
+module.exports = tiles => Object.keys(tiles).map(coord => coord.split(',').map(num => parseInt(num)));