Full city: ok

This commit is contained in:
Thibault Neveu
2018-06-08 11:32:49 +01:00
parent 6999a8532e
commit 3b5eaf35a8
24 changed files with 1535 additions and 102 deletions
+6
View File
@@ -0,0 +1,6 @@
*.baseDir.ts*
node_modules/*
dist/dist-es6/*
demo/dist/dist-es6/*
demo/node_modules/
*package-lock.json*
+10 -22
View File
@@ -1,49 +1,38 @@
'use strict';
var fs = require('fs');
// [START app]
var fs = require('fs');
const express = require('express');
var path = require("path");
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) {
if (filename.indexOf(filter)>=0) {
files_list.push(filename);
};
};
return files_list;
}
function get_path(file){
return path.join(path.join(__dirname, "webapp/"), file);
}
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"));
});
/**
* Create all HTML routes
* **/
const files = fromDir(path.join(__dirname, "webapp/"),'.html');
files.forEach(file => {
let route = file.split("/");
@@ -61,4 +50,3 @@ app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
// [END app]
+257
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -9,6 +9,7 @@
"author": "Thibault Neveu",
"license": "ISC",
"dependencies": {
"express": "^4.16.3"
"express": "^4.16.3",
"pixi.js": "^4.8.1"
}
}
+20
View File
@@ -5,6 +5,26 @@
<title>Hello World</title>
</head>
<body>
<div class="canvas" id="canvas"></div>
<div class="mainButton">
<button id="train">Train</button>
<button id="play">Play</button>
<button id="stop">Stop</button>
<button id="reset">Reset</button>
<button id="saveAgent">Save</button>
<button onclick="document.getElementById('dumpFile').click();">Load</button>
<input id="dumpFile" type='file' accept='*/*' style="display: none">
<br>
<br>
<span id="rewardDisplay"></span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.7.1/pixi.min.js"></script>
<script src="/dist/metacar.min.js"></script>
<script type="text/javascript" src="/public/js/full_city.js"></script>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
// Get the url of the desired level
let levelUrl = metacar.level.fullCity;
// Create the environement (canvasID, levelUrl)
var env = new metacar.env("canvas", levelUrl);
env.load().then(() => {
// The level is loaded
});
+183
View File
@@ -0,0 +1,183 @@
{"frames": {
"bench.png":
{
"frame": {"x":0,"y":0,"w":31,"h":16},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":31,"h":16},
"sourceSize": {"w":31,"h":16},
"pivot": {"x":0.5,"y":0.5}
},
"car.png":
{
"frame": {"x":31,"y":0,"w":42,"h":24},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":42,"h":24},
"sourceSize": {"w":42,"h":24},
"pivot": {"x":0.5,"y":0.5}
},
"car_agent.png":
{
"frame": {"x":73,"y":0,"w":42,"h":24},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":42,"h":24},
"sourceSize": {"w":42,"h":24},
"pivot": {"x":0.5,"y":0.5}
},
"house.png":
{
"frame": {"x":0,"y":469,"w":88,"h":62},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":88,"h":62},
"sourceSize": {"w":88,"h":62},
"pivot": {"x":0.5,"y":0.5}
},
"house2.png":
{
"frame": {"x":0,"y":264,"w":60,"h":85},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":85},
"sourceSize": {"w":60,"h":85},
"pivot": {"x":0.5,"y":0.5}
},
"house3.png":
{
"frame": {"x":0,"y":531,"w":89,"h":67},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":89,"h":67},
"sourceSize": {"w":89,"h":67},
"pivot": {"x":0.5,"y":0.5}
},
"road_d2.png":
{
"frame": {"x":0,"y":349,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_l2.png":
{
"frame": {"x":60,"y":264,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_r.png":
{
"frame": {"x":0,"y":409,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_r2.png":
{
"frame": {"x":60,"y":409,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_rotate_dl.png":
{
"frame": {"x":60,"y":349,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_rotate_ld.png":
{
"frame": {"x":60,"y":204,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_rotate_rl.png":
{
"frame": {"x":0,"y":84,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_rotate_ul.png":
{
"frame": {"x":48,"y":24,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_rp.png":
{
"frame": {"x":60,"y":84,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_u.png":
{
"frame": {"x":0,"y":204,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_u2.png":
{
"frame": {"x":0,"y":144,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"road_up.png":
{
"frame": {"x":60,"y":144,"w":60,"h":60},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":60,"h":60},
"sourceSize": {"w":60,"h":60},
"pivot": {"x":0.5,"y":0.5}
},
"tree.png":
{
"frame": {"x":0,"y":24,"w":48,"h":46},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":48,"h":46},
"sourceSize": {"w":48,"h":46},
"pivot": {"x":0.5,"y":0.5}
}},
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "textures.png",
"format": "RGBA8888",
"size": {"w":120,"h":598},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:8bfcaef7ab9440b3c4c801d035ca0e1a:71d4ef3f85e164cd8a50ee71cd767fd7:cf827d152df04949dfee256cd0c8efd8$"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+257
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -17,6 +17,7 @@
},
"scripts": {
"build": "./node_modules/.bin/webpack-cli --mode production",
"build-dev": "./node_modules/.bin/webpack-cli --mode development",
"watch": "./node_modules/.bin/webpack-cli --watch --mode development"
}
}
+27 -17
View File
@@ -14,6 +14,7 @@ import {CarOptions, Car} from "./car";
import {BasicMotionEngine} from "./basic_motion_engine";
import {ControlMotionEngine} from "./control_motion_engine";
import {BotMotionEngine} from "./bot_motion_engine";
import { Editor } from "./editor";
export interface AssetInfo {
readonly mx?: number;
@@ -38,6 +39,7 @@ export interface RoadSprite extends PIXI.Sprite {
// (x, y position) Relatif to the map
mx?: number;
my?: number;
isremove?: boolean;
}
export interface SimpleSprite extends PIXI.Sprite {
@@ -45,17 +47,18 @@ export interface SimpleSprite extends PIXI.Sprite {
// name of the asset (not the name of the image)
type?: string;
mapId?: number;
isremove?: boolean;
}
export class AssetManger {
private level: Level;
private level: Level|Editor;
// 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[] = [];
public assets: SimpleSprite[] = [];
constructor(level: Level) {
constructor(level: Level|Editor) {
this.level = level;
this.motion = {
"BasicMotionEngine": BasicMotionEngine,
@@ -111,7 +114,8 @@ export class AssetManger {
*/
if (line == undefined)
line = 0;
if (road.cars.length >= 1 && this.level.findCarById(road.cars[0]).core.line == line){
let carById = this.level.findCarById(road.cars[0]);
if (road.cars.length >= 1 && carById && carById.core.line == line){
line = line == 0 ? 1:0;
}
if (road.cars.length >= 2){
@@ -129,10 +133,10 @@ export class AssetManger {
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));
road.orientation = (Math.floor(road.orientation / car.rotationStep)*car.rotationStep);
let th = -road.orientation*(Math.PI); // theta
let x_m = (Math.cos(th) * x_margin) - (y_margin * Math.sin(th));
let y_m = (Math.cos(th) * y_margin) + (x_margin * Math.sin(th));
let line_side_factor = line == 0 ? 1:-1;
let line_side_factor_t = line == 0 ? 0:Math.PI;
@@ -143,7 +147,7 @@ export class AssetManger {
car.mx = Math.floor(car.x / ROADSIZE);
car.my = Math.floor(car.y / ROADSIZE);
// Car rotation
car.rotation = (theta+line_side_factor_t);
car.rotation = (th+line_side_factor_t);
// Set the new road of the car
car.checkAndsetNewRoad(road);
}
@@ -169,7 +173,7 @@ export class AssetManger {
this.assets.push(asset);
}
createMap(map: (string|number)[][], info: LevelInfo, textures: PIXI.Texture, roadside:boolean=true){
createMap(map: (string|number)[][], info: LevelInfo, textures: (PIXI.Texture|PIXI.loaders.TextureDictionary), roadside:boolean=true){
/*
Method used to create the map with all assets (except the cars)
@map (2dim Array)
@@ -204,7 +208,7 @@ export class AssetManger {
}
}
createCars(map: (string|number)[][], info: LevelInfo, textures: PIXI.Texture){
createCars(map: (string|number)[][], info: any, textures: (PIXI.Texture|PIXI.loaders.TextureDictionary)){
/*
Method used to create all the cars
@map (2dim Array)
@@ -213,7 +217,7 @@ export class AssetManger {
*/
// 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}};
let options: CarOptions = {lidar: true, lidarInfo: {pts: 2, width: 0.5, height: 1, pos: 1}};
if (info.cars[c].auto){
options.lidar = true;
options.motionEngine = new BotMotionEngine(this.level);
@@ -221,10 +225,11 @@ export class AssetManger {
let n_car = new Car(this.level, info.cars[c], textures, options);
// Append the car to the canvas
this.level.addCar(n_car);
//this.level.addChild(n_car.lidar);
}
}
createAgent(map: (string|number)[][], info: LevelInfo, textures: PIXI.Texture){
createAgent(map: (string|number)[][], info: any, textures: (PIXI.Texture|PIXI.loaders.TextureDictionary)){
/*
Method used to create the agent's car.
@map (2dim Array)
@@ -244,6 +249,10 @@ export class AssetManger {
return agent;
}
addAsset(asset: any){
this.assets.push(asset);
}
exportMap(width: number, height: number, file_name: string){
/*
Export the map
@@ -251,8 +260,9 @@ export class AssetManger {
@height (Integer) Height of the new map
@file_name (String) name of the file to export
*/
var file = {"cars": []};
var file: any = {"cars": []};
let map = [];
let envs = this.level.getEnvs();
for (var y = 0; y < height; y++) {
let line = [];
for (var x = 0; x < width; x++) {
@@ -260,8 +270,8 @@ export class AssetManger {
}
map.push(line);
}
for (var e = 0; e < this.level.envs.length; e++) {
let elem = this.level.envs[e];
for (var e = 0; e < envs.length; e++) {
let elem = envs[e];
if (!elem.isremove){
// Add the cars
if (elem.mapId == MAP.CAR && !elem.agent && elem.is_valid){
@@ -300,7 +310,7 @@ export class AssetManger {
"motion": {
"type": "BasicMotionEngine",
"options":{
"rotation_step": 0.5,
"rotationStep": 0.5,
"actions": ["UP", "LEFT", "RIGHT", "DOWN", "WAIT"]
}
}
+17 -11
View File
@@ -4,6 +4,7 @@ import {Level} from "./level";
import {
MAP, ROADSIZE
} from "./global";
import { Editor } from "./editor";
export class BotMotionEngine extends MotionEngine {
/*
@@ -18,7 +19,7 @@ export class BotMotionEngine extends MotionEngine {
private map: (string|number)[][];
private rotationToNextCase: any;
constructor(level: Level) {
constructor(level: Level|Editor) {
super(level);
this.map = this. level.getMap();
@@ -35,7 +36,7 @@ export class BotMotionEngine extends MotionEngine {
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};
//this.rotationToNextCase[undefined] = {"mx": 0, "my": 0};
}
setUp(car: any, lidar: any){
@@ -93,7 +94,7 @@ export class BotMotionEngine extends MotionEngine {
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){
if (ny < 0 || ny >= this.mapSizeY || nx < 0 || nx >= this.mapSizeX || this.map[ny][nx] == 0){
return false;
}
return true;
@@ -114,7 +115,7 @@ export class BotMotionEngine extends MotionEngine {
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.haveTurned = true;
this.car.rotation = this.car.rotation += Math.PI/2;
this.car.rotation = this.car.rotation % (2*Math.PI);
this.car.next_road = undefined;
@@ -124,7 +125,7 @@ export class BotMotionEngine extends MotionEngine {
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.haveTurned = true;
this.car.rotation = this.car.rotation -= Math.PI/2;
this.car.rotation = this.car.rotation % (2*Math.PI);
this.car.next_road = undefined;
@@ -134,7 +135,7 @@ export class BotMotionEngine extends MotionEngine {
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.haveTurned = true;
this.car.rotation = this.car.rotation -= Math.PI;
this.car.rotation = this.car.rotation % (2*Math.PI);
@@ -176,19 +177,24 @@ export class BotMotionEngine extends MotionEngine {
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.haveTurned && !this.car.turnedRandom){
this.car.turnedRandom = 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)){
else if(!this.car.haveTurned && this.car.turnedRandom < 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)){
else if(!this.car.haveTurned && this.car.turnedRandom > 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 next_pos;
if (possibles_rotations[p] != null){
next_pos = this.rotationToNextCase[possibles_rotations[p]];
} else{
next_pos = {"mx": 0, "my": 0};
}
let px = this.car.mx + next_pos.mx;
let py = this.car.my + next_pos.my;
if (this.isRoad(px, py)){
+16 -8
View File
@@ -4,7 +4,8 @@
This class create the agent and the associated sensor (lidar)
*/
import {Level, LevelInfo} from "./level";
import {Level} from "./level";
import {Editor} from "./editor";
import {
CAR_IMG, Sprite, MAP, ROADSIZE, Container, Graphics
@@ -49,17 +50,22 @@ export interface CarSprite extends PIXI.Sprite {
agent?: boolean;
}
export interface LidarChild extends PIXI.Graphics {
// Is a lidar point
pt?: boolean;
}
export class Car {
public level: Level;
public level: Level|Editor;
public core: CarSprite;
public lidar: any;
private info: CarInfo;
private motion: any;
private turnedRandom: any;
public turnedRandom: any;
constructor(level: Level, info: CarInfo, textures: any, options:CarOptions={}) {
constructor(level: Level|Editor, info: CarInfo, textures: any, options:CarOptions={}) {
/*
@level (Level class)
@info: (CarInfo) Info from the json file about the car
@@ -200,9 +206,9 @@ export class Car {
@height: Height of the lidar (in proportion to the car)
*/
this.lidar = new Container();
// Area of the lidar
let area = new Graphics();
let area: LidarChild = new Graphics();
area.pt = false;
area.alpha = 0.5;
area.beginFill(0x515151);
area.drawRect(0, 0, this.core.width*lidarOptions.width, this.core.height*lidarOptions.height);
@@ -216,8 +222,8 @@ export class Car {
let x = (x_step/4) + xs * x_step;
let y = (y_step/4) + ys * y_step;
let pt = new Graphics();
let pt: LidarChild = new Graphics();
pt.pt = true;
pt.beginFill(0xffffff);
pt.drawRect(x, y, 5, 5);
pt.endFill();
@@ -227,8 +233,10 @@ export class Car {
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;
+332
View File
@@ -0,0 +1,332 @@
import { LevelInfo, Roads } from "./level";
import { AssetManger, RoadSprite } from "./asset_manager";
import { Car} from "./car";
import {Loader, Sprite, JSON_TEXTURES, ROADSIZE, MAP, LEVEL_FOLDER} from "./global";
import * as U from "./utils";
/*
@Level class
This is the core of game, the class is used create all the differents
services in the game (assets, map, agents...).
*/
export class Editor {
public app: PIXI.Application = null; // The pixi app.
// Informations on the level
private levelName: string;
private info: LevelInfo = null;
private envs: any[] = [] // Used to list all elements the car can crash with
public am: AssetManger;
public map: (string|number)[][] = null; // Use the roads positions of the level
public 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 cars: Car[] = []; // Array used to store all the cars assets
private loop: any = null; // Loop method called for each render
// Current selector items by the editor
public selectedItems: any = null;
private canvasId: string; // Id of the target canvas
private roads: Roads = {};
constructor(levelName: string, canvasId: string) {
/*
@levelName (String) Name of the level to load (.json)
*/
this.levelName = levelName;
this.canvasId = canvasId;
this.am = new AssetManger(this);
}
selectedItem(item: any){
/*
Method to select an items.
Once an item is selected, the item stick to the current
mouse position
@items (Object)
@img (String)
@type (String)
*/
if(this.selectedItems){
this.app.stage.removeChild(this.selectedItems);
}
let textures = Loader.resources[JSON_TEXTURES].textures;
this.selectedItems = new Sprite(textures[item.image]);
this.selectedItems.type = item.type;
this.selectedItems.image = item.image;
this.selectedItems.data = item.data;
// The road is not an obstacle
this.selectedItems.x = 0;
this.selectedItems.y = 0;
this.app.stage.addChild(this.selectedItems);
}
removeItem(item: any){
if (item.lidar){
this.app.stage.removeChild(item.lidar);
}
if (item.map_id == MAP.CAR){
item.road.cars.splice(item.road.cars.indexOf(item.car_id), 1);
}
if (item.agent)
this.agent = undefined;
this.app.stage.removeChild(item);
item.isremove = true;
}
setCurrentItem(){
this.am.addAsset(this.selectedItems);
let textures = Loader.resources[JSON_TEXTURES].textures;
var that = this;
if (this.selectedItems.type == "road"){
this.am.createRoad({
mx: Math.floor(this.selectedItems.x / ROADSIZE),
my: Math.floor(this.selectedItems.y / ROADSIZE),
type: this.selectedItems.data
}, textures);
this.envs[this.envs.length - 1].interactive = true;
this.envs[this.envs.length - 1].on("rightclick", function(this: any) {that.removeItem(this)});
}
else if (this.selectedItems.type == "car"){
this.am.createCars(this.map, {cars:[{
"mx": Math.floor(this.selectedItems.x / ROADSIZE),
"my": Math.floor(this.selectedItems.y / ROADSIZE),
"rotation": 0,
"line": 0,
}]}, textures);
this.envs[this.envs.length - 1].interactive = true;
this.envs[this.envs.length - 1].on("rightclick", function(this: any) {that.removeItem(this)});
}
else if (this.selectedItems.type == "agent" && this.agent == undefined){
this.agent = this.am.createAgent(this.map, {agent:{
"mx": Math.floor(this.selectedItems.x / ROADSIZE),
"my": Math.floor(this.selectedItems.y / ROADSIZE),
"rotation": 0,
"line": 0,
"motion": {
"type": "BasicMotionEngine",
"options":{
"rotation_step": 0.5,
"actions": ["UP", "LEFT", "RIGHT", "DOWN", "WAIT"]
}
}
}}, textures);
this.envs[this.envs.length - 1].interactive = true;
this.envs[this.envs.length - 1].on("rightclick", function(this: any) {that.removeItem(this)});
}
else if (this.selectedItems.type == "asset"){
this.am.createAsset(this.selectedItems.image, {x: this.selectedItems.x, y: this.selectedItems.y} ,textures, this.selectedItems.data);
this.am.assets[this.am.assets.length - 1].interactive = true;
this.am.assets[this.am.assets.length - 1].on("rightclick", function(this: any) {that.removeItem(this)});
}
this.app.stage.removeChild(this.selectedItems);
this.selectedItems = null;
}
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.levelName, (response: any) => {
// Parse JSON string into object
this.info = JSON.parse(response);
this.createLevel(this.info).then(() => resolve());
});
});
}
createLevel(info: LevelInfo){
/*
Create the level
@info (Object) Level's json.
*/
this.map = info.map; // Store the map to let it accessible faster later on.
//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: any){
/*
Setup all the element of the map
@info (Object) Level's json.
*/
// Load the textures file
let textures = Loader.resources[JSON_TEXTURES].textures;
// Load all elements of the car
this.am.createMap(this.map, info, textures, false);
this.am.createCars(this.map, info, textures);
if (info.agent)
this.agent = this.am.createAgent(this.map, info, textures);
var that = this;
for (var i = 0; i < this.envs.length; i++) {
this.envs[i].interactive = true;
this.envs[i].on("rightclick", function(this: any) {that.removeItem(this)});
}
for (var i = 0; i < this.am.assets.length; i++) {
this.am.assets[i].interactive = true;
this.am.assets[i].on("rightclick", function(this: any) {that.removeItem(this)});
}
// Set up the main loop
this.app.ticker.add(delta => this.loop(delta));
}
resize(width: number, height: number){
/*
Resize the map
*/
this.app.renderer.resize(width * ROADSIZE, height * ROADSIZE);
}
addChild(child: any){
/**
* Add child to the app
*/
this.app.stage.addChild(child);
}
addCar(car: Car){
/**
* Add car to the level
*/
this.cars.push(car);
this.app.stage.addChild(car.core);
this.envs.push(car.core);
}
getMap(): (string|number)[][] {
return this.map;
}
getEnvs(): any[] {
/**
* Return the list of envs
* /
*/
return this.envs;
}
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);
}
findCarById(id: number){
/*
Find car by @id
*/
return this.cars.find((e: any) => {return e.car_id == id});
}
getRoad(my: number, mx: number){
return this.roads[[my.toString(), mx.toString()].toString()];
}
getRoads(){
return this.roads;
}
}
/*
function createEditor(id: string){
// Get the DOM element to change the width/height of the map
const height_input = document.getElementById("height_input");
const width_input = document.getElementById("width_input");
var width = parseInt((<HTMLInputElement>width_input).value);
var height = parseInt((<HTMLInputElement>height_input).value);
// Create the editor class
let file = window.location.hash || "template.json";
file = file.slice(1, file.length)
console.log(file);
var editor = new Editor(file, "string");
// For the first init retrieve the widht and height of the map
var fist_init = false;
function init(){
width = editor.map[0].length;
height = editor.map.length;
(<HTMLInputElement>width_input).value = width.toString();
(<HTMLInputElement>height_input).value = height.toString();
}
function load(){
if (!fist_init){
fist_init = true;
init();
}
let mouse_position = editor.app.renderer.plugins.interaction.mouse.global;
if (editor.selectedItems && editor.selectedItems.type == "asset"){
editor.selectedItems.x = mouse_position.x;
editor.selectedItems.y = mouse_position.y;
}
else if (editor.selectedItems) {
editor.selectedItems.x = Math.floor(mouse_position.x/ROADSIZE) * ROADSIZE;
editor.selectedItems.y = Math.floor(mouse_position.y/ROADSIZE) * ROADSIZE;
}
}
// Game loop
editor.load((delta: any) => {
load();
});
var classname = document.getElementsByClassName("img_item");
for (var i = 0; i < classname.length; i++) {
classname[i].addEventListener('click', function(this: any){
editor.selectedItem({image : this.getAttribute('data-img'), type: this.getAttribute('data-type'), data: this.getAttribute("data-data")});
}, false);
}
width_input.onchange = () => {
width = parseInt((<HTMLInputElement>width_input).value);
editor.resize(width, height);
}
height_input.onchange = () => {
height = parseInt((<HTMLInputElement>height_input).value);
editor.resize(width, height);
}
document.getElementById("saveMap").onclick = () => {
editor.am.exportMap(width, height, file);
};
document.getElementById("canvas").addEventListener("click", () => {
if (editor.selectedItems){
editor.setCurrentItem();
}
});
document.getElementById("loadMap").addEventListener("click", () => {
window.location.href = "/editor.html#"+prompt("What is the name of the level ? (The file should be located in your level folder)");
location.reload();
});
document.getElementById("canvas").addEventListener('contextmenu', event => event.preventDefault());
}*/
+11
View File
@@ -0,0 +1,11 @@
import {fullCity} from "./embedded/level/full_city";
export const embeddedUrl: any = {
fullCity: "embedded://level/fullCity"
}
export const embeddedContent: any = {
level: {
fullCity: fullCity
}
}
+272
View File
@@ -0,0 +1,272 @@
export const fullCity: any = {
"cars": [
{
"mx": 2,
"my": 5,
"line": 0,
"auto": true
},
{
"mx": 3,
"my": 4,
"line": 0,
"auto": true
},
{
"mx": 3,
"my": 4,
"line": 1,
"auto": true
},
{
"mx": 6,
"my": 3,
"line": 0,
"auto": true
},
{
"mx": 6,
"my": 3,
"line": 1,
"auto": true
},
{
"mx": 8,
"my": 2,
"line": 0,
"auto": true
},
{
"mx": 8,
"my": 2,
"line": 1,
"auto": true
},
{
"mx": 2,
"my": 7,
"line": 0,
"auto": true
}
],
"house": [
{
"x": 9,
"y": 409
},
{
"x": 146,
"y": 162
},
{
"x": 439,
"y": 39
}
],
"house2": [
{
"x": 425,
"y": 192
},
{
"x": 431,
"y": 354
}
],
"house3": [
{
"x": 426,
"y": 281
},
{
"x": 51,
"y": 157
},
{
"x": 553,
"y": 34
}
],
"bench": [
{
"x": 259,
"y": 206
},
{
"x": 8,
"y": 205
}
],
"tree": [
{
"x": 1,
"y": 303
},
{
"x": 54,
"y": 357
},
{
"x": 215,
"y": 325
},
{
"x": 230,
"y": 400
},
{
"x": 282,
"y": 368
},
{
"x": 182,
"y": 433
},
{
"x": 285,
"y": 309
},
{
"x": 576,
"y": 233
},
{
"x": 581,
"y": 300
},
{
"x": 519,
"y": 388
},
{
"x": 582,
"y": 391
},
{
"x": 256,
"y": 69
},
{
"x": 151,
"y": 43
},
{
"x": 77,
"y": 77
},
{
"x": 26,
"y": 24
},
{
"x": 189,
"y": 98
}
],
"map": [
[
0,
0,
0,
0,
0,
0,
"↕",
0,
0,
0,
0
],
[
0,
0,
0,
0,
0,
0,
"↠",
0,
0,
0,
0
],
[
0,
0,
0,
0,
0,
0,
"↦",
"↔",
"↔",
"↟",
"↔"
],
[
0,
0,
0,
0,
0,
0,
"↕",
0,
0,
0,
0
],
[
"↔",
"↔",
"↧",
"↔",
"↠",
"↔",
"↤",
0,
0,
0,
0
],
[
0,
0,
"↕",
0,
0,
0,
"↕",
0,
0,
0,
0
],
[
0,
0,
"↠",
0,
0,
0,
"↠",
0,
0,
0,
0
],
[
0,
0,
"↕",
0,
0,
0,
"↕",
0,
0,
0,
0
]
]
}
+1 -1
View File
@@ -13,7 +13,7 @@ export const Graphics = PIXI.Graphics;
export const Container = PIXI.Container;
// Textures files
export const JSON_TEXTURES = "textures/textures.json";
export const JSON_TEXTURES = "public/textures/textures.json";
// Level foler
export const LEVEL_FOLDER = "/level/";
+8 -1
View File
@@ -1,2 +1,9 @@
import {MetaCar} from "./metacar";
export default MetaCar;
import {embeddedUrl} from "./embedded";
const metacar = {
env: MetaCar,
level: embeddedUrl
}
export default metacar;
+17 -18
View File
@@ -8,9 +8,9 @@ import * as U from "./utils";
import {
LEVEL_FOLDER, ROADSIZE, Loader, JSON_TEXTURES
} from "./global";
import {AssetManger, RoadSprite, SimpleSprite, AssetInfo, } from "./asset_manager";
import {AssetManger, RoadSprite} from "./asset_manager";
import {Car} from "./car";
import {Car, CarSprite} from "./car";
export interface LevelInfo {
map: (string|number)[][];
@@ -24,26 +24,27 @@ export interface Roads {
export class Level {
private app: any = null; // The pixi app.
private name: string = null; // Name fo the level
public app: any = null; // The pixi app.
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
public 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 roads: Roads = {};
private cars: Car[] = []; // 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) {
constructor(levelContent: LevelInfo, canvasId: string) {
/*
@levelName: Name of the level to load (.json)
@levelContent: Content of the level
@canvasID: HTML canvas id
*/
this.name = levelName;
this.info = levelContent;
this.map = this.info.map;
this.canvasId = canvasId;
this.am = new AssetManger(this);
}
@@ -55,13 +56,7 @@ export class Level {
*/
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());
});
this.createLevel(this.info).then(() => resolve());
});
}
@@ -81,7 +76,7 @@ export class Level {
document.getElementById(this.canvasId).appendChild(this.app.view);
return new Promise((resolve, reject) => {
Loader.add(["textures/textures.json", "textures/textures.png"]).load(() => {
Loader.add(["public/textures/textures.json", "public/textures/textures.png"]).load(() => {
this.setup(info); // Set up the level (Add assets)
resolve();
});
@@ -193,6 +188,10 @@ export class Level {
getRoad(my: number, mx: number){
return this.roads[[my.toString(), mx.toString()].toString()];
}
getRoads(){
return this.roads;
}
findCarById(id: number){
/*
+36 -9
View File
@@ -2,26 +2,52 @@
Main class of the project
*/
import {Level} from "./level";
import {Level, LevelInfo} from "./level";
import * as U from "./utils";
export class MetaCar {
private isPlaying: boolean;
private agent: any;
private level: Level;
private canvasId: string;
private levelUrl: string;
constructor() {
constructor(canvasId: string, levelUrl: string) {
/**
* @canvasId: HTML canvas ID to used
* @level: URL or Local storage URL.
* localstorage://level-name
* embedded://
* http(s)://
*/
if (!canvasId || this.levelUrl){
console.error("You must specify the canvasId and the levelUrl");
}
this.isPlaying = false;
this.canvasId = canvasId;
this.levelUrl = levelUrl;
}
load(level: string, agent: any){
load(level: string, agent: any): Promise<void>{
/*
Load the environement
@level (String) Name of the json level to load
@agent (Agent class)
*/
this.isPlaying = false;
return new Promise((resolve, reject) => {
U.loadCustomURL(this.levelUrl, (content: LevelInfo) => {
this.level = new Level(content, this.canvasId);
this.level.load((delta: number) => this.loop(delta));
resolve();
});
});
/*
this.agent = agent;
this.level = new Level(level);
this.level = new Level(level, "canvas");
this.level.load((delta: number) => this.loop(delta));
document.getElementById("train").addEventListener("click", () => {
@@ -45,6 +71,7 @@ export class MetaCar {
document.getElementById("dumpFile").addEventListener("change", (e) => {
//readDump(e, (content) => this.agent.restore(this, content));
});
*/
}
render(){
@@ -59,7 +86,7 @@ export class MetaCar {
/*
Save the agent
*/
saveAs(content, file_name);
U.saveAs(content, file_name);
}
actionSpace(){
@@ -97,10 +124,11 @@ export class MetaCar {
This position
*/
this.level.agent.last_position = [];
let keys = Object.keys(this.level.roads);
let roads = this.level.getRoads();
let keys = Object.keys(roads);
keys.sort(function() {return Math.random()-0.5;});
for (let k in keys){
let road = this.level.roads[keys[k]];
let road = roads[keys[k]];
if (road.cars.length == 0){
road.setCarPosition(this.level.agent.core);
break;
@@ -115,7 +143,6 @@ export class MetaCar {
else {
this.level.step(delta);
}
document.getElementById("rewardDisplay").innerHTML = this.level.last_reward;
}
}
+8 -9
View File
@@ -8,6 +8,7 @@ import {Level} from "./level";
import {
MAP
} from "./global";
import { Editor } from "./editor";
export interface MotionOption{
readonly rotationStep?: number;
@@ -16,12 +17,12 @@ export interface MotionOption{
export class MotionEngine {
protected level: Level;
protected level: Level|Editor;
protected car: any;
protected lidar: any;
protected state: any;
constructor(level: Level) {
constructor(level: Level|Editor) {
this.level = level;
}
@@ -53,7 +54,7 @@ export class MotionEngine {
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)){
else if (envs[i].mapId == MAP.ROAD && this.boxesIntersect(envs[i], this.car)){
on_road = true;
}
if (this.boxesIntersect(envs[i], this.lidar)){
@@ -70,16 +71,14 @@ export class MotionEngine {
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);
let pt_y = Math.floor(pt_id/this.lidar.pts);
let pt_x = Math.floor(pt_id%this.lidar.pts);
this.state[pt_y][pt_x] = MAP.DEFAULT;
for (var a = 0; a < lidar_collisions.length; a++) {
@@ -89,11 +88,11 @@ export class MotionEngine {
// 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))){
if (touch && (lidar_collisions[a].mapId > this.state[pt_y][pt_x] || (lidar_collisions[a].mapId == 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;
this.state[pt_y][pt_x] = lidar_collisions[a].mapId;
}
}
}
+31 -2
View File
@@ -1,3 +1,5 @@
import {embeddedContent} from "./embedded";
/*
File with some usefull methods
*/
@@ -19,6 +21,33 @@ export function loadJSON(url: string, callback: any) {
xobj.send(null);
}
export function loadEmbeddedURL(url: string, callback: any):void {
let split = url.split("//");
split = split[1].split("/");
callback(embeddedContent[split[0]][split[1]]);
}
export function loadLocalStorageURL(url: string, callback: any):void{
}
export function loadCustomURL(url: string, callback: any):void {
/**
* @url: CustomURL to load
* localstorage://level-name
* http(s)://
* embedded:://
@callback function onece the content is loaded
*/
const customCall: any = {
"embedded:": loadEmbeddedURL,
"http://": loadJSON,
"https://": loadJSON,
"localstorage://": loadLocalStorageURL
}
const split = url.split("//");
customCall[split[0]](url, callback);
}
export function mod(n: number, m:number) {
return ((n % m) + m) % m;
}
@@ -33,7 +62,7 @@ export function argMax(array: number[]) {
return array.map((x, i) => [x, i]).reduce((r, a) => (a[0] > r[0] ? a : r))[1];
}
export function mean(array: number){
export function mean(array: number[]){
/**
* Return the mean of the array
*/
@@ -44,7 +73,7 @@ export function mean(array: number){
return avg;
}
function saveAs(content: string, name: string) {
export function saveAs(content: string, name: string) {
var a = document.createElement("a");
//a.style = "display: none";
document.body.appendChild(a);
+1 -1
View File
@@ -10,7 +10,7 @@
"target": "es5",
"lib": ["es2015", "dom"],
"outDir": "./dist-es6",
"noUnusedLocals": true,
"noUnusedLocals": false,
"noImplicitReturns": true,
"noImplicitThis": true,
"alwaysStrict": true,
+14 -2
View File
@@ -1,4 +1,5 @@
const path = require('path');
var webpack = require('webpack')
var config = {
entry: './src/index',
@@ -7,26 +8,37 @@ var config = {
{
test: /\.ts$/,
use: 'ts-loader',
},
}
],
},
resolve: {
extensions: [
'.ts',
],
}
},
externals: [
// Don't bundle pixi.js, assume it'll be included in the HTML via a script
// tag, and made available in the global variable PIXI.
{"pixi.js": "PIXI"}
]
};
var packageConfig = Object.assign({}, config, {
output: {
filename: 'metacar.min.js',
path: path.resolve(__dirname, './dist'),
library: 'metacar',
libraryTarget: 'window',
libraryExport: 'default'
}
});
var demoConfig = Object.assign({}, config,{
output: {
filename: 'metacar.min.js',
path: path.resolve(__dirname, './demo/dist'),
library: 'metacar',
libraryTarget: 'window',
libraryExport: 'default'
}
});