mirror of
https://github.com/wassname/metacar.git
synced 2026-09-09 11:26:47 +08:00
WIP: js to ts
This commit is contained in:
@@ -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
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import {MetaCar} from "./metacar";
|
||||
export default MetaCar;
|
||||
+215
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
Reference in New Issue
Block a user