mirror of
https://github.com/wassname/metacar.git
synced 2026-09-10 12:12:06 +08:00
Api Doc + Policy exemple working
This commit is contained in:
+2
-1
@@ -3,4 +3,5 @@ node_modules/*
|
||||
dist/dist-es6/*
|
||||
demo/dist/dist-es6/*
|
||||
demo/node_modules/
|
||||
*package-lock.json*
|
||||
*package-lock.json*
|
||||
docs
|
||||
Vendored
+16
-4
File diff suppressed because one or more lines are too long
@@ -8,18 +8,6 @@
|
||||
|
||||
<div class="canvas" id="canvas"></div>
|
||||
|
||||
<div class="mainButton">
|
||||
<button id="train">Train</button>
|
||||
<button id="play">Play</button>
|
||||
<button id="stop">Stop</button>
|
||||
<button id="reset">Reset</button>
|
||||
<button id="saveAgent">Save</button>
|
||||
<button onclick="document.getElementById('dumpFile').click();">Load</button>
|
||||
<input id="dumpFile" type='file' accept='*/*' style="display: none">
|
||||
<br>
|
||||
<br>
|
||||
<span id="rewardDisplay"></span>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.7.1/pixi.min.js"></script>
|
||||
<script src="/dist/metacar.min.js"></script>
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
|
||||
<div class="canvas" id="canvas"></div>
|
||||
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.7.1/pixi.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.11.6"> </script>
|
||||
<script src="/dist/metacar.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/public/js/utils.js"></script>
|
||||
<script type="text/javascript" src="/public/js/policy_agent.js"></script>
|
||||
<script type="text/javascript" src="/public/js/level1.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -2,27 +2,22 @@
|
||||
let levelUrl = metacar.level.level1;
|
||||
// Create the environement (canvasID, levelUrl)
|
||||
var env = new metacar.env("canvas", levelUrl);
|
||||
|
||||
// Create the Policy agent
|
||||
var agent = new PolicyAgent(env);
|
||||
|
||||
|
||||
env.load().then(() => {
|
||||
// The level is loaded. Add listernes
|
||||
env.addEvent("train", () => {
|
||||
console.log("On train!");
|
||||
});
|
||||
env.addEvent("play", () => {
|
||||
console.log("On play!");
|
||||
});
|
||||
env.addEvent("stop", () => {
|
||||
console.log("On sotp!");
|
||||
});
|
||||
env.addEvent("train", () => agent.train());
|
||||
env.addEvent("play", () => agent.play());
|
||||
env.addEvent("stop", () => agent.stop());
|
||||
env.addEvent("reset_env", () => {
|
||||
console.log("On reset env!");
|
||||
});
|
||||
env.addEvent("reset_agent", () => {
|
||||
console.log("On reset agent");
|
||||
})
|
||||
env.addEvent("save", () => {
|
||||
console.log("On save");
|
||||
});
|
||||
env.addEvent("load", (content) => {
|
||||
console.log("content", content);
|
||||
});
|
||||
env.addEvent("save", () => agent.save());
|
||||
env.addEvent("load", () => agent.restore());
|
||||
});
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
|
||||
class PolicyAgent {
|
||||
/*
|
||||
Policy Agent
|
||||
*/
|
||||
|
||||
constructor(env) {
|
||||
// Number of timestep for one episode
|
||||
this.lidarPts = 5;
|
||||
this.ttLidarPts = 5*5;
|
||||
this.actionsNb = 3;
|
||||
|
||||
this.env = env
|
||||
|
||||
// Build the policy model and the value model
|
||||
this.buildValueFc();
|
||||
this.buildPolicy();
|
||||
}
|
||||
|
||||
buildValueFc(){
|
||||
/*
|
||||
Build the Value function
|
||||
@weights (Object) Weights for the layer
|
||||
*/
|
||||
const LEARNING_RATE = 0.01;
|
||||
const value_optimizer = tf.train.adam(LEARNING_RATE);
|
||||
/*
|
||||
-----------------------
|
||||
** -- Value Model -- **
|
||||
-----------------------
|
||||
*/
|
||||
this.valueModel = tf.sequential();
|
||||
// First Hidden Layer
|
||||
this.valueF1 = tf.layers.dense({
|
||||
inputShape: this.ttLidarPts,
|
||||
units: 9,
|
||||
kernelInitializer: 'randomNormal',
|
||||
activation: 'tanh'
|
||||
});
|
||||
this.valueModel.add(this.valueF1);
|
||||
// Output of the value function
|
||||
this.valueF2 = tf.layers.dense({
|
||||
units: 1,
|
||||
kernelInitializer: "randomNormal",
|
||||
activation: 'linear',
|
||||
inputShape: 9,
|
||||
});
|
||||
this.valueModel.add(this.valueF2);
|
||||
// Compile the value model
|
||||
this.valueModel.compile({
|
||||
optimizer: value_optimizer,
|
||||
loss: 'meanSquaredError',
|
||||
metrics: [],
|
||||
});
|
||||
}
|
||||
|
||||
buildPolicy(){
|
||||
/*
|
||||
Build the policy network
|
||||
@weights (Object) Weights for the layer
|
||||
*/
|
||||
const LEARNING_RATE = 0.01;
|
||||
this.policy_optimizer = tf.train.adam(LEARNING_RATE);
|
||||
/*
|
||||
-----------------------
|
||||
** -- Policy Model -- **
|
||||
-----------------------
|
||||
*/
|
||||
this.policyInput = tf.input({shape: [this.ttLidarPts]});
|
||||
// First layer
|
||||
this.policyF1 = tf.layers.dense({
|
||||
inputShape: this.ttLidarPts,
|
||||
units: 9,
|
||||
kernelInitializer: 'randomNormal',
|
||||
activation: 'tanh'
|
||||
});
|
||||
// Second layer
|
||||
this.policyF2 = tf.layers.dense({
|
||||
units: this.actionsNb,
|
||||
kernelInitializer: 'randomNormal',
|
||||
activation: 'softmax',
|
||||
inputShape: 9,
|
||||
});
|
||||
// Return the softmax of the policy
|
||||
this.policyPredict = (state) => {
|
||||
return tf.tidy(() => {
|
||||
return this.policyF2.apply(this.policyF1.apply(state));
|
||||
});
|
||||
}
|
||||
// Loss function -log(p)*advantages
|
||||
this.policy_loss = (softmaxs, actions, advantages) => {
|
||||
return tf.tidy(() => {
|
||||
const one_hot = tf.oneHot(actions, this.actionsNb);
|
||||
const log_term = tf.log(tf.sum(tf.mul(softmaxs, one_hot.asType("float32")), 1));
|
||||
const loss = tf.mul(tf.scalar(-1), tf.sum(tf.mul(advantages, log_term)) );
|
||||
return loss;
|
||||
});
|
||||
}
|
||||
// Usefull method to get the entropy of the softmax
|
||||
this.policy_entropy = (softmaxs) => {
|
||||
return tf.tidy(() => {
|
||||
return tf.mul(tf.scalar(-1), tf.sum(tf.mul(tf.log(softmaxs), softmaxs)));
|
||||
});
|
||||
}
|
||||
const output = this.policyF2.apply(this.policyF1.apply(this.policyInput))
|
||||
this.policyModel = tf.model({inputs: this.policyInput, outputs: output});
|
||||
}
|
||||
|
||||
trainValueFc(inputs, targets, mini_batch_size){
|
||||
/*
|
||||
Train the value model
|
||||
@inputs (tf.tensor)
|
||||
@targets (tf.tensor)
|
||||
@mini_batch_size (Integer) Size of each mini batch
|
||||
*/
|
||||
return this.valueModel.fit(
|
||||
inputs, targets, {
|
||||
batchSize: mini_batch_size,
|
||||
epochs: 1
|
||||
});
|
||||
}
|
||||
|
||||
trainPolicy(states, actions, advantages, batch_size, mini_batch_size){
|
||||
/*
|
||||
Train the policy model
|
||||
@states (Js array)
|
||||
@actions (Js array)
|
||||
@advantages (Js array)
|
||||
@batch_size (Integer) Size of the batch size
|
||||
@mini_batch_size (Integer) Size of each mini batch size
|
||||
*/
|
||||
for (var b = 0; b < batch_size; b+=mini_batch_size) {
|
||||
|
||||
const tf_states = tf.tensor3d(states.slice(b, b+mini_batch_size)).reshape([-1, this.ttLidarPts]);
|
||||
const tf_actions = tf.tensor1d(actions.slice(b, b+mini_batch_size), "int32");
|
||||
const tf_advantages = tf.tensor1d(advantages.slice(b, b+mini_batch_size));
|
||||
|
||||
this.policy_optimizer.minimize(() => {
|
||||
let softmaxs = this.policyPredict(tf_states);
|
||||
let loss = this.policy_loss(softmaxs, tf_actions, tf_advantages);
|
||||
return loss;
|
||||
});
|
||||
|
||||
tf_states.dispose();
|
||||
tf_actions.dispose();
|
||||
tf_advantages.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultTrainingValues(){
|
||||
this.gamma = 0.95;
|
||||
// Maximum number of step per episode
|
||||
this.nb_step = 800;
|
||||
this.mini_batch_size = 200;
|
||||
this.episodeNb = 250;
|
||||
}
|
||||
|
||||
save(env){
|
||||
/*
|
||||
Save the network
|
||||
*/
|
||||
this.valueModel.save('downloads://value-model-policy-agent');
|
||||
this.policyModel.save('downloads://policy-model-policy-agent');
|
||||
}
|
||||
|
||||
async restore(){
|
||||
/*
|
||||
Restore the weights of the network
|
||||
*/
|
||||
this.valueModel = await tf.loadModel('http://localhost:3000/public/models/policy/value-model-policy-agent.json');
|
||||
this.policyModel = await tf.loadModel("http://localhost:3000/public/models/policy/policy-model-policy-agent.json");
|
||||
}
|
||||
|
||||
play(){
|
||||
tf.tidy(() => {
|
||||
// Get the current state
|
||||
const st = tf.tensor2d(this.env.getState(), [this.lidarPts, this.lidarPts]).reshape([1, this.ttLidarPts]);
|
||||
// Predict the policy
|
||||
const softmax = this.policyModel.predict(st);
|
||||
softmax.print();
|
||||
// Get the action
|
||||
const argmax = softmax.argMax(1);
|
||||
const a = argmax.buffer().values[0];
|
||||
|
||||
argmax.dispose();
|
||||
st.dispose();
|
||||
softmax.dispose();
|
||||
|
||||
this.env.step(a);
|
||||
});
|
||||
}
|
||||
|
||||
stop(){
|
||||
/*
|
||||
We stop the training process (if a training is running)
|
||||
*/
|
||||
this.episodeNb = 0;
|
||||
}
|
||||
|
||||
train(env, it=0){
|
||||
if (it == 0)
|
||||
this.setDefaultTrainingValues();
|
||||
if (it >= this.episodeNb){
|
||||
this.env.render(true); // Render the canvas again
|
||||
return;
|
||||
}
|
||||
console.log("Training it=", it, "/", this.episodeNb);
|
||||
|
||||
(async () => {
|
||||
// Get the current state
|
||||
let reward = 0;
|
||||
const rewards = [];
|
||||
const states = [];
|
||||
const actions = [];
|
||||
|
||||
console.log("---");
|
||||
console.time("Exploring");
|
||||
for (var step = 0; step < this.nb_step; step++) {
|
||||
// Get the current state
|
||||
const array_st = this.env.getState();
|
||||
// Convert the state into a tensor
|
||||
const st = tf.tensor(array_st, [this.lidarPts, this.lidarPts]).reshape([1, this.ttLidarPts]);
|
||||
// Predict the policy
|
||||
const softmax = this.policyPredict(st);
|
||||
// Get the action. Pseudo Random choice. We prefer action with
|
||||
// Higher probability
|
||||
const action = randomChoice(softmax.buffer().values);
|
||||
// Create the next batch
|
||||
rewards.push(reward);
|
||||
states.push(array_st);
|
||||
actions.push(action);
|
||||
// Stop the episode if the car go out of the road or crash an
|
||||
// other car
|
||||
if (reward == -10){
|
||||
console.log("Early stop Episode");
|
||||
softmax.dispose();
|
||||
st.dispose();
|
||||
break;
|
||||
}
|
||||
softmax.dispose();
|
||||
st.dispose();
|
||||
// Step in the environement with this action
|
||||
reward = this.env.step(action);
|
||||
}
|
||||
// Size of the next batches && minibatches
|
||||
const batch_size = rewards.length;
|
||||
const mini_batch_size = Math.min(this.mini_batch_size, batch_size);
|
||||
|
||||
console.log("Episode duration:", step);
|
||||
console.log("Mean rewards:", mean(rewards));
|
||||
console.timeEnd("Exploring");
|
||||
|
||||
let advantages = [];
|
||||
let returns = [];
|
||||
let G = 0.0;
|
||||
// Compute the total reward for each state
|
||||
for (let t = batch_size - 1; t >= 0; t--){
|
||||
G = rewards[t] + (this.gamma*G);
|
||||
returns.push(G);
|
||||
// Predict the value function for this state
|
||||
const st = tf.tensor2d(states[t], [this.lidarPts, this.lidarPts]).reshape([1, this.ttLidarPts]);
|
||||
const Vs = this.valueModel.predict(st);
|
||||
// Advantage
|
||||
advantages.push(G - Vs.buffer().values[0]);
|
||||
st.dispose();
|
||||
Vs.dispose();
|
||||
}
|
||||
returns = returns.reverse();
|
||||
advantages = advantages.reverse();
|
||||
|
||||
// Train the value model
|
||||
const tf_batch_states = tf.tensor3d(states).reshape([batch_size, this.ttLidarPts]);
|
||||
const tf_value_target = tf.tensor1d(returns);
|
||||
await this.trainValueFc(tf_batch_states, tf_value_target, mini_batch_size);
|
||||
tf_batch_states.dispose();
|
||||
tf_value_target.dispose();
|
||||
// Train the policy model
|
||||
this.trainPolicy(states, actions, advantages, batch_size, mini_batch_size);
|
||||
// Set the agent on a new free road
|
||||
this.env.randomRoadPosition();
|
||||
//env.reset();
|
||||
// Go to the next episode
|
||||
this.train(this.env, it+1);
|
||||
})();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
function randomChoice(p) {
|
||||
let rnd = p.reduce( (a, b) => a + b ) * Math.random();
|
||||
return p.findIndex( a => (rnd -= a) < 0 );
|
||||
}
|
||||
|
||||
function mean(array){
|
||||
if (array.length == 0)
|
||||
return null;
|
||||
var sum = array.reduce(function(a, b) { return a + b; });
|
||||
var avg = sum / array.length;
|
||||
return avg;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"modelTopology":{"class_name":"Model","config":{"name":"model1","layers":[{"name":"input1","class_name":"InputLayer","config":{"batch_input_shape":[null,25],"dtype":"float32","sparse":false,"name":"input1"},"inbound_nodes":[]},{"name":"dense_Dense3","class_name":"Dense","config":{"units":9,"activation":"tanh","use_bias":true,"kernel_initializer":{"class_name":"RandomNormal","config":{"mean":0,"stddev":0.05,"seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense3","trainable":true,"batch_input_shape":[null,25],"dtype":"float32"},"inbound_nodes":[[["input1",0,0,{}]]]},{"name":"dense_Dense4","class_name":"Dense","config":{"units":3,"activation":"softmax","use_bias":true,"kernel_initializer":{"class_name":"RandomNormal","config":{"mean":0,"stddev":0.05,"seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense4","trainable":true,"batch_input_shape":[null,9],"dtype":"float32"},"inbound_nodes":[[["dense_Dense3",0,0,{}]]]}],"input_layers":[["input1",0,0]],"output_layers":[["dense_Dense4",0,0]]},"keras_version":"tfjs-layers 0.6.6","backend":"tensor_flow.js"},"weightsManifest":[{"paths":["./policy-model-policy-agent.weights.bin"],"weights":[{"name":"dense_Dense3/kernel","shape":[25,9],"dtype":"float32"},{"name":"dense_Dense3/bias","shape":[9],"dtype":"float32"},{"name":"dense_Dense4/kernel","shape":[9,3],"dtype":"float32"},{"name":"dense_Dense4/bias","shape":[3],"dtype":"float32"}]}]}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"modelTopology":{"class_name":"Model","config":{"name":"model1","layers":[{"name":"input1","class_name":"InputLayer","config":{"batch_input_shape":[null,25],"dtype":"float32","sparse":false,"name":"input1"},"inbound_nodes":[]},{"name":"dense_Dense3","class_name":"Dense","config":{"units":9,"activation":"tanh","use_bias":true,"kernel_initializer":{"class_name":"RandomNormal","config":{"mean":0,"stddev":0.05,"seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense3","trainable":true,"batch_input_shape":[null,25],"dtype":"float32"},"inbound_nodes":[[["input1",0,0,{}]]]},{"name":"dense_Dense4","class_name":"Dense","config":{"units":3,"activation":"softmax","use_bias":true,"kernel_initializer":{"class_name":"RandomNormal","config":{"mean":0,"stddev":0.05,"seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense4","trainable":true,"batch_input_shape":[null,9],"dtype":"float32"},"inbound_nodes":[[["dense_Dense3",0,0,{}]]]}],"input_layers":[["input1",0,0]],"output_layers":[["dense_Dense4",0,0]]},"keras_version":"tfjs-layers 0.6.6","backend":"tensor_flow.js"},"weightsManifest":[{"paths":["./policy-model-policy-agent.weights.bin"],"weights":[{"name":"dense_Dense3/kernel","shape":[25,9],"dtype":"float32"},{"name":"dense_Dense3/bias","shape":[9],"dtype":"float32"},{"name":"dense_Dense4/kernel","shape":[9,3],"dtype":"float32"},{"name":"dense_Dense4/bias","shape":[3],"dtype":"float32"}]}]}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"modelTopology":{"class_name":"Sequential","config":[{"class_name":"Dense","config":{"units":9,"activation":"tanh","use_bias":true,"kernel_initializer":{"class_name":"RandomNormal","config":{"mean":0,"stddev":0.05,"seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense1","trainable":true,"batch_input_shape":[null,25],"dtype":"float32"}},{"class_name":"Dense","config":{"units":1,"activation":"linear","use_bias":true,"kernel_initializer":{"class_name":"RandomNormal","config":{"mean":0,"stddev":0.05,"seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense2","trainable":true,"batch_input_shape":[null,9],"dtype":"float32"}}],"keras_version":"tfjs-layers 0.6.6","backend":"tensor_flow.js"},"weightsManifest":[{"paths":["./value-model-policy-agent.weights.bin"],"weights":[{"name":"dense_Dense1/kernel","shape":[25,9],"dtype":"float32"},{"name":"dense_Dense1/bias","shape":[9],"dtype":"float32"},{"name":"dense_Dense2/kernel","shape":[9,1],"dtype":"float32"},{"name":"dense_Dense2/bias","shape":[1],"dtype":"float32"}]}]}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"modelTopology":{"class_name":"Sequential","config":[{"class_name":"Dense","config":{"units":9,"activation":"tanh","use_bias":true,"kernel_initializer":{"class_name":"RandomNormal","config":{"mean":0,"stddev":0.05,"seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense1","trainable":true,"batch_input_shape":[null,25],"dtype":"float32"}},{"class_name":"Dense","config":{"units":1,"activation":"linear","use_bias":true,"kernel_initializer":{"class_name":"RandomNormal","config":{"mean":0,"stddev":0.05,"seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense2","trainable":true,"batch_input_shape":[null,9],"dtype":"float32"}}],"keras_version":"tfjs-layers 0.6.6","backend":"tensor_flow.js"},"weightsManifest":[{"paths":["./value-model-policy-agent.weights.bin"],"weights":[{"name":"dense_Dense1/kernel","shape":[25,9],"dtype":"float32"},{"name":"dense_Dense1/bias","shape":[9],"dtype":"float32"},{"name":"dense_Dense2/kernel","shape":[9,1],"dtype":"float32"},{"name":"dense_Dense2/bias","shape":[1],"dtype":"float32"}]}]}
|
||||
Binary file not shown.
Vendored
+16
-4
File diff suppressed because one or more lines are too long
+3
-1
@@ -6,6 +6,7 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"ts-loader": "^4.3.1",
|
||||
"typedoc": "^0.11.1",
|
||||
"typescript": "^2.9.1",
|
||||
"webpack": "^4.10.2",
|
||||
"webpack-cli": "^3.0.2"
|
||||
@@ -18,6 +19,7 @@
|
||||
"scripts": {
|
||||
"build": "./node_modules/.bin/webpack-cli --mode production",
|
||||
"build-dev": "./node_modules/.bin/webpack-cli --mode development",
|
||||
"watch": "./node_modules/.bin/webpack-cli --watch --mode development"
|
||||
"watch": "./node_modules/.bin/webpack-cli --watch --mode development",
|
||||
"docs": "./node_modules/.bin/typedoc --options typedoc.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "./global";
|
||||
|
||||
import * as U from "./utils";
|
||||
import {actionSpaceDescription} from "./motion_engine";
|
||||
|
||||
export class BasicMotionEngine extends MotionEngine {
|
||||
/*
|
||||
@@ -123,12 +124,15 @@ export class BasicMotionEngine extends MotionEngine {
|
||||
return {agent_col, on_road};
|
||||
}
|
||||
|
||||
actionSpace(){
|
||||
actionSpace(): actionSpaceDescription{
|
||||
/*
|
||||
Return an array with all possibles actions
|
||||
Ex: [0, 1, 2]
|
||||
Return a description of the action space.
|
||||
*/
|
||||
return Array.apply(null, {length: this.actions.length}).map(Number.call, Number);
|
||||
return {
|
||||
type: "Discrete",
|
||||
size: 1,
|
||||
range: [0, 2]
|
||||
}
|
||||
}
|
||||
|
||||
step(delta: number){
|
||||
|
||||
+1
-1
@@ -173,7 +173,7 @@ export class Car {
|
||||
}
|
||||
}
|
||||
|
||||
getState(){
|
||||
getState(): number[][]{
|
||||
/*
|
||||
Get the current state of the car
|
||||
The state is the current value of each point
|
||||
|
||||
+14
-1
@@ -1,7 +1,20 @@
|
||||
import {fullCity} from "./embedded/level/full_city";
|
||||
import {level1} from "./embedded/level/level_1";
|
||||
|
||||
export const embeddedUrl: any = {
|
||||
/**
|
||||
* Object used to enumerate each
|
||||
* level embedded into the library.
|
||||
*
|
||||
* @fullCity: A level to show the current capabilities of the environement.
|
||||
* @level1: A level with one agent, two cars, and simple control (top, down, left, right).
|
||||
*
|
||||
*/
|
||||
export interface embeddedUrlI {
|
||||
fullCity: string
|
||||
level1: string
|
||||
};
|
||||
|
||||
export const embeddedUrl: embeddedUrlI = {
|
||||
fullCity: "embedded://level/fullCity",
|
||||
level1: "embedded://level/level1"
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
@Level class
|
||||
This is the core of game, the class is used create all the differents
|
||||
services in the game (assets, map, agents...).
|
||||
*/
|
||||
@@ -122,7 +121,7 @@ export class Level {
|
||||
TODO: Let's the reward define in the agent class
|
||||
*/
|
||||
let reward = -0.1;
|
||||
if (action == 0 || this.agent.core.vx == 1)
|
||||
if (action == 0 || this.agent.core.v == 1)
|
||||
reward += 0.5;
|
||||
if (agent_col.length > 0){
|
||||
reward = -10;
|
||||
@@ -193,7 +192,7 @@ export class Level {
|
||||
getRoads(){
|
||||
return this.roads;
|
||||
}
|
||||
|
||||
|
||||
findCarById(id: number){
|
||||
/*
|
||||
Find car by @id
|
||||
|
||||
+40
-127
@@ -3,22 +3,23 @@
|
||||
*/
|
||||
|
||||
import {Level, LevelInfo} from "./level";
|
||||
import {actionSpaceDescription} from "./motion_engine";
|
||||
import {UIEvent} from "./ui_event";
|
||||
import * as U from "./utils";
|
||||
|
||||
export interface eventLoadOptions {
|
||||
computer: boolean;
|
||||
local: boolean;
|
||||
}
|
||||
|
||||
export class MetaCar {
|
||||
|
||||
private isPlaying: boolean;
|
||||
private agent: any;
|
||||
private level: Level;
|
||||
private canvasId: string;
|
||||
private levelUrl: string;
|
||||
private eventList: string[] = ["train", "play", "stop", "reset_env", "reset_agent", "save", "load"]
|
||||
private eventList: string[] = ["train", "play", "stop", "reset_env", "reset_agent", "load"]
|
||||
private eventCallback: any[];
|
||||
private buttonsContainer: HTMLDivElement;
|
||||
private event: UIEvent;
|
||||
|
||||
constructor(canvasId: string, levelUrl: string) {
|
||||
/**
|
||||
@@ -31,26 +32,21 @@ export class MetaCar {
|
||||
if (!canvasId || this.levelUrl){
|
||||
console.error("You must specify the canvasId and the levelUrl");
|
||||
}
|
||||
this.isPlaying = false;
|
||||
this.canvasId = canvasId;
|
||||
this.levelUrl = levelUrl;
|
||||
this.eventCallback = [
|
||||
(fc: any) => this.onTrain(fc),
|
||||
(fc: any) => this.onPlay(fc),
|
||||
(fc:any) => this.onStop(fc),
|
||||
(fc: any) => this.onResetEnv(fc),
|
||||
(fc: any) => this.onResetAgent(fc),
|
||||
(fc: any) => this.onSave(fc),
|
||||
(fc: any, opt: eventLoadOptions) => this.onLoad(fc, opt)
|
||||
];
|
||||
}
|
||||
|
||||
// Insert the event div
|
||||
var canvas = document.getElementById(canvasId);
|
||||
var buttons = document.createElement('div'); // create new textarea
|
||||
buttons.classList.add("metacar_buttons_container");
|
||||
buttons.id = "metacar_"+ canvasId + "_buttons_container";
|
||||
canvas.parentNode.insertBefore(buttons, canvas.nextSibling);
|
||||
this.buttonsContainer = buttons;
|
||||
private _setEvents(){
|
||||
// SetEvents callback
|
||||
this.event = new UIEvent(this.level, this.canvasId);
|
||||
this.eventCallback = [
|
||||
(fc: any) => this.event.onTrain(fc),
|
||||
(fc: any) => this.event.onPlay(fc),
|
||||
(fc:any) => this.event.onStop(fc),
|
||||
(fc: any) => this.event.onResetEnv(fc),
|
||||
(fc: any) => this.event.onResetAgent(fc),
|
||||
(fc: any, opt: eventLoadOptions) => this.event.onLoad(fc, opt)
|
||||
];
|
||||
}
|
||||
|
||||
load(level: string, agent: any): Promise<void>{
|
||||
@@ -62,7 +58,8 @@ export class MetaCar {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
U.loadCustomURL(this.levelUrl, (content: LevelInfo) => {
|
||||
this.level = new Level(content, this.canvasId);
|
||||
this.level = new Level(content, this.canvasId);
|
||||
this._setEvents();
|
||||
this.level.load((delta: number) => this.loop(delta));
|
||||
resolve();
|
||||
});
|
||||
@@ -97,103 +94,19 @@ export class MetaCar {
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
private _createButton(parent: HTMLDivElement, name: string): HTMLButtonElement{
|
||||
var button = document.createElement('button'); // create new textarea
|
||||
button.classList.add("metacar_button_train");
|
||||
button.id = "metacar_"+ this.canvasId + "_button_" + name;
|
||||
// Uppercase first letter and replace _
|
||||
name = name.replace(/_/g , " ");
|
||||
button.innerHTML = name.charAt(0).toUpperCase() + name.slice(1);;
|
||||
parent.appendChild(button);
|
||||
return button
|
||||
}
|
||||
|
||||
onTrain(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "train");
|
||||
// Listen the event
|
||||
button.addEventListener("click", () => {
|
||||
this.render(false);
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
onPlay(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "play");
|
||||
// Listen the event
|
||||
button.addEventListener("click", () => {
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
onStop(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "stop");
|
||||
// Listen the event
|
||||
button.addEventListener("click", () => {
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
onResetEnv(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "reset_env");
|
||||
button.addEventListener("click", () => {
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
onResetAgent(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "reset_agent");
|
||||
button.addEventListener("click", () => {
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
onSave(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "save");
|
||||
button.addEventListener("click", () => {
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
onLoad(fc: any, options: eventLoadOptions){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "load_trained_agent");
|
||||
// Create the fake input input file
|
||||
var input_file = document.createElement('input'); // create new textarea
|
||||
input_file.type = "file";
|
||||
input_file.accept = "*/*";
|
||||
input_file.style.display = "none";
|
||||
input_file.classList.add("metacar_button_input_file");
|
||||
input_file.id = "metacar_"+ this.canvasId + "_button_input_file";
|
||||
this.buttonsContainer.appendChild(input_file);
|
||||
|
||||
input_file.addEventListener("change", (dump) => {
|
||||
console.log("New file to handle");
|
||||
U.readDump(dump, (content: any) => {
|
||||
if (fc) fc(content);
|
||||
});
|
||||
});
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
input_file.click();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is used to add button under the canvas. When a
|
||||
* click is detected on the window, the associated @fc is called.
|
||||
* Some events are recognized by the environement, others can be custom.
|
||||
* @eventName Name of the event to listen.
|
||||
* @fc Function to call each time this event is raised.
|
||||
*/
|
||||
addEvent(eventName: string, fc: any, options?: eventLoadOptions):void {
|
||||
/**
|
||||
* eventName: Name of the event to listen
|
||||
* fc: Function to call each time this event is raise
|
||||
*/
|
||||
const index = this.eventList.indexOf(eventName);
|
||||
if (index == -1){
|
||||
console.error("The environement does not support this event. Only the following are\
|
||||
avaible:" + this.eventList);
|
||||
this.event.onCustomEvent(eventName, fc);
|
||||
return;
|
||||
}
|
||||
const event = this.eventList[index];
|
||||
if (event != "load"){
|
||||
@@ -218,18 +131,18 @@ export class MetaCar {
|
||||
U.saveAs(content, file_name);
|
||||
}
|
||||
|
||||
actionSpace(){
|
||||
/*
|
||||
Get the possible action to do in the environement
|
||||
Ex: [0, 1, 2]
|
||||
*/
|
||||
/**
|
||||
* Get the action space of the environement
|
||||
*/
|
||||
actionSpace(): actionSpaceDescription{
|
||||
return this.level.agent.motion.actionSpace();
|
||||
}
|
||||
|
||||
getState(){
|
||||
/*
|
||||
Get the state of this environement
|
||||
*/
|
||||
/**
|
||||
* Return the current state of the environement.
|
||||
* The size of the state depends of the size of the Lidar.
|
||||
*/
|
||||
getState(): number[][]{
|
||||
return this.level.agent.getState();
|
||||
}
|
||||
|
||||
@@ -266,8 +179,8 @@ export class MetaCar {
|
||||
}
|
||||
|
||||
loop(delta: number){
|
||||
if (this.isPlaying){
|
||||
this.agent.play(this);
|
||||
if (this.event.isPlaying()){
|
||||
this.event.playCallback();
|
||||
}
|
||||
else {
|
||||
this.level.step(delta);
|
||||
|
||||
@@ -15,6 +15,18 @@ export interface MotionOption{
|
||||
readonly actions: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure used to describe the action space.
|
||||
* @type: Discrete or continous values
|
||||
* @size: Number of expected values.
|
||||
* @range: Range of each values
|
||||
*/
|
||||
export interface actionSpaceDescription {
|
||||
type: "Discrete"|"Continous"
|
||||
size: number,
|
||||
range: number[]
|
||||
}
|
||||
|
||||
export class MotionEngine {
|
||||
|
||||
protected level: Level|Editor;
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Event class
|
||||
*/
|
||||
|
||||
import {Level} from "./level";
|
||||
import {eventLoadOptions} from "./metacar";
|
||||
import * as U from "./utils";
|
||||
|
||||
export class UIEvent {
|
||||
|
||||
private playing: boolean;
|
||||
private canvasId: string;
|
||||
private buttonsContainer: HTMLDivElement;
|
||||
private level: Level;
|
||||
|
||||
public playCallback: any;
|
||||
|
||||
constructor(level: Level, canvasId: string){
|
||||
this.level = level;
|
||||
this.canvasId = this.canvasId;
|
||||
// Insert the event div
|
||||
var canvas = document.getElementById(canvasId);
|
||||
var buttons = document.createElement('div'); // create new textarea
|
||||
buttons.classList.add("metacar_buttons_container");
|
||||
buttons.id = "metacar_"+ canvasId + "_buttons_container";
|
||||
canvas.parentNode.insertBefore(buttons, canvas.nextSibling);
|
||||
this.buttonsContainer = buttons;
|
||||
}
|
||||
|
||||
public isPlaying(): boolean{
|
||||
return this.playing;
|
||||
}
|
||||
|
||||
private _createButton(parent: HTMLDivElement, name: string): HTMLButtonElement{
|
||||
var button = document.createElement('button'); // create new textarea
|
||||
button.classList.add("metacar_button_train");
|
||||
button.id = "metacar_"+ this.canvasId + "_button_" + name;
|
||||
// Uppercase first letter and replace _
|
||||
name = name.replace(/_/g , " ");
|
||||
button.innerHTML = name.charAt(0).toUpperCase() + name.slice(1);;
|
||||
parent.appendChild(button);
|
||||
return button
|
||||
}
|
||||
|
||||
public onTrain(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "train");
|
||||
// Listen the event
|
||||
button.addEventListener("click", () => {
|
||||
this.level.render(false);
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
public onPlay(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "play");
|
||||
// Listen the event
|
||||
button.addEventListener("click", () => {
|
||||
if (fc) {
|
||||
this.playing = true;
|
||||
this.playCallback = fc;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public onStop(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "stop");
|
||||
// Listen the event
|
||||
button.addEventListener("click", () => {
|
||||
this.playing = false;
|
||||
this.playCallback = undefined;
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
public onResetEnv(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "reset_env");
|
||||
button.addEventListener("click", () => {
|
||||
this.level.reset();
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
public onResetAgent(fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "reset_agent");
|
||||
button.addEventListener("click", () => {
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
public onCustomEvent(name: string, fc: any){
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, name);
|
||||
button.addEventListener("click", () => {
|
||||
if (fc) fc();
|
||||
});
|
||||
}
|
||||
|
||||
public onLoad(fc: any, options:eventLoadOptions = Object()){
|
||||
options.local = options.local || false;
|
||||
// Create the button
|
||||
const button = this._createButton(this.buttonsContainer, "load_trained_agent");
|
||||
// Create the fake input input file
|
||||
var input_file = document.createElement('input'); // create new textarea
|
||||
input_file.type = "file";
|
||||
input_file.accept = "*/*";
|
||||
input_file.style.display = "none";
|
||||
input_file.classList.add("metacar_button_input_file");
|
||||
input_file.id = "metacar_"+ this.canvasId + "_button_input_file";
|
||||
this.buttonsContainer.appendChild(input_file);
|
||||
|
||||
input_file.addEventListener("change", (dump) => {
|
||||
U.readDump(dump, (content: any) => {
|
||||
if (fc) fc(content);
|
||||
});
|
||||
});
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
if (options.local) {
|
||||
input_file.click();
|
||||
}
|
||||
else{
|
||||
if (fc) fc();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"mode": "modules",
|
||||
"out": "docs",
|
||||
"src": "src/index.ts",
|
||||
"theme": "default",
|
||||
"ignoreCompilerErrors": "true",
|
||||
"experimentalDecorators": "true",
|
||||
"emitDecoratorMetadata": "true",
|
||||
"target": "ES5",
|
||||
"moduleResolution": "node",
|
||||
"preserveConstEnums": "true",
|
||||
"stripInternal": "true",
|
||||
"suppressExcessPropertyErrors": "true",
|
||||
"suppressImplicitAnyIndexErrors": "true",
|
||||
"module": "commonjs",
|
||||
"hideGenerator": true,
|
||||
"excludePrivate": true
|
||||
}
|
||||
Reference in New Issue
Block a user