diff --git a/Games/CopyCat/README.md b/Games/CopyCat/README.md
new file mode 100644
index 0000000000..9bf23db0a0
--- /dev/null
+++ b/Games/CopyCat/README.md
@@ -0,0 +1,32 @@
+
CopyCat
+
+## **Description 📃*
+The game contains two cats and two homes separated by one division of water you have to make use of arrows such that they reach their homes and if you fall in water the game restrats from that level . Once you win the level you get entered into another level.
+
+## **How to play? 🕹️**
+Once you enter to the starting page three options comes play , select and control choose one option using arrows and then press the key 'x' .
+ Press 'x' to select any option
+to start the game choose arrows and press 'x' on the keyboard to play option , once the game has started make the use of arrows to move the cats to their respective home
+ The home of the cat represented by animated circle
+Press 'e' to exit the game
+Press 'r' to restart the game
+
+
+# Screenshots -
+ Beginning of the Game
+ Select play option using arrows and Press 'x' from keyboard to start the game.
+
+Level 1
+
+Level 2
+
+Level 3
+
+Level 4
+
+Level 5
+
+Level 6
+
+
+END
\ No newline at end of file
diff --git a/Games/CopyCat/index.html b/Games/CopyCat/index.html
new file mode 100644
index 0000000000..81bc7d9968
--- /dev/null
+++ b/Games/CopyCat/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ CopyCat
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Games/CopyCat/script.js b/Games/CopyCat/script.js
new file mode 100644
index 0000000000..9f3d97131b
--- /dev/null
+++ b/Games/CopyCat/script.js
@@ -0,0 +1,2317 @@
+// ----------
+// Utility
+// ----------
+Util = {};
+Util.timeStamp = function() {
+ return window.performance.now();
+};
+Util.random = function(min, max) {
+ return min + Math.random() * (max - min);
+};
+Util.array2D = function(tableau, largeur) {
+ var result = [];
+ for (var i = 0; i < tableau.length; i += largeur)
+ result.push(tableau.slice(i, i + largeur));
+ return result;
+};
+Util.toDio = function(array) {
+ let tab = array.map(x => {
+ if (x !== 0) {
+ return x - 1;
+ } else {
+ return x;
+ }
+ });
+ let render = Util.array2D(tab, 16);
+ return JSON.stringify(render);
+};
+Util.map = function(a, b, c, d, e) {
+ return (a - b) / (c - b) * (e - d) + d;
+};
+Util.lerp = function(value1, value2, amount) {
+ return value1 + (value2 - value1) * amount;
+};
+Util.linearTween = function(currentTime, start, degreeOfChange, duration) {
+ return degreeOfChange * currentTime / duration + start;
+};
+Util.easeInOutQuad = function(t, b, c, d) {
+ t /= d / 2;
+ if (t < 1) return c / 2 * t * t + b;
+ t--;
+ return -c / 2 * (t * (t - 2) - 1) + b;
+};
+Util.easeInOutExpo = function(t, b, c, d) {
+ t /= d / 2;
+ if (t < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
+ t--;
+ return c / 2 * (-Math.pow(2, -10 * t) + 2) + b;
+};
+
+// ----------
+// Scene
+// ----------
+class Scene {
+ constructor(name) {
+ this.name = name;
+ this.loop = true;
+ this.init_once = false;
+ }
+ giveWorld(world) {
+ this.world = world;
+ this.ctx = world.ctx;
+ }
+ keyEvents(event) {}
+ init() {}
+ render() {}
+ addEntity() {}
+}
+class Entity {
+ constructor(scene, x, y) {
+ this.scene = scene;
+ this.world = scene.world;
+ this.ctx = this.world.ctx;
+ this.body = new Body(this, x, y);
+ }
+ setSprite(sprite_data) {
+ this.sprite = new Sprite(this, sprite_data);
+ }
+ display() {
+ if (this.sprite === undefined) {
+ this.ctx.strokeStyle = "#000";
+ this.ctx.strokeRect(
+ this.body.position.x,
+ this.body.position.y,
+ this.body.size.x,
+ this.body.size.y
+ );
+ } else {
+ this.sprite.display();
+ }
+ }
+ integration() {
+ this.body.integration();
+ }
+}
+
+// class for animated sprites !
+class Sprite {
+ constructor(entity, sprite_data) {
+ this.entity = entity;
+ this.world = this.entity.world;
+ this.tile_size = this.world.tile_size;
+ this.ctx = this.world.ctx;
+ // image data
+ this.image = this.world.assets.image[sprite_data.image].image;
+ // sprite
+ this.size = sprite_data.size;
+ this.current_frame = 0;
+ this.animations = {};
+ this.current_animation = undefined;
+ this.width = this.image.width / this.size.x;
+ this.height = this.image.height / this.size.y;
+ // timer
+ this.tick = 0;
+ this.speed = 0.2;
+ // offset
+ this.offset = {
+ x: 0,
+ y: 0
+ };
+ }
+ addAnimation(name, frames) {
+ this.animations[name] = frames;
+ this.current_animation = name;
+ }
+ animate(animation_name) {
+ this.current_animation = animation_name;
+ if (this.tick < 1) {
+ this.tick += this.speed;
+ } else {
+ this.tick = 0;
+ if (this.current_frame < this.animations[animation_name].length - 1) {
+ this.current_frame += 1;
+ } else {
+ this.current_frame = 0;
+ }
+ }
+ }
+ display() {
+ this.ctx.drawImage(
+ this.image,
+ Math.floor(
+ this.animations[this.current_animation][this.current_frame] % this.width
+ ) * this.size.x,
+ Math.floor(
+ this.animations[this.current_animation][this.current_frame] / this.width
+ ) * this.size.y,
+ this.size.x,
+ this.size.y,
+ this.entity.body.position.x +
+ (this.tile_size / 2 - this.size.x / 2) +
+ this.offset.x,
+ this.entity.body.position.y +
+ (this.tile_size / 2 - this.size.x / 2) +
+ this.offset.y,
+ this.size.x,
+ this.size.y
+ );
+ }
+}
+
+class Body {
+ constructor(entity, x, y) {
+ this.world = entity.world;
+ this.step = this.world.FPS.step;
+ this.position = new Vector(x, y);
+ this.next_position = new Vector(x, y);
+ this.velocity = new Vector(0, 0);
+ this.stepped_velocity = new Vector(0, 0);
+ this.acceleration = new Vector(0, 0);
+ this.drag = 0.98;
+ this.size = {
+ x: 16,
+ y: 16
+ };
+ }
+ setSize(x, y) {
+ this.size.x = x;
+ this.size.y = y;
+ }
+ updateVelocity() {
+ this.velocity.add(this.acceleration);
+ this.velocity.mult(this.drag);
+ this.stepped_velocity = this.velocity.copy();
+ this.stepped_velocity.mult(this.step);
+ this.next_position = this.position.copy();
+ this.next_position.add(this.stepped_velocity);
+ // reset acceleration
+ this.acceleration.mult(0);
+ }
+ updatePosition() {
+ this.position.add(this.stepped_velocity);
+ }
+ integration() {
+ this.updateVelocity();
+ this.updatePosition();
+ }
+ applyForce(force_vector) {
+ this.acceleration.add(force_vector);
+ }
+}
+
+class Vector {
+ constructor(x, y) {
+ this.x = x || 0;
+ this.y = y || 0;
+ }
+ set(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+ add(vector) {
+ this.x += vector.x;
+ this.y += vector.y;
+ }
+ sub(vector) {
+ this.x -= vector.x;
+ this.y -= vector.y;
+ }
+ mult(scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ }
+ div(scalar) {
+ this.x /= scalar;
+ this.y /= scalar;
+ }
+ limit(limit_value) {
+ if (this.mag() > limit_value) this.setMag(limit_value);
+ }
+ mag() {
+ return Math.hypot(this.x, this.y);
+ }
+ setMag(new_mag) {
+ if (this.mag() > 0) {
+ this.normalize();
+ } else {
+ this.x = 1;
+ this.y = 0;
+ }
+ this.mult(new_mag);
+ }
+ dist(vector) {
+ return new Vector(this.x - vector.x, this.y - vector.y).mag();
+ }
+ normalize() {
+ let mag = this.mag();
+ if (mag > 0) {
+ this.x /= mag;
+ this.y /= mag;
+ }
+ }
+ heading() {
+ return Math.atan2(this.x, this.y);
+ }
+ setHeading(angle) {
+ let mag = this.mag();
+ this.x = Math.cos(angle) * mag;
+ this.y = Math.sin(angle) * mag;
+ }
+ copy() {
+ return new Vector(this.x, this.y);
+ }
+}
+
+class Box {
+ constructor(world, box_data) {
+ this.world = world;
+ this.ctx = world.ctx;
+ this.c_ctx = world.c_ctx;
+ this.box_data = box_data;
+ this.resolution = box_data.resolution;
+ this.image = world.assets.image[box_data.image].image;
+ }
+ display(x, y, width, height) {
+ // background
+ this.ctx.fillRect(x + 1, y + 1, width - 2, height - 2);
+ // corners
+ this.ctx.lineWidth = 2;
+ let coners = [0, 2, 6, 8];
+ for (let i = 0; i < 4; i++) {
+ let pos_x = x + Math.floor(i % 2) * (width - this.resolution),
+ pos_y = y + Math.floor(i / 2) * (height - this.resolution);
+ let clip_x = Math.floor(i % 2) * (this.resolution * 2),
+ clip_y = Math.floor(i / 2) * (this.resolution * 2);
+ this.ctx.drawImage(
+ this.image,
+ clip_x,
+ clip_y,
+ this.resolution,
+ this.resolution,
+ pos_x,
+ pos_y,
+ this.resolution,
+ this.resolution
+ );
+ }
+ let offset = this.resolution * 3;
+ // top
+ this.ctx.drawImage(
+ this.image,
+ 8,
+ 0,
+ this.resolution,
+ this.resolution,
+ x + 8,
+ y,
+ this.resolution + width - offset,
+ this.resolution
+ );
+ // bottom
+ this.ctx.drawImage(
+ this.image,
+ 8,
+ 16,
+ this.resolution,
+ this.resolution,
+ x + 8,
+ y + height - this.resolution,
+ this.resolution + width - offset,
+ this.resolution
+ );
+ // left
+ this.ctx.drawImage(
+ this.image,
+ 0,
+ 8,
+ this.resolution,
+ this.resolution,
+ x,
+ y + 8,
+ this.resolution,
+ this.resolution + height - offset
+ );
+ // right
+ this.ctx.drawImage(
+ this.image,
+ 16,
+ 8,
+ this.resolution,
+ this.resolution,
+ x + width - this.resolution,
+ y + this.resolution,
+ this.resolution,
+ this.resolution + height - offset
+ );
+ }
+}
+// ----------
+// 🕹️ Diorama.js
+// ----------
+class Diorama {
+ constructor(parameters) {
+ this.parameters = parameters;
+ // Game and author's name
+ this.game_info = {
+ name: parameters.name || "Untitled",
+ author: parameters.author || "Anonymous"
+ };
+ // canvas
+ this.background_color = parameters.background_color || "#000";
+ this.initCanvas(parameters);
+ // Assets
+ this.counter = 0;
+ this.toLoad = parameters.assets.length;
+ this.assets = {
+ image: {},
+ audio: {}
+ };
+ this.audio_muted = false;
+ // keyboard event
+ this.keys = {};
+ // Scenes
+ this.scenes = {};
+ this.start_screen = parameters.start_screen || undefined;
+ this.current_scene = "";
+ // Bitmap font Data
+ this.alphabet =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ?!:',.()<>[]";
+ this.fonts = {};
+ // Maps
+ this.tile_size = parameters.tile_size || 16;
+ this.tiles_data = {};
+ if (parameters.tiles !== undefined) {
+ parameters.tiles.map(tile => {
+ this.tiles_data[tile.id] = tile;
+ });
+ }
+ this.mapsMax = parameters.maps.length;
+ this.maps = {};
+ if (parameters.maps !== undefined) {
+ parameters.maps.map(map => {
+ this.maps[map.name] = map;
+ });
+ }
+ // Box system
+ this.boxes = {};
+ // By default the current font is the first font you create
+ this.currentFont = undefined;
+ // Game loop Data
+ this.FPS = {
+ now: 0,
+ delta: 0,
+ last: Util.timeStamp(),
+ step: 1 / (parameters.frame_rate || 60)
+ };
+ this.requestChange = {
+ value: false,
+ action: ""
+ };
+ this.main_loop = undefined;
+ }
+ // ---
+ // Setup & Loading
+ // ---
+ ready() {
+ this.loadAssets(this.parameters.assets);
+ }
+ initCanvas(parameters) {
+ this.canvas = document.createElement("canvas");
+ this.ctx = this.canvas.getContext("2d");
+ this.W = this.canvas.width = parameters.width || 256;
+ this.H = this.canvas.height = parameters.height || 256;
+ this.scale = parameters.scale || 1;
+ this.full = false;
+ this.ctx.imageSmoothingEnabled = false;
+ this.canvas.classList.add("crisp");
+ document.body.appendChild(this.canvas);
+ // cache canvas
+ this.cache = document.createElement("canvas");
+ this.c_ctx = this.cache.getContext("2d");
+ }
+ loader() {
+ // increment loader
+ this.clear("#222");
+ this.counter += 1;
+ let padding = 20;
+ let width = this.W - padding * 2,
+ x = padding,
+ y = this.H - padding * 2;
+ this.ctx.fillStyle = "#111";
+ this.ctx.fillRect(x, y, width, 20);
+ this.ctx.fillStyle = "#333";
+ this.ctx.fillRect(x, y, this.counter * width / this.toLoad, 20);
+ this.ctx.strokeStyle = "#000";
+ this.ctx.lineWidth = 4;
+ this.ctx.strokeRect(x, y, width, 20);
+ if (this.counter === this.toLoad) {
+ this.launch();
+ }
+ }
+ loadAssets(assets) {
+ if (assets === undefined) console.log("Nothing to load");
+ assets.map(obj => this.checkAssets(obj));
+ }
+ checkAssets(obj) {
+ let subject = obj;
+ switch (obj.type) {
+ case "img":
+ let img = new Image();
+ img.onload = () => {
+ this.loader();
+ };
+ img.onerror = () => {
+ console.log("can't load Image: " + obj.name);
+ };
+ img.src = obj.path;
+ subject.image = img;
+ this.assets.image[obj.name] = subject;
+ break;
+ case "audio":
+ let audio = new Audio(obj.path);
+ audio.addEventListener("canplaythrough", this.loader());
+ audio.onerror = () => {
+ console.log("can't load audio: " + obj.name);
+ };
+ subject.audio = audio;
+ this.assets.audio[obj.name] = subject;
+ break;
+ case undefined:
+ console.log(obj.name, " doesn't have any type");
+ break;
+ default:
+ console.log(obj.name, " has a none known type");
+ }
+ }
+ launch() {
+ this.eventSetup();
+ this.initBoxes(this.parameters.boxes);
+ this.initFonts(this.parameters.fonts);
+ this.startScene(this.start_screen);
+ }
+ initBoxes(boxes_data) {
+ if (boxes_data === undefined) return false;
+ boxes_data.map(box => {
+ this.boxes[box.name] = new Box(this, box);
+ });
+ }
+ drawBox(box_name, x, y, width, height) {
+ this.boxes[box_name].display(x, y, width, height);
+ }
+ // ---
+ // Font manager
+ // ---
+ setFont(font_name) {
+ this.currentFont = font_name;
+ }
+ initFonts(fonts_data) {
+ if (fonts_data === undefined && fonts_data.length > 0) return false;
+ fonts_data.map(font => {
+ if (this.assets.image[font.image] === undefined) {
+ console.log("can't load font, " + font.image + " doesn't exist");
+ return false;
+ }
+ font.image = this.assets.image[font.image].image;
+ this.fonts[font.name] = font;
+ });
+ // set current font to the first font !
+ this.currentFont = Object.keys(this.fonts)[0];
+ }
+ write(text, x, y, justify, colorID) {
+ if (this.currentFont === undefined) {
+ console.log("No bitmap_font");
+ return false;
+ }
+ if (typeof justify === "string") {
+ switch (justify) {
+ case "center":
+ x -= text.length * this.fonts[this.currentFont].size.x / 2;
+ break;
+ case "right":
+ x -= text.length * this.fonts[this.currentFont].size.x;
+ break;
+ default:
+ }
+ this.writeLine(text, x, y, colorID || 0);
+ } else {
+ this.writeParagraph(text, x, y, justify, colorID || 0);
+ }
+ }
+ writeParagraph(text, x, y, justify, colorID) {
+ let y_offset = 0,
+ line_height = this.fonts[this.currentFont].size.y + 5,
+ size_x = this.fonts[this.currentFont].size.x,
+ words = text.split(" "),
+ line = "";
+ for (let i = 0; i < words.length; i++) {
+ line += words[i] + " ";
+ let nextword_width = 0,
+ next_word = words[i + 1],
+ line_length = line.length * size_x;
+ next_word ? (nextword_width = next_word.length * size_x) : 0;
+ if (line_length + nextword_width > justify) {
+ this.writeLine(line, x, y + y_offset, 0, colorID);
+ y_offset += line_height;
+ line = "";
+ } else {
+ this.writeLine(line, x, y + y_offset, 0, colorID);
+ }
+ }
+ }
+ writeLine(text, x, y, colorID) {
+ // write line
+ let size_x = this.fonts[this.currentFont].size.x,
+ size_y = this.fonts[this.currentFont].size.y,
+ font_img = this.fonts[this.currentFont].image;
+ for (let i = 0; i < text.length; i++) {
+ let index = this.alphabet.indexOf(text.charAt(i)),
+ clipX = size_x * index,
+ posX = x + i * size_x;
+ this.ctx.drawImage(
+ font_img,
+ clipX,
+ colorID * size_y,
+ size_x,
+ size_y,
+ posX,
+ y,
+ size_x,
+ size_y
+ );
+ }
+ }
+ // -----------------
+ // Events
+ // -----------------
+ eventSetup() {
+ document.addEventListener("keydown", event => this.keyDown(event), false);
+ document.addEventListener("keyup", event => this.keyUp(event), false);
+ }
+ keyDown(event) {
+ event.preventDefault();
+ this.keys[event.code] = true;
+ if (this.keys.KeyF) {
+ this.fullScreen();
+ }
+ if (this.keys.KeyM) {
+ this.mute();
+ }
+ this.current_scene.keyEvents(event);
+ }
+ keyUp(event) {
+ event.preventDefault();
+ this.keys[event.code] = false;
+ }
+ // ---
+ // Scene Manager
+ // ---
+ startScene(scene_name) {
+ // check if the scene exist
+ if (this.scenes[scene_name] === undefined)
+ return scene_name + " - doesn't exist";
+ // request the change of scene if this.main_loop is active
+ if (this.main_loop !== undefined) {
+ this.requestChange.value = true;
+ this.requestChange.action = scene_name;
+ return false;
+ }
+ this.requestChange.value = false;
+ this.requestChange.action = "";
+ this.FPS.last = Util.timeStamp();
+ this.current_scene = this.scenes[scene_name];
+ this.initScene();
+ // does this scenes needs a gameloop ?
+ if (this.current_scene.loop === true) {
+ this.gameLoop();
+ } else {
+ this.mainRender();
+ }
+ }
+ initScene() {
+ if (this.current_scene.init_once) return false;
+ this.current_scene.init();
+ }
+ addScene(scene) {
+ // links this world to this scene
+ scene.giveWorld(this);
+ this.scenes[scene.name] = scene;
+ }
+ // ---
+ // Main Loop
+ // ---
+ mainRender() {
+ this.clear();
+ this.current_scene.render();
+ }
+ loopCheck() {
+ if (this.requestChange.value === false) {
+ this.main_loop = requestAnimationFrame(() => this.gameLoop());
+ } else {
+ cancelAnimationFrame(this.main_loop);
+ this.main_loop = undefined;
+ this.startScene(this.requestChange.action);
+ }
+ }
+ gameLoop() {
+ this.FPS.now = Util.timeStamp();
+ this.FPS.delta += Math.min(1, (this.FPS.now - this.FPS.last) / 1000);
+ while (this.FPS.delta > this.FPS.step) {
+ this.FPS.delta -= this.FPS.step;
+ this.mainRender();
+ }
+ this.FPS.last = this.FPS.now;
+ this.loopCheck();
+ }
+ // Basic functions
+ soundLevel(volume) {
+ for (let [k, v] of Object.entries(this.assets.audio)) {
+ v.audio.volume = volume;
+ }
+ }
+ mute() {
+ this.audio_muted = !this.audio_muted;
+ for (let [k, v] of Object.entries(this.assets.audio)) {
+ v.audio.muted = this.audio_muted;
+ }
+ }
+ clear(custom_color) {
+ this.ctx.fillStyle = custom_color || this.background_color;
+ this.ctx.fillRect(0, 0, this.W, this.H);
+ }
+ setScale() {
+ this.canvas.style.width = this.W * this.scale + "px";
+ this.canvas.style.height = this.H * this.scale + "px";
+ }
+ fullScreen() {
+ this.full = !this.full;
+ if (!this.full) {
+ this.setScale();
+ } else {
+ this.canvas.style.width = "100%";
+ this.canvas.style.height = "100%";
+ }
+ }
+ // ---
+ // Tile map
+ // ---
+ getTile(layer_id, x, y) {
+ if (x < 0 || x > this.terrain.layers[layer_id].size.x - 1) return false;
+ if (y < 0 || y > this.terrain.layers[layer_id].size.y - 1) return false;
+ let tile = this.tiles_data[this.terrain.layers[layer_id].geometry[y][x]];
+ if (tile === undefined) return false;
+ return tile;
+ }
+ findTile(layer_id, tile_id) {
+ let layer = this.terrain.layers[layer_id];
+ let result = [];
+ for (let y = 0; y < layer.size.y; y++) {
+ for (let x = 0; x < layer.size.x; x++) {
+ let id = layer.geometry[y][x];
+ if (id === tile_id) {
+ result.push({ x: x, y: y });
+ }
+ }
+ }
+ return result;
+ }
+ initMap(map_name) {
+ this.terrain = JSON.parse(JSON.stringify(this.maps[map_name]));
+ // give size to layers
+ for (var i = 0; i < this.terrain.layers.length; i++) {
+ this.terrain.layers[i].size = {
+ x: this.terrain.layers[i].geometry[0].length,
+ y: this.terrain.layers[i].geometry.length
+ };
+ }
+ this.terrain.tileset = this.assets.image[this.maps[map_name].tileset].image;
+ this.terrain.tileset_size = {
+ width: this.terrain.tileset.width / this.tile_size,
+ height: this.terrain.tileset.height / this.tile_size + 1
+ };
+ this.terrain.layers.forEach((layer, index) => {
+ this.marchingSquare(layer);
+ this.bitMasking(layer);
+
+ // create a cache for reducing draw call in the gameLoop
+ this.terrainCache(layer);
+ // prepare animated tiles
+ layer.animated = [];
+ for (var id in this.tiles_data) {
+ if (this.tiles_data[id].animated === true) {
+ let tiles = this.findTile(index, parseInt(id));
+ layer.animated.push({
+ id: id,
+ spritesheet: this.assets.image[this.tiles_data[id].spritesheet]
+ .image,
+ positions: tiles,
+ current: 0,
+ steps: this.tiles_data[id].steps,
+ max_frame:
+ this.assets.image[this.tiles_data[id].spritesheet].image.width /
+ this.tile_size
+ });
+ }
+ }
+ });
+ this.clear("black");
+ }
+ terrainCache(layer) {
+ layer.cache = {};
+ let c = (layer.cache.c = document.createElement("canvas"));
+ let ctx = (layer.cache.ctx = layer.cache.c.getContext("2d"));
+ let W = (c.width = layer.size.x * this.tile_size),
+ H = (c.height = layer.size.y * this.tile_size);
+ // Draw on cache
+ this.ctx.clearRect(0, 0, W, H);
+ this.drawLayer(layer);
+ ctx.drawImage(this.canvas, 0, 0);
+ this.clear();
+ }
+ marchingSquare(layer) {
+ layer.square = [];
+ for (let y = 0; y < layer.size.y; y++) {
+ for (let x = 0; x < layer.size.x; x++) {
+ let p1 = 0,
+ p2 = 0,
+ p3 = 0,
+ p4 = 0;
+
+ if (y + 1 < layer.size.y && x + 1 < layer.size.x) {
+ p1 = layer.geometry[y][x];
+ p2 = layer.geometry[y][x + 1];
+ p3 = layer.geometry[y + 1][x + 1];
+ p4 = layer.geometry[y + 1][x];
+ }
+ let id = p1 * 8 + p2 * 4 + p3 * 2 + p4;
+ layer.square.push(id);
+ }
+ }
+
+ layer.square = Util.array2D(layer.square, layer.size.x);
+ }
+ bitMasking(layer) {
+ layer.bitMask = [];
+ for (let y = 0; y < layer.size.y; y++) {
+ for (let x = 0; x < layer.size.x; x++) {
+ let id = layer.geometry[y][x];
+ let neighbor = [0, 0, 0, 0];
+ if (y - 1 > -1) {
+ if (id === layer.geometry[y - 1][x]) {
+ //top
+ neighbor[0] = 1;
+ }
+ } else {
+ neighbor[0] = 1;
+ }
+ if (x - 1 > -1) {
+ if (id === layer.geometry[y][x - 1]) {
+ // left
+ neighbor[1] = 1;
+ }
+ } else {
+ neighbor[1] = 1;
+ }
+ if (x + 1 < layer.size.x) {
+ if (id === layer.geometry[y][x + 1]) {
+ // right
+ neighbor[2] = 1;
+ }
+ } else {
+ neighbor[2] = 1;
+ }
+
+ if (y + 1 < layer.size.y) {
+ if (id === layer.geometry[y + 1][x]) {
+ //down
+ neighbor[3] = 1;
+ }
+ } else {
+ neighbor[3] = 1;
+ }
+ id =
+ 1 * neighbor[0] + 2 * neighbor[1] + 4 * neighbor[2] + 8 * neighbor[3];
+ layer.bitMask.push(id);
+ }
+ }
+ layer.bitMask = Util.array2D(layer.bitMask, layer.size.x);
+ }
+ renderMap() {
+ this.terrain.layers.forEach(layer => {
+ this.ctx.drawImage(layer.cache.c, 0, 0);
+ // draw animated layer
+ layer.animated.forEach(tile => {
+ if (tile.current < tile.max_frame - 1) {
+ tile.current += tile.steps;
+ } else {
+ tile.current = 0;
+ }
+ // render animated tiles
+ tile.positions.forEach(position => {
+ let x = position.x * this.tile_size,
+ y = position.y * this.tile_size;
+ this.ctx.drawImage(
+ tile.spritesheet,
+ Math.floor(tile.current) * this.tile_size,
+ 0,
+ this.tile_size,
+ this.tile_size,
+ x,
+ y,
+ this.tile_size,
+ this.tile_size
+ );
+ });
+ });
+ });
+ }
+ drawMap() {
+ this.terrain.layers.forEach(layer => {
+ this.drawLayer(layer);
+ });
+ }
+ drawLayer(layer) {
+ for (let y = 0; y < layer.size.y; y++) {
+ for (let x = 0; x < layer.size.x; x++) {
+ // ID of the tile
+ let id = layer.geometry[y][x];
+ // Don't draw invisible tiles
+ // Position of the tile :)
+ let positionX = x * this.tile_size + layer.offset.x,
+ positionY = y * this.tile_size + layer.offset.y;
+ let sourceX =
+ Math.floor(id % this.terrain.tileset_size.width) * this.tile_size,
+ sourceY =
+ Math.floor(id / this.terrain.tileset_size.width) * this.tile_size;
+ if (this.tiles_data[id] && this.tiles_data[id].look === "bitmask") {
+ sourceX = Math.floor(layer.bitMask[y][x]) * this.tile_size;
+ sourceY = this.tiles_data[id].line * this.tile_size;
+ }
+
+ if (layer.look === "square") {
+ if (layer.square[y][x] === 0) continue;
+ positionX += this.tile_size / 2;
+ positionY += this.tile_size / 2;
+ sourceX = Math.floor(layer.square[y][x] % 16) * 16;
+ sourceY = 7 * this.tile_size;
+ }
+
+ if (this.tiles_data[id] && this.tiles_data[id].animated === true) {
+ // hide animated sprites on the cache
+ continue;
+ }
+
+ // render tile
+
+ this.ctx.drawImage(
+ this.terrain.tileset,
+ sourceX,
+ sourceY,
+ this.tile_size,
+ this.tile_size,
+ positionX,
+ positionY,
+ this.tile_size,
+ this.tile_size
+ );
+ }
+ }
+ }
+}
+let parameters = {
+ name: "Copycat",
+ start_screen: "menu",
+ background_color: "#223d8c",
+ width: 256,
+ height: 256,
+ tile_size: 16,
+ assets: [
+ // Images
+ {
+ type: "img",
+ name: "coderscrux_font",
+ path: "https://image.ibb.co/fCOd7T/coderscrux_font.png"
+ },
+ {
+ type: "img",
+ name: "controls",
+ path: "https://image.ibb.co/nApwu8/controls.png"
+ },
+ {
+ type: "img",
+ name: "player_sprite",
+ path: "https://image.ibb.co/co3NZ8/player.png"
+ },
+ {
+ type: "img",
+ name: "spawn_effect",
+ path: "https://image.ibb.co/njVQnT/spawn_effect.png"
+ },
+ {
+ type: "img",
+ name: "water_splash",
+ path: "https://image.ibb.co/jm7hZ8/water_splash.png"
+ },
+ {
+ type: "img",
+ name: "shadow",
+ path: "https://image.ibb.co/djchZ8/shadow.png"
+ },
+ {
+ type: "img",
+ name: "main_title",
+ path: "https://image.ibb.co/mrBLMo/main_title.png"
+ },
+ {
+ type: "img",
+ name: "origami_dark",
+ path: "https://image.ibb.co/gzk2Z8/origami_dark.png"
+ },
+ {
+ type: "img",
+ name: "origami_light",
+ path: "https://image.ibb.co/jruknT/origami_light.png"
+ },
+ {
+ type: "img",
+ name: "box_texture",
+ path: "https://image.ibb.co/kpO0Mo/box.png"
+ },
+ {
+ type: "img",
+ name: "selection",
+ path: "https://image.ibb.co/fmJpE8/selection.png"
+ },
+ {
+ type: "img",
+ name: "flat_frame",
+ path: "https://image.ibb.co/hqSugo/flat_frame.png"
+ },
+ {
+ type: "img",
+ name: "pattern",
+ path: "https://image.ibb.co/cv02Z8/pattern.png"
+ },
+ {
+ type: "img",
+ name: "cursor",
+ path: "https://image.ibb.co/bFiNZ8/cursor.png"
+ },
+ {
+ type: "img",
+ name: "demo_tileset",
+ path: "https://image.ibb.co/b8rLMo/demo_tileset.png"
+ },
+ {
+ type: "img",
+ name: "exit",
+ path: "https://image.ibb.co/esCS1o/exit.png"
+ },
+ {
+ type: "img",
+ name: "water_sprite",
+ path: "https://image.ibb.co/cSFEgo/water_sprite.png"
+ },
+ {
+ type: "img",
+ name: "dust_effect",
+ path: "https://image.ibb.co/mKy0Mo/dust.png"
+ },
+ // Audio
+ {
+ type: "audio",
+ name: "jingle",
+ path: "http://www.noiseforfun.com/waves/musical-and-jingles/NFF-bravo.wav"
+ },
+ {
+ type: "audio",
+ name: "mouvement",
+ path:
+ "http://www.noiseforfun.com/waves/interface-and-media/NFF-select-04.wav"
+ },
+ {
+ type: "audio",
+ name: "selection",
+ path:
+ "http://www.noiseforfun.com/waves/interface-and-media/NFF-select.wav"
+ },
+ {
+ type: "audio",
+ name: "apparition",
+ path:
+ "http://www.noiseforfun.com/waves/interface-and-media/NFF-bubble-input.wav"
+ },
+ {
+ type: "audio",
+ name: "eboulement",
+ path:
+ "http://www.noiseforfun.com/waves/action-and-game/NFF-moving-block.wav"
+ },
+ {
+ type: "audio",
+ name: "splash",
+ path:
+ "http://www.noiseforfun.com/waves/action-and-game/NFF-mud-splash.wav"
+ },
+ {
+ type: "audio",
+ name: "bump",
+ path: "http://www.noiseforfun.com/waves/action-and-game/NFF-bump.wav"
+ }
+
+ // Bitmap font
+ ],
+ fonts: [
+ // basic font
+ {
+ name: "coderscrux",
+ image: "coderscrux_font",
+ size: { x: 6, y: 9 }
+ },
+ {
+ name: "origami_dark",
+ image: "origami_dark",
+ size: { x: 8, y: 9 }
+ },
+ {
+ name: "origami_light",
+ image: "origami_light",
+ size: { x: 8, y: 9 }
+ }
+ ],
+ // box system
+ boxes: [
+ {
+ name: "box",
+ resolution: 8,
+ image: "box_texture"
+ },
+ {
+ name: "selection",
+ resolution: 8,
+ image: "selection"
+ },
+ {
+ name: "flat_frame",
+ resolution: 8,
+ image: "flat_frame"
+ }
+ ],
+ tiles: [
+ { name: "empty", id: 0, collision: false, visibility: false },
+ { name: "water", id: 1, collision: false, look: "square", line: 7 },
+ { name: "shores", id: 2, collision: false, look: "bitmask", line: 6 },
+ { name: "ground", id: 3, collision: false, look: "bitmask", line: 1 },
+ { name: "wall", id: 4, collision: true, look: "bitmask", line: 2 },
+ { name: "fence", id: 11, collision: true, look: "bitmask", line: 4 },
+ { name: "bush", id: 5, collision: true },
+ { name: "ice", id: 6, collision: false, look: "bitmask", line: 3 },
+ { name: "spawn", id: 7, collision: false },
+ {
+ name: "exit",
+ id: 8,
+ collision: false,
+ animated: true,
+ spritesheet: "exit",
+ steps: 0.4
+ },
+ {
+ name: "waves",
+ id: 16,
+ collision: false,
+ animated: true,
+ spritesheet: "water_sprite",
+ steps: 0.2
+ },
+ { name: "trap", id: 9, collision: false },
+ { name: "hole", id: 10, collision: true },
+ // arrows
+ { name: "arrowLeft", id: 12, collision: false },
+ { name: "arrowUp", id: 13, collision: false },
+ { name: "arrowRight", id: 14, collision: false },
+ { name: "arrowDown", id: 15, collision: false }
+ ],
+ maps: [
+ // map 1
+ {
+ name: "map_1",
+ tileset: "demo_tileset",
+ // ground
+ layers: [
+ // ground layer
+ {
+ name: "ground",
+ offset: {
+ x: 0,
+ y: 4
+ },
+ geometry: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 3, 3, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ]
+ },
+ // ice / arrows / layer
+ {
+ name: "onGround",
+ offset: {
+ x: 0,
+ y: 0
+ },
+ geometry: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 11, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ]
+ }
+ // wall layer
+ ]
+ },
+ // map 2
+ {
+ name: "map_2",
+ tileset: "demo_tileset",
+ // ground
+ layers: [
+ // ground layer
+ {
+ name: "ground",
+ offset: {
+ x: 0,
+ y: 4
+ },
+ geometry: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ]
+ },
+ // ice / arrows / layer
+ {
+ name: "onGround",
+ offset: {
+ x: 0,
+ y: 0
+ },
+ geometry: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 11, 11, 11, 11, 11, 0, 0, 11, 11, 11, 11, 11, 0, 0],
+ [0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
+ [0, 0, 11, 0, 8, 0, 11, 0, 0, 11, 0, 8, 0, 11, 0, 0],
+ [0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
+ [0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
+ [0, 0, 11, 4, 4, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
+ [0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 4, 4, 11, 0, 0],
+ [0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
+ [0, 0, 11, 0, 7, 0, 11, 0, 0, 11, 0, 7, 0, 11, 0, 0],
+ [0, 0, 11, 11, 11, 11, 11, 0, 0, 11, 11, 11, 11, 11, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ]
+ }
+ // wall layer
+ ]
+ },
+ {
+ name: "map_3",
+ tileset: "demo_tileset",
+ // ground
+ layers: [
+ // ground layer
+ {
+ name: "ground",
+ offset: {
+ x: 0,
+ y: 4
+ },
+ geometry: [
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
+ ]
+ },
+ // ice / arrows / layer
+ {
+ name: "onGround",
+ offset: {
+ x: 0,
+ y: 0
+ },
+ geometry: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 4, 0, 0],
+ [0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
+ [0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
+ [0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 4, 0, 0],
+ [0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
+ [0, 0, 4, 8, 0, 0, 0, 0, 0, 0, 5, 0, 0, 4, 0, 0],
+ [0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
+ [0, 0, 4, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
+ [0, 0, 4, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 4, 0, 0],
+ [0, 0, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 4, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ]
+ }
+ // wall layer
+ ]
+ },
+
+ {
+ name: "map_4",
+ tileset: "demo_tileset",
+ // ground
+ layers: [
+ // ground layer
+ {
+ name: "ground",
+ offset: {
+ x: 0,
+ y: 4
+ },
+ geometry: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0],
+ [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0],
+ [0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0],
+ [0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ]
+ },
+ // ice / arrows / layer
+ {
+ name: "onGround",
+ offset: {
+ x: 0,
+ y: 0
+ },
+ geometry: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 11, 11, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 11, 0, 0, 0],
+ [0, 0, 0, 0, 11, 11, 11, 0, 0, 0, 11, 0, 11, 0, 0, 0],
+ [0, 0, 0, 0, 11, 0, 11, 0, 0, 0, 11, 0, 11, 0, 0, 0],
+ [0, 0, 0, 0, 11, 0, 11, 0, 0, 0, 11, 9, 11, 0, 0, 0],
+ [0, 0, 0, 0, 11, 8, 11, 0, 0, 0, 11, 8, 11, 0, 0, 0],
+ [0, 0, 0, 0, 11, 0, 11, 0, 0, 0, 11, 7, 11, 0, 0, 0],
+ [0, 0, 0, 0, 11, 0, 11, 0, 0, 0, 11, 0, 11, 0, 0, 0],
+ [0, 0, 0, 0, 11, 0, 11, 5, 0, 0, 11, 11, 11, 0, 0, 0],
+ [0, 0, 0, 0, 11, 7, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 11, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ]
+ } // wall layer
+ ]
+ },
+ {
+ name: "map_5",
+ tileset: "demo_tileset",
+ // ground
+ layers: [
+ // ground layer
+ {
+ name: "ground",
+ offset: {
+ x: 0,
+ y: 4
+ },
+ geometry: [
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
+ ]
+ },
+ // ice / arrows / layer
+ {
+ name: "onGround",
+ offset: {
+ x: 0,
+ y: 0
+ },
+ geometry: [
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
+ [4, 0, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
+ [4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 8, 4, 4],
+ [4, 4, 4, 6, 6, 4, 6, 6, 4, 6, 4, 4, 6, 4, 4, 4],
+ [4, 4, 4, 4, 6, 6, 6, 6, 4, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 5, 6, 6, 6, 6, 0, 0, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 0, 7, 4, 4, 4],
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4],
+ [4, 5, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4],
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
+ ]
+ } // wall layer
+ ]
+ },
+
+ {
+ name: "map_6",
+ tileset: "demo_tileset",
+ // ground
+ layers: [
+ // ground layer
+ {
+ name: "ground",
+ offset: {
+ x: 0,
+ y: 4
+ },
+ geometry: [
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
+ ]
+ },
+ // ice / arrows / layer
+ {
+ name: "onGround",
+ offset: {
+ x: 0,
+ y: 0
+ },
+ geometry: [
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
+ [4, 4, 4, 6, 6, 14, 0, 6, 6, 6, 6, 15, 4, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 8, 6, 6, 6, 4, 6, 6, 4, 4, 4],
+ [4, 4, 4, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 4, 4, 4],
+ [4, 4, 4, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 4, 4, 4],
+ [4, 4, 4, 6, 6, 14, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 0, 6, 4, 4, 4, 4],
+ [4, 4, 4, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 8, 13, 4, 4, 4],
+ [4, 4, 4, 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 4, 4, 4],
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
+ ]
+ } // wall layer
+ ]
+ }
+ ]
+};
+
+// Don't mind me
+// just too lazy to modify the maps by hand
+parameters.maps.forEach(map => {
+ new_layer = {};
+ new_layer.name = "water";
+ new_layer.look = "square";
+ new_layer.offset = { x: 0, y: 8 };
+ new_layer.geometry = Array(16)
+ .fill()
+ .map(() => Array(16).fill(0));
+ map.layers.unshift(new_layer);
+ //
+ new_layer = {};
+ new_layer.name = "splash";
+ new_layer.offset = { x: 0, y: 8 };
+ new_layer.geometry = Array(16)
+ .fill()
+ .map(() => Array(16).fill(0));
+ map.layers.splice(2, 0, new_layer);
+
+ let water = map.layers[0];
+ let ground = map.layers[1];
+ let splash = map.layers[2];
+
+ for (let y = 0; y < ground.geometry.length; y++) {
+ for (let x = 0; x < ground.geometry[0].length; x++) {
+ if (
+ y - 1 > 0 &&
+ ground.geometry[y][x] !== 3 &&
+ ground.geometry[y - 1][x] == 3
+ ) {
+ ground.geometry[y][x] = 2;
+ }
+ }
+ }
+
+ for (let y = 0; y < ground.geometry.length; y++) {
+ for (let x = 0; x < ground.geometry[0].length; x++) {
+ if (ground.geometry[y][x] == 2) {
+ splash.geometry[y][x] = 16;
+ }
+ }
+ }
+
+ for (let y = 0; y < water.geometry.length; y++) {
+ for (let x = 0; x < water.geometry[0].length; x++) {
+ if (ground.geometry[y][x] == 3) {
+ water.geometry[y][x] = 1;
+ }
+
+ if (ground.geometry[y][x] !== 3 && ground.geometry[y][x + 1] == 3) {
+ water.geometry[y][x] = 1;
+ }
+ if (ground.geometry[y][x] !== 3 && ground.geometry[y][x - 1] == 3) {
+ water.geometry[y][x] = 1;
+ }
+ if (
+ y + 1 < water.geometry.length &&
+ ground.geometry[y][x] !== 3 &&
+ ground.geometry[y + 1][x] == 3
+ ) {
+ water.geometry[y][x] = 1;
+ }
+ if (
+ y - 1 > 0 &&
+ ground.geometry[y][x] !== 3 &&
+ ground.geometry[y - 1][x] == 3
+ ) {
+ water.geometry[y][x] = 1;
+ }
+ }
+ }
+
+ for (let y = 0; y < water.geometry.length; y++) {
+ for (let x = 0; x < water.geometry[0].length; x++) {
+ if (water.geometry[y][x] == -1) {
+ water.geometry[y][x] = 1;
+ }
+ }
+ }
+});
+// menu scene
+let menu = new Scene("menu");
+menu.keyEvents = function(event) {
+ if (this.world.keys.ArrowDown && this.selection < this.button.length - 1) {
+ this.world.assets.audio.selection.audio.play();
+ this.selection += 1;
+ } else if (this.world.keys.ArrowUp && this.selection > 0) {
+ this.world.assets.audio.selection.audio.play();
+ this.selection -= 1;
+ }
+ if (this.world.keys.KeyX) {
+ this.world.assets.audio.selection.audio.play();
+ this.world.startScene(this.button[this.selection].link);
+ }
+};
+menu.init = function() {
+ this.init_once = true;
+ // custom data
+ this.button = [
+ {
+ name: "PLAY",
+ link: "inGame"
+ },
+ {
+ name: "SELECT",
+ link: "levels"
+ },
+ {
+ name: "CONTROLS",
+ link: "controls"
+ }
+ ];
+ this.texteMax =
+ Math.max(...this.button.map(button => button.name.length)) * 6;
+ this.selection = 0;
+ this.select_pos = {
+ x: this.world.W / 2,
+ y: 110
+ };
+ this.cursor_phase = 0;
+ this.cursor = this.world.assets.image.cursor.image;
+ // background
+ let background_image = this.world.assets.image.pattern.image;
+ this.pattern = this.world.ctx.createPattern(background_image, "repeat");
+ this.offset = {
+ x: 0,
+ y: 0
+ };
+ // add cat on
+ this.cat = new Entity(this, -this.world.tile_size, -this.world.tile_size);
+ let sprite_data = {
+ image: "player_sprite",
+ size: {
+ x: 18,
+ y: 18
+ }
+ };
+ this.cat.setSprite(sprite_data);
+ this.cat.sprite.addAnimation("idle", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
+ this.cat.sprite.speed = 0.2;
+ this.cat.sprite.offset.y = -3;
+};
+menu.render = function() {
+ this.animatedBackground();
+ this.ctx.drawImage(this.world.assets.image["main_title"].image, 0, 0);
+ this.displaySelection();
+ // notice
+ this.world.ctx.fillStyle = "rgba(0,0,0,0.6)";
+ this.world.ctx.fillRect(0, this.world.H - 50, this.world.W, 33);
+ this.world.setFont("origami_light");
+ this.world.write(
+ "Arrow keys to select",
+ this.world.W / 2,
+ this.world.H - 46,
+ "center"
+ );
+ this.world.write(
+ "[x] to Confirm",
+ this.world.W / 2,
+ this.world.H - 30,
+ "center"
+ );
+};
+menu.displaySelection = function() {
+ // display box
+ this.ctx.fillStyle = "#82769e";
+ this.world.drawBox(
+ "box",
+ this.select_pos.x - (this.texteMax + 60) / 2,
+ this.select_pos.y - 16,
+ this.texteMax + 60,
+ this.button.length * 20 + 20
+ );
+ // display text and cursor
+ for (i in this.button) {
+ if (i == this.selection) {
+ this.world.setFont("origami_light");
+ } else {
+ this.world.setFont("origami_dark");
+ }
+ let title = this.button[i].name;
+ this.world.write(
+ title,
+ this.select_pos.x,
+ this.select_pos.y + i * 20,
+ "center"
+ );
+ }
+ this.cursor_phase += 0.1;
+ if (this.cursor_phase > 1 / Math.sin(0.2)) {
+ this.cursor_phase = -1;
+ }
+ let x = this.select_pos.x + Math.sin(this.cursor_phase) * 2 - 20;
+ this.world.ctx.drawImage(
+ this.cursor,
+ x - this.button[this.selection].name.length * 10 / 2,
+ this.select_pos.y + 20 * this.selection - 2
+ );
+};
+menu.animatedBackground = function() {
+ this.offset.x += 0.8;
+ this.offset.y += 0.6;
+ if (this.offset.x > 63) {
+ this.offset.x = 0;
+ }
+ if (this.offset.y > 63) {
+ this.offset.y = 0;
+ }
+ let ctx = this.world.ctx;
+ ctx.save();
+ ctx.translate(this.offset.x, this.offset.y);
+ ctx.fillStyle = this.pattern;
+ ctx.fillRect(-this.offset.x, -this.offset.y, this.world.W, this.world.H);
+ ctx.restore();
+};
+let levels = new Scene("levels");
+levels.keyEvents = function(event) {
+ if (this.world.keys.KeyE) {
+ this.world.assets.audio.selection.audio.play();
+ this.world.startScene("menu");
+ }
+ if (this.world.keys.ArrowDown && this.selection + 5 < this.world.mapsMax) {
+ this.world.assets.audio.selection.audio.play();
+ this.selection += 5;
+ }
+ if (this.world.keys.ArrowUp && this.selection - 5 >= 0) {
+ this.world.assets.audio.selection.audio.play();
+ this.selection -= 5;
+ }
+ if (this.world.keys.ArrowRight && this.selection + 1 < this.world.mapsMax) {
+ this.world.assets.audio.selection.audio.play();
+ this.selection += 1;
+ }
+ if (this.world.keys.ArrowLeft && this.selection - 1 >= 0) {
+ this.world.assets.audio.selection.audio.play();
+ this.selection -= 1;
+ }
+ if (this.world.keys.KeyX) {
+ this.world.assets.audio.selection.audio.play();
+ this.world.current_level = this.selection + 1;
+ this.world.startScene("inGame");
+ }
+};
+levels.init = function() {
+ this.init_once = true;
+ this.selection = 0;
+ this.scale = 0;
+};
+levels.render = function() {
+ this.world.clear("black");
+ // animate selection
+ this.scale += 0.1;
+ if (this.scale > 1 / Math.sin(0.2)) {
+ this.scale = -1;
+ }
+ let offset = Math.sin(this.scale) * 2;
+ // display box
+ this.ctx.fillStyle = "#82769e";
+ this.world.drawBox("box", 16, 16, this.world.W - 32, this.world.H - 46 - 32);
+ this.world.setFont("origami_light");
+ this.world.setFont("origami_dark");
+ let show = Math.min(this.world.mapsMax, 20);
+ for (let i = 0; i < show; i++) {
+ let level_id = i + 20 * Math.floor(this.selection / 20);
+ let position_x = 32 + Math.floor(i % 5) * 40,
+ position_y = 32 + Math.floor(i / 5) * 40;
+ if (level_id == this.selection) {
+ this.world.setFont("origami_light");
+ this.world.drawBox(
+ "selection",
+ position_x - offset / 2,
+ position_y - offset / 2,
+ 24 + offset,
+ 24 + offset
+ );
+ } else {
+ this.world.setFont("origami_dark");
+ this.world.drawBox("flat_frame", position_x, position_y, 24, 24);
+ }
+ this.world.write(
+ (level_id + 1).toString(),
+ position_x + 13,
+ position_y + 8,
+ "center"
+ );
+ }
+ // notice
+ this.world.ctx.fillStyle = "rgba(0,0,0,0.6)";
+ this.world.ctx.fillRect(0, this.world.H - 50, this.world.W, 33);
+ this.world.setFont("origami_light");
+ this.world.write(
+ "Arrow keys to select",
+ this.world.W / 2,
+ this.world.H - 46,
+ "center"
+ );
+ this.world.write(
+ "[x] to Confirm, [E] to exit",
+ this.world.W / 2,
+ this.world.H - 30,
+ "center"
+ );
+};
+let inGame = new Scene("inGame");
+inGame.keyEvents = function(event) {
+ if (this.world.keys.KeyE && this.userInput) {
+ this.transition.start(
+ 0,
+ Math.max(this.world.W / 2, this.world.H / 2),
+ () => {
+ this.world.startScene("menu");
+ }
+ );
+ }
+ if (this.world.keys.KeyR && this.userInput) {
+ this.transition.start(
+ 0,
+ Math.max(this.world.W / 2, this.world.H / 2),
+ () => {
+ this.world.startScene("inGame");
+ }
+ );
+ }
+};
+inGame.init = function() {
+ this.won = false;
+ this.userInput = true;
+ this.world.initMap("map_" + this.world.current_level);
+ this.cats = [];
+ let spawn_cat = () => {
+ // add cats on spawn tile_size
+ let spawns = this.world.findTile(3, 7);
+ spawns.forEach(spawn => {
+ this.addCat(spawn.x, spawn.y);
+ });
+ };
+ // effects
+ this.effects = [];
+ // transition effects
+ this.transition = {
+ scene: this,
+ active: true,
+ // between 0 and 100
+ state: 0,
+ value: 0,
+ duration: 500,
+ start: 0,
+ // between whatever and whatever
+ from: 0,
+ to: Math.max(this.world.W, this.world.H),
+ //
+ start: function(from, to, callback) {
+ this.scene.userInput = false;
+ this.active = true;
+ this.from = from;
+ this.start_time = new Date();
+ this.to = to;
+ this.callback = callback;
+ },
+ update: function() {
+ let time = new Date() - this.start_time;
+ if (time < this.duration) {
+ this.value = Util.easeInOutQuad(
+ time,
+ this.from,
+ this.to - this.from,
+ this.duration
+ );
+ } else {
+ this.active = false;
+ this.scene.userInput = true;
+ if (this.callback !== undefined) {
+ this.callback();
+ }
+ }
+ },
+ render: function() {
+ this.scene.ctx.fillStyle = "black";
+ this.scene.ctx.fillRect(0, 0, this.scene.world.W, this.value);
+ this.scene.ctx.fillRect(
+ 0,
+ this.scene.world.H,
+ this.scene.world.W,
+ -this.value
+ );
+ this.scene.ctx.fillRect(0, 0, this.value, this.scene.world.H);
+ this.scene.ctx.fillRect(
+ this.scene.world.W,
+ 0,
+ -this.value,
+ this.scene.world.H
+ );
+ }
+ };
+ this.transition.start(
+ Math.max(this.world.W / 2, this.world.H / 2),
+ 0,
+ spawn_cat
+ );
+};
+inGame.addCat = function(x, y) {
+ let cat = new Cat(this, x, y);
+ let sprite_data = {
+ image: "player_sprite",
+ size: {
+ x: 18,
+ y: 18
+ }
+ };
+ cat.setSprite(sprite_data);
+ cat.sprite.addAnimation("idle", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
+ cat.sprite.speed = 0.2;
+ cat.sprite.offset.y = -3;
+ let spawn_data = {
+ image: "spawn_effect",
+ frames: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
+ size: {
+ x: 20,
+ y: 40
+ }
+ };
+ let spawn_effect = new Effect(this, spawn_data, x, y - 1, () => {
+ this.cats.push(cat);
+ this.world.assets.audio.apparition.audio.play();
+ });
+ spawn_effect.trigger = 4;
+ this.effects.push(spawn_effect);
+};
+inGame.render = function() {
+ this.control();
+ this.world.renderMap();
+ for (let i = this.cats.length; i--; ) {
+ this.cats[i].sprite.animate("idle");
+ // draw shadow and cat
+ this.ctx.drawImage(
+ this.world.assets.image["shadow"].image,
+ this.cats[i].body.position.x,
+ this.cats[i].body.position.y + 2
+ );
+ this.cats[i].display();
+ this.cats[i].translation();
+ }
+ for (let i = this.effects.length; i--; ) {
+ this.effects[i].render();
+ }
+ if (this.transition.active) {
+ this.transition.update();
+ this.transition.render();
+ }
+};
+inGame.control = function() {
+ if (this.userInput == false) return false;
+ if (this.world.keys.ArrowUp) {
+ this.moveCats(0, -1);
+ }
+ if (this.world.keys.ArrowDown) {
+ this.moveCats(0, 1);
+ }
+ if (this.world.keys.ArrowLeft) {
+ this.moveCats(-1, 0);
+ }
+ if (this.world.keys.ArrowRight) {
+ this.moveCats(1, 0);
+ }
+};
+inGame.moveCats = function(x, y) {
+ // see if every cat are ready to move
+ let canMove = this.cats.every(cat => {
+ return cat.inTranslation == false;
+ });
+ if (!canMove) return false;
+ this.cats.forEach(cat => {
+ if (cat.canBeControlled === false) return false;
+ if (cat.isDead) return false;
+ cat.move(x, y);
+ });
+ this.collisionCats();
+ this.cats.forEach(cat => {
+ cat.applyMove();
+ });
+};
+inGame.collisionCats = function() {
+ // check for other cats !
+ let need_to_check = true;
+ while (need_to_check === true) {
+ need_to_check = false;
+ this.cats.forEach(cat => {
+ if (cat.checkOthers()) {
+ cat.target = cat.old_position.copy();
+ need_to_check = true;
+ }
+ });
+ }
+};
+inGame.checkWin = function() {
+ if (this.cats.length === 0) {
+ // everyone is dead :/
+ this.transition.start(
+ 0,
+ Math.max(this.world.W / 2, this.world.H / 2),
+ () => {
+ this.world.startScene("inGame");
+ }
+ );
+ return false;
+ }
+ let win = this.cats.every(cat => {
+ let tile = this.world.getTile(3, cat.target.x, cat.target.y);
+ return tile.name == "exit";
+ });
+ if (
+ win === true &&
+ this.cats.length >= this.world.findTile(3, 8).length &&
+ !this.won
+ ) {
+ this.won = true;
+
+ this.world.assets.audio.jingle.audio.play();
+
+ if (
+ this.world.maps["map_" + (this.world.current_level + 1)] !== undefined
+ ) {
+ this.transition.start(
+ 0,
+ Math.max(this.world.W / 2, this.world.H / 2),
+ () => {
+ this.world.current_level += 1;
+ this.world.startScene("inGame");
+ }
+ );
+ } else {
+ this.transition.start(
+ 0,
+ Math.max(this.world.W / 2, this.world.H / 2),
+ () => {
+ this.world.startScene("menu");
+ }
+ );
+ }
+ }
+};
+// destroy itself when animation is finish
+class Effect extends Entity {
+ constructor(scene, sprite_data, x, y, callback) {
+ super(scene, x * scene.world.tile_size, y * scene.world.tile_size);
+ this.setSprite(sprite_data);
+ this.sprite.addAnimation("full", sprite_data.frames);
+ this.sprite.speed = 0.4;
+ this.sprite.offset.y = -3;
+ this.trigger = sprite_data.frames.length;
+ this.callback = callback || undefined;
+ }
+ render() {
+ if (this.sprite.current_frame + 1 === this.trigger) {
+ if (this.callback !== undefined) {
+ this.callback();
+ this.callback = undefined;
+ }
+ }
+ if (
+ this.sprite.current_frame + 1 ===
+ this.sprite.animations[this.sprite.current_animation].length
+ ) {
+ this.scene.effects.splice(this.scene.effects.indexOf(this), 1);
+ }
+ this.sprite.animate("full");
+ this.display();
+ }
+}
+class Cat extends Entity {
+ constructor(scene, x, y) {
+ super(scene, x * scene.world.tile_size, y * scene.world.tile_size);
+ this.old_position = new Vector(x, y);
+ this.target = new Vector(x, y);
+ this.canBeControlled = true;
+ this.inTranslation = false;
+ this.lastDirection = new Vector(0, 0);
+ this.isDead = false;
+ // Trasnlation of the cat when they move
+ this.transition = {
+ start: new Date(),
+ duration: 300,
+ type: Util.easeInOutQuad,
+ start_pos: new Vector()
+ };
+ }
+ // apply translation on cat when necessary
+ translation() {
+ if (this.inTranslation) {
+ // get current time !
+ let time = new Date() - this.transition.start;
+ if (time < this.transition.duration) {
+ let x = this.transition.type(
+ time,
+ this.transition.start_pos.x,
+ this.transition.target.x - this.transition.start_pos.x,
+ this.transition.duration
+ ),
+ y = this.transition.type(
+ time,
+ this.transition.start_pos.y,
+ this.transition.target.y - this.transition.start_pos.y,
+ this.transition.duration
+ );
+ this.body.position = new Vector(x, y);
+ } else {
+ // apply position when translation is finish :) !
+ this.old_position = this.target.copy();
+ let next_move = this.target.copy();
+ next_move.mult(this.world.tile_size);
+ this.body.position = next_move;
+ this.inTranslation = false;
+ if (this.isDead) {
+ // delete cat
+ let spawn_data = {
+ image: "water_splash",
+ frames: [0, 1, 2, 3, 4, 5, 6, 7, 8],
+ size: {
+ x: 20,
+ y: 32
+ }
+ };
+ let spawn_effect = new Effect(
+ this.scene,
+ spawn_data,
+ this.target.x,
+ this.target.y - 1,
+ () => {
+ this.scene.cats.splice(this.scene.cats.indexOf(this), 1);
+ this.world.assets.audio.splash.audio.play();
+ this.scene.checkWin();
+ }
+ );
+ spawn_effect.sprite.offset.y = 0;
+ spawn_effect.trigger = 2;
+ this.scene.effects.push(spawn_effect);
+ }
+ if (this.canBeControlled === false) {
+ this.move(this.lastDirection.x, this.lastDirection.y);
+ this.scene.collisionCats();
+ this.applyMove();
+ } else {
+ this.world.assets.audio.mouvement.audio.play();
+ // check arrows
+ let current_tile = this.world.getTile(
+ 3,
+ this.target.x,
+ this.target.y
+ );
+ switch (current_tile.name) {
+ case "arrowRight":
+ this.move(1, 0);
+ this.scene.collisionCats();
+ this.applyMove();
+ break;
+ case "arrowLeft":
+ this.move(-1, 0);
+ this.scene.collisionCats();
+ this.applyMove();
+ break;
+ case "arrowUp":
+ this.move(0, -1);
+ this.scene.collisionCats();
+ this.applyMove();
+ break;
+ case "arrowDown":
+ this.move(0, 1);
+ this.scene.collisionCats();
+ this.applyMove();
+ break;
+ default:
+ }
+ }
+ // check if we won when a cat finish a step
+ this.scene.checkWin();
+ }
+ }
+ }
+ move(x, y) {
+ this.target = this.old_position.copy();
+ let direction = new Vector(x, y);
+ // get future position
+ let future_position = this.target.copy();
+ future_position.add(direction);
+ let layers = this.world.terrain.layers;
+ let future_tile = layers.map(layer => {
+ let index = layers.indexOf(layer);
+ return this.world.getTile(index, future_position.x, future_position.y);
+ });
+ let collision = future_tile.every(tile => {
+ if (tile == false) {
+ return tile == false;
+ } else {
+ return tile.collision === false;
+ }
+ });
+ if (collision == true) {
+ this.target.add(direction);
+ }
+ if (future_tile[3].name === "ice") {
+ this.canBeControlled = false;
+ this.transition.type = Util.linearTween;
+ this.transition.duration = 100;
+ return false;
+ }
+ if (future_tile[3].name === "trap") {
+ let dust_data = {
+ image: "dust_effect",
+ frames: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
+ size: {
+ x: 32,
+ y: 32
+ }
+ };
+ let dust_effect = new Effect(
+ this.scene,
+ dust_data,
+ this.target.x,
+ this.target.y
+ );
+ this.scene.effects.push(dust_effect);
+ this.world.assets.audio.eboulement.audio.play();
+
+ this.world.terrain.layers[3].geometry[future_position.y][
+ future_position.x
+ ] = 10;
+ // cache the map
+ this.world.terrainCache(this.world.terrain.layers[3]);
+ return false;
+ }
+ if (future_tile[1].name !== "ground") {
+ this.transition.type = Util.easeInOutQuad;
+ this.transition.duration = 200;
+ this.isDead = true;
+ return false;
+ } else {
+ this.canBeControlled = true;
+ this.transition.type = Util.easeInOutQuad;
+ this.transition.duration = 200;
+ return false;
+ }
+ }
+ applyMove() {
+ // prevent cat to move if his target equal his actual position :V
+ if (
+ this.old_position.x === this.target.x &&
+ this.old_position.y === this.target.y
+ ) {
+ this.canBeControlled = true;
+ this.world.assets.audio.bump.audio.play();
+ return false;
+ }
+ this.lastDirection = new Vector(
+ this.target.x - this.old_position.x,
+ this.target.y - this.old_position.y
+ );
+ this.shouldMove = false;
+ this.transition.start_pos = this.old_position.copy();
+ this.transition.start_pos.mult(this.world.tile_size);
+ this.transition.target = this.target.copy();
+ this.transition.target.mult(this.world.tile_size);
+ this.transition.start = new Date();
+ this.inTranslation = true;
+ }
+ checkOthers() {
+ let others = this.scene.cats;
+ let result = false;
+ for (let i = 0; i < others.length; i++) {
+ if (this === others[i]) continue;
+ if (
+ others[i].target.x === this.target.x &&
+ others[i].target.y === this.target.y
+ ) {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+}
+let controls = new Scene("controls");
+controls.keyEvents = function(event) {
+ if (this.world.keys.KeyE) {
+ this.world.startScene("menu");
+ }
+};
+controls.init = function() {
+ this.loop = false;
+ this.controls = this.world.assets.image.controls.image;
+};
+controls.render = function() {
+ this.world.clear("black");
+ this.ctx.drawImage(this.controls, 0, 0);
+ // notice
+ this.world.setFont("origami_light");
+ this.world.write(
+ "[E] to exit",
+ this.world.W / 2,
+ this.world.H - 46,
+ "center"
+ );
+};
+
+let game = new Diorama(parameters);
+// global variables
+game.current_level = 1;
+// Add the different scenes here
+// the addScene function link the scene with the world (game)
+game.addScene(menu);
+game.addScene(levels);
+game.addScene(controls);
+game.addScene(inGame);
+game.ready();
+// everything start being loaded now !
+// the ready function must be called last !
+// Making the game full screen and with a 10% audio volume by default
+game.soundLevel(0.2);
+game.fullScreen();
\ No newline at end of file
diff --git a/Games/CopyCat/style.css b/Games/CopyCat/style.css
new file mode 100644
index 0000000000..310533f426
--- /dev/null
+++ b/Games/CopyCat/style.css
@@ -0,0 +1,24 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
+body {
+ color:white;
+ background-color: #000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+}
+canvas {
+ flex-shrink: 0;
+ background-color: #000;
+ object-fit: contain;
+}
+.crisp{
+ image-rendering: -moz-crisp-edges;
+ image-rendering: -webkit-crisp-edges;
+ image-rendering: pixelated;
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 0c95c64584..e80465005a 100644
--- a/README.md
+++ b/README.md
@@ -198,7 +198,9 @@ This repository also provides one such platforms where contributers come over an
| [Emoji_Intruder](https://github.com/kunjgit/GameZone/tree/main/Games/Emoji_Intruder) | [Guess The Weapon](https://github.com/kunjgit/GameZone/tree/main/Games/Guess_The_Weapon) | [Guess Who](https://github.com/kunjgit/GameZone/tree/main/Games/Guess_Who) | [Pop My Balloon](https://github.com/kunjgit/GameZone/tree/main/Games/Pop_My_Balloon) | [Tower Stack](https://github.com/kunjgit/GameZone/tree/main/Games/Tower_Stack) |
| [Maze_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Maze_Game) | [TriHand_Tactics](https://github.com/kunjgit/GameZone/tree/main/Games/TriHand_Tactics) | [Earth_Guardian](https://github.com/kunjgit/GameZone/tree/main/Games/Earth_Guardian) | [Ball_Shooting_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Ball_Shooting_Game) |
| [Ball_Shooting_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Ball_Shooting_Game) | [CatchTheBall](https://github.com/kunjgit/GameZone/tree/main/Games/CatchTheBall) |
-| [Ball_Shooting_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Ball_Shooting_Game) | [DoraemonRun ](https://github.com/kunjgit/GameZone/tree/main/Games/DoraemonRun) |
+| [Ball_Shooting_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Ball_Shooting_Game) |
+| [CopyCat](https://github.com/kunjgit/GameZone/tree/main/Games/CopyCat) |
+[DoraemonRun ](https://github.com/kunjgit/GameZone/tree/main/Games/DoraemonRun) |
| [Memory_Cards_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Memory_Cards_Game) |
| [Typing_Speed_Test2](https://github.com/kunjgit/GameZone/tree/main/Games/Typing_Speed_Test2) | [Tic Tac Toe Responsive ](https://github.com/kunjgit/GameZone/tree/main/Games/Tic_tac_toe_responsive) |
| [Technical_Mind_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Technical_Mind_Game) |
diff --git a/assets/images/CopyCat.png b/assets/images/CopyCat.png
new file mode 100644
index 0000000000..306bea8ced
Binary files /dev/null and b/assets/images/CopyCat.png differ
diff --git a/assets/images/CopyCat1.png b/assets/images/CopyCat1.png
new file mode 100644
index 0000000000..510fbcab13
Binary files /dev/null and b/assets/images/CopyCat1.png differ