Api Doc + Policy exemple working

This commit is contained in:
Thibault Neveu
2018-06-13 15:59:58 +01:00
parent ba524f28fa
commit 3612a27e1f
25 changed files with 579 additions and 174 deletions
+2 -1
View File
@@ -3,4 +3,5 @@ node_modules/*
dist/dist-es6/*
demo/dist/dist-es6/*
demo/node_modules/
*package-lock.json*
*package-lock.json*
docs
+16 -4
View File
File diff suppressed because one or more lines are too long
-12
View File
@@ -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>
+3 -1
View File
@@ -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>
+10 -15
View File
@@ -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());
});
+286
View File
@@ -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);
})();
}
}
+13
View File
@@ -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"}]}]}
@@ -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"}]}]}
@@ -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"}]}]}
@@ -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"}]}]}
+16 -4
View File
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -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"
}
}
+8 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+12
View File
@@ -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
View File
@@ -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();
}
});
}
}
+18
View File
@@ -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
}