@statelyai/agent 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.env.template +6 -0
- package/.github/actions/ci-setup/action.yml +24 -0
- package/.github/workflows/release.yml +35 -0
- package/.vscode/launch.json +17 -0
- package/CHANGELOG.md +13 -0
- package/dist/index.d.ts +66 -6
- package/dist/index.js +86 -11
- package/examples/helpers/helpers.ts +17 -0
- package/examples/helpers/loader.ts +32 -0
- package/examples/helpers/runner.ts +27 -0
- package/examples/joke.ts +181 -152
- package/examples/ticTacToe.ts +133 -121
- package/examples/weather.ts +147 -0
- package/package.json +22 -10
- package/readme.md +41 -0
- package/src/index.ts +1 -0
- package/src/openai.ts +154 -12
- package/src/utils.ts +59 -1
- package/dist/index.d.mts +0 -3
- package/dist/index.mjs +0 -7
package/examples/joke.ts
CHANGED
|
@@ -1,185 +1,214 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
assign,
|
|
4
|
+
createActor,
|
|
5
|
+
fromCallback,
|
|
6
|
+
fromPromise,
|
|
7
|
+
log,
|
|
8
|
+
setup,
|
|
9
|
+
} from 'xstate';
|
|
10
|
+
import { createAgent } from '../src';
|
|
11
|
+
import { loadingAnimation } from './helpers/loader';
|
|
4
12
|
|
|
5
13
|
const openai = new OpenAI({
|
|
6
14
|
apiKey: process.env.OPENAI_API_KEY,
|
|
7
15
|
});
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
const agent = createAgent(openai, {
|
|
18
|
+
model: 'gpt-3.5-turbo-1106',
|
|
19
|
+
context: {
|
|
20
|
+
topic: { type: 'string' },
|
|
21
|
+
jokes: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
items: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
},
|
|
26
|
+
desire: { type: ['string', 'null'] },
|
|
27
|
+
lastRating: { type: ['string', 'null'] },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
events: {},
|
|
31
|
+
});
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
const getJokeCompletion = agent.fromChatCompletion(
|
|
34
|
+
(topic: string) => `Tell me a joke about ${topic}.`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const rateJoke = agent.fromChatCompletion(
|
|
38
|
+
(joke: string) => `Rate this joke on a scale of 1 to 10: ${joke}`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const getTopic = fromPromise(async () => {
|
|
42
|
+
const topic = await new Promise<string>((res) => {
|
|
43
|
+
console.log('Give me a joke topic:');
|
|
44
|
+
const listener = (data: Buffer) => {
|
|
45
|
+
const result = data.toString().trim();
|
|
46
|
+
process.stdin.off('data', listener);
|
|
47
|
+
res(result);
|
|
48
|
+
};
|
|
49
|
+
process.stdin.on('data', listener);
|
|
50
|
+
});
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const res = await openai.chat.completions.create({
|
|
32
|
-
messages: [
|
|
33
|
-
{
|
|
34
|
-
role: 'user',
|
|
35
|
-
content: `Rate this joke on a scale of 1 to 10: ${input.joke}`,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
model: 'gpt-3.5-turbo',
|
|
39
|
-
n: 1,
|
|
40
|
-
});
|
|
52
|
+
return topic;
|
|
53
|
+
});
|
|
41
54
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
const decide = agent.fromEvent(
|
|
56
|
+
(lastRating: string) =>
|
|
57
|
+
`Choose what to do next, given the previous rating of the joke: ${lastRating}`
|
|
58
|
+
);
|
|
59
|
+
export function getRandomFunnyPhrase() {
|
|
60
|
+
const funnyPhrases = [
|
|
61
|
+
'Concocting chuckles...',
|
|
62
|
+
'Brewing belly laughs...',
|
|
63
|
+
'Fabricating funnies...',
|
|
64
|
+
'Assembling amusement...',
|
|
65
|
+
'Molding merriment...',
|
|
66
|
+
'Whipping up wisecracks...',
|
|
67
|
+
'Generating guffaws...',
|
|
68
|
+
'Inventing hilarity...',
|
|
69
|
+
'Cultivating chortles...',
|
|
70
|
+
'Hatching howlers...',
|
|
71
|
+
];
|
|
72
|
+
return funnyPhrases[Math.floor(Math.random() * funnyPhrases.length)]!;
|
|
73
|
+
}
|
|
45
74
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
75
|
+
export function getRandomRatingPhrase() {
|
|
76
|
+
const ratingPhrases = [
|
|
77
|
+
'Assessing amusement...',
|
|
78
|
+
'Evaluating hilarity...',
|
|
79
|
+
'Ranking chuckles...',
|
|
80
|
+
'Classifying cackles...',
|
|
81
|
+
'Scoring snickers...',
|
|
82
|
+
'Rating roars...',
|
|
83
|
+
'Judging jollity...',
|
|
84
|
+
'Measuring merriment...',
|
|
85
|
+
'Rating rib-ticklers...',
|
|
86
|
+
];
|
|
87
|
+
return ratingPhrases[Math.floor(Math.random() * ratingPhrases.length)]!;
|
|
88
|
+
}
|
|
54
89
|
|
|
55
|
-
|
|
56
|
-
|
|
90
|
+
const loader = fromCallback(({ input }: { input: string }) => {
|
|
91
|
+
const anim = loadingAnimation(input);
|
|
57
92
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
jokes: string[];
|
|
63
|
-
desire: string | null;
|
|
64
|
-
lastRating: string | null;
|
|
65
|
-
},
|
|
66
|
-
input: {} as { topic: string },
|
|
67
|
-
},
|
|
68
|
-
actors: {
|
|
69
|
-
getJokeCompletion,
|
|
70
|
-
getTopic,
|
|
71
|
-
rateJoke,
|
|
72
|
-
decide: fromEventChoice(openai, (desire: string) => ({
|
|
73
|
-
model: 'gpt-4-1106-preview',
|
|
74
|
-
messages: [
|
|
75
|
-
{
|
|
76
|
-
role: 'user',
|
|
77
|
-
content: `Execute the function that best satisfies this desire:
|
|
93
|
+
return () => {
|
|
94
|
+
anim.stop();
|
|
95
|
+
};
|
|
96
|
+
});
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
const jokeMachine = setup({
|
|
99
|
+
types: {
|
|
100
|
+
context: {} as {
|
|
101
|
+
topic: string;
|
|
102
|
+
jokes: string[];
|
|
103
|
+
desire: string | null;
|
|
104
|
+
lastRating: string | null;
|
|
84
105
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
input: {} as { topic: string },
|
|
107
|
+
},
|
|
108
|
+
actors: {
|
|
109
|
+
getJokeCompletion,
|
|
110
|
+
getTopic,
|
|
111
|
+
rateJoke,
|
|
112
|
+
decide,
|
|
113
|
+
loader,
|
|
114
|
+
},
|
|
115
|
+
}).createMachine({
|
|
116
|
+
context: () => ({
|
|
117
|
+
topic: '',
|
|
118
|
+
jokes: [],
|
|
119
|
+
desire: null,
|
|
120
|
+
lastRating: null,
|
|
121
|
+
loader: null,
|
|
122
|
+
}),
|
|
123
|
+
initial: 'waitingForTopic',
|
|
124
|
+
states: {
|
|
125
|
+
waitingForTopic: {
|
|
126
|
+
invoke: {
|
|
127
|
+
src: 'getTopic',
|
|
128
|
+
onDone: {
|
|
129
|
+
actions: assign({
|
|
130
|
+
topic: ({ event }) => event.output,
|
|
131
|
+
}),
|
|
132
|
+
target: 'tellingJoke',
|
|
103
133
|
},
|
|
104
134
|
},
|
|
105
|
-
|
|
106
|
-
|
|
135
|
+
},
|
|
136
|
+
tellingJoke: {
|
|
137
|
+
invoke: [
|
|
138
|
+
{
|
|
107
139
|
src: 'getJokeCompletion',
|
|
108
|
-
input: ({ context }) =>
|
|
140
|
+
input: ({ context }) => context.topic,
|
|
109
141
|
onDone: {
|
|
110
|
-
actions:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
142
|
+
actions: [
|
|
143
|
+
assign({
|
|
144
|
+
jokes: ({ context, event }) =>
|
|
145
|
+
context.jokes.concat(
|
|
146
|
+
event.output.choices[0]!.message.content!
|
|
147
|
+
),
|
|
148
|
+
}),
|
|
149
|
+
log((x) => x.context.jokes.at(-1)),
|
|
150
|
+
],
|
|
114
151
|
target: 'rateJoke',
|
|
115
152
|
},
|
|
116
153
|
},
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
src: 'rateJoke',
|
|
121
|
-
input: ({ context }) => ({
|
|
122
|
-
joke: context.jokes[context.jokes.length - 1]!,
|
|
123
|
-
}),
|
|
124
|
-
onDone: {
|
|
125
|
-
actions: assign({
|
|
126
|
-
lastRating: ({ event }) => event.output as string,
|
|
127
|
-
}),
|
|
128
|
-
target: 'joked',
|
|
129
|
-
},
|
|
154
|
+
{
|
|
155
|
+
src: 'loader',
|
|
156
|
+
input: getRandomFunnyPhrase,
|
|
130
157
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
rateJoke: {
|
|
161
|
+
invoke: [
|
|
162
|
+
{
|
|
163
|
+
src: 'rateJoke',
|
|
164
|
+
input: ({ context }) => context.jokes[context.jokes.length - 1]!,
|
|
135
165
|
onDone: {
|
|
136
|
-
actions:
|
|
137
|
-
|
|
138
|
-
|
|
166
|
+
actions: [
|
|
167
|
+
assign({
|
|
168
|
+
lastRating: ({ event }) =>
|
|
169
|
+
event.output.choices[0]!.message.content!,
|
|
170
|
+
}),
|
|
171
|
+
log(({ context }) => context.lastRating),
|
|
172
|
+
],
|
|
139
173
|
target: 'decide',
|
|
140
174
|
},
|
|
141
175
|
},
|
|
176
|
+
{
|
|
177
|
+
src: 'loader',
|
|
178
|
+
input: getRandomRatingPhrase,
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
decide: {
|
|
183
|
+
invoke: {
|
|
184
|
+
src: 'decide',
|
|
185
|
+
input: ({ context }) => context.lastRating!,
|
|
186
|
+
onDone: {
|
|
187
|
+
actions: log(({ event }) => event),
|
|
188
|
+
},
|
|
142
189
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
190
|
+
on: {
|
|
191
|
+
askForTopic: {
|
|
192
|
+
target: 'waitingForTopic',
|
|
193
|
+
actions: log("That joke wasn't good enough. Let's try again."),
|
|
194
|
+
description:
|
|
195
|
+
'Ask for a new topic, because the last joke rated 6 or lower',
|
|
147
196
|
},
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
'Ask for a new topic, because the last joke was almost perfect',
|
|
153
|
-
},
|
|
154
|
-
endJokes: {
|
|
155
|
-
target: 'end',
|
|
156
|
-
description: 'End the jokes, since the last joke was not too good',
|
|
157
|
-
},
|
|
197
|
+
endJokes: {
|
|
198
|
+
target: 'end',
|
|
199
|
+
actions: log('That joke was good enough. Goodbye!'),
|
|
200
|
+
description: 'End the jokes, since the last joke rated 7 or higher',
|
|
158
201
|
},
|
|
159
202
|
},
|
|
160
|
-
end: {},
|
|
161
203
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const actor = createActor(chain, {
|
|
165
|
-
input: {
|
|
166
|
-
topic: 'donuts',
|
|
204
|
+
end: {
|
|
205
|
+
type: 'final',
|
|
167
206
|
},
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (st.context.jokes) {
|
|
174
|
-
console.log('Joke: ', st.context.jokes[st.context.jokes.length - 1]);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
actor.start();
|
|
179
|
-
|
|
180
|
-
await waitFor(actor, (snap) => snap.status === 'done', {
|
|
181
|
-
timeout: Infinity,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
207
|
+
},
|
|
208
|
+
exit: () => {
|
|
209
|
+
process.exit();
|
|
210
|
+
},
|
|
211
|
+
});
|
|
184
212
|
|
|
185
|
-
|
|
213
|
+
const actor = createActor(jokeMachine);
|
|
214
|
+
actor.start();
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { assign, setup, assertEvent, createActor
|
|
2
|
-
import { fromChatCompletionStream, fromEventChoice } from '../src/openai';
|
|
1
|
+
import { assign, setup, assertEvent, createActor } from 'xstate';
|
|
3
2
|
import OpenAI from 'openai';
|
|
3
|
+
import { createAgent } from '../src/openai';
|
|
4
4
|
|
|
5
5
|
const openai = new OpenAI({
|
|
6
6
|
apiKey: process.env.OPENAI_API_KEY,
|
|
@@ -8,64 +8,126 @@ const openai = new OpenAI({
|
|
|
8
8
|
|
|
9
9
|
type Player = 'x' | 'o';
|
|
10
10
|
|
|
11
|
+
const agent = createAgent(openai, {
|
|
12
|
+
model: 'gpt-4-1106-preview',
|
|
13
|
+
context: {
|
|
14
|
+
board: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: {
|
|
17
|
+
type: ['null', 'string'],
|
|
18
|
+
enum: [null, 'x', 'o'],
|
|
19
|
+
},
|
|
20
|
+
minItems: 9,
|
|
21
|
+
maxItems: 9,
|
|
22
|
+
description: 'The board of the tic-tac-toe game',
|
|
23
|
+
},
|
|
24
|
+
moves: {
|
|
25
|
+
type: 'number',
|
|
26
|
+
description: 'The number of moves that have been played',
|
|
27
|
+
},
|
|
28
|
+
player: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
enum: ['x', 'o'],
|
|
31
|
+
description: 'The player whose turn it is',
|
|
32
|
+
},
|
|
33
|
+
gameReport: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'The game report',
|
|
36
|
+
},
|
|
37
|
+
events: {
|
|
38
|
+
type: 'array',
|
|
39
|
+
items: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
} as const,
|
|
44
|
+
events: {
|
|
45
|
+
'x.play': {
|
|
46
|
+
properties: {
|
|
47
|
+
index: {
|
|
48
|
+
description: 'The index of the cell to play on',
|
|
49
|
+
type: 'number',
|
|
50
|
+
|
|
51
|
+
minimum: 0,
|
|
52
|
+
maximum: 8,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
'o.play': {
|
|
57
|
+
properties: {
|
|
58
|
+
index: {
|
|
59
|
+
description: 'The index of the cell to play on',
|
|
60
|
+
type: 'number',
|
|
61
|
+
minimum: 0,
|
|
62
|
+
maximum: 8,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
reset: {
|
|
67
|
+
properties: {},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
11
72
|
const initialContext = {
|
|
12
73
|
board: Array(9).fill(null) as Array<Player | null>,
|
|
13
74
|
moves: 0,
|
|
14
75
|
player: 'x' as Player,
|
|
15
|
-
winner: undefined as Player | undefined,
|
|
16
76
|
gameReport: '',
|
|
17
|
-
|
|
77
|
+
events: [],
|
|
78
|
+
} satisfies typeof agent.types.context;
|
|
79
|
+
|
|
80
|
+
const bot = agent.fromEvent(
|
|
81
|
+
({ context }: { context: typeof agent.types.context }) => `
|
|
82
|
+
You are playing a game of tic tac toe. This is the current game state. The 3x3 board is represented by a 9-element array. The first element is the top-left cell, the second element is the top-middle cell, the third element is the top-right cell, the fourth element is the middle-left cell, and so on. The value of each cell is either null, x, or o. The value of null means that the cell is empty. The value of x means that the cell is occupied by an x. The value of o means that the cell is occupied by an o.
|
|
83
|
+
|
|
84
|
+
${JSON.stringify(context, null, 2)}
|
|
85
|
+
|
|
86
|
+
Execute the single best next move to try to win the game. Do not play on an existing cell.`
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const gameReporter = agent.fromChatCompletionStream(
|
|
90
|
+
({
|
|
91
|
+
context,
|
|
92
|
+
}: {
|
|
93
|
+
context: typeof agent.types.context;
|
|
94
|
+
}) => `Here is the game board:
|
|
18
95
|
|
|
19
|
-
export const ticTacToeMachine = setup({
|
|
20
|
-
types: {} as {
|
|
21
|
-
context: typeof initialContext;
|
|
22
|
-
events:
|
|
23
|
-
| { type: 'x.play'; index: number }
|
|
24
|
-
| {
|
|
25
|
-
type: 'o.play';
|
|
26
|
-
index: number;
|
|
27
|
-
}
|
|
28
|
-
| { type: 'RESET' };
|
|
29
|
-
},
|
|
30
|
-
actors: {
|
|
31
|
-
bot: fromEventChoice(
|
|
32
|
-
openai,
|
|
33
|
-
({ context }: { context: typeof initialContext }) => ({
|
|
34
|
-
model: 'gpt-4-1106-preview',
|
|
35
|
-
messages: [
|
|
36
|
-
{
|
|
37
|
-
role: 'system',
|
|
38
|
-
content: `You are playing a game of tic tac toe. This is the current game state. The 3x3 board is represented by a 9-element array. The first element is the top-left cell, the second element is the top-middle cell, the third element is the top-right cell, the fourth element is the middle-left cell, and so on. The value of each cell is either null, x, or o. The value of null means that the cell is empty. The value of x means that the cell is occupied by an x. The value of o means that the cell is occupied by an o.
|
|
39
|
-
|
|
40
|
-
${JSON.stringify(context, null, 2)}`,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
role: 'user',
|
|
44
|
-
content:
|
|
45
|
-
'Execute the single best next move to try to win the game. Do not play on an existing cell.',
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
})
|
|
49
|
-
),
|
|
50
|
-
gameReporter: fromChatCompletionStream(
|
|
51
|
-
openai,
|
|
52
|
-
({ context }: { context: typeof initialContext }) => ({
|
|
53
|
-
model: 'gpt-4-1106-preview',
|
|
54
|
-
messages: [
|
|
55
|
-
{
|
|
56
|
-
role: 'user',
|
|
57
|
-
content: `The tic-tac-toe game is over. The winner is ${
|
|
58
|
-
context.winner ?? 'nobody'
|
|
59
|
-
}. This was the ending board state:
|
|
60
|
-
|
|
61
96
|
${JSON.stringify(context.board, null, 2)}
|
|
62
97
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
98
|
+
And here are the events that led to this game state:
|
|
99
|
+
|
|
100
|
+
${context.events.join('\n')}
|
|
101
|
+
|
|
102
|
+
The winner is ${getWinner(context.board)}.
|
|
103
|
+
|
|
104
|
+
Provide a very short game report analyzing the game.`
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
function getWinner(board: typeof initialContext.board): Player | null {
|
|
108
|
+
const lines = [
|
|
109
|
+
[0, 1, 2],
|
|
110
|
+
[3, 4, 5],
|
|
111
|
+
[6, 7, 8],
|
|
112
|
+
[0, 3, 6],
|
|
113
|
+
[1, 4, 7],
|
|
114
|
+
[2, 5, 8],
|
|
115
|
+
[0, 4, 8],
|
|
116
|
+
[2, 4, 6],
|
|
117
|
+
] as const;
|
|
118
|
+
for (const [a, b, c] of lines) {
|
|
119
|
+
if (board[a] !== null && board[a] === board[b] && board[a] === board[c]) {
|
|
120
|
+
return board[a]!;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const ticTacToeMachine = setup({
|
|
127
|
+
types: agent.types,
|
|
128
|
+
actors: {
|
|
129
|
+
bot,
|
|
130
|
+
gameReporter,
|
|
69
131
|
},
|
|
70
132
|
actions: {
|
|
71
133
|
updateBoard: assign({
|
|
@@ -77,45 +139,22 @@ Provide a game report analyzing the game.`,
|
|
|
77
139
|
},
|
|
78
140
|
moves: ({ context }) => context.moves + 1,
|
|
79
141
|
player: ({ context }) => (context.player === 'x' ? 'o' : 'x'),
|
|
142
|
+
events: ({ context, event }) => {
|
|
143
|
+
return [...context.events, JSON.stringify(event)];
|
|
144
|
+
},
|
|
80
145
|
}),
|
|
81
146
|
resetGame: assign(initialContext),
|
|
82
|
-
|
|
83
|
-
|
|
147
|
+
recordEvent: assign({
|
|
148
|
+
events: ({ context, event }) => {
|
|
149
|
+
return [...context.events, JSON.stringify(event)];
|
|
150
|
+
},
|
|
84
151
|
}),
|
|
85
152
|
},
|
|
86
153
|
guards: {
|
|
87
154
|
checkWin: ({ context }) => {
|
|
88
|
-
const
|
|
89
|
-
const winningLines = [
|
|
90
|
-
[0, 1, 2],
|
|
91
|
-
[3, 4, 5],
|
|
92
|
-
[6, 7, 8],
|
|
93
|
-
[0, 3, 6],
|
|
94
|
-
[1, 4, 7],
|
|
95
|
-
[2, 5, 8],
|
|
96
|
-
[0, 4, 8],
|
|
97
|
-
[2, 4, 6],
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
for (let line of winningLines) {
|
|
101
|
-
const xWon = line.every((index) => {
|
|
102
|
-
return board[index] === 'x';
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
if (xWon) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const oWon = line.every((index) => {
|
|
110
|
-
return board[index] === 'o';
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (oWon) {
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
155
|
+
const winner = getWinner(context.board);
|
|
117
156
|
|
|
118
|
-
return
|
|
157
|
+
return !!winner;
|
|
119
158
|
},
|
|
120
159
|
checkDraw: ({ context }) => {
|
|
121
160
|
return context.moves === 9;
|
|
@@ -145,11 +184,6 @@ Provide a game report analyzing the game.`,
|
|
|
145
184
|
invoke: {
|
|
146
185
|
src: 'bot',
|
|
147
186
|
input: ({ context }) => ({ context }),
|
|
148
|
-
onDone: {
|
|
149
|
-
actions: raise(({ event }) => {
|
|
150
|
-
return event.output![0] as any;
|
|
151
|
-
}),
|
|
152
|
-
},
|
|
153
187
|
},
|
|
154
188
|
on: {
|
|
155
189
|
'x.play': [
|
|
@@ -157,18 +191,8 @@ Provide a game report analyzing the game.`,
|
|
|
157
191
|
target: 'o',
|
|
158
192
|
guard: 'isValidMove',
|
|
159
193
|
actions: 'updateBoard',
|
|
160
|
-
meta: {
|
|
161
|
-
parameters: {
|
|
162
|
-
index: {
|
|
163
|
-
description: 'The index of the cell to play on',
|
|
164
|
-
type: 'number',
|
|
165
|
-
min: 0,
|
|
166
|
-
max: 8,
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
194
|
},
|
|
171
|
-
{ reenter: true },
|
|
195
|
+
{ target: 'x', reenter: true },
|
|
172
196
|
],
|
|
173
197
|
},
|
|
174
198
|
},
|
|
@@ -176,13 +200,6 @@ Provide a game report analyzing the game.`,
|
|
|
176
200
|
invoke: {
|
|
177
201
|
src: 'bot',
|
|
178
202
|
input: ({ context }) => ({ context }),
|
|
179
|
-
onDone: {
|
|
180
|
-
// @ts-ignore
|
|
181
|
-
actions: raise(({ event }) => {
|
|
182
|
-
console.log('output', event.output);
|
|
183
|
-
return event.output![0];
|
|
184
|
-
}),
|
|
185
|
-
},
|
|
186
203
|
},
|
|
187
204
|
on: {
|
|
188
205
|
'o.play': [
|
|
@@ -190,18 +207,8 @@ Provide a game report analyzing the game.`,
|
|
|
190
207
|
target: 'x',
|
|
191
208
|
guard: 'isValidMove',
|
|
192
209
|
actions: 'updateBoard',
|
|
193
|
-
meta: {
|
|
194
|
-
parameters: {
|
|
195
|
-
index: {
|
|
196
|
-
description: 'The index of the cell to play on',
|
|
197
|
-
type: 'number',
|
|
198
|
-
min: 0,
|
|
199
|
-
max: 8,
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
210
|
},
|
|
204
|
-
{ reenter: true },
|
|
211
|
+
{ target: 'o', reenter: true },
|
|
205
212
|
],
|
|
206
213
|
},
|
|
207
214
|
},
|
|
@@ -226,14 +233,13 @@ Provide a game report analyzing the game.`,
|
|
|
226
233
|
states: {
|
|
227
234
|
winner: {
|
|
228
235
|
tags: 'winner',
|
|
229
|
-
entry: 'setWinner',
|
|
230
236
|
},
|
|
231
237
|
draw: {
|
|
232
238
|
tags: 'draw',
|
|
233
239
|
},
|
|
234
240
|
},
|
|
235
241
|
on: {
|
|
236
|
-
|
|
242
|
+
reset: {
|
|
237
243
|
target: 'playing',
|
|
238
244
|
actions: 'resetGame',
|
|
239
245
|
},
|
|
@@ -242,7 +248,13 @@ Provide a game report analyzing the game.`,
|
|
|
242
248
|
},
|
|
243
249
|
});
|
|
244
250
|
|
|
245
|
-
const actor = createActor(ticTacToeMachine
|
|
251
|
+
const actor = createActor(ticTacToeMachine, {
|
|
252
|
+
inspect: (e) => {
|
|
253
|
+
if (e.type === '@xstate.event') {
|
|
254
|
+
console.log(e.event);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
});
|
|
246
258
|
actor.subscribe((s) => {
|
|
247
259
|
console.log(s.value, s.context);
|
|
248
260
|
});
|