Skip to content

Instantly share code, notes, and snippets.

@kenwebb
Last active November 17, 2021 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kenwebb/353b46288703f5711f2a61a552c91ce8 to your computer and use it in GitHub Desktop.
Save kenwebb/353b46288703f5711f2a61a552c91ce8 to your computer and use it in GitHub Desktop.
Coast Walker
Coast Walker for use in Island Game
Ken Webb
Novemeber 13, 2021
cd ~/nodespace
mkdir expressCWalker
cd expressCWalker
npm init
"main": "app.js",
npm install express --save
npm install cors
- add JS content to app.js
node app.js
/**
Ken Webb
Learning Express.
November 13, 2021
Coast Walker or Coast Swimmer
see Xholon workbook: Express - web framework for node
3ede7991936fe5281657c494f8a92caf
(base) ken@ken-HP-ZBook-15-G2:~/nodespace/expressCWalker$ node app.js
Express+CoastWalker app listening at http://localhost:3000
NODE_ENV: undefined
see also: ~/gwtspace/Xholon/Xholon/module/ava2srvr/mCoastWalker.xml moduleAvatar2Server.xml etc.
Performance
-----------
see: https://expressjs.com/en/advanced/best-practice-performance.html
To set NODE_ENV to “production”, enter this command in the Linux/Ubuntu terminal window, before running node:
export NODE_ENV=production
This is temporary, and is only active for that terminal window.
*/
const express = require('express')
const cors = require('cors')
const {ftest, nesw, bchcmr, walk, swim} = require('./functions') // Procedural Memory
const {stest} = require('./storage') // Declarative Memory
const app = express()
const port = 3000
// enable CORS for all origins
app.use(cors());
app.use(express.json()) // for parsing application/json
app.get('/', (req, res) => {
res.send('Hello Coast Walker!')
})
// KSW test
app.get('/test1', (req, res) => {
res.send('Testing CWalker 1.')
})
// KSW test
app.get('/test2', (req, res) => {
res.send('Testing CWalker 2.')
})
app.get('/test2/:one', (req, res) => {
res.send('Testing CWalker 3: ' + req.params.one)
})
app.get('/add/:one/:two', (req, res) => {
res.send(`${req.params.one} + ${req.params.two} = ` + (0 + Number(req.params.one) + Number(req.params.two)))
})
/**
* Walk.
* @param {Request} req A Perception or similar Request.
* @param {Response} res A Motor command or similar Response.
*/
app.post('/walk', (req, res) => {
res.send("" + walk(req.body))
})
/**
* Swim.
* @param {Request} req A Perception or similar Request.
* @param {Response} res A Motor command or similar Response.
*/
app.post('/swim', (req, res) => {
res.send("" + swim(req.body))
})
/**
* Test Walk
* http://localhost:3000/testwalk
*/
app.get('/testwalk', (req, res) => {
res.send("" + walk({ me: 'CoastCell', neighborhood: { n: 'CoastCell', e: 'LandCell', s: 'CoastCell', w: 'OceanCell' }, heading: 'n' }))
})
/**
* Test Swim
* http://localhost:3000/testswim
*/
app.get('/testswim', (req, res) => {
res.send("" + swim({ me: 'CoastCell', neighborhood: { n: 'CoastCell', e: 'LandCell', s: 'CoastCell', w: 'OceanCell' }, heading: 'n' }))
})
app.listen(port, () => {
console.log(`Express+CoastWalker app listening at http://localhost:${port}`)
console.log("NODE_ENV: " + process.env.NODE_ENV)
})
/**
Ken Webb
November 13, 2021
functions.js
Functions for use with app.js
Procedural Long-term Memory
*/
const ftest = () => "Hello Coasties from FTest!"
/**
* Various functions that generate random integers within specified ranges.
*/
const random = max => () => Math.floor(Math.random() * max)
const random2 = random(2)
/**
* Process a NESW JavaScript Object.
* @param {Object} obj ex: { me: 'LandCell', n: 'CoastCell', e: 'LandCell', s: 'LandCell', w: 'LandCell' }
* @return {Number} A direction to move to a random neighboring non-OceanCell gridCell.
*/
const nesw = obj => {
const neswArr = Object.entries(obj).reduce((prev, curr) => {
if ((curr[0] !== "me") && (curr[1] !== "OceanCell")) {
prev.push(curr[0])
}
return prev
}, [])
const moveto = neswArr[random(neswArr.length)()]
const movetoNum = moveto === "n" ? 0
: moveto === "e" ? 1
: moveto === "s" ? 2
: 3
//console.log(obj, neswArr, moveto, movetoNum)
return movetoNum
}
/**
* Process a Beach Comber JavaScript Object.
* @param {Object} obj ex: { me: 'CoastCell', n: 'CoastCell', e: 'LandCell', s: 'CoastCell', w: 'OceanCell' }
* @return {Number} A direction to move to a random neighboring CoastCell gridCell.
*/
const bchcmr = obj => {
const arr = Object.entries(obj).reduce((prev, curr) => {
if ((curr[0] !== "me") && (curr[1] === "CoastCell")) {
prev.push(curr[0])
}
return prev
}, [])
const moveto = arr[random(arr.length)()]
const movetoNum = moveto === "n" ? 0
: moveto === "e" ? 1
: moveto === "s" ? 2
: 3
//console.log(obj, arr, moveto, movetoNum)
return movetoNum
}
/**
* Return a left or right direction, chosen randomly if they both match.
* @param {String} lcell Left Cell, with possible values: "CoastCell"|"LandCell"|"OceanCell"
* @param {String} rcell Right Cell with possible values: "CoastCell"|"LandCell"|"OceanCell"
* @param {String} ldir Left Direction
* @param {String} rdir Right Direction
* @param {String} searchstr Search String, either of "CoastCell"|"LandCell"
* @return {String} ldir or rdir
*/
const leftright = (lcell, rcell, ldir, rdir, searchstr) =>
lcell === searchstr && rcell !== searchstr ? ldir
: rcell === searchstr && lcell !== searchstr ? rdir
: [ldir, rdir][random2()]
/**
* Process a Coast Walker JavaScript Object.
* @param {Object} obj ex: { me: 'CoastCell', neighborhood: { n: 'CoastCell', e: 'LandCell', s: 'CoastCell', w: 'OceanCell' }, heading: 'n' }
* @param {String} adjacentCell The adjacent type of cell that the Avatar might move into if it can't move to a CoastCell. "LandCell" | "OceanCell"
* @return {Number} A direction to move to a random neighboring CoastCell gridCell.
*/
const travel = (obj, adjacentCell) => {
//console.log("traveling");
//console.log(obj);
//console.log(adjacentCell);
// heading direction
const hdir = obj.heading
// left direction
const ldir = hdir === "n" ? "w"
: hdir === "e" ? "n"
: hdir === "s" ? "e"
: "s"
// rigt direction
const rdir = hdir === "n" ? "e"
: hdir === "e" ? "s"
: hdir === "s" ? "w"
: "n"
// back direction
const bdir = hdir === "n" ? "s"
: hdir === "e" ? "w"
: hdir === "s" ? "n"
: "e"
const hood = obj.neighborhood
const hcell = hood[hdir]
const lcell = hood[ldir]
const rcell = hood[rdir]
//console.log(hood + " " + hcell + " " + lcell + " " + rcell)
const moveto = hcell === "CoastCell" ? hdir
: lcell === "CoastCell" || rcell === "CoastCell" ? leftright(lcell, rcell, ldir, rdir, "CoastCell")
: hcell === adjacentCell ? hdir
: lcell === adjacentCell || rcell === adjacentCell ? leftright(lcell, rcell, ldir, rdir, adjacentCell)
: bdir
// TODO can I just return a String "n" "e" "s" "w" instead of a number 0 1 2 3 ?
const movetoNum = moveto === "n" ? 0
: moveto === "e" ? 1
: moveto === "s" ? 2
: 3
return movetoNum
}
const walk = obj => travel(obj, "LandCell")
const swim = obj => travel(obj, "OceanCell")
module.exports = {ftest, nesw, bchcmr, walk, swim};
<?xml version="1.0" encoding="UTF-8"?>
<!--
Xholon Avatar that walks or swims along the Coast within the Island Game world,
always moving in a consistent direction,
so that eventually it should return to its starting point.
Ken Webb
November 13, 2021
~/gwtspace/Xholon/Xholon/module/ava2srv/mCoastWalker.xml
based on mAva2SrvPostNesw.xml and mBeachComber.xml
See Xholon workbook: Express - web framework for node
Copy and paste, or drag, this entire text into a running Xholon app.
November 14, 2021
Added a check for network errors, and set a timer for recovery if there is a network error.
- for example, if I stop and later restart the node server.
-->
<XholonModule>
<XholonMap>
<Attribute_String roleName="ih"><![CDATA[
<_-.XholonClass>
<CoastWalker superClass="Avatar"/>
<Talk2ServerCW superClass="Script"/>
</_-.XholonClass>
]]></Attribute_String>
<Attribute_String roleName="cd"><![CDATA[
<xholonClassDetails>
<CoastWalker><Color>magenta</Color></CoastWalker>
<Talk2ServerCW><DefaultContent>
const REQ_REMOTE = true;
const SIGNAL_TIMEOUT = -1; // see xholon base/ISignal.java
var me, ava, request, headingArr, jstimeout, beh = {
postConfigure: function() {
me = this.cnode;
ava = me.parent().parent();
if (!ava["subtrees"]) {
ava.action("param subtrees true");
}
ava.action("param transcript false");
const url = [`http://127.0.0.1:3000/${ava.travel}`, `http://192.168.0.39:3000/${ava.travel}`, `http://192.168.1.10:3000/${ava.travel}`];
request = new Request(url[0]);
headingArr = ["n", "e", "s", "w"];
jstimeout = null;
me.msg(101, "0");
//console.log(ava.unveil);
//console.log(ava.travel);
},
processReceivedMessage(msg) {
this.doRequest(this.prepData(ava.parent(), msg.data));
},
prepData: function(pava, heading) {
// { me: 'CoastCell', neighborhood: { n: 'CoastCell', e: 'LandCell', s: 'CoastCell', w: 'OceanCell' }, heading: 'n' }
return {
me: pava.xhc().name(),
neighborhood: {
n: pava.port(0).xhc().name(),
e: pava.port(1).xhc().name(),
s: pava.port(2).xhc().name(),
w: pava.port(3).xhc().name()
},
//heading: heading // TODO this should be the current heading (=== direction of Avatar's last movement)
heading: headingArr[heading]
}
},
doRequest: function(jso) {
if (REQ_REMOTE) {
this.doRequestRemote(jso);
}
else {
this.doRequestLocal(jso);
}
},
doRequestRemote: function(jso) {
fetch(request, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(jso)
})
.then((res) => {
if (!res.ok) {
throw new Error('Network response was not OK');
}
return res.text();
})
.then((responseText) => { // responseText is a Number without quotes, for example: 1
this.moveto(responseText);
me.msg(101, responseText);
return null;
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
jstimeout = setTimeout(() => {
console.log("timeout has expired");
me.msg(SIGNAL_TIMEOUT, "0");
}, 10*1000);
})
},
doRequestLocal: function(obj) {
// TODO this should be the same code used in the Server functions.js
},
moveto: function(direction) {
ava.action("go " + direction); // go 0|1|2|3 OK
if (ava.unveil === "true") {
ava.parent().incognita = null;
}
}
}
//# sourceURL=Talk2ServerCWbehavior.js
</DefaultContent></Talk2ServerCW>
</xholonClassDetails>
]]></Attribute_String>
<Attribute_String roleName="csh"><![CDATA[
<_-.csh>
<!-- I can create additional instances of CoastWalker, by dragging in just the following subtree -->
<CoastWalker unveil="true" travel="walk"> <!-- walk|swim -->
<BehaviorsST>
<Talk2ServerCW/>
</BehaviorsST>
</CoastWalker>
</_-.csh>
]]></Attribute_String>
</XholonMap>
</XholonModule>
/**
Ken Webb
November 13, 2021
storage.js
Storage for use with app.js
Declarative Long-term Memory
I'm not sure if this will be needed with Coast Walker.
*/
const stest = () => "Hello from Coast Walker STest!"
// storage, database
let sensorVal = 0
//const setVal = val => sensorVal = val
//const getVal = () => sensorVal
module.exports = {stest}; //, setVal, getVal};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment