@game_ryo/lsji 0.1.0
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/.github/workflows/node.yml +46 -0
- package/AGENTS.md +143 -0
- package/LICENSE +185 -0
- package/PROPOSAL.md +18 -0
- package/README.md +102 -0
- package/bin/lsji.js +8 -0
- package/docs/README.md +43 -0
- package/docs/blog/2019-05-28-first-blog-post.mdx +12 -0
- package/docs/blog/2019-05-29-long-blog-post.mdx +44 -0
- package/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs/blog/2021-08-26-welcome/index.mdx +29 -0
- package/docs/blog/authors.yml +25 -0
- package/docs/blog/tags.yml +19 -0
- package/docs/docs/api/agent.md +151 -0
- package/docs/docs/api/env.md +133 -0
- package/docs/docs/api/environments.md +102 -0
- package/docs/docs/api/qlearning.md +138 -0
- package/docs/docs/api/storage.md +168 -0
- package/docs/docs/architecture.md +155 -0
- package/docs/docs/cli.md +210 -0
- package/docs/docs/contributing.md +162 -0
- package/docs/docs/core-concepts.md +152 -0
- package/docs/docs/examples/advanced-training.md +244 -0
- package/docs/docs/examples/custom-environment.md +198 -0
- package/docs/docs/examples/custom-storage.md +251 -0
- package/docs/docs/getting-started.md +91 -0
- package/docs/docusaurus.config.ts +149 -0
- package/docs/package-lock.json +19522 -0
- package/docs/package.json +49 -0
- package/docs/sidebars.ts +33 -0
- package/docs/src/components/HomepageFeatures/index.tsx +71 -0
- package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- package/docs/src/css/custom.css +79 -0
- package/docs/src/pages/index.module.css +23 -0
- package/docs/src/pages/index.tsx +44 -0
- package/docs/src/pages/markdown-page.mdx +7 -0
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/docusaurus-social-card.jpg +0 -0
- package/docs/static/img/docusaurus.png +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo.png +0 -0
- package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs/tsconfig.json +12 -0
- package/legacy/worker.js +166 -0
- package/legacy/wrangler.toml +11 -0
- package/package.json +26 -0
- package/src/cli.js +232 -0
- package/src/core/agent.js +239 -0
- package/src/core/env.js +86 -0
- package/src/core/qlearning.js +197 -0
- package/src/envs/rps.js +168 -0
- package/src/index.js +22 -0
- package/src/storage/better-sqlite.js +133 -0
- package/src/storage/index.js +146 -0
- package/src/storage/memory.js +98 -0
- package/src/storage/sqlite.js +123 -0
- package/test/core/qlearning.test.js +150 -0
- package/test/storage/memory.test.js +81 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
yangshun:
|
|
2
|
+
name: Yangshun Tay
|
|
3
|
+
title: Ex-Meta Staff Engineer, Co-founder GreatFrontEnd
|
|
4
|
+
url: https://linkedin.com/in/yangshun
|
|
5
|
+
image_url: https://github.com/yangshun.png
|
|
6
|
+
page: true
|
|
7
|
+
socials:
|
|
8
|
+
x: yangshunz
|
|
9
|
+
linkedin: yangshun
|
|
10
|
+
github: yangshun
|
|
11
|
+
newsletter: https://www.greatfrontend.com
|
|
12
|
+
|
|
13
|
+
slorber:
|
|
14
|
+
name: Sébastien Lorber
|
|
15
|
+
title: Docusaurus maintainer
|
|
16
|
+
url: https://sebastienlorber.com
|
|
17
|
+
image_url: https://github.com/slorber.png
|
|
18
|
+
page:
|
|
19
|
+
# customize the url of the author page at /blog/authors/<permalink>
|
|
20
|
+
permalink: '/all-sebastien-lorber-articles'
|
|
21
|
+
socials:
|
|
22
|
+
x: sebastienlorber
|
|
23
|
+
linkedin: sebastienlorber
|
|
24
|
+
github: slorber
|
|
25
|
+
newsletter: https://thisweekinreact.com
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
facebook:
|
|
2
|
+
label: Facebook
|
|
3
|
+
permalink: /facebook
|
|
4
|
+
description: Facebook tag description
|
|
5
|
+
|
|
6
|
+
hello:
|
|
7
|
+
label: Hello
|
|
8
|
+
permalink: /hello
|
|
9
|
+
description: Hello tag description
|
|
10
|
+
|
|
11
|
+
docusaurus:
|
|
12
|
+
label: Docusaurus
|
|
13
|
+
permalink: /docusaurus
|
|
14
|
+
description: Docusaurus tag description
|
|
15
|
+
|
|
16
|
+
hola:
|
|
17
|
+
label: Hola
|
|
18
|
+
permalink: /hola
|
|
19
|
+
description: Hola tag description
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Agent API
|
|
3
|
+
description: High-level agent orchestration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent API
|
|
7
|
+
|
|
8
|
+
The `Agent` class provides high-level orchestration of Q-Learning, Storage, and Environment.
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { Agent } from 'lsji';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Constructor
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
const agent = new Agent({
|
|
20
|
+
qlearning: QLearning, // Required: Q-Learning engine
|
|
21
|
+
storage: Storage, // Required: Storage backend
|
|
22
|
+
env: Env // Optional: Environment for train/play
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Methods
|
|
27
|
+
|
|
28
|
+
### `start()`
|
|
29
|
+
Enable the system for training and play.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
await agent.start();
|
|
33
|
+
// Returns: { status: 'success', message: 'System STARTED' }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `stop()`
|
|
37
|
+
Disable the system (pauses training and play).
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
await agent.stop();
|
|
41
|
+
// Returns: { status: 'success', message: 'System STOPPED' }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `status()`
|
|
45
|
+
Get current system status and statistics.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const status = await agent.status();
|
|
49
|
+
// Returns:
|
|
50
|
+
{
|
|
51
|
+
status: 'running' | 'stopped',
|
|
52
|
+
todayTotal: number, // Battles today
|
|
53
|
+
limit: 90000, // Daily limit
|
|
54
|
+
performance: [ // Per-mode statistics
|
|
55
|
+
{ mode: 'train', total: 100, win_rate: 65.5 },
|
|
56
|
+
{ mode: 'test', total: 50, win_rate: 72.0 }
|
|
57
|
+
],
|
|
58
|
+
aiBrain: [ // Full Q-table
|
|
59
|
+
{ state: '0', action: 0, q_value: 0.45 },
|
|
60
|
+
{ state: '0', action: 1, q_value: 0.12 },
|
|
61
|
+
...
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `train(options)`
|
|
67
|
+
Train the agent.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const result = await agent.train({
|
|
71
|
+
episodes: 200, // Number of episodes (default: 200)
|
|
72
|
+
actionSelector: (episode, lastAction) => number, // Custom pattern (optional)
|
|
73
|
+
batchSize: 200 // DB batch size (default: 200)
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Returns:
|
|
77
|
+
{
|
|
78
|
+
episodes: 200,
|
|
79
|
+
wins: 85,
|
|
80
|
+
losses: 62,
|
|
81
|
+
draws: 53
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Built-in Training Patterns:**
|
|
86
|
+
```typescript
|
|
87
|
+
import { TrainingPattern, getTrainingAction } from 'lsji';
|
|
88
|
+
|
|
89
|
+
// Pattern 0: Random (default)
|
|
90
|
+
await agent.train({ episodes: 500 });
|
|
91
|
+
|
|
92
|
+
// Pattern 1: Always Rock
|
|
93
|
+
await agent.train({
|
|
94
|
+
episodes: 500,
|
|
95
|
+
actionSelector: (ep, last) => getTrainingAction(TrainingPattern.ALWAYS_ROCK, ep, last)
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Pattern 2: Counter
|
|
99
|
+
await agent.train({
|
|
100
|
+
episodes: 500,
|
|
101
|
+
actionSelector: (ep, last) => getTrainingAction(TrainingPattern.COUNTER, ep, last)
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Pattern 3: Sequential
|
|
105
|
+
await agent.train({
|
|
106
|
+
episodes: 500,
|
|
107
|
+
actionSelector: (ep, last) => getTrainingAction(TrainingPattern.SEQUENTIAL, ep, last)
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `play(options)`
|
|
112
|
+
Play a single step against the agent.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const result = await agent.play({
|
|
116
|
+
userAction: 0 // Optional: user action for envs that need it
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Returns:
|
|
120
|
+
{
|
|
121
|
+
action: 1, // Agent's chosen action
|
|
122
|
+
reward: 1, // Reward received
|
|
123
|
+
done: false, // Episode ended
|
|
124
|
+
info: { opponentAction: 0 } // Environment-specific info
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `setEnvironment(env)`
|
|
129
|
+
Inject or change the environment at runtime.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
agent.setEnvironment(new MyCustomEnv());
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Example
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { Agent, QLearning, createStorage, RockPaperScissorsEnv } from 'lsji';
|
|
139
|
+
|
|
140
|
+
const storage = await createStorage('sqlite', { path: './agent.db' });
|
|
141
|
+
const qlearning = new QLearning({ alpha: 0.1, gamma: 0.9, epsilon: 0.1, storage });
|
|
142
|
+
const env = new RockPaperScissorsEnv({ opponent: 'random' });
|
|
143
|
+
|
|
144
|
+
const agent = new Agent({ qlearning, storage, env });
|
|
145
|
+
|
|
146
|
+
await agent.train({ episodes: 1000 });
|
|
147
|
+
const result = await agent.play(0); // Play Rock
|
|
148
|
+
console.log(`AI played: ${result.action}, Result: ${result.reward > 0 ? 'WIN' : result.reward < 0 ? 'LOSE' : 'DRAW'}`);
|
|
149
|
+
|
|
150
|
+
await storage.close();
|
|
151
|
+
```
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Env Interface
|
|
3
|
+
description: Base environment interface for RL problems
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Env Interface
|
|
7
|
+
|
|
8
|
+
All reinforcement learning environments must extend the abstract `Env` class.
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { Env } from 'lsji';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Abstract Methods
|
|
17
|
+
|
|
18
|
+
### `getState()`
|
|
19
|
+
Return current state as a string key.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
getState(): string;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### `step(action)`
|
|
26
|
+
Execute an action and return the result.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
async step(action: number): Promise<StepResult>;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**StepResult:**
|
|
33
|
+
```typescript
|
|
34
|
+
interface StepResult {
|
|
35
|
+
state: string; // New state
|
|
36
|
+
reward: number; // Reward for this step
|
|
37
|
+
done: boolean; // Episode ended
|
|
38
|
+
info?: object; // Additional info
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `actionSize()`
|
|
43
|
+
Return number of possible actions.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
actionSize(): number;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `reset()`
|
|
50
|
+
Reset environment to initial state.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
async reset(): Promise<string>; // Returns initial state
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Optional Methods
|
|
57
|
+
|
|
58
|
+
### `render()`
|
|
59
|
+
Human-readable representation for debugging.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
render(): string;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## StateEncoder Utility
|
|
66
|
+
|
|
67
|
+
Helper for encoding/decoding complex states:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { StateEncoder } from 'lsji';
|
|
71
|
+
|
|
72
|
+
// Encode object to string
|
|
73
|
+
const key = StateEncoder.encode({ position: 5, inventory: ['sword'] });
|
|
74
|
+
// Returns: '{"position":5,"inventory":["sword"]}'
|
|
75
|
+
|
|
76
|
+
// Decode string to object
|
|
77
|
+
const state = StateEncoder.decode(key);
|
|
78
|
+
// Returns: { position: 5, inventory: ['sword'] }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Creating a Custom Environment
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { Env } from 'lsji';
|
|
85
|
+
|
|
86
|
+
class GridWorldEnv extends Env {
|
|
87
|
+
constructor() {
|
|
88
|
+
super();
|
|
89
|
+
this.position = 0;
|
|
90
|
+
this.gridSize = 10;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getState() {
|
|
94
|
+
return String(this.position);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async step(action) {
|
|
98
|
+
// Actions: 0=left, 1=right
|
|
99
|
+
if (action === 0) this.position = Math.max(0, this.position - 1);
|
|
100
|
+
if (action === 1) this.position = Math.min(this.gridSize - 1, this.position + 1);
|
|
101
|
+
|
|
102
|
+
const done = this.position === this.gridSize - 1;
|
|
103
|
+
const reward = done ? 1 : -0.01;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
state: String(this.position),
|
|
107
|
+
reward,
|
|
108
|
+
done,
|
|
109
|
+
info: { position: this.position }
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
actionSize() {
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async reset() {
|
|
118
|
+
this.position = 0;
|
|
119
|
+
return '0';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
render() {
|
|
123
|
+
return `GridWorld: position ${this.position}/${this.gridSize - 1}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Best Practices
|
|
129
|
+
|
|
130
|
+
1. **State as string** — Use simple string keys for Q-table indexing
|
|
131
|
+
2. **Deterministic rewards** — Same state-action should give consistent rewards
|
|
132
|
+
3. **Action space** — Keep action space small for tabular Q-learning
|
|
133
|
+
4. **Reset** — Always implement proper reset for episode boundaries
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Built-in Environments
|
|
3
|
+
description: Pre-built environments for quick start
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Built-in Environments
|
|
7
|
+
|
|
8
|
+
LSJI includes a Rock-Paper-Scissors environment for demonstration and testing.
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import {
|
|
14
|
+
RockPaperScissorsEnv,
|
|
15
|
+
TrainingPattern,
|
|
16
|
+
getTrainingAction
|
|
17
|
+
} from 'lsji';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## RockPaperScissorsEnv
|
|
21
|
+
|
|
22
|
+
Classic Rock-Paper-Scissors game environment.
|
|
23
|
+
|
|
24
|
+
### Constructor
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
const env = new RockPaperScissorsEnv({
|
|
28
|
+
opponent: 'random' // 'random' | 'always_rock' | 'counter' | 'sequential'
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Opponent Strategies
|
|
33
|
+
|
|
34
|
+
| Strategy | Description |
|
|
35
|
+
|----------|-------------|
|
|
36
|
+
| `'random'` | Uniform random actions (default) |
|
|
37
|
+
| `'always_rock'` | Always plays Rock (0) |
|
|
38
|
+
| `'counter'` | Plays counter to agent's previous action |
|
|
39
|
+
| `'sequential'` | Cycles through Rock→Scissors→Paper |
|
|
40
|
+
|
|
41
|
+
### Methods
|
|
42
|
+
|
|
43
|
+
All standard `Env` methods plus:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Static helpers
|
|
47
|
+
RockPaperScissorsEnv.getHandName(0); // 'Rock'
|
|
48
|
+
RockPaperScissorsEnv.getHandName(1); // 'Scissors'
|
|
49
|
+
RockPaperScissorsEnv.getHandName(2); // 'Paper'
|
|
50
|
+
|
|
51
|
+
const { judge, reward, outcome } = RockPaperScissorsEnv.calculateOutcome(0, 2);
|
|
52
|
+
// judge: 2, reward: 1, outcome: 'WIN'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Training Patterns
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { TrainingPattern, getTrainingAction } from 'lsji';
|
|
59
|
+
|
|
60
|
+
// Pattern IDs
|
|
61
|
+
TrainingPattern.RANDOM; // 0
|
|
62
|
+
TrainingPattern.ALWAYS_ROCK; // 1
|
|
63
|
+
TrainingPattern.COUNTER; // 2
|
|
64
|
+
TrainingPattern.SEQUENTIAL; // 3
|
|
65
|
+
|
|
66
|
+
// Get action for pattern
|
|
67
|
+
const action = getTrainingAction(TrainingPattern.COUNTER, episode, lastAction);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Example
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import {
|
|
74
|
+
Agent, QLearning, createStorage,
|
|
75
|
+
RockPaperScissorsEnv, TrainingPattern, getTrainingAction
|
|
76
|
+
} from 'lsji';
|
|
77
|
+
|
|
78
|
+
const storage = await createStorage('sqlite', { path: './rps.db' });
|
|
79
|
+
const qlearning = new QLearning({ alpha: 0.1, gamma: 0.9, epsilon: 0.1, storage });
|
|
80
|
+
|
|
81
|
+
// Train against counter opponent
|
|
82
|
+
const env = new RockPaperScissorsEnv({ opponent: 'counter' });
|
|
83
|
+
const agent = new Agent({ qlearning, storage, env });
|
|
84
|
+
|
|
85
|
+
await agent.train({
|
|
86
|
+
episodes: 1000,
|
|
87
|
+
actionSelector: (ep, last) => getTrainingAction(TrainingPattern.RANDOM, ep, last)
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Play against random opponent
|
|
91
|
+
const playEnv = new RockPaperScissorsEnv({ opponent: 'random' });
|
|
92
|
+
agent.setEnvironment(playEnv);
|
|
93
|
+
|
|
94
|
+
const result = await agent.play(0); // You play Rock
|
|
95
|
+
console.log(`AI: ${RockPaperScissorsEnv.getHandName(result.action)} | ${result.reward > 0 ? 'WIN' : 'LOSE'}`);
|
|
96
|
+
|
|
97
|
+
await storage.close();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Creating Custom Environments
|
|
101
|
+
|
|
102
|
+
See [Custom Environment Example](/docs/examples/custom-environment) for a complete guide.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: QLearning API
|
|
3
|
+
description: Tabular Q-Learning engine with TD updates
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# QLearning API
|
|
7
|
+
|
|
8
|
+
The `QLearning` class implements tabular Q-Learning with epsilon-greedy exploration.
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { QLearning } from 'lsji';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Constructor
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
const qlearning = new QLearning({
|
|
20
|
+
alpha: 0.1, // Learning rate [0, 1] (default: 0.1)
|
|
21
|
+
gamma: 0.9, // Discount factor [0, 1] (default: 0.9)
|
|
22
|
+
epsilon: 0.1, // Exploration rate [0, 1] (default: 0.1)
|
|
23
|
+
storage: Storage // Required: Storage backend
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Values are automatically clamped to [0, 1] range.
|
|
28
|
+
|
|
29
|
+
## Methods
|
|
30
|
+
|
|
31
|
+
### `initialize()`
|
|
32
|
+
Load Q-table from storage. Called automatically by other methods.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
await qlearning.initialize();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `getQValue(state, action)`
|
|
39
|
+
Get Q-value for a state-action pair.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const value = qlearning.getQValue('state1', 0);
|
|
43
|
+
// Returns: number (0 if unseen)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `setQValue(state, action, value)`
|
|
47
|
+
Set Q-value and persist to storage.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
await qlearning.setQValue('state1', 0, 0.5);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `act(state, actionSize)`
|
|
54
|
+
Select action using epsilon-greedy policy.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const action = await qlearning.act('state1', 3);
|
|
58
|
+
// Returns: number (0 to actionSize-1)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Behavior:**
|
|
62
|
+
- With probability `epsilon`: random action
|
|
63
|
+
- With probability `1-epsilon`: best known action (ties broken randomly)
|
|
64
|
+
|
|
65
|
+
### `learn(state, action, reward, nextState, nextActionSize)`
|
|
66
|
+
Full TD update: Q(s,a) ← Q(s,a) + α[r + γ·maxₐ' Q(s',a') - Q(s,a)]
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const newQ = await qlearning.learn('state1', 0, 1, 'state2', 3);
|
|
70
|
+
// Returns: updated Q-value
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `learnSimple(state, action, reward)`
|
|
74
|
+
Simplified update for terminal states: Q(s,a) ← Q(s,a) + α[r - Q(s,a)]
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const newQ = await qlearning.learnSimple('state1', 0, 1);
|
|
78
|
+
// Returns: updated Q-value
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `reset()`
|
|
82
|
+
Clear in-memory Q-table cache.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
await qlearning.reset();
|
|
86
|
+
// Note: Does not clear storage
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `getStateValues(state, actionSize)`
|
|
90
|
+
Get all Q-values for a state.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const values = qlearning.getStateValues('state1', 3);
|
|
94
|
+
// Returns: { 0: 0.5, 1: 0.2, 2: -0.1 }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `getFullQTable()`
|
|
98
|
+
Get entire Q-table for inspection.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const table = await qlearning.getFullQTable();
|
|
102
|
+
// Returns:
|
|
103
|
+
[
|
|
104
|
+
{ state: 'state1', action: 0, q_value: 0.5 },
|
|
105
|
+
{ state: 'state1', action: 1, q_value: 0.2 },
|
|
106
|
+
...
|
|
107
|
+
]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Hyperparameters
|
|
111
|
+
|
|
112
|
+
| Parameter | Default | Range | Description |
|
|
113
|
+
|-----------|---------|-------|-------------|
|
|
114
|
+
| `alpha` | 0.1 | [0, 1] | Learning rate — how much new info overrides old |
|
|
115
|
+
| `gamma` | 0.9 | [0, 1] | Discount factor — future reward importance |
|
|
116
|
+
| `epsilon` | 0.1 | [0, 1] | Exploration rate — random action probability |
|
|
117
|
+
|
|
118
|
+
## Example
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { QLearning, createStorage } from 'lsji';
|
|
122
|
+
|
|
123
|
+
const storage = await createStorage('memory');
|
|
124
|
+
const ql = new QLearning({ alpha: 0.1, gamma: 0.9, epsilon: 0.1, storage });
|
|
125
|
+
|
|
126
|
+
await ql.initialize();
|
|
127
|
+
|
|
128
|
+
// Train on simple state-action pairs
|
|
129
|
+
await ql.learnSimple('state1', 0, 1); // Win
|
|
130
|
+
await ql.learnSimple('state1', 0, 1); // Win again
|
|
131
|
+
await ql.learnSimple('state1', 1, -1); // Lose
|
|
132
|
+
|
|
133
|
+
// Check learned values
|
|
134
|
+
console.log(ql.getQValue('state1', 0)); // ~0.19
|
|
135
|
+
console.log(ql.getQValue('state1', 1)); // ~-0.1
|
|
136
|
+
|
|
137
|
+
await storage.close();
|
|
138
|
+
```
|