WIP: js to ts

This commit is contained in:
Thibault Neveu
2018-06-06 10:04:06 +01:00
parent 237e1155c0
commit 6999a8532e
19 changed files with 2116 additions and 1 deletions
+56 -1
View File
@@ -1 +1,56 @@
# metacar
# Quickstart for Node.js in the App Engine flexible environment
This is the sample application for the
[Quickstart for Node.js in the App Engine flexible environment][tutorial]
tutorial found in the [Google App Engine Node.js flexible environment][appengine]
documentation.
* [Setup](#setup)
* [Running locally](#running-locally)
* [Deploying to App Engine](#deploying-to-app-engine)
* [Running the tests](#running-the-tests)
## Setup
Before you can run or deploy the sample, you need to do the following:
1. Refer to the [appengine/README.md][readme] file for instructions on
running and deploying.
1. Install dependencies:
With `npm`:
npm install
or with `yarn`:
yarn install
## Running locally
With `npm`:
npm start
or with `yarn`:
yarn start
## Deploying to App Engine
With `npm`:
npm run deploy
or with `yarn`:
yarn run deploy
## Running the tests
See [Contributing][contributing].
[appengine]: https://cloud.google.com/appengine/docs/flexible/nodejs
[tutorial]: https://cloud.google.com/appengine/docs/flexible/nodejs/quickstart
[readme]: ../../README.md
[contributing]: https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/CONTRIBUTING.md
+64
View File
@@ -0,0 +1,64 @@
'use strict';
var fs = require('fs');
// [START app]
const express = require('express');
var path = require("path");
function fromDir(startPath,filter){
//console.log('Starting from dir '+startPath+'/');
if (!fs.existsSync(startPath)){
console.log("no dir ",startPath);
return [];
}
var files_list = [];
var files=fs.readdirSync(startPath);
for(var i=0;i<files.length;i++){
var filename=path.join(startPath,files[i]);
var stat = fs.lstatSync(filename);
if (stat.isDirectory()){
fromDir(filename,filter); //recurse
}
else if (filename.indexOf(filter)>=0) {
files_list.push(filename);
};
};
return files_list;
}
const app = express();
app.use("/dist", express.static(path.join(__dirname, "dist/")));
app.use("/public", express.static(path.join(__dirname, "webapp/public/")));
function get_path(file){
return path.join(path.join(__dirname, "webapp/"), file);
}
app.get('/', (req, res) => {
res.sendFile(get_path("index.html"));
});
const files = fromDir(path.join(__dirname, "webapp/"),'.html');
files.forEach(file => {
let route = file.split("/");
route = route[route.length - 1];
console.log("Open route:", route, file);
app.get('/'+route, (req, res) => {
res.sendFile(file);
});
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
// [END app]
+32
View File
@@ -0,0 +1,32 @@
# Copyright 2017, Google, Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# [START app_yaml]
runtime: nodejs
env: flex
skip_files:
- yarn.lock
# This sample incurs costs to run on the App Engine flexible environment.
# The settings below are to reduce costs during testing and are not appropriate
# for production use. For more information, see:
# https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
# [END app_yaml]
+14
View File
@@ -0,0 +1,14 @@
{
"name": "metacar-demo",
"version": "0.0.1",
"description": "",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"author": "Thibault Neveu",
"license": "ISC",
"dependencies": {
"express": "^4.16.3"
}
}
+10
View File
@@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World</title>
</head>
<body>
<script src="/dist/metacar.min.js"></script>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
{
"name": "metacar",
"version": "0.0.1",
"main": "index.js",
"author": "Thibault Neveu",
"license": "MIT",
"devDependencies": {
"ts-loader": "^4.3.1",
"typescript": "^2.9.1",
"webpack": "^4.10.2",
"webpack-cli": "^3.0.2"
},
"dependencies": {
"@types/pixi.js": "^4.7.5",
"express": "^4.16.3",
"pixi.js": "^4.8.0"
},
"scripts": {
"build": "./node_modules/.bin/webpack-cli --mode production",
"watch": "./node_modules/.bin/webpack-cli --watch --mode development"
}
}
+313
View File
@@ -0,0 +1,313 @@
/*
@AssetManger class
Used to create all assets on the map (Trees, roads, cars...)
*/
import {Level, LevelInfo} from "./level";
import * as U from "./utils";
import {
ROADSIZE, Graphics, Sprite, ASSETS, MAP, CAR_IMG
} from "./global";
import {CarOptions, Car} from "./car";
import {BasicMotionEngine} from "./basic_motion_engine";
import {ControlMotionEngine} from "./control_motion_engine";
import {BotMotionEngine} from "./bot_motion_engine";
export interface AssetInfo {
readonly mx?: number;
readonly my?: number;
readonly type?: any;
readonly x?: number;
readonly y?: number;
};
export interface RoadSprite extends PIXI.Sprite {
// The road is not an obstacle (should be false)
obstacle?: boolean;
// Id of the road
mapId?: number;
// Orientation of the road
orientation?: number;
// Method call to set a car on this road
setCarPosition?: any;
// Array used to stored all the car currently on this road
// This method take a list of id (id of each car)
cars?: number[];
// (x, y position) Relatif to the map
mx?: number;
my?: number;
}
export interface SimpleSprite extends PIXI.Sprite {
obstacle?: boolean;
// name of the asset (not the name of the image)
type?: string;
mapId?: number;
}
export class AssetManger {
private level: Level;
// Used to list the differents MotionEngine possible to add on the car
private motion: any;
// List of all items on the map
private assets: SimpleSprite[] = [];
constructor(level: Level) {
this.level = level;
this.motion = {
"BasicMotionEngine": BasicMotionEngine,
"ControlMotionEngine": ControlMotionEngine
}
}
createRoadSide(info: AssetInfo, textures: any){
/*
@textures: (Pixi textures)
*/
let x = (info.mx) * ROADSIZE;
let y = (info.my) * ROADSIZE;
let area = new Graphics();
area.beginFill(0xe8e8e8);
area.drawRoundedRect(0, 0, ROADSIZE+30, ROADSIZE+30, undefined);
area.x = x-15;
area.y = y-15;
area.endFill();
this.level.addChild(area);
}
createRoad(info: AssetInfo, textures: any){
/*
Method use to add a new road on the map
↕, ↱ or ↔, ↰, ↲, ↳
@textures: (Pixi textures)
*/
let road: RoadSprite = new Sprite(textures[ASSETS.ROADS[info.type].image]);
// Set the position of this road
road.x = (info.mx) * ROADSIZE;
road.y = (info.my) * ROADSIZE;
road.obstacle = false;
road.mapId = MAP.ROAD;
road.orientation = ASSETS.ROADS[info.type].orientation;
road.setCarPosition = (car: any, line: number) => this.setCarOnRoad(road, car, line);
road.cars = [];
road.mx = info.mx; // Map x
road.my = info.my; // Map y
this.level.addRoad(road);
}
setCarOnRoad(road: RoadSprite, car: any, line: number){
/*
@road: (Obejct) road to position the car on
@car: (Object) car object to position
@line is Optional. 0 By default.
*/
if (line == undefined)
line = 0;
if (road.cars.length >= 1 && this.level.findCarById(road.cars[0]).core.line == line){
line = line == 0 ? 1:0;
}
if (road.cars.length >= 2){
car.is_valid = false;
return;
}
car.line = line;
// Set the car on the road
car.x = road.x;
car.y = road.y;
car.x += ROADSIZE / 2;
car.y += ROADSIZE / 2;
let x_margin = 0
let y_margin = (ROADSIZE*1/4);
// transform to map x coordinate
// transform to map y coordinate
road.orientation = (Math.floor(road.orientation / car.rotation_step)*car.rotation_step);
let theta = -road.orientation*(Math.PI);
let x_m = (Math.cos(theta) * x_margin) - (y_margin * Math.sin(theta));
let y_m = (Math.cos(theta) * y_margin) + (x_margin * Math.sin(theta));
let line_side_factor = line == 0 ? 1:-1;
let line_side_factor_t = line == 0 ? 0:Math.PI;
car.x += line_side_factor*Math.round(x_m);
car.y += line_side_factor*Math.round(y_m);
// (x, y position) Relatif to the map
car.mx = Math.floor(car.x / ROADSIZE);
car.my = Math.floor(car.y / ROADSIZE);
// Car rotation
car.rotation = (theta+line_side_factor_t);
// Set the new road of the car
car.checkAndsetNewRoad(road);
}
createAsset(img: string, info: AssetInfo, textures: any, type: string){
/*
Create a simple asset on the map
@type: (String) name of the asset (image)
@info: (Object) x,y position of the asset
@textures: (Pixi textures)
@type (String) name of the asset (not the name of the image)
*/
let asset: SimpleSprite = new Sprite(textures[img]);
// The road is not an obstacle
asset.obstacle = false;
// Set the id of the road)
asset.type = type;
asset.mapId = 0;
asset.x = info.x;
asset.y = info.y;
this.level.addChild(asset);
this.assets.push(asset);
}
createMap(map: (string|number)[][], info: LevelInfo, textures: PIXI.Texture, roadside:boolean=true){
/*
Method used to create the map with all assets (except the cars)
@map (2dim Array)
@info (Object) Level's json.
@textures: (Pixi textures)
*/
if (roadside){
// Draw the roads side
for (let my = 0; my < map.length; my++) {
for (let mx = 0; mx < map[my].length; mx++) {
if (ASSETS.ROADS[map[my][mx]]){
this.createRoadSide({mx, my, type: map[my][mx]}, textures);
}
}
}
}
// Draw the roads
for (let my = 0; my < map.length; my++) {
for (let mx = 0; mx < map[my].length; mx++) {
if (ASSETS.ROADS[map[my][mx]]){
this.createRoad({mx, my, type: map[my][mx]}, textures);
}
}
}
// Draw the others items
for (let key in ASSETS){
if (key != "ROADS" && info[key]){
for (let a=0; a < info[key].length; a++){
this.createAsset(ASSETS[key].image, info[key][a], textures, key);
}
}
}
}
createCars(map: (string|number)[][], info: LevelInfo, textures: PIXI.Texture){
/*
Method used to create all the cars
@map (2dim Array)
@info (Object) Level's json.
@textures: (Pixi textures)
*/
// Go through all the bot cars on the map
for (let c in info.cars){
let options: CarOptions = {lidar: false, lidarInfo: {pts: 2, width: 0.5, height: 1, pos: 1}};
if (info.cars[c].auto){
options.lidar = true;
options.motionEngine = new BotMotionEngine(this.level);
}
let n_car = new Car(this.level, info.cars[c], textures, options);
// Append the car to the canvas
this.level.addCar(n_car);
}
}
createAgent(map: (string|number)[][], info: LevelInfo, textures: PIXI.Texture){
/*
Method used to create the agent's car.
@map (2dim Array)
@info (Object) Level's json.
@textures: (Pixi textures)
*/
let agent = new Car(this.level, info.agent, textures, {
image: CAR_IMG.AGENT,
lidar: true,
motionEngine: new this.motion[info.agent.motion.type](this.level, info.agent.motion.options)
});
this.level.addChild(agent.lidar)
this.level.addCar(agent);
agent.core.agent = true;
return agent;
}
exportMap(width: number, height: number, file_name: string){
/*
Export the map
@width (Integer) Width of the new map
@height (Integer) Height of the new map
@file_name (String) name of the file to export
*/
var file = {"cars": []};
let map = [];
for (var y = 0; y < height; y++) {
let line = [];
for (var x = 0; x < width; x++) {
line.push(0);
}
map.push(line);
}
for (var e = 0; e < this.level.envs.length; e++) {
let elem = this.level.envs[e];
if (!elem.isremove){
// Add the cars
if (elem.mapId == MAP.CAR && !elem.agent && elem.is_valid){
file.cars.push({
"mx": elem.mx,
"my": elem.my,
"line": elem.line
});
}
else if (elem.mapId == MAP.ROAD){
map[elem.my][elem.mx] = elem.arrow;
}
}
}
for (var e = 0; e < this.assets.length; e++) {
let elem = this.assets[e];
if (!elem.isremove){
if (elem.type != "car"){
if (!file[elem.type])
file[elem.type] = [];
file[elem.type].push({
"x": elem.x,
"y": elem.y
});
}
}
}
// Set the map
file.map = map;
// If the agent exist
if (this.level.agent){
file.agent = {
"mx": this.level.agent.core.mx,
"my": this.level.agent.core.my,
"line": this.level.agent.core.line,
"motion": {
"type": "BasicMotionEngine",
"options":{
"rotation_step": 0.5,
"actions": ["UP", "LEFT", "RIGHT", "DOWN", "WAIT"]
}
}
};
}
console.log(file);
file = JSON.stringify(file, null, 4);
U.saveAs(file, file_name);
}
}
+163
View File
@@ -0,0 +1,163 @@
import { MotionEngine, MotionOption } from "./motion_engine";
import {Level} from "./level";
import {
MAP, ROADSIZE
} from "./global";
import * as U from "./utils";
export class BasicMotionEngine extends MotionEngine {
/*
Basic Motion Engine
In this configuration the possible action of the environement are
either left, right, up, down or wait.
*/
private rotationStep: number;
private actions: string[];
constructor(level: Level, options: MotionOption) {
super(level);
this.rotationStep = options.rotationStep;
this.actions = options.actions;
}
setUp(car: any, lidar: any){
/*
Setup the motion engine
@car: (Vehicle Object)
@lidar: (Lidar Object)
*/
this.car = car;
this.lidar = lidar;
// Init the lidar state
this.state = [];
for (let y = 0; y < lidar.pts; y++) {
let line = [];
for (let x = 0; x < lidar.pts; x++) {
line.push(MAP.DEFAULT);
}
this.state.push(line);
}
// Set up keyboard interaction
this.setUpKeyboard();
// Setup up velocity to 0
this.car.v = 0;
this.detectInteractions();
}
setUpKeyboard(){
/*
Setup possible keyboard interactions
*/
let left = U.keyboard(37);
let up = U.keyboard(38);
let right = U.keyboard(39);
let down = U.keyboard(40);
// Left and Right
if (this.actions.indexOf("LEFT") != -1)
left.press = () => { this.turnLeft(); };
if (this.actions.indexOf("RIGHT") != -1)
right.press = () => { this.turnRight(); };
// Move forward
if (this.actions.indexOf("UP") != -1){
up.press = () => { this.moveForward(); };
up.release = () => {
this.car.v = 0;
};
}
if (this.actions.indexOf("DOWN") != -1){
// Move backward
down.press = () => { this.moveBackward();};
down.release = () => {
this.car.v = 0;
};
}
}
turnLeft(){
/*
Turn left
*/
this.car.rotation -= this.rotationStep*Math.PI;
this.lidar.rotation = this.car.rotation;
}
turnRight(){
/*
Turn right
*/
this.car.rotation += this.rotationStep*Math.PI;
this.lidar.rotation = this.car.rotation;
}
moveForward(){
/*
Move forward
*/
this.car.v = 1;
}
moveBackward(){
/*
Move backward
*/
this.car.v = -1;
}
actionStep(delta: number, action: number){
/*
Step into the environement with one action
@delta (Float) time since the last update
@action: (Integer) The action to take (can be null if no action)
*/
if (this.actions[action] == "LEFT") { this.turnLeft();}
if (this.actions[action] == "RIGHT") { this.turnRight();}
if (this.actions[action] == "UP") { this.moveForward();}
if (this.actions[action] == "DOWN") { this.moveBackward();}
let {agent_col, on_road} = this.step(delta);
this.car.v = 0;
return {agent_col, on_road};
}
actionSpace(){
/*
Return an array with all possibles actions
Ex: [0, 1, 2]
*/
return Array.apply(null, {length: this.actions.length}).map(Number.call, Number);
}
step(delta: number){
/*
Step into the environement
@delta (Float) time since the last update
*/
// Update the x and y position according to the velocity
this.car.x += this.car.v * Math.cos(this.car.rotation)*delta;
this.car.y += this.car.v * Math.sin(this.car.rotation)*delta;
// Update the x and x position according to the map
this.car.mx = Math.floor(this.car.x / ROADSIZE);
this.car.my = Math.floor(this.car.y / ROADSIZE);
// Set the new road of the car (if changed)
this.car.checkAndsetNewRoad();
// Be sure to keep the lidar position at the same position than the car
this.lidar.x = this.car.x;
this.lidar.y = this.car.y;
this.lidar.rotation = this.car.rotation;
// Detection the new collision with the environement
let {agent_col, on_road} = this.detectInteractions();
if (agent_col.length > 0){ // Stop the vehicle if a collision is detected
this.car.v = 0;
this.car.vy = 0;
}
return {agent_col, on_road};
}
}
+244
View File
@@ -0,0 +1,244 @@
import { MotionEngine } from "./motion_engine";
import {Level} from "./level";
import {
MAP, ROADSIZE
} from "./global";
export class BotMotionEngine extends MotionEngine {
/*
Motion Engine for the bot cars (not for the agent)
This motion Engin let the cars move by themself.
The roation of the cars is either left, right or forward.
*/
private mapSizeY: number;
private mapSizeX: number;
private rotationStep: number;
private map: (string|number)[][];
private rotationToNextCase: any;
constructor(level: Level) {
super(level);
this.map = this. level.getMap();
this.mapSizeY = this.map.length;
this.mapSizeX = this.map[0].length;
this.rotationStep = 0.5;
this.rotationToNextCase = {};
this.rotationToNextCase[0] = {"mx": 1, "my": 0};
this.rotationToNextCase[0.5] = {"mx": 0, "my": 1};
this.rotationToNextCase[1.0] = {"mx": -1, "my": 0};
this.rotationToNextCase[1.5] = {"mx": 0, "my": -1};
this.rotationToNextCase[-0.5] = {"mx": 0, "my": -1};
this.rotationToNextCase[-1.0] = {"mx": -1, "my": 0};
this.rotationToNextCase[-1.5] = {"mx": 0, "my": 1};
// this.rotationToNextCase[null] = {"mx": 0, "my": 0};
}
setUp(car: any, lidar: any){
/*
Setup the motion engine
@car: (Vehicle Object)
@lidar: (Lidar Object)
*/
this.car = car;
this.lidar = lidar;
// Init the lidar state
this.state = [];
for (let y = 0; y < lidar.pts; y++) {
let line = [];
for (let x = 0; x < lidar.pts; x++) {
line.push(MAP.DEFAULT);
}
this.state.push(line);
}
// Setup up velocity to 0
this.car.v = 0;
// Set up the lidar state by detecting interactions
this.detectInteractions();
}
turnLeft(){
/*
Turn left
*/
this.car.rotation -= this.rotationStep*Math.PI;
this.lidar.rotation = this.car.rotation;
}
turnRight(){
/*
Turn right
*/
this.car.rotation += this.rotationStep*Math.PI;
this.lidar.rotation = this.car.rotation;
}
moveUp(){
this.car.v = 1;
}
moveDown(){
/*
Move forward
*/
this.car.v = -1;
}
isRoad(nx: number, ny: number){
/*
Method used to check is there a road is present at this potion
on the map
*/
if (ny < 0 || ny >= this.mapSizeY || nx < 0 || nx >= this.mapSizeX || this.level.map[ny][nx] == 0){
return false;
}
return true;
}
autoRotation(){
/*
Auto roation of the bot car
*/
let right_dist = ROADSIZE + (ROADSIZE/3);
let left_dist = ROADSIZE - (ROADSIZE/3.5);
let same_dist = ROADSIZE;
// If a next road is already define, we just check if this is the
// good moment to turn
if (this.car.next_road){
let y_dist = Math.abs(((this.car.next_road.outroad.y + ROADSIZE/2) - this.car.y) * this.car.next_road.outroad_np.my);
let x_dist = Math.abs(((this.car.next_road.outroad.x + ROADSIZE/2) - this.car.x) * this.car.next_road.outroad_np.mx);
if (this.car.next_road.rotation_type == 0){ // Turn right
if (y_dist < right_dist && x_dist < right_dist){
this.car.have_turned = true;
this.car.rotation = this.car.rotation += Math.PI/2;
this.car.rotation = this.car.rotation % (2*Math.PI);
this.car.next_road = undefined;
return;
}
}
if (this.car.next_road.rotation_type == 1){ // Turn left
if (y_dist < left_dist && x_dist < left_dist){
this.car.have_turned = true;
this.car.rotation = this.car.rotation -= Math.PI/2;
this.car.rotation = this.car.rotation % (2*Math.PI);
this.car.next_road = undefined;
return;
}
}
if (this.car.next_road.rotation_type == 2){ // Rotation
if (y_dist < same_dist && x_dist < same_dist){
this.car.have_turned = true;
this.car.rotation = this.car.rotation -= Math.PI;
this.car.rotation = this.car.rotation % (2*Math.PI);
if (y_dist != 0 && this.car.x < this.car.next_road.outroad.x + ROADSIZE/2){
this.car.x = this.car.next_road.outroad.x + ROADSIZE/2 + ROADSIZE/4;
this.car.y = this.car.next_road.outroad.y;
}
else if (y_dist != 0 && this.car.x > this.car.next_road.outroad.x + ROADSIZE/2){
this.car.x = this.car.next_road.outroad.x + ROADSIZE - ROADSIZE/2 - ROADSIZE/4;
this.car.y = this.car.next_road.outroad.y;
}
else if (x_dist != 0 && this.car.y < this.car.next_road.outroad.y + ROADSIZE/2){
this.car.y = this.car.next_road.outroad.y + ROADSIZE/2 + ROADSIZE/4;
this.car.x = this.car.next_road.outroad.x;
}
else if (x_dist != 0 && this.car.y > this.car.next_road.outroad.y + ROADSIZE/2){
this.car.y = this.car.next_road.outroad.y + ROADSIZE - ROADSIZE/2 - ROADSIZE/4;
this.car.x = this.car.next_road.outroad.x;
}
this.car.next_road = undefined;
return;
}
}
}
else{
let key = this.car.rotation / Math.PI;
let key_r = ((this.car.rotation / Math.PI) + 0.5) % 2;
let key_l = ((this.car.rotation / Math.PI) - 0.5) % 2;
let is_next_pos = this.rotationToNextCase[key];
let is_next_pos_r = this.rotationToNextCase[key_r];
let is_next_pos_l = this.rotationToNextCase[key_l];
let turn = false;
let nx = this.car.mx + is_next_pos.mx;
let ny = this.car.my + is_next_pos.my;
if (!this.isRoad(nx, ny)){
turn = true;
}
else if (!this.car.have_turned && !this.car.turn_random){
this.car.turn_random = Math.random();
}
else if(!this.car.have_turned && this.car.turn_random < 0.33 && this.isRoad(this.car.mx + is_next_pos_r.mx, this.car.my + is_next_pos_r.my)){
turn = true;
}
else if(!this.car.have_turned && this.car.turn_random > 0.66 && this.isRoad(this.car.mx + is_next_pos_l.mx, this.car.my + is_next_pos_l.my)){
turn = true;
}
if (turn){
let possibles_rotations = [(key+0.5) % 2., (key-0.5) % 2., null];
for (let p = 0; p < possibles_rotations.length; p++){
let next_pos = this.rotationToNextCase[possibles_rotations[p]];
let px = this.car.mx + next_pos.mx;
let py = this.car.my + next_pos.my;
if (this.isRoad(px, py)){
this.car.next_road = {
"outroad": {y : ny*ROADSIZE, x: nx*ROADSIZE},
"outroad_np": is_next_pos,
"rotation_type": p,
};
return;
}
}
}
}
}
step(delta: number){
/*
Step into the environement
@delta (Float) time since the last update
*/
// Possible collision detected
if (this.state.toString().indexOf(","+MAP.CAR.toString()) != -1){
this.car.v = 0;
}
else{ // No collision, move forward
this.car.v = 0.8;
this.autoRotation();
}
// Update the x and y position according to the velocity
this.car.x += this.car.v * Math.cos(this.car.rotation)*delta;
this.car.y += this.car.v * Math.sin(this.car.rotation)*delta;
// Update the x and x position according to the map
this.car.mx = Math.floor(this.car.x / ROADSIZE);
this.car.my = Math.floor(this.car.y / ROADSIZE);
// Set the new road of the car (if changed)
this.car.checkAndsetNewRoad();
// Be sure to keep the lidar position at the same position than the car
this.lidar.x = this.car.x;
this.lidar.y = this.car.y;
this.lidar.rotation = this.car.rotation;
// Detection the new collision with the environement
let {agent_col, on_road} = this.detectInteractions();
if (agent_col.length > 0){
this.car.v = 0;
}
return {agent_col, on_road};
}
}
+236
View File
@@ -0,0 +1,236 @@
/*
Car class
Class used to create a new car on the map
This class create the agent and the associated sensor (lidar)
*/
import {Level, LevelInfo} from "./level";
import {
CAR_IMG, Sprite, MAP, ROADSIZE, Container, Graphics
} from "./global";
import { RoadSprite } from "./asset_manager";
var Global_carId = 0;
export interface LidarInfoI {
pts?: number;
width?: number;
height?: number;
pos?: number;
}
export interface CarOptions {
image?: string;
lidar?: boolean;
lidarInfo?: LidarInfoI;
motionEngine?: any;
}
export interface CarInfo{
readonly mx: number;
readonly my: number;
readonly line: number;
}
export interface CarSprite extends PIXI.Sprite {
road?: RoadSprite;
carId?: number;
obstacle?: boolean;
mapId?: number;
lidar?: any;
rotationStep?: any;
mx?: number;
my?: number;
checkAndsetNewRoad?: any;
line?: number;
haveTurned?: boolean;
agent?: boolean;
}
export class Car {
public level: Level;
public core: CarSprite;
public lidar: any;
private info: CarInfo;
private motion: any;
private turnedRandom: any;
constructor(level: Level, info: CarInfo, textures: any, options:CarOptions={}) {
/*
@level (Level class)
@info: (CarInfo) Info from the json file about the car
@textures: (Pixi: loader.resources) Used ot load the agent texture
@options
image (String) Default CAR_IMG.DEFAULT
lidar (False) Default: false
lidar_info: (Object) Object with the informations about the lidar
motionEngine (Class) Motion engine to use for this car
*/
this.level = level;
options.image = options.image || CAR_IMG.DEFAULT;
options.lidar = options.lidar || false;
options.lidarInfo = options.lidarInfo || {pts: 5, width: 4, height: 5, pos: 0};
// Create the sprite of the car
this.core = new Sprite(textures[options.image]);
this.info = info; // Store the original information about the car
// Change the scale and the anchor of the sprite
this.core.scale.x = 0.8;
this.core.scale.y = 0.8;
this.core.anchor.x = 0.5;
this.core.anchor.y = 0.5;
this.core.road = undefined;
// Id of this car
this.core.carId = Global_carId;
Global_carId += 1;
// THis car is not an agent by default
this.core.agent = false;
// A car is an obstacle
this.core.obstacle = true;
// Set the map id
this.core.mapId = MAP.CAR;
// Create the lidar of the car if required
if (options.lidar){
this.createLidar(options.lidarInfo);
this.core.lidar = this.lidar;
}
// Setup the motionEngine of the car if needded
// TODO: Remove useless setUp
if (options.motionEngine){
this.motion = options.motionEngine;
this.motion.setUp(this.core, this.lidar);
this.core.rotationStep = 0.5;
}
else{
this.core.rotationStep = 0.5;
}
// Set the position of the car
this.core.mx = this.info.mx;
this.core.my = this.info.my;
this.core.x = (this.info.mx) * ROADSIZE;
this.core.y = (this.info.my) * ROADSIZE;
// Usefull method to set the new road position of the car
// and keep the list of cars on each road up to date
this.core.checkAndsetNewRoad = (road: RoadSprite) => this.checkAndsetNewRoad(road);
// If the car is on a road, we set the position of the car on
// the road properly
let road = this.level.getRoad(this.core.my, this.core.mx);
this.core.line = 0; // By defaut
if (road){ // If the car is on a road
road.setCarPosition(this.core, this.info.line);
}
}
checkAndsetNewRoad(n_road?: RoadSprite){
/*
Check if the car is on a new road
If the car is on a new road, we keep the list up to date.
@n_road (Road object) The new road
*/
let current_road = this.core.road;
if (!n_road){ // If the road is not define
n_road = this.level.getRoad(this.core.my, this.core.mx);;
}
if (n_road != current_road){
if (current_road){ // The car is not on this road anymore
current_road.cars.splice(current_road.cars.indexOf(this.core.carId), 1);
}
// Add the car to the road list
this.core.road = n_road;
// TOdo: CHECK something is strange here
this.core.haveTurned = false;
this.turnedRandom = undefined;
if (n_road)
n_road.cars.push(this.core.carId);
}
}
reset(){
/*
Method used to restore the position of the car
to the original position (as set into the json file)
*/
this.core.mx = this.info.mx;
this.core.my = this.info.my;
let road = this.level.getRoad(this.core.my, this.core.mx);
if (road){ // If the car is on a road
road.setCarPosition(this.core, this.info.line);
}
}
getState(){
/*
Get the current state of the car
The state is the current value of each point
of the lidar.
*/
return this.motion.state.map(function(arr: any) { return arr.slice(); });
}
step(delta: number, action:number=null){
/*
Take one step into the environement
@delta (Float) time since the last update
@action: (Integer) The action to take (can be null if no action)
*/
if (action == null){
var {agent_col, on_road} = this.motion.step(delta);
}
else{
var {agent_col, on_road} = this.motion.actionStep(delta, action);
}
return {agent_col, on_road};
}
createLidar(lidarOptions: LidarInfoI){
/*
Create the lidar sensor of the car
@lidarOptions (Object) option to create the lidar
@pts (Integer) Number of point required
@width: Width of the lidar (in proportion to the car)
@height: Height of the lidar (in proportion to the car)
*/
this.lidar = new Container();
// Area of the lidar
let area = new Graphics();
area.alpha = 0.5;
area.beginFill(0x515151);
area.drawRect(0, 0, this.core.width*lidarOptions.width, this.core.height*lidarOptions.height);
area.endFill();
// Create all the points of the lidar
let x_step = area.width/lidarOptions.pts;
let y_step = area.height/lidarOptions.pts;
for (let xs = lidarOptions.pts - 1; xs >= 0; xs--) {
for (let ys = 0; ys < lidarOptions.pts; ys++) {
let x = (x_step/4) + xs * x_step;
let y = (y_step/4) + ys * y_step;
let pt = new Graphics();
pt.beginFill(0xffffff);
pt.drawRect(x, y, 5, 5);
pt.endFill();
this.lidar.addChild(pt);
}
}
this.lidar.pts = lidarOptions.pts;
this.lidar.addChild(area);
this.lidar.x = this.core.x;
this.lidar.y = this.core.y;
this.lidar.pivot.y = this.lidar.height/2;
this.lidar.pivot.x = this.core.width/2 - (lidarOptions.pos*this.core.width);
this.lidar.rotation = this.core.rotation;
}
}
+178
View File
@@ -0,0 +1,178 @@
import { MotionEngine } from "./motion_engine";
import {Level} from "./level";
import {
MAP, ROADSIZE
} from "./global";
import * as U from "./utils";
export class ControlMotionEngine extends MotionEngine {
private actions: (string|number)[];
constructor(level: Level) {
/*
@level (Level class)
@options (Object) Option to build the motion engine
@rotation_step: Between 0 and 2. (rotation_step*Pi)
@actions: List of possible actions (Left, Right, Up, Down, Wait)
*/
super(level);
this.level = level;
this.actions = ["SteeringAngle", "Throttle"]
}
setUp(car: any, lidar: any){
/*
Setup the motion engine
@car: (Vehicle Object)
@lidar: (Lidar Object)
*/
this.car = car;
this.lidar = lidar;
// Init the lidar state
this.state = [];
for (let y = 0; y < lidar.pts; y++) {
let line = [];
for (let x = 0; x < lidar.pts; x++) {
line.push(MAP.DEFAULT);
}
this.state.push(line);
}
// Set up keyboard interaction
this.setUpKeyboard();
// Setup up velocity to 0
this.car.v = 0;
this.car.a = 0; // Acceleration
this.car.yaw_rate = 0;
this.detectInteractions();
}
setUpKeyboard(){
/*
Setup possible keyboard interactions
*/
let left = U.keyboard(37);
let up = U.keyboard(38);
let right = U.keyboard(39);
let down = U.keyboard(40);
left.press = () => {
this.turn(-0.5);
};
left.release = () => {
this.turn(0);
};
right.press = () => {
this.turn(0.5);
};
right.release = () => {
this.turn(0);
};
// Move forward
up.press = () => { this.move(1); };
up.release = () => {
this.car.a = 0;
};
// Move backward
down.press = () => { this.move(-1);};
down.release = () => {
this.car.a = 0;
};
}
turn(value: number){
/*
Turn
@value: yaw_rate value Between -1 and 1
*/
this.car.yaw_rate = value;
}
move(throttle: number){
/*
Move forward
@throttle throttle value Between -1 and 1
*/
this.car.a = throttle;
}
actionStep(delta: number, actions: number[]){
/*
Step into the environement with one action
@delta (Float) time since the last update
@action: (Array of Float) The throttle and the steering angle
*/
// TODO: CHeck why parseInt ???
this.car.a = Math.min(Math.max(parseInt(actions[0].toString()), -1), 1);
this.car.yaw_rate = Math.min(Math.max(parseInt(actions[1].toString()), -1), 1);
let {agent_col, on_road} = this.step(delta);
this.car.a = 0;
this.car.yaw_rate = 0;
return {agent_col, on_road};
}
actionSpace(){
/*
Return an array with all possibles actions
Ex: [0, 1, 2]
*/
return Array.apply(null, {length: this.actions.length}).map(Number.call, Number);
}
step(delta: number){
/*
Step into the environement
@delta (Float) time since the last update
*/
// Update the x and y position according to the velocity
if (this.car.v > 0)
this.car.v = Math.max(0, this.car.v - 0.01);
else if (this.car.v < 0)
this.car.v = Math.min(0, this.car.v + 0.01);
if (this.car.a > 0)
this.car.v = Math.min(2., this.car.v + this.car.a*0.02);
else
this.car.v = Math.max(-2., this.car.v + this.car.a*0.02);
if (this.car.yaw_rate == 0){
this.car.x += this.car.v * Math.cos(this.car.rotation)*delta;
this.car.y += this.car.v * Math.sin(this.car.rotation)*delta;
}
else{
// Rewrite the yaw rate
let yr = this.car.yaw_rate*0.01*(this.car.v+0.001)*Math.PI;
this.car.x += (this.car.v/yr)*(Math.sin(this.car.rotation+(yr*delta)) - Math.sin(this.car.rotation));
this.car.y += (this.car.v/yr)*(Math.cos(this.car.rotation) - Math.cos(this.car.rotation+(yr*delta)));
this.car.rotation += yr*delta;
}
// Update the x and x position according to the map
this.car.mx = Math.floor(this.car.x / ROADSIZE);
this.car.my = Math.floor(this.car.y / ROADSIZE);
// Set the new road of the car (if changed)
this.car.checkAndsetNewRoad();
// Be sure to keep the lidar position at the same position than the car
this.lidar.x = this.car.x;
this.lidar.y = this.car.y;
this.lidar.rotation = this.car.rotation;
// Detection the new collision with the environement
let {agent_col, on_road} = this.detectInteractions();
if (agent_col.length > 0){ // Stop the vehicle if a collision is detected
this.car.v = 0;
this.car.vy = 0;
}
return {agent_col, on_road};
}
}
+109
View File
@@ -0,0 +1,109 @@
/*
Global variables
*/
import * as PIXI from 'pixi.js'
// PIXI Aliases
export const Application = PIXI.Application;
export const Loader = PIXI.loader;
export const Resources = PIXI.loader.resources;
export const Sprite = PIXI.Sprite;
export const Graphics = PIXI.Graphics;
export const Container = PIXI.Container;
// Textures files
export const JSON_TEXTURES = "textures/textures.json";
// Level foler
export const LEVEL_FOLDER = "/level/";
// Width and height of each road asset
export const ROADSIZE = 60;
export interface CARIMGI{
DEFAULT: string;
AGENT: string;
}
export const CAR_IMG: CARIMGI = {
DEFAULT: "car.png", // Name of the image to represent a simple car
AGENT: "car_agent.png" // Name of the image to represent an agent
}
export const MAP = {
ROAD: -1,
DEFAULT: 0,
CAR: 1,
AGENT: 1,
};
/*
* Asset configurations
*/
export const ASSETS: any = {
ROADS: {
"↕": {
"image": "road_u.png",
"orientation": 1.5
},
"↔": {
"image": "road_r.png",
"orientation": 0.
},
"↱": {
"image": "road_rotate_ld.png",
"orientation": 1.25
},
"↰": {
"image": "road_rotate_ul.png",
"orientation": 0.75
},
"↲": {
"image": "road_rotate_rl.png",
"orientation": 0.25
},
"↳": {
"image": "road_rotate_dl.png",
"orientation": 1.75
},
"↟": {
"image": "road_up.png",
"orientation": 1.5
},
"↠": {
"image": "road_rp.png",
"orientation": 0.
},
"↤": {
"image": "road_r2.png",
"orientation": 0.
},
"↦": {
"image": "road_l2.png",
"orientation": 1.
},
"↥": {
"image": "road_d2.png",
"orientation": 0.5
},
"↧": {
"image": "road_u2.png",
"orientation": 1.5
},
},
"house":{
"image": "house.png"
},
"house2":{
"image": "house2.png"
},
"house3":{
"image": "house3.png"
},
"bench":{
"image": "bench.png"
},
"tree":{
"image": "tree.png"
}
}
+2
View File
@@ -0,0 +1,2 @@
import {MetaCar} from "./metacar";
export default MetaCar;
+215
View File
@@ -0,0 +1,215 @@
/*
@Level class
This is the core of game, the class is used create all the differents
services in the game (assets, map, agents...).
*/
import * as U from "./utils";
import {
LEVEL_FOLDER, ROADSIZE, Loader, JSON_TEXTURES
} from "./global";
import {AssetManger, RoadSprite, SimpleSprite, AssetInfo, } from "./asset_manager";
import {Car} from "./car";
export interface LevelInfo {
map: (string|number)[][];
agent: any;
[index: string]: any;
}
export interface Roads {
[key: string]: RoadSprite
}
export class Level {
private app: any = null; // The pixi app.
private name: string = null; // Name fo the level
private info: LevelInfo = null; // Information of the loaded level
private envs: any[] = [] // Used to list all elements the car can crash with
private map: (string|number)[][] = null; // Use the roads positions of the level
private agent: any = null; // (@Car class) for the agent
// Object to store all the roads assets
// Each road is accesible as follow this.roads[[my, mx]]
// with my and mx the (x, y) position relative to the map (not pixel).
private roads: Roads;
private cars: any; // Array used to store all the cars assets
private loop: any; // Loop method called for each render
private am: AssetManger; // The AssetManger is used to set most of the elements on the map
private canvasId: string; // Id of the target canvas
constructor(levelName: string, canvasId: string) {
/*
@levelName: Name of the level to load (.json)
*/
this.name = levelName;
this.canvasId = canvasId;
this.am = new AssetManger(this);
}
load(loop: any){
/*
Load the map from the file given in the constructor of the class
@loop (Method) This method will be call for each render
*/
this.loop = loop;
return new Promise((resolve, reject) => {
// Load the level
U.loadJSON(LEVEL_FOLDER + this.name, (response: string) => {
// Parse JSON string into object
this.info = JSON.parse(response);
this.map = this.info.map; // Store the map to let it accessible faster later on.
this.createLevel(this.info).then(() => resolve());
});
});
}
createLevel(info: LevelInfo){
/*
Create the level
@info Information about the level
*/
//Create the Pixi Application
this.app = new PIXI.Application({
width: this.map[0].length * ROADSIZE,
height: this.map.length * ROADSIZE,
backgroundColor: 0x80bf3e
}
);
// Append the app to the body
document.getElementById(this.canvasId).appendChild(this.app.view);
return new Promise((resolve, reject) => {
Loader.add(["textures/textures.json", "textures/textures.png"]).load(() => {
this.setup(info); // Set up the level (Add assets)
resolve();
});
});
}
setup(info: LevelInfo){
/*
Setup all the element of the map
*/
// Load the textures file
let textures = Loader.resources[JSON_TEXTURES].textures;
// Load all elements of the car
this.am.createMap(this.map, info, textures);
this.am.createCars(this.map, info, textures);
if (info.agent)
this.agent = this.am.createAgent(this.map, info, textures);
// Set up the main loop
this.app.ticker.add((delta: number) => this.loop(delta));
}
render(){
/*
If the rendering has been stopped, this Method
restart the rendering.
*/
this.app.ticker.start();
}
reset(){
/*
Reset the game.
For the moment, only the agent position is reset.
TODO: Reset the positions of the others car too to avoid
useless collisions.
*/
this.agent.reset();
}
setReward(agent_col: any, on_road: any, action: any){
/*
TODO: Let's the reward define in the agent class
*/
let reward = -0.1;
if (action == 0 || this.agent.core.vx == 1)
reward += 0.5;
if (agent_col.length > 0){
reward = -10;
}
else if (!on_road){
reward = -10;
}
return reward;
}
step(delta: number, action:number=null){
/*
Process one step into the environement
@delta (Float) time since the last update
@action: (Integer) The action to take (can be null if no action)
*/
// Go through all cars to move each one
for (var c = 0; c < this.cars.length; c++) {
if (this.cars[c].lidar && !this.cars[c].core.agent) // If this car can move
this.cars[c].step(delta);
}
// Move the agent
if (this.agent){
let {agent_col, on_road} = this.agent.step(delta, action);
// Get the reward
let reward = this.setReward(agent_col, on_road, action);
return reward;
}
return 0;
}
stopRender(){
/**
* Stop to render the canvas
*/
this.app.ticker.stop();
}
addChild(child: any){
/**
* Add child to the app
*/
this.app.stage.addChild(child);
}
addRoad(road: RoadSprite){
/*
Add road using the mx and my positions
*/
this.roads[[road.my.toString(), road.mx.toString()].toString()] = road;
this.envs.push(road);
this.app.stage.addChild(road);
}
addCar(car: Car){
/**
* Add car to the level
*/
this.cars.push(car);
this.app.stage.addChild(car.core);
this.envs.push(car.core);
}
getRoad(my: number, mx: number){
return this.roads[[my.toString(), mx.toString()].toString()];
}
findCarById(id: number){
/*
Find car by @id
*/
return this.cars.find((e: any) => {return e.car_id == id});
}
getEnvs(): any[] {
/**
* Return the list of envs
* /
*/
return this.envs;
}
getMap(): (string|number)[][] {
return this.map;
}
}
+121
View File
@@ -0,0 +1,121 @@
/*
Main class of the project
*/
import {Level} from "./level";
export class MetaCar {
private isPlaying: boolean;
private agent: any;
private level: Level;
constructor() {
}
load(level: string, agent: any){
/*
Load the environement
@level (String) Name of the json level to load
@agent (Agent class)
*/
this.isPlaying = false;
this.agent = agent;
this.level = new Level(level);
this.level.load((delta: number) => this.loop(delta));
document.getElementById("train").addEventListener("click", () => {
this.level.app.ticker.stop(); // .add(delta => this.loop(delta));
this.agent.train(this);
});
document.getElementById("stop").addEventListener("click", () => {
this.isPlaying = false;
this.agent.stop();
this.level.render();
});
document.getElementById("reset").addEventListener("click", () => {
this.level.reset();
});
document.getElementById("play").addEventListener("click", () => {
this.isPlaying = true
});
document.getElementById("saveAgent").addEventListener("click", () => {
this.agent.save(this);
});
document.getElementById("dumpFile").addEventListener("change", (e) => {
//readDump(e, (content) => this.agent.restore(this, content));
});
}
render(){
/*
Render the environement
again
*/
this.level.render();
}
save(content: any, file_name: string){
/*
Save the agent
*/
saveAs(content, file_name);
}
actionSpace(){
/*
Get the possible action to do in the environement
Ex: [0, 1, 2]
*/
return this.level.agent.motion.actionSpace();
}
getState(){
/*
Get the state of this environement
*/
return this.level.agent.getState();
}
step(action: number){
/*
Step into the environement
@action (Integer)
*/
return this.level.step(1, action);
}
reset(){
/*
Reset the agent position
*/
this.level.reset();
}
randomRoadPosition(){
/*
This position
*/
this.level.agent.last_position = [];
let keys = Object.keys(this.level.roads);
keys.sort(function() {return Math.random()-0.5;});
for (let k in keys){
let road = this.level.roads[keys[k]];
if (road.cars.length == 0){
road.setCarPosition(this.level.agent.core);
break;
}
}
}
loop(delta: number){
if (this.isPlaying){
this.agent.play(this);
}
else {
this.level.step(delta);
}
document.getElementById("rewardDisplay").innerHTML = this.level.last_reward;
}
}
+104
View File
@@ -0,0 +1,104 @@
/*
Motion engine
Parent of all motion engines classes
*/
import {Level} from "./level";
import {
MAP
} from "./global";
export interface MotionOption{
readonly rotationStep?: number;
readonly actions: string[];
}
export class MotionEngine {
protected level: Level;
protected car: any;
protected lidar: any;
protected state: any;
constructor(level: Level) {
this.level = level;
}
protected boxesIntersect(a: any, b: any) {
/*
Chack if a the two elements intersect each others
@a (Pixi sprite)
@b (Pixi sprite)
*/
var ab = a.getBounds();
var bb = b.getBounds();
return ab.x + ab.width > bb.x && ab.x < bb.x + bb.width && ab.y + ab.height > bb.y && ab.y < bb.y + bb.height;
}
protected detectInteractions(){
/*
Detect the interaction with the environement
- Car collisions
- Lidar collisions
- Road position (Is the vehicle on the road)
*/
let envs = this.level.getEnvs();
let agent_col = [];
let lidar_collisions = [];
let on_road = false;
for (let i = 0; i < envs.length; i++) {
if (envs[i] != this.car && envs[i].obstacle && this.boxesIntersect(envs[i], this.car)){
agent_col.push(envs[i]);
}
else if (envs[i].map_id == MAP.ROAD && this.boxesIntersect(envs[i], this.car)){
on_road = true;
}
if (this.boxesIntersect(envs[i], this.lidar)){
lidar_collisions.push(envs[i]);
}
}
this.setState(lidar_collisions);
return {agent_col, on_road};
}
protected setState(lidar_collisions: any){
/*
Set up the current state of the agent
@lidar_collisions List with all possible lidar collisions
*/
let state = [];
let f = 0.1;
let pt_id = 0;
for (var i = 0; i < this.lidar.children.length; i++) {
this.lidar.children[i].alpha = 0.1;
if (this.lidar.children[i].pt){ // If this is a lidar point
let pt_y = Math.round(pt_id/this.lidar.pts);
let pt_x = Math.round(pt_id%this.lidar.pts);
this.state[pt_y][pt_x] = MAP.DEFAULT;
for (var a = 0; a < lidar_collisions.length; a++) {
if (lidar_collisions[a] != this.car){
let touch = this.boxesIntersect(lidar_collisions[a], this.lidar.children[i]);
if (touch && lidar_collisions[a].obstacle){
// If this is an obstacle
this.lidar.children[i].alpha = 1.;
}
if (touch && (lidar_collisions[a].map_id > this.state[pt_y][pt_x] || (lidar_collisions[a].map_id == MAP.ROAD && this.state[pt_y][pt_x]==MAP.DEFAULT))){
// Add this interaction to the state
// If this interaction is more important than the one befor
// we kept the more important one
this.state[pt_y][pt_x] = lidar_collisions[a].map_id;
}
}
}
pt_id += 1;
}
}
}
};
+168
View File
@@ -0,0 +1,168 @@
/*
File with some usefull methods
*/
export function loadJSON(url: string, callback: any) {
/*
Utils method to load a json on the server
@url: Url to the json file to load
@callback: Method to call when the json file is loaded
*/
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', url, true); // Replace 'my_data' with the path to your file
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == 200) {
// Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
callback(xobj.responseText);
}
};
xobj.send(null);
}
export function mod(n: number, m:number) {
return ((n % m) + m) % m;
}
export function argMax(array: number[]) {
/*
Return the argmax of an array.
*/
if (array.every((v) => v == -Infinity)){
return Math.floor(Math.random()*array.length);
}
return array.map((x, i) => [x, i]).reduce((r, a) => (a[0] > r[0] ? a : r))[1];
}
export function mean(array: number){
/**
* Return the mean of the array
*/
if (array.length == 0)
return null;
var sum = array.reduce(function(a, b) { return a + b; });
var avg = sum / array.length;
return avg;
}
function saveAs(content: string, name: string) {
var a = document.createElement("a");
//a.style = "display: none";
document.body.appendChild(a);
var blob = new Blob([content], { type: 'application/octet-binary' }),
tmpURL = window.URL.createObjectURL(blob);
a.href = tmpURL;
a.download = name;
a.click();
window.URL.revokeObjectURL(tmpURL);
a.href = "";
}
/*
function readBuffer(buffer) {
var reader = new BinaryReader(buffer)
console.log(reader);
var validation = reader.getUint8()
if (validation !== BinaryWriter.validationByte) {
throw "validation byte doesn't match."
}
var tocLength = reader.getUint32();
var tocString = reader.getString(tocLength);
var toc = JSON.parse(tocString);
var contents = [];
for (var i = 0; i < toc.length; i++) {
switch (toc[i].t) {
case 's': contents.push(reader.getString(toc[i].l)); break
case 't': contents.push(new (typeof window !== 'undefined' ? window : global)[toc[i].i](reader.getBuffer(toc[i].l))); break
case 'b': contents.push(reader.getBuffer(toc[i].l)); break
}
if (toc[i]['o'] === 1) {
contents[i] = JSON.parse(contents[i]);
}
}
return contents
}
*/
/*
function readDump(e, callback) {
var input = e.target;
var file = input.files[0];
var reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = (evt) => {
callback(evt.target.result);
}
}
*/
export function shuffleArray(array: number[]) {
/**
* Shuffle an @array of numbers
*/
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
/*
function randomChoice(p) {
let rnd = p.reduce( (a, b) => a + b ) * Math.random();
return p.findIndex( a => (rnd -= a) < 0 );
}
*/
export interface KeyBoard{
[key: string]: any;
}
export function keyboard(keyCode: any ) {
let key: KeyBoard = {};
key.code = keyCode;
key.isDown = false;
key.isUp = true;
key.press = undefined;
key.release = undefined;
key.downHandler = (event: any) => {
if (event.keyCode === key.code) {
if (key.isUp && key.press) key.press();
key.isDown = true;
key.isUp = false;
}
event.preventDefault();
};
key.upHandler = (event: any) => {
if (event.keyCode === key.code) {
if (key.isDown && key.release) key.release();
key.isDown = false;
key.isUp = true;
}
event.preventDefault();
};
//Attach event listeners
window.addEventListener(
"keydown", key.downHandler.bind(key), false
);
window.addEventListener(
"keyup", key.upHandler.bind(key), false
);
return key;
}
+30
View File
@@ -0,0 +1,30 @@
{
"compilerOptions": {
"module": "ES2015",
"moduleResolution": "node",
"noImplicitAny": true,
"sourceMap": true,
"removeComments": true,
"preserveConstEnums": true,
"declaration": true,
"target": "es5",
"lib": ["es2015", "dom"],
"outDir": "./dist-es6",
"noUnusedLocals": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedParameters": false,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"allowUnreachableCode": false,
"experimentalDecorators": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"demo"
]
}
+35
View File
@@ -0,0 +1,35 @@
const path = require('path');
var config = {
entry: './src/index',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
},
],
},
resolve: {
extensions: [
'.ts',
],
}
};
var packageConfig = Object.assign({}, config, {
output: {
filename: 'metacar.min.js',
path: path.resolve(__dirname, './dist'),
}
});
var demoConfig = Object.assign({}, config,{
output: {
filename: 'metacar.min.js',
path: path.resolve(__dirname, './demo/dist'),
}
});
module.exports = [
packageConfig, demoConfig,
];