1 Commits
Author SHA1 Message Date
marc a17ee9aa89 added source code 2021-03-26 21:40:55 +01:00
93 changed files with 10940 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules/
.idea/
.DS_Store
-1
View File
@@ -13,4 +13,3 @@ yarn build
```
to bundle your application
# Asteroids
+100
View File
@@ -0,0 +1,100 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./models/client.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./models/client.js":
/*!**************************!*\
!*** ./models/client.js ***!
\**************************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("\n\n\n\n\n\n\nvar canvas = document.getElementById(\"canvas\");\n\nvar context = new ContextAdapter(canvas.getContext(\"2d\"));\n\n\ncontext.width = canvas.width = window.innerWidth;\ncontext.height = canvas.height = window.innerHeight;\n\ncontext.plane.units.position = new Vector(0.2, 0.2);\n\n// var name = prompt(\"name\");\nvar host = String(document.location);\nvar socket = io.connect(host) //, {query: \"name=\"+name});\n\n\n\n// var group = SuperGroup.random();\n// var game = new Game(group);\nvar game = Game.random();\n\n\n\ngameClient = new GameClient(canvas, game, socket)\n\ngameClient.main();\n\n\n\n//# sourceURL=webpack:///./models/client.js?");
/***/ })
/******/ });
+15
View File
@@ -0,0 +1,15 @@
class Activity {
constructor(components) {
this.components = components;
}
show(context) {
for(const component of this.components) {
component.show(context);
}
}
update(dt) {
for(const component of this.components) {
component.update(dt);
}
}
}
+8
View File
@@ -0,0 +1,8 @@
interface Anatomy {
// constructor: func;
// contains: func;
// rotate: func;
// move: func;
lineWidth: number;
str: string;
}
+8
View File
@@ -0,0 +1,8 @@
var Angle = {
toRadian: function(angle) {
return Math.pi*angle/180;
},
toDegree: function(angle) {
return 180*angle/Math.pi;
}
}
+57
View File
@@ -0,0 +1,57 @@
export default class Animation {
static on = false;
static state = 0;
constructor(object, on=Animation.on, state=Animation.state) {
this.object = object;
this.on = on;
this.state = state;
}
trigger() {
this.on = true;
this.start();
}
start() {
}
next() {
}
end() {
}
update(dt) {
throw 'The update method of an animation must be implemented.'
}
}
export class ColorChanger extends Animation {
constructor(entity, state, colors) {
super(entity, state);
this.colors = colors;
}
get color() {
const [c1, c2] = this.colors;
const v1 = Color.toVector(c1);
const v2 = Color.toVector(c2);
const v = v1.rmul(1-this.state).add(v2.rmul(this.state));
return Color.fromVector(v);
}
update(dt) {
if (this.on) {
if (this.state<1) {
this.next()
} else {
this.end();
}
}
}
next() {
this.state += dt;
this.object.form.color = this.color;
}
end() {
this.state = 0;
this.on = false;
}
}
+187
View File
@@ -0,0 +1,187 @@
import Form from './form.js'
export default class BasePolygon extends Form {
/**
* Radius of the smallest circle containing the base polygon
* which center is the center of the base polygon.
* Usefull for collision detection.
*/
static getRadiusBubble(points, center) {
let radius = 0;
let distance = 0;
for (const point of points) {
distance = Point.distance(point, center);
if (distance>radius) { // slightly faster than using max
radius = distance;
}
}
return radius;
}
constructor(color, lineWidth, fill) {
super();
this.color = color;
this.lineWidth = lineWidth;
this.fill = fill;
}
// get vectors() {
// throw "A base polygon must have a points getter."
// }
// set vectors() {
// throw "A base polygon must have a points getter."
// }
// get points() {
// throw "A base polygon must have a points getter."
// }
// set points(pts) {
// throw "A base polygon must have a points setter."
// }
get center() {
return Vector.average(...this.points);
}
set center(point) {
const v = point.sub(this.center)
this.translate(v);
}
/**
* Radius of the smallest circle containing the base polygon
* which center is the center of the base polygon.
* Usefull for collision detection.
*/
get hitBubbleRadius() {
const points = this.points;
const center = Vector.average(...points);
return this.constructor.getRadiusBubble(points, center);
}
/**
* Smallest circle containing the base polygon which center
* is the center of the polygon.
*/
get hitBubble() {
const points = this.points;
const center = Vector.average(...points);
const radius = this.constructor.getRadiusBubble(points, center);
return new Circle(center, radius, 0.1);
}
/**
* Smallest rectangle containing the base polygon.
*/
get hitBox() {
const points = this.points;
let xmin = Infinity;
let ymin = Infinity;
let xmax = -Infinity;
let ymax = -Infinity;
for (const p of points) {
if (p.x<xmin) { xmin = p.x; }
if (p.x>xmax) { xmax = p.x; }
if (p.y<ymin) { ymin = p.y; }
if (p.y>ymax) { ymax = p.y; }
}
return new Rectangle([xmin, ymin], [xmax-xmin, ymax-ymin], 0.1);
}
translate(vector) {
throw "A base polygon must have a translate method."
}
scale(n) {
throw "A base polygon must have a scale method."
}
move(...vector) {
this.translate(Vector.from(vector));
}
get length() {
return this.points.length;
}
show(ctx) {
if (this.fill) {
ctx.fillStyle = this.color;
} else {
ctx.strokeStyle = this.color;
}
if (this.lineWidth) {
ctx.lineWidth = this.lineWidth;
}
const points = this.points;
ctx.beginPath();
ctx.moveTo(...points[points.length-1]);
for (const pt of points) {
ctx.lineTo(...pt);
}
ctx.closePath();
if (this.fill) {
ctx.fill();
} else {
ctx.stroke();
}
}
get segments() {
const segments = [];
const points = this.points;
let p = points[this.points.length-1];
for (const pi of points) {
segments.push(new Segment(p, pi));
p = pi;
}
return segments;
}
set segments(segs) {
// this is naive mapping,
//because the information is redundant
this.points = segs.map(s => s.p1);
}
showPoints(ctx) {
for (const point of this.points) {
point.show(ctx);
}
}
showSegments(ctx) {
for (const segment of this.segments) {
segment.show(ctx);
}
}
get area() {
}
get perimeter() {
return Math.sum(this.segments.map(s => s.length));
}
contains2(point) {
// make a segment which must cross the polygon.
// then check if it crosses.
const s = new Segment(point, point.radd(0, this.perimeter))
}
contains(p) {
const points = this.points;
var isInside = false;
var minX = points[0].x, maxX = points[0].x;
var minY = points[0].y, maxY = points[0].y;
for (let n = 1; n < points.length; n++) {
let q = points[n];
minX = Math.min(q.x, minX);
maxX = Math.max(q.x, maxX);
minY = Math.min(q.y, minY);
maxY = Math.max(q.y, maxY);
}
if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {
return false;
}
let i = 0, j = points.length - 1;
for (i, j; i < points.length; j = i++) {
if ( (points[i].y > p.y) != (points[j].y > p.y) &&
p.x < (points[j].x - points[i].x) * (p.y - points[i].y) / (points[j].y - points[i].y) + points[i].x ) {
isInside = !isInside;
}
}
return isInside;
}
collide(polygon) {
for (const p of polygon.points) {
if (this.contains(p)) {
return true;
}
}
return false;
}
}
+124
View File
@@ -0,0 +1,124 @@
import Tensor from "./tensor.js";
import Motion from "./motion.js";
import Vector from "./vector.js";
export default class Body extends Tensor {
static n = 1;
static w = 2;
static h = 2;
static random(n=Body.n, w=Body.w, h=Body.h) {
let motions = new Tensor(n);
for (let i=0; i<n; i++) {
motions[i] = Motion.random(w, h);
}
return this.from(motions);
}
get motion() {
return this[0];
}
set motion(value) {
this[0] = value;
}
get moment() {
return this[1];
}
set moment(value) {
this[1] = value;
}
get x() {
return this.motion.x;
}
set x(v) {
this.motion.x = v;
}
get y() {
return this.motion.y;
}
set y(v) {
this.motion.y = v;
}
get z() {
return this.motion.z;
}
set z(v) {
this.motion.z = v;
}
get vx() {
return this.motion.vx;
}
set vx(v) {
this.motion.vx = v;
}
get vy() {
return this.motion.vy;
}
set vy(v) {
this.motion.vy = v;
}
get vz() {
return this.motion.vz;
}
set vz(v) {
this.motion.vz = v;
}
get ax() {
return this.motion.ax;
}
set ax(v) {
this.motion.ax = v;
}
get ay() {
return this.motion.ay;
}
set ay(v) {
this.motion.ay = v;
}
get az() {
return this.motion.az;
}
set az(v) {
this.motion.az = v;
}
get position() {
return this.motion.position;
}
set position(v) {
this.motion.position = v;
}
get velocity() {
return this.motion.velocity;
}
set velocity(v) {
this.motion.velocity = v;
}
get acceleration() {
return this.motion.acceleration;
}
set acceleration(v) {
this.motion.acceleration;
}
update(dt) {
for (let i=0; i<this.length; i++) {
this[i].update(dt);
}
// this.updateFriction(0.1)
// this.map(motion => motion.update(dt));
}
updateFriction(friction=0.1) {
for (const motion of this) {
motion.updateFriction(friction);
}
}
follow(vector) {
let norm = vector.sub(this.position).norm/10;
// norm = sigmoid(norm);
// norm = Math.min(Math.max(0, norm),1);
let angle = vector.sub(this.position).angle;
// this[0][2].angle = angle;
this[0][2] = Vector.polar(norm, angle);
// this.motion.acceleration = Vector.polar(norm, angle);
}
limit(vmin, vmax) {
this.motion[0].limit(vmin, vmax);
}
}
+8
View File
@@ -0,0 +1,8 @@
import Rectangle from './rectangle.js';
export default class Box extends Rectangle {
constructor(...ars, position, size) {
}
}
+58
View File
@@ -0,0 +1,58 @@
import Form from './form.js';
export default class Circle extends Form {
constructor(
center,
radius,
lineWidth=Form.lineWidth,
color=Form.color,
fill=Form.fill
) {
super();
this.center = center
this.radius = radius;
this.lineWidth = lineWidth;
this.color = color;
this.fill = fill;
}
get x() {
return this.center[0];
}
set x(value) {
this.center[0] = value;
}
get y() {
return this.center[1];
}
set y(value) {
this.center[1] = value;
}
get r() {
return this.radius;
}
set r(value) {
this.radius = value;
}
show(context) {
context.lineWidth = this.lineWidth;
if (this.fill) {
context.fillStyle = this.color;
} else {
context.strokeStyle = this.color;
}
context.beginPath();
context.arc(...this.center, this.radius, 0, 2*Math.PI);
if (this.fill) {
context.fill();
} else {
context.stroke();
}
context.closePath();
}
contains(position) {
return this.center.sub(position).norm < this.radius;
}
crossCircle(circle) {
return this.center.sub(circle.center).norm < this.radius+other.radius;
}
}
+12
View File
@@ -0,0 +1,12 @@
import Connexion from './connexion.js';
export default class Client extends Connexion {
constructor() {
self.url = url;
}
/* Return the url. */
getUrl() {
return
}
}
+92
View File
@@ -0,0 +1,92 @@
import Vector from './vector.js';
/*
* Color namespace.
*/
var Color = {
/*
* Return a random color.
*/
random: function() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i=0; i<6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
},
/*
* Create a vector using a color.
*/
toVector: function(color) {
const letters = '0123456789ABCDEF';
let v = new Vector();
for (let i=0; i<3; i++) {
let a = letters.indexOf(color[2*i+1].toUpperCase());
let b = letters.indexOf(color[2*i+2].toUpperCase());
v.push(a*16+b);
}
return v;
},
/*
* Create a color using a vector.
*/
fromVector: function(vector) {
const letters = '0123456789ABCDEF';
let v = vector.floor();
let color = "#";
for (let i=0; i<3; i++) {
let b = v[i]%16;
let a = (v[i]-b)/16;
color += letters[a];
color += letters[b];
}
return color;
},
/*
* Mix the given colors together.
*/
mix: function(...colors) {
let vectors = colors.map(Color.toVector);
let vector = Vector.average(...vectors);
return Color.fromVector(vector);
},
/*
* Ligthen a given color using some k factor which must be between 0 and 1.
*/
lighten: function(color, k=0.5) {
let v = Color.toVector(color);
let m = new Vector(255, 255, 255);
let d = m.sub(v);
d.irmul(k);
v.iadd(d);
return Color.fromVector(v);
},
/*
* Ligthen a given color using some k factor which must be between 0 and 1.
*/
darken: function(color, k=0.5) {
let v = Color.toVector(color);
let d = Vector.copy(v);
d.irmul(k);
v.isub(d);
return Color.fromVector(v);
},
/*
* Interpolate a color between a list of couples (color,value) using a t parameter between 0 and 1.
*/
interpolate: function(couples, t) {
let i = 0;
while (t>couples[i][1]) {
i+=1;
}
let v1 = Color.toVector(couples[i-1][0]);
let v2 = Color.toVector(couples[i][0]);
t = Color.linear(t, couples[i-1][1], couples[i][1], 0, 1);
return Color.fromVector(v1.rmul(1-t).add(v2.rmul(t)));
},
linear: function(x, e1, e2, s1, s2) {
return (x-e1)/(e2-e1)*(s2-s1)+s1;
},
}
+137
View File
@@ -0,0 +1,137 @@
import Plane from './plane.js';
import Vector from './vector.js';
export default class ContextAdapter {
constructor(context, plane=new Plane()) {
this.context = context;
this.plane = plane;
this.textSeparator = "px ";
}
toScreen(v) {
return this.plane.toScreen(v).add(this.center);
}
fromScreen(v) {
return this.plane.fromScreen(v.sub(this.center));;
}
get width() {
return this.context.width;
}
set width(value) {
this.context.width = value;
}
get height() {
return this.context.height;
}
set height(value) {
this.context.height = value;
}
get center() {
return new Vector(this.context.width/2, this.context.height/2);
}
set strokeStyle(value) {
this.context.strokeStyle = value;
}
get strokeStyle() {
return this.context.strokeStyle;
}
set fillStyle(value) {
this.context.fillStyle = value;
}
get fillStyle() {
return this.context.fillStyle;
}
set strokeWidth(value) {
this.context.strokeWidth = value;
}
get strokeWidth() {
return this.context.strokeWidth;
}
set lineWidth(value) {
this.context.lineWidth = value * Math.max(...this.plane.units.position);
}
get lineWidth() {
return this.context.lineWidth / Math.max(...this.plane.units.position);
}
beginPath() {
this.context.beginPath();
}
closePath() {
this.context.closePath();
}
stroke() {
this.context.stroke();
}
fill() {
this.context.fill();
}
translate(x, y) {
let v = this.plane.units.position.tdot(new Vector(x,y));
this.plane.position = v;
}
clear() {
this.context.clearRect(0, 0, this.context.width, this.context.height);
}
strokeRect(x, y, w, h) {
[x, y] = this.toScreen(new Vector(x,y));
[w, h] = this.plane.units.position.tdot(new Vector(w,h));
this.context.strokeRect(x, y, w, h);
}
fillRect(x, y, w, h, mg=0) {
[x, y] = this.toScreen(new Vector(x,y));
[w, h] = this.plane.units.position.tdot(new Vector(w,h));
this.context.fillRect(x, y, w+mg, h+mg);
}
clearRect(x, y, w, h) {
[x, y] = this.toScreen(new Vector(x,y));
[w, h] = this.plane.units.position.tdot(new Vector(w,h));
this.context.clearRect(x, y, w, h);
}
fillText(text, x, y) {
[x, y] = this.toScreen(new Vector(x,y));
this.context.fillText(text, x, y, 1000);
}
drawImage(img, x, y, w=undefined, h=undefined) {
if (w && h) {
[x, y] = this.toScreen(new Vector(x,y));
[w, h] = this.plane.units.position.tdot(new Vector(w,h));
this.context.drawImage(img, x, y, w, h);
} else {
[x, y] = this.toScreen(new Vector(x,y));
this.context.drawImage(img, x, y);
}
}
arc(x, y, r, a, b, conversion=true) {
[x, y] = this.toScreen(new Vector(x,y));
if (conversion) {
r *= Math.max(...this.plane.units.position);
}
this.context.arc(x, y, r, a, b);
}
moveTo(x, y) {
[x, y] = this.toScreen(new Vector(x,y));
this.context.moveTo(x, y);
}
lineTo(x, y) {
[x, y] = this.toScreen(new Vector(x,y));
this.context.lineTo(x, y);
}
get font() {
return this.context.font;
}
set font(value) {
this.context.font = value;
}
get textSize() {
return Number(this.context.font.split(this.textSeparator)[0])/Math.max(...this.plane.units.position);
}
get textFont() {
return this.context.font.split(this.textSeparator)[1];
}
set textSize(value) {
value *= Math.max(...this.plane.units.position);
this.context.font = String(value) + this.textSeparator + this.textFont;
}
set textFont(value) {
this.context.font = String(this.textSize) + this.textSeparator + value;
}
}
+54
View File
@@ -0,0 +1,54 @@
/*
Polygon...
*/
class Polygon extends Form {
static random(...args) {
var p = new Polygon(Form.random(...args));
}
get area() {
}
get center() {
return Point.average(...this);
}
set center(value) {
const t = value.sub(this.center);
this.imap(p => p.add(t));
}
get segments() {
const sgs = new Tensor();
var p = undefined;
for (const pi of this) {
if (p) {
sgs.push(new Segment(pi, p));
}
}
return sgs;
}
set segments(value) {
}
show(context) {
if (this.fill) {
context.strokeStyle = this.color;
} else {
context.fillStyle = this.color;
}
context.lineWidth = this.lineWidth;
context.beginPath();
context.moveTo(...this[0]);
for (const point of this) {
context.lineTo(...point);
}
if (this.fill) {
context.stroke();
} else {
context.fill();
}
context.closePath();
}
rotate(angle, point=Point.zero) {
this.map(v => v.rotate(angle, point));
}
}
+61
View File
@@ -0,0 +1,61 @@
/**
* Python like dictionary in javascript
* - btw:
* The keys in Map are ordered. Thus, when iterating over it,
* a Map object returns keys in order of insertion. And so
* it is for this dictionary class.
* - also:
* Performs better in scenarios involving frequent additions
* and removals of key-value pairs.
*/
export default class Dict extends Map {
static fromIterables(iterable) {
}
get items() {
return this.entries();
}
/**
* Same as dict.setdefault in python.
* @param {Any} key
* @param {Any} value
*/
setDefault(key, value) {
if (!this.has(key)) {
this.set(key, value);
}
}
/**
* Same as dict.update in python
* @param {IterableCouples, ArrayOfCouples, Map, Dict} entries
*/
update(entries) {
for (const [k,v] in entries) {
if (!this.has(k)) {
this.set(k, v);
}
}
}
/**
* Same as update but it is syntactic sugar.
* @param {...Couple} entries
*/
setDefaults(...entries) {
this.update(entries);
}
imap(f) {
for (const [k,v] in this) {
this.set(k, f(v));
}
}
map(f) {
const dict = new this();
for (const [k,v] in this) {
dict.set(k, f(v));
}
return dict;
}
}
+109
View File
@@ -0,0 +1,109 @@
import Polygon from './polygon.js';
import Body from './body.js';
export default class Entity {
static random(form_args=[], body_args=[]) {
const form = Polygon.random(...form_args);
const body = Body.random(...body_args);
return new this(form, body);
}
constructor(form, body) {
this.form = form;
this.body = body;
}
update(dt) {
this.body.update(dt);
// if (this.body.length==1) {
// console.log(this.form, this.body)
// this.form.angle = this.body[0][1].angle;
// }
// else if (this.body.length==2) {
// this.form.angle = this.body.angle;
// }
// console.log(this.form, this.body)
this.form.center = this.body.position;
}
show(ctx) {
this.form.show(ctx);
// this.hitBox.show(ctx);
// this.hitBubble.show(ctx);
}
get motion() {
return this.body[0];
}
set motion(v) {
this.body[0].set(v);
}
get angularMotion() {
return this.body[1];
}
set angularMotion(v) {
this.body[1].set(v);
}
get x() {
return this.body[0][0][0];
}
set x(v) {
this.body[0][0][0] = v;
}
get y() {
return this.body[0][0][1];
}
set y(v) {
this.body[0][0][1] = v;
}
get z() {
return this.body[0][0][2];
}
set z(v) {
this.body[0][0][2] = v;
}
get vx() {
return this.body[0][1][0];
}
set vx(v) {
this.body[0][1][0].set(v);
}
get ax() {
return this.body[0][1][2];
}
set ax(v) {
this.body[0][1][2].set(v);
}
get position() {
return this.body[0][0];
}
set position(v) {
this.body[0][0].set(v);
}
get p() {
return this.body[0][0];
}
set p(v) {
this.body[0][0].set(v);
}
get velocity() {
return this.body[0][1];
}
set velocity(v) {
this.body[0][1].set(v);
}
get velocity() {
return this.body[0][1];
}
set velocity(v) {
this.body[0][1].set(v);
}
get angle() {
return this.body.angle;
}
set angle(angle) {
this.body.angle = angle;
}
get hitBox() {
return this.form.hitBox;
}
get hitBubble() {
return this.form.hitBubble;
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* A figure is basically a base class
* for any visual object.
*/
export default class Figure {
static lineWidth = 1;
static color = "#ffffff";
static conversion = true;
static random(...args) {
throw "Static function random is not implemented."
}
}
+11
View File
@@ -0,0 +1,11 @@
import VectorField from './vectorfield.js';
export default class Moment extends VectorField {
constructor(force) {
this.force = force;
this.f = (v => v.cross(force));
}
call(vector) {
return this.f(vector);
}
}
+32
View File
@@ -0,0 +1,32 @@
import Figure from './figure.js';
/*
Unlike a figure, a form does have an area.
This class is more of an interface than
a true class or we can also see it as an
abstract class. (reference to PHP, C#, C++, ...)
*/
export default class Form extends Figure {
static fill = false;
static random(...args) {
throw "Static function random is not implemented."
}
// get center() {
// throw "The get center method is not implemented for "+this+".";
// }
// set center(vector) {
// throw "The set center method is not implemented for "+this+".";
// }
get area() {
throw "The get area method is not implemented for "+this+".";
}
set area(area) {
throw "The set area method is not implemented for "+this+".";
}
translate(v) {
throw "The translate method is not implemented for "+this+".";
}
rotate(r, vector=undefined) {
throw "The rotate method is not implemented for "+this+".";
}
}
+17
View File
@@ -0,0 +1,17 @@
import Point from './points.js';
import Segment from './segment.js';
import Polygon from './polygon.js';
var p1 = new Point(1,2);
var p2 = Point.random();
var p3 = new Point(-5, 2);
var p4 = Point.random();
var p5 = p1.add(p2);
var s1 = new Segment(p1, p2);
var s2 = Segment.random();
var s3 = s1.add(s2);
var pl1 = new Polygon(p1, p2, p3);
var pl2 = pl1.neg();
+75
View File
@@ -0,0 +1,75 @@
import Tree from './tree.js';
export default class Group extends Tree {
// type of the entities of the group
static type = undefined;
// number of entites created (usefull for making ids)
static n=0;
// separator for naming entities
static separator = ":";
static of(items) {
const group = new this(items);
group.type = this.type;
return group;
}
static random(n, args=[]) {
const g = new this();
for (let i=0; i<n; i++) {
g.set(this.type.name.toLowerCase()+this.separator+this.n, this.type.random(...args));
this.n += 1;
}
return g;
}
show(ctx) {
for (const v of this.values()) {
v.show(ctx);
}
}
update(dt) {
for (const v of this.values()) {
v.update(dt);
}
}
take(key) {
return this.get(this.constructor.type.name.toLowerCase()+this.constructor.separator+key);
}
add(key, value) {
this.set(this.constructor.type.name.toLowerCase()+this.constructor.separator+key, value);
}
remove(key) {
this.delete(this.constructor.type.name.toLowerCase()+this.constructor.separator+key);
}
getBodies() {
const bodies = [];
for (const [k,v] of this) {
if (v instanceof Group) {
bodies.push([k, v.getBodies()]);
} else {
bodies.push([k, v.body]);
}
}
return bodies;
}
setBodies(bodies) {
for (const [k,v] of bodies) {
if (v[0] instanceof String) {
this.get(k).setBodies(v);
} else {
this.get(k).body.rset(v);
}
}
}
}
// A function that returns a class with a
// predefined type
export function Grouper(type) {
class SubGroup extends Group {} // Just here to bypass a bug
const g = SubGroup.bind({});
g.type = type;
return g;
}
+117
View File
@@ -0,0 +1,117 @@
export function permutation(array) {
const l = array.length,
used = Array(l),
data = Array(l);
return function* backtracking(pos) {
if(pos == l) yield data.slice();
else for(let i=0; i<l; i+=1) if(!used[i]) {
used[i] = true;
data[pos] = array[i];
yield* backtracking(pos+1);
used[i] = false;
}
}(0);
}
export function permuted(array) {
return [...permutation(array)];
}
export function* range(...args) {
let end;
let start = 0;
let step = 1;
if (args.length == 1) {
[end] = args;
} else if (args.length == 2) {
[start, end] = args;
} else if (args.length == 3) {
[start, end, step] = args;
} else {
throw new RangeError("Invalid number of arguments.");
}
for (let i = start; i < end; i+=1) {
yield i;
}
}
export function ranged(...args) {
return [...range(...args)];
}
export function *enumerate(array) {
for (let i = 0; i < array.length; i+=1) {
yield [i, array[i]];
}
}
export function enumerated(array) {
return [...enumerate(array)];
}
export function *zip (...iterables){
let iterators = iterables.map(i => i[Symbol.iterator]() )
while (true) {
let results = iterators.map(iter => iter.next() )
if (results.some(res => res.done) ) return
else yield results.map(res => res.value )
}
}
export function zipped(...iterables) {
return [...zip(...iterables)]
}
export function last(array) { return array[array.length - 1]; }
export function* numericCombinations(n, r, loc = []) {
const idx = loc.length;
if (idx === r) {
yield loc;
return;
}
for (let next of range(idx ? last(loc) + 1 : 0, n - r + 1 + idx)) { yield* numericCombinations(n, r, loc.concat(next)); }
}
export function numericCombinated(n, r) {
return [...numericCombinations(n, r)];
}
function* combinations(array, r) {
for (let idxs of numericCombinations(array.length, r)) { yield idxs.map(i => array[i]); }
}
export function combinated(array, r) {
return [...combinations(array, r)];
}
export function count() {
let index = 0;
return {
next: function(){
return {value: index++, done: false};
}
}
}
export function repeat(value, n=Infinity) {
let i = 0;
return {
next: function(){
i+=1;
return {value: value, done: i>=n};
}
}
}
export function cycle(array) {
let index = 0;
return {
next: function(){
return {value: array[(index++)%array.length], done: false};
}
}
}
+5
View File
@@ -0,0 +1,5 @@
export default class Level {
constructor(group) {
this.group = group;
}
}
+108
View File
@@ -0,0 +1,108 @@
import Figure from './figure.js';
import Point from './point.js';
export default class Line extends Figure {
static color = "#ffffff";
static lineWidth = 1;
constructor(point, vector, color=Line.color, lineWidth=Line.lineWidth) {
this.point = point;
this.vector = vector;
this.color = color;
this.lineWidth = lineWidth;
}
/*
Reset the point to the closest point of the line to the origin.
*/
correct() {
this.vector.iunit();
}
show(context) {
context.lineWidth = this.lineWidth;
context.strokeStyle = this.color;
context.beginPath();
context.closePath();
context.close();
}
get other() {
return this.vector.call(this.point);
}
set other(point) {
this.point = this.vector.sub(point)
}
get slope() {
const [x1, y1] = this.point;
const [x2, y2] = this.other;
return (y2-y1)/(x2-x1);
}
get a() {
return this.slope;
}
get intercept() {
const [x, y] = this.point;
return y-this.a*x;
}
get b() {
return this.intercept;
}
get cartesianComplete() {
throw "Not implemented yet.";
}
get cartesianReduced() {
return [this.a, this.b];
}
get normal() {
return this.vector.rot(Math.PI/2);
}
contains(point) {
return point.sub(this.point);
}
distance(point) {
return Math.abs(point.sub(this.point).dot(this.normal));
}
height(point) {
return point.sub(this.point).dot(this.normal);
}
project(point) {
return this.vector.rmul(point.sub(this.point).dot(this.vector)/this.vector.norm**2).add(this.point);
}
crossLine(line, precision=1e-10) { // Done the Wikipedia way
const v1 = this.vector;
const v2 = line.vector;
const [x1, y1] = this.point;
const [x3, y3] = line.point;
const [x2, y2] = this.vector.call(this);
const [x4, y4] = line.vector.call(line.point);
const d = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4);
if (abs(d)<precision) { // 2 lines parallels
return undefined;
}
const t = (x1-x3)*(y3-y4)-(y1-y3)*(x3-x4)/d;
const u = (x1-x2)*(y1-y3)-(y1-y2)*(x1-x3)/d;
return new Point(x1+t*(x2-x1),y1+t*(y2-y1))
}
crossSegment(segment) {
return segment.crossLine(Line);
}
cross(figure) {
if (figure instanceof Line) {
return this.crossLine(figure);
} else if (figure instanceof Segment) {
return this.crossSegment(figure);
} else if (figure instanceof Polygon) {
figure.crossSegment(this);
} else {
throw `Collisions between ${this.constructor.name} and ${figure.constructor.name} are not dealt with`;
}
}
crossEquations(line, precision=1e-10) { // Done the Wikipedia way
const a = this.slope;
const b = line.slope;
const c = this.intercept;
const d = line.intercept;
if (abs(a-b)<precision) { // 2 lines parallels
return undefined;
}
return new Point((d-c)*(a-b), a*(d-c)/(a-b)+c);
}
}
+49
View File
@@ -0,0 +1,49 @@
/**
* More responsible array.
* Inspired from python.
*/
export default class List extends Array {
removeOne(item, n=undefined) {
let i = 0;
while (i < this.length) {
if (this[i] === item) {
this.splice(i, n);
} else {
++i;
}
}
}
removeMany(items, n=undefined) {
for (const item in items) {
this.removeOne(item, n);
}
}
remove(...items) {
this.removeMany(items);
}
deleteOne(index, n=undefined) {
this.splice(index, n);
}
deleteMany(indices, n=undefined) {
for (const index of indices) {
this.splice(index, n);
}
}
delete(...indices) {
this.deleteMany(indices);
}
contains(item) {
for (const item_ of this) {
if (item==item_) {
return true;
}
}
return false;
}
str() {
return String(this);
}
append = this.push;
extend = this.concat;
len = this.length;
}
+156
View File
@@ -0,0 +1,156 @@
import ContextAdapter from 'context.js';
import Vector from 'vector.js';
export default class Manager {
static zoomDelta = 0.1;
static scrollZoomFactor = 0.5;
static entities = [];
static movement = {up: false, down: false, right: false, left: false, zoomin: false, zoomout: false};
static backgroundColor = "#000000";
addEventListeners() {
// using window as a global
window.addEventListener("keydown", this.onKeyDown.bind(this));
window.addEventListener("keyup", this.onKeyUp.bind(this));
window.addEventListener("mousemove", this.onMouseMotion.bind(this));
window.addEventListener("scroll", this.onScroll.bind(this));
window.addEventListener("mousewheel", this.onScroll.bind(this));
window.addEventListener("resize", this.resize.bind(this));
}
deactivate() {
// prevent default actions from space and arrow keys
window.addEventListener("keydown", function(e) {
if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
e.preventDefault();
}
}, false);
}
constructor(
canvas,
game,
movement=Manager.movement,
backgroundColor=Manager.backgroundColor
) {
this.canvas = canvas;
this.context = new ContextAdapter(this.canvas.getContext("2d"));
this.game = game;
this.movement = movement;
this.backgroundColor = backgroundColor;
this.mouse = new Vector(0, 0);
}
clear() {
this.context.fillStyle = this.backgroundColor;
this.context.clear();
}
show() {
this.clear();
this.game.show(this.context);
}
update() {
this.context.plane.location.update();
this.context.plane.units.update();
this.move();
this.game.update();
}
resize() {
this.canvas.width = this.context.width = window.innerWidth;
this.canvas.height = this.context.height = window.innerHeight;
}
onKeyDown(evt) {
switch(evt.keyCode){
case 39: // Arrow Right for moving right
this.movement.right = true;
break;
case 37: // Arrow Left for moving left
this.movement.left = true;
break;
case 40: // Arrow Up for moving up
this.movement.up = true;
break;
case 38: // Arrow Down for moving down
this.movement.down = true;
break;
case 16: // Left and Right shift for zooming in and out
if (evt.location == 1) {
this.movement.zoomout = true;
} else {
this.movement.zoomin = true;
}
break;
case 18: // Alt for pause
if (this.game.dt) {
this.game.dt = 0;
} else {
this.game.dt = Game.dt;
}
}
}
onKeyUp(evt) {
switch(evt.keyCode) {
case 39: // Arrow Right
this.movement.right = false;
break;
case 37: // Arrow Left
this.movement.left = false;
break;
case 40: // Arrow Up
this.movement.up = false;
break;
case 38: // Arrow Down
this.movement.down = false;
break;
case 16:
if (evt.location == 1) {
this.movement.zoomout = false;
} else {
this.movement.zoomin = false;
}
break;
}
}
onScroll(evt) {
if (evt.wheelDeltaY>0) {
this.context.plane.units[0].irmul(1+Manager.scrollZoomFactor*Manager.zoomDelta);
} else {
this.context.plane.units[0].irmul(1-Manager.scrollZoomFactor*Manager.zoomDelta);
}
}
onMouseMotion(evt) {
this.mouse = new Vector(evt.x, evt.y);
}
move() {
if (this.movement.up) {
this.context.plane.y += this.context.plane.speed/this.context.plane.ux;
}
if (this.movement.down) {
this.context.plane.y -= this.context.plane.speed/this.context.plane.uy;
}
if (this.movement.left) {
this.context.plane.x -= this.context.plane.speed/this.context.plane.ux;
}
if (this.movement.right) {
this.context.plane.x += this.context.plane.speed/this.context.plane.uy;
}
if (this.movement.zoomin) {
this.context.plane.units[0].irmul(1+Manager.zoomDelta);
}
if (this.movement.zoomout) {
this.context.plane.units[0].irmul(1-Manager.zoomDelta);
}
}
loop() {
this.update();
this.show();
// requestAnimationFrame(this.loop.bind(this));
}
start() {
this.addEventListeners();
}
main() {
this.start();
setInterval(this.loop.bind(this), 100);
}
}
+3
View File
@@ -0,0 +1,3 @@
export function kronecker(v) {
return x => Number(x==v);
}
+147
View File
@@ -0,0 +1,147 @@
import Tensor from './tensor.js';
import Vector from './vector.js';
export default class Matrix extends Tensor {
/*
* A matrix can be built from a 2d array, a list of vectors
*/
static get order() {return 2}; /* Fancy way of declaring a static constant. */
static w = 1;
static h = 1;
static format = [this.w, this.h];
static zero(...format) {
let v = Tensor.zero(...format)
v = v.map(x => Matrix.from(x))
return v;
}
static random(w=this.w, h=this.h) {
return super.random(w,h).map(t => Vector.from(t));
}
static toVectors(...vectors) {
return this.from(vectors.map(v => Vector.from(v)))
}
static from(vectors) {
return super.from(vectors.map(v => Vector.from(v)));
}
constructor(...vectors) {
super(...vectors)
for (let i=0; i<this.length; i++) {
if (this[i]!=undefined) {
this[i] = Vector.from(this[i]);
}
}
}
get width() {
return this.length;
}
get height() {
return this[0].length;
}
get format() {
return [this.width, this.height];
}
str() {
let m = this.slice();
for (let x=0; x<this.width; x++) {
m[x] = m[x].join(" ");
}
return m.join("\n");
}
slice(...args) {
return new Matrix(...super.slice(...args));
}
dot(matrix) {
let d = this.width;
let h = this.height;
let w = matrix.width;
let m = Matrix.fromFormat(w, h);
for (let x=0; x<w; x++) {
for (let y=0; y<h; y++) {
m[x][y] = this[x][y]*matrix[x][y];
}
}
return m;
}
idot(matrix) {
this.set(this.dot(matrix));
}
mul(matrix) {
let d = this.width;
let h = this.height;
let w = matrix.width;
let m = Matrix.fromFormat(w, h);
for (let x=0; x<w; x++) {
for (let y=0; y<h; y++) {
m[x][y] = 0;
for (let z=0; z<d; z++) {
m[x][y] += this[z][y]*matrix[x][z];
}
}
}
return m;
}
imul(matrix) {
return this.set(this.mul(matrix));
}
vmul(vector) {
let w = this.width;
let h = this.height;
let a = Array(h);
for (let y=0; y<h; y++) {
a[y] = 0;
for (let x=0; x<w; x++) {
a[y] += this[x][y] * vector[x];
}
}
return new Vector(...a);
}
}
export class SquareMatrix extends Matrix {
static length = 1;
static random(length=Matrix.length) {
let m = new SquareMatrix(...Matrix.fromFormat(length, length));
for (let x=0; x<length; x++) {
for (let y=0; y<length; y++) {
m[x][y] = Math.random();
}
}
return m;
}
constructor(...vectors) {
if (Math.max(...vectors.map(v => v.length)) != Math.min(...vectors.map(v => v.length))) {
throw new Error("The vectors must have the same length.");
}
super(...vectors);
}
get det2() {
const n = this.length;
let v = 0;
for (let i=0; i<n; i++) {
let p1 = 1;
let p2 = 1;
for (let j=0; j<n; j++) {
console.log()
p1 *= this[(i+j)%n][j];
p2 *= this[(i-j+n)%n][j];
}
v += (p1-p2);
}
return v;
}
get det() {
let v = 0;
for (p of permutation([...range(this.length)])) {
// v +=
}
return v;
}
}
/*
node
.load tensor.js
.load matrix.js
m = new SquareMatrix([1, 2], [3, 4]);
*/
+119
View File
@@ -0,0 +1,119 @@
import Matrix from './matrix.js';
import Vector from './vector.js';
/*
A motion is a matrix and also a list of vectors.
*/
export default class Motion extends Matrix {
static width = 3;
static height = 2;
static get length() {return Motion.width};
static set length(n) {Motion.width = n};
static get dimension() {return Motion.height};
static set dimension(n) {Motion.height = n};
static random(width=Motion.width, height=Motion.height) {
return Motion.from(Matrix.random(width, height));
}
static zero(width=Motion.width, height=Motion.height) {
return Motion.from(Matrix.zero(width, height));
}
slice(...args) {
return new Motion(...super.slice(...args));
}
// constructor(...vectors) {
// console.log("motion_constructor:", vectors);
// super(...vectors);
// console.log(this);
// // this.imap(t => new Vector(...t));
// for (let i=0; i<this.length; i++) {
// this[i] = new Vector(...this[i]);
// }
// }
get position() {
return new Vector(...this[0]);
}
set position(value) {
this[0] = value;
}
get velocity() {
return new Vector(...this[1]);
}
set velocity(value) {
this[1] = value;
}
get acceleration() {
return new Vector(...this[2]);
}
set acceleration(value) {
this[2] = v;
}
get x() {
return this[0][0];
}
set x(v) {
this[0][0] = v;
}
get y() {
return this[0][1];
}
set y(v) {
this[0][1] = v;
}
get z() {
return this[0][2];
}
set z(v) {
this[0][2] = v;
}
get vx() {
return this[1][0];
}
set vx(v) {
this[1][0] = v;
}
get vy() {
return this[1][1];
}
set vy(v) {
this[1][1] = v;
}
get vz() {
return this[1][2];
}
set vz(v) {
this[1][2] = v;
}
get ax() {
return this[2][0];
}
set ax(v) {
this[2][0] = v;
}
get ay() {
return this[2][1];
}
set ay(v) {
this[2][1] = v;
}
get az() {
return this[2][2];
}
set az(v) {
this[2][2] = v;
}
update(dt=1) {
// console.log(this)
for (let x=this.width-2; x>=0; x--) {
this[x].iadd(this[x+1].rmul(dt));
}
}
updateFriction(friction=0.1) {
this[1].irmul(1-friction);
}
show(ctx) {
this.velocity.show(ctx, this.position, "blue");
this.acceleration.show(ctx, this.position, "red");
}
}
+7
View File
@@ -0,0 +1,7 @@
export default class Node {
constructor(children, parent) {
this.children = children;
this.parent = parent;
}
get()
}
+310
View File
@@ -0,0 +1,310 @@
/*
* A speed-improved perlin and simplex noise algorithms for 2D.
*
* Based on example code by Stefan Gustavson (stegu@itn.liu.se).
* Optimisations by Peter Eastman (peastman@drizzle.stanford.edu).
* Better rank ordering method by Stefan Gustavson in 2012.
* Converted to Javascript by Joseph Gentle.
*
* Version 2012-03-09
*
* This code was placed in the public domain by its original author,
* Stefan Gustavson. You may use it as you see fit, but
* attribution is appreciated.
*
*/
(function(global){
var module = global.noise = {};
function Grad(x, y, z) {
this.x = x; this.y = y; this.z = z;
}
Grad.prototype.dot2 = function(x, y) {
return this.x*x + this.y*y;
};
Grad.prototype.dot3 = function(x, y, z) {
return this.x*x + this.y*y + this.z*z;
};
var grad3 = [new Grad(1,1,0),new Grad(-1,1,0),new Grad(1,-1,0),new Grad(-1,-1,0),
new Grad(1,0,1),new Grad(-1,0,1),new Grad(1,0,-1),new Grad(-1,0,-1),
new Grad(0,1,1),new Grad(0,-1,1),new Grad(0,1,-1),new Grad(0,-1,-1)];
var p = [151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
// To remove the need for index wrapping, double the iterator table length
var perm = new Array(512);
var gradP = new Array(512);
// This isn't a very good seeding function, but it works ok. It supports 2^16
// different seed values. Write something better if you need more seeds.
module.seed = function(seed) {
if(seed > 0 && seed < 1) {
// Scale the seed out
seed *= 65536;
}
seed = Math.floor(seed);
if(seed < 256) {
seed |= seed << 8;
}
for(var i = 0; i < 256; i++) {
var v;
if (i & 1) {
v = p[i] ^ (seed & 255);
} else {
v = p[i] ^ ((seed>>8) & 255);
}
perm[i] = perm[i + 256] = v;
gradP[i] = gradP[i + 256] = grad3[v % 12];
}
};
module.seed(0);
/*
for(var i=0; i<256; i++) {
perm[i] = perm[i + 256] = p[i];
gradP[i] = gradP[i + 256] = grad3[perm[i] % 12];
}*/
// Skewing and unskewing factors for 2, 3, and 4 dimensions
var F2 = 0.5*(Math.sqrt(3)-1);
var G2 = (3-Math.sqrt(3))/6;
var F3 = 1/3;
var G3 = 1/6;
// 2D simplex noise
module.simplex2 = function(xin, yin) {
var n0, n1, n2; // Noise contributions from the three corners
// Skew the input space to determine which simplex cell we're in
var s = (xin+yin)*F2; // Hairy factor for 2D
var i = Math.floor(xin+s);
var j = Math.floor(yin+s);
var t = (i+j)*G2;
var x0 = xin-i+t; // The x,y distances from the cell origin, unskewed.
var y0 = yin-j+t;
// For the 2D case, the simplex shape is an equilateral triangle.
// Determine which simplex we are in.
var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
if(x0>y0) { // lower triangle, XY order: (0,0)->(1,0)->(1,1)
i1=1; j1=0;
} else { // upper triangle, YX order: (0,0)->(0,1)->(1,1)
i1=0; j1=1;
}
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
// c = (3-sqrt(3))/6
var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
var y1 = y0 - j1 + G2;
var x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords
var y2 = y0 - 1 + 2 * G2;
// Work out the hashed gradient indices of the three simplex corners
i &= 255;
j &= 255;
var gi0 = gradP[i+perm[j]];
var gi1 = gradP[i+i1+perm[j+j1]];
var gi2 = gradP[i+1+perm[j+1]];
// Calculate the contribution from the three corners
var t0 = 0.5 - x0*x0-y0*y0;
if(t0<0) {
n0 = 0;
} else {
t0 *= t0;
n0 = t0 * t0 * gi0.dot2(x0, y0); // (x,y) of grad3 used for 2D gradient
}
var t1 = 0.5 - x1*x1-y1*y1;
if(t1<0) {
n1 = 0;
} else {
t1 *= t1;
n1 = t1 * t1 * gi1.dot2(x1, y1);
}
var t2 = 0.5 - x2*x2-y2*y2;
if(t2<0) {
n2 = 0;
} else {
t2 *= t2;
n2 = t2 * t2 * gi2.dot2(x2, y2);
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
return 70 * (n0 + n1 + n2);
};
// 3D simplex noise
module.simplex3 = function(xin, yin, zin) {
var n0, n1, n2, n3; // Noise contributions from the four corners
// Skew the input space to determine which simplex cell we're in
var s = (xin+yin+zin)*F3; // Hairy factor for 2D
var i = Math.floor(xin+s);
var j = Math.floor(yin+s);
var k = Math.floor(zin+s);
var t = (i+j+k)*G3;
var x0 = xin-i+t; // The x,y distances from the cell origin, unskewed.
var y0 = yin-j+t;
var z0 = zin-k+t;
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
// Determine which simplex we are in.
var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
if(x0 >= y0) {
if(y0 >= z0) { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; }
else if(x0 >= z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; }
else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; }
} else {
if(y0 < z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; }
else if(x0 < z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; }
else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; }
}
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
// c = 1/6.
var x1 = x0 - i1 + G3; // Offsets for second corner
var y1 = y0 - j1 + G3;
var z1 = z0 - k1 + G3;
var x2 = x0 - i2 + 2 * G3; // Offsets for third corner
var y2 = y0 - j2 + 2 * G3;
var z2 = z0 - k2 + 2 * G3;
var x3 = x0 - 1 + 3 * G3; // Offsets for fourth corner
var y3 = y0 - 1 + 3 * G3;
var z3 = z0 - 1 + 3 * G3;
// Work out the hashed gradient indices of the four simplex corners
i &= 255;
j &= 255;
k &= 255;
var gi0 = gradP[i+ perm[j+ perm[k ]]];
var gi1 = gradP[i+i1+perm[j+j1+perm[k+k1]]];
var gi2 = gradP[i+i2+perm[j+j2+perm[k+k2]]];
var gi3 = gradP[i+ 1+perm[j+ 1+perm[k+ 1]]];
// Calculate the contribution from the four corners
var t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
if(t0<0) {
n0 = 0;
} else {
t0 *= t0;
n0 = t0 * t0 * gi0.dot3(x0, y0, z0); // (x,y) of grad3 used for 2D gradient
}
var t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
if(t1<0) {
n1 = 0;
} else {
t1 *= t1;
n1 = t1 * t1 * gi1.dot3(x1, y1, z1);
}
var t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
if(t2<0) {
n2 = 0;
} else {
t2 *= t2;
n2 = t2 * t2 * gi2.dot3(x2, y2, z2);
}
var t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
if(t3<0) {
n3 = 0;
} else {
t3 *= t3;
n3 = t3 * t3 * gi3.dot3(x3, y3, z3);
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
return 32 * (n0 + n1 + n2 + n3);
};
// ##### Perlin noise stuff
function fade(t) {
return t*t*t*(t*(t*6-15)+10);
}
function lerp(a, b, t) {
return (1-t)*a + t*b;
}
// 2D Perlin Noise
module.perlin2 = function(x, y) {
// Find unit grid cell containing point
var X = Math.floor(x), Y = Math.floor(y);
// Get relative xy coordinates of point within that cell
x = x - X; y = y - Y;
// Wrap the integer cells at 255 (smaller integer period can be introduced here)
X = X & 255; Y = Y & 255;
// Calculate noise contributions from each of the four corners
var n00 = gradP[X+perm[Y]].dot2(x, y);
var n01 = gradP[X+perm[Y+1]].dot2(x, y-1);
var n10 = gradP[X+1+perm[Y]].dot2(x-1, y);
var n11 = gradP[X+1+perm[Y+1]].dot2(x-1, y-1);
// Compute the fade curve value for x
var u = fade(x);
// Interpolate the four results
return lerp(
lerp(n00, n10, u),
lerp(n01, n11, u),
fade(y));
};
// 3D Perlin Noise
module.perlin3 = function(x, y, z) {
// Find unit grid cell containing point
var X = Math.floor(x), Y = Math.floor(y), Z = Math.floor(z);
// Get relative xyz coordinates of point within that cell
x = x - X; y = y - Y; z = z - Z;
// Wrap the integer cells at 255 (smaller integer period can be introduced here)
X = X & 255; Y = Y & 255; Z = Z & 255;
// Calculate noise contributions from each of the eight corners
var n000 = gradP[X+ perm[Y+ perm[Z ]]].dot3(x, y, z);
var n001 = gradP[X+ perm[Y+ perm[Z+1]]].dot3(x, y, z-1);
var n010 = gradP[X+ perm[Y+1+perm[Z ]]].dot3(x, y-1, z);
var n011 = gradP[X+ perm[Y+1+perm[Z+1]]].dot3(x, y-1, z-1);
var n100 = gradP[X+1+perm[Y+ perm[Z ]]].dot3(x-1, y, z);
var n101 = gradP[X+1+perm[Y+ perm[Z+1]]].dot3(x-1, y, z-1);
var n110 = gradP[X+1+perm[Y+1+perm[Z ]]].dot3(x-1, y-1, z);
var n111 = gradP[X+1+perm[Y+1+perm[Z+1]]].dot3(x-1, y-1, z-1);
// Compute the fade curve value for x, y, z
var u = fade(x);
var v = fade(y);
var w = fade(z);
// Interpolate
return lerp(
lerp(
lerp(n000, n100, u),
lerp(n001, n101, u), w),
lerp(
lerp(n010, n110, u),
lerp(n011, n111, u), w),
v);
};
})(this);
+111
View File
@@ -0,0 +1,111 @@
import Motion from './motion.js';
export default class Plane {
static location = Motion.zero(3, 2)
static units = Motion.zero(3, 2);
static speed = 10;
constructor(location=Plane.location, units=Plane.units, speed=Plane.speed) {
this.location = location;
this.units = units;
this.speed = speed;
}
get position() {
return this.location.position;
}
set position(value) {
this.location.position = value;
}
get velocity() {
return this.location.velocity;
}
set velocity(value) {
this.location.velocity = value;
}
get acceleration() {
return this.location.acceleration;
}
set acceleration(value) {
this.location.acceleration = value;
}
get x() {
return this.location[0][0];
}
set x(value) {
this.location[0][0] = value;
}
get y() {
return this.location[0][1];
}
set y(value) {
this.location[0][1] = value;
}
get vx() {
return this.location[1][0];
}
set vx(value) {
this.location[1][0] = value;
}
get vy() {
return this.location[1][1];
}
set vy(value) {
this.location[1][1] = value;
}
get ax() {
return this.location[2][0];
}
set ax(value) {
this.location[2][0] = value;
}
get ay() {
return this.location[2][1];
}
set ay(value) {
this.location[2][1] = value;
}
get ux() {
return this.units[0][0];
}
set ux(value) {
this.units[0][0] = value;
}
get uy() {
return this.units[0][1];
}
set uy(value) {
this.units[0][1] = value;
}
get uvx() {
return this.units[1][0];
}
set uvx(value) {
this.units[1][0] = value;
}
get uvy() {
return this.units[1][1];
}
set uvy(value) {
this.units[1][1] = value;
}
get uax() {
return this.units[2][0];
}
set uax(value) {
this.units[2][0] = value;
}
get uay() {
return this.units[2][1];
}
set uay(value) {
this.units[2][1] = value;
}
toScreen(position) {
return position.sub(this.location.position).tdot(this.units.position);
}
fromScreen(position) {
return position.tdot(this.units.position.map(x => 1/x)).add(this.location.position);
}
zoom(value) {
this.units[0].irmul(value);
}
}
+34
View File
@@ -0,0 +1,34 @@
import Vector from './vector.js';
export default class Point extends Vector {
static radius = 1;
static color = "#ffffff";
static conversion = false;
static average(...points) {
return new Point(...Vector.average(...points));
}
static random(n=2) {
return Point.from(Vector.random(n));
}
map(f) {
return new Point(...super.map(f));
}
get r() {
return this.radius;
}
set r(value) {
this.radius = value;
}
show(
context,
color=Point.color,
radius=Point.radius,
conversion=Point.conversion
) {
context.beginPath();
context.fillStyle = color;
context.arc(this.x, this.y, radius, 0, 2*Math.PI, conversion);
context.fill();
context.closePath();
}
}
+7
View File
@@ -0,0 +1,7 @@
import Point from './point.js';
let p1 = new Point(1, 2);
let p2 = Point.random();
console.log("I'm executed");
+146
View File
@@ -0,0 +1,146 @@
import BasePolygon from './basepolygon.js';
import Matrix from './matrix.js';
import Segment from './segment.js';
import Point from './point.js';
import Vector from './vector.js';
export default class Polygon extends BasePolygon {
static color = "#ffffff";
static width=null;
static fill=false;
static w=3; // number of points (triangle)
static h=2; // number of components (2d)
static s=1; // size factor
static get matrix() {
return Matrix.zero(this.w, this.h);
}
static random(
w=Polygon.w,
h=Polygon.h,
s=Polygon.s,
color=Polygon.color,
width=Polygon.width,
fill=Polygon.fill
) {
let m = new Matrix(w);
let v = Vector.random(w, 0, s);
v.irdiv(v.sum);
let a = 0;
for (let i=0; i<w; i++) {
a += v[i];
m[i] = Vector.polar(s * Math.random(), 2*Math.PI*a);
}
return new this(m, color, width, fill);
}
constructor(
matrix=Polygon.matrix,
color=Polygon.color,
width=Polygon.width,
fill=Polygon.fill,
) {
super(color, width, fill);
this.matrix = Matrix.from(matrix);
}
get points() {
return Array.from(this.matrix);
}
set points(points) {
this.matrix.set(points);
}
move(x, y) {
}
get segments() {
const segments = [];
let p = this.matrix[this.matrix.length-1];
for (const pi of this.matrix) {
segments.push(new Segment(p, pi));
p = pi;
}
return segments;
}
get center() {
return Point.average(...this.matrix);
}
set center(point) {
const v = point.sub(this.center)
this.translate(v);
}
get angle(){
//Retourne l'angle du polygone, lors de la création du polygone, celui-ci vaut 0
const c = this.center;
const p = this.points[0];
return p.sub(c).angle;
}
set angle(r) {
this.rotate(r-this.angle);
}
translate(vector) {
this.matrix.map(v => v.translate(vector));
}
rotate(angle, point=Point.zero) {
this.matrix.map(v => v.rotate(angle, point));
}
show(ctx) {
if (this.fill) {
ctx.fillStyle = this.color;
} else {
ctx.strokeStyle = this.color;
}
if (this.lineWidth) {
ctx.lineWidth = this.lineWidth;
}
ctx.beginPath();
ctx.moveTo(...this.matrix[this.matrix.length-1]);
for (const pt of this.matrix) {
ctx.lineTo(...pt);
}
ctx.closePath();
if (this.fill) {
ctx.fill();
} else {
ctx.stroke();
}
}
/**
* Determine the distance of a point to the polygon.
* @param {Point} point
*/
distance(point) {
let d = 0;
for (const p of this.matrix) {
d += Point.distance(point, p);
}
return d;
}
/**
* Determine if the polygon contains a point
* @param {*} point
*/
contains(p) {
const points = this.matrix;
var isInside = false;
var minX = points[0].x, maxX = points[0].x;
var minY = points[0].y, maxY = points[0].y;
for (let n = 1; n < points.length; n++) {
let q = points[n];
minX = Math.min(q.x, minX);
maxX = Math.max(q.x, maxX);
minY = Math.min(q.y, minY);
maxY = Math.max(q.y, maxY);
}
if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {
return false;
}
let i = 0, j = points.length - 1;
for (i, j; i < points.length; j = i++) {
if ( (points[i].y > p.y) != (points[j].y > p.y) &&
p.x < (points[j].x - points[i].x) * (p.y - points[i].y) / (points[j].y - points[i].y) + points[i].x ) {
isInside = !isInside;
}
}
return isInside;
}
}
+96
View File
@@ -0,0 +1,96 @@
import Vector from './vector.js';
export default class Polynomial extends Vector {
static fromRoots(roots) {
let p = this.one;
for (const root of roots) {
p.imul(new this(root, 1))
}
return p;
}
/*
Create a polynomial using the unpacked list of components.
*/
constructor(...components) {
super(...components);
}
/*
Evaluate the polynomial at a given value.
*/
call(x) {
let v = 0;
for (let i=0; i<this.length; i++) {
v += x*this[i]**i;
}
return v;
}
str() {
let ws = [];
let w = "";
let c, j;
for (let i=0; i<this.length; i++) {
j = this.length-1-i;
c = this[j];
if (c!=0) {
if (j==0) {
w = String(c);
} else if (j==1) {
if (c==1) {
w = "X";
} else {
w = String(c)+"X";
}
} else {
if (c==1) {
w = "X^"+String(j);
} else {
w = String(c)+"X^"+String(j);
}
}
ws.push(w);
}
}
let s = ws.join("+");
if (s=="") {
s = "0";
}
return s;
}
/*
Delete the zeros at the end of the polynomial.
*/
correct() {
while (this[this.length-1]==0) {
delete this[this.length-1];
}
}
/*
Return the degree of the polynomial.
*/
get degree() {
return this.length;
}
/*
Multiply the polynomial with another polynomial.
*/
mul(other) {
let n = this.degree;
let p = other.degree;
let m = Math.max(n, p);
this.iadapt(m);
other.iadapt(m);
let polynomial = new Polynomial();
for (let k=0; k<m; k++) {
let c = 0;
for (let i=0; i<k; i++) {
c += this[i] * other[k-i];
}
polynomial.push(c);
}
polynomial.correct();
return polynomial;
}
factorize() {
}
}
+112
View File
@@ -0,0 +1,112 @@
export default class Quadtree {
/*
format :
recursive case:
tree = [tree1, tree2, tree3, tree4]
base case:
tree = [point1, point2, ...]
point = [x, y]
2|3
0|1
*/
constructor(tree=new Map(), chart=new Map(), nodes=3) {
this.tree = tree;
this.chart = chart;
this.nodes = nodes;
}
create(points) {
this.borders = this.getBorders(points);
this.size = this.getSize(this.borders);
this.tree = this.divide(points, ...this.size);
}
divide(points, sx, sy, hx=0, hy=0, n=0) {
if (points.length<this.nodes) {return points}
if (n>10) {return points}
n += 1;
sx /= 2;
sy /= 2;
const xmin = this.borders[0];
const ymin = this.borders[1];
const tree = new Map([
[0, []], [1, []], [2, []], [3, []]
]);
for (const [x, y] of points) {
if (y<hy+sx+ymin) {
if (x<hx+sy+xmin) {
tree.get(0).push([x, y]);
} else {
tree.get(1).push([x, y]);
}
} else {
if (x<hx+xmin) {
tree.get(2).push([x, y]);
} else {
tree.get(3).push([x, y]);
}
}
}
// console.log(points.length, n, sx, hx);
let x, y;
for (let i=0; i<4; i++) {
if (tree.get(i).length==0) {
tree.delete(i);
} else {
x = i%2;
y = Math.floor(i/2);
console.log(i, x, y);
tree.set(i,
this.divide(
tree.get(i), sx, sy, hx+sx*x, hy+sx*y, n
)
);
}
}
return tree;
}
access(path) {
let tree = this.tree;
while (path.lenth!=0) {
tree = tree.get(path.pop());
}
return tree;
}
getBorders(points) {
let xmin = Infinity;
let ymin = Infinity;
let xmax = -Infinity;
let ymax = -Infinity;
for (const [x, y] of points) {
xmin = Math.min(xmin, x);
ymin = Math.min(ymin, y);
xmax = Math.max(xmax, x);
ymax = Math.max(ymax, y);
}
return [xmin, ymin, xmax, ymax];
}
getSize(borders) {
return [borders[2]-borders[0], borders[3]-borders[1]];
}
getPoints(tree) {
const points = [];
return points;
}
contains(x, y, r) {
}
}
function test(n=1000) {
let pts = [];
for (let i=0; i<n; i++) {
pts.push([Math.random(), Math.random()]);
}
let q = new Quadtree();
q.create(pts);
return q;
}
+92
View File
@@ -0,0 +1,92 @@
import BasePolygon from './basepolygon.js';
import Vector from './vector.js';
import Point from './point.js';
export default class Rectangle extends BasePolygon {
static color = "#ffffff";
static lineWidth = 1;
static of(x, y, w, h, ...args) {
return this(x,y, w,h, ...args);
}
static from(a, ...args) {
return new this(a.slice(0,2), a.slice(2), ...args)
}
static random() {
return new this(Vector.random(), Vector.random());
}
constructor(
position,
size,
lineWidth=Rectangle.lineWidth,
color=Rectangle.color,
fill=Rectangle.fill,
) {
super(color, lineWidth, fill);
this.position = new Vector(...position);
this.size = new Vector(...size);
}
translate(vector) {
this.position.iadd(vector);
}
get x() {
return this.position[0]
}
set x(value) {
this.position[0] = value;
}
get y() {
return this.position[1];
}
set y(value) {
this.position[1] = value;
}
get w() {
return this.size[0];
}
set w(value) {
this.size[0] = value;
}
get h() {
return this.size[1];
}
set h(value) {
this.size[1] = value;
}
get components() {
return [...this.position, ...this.size];
}
set components(value) {
this.position = value.slice(0, 2);
this.size = value.slice(2, 4);
}
get points() {
return [
new Point(this.x, this.y),
new Point(this.x+this.w, this.y),
new Point(this.x+this.w, this.y+this.h),
new Point(this.x, this.y+this.h)
];
}
set points(value) {
this.x = Math.min(value.map((p) => p.x));
this.y = Math.min(value.map((p) => p.y));
this.w = Math.max(value.map((p) => p.x))-this.x;
this.h = Math.max(value.map((p) => p.y))-this.y;
}
get area() {
return this.w*this.h;
}
show(ctx) {
if (this.lineWidth) {
ctx.lineWidth = this.lineWidth;
}
if (this.fill) {
ctx.fillStyle = this.color;
ctx.fillRect(...this.components);
} else {
ctx.strokeStyle = this.color;
ctx.strokeRect(...this.components);
}
}
}
+129
View File
@@ -0,0 +1,129 @@
import BasePolygon from './basepolygon.js';
import Matrix from './matrix.js';
import Point from './point.js';
export default class Segment extends BasePolygon {
static lineWidth = 3; // pixels
static color = "#ffffff";
static random(lineWidth=super.width, color=super.color) {
return new Segment(
Point.random(),
Point.random(),
lineWidth,
color)
}
constructor(p1=Point.zero2, p2=Point.zero2,
lineWidth=Segment.lineWidth,
color=Segment.color
) {
super();
this.points = new Matrix(Point.from(p1), Point.from(p2));
this.lineWidth = lineWidth;
this.color = color;
}
get p1() {
return this.points[0];
}
set p1(value) {
this.points[0] = value;
}
get p2() {
return this.points[1];
}
set p2(value) {
this.points[1] = value;
}
translate(vector) {
for (const p of this.points) {
p.translate(vector);
}
}
rotate(angle, vector=undefined) {
vector = vector || this.center;
for (const p of this.points) {
p.rotate(angle, vector);
}
}
get center() {
return Vector.average(...this.points);
}
set center(v) {
this.translate(v.sub(this.center));
}
get vector() {
return this.p2.sub(this.p1);
}
set vector(v) {
this.p2 = v.sub(this.p1);
}
get normal() {
return this.vector.rot(Math.PI/2);
}
set normal(vector) {
this.vector = vector.rot(-Math.PI/2);
}
get angle() {
return this.vector.angle;
}
set angle(a) {
this.rotate(a-this.angle);
}
get length() {
return this.p1.sub(this.p2).norm
}
get line() {
return new Line(this.p1, this.vector);
}
show(context) {
context.context.lineWidth = this.lineWidth;
context.strokeStyle = this.color;
context.beginPath();
context.moveTo(...this.p1);
context.lineTo(...this.p2);
context.closePath();
context.stroke();
}
get base() {
const [a, b] = this.vector.unit();
const [c, d] = this.normal.unit();
return new Matrix([a,b],[c,d]);
}
toBase(vector) {
return new Vector(
vector.dot(this.vector)/this.vector.norm,
vector.dot(this.normal)/this.normal.norm
);
}
contains(point, precision=1e-10) {
const d = Point.distance(this.p1, point)+Point.distance(this.p2, point);
return d-this.length <= precision;
}
crossLine(line) {
const point = this.line.cross(line);
if (this.contains(point)) {
return point;
}
return undefined;
}
crossSegment(segment) {
const point = this.line.cross(segment.line);
if (this.contains(point)) {
if (segment.contains(point)) {
return point;
}
}
return undefined;
}
cross(figure) {
if (this.figure instanceof Segment) {
return this.crossSegment(segment);
} else if (this.figure instanceof Line) {
return this.crossLine(figure);
} else if (this.figure instanceof Polygon) {
return figure.crossSegment(segment);
} else {
throw `Collisions between ${this.constructor.name} and ${figure.constructor.name} are not dealt with`;
}
}
}
+28
View File
@@ -0,0 +1,28 @@
export default class Server extends Connexion {
constructor() {
}
}
const express = require('express'),
app = express(),
server = require('http').createServer(app),
io = require('socket.io').listen(server),
ent = require('ent'), // Permet de bloquer les caractères HTML (sécurité équivalente à htmlentities en PHP)
os = require('os');
server.maxConnections = 2;
app.use(express.static('public'));
app.get('/', function (req, res) {
res.sendFile(__dirname + "/index.html");
});
io.sockets.on('connection', function (socket, pseudo) {
// Dès qu'on nous donne un pseudo, on le stocke en variable de session et on informe les autres personnes
socket.on('nouveau_client', function(pseudo) {
}
}
+80
View File
@@ -0,0 +1,80 @@
import BasePolygon from './basepolygon.js';
import Point from './point.js';
import Vector from './vector.js';
export default class Square extends BasePolygon {
static random(...args) {
return new this(
Math.random(-1, 1),
Math.random(-1, 1),
Math.random(0, 1),
...args
);
}
constructor(
x, y, s,
color=BasePolygon.color,
lineWidth=BasePolygon.lineWidth,
fill=BasePolygon.fill
) {
super(color, lineWidth, fill);
this.x = x;
this.y = y;
this.s = s;
}
get size() {return this.s;}
set size(v) {this.s = v;}
get side() {return this.s;}
set side(v) {this.s = v;}
get w() {return s;}
set w(v) {this.s = v;}
get h() {return this.s;}
set h(v) {this.s = v;}
get area() {return this.s**2;}
get center() {
return new Vector(this.x+this.s/2, this.y+this.s/2);
}
set center(vector) {
this.x = vector.x - this.s/2;
this.y = vector.y - this.s/2;
}
get points() {
return [
new Point(this.x, this.y),
new Point(this.x+this.s, this.y),
new Point(this.x+this.s, this.y+this.s),
new Point(this.x, this.y+this.s)
];
}
set points(pts) {
this.x = Math.min(value.map(pts => pts.x));
this.y = Math.min(value.map(pts => pts.y));
// naive mapping because the information is redundant
this.s = Math.max(value.map(pts => pts.x))-this.x;
}
get components() {
return [this.x, this.y, this.z];
}
set components(cps) {
this.x = cps[0];
this.y = cps[1];
this.s = cps[2];
}
show(ctx) {
if (this.lineWidth) {
ctx.lineWidth = this.lineWidth;
}
if (this.fill) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.s, this.s);
} else {
ctx.strokeStyle = this.color;
ctx.strokeRect(this.x, this.y, this.s, this.s);
}
}
contains(p) {
return this.x <= p[0] <= this.x+this.s &&
this.y <= p[1] <= this.y+this.s;
}
}
+373
View File
@@ -0,0 +1,373 @@
/*
A tensor is either an array of tensors or an array of numbers.
Every operation done on tensors is executed recursively.
It makes it easier to use but maybe slower to compute with.
*/
export default class Tensor extends Array {
/*
Every tensor has a norm, order, format and size. Here are stored default values.
*/
static norm = 2;
static order = 1;
static format = [1];
static empty(...format) {
format = format || this.format;
let t = this.from(Array(format[0]));
if (format.length>1) {
for (let i = 0; i < format[0]; i++) {
t[i] = this.empty(...format.slice(1, format.length));
}
}
return t;
}
/**
* Create a tensor filled with zero with given format.
* @param {...Number} format
*/
static zero(...format) {
format = format || this.format;
let t = new Tensor(format[0]);
if (format.length > 1) {
for (let i = 0; i < format[0]; i++) {
t[i] = Tensor.zero(...format.slice(1));
}
} else {
for (let i = 0; i < format[0]; i++) {
t[i] = 0;
}
}
return t;
}
/*
Create a new tensor with the same format of the given one but filled with zeros.
*/
static zeroLike(tensor) {
return this.zero(...tensor.format);
}
static fill(value, ...format) {
let t = this.zero(...format);
return t.fill(value);
}
static fillLike(value, tensor) {
return this.fill(value, ...tensor.format);
}
static random(...format) {
format = format || this.format;
let t = new Tensor(format[0]);
if (format.length==0) {return t};
for (let i = 0; i < format[0]; i++) {
if (format.length==1) {
t[i] = Math.random();
} else {
t[i] = Tensor.random(...format.slice(1));
}
}
return this.from(t);
}
/*
Does the sum of all tensors.
*/
static sum(...tensors) {
return tensors.reduce((x, y) => x.add(y));
}
/**
* Does the average of all tensors.
* @param {...Tensor} Tensor
*/
static average(...tensors) {
return Tensor.sum(...tensors).rdiv(tensors.length);
}
/**
* Multiply all the tensors together.
* @param {...Tensor} tensors
*/
static prod(...tensors) {
return tensors.reduce((x, y) => x.mul(y));
}
/**
* Copy a tensor.
* @param {Tensor} tensor
*/
static copy(tensor) {
return tensor.slice(0);
}
/**
* Determine the distance between multiple tensors.
* @param {...Tensor} tensors
*/
static distance(...tensors) {
if (tensors.length==2) {
return tensors[0].sub(tensors[1]).norm;
}
let d = 0;
let t = undefined;
for (const ti of tensors) {
if (t) {
d += Tensor.distance(t, ti);
}
t = ti;
}
return d;
}
/*
Convert an array of arrays into a tensor of tensors using
a recursive approach.
*/
static convert(v, type=this) {
if (v instanceof Array) {
if (v[0] instanceof Array) {
return v.map(t => this.convert(t, type=type));
} else {
return type.from(v);
}
} else {
return type.from(v);
}
}
/*
The constructor takes an array of arrays as input and makes
it a tensor of tensors.
*/
// constructor(...array) {
// if (array.length==1) {
// super(1);
// this[0] = array[0];
// } else {
// // super(...Tensor.convert(array));
// super(...array);
// }
// }
/*
Return the order of the tensor.
*/
get order() {
if (this[0] instanceof Tensor) {
return this[0].order + 1;
} else {
return 1;
}
}
/*
Return the format of the tensor.
*/
get format() {
if (this[0] instanceof Tensor) {
return [this.length].concat(this[0].format);
} else {
return [this.length];
}
}
/*
Return the size of the tensor which is the product of the format.
*/
get size() {
return this.format.reduce(Math.imul);
}
/*
Return the norm of the tensor.
*/
get norm() {
if (this[0] instanceof Tensor) {
return this.map(t => t.norm ** Tensor.norm).reduce((a, b) => a + b)**(1/Tensor.norm);
} else {
return this.map(t => t ** Tensor.norm).reduce((a, b) => a + b)**(1/Tensor.norm);;
}
}
/*
Set the value of the tensor to the given tensor.
*/
set(t) {
for (let i=0; i<Math.max(t.length, this.length); i++) {
this[i] = t[i];
}
}
/*
Set the value of the tensor to the given tensor recursively.
*/
rset(t) {
if (t[0] instanceof Array) {
for (let i = 0; i < this.length; i++) {
this[i].rset(t[i]);
}
} else {
for (let i = 0; i < this.length; i++) {
this[i] = t[i];
}
}
}
// isort(f) {
// this.set(this.sort(f));
// }
/*
Adapt the tensor length to the given one, by adding zero-tensors or deleting terms.
*/
adapt(n) {
let t = Tensor.copy(this);
if (n > this.length) {
for (let i=0; i<n-this.length; i++) {
t.push(Tensor.zero(...t[0].format));
}
} else {
for (let i=0; i<this.length-n; i++) {
delete t[t.length-1];
}
}
return t;
}
iadapt(n) {
this.set(this.adapt(n));
}
imap(f) {
for (let i=0; i<this.length; i++) {
this[i] = f(this[i]);
}
}
/*
Map that is applied to the lowest elements only.
*/
rmap(f) {
if (this[0] instanceof Tensor) {
return this.map(t => t.rmap(f));
} else {
return this.map(f);
}
}
irmap(f) {
if (this[0] instanceof Tensor) {
for (let i=0; i<this.length; i++) {
this[i].irmap(f);
}
} else {
this.imap(f);
}
}
neg() {
return this.rmap(t => -t);
}
ineg() {
this.irmap(t => -t);
}
inv() {
return this.rmap(t => 1 / t);
}
iinv() {
this.irmap(t => 1 / t);
}
floor() {
return this.rmap(Math.floor);
}
ifloor() {
this.irmap(Math.floor);
}
rmul(k) {
return this.rmap(t => k * t);
}
irmul(k) {
this.irmap(t => k * t);
}
rdiv(k) {
return this.rmap(t => t/k);
}
irdiv(k) {
this.irmap(t => t/k);
}
pow(k) {
return this.rmap(x => x ** k)
}
ipow(k) {
this.irmap(x => x ** k);
}
round(k) {
return this.rmap(x => Math.round(x*10**k)/10**k);
}
iround(k) {
this.irmap(x => Math.round(x*10**k)/10**k);
}
fill(k) {
return this.rmap(t => k);
}
ifill(k) {
this.set(this.fill(k));
}
// component wise operations
map2(tensor, f) {
let m = Math.max(this.length, tensor.length);
let v = this.copy();
if (this[0] instanceof Tensor) {
for (let i=0; i<m; i++) {
v[i] = v.map2(tensor[i], f);
}
} else {
for (let i = 0; i < m; i++) {
v[i] = f(this[i], tensor[i]);
}
}
return v;
}
imap2(tensor, f) {
let m = Math.max(this.length, tensor.length);
if (this[0] instanceof Tensor) {
for (let i=0; i<m; i++) {
this[i].imap2(tensor[i], f);
}
} else {
for (let i = 0; i < m; i++) {
this[i] = f(this[i], tensor[i]);
}
}
}
mul(tensor) {
return this.map2(tensor, (x,y) => x*y);
}
imul(tensor) {
this.imap2(tensor, (x,y) => x*y);
}
add(tensor) {
return this.map2(tensor, (x,y) => x+y);
}
iadd(tensor) {
this.imap2(tensor, (x,y) => x+y);
}
radd(k) {
return this.rmap(t => t+k);
}
iradd(k) {
this.irmap(t => t+k);
}
rsub(k) {
return this.rmap(t => t-k);
}
irsub(k) {
this.irmap(t => t-k);
}
sub(tensor) {
return this.map2(tensor, (x,y) => x-y);
}
isub(tensor) {
this.imap2(tensor, (x,y) => x-y);
}
dot(tensor) {
return Math.sum(this.mul(tensor))
}
copy() {
return this.slice(0);
}
/*
Reshape the tensor to another given format.
*/
reshape(...format) {
throw "Most difficult function to implement.";
}
flatten() {
if (this.order == 1) {
return Tensor.from(this);
} else {
let t = new Tensor();
for (const ti of this) {
t = t.concat(ti.flatten());
}
return t;
}
}
}
+21
View File
@@ -0,0 +1,21 @@
import Tensor from './tensor.js';
let t1 = new Tensor1([1,2], [3,4]);
let t2 = new Tensor1([3,4], [5,6]);
let ta = t1.add(t2);
console.log("addition:", ta);
let tn = t1.neg();
console.log("negation:", tn);
let ts = t1.sub(t2);
console.log("substraction:", ts);
let format = [1,2,3];
console.log(Tensor1.zero(format));
// iadd
// isub
// imul
+27
View File
@@ -0,0 +1,27 @@
export function test(f, args=[], iterations=10**6) {
const start = performance.now();
for(let i=0; i<iterations; i++ ){
f(...args);
};
return performance.now() - start;
}
export function compare(f1, f2, args=[], n=10**6, comparison=(x,y)=>x/y) {
const t1 = test(f1, args, n);
const t2 = test(f2, args, n);
return comparison(t1,t2);
}
export function analyse(fs=[], args=[], iterations=10**6) {
const results = [];
for(let i=0; i<fs.length; i++ ){
results.push(test(fs[i],args,iterations));
};
const m = Math.max(...results);
for(let i=0; i<fs.length; i++ ){
results[i]/=m;
};
return results;
}
+56
View File
@@ -0,0 +1,56 @@
export function map2str(map) {
let str = "{";
for (const [key, value] of map) {
str += key;
str += ":"
str += String(value);
str += ","
}
return str.slice(0, str.length-1)+"}";
}
export function str2map(str) {
str = str.replace("{","").replace("}","");
let m = new Map();
let key, value;
for (const parameters of str.split(",")) {
[key, value] = parameters.split(":")
m.set(key, eval(value));
}
return m;
}
export function tree2str(tree) {
str = "{";
for (const [key, value] of tree) {
str += key;
str += ":"
if (value instanceof Map) {
str += tree2str(String(value))
} else {
str += value;
}
str += ","
}
return str.slice(0, str.length-1)+"}";
}
export function prod(...values) {
if (values.length>1) {
return values[0] * prod(...values.slice(1, values.length));
} else {
return values[0];
}
}
export function round(r, n) {
return Math.round(r*10**n)/10**n;
}
let sigmoid = (x) => 1/(1+Math.exp(-x));
export function hash(s){
return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
}
+5
View File
@@ -0,0 +1,5 @@
export default class Torsor extends VectorField {
constructor() {
}
}
+100
View File
@@ -0,0 +1,100 @@
import Dict from './dict.js';
export default class Tree extends Dict {
paths(path=[]) {
const ps = [];
for (const [k, v] of this) {
if (v instanceof Tree) {
ps.push(...v.paths(path+[k]));
} else {
ps.push(path+[k]);
}
}
return ps;
}
iforEach(f) {
for (const [k,v] of this) {
f(this[k]);
}
}
imap(f) {
for (const [k, v] of this) {
this[k] = f(v);
}
}
irmap(f) {
for (const [k, v] of this) {
if (v instanceof Tree) {
v.irmap(f);
} else {
this[k] = f(v);
}
}
}
traverse() {
}
filter(condition) {
const l = [];
for (const [k,v] of this) {
if (condition(k, v)) {
l.push(v);
}
if (v instanceof Tree) {
l.push(...v.filter(key));
}
}
return l;
}
findOne(key) {
for (const [k,v] of this) {
if (k==key) {
return v;
}
if (v instanceof Tree) {
return v.findOne(k);
}
}
}
findAll(key) {
const l = [];
for (const [k,v] of this) {
if (k==key) {
l.push(v);
}
if (v instanceof Tree) {
l.push(...v.findAll(key));
}
}
return l;
}
rget(path, v) {
const k = path[0];
path = path.slice(1);
if (path.length==0) {
return this.get(k);
}
return this.get(k).rget(path);
}
rset(path, v) {
const k = path[0];
path = path.slice(1);
if (path.length==0) {
return this.set(k, v);
}
return this.get(k).rset(path);
}
getAll() {
const all = [];
for (const [k,v] of this) {
if (v instanceof Tree) {
all.push(...v.getAll());
} else {
all.push(v);
}
}
return all;
}
}
//t = new Tree([[1,2],[3,new Tree([[1,2]])]]);
+36
View File
@@ -0,0 +1,36 @@
export default class Triangle extends Polygon {
// number of points is always 3
static get length() {return 3;}
static get w() {return 3;}
static get width() {return 3;}
static random(
s=1,
color=this.color,
lineWidth=this.lineWidth,
fill=this.fill
) {
return new this(
Matrix.random(this.w, this.h).rmul(s),
color, lineWidth, fill);
}
constructor(matrix, ...args) {
super(matrix, ...args)
}
get area() {
const [a,b,c] = this.segments.map(s => s.length);
console.log(a, b, c);
return 1/4*Math.sqrt((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c));
}
contains(p) {
const [p0, p1, p2] = this.matrix;
const dX = p.x-p2.x;
const dY = p.y-p2.y;
const dX21 = p2.x-p1.x;
const dY12 = p1.y-p2.y;
const D = dY12*(p0.x-p2.x) + dX21*(p0.y-p2.y);
const s = dY12*dX + dX21*dY;
const t = (p2.y-p0.y)*dX + (p0.x-p2.x)*dY;
if (D<0) return s<=0 && t<=0 && s+t>=D;
return s>=0 && t>=0 && s+t<=D;
}
}
+378
View File
@@ -0,0 +1,378 @@
import Tensor from './tensor.js';
export default class Vector extends Tensor {
static length = 2;
// Mathematically a vector doesn't have a dimension
// only a length we abuse of this term to qualify
// the length, same goes for size.
static get dim() {return this.length;}
static set dim(v) {this.length = v;}
static get size() {return this.length;}
static set size(v) {this.length = v;}
static color = "#ffffff";
static norm = 2; // euclidan norm
static lineWidth = 2; // in pixels
static zero(...format) {
return this.from(Tensor.zero(...format));
}
static one(n) {
return this.fill(1, length);
}
static copy = this.from; // Abusive
static empty(n=0) {
return this.from(Array(n));
}
static get zero2() {
return new this(0, 0);
}
static get zero3() {
return new this(0, 0, 0);
}
static random(dim = this.dim, min=-1, max=1) {
let v = new this(dim);
for (let i = 0; i < dim; i++) {
v[i] = (Math.random()*(max-min))+min;
}
return v;
}
static randoms(n, dim = this.dim) {
let vs = [];
for (let i = 0; i < n; i++) {
vs.push(this.random(dim));
}
return vs;
}
static polar(norm, angle) {
return new this(
norm * Math.cos(angle),
norm * Math.sin(angle)
);
}
static fill(value, dim = this.dim) {
let v = new this(dim);
for (let i = 0; i < dim; i++) {
v[i] = value;
}
return v;
}
static adapt(dim, ...vectors) {
// let dim = Math.max(...vectors.map(v => v.dim));
for (let i = 0; i < vectors.length; i++) {
vectors[i].push(...Array(dim - vectors[i].dim).fill(0));
}
}
get x() {
return this[0];
}
set x(value) {
this[0] = value;
}
get y() {
return this[1];
}
set y(value) {
this[1] = value;
}
get z() {
return this[2];
}
set z(value) {
this[2] = value;
}
get sum() {
return this.reduce((x, y) => x+y);
}
get norm() {
return (this.map(x => x ** Vector.norm).reduce((x, y) => x + y))**(1/Vector.norm);
}
set norm(value) {
this.irmul(value/this.norm);
}
// The angle of a vector can only make sense
// when applied on 2d vectors.
// This is an abusive way to generalize for
// simple usage.
get angle() {
return Math.atan2(this.y,this.x);
}
set angle(value) {
const n = this.norm;
this.x = n*Math.cos(value);
this.y = n*Math.sin(value);
}
get components() {
return Array.from(this);
}
set components(values) {
this.set(values);
}
get dim() {
return this.length;
}
set dim(value) {
this.length = value;
}
get inv() {
return this.map(x => 1 / x);
}
get unit() {
let m = Math.max(...this);
return this.map(x => x / m);
}
tdot(other) {
const v = new Vector(this.length);
for (let i=0; i<this.length; i++) {
v[i] = this[i]*other[i];
}
return v;
}
dot(other) {
let r = 0;
for (const [r1, r2] of zip(this, other)) {
r += r1*r2;
}
return r;
}
project(other) {
return other.rmul(this.dot(other)/other.norm)
}
iproject(other) {
this.set(this.project(other));
}
towards(vector) {
return Vector.polar(this.norm, vector.sub(this).angle);
}
itowards(vector) {
this.angle = vector.sub(this).angle;
}
translate(vector) {
this.iadd(vector);
}
rotate(angle, vector=undefined) {
vector = vector || Vector.zero(Vector.length);
this.isub(vector)
this.angle += angle;
this.iadd(vector);
}
irot(angle, vector=undefined) {
this.rotate(angle, vector);
}
rot(angle, vector=undefined) {
vector = vector || Vector.zero(Vector.length);
const v = this.sub(vector)
v.angle += angle;
v.iadd(vector);
return v;
}
equals(vector) {
for (let i = 0; i < Math.max(vector.dim, this.dim); i++) {
if (this[i] != vector[i]) {
return false;
}
}
return true;
}
colinear(vector, error = 10 ** -10) {
return this.mul(vector).norm < error;
}
fill(value) {
return Vector.from(super.fill(value));
}
ifill(value) {
for (let i = 0; i < this.dim; i++) {
this[i] = value;
}
}
map(f) {
return Vector.from(super.map(f));
}
imap(f) {
for (let i = 0; i < this.dim; i++) {
this[i] = f(this[i]);
}
}
slice(a, b) {
return Vector.from(super.slice(a, b));
}
map2(vector, f) {
let m = Math.max(this.dim, vector.dim);
let v = Vector.empty(m);
for (let i = 0; i < m; i++) {
v[i] = (f(this[i], vector[i]));
}
return v;
}
imap2(vector, f) {
let m = Math.max(this.dim, vector.dim);
for (let i = 0; i < m; i++) {
this[i] = (f(this[i], vector[i]));
}
}
adapt(n) {
let v = Vector.copy(this);
if (n>this.length) {
for (let i=0; i<n-this.length; i++) {
v.push(0);
}
} else {
for (let i=0; i<this.length-n; i++) {
delete v[v.length-1];
}
}
return v;
}
// prod(vector) {
// // Vectorial product
// throw "Not implemented error";
// }
div(k) {
return this.rmul(1 / k);
}
idiv(k) {
this.imul(1 / k);
}
ifloor() {
this.imap(Math.floor);
}
floor() {
return this.map(Math.floor);
}
limit(vmin, vmax) {
let m = Math.max(this.length, vmin.length, vmax.length);
for (let i=0; i<m; i++) {
this[i] = Math.max(this[i], vmin[i]);
this[i] = Math.min(this[i], vmax[i]);
}
}
call(point) {
return Point.from(this.add(point));
}
get normalized() {
return this.rdiv(this.norm);
}
/**
* Shows an arrow.
*/
show(
ctx,
position=Vector.zero(this.dim),
color=Vector.color,
lineWidth=Vector.lineWidth,
factor=10,
angle=Math.PI/6
) {
ctx.beginPath();
ctx.context.lineWidth = lineWidth;
ctx.strokeStyle = color;
ctx.moveTo(...position);
const p = this.add(position)
ctx.lineTo(...p);
const v = this.rdiv(-factor);
v.rotate(angle)
ctx.lineTo(...v.add(p));
v.rotate(-2*angle)
ctx.moveTo(...p)
ctx.lineTo(...v.add(p));
ctx.stroke();
ctx.closePath();
}
closest(vectors) {
let a = Infinity;
let b, v;
for (const vi of vectors) {
b = this.sub(vi).norm;
if (a > b) {
v = vi;
a = b;
}
}
return v;
}
closestIndex(vectors) {
let a = Infinity;
let b;
let i = 0,
j = 0;
for (const vi of vectors) {
b = this.sub(vi).norm;
if (a > b) {
a = b;
j = i;
}
i++;
}
return j;
}
farthest(vectors) {
let a = 0;
let b, v;
for (const vi of vectors) {
b = this.sub(vi).norm;
if (a < b) {
v = vi;
a = b;
}
}
return v;
}
farthestIndex(vectors) {
let a = Infinity;
let b;
let i = 0,
j = 0;
for (const vi of vectors) {
b = this.sub(vi).norm;
if (a < b) {
a = b;
j = i;
}
i++;
}
return j;
}
get directionCosines() {
for (let i=0; i<this.length; i++) {
}
}
}
export class Vector2 extends Vector {
// force the dimension to be 2
static get dim() {return 2;}
static polar(norm, angle) {
return new this(
norm * Math.cos(angle),
norm * Math.sin(angle)
);
}
constructor(x, y) {
super(x, y);
}
get angle() {
return Math.atan2(this[1],this[0]);
}
set angle(value) {
this[0] = this.norm*Math.cos(value);
this[1] = this.norm*Math.sin(value);
}
}
export class Vector3 extends Vector {
// force the dimension to be 3
static get dim() {return 3;}
constructor(x, y, z) {
super(x, y, z);
}
cross(vector) {
return new Vector(
this[2]*vector[3]-this[3]*vector[2],
this[3]*vector[1]-this[1]*vector[3],
this[1]*vector[2]-this[2]*vector[1]
);
}
rotate(angle, vector) {
}
}
+72
View File
@@ -0,0 +1,72 @@
/**
* Wikipedia:
* In vector calculus and physics, a vector field is an
* assignment of a vector to each point in a subset of space.
*/
export class TensorField {
constructor(f) {
this.f = f;
}
call(vector) {
return this.f(vector);
}
add(vf) {
return new VectorField(
function add(f1, f2, x) {
return f1(x).add(f2(x));
}.bind(null, this.f, vf.f));
}
sub(vf) {
return new VectorField(function sub(f1, f2, x) {
return f1(x).sub(f2(x));
}.bind(null, this.f, vf.f));
}
mul(vf) {
return new VectorField(function mul(f1, f2, x) {
return f1(x).mul(f2(x));
}.bind(null, this.f, vf.f));
}
neg() {
return new VectorField(function neg(f, x) {
return f(x).neg();
}.bind(null, this.f));
}
vadd(v) {
return new VectorField(function vadd(f1, v, x) {
return f1(x).add(v);
}.bind(null, this.f, v));
}
vsub(v) {
return new VectorField(function vsub(f1, v, x) {
return f1(x).sub(v);
}.bind(null, this.f, v));
}
vmul(v) {
return new VectorField(function vmul(f1, v, x) {
return f1(x).mul(v);
}.bind(null, this.f, v));
}
}
export default class VectorField extends {
}
function vadd2(v) {return v.radd(2)}
function vsub1(v) {return v.rsub(1)}
var vf1 = new VectorField(vadd2);
var vf2 = new VectorField(vsub1);
var vf3 = vf2.add(vf1)
var v = Vector.of(1, 0);
console.log(vf1.call(v));
console.log(vf2.call(v));
console.log(vf3.call(v));
/**
.load tensor.js
.load vector.js
.load VectorField.js
*/
+50
View File
@@ -0,0 +1,50 @@
import Polygon from "../libs/polygon.js";
import Entity from "../libs/entity.js";
import Body from '../libs/body.js';
import Motion from '../libs/motion.js';
export class AsteroidForm extends Polygon {
static color = "#3f87a5";
static w = 2;
static fill = true;
static maxPoints = 8;
static minPoints = 3;
static size = 1;
static random(color=AsteroidForm.color, fill=Asteroid.fill) {
const n = Math.floor((AsteroidForm.maxPoints-AsteroidForm.minPoints)*Math.random())+AsteroidForm.minPoints;
const p = Polygon.random(n, 2, 10);
return new this(p.matrix, color, p.width, fill);
}
}
export default class Asteroid extends Entity {
static color = "#3f87a5";
static w = 5;
static riskOfSelfDestruction = 0.00005;
static rotation = 0.1;
static movement = 0.1;
static random() {
const form = AsteroidForm.random()
form.fill = true;
const body = new Body(
Motion.random(2, 2).rsub(1/2).rmul(2), // values between 0 and 1
Motion.random(2, 1).rsub(1/2).rmul(2)
);
body[0][1].irmul(Asteroid.rotation); // reducing velocity
body[1][1].irmul(Asteroid.movement); // reducing angular velocity
return new this(form, body)
}
update(dt) {
super.update(dt);
this.form.angle = this.body[1][0][0]; // setting form angle
// the form position is already dealt with
if (Math.random() <= dt * Asteroid.riskOfSelfDestruction) {
console.log("Explosion imminente !!!!");
this.selfDestruction();
}
}
selfDestruction() {
//silly way of destroying an asteroid
return this.removing=true;
}
}
+49
View File
@@ -0,0 +1,49 @@
import Group, {Grouper} from "../libs/group.js";
import Asteroid from './asteroid.js';
export default class AsteroidGroup extends Group {
static n = 50;
static asteroidField(numberAst=AsteroidGroup.n, centerPosition, dimension) {
let listDelist=[];
let id, ast;
for (let pas = 0; pas < numberAst; pas++) {
ast = Asteroid.random();
ast.x = centerPosition.x + (Math.random() * dimension.x)/2;
ast.y = centerPosition.y + (Math.random() * dimension.y)/2;
id = "asteroid:"+pas;
listDelist.push([id,ast])
}
return new this(listDelist);
}
constructor(g) {
super(g);
this.spread(100);
}
spread(n) {
for (const v of this.values()) {
v.position.angle = 2*Math.PI * Math.random();
v.position.norm = n*Math.random();
}
}
removeDeads() {
const deads = []
for (const [k,v] of this) {
if (v.removing) {
deads.push(k)
}
}
for (const dead of deads) {
this.delete(dead)
}
}
update(dt) {
super.update(dt);
this.removeDeads()
}
selfDestruction() {
this.clear();
//Détruit tous les asteroids du groupe
}
}
+33
View File
@@ -0,0 +1,33 @@
// import Tensor from "../libs/tensor.js";
// import Vector from "../libs/vector.js";
// import zip from "../libs/iterator.js";
var canvas = document.getElementById("canvas");
var context = new ContextAdapter(canvas.getContext("2d"));
context.width = canvas.width = window.innerWidth;
context.height = canvas.height = window.innerHeight;
context.plane.units.position = new Vector(0.2, 0.2);
// var name = prompt("name");
var host = String(document.location);
var socket = io.connect(host) //, {query: "name="+name});
// var group = SuperGroup.random();
// var game = new Game(group);
var game = Game.random();
gameClient = new GameClient(canvas, game, socket)
gameClient.main();
+51
View File
@@ -0,0 +1,51 @@
import EventEmitter from 'events';
import Missile from './missile.js';
import Spaceship from './spaceship.js';
export default class Collider {
collide(group) {
throw "A Collider must have a collide method which takes a group as an argument."
}
}
export class SuperCollider extends Collider {
constructor(...colliders) {
super();
this.colliders = colliders;
this.collisionEmitter = new EventEmitter();
}
initCollisionListener(serverEmitter) {
Missile.initCollisionListener(this.collisionEmitter, serverEmitter);
Spaceship.initCollisionListener(this.collisionEmitter, serverEmitter);
}
collide(superGroup) {
for (const collider of this.colliders) {
collider.collide(superGroup, this.collisionEmitter);
}
}
}
export class MissileSpaceshipCollider extends Collider {
collide(group, collisionEmitter) {
const deadMissiles = [];
const deadSpaceships = [];
const missileGroup = group.get('missileGroup');
const spaceshipGroup = group.get('spaceshipGroup');
for (const [mid,missile] of missileGroup) {
for (const [sid, spaceship] of spaceshipGroup) {
if (missile.collideWithSpaceship(spaceship)) {
collisionEmitter.emit('spaceship/missile');
collisionEmitter.emit('missile/spaceship');
console.log("collision:", mid, sid);
deadMissiles.push(mid);
deadSpaceships.push(sid);
}
}
}
for (const sid of deadSpaceships) {
spaceshipGroup.delete(sid);
}
return [deadMissiles, deadSpaceships];
}
}
+3
View File
@@ -0,0 +1,3 @@
export default class Dasher {
}
+11
View File
@@ -0,0 +1,11 @@
// Cool explosion when the missiles hit something.
import { ColorChanger } from '../libs/animation.js';
export default class Explosion extends ColorChanger {
constructor() {
}
end() {
super.end();
this.object.removing = true;
}
}
+14
View File
@@ -0,0 +1,14 @@
// Using strategy pattern for spaceships
export default class Follower {
orientate(body, form) {
//tourne le Follower vers sa vitesse
form.angle = body.velocity.angle;
}
follow(body, position, velocityNorm) {
//change l'acceleration du Follower en fonction de la position donnée
const velocityFollower = position.sub(body.p);
velocityFollower.norm = velocityNorm;
body.velocity = velocityFollower;
}
}
+185
View File
@@ -0,0 +1,185 @@
// GameClient => Game => SuperGroup => ...
// => GameMap
// => Context
// GameServer => Game => SuperGroup => ...
// => GameMap
import SuperGroup from './supergroup.js';
import SpaceshipGroup from './spaceshipgroup.js';
import MissileGroup from './missilegroup.js';
import AsteroidGroup from './asteroidgroup.js';
import MeteorGroup from './missilegroup.js';
import Group from '../libs/group.js';
import Motion from '../libs/motion.js';
import Matrix from '../libs/matrix.js';
import Body from '../libs/body.js';
import Point from '../libs/point.js';
import Vector from '../libs/vector.js';
import Tensor from '../libs/tensor.js';
import Spaceship from './spaceship.js';
import Missile from './missile.js';
import Asteroid, {AsteroidForm} from './asteroid.js';
import Meteor from './meteor.js';
import GameMap from './gamemap.js';
import Life, {LifeBar} from './life.js';
import Follower from './follower.js';
import Shooter from './shooter.js';
import Square from '../libs/square.js';
import Rectangle from '../libs/rectangle.js';
import Segment from '../libs/segment.js';
import Polygon from '../libs/polygon.js';
import BasePolygon from '../libs/basepolygon.js';
import { SuperCollider,MissileSpaceshipCollider} from './collider.js';
export default class Game {
static dt = 0.1;
static mapTypes = [
SuperGroup,
SpaceshipGroup,
MissileGroup,
AsteroidGroup,
MeteorGroup,
Group,
]
static arrayTypes = [
Motion,
Matrix,
Body,
Point,
Vector,
Tensor,
]
// NOTE THIS IS VERY IMPORTANT
// EVERY OBJECTS MUST HAVE DEFAULTS ARGUMENTS IN THEIR CONSTRUCTORS
// OTHERWISE BUGS WILL APPEAR WHILE CREATING OBJECTS WITH NO ARGUMENTS
// THIS OPERATION IS DONE WHEN ITS IS WRITTEN:
// return Object.assign(new type(), value.value);
static objectTypes = [
Spaceship,
Missile,
Asteroid,
Meteor,
GameMap,
Life,
LifeBar,
Follower,
Shooter,
//Hunter,
AsteroidForm,
Square,
Rectangle,
// Circle,
Segment,
// Triangle,
Polygon,
BasePolygon,
]
static reviver(key, value) {
if(typeof value === 'object' && value !== null) {
for (const type of Game.mapTypes) {
if (value.type === type.name) {
return new type(value.value);
}
}
for (const type of Game.arrayTypes) {
if (value.type === type.name) {
return type.from(value.value);
}
}
for (const type of Game.objectTypes) {
if (value.type === type.name) {
return Object.assign(new type(), value.value);
}
}
if (value.type === 'Map') {
return new Map(value.value);
}
if (value instanceof Array) {
return value;
}
// return Object.assign(new AsteroidForm(), value);
// console.log(value);
}
return value;
}
static replacer(key, value) {
const originalObject = this[key];
if (value === null) return null;
if(originalObject instanceof Map) {
for (const type of Game.mapTypes) {
if (originalObject instanceof type) {
return {
type : type.name,
value: Array.from(originalObject.entries())
};
}
}
return {
type: "Map",
value: Array.from(originalObject.entries())
};
} else if (originalObject instanceof Array) {
for (const type of Game.arrayTypes) {
if (originalObject instanceof type) {
return {
type : type.name,
value: Array.from(originalObject.round(2))
};
}
}
} else if (typeof a == 'number') {
return Math.round(number*10**2, 2)/10**2;
} else {
for (const type of Game.objectTypes) {
if (originalObject instanceof type) {
return {
type : type.name,
value: {...originalObject}
};
}
}
return value;
}
return value;
}
// Making a getter is memory friendly
// and it doesn't matter that we can't
// change the result.
static get gameMap() {
return GameMap.fromSize(200,200);
}
static random() {
return new this(
SuperGroup.random()
);
}
constructor(
group = new SuperGroup(), // this super group is empty
collider = new SuperCollider(new MissileSpaceshipCollider()),
gameMap = Game.gameMap,
dt = Game.dt
) {
this.group = group;
this.collider = collider;
this.gameMap = gameMap;
this.dt = dt;
}
update() {
// this.collider.collide(this.group);
this.group.update(this.dt);
this.limit();
}
collide() {
const collisions = this.collider.collide(this.group);
}
show(ctx) {
this.gameMap.show(ctx);
this.group.show(ctx);
}
limit() {
const entities = this.group.getAll();
this.gameMap.limit(entities);
}
}
+176
View File
@@ -0,0 +1,176 @@
export default class GameClient extends Manager {
static movement = {up: false, down: false, right: false, left: false, zoomin: false, zoomout: false};
static backgroundColor = "#000000";
constructor(
canvas,
game,
socket,
) {
super(canvas, game)
this.socket = socket;
this.id = null;
this.mouses = new Map();
}
on() {
this.socket.on('newMissile', function (data) {
const missile = JSON.parse(data.missile, Game.reviver);
const missileGroup = this.game.group.get('missileGroup');
missileGroup.add(missile);
}.bind(this));
this.socket.on("oneSpaceshipBody", function (data) {
const spaceship = this.game.group.get('spaceshipGroup').take(data.uuid);
spaceship.body.rset(data.body);
}.bind(this));
console.log("on écoute les sockets du serv");
this.socket.on('spaceshipGroup', function (data) {
const spaceshipGroup = JSON.parse(data, Game.reviver);
this.game.group.set("spaceshipGroup", spaceshipGroup);
// console.log("spaceship");
}.bind(this));
this.socket.on('spaceshipGroupBodies', function (data) {
//console.log('spaceshipGroupBodies');
// console.log(data.bodies);
this.game.group.get("spaceshipGroup").setBodies(data.bodies);
}.bind(this));
this.socket.on('superGroup', function (data) {
const superGroup = JSON.parse(data, Game.reviver);
this.game.group = superGroup;
}.bind(this));
this.socket.on('asteroidGroup', function (data) {
const asteroidGroup = JSON.parse(data, Game.reviver);
console.log(`On reçois un asteroidGroup`);// ${data}`);
this.game.group.set('asteroidGroup', asteroidGroup);
}.bind(this));
this.socket.on('oneMouseMove', function(data) {
this.mouses.set(data.uuid, Vector.from(data.mouse));
console.log(this.mouses);
}.bind(this));
this.socket.on('mouses', function (data) {
const spaceshipGroup = this.game.group.get('spaceshipGroup')
for (const [k,v] of data.mouses) {
spaceshipGroup.take(k).follow(Vector.from(v));
}
}.bind(this));
const socketID = this.socket.id;
this.socket.on('spaceshipID', function (data) {
const spaceshipID = data.spaceshipID;
//console.log(`On reçois l'id : socket.id chez nous : ${socketID} socket id chez serv : ${spaceshipID}`);// ${data}`);
console.log(`l'id de notre spaceShip ${spaceshipID}`);
this.id = spaceshipID;
}.bind(this));
this.socket.on("summonSpaceship", function (data) {
//ajoute le vaisseau en
//data={uuid: this.socket.id, coordinates: [10,10]}
const spaceshipGroup = this.game.group.get('spaceshipGroup');
const spaceship = Spaceship.random();
spaceship.form.color = "green";
spaceship.position = Vector.from(data.coordinates);
spaceshipGroup.add(data.uuid, spaceship);
// spaceshipGroup.addSpaceship(data.uuid, spaceship);
}.bind(this));
this.socket.on("unsummonSpaceship", function (data) {
const spaceshipGroup = this.game.group.get('spaceshipGroup');
spaceshipGroup.remove(data.uuid);
}.bind(this));
this.socket.on("spaceship-update", function(data) {
const spaceshipGroup = this.game.group.get('spaceshipGroup');
if (data.event=='life-change') {
const spaceship = spaceshipGroup.get(data.sid);
spaceship.life.value = data.life;
} else if (data.event=='removing') {
spaceshipGroup.delete(data.sid);
}
}.bind(this));
}
get player() {
const spaceshipGroup = this.game.group.get('spaceshipGroup');
return spaceshipGroup.take(this.id);
}
get spaceship() {
const spaceshipGroup = this.game.group.get('spaceshipGroup');
return spaceshipGroup.take(this.id);
}
show() {
const player = this.player;
if (player) {
this.context.plane.position = player.position;
}
super.show();
}
update() {
const player = this.player;
if (player) {
player.follow(this.context.fromScreen(this.mouse));
// this.socket.emit("spaceshipBody", {body: player.body});
this.socket.emit("mouseMove", {mouse: Array.from(this.context.fromScreen(this.mouse))});
}
// console.log('update');
this.followAllMouses();
super.update()
}
followAllMouses() {
const spaceshipGroup = this.game.group.get('spaceshipGroup');
for (const [k,v] of this.mouses) {
spaceshipGroup.take(k).follow(v);
}
}
onKeyDown(e) {
super.onKeyDown(e);
if (this.id === null) {
//on n'a pas encore de spaceship
console.log(`Vous n'avez pas encore de spaceship !`);
return false;
}
if (e.code == 'Space') {
const missile = this.player.shoot();
this.game.group.get('missileGroup').add(missile);
const data = JSON.stringify(missile, Game.replacer);
this.socket.emit("missile", {missile:data});
}
}
// onMouseMotion(e) {
// super.onMouseMotion(e);
// this.socket.emit("mouseMove", {mouse: Array.from(this.context.fromScreen(this.mouse))});
// }
start() {
super.start()
this.on()
}
main() {
this.start();
setInterval(this.loop.bind(this), 20);
}
// let player = this.game.map.group.playerGroup.map.get(this.id);
// if (player) {
// if (!player.alive) {
// console.log("you are dead");
// this.socket.emit("player-respawn");
// }
// }
// if ('id' in this) {
// this.game.map.group.collideClient(this.id);
// }
// if (this.game.map.group.cache.size>0) { // could overrun the server
// this.socket.emit(
// "cache",
// JSON.stringify(this.game.map.group.cache, Game.replacer)
// );
// this.game.map.group.cache.clear();
// }
// this.socket.emit("control-mousemove", this.context.fromScreen(this.mouse));
// }
}
+17
View File
@@ -0,0 +1,17 @@
import Entity from '../libs/entity.js';
export default class GameEntity extends Entity {
constructor(form, body) {
super(form, body)
this.removing = false;
}
}
// alive() {
// this.life.
// }
// die() {
// this.life.value = 0;
// }
// spawn() {
// this.life.value = this.life.max;
// }
+28
View File
@@ -0,0 +1,28 @@
import SuperGroup from './supergroup.js';
import AsteroidGroup from './asteroidgroup.js';
export default class GameLevel {
constructor() {
}
get group() {
return new SuperGroup();
}
}
export class AsteroidLevel extends GameLevel {
constructor(n) {
super();
this.n = n;
}
get group() {
return new SuperGroup([
['asteroidGroup', AsteroidGroup.random(this.n)]
]);
}
update() {
}
}
+73
View File
@@ -0,0 +1,73 @@
import Rectangle from '../libs/rectangle.js';
import Vector from '../libs/vector.js';
export default class GameMap {
static fromSize(n,m) {
const x=-n/2, y=-n/2, w=n, h=n;
const r = new Rectangle(new Vector(x, y), new Vector(w, h))
return new this(r);
}
constructor(rectangle) {
this.rectangle = rectangle;
}
get r() {
return this.rectangle;
}
set r(value) {
this.rectangle = value;
}
compute(position) {
let x,y
if (Math.abs(position.x) > this.r.w / 2) {
let delta = position.x % this.r.w / 2;
delta = 0;
x = -(position.x/Math.abs(position.x)) * (this.r.w / 2) + delta;
} else {
x = position.x;
}
if (Math.abs(position.y) > this.r.h / 2) {
let delta = position.y % this.r.h / 2;
delta = 0;
y = -(position.y/Math.abs(position.y)) * (this.r.h / 2) + delta;
} else {
y = position.y;
}
return new Vector(x, y);
}
compute2(position) {
const t = this.r.size.rdiv(2);
// console.log(position)
// console.log(t)
// position.iradd(t);
position.x += this.r.w/2;
position.y += this.r.h/2;
position.x = position.x % this.r.w;
position.y = position.y % this.r.h;
position.x -= this.r.w/2;
position.y -= this.r.h/2;
// position.irsub(t);
return position;
}
limit(entities) {
for (const entity of entities) {
entity.position = this.compute(entity.position);
}
}
show(ctx) {
this.rectangle.show(ctx)
}
update(dt) {
}
}
+225
View File
@@ -0,0 +1,225 @@
import EventEmitter from 'events';
var ping = 0;
export class Player {
constructor(id) {
this.id = id;
this.update = new EventEmitter();//Utilise par SockCom/SockClient pour les socket
this.mouse = Vector.zero2;
//this.update.emit("lifeUpdate", this.life.value);
}
join() {
//ajoute le vaisseau de this dans le jeu
}
left() {
//retire le vaisseau du joueur
}
}
export class socketClient {
//se trouve côté server. Une instance de socketClient par onglet du jeu
constructor(socket, gameServer) {
this.socket = socket;
this.player = new Player(this.socket.id);//fabrication d'une instance Player pour notre client
this.gameServer = gameServer;
}
connect() {
//lancée quand l'objet se connecte au serveur
const spaceshipGroup = this.gameServer.game.group.get('spaceshipGroup');
spaceshipGroup.add(this.socket.id, Spaceship.random());
this.socket.emit("spaceshipID", { spaceshipID: this.socket.id });
this.player.join();
this.registerPlayerEvent(this.player);
this.on();
const superGroup = this.gameServer.game.group;
//hipGroup = this.gam erver.game.group.get("spaceshipGroup")
//const asteroidGroup = this.gameServer.game.group.get("asteroidGroup");
//const str = JSON.stringify(group, Game.replacer);
//console.log(`On envoie à ${this.socket.id} le groupe de ${str.length} caractère.`); //superGroup : ${str}`);
//this.socket.emit("superGroup", str);
const data = JSON.stringify(superGroup, Game.replacer)
this.socket.emit("superGroup", data);//le nouveau joueur reçois son spaceship ici
for (let so_id in this.gameServer.socketList) {//todo faire un truc async et utiliser broadcast
if (so_id != this.socket.id) {
this.gameServer.socketList[so_id].socket.emit("summonSpaceship", {uuid: this.socket.id, coordinates: [0,0]})//todo
}
}
}
get spaceship() {
return this.gameServer.game.group.get('spaceshipGroup').take(this.socket.id);
}
disconnect() {
//quand l'objet se déconnecte du serveur
this.player.left();
const spaceshipGroup = this.gameServer.game.group.get("spaceshipGroup")
spaceshipGroup.remove(this.socket.id);
this.gameServer.sendAll("unsummonSpaceship", {uuid:this.socket.id});
}
on() {
//permet de recevoir les commandes envoyée par le joueur
if (this.player === undefined || this.player === null) {
console.log("on essai de iniSocketAPI alors que le joueur est pas co :/");
}
// const player = this.player;
this.socket.on("message", function (message) {
console.log(message);
this.socket.emit("message", message);
});
this.socket.on("mouseMove", function (data) {
this.player.mouse.set(data.mouse);
// this.socket.broadcast.emit("oneMouseMove", {uuid:this.socket.id, mouse:data.mouse});
// console.log(data.mouse)
// const spaceship = this.gameServer.game.group.get('spaceshipGroup').take(this.socket.id);
// if (spaceship) {
// spaceship.follow(Vector.from(data.mouse));
// this.gameServer.sendAll("oneSpaceshipBody", {body: spaceship.body, uuid: this.socket.id});
// }
}.bind(this));
this.socket.on("spaceshipBody", function(data) {
this.spaceship.body.rset(data.body);
}.bind(this));
this.socket.on("missile", function (data) {
//le client a envoyé un missile
//todo : vérif que le client respecte les cooldown
// console.log(`le client envoi un missile`);
const missile = JSON.parse(data.missile, Game.reviver);
// console.log(missile);
this.gameServer.game.group.get('missileGroup').add(missile);
// console.log(this.gameServer.game.group.get('missileGroup'));
this.socket.broadcast.emit("newMissile", data);
}.bind(this));
}
registerPlayerEvent(player) {
//permet d'annnoncer à tous les joueurs des updates
const socket = this.socket;
// const player = this.player;
const myGameServer = this.gameServer;
player.update.on("coordinatesUpdate", function (data) {
//console.log("coordinatesUpdate event", data);
myGameServer.sendAll("coordinatesUpdate", { uuid: player.id, coordinates: data.coordinates });
});
player.update.on("directionUpdate", function (data) {
//console.log("directionUpdate event", data);
myGameServer.sendAll("directionUpdate", { uuid: player.id, direction: data.direction });
});
player.update.on("unsummonPlayer", function (data) {
myGameServer.sendAll("unsummonPlayer", { uuid: data.uuid });
});
player.update.on("summonPlayer", function (data) {
myGameServer.sendAll("summonPlayer", { name: data.name, uuid: data.uuid, coordinates: data.coordinates });
});
}
}
export default class GameServer {
constructor(game, io) {
this.game = game;
this.socketList = {};
this.io = io;
this.eventEmitter = new EventEmitter(); // intern event emitter
}
setUp() {
//A lancer lors du démarrage du server
this.initUpdate();
this.acceptConnection();
}
sendAll(event, message) {//todo utiliser les boradcasts https://socket.io/docs/#Broadcasting-messages
for (let so_id in this.socketList) {//todo faire un truc async
this.socketList[so_id].socket.emit(event, message);
}
}
acceptConnection() {
//demande au seveur d'accepter les connexions des clients
console.log(`On écoute les connexions des clients`);
this.io.sockets.on("connection", function (socket) {
const id = socket.id;
console.log(`Nouveau client d'id ${JSON.stringify(id)}`);
this.socketList[id] = new socketClient(socket, this);
this.socketList[id].connect();
socket.on("disconnect", function () {
console.log(`Deconnexion client d'id ${JSON.stringify(id)}`);
this.socketList[id].disconnect();
delete this.socketList[id];
}.bind(this));
}.bind(this));
}
initUpdate() {
this.eventEmitter.on("spaceship-update", function(data) {
this.sendAll("spaceship-update", data);
}.bind(this));
//A lancer au lancement du serveur, permet d'informer les clients des dernières news
//this.game.on("Event Update News !!", function (data) {
// console.log("event caught by GameServer !!");
// this.sendAll("Update de la mort", { param1: "ciao les losers", param2: "toujours pour décrire l'update" });
//});
}
update() {
this.follow();
// this.sendMouses();
this.game.update();
this.game.collide();
}
sendMouses() {
const mouses = [];
for (const id in this.socketList) {//todo faire un truc async
mouses.push([id, this.socketList[id].player.mouse]);
}
this.sendAll("mouses", {mouses: mouses});
}
follow() {
let socket, spaceship;
for (const id in this.socketList) {
socket = this.socketList[id]
spaceship = socket.spaceship;
if (spaceship) { // spaceship might die.
spaceship.follow(socket.player.mouse);
}
}
}
loop() {
this.update();
}
start() {
console.log("starting the game");
this.game.collider.initCollisionListener(this.eventEmitter);
}
main() {
this.start();
// console.log(this.game.dt*100);
// this.game.dt = 0.3;
setInterval(this.loop.bind(this), 20);
setInterval(this.streamSpaceshipGroupBodies.bind(this), 20);
}
streamSpaceshipGroupBodies() {
const spaceshipGroup = this.game.group.get('spaceshipGroup');
this.sendAll('spaceshipGroupBodies', {bodies: spaceshipGroup.getBodies()});
}
}
+10
View File
@@ -0,0 +1,10 @@
// Using strategy pattern for spaceships
export default class Hunter {
constructor(spaceship, target=undefined) {
this.spaceship = spaceship;
this.target = target;
}
hunt() {
this.spaceship.follow(this.target.center);
}
}
+54
View File
@@ -0,0 +1,54 @@
import Rectangle from '../libs/rectangle.js';
import Vector from '../libs/vector.js';
export default class Life {
static value = 1;
static max = 1;
constructor(value=Life.value, max=Life.max) {
this.value = value;
this.max = max;
// It doesn't matter where the lifebar is
// because it only matters when showing it.
this.bar = new LifeBar();
}
isAlive() {
return this.value > 0;
}
regen() {
this.value = this.max;
}
show(ctx, position) {
this.bar.show(ctx, position, this.value/this.max);
}
}
export class LifeBar extends Rectangle {
static areaColor = "lightgreen";
static borderColor = "white";
static margin = 2;
static position = [0,0];
static size = [3, 0.5];
static lineWidth = 0.1;
// defining new default arguments
constructor(
position=LifeBar.position,
size=LifeBar.size,
color=undefined,
lineWidth=LifeBar.lineWidth,
fill=undefined,
) {
super(position, size, lineWidth, color, fill);
}
// the order of the operations is very important
show(ctx, position, ratio, margin=LifeBar.margin) {
this.center = position.sub(new Vector(0, -margin));
const w = this.w;
this.w *= ratio;
this.color = LifeBar.areaColor;
this.fill = true;
super.show(ctx)
this.w = w;
this.color = LifeBar.borderColor;
this.fill = false;
super.show(ctx);
}
}
+22
View File
@@ -0,0 +1,22 @@
import GameEntity from './gameentity.js';
import Vector from '../libs/vector.js';
import Square from '../libs/square.js';
import Body from '../libs/body.js';
export default class Meteor extends GameEntity {
static color = "#c50000";
//m = new Motion(new Vector(1, 2))
static fromVelocity(velocity) {
let form = new Square(-1, -1, 2);
form.fill = true;
let body = Body.random(2, 2, 2);
form.color = Meteor.color;
body.velocity = velocity;
return new this(form, body)
}
static random() {
return this.fromVelocity(Vector.random());
}
}
+12
View File
@@ -0,0 +1,12 @@
import {Grouper} from '../libs/group.js';
import Meteor from './meteor.js';
export default class MeteorGroup extends Grouper(Meteor) {
static meteorShower(n) {
let wtf = [];
for (let pas = 0; pas < n; pas++) {
wtf.push(["meteor:"+pas, Meteor.random()]);
}
return new this(wtf);
}
}
+88
View File
@@ -0,0 +1,88 @@
import Entity from '../libs/entity.js';
import Segment from '../libs/segment.js';
export default class Missile extends Entity {
static size = 3;
static duration = 1000/10; // mili-seconds
static velocityBoost = 10;
static damage = 0.1;
static margin = 1;
static color = "yellow";
static random(
source=undefined,
target=undefined
) {
return new this(
new Segment([0, 0], [0, Missile.size]),
Body.random(1, 2),
source,
target
);
}
static make(
source,
target=undefined,
damage=Missile.damage,
duration=Missile.duration
) {
const body = new Body(source.body[0].slice(0, 2).copy());
body[0][1].norm += this.velocityBoost;
const radius = source.form.hitBubbleRadius;
const margin = body[0][1].normalized.rmul(radius+Missile.size/2+Missile.margin);
body[0][0].iadd(margin);
return new this(
new Segment([0, 0], [0, Missile.size], 1, Missile.color),
body,
source,
target,
damage,
duration
);
}
constructor(
form,
body,
source=undefined,
target=undefined,
damage=Missile.damage,
duration=Missile.duration,
) {
super(form, body);
Missile.initCollision(this);
this.source = source;
this.target = target;
this.damage = damage;
this.duration = duration;
this.time = undefined;
}
update(dt) {
if (!this.time) {
this.time = Date.now()
}
if (!this.isAlive(dt)) {
this.removing = true;
}
this.form.angle = this.body[0][1].angle;
super.update(dt);
}
get left() {
return Date.now() - this.time;
}
isAlive(dt) {
return this.left*dt<this.duration;
}
/**
* Determine whether a missile is colliding with a spaceshiop
* @param {Spaceship} spaceship
*/
collideWithSpaceship(spaceship) {//pk c'est pas static ? parce que c'est la vie
return spaceship.form.contains(this.form.p1) || spaceship.form.contains(this.form.p2);
}
static initCollisionListener(collisionEmitter, mainEmitter) {
collisionEmitter.on('missile/spaceship', function(missile, spaceship) {
missile.removing = true;
})
}
}
+25
View File
@@ -0,0 +1,25 @@
import {Grouper} from '../libs/group.js';
import Missile from './missile.js';
export default class MissileGroup extends Grouper(Missile) {
static n = 0;
removeDeads() {
const deads = []
for (const [k,v] of this) {
if (v.removing) {
deads.push(k)
}
}
for (const dead of deads) {
this.delete(dead)
}
}
update(dt) {
super.update(dt);
this.removeDeads()
}
add(missile) {
MissileGroup.n += 1;
super.add(String(MissileGroup.n), missile);
}
}
+23
View File
@@ -0,0 +1,23 @@
import Missile from './missile.js';
// Using strategy pattern for spaceships
export default class Shooter {
static types = [Missile];
constructor(types, selection=0) {
this.types = types;
this.selection = selection;
this.shooted = 0; // optional
this.last_shooting = 0; // against shoot spam
}
get type() {
return this.types[this.selection];
}
set type(v) {
this.types[this.selection] = v;
}
shoot(entity) {
this.last_shooting = Date.now();
this.shooted += 1;
return this.constructor.types[this.type].make(entity);
}
}
+94
View File
@@ -0,0 +1,94 @@
import Entity from '../libs/entity.js';
// Using strategy pattern is used
export default class Spaceship extends Entity {
static color = "#ef6c00";//du orange
static initFuel = 1000;
static extraDashVelocityNorm = 2;
static afterDashVelocityNorm = 1;
static dashing = false;
static dashDuration = 2;
static random() {
const body = Body.random(1, 3, 2);
const form = new Polygon([[4,0],[-2,2],[-1,0],[-2,-2]]);//triangle regardant vers la droite
form.color = Spaceship.color;
form.fill = true;
form.center = Vector.zero(2);
return new this(form, body);
}
constructor(
form,
body,
life = new Life(),
shooter = new Shooter([0]),
follower = new Follower(),
fuel = Spaceship.initFuel,
dashing = Spaceship.dashing, // dashing should have its own class using strategy pattern (dasher.js)
dashDuration = Spaceship.dashDuration
) {
super(form, body);
this.life = life
this.shooter = shooter;
this.follower = follower;
this.dashing = dashing;
this.dashTime = 0;
this.dashDuration = dashDuration;
this.fuel = fuel; // We'd be better of only using time instead of fuel
}
canDash() {
return Date.now() - this.dashTime > this.dashDuration;
}
startDash(){
//Commence un dash s'il reste du fuel
this.dashing = this.fuel>0;
this.dashTime = Date.now();
this.velocity.norm = this.velocity.norm + this.extraDashVelocityNorm;
}
endDash(){
//Termine un dash et remet la vitesse à la normale
this.dashing = false;
this.velocity.norm = Spaceship.afterDashVelocityNorm;
}
update(dt) {
this.body.updateFriction(0.1);
if (this.isDashing){
if (this.fuel>=dt){
this.fuel+=-dt;
this.velocity.norm=Spaceship.dashVelocityNorm;
}else{
this.isDashing=false;
this.velocity.norm=Spaceship.velocityNorm;
}
}
this.follower.orientate(this.body, this.form);
super.update(dt);
//this.follower.follow(this.body, position);
//this.follower.follow(this.body, new Vector(10,0));
}
shoot() {
return this.shooter.shoot(this);
}
follow(position) {
this.body.follow(position);
// this.follower.follow(this.body, position, 4);
}
show(ctx) {
super.show(ctx);
this.life.show(ctx, this.position);
this.body.motion.show(ctx);
}
static initCollisionListener(collisionEmitter, mainEmitter) {
collisionEmitter.on('spaceship/missile', function([spaceship, sid], [missile, mid]) {
spaceship.life.value -= missile.damage;
mainEmitter.emit("spaceship-update", {sid: sid, event:"life-change", life:spaceship.life.value});
if (spaceship.life.value<=0) {
spaceship.removing = true;
mainEmitter.emit("spaceship-update", {sid: sid, event:"removing"});
}
})
}
}
+28
View File
@@ -0,0 +1,28 @@
import Spaceship from './spaceship.js';
import { Grouper } from '../libs/group.js';
export default class SpaceshipGroup extends Grouper(Spaceship) {
static type = Spaceship;
static readyOnePlayer(){
const s = Spaceship.random();
return new this([["spaceship:0",s]]);
}
static readyZeroPlayer() {
return new this([]);
}
// sg = new SpaceshipGroup();
// sg.set(key, value)
// sg.delete(key)
// addSpaceship(idSpaceship, spaceshipObject) {
// //Ajoute le spaceship spaceshipObject dans le groupe this avec l'id idSpaceship
// this.set(`spaceship:${idSpaceship}`, spaceshipObject);
// }
// removeSpaceship(idSpaceship) {
// // debug(`la classe de notre instance spaceshipGroup ${this.constructor.name}`);
// // this=this.filter(array => array[0] !== `spaceship:${idSpaceship}`);
// // debug(`la classe de notre instance spaceshipGroup ${this.constructor.name}`);
// }
}
+22
View File
@@ -0,0 +1,22 @@
import Group from '../libs/group.js';
import AsteroidGroup from './asteroidgroup.js';
import MeteorGroup from './meteorgroup.js';
import SpaceshipGroup from './spaceshipgroup.js';
import MissileGroup from './missilegroup.js';
export default class SuperGroup extends Group {
static random() {
const g = new this();
g.set('asteroidGroup', AsteroidGroup.asteroidField(50, {x: 0, y: 0}, {x: 100, y: 100}));
g.set("meteorGroup", MeteorGroup.meteorShower(100));
g.set("spaceshipGroup", SpaceshipGroup.readyZeroPlayer());
return g;
}
constructor(...args) {
super(...args);
this.setDefault("asteroidGroup", new AsteroidGroup());
this.setDefault("meteorGroup", new MeteorGroup());
this.setDefault("missileGroup", new MissileGroup());
this.setDefault("spaceshipGroup", new SpaceshipGroup());
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+4627
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "asteroids",
"version": "1.0.0",
"description": "A space battle with asteroids and spaceships.",
"private": true,
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/MarcPartensky/Web.git"
},
"keywords": [
"asteroids"
],
"author": "Marc Partensky",
"license": "ISC",
"bugs": {
"url": "https://github.com/MarcPartensky/Web/issues"
},
"homepage": "https://github.com/MarcPartensky/Web#readme",
"dependencies": {
"express": "^4.17.1",
"socket.io": "^2.3.0"
},
"devDependencies": {
"webpack": "^4.43.0",
"webpack-cli": "^4.0.0-beta.8"
}
}
+100
View File
@@ -0,0 +1,100 @@
const express = require('express'), // for easier syntax;
http = require('http'),
socket = require('socket.io'), // for easier connexion
ent = require('ent'), // html url security
os = require('os'), // os access
fs = require('fs'), // file system
vm = require('vm'), // to execute scripts
ss = require('socket.io-stream'); // to stream data
const app = express();
const server = http.createServer(app);
const io = socket.listen(server);
// server.maxConnections = 10;
const files = [
"libs/tools.js",
"libs/test.js",
"libs/iterator.js",
"libs/group.js",
"libs/tensor.js",
"libs/vector.js",
"libs/matrix.js",
"libs/color.js",
"libs/point.js",
"libs/figure.js",
"libs/form.js",
"libs/polygon.js",
"libs/rectangle.js",
"libs/circle.js",
"libs/motion.js",
"libs/body.js",
"libs/plane.js",
"libs/context.js",
"libs/manager.js",
"model/basecircle.js",
"model/game/ball.js",
"model/game/food.js",
"model/game/virus.js",
"model/game/collider.js",
"model/game/supergroup.js",
"model/game/player.js",
"model/game/gamemap.js",
"model/game/game.js",
"model/game/gameclient.js",
"model/game/gameserver.js",
];
for (const file of files) {
let data = fs.readFileSync(file);
let script = new vm.Script(data);
script.runInThisContext();
}
app.use('/libs', express.static('libs'));
app.use('/public', express.static('model/game'));
app.use('/view/home/', express.static('../view'));
var path = __dirname.split("/");
path = path.slice(0,-1).join("/");
app.get('/', function (req, res) {
res.sendFile(path + "/view/game/index.html");
});
app.get('/game', function (req, res) {
res.sendFile(path + "/view/game/index.html");
});
io.on('connection', function (socket, name) {
socket.on('play-button', function(name) {
console.log("play button pressed");
res.redirect('/game?name=${name}');
});
socket.on('disconnect', function() {
console.log('disconnected');
});
socket.on("message", function(message) {
console.log(message);
})
});
server.listen(process.env.PORT || 8000)
var game = Game.random(5);
game.updateMap();
// console.log(game.map);
var gameServer= new GameServer(game);
// console.log(io.sockets.broadcast);
gameServer.main(io, ss);
+144
View File
@@ -0,0 +1,144 @@
// EventEmitter = require('events'); // to execute scripts
// const express = require('express'),
// http = require('http'),
// socket = require('socket.io'), // for easier connexion
// fs = require('fs'), // file system
// vm = require('vm');
//import { SocketClient } from "models/gameserver.js";
import EventEmitter from 'events';
import express from 'express';
import http from 'http';
import vm from 'vm';
import fs from 'fs';
import socket from 'socket.io';
import GameServer from './models/gameserver.js';
// "type": "module",
// "exports": {
// "./libs": "./libs",
// "./models": "./models",
// "node": {
// "import": "./libs",
// "require": "./libs"
// }
// },
const app = express();
const server = http.createServer(app);
const io = socket.listen(server);
// import './libs/tensor.js';
import Tensor from './libs/tensor.js';
// import Group from './libs/group.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
// import {Tensor} from './libs/tensor.js';
import Game from './models/game.js';
const files = [
// Tools and tests
// "libs/tools.js",
// "libs/test.js",
// Base types
"libs/iterator.js",
"libs/dict.js",
"libs/tree.js",
// Math types
"libs/group.js",
// "libs/tensor.js",
// "libs/vector.js",
"libs/matrix.js",
// Visual types
"libs/color.js",
"libs/point.js",
"libs/figure.js",
// "libs/line.js",
"libs/form.js",
"libs/basepolygon.js",
"libs/segment.js",
"libs/polygon.js",
"libs/rectangle.js",
"libs/square.js",
"libs/circle.js",
// Physics types
"libs/motion.js",
"libs/body.js",
"libs/plane.js",
"libs/context.js",
"libs/entity.js",
"libs/manager.js",
// Game types
"models/gameentity.js",
"models/missile.js",
"models/missilegroup.js",
"models/life.js",
"models/follower.js",
"models/shooter.js",
"models/asteroid.js",
"models/asteroidgroup.js",
"models/spaceship.js",
"models/spaceshipgroup.js",
"models/meteor.js",
"models/meteorgroup.js",
"models/collider.js",
"models/gamemap.js",
"models/supergroup.js",
"models/game.js",
// Entry point
"models/gameserver.js",
];
server.listen(process.env.PORT || 8000)
app.use('/libs', express.static('libs'));
app.use('/models', express.static('models'));
// for (const file of files) {
// console.log(file);
// let data = fs.readFileSync(file);
// let script = new vm.Script(data);
// script.runInThisContext();
// }
// import * from "libs";
// import * from "./libs/tensor.js"
// var path = __dirname.split("/");
// path = path.slice(0,-1).join("/");
app.get('/', function (req, res) {
res.sendFile(__dirname + "/views/index.html");
});
const game = Game.random();
//console.log(`Le group ${game.group.get('asteroidGroup')}`);
const gameServer = new GameServer(game, io);
gameServer.setUp();
gameServer.main();
View File
+1
View File
@@ -0,0 +1 @@
console.log("Hello World from pageThree main file!");
+9
View File
@@ -0,0 +1,9 @@
Utiliser webAPck
https://webpack.js.org/
Regarder Meteor pour propulser le projet vers l'infini
https://www.meteor.com/
Utiliser du protobuf
+129
View File
@@ -0,0 +1,129 @@
@startuml asteroids
skinparam style strictuml
skinparam DefaultFontName Arial
skinparam DefaultFontSize 16
skinparam DefaultFontStyle bold
skinparam ArrowThickness 2.2
skinparam ArrowColor #gray
hide empty members
class "GameServer" as GameServer {
game : Game
dt: float
}
class "GameClient(Manager)" as GameClient {
game : Game
context: Context
dt: float
}
class "Context" as Context {
plane: Plane
context: Context
}
class "Game" as Game {
superGroup: SuperGroup
gameMap: GameMap
static random()
update(dt)
show(ctx)
limit()
}
class "GameMap" as GameMap {
show(ctx)
limit()
}
class "SuperGroup" as SuperGroup {
spaceshipGroup : SpaceshipGroup
asteroidGroup: AsteroidGroup
missileGroup: MissileGroup
meteorGroup: meteorGroup
static random()
update(dt)
show(ctx)
collide()
}
class "AsteroidGroup" as AsteroidGroup {
static random()
update(dt)
show(ctx)
}
class "Asteroid" as Asteroid {
form: Figure
body: Body
static random()
update(dt)
show(ctx)
}
class "SpaceshipGroup" as SpaceshipGroup {
static random()
update(dt)
show(ctx)
}
class "Spaceship" as Spaceship {
form : Figure
body: Body
static random()
update(dt)
show(ctx)
follow()
shoot()
explode()
}
class "MeteorGroup" as MeteorGroup {
static random()
update(dt)
show(ctx)
}
class "Meteor" as Meteor {
form: Figure
body: Body
static random()
update(dt)
show(ctx)
}
class "MissileGroup" as MissileGroup {
static random()
update(dt)
show(ctx)
}
class "Missile" as Missile {
form: Figure
body: Body
static random()
update(dt)
show(ctx)
}
GameServer *-- Game
GameClient *-- Game
GameClient *-- Context
Game *-- GameMap
Game *-- SuperGroup
SuperGroup *-- SpaceshipGroup
SuperGroup *-- MeteorGroup
SuperGroup *-- AsteroidGroup
SuperGroup *-- MissileGroup
SpaceshipGroup "1" *-- "*" Spaceship
MissileGroup "1" *-- "*" Missile
MeteorGroup "1" *-- "*" Meteor
AsteroidGroup "1" *-- "*" Asteroid
@enduml
+117
View File
@@ -0,0 +1,117 @@
@startuml forms
skinparam style strictuml
skinparam DefaultFontName Arial
skinparam DefaultFontSize 16
skinparam DefaultFontStyle bold
skinparam ArrowThickness 2.2
skinparam ArrowColor #gray
hide empty members
class Figure {
fill: Bool
lineWidth: Number
color: Color
show(ctx)
}
class Form {
get/set center()
get/set perimeter()
get/set area()
show(ctx)
}
class BasePolygon {
get/set center()
get/set perimeter()
get/set area()
get/set segments()
show(ctx)
}
class Point {
show(ctx)
}
class Vector {
show(ctx)
}
class Segment {
points: Matrix[Point]
}
class Line {
point: Point
vector: Vector
}
class Circle {
center: Vector
radius: Number
get/set perimeter()
get/set area()
show(ctx)
}
class Polygon {
matrix: Matrix[Vector]
get length = n
get/set center()
get/set perimeter()
get/set area()
get/set points()
get/set segments()
show(ctx)
}
class Triangle {
matrix: Matrix[n][Vector]
get length = 3
get/set center()
get/set perimeter()
get/set area()
get/set points()
get/set segments()
show(ctx)
}
class Rectangle {
position: Vector
size: Vector
get length = 4
get/set center()
get/set perimeter()
get/set area()
get/set points()
get/set segments()
show(ctx)
}
class Square {
x: Number
y: Number
s: Number
get length = 4
get/set center()
get/set perimeter()
get/set area()
get/set points()
get/set segments()
show(ctx)
}
Array <|-- Tensor
Tensor <|-- Vector
Vector <|-- Point
Figure <|-- Form
Figure <|-- Segment
Figure <|-- Line
Form <|-- BasePolygon
Form <|-- Circle
BasePolygon <|-- Polygon
BasePolygon <|-- Square
BasePolygon <|-- Rectangle
Polygon <|-- Triangle
@enduml
+57
View File
@@ -0,0 +1,57 @@
@startuml maths
skinparam style strictuml
skinparam DefaultFontName Arial
skinparam DefaultFontSize 16
skinparam DefaultFontStyle bold
skinparam ArrowThickness 2.2
skinparam ArrowColor #gray
hide empty members
class Array {
}
class Tensor {
order: int
}
class Vector {
order:int = 1
get/set x()
get/set y()
get/set z()
get/set angle()
get/set norm()
add()
sub()
mul()
show()
}
class Matrix {
order:int = 2
get/set vectors()
}
class Motion {
order:int = 2
get/set vectors()
get/set width()
get/set height()
update()
show()
}
class Body {
order: int = 3
get/set motions()
update()
}
Array <|-- Tensor
Tensor <|-- Vector
Tensor <|-- Matrix
Tensor <|-- Body
Matrix <|-- Motion
Vector <|-- Point
@enduml
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Playground</title>
<style>
#html {
background-color: black;
}
#canvas {
width: 100%;
height: 100%;
background-color: black;
border-width: thick;
border-color: white;
}
</style>
</head>
<body bgcolor="#000000">
<canvas id="canvas" width="300" height="300"></canvas>
<!-- Web Dependencies -->
<script src="/socket.io/socket.io.js"></script>
<!-- <script src="https://code.jquery.com/pep/0.4.1/pep.js"></script> -->
<!-- <script src="https://github.com/josephg/noisejs" charset="utf-8"></script> -->
<!-- Own Dependencies -->
<!-- Tools and tests -->
<script src="libs/tools.js" charset="utf-8"></script>
<script src="libs/test.js" charset="utf-8"></script>
<!-- Base types -->
<script src="libs/iterator.js" charset="utf-8"></script>
<script src="libs/dict.js" charset="utf-8"></script>
<script src="libs/tree.js" charset="uft-8"></script>
<!-- Math types -->
<script src="libs/tensor.js" charset="utf-8"></script>
<script src="libs/vector.js" charset="utf-8"></script>
<script src="libs/matrix.js" charset="utf-8"></script>
<!-- Visual types -->
<script src="libs/color.js" charset="utf-8"></script>
<script src="libs/point.js" charset="utf-8"></script>
<script src="libs/figure.js" charset="utf-8"></script>
<script src="libs/form.js" charset="utf-8"></script>
<script src="libs/circle.js" charset="utf-8"></script>
<script src="libs/line.js" charset="utf-8"></script>
<script src="libs/segment.js" charset="utf-8"></script>
<script src="libs/basepolygon.js" charset="utf-8"></script>
<script src="libs/polygon.js" charset="utf-8"></script>
<script src="libs/triangle.js" charset="utf-8"></script>
<script src="libs/rectangle.js" charset="utf-8"></script>
<script src="libs/square.js" charset="utf-8"></script>
<script src="libs/manager.js" charset="utf-8"></script>
<script type="text/javascript">
class Game {
constructor() {
this.s1 = Segment.random();
this.s2 = Segment.random();
}
show(ctx) {
this.s1.show(ctx);
this.s2.show(ctx);
}
update(dt) {
if (s1.line.cross(s2.line)) {
s1.color = "red";
s2.color = "red";
}
}
}
class GameManager extends Manager {
update(dt) {
this.s1.p1 = this.context.getFromScreen(this.mouse);
super.update(dt);
}
}
const m = new GameManager(canvas, game);
</script>
</body>
</html>
+79
View File
@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Playground</title>
<style>
#html {
background-color: black;
}
#canvas {
width: 100%;
height: 100%;
background-color: black;
border-width: thick;
border-color: white;
}
</style>
</head>
<body bgcolor="#000000">
<canvas id="canvas" width="300" height="300"></canvas>
<!-- Web Dependencies -->
<script src="/socket.io/socket.io.js"></script>
<!-- <script src="https://code.jquery.com/pep/0.4.1/pep.js"></script> -->
<!-- <script src="https://github.com/josephg/noisejs" charset="utf-8"></script> -->
<!-- Own Dependencies -->
<!-- Tools and tests -->
<!-- <script src="libs/tools.js" charset="utf-8"></script>
<script src="libs/test.js" charset="utf-8"></script> -->
<!-- Base types -->
<!-- <script src="libs/iterator.js" charset="utf-8"></script>
<script src="libs/dict.js" charset="utf-8"></script>
<script src="libs/tree.js" charset="uft-8"></script> -->
<!-- Math types -->
<!-- <script src="libs/tensor.js" charset="utf-8"></script>
<script src="libs/vector.js" charset="utf-8"></script>
<script src="libs/matrix.js" charset="utf-8"></script> -->
<!-- Visual types -->
<!-- <script src="libs/color.js" charset="utf-8"></script>
<script src="libs/point.js" charset="utf-8"></script>
<script src="libs/figure.js" charset="utf-8"></script>
<script src="libs/form.js" charset="utf-8"></script>
<script src="libs/circle.js" charset="utf-8"></script>
<script src="libs/basepolygon.js" charset="utf-8"></script>
<script src="libs/segment.js" charset="utf-8"></script>
<script src="libs/polygon.js" charset="utf-8"></script>
<script src="libs/triangle.js" charset="utf-8"></script>
<script src="libs/rectangle.js" charset="utf-8"></script>
<script src="libs/square.js" charset="utf-8"></script> -->
<!-- Physics types -->
<!-- <script src="libs/motion.js" charset="utf-8"></script>
<script src="libs/body.js" charset="utf-8"></script>
<script src="libs/entity.js" charset="utf-8"></script>
<script src="libs/group.js" charset="utf-8"></script>
<script src="libs/plane.js" charset="utf-8"></script>
<script src="libs/context.js" charset="utf-8"></script>
<script src="libs/manager.js" charset="utf-8"></script> -->
<!-- Game types -->
<!-- <script src="models/supergroup.js" charset="utf-8"></script>
<script src="models/gameentity.js" charset="utf-8"></script>
<script src="models/life.js" charset="utf-8"></script>
<script src="models/missile.js" charset="utf-8"></script>
<script src="models/missilegroup.js" charset="utf-8"></script>
<script src="models/shooter.js" charset="utf-8"></script>
<script src="models/follower.js" charset="utf-8"></script>
<script src="models/spaceship.js" charset="utf-8"></script>
<script src="models/spaceshipgroup.js" charset="utf-8"></script>
<script src="models/meteor.js" charset="utf-8"></script>
<script src="models/meteorgroup.js" charset="utf-8"></script>
<script src="models/asteroid.js" charset="utf-8"></script>
<script src="models/asteroidgroup.js" charset="utf-8"></script>
<script src="models/collider.js" charset="utf-8"></script>
<script src="models/gamemap.js" charset="utf-8"></script>
<script src="models/game.js" charset="utf-8"></script>
<script src="models/gameclient.js" charset="utf-8"></script> -->
<!-- Entry point -->
<!-- <script src="models/client.js" charset="utf-8"></script> -->
<script src="dist/clients.bundle.js"></script>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
//const path = require('path');
//import path from "path";
module.exports = {
mode: "development",
watch: 'true',
entry: './models/client.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'client.bundle.js'
}
};
+25
View File
@@ -0,0 +1,25 @@
// module = {
// entry: './client.js',
// output: {
// filename: 'main.js',
// },
// };
// export module;
const path = require('path');
module.exports = {
entry: './models/client.js',
mode:"development",
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{ test: new RegExp('[libs|models]*\.js'), use: 'raw-loader' }
]
},
};
+11
View File
@@ -0,0 +1,11 @@
import path from "path";
module = {
mode: "development",
watch: 'true',
entry: './models/client.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'client.bundle.js'
}
};
+120
View File
@@ -0,0 +1,120 @@
//const path = require('path');
// const webpack = require('webpack');
import path from 'path';
import webpack from 'webpack';
/*
* SplitChunksPlugin is enabled by default and replaced
* deprecated CommonsChunkPlugin. It automatically identifies modules which
* should be splitted of chunk by heuristics using module duplication count and
* module category (i. e. node_modules). And splits the chunks…
*
* It is safe to remove "splitChunks" from the generated configuration
* and was added as an educational example.
*
* https://webpack.js.org/plugins/split-chunks-plugin/
*
*/
/*
* We've enabled MiniCssExtractPlugin for you. This allows your app to
* use css modules that will be moved into a separate CSS file instead of inside
* one of your module entries!
*
* https://github.com/webpack-contrib/mini-css-extract-plugin
*
*/
// const MiniCssExtractPlugin = require('mini-css-extract-plugin');
import MiniCssExtraPlugin from 'mini-css-extract-plugin';
/*
* We've enabled TerserPlugin for you! This minifies your app
* in order to load faster and run less javascript.
*
* https://github.com/webpack-contrib/terser-webpack-plugin
*
*/
// const TerserPlugin = require('terser-webpack-plugin');
import TerserPlugin from 'terser-webpack-plugin'
// const workboxPlugin = require('workbox-webpack-plugin');
import workboxPlugin from 'workbox-webpack-plugin';
module.exports = {
mode: 'development',
entry: {
pageOne: './src.js',
pageTwo: './src.js'
},
output: {
chunkFilename: '[name].[chunkhash].js',
filename: '[name].[chunkhash].js'
},
plugins: [
new webpack.ProgressPlugin(),
new MiniCssExtractPlugin({ filename: 'main.scss.[chunkhash].css' }),
new workboxPlugin.GenerateSW({
swDest: 'sw.js',
clientsClaim: true,
skipWaiting: false
})
],
module: {
rules: [
{
test: /.(js|jsx)$/,
include: [],
loader: 'babel-loader'
},
{
test: /.(scss|css)$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
}
]
},
optimization: {
minimizer: [new TerserPlugin()],
splitChunks: {
cacheGroups: {
vendors: {
priority: -10,
test: /[\\/]node_modules[\\/]/
}
},
chunks: 'async',
minChunks: 1,
minSize: 30000,
name: true
}
}
};