├── .gitignore
├── LICENSE.md
├── README.md
├── _config.yml
├── dist
├── CopycatJS.js
├── btn_pause.png
├── btn_play.png
├── btn_reset.png
├── btn_step.png
├── cc_logo.png
├── index.html
└── styles.css
├── docs
└── Slipnet.pdf
├── md_screenshot-1.jpg
└── src
├── Bond.js
├── Codelets
├── BondBuilder.js
├── BondStrengthTester.js
├── BottomUpBondScout.js
├── BottomUpCorrespondenceScout.js
├── BottomUpDescriptionScout.js
├── Breaker.js
├── CodeletBase.js
├── CodeletFactory.js
├── CodeletUtils.js
├── CorrespondenceBuilder.js
├── CorrespondenceStrengthTester.js
├── DescriptionBuilder.js
├── DescriptionStrengthTester.js
├── GroupBuilder.js
├── GroupScout_WholeString.js
├── GroupStrengthTester.js
├── ImportantObjectCorrespondenceScout.js
├── ReplacementFinder.js
├── RuleBuilder.js
├── RuleScout.js
├── RuleStrengthTester.js
├── RuleTranslator.js
├── TopDownBondScout_Category.js
├── TopDownBondScout_Direction.js
├── TopDownDescriptionScout.js
├── TopDownGroupScout_Category.js
└── TopDownGroupScout_Direction.js
├── Coderack.js
├── ConceptMapping.js
├── ConsoleReporter.js
├── Copycat.js
├── Correspondence.js
├── Description.js
├── Group.js
├── Letter.js
├── RandGen.js
├── Replacement.js
├── Rule.js
├── SlipLink.js
├── SlipNode.js
├── Slipnet.js
├── Temperature.js
├── UI
├── ArrowGraphic.js
├── BatchmodeUi.js
├── BondsGraphic.js
├── CoderackUi.js
├── CopycatUi.js
├── CorrsGraphic.js
├── DescriptionsGraphic.js
├── Dialog.js
├── Flasher.js
├── GroupsGraphic.js
├── HelpDialog.js
├── InputUi.js
├── LettersGraphic.js
├── ReplacementsGraphic.js
├── RuleGraphic.js
├── SlipnetUi.js
├── StringGraphic.js
├── TopbarUi.js
├── UiUtils.js
├── WorkspaceHeaderUi.js
├── WorkspaceUi.js
├── btn_pause.png
├── btn_play.png
├── btn_reset.png
├── btn_step.png
├── cc_logo.png
└── styles.css
├── Workspace.js
├── WorkspaceObject.js
├── WorkspaceString.js
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Paul Geiger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # copycat-js
2 |
3 | [](https://paul-g2.github.io/copycat-js/)
4 |
5 |
6 |
7 | This is a javascript version of the [Copycat](https://en.wikipedia.org/wiki/Copycat_(software)) program
8 | developed by [Douglas Hofstadter](https://cogs.indiana.edu/directory/faculty/profile.php?faculty=dughof)
9 | and [Melanie Mitchell](https://melaniemitchell.me).
10 |
11 | [You can try it out here.](https://paul-g2.github.io/copycat-js/)
12 |
13 | Copycat is a computer model of human analogy-making.
14 | It tries to solve letter puzzles of the form "abc is to abd as ijk is to what?"
15 |
16 | It's internal workings are described in great detail in the books
17 | [Fluid Concepts and Creative Analogies](https://www.amazon.com/Fluid-Concepts-Creative-Analogies-Fundamental/dp/0465024750), and
18 | [Analogy-Making as Perception](https://www.amazon.com/Analogy-Making-Perception-Computer-Modeling-Connectionism/dp/026251544X).
19 |
20 | I got interested in Copycat after reading the Fluid Concepts and Creative Analogies book,
21 | but I couldn't find a demo that was easy to install and use ... hence this project.
22 |
23 | The [original Copycat code](https://github.com/fargonauts/copycat-lisp) was written in lisp,
24 | which I am not fluent in, so I based this port
25 | (except for the UI) mainly on the [python version by Lucas Saldyt](https://github.com/fargonauts/copycat),
26 | occasionaly referring to the lisp version when something was unclear.
27 |
28 | The look of the UI is based on that of [James Marshall's Metacat](http://science.slc.edu/jmarshall/metacat/)
29 | (which is an extension of Copycat).
30 |
31 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-minimal
--------------------------------------------------------------------------------
/dist/btn_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Paul-G2/copycat-js/536ddbf5f776ac25aeb8081269a1808364b3c26a/dist/btn_pause.png
--------------------------------------------------------------------------------
/dist/btn_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Paul-G2/copycat-js/536ddbf5f776ac25aeb8081269a1808364b3c26a/dist/btn_play.png
--------------------------------------------------------------------------------
/dist/btn_reset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Paul-G2/copycat-js/536ddbf5f776ac25aeb8081269a1808364b3c26a/dist/btn_reset.png
--------------------------------------------------------------------------------
/dist/btn_step.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Paul-G2/copycat-js/536ddbf5f776ac25aeb8081269a1808364b3c26a/dist/btn_step.png
--------------------------------------------------------------------------------
/dist/cc_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Paul-G2/copycat-js/536ddbf5f776ac25aeb8081269a1808364b3c26a/dist/cc_logo.png
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
--------------------------------------------------------------------------------
/dist/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | background-color: #000000;
5 | }
6 | div#frame_area {
7 | position: relative;
8 | width: 100vw;
9 | height: 100vh;
10 | height: 100dvh;
11 | margin: 0;
12 | padding: 0;
13 | overflow: hidden;
14 | background: #7092BE;
15 | }
16 | div#app_area {
17 | margin: 0;
18 | padding: 0;
19 | position: absolute;
20 | top: 0px;
21 | right: 0px;
22 | bottom: 0px;
23 | left: 0px;
24 | background: #e0d20b;
25 | }
26 | div#topbar_area {
27 | margin: 0;
28 | padding: 0;
29 | position: absolute;
30 | top: 0px;
31 | height: 7%;
32 | left: 0px;
33 | right: 0px;
34 | background: #bfcbdf;
35 | }
36 | div#widgets_area {
37 | margin: 0;
38 | padding: 0;
39 | position: absolute;
40 | top: 7%;
41 | height: 93%;
42 | left: 0px;
43 | right: 0px;
44 | background: #a870be;
45 | }
46 | div#input_area {
47 | margin: 0;
48 | padding: 0;
49 | position: absolute;
50 | top: 0px;
51 | height: 15%;
52 | left: 0px;
53 | width: 60%;
54 | background: #b3ddcc;
55 | }
56 | div#workspace_header_area {
57 | margin: 0;
58 | padding: 0;
59 | position: absolute;
60 | top: 15%;
61 | height: 12%;
62 | left: 0px;
63 | width: 60%;
64 | background: #eeeeee;
65 | }
66 | div#workspace_area {
67 | margin: 0;
68 | padding: 0;
69 | position: absolute;
70 | top: 27%;
71 | height: 73%;
72 | left: 0px;
73 | width: 60%;
74 | background: #ffffff;
75 | }
76 | div#batchmode_area {
77 | margin: 0;
78 | padding: 0;
79 | position: absolute;
80 | top: 15%;
81 | height: 85%;
82 | left: 0px;
83 | width: 100%;
84 | background: #ffffff;
85 | display: none;
86 | }
87 | div#slipnet_area {
88 | margin: 0;
89 | padding: 0;
90 | position: absolute;
91 | top: 0px;
92 | bottom: 0px;
93 | left: 60%;
94 | width: 25%;
95 | background: #fffbcc;
96 | }
97 | div#coderack_area {
98 | margin: 0;
99 | padding: 0;
100 | position: absolute;
101 | top: 0px;
102 | bottom: 0px;
103 | left: 85%;
104 | width: 15%;
105 | background: #ffe5e0;
106 | }
107 | .noselect {
108 | -webkit-touch-callout: none;
109 | -webkit-user-select: none;
110 | -khtml-user-select: none;
111 | -moz-user-select: none;
112 | -ms-user-select: none;
113 | user-select: none;
114 | }
115 | p + ul {
116 | margin-top: -16px;
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/docs/Slipnet.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Paul-G2/copycat-js/536ddbf5f776ac25aeb8081269a1808364b3c26a/docs/Slipnet.pdf
--------------------------------------------------------------------------------
/md_screenshot-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Paul-G2/copycat-js/536ddbf5f776ac25aeb8081269a1808364b3c26a/md_screenshot-1.jpg
--------------------------------------------------------------------------------
/src/Bond.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * A Bond is a relation between Letters and/or Groups in the same string.
9 | * For example, a letter-successorship relation between 'a' and 'b' in the string 'abc'.
10 | */
11 | Namespace.Bond = class {
12 |
13 | /**
14 | * @constructor
15 | *
16 | * @param {WorkspaceObject} from - The source object of the bond.
17 | * @param {WorkspaceObject} to - The destination object of the bond.
18 | * @param {SlipNode} category - The bond category (successor, predecessor, or sameness).
19 | * @param {SlipNode} facet - The facet of the bond category (letterCategory or length).
20 | * @param {SlipNode} fromDescriptor - The facet's value for the source object, e.g. 'a'.
21 | * @param {SlipNode} toDescriptor - The facet's value for the destination object, e.g. 'b'.
22 | */
23 | constructor(from, to, category, facet, fromDescriptor, toDescriptor)
24 | {
25 | // WorkspaceStructure members
26 | this.wksp = from.wksp;
27 | this.string = from.string;
28 | this.totalStrength = 0;
29 |
30 | this.source = from;
31 | this.destination = to;
32 | this.category = category;
33 | this.facet = facet;
34 | this.sourceDescriptor = fromDescriptor;
35 | this.destDescriptor = toDescriptor;
36 |
37 | this.leftObject = this.source;
38 | this.rightObject = this.destination;
39 | this.directionCategory = this.wksp.ctx.slipnet.right;
40 | if (this.source.leftIndex > this.destination.rightIndex) {
41 | this.leftObject = this.destination;
42 | this.rightObject = this.source;
43 | this.directionCategory = this.wksp.ctx.slipnet.left;
44 | }
45 | if (fromDescriptor == toDescriptor){
46 | this.directionCategory = null;
47 | }
48 | }
49 |
50 |
51 | /**
52 | * Returns a string describing the object.
53 | */
54 | synopsis(type)
55 | {
56 | const s = this.source.synopsis(1) + ' to ' +
57 | this.destination.synopsis(1) + ` (${this.category.name}, ` +
58 | `${this.facet.name}, ` + `${this.sourceDescriptor.name}, ` +
59 | `${this.destDescriptor.name})`;
60 |
61 | return !type ? s : '';
62 | }
63 |
64 |
65 | /**
66 | * Adds the Bond to the workspace, the parent string, and the
67 | * source and destination objects. Activates its category and
68 | * directionCategory concepts.
69 | *
70 | */
71 | build()
72 | {
73 | this.wksp.structures.push(this);
74 | this.string.bonds.push(this);
75 | this.leftObject.bonds.push(this);
76 | this.rightObject.bonds.push(this);
77 | this.leftObject.rightBond = this;
78 | this.rightObject.leftBond = this;
79 |
80 | this.category.activation = 100;
81 | if (this.directionCategory) {
82 | this.directionCategory.activation = 100;
83 | }
84 | }
85 |
86 |
87 | /**
88 | * Removes the Bond from the workspace, the parent string, and the
89 | * source and destination objects.
90 | *
91 | */
92 | break()
93 | {
94 | this.wksp.structures = this.wksp.structures.filter(s => s !== this);
95 | this.string.bonds = this.string.bonds.filter(s => s !== this);
96 | this.leftObject.bonds = this.leftObject.bonds.filter(s => s !== this);
97 | this.rightObject.bonds = this.rightObject.bonds.filter(s => s !== this);
98 | this.leftObject.rightBond = null;
99 | this.rightObject.leftBond = null;
100 | }
101 |
102 |
103 | /**
104 | * Creates a Bond like this one, except that the source and
105 | * destination are swapped.
106 | */
107 | flippedVersion()
108 | {
109 | return new Namespace.Bond(
110 | this.destination, this.source,
111 | this.category.getRelatedNode(this.wksp.ctx.slipnet.opposite),
112 | this.facet, this.destDescriptor, this.sourceDescriptor
113 | );
114 | }
115 |
116 |
117 | /**
118 | * Updates the total strength value.
119 | *
120 | */
121 | updateStrength()
122 | {
123 | // Calculate the internal strength.
124 | // (Bonds between objects of same type (ie. letter or group) are stronger than
125 | // bonds between different types, and letter bonds are stronger than length bonds.)
126 | const compat = (this.source instanceof Namespace.Letter) == (this.destination instanceof Namespace.Letter) ? 1.0 : 0.7;
127 | const facetFactor = (this.facet == this.wksp.ctx.slipnet.letterCategory) ? 1.0 : 0.7;
128 | let internalStrength = Math.min(100, compat * facetFactor * this.category.bondDegreeOfAssociation() );
129 |
130 | // External strength:
131 | let externalStrength = 0;
132 | const supports = this.string.bonds.map(b => (b.string == this.source.string) &&
133 | (this.leftObject.letterDistance(b.leftObject) !== 0) &&
134 | (this.rightObject.letterDistance(b.rightObject) !== 0) &&
135 | (this.category == b.category) &&
136 | (this.directionCategory == b.directionCategory) ? 1 : 0);
137 |
138 | const nsupporters = supports.reduce((a, b) => a + b, 0);
139 | if (nsupporters > 0) {
140 | const density = 100 * Math.sqrt(this._localDensity());
141 | let supportFactor = Math.pow(0.6, (1/Math.pow(nsupporters,3)));
142 | supportFactor = Math.max(1.0, supportFactor);
143 | externalStrength = supportFactor * density;
144 | }
145 |
146 | // Total strength:
147 | const wti = internalStrength / 100;
148 | const wte = 1 - wti;
149 | this.totalStrength = wti*internalStrength + wte*externalStrength;
150 | }
151 |
152 |
153 | /**
154 | * Returns a measure of the density in the workspace strings of
155 | * bonds with the same bond-category and same direction-category
156 | * as this bond.
157 | *
158 | * @private
159 | */
160 | _localDensity()
161 | {
162 | let slotSum = 0;
163 | let supportSum = 0;
164 |
165 | for (let obj1 of this.wksp.objects.filter(o => o.string == this.string)) {
166 | for (let obj2 of this.wksp.objects.filter(o2 => obj1.isBeside(o2)) ) {
167 | slotSum += 1;
168 | for (const b of this.string.bonds.filter(b => b != this)) {
169 | const sameCats = (b.category == this.category) && (b.directionCategory == this.directionCategory);
170 | const sameEnds = ((this.source == obj1) && (this.destination == obj2)) || ((this.source == obj2) && (this.destination == obj1));
171 | if (sameCats && sameEnds) { supportSum += 1; }
172 | }
173 | }
174 | }
175 | return slotSum === 0 ? 0 : supportSum/slotSum;
176 | }
177 |
178 | };
179 |
180 |
181 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/BondBuilder.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This codelet tries to build a proposed bond, fighting with any
10 | * incompatible structures if necessary.
11 | *
12 | */
13 | Namespace.Codelets.BondBuilder = class extends Namespace.Codelets.CodeletBase
14 | {
15 | /**
16 | * @constructor
17 | *
18 | * @param {Copycat} ctx - The Copycat instance.
19 | * @param {Number} urgency - The urgency of the codelet.
20 | * @param {Array} args - Arguments to pass to the codelet.
21 | * @param {Number} birthdate - The birthdate of the codelet.
22 | */
23 | constructor(ctx, urgency, args, birthdate)
24 | {
25 | super('bond-builder', ctx, urgency, birthdate);
26 | this.bond = args[0];
27 | }
28 |
29 |
30 | /**
31 | * Runs the codelet.
32 | */
33 | run()
34 | {
35 | const ctx = this.ctx;
36 | const bond = this.bond;
37 | bond.updateStrength();
38 |
39 | // Fizzle if the bond's objects are gone
40 | const wobjs = ctx.workspace.objects;
41 | if (!wobjs.includes(bond.source) || !wobjs.includes(bond.destination)) {
42 | return;
43 | }
44 |
45 | // Fizzle if the bond already exists
46 | if (bond.string.bonds.some(sbond => this._sameNeighbors(bond, sbond) && this._sameCategories(bond, sbond))) {
47 | bond.category.activation = 100;
48 | if (bond.directionCategory) { bond.directionCategory.activation = 100; }
49 | return;
50 | }
51 |
52 | // Provide UI feedback
53 | if (ctx.ui && !ctx.batchMode) {
54 | ctx.ui.workspaceUi.getStringGraphic(bond.string).bondsGraphic.flashProposed(bond);
55 | }
56 |
57 | // Fight it out with any incompatible bonds
58 | const Utils = Namespace.Codelets.CodeletUtils;
59 | const incompatibleBonds = bond.string.bonds.filter(b => this._sameNeighbors(bond,b));
60 | if (!Utils.fightItOut(bond, incompatibleBonds, 1.0, 1.0)) {
61 | return;
62 | }
63 |
64 | // Fight it out with any incompatible groups
65 | const incompatibleGroups = bond.source.getCommonGroups(bond.destination);
66 | if (!Utils.fightItOut(bond, incompatibleGroups, 1.0, 1.0)) {
67 | return;
68 | }
69 |
70 | // Fight it out with any incompatible correspondences
71 | const incompatibleCorrespondences = [];
72 | if (bond.leftObject.leftmost || bond.rightObject.rightmost) {
73 | if (bond.directionCategory) {
74 | const incompatibleCorrespondences = this._getIncompatibleCorrespondences(bond);
75 | if (incompatibleCorrespondences.length > 0) {
76 | if (!Utils.fightItOut(bond, incompatibleCorrespondences, 2.0, 3.0)) {
77 | return;
78 | }
79 | }
80 | }
81 | }
82 |
83 | // We won! Destroy the incompatibles and build our bond.
84 | incompatibleBonds.forEach(x => x.break());
85 | incompatibleGroups.forEach(x => x.break());
86 | incompatibleCorrespondences.forEach(x => x.break());
87 |
88 | bond.build();
89 | }
90 |
91 |
92 | /**
93 | * Checks whether two bonds have the same neighbors.
94 | * @private
95 | *
96 | */
97 | _sameNeighbors(bond1, bond2)
98 | {
99 | return (bond1.leftObject == bond2.leftObject) && (bond1.rightObject == bond2.rightObject);
100 | }
101 |
102 |
103 | /**
104 | * Checks whether two bonds have the same categories.
105 | * @private
106 | *
107 | */
108 | _sameCategories(bond1, bond2)
109 | {
110 | return (bond1.category == bond2.category) && (bond1.directionCategory == bond2.directionCategory);
111 | }
112 |
113 |
114 | /**
115 | * Returns a list of correspondences that are incompatible with a given bond.
116 | * @private
117 | */
118 | _getIncompatibleCorrespondences(bond)
119 | {
120 | const incompatibles = [];
121 | if (bond.leftObject.leftmost && bond.leftObject.correspondence)
122 | {
123 | const obj = (bond.string == bond.wksp.initialWString) ?
124 | bond.leftObject.correspondence.objFromTarget : bond.leftObject.correspondence.objFromInitial;
125 |
126 | if (obj.leftmost && obj.rightBond && obj.rightBond.directionCategory) {
127 | if (obj.rightBond.directionCategory != bond.directionCategory) {
128 | incompatibles.push(bond.leftObject.correspondence);
129 | }
130 | }
131 | }
132 | if (bond.rightObject.rightmost && bond.rightObject.correspondence)
133 | {
134 | const obj = (bond.string == bond.wksp.initialWString) ?
135 | bond.rightObject.correspondence.objFromTarget : bond.rightObject.correspondence.objFromInitial;
136 |
137 | if (obj.rightmost && obj.leftBond && obj.leftBond.directionCategory) {
138 | if (obj.leftBond.directionCategory != bond.directionCategory) {
139 | incompatibles.push(bond.rightObject.correspondence);
140 | }
141 | }
142 | }
143 | return incompatibles;
144 | }
145 |
146 |
147 | };
148 |
149 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/BondStrengthTester.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet tests the strength of a bond, and if it's strong enough,
9 | * may post a BondBuilder codelet.
10 | *
11 | */
12 | Namespace.Codelets.BondStrengthTester = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('bond-strength-tester', ctx, urgency, birthdate);
25 | this.bond = args[0];
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const coderack = ctx.coderack;
36 | const bond = this.bond;
37 |
38 | // Provide UI feedback
39 | if (ctx.ui && !ctx.batchMode) {
40 | ctx.ui.workspaceUi.getStringGraphic(bond.string).bondsGraphic.flashProposed(bond);
41 | }
42 |
43 | // Maybe fizzle, if the strength is too low
44 | bond.updateStrength();
45 | const strength = bond.totalStrength;
46 | const prob = ctx.temperature.getAdjustedProb(strength/100);
47 | if ( !ctx.randGen.coinFlip(prob) ) {
48 | return;
49 | }
50 |
51 | // Post a BondBuilder codelet
52 | bond.facet.activation = 100;
53 | bond.sourceDescriptor.activation = 100;
54 | bond.destDescriptor.activation = 100;
55 | const urgency = Namespace.Codelets.CodeletUtils.getUrgencyBin(strength);
56 | const newCodelet = ctx.coderack.factory.create('bond-builder', urgency, [bond]);
57 | coderack.post(newCodelet);
58 | }
59 |
60 | };
61 |
62 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/BottomUpBondScout.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 | const CodeletUtils = Namespace.Codelets.CodeletUtils;
6 |
7 | /**
8 | * @classdesc
9 | * This codelet looks for potential bonds between
10 | * neighboring object pairs in the initial or target string.
11 | *
12 | */
13 | Namespace.Codelets.BottomUpBondScout = class extends Namespace.Codelets.CodeletBase
14 | {
15 | /**
16 | * @constructor
17 | *
18 | * @param {Copycat} ctx - The Copycat instance.
19 | * @param {Number} urgency - The urgency of the codelet.
20 | * @param {Array} args - Arguments to pass to the codelet. (Empty for this codelet.)
21 | * @param {Number} birthdate - The birthdate of the codelet.
22 | */
23 | constructor(ctx, urgency, args, birthdate)
24 | {
25 | super('bottom-up-bond-scout', ctx, urgency, birthdate);
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const sn = ctx.slipnet;
36 |
37 | // Choose a workspace object at random, based on intra-string salience.
38 | const bondSource = CodeletUtils.chooseUnmodifiedObject(ctx, 'intraStringSalience', ctx.workspace.objects);
39 | if (!bondSource) { return; }
40 |
41 | // Choose a neighboring object
42 | const bondDest = CodeletUtils.chooseNeighbor(ctx, bondSource);
43 | if (!bondDest) { return; }
44 |
45 | // Provide UI feedback.
46 | if (ctx.ui && !ctx.batchMode) {
47 | const dummyBond = new Namespace.Bond(bondSource, bondDest, sn.sameness, sn.letterCategory, sn.letters[0], sn.letters[0]);
48 | ctx.ui.workspaceUi.getStringGraphic(dummyBond.string).bondsGraphic.flashGrope(dummyBond);
49 | }
50 |
51 | // Choose a bond facet
52 | const bondFacet = CodeletUtils.chooseBondFacet(ctx, bondSource, bondDest);
53 | if (!bondFacet) { return; }
54 |
55 | // Get the bond category
56 | const sourceDescriptor = bondSource.getDescriptor(bondFacet);
57 | const destDescriptor = bondDest.getDescriptor(bondFacet);
58 | let bondCategory = sourceDescriptor.getBondCategory(destDescriptor);
59 | if (!bondCategory) {
60 | return;
61 | }
62 | if (bondCategory == ctx.slipnet.identity) {
63 | bondCategory = ctx.slipnet.sameness;
64 | }
65 |
66 | // Propose the bond
67 | ctx.coderack.proposeBond(bondSource, bondDest, bondCategory, bondFacet, sourceDescriptor, destDescriptor);
68 | }
69 |
70 | };
71 |
72 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/BottomUpCorrespondenceScout.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet looks for potential correspondences between
9 | * objects in the initial and target strings.
10 | *
11 | */
12 | Namespace.Codelets.BottomUpCorrespondenceScout = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet. (Empty for this codelet.)
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('bottom-up-correspondence-scout', ctx, urgency, birthdate);
25 | }
26 |
27 |
28 | /**
29 | * Runs the codelet.
30 | */
31 | run()
32 | {
33 | const ctx = this.ctx;
34 | const wksp = ctx.workspace;
35 | const sn = ctx.slipnet;
36 | const Utils = Namespace.Codelets.CodeletUtils;
37 |
38 | // Choose an object from the initial string, based on salience.
39 | const objFromInitial = Utils.chooseUnmodifiedObject(ctx, 'interStringSalience', wksp.initialWString.objects);
40 | if (!objFromInitial) { return; }
41 |
42 | // Choose an object from the target string, based on salience.
43 | let objFromTarget = Utils.chooseUnmodifiedObject(ctx, 'interStringSalience', wksp.targetWString.objects);
44 | if (!objFromTarget) { return; }
45 |
46 | // Check that the initial and target objects are compatible.
47 | if (objFromInitial.spansString() != objFromTarget.spansString()) { return; }
48 |
49 | // Provide UI feedback.
50 | if (ctx.ui && !ctx.batchMode) {
51 | const dummyCorresp = new Namespace.Correspondence(objFromInitial, objFromTarget, [], false);
52 | ctx.ui.workspaceUi.corrsGraphic.flashGrope(dummyCorresp);
53 | }
54 |
55 | // Get concept mappings between the two objects.
56 | let conceptMappings = Namespace.ConceptMapping.getMappings(objFromInitial, objFromTarget,
57 | objFromInitial.relevantDescriptions(), objFromTarget.relevantDescriptions());
58 | if (!conceptMappings.length) { return; }
59 |
60 | // Check for slippability
61 | const slippageProbs = conceptMappings.map(m => ctx.temperature.getAdjustedProb( m.slippability()/100 ));
62 | const slippable = slippageProbs.some(p => ctx.randGen.coinFlip(p));
63 | if (!slippable) { return; }
64 |
65 | // Get any distinguishing mappings
66 | const distinguishingMappings = conceptMappings.filter( m => m.isDistinguishing() );
67 | if (!distinguishingMappings.length) { return; }
68 |
69 | // If both objects span the strings, then check to see if the string description needs to be flipped.
70 | let flipTargetObject = false;
71 | if (objFromInitial.spansString() && objFromTarget.spansString() && (sn.opposite.activation != 100))
72 | {
73 | const opposites = distinguishingMappings.filter(m =>
74 | (m.initialDescType == sn.stringPositionCategory) && (m.initialDescType != sn.bondFacet));
75 |
76 | if (opposites.every(m => m.label == sn.opposite)) {
77 | const initialDescTypes = opposites.map(m => m.initialDescType);
78 | if (initialDescTypes.includes(sn.directionCategory)) {
79 | objFromTarget = objFromTarget.flippedVersion();
80 | conceptMappings = Namespace.ConceptMapping.getMappings(objFromInitial, objFromTarget,
81 | objFromInitial.relevantDescriptions(), objFromTarget.relevantDescriptions());
82 | flipTargetObject = true;
83 | }
84 | }
85 | }
86 |
87 | // Propose a correspondence.
88 | ctx.coderack.proposeCorrespondence(objFromInitial, objFromTarget, conceptMappings, flipTargetObject);
89 | }
90 |
91 |
92 | };
93 |
94 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/BottomUpDescriptionScout.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | *
9 | * Thic codelet seeks an appropriate Description for a randomly
10 | * selected workspace object.
11 | */
12 | Namespace.Codelets.BottomUpDescriptionScout = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet. (Empty for this codelet.)
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('bottom-up-description-scout', ctx, urgency, birthdate);
25 | }
26 |
27 |
28 | /**
29 | * Runs the codelet.
30 | */
31 | run()
32 | {
33 | const ctx = this.ctx;
34 | const Utils = Namespace.Codelets.CodeletUtils;
35 |
36 | // Choose a workspace object, based on salience.
37 | const chosenObject = Utils.chooseUnmodifiedObject(ctx, 'totalSalience', ctx.workspace.objects);
38 | if (!chosenObject) { return; }
39 |
40 | // Choose a relevant description by activation
41 | const descriptions = chosenObject.relevantDescriptions();
42 | const description = ctx.randGen.weightedChoice(descriptions, descriptions.map(d => d.activation));
43 | if (!description) { return; }
44 |
45 | // Choose one of the description's property links
46 | const propertyLinks = this._shortPropertyLinks(ctx, description.descriptor);
47 | if (!propertyLinks.length) { return; }
48 |
49 | const sWeights = propertyLinks.map(s => s.degreeOfAssociation() * s.destination.activation);
50 | const chosen = ctx.randGen.weightedChoice(propertyLinks, sWeights);
51 | const chosenProperty = chosen.destination;
52 |
53 | // Propose the description
54 | ctx.coderack.proposeDescription(chosenObject, chosenProperty.category(), chosenProperty);
55 | }
56 |
57 |
58 |
59 | /**
60 | * Returns a random subset a descriptor's property links, preferring
61 | * links that are short.
62 | * @private
63 | *
64 | */
65 | _shortPropertyLinks(ctx, descriptor)
66 | {
67 | const result = [];
68 | for (let propertyLink of descriptor.propertyLinks)
69 | {
70 | const association = propertyLink.degreeOfAssociation() / 100;
71 | const prob = ctx.temperature.getAdjustedProb(association);
72 | if (ctx.randGen.coinFlip(prob)) { result.push(propertyLink); }
73 | }
74 | return result;
75 | }
76 |
77 | };
78 |
79 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/Breaker.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This codelet tries to break a randomly selected bond,
10 | * group, or correspondence in the workspace.
11 | *
12 | */
13 | Namespace.Codelets.Breaker = class extends Namespace.Codelets.CodeletBase
14 | {
15 | /**
16 | * @constructor
17 | *
18 | * @param {Copycat} ctx - The Copycat instance.
19 | * @param {Number} urgency - The urgency of the codelet.
20 | * @param {Array} args - Arguments to pass to the codelet. (Empty for this codelet.)
21 | * @param {Number} birthdate - The birthdate of the codelet.
22 | */
23 | constructor(ctx, urgency, args, birthdate)
24 | {
25 | super('breaker', ctx, urgency, birthdate);
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const randGen = this.ctx.randGen;
35 | const temperature = this.ctx.temperature;
36 |
37 | // Maybe fizzle
38 | const fizzleProb = (1 - temperature.value()/100);
39 | if (randGen.coinFlip(fizzleProb)) { return; }
40 |
41 | // Choose a Group or Bond or Correspondence at random
42 | const structures = this.ctx.workspace.structures.filter(s =>
43 | (s instanceof Namespace.Group) || (s instanceof Namespace.Bond) || (s instanceof Namespace.Correspondence));
44 | if (!structures.length) { return; }
45 |
46 | const structure = randGen.choice(structures);
47 | const breakObjects = [structure];
48 | if (structure instanceof Namespace.Bond) {
49 | if (structure.source.group && (structure.source.group == structure.destination.group)) {
50 | breakObjects.push(structure.source.group);
51 | }
52 | }
53 |
54 | // Break the bond(s)
55 | for (let structure of breakObjects) {
56 | const breakProb = temperature.getAdjustedProb(structure.totalStrength/100);
57 | if (randGen.coinFlip(breakProb)) { return; }
58 | }
59 | breakObjects.forEach( (structure) => structure.break() );
60 | }
61 | };
62 |
63 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/CodeletBase.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This is the base class for all codelets.
10 | *
11 | */
12 | Namespace.Codelets.CodeletBase = class
13 | {
14 | /**
15 | * @constructor
16 | */
17 | constructor(name, ctx, urgency, birthdate)
18 | {
19 | this.name = name;
20 | this.ctx = ctx;
21 | this.urgency = urgency;
22 | this.birthdate = birthdate;
23 | }
24 |
25 |
26 | /**
27 | * Returns a string describing the object.
28 | *
29 | */
30 | synopsis(type)
31 | {
32 | return !type ? this.name : '';
33 | }
34 | };
35 |
36 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/CodeletFactory.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This class provides a factory method for creating codelets.
10 | *
11 | */
12 | Namespace.Codelets.CodeletFactory = class
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | */
19 | constructor(ctx)
20 | {
21 | this.ctx = ctx;
22 | }
23 |
24 |
25 | /**
26 | * Creates a codelet given its name and constructor arguments.
27 | *
28 | * @param {string} name - The name of the codelet.
29 | * @param {Number} urgency - The urgency of the codelet.
30 | * @param {Array} codeletArgs - Arguments to pass to the codelet.
31 | * @param {Number} birthdate - The birthdate of the codelet.
32 | *
33 | */
34 | create(name, urgency, codeletArgs, birthdate)
35 | {
36 | if (!birthdate && birthdate !== 0) {
37 | birthdate = this.ctx.coderack.numCodeletsRun;
38 | }
39 |
40 | const NS = Namespace.Codelets;
41 | const ctorArgs = [this.ctx, urgency, codeletArgs, birthdate];
42 |
43 | let result = null;
44 | switch (name)
45 | {
46 | case 'bond-builder':
47 | result = new NS.BondBuilder(...ctorArgs);
48 | break;
49 |
50 | case 'bond-strength-tester':
51 | result = new NS.BondStrengthTester(...ctorArgs);
52 | break;
53 |
54 | case 'bottom-up-bond-scout':
55 | result = new NS.BottomUpBondScout(...ctorArgs);
56 | break;
57 |
58 | case 'bottom-up-correspondence-scout':
59 | result = new NS.BottomUpCorrespondenceScout(...ctorArgs);
60 | break;
61 |
62 | case 'bottom-up-description-scout':
63 | result = new NS.BottomUpDescriptionScout(...ctorArgs);
64 | break;
65 |
66 | case 'breaker':
67 | result = new NS.Breaker(...ctorArgs);
68 | break;
69 |
70 | case 'correspondence-builder':
71 | result = new NS.CorrespondenceBuilder(...ctorArgs);
72 | break;
73 |
74 | case 'correspondence-strength-tester':
75 | result = new NS.CorrespondenceStrengthTester(...ctorArgs);
76 | break;
77 |
78 | case 'description-builder':
79 | result = new NS.DescriptionBuilder(...ctorArgs);
80 | break;
81 |
82 | case 'description-strength-tester':
83 | result = new NS.DescriptionStrengthTester(...ctorArgs);
84 | break;
85 |
86 | case 'group-builder':
87 | result = new NS.GroupBuilder(...ctorArgs);
88 | break;
89 |
90 | case 'group-strength-tester':
91 | result = new NS.GroupStrengthTester(...ctorArgs);
92 | break;
93 |
94 | case 'important-object-correspondence-scout':
95 | result = new NS.ImportantObjectCorrespondenceScout(...ctorArgs);
96 | break;
97 |
98 | case 'replacement-finder':
99 | result = new NS.ReplacementFinder(...ctorArgs);
100 | break;
101 |
102 | case 'rule-builder':
103 | result = new NS.RuleBuilder(...ctorArgs);
104 | break;
105 |
106 | case 'rule-scout':
107 | result = new NS.RuleScout(...ctorArgs);
108 | break;
109 |
110 | case 'rule-strength-tester':
111 | result = new NS.RuleStrengthTester(...ctorArgs);
112 | break;
113 |
114 | case 'rule-translator':
115 | result = new NS.RuleTranslator(...ctorArgs);
116 | break;
117 |
118 | case 'top-down-bond-scout--category':
119 | result = new NS.TopDownBondScout_Category(...ctorArgs);
120 | break;
121 |
122 | case 'top-down-bond-scout--direction':
123 | result = new NS.TopDownBondScout_Direction(...ctorArgs);
124 | break;
125 |
126 | case 'top-down-description-scout':
127 | result = new NS.TopDownDescriptionScout(...ctorArgs);
128 | break;
129 |
130 | case 'top-down-group-scout--category':
131 | result = new NS.TopDownGroupScout_Category(...ctorArgs);
132 | break;
133 |
134 | case 'top-down-group-scout--direction':
135 | result = new NS.TopDownGroupScout_Direction(...ctorArgs);
136 | break;
137 |
138 | case 'group-scout--whole-string':
139 | result = new NS.GroupScout_WholeString(...ctorArgs);
140 | break;
141 |
142 | default:
143 | throw new Error('Unknown codelet name: ' + name);
144 | }
145 | return result;
146 | }
147 | };
148 |
149 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/CodeletUtils.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This class provides several utility functions
10 | * needed by the codelets.
11 | *
12 | */
13 | Namespace.Codelets.CodeletUtils = class
14 | {
15 | /**
16 | * Returns a quantized measure of urgency.
17 | *
18 | * @param {Number} urgency - The input urgency.
19 | */
20 | static getUrgencyBin(urgency)
21 | {
22 | const numUrgencyBins = 7;
23 | const i = Math.floor(urgency) * numUrgencyBins / 100;
24 | return (i >= numUrgencyBins) ? numUrgencyBins : i + 1;
25 | }
26 |
27 |
28 | /**
29 | * Returns a random object from a given list, that is
30 | * not from the modified string. The probability of
31 | * choosing an object is weighted by its value of the
32 | * given attribute.
33 | *
34 | * @param {Copycat} ctx - The Copycat instance.
35 | * @param {String} attribute - The attribute to use for weighting.
36 | * @param {Array} inObjects - The list of objects to choose from.
37 | *
38 | */
39 | static chooseUnmodifiedObject(ctx, attribute, inObjects)
40 | {
41 | const candidates = inObjects.filter(o => o.string != ctx.workspace.modifiedWString);
42 | const weights = candidates.map( o => ctx.temperature.getAdjustedValue(o[attribute]) );
43 | return ctx.randGen.weightedChoice(candidates, weights);
44 | }
45 |
46 |
47 | /**
48 | * Returns a random nearest-neighbor of a given object.
49 | *
50 | * @param {Copycat} ctx - The Copycat instance.
51 | * @param {WorkspaceObject} sourceObj - The input object.
52 | *
53 | */
54 | static chooseNeighbor(ctx, sourceObj)
55 | {
56 | const candidates = ctx.workspace.objects.filter(o => o.isBeside(sourceObj));
57 | const weights = candidates.map( o => ctx.temperature.getAdjustedValue(o.intraStringSalience) );
58 | return ctx.randGen.weightedChoice(candidates, weights);
59 | }
60 |
61 |
62 | /**
63 | * Randomly selects an object from the initial or target string that
64 | * matches the given criterion.
65 | *
66 | * @param {Copycat} ctx - The Copycat instance.
67 | * @param {String} relevanceCriterion - 'bondCategory' or 'bondDirection'.
68 | * @param {SlipNode} toMatch - The SlipNode to match.
69 | */
70 | static getScoutSource(ctx, relevanceCriterion, toMatch)
71 | {
72 | // Define a function to compute the relevance of a string.
73 | const relevance = function(string, criterion, nodeToMatch)
74 | {
75 | if ((criterion == 'bondCategory') && (string.objects.length == 1)) {
76 | return 0;
77 | }
78 |
79 | const nonSpanningObjs = string.objects.filter(o => !o.spansString());
80 | const numNonSpanningObjects = nonSpanningObjs.length;
81 | let numMatches = 0;
82 | if (criterion == 'bondCategory') {
83 | numMatches = nonSpanningObjs.filter(o => o.rightBond && (o.rightBond.category == nodeToMatch)).length;
84 | }
85 | else if (criterion == 'bondDirection') {
86 | numMatches = nonSpanningObjs.filter(o => o.rightBond && (o.rightBond.directionCategory == nodeToMatch)).length;
87 | }
88 |
89 | return (numNonSpanningObjects == 1) ? 100 * numMatches : (100 * numMatches) / (numNonSpanningObjects - 1);
90 | };
91 |
92 |
93 | // Get relevance and unhappiness values.
94 | const wksp = ctx.workspace;
95 | const initialRelevance = relevance(wksp.initialWString, relevanceCriterion, toMatch);
96 | const targetRelevance = relevance(wksp.targetWString, relevanceCriterion, toMatch);
97 | const initialUnhappiness = wksp.initialWString.intraStringUnhappiness;
98 | const targetUnhappiness = wksp.targetWString.intraStringUnhappiness;
99 | const initials = initialRelevance + initialUnhappiness;
100 | const targets = targetRelevance + targetUnhappiness;
101 |
102 | // Choose a source object.
103 | let result;
104 | const Utils = Namespace.Codelets.CodeletUtils;
105 | if (ctx.randGen.weightedGreaterThan(targets, initials)) {
106 | result = Utils.chooseUnmodifiedObject(ctx, 'intraStringSalience', wksp.targetWString.objects);
107 | }else {
108 | result = Utils.chooseUnmodifiedObject(ctx, 'intraStringSalience', wksp.initialWString.objects);
109 | }
110 |
111 | return result;
112 | }
113 |
114 |
115 | /**
116 | * Randomly selects a bond facet that is common to the
117 | * given source and destination objects.
118 | *
119 | * @param {Copycat} ctx - The Copycat instance.
120 | * @param {WorkspaceObject} sourceObj - The source object.
121 | * @param {WorkspaceObject} destObj - The destination object.
122 | *
123 | */
124 | static chooseBondFacet(ctx, sourceObj, destObj)
125 | {
126 | // The allowed bond facets:
127 | const bondFacets = [ctx.slipnet.letterCategory, ctx.slipnet.length];
128 |
129 | // Get the bond facets that are present in both source and destination.
130 | const sourceFacets = sourceObj.descriptions.map(d => d.descriptionType).filter(d => bondFacets.includes(d));
131 |
132 | const candidateFacets = destObj.descriptions.map(d => d.descriptionType).filter(d => sourceFacets.includes(d));
133 |
134 | // For each such facet, compute a support value based on both the facet's activation
135 | // and the number of objects in the source's string that share the facet.
136 | const facetWeights = [];
137 | const sourceSiblings = ctx.workspace.objects.filter(o => o.string == sourceObj.string);
138 | const siblingDescriptions = sourceSiblings.map(o => o.descriptions).flat();
139 | for (let facet of candidateFacets) {
140 | let supportCount = 0;
141 | for (let d of siblingDescriptions) {
142 | supportCount += (d.descriptionType == facet) ? 1 : 0;
143 | }
144 | const stringSupport = 100 * supportCount / (sourceSiblings.length || 1);
145 | facetWeights.push( (facet.activation + stringSupport)/2 );
146 | }
147 |
148 | return ctx.randGen.weightedChoice(candidateFacets, facetWeights);
149 | }
150 |
151 |
152 | /**
153 | * Probabilistically decides which of two given structures to return,
154 | * based on their strengths as well as external weight factors.
155 | *
156 | * @param {WorkspaceStructure} structure1 - The first structure.
157 | * @param {Number} weight1 - The weight factor for the first structure.
158 | * @param {WorkspaceStructure} structure2 - The second structure.
159 | * @param {Number} weight2 - The weight factor for the second structure.
160 | *
161 | */
162 | static structureVsStructure(structure1, weight1, structure2, weight2)
163 | {
164 | const ctx = structure1.wksp.ctx;
165 | const temperature = ctx.temperature;
166 |
167 | structure1.updateStrength();
168 | structure2.updateStrength();
169 | const weightedStrength1 = temperature.getAdjustedValue(structure1.totalStrength * weight1);
170 | const weightedStrength2 = temperature.getAdjustedValue(structure2.totalStrength * weight2);
171 |
172 | return ctx.randGen.weightedGreaterThan(weightedStrength1, weightedStrength2);
173 | }
174 |
175 |
176 | /**
177 | * Pits the given structure against the given list of incompatible
178 | * structures, deciding probabilistically on a winner.
179 | *
180 | * @param {WorkspaceStructure} structure - The structure to test.
181 | * @param {Number} structureWeight - The weight factor for the structure.
182 | * @param {Array} incompatibles - The list of incompatible structures.
183 | * @param {Number} incompatiblesWeight - The weight factor for the incompatible structures.
184 | *
185 | */
186 | static fightItOut(structure, structureWeight, incompatibles, incompatiblesWeight)
187 | {
188 | if (!incompatibles || !incompatibles.length) {
189 | return true;
190 | }
191 |
192 | return incompatibles.every(incomp =>
193 | Namespace.Codelets.CodeletUtils.structureVsStructure(structure, structureWeight, incomp, incompatiblesWeight)
194 | );
195 | }
196 |
197 | };
198 |
199 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/CorrespondenceBuilder.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This codelet tries to build a proposed correspondence, fighting against
10 | * competitors if necessary.
11 | *
12 | */
13 | Namespace.Codelets.CorrespondenceBuilder = class extends Namespace.Codelets.CodeletBase
14 | {
15 | /**
16 | * @constructor
17 | *
18 | * @param {Copycat} ctx - The Copycat instance.
19 | * @param {Number} urgency - The urgency of the codelet.
20 | * @param {Array} args - Arguments to pass to the codelet.
21 | * @param {Number} birthdate - The birthdate of the codelet.
22 | */
23 | constructor(ctx, urgency, args, birthdate)
24 | {
25 | super('correspondence-builder', ctx, urgency, birthdate);
26 | this.correspondence = args[0];
27 | }
28 |
29 |
30 | /**
31 | * Runs the codelet.
32 | */
33 | run()
34 | {
35 | const ctx = this.ctx;
36 | const wksp = ctx.workspace;
37 | const SvS = Namespace.Codelets.CodeletUtils.structureVsStructure;
38 |
39 | const corresp = this.correspondence;
40 | const objFromInitial = corresp.objFromInitial;
41 | const objFromTarget = corresp.objFromTarget;
42 |
43 | // If either of the two objects (or possibly a flipped version) no longer exists, then fizzle.
44 | const wantFlip = corresp.flipTargetObject;
45 | const flippedTargetObj = wantFlip ? objFromTarget.flippedVersion() : null;
46 | const objsExist = wksp.objects.includes(objFromInitial) && (wksp.objects.includes(objFromTarget) ||
47 | (wantFlip && wksp.targetWString.getEquivalentGroup(flippedTargetObj)));
48 | if (!objsExist) { return; }
49 |
50 | // If the correspondence already exists, activate its concept mapping labels,
51 | // and add relevant new ones, and fizzle.
52 | if (this._isReflexive(corresp))
53 | {
54 | const existing = objFromInitial.correspondence;
55 | for (let mapping of corresp.conceptMappings) {
56 | if (mapping.label) {
57 | mapping.label.activation = 100;
58 | }
59 | if (!mapping.isContainedIn(existing.conceptMappings)) {
60 | existing.conceptMappings.push(mapping);
61 | }
62 | }
63 | return;
64 | }
65 |
66 | // Fight incompatibles.
67 | // The weights for the fight depend on the letter-span of the objects.
68 | // This is one of the reasons the program prefers correspondences to groups rather than to letters.
69 | // Another reason is that groups are more salient than letters, so they are more likely to be chosen by correspondence scouts.
70 | const incompatibles = wksp.initialWString.objects.filter(o =>
71 | o.correspondence && corresp.isIncompatibleWith(o.correspondence)).map(o => o.correspondence);
72 | if (incompatibles.length) {
73 | const correspSpan = corresp.objFromInitial.letterSpan() + corresp.objFromTarget.letterSpan();
74 | for (let incompat of incompatibles) {
75 | const incompatSpan = incompat.objFromInitial.letterSpan() + incompat.objFromTarget.letterSpan();
76 | if (!SvS(corresp, correspSpan, incompat, incompatSpan)) {
77 | return;
78 | }
79 | }
80 | }
81 |
82 | // If there is an incompatible bond, then fight against it, and its group, if any.
83 | let incompatibleBond;
84 | let incompatibleGroup;
85 | if ((objFromInitial.leftmost || objFromInitial.rightmost) && (objFromTarget.leftmost || objFromTarget.rightmost))
86 | {
87 | incompatibleBond = this._getIncompatibleBond(corresp);
88 | if (incompatibleBond) {
89 | if (!SvS(corresp, 3, incompatibleBond, 2)) {
90 | return;
91 | }
92 | incompatibleGroup = objFromTarget.group;
93 | if (incompatibleGroup) {
94 | if (!SvS(corresp, 3, incompatibleGroup, 2)) {
95 | return;
96 | }
97 | }
98 | }
99 | }
100 |
101 | // If there is an incompatible rule, fight against it
102 | let incompatibleRule;
103 | if (wksp.rule && this._incompatibleRuleCorrespondence(wksp.rule, corresp)) {
104 | incompatibleRule = wksp.rule;
105 | if (!SvS(corresp, 1, incompatibleRule, 1)) {
106 | return;
107 | }
108 | }
109 |
110 | incompatibles.forEach(x => x.break());
111 | if (incompatibleBond) {
112 | incompatibleBond.break();
113 | }
114 | if (incompatibleGroup) {
115 | incompatibleGroup.break();
116 | }
117 | if (incompatibleRule) {
118 | incompatibleRule.break();
119 | }
120 | corresp.build();
121 | }
122 |
123 |
124 | /**
125 | * Determines if the given rule and correspondence are incompatible.
126 | * @private
127 | *
128 | */
129 | _incompatibleRuleCorrespondence(rule, corresp)
130 | {
131 | if (!rule || !corresp) {
132 | return false;
133 | }
134 |
135 | // Find changed object
136 | const changeds = this.ctx.workspace.initialWString.objects.filter(o => o.changed);
137 | if (!changeds.length) {
138 | return false;
139 | }
140 |
141 | const changed = changeds[0];
142 | if (corresp.objFromInitial != changed) {
143 | return false;
144 | }
145 |
146 | // It is incompatible if the rule descriptor is not in the mapping list
147 | return corresp.conceptMappings.some(m => m.initialDescriptor == rule.descriptor);
148 | }
149 |
150 |
151 | /**
152 | * Gets the incompatible bond, if any, for the given correspondence.
153 | * @private
154 | *
155 | */
156 | _getIncompatibleBond(corresp)
157 | {
158 | const sn = this.ctx.slipnet;
159 |
160 | const initialBond = corresp.objFromInitial.leftmost ? corresp.objFromInitial.rightBond :
161 | corresp.objFromInitial.rightmost ? corresp.objFromInitial.leftBond : null;
162 | if (!initialBond ) {
163 | return null;
164 | }
165 |
166 | const targetBond = corresp.objFromTarget.leftmost ? corresp.objFromTarget.rightBond :
167 | corresp.objFromTarget.rightmost ? corresp.objFromTarget.leftBond : null;
168 | if (!targetBond ) {
169 | return null;
170 | }
171 |
172 | if (initialBond.directionCategory && targetBond.directionCategory) {
173 | const mapping = new Namespace.ConceptMapping(sn.directionCategory, sn.directionCategory,
174 | initialBond.directionCategory, targetBond.directionCategory, null, null);
175 | if (corresp.conceptMappings.some(m => m.isIncompatibleWith(mapping))) {
176 | return targetBond;
177 | }
178 | }
179 | return null;
180 | }
181 |
182 |
183 | /**
184 | * Determines if the given correspondence is reflexive.
185 | * @private
186 | *
187 | */
188 | _isReflexive(corresp)
189 | {
190 | const initial = corresp.objFromInitial;
191 | return initial.correspondence && (initial.correspondence.objFromTarget == corresp.objFromTarget);
192 | }
193 |
194 | };
195 |
196 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/CorrespondenceStrengthTester.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet tests the strength of a correspondence, and if it's
9 | * strong enough, may post a CorrespondenceBuilder codelet.
10 | *
11 | */
12 | Namespace.Codelets.CorrespondenceStrengthTester = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('correspondence-strength-tester', ctx, urgency, birthdate);
25 | this.correspondence = args[0];
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const wksp = ctx.workspace;
36 | const coderack = ctx.coderack;
37 |
38 | const corresp = this.correspondence;
39 | const objFromInitial = corresp.objFromInitial;
40 | const objFromTarget = corresp.objFromTarget;
41 |
42 | // If either of the two objects (or possibly a flipped version) no longer
43 | // exists, then fizzle.
44 | const wantFlip = corresp.flipTargetObject;
45 | const flippedTargetObj = wantFlip ? objFromTarget.flippedVersion() : null;
46 | const objsExist = wksp.objects.includes(objFromInitial) && (wksp.objects.includes(objFromTarget) ||
47 | (wantFlip && wksp.targetWString.getEquivalentGroup(flippedTargetObj)));
48 | if (!objsExist) { return; }
49 |
50 | // Provide UI feedback
51 | if (ctx.ui && !ctx.batchMode) {
52 | ctx.ui.workspaceUi.corrsGraphic.flashProposed(corresp);
53 | }
54 |
55 | corresp.updateStrength();
56 | const strength = corresp.totalStrength;
57 | if (ctx.randGen.coinFlip( ctx.temperature.getAdjustedProb(strength/100) ))
58 | {
59 | // Activate the correspondence's mappings
60 | corresp.conceptMappings.forEach(m => {
61 | m.initialDescType.activation = 100;
62 | m.initialDescriptor.activation = 100;
63 | m.targetDescType.activation = 100;
64 | m.targetDescriptor.activation = 100;
65 | });
66 |
67 | const urgency = Namespace.Codelets.CodeletUtils.getUrgencyBin(strength);
68 | const newCodelet = coderack.factory.create('correspondence-builder', urgency, [corresp]);
69 | coderack.post(newCodelet);
70 | }
71 | }
72 |
73 | };
74 |
75 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/DescriptionBuilder.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This codelet tries to build a proposed Description.
10 | *
11 | */
12 | Namespace.Codelets.DescriptionBuilder = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('description-builder', ctx, urgency, birthdate);
25 | this.description = args[0];
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const descr = this.description;
35 |
36 | // Maybe fizzle
37 | if (!this.ctx.workspace.objects.includes(descr.object)) { return; }
38 |
39 | // Build or activate the description
40 | if (descr.object.hasDescriptor(descr.descriptor)) {
41 | descr.activate();
42 | }
43 | else {
44 | descr.build();
45 | }
46 | }
47 | };
48 |
49 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/DescriptionStrengthTester.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet tests the strength of a Description, and if it's
9 | * strong enough, may post a DescriptionBuilder codelet.
10 | *
11 | */
12 | Namespace.Codelets.DescriptionStrengthTester = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('description-strength-tester', ctx, urgency, birthdate);
25 | this.description = args[0];
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const coderack = ctx.coderack;
36 | const description = this.description;
37 |
38 | // Maybe fizzle, if the strength is too low
39 | description.descriptor.activation = 100;
40 | description.updateStrength();
41 | const strength = description.totalStrength;
42 | const prob = ctx.temperature.getAdjustedProb(strength/100);
43 | if (!ctx.randGen.coinFlip(prob)) { return; }
44 |
45 | // Post a DescriptionBuilder codelet
46 | const urgency = Namespace.Codelets.CodeletUtils.getUrgencyBin(strength);
47 | const newCodelet = coderack.factory.create('description-builder', urgency, [description]);
48 | coderack.post(newCodelet);
49 | }
50 |
51 | };
52 |
53 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/GroupBuilder.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet tries to build a proposed Group, fighting against
9 | * competitors if necessary.
10 | *
11 | */
12 | Namespace.Codelets.GroupBuilder = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('group-builder', ctx, urgency, birthdate);
25 | this.group = args[0];
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const wksp = ctx.workspace;
36 | const sn = ctx.slipnet;
37 | const CodeletUtils = Namespace.Codelets.CodeletUtils;
38 | const group = this.group;
39 |
40 | // If an equivalent group already exists, just activate it.
41 | const equivalent = group.string.getEquivalentGroup(group);
42 | if (equivalent) {
43 | group.descriptions.forEach(d => d.descriptor.activation = 100);
44 | equivalent.addDescriptions(group.descriptions);
45 | return;
46 | }
47 |
48 | // Check to see if all objects are still there
49 | if (group.objectList.some(o => !wksp.objects.includes(o))) {
50 | return;
51 | }
52 |
53 | // Provide UI feedback
54 | if (ctx.ui && !ctx.batchMode) {
55 | ctx.ui.workspaceUi.getStringGraphic(group.string).groupsGraphic.flashProposed(group);
56 | }
57 |
58 | // Check to see if bonds have the same direction
59 | const incompatibleBonds = [];
60 | if (group.objectList.length > 1) {
61 | let previous = group.objectList[0];
62 | for (let obj of group.objectList.slice(1)) {
63 | const leftBond = obj.leftBond;
64 | if (leftBond) {
65 | if (leftBond.leftObject == previous) {
66 | continue;
67 | }
68 | if (leftBond.directionCategory == group.directionCategory) {
69 | continue;
70 | }
71 | incompatibleBonds.push(leftBond);
72 | }
73 | previous = obj;
74 | }
75 |
76 | const n = group.objectList.length;
77 | let next = group.objectList[n-1];
78 | for (let i=n-2; i>=0; i--) { // Don't use reverse(), as it changes the array
79 | const obj = group.objectList[i];
80 | const rightBond = obj.rightBond;
81 | if (rightBond) {
82 | if (rightBond.rightObject == next) {
83 | continue;
84 | }
85 | if (rightBond.directionCategory == group.directionCategory) {
86 | continue;
87 | }
88 | incompatibleBonds.push(rightBond);
89 | }
90 | next = obj;
91 | }
92 | }
93 |
94 | // If incompatible bonds exist then fight
95 | group.updateStrength();
96 | if (!CodeletUtils.fightItOut(group, incompatibleBonds, 1.0, 1.0)) {
97 | return;
98 | }
99 |
100 | // If incompatible groups exist then fight
101 | const incompatibleGroups = this._getIncompatibleGroups(group);
102 | if (!CodeletUtils.fightItOut(group, incompatibleGroups, 1.0, 1.0)) {
103 | return;
104 | }
105 |
106 | // Break incompatible bonds
107 | incompatibleBonds.forEach(b => b.break());
108 |
109 | // Create new bonds
110 | let source, dest;
111 | group.bondList = [];
112 | for (let i=1; i g.break());
125 | group.build();
126 | group.descriptions.forEach(d => d.descriptor.activation = 100);
127 | }
128 |
129 |
130 | /**
131 | * Gets all groups that have an object in common with
132 | * the given one.
133 | * @private
134 | */
135 | _getIncompatibleGroups(group)
136 | {
137 | const result = [];
138 | for (let obj of group.objectList) {
139 | while (obj.group) {
140 | if (obj.group != group) {
141 | result.push(obj.group);
142 | obj = obj.group;
143 | }
144 | }
145 | }
146 | return result;
147 | }
148 |
149 |
150 | };
151 |
152 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/GroupScout_WholeString.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet tries to make a group out of the entire string.
9 | *
10 | */
11 | Namespace.Codelets.GroupScout_WholeString = class extends Namespace.Codelets.CodeletBase
12 | {
13 | /**
14 | * @constructor
15 | *
16 | * @param {Copycat} ctx - The Copycat instance.
17 | * @param {Number} urgency - The urgency of the codelet.
18 | * @param {Array} args - Arguments to pass to the codelet.
19 | * @param {Number} birthdate - The birthdate of the codelet.
20 | */
21 | constructor(ctx, urgency, args, birthdate)
22 | {
23 | super('group-scout--whole-string', ctx, urgency, birthdate);
24 | this.direction = args[0];
25 | }
26 |
27 |
28 | /**
29 | * Runs the codelet.
30 | */
31 | run()
32 | {
33 | const ctx = this.ctx;
34 | const sn = ctx.slipnet;
35 | const wksp = ctx.workspace;
36 |
37 | // Choose either the initial or target string
38 | const string = ctx.randGen.choice([wksp.initialWString, wksp.targetWString]);
39 |
40 | // Find leftmost object & the highest group to which it belongs
41 | const lms = string.objects.filter(o => o.leftmost);
42 | let leftmost = lms.length ? lms[0] : null;
43 | if (!leftmost) { return; }
44 | while (leftmost.group && (leftmost.group.bondCategory == sn.sameness)) {
45 | leftmost = leftmost.group;
46 | }
47 |
48 | if (leftmost.spansString()) {
49 | // The object already spans the string - propose this object
50 | if (leftmost instanceof Namespace.Group) {
51 | const lmGroup = leftmost;
52 | ctx.coderack.proposeGroup(lmGroup.objectList, lmGroup.bondList,
53 | lmGroup.groupCategory, lmGroup.directionCategory, lmGroup.facet);
54 | }
55 | else {
56 | ctx.coderack.proposeGroup([leftmost], [], sn.samenessGroup, null, sn.letterCategory);
57 | }
58 | return;
59 | }
60 |
61 | let bonds = [];
62 | const objects = [leftmost];
63 | while (leftmost.rightBond) {
64 | bonds.push(leftmost.rightBond);
65 | leftmost = leftmost.rightBond.rightObject;
66 | objects.push(leftmost);
67 | }
68 | if (!leftmost.rightmost) { return; }
69 |
70 | // Choose a random bond from list
71 | const chosenBond = ctx.randGen.choice(bonds);
72 | bonds = this._possibleGroupBonds(chosenBond, bonds);
73 | if (!bonds.length) { return; }
74 |
75 | const groupCategory = chosenBond.category.getRelatedNode(sn.groupCategory);
76 | ctx.coderack.proposeGroup(objects, bonds, groupCategory, chosenBond.directionCategory, chosenBond.facet);
77 | }
78 |
79 |
80 | /**
81 | * From a given list of bonds, get bonds that match the chosen bond.
82 | * @private
83 | */
84 | _possibleGroupBonds(chosenBond, bonds)
85 | {
86 | const result = [];
87 |
88 | for (let bond of bonds) {
89 | if ((bond.category == chosenBond.category) &&
90 | (bond.directionCategory == chosenBond.directionCategory)) {
91 | result.push(bond);
92 | }
93 | else {
94 | // A modified bond might be made
95 | if (bond.category == chosenBond.category) {
96 | return [];
97 | }
98 | if (bond.directionCategory == chosenBond.directionCategory) {
99 | return [];
100 | }
101 | if ([chosenBond.category, bond.category].includes(this.ctx.slipnet.sameness)) {
102 | return [];
103 | }
104 | const newBond = new Namespace.Bond(bond.destination, bond.source, chosenBond.category,
105 | chosenBond.facet, bond.destDescriptor, bond.sourceDescriptor);
106 | result.push(newBond);
107 | }
108 | }
109 | return result;
110 | }
111 |
112 | };
113 |
114 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/GroupStrengthTester.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This codelet tests the strength of a Group, and if it's strong enough,
10 | * posts a GroupBuilder codelet.
11 | *
12 | */
13 | Namespace.Codelets.GroupStrengthTester = class extends Namespace.Codelets.CodeletBase
14 | {
15 | /**
16 | * @constructor
17 | *
18 | * @param {Copycat} ctx - The Copycat instance.
19 | * @param {Number} urgency - The urgency of the codelet.
20 | * @param {Array} args - Arguments to pass to the codelet.
21 | * @param {Number} birthdate - The birthdate of the codelet.
22 | */
23 | constructor(ctx, urgency, args, birthdate)
24 | {
25 | super('group-strength-tester', ctx, urgency, birthdate);
26 | this.group = args[0];
27 | }
28 |
29 |
30 | /**
31 | * Runs the codelet.
32 | */
33 | run()
34 | {
35 | const ctx = this.ctx;
36 | const coderack = ctx.coderack;
37 | const group = this.group;
38 |
39 | // Provide UI feedback
40 | if (ctx.ui && !ctx.batchMode) {
41 | ctx.ui.workspaceUi.getStringGraphic(group.string).groupsGraphic.flashProposed(group);
42 | }
43 |
44 | // Maybe fizzle, if the strength is too low
45 | group.updateStrength();
46 | const strength = group.totalStrength;
47 | const prob = ctx.temperature.getAdjustedProb(strength/100);
48 | if (!ctx.randGen.coinFlip(prob)) { return; }
49 |
50 | // Post a GroupBuilder codelet
51 | group.groupCategory.getRelatedNode(ctx.slipnet.bondCategory).activation = 100;
52 | if (group.directionCategory) { group.directionCategory.activation = 100; }
53 | const urgency = Namespace.Codelets.CodeletUtils.getUrgencyBin(strength);
54 | const newCodelet = ctx.coderack.factory.create('group-builder', urgency, [group]);
55 | coderack.post(newCodelet);
56 | }
57 |
58 |
59 |
60 |
61 | };
62 |
63 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/ImportantObjectCorrespondenceScout.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This codelet chooses an object and its description from the initial string,
10 | * and looks for an object in the target string with the same description or
11 | * slipped description. It then tries to propose a Correspondence between
12 | * the two objects.
13 | */
14 | Namespace.Codelets.ImportantObjectCorrespondenceScout = class extends Namespace.Codelets.CodeletBase
15 | {
16 | /**
17 | * @constructor
18 | *
19 | * @param {Copycat} ctx - The Copycat instance.
20 | * @param {Number} urgency - The urgency of the codelet.
21 | * @param {Array} args - Arguments to pass to the codelet.
22 | * @param {Number} birthdate - The birthdate of the codelet.
23 | */
24 | constructor(ctx, urgency, args, birthdate)
25 | {
26 | super('important-object-correspondence-scout', ctx, urgency, birthdate);
27 | }
28 |
29 |
30 | /**
31 | * Runs the codelet.
32 | */
33 | run()
34 | {
35 | const ctx = this.ctx;
36 | const wksp = ctx.workspace;
37 | const sn = ctx.slipnet;
38 | const Utils = Namespace.Codelets.CodeletUtils;
39 |
40 | // Choose an object from the initial string, based on salience.
41 | const objFromInitial = Utils.chooseUnmodifiedObject(ctx, 'relativeImportance', wksp.initialWString.objects);
42 | if (!objFromInitial) { return; }
43 |
44 | const descriptors = objFromInitial.relevantDistinguishingDescriptors();
45 | const weights = descriptors.map( d => ctx.temperature.getAdjustedValue(d.depth) );
46 | const descriptor = ctx.randGen.weightedChoice(descriptors, weights);
47 | if (!descriptor) { return; }
48 |
49 | let initialDescriptor = descriptor;
50 | for (let m of wksp.getSlippableMappings()) {
51 | if (m.initialDescriptor == descriptor) {
52 | initialDescriptor = m.targetDescriptor;
53 | break;
54 | }
55 | }
56 | const targetCandidates = wksp.targetWString.objects.filter(
57 | o => o.relevantDescriptions().some(d => d.descriptor == initialDescriptor));
58 | if (!targetCandidates.length) { return; }
59 |
60 | let objFromTarget = Utils.chooseUnmodifiedObject(ctx, 'interStringSalience', targetCandidates);
61 | if (objFromInitial.spansString() != objFromTarget.spansString()) { return; }
62 |
63 | // Provide UI feedback
64 | if (ctx.ui && !ctx.batchMode) {
65 | const dummyCorresp = new Namespace.Correspondence(
66 | objFromInitial, objFromTarget, [], false);
67 | ctx.ui.workspaceUi.corrsGraphic.flashGrope(dummyCorresp);
68 | }
69 |
70 | // Get the posible concept mappings
71 | let conceptMappings = Namespace.ConceptMapping.getMappings(
72 | objFromInitial, objFromTarget, objFromInitial.relevantDescriptions(),
73 | objFromTarget.relevantDescriptions());
74 |
75 | // Check for slippability
76 | const slippageProbs = conceptMappings.map(
77 | m => ctx.temperature.getAdjustedProb( m.slippability()/100 ));
78 | const slippable = slippageProbs.some(p => ctx.randGen.coinFlip(p));
79 | if (!slippable) { return; }
80 |
81 | // Find out if any are distinguishing
82 | const distinguishingMappings = conceptMappings.filter(m => m.isDistinguishing());
83 | if (!distinguishingMappings.length) { return; }
84 |
85 | // If both objects span the strings, check to see if the
86 | // string description needs to be flipped
87 | const opposites = distinguishingMappings.filter(m =>
88 | (m.initialDescType == sn.stringPositionCategory) && (m.initialDescType != sn.bondFacet));
89 | const initialDescriptionTypes = opposites.map(m => m.initialDescType);
90 | let flipTargetObject = false;
91 | if (objFromInitial.spansString() && objFromTarget.spansString() && (sn.opposite.activation != 100) &&
92 | initialDescriptionTypes.includes(sn.directionCategory) && opposites.every(m => m.label == sn.opposite) ) {
93 | objFromTarget = objFromTarget.flippedVersion();
94 | conceptMappings = Namespace.ConceptMapping.getMappings( objFromInitial, objFromTarget,
95 | objFromInitial.relevantDescriptions(), objFromTarget.relevantDescriptions() );
96 | flipTargetObject = true;
97 | }
98 |
99 | ctx.coderack.proposeCorrespondence(objFromInitial, objFromTarget, conceptMappings, flipTargetObject);
100 | }
101 |
102 |
103 | };
104 |
105 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/ReplacementFinder.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This codelet chooses a letter at random from the initial string,
10 | * and marks it as changed if the letter at the same position in the
11 | * modified string is different.
12 | *
13 | */
14 | Namespace.Codelets.ReplacementFinder = class extends Namespace.Codelets.CodeletBase
15 | {
16 | /**
17 | * @constructor
18 | *
19 | * @param {Copycat} ctx - The Copycat instance.
20 | * @param {Number} urgency - The urgency of the codelet.
21 | * @param {Array} args - Arguments to pass to the codelet. (Empty for this codelet.)
22 | * @param {Number} birthdate - The birthdate of the codelet.
23 | */
24 | constructor(ctx, urgency, args, birthdate)
25 | {
26 | super('replacement-finder', ctx, urgency, birthdate);
27 | }
28 |
29 |
30 | /**
31 | * Runs the codelet.
32 | *
33 | */
34 | run()
35 | {
36 | const ctx = this.ctx;
37 | const sn = ctx.slipnet;
38 | const wksp = ctx.workspace;
39 |
40 | // Choose a random letter from the initial string
41 | const letters = wksp.initialWString.objects.filter(o => o instanceof Namespace.Letter);
42 | if ( letters.length < 1) { return; }
43 |
44 | const letterOfInitialString = ctx.randGen.choice(letters);
45 | if (letterOfInitialString.replacement) { return; }
46 |
47 | const position = letterOfInitialString.leftIndex;
48 | const modStringLength = wksp.modifiedWString.letters.length;
49 | const letterOfModifiedString = (position > modStringLength) ? null : wksp.modifiedWString.letters[position - 1];
50 | if (letterOfModifiedString == null) { return; }
51 |
52 | const initialAscii = wksp.initialWString.jstring[position - 1].codePointAt(0);
53 | const modifiedAscii = wksp.modifiedWString.jstring[position - 1].codePointAt(0);
54 | const diff = initialAscii - modifiedAscii;
55 | const relation = (diff == -1) ? sn.successor : (diff === 0) ? sn.sameness : (diff == 1) ? sn.predecessor : null;
56 |
57 | const repl = new Namespace.Replacement(letterOfInitialString, letterOfModifiedString, relation);
58 | letterOfInitialString.replacement = repl;
59 | if (relation != sn.sameness) {
60 | letterOfInitialString.changed = true;
61 | wksp.changedObject = letterOfInitialString;
62 | }
63 | }
64 | };
65 |
66 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/RuleBuilder.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet tries to build a proposed Rule, fighting with
9 | * competitors if necessary.
10 | *
11 | */
12 | Namespace.Codelets.RuleBuilder = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('rule-builder', ctx, urgency, birthdate);
25 | this.rule = args[0];
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const wksp = ctx.workspace;
36 | const rule = this.rule;
37 | const Utils = Namespace.Codelets.CodeletUtils;
38 |
39 | // If this rule already exists, then fizzle.
40 | if ( rule.sameAs(wksp.rule) ) {
41 | rule.activate();
42 | return;
43 | }
44 |
45 | // If the rule is too weak, then fizzle.
46 | rule.updateStrength();
47 | if (rule.totalStrength === 0) { return; }
48 |
49 | // If a different rule already exists, then fight.
50 | if ( !wksp.rule || Utils.structureVsStructure(rule, 1.0, wksp.rule, 1.0)) {
51 | rule.build();
52 | }
53 | }
54 |
55 |
56 | };
57 |
58 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/RuleScout.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 |
7 | /**
8 | * @classdesc
9 | * This codelet tries to propose a Rule based on the descriptions
10 | * of the initial string's changed letter and its replacement.
11 | *
12 | */
13 | Namespace.Codelets.RuleScout = class extends Namespace.Codelets.CodeletBase
14 | {
15 | /**
16 | * @constructor
17 | *
18 | * @param {Copycat} ctx - The Copycat instance.
19 | * @param {Number} urgency - The urgency of the codelet.
20 | * @param {Array} args - Arguments to pass to the codelet. (Empty for this codelet.)
21 | * @param {Number} birthdate - The birthdate of the codelet.
22 | */
23 | constructor(ctx, urgency, args, birthdate)
24 | {
25 | super('rule-scout', ctx, urgency, birthdate);
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const sn = ctx.slipnet;
36 | const wksp = ctx.workspace;
37 | const coderack = ctx.coderack;
38 |
39 | // If not all replacements have been found, then fizzle.
40 | const numUnreplaced = wksp.objects.filter(o =>
41 | (o.string == wksp.initialWString) && (o instanceof Namespace.Letter) && !o.replacement).length;
42 | if (numUnreplaced !== 0) { return; }
43 |
44 | const changedObjects = wksp.initialWString.objects.filter(o => o.changed);
45 |
46 | // If there are no changed objects, propose a rule with no changes
47 | if (!changedObjects.length) {
48 | coderack.proposeRule();
49 | return;
50 | }
51 |
52 | // Generate a list of distinguishing descriptions for the first object
53 | let objectList = [];
54 | const changed = changedObjects[changedObjects.length-1];
55 | const position = changed.getDescriptor(sn.stringPositionCategory);
56 | if (position) {
57 | objectList.push(position);
58 | }
59 |
60 | const letter = changed.getDescriptor(sn.letterCategory);
61 | const otherObjectsOfSameLetter = wksp.initialWString.objects.filter(o => (o != changed) && o.getDescriptionType(letter));
62 | if (!otherObjectsOfSameLetter.length) {
63 | objectList.push(letter);
64 | }
65 |
66 | if (changed.correspondence) {
67 | const targetObject = changed.correspondence.objFromTarget;
68 | const newList = [];
69 | const slippages = wksp.getSlippableMappings();
70 | for (let node of objectList) {
71 | node = node.applySlippages(slippages);
72 | if (targetObject.hasDescriptor(node) && targetObject.isDistinguishingDescriptor(node)) {
73 | newList.push(node);
74 | }
75 | }
76 | objectList = newList;
77 | }
78 | if (!objectList.length) { return; }
79 |
80 | // Choose the relation
81 | let weights = objectList.map(o => ctx.temperature.getAdjustedValue(o.depth));
82 | const descriptor = ctx.randGen.weightedChoice(objectList, weights);
83 |
84 | objectList = [];
85 | if (changed.replacement.relation) { objectList.push(changed.replacement.relation); }
86 | objectList.push(changed.replacement.objFromModified.getDescriptor(sn.letterCategory));
87 | weights = objectList.map(o => ctx.temperature.getAdjustedValue(o.depth));
88 | const relation = ctx.randGen.weightedChoice(objectList, weights);
89 |
90 | coderack.proposeRule(sn.letterCategory, descriptor, sn.letter, relation);
91 | }
92 | };
93 |
94 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/RuleStrengthTester.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet calculates a proposed Rule's strength, and decides
9 | * whether or not to post a RuleBuilder codelet.
10 | */
11 | Namespace.Codelets.RuleStrengthTester = class extends Namespace.Codelets.CodeletBase
12 | {
13 | /**
14 | * @constructor
15 | *
16 | * @param {Copycat} ctx - The Copycat instance.
17 | * @param {Number} urgency - The urgency of the codelet.
18 | * @param {Array} args - Arguments to pass to the codelet.
19 | * @param {Number} birthdate - The birthdate of the codelet.
20 | */
21 | constructor(ctx, urgency, args, birthdate)
22 | {
23 | super('rule-strength-tester', ctx, urgency, birthdate);
24 | this.rule = args[0];
25 | }
26 |
27 |
28 | /**
29 | * Runs the codelet.
30 | */
31 | run()
32 | {
33 | const ctx = this.ctx;
34 | const coderack = ctx.coderack;
35 | const rule = this.rule;
36 |
37 | // Maybe fizzle, if the strength is too low
38 | rule.updateStrength();
39 | const strength = rule.totalStrength;
40 | const prob = ctx.temperature.getAdjustedProb(strength/100);
41 | if (!ctx.randGen.coinFlip(prob)) { return; }
42 |
43 | // Post a RuleBuilder codelet
44 | const urgency = Namespace.Codelets.CodeletUtils.getUrgencyBin(strength);
45 | const newCodelet = ctx.coderack.factory.create('rule-builder', urgency, [rule]);
46 | coderack.post(newCodelet);
47 | }
48 |
49 |
50 |
51 |
52 | };
53 |
54 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/RuleTranslator.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet attempts to translate a Rule by applying the
9 | * slippages that have been built in the workspace.
10 | *
11 | */
12 | Namespace.Codelets.RuleTranslator = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet. (Empty for this codelet.)
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('rule-translator', ctx, urgency, birthdate);
25 | }
26 |
27 |
28 | /**
29 | * Runs the codelet.
30 | */
31 | run()
32 | {
33 | const ctx = this.ctx;
34 | const wksp = ctx.workspace;
35 |
36 | // If we don't have a rule, then fizzle.
37 | if (!wksp.rule) { return; }
38 |
39 | let bondDensity = 1.0;
40 | const totalLength = wksp.initialWString.length + wksp.targetWString.length;
41 | if (totalLength > 2) {
42 | const numBonds = wksp.initialWString.bonds.length + wksp.targetWString.bonds.length;
43 | bondDensity = Math.min(1.0, numBonds/(totalLength - 2));
44 | }
45 | const weights =
46 | bondDensity > 0.8 ? [5, 150, 5, 2, 1, 1, 1, 1, 1, 1] :
47 | bondDensity > 0.6 ? [2, 5, 150, 5, 2, 1, 1, 1, 1, 1] :
48 | bondDensity > 0.4 ? [1, 2, 5, 150, 5, 2, 1, 1, 1, 1] :
49 | bondDensity > 0.2 ? [1, 1, 2, 5, 150, 5, 2, 1, 1, 1] :
50 | [1, 1, 1, 2, 5, 150, 5, 2, 1, 1];
51 |
52 | const oneToTen = Array.from({length: 10}, (_, i) => i + 1);
53 | const cutoff = 10.0 * ctx.randGen.weightedChoice(oneToTen, weights);
54 |
55 | if (cutoff >= ctx.temperature.actualValue) {
56 | const result = wksp.rule.applyRuleToTarget();
57 | if (result) {
58 | wksp.finalAnswer = result;
59 | } else {
60 | ctx.temperature.clampUntil(ctx.coderack.numCodeletsRun + 100);
61 | }
62 | }
63 | }
64 |
65 | };
66 |
67 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/TopDownBondScout_Category.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet seeks potential Bonds between
9 | * neighboring object pairs in the initial or target string.
10 | *
11 | */
12 | Namespace.Codelets.TopDownBondScout_Category = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('top-down-bond-scout--category', ctx, urgency, birthdate);
25 | this.category = args[0];
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const sn = ctx.slipnet;
36 | const CodeletUtils = Namespace.Codelets.CodeletUtils;
37 | const cat = this.category;
38 |
39 | const bondSource = CodeletUtils.getScoutSource(ctx, 'bondCategory', cat);
40 |
41 | const bondDest = CodeletUtils.chooseNeighbor(ctx, bondSource);
42 | if (!bondDest) { return; }
43 |
44 | // Provide UI feedback.
45 | if (ctx.ui && !ctx.batchMode) {
46 | const dummyBond = new Namespace.Bond(bondSource, bondDest, sn.sameness, sn.letterCategory, sn.letters[0], sn.letters[0]);
47 | ctx.ui.workspaceUi.getStringGraphic(dummyBond.string). bondsGraphic.flashGrope(dummyBond);
48 | }
49 |
50 | const bondFacet = CodeletUtils.chooseBondFacet(ctx, bondSource, bondDest);
51 | if (!bondFacet) { return; }
52 |
53 | const sourceDescriptor = bondSource.getDescriptor(bondFacet);
54 | const destDescriptor = bondDest.getDescriptor(bondFacet);
55 |
56 | let forwardBond = sourceDescriptor.getBondCategory(destDescriptor);
57 | let backwardBond = null;
58 | if (forwardBond == sn.identity) {
59 | forwardBond = sn.sameness;
60 | backwardBond = sn.sameness;
61 | } else {
62 | backwardBond = destDescriptor.getBondCategory(sourceDescriptor);
63 | }
64 |
65 | if (cat == forwardBond) {
66 | ctx.coderack.proposeBond(bondSource, bondDest, cat, bondFacet, sourceDescriptor, destDescriptor);
67 | }
68 | else if (cat == backwardBond) {
69 | ctx.coderack.proposeBond(bondDest, bondSource, cat, bondFacet, destDescriptor, sourceDescriptor);
70 | }
71 | }
72 |
73 | };
74 |
75 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/TopDownBondScout_Direction.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet seeks potential Bonds between
9 | * neighboring object pairs in the initial or target string.
10 | *
11 | */
12 | Namespace.Codelets.TopDownBondScout_Direction = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('top-down-bond-scout--direction', ctx, urgency, birthdate);
25 | this.bondDirection = args[0]; // left or right
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const CodeletUtils = Namespace.Codelets.CodeletUtils;
36 |
37 | const bondSource = CodeletUtils.getScoutSource(ctx, 'bondDirection', this.bondDirection);
38 |
39 | const bondDest = this._chooseDirectedNeighbor(bondSource);
40 | if (!bondDest) { return; }
41 |
42 | // Provide UI feedback.
43 | if (ctx.ui && !ctx.batchMode) {
44 | const sn = ctx.slipnet;
45 | const dummyBond = new Namespace.Bond(bondSource, bondDest, sn.sameness, sn.letterCategory, sn.letters[0], sn.letters[0]);
46 | ctx.ui.workspaceUi.getStringGraphic(dummyBond.string). bondsGraphic.flashGrope(dummyBond);
47 | }
48 |
49 | const bondFacet = CodeletUtils.chooseBondFacet(ctx, bondSource, bondDest);
50 | if (!bondFacet) { return; }
51 |
52 | const sourceDescriptor = bondSource.getDescriptor(bondFacet);
53 | const destDescriptor = bondDest.getDescriptor(bondFacet);
54 |
55 | let category = sourceDescriptor.getBondCategory(destDescriptor);
56 | if (!category) { return; }
57 |
58 | if (category == ctx.slipnet.identity) { category = ctx.slipnet.sameness; }
59 |
60 | // Propose the bond
61 | ctx.coderack.proposeBond(bondSource, bondDest, category, bondFacet, sourceDescriptor, destDescriptor);
62 | }
63 |
64 |
65 |
66 | /**
67 | * Chooses a neighbor of the given object in the given direction.
68 | * @private
69 | */
70 | _chooseDirectedNeighbor(source)
71 | {
72 | const ctx = this.ctx;
73 | let objects = [];
74 |
75 | if (this.bondDirection == ctx.slipnet.left) {
76 | objects = ctx.workspace.objects.filter(o => (o.string == source.string) && (source.leftIndex == o.rightIndex + 1));
77 | } else {
78 | objects = ctx.workspace.objects.filter(o => (o.string == source.string) && (source.rightIndex == o.leftIndex - 1));
79 | }
80 |
81 | const weights = objects.map( o => ctx.temperature.getAdjustedValue(o.intraStringSalience) );
82 | return ctx.randGen.weightedChoice(objects, weights);
83 | }
84 |
85 | };
86 |
87 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/TopDownDescriptionScout.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * Thic codelet seeks an appropriate Description for a randomly
9 | * selected workspace object.
10 | *
11 | */
12 | Namespace.Codelets.TopDownDescriptionScout = class extends Namespace.Codelets.CodeletBase
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {Copycat} ctx - The Copycat instance.
18 | * @param {Number} urgency - The urgency of the codelet.
19 | * @param {Array} args - Arguments to pass to the codelet.
20 | * @param {Number} birthdate - The birthdate of the codelet.
21 | */
22 | constructor(ctx, urgency, args, birthdate)
23 | {
24 | super('top-down-description-scout', ctx, urgency, birthdate);
25 | this.descriptionType = args[0]; // e.g., stringPositionCategory, alphabeticPositionCategory
26 | }
27 |
28 |
29 | /**
30 | * Runs the codelet.
31 | */
32 | run()
33 | {
34 | const ctx = this.ctx;
35 | const Utils = Namespace.Codelets.CodeletUtils;
36 |
37 | // Choose a workspace object, based on salience.
38 | const chosenObject = Utils.chooseUnmodifiedObject(ctx, 'totalSalience', ctx.workspace.objects);
39 | if (!chosenObject) { return; }
40 |
41 | // Choose one of the object's descriptions
42 | const descriptions = this._getPossibleDescriptions(chosenObject, this.descriptionType);
43 | if (!descriptions || !descriptions.length) { return; }
44 |
45 | const weights = descriptions.map(d => d.activation);
46 | const chosenDescription = ctx.randGen.weightedChoice(descriptions, weights);
47 |
48 | // Propose the description
49 | ctx.coderack.proposeDescription(chosenObject, chosenDescription.category(), chosenDescription);
50 | }
51 |
52 |
53 | /**
54 | * Gets appropriate descriptions of an object, that match a given description type.
55 | *
56 | * @param {WorkspaceObject} obj - The object to describe
57 | * @param {SlipNode} descriptionType - The description type to test against.
58 | *
59 | * @private
60 | */
61 | _getPossibleDescriptions(obj, descriptionType)
62 | {
63 | const sn = this.ctx.slipnet;
64 | const descriptions = [];
65 | for (let link of descriptionType.instanceLinks) {
66 | const node = link.destination;
67 | if ((node == sn.first) && obj.hasDescriptor(sn.letters[0])) {
68 | descriptions.push(node);
69 | }
70 | else if ((node == sn.last) && obj.hasDescriptor(sn.letters[sn.letters.length-1])) {
71 | descriptions.push(node);
72 | }
73 | else if ((node == sn.middle) && obj.isMiddleObject()) {
74 | descriptions.push(node);
75 | }
76 | for (let i=1; i<=sn.numbers.length; i++) {
77 | if ((node == sn.numbers[i-1]) && (obj instanceof Namespace.Group)) {
78 | if (obj.objectList.length == i) { descriptions.push(node); }
79 | }
80 | }
81 | }
82 | return descriptions;
83 | }
84 |
85 | };
86 |
87 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/TopDownGroupScout_Category.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet seeks potential Groups in the initial and target strings.
9 | *
10 | */
11 | Namespace.Codelets.TopDownGroupScout_Category = class extends Namespace.Codelets.CodeletBase
12 | {
13 | /**
14 | * @constructor
15 | *
16 | * @param {Copycat} ctx - The Copycat instance.
17 | * @param {Number} urgency - The urgency of the codelet.
18 | * @param {Array} args - Arguments to pass to the codelet.
19 | * @param {Number} birthdate - The birthdate of the codelet.
20 | */
21 | constructor(ctx, urgency, args, birthdate)
22 | {
23 | super('top-down-group-scout--category', ctx, urgency, birthdate);
24 | this.groupCategory = args[0];
25 | }
26 |
27 |
28 | /**
29 | * Runs the codelet.
30 | */
31 | run()
32 | {
33 | const ctx = this.ctx;
34 | const sn = ctx.slipnet;
35 | const CodeletUtils = Namespace.Codelets.CodeletUtils;
36 |
37 | const cat = this.groupCategory.getRelatedNode(sn.bondCategory);
38 | if (!cat) { return; }
39 |
40 | let source = CodeletUtils.getScoutSource(ctx, 'bondCategory', cat);
41 | if (!source || source.spansString()) { return; }
42 |
43 | let direction = source.leftmost ? sn.right : source.rightmost ? sn.left :
44 | ctx.randGen.weightedChoice([sn.left, sn.right], [sn.left.activation, sn.right.activation]);
45 |
46 | let firstBond = (direction == sn.left) ? source.leftBond : source.rightBond;
47 |
48 | if (!firstBond || (firstBond.category != cat)) {
49 | // Check other side of object
50 | firstBond = (direction == sn.right) ? source.leftBond : source.rightBond;
51 | if (!firstBond || (firstBond.category != cat)) {
52 | if ((cat == sn.sameness) && (source instanceof Namespace.Letter)) {
53 | if (ctx.randGen.coinFlip(this._singleLetterGroupProbability(source))) {
54 | // Propose a single-letter group
55 | ctx.coderack.proposeGroup([source], [], sn.samenessGroup, null, sn.letterCategory);
56 | return;
57 | }
58 | }
59 | }
60 | return;
61 | }
62 |
63 | direction = firstBond.directionCategory;
64 | let search = true;
65 | let bondFacet = null;
66 | // Find leftmost object in group with these bonds
67 | while (search) {
68 | search = false;
69 | if (!source.leftBond) {
70 | continue;
71 | }
72 | if (source.leftBond.category != cat) {
73 | continue;
74 | }
75 | if (source.leftBond.directionCategory != direction) {
76 | if (source.leftBond.directionCategory) {
77 | continue;
78 | }
79 | }
80 | if (!bondFacet || (bondFacet == source.leftBond.facet)) {
81 | bondFacet = source.leftBond.facet;
82 | direction = source.leftBond.directionCategory;
83 | source = source.leftBond.leftObject;
84 | search = true;
85 | }
86 | }
87 |
88 | // Find rightmost object in group with these bonds
89 | search = true;
90 | let destination = source;
91 | while (search) {
92 | search = false;
93 | if (!destination.rightBond) {
94 | continue;
95 | }
96 | if (destination.rightBond.category != cat) {
97 | continue;
98 | }
99 | if (destination.rightBond.directionCategory != direction) {
100 | if (destination.rightBond.directionCategory) {
101 | continue;
102 | }
103 | }
104 | if (!bondFacet || (bondFacet == destination.rightBond.facet)) {
105 | bondFacet = destination.rightBond.facet;
106 | direction = source.rightBond.directionCategory;
107 | destination = destination.rightBond.rightObject;
108 | search = true;
109 | }
110 | }
111 | if (destination == source) { return; }
112 |
113 | const objects = [source];
114 | const bonds = [];
115 | while (source != destination) {
116 | bonds.push(source.rightBond);
117 | objects.push(source.rightBond.rightObject);
118 | source = source.rightBond.rightObject;
119 | }
120 | ctx.coderack.proposeGroup(objects, bonds, this.groupCategory, direction, bondFacet);
121 | }
122 |
123 |
124 |
125 | /**
126 | * Calculates the probability of a single letter group.
127 | * @private
128 | */
129 | _singleLetterGroupProbability(letter)
130 | {
131 | const sn = this.ctx.slipnet;
132 | const group = new Namespace.Group(letter.string, sn.samenessGroup, null, sn.letterCategory, [letter], []);
133 |
134 | const numSupporters = group._numberOfLocalSupportingGroups();
135 | if (numSupporters === 0) {
136 | return 0.0;
137 | }
138 |
139 | const exp = (numSupporters == 1) ? 4.0 : (numSupporters == 2) ? 2.0 : 1.0;
140 | const support = group._localSupport() / 100;
141 | const activation = sn.length.activation / 100;
142 | const supportedActivation = Math.pow((support * activation), exp);
143 | return this.ctx.temperature.getAdjustedProb(supportedActivation);
144 | }
145 | };
146 |
147 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Codelets/TopDownGroupScout_Direction.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 | Namespace.Codelets = Namespace.Codelets || {};
5 |
6 | /**
7 | * @classdesc
8 | * This codelet seeks potential Groups in the initial and target strings.
9 | *
10 | */
11 | Namespace.Codelets.TopDownGroupScout_Direction = class extends Namespace.Codelets.CodeletBase
12 | {
13 | /**
14 | * @constructor
15 | *
16 | * @param {Copycat} ctx - The Copycat instance.
17 | * @param {Number} urgency - The urgency of the codelet.
18 | * @param {Array} args - Arguments to pass to the codelet.
19 | * @param {Number} birthdate - The birthdate of the codelet.
20 | */
21 | constructor(ctx, urgency, args, birthdate)
22 | {
23 | super('top-down-group-scout--direction', ctx, urgency, birthdate);
24 | this.direction = args[0];
25 | }
26 |
27 |
28 | /**
29 | * Runs the codelet.
30 | */
31 | run()
32 | {
33 | const ctx = this.ctx;
34 | const sn = ctx.slipnet;
35 | const CodeletUtils = Namespace.Codelets.CodeletUtils;
36 |
37 | let direction = this.direction;
38 | let source = CodeletUtils.getScoutSource(ctx, 'bondDirection', direction);
39 | if (!source || source.spansString()) { return; }
40 |
41 | const myDir = source.leftmost ? sn.right : source.rightmost ? sn.left :
42 | ctx.randGen.weightedChoice([sn.left, sn.right], [sn.left.activation, sn.right.activation]);
43 |
44 | let firstBond = (myDir == sn.left) ? source.leftBond : source.rightBond;
45 | if (firstBond && !firstBond.directionCategory) {
46 | direction = null;
47 | }
48 |
49 | if (!firstBond || (firstBond.directionCategory != direction)) {
50 | firstBond = (myDir == sn.right) ? source.leftBond : source.rightBond;
51 | if (firstBond && !firstBond.directionCategory) {
52 | direction = null;
53 | }
54 | if (!firstBond || (firstBond.directionCategory != direction)) { return; }
55 | }
56 |
57 | const category = firstBond.category;
58 | if (!category) { return; }
59 |
60 | const groupCategory = category.getRelatedNode(sn.groupCategory);
61 | let bondFacet = null;
62 | // Find the leftmost object in group with these bonds
63 | let search = true;
64 | while (search) {
65 | search = false;
66 | if (!source.leftBond) {
67 | continue;
68 | }
69 | if (source.leftBond.category != category) {
70 | continue;
71 | }
72 | if (source.leftBond.directionCategory != direction) {
73 | if (source.leftBond.directionCategory) {
74 | continue;
75 | }
76 | }
77 | if (!bondFacet || (bondFacet == source.leftBond.facet)) {
78 | bondFacet = source.leftBond.facet;
79 | direction = source.leftBond.directionCategory;
80 | source = source.leftBond.leftObject;
81 | search = true;
82 | }
83 | }
84 |
85 | // Find rightmost object in group with these bonds
86 | search = true;
87 | let destination = source;
88 | while (search) {
89 | search = false;
90 | if (!destination.rightBond) {
91 | continue;
92 | }
93 | if (destination.rightBond.category != category) {
94 | continue;
95 | }
96 | if (destination.rightBond.directionCategory != direction) {
97 | if (destination.rightBond.directionCategory) {
98 | continue;
99 | }
100 | }
101 | if (!bondFacet || (bondFacet == destination.rightBond.facet)) {
102 | bondFacet = destination.rightBond.facet;
103 | direction = source.rightBond.directionCategory;
104 | destination = destination.rightBond.rightObject;
105 | search = true;
106 | }
107 | }
108 | if (destination == source) { return; }
109 |
110 | const objects = [source];
111 | const bonds = [];
112 | while (source != destination) {
113 | bonds.push(source.rightBond);
114 | objects.push(source.rightBond.rightObject);
115 | source = source.rightBond.rightObject;
116 | }
117 | ctx.coderack.proposeGroup(objects, bonds, groupCategory, direction, bondFacet);
118 |
119 | }
120 | };
121 |
122 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/ConsoleReporter.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * This class is used to report information to the console.
9 | *
10 | */
11 | Namespace.ConsoleReporter = class {
12 |
13 | /**
14 | * @constructor
15 | *
16 | */
17 | constructor()
18 | { }
19 |
20 |
21 | /**
22 | * Reports an informational message.
23 | */
24 | info(msg)
25 | {
26 | console.info(msg);
27 | }
28 |
29 |
30 | /**
31 | * Reports a warning message.
32 | */
33 | warn(msg)
34 | {
35 | console.warn(msg);
36 | }
37 |
38 |
39 | /**
40 | * Reports an error message.
41 | */
42 | error(msg)
43 | {
44 | console.error(msg);
45 | }
46 |
47 | };
48 |
49 |
50 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Description.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * This class encapsulates a description of a Letter or Group.
9 | *
10 | * A description consists of a (descriptionType, descriptor) pair. Some examples are:
11 | *
12 | *
(objectCategory, letter)
13 | *
(letterCategory, a)
14 | *
(stringPositionCategory, leftmost)
15 | *
(groupCategory, samenessGroup)
16 | *
17 | *
18 | */
19 | Namespace.Description = class {
20 |
21 | /**
22 | * @constructor
23 | *
24 | * @param {WorkspaceObject} obj - The object being described.
25 | * @param {SlipNode} descriptionType - The aspect being described, e.g., objectCategory.
26 | * @param {SlipNode} descriptor - The value of the aspect, e.g., letter.
27 | */
28 | constructor(obj, descriptionType, descriptor)
29 | {
30 | // WorkspaceStructure members
31 | this.wksp = obj.wksp;
32 | this.string = obj.string;
33 | this.totalStrength = 0;
34 |
35 | // Description members
36 | this.object = obj;
37 | this.descriptionType = descriptionType;
38 | this.descriptor = descriptor;
39 | }
40 |
41 |
42 | /**
43 | * Returns a string describing the object.
44 | */
45 | synopsis(type)
46 | {
47 | const wksp = this.wksp;
48 | let s = this.object.synopsis(1);
49 |
50 | if (this.object.string == wksp.initialWString) {
51 | s += ' in initial string';
52 | }
53 | else if (this.object.string == wksp.modifiedWString) {
54 | s += ' in modified string';
55 | }
56 | else if (this.object.string == wksp.targetWString) {
57 | s += ' in target string';
58 | }
59 | else {
60 | s += ' in unknown string';
61 | }
62 | s += ', ' + this.descriptionType.name + ' == ' + this.descriptor.name;
63 |
64 | return !type ? s : '';
65 | }
66 |
67 |
68 | /**
69 | * Indicates whether this Description has the same descriptionType and
70 | * descriptor as another one.
71 | *
72 | * @param {Description} other - The Description to compare with.
73 | */
74 | sameAs(other)
75 | {
76 | return ((other.descriptionType == this.descriptionType) && (other.descriptor == this.descriptor));
77 | }
78 |
79 |
80 | /**
81 | * Sets the activation of the descriptionType and descriptor to 100.
82 | *
83 | */
84 | activate()
85 | {
86 | this.descriptionType.activation = 100;
87 | this.descriptor.activation = 100;
88 | }
89 |
90 |
91 | /**
92 | * Updates the total strength value.
93 | *
94 | */
95 | updateStrength()
96 | {
97 | // Internal strength
98 | let internalStrength = this.descriptor.depth;
99 |
100 | // Local support: Count the number of other objects in this
101 | // object's string that are described like this one.
102 | let numDescribedLikeThis = 0;
103 | for (let other of this.string.objects.filter(o => o != this.object)) {
104 | if ( !this.object.isWithin(other) && !other.isWithin(this.object) ) {
105 | numDescribedLikeThis += other.descriptions.filter(od => od.descriptionType == this.descriptionType).length;
106 | }
107 | }
108 | const supportVals = [0, 20, 60, 90, 100];
109 | const localSupport = supportVals[Math.min(numDescribedLikeThis,4)];
110 |
111 | // External strength
112 | let externalStrength = (localSupport + this.descriptionType.activation)/2;
113 |
114 | // Total strength
115 | const wti = internalStrength / 100;
116 | const wte = 1 - wti;
117 | this.totalStrength = wti*internalStrength + wte*externalStrength;
118 | }
119 |
120 |
121 | /**
122 | * Activates the description and adds it to its owner's description list
123 | * and to the workspace.
124 | */
125 | build()
126 | {
127 | this.activate();
128 | if (!this.object.hasDescriptor(this.descriptor)) {
129 | this.object.descriptions.push(this);
130 | }
131 | if (!this.wksp.structures.includes(this)) {
132 | this.wksp.structures.push(this);
133 | }
134 | }
135 |
136 |
137 | /**
138 | * Removes the Description from its owner's description list, and
139 | * from the workspace structures list.
140 | */
141 | break()
142 | {
143 | this.wksp.structures = this.wksp.structures.filter(s => s !== this);
144 | this.object.descriptions = this.object.descriptions.filter(s => s !== this);
145 | }
146 |
147 | };
148 |
149 |
150 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Letter.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * The Letter class represents a single alphabetic character.
9 | * It inherits from WorkspaceObject.
10 | *
11 | */
12 | Namespace.Letter = class extends Namespace.WorkspaceObject {
13 |
14 | /**
15 | * @constructor
16 | *
17 | * @param {WorkspaceString} str - The string that the letter is in.
18 | * @param {Number} position - The (1-based) position of the letter within the string.
19 | */
20 | constructor(str, position)
21 | {
22 | super(str);
23 | this.char = str.jstring.charAt(position-1);
24 | this.position = position;
25 | this.leftIndex = position;
26 | this.rightIndex = position;
27 | this.leftmost = (position == 1);
28 | this.rightmost = (position == str.length);
29 |
30 | // Create and cache my descriptions
31 | this._addDescriptions();
32 | }
33 |
34 |
35 | /**
36 | * Creates and caches the descriptions of the Letter.
37 | * @private
38 | */
39 | _addDescriptions()
40 | {
41 | const sn = this.wksp.ctx.slipnet;
42 | this.descriptions.push(new Namespace.Description(this, sn.objectCategory, sn.letter));
43 |
44 | const letterNode = sn.letters[this.string.jstring.toUpperCase().charCodeAt(this.position-1) - 'A'.charCodeAt(0)];
45 | this.descriptions.push(new Namespace.Description(this, sn.letterCategory, letterNode));
46 |
47 | if (this.string.length == 1) {
48 | this.descriptions.push( new Namespace.Description(this, sn.stringPositionCategory, sn.single) );
49 | }
50 | if (this.leftmost) {
51 | this.descriptions.push( new Namespace.Description(this, sn.stringPositionCategory, sn.leftmost) );
52 | }
53 | if (this.rightmost) {
54 | this.descriptions.push( new Namespace.Description(this, sn.stringPositionCategory, sn.rightmost) );
55 | }
56 | if (2*this.position == this.string.length + 1) {
57 | this.descriptions.push( new Namespace.Description(this, sn.stringPositionCategory, sn.middle) );
58 | }
59 | }
60 |
61 |
62 | /**
63 | * Returns a string describing the object.
64 | *
65 | */
66 | synopsis(type)
67 | {
68 | return !type ? this.char : '';
69 | }
70 |
71 |
72 | /**
73 | * Indicates whether no other Letter in this Letter's string has a descriptor matching the given one.
74 | *
75 | * @param {SlipNode} descriptor - The descriptor to match.
76 | */
77 | isDistinguishingDescriptor(descriptor)
78 | {
79 | let sn = this.wksp.ctx.slipnet;
80 | if ((descriptor == sn.letter) || (descriptor == sn.group) || sn.numbers.includes(descriptor)) {
81 | return false;
82 | }
83 |
84 | if (this.string.objects.some(obj => (obj instanceof Namespace.Letter) && (obj != this) &&
85 | obj.descriptions.some(d => d.descriptor == descriptor))) {
86 | return false;
87 | }
88 |
89 | return true;
90 | }
91 |
92 | };
93 |
94 |
95 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/RandGen.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * This class provides methods for generating random values
9 | * and making random selections.
10 | *
11 | */
12 | Namespace.RandGen = class {
13 |
14 | /**
15 | * @constructor
16 | * @param {Number|String} [seed=Date.now] - A seed for initializing
17 | * the randomness generator.
18 | */
19 | constructor(seed)
20 | {
21 | // Initialize
22 | const strSeed = ((typeof(seed) !== "undefined") && (seed !== null)) ?
23 | seed.toString() : Date.now().toString();
24 |
25 | const cyrSeed = Namespace.RandGen._get_seed_cyrb128(strSeed);
26 | this.rng = Namespace.RandGen._sfc32(...cyrSeed);
27 | }
28 |
29 |
30 | /**
31 | * Returns a random number in the range [0,1), sampled from
32 | * a uniform distribution.
33 | *
34 | * @returns A number in the range [0,1).
35 | */
36 | rand()
37 | {
38 | return this.rng();
39 | }
40 |
41 |
42 | /**
43 | * Returns true or false, with probabilities p and 1-p, respectively.
44 | *
45 | * @param {Number} p - The probability of a 'true' result.
46 | * @returns true or false.
47 | */
48 | coinFlip(p = 0.5)
49 | {
50 | return this.rng() < p;
51 | }
52 |
53 |
54 | /**
55 | * Returns a random element from a given sequence.
56 | *
57 | * @param {Indexable} seq - The sequence to select from.
58 | * @returns A random element from seq.
59 | */
60 | choice(seq)
61 | {
62 | const idx = Math.floor(this.rng()*seq.length);
63 | return seq[idx];
64 | }
65 |
66 |
67 | /**
68 | * Returns a random element from the given sequence,
69 | * with weighted probabilities.
70 | *
71 | * @param {Indexable} seq - The sequence to select from.
72 | * @param {Array} weights - The relative weights.
73 | * @returns A random element from seq.
74 | */
75 | weightedChoice(seq, weights)
76 | {
77 | if (!seq || !seq.length) {
78 | return null; // (Apparently, many callers rely on this behavior)
79 | }
80 |
81 | const N = seq.length;
82 | if ( N !== weights.length ){
83 | throw new Error("Incompatible array lengths, in RandGen.weightedChoice");
84 | }
85 |
86 | let csum = 0;
87 | const cumWeights = weights.map((csum = 0, n => csum += n));
88 | const r = this.rng() * cumWeights[N-1];
89 | let idx = N-1;
90 | for (let i=0; i>> 18), 597399067);
163 | h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
164 | h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
165 | h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
166 | return [(h1^h2^h3^h4)>>>0, (h2^h1)>>>0, (h3^h1)>>>0, (h4^h1)>>>0];
167 | }
168 |
169 |
170 | /**
171 | * Returns a function that generates random numbers, seeded
172 | * with the given 4-component seed.
173 | * Reference: https://stackoverflow.com/questions/521295/
174 | * @private
175 | *
176 | * @param {Number} a - 1st seed component.
177 | * @param {Number} b - 2nd seed component.
178 | * @param {Number} c - 3rd seed component.
179 | * @param {Number} d - 4th seed component.
180 | * @return {Function} A function that produces random numbers in
181 | * the range [0,1).
182 | */
183 | static _sfc32(a, b, c, d) {
184 | return function() {
185 | a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
186 | let t = (a + b) | 0;
187 | a = b ^ b >>> 9;
188 | b = c + (c << 3) | 0;
189 | c = (c << 21 | c >>> 11);
190 | d = d + 1 | 0;
191 | t = t + d | 0;
192 | c = c + t | 0;
193 | return (t >>> 0) / 4294967296;
194 | };
195 | }
196 | };
197 |
198 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Replacement.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * The Replacement class encapsulates a relation (sameness, predecessor, or
9 | * successor) between a Letter in the initial string and a Letter in the
10 | * modified string.
11 | *
12 | */
13 | Namespace.Replacement = class {
14 |
15 | /**
16 | * @constructor
17 | *
18 | * @param {Letter} objFromInitial - A letter from the initial string.
19 | * @param {Letter} objFromModified - A letter from the modified string.
20 | * @param {SlipNode} relation - The relation between the two letters.
21 | *
22 | */
23 | constructor(objFromInitial, objFromModified, relation)
24 | {
25 | // WorkspaceStructure members
26 | this.wksp = objFromInitial.wksp;
27 | this.string = objFromInitial.string;
28 | this.totalStrength = 0;
29 |
30 | this.objFromInitial = objFromInitial;
31 | this.objFromModified = objFromModified;
32 | this.relation = relation;
33 | }
34 |
35 |
36 | /**
37 | * Returns a string describing the object.
38 | *
39 | */
40 | synopsis(type)
41 | {
42 | let s = this.objFromInitial.synopsis() + ' -> ' +
43 | this.objFromModified.synopsis() + ' (' +
44 | (this.relation ? this.relation.name : 'null') + ')';
45 |
46 | return !type ? s : '';
47 | }
48 |
49 | };
50 |
51 |
52 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/SlipLink.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * The SlipLink class represents a relation between two concepts.
9 | *
10 | */
11 | Namespace.SlipLink = class {
12 |
13 | /**
14 | * @constructor
15 | *
16 | * @param {String} type - The link type ('category', 'property', 'instance', 'lateralSlip', or 'lateralNonSlip').
17 | * @param {SlipNode} source - The source node.
18 | * @param {SlipNode} destination - The destination node.
19 | * @param {SlipNode} [label=null] - A SlipNode that labels the link.
20 | * @param {Number} [length=0] - The "conceptual distance" between the source and destination nodes. Slippage
21 | * occurs more easily when this distance is smaller.
22 | */
23 | constructor(type, source, destination, label=null, length=0)
24 | {
25 | this.type = type;
26 | this.source = source;
27 | this.destination = destination;
28 | this.label = label;
29 | this.fixedLength = length;
30 |
31 | // Add this link to the source and destination nodes.
32 | source.outgoingLinks.push(this);
33 | destination.incomingLinks.push(this);
34 |
35 | Object.freeze(this);
36 | }
37 |
38 |
39 | /**
40 | * Returns a string describing the object.
41 | *
42 | */
43 | synopsis(type)
44 | {
45 | const s = `${this.source.synopsis(1)} to ` +
46 | `${this.destination.synopsis(1)} (length=` +
47 | `${this.fixedLength.toFixed(0)}` +
48 | (this.label ? `, label=${this.label.name}` : '') + ')';
49 |
50 | return !type ? s : '';
51 | }
52 |
53 |
54 | /**
55 | * Returns a measure of the shortness of this link, or
56 | * of its label if it has one.
57 | *
58 | */
59 | degreeOfAssociation()
60 | {
61 | if ((this.fixedLength > 0) || !this.label) {
62 | return 100 - this.fixedLength;
63 | }
64 | else {
65 | return this.label.degreeOfAssociation();
66 | }
67 | }
68 |
69 |
70 | /**
71 | * Returns a measure of the shortness of this link, or
72 | * of its label's intrinsic shortness if it has a label.
73 | *
74 | */
75 | intrinsicDegreeOfAssociation()
76 | {
77 | if (this.fixedLength !== 0) {
78 | return 100 - this.fixedLength;
79 | }
80 | else if (this.label) {
81 | return 100 - this.label.intrinsicLinkLength;
82 | }
83 | else {
84 | return 0;
85 | }
86 | }
87 |
88 |
89 | };
90 |
91 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/Temperature.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * The Temperature class keeps track of the workspace temperature.
9 | *
10 | */
11 | Namespace.Temperature = class {
12 |
13 | /**
14 | * @constructor
15 | *
16 | */
17 | constructor()
18 | {
19 | this.reset();
20 | }
21 |
22 |
23 | /**
24 | * Resets the temperature to 100 and clamps it until time = 30.
25 | *
26 | */
27 | reset()
28 | {
29 | this.actualValue = 100;
30 | this.lastUnclampedValue = 100;
31 | this.clamped = true;
32 | this.clampTime = 30;
33 | }
34 |
35 |
36 | /**
37 | * Returns the current temperature value.
38 | *
39 | */
40 | value() {
41 | return this.clamped ? 100 : this.actualValue;
42 | }
43 |
44 |
45 | /**
46 | * Sets the temperature to the given value, unless we
47 | * are currently clamped, in which case the value is cached.
48 | *
49 | * @param {Number} value - The value to set.
50 | */
51 | set(value)
52 | {
53 | this.lastUnclampedValue = value;
54 | this.actualValue = this.clamped ? 100 : value;
55 | }
56 |
57 |
58 | /**
59 | * Clamps the temperature until the given time.
60 | *
61 | * @param {Number} when - The time to unclamp.
62 | */
63 | clampUntil(when)
64 | {
65 | this.clamped = true;
66 | this.clampTime = when;
67 | }
68 |
69 |
70 | /**
71 | * Unclamps the temperature if the given time is greater than
72 | * the clamping time.
73 | *
74 | * @param {Number} currentTime - The current time.
75 | */
76 |
77 | tryUnclamp(currentTime) {
78 | if (this.clamped && (currentTime >= this.clampTime)) {
79 | this.clamped = false;
80 | }
81 | }
82 |
83 |
84 | /**
85 | * Adjusts the given value according to the current temperature.
86 | * (The value is raised to a power that decreases with temperature.)
87 | *
88 | * @param {Number} input - The value to adjust.
89 | */
90 | getAdjustedValue(input)
91 | {
92 | const exp = (100 - this.value())/30 + 0.5;
93 | return Math.pow(input, exp);
94 | }
95 |
96 |
97 | /**
98 | * Ajusts the given probability value based on the current temperature.
99 | * If the temperature is 0, no adjustment is made. Otherwise, values
100 | * above .5 are lowered and values below .5 are raised.
101 | *
102 | * @param {Number} inProb - The probability to adjust.
103 | */
104 | getAdjustedProb(inProb)
105 | {
106 | if (inProb === 0) { return 0; }
107 | if (inProb === 0.5) { return 0.5; }
108 |
109 | const temp = this.value();
110 | if (inProb < 0.5) {
111 | return 1 - this.getAdjustedProb(1 - inProb);
112 | }
113 | else {
114 | return Math.max(0.5, inProb*(1 - (10 - Math.sqrt(100 - temp))/100) );
115 | }
116 | }
117 |
118 | };
119 |
120 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/UI/ArrowGraphic.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * This class draws the string-to-corresponding-string arrows.
9 | *
10 | */
11 | Namespace.ArrowGraphic = class
12 | {
13 | /**
14 | * @constructor
15 | *
16 | * @param {WorkspaceUi} workspaceUi - The parent Ui.
17 | */
18 | constructor(workspaceUi)
19 | {
20 | this.wkspUi = workspaceUi;
21 | this.drawParams = {};
22 | }
23 |
24 |
25 | /**
26 | * Draws the arrows.
27 | *
28 | */
29 | redraw(ctx)
30 | {
31 | // Update our drawing parameters if necessary
32 | if (Namespace.UiUtils.NeedToRescale(this.drawParams, ctx)) { this._updateDrawParams(ctx);}
33 |
34 | const dp = this.drawParams;
35 | ctx.strokeStyle = this.wkspUi.letterColor;
36 | ctx.lineWidth = 1;
37 | ctx.setLineDash([]);
38 |
39 | for (let line of dp.lines) {
40 | Namespace.UiUtils.DrawLine(ctx, line.xa, line.ya, line.xb, line.yb);
41 | }
42 | }
43 |
44 |
45 | /**
46 | * Recalculates the drawing parameters for this object.
47 | *
48 | * @private
49 | */
50 | _updateDrawParams(ctx)
51 | {
52 | const [w, h] = [ctx.canvas.width, ctx.canvas.height];
53 | const dp = this.drawParams;
54 | dp.canvasWidth = w; dp.canvasHeight = h;
55 |
56 | let cx = w/2;
57 | let cy = h/3 - h/80;
58 | let sz = w/25;
59 | dp.lines = [];
60 |
61 | dp.lines.push({xa: cx-0.5*sz, ya: cy-0.08*sz, xb: cx+0.34*sz, yb: cy-0.08*sz});
62 | dp.lines.push({xa: cx-0.5*sz, ya: cy+0.08*sz, xb: cx+0.34*sz, yb: cy+0.08*sz});
63 | dp.lines.push({xa: cx+0.1*sz, ya: cy-0.2*sz, xb: cx+0.5*sz, yb: cy});
64 | dp.lines.push({xa: cx+0.1*sz, ya: cy+0.2*sz, xb: cx+0.5*sz, yb: cy});
65 |
66 | cy = 2*h/3 - h/80;
67 | dp.lines.push({xa: cx-0.5*sz, ya: cy-0.08*sz, xb: cx+0.34*sz, yb: cy-0.08*sz});
68 | dp.lines.push({xa: cx-0.5*sz, ya: cy+0.08*sz, xb: cx+0.34*sz, yb: cy+0.08*sz});
69 | dp.lines.push({xa: cx+0.1*sz, ya: cy-0.2*sz, xb: cx+0.5*sz, yb: cy});
70 | dp.lines.push({xa: cx+0.1*sz, ya: cy+0.2*sz, xb: cx+0.5*sz, yb: cy});
71 | }
72 | };
73 |
74 |
75 |
76 |
77 |
78 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/UI/CopycatUi.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * This is the top level container for the Copycat UI elements.
9 | *
10 | */
11 | Namespace.CopycatUi = class {
12 |
13 | /**
14 | * @constructor
15 | *
16 | * @param {Copycat} copycat - The Copycat instance to be visualized.
17 | */
18 | constructor(copycat)
19 | {
20 | this.copycat = copycat;
21 | this.rafId = null;
22 |
23 | // Create the UI elements
24 | this.topbarUi = new Namespace.TopbarUi(this, document.getElementById('topbar_area'));
25 | this.inputUi = new Namespace.InputUi(this, document.getElementById('input_area'));
26 | this.workspaceHeaderUi = new Namespace.WorkspaceHeaderUi(this, document.getElementById('workspace_header_area'));
27 | this.workspaceUi = new Namespace.WorkspaceUi(this, document.getElementById('workspace_area'));
28 | this.slipnetUi = new Namespace.SlipnetUi(this, document.getElementById('slipnet_area'));
29 | this.coderackUi = new Namespace.CoderackUi(this, document.getElementById('coderack_area'));
30 | this.batchmodeUi = new Namespace.BatchmodeUi(this, document.getElementById('batchmode_area'));
31 |
32 | this.singleModeUiList = [this.topbarUi, this.inputUi, this.workspaceHeaderUi, this.workspaceUi, this.slipnetUi, this.coderackUi];
33 | this.batchModeUiList = [this.topbarUi, this.inputUi, this.batchmodeUi];
34 | this.uiList = this.singleModeUiList;
35 |
36 | // Listen for resize events
37 | const resizeFunc = this._onResize.bind(this);
38 | window.addEventListener('resize', resizeFunc);
39 | window.addEventListener('orientationchange', resizeFunc);
40 |
41 | // Do an initial rendering, after the DOM settles.
42 | setTimeout( resizeFunc, 250 );
43 | setTimeout( resizeFunc, 1500 );
44 | }
45 |
46 |
47 | /**
48 | * Handler for state-change events
49 | * @private
50 | *
51 | */
52 | _onCopycatStateChange()
53 | {
54 | this.uiList.forEach( ui => ui._onCopycatStateChange() );
55 | }
56 |
57 |
58 | /**
59 | * Handler for batchmode-toggle events
60 | * @private
61 | *
62 | */
63 | _onBatchModeToggled()
64 | {
65 | this.batchmodeUi.parentDiv.style.display = this.copycat.batchMode ? 'block' : 'none';
66 |
67 | [this.workspaceHeaderUi, this.workspaceUi, this.slipnetUi, this.coderackUi].forEach(
68 | ui => ui.parentDiv.style.display = this.copycat.batchMode ? 'none' : 'block');
69 |
70 | this.inputUi.parentDiv.style.width = this.copycat.batchMode ? '100%' : '60%';
71 |
72 | this.uiList = this.copycat.batchMode ? this.batchModeUiList : this.singleModeUiList;
73 | this._onCopycatStateChange();
74 | this._onResize();
75 | }
76 |
77 |
78 | /**
79 | * Handler for resize events.
80 | * @private
81 | *
82 | */
83 | _onResize()
84 | {
85 | if ( this.rafId ) { window.cancelAnimationFrame(this.rafId); }
86 |
87 | this.rafId = window.requestAnimationFrame( () => {
88 | this.uiList.forEach( ui => ui._onResize() );
89 | this.rafId = null;
90 | }
91 | );
92 | }
93 |
94 |
95 | /**
96 | * Makes sure that all input strings are valid.
97 | * @private
98 | *
99 | */
100 | checkInputStrings()
101 | {
102 | const wksp = this.copycat.workspace;
103 | const inputStrings = this.inputUi.getInputStrings();
104 | const wkspStrings = [wksp.initialWString.jstring, wksp.modifiedWString.jstring, wksp.targetWString.jstring];
105 | const inputModified = !inputStrings.every((str, idx) => str.toLowerCase() == wkspStrings[idx]);
106 |
107 | if (inputModified) {
108 | if (inputStrings.every(this.copycat.checkInputString)) {
109 | this.copycat.setStrings(...inputStrings);
110 | return true;
111 | } else {
112 | this.inputUi.displayMessage('Invalid input!');
113 | return false;
114 | }
115 | }
116 | else {
117 | return true;
118 | }
119 | }
120 |
121 | };
122 |
123 |
124 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/UI/DescriptionsGraphic.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 |
6 | /**
7 | * @classdesc
8 | * This class is responsible for drawing the Descriptions of
9 | * letters in a given string.
10 | *
11 | */
12 | Namespace.DescriptionsGraphic = class
13 | {
14 | /**
15 | * @constructor
16 | *
17 | * @param {StringGraphic} stringGraphic - The parent graphic.
18 | *
19 | */
20 | constructor(stringGraphic)
21 | {
22 | this.stringGraphic = stringGraphic;
23 | this.wstring = stringGraphic.wstring;
24 | this.wkspUi = stringGraphic.wkspUi;
25 |
26 | this.descriptionGraphics = stringGraphic.lettersGraphic.letterGraphics.map(
27 | lg => new Namespace.DescriptionGraphic(lg, this));
28 | }
29 |
30 |
31 | /**
32 | * Draws the descriptions.
33 | *
34 | * @param {CanvasRenderingContext2D} ctx - The canvas context
35 | * to draw on.
36 | */
37 | redraw(ctx)
38 | {
39 | this.descriptionGraphics.forEach(g => g.redraw(ctx));
40 | }
41 | };
42 |
43 |
44 | /**
45 | * @classdesc
46 | * This class draws a single Description.
47 | *
48 | */
49 | Namespace.DescriptionGraphic = class
50 | {
51 | /**
52 | * @constructor
53 | *
54 | * @param {LetterGraphic} lg - The associated LetterGraphic.
55 | * @param {DescriptionsGraphic} parent - The DescriptionsGraphic that
56 | * owns this object.
57 | */
58 | constructor(lg, parent)
59 | {
60 | this.letterGraphic = lg;
61 | this.parent = parent;
62 | this.drawParams = {};
63 | }
64 |
65 | /**
66 | * Draws the Description graphics.
67 | *
68 | * @param {CanvasRenderingContext2D} ctx - The canvas context
69 | * to draw on.
70 | */
71 | redraw(ctx)
72 | {
73 | // Update our drawing parameters if necessary
74 | if (Namespace.UiUtils.NeedToRescale(this.drawParams, ctx)) {
75 | this._updateDrawParams(ctx);
76 | }
77 |
78 | const wkspUi = this.parent.wkspUi;
79 | const dp = this.drawParams;
80 | ctx.textAlign = 'left';
81 | ctx.fillStyle = wkspUi.descriptionColor;
82 |
83 | const descrips = this.letterGraphic.letter.descriptions;
84 | for (let i=0; i
77 | {
78 | this.canvasCtx.clearRect(0, 0, this.canvas.width, this.canvas.height);
79 |
80 | // Draw the first-in-line item
81 | const f = this.flashables[0];
82 | f.graphic.redraw(this.canvasCtx);
83 | f.flashCount--;
84 | if (f.flashCount <= 0) {
85 | this.flashables.shift();
86 | }
87 |
88 | // Erase everything after a delay
89 | setTimeout( () => {
90 | window.requestAnimationFrame( () => {
91 | this.canvasCtx.clearRect(0, 0, this.canvas.width, this.canvas.height);
92 |
93 | if (this.flashables.length === 0) {
94 | this.state = 'idle';
95 | this.wkspUi.redraw();
96 | } else {
97 | setTimeout(
98 | () => { this._animationLoop(); },
99 | Math.min(100, 1.5*this.copycat.stepDelay)
100 | );
101 | }
102 | });
103 | }, Math.min(100, 1.5*this.copycat.stepDelay));
104 | });
105 | }
106 |
107 | };
108 |
109 |
110 | })( window.CopycatJS = window.CopycatJS || {} );
--------------------------------------------------------------------------------
/src/UI/HelpDialog.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-shadow-restricted-names, no-unused-vars, no-extra-semi
2 | ;(function(Namespace, undefined) {
3 | "use strict";
4 |
5 | /**
6 | * @classdesc
7 | * This class implements a dialog for displaying help info.
8 | *
9 | */
10 | Namespace.HelpDialog = class extends Namespace.Dialog
11 | {
12 |
13 | /**
14 | * @constructor
15 | *
16 | * @param {CopycatUi} copycatUi - The parent Ui.
17 | * @param {HTMLElement} parent - The html div that hosts the dialog.
18 | */
19 | constructor(parent)
20 | {
21 | super(parent, 65, 80, 'Copycat Help', false, '#d5bfa2', '#c09f72');
22 | this._buildUi();
23 | }
24 |
25 |
26 | _buildUi()
27 | {
28 | Namespace.UiUtils.StyleElement(this.userDiv, {overflowX:'auto', overflowY:'scroll'});
29 |
30 | this.textDiv = Namespace.UiUtils.CreateElement('div', 'text-div',
31 | this.userDiv, {left:'3%', width:'94%', height:'100%', fontSize:'20px', fontFamily:this.fontFamily}
32 | );
33 |
34 | this.textDiv.innerHTML =
35 | '' +
36 | '
Copycat is a computer model of human analogy-making.
' +
37 | '
It tries to solve letter puzzles of the form "abc is to abd as ijk is to what?"
' +
38 | '
You can enter puzzle strings in the green upper-left area, then click the play button to watch Copycat "think." ' +
39 | 'You can also pause, single-step, reset, and adjust the speed of the demo.
' +
40 |
41 | '
Some interesting examples to try are:
' +
42 | '
' +
43 | '
abc → abd, kji → ?
' +
44 | '
abc → abd, iijjkk → ?
' +
45 | '
abc → abd, mrrjjj → ?
' +
46 | '
' +
47 |
48 | '
The algorithm is probabilistic, so you may get different results each time you run it.
' +
49 |
50 | '
While running, Copycat displays
' +
51 | '
' +
52 | '
bonds it finds between adjacent letters, as blue lines
' +
53 | '
correspondences it finds between the initial and target strings, as purple jagged lines
' +
54 | '
groups that it thinks may be important, as green boxes
' +
55 | '
descriptions of all the letters, in gray text
' +
56 | '
' +
57 |
58 | 'A flashing graphic indicates a structure that Copycat is considering but hasn’t yet committed to.' +
59 |
60 | '
The thermometer shows how "happy" Copycat is with its current progress; lower temperatures ' +
61 | 'imply greater happiness.
' +
62 |
63 | '
In the yellow Slipnet area, Copycat’s built-in concepts are shown in a grid. ' +
64 | 'The size of the dot over each concept indicates how important that concept is to Copycat at the current moment.
' +
65 |
66 | '
The pink Coderack area displays the list of subroutines or "codelets" that Copycat ' +
67 | 'uses to perform its work. The number of each type of codelet currently in the coderack is shown in a dynamical ' +
68 | 'bar graph. The last-run codelet is indicated by a black triangle.
' +
69 |
70 | '
For (much) more information, check out the book Fluid Concepts and Creative Analogies by Douglas Hofstadter et al.