Coast Walker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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