Skip to content

Instantly share code, notes, and snippets.

@kenwebb
Last active March 4, 2025 14:01
Show Gist options
  • Save kenwebb/fb2b0319178a1b3e2cb085e631d03ff1 to your computer and use it in GitHub Desktop.
Save kenwebb/fb2b0319178a1b3e2cb085e631d03ff1 to your computer and use it in GitHub Desktop.
Island 27x27

Use Pimoroni game controller + MQTT + PiZero + Island Game

This gist has three files:

  1. this README file (.md)
  2. a Xholon workbook file (.xml)
  3. a Python3 program that captures game controller button presses that it publishes to MQTT (.py)
# pi@pizero2w:~/grove/grove.py/grove/pimQwStPad/ksw01 $ more test02_mqtt.py
# Ken Webb, 3 Mar 2025
# https://github.com/pimoroni/qwstpad-python/blob/main/docs/reference.md
# QwSTPad Python
# MQTT
# run the following command in the terminal window before running this program
# source ~/.virtualenvs/pimoroni/bin/activate
# TODO
# debounce the buttons
import time
from qwstpad import QwSTPad
#from qwstpad import DEFAULT_ADDRESS as ADDRESS
from qwstpad import ADDRESSES
import paho.mqtt.publish as publish
I2C_ADDRESS = ADDRESSES[1] #ADDRESS
SLEEP = 0.2
try:
qwstpad = QwSTPad(address=I2C_ADDRESS)
except OSError:
print("QwSTPad: Not Connected ... Exiting")
raise SystemExit
print("QwSTPad: Connected ... Starting")
time.sleep(1)
qwstpad.clear_leds()
buttons = qwstpad.read_buttons()
while True:
buttons = qwstpad.read_buttons()
if buttons['U']:
print("Up NORTH")
publish.single("test/message", "NORTH", hostname="192.168.1.3")
elif buttons['D']:
print('Down SOUTH')
publish.single("test/message", "SOUTH", hostname="192.168.1.3")
elif buttons['L']:
print('Left WEST')
publish.single("test/message", "WEST", hostname="192.168.1.3")
elif buttons['R']:
print('Right EAST')
publish.single("test/message", "EAST", hostname="192.168.1.3")
elif buttons['A']:
print('A TAKE 0?')
publish.single("test/message", "TAKE0", hostname="192.168.1.3")
else:
#print("nothing to see here")
None
time.sleep(SLEEP) # 0.2
# Supported characters are A, B, X, Y, U, D, L, R, +, and -.
qwstpad.clear_leds()
<?xml version="1.0" encoding="UTF-8"?>
<!--Xholon Workbook http://www.primordion.com/Xholon/gwt/ MIT License, Copyright (C) Ken Webb, Mon Mar 03 2025 11:16:03 GMT-0500 (Eastern Standard Time)-->
<XholonWorkbook>
<Notes><![CDATA[
Xholon
------
Title: Island 27x27
Description:
Url: http://www.primordion.com/Xholon/gwt/
InternalName: fb2b0319178a1b3e2cb085e631d03ff1 based on 143b7324d1cff61278342d24f3b5928f
Keywords:
My Notes
--------
3 Mar 2025
- I added support for MQTT messages from PiZero + Grove HAT + Pimoroni pimQtSwPad
- this is a game controller with U D L R A B X Y and other buttons
- it works!
18 Feb 2025
27x27 Island Game with MQTT
with MQTT
20 Jan 2025
Jen email from Jan 19:
---
I think in the actual trials with participants we could start by having people take a few practice turns in a small tutorial version of the island (e.g. 9x9 tiles with one water tile and one other player, controlled by the controller) so they could get used to the set up and ask any questions.
I also realized we should sync up this manual with the implementation of the island game, in terms of available objects, recipes, etc. The current version of the manual just has some placeholder objects, and one recipe - build hut - which I don’t think even reflects how huts are built in the implementation. From an experimental point of view, I don’t think the particular objects and recipes available to players are super critical at this stage. Would you say it’s easier to match the manual to the implementation, the other way around, or some combo of the two?
end of quote from Jen email
---
To load and run this workbook:
127.0.0.1:8080/Xholon.html?app=Island+27x27&src=lstr&gui=none&jslib=mqttws31
127.0.0.1:8081/Xholon.html?app=Island+27x27&src=lstr&gui=none&jslib=mqttws31
- can't run it using https://primordion.com/...
### References
(1)
]]></Notes>
<_-.XholonClass>
<!-- Types of things needed by the Xholon framework =============================================== -->
<IslandSystem/>
<!-- containers for objects that will be placed in the Ocean and Island parts of the grid; only used for initial setup -->
<Ocean/>
<Island/>
<!-- DO NOT SPECIFY these two Xholon classes; GridGenerator will create them
<Space/>
<FieldRow/>
-->
<!-- GridGenerator requires that these NOT have a superclass such as IslandGridCell -->
<!--<IslandGridCell>-->
<OceanCell/> <!-- OceanCell is the default -->
<LandCell/>
<CoastCell/>
<!--</IslandGridCell>-->
<GridCellPattern superClass="Attribute_String"/>
<GridCellPatterns/>
<!-- containers for behaviors -->
<PlantBehaviors/>
<FishBehaviors/>
<AnimalBehaviors/> <!-- ex: cat behaviors -->
<!-- end of Types of things needed by the Xholon framework =============================================== -->
<!-- Jen's List =============================================== -->
<!-- <Cell/> see IslandGridCell above -->
<Plant>
<Tree>
<GreenTree/>
<RedTree/>
</Tree>
<!-- <Weed/> -->
</Plant>
<Produce>
<Fruit/>
<PricklyFruit/>
</Produce>
<Berry>
<JuicyBerry/>
</Berry>
<!-- <Flower>
<ChewyFlower/>
</Flower> -->
<Material>
<Stick/>
<Thorn/>
<Vine/>
<!-- <Leaves/> Ken ? -->
<FreshWater/>
</Material>
<Animal>
<Fish/>
</Animal>
<Artifact>
<Tool>
<FishingRod/>
<Basket/>
<YokeCarrier/> <!-- see https://en.wikipedia.org/wiki/Carrying_pole -->
<Backpack/>
</Tool>
<Structure>
<Hut/>
</Structure>
</Artifact>
<GeoFeature> <!-- Geography -->
<Spring/> <!-- a place where fresh water might be located -->
</GeoFeature>
<!-- end of Jen's List =============================================== -->
<!-- Other things that Ken needs or is experimenting with =========================== -->
<!-- environment -->
<Weather/>
<!-- Ken's black cat Licorice who insists on being part of any project Ken works on. He aimlessly wanders around on the land, and has no effect other than being visible and sometimes annoying. -->
<Cat/>
<!-- Island Control Centre; a hidden place -->
<City/>
<University/>
<Library/>
<CoffeeShop/>
<IslandControlCentre/>
<IslandControlCentreBranch/>
<!-- an artifact that provides some useful items for free, for now -->
<TreasureChest/>
<TreasureChests/>
<!-- a node I can create to test <Symbol>js:...</Symbol> -->
<Jackal/>
<!-- use TestTool to test the Avatar apply command; TestTool has to be a GWT behavior -->
<TestTool superClass="Script"/>
<!-- Island 4 - experimental: try to parse Ceptre syntax into JavaScript objects -->
<stageCeptre/>
<StageCeptreRunner/>
<!-- log Avatar state and actions -->
<AvatarLogger/>
<!-- handle menus for keys d e r t (drop eat recipe take) -->
<MenuHandler/>
<GameOver/>
<LevelII/>
<SecretProjects/>
<ElDorado/>
<CatTreat/>
<Volleyball/>
<!-- for use with WebRTC-based collection of game results; IndexedDB data exports will go here -->
<DataExport/>
<DataExports/>
<!-- Boat stuff -->
<Canoe/>
<Paddle superClass="Script"/>
<!-- initial Options -->
<Options/>
<Elixir/>
<!-- Generate current state as JSON in the out tab -->
<GenStateAsJSON superClass="Script"/>
<!-- MQTT -->
<MqttClient/>
</_-.XholonClass>
<xholonClassDetails>
<LandCell implName="org.primordion.xholon.base.GridEntity"><Color>PaleGoldenRod</Color></LandCell> <!-- PaleGoldenRod -->
<CoastCell implName="org.primordion.xholon.base.GridEntity"><Color>#d0c883</Color></CoastCell> <!-- #d0c883(olive) -->
<Avatar><Color>RoyalBlue</Color></Avatar>
<!--<Avatar><Symbol>GPSHAPE_NOSHAPE</Symbol></Avatar>-->
<GameOver><Color>black</Color></GameOver>
<Volleyball>
<!--<Icon>https://upload.wikimedia.org/wikipedia/en/f/fb/Wilson_The_Volleyball.jpg</Icon>-->
<Icon>images/castaway/Wilson_circ_transparent.png</Icon>
</Volleyball>
<GreenTree><DefaultContent><![CDATA[
<_-.treedc>
<script>this.parent().mass = 9999;</script>
<Fruit maxClones="10" energy="10"/>
<JuicyBerry maxClones="10" energy="10"/>
<Stick maxClones="10" energy="0"/>
</_-.treedc>
]]></DefaultContent></GreenTree>
<RedTree><DefaultContent><![CDATA[
<_-.treedc>
<script>this.parent().mass = 9999;</script>
<PricklyFruit maxClones="10" energy="10"/>
<Thorn maxClones="10" energy="0"/>
<Vine maxClones="10" energy="0"/>
</_-.treedc>
]]></DefaultContent></RedTree>
<TreasureChest><DefaultContent><![CDATA[
<_-.tcdc>
<script>this.parent().mass = 1000;</script>
<Stick maxClones="1" energy="0"/>
<Vine maxClones="1" energy="0"/>
<Thorn maxClones="1" energy="0"/>
<CatTreat maxClones="10" energy="0"/>
<Volleyball roleName="Wilson" maxClones="1" energy="0"><Annotation>https://en.wikipedia.org/wiki/Cast_Away</Annotation></Volleyball>
</_-.tcdc>
]]></DefaultContent></TreasureChest>
<Spring><DefaultContent><![CDATA[
<_-.springdc>
<script>this.parent().mass = 9999;</script>
<FreshWater maxClones="100" energy="0"/>
</_-.springdc>
]]></DefaultContent></Spring>
<City><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 9999;this.parent().energy = 0;</script></_-.immovabledc>]]></DefaultContent></City>
<University><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 9999;this.parent().energy = 0;</script></_-.immovabledc>]]></DefaultContent></University>
<Library><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 9999;this.parent().energy = 0;</script></_-.immovabledc>]]></DefaultContent></Library>
<CoffeeShop><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 9999;this.parent().energy = 0;</script></_-.immovabledc>]]></DefaultContent></CoffeeShop>
<IslandControlCentre><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 9999;this.parent().energy = 0;</script></_-.immovabledc>]]></DefaultContent></IslandControlCentre>
<IslandControlCentreBranch><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 9999;this.parent().energy = 0;</script></_-.immovabledc>]]></DefaultContent></IslandControlCentreBranch>
<Fruit><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></Fruit>
<PricklyFruit><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></PricklyFruit>
<JuicyBerry><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></JuicyBerry>
<Stick><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></Stick>
<Thorn><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></Thorn>
<Vine><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></Vine>
<FreshWater><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></FreshWater>
<CatTreat><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></CatTreat>
<Volleyball><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></Volleyball>
<!-- Items generated using a recipe
a generated Basket has no mass attribute
TODO specify the mass within the Minecraft-style recipe
-->
<!--<Hut><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 9999;</script></_-.immovabledc>]]></DefaultContent></Hut>
<FishingRod><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></FishingRod>
<Basket><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></Basket>
<YokeCarrier><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></YokeCarrier>
<Backpack><DefaultContent><![CDATA[<_-.immovabledc><script>this.parent().mass = 1;</script></_-.immovabledc>]]></DefaultContent></Backpack>-->
<Stick>
<!-- Symbol + Color -->
<Symbol>Triangle</Symbol>
<Color>Sienna</Color>
</Stick>
<Fish><Symbol>LRTriangle</Symbol><Color>SteelBlue</Color></Fish> <!-- Tuna silver,SteelBlue -->
<GreenTree><Symbol>Circle</Symbol><Color>Green</Color></GreenTree>
<RedTree><Symbol>Circle</Symbol><Color>Red</Color></RedTree>
<Vine><Symbol>SmallRectangle</Symbol><Color>DarkGreen</Color></Vine>
<Thorn><Symbol>SmallCircle</Symbol><Color>DarkOliveGreen</Color></Thorn>
<Fruit><Symbol>Cross</Symbol><Color>Orange</Color></Fruit>
<PricklyFruit><Symbol>Cross</Symbol><Color>DeepPink</Color></PricklyFruit>
<JuicyBerry><Symbol>ReverseTriangle</Symbol><Color>Violet</Color></JuicyBerry>
<Cat><Symbol>Wye</Symbol><Color>black</Color></Cat>
<City>
<Color>gold</Color>
<Symbol>js:ctx.fillRect(x+1,y+1,cellSize-2,cellSize-2);</Symbol> <!-- this works; it uses the fillStyle specified in <Color> -->
</City>
<Hut>
<Color>DarkGoldenRod</Color>
<!--<Symbol>js:ctx.fillRect(x,y,3,3);ctx.fillRect(x+5,y,3,3);ctx.fillRect(x,y+5,3,3);ctx.fillRect(x+5,y+5,3,3);</Symbol>--> <!-- this works -->
<Symbol>
js:ctx.fillStyle='rgba(255,0,0,1.0)';ctx.fillRect(x,y,3,3);
ctx.fillStyle='rgba(0,255,0,1.0)';ctx.fillRect(x+5,y,3,3);
ctx.fillStyle='rgba(0,0,255,1.0)';ctx.fillRect(x,y+5,3,3);
ctx.fillStyle='rgba(255,255,0,1.0)';ctx.fillRect(x+5,y+5,3,3);
</Symbol> <!-- this works -->
</Hut>
<FishingRod><Color>Orange</Color></FishingRod>
<Basket><Color>Coral</Color></Basket>
<YokeCarrier><Color>Coral</Color></YokeCarrier>
<Backpack><Color>Coral</Color></Backpack>
<Spring><Symbol>Circle</Symbol><Color>Chocolate</Color></Spring>
<FreshWater><Color>Aqua</Color></FreshWater>
<CatTreat><Symbol>SmallRectangle</Symbol><Color>Brown</Color></CatTreat>
<GridCellPattern><Color>yellow</Color></GridCellPattern>
<TreasureChest>
<Color>Fuchsia</Color>
<Symbol>js:ctx.fillRect(x+1,y+1,cellSize-2,cellSize-2);</Symbol>
</TreasureChest>
<Jackal>
<Color>purple</Color>
<Symbol>js:ctx.fillRect(x+1,y+1,cellSize-2,cellSize-2);</Symbol>
</Jackal>
<TestTool><DefaultContent><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode;
$wnd.console.log(me.name());
},
processReceivedSyncMessage: function(msg) {
var data = msg.data;
$wnd.console.log(data);
var str = "TestTool received message";
if (data && (data.length)) {
for (var i = 0; i < data.length; i++) {
str += " " + data[i];
}
}
return str;
}
}
]]></DefaultContent></TestTool>
<DataExports xhType="XhtypePureActiveObject">
<port name="trop" index="0" connector="RemoteNodeService-PeerJS,localid0,delete,3,delete,9000,/"/>
</DataExports>
<ElDorado>
<!--<Color>gold</Color>-->
<!--<Icon>https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/1599_Guyana_Hondius.jpg/778px-1599_Guyana_Hondius.jpg</Icon>-->
<Icon>images/castaway/778px-1599_Guyana_Hondius.jpg</Icon>
</ElDorado>
<!-- Boat stuff -->
<Canoe><Color>lime</Color></Canoe>
<Paddle><Color>brown</Color><DefaultContent><![CDATA[
var me, ava, toolsST, beh = {
postConfigure: function() {
me = this.cnode;
$wnd.console.log(me.name());
$wnd.console.log(me);
toolsST = null;
ava = $wnd.xh.avatar();
// ava.action('param subtrees true EdiblesST,WaterST,ToolsST,BehaviorsST,MaterialsST'); // will create ava["subtrees"] with the specified IXholon subtrees
// this is already done in the Island game code, as part of testing TestTool
if (ava["subtrees"]) {
toolsST = ava.subtree('ToolsST');
}
if (toolsST) {
toolsST.append(me.remove()); // move this instance of Paddle into the ToolsST subtree
}
else {
testTool.remove();
}
},
// ava.action("apply subtree(ToolsST)Paddle to canoe|parent|context"); should invoke processReceivedSyncMessage
// ava.action("apply subtree(ToolsST)Paddle to context"); THIS WORKED when Avatar was inside the Canoe and Paddle was in ToolsST
processReceivedSyncMessage: function(msg) {
var data = msg.data;
//$wnd.console.log(data);
var str = me.name() + " received message";
if (data && (data.length)) {
for (var i = 0; i < data.length; i++) {
str += " " + data[i];
}
// data[0] should be an instance of Canoe, which should be inside a gridCell
if (data[0]) {
var canoe = data[0];
if (canoe && (canoe.xhc().name() == "Canoe")) {
var cell = canoe.parent();
var nextCell = cell.port(0);
if (nextCell) {
switch (ava["stroke"]) {
case "left": nextCell = nextCell.port(1); break; // paddle stroke on left side of canoe, moves up and veers right
case "right": nextCell = nextCell.port(3); break; // paddle stroke on right side of canoe, moves up and veers left
default: break;
}
// don't allow movement onto a LandCell
var nextCellName = nextCell.xhc().name();
if (nextCell && !(nextCellName == "LandCell")) {
nextCell.append(canoe.remove());
ava.action("become this incognita n");
}
}
}
}
}
// the contents of str will be logged to the debug console, unless I return null
return null; //str;
}
}
//# sourceURL=Paddlebehavior.js
]]></DefaultContent></Paddle>
<!-- Options -->
<Options><Color>black</Color></Options>
<Elixir><Color>orange</Color></Elixir>
</xholonClassDetails>
<IslandSystem>
<!-- rows and cols must be same as const ROWS and const COLS in Behaviors -->
<!--<GridGenerator rows="80" cols="120" gridType="Gvt" names="Space,FieldRow,OceanCell" columnColor="171c8f" gridViewerParams="IslandSystem/Space,7,Island Viewer,true" cellsCanSupplyOwnColor="true"> GOOD -->
<GridGenerator rows="29" cols="29" gridType="Gvt" names="Space,FieldRow,OceanCell" columnColor="171c8f" gridViewerParams="IslandSystem/Space,8,Island Viewer,true" cellsCanSupplyOwnColor="true"> GOOD 40,40,20 40,40,30 30,30,30 25,25,30 20,20,30 15,15,30 11,11,30
<Attribute_String>TODO CellPattern ?</Attribute_String>
</GridGenerator>
<!-- L = LandCell C = CoastCell # = LandCell(where the islandID will be printed at start of game) -->
<GridCellPatterns roleName="Isles ">
<!-- The islandID attributes must be in a strict numeric sequence, and the first one must have a value of 0. -->
<GridCellPattern roleName="Isle 5" xpos="0" ypos="0" islandID="0"><![CDATA[
.............................
..CCCCCCC....................
.CCCLLLCCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCCLLLCCC...................
..CCCCCCC....................
..CCCCCCC....................
.CCCLLLCCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCCLLLCCC...................
..CCCCCCC....................
..CCCCCCC....................
.CCCLLLCCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCLLLLLCC...................
.CCCLLLCCC...................
..CCCCCCC....................
.............................
]]></GridCellPattern>
</GridCellPatterns>
<!-- each PlantBehavior and FishBehavior node moves itself into one of these containers at runtime -->
<PlantBehaviors/>
<FishBehaviors/>
<AnimalBehaviors/>
<!-- see Xml2Xholon - DefaultContent only works if I include RoomModel somewhere before I need to use DefaultContent -->
<RoomModel/>
<Ocean>
<!--<Fish multiplicity="100"/>-->
<Island>
<!--<Cat roleName="Licorice"/>-->
<!-- TODO multiplicity causes DefaultContent content to be duplicated in each instance node -->
<!--<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>
<GreenTree/>-->
<!--<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>
<RedTree/>-->
<Spring/>
<!--<Spring/>
<Spring/>
<Spring/>
<Spring/>
<Spring/>
<Spring/>
<Spring/>
<Spring/>
<Spring/>-->
<!--<City roleName="Ottawa">
<University roleName="Carleton">
<Library>
<CoffeeShop roleName="Starbucks">
param caption #xhgraph; works
<Avatar roleName="Troll" params="param transcript false;param repeat true;param debug false;param speech false;param caption false;"><Attribute_String><![CDATA[
[Troll will repeatedly look for and take students from anywhere in the city, and will drop them in the IslandControlCentre];
wait 1;
exit;
take *treeWanderer;
exit;
wait 1;
enter library;
wait 1;
enter *coffeeShop;
wait 1;
enter islandControlCentre;
drop *treeWanderer;
wait 1;
exit;
wait 1;
]]></Attribute_String></Avatar>
<IslandControlCentre>
<SecretProjects>
Players should be able to slog their way here to find Xholon modules and other pastable content.
<Anno>working on it</Anno>
<ElDorado/>
</SecretProjects>
</IslandControlCentre>
</CoffeeShop>
</Library>
</University>
TreeWanderer nodes must be last children
<TreeWanderer prob="0.1,0.1,0.05,0.05" roleName="Able" exclude="Avatar,TreeWanderer"/>
<TreeWanderer prob="0.1,0.1,0.05,0.05" roleName="Baker" exclude="Avatar,TreeWanderer"/>
<TreeWanderer prob="0.1,0.1,0.05,0.05" roleName="Jen" exclude="Avatar,TreeWanderer"/>
<TreeWanderer prob="0.1,0.1,0.05,0.05" roleName="Ken" exclude="Avatar,TreeWanderer"/>
<CoffeeShop roleName="Oh So Good">
<IslandControlCentreBranch roleName="Westboro Branch"/>
</CoffeeShop>
</City>-->
<!--<TreasureChests>
<TreasureChest/>
<TreasureChest/>
<TreasureChest/>
<TreasureChest/>
<TreasureChest/>
</TreasureChests>-->
</Island>
</Ocean>
<Weather state="sunny and windy"/>
<MinecraftStyleRecipeBook roleName="Island"><Attribute_String><![CDATA[
{
"FishingRod": [
{
"ingredients": [
"Stick",
"Vine",
"Thorn"
],
"result": {
"multiplicity": 1,
"xhc": "FishingRod",
"attrs": {
"mass": 3,
"energy": 0
}
}
}
],
"Hut": [
{
"ingredients": [
"Stick",
"Stick",
"Stick",
"Vine",
"Vine",
"Vine"
],
"result": {
"multiplicity": 1,
"xhc": "Hut",
"role": "Cottage",
"attrs": {
"mass": 9999,
"energy": 0
}
}
}
],
"Basket": [
{
"ingredients": [
"Vine",
"Vine"
],
"result": {
"multiplicity": 1,
"xhc": "Basket",
"attrs": {
"mass": 2,
"energy": 0
}
}
}
],
"YokeCarrier": [
{
"ingredients": [
"Basket",
"Basket",
"Stick"
],
"result": {
"multiplicity": 1,
"xhc": "YokeCarrier",
"attrs": {
"mass": 5,
"energy": 0
}
}
}
],
"Backpack": [
{
"ingredients": [
"Basket",
"Vine"
],
"result": {
"multiplicity": 1,
"xhc": "Backpack",
"attrs": {
"mass": 3,
"energy": 0
}
}
}
],
"Canoe": [
{
"ingredients": [
"Stick",
"Stick",
"Stick",
"Stick",
"Stick",
"Vine",
"Vine",
"Vine",
"Vine",
"Vine"
],
"result": {
"multiplicity": 1,
"xhc": "Canoe",
"attrs": {
"mass": 5,
"energy": 0
}
}
}
],
"Paddle": [
{
"ingredients": [
"Stick",
"Stick",
"Vine"
],
"result": {
"multiplicity": 1,
"xhc": "Paddle",
"attrs": {
"mass": 1,
"energy": 0,
"stroke": false,
"angle": 0
}
}
}
]
}
]]></Attribute_String></MinecraftStyleRecipeBook>
<!-- this is not active yet -->
<JsonRulesEngineRecipeBook roleName="Island"><Attribute_String><![CDATA[
[
{
"conditions": {
"any": [{
"all": [{
"fact": "inventory",
"operator": "contains",
"value": "Stick",
"fate": "remove"
}, {
"fact": "inventory",
"operator": "contains",
"value": "Vine",
"fate": "remove"
}, {
"fact": "inventory",
"operator": "contains",
"value": "Thorn",
"fate": "remove"
}, {
"fact": "wants",
"operator": "equal",
"value": "FishingRod"
}]
}]
},
"event": {
"type": "fishingRod",
"params": {
"message": "A FishingRod can be created."
}
}
}
]
]]></Attribute_String></JsonRulesEngineRecipeBook>
<AvatarLogger formatName="_other,SExpression"/> <!-- _other,Newick _other,SExpression -->
<MenuHandler/>
<!--<TestTool/> have Avatar build this -->
<!--
/**
Ken:
- JenJsonFormat02.js
- 18 Jan 2025
- based on emails and discussion with Jen
- I have run this code in Firefox Dev Tools, with the app running from primordion.com
- it should output valid JSO and JSON
TODO:
- how do I get the player heading, and is this really necessary?
Jen email:
Turn : 1899
My Square : Land Square
My Square Contents: (GreenTree (Fruit*10 JuicyBerry*10 Stick*10) RedTree (PricklyFruit*10 Thorn*10 Vine*10) Castaway (PricklyFruit)) ,
Player Energy : 100037
Neighboring Squares:
North: Coast Square,
North Square Contents:
East : Land Square,
East Square Contents:
South: Coast Square
South Square Contents:
West Square: OceanSquare
West Square Contents:
Player Heading: North
*/
-->
<GenStateAsJSON><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode;
},
handleNodeSelection: function() {
this.genJSON();
return("");
},
genJSON: function() {
const jso = {};
/**
* Recursively hashify a Xholon subtree, with output in a Lisp S-expression format.
* @param node A node in the Xholon hierarchy.
* @param sb A String instance.
*/
const hashifySXpres = (node, newState) => {
newState += node.name("R^^^^^");
if (node.xhc().name() == "Space") {
newState += (" (Avatar)");
}
else if (node.first()) {
newState += " (";
var childNode = node.first();
while (childNode != null) {
newState = hashifySXpres(childNode, newState);
childNode = childNode.next();
if (childNode != null) {
newState += " ";
}
}
newState += ")";
}
else if (node["maxClones"] && (node.parent().xhc().name() != "Avatar")) {
newState += "*" + node["maxClones"];
}
return newState;
}
const NORTH = 0;
const EAST = 1;
const SOUTH = 2;
const WEST = 3;
/**
* Add a neighbor to the data object.
* @param dirctn 0|1|2|3 for NORTH|EAST|SOUTH|WEST.
* @param jso the "Neighboring Squares" data object (a JavaScript Object).
*/
const addNeighbor = (dirctn, jso) => {
const node = mySquare.port(dirctn);
const dirctnstr =
dirctn == NORTH ? "North"
: dirctn == EAST ? "East"
: dirctn == SOUTH ? "South"
: "West";
jso[dirctnstr] = node.name();
jso[dirctnstr + " Square Contents"] = hashifySXpres(node, "");
};
const turn = $wnd.xh.param("TimeStep");
jso.Turn = turn;
const player = $wnd.xh.avatar();
const mySquare = player.parent();
jso["My Square"] = mySquare.name();
jso["My Square Contents"] = hashifySXpres(mySquare, "");
jso["Player Energy"] = player.energy;
const nsquares = {};
addNeighbor(NORTH, nsquares);
addNeighbor(EAST, nsquares);
addNeighbor(SOUTH, nsquares);
addNeighbor(WEST, nsquares);
jso["Neighboring Squares"] = nsquares;
jso["Player Heading"] = "TODO North|East|South|West";
const jsonstr = JSON.stringify(jso, null, 2);
me.print("email the following text to [email protected]\n" + jsonstr.trim());
} // end of genJSON function
}
//# sourceURL=GenStateAsJSON.js
]]></GenStateAsJSON>
<!-- GenStateAsJSON Result JSON
{
"Turn": "9760",
"My Square": "coastCell_8246",
"My Square Contents": "CoastCell (Ottawa (Carleton (Library (Starbucks (IslandControlCentre (SecretProjects (ElDorado Baker) Able) Troll)) Jen) Oh So Good (Westboro Branch) Ken) Castaway (Fruit JuicyBerry Stick PricklyFruit Thorn Vine Fish))",
"Player Energy": 99544,
"Neighboring Squares": {
"North": "oceanCell_8125",
"North Square Contents": "OceanCell",
"East": "oceanCell_8247",
"East Square Contents": "OceanCell",
"South": "coastCell_8367",
"South Square Contents": "CoastCell",
"West": "coastCell_8245",
"West Square Contents": "CoastCell"
},
"Player Heading": "TODO North|East|South|West"
}
-->
<!-- Prevent children and siblings from running their act(), to prevent navigating through the large grid each timestep. -->
<ActRegulator val="1.0"/>
<!-- the Space grid is inserted here at runtime -->
<GameOver>
<!-- GameOver node should contain nodes and/or subtrees that will help a player get back in the game. -->
<LevelII><Anno>Secret instructions on getting to Level II. We regret to inform you that it doesn't exist yet.</Anno></LevelII>
</GameOver>
<!-- MQTT the topic should be island/msg -->
<MqttClient host="192.168.2.30" port="8080" clientidPrefix="clientID-" topic="test/message"/>
<!--<DataExports>
test data
<DataExport>
<Attribute_String roleName="SessionData">test 123</Attribute_String>
<Attribute_String roleName="AvastateData">test 456</Attribute_String>
</DataExport>
</DataExports>-->
<!-- work-around - use "pointer-events: none;" to prevent d3 gui from searching the entire tree when the mouse is hovering; NOT NEEDED ANYMORE -->
<Animate selection="#xhcanvas &gt; div#d3cp" xpath="./IslandSystem/GridCellPatterns" duration="0.1" cssStyle="stroke-width: {0px;}" efParams="{&quot;selection&quot;:&quot;#xhcanvas &gt; div#d3cp&quot;,&quot;sort&quot;:&quot;disable&quot;,&quot;width&quot;:500,&quot;height&quot;:500,&quot;mode&quot;:&quot;tween&quot;,&quot;labelContainers&quot;:true,&quot;includeId&quot;:true,&quot;shape&quot;:&quot;circle&quot;,&quot;useIcons&quot;:true,&quot;maxChars&quot;:-9,&quot;togglePortColors&quot;:false,&quot;supportTouch&quot;:false,&quot;supportClick&quot;:false,&quot;supportContextmenu&quot;:false,&quot;supportDblclick&quot;:false,&quot;fontSizeMultiplier&quot;:2.6}"/>
<!-- ,&quot;useAnno&quot;:true,&quot;annoPos&quot;:&quot;inside&quot; -->
</IslandSystem>
<IslandSystembehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
const ROWS = 29; // same as value in GridGenerator
const COLS = 29; // same as value in GridGenerator
const GRID_CELL_SIZE = 8; // same as value in GridGenerator gridViewerParams
const INCOGNITA = true;
//$wnd.xh.param("RandomNumberSeed", "123"); // the effect happens too late
$wnd.xh.seed(234); // 234 200 12345
$wnd.xh.param("TimeStepInterval","100"); // "500" "100"
$wnd.xh.param("AppM","true");
if ($wnd.xh.html["selectTab"]) {
$wnd.xh.html.selectTab(0); // display contents of the "out" tab
}
$wnd.xh.css.style("#xhchart {font-family: monospace; font-size: 13.3333px; font-weight: 400;}");
// commands for built-in Avatar
var akm = '{ \
"SHIFT":"true", \
"NOSCROLL":"true", \
"UP":"go port0;become this incognita n;if xpath(Avatar/ancestor::OceanCell) go port2;become this energy --;ifeq xpath(Avatar) energy 0 go xpath(ancestor::IslandSystem/GameOver);", \
"DOWN":"go port2;become this incognita n;if xpath(Avatar/ancestor::OceanCell) go port0;become this energy --;ifeq xpath(Avatar) energy 0 go xpath(ancestor::IslandSystem/GameOver);", \
"LEFT":"go port3;become this incognita n;if xpath(Avatar/ancestor::OceanCell) go port1;become this energy --;ifeq xpath(Avatar) energy 0 go xpath(ancestor::IslandSystem/GameOver);", \
"RIGHT":"go port1;become this incognita n;if xpath(Avatar/ancestor::OceanCell) go port3;become this energy --;ifeq xpath(Avatar) energy 0 go xpath(ancestor::IslandSystem/GameOver);", \
"d":"become this menu drop", \
"e":"become this menu eat", \
"f":"follow nextprev", \
"F":"unfollow", \
"m":"become this menu menu", \
"n":"enter nextprev", \
"p":"pause;become this menu menu", \
"r":"become this menu recipe", \
"s":"step", \
" ":"step", \
"t":"become this menu take", \
"x":"if xpath(../../FieldRow) wait else exit", \
"z":"param transcript toggle;", \
"<":"flip prev", \
">":"flip next", \
",":"flip prev", \
".":"flip next", \
"?":"help keymap", \
"/":"help commands", \
"[":"become this stroke left;apply subtree(ToolsST)Paddle to context;become this energy --;ifeq xpath(Avatar) energy 0 go xpath(ancestor::IslandSystem/GameOver);", \
"]":"become this stroke right;apply subtree(ToolsST)Paddle to context;become this energy --;ifeq xpath(Avatar) energy 0 go xpath(ancestor::IslandSystem/GameOver);", \
"0":"go link0;pause;become this menu menu", \
"1":"go link1;pause;become this menu menu", \
"2":"go link2;pause;become this menu menu", \
"3":"go link3;pause;become this menu menu", \
"4":"go link4;pause;become this menu menu", \
"5":"go link5;pause;become this menu menu", \
"$":"become this menu exportdata" \
}';
$wnd.xh.avatarKeyMap(akm);
var ava = $wnd.xh.avatar();
//ava.action('enter;enter space_;enter;enter;appear;');
ava.action('param transcript false;');
ava.action('param caption #xhchart;out caption Castaway;');
ava.action('param meteor true;');
//ava.action('param meteormove true;');
//ava.action('param meteorattr one,two,three,energy'); // testing
//ava.action('param recipebook Island;');
ava.action('param recipebook MinecraftStyleRecipeBook-Island;');
//ava.action('param recipebook JsonRulesEngineRecipeBook-Island;');
ava.action('become this role Castaway;');
ava.action('become this energy 1000;'); // 1000
ava.action('enter;enter *gridCellPatterns;appear;');
// Add a TestTool to Avatar's inventory, and use ava.subtrees
ava.action("build TestTool; take testTool;");
ava.action('param subtrees true EdiblesST,WaterST,ToolsST,BehaviorsST,MaterialsST'); // will create ava["subtrees"] with the specified IXholon subtrees
var testTool = ava.last();
var toolsST = null;
if (ava["subtrees"]) {
toolsST = ava.subtree('ToolsST');
}
if (testTool && toolsST) {
toolsST.append(testTool.remove()); // move the instance of TestTool into the ToolsST subtree
}
else {
testTool.remove();
}
ava.action('step');
// allow player to select which island their Avatar will inhabit
/*ava.println("Welcome castaway. Which Island would you like to inhabit.");
var gcps = $wnd.xh.root().first().xpath("GridCellPatterns");
var gcp = gcps.first();
while (gcp) {
if (gcp.xhc().name() == "GridCellPattern") {
ava.println("" + gcp.islandID + ": inhabit Island " + gcp.islandID);
}
gcp = gcp.next();
}
ava.print("?- ");*/
// fix problem where if I click within the canvas, then keystrokes don't get to the Avatar (see Island B3 workbook, rev 8)
var canvas = $doc.querySelector("div#xhcanvas > canvas");
canvas.onmousedown = function(event) {event.preventDefault();};
// prevent display of Xholon context menu (right-click)
canvas.oncontextmenu = function(event) {event.preventDefault();};
// disable context menu for the entire screen
$doc.oncontextmenu = function(event) {event.preventDefault();};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// specify Plant behavior
$wnd.xh.Plantbehavior = function Plantbehavior() {}
$wnd.xh.Plantbehavior.prototype.postConfigure = function() {
this.plant = this.cnode.parent();
this.plant.energy = 0;
if (!this.plant.mass) {this.plant.mass = 1;}
this.plant.xpath("ancestor::IslandSystem/PlantBehaviors").append(this.cnode.remove());
};
$wnd.xh.Plantbehavior.prototype.act = function() {
//this.plant.println("I am " + this.plant.name() + " energy:" + this.plant.energy);
if (this.plant.parent() == null) {
// I've been eaten, so remove this behavior from the simulation
this.cnode.remove();
}
else if (this.plant.parent().xhc().name() == "Avatar") {
// I've been picked
//this.plant.energy--;
//if (this.plant.energy <= 0) {
// I've gone bad
//this.plant.remove();
//this.cnode.remove();
//}
}
else if (this.plant.parent().xhc().name() == "Island") {
//this.plant.energy++;
this.moveSelfToGrid();
}
else {
//this.plant.energy++;
}
};
$wnd.xh.Plantbehavior.prototype.moveSelfToGrid = function() {
// DO NOT run this code in postConfigure(); it prevents a next Plant node from obtaining a Plantbehavior
// move this.plant to a random position within the land part of the grid
var ix = $wnd.Math.floor($wnd.xh.random() * landCellArr.length);
var fcell = landCellArr[ix];
fcell.append(this.plant.remove());
};
// specify GreenTree behavior
$wnd.xh.GreenTreebehavior = function GreenTreebehavior() {};
$wnd.xh.GreenTreebehavior.prototype = $wnd.Object.create($wnd.xh.Plantbehavior.prototype);
// specify RedTree behavior
$wnd.xh.RedTreebehavior = function RedTreebehavior() {};
$wnd.xh.RedTreebehavior.prototype = $wnd.Object.create($wnd.xh.Plantbehavior.prototype);
// specify Spring behavior; for now make it a type of plant
$wnd.xh.Springbehavior = function Springbehavior() {};
$wnd.xh.Springbehavior.prototype = $wnd.Object.create($wnd.xh.Plantbehavior.prototype);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// specify Fish Grow behavior
$wnd.xh.FishGrowbehavior = function FishGrowbehavior() {}
$wnd.xh.FishGrowbehavior.prototype.postConfigure = function() {
this.fish = this.cnode.parent();
this.fish.energy = 10;
this.fish.mass = 2;
this.fish.xpath("ancestor::IslandSystem/FishBehaviors").append(this.cnode.remove());
};
$wnd.xh.FishGrowbehavior.prototype.act = function() {
//this.fish.println("I am " + this.fish.name() + " energy:" + this.fish.energy);
if (this.fish.parent() == null) {
// I've been eaten, so remove this behavior from the simulation
this.cnode.remove();
}
else if (this.fish.parent().xhc().name() == "Avatar") {
// I've been caught
this.fish.energy -= 0.01;
if (this.fish.energy <= 0) {
// I've gone bad
this.fish.remove();
this.cnode.remove();
}
}
else if (this.fish.parent().xhc().name() == "Ocean") {
this.fish.energy++;
//this.moveSelfToGrid();
}
else if (this.fish.parent().xhc().name() == "OceanCell") {
this.fish.energy *= 1.0001;
//this.moveSelfToGrid();
}
else if (this.fish.parent().xhc().name() == "CoastCell") {
this.fish.energy *= 1.0001;
//this.moveSelfToGrid();
}
else {
// I've probably been caught and am now somewhere on land in a hut, or maybe in a boat
this.fish.energy -= 0.01;
if (this.fish.energy <= 0) {
// I've gone bad :-(
this.fish.remove();
this.cnode.remove();
}
}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// specify Fish Move behavior
$wnd.xh.FishMovebehavior = function FishMovebehavior() { // (destCellTypeNot) {
//$wnd.xh.FishMovebehavior.destCellTypeNot = destCellTypeNot;
}
$wnd.xh.FishMovebehavior.prototype.postConfigure = function() {
this.fish = this.cnode.parent();
this.fish.xpath("ancestor::IslandSystem/FishBehaviors").append(this.cnode.remove());
};
$wnd.xh.FishMovebehavior.prototype.act = function() {
//this.fish.println("I am " + this.fish.name() + " moving");
if (this.fish.parent() == null) {
// I've been eaten, so remove this behavior from the simulation
this.cnode.remove();
return;
}
var pname = this.fish.parent().xhc().name();
if (pname == "Ocean") {
this.moveSelfToGrid();
}
else if ((pname == "OceanCell") || (pname == "CoastCell")) {
// move randomly in the grid
if (!this.nibble()) {
this.move();
}
}
else if (pname == "Avatar") {
// I've been caught, so don't move
}
else {
//$wnd.console.log("ERROR " + this.fish.parent().name()); // Hut, LandCell, etc.
}
};
$wnd.xh.FishMovebehavior.prototype.moveSelfToGrid = function() {
// DO NOT run this code in postConfigure(); it prevents a next Fish node from obtaining a Fishbehavior
// move this.fish to a random position within the ocean part of the grid
var ix = $wnd.Math.floor($wnd.xh.random() * oceanCellArr.length);
var fcell = oceanCellArr[ix];
fcell.append(this.fish.remove());
};
$wnd.xh.FishMovebehavior.prototype.nibble = function() {
// a fish nibbles everything it's near, to see if it's food
// there's a higher probability of stopping to nibble if the Avatar has a FishingRod, than if the Avatar has no FishingRod
var neighbor = this.fish.prev();
if (!neighbor) {return false;}
var rnum = $wnd.xh.random();
var fishingRod = null;
var nname = neighbor.xhc().name();
switch (nname) {
case "Avatar": // possibly nibble the avatar/player
//this.fish.println("rnum: " + rnum);
fishingRod = neighbor.xpath("FishingRod");
if (fishingRod) {
if (rnum > 0.99) { // 0.9
return false;
}
}
else {
if (rnum > 0.9) { // 0.5
return false;
}
}
break;
case "Anopheles": // http://www.primordion.com/Xholon/gwt/wb/editwb.html?app=ff588e5f277f06fd9caa295e68fdc910&src=gist
if (rnum > 0.8) {
// eat the mosquito
neighbor.remove();
this.fish.energy += 10;
}
return false;
default:
//this.fish.println(this.fish.name() + " is co-located with " + neighbor.name());
return false;
}
//this.fish.println(this.fish.name() + " is nibbling a " + nname + (fishingRod ? " who is using a fishing rod" : ""));
// become the first child in the current cell, making it possible for the Avatar to "take prev;"
//this.fish.parent().prepend(this.fish.remove());
return true;
};
$wnd.xh.FishMovebehavior.prototype.move = function() {
var foundNewLocation = false;
var count = 0;
while ((!foundNewLocation) && (count < 1)) { // 10
var moveX = $wnd.Math.floor($wnd.xh.random() * 3) - 1;
var moveY = $wnd.Math.floor($wnd.xh.random() * 3) - 1;
if ((moveX == 0) && (moveY == 0)) {
return;
}
var portX = -1;
var portY = -1;
if (moveX > 0) {
portX = 1; //IGrid.P_EAST
}
else {
portX = 3; //IGrid.P_WEST
}
if (moveY > 0) {
portY = 0; //IGrid.P_NORTH
}
else {
portY = 2; //IGrid.P_SOUTH
}
count++;
var destination = this.fish.parent();
if (moveX != 0) {
destination = destination.port(portX);
}
if (moveY != 0) {
destination = destination.port(portY);
}
if (destination && (destination.xhc().name() != "LandCell")) { //$wnd.xh.FishMovebehavior.destCellTypeNot)) {
destination.append(this.fish.remove());
foundNewLocation = true;
}
}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// specify Cat Move behavior
$wnd.xh.CatMovebehavior = function CatMovebehavior() {}
$wnd.xh.CatMovebehavior.prototype.postConfigure = function() {
this.cat = this.cnode.parent();
this.cat.mass = 2;
this.cat.xpath("ancestor::IslandSystem/AnimalBehaviors").append(this.cnode.remove());
this.cat.avatar = null; // this is for later use, if an Avatar gives the Cat a treat
};
$wnd.xh.CatMovebehavior.prototype.act = function() {
//this.cat.println("I am " + this.cat.name() + " moving");
if (this.cat.parent() == null) {
// I've been eaten, so remove this behavior from the simulation
this.cnode.remove();
}
else if (this.cat.parent().xhc().name() == "Island") {
this.moveSelfToGrid();
}
else if (this.cat.prev() && (this.cat.prev().xhc().name() == "CatTreat")) {
this.cat.append(this.cat.prev().remove());
// TODO stick close to Avatar if there is also a co-located Avatar
if (this.cat.prev() && (this.cat.prev().xhc().name() == "Avatar")) {
this.cat.avatar = this.cat.prev();
}
}
else if (this.cat.parent().xhc().name().endsWith("Cell")) { // FieldCell OceanCell LandCell
// move randomly in the grid
this.move();
}
else if (this.cat.parent().xhc().name() == "Avatar") {
// I've been caught, so don't move
}
else {
$wnd.console.log("ERROR " + this.cat.parent().name());
}
this.beFriendly();
};
$wnd.xh.CatMovebehavior.prototype.moveSelfToGrid = function() {
// DO NOT run this code in postConfigure(); it prevents a next Cat node from obtaining a Catbehavior
// move this.cat to a random position within the ocean part of the grid
var ix = $wnd.Math.floor($wnd.xh.random() * landCellArr.length);
var fcell = landCellArr[ix];
fcell.append(this.cat.remove());
};
$wnd.xh.CatMovebehavior.prototype.move = function() {
var foundNewLocation = false;
var count = 0;
while ((!foundNewLocation) && (count < 1)) { // 10
var moveX = $wnd.Math.floor($wnd.xh.random() * 3) - 1;
var moveY = $wnd.Math.floor($wnd.xh.random() * 3) - 1;
if ((moveX == 0) && (moveY == 0)) {
return;
}
var portX = -1;
var portY = -1;
if (moveX > 0) {
portX = 1; //IGrid.P_EAST
}
else {
portX = 3; //IGrid.P_WEST
}
if (moveY > 0) {
portY = 0; //IGrid.P_NORTH
}
else {
portY = 2; //IGrid.P_SOUTH
}
count++;
var destination = this.cat.parent();
if (moveX != 0) {
destination = destination.port(portX);
}
if (moveY != 0) {
destination = destination.port(portY);
}
if (destination && (destination.xhc().name() != "OceanCell")) {
destination.append(this.cat.remove());
foundNewLocation = true;
}
}
};
$wnd.xh.CatMovebehavior.prototype.beFriendly = function() {
if (this.cat.avatar && (this.cat.first() && this.cat.first().xhc().name() == "CatTreat")) {
// the cat is friendly with an Avatar, and the cat still remembers (is carrying) the CatTreat
if (Math.random() < 0.01) {
// cat decides to leave the avatar
this.cat.avatar = null;
this.cat.first().remove();
}
else {
if (!(this.cat.avatar == this.cat.next())) {
this.cat.avatar.before(this.cat.remove());
}
}
}
};
var oceanCellArr = [];
var landCellArr = [];
var coastCellArr = [];
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// specify behavior to add all Cell nodes into class-specific arrays, for use by moveSelfToGrid functions
// position system Avatar
$wnd.xh.CellArraybehavior = function CellArraybehavior() {}
$wnd.xh.CellArraybehavior.prototype.postConfigure = function() {
this.isys = this.cnode.parent();
var space = this.isys.xpath("Space");
var row = space.first();
while (row) {
var cell = row.first();
while (cell) {
if (INCOGNITA) {
cell["incognita"] = INCOGNITA;
}
switch (cell.xhc().name()) {
case "OceanCell":
oceanCellArr.push(cell);
break;
case "LandCell":
landCellArr.push(cell);
break;
case "CoastCell":
coastCellArr.push(cell);
break;
default: break;
}
cell = cell.next();
}
row = row.next();
}
//ava.action('go xpath()');
// each Avatar should go in it's own random coastCell, so use Math.random() rather than xh.random()
//var ix = $wnd.Math.floor($wnd.Math.random() * coastCellArr.length);
//var fcell = coastCellArr[ix];
//fcell.append(ava.remove());
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// specify City behavior
$wnd.xh.Citybehavior = function Citybehavior() {}
$wnd.xh.Citybehavior.prototype.postConfigure = function() {
this.city = this.cnode.parent();
var ix = $wnd.Math.floor($wnd.xh.random() * coastCellArr.length);
var ccell = coastCellArr[ix];
ccell.append(this.city.remove());
this.cnode.remove();
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// specify TreasureChests behavior
$wnd.xh.TreasureChestsbehavior = function TreasureChestsbehavior() {}
$wnd.xh.TreasureChestsbehavior.prototype.postConfigure = function() {
this.treasureChests = this.cnode.parent();
var tchest = this.treasureChests.first();
while (tchest && tchest.xhc().name() == "TreasureChest") {
var ix = $wnd.Math.floor($wnd.xh.random() * coastCellArr.length);
var ccell = coastCellArr[ix];
var tchestNext = tchest.next();
ccell.append(tchest.remove());
tchest = tchestNext;
}
this.cnode.remove();
};
var canvas = $doc.querySelector("div#xhcanvas > canvas");
var ctx = canvas.getContext("2d");
var xyMult = GRID_CELL_SIZE;
var xyAdd = GRID_CELL_SIZE / 2;
var radius = 6;
var startAngle = 0;
var endAngle = 2 * Math.PI;
var anticlockwise = false;
// draw a circle around the Avatar's current location in the grid
$wnd.xh.postStep2 = function() {
var ancestorNode = ava.obj();
var rcObj = null;
while (ancestorNode) {
if (ancestorNode.xhc() && ancestorNode.xhc().name().endsWith("Cell")) { // OceanCell LandCell CoastCell
rcObj = ancestorNode.obj();
break;
}
ancestorNode = ancestorNode.parent();
}
if (rcObj) {
ctx.strokeStyle = "blue";
var x = rcObj.col * xyMult + xyAdd;
var y = rcObj.row * xyMult + xyAdd;
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
ctx.stroke();
}
}
// this should run once at the beginning, to label each Island with it's numeric islandID
$wnd.xh.postStep = function() {
//var canvas = $doc.querySelector("div#xhcanvas > canvas");
//var ctx = canvas.getContext("2d");
ctx.font = '48px monospace';
ctx.fillStyle = "black";
var arr = $wnd.xh.islandIDNodes; // created by GridCellPatternbehavior.js
if (arr) {
for (var i = 0; i < arr.length; i++) {
var islandIDNode = arr[i];
var str = islandIDNode.str;
var x = islandIDNode.x * GRID_CELL_SIZE;
var y = islandIDNode.y * GRID_CELL_SIZE;
ctx.fillText(str, x, y);
}
$wnd.xh.postStep = $wnd.xh.postStep2; // null;
}
}
// add HTML links to documentation for Island game
/*var xhgraph = $doc.querySelector("div#xhgraph");
xhgraph.innerHTML =
'<div>' +
'<h3>Read the Island documentation:</h3>' +
'<p><a href="http://www.jensplanet.com/islandsgame/recipecatalogue.html" target="_blank">Recipe Catalogue</a></p>' +
'<p><a href="http://www.jensplanet.com/islandsgame/objectcatalogue.html" target="_blank">Object Catalogue</a></p>' +
'<p><a href="http://www.jensplanet.com/islandsgame/playermanual.html" target="_blank">Player Manual</a></p>' +
'<p><a href="http://www.primordion.com/Xholon/wiki/IslandGame/" target="_blank">Island Game Wiki</a></p>' +
'</div>'
;*/
//# sourceURL=IslandSystembehavior.js
]]></IslandSystembehavior>
<GreenTreebehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.GreenTreebehavior();
//# sourceURL=GreenTreebehavior.js
]]></GreenTreebehavior>
<RedTreebehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.RedTreebehavior();
//# sourceURL=RedTreebehavior.js
]]></RedTreebehavior>
<Fishbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.FishGrowbehavior();
//# sourceURL=FishGrowbehavior.js
]]></Fishbehavior>
<Fishbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.FishMovebehavior(); //("LandCell");
//# sourceURL=FishMovebehavior.js
]]></Fishbehavior>
<Catbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.CatMovebehavior();
//# sourceURL=CatMovebehavior.js
]]></Catbehavior>
<Springbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.Springbehavior();
//# sourceURL=Springbehavior.js
]]></Springbehavior>
<GridCellPatternbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode.parent();
var pNode = me.parent(); // GridCellPatterns node
this.cnode.remove();
//me.println("xpos:" + me.xpos + " ypos:" + me.ypos);
var gcpstr = me.text().trim();
var gcparr = gcpstr.split("\n");
var row = me.xpath("../../Space/FieldRow[" + me.ypos + "]");
var fcol = row.xpath("OceanCell[" + me.xpos + "]");
var col = fcol;
var ccArr = []; // cache CoastCells
for (var i = 0; i < gcparr.length; i++) {
var gcpline = gcparr[i].trim();
//me.println(gcpline);
for (var j = 0; j < gcpline.length; j++) {
switch (gcpline[j]) {
case "L":
col.xhc("LandCell");
break;
case "#":
col.xhc("LandCell");
// cache the node where the islandID will be printed at the start of the game
if (!$wnd.xh.islandIDNodes) {
$wnd.xh.islandIDNodes = [];
}
var islandIDNode = {};
islandIDNode.x = Number(me.xpos) + j;
islandIDNode.y = Number(me.ypos) + i;
islandIDNode.str = me.islandID;
$wnd.xh.islandIDNodes.push(islandIDNode);
break;
case "C":
col.xhc("CoastCell");
ccArr.push(col);
break;
default: // "."
break;
}
col = col.next();
}
fcol = fcol.port(2);
col = fcol;
}
// randomly select a CoastCell where Avatar might start
var ccNode = ccArr[Math.floor(Math.random() * ccArr.length)];
if (!pNode["avatarStartArr"]) {
pNode["avatarStartArr"] = [];
}
pNode["avatarStartArr"].push(ccNode);
}
}
//# sourceURL=GridCellPatternbehavior.js
]]></GridCellPatternbehavior>
<IslandSystembehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.CellArraybehavior();
//# sourceURL=IslandSystembehavior2.js
]]></IslandSystembehavior>
<Citybehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.Citybehavior();
//# sourceURL=Citybehavior.js
]]></Citybehavior>
<TreasureChestsbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var beh = new $wnd.xh.TreasureChestsbehavior();
//# sourceURL=TreasureChestsbehavior.js
]]></TreasureChestsbehavior>
<IslandControlCentrebehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
const AVA_ENERGY_LOSS_INTERVAL = 100; // Avatar will lose one unit of energy each N timesteps
var me, count, ava, timestep, beh = {
postConfigure: function() {
me = this.cnode.parent();
count = 0;
ava = $wnd.xh.avatar();
timestep = Number($wnd.xh.param("TimeStep"));
me.xpath("ancestor::IslandSystem/AnimalBehaviors").after(this.cnode.remove());
//this.cnode.remove();
//$wnd.console.log("IslandControlCentrebehavior starting...");
},
act: function() {
// don't let Meteor start until the app has a chance to deploy everything to their initial/default GridCells
if (count < 2) {
//$wnd.console.log("IslandControlCentrebehavior waiting...");
count++;
}
else if (count == 2) {
//$wnd.console.log("IslandControlCentrebehavior ReadyForMeteor true");
$wnd.xh.param("ReadyForMeteor","true");
//this.cnode.remove();
count++;
}
timestep++;
if (timestep >= AVA_ENERGY_LOSS_INTERVAL) {
ava["energy"] = String(Number(ava["energy"]) - 1);
timestep = 0;
}
}
}
//# sourceURL=IslandControlCentrebehavior.js
]]></IslandControlCentrebehavior>
<AvatarLoggerbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
const DB_NAME = "IslandGameDB";
const DB_VERSION = 1;
const DB_SESSION_STORE_NAME = "session";
const DB_SESSION_STORE_KEY = ["sessionid"];
const DB_AVASTATE_STORE_NAME = "avastate";
const DB_AVASTATE_STORE_KEY = ["sessionid","timestep"];
const DB_ENABLED = false;
var me, ava, newState, prevState, newEnergy, prevEnergy, animate, idbs, idbssetup, idbsobj, sessionRecord, beh = {
postConfigure: function() {
me = this.cnode.parent();
if (DB_ENABLED) {
idbs = $wnd.xh.service("IndexedDBService");
idbssetup = false;
}
sessionRecord = null;
ava = $wnd.xh.avatar();
// formatName, node, efParams, writeToTab, returnString
//prevState = this.getXport();
newState = "";
this.hashifySXpres(ava.obj());
prevState = newState;
newEnergy = ava.energy;
prevEnergy = newEnergy;
var jsonStr = '"timestep":"' + $wnd.xh.param("TimeStep") + '",';
jsonStr += '"state":"' + prevState + '",';
jsonStr += '"energy":"' + prevEnergy + '"';
animate = me.xpath("../Animate"); // d3cp Animate node
this.saveToDB("{" + jsonStr + "}");
},
act: function() {
if (DB_ENABLED && !sessionRecord) {
var session = this.makeSessionRecord();
//$wnd.console.log(session);
var rc = this.saveSessionToDB(session);
if (rc) {
sessionRecord = session;
$wnd.console.log(sessionRecord);
// now create data store for Avatar state records
//var avastateObj = idbsobj.setup(DB_NAME, DB_AVASTATE_STORE_NAME, DB_AVASTATE_STORE_KEY);
}
}
newState = "";
newEnergy = ava.energy;
this.hashifySXpres(ava.obj()); //parent());
if ((newState != prevState) || (prevEnergy != newEnergy)) {
prevState = newState;
prevEnergy = newEnergy;
var jsonStr = '"timestep":"' + $wnd.xh.param("TimeStep") + '",';
jsonStr += '"state":"' + prevState + '",';
jsonStr += '"energy":"' + ava.energy + '"';
this.saveToDB("{" + jsonStr + "}");
}
},
makeSessionRecord: function() {
var playerid = "X"; // TODO get this from the human player
var session = {};
var params = $wnd.location.search;
session.sessionid = playerid + Date.now(); // key
session.gameid = new URLSearchParams(params).get("app");
session.gamerev = new URLSearchParams(params).get("apprev") || "00";
session.islandID = ava.islandID;
return session;
},
saveSessionToDB: function(session) {
var rc = false;
idbsobj = idbs.obj();
if (idbsobj && !idbssetup) {
idbsobj.setup(DB_NAME, [DB_SESSION_STORE_NAME, DB_AVASTATE_STORE_NAME], [DB_SESSION_STORE_KEY, DB_AVASTATE_STORE_KEY]);
idbssetup = true;
}
if (idbsobj) {
idbsobj.update(DB_NAME, DB_SESSION_STORE_NAME, DB_SESSION_STORE_KEY, session);
rc = true;
}
return rc;
},
/**
* Recursively hashify a Xholon subtree, with output in a Lisp S-expression format.
* @param node A node in the Xholon hierarchy.
* @param sb A String instance.
*/
hashifySXpres: function(node) {
newState += node.name("R^^^^^");
if (node.xhc().name() == "Space") {
newState += (" (Avatar)");
}
else if (node.first()) {
newState += " (";
var childNode = node.first();
while (childNode != null) {
this.hashifySXpres(childNode);
childNode = childNode.next();
if (childNode != null) {
newState += " ";
}
}
newState += ")";
}
else if (node["maxClones"] && (node.parent().xhc().name() != "Avatar")) {
newState += "*" + node["maxClones"];
}
},
saveToDB: function(str) {
if (ava.obj().xhc().name() == "GridCellPatterns") {
//var session = this.makeSessionRecord();
//$wnd.console.log(session);
//this.saveSessionToDB(session);
}
else {
//me.println(str);
ava.action("out caption " + str);
if (animate) {
//$wnd.console.log($wnd.xh.root());
//$wnd.console.log(ava.obj());
var animRoot = $wnd.xh.xpathExpr(ava.obj(), $wnd.xh.root().first());
//$wnd.console.log(animRoot);
animate.attr("AnimRoot", animRoot);
}
if (DB_ENABLED && idbsobj && sessionRecord) {
var jsObj = JSON.parse(str);
jsObj.sessionid = sessionRecord.sessionid;
jsObj.timestep = Number(jsObj.timestep); //$wnd.xh.param("TimeStep");
idbsobj.update(DB_NAME, DB_AVASTATE_STORE_NAME, DB_AVASTATE_STORE_KEY, jsObj);
}
}
}
}
//# sourceURL=AvatarLoggerbehavior.js
]]></AvatarLoggerbehavior>
<MenuHandlerbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
// default Avatar name template "r:c_i^"
const MAX_INVENTORY_MASS = 10;
const DEFAULT_INVENTORY_MASS = 1;
const INCOGNITA_STYLE = 9; // 1 = contextNode only, 5 = contextNode + 4 cardinal neighbors, 9 = contextNode + 8 cardinal neighbors
var me, ava, xhcanvas, akmObj, initialized, menuDiv, cachedAkmObj, gameOver, dataExportsNode, dataExportedToPage, beh = {
postConfigure: function() {
me = this.cnode.parent();
ava = $wnd.xh.avatar();
ava["menu"] = null;
initialized = false;
gameOver = false;
dataExportsNode = $wnd.xh.root().first().xpath("DataExports");
dataExportedToPage = false;
// #xhcanvas
xhcanvas = $doc.querySelector("#xhcanvas");
xhcanvas.style.display = "flex";
xhcanvas.style.alignItems = "start";
// #xhcanvas > div#d3cp
var d3cpDiv = $doc.createElement("DIV");
d3cpDiv.id = "d3cp";
xhcanvas.appendChild(d3cpDiv);
// #xhcanvas > div#menu
menuDiv = $doc.createElement("DIV");
menuDiv.id = "menu";
//menuDiv.innerText = "menus";
xhcanvas.appendChild(menuDiv);
$wnd.xh.css.style("#xhcanvas > div#menu {font-family: monospace; font-size: 16px; font-weight: 400; margin-left: 5px;}"); // 13.3333px;
// temporarily, at the start of the game, only allow numbered commands that can be used to select an Island
cachedAkmObj = $wnd.JSON.parse($wnd.xh.avatarKeyMap());
akmStr = '{';
// allow player to select which island their Avatar will inhabit
var welcome = "Welcome castaway.\n\nWhere will you live?\nPlease type a number:\n--------------------\n";
var gcps = $wnd.xh.root().first().xpath("GridCellPatterns");
var gcp = gcps.first();
var akmComma = ''; // no comma the first time
while (gcp) {
if (gcp.xhc().name() == "GridCellPattern") {
welcome += "" + gcp.islandID + ": Island " + gcp.islandID + "\n";
akmStr += akmComma + '"' + gcp.islandID + '":"become this islandID ' + gcp.islandID + ';go link' + gcp.islandID + ';pause;become this menu menu"';
akmComma = ',';
}
gcp = gcp.next();
}
//welcome += "?- ";
menuDiv.innerText = welcome;
akmStr += '}';
$wnd.xh.avatarKeyMap(akmStr);
},
act: function() {
if (!initialized && (ava.obj().xhc().name() != "GridCellPatterns")) {
akmObj = cachedAkmObj;
cachedAkmObj = null;
// remove all the numbered keys in the key map
for (var i = 0; i < 10; i++) {
akmObj[i] = "";
}
$wnd.xh.avatarKeyMap($wnd.JSON.stringify(akmObj));
//ava.action("build Options;become options mass 9999;enter options;build Elixir;become elixir maxClones 1;become elixir energy 100000;become elixir mass 1;exit;");
//ava.obj().first().first().anno("Explore mode");
initialized = true;
}
if (!gameOver && (ava.obj().xhc().name() == "GameOver")) {
akmObj = {p:"pause;become this menu menu"};
cachedAkmObj = null;
$wnd.xh.avatarKeyMap($wnd.JSON.stringify(akmObj));
gameOver = true;
}
if (dataExportedToPage) {
var xhdiv = $doc.querySelector("div#xhimg");
if (xhdiv) {
//arrMsg = "data is being written to web page";
xhdiv.innerHTML = null;
}
dataExportedToPage = false;
}
if (ava["incognita"]) {
var pnode = ava.parent();
// this.cat.parent().xhc().name().endsWith("Cell")) { // FieldCell OceanCell LandCell
while (pnode && !pnode.xhc().name().endsWith("Cell")) {
pnode = pnode.parent();
if (!pnode.xhc()) { // Application node does not have an xhc function
pnode = null;
}
}
if (pnode) {
switch(INCOGNITA_STYLE) {
case 1:
pnode["incognita"] = null;
break;
case 5:
pnode["incognita"] = null;
if (pnode.port(0)) {
// the avatar is probably inside a gridCell
for (var ii = 0; ii < 4; ii++) {
if (pnode.port(ii)) {
pnode.port(ii)["incognita"] = null;
}
}
}
break;
case 9:
pnode["incognita"] = null;
if (pnode.port(0)) {
// the avatar is probably inside a gridCell
// do the four cardinal directions (N E S W)
for (var ii = 0; ii < 4; ii++) {
if (pnode.port(ii)) {
pnode.port(ii)["incognita"] = null;
}
}
// do the other four directions
if (pnode.port(4)) { // Gmt
for (var ii = 4; ii < 8; ii++) {
if (pnode.port(ii)) {
pnode.port(ii)["incognita"] = null;
}
}
}
else { // Gvt
var north = pnode.port(0);
if (north) {
north.port(1)["incognita"] = null; // NE
north.port(3)["incognita"] = null; // NW
}
var south = pnode.port(2);
if (south) {
south.port(1)["incognita"] = null; // SE
south.port(3)["incognita"] = null; // SW
}
}
}
break;
default:
break;
}
}
delete ava["incognita"];
}
var key = ava["menu"];
if (key) {
if (cachedAkmObj) {
// restore cached avatarKeyMap
akmObj = cachedAkmObj;
cachedAkmObj = null;
$wnd.xh.avatarKeyMap($wnd.JSON.stringify(akmObj));
}
akmObj = $wnd.JSON.parse($wnd.xh.avatarKeyMap());
// remove all the numbered keys in the key map
for (var i = 0; i < 10; i++) {
akmObj[i] = "";
// TODO cache the current menu
}
var arr = null;
switch (key) {
case "drop": // "drop;"
this.cacheAndClearMenu();
ava.action("pause;");
arr = ["DROP"];
this.makeInventoryStr("drop", "", arr);
arr.push("p:continue");
if (menuDiv) {menuDiv.innerText = arr.join("\n");}
break;
case "eat": // "eat;become this energy +=50"
this.cacheAndClearMenu();
ava.action("pause;");
arr = ["EAT"];
this.makeInventoryStr("eat", "become this energy +=", arr);
arr.push("p:continue");
if (menuDiv) {menuDiv.innerText = arr.join("\n");}
break;
case "recipe": // "recipe;"
this.cacheAndClearMenu();
ava.action("pause;");
arr = ["RECIPE"];
this.makeRecipesStr("recipe", arr);
arr.push("p:continue");
//arr.push("?- ");
if (menuDiv) {menuDiv.innerText = arr.join("\n");}
break;
case "take": // "take;" or "takeclone;"
this.cacheAndClearMenu();
ava.action("pause;");
arr = ["TAKE"];
this.makeSiblingsStr("take", arr);
arr.push("p:continue");
if (menuDiv) {menuDiv.innerText = arr.join("\n");}
break;
case "menu": // menu of menus
ava.action("pause;");
arr = ["MENUS"];
arr.push("d:DROP menu");
arr.push("e:EAT menu");
arr.push("r:RECIPE menu");
arr.push("t:TAKE menu");
arr.push("");
arr.push("MOVING");
arr.push("four arrow keys");
arr.push("n:enter (optional)");
arr.push("x:exit (optional)");
// "<" and ">" ?
arr.push("[:paddle leftside");
arr.push("]:paddle rightside");
arr.push("");
arr.push("OTHER");
arr.push("p:pause or unpause");
arr.push("s:step");
arr.push("$:export data");
if (menuDiv) {menuDiv.innerText = arr.join("\n");}
ava.action("pause");
break;
case "exportdata": //
this.cacheAndClearMenu();
ava.action("pause;");
arr = ["EXPORT DATA"];
this.exportData(arr);
dataExportedToPage = true;
arr.push("p:continue");
if (menuDiv) {menuDiv.innerText = arr.join("\n");}
break;
default: break;
}
ava["menu"] = null;
$wnd.xh.avatarKeyMap($wnd.JSON.stringify(akmObj));
}
},
// cache the current menu, and then set current menu to {}
cacheAndClearMenu: function() {
cachedAkmObj = akmObj;
akmObj = {};
akmObj["p"] = "pause;become this menu menu";
},
// export data to Dev Tools console
exportData: function(arr) {
var arrMsg = "data is being exported";
var xhdiv = $doc.querySelector("div#xhimg");
if (xhdiv) {
arrMsg = "data is being written to web page";
}
else {
arrMsg = "data has been written to Dev Tools";
}
arr.push(arrMsg);
arr.push("session data is ready for WebRTC Island Admin");
arr.push("avatar-state data is ready for WebRTC Island Admin");
var dataExportNode = null;
if (dataExportsNode) {
// remove any existing data
var denode = dataExportsNode.first();
while (denode) {
var denodeNext = denode.next();
denode.remove();
denode = denodeNext;
}
dataExportsNode.append('<DataExport></DataExport>');
dataExportNode = dataExportsNode.last();
}
var db;
var request = indexedDB.open("IslandGameDB");
request.onerror = function(event) {
console.log("Oops");
};
request.onsuccess = function(event) {
db = event.target.result;
var objectStore = db.transaction("session").objectStore("session");
objectStore.getAll().onsuccess = function(event) {
var jsonStr = JSON.stringify(event.target.result).trim();
jsonStr = jsonStr.substring(1, jsonStr.length-1); // remove [ and ]
var results = "[\n" + jsonStr.replace(/},{/gi, "},\n{") + "\n]";
if (xhdiv) {
var sessionDiv = $doc.createElement("PRE");
xhdiv.appendChild(sessionDiv);
sessionDiv.innerHTML = results;
}
else {
console.log(results);
}
if (dataExportNode) {
var xmlStr = '<Attribute_String roleName="SessionData">' + results + '</Attribute_String>';
dataExportNode.append(xmlStr);
}
};
var objectStore = db.transaction("avastate").objectStore("avastate");
objectStore.getAll().onsuccess = function(event) {
var jsonStr = JSON.stringify(event.target.result).trim();
jsonStr = jsonStr.substring(1, jsonStr.length-1); // remove [ and ]
var results = "[\n" + jsonStr.replace(/},{/gi, "},\n{") + "\n]";
if (xhdiv) {
var avastateDiv = $doc.createElement("PRE");
xhdiv.appendChild(avastateDiv);
avastateDiv.innerHTML = results;
}
else {
console.log(results);
}
if (dataExportNode) {
var xmlStr = '<Attribute_String roleName="AvastateData">' + results + '</Attribute_String>';
dataExportNode.append(xmlStr);
}
};
};
},
// drop 0:fish 1:fishingRod 2:fish 3:stick
// eat
makeInventoryStr: function(command, extras, arr) {
var item = ava.first();
var counter = 0;
var extrazz = "";
while (item) {
if (extras && (command == "eat")) {
if (item["energy"] && (Number(item["energy"])) > 0) {
extrazz = Math.floor(Number(item["energy"])) + ";";
}
else {
// this is non-editable, so don't add it to the EAT menu
item = item.next();
continue;
}
}
arr.push(counter + ":" + item.name("R^^^^^"));
akmObj[counter] = command + " " + item.name("r:c_i^") + ";" + extras + extrazz + "start;become this menu menu;";
counter++;
item = item.next();
extrazz = "";
}
},
// take
makeSiblingsStr: function(command, arr) {
var inventoryMass = this.sumInventory(ava, "mass", DEFAULT_INVENTORY_MASS);
//$wnd.console.log("inventoryMass " + inventoryMass);
if (inventoryMass >= MAX_INVENTORY_MASS) {return;}
var item = ava.obj().first();
var counter = 0;
while (item) {
if (item != ava && (item.first()) && (item.first())["maxClones"]) {
var cloneable = item.first();
while (cloneable) {
if (cloneable["maxClones"] && ((cloneable["mass"] && ((inventoryMass + Number(cloneable["mass"])) <= MAX_INVENTORY_MASS)) || !cloneable["mass"])) {
arr.push(counter + ":" + cloneable.name("R^^^^^"));
akmObj[counter] = "enter " + item.name("r:c_i^") + ";" + "takeclone" + " " + cloneable.name("r:c_i^") + ";exit;start;become this menu menu;";
counter++;
}
cloneable = cloneable.next();
}
}
else if ((item != ava) && !item["maxClones"] && ((item["mass"] && ((inventoryMass + Number(item["mass"])) <= MAX_INVENTORY_MASS)) || !item["mass"])) {
arr.push(counter + ":" + item.name("R^^^^^"));
akmObj[counter] = command + " " + item.name("r:c_i^") + ";start;become this menu menu;";
counter++;
}
item = item.next();
}
},
// sum the value of some attribute, for all items in inventory
// ex: var sum = this.sumInventory("mass", 1);
sumInventory: function(node, attrName, defaultAttrValue) {
var item = node.first();
var sum = 0;
while (item) {
var attrValue = Number(item[attrName]);
if (attrValue) { // true 123 "123" false "abc" "" null undefined NaN
sum += attrValue;
}
else {
sum += defaultAttrValue;
}
sum += this.sumInventory(item, attrName, defaultAttrValue);
item = item.next();
}
return sum;
},
// recipe 0:basket 1:fishingRod 2:hut
makeRecipesStr: function(command, arr) {
var counter = 0;
var recipeService = $wnd.xh.service("RecipeService");
var nmsg = recipeService.call(-3895, "RecipeService-MinecraftStyleRecipeBook-Island", me);
var namesStr = null;
if (nmsg) {
namesStr = nmsg.data;
}
if (namesStr) {
var namesArr = namesStr.split(",");
var potentialsArr = []; // recipes that currently lack sufficient ingredients
for (var i = 0; i < namesArr.length; i++) {
var name = namesArr[i];
var imsg = recipeService.call(-3896, ["RecipeService-MinecraftStyleRecipeBook-Island", name], me); // get ingredients
if (imsg && imsg.data && imsg.data[0]) {
var ingredients = imsg.data[0]["ingredients"];
var ingredientsStr = ingredients.join(",");
var bool = ava.includes(ingredientsStr, "^^C^^^", ",");
var istr = ingredientsStr.replace(/,/gi, '+');
if (bool) {
var nameLcase = name.charAt(0).toLowerCase() + name.substring(1);
arr.push(counter + ":" + name + " = " + istr);
akmObj[counter] = command + " " + name + ";take " + nameLcase + ";start;become this menu menu;";
counter++;
}
else {
potentialsArr.push(name + " = " + istr);
}
}
}
// put the potential recipes after those that are currently actionable
Array.prototype.push.apply(arr, potentialsArr);
}
}
}
//# sourceURL=MenuHandlerbehavior.js
]]></MenuHandlerbehavior>
<MqttClientbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
var me, ava, keyMap, beh = {
postConfigure: function() {
me = this.cnode.parent();
ava = $wnd.xh.avatar();
this.startConnect();
},
startConnect: function() {
me.clientID = me.clientidPrefix + parseInt(Math.random() * 100);
me.println("Connecting to: " + me.host + " on port: " + me.port);
me.client = new $wnd.Paho.MQTT.Client(me.host, Number(me.port), me.clientID);
me.client.onConnectionLost = this.onConnectionLost;
me.client.onMessageArrived = this.onMessageArrived;
me.client.connect({
onSuccess: this.onConnect,
//userName:"USERNAME",
//password:"PASSWORD"
//userName: "pizerosensors",
//password: "password",
//useSSL: true
});
},
onConnect: function() {
me.println("Subscribing to topic: " + me.topic);
me.client.subscribe(me.topic);
me.println("Temperature,Humidity,Light,Sound,Motion,Where,Datetime");
},
onConnectionLost: function(responseObject) {
me.println("Connection Lost");
if (responseObject.errorCode !== 0) {
me.println("onConnectionLost: " + responseObject.errorMessage);
}
},
onMessageArrived: function(message) {
//me.println("Topic: " + message.destinationName + " " + message.payloadString);
beh.processPayload(message.payloadString);
},
startDisconnect: function() {
me.client.disconnect();
me.println("Disconnected");
},
processPayload: function(json) {
// json is a JSON Oject ex: {"t":24.2,"h":33.0,"l":1248} {"t":24.3,"h":37.0,"l":40265,"m":0,"r":[2023, 1, 9, 1, 11, 49, 3]}
if (json.trim().startsWith("{")) {
var jsobj = JSON.parse(json);
var dt = jsobj.r.map(ele => ele < 10 ? "0" + ele : "" + ele);
//me.println(`${jsobj.t},${jsobj.h},${jsobj.l},${jsobj.s},${jsobj.m},${jsobj.w},${dt[1]}/${dt[2]}/${dt[0]}T${dt[4]}:${dt[5]}:${dt[6]}`);
}
else {
//me.println(json);
keyMap = JSON.parse($wnd.xh.avatarKeyMap());
//me.println(keyMap);
if (json == "NORTH") {
//me.println("UP: " + keyMap["UP"]);
//ava.action("go north");
ava.action(keyMap["UP"]);
}
else if (json == "SOUTH") {
//me.println("DOWN: " + keyMap["DOWN"]);
ava.action(keyMap["DOWN"]);
}
else if (json == "WEST") {
//me.println("LEFT: " + keyMap["LEFT"]);
ava.action(keyMap["LEFT"]);
}
else if (json == "EAST") { // "EAST"
//me.println("RIGHT: " + keyMap["RIGHT"]);
ava.action(keyMap["RIGHT"]);
}
else if (json == "TAKE0") { // "TAKE0"
//me.println("TAKE0: " + keyMap["t"]);
ava.action(keyMap["t"]); // is there a way to handle 0 ?
}
else {
me.println("unknown:" + json);
}
}
}
}
//# sourceURL=MqttClientbehavior.js
]]></MqttClientbehavior>
<SvgClient><Attribute_String roleName="svgUri"><![CDATA[data:image/svg+xml,
<svg width="180" height="50" xmlns="http://www.w3.org/2000/svg">
<g>
<title>Generate current state as JSON in the out tab</title>
<text x="5" y="35" fill="grey" stroke="black" font-size="35">Gen JSON</text>
<rect id="IslandSystem/GenStateAsJSON" fill="#d0c883" fill-opacity="0.5" height="50" width="180" x="0" y="0"/>
<!--<text x="0" y="0" fill="#98FB98">Gen JSON</text>-->
<!--<g>
<title>GenStateAsJSONscript</title>
<rect id="IslandSystem/GenStateAsJSONscript" fill="#6AB06A" height="10" width="10" x="55" y="0"/>
</g>
<g>
<title>PlantBehaviors</title>
<rect id="IslandSystem/PlantBehaviors" fill="orange" height="10" width="10" x="75" y="0"/>
</g>
<g>
<title>FishBehaviors</title>
<rect id="IslandSystem/FishBehaviors" fill="orange" height="10" width="10" x="95" y="0"/>
</g>
<g>
<title>GridCellPatterns</title>
<rect id="IslandSystem/GridCellPatterns" fill="blue" height="10" width="10" x="115" y="0"/>
</g>
<g>
<title>City</title>
<rect id="IslandSystem/Space/descendant::City" fill="purple" height="10" width="10" x="135" y="0"/>
</g>-->
</g>
</svg>
]]></Attribute_String><Attribute_String roleName="setup">${MODELNAME_DEFAULT},${SVGURI_DEFAULT}</Attribute_String></SvgClient>
</XholonWorkbook>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment