commit 9c1c237a3d759a00f79f5ab331f3d4114bd89d09 Author: ExelentHA <108582881+ExelentHA@users.noreply.github.com> Date: Wed Jun 24 15:34:55 2026 +0800 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2cb3ec5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +static/img/* +__pycache__* +.vscode* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..12d0e48 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Portfolio Project + +Original Project is from Bemicode +## Repo: +- [Orignal Project](https://github.com/bedimcode/portfolio-responsive-complete) + +## Reference: +- [2048](https://github.com/gabrielecirulli/2048) +- [Space-Impact-Web](https://sidsinr.github.io/Space-Impact-Web) diff --git a/app.py b/app.py new file mode 100644 index 0000000..0035ec7 --- /dev/null +++ b/app.py @@ -0,0 +1,76 @@ +from flask import Flask +from flask import render_template +from flask import request +from flask import session, redirect, url_for, abort +from sqlalchemy import * + +app = Flask(__name__) +app.secret_key = 'my_super_duper_secret_key' # session keys + +engine = create_engine('postgresql+psycopg2://Exel:kali%20linux@127.0.0.1:5432/user', echo=True) + +@app.route("/login", methods=['POST', 'GET']) +def login(): + # check if the user is curently logged + if session.get('id') is not None: + return redirect(url_for('index')) + wrongpass = False # wrong pass flag + if request.method == 'POST': + usrname = request.form.get("username") + passwd = request.form.get('passwd') + print(f"Username: {usrname}, Password: {passwd}") + #connect to db + try: + with engine.connect() as c: + # vulnerable to sql injection + res = c.execute(text(f"select * from users where username = '{usrname}'")) + r1 = res.fetchone() + if r1 != None: + if r1.password == passwd: + session['id'] = r1.id; # register a session + return redirect(url_for("index")) + print("Correct") + else: + wrongpass = True + print("Error") + else: + wrongpass = True + print("Error") + except Exception as e: + print(f"[*] Error: {e}") + if wrongpass: + return render_template('login.html', error=True) + return render_template('login.html', error=False) + + +@app.route('/games', methods=['GET']) +def games(): + if session.get('id') is not None: + return render_template('games.html') + abort(403) + +@app.route('/2048', methods=['GET']) +def twentyfouroeight(): + if session.get('id') is not None: + return render_template('2048.html') + abort(403) + +@app.route('/SpaceImpact', methods=['GET']) +def SpaceImpact(): + if session.get('id') is not None: + return render_template('Space-Impact-Web.html') + abort(403) + + +@app.route("/logout", methods=['GET']) +def logout(): + if session.get('id') is not None: + session.pop("id", None) + return redirect(url_for('index')) + abort(403) + +@app.route("/", methods=['GET']) +def index(): + if session.get('id') is not None: + return render_template("index.html", logged_in=True) + return render_template("index.html", logged_in=False) diff --git a/preview.png b/preview.png new file mode 100644 index 0000000..0adebd5 Binary files /dev/null and b/preview.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..075688c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask +psycopg2 +sqlalchemy diff --git a/static/2048/CONTRIBUTING.md b/static/2048/CONTRIBUTING.md new file mode 100644 index 0000000..8b269ae --- /dev/null +++ b/static/2048/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing +Changes and improvements are more than welcome! Feel free to fork and open a pull request. + +Please follow the house rules to have a bigger chance of your contribution being merged. + +## House rules + +### How to make changes + - To make changes, create a new branch based on `master` (do not create one from `gh-pages` unless strictly necessary) and make them there, then create a Pull Request to master. + `gh-pages` is different from master in that it contains sharing features, analytics and other things that have no direct bearing with the game. `master` is the "pure" version of the game. + - If you want to modify the CSS, please edit the SCSS files present in `style/`: `main.scss` and others. Don't edit the `main.css`, because it's supposed to be generated. + In order to compile your SCSS modifications, you need to use the `sass` gem (install it by running `gem install sass` once Ruby is installed). + To run SASS, simply use the following command: + `sass --unix-newlines --watch style/main.scss` + SASS will automatically recompile your css when changed. + - `Rakefile` contains some tasks that help during development. Feel free to add useful tasks if needed. + - Please use 2-space indentation when editing the JavaScript. A `.jshintrc` file is present, which will help your code to follow the guidelines if you install and run `jshint`. + - Please test your modification thoroughly before submitting your Pull Request. + +### Changes that might not be accepted +We have to be conservative with the core game. This means that some modifications won't be merged, or will have to be evaluated carefully before being merged: + + - Undo/redo features + - Save/reload features + - Changes to how the tiles look or their contents + - Changes to the layout + - Changes to the grid size + +### Changes that are welcome + - Bug fixes + - Compatibility improvements + - "Under the hood" enhancements + - Small changes that don't have an impact on the core gameplay diff --git a/static/2048/LICENSE.txt b/static/2048/LICENSE.txt new file mode 100644 index 0000000..b0dbfa4 --- /dev/null +++ b/static/2048/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Gabriele Cirulli + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/static/2048/README.md b/static/2048/README.md new file mode 100644 index 0000000..c947a03 --- /dev/null +++ b/static/2048/README.md @@ -0,0 +1,38 @@ +# 2048 +A small clone of [1024](https://play.google.com/store/apps/details?id=com.veewo.a1024), based on [Saming's 2048](http://saming.fr/p/2048/) (also a clone). 2048 was indirectly inspired by [Threes](https://asherv.com/threes/). + +Made just for fun. [Play it here!](http://gabrielecirulli.github.io/2048/) + +The official app can also be found on the [Play Store](https://play.google.com/store/apps/details?id=com.gabrielecirulli.app2048) and [App Store!](https://itunes.apple.com/us/app/2048-by-gabriele-cirulli/id868076805) + +### Contributions + +[Anna Harren](https://github.com/iirelu/) and [sigod](https://github.com/sigod) are maintainers for this repository. + +Other notable contributors: + + - [TimPetricola](https://github.com/TimPetricola) added best score storage + - [chrisprice](https://github.com/chrisprice) added custom code for swipe handling on mobile + - [marcingajda](https://github.com/marcingajda) made swipes work on Windows Phone + - [mgarciaisaia](https://github.com/mgarciaisaia) added support for Android 2.3 + +Many thanks to [rayhaanj](https://github.com/rayhaanj), [Mechazawa](https://github.com/Mechazawa), [grant](https://github.com/grant), [remram44](https://github.com/remram44) and [ghoullier](https://github.com/ghoullier) for the many other good contributions. + +### Screenshot + +

+ Screenshot +

+ +That screenshot is fake, by the way. I never reached 2048 :smile: + +## Contributing +Changes and improvements are more than welcome! Feel free to fork and open a pull request. Please make your changes in a specific branch and request to pull into `master`! If you can, please make sure the game fully works before sending the PR, as that will help speed up the process. + +You can find the same information in the [contributing guide.](https://github.com/gabrielecirulli/2048/blob/master/CONTRIBUTING.md) + +## License +2048 is licensed under the [MIT license.](https://github.com/gabrielecirulli/2048/blob/master/LICENSE.txt) + +## Donations +I made this in my spare time, and it's hosted on GitHub (which means I don't have any hosting costs), but if you enjoyed the game and feel like buying me coffee, you can donate at my BTC address: `1Ec6onfsQmoP9kkL3zkpB6c5sA4PVcXU2i`. Thank you very much! diff --git a/static/2048/Rakefile b/static/2048/Rakefile new file mode 100644 index 0000000..3e9851e --- /dev/null +++ b/static/2048/Rakefile @@ -0,0 +1,11 @@ +require "date" + +namespace :appcache do + desc "update the date in the appcache file (in the gh-pages branch)" + task :update do + appcache = File.read("cache.appcache") + updated = "# Updated: #{DateTime.now}" + + File.write("cache.appcache", appcache.sub(/^# Updated:.*$/, updated)) + end +end diff --git a/static/2048/favicon.ico b/static/2048/favicon.ico new file mode 100644 index 0000000..22109e0 Binary files /dev/null and b/static/2048/favicon.ico differ diff --git a/static/2048/js/animframe_polyfill.js b/static/2048/js/animframe_polyfill.js new file mode 100644 index 0000000..c524a99 --- /dev/null +++ b/static/2048/js/animframe_polyfill.js @@ -0,0 +1,28 @@ +(function () { + var lastTime = 0; + var vendors = ['webkit', 'moz']; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || + window[vendors[x] + 'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function (callback) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + } +}()); diff --git a/static/2048/js/application.js b/static/2048/js/application.js new file mode 100644 index 0000000..2c1108e --- /dev/null +++ b/static/2048/js/application.js @@ -0,0 +1,4 @@ +// Wait till the browser is ready to render the game (avoids glitches) +window.requestAnimationFrame(function () { + new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager); +}); diff --git a/static/2048/js/bind_polyfill.js b/static/2048/js/bind_polyfill.js new file mode 100644 index 0000000..8d9c4a4 --- /dev/null +++ b/static/2048/js/bind_polyfill.js @@ -0,0 +1,9 @@ +Function.prototype.bind = Function.prototype.bind || function (target) { + var self = this; + return function (args) { + if (!(args instanceof Array)) { + args = [args]; + } + self.apply(target, args); + }; +}; diff --git a/static/2048/js/classlist_polyfill.js b/static/2048/js/classlist_polyfill.js new file mode 100644 index 0000000..1789ae7 --- /dev/null +++ b/static/2048/js/classlist_polyfill.js @@ -0,0 +1,71 @@ +(function () { + if (typeof window.Element === "undefined" || + "classList" in document.documentElement) { + return; + } + + var prototype = Array.prototype, + push = prototype.push, + splice = prototype.splice, + join = prototype.join; + + function DOMTokenList(el) { + this.el = el; + // The className needs to be trimmed and split on whitespace + // to retrieve a list of classes. + var classes = el.className.replace(/^\s+|\s+$/g, '').split(/\s+/); + for (var i = 0; i < classes.length; i++) { + push.call(this, classes[i]); + } + } + + DOMTokenList.prototype = { + add: function (token) { + if (this.contains(token)) return; + push.call(this, token); + this.el.className = this.toString(); + }, + contains: function (token) { + return this.el.className.indexOf(token) != -1; + }, + item: function (index) { + return this[index] || null; + }, + remove: function (token) { + if (!this.contains(token)) return; + for (var i = 0; i < this.length; i++) { + if (this[i] == token) break; + } + splice.call(this, i, 1); + this.el.className = this.toString(); + }, + toString: function () { + return join.call(this, ' '); + }, + toggle: function (token) { + if (!this.contains(token)) { + this.add(token); + } else { + this.remove(token); + } + + return this.contains(token); + } + }; + + window.DOMTokenList = DOMTokenList; + + function defineElementGetter(obj, prop, getter) { + if (Object.defineProperty) { + Object.defineProperty(obj, prop, { + get: getter + }); + } else { + obj.__defineGetter__(prop, getter); + } + } + + defineElementGetter(HTMLElement.prototype, 'classList', function () { + return new DOMTokenList(this); + }); +})(); diff --git a/static/2048/js/game_manager.js b/static/2048/js/game_manager.js new file mode 100644 index 0000000..1c13d15 --- /dev/null +++ b/static/2048/js/game_manager.js @@ -0,0 +1,272 @@ +function GameManager(size, InputManager, Actuator, StorageManager) { + this.size = size; // Size of the grid + this.inputManager = new InputManager; + this.storageManager = new StorageManager; + this.actuator = new Actuator; + + this.startTiles = 2; + + this.inputManager.on("move", this.move.bind(this)); + this.inputManager.on("restart", this.restart.bind(this)); + this.inputManager.on("keepPlaying", this.keepPlaying.bind(this)); + + this.setup(); +} + +// Restart the game +GameManager.prototype.restart = function () { + this.storageManager.clearGameState(); + this.actuator.continueGame(); // Clear the game won/lost message + this.setup(); +}; + +// Keep playing after winning (allows going over 2048) +GameManager.prototype.keepPlaying = function () { + this.keepPlaying = true; + this.actuator.continueGame(); // Clear the game won/lost message +}; + +// Return true if the game is lost, or has won and the user hasn't kept playing +GameManager.prototype.isGameTerminated = function () { + return this.over || (this.won && !this.keepPlaying); +}; + +// Set up the game +GameManager.prototype.setup = function () { + var previousState = this.storageManager.getGameState(); + + // Reload the game from a previous game if present + if (previousState) { + this.grid = new Grid(previousState.grid.size, + previousState.grid.cells); // Reload grid + this.score = previousState.score; + this.over = previousState.over; + this.won = previousState.won; + this.keepPlaying = previousState.keepPlaying; + } else { + this.grid = new Grid(this.size); + this.score = 0; + this.over = false; + this.won = false; + this.keepPlaying = false; + + // Add the initial tiles + this.addStartTiles(); + } + + // Update the actuator + this.actuate(); +}; + +// Set up the initial tiles to start the game with +GameManager.prototype.addStartTiles = function () { + for (var i = 0; i < this.startTiles; i++) { + this.addRandomTile(); + } +}; + +// Adds a tile in a random position +GameManager.prototype.addRandomTile = function () { + if (this.grid.cellsAvailable()) { + var value = Math.random() < 0.9 ? 2 : 4; + var tile = new Tile(this.grid.randomAvailableCell(), value); + + this.grid.insertTile(tile); + } +}; + +// Sends the updated grid to the actuator +GameManager.prototype.actuate = function () { + if (this.storageManager.getBestScore() < this.score) { + this.storageManager.setBestScore(this.score); + } + + // Clear the state when the game is over (game over only, not win) + if (this.over) { + this.storageManager.clearGameState(); + } else { + this.storageManager.setGameState(this.serialize()); + } + + this.actuator.actuate(this.grid, { + score: this.score, + over: this.over, + won: this.won, + bestScore: this.storageManager.getBestScore(), + terminated: this.isGameTerminated() + }); + +}; + +// Represent the current game as an object +GameManager.prototype.serialize = function () { + return { + grid: this.grid.serialize(), + score: this.score, + over: this.over, + won: this.won, + keepPlaying: this.keepPlaying + }; +}; + +// Save all tile positions and remove merger info +GameManager.prototype.prepareTiles = function () { + this.grid.eachCell(function (x, y, tile) { + if (tile) { + tile.mergedFrom = null; + tile.savePosition(); + } + }); +}; + +// Move a tile and its representation +GameManager.prototype.moveTile = function (tile, cell) { + this.grid.cells[tile.x][tile.y] = null; + this.grid.cells[cell.x][cell.y] = tile; + tile.updatePosition(cell); +}; + +// Move tiles on the grid in the specified direction +GameManager.prototype.move = function (direction) { + // 0: up, 1: right, 2: down, 3: left + var self = this; + + if (this.isGameTerminated()) return; // Don't do anything if the game's over + + var cell, tile; + + var vector = this.getVector(direction); + var traversals = this.buildTraversals(vector); + var moved = false; + + // Save the current tile positions and remove merger information + this.prepareTiles(); + + // Traverse the grid in the right direction and move tiles + traversals.x.forEach(function (x) { + traversals.y.forEach(function (y) { + cell = { x: x, y: y }; + tile = self.grid.cellContent(cell); + + if (tile) { + var positions = self.findFarthestPosition(cell, vector); + var next = self.grid.cellContent(positions.next); + + // Only one merger per row traversal? + if (next && next.value === tile.value && !next.mergedFrom) { + var merged = new Tile(positions.next, tile.value * 2); + merged.mergedFrom = [tile, next]; + + self.grid.insertTile(merged); + self.grid.removeTile(tile); + + // Converge the two tiles' positions + tile.updatePosition(positions.next); + + // Update the score + self.score += merged.value; + + // The mighty 2048 tile + if (merged.value === 2048) self.won = true; + } else { + self.moveTile(tile, positions.farthest); + } + + if (!self.positionsEqual(cell, tile)) { + moved = true; // The tile moved from its original cell! + } + } + }); + }); + + if (moved) { + this.addRandomTile(); + + if (!this.movesAvailable()) { + this.over = true; // Game over! + } + + this.actuate(); + } +}; + +// Get the vector representing the chosen direction +GameManager.prototype.getVector = function (direction) { + // Vectors representing tile movement + var map = { + 0: { x: 0, y: -1 }, // Up + 1: { x: 1, y: 0 }, // Right + 2: { x: 0, y: 1 }, // Down + 3: { x: -1, y: 0 } // Left + }; + + return map[direction]; +}; + +// Build a list of positions to traverse in the right order +GameManager.prototype.buildTraversals = function (vector) { + var traversals = { x: [], y: [] }; + + for (var pos = 0; pos < this.size; pos++) { + traversals.x.push(pos); + traversals.y.push(pos); + } + + // Always traverse from the farthest cell in the chosen direction + if (vector.x === 1) traversals.x = traversals.x.reverse(); + if (vector.y === 1) traversals.y = traversals.y.reverse(); + + return traversals; +}; + +GameManager.prototype.findFarthestPosition = function (cell, vector) { + var previous; + + // Progress towards the vector direction until an obstacle is found + do { + previous = cell; + cell = { x: previous.x + vector.x, y: previous.y + vector.y }; + } while (this.grid.withinBounds(cell) && + this.grid.cellAvailable(cell)); + + return { + farthest: previous, + next: cell // Used to check if a merge is required + }; +}; + +GameManager.prototype.movesAvailable = function () { + return this.grid.cellsAvailable() || this.tileMatchesAvailable(); +}; + +// Check for available matches between tiles (more expensive check) +GameManager.prototype.tileMatchesAvailable = function () { + var self = this; + + var tile; + + for (var x = 0; x < this.size; x++) { + for (var y = 0; y < this.size; y++) { + tile = this.grid.cellContent({ x: x, y: y }); + + if (tile) { + for (var direction = 0; direction < 4; direction++) { + var vector = self.getVector(direction); + var cell = { x: x + vector.x, y: y + vector.y }; + + var other = self.grid.cellContent(cell); + + if (other && other.value === tile.value) { + return true; // These two tiles can be merged + } + } + } + } + } + + return false; +}; + +GameManager.prototype.positionsEqual = function (first, second) { + return first.x === second.x && first.y === second.y; +}; diff --git a/static/2048/js/grid.js b/static/2048/js/grid.js new file mode 100644 index 0000000..29f0821 --- /dev/null +++ b/static/2048/js/grid.js @@ -0,0 +1,117 @@ +function Grid(size, previousState) { + this.size = size; + this.cells = previousState ? this.fromState(previousState) : this.empty(); +} + +// Build a grid of the specified size +Grid.prototype.empty = function () { + var cells = []; + + for (var x = 0; x < this.size; x++) { + var row = cells[x] = []; + + for (var y = 0; y < this.size; y++) { + row.push(null); + } + } + + return cells; +}; + +Grid.prototype.fromState = function (state) { + var cells = []; + + for (var x = 0; x < this.size; x++) { + var row = cells[x] = []; + + for (var y = 0; y < this.size; y++) { + var tile = state[x][y]; + row.push(tile ? new Tile(tile.position, tile.value) : null); + } + } + + return cells; +}; + +// Find the first available random position +Grid.prototype.randomAvailableCell = function () { + var cells = this.availableCells(); + + if (cells.length) { + return cells[Math.floor(Math.random() * cells.length)]; + } +}; + +Grid.prototype.availableCells = function () { + var cells = []; + + this.eachCell(function (x, y, tile) { + if (!tile) { + cells.push({ x: x, y: y }); + } + }); + + return cells; +}; + +// Call callback for every cell +Grid.prototype.eachCell = function (callback) { + for (var x = 0; x < this.size; x++) { + for (var y = 0; y < this.size; y++) { + callback(x, y, this.cells[x][y]); + } + } +}; + +// Check if there are any cells available +Grid.prototype.cellsAvailable = function () { + return !!this.availableCells().length; +}; + +// Check if the specified cell is taken +Grid.prototype.cellAvailable = function (cell) { + return !this.cellOccupied(cell); +}; + +Grid.prototype.cellOccupied = function (cell) { + return !!this.cellContent(cell); +}; + +Grid.prototype.cellContent = function (cell) { + if (this.withinBounds(cell)) { + return this.cells[cell.x][cell.y]; + } else { + return null; + } +}; + +// Inserts a tile at its position +Grid.prototype.insertTile = function (tile) { + this.cells[tile.x][tile.y] = tile; +}; + +Grid.prototype.removeTile = function (tile) { + this.cells[tile.x][tile.y] = null; +}; + +Grid.prototype.withinBounds = function (position) { + return position.x >= 0 && position.x < this.size && + position.y >= 0 && position.y < this.size; +}; + +Grid.prototype.serialize = function () { + var cellState = []; + + for (var x = 0; x < this.size; x++) { + var row = cellState[x] = []; + + for (var y = 0; y < this.size; y++) { + row.push(this.cells[x][y] ? this.cells[x][y].serialize() : null); + } + } + + return { + size: this.size, + cells: cellState + }; +}; diff --git a/static/2048/js/html_actuator.js b/static/2048/js/html_actuator.js new file mode 100644 index 0000000..6b31f2d --- /dev/null +++ b/static/2048/js/html_actuator.js @@ -0,0 +1,139 @@ +function HTMLActuator() { + this.tileContainer = document.querySelector(".tile-container"); + this.scoreContainer = document.querySelector(".score-container"); + this.bestContainer = document.querySelector(".best-container"); + this.messageContainer = document.querySelector(".game-message"); + + this.score = 0; +} + +HTMLActuator.prototype.actuate = function (grid, metadata) { + var self = this; + + window.requestAnimationFrame(function () { + self.clearContainer(self.tileContainer); + + grid.cells.forEach(function (column) { + column.forEach(function (cell) { + if (cell) { + self.addTile(cell); + } + }); + }); + + self.updateScore(metadata.score); + self.updateBestScore(metadata.bestScore); + + if (metadata.terminated) { + if (metadata.over) { + self.message(false); // You lose + } else if (metadata.won) { + self.message(true); // You win! + } + } + + }); +}; + +// Continues the game (both restart and keep playing) +HTMLActuator.prototype.continueGame = function () { + this.clearMessage(); +}; + +HTMLActuator.prototype.clearContainer = function (container) { + while (container.firstChild) { + container.removeChild(container.firstChild); + } +}; + +HTMLActuator.prototype.addTile = function (tile) { + var self = this; + + var wrapper = document.createElement("div"); + var inner = document.createElement("div"); + var position = tile.previousPosition || { x: tile.x, y: tile.y }; + var positionClass = this.positionClass(position); + + // We can't use classlist because it somehow glitches when replacing classes + var classes = ["tile", "tile-" + tile.value, positionClass]; + + if (tile.value > 2048) classes.push("tile-super"); + + this.applyClasses(wrapper, classes); + + inner.classList.add("tile-inner"); + inner.textContent = tile.value; + + if (tile.previousPosition) { + // Make sure that the tile gets rendered in the previous position first + window.requestAnimationFrame(function () { + classes[2] = self.positionClass({ x: tile.x, y: tile.y }); + self.applyClasses(wrapper, classes); // Update the position + }); + } else if (tile.mergedFrom) { + classes.push("tile-merged"); + this.applyClasses(wrapper, classes); + + // Render the tiles that merged + tile.mergedFrom.forEach(function (merged) { + self.addTile(merged); + }); + } else { + classes.push("tile-new"); + this.applyClasses(wrapper, classes); + } + + // Add the inner part of the tile to the wrapper + wrapper.appendChild(inner); + + // Put the tile on the board + this.tileContainer.appendChild(wrapper); +}; + +HTMLActuator.prototype.applyClasses = function (element, classes) { + element.setAttribute("class", classes.join(" ")); +}; + +HTMLActuator.prototype.normalizePosition = function (position) { + return { x: position.x + 1, y: position.y + 1 }; +}; + +HTMLActuator.prototype.positionClass = function (position) { + position = this.normalizePosition(position); + return "tile-position-" + position.x + "-" + position.y; +}; + +HTMLActuator.prototype.updateScore = function (score) { + this.clearContainer(this.scoreContainer); + + var difference = score - this.score; + this.score = score; + + this.scoreContainer.textContent = this.score; + + if (difference > 0) { + var addition = document.createElement("div"); + addition.classList.add("score-addition"); + addition.textContent = "+" + difference; + + this.scoreContainer.appendChild(addition); + } +}; + +HTMLActuator.prototype.updateBestScore = function (bestScore) { + this.bestContainer.textContent = bestScore; +}; + +HTMLActuator.prototype.message = function (won) { + var type = won ? "game-won" : "game-over"; + var message = won ? "You win!" : "Game over!"; + + this.messageContainer.classList.add(type); + this.messageContainer.getElementsByTagName("p")[0].textContent = message; +}; + +HTMLActuator.prototype.clearMessage = function () { + // IE only takes one value to remove at a time. + this.messageContainer.classList.remove("game-won"); + this.messageContainer.classList.remove("game-over"); +}; diff --git a/static/2048/js/keyboard_input_manager.js b/static/2048/js/keyboard_input_manager.js new file mode 100644 index 0000000..ca01b3c --- /dev/null +++ b/static/2048/js/keyboard_input_manager.js @@ -0,0 +1,144 @@ +function KeyboardInputManager() { + this.events = {}; + + if (window.navigator.msPointerEnabled) { + //Internet Explorer 10 style + this.eventTouchstart = "MSPointerDown"; + this.eventTouchmove = "MSPointerMove"; + this.eventTouchend = "MSPointerUp"; + } else { + this.eventTouchstart = "touchstart"; + this.eventTouchmove = "touchmove"; + this.eventTouchend = "touchend"; + } + + this.listen(); +} + +KeyboardInputManager.prototype.on = function (event, callback) { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(callback); +}; + +KeyboardInputManager.prototype.emit = function (event, data) { + var callbacks = this.events[event]; + if (callbacks) { + callbacks.forEach(function (callback) { + callback(data); + }); + } +}; + +KeyboardInputManager.prototype.listen = function () { + var self = this; + + var map = { + 38: 0, // Up + 39: 1, // Right + 40: 2, // Down + 37: 3, // Left + 75: 0, // Vim up + 76: 1, // Vim right + 74: 2, // Vim down + 72: 3, // Vim left + 87: 0, // W + 68: 1, // D + 83: 2, // S + 65: 3 // A + }; + + // Respond to direction keys + document.addEventListener("keydown", function (event) { + var modifiers = event.altKey || event.ctrlKey || event.metaKey || + event.shiftKey; + var mapped = map[event.which]; + + if (!modifiers) { + if (mapped !== undefined) { + event.preventDefault(); + self.emit("move", mapped); + } + } + + // R key restarts the game + if (!modifiers && event.which === 82) { + self.restart.call(self, event); + } + }); + + // Respond to button presses + this.bindButtonPress(".retry-button", this.restart); + this.bindButtonPress(".restart-button", this.restart); + this.bindButtonPress(".keep-playing-button", this.keepPlaying); + + // Respond to swipe events + var touchStartClientX, touchStartClientY; + var gameContainer = document.getElementsByClassName("game-container")[0]; + + gameContainer.addEventListener(this.eventTouchstart, function (event) { + if ((!window.navigator.msPointerEnabled && event.touches.length > 1) || + event.targetTouches.length > 1) { + return; // Ignore if touching with more than 1 finger + } + + if (window.navigator.msPointerEnabled) { + touchStartClientX = event.pageX; + touchStartClientY = event.pageY; + } else { + touchStartClientX = event.touches[0].clientX; + touchStartClientY = event.touches[0].clientY; + } + + event.preventDefault(); + }); + + gameContainer.addEventListener(this.eventTouchmove, function (event) { + event.preventDefault(); + }); + + gameContainer.addEventListener(this.eventTouchend, function (event) { + if ((!window.navigator.msPointerEnabled && event.touches.length > 0) || + event.targetTouches.length > 0) { + return; // Ignore if still touching with one or more fingers + } + + var touchEndClientX, touchEndClientY; + + if (window.navigator.msPointerEnabled) { + touchEndClientX = event.pageX; + touchEndClientY = event.pageY; + } else { + touchEndClientX = event.changedTouches[0].clientX; + touchEndClientY = event.changedTouches[0].clientY; + } + + var dx = touchEndClientX - touchStartClientX; + var absDx = Math.abs(dx); + + var dy = touchEndClientY - touchStartClientY; + var absDy = Math.abs(dy); + + if (Math.max(absDx, absDy) > 10) { + // (right : left) : (down : up) + self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0)); + } + }); +}; + +KeyboardInputManager.prototype.restart = function (event) { + event.preventDefault(); + this.emit("restart"); +}; + +KeyboardInputManager.prototype.keepPlaying = function (event) { + event.preventDefault(); + this.emit("keepPlaying"); +}; + +KeyboardInputManager.prototype.bindButtonPress = function (selector, fn) { + var button = document.querySelector(selector); + button.addEventListener("click", fn.bind(this)); + button.addEventListener(this.eventTouchend, fn.bind(this)); +}; diff --git a/static/2048/js/local_storage_manager.js b/static/2048/js/local_storage_manager.js new file mode 100644 index 0000000..2ca9cc3 --- /dev/null +++ b/static/2048/js/local_storage_manager.js @@ -0,0 +1,63 @@ +window.fakeStorage = { + _data: {}, + + setItem: function (id, val) { + return this._data[id] = String(val); + }, + + getItem: function (id) { + return this._data.hasOwnProperty(id) ? this._data[id] : undefined; + }, + + removeItem: function (id) { + return delete this._data[id]; + }, + + clear: function () { + return this._data = {}; + } +}; + +function LocalStorageManager() { + this.bestScoreKey = "bestScore"; + this.gameStateKey = "gameState"; + + var supported = this.localStorageSupported(); + this.storage = supported ? window.localStorage : window.fakeStorage; +} + +LocalStorageManager.prototype.localStorageSupported = function () { + var testKey = "test"; + + try { + var storage = window.localStorage; + storage.setItem(testKey, "1"); + storage.removeItem(testKey); + return true; + } catch (error) { + return false; + } +}; + +// Best score getters/setters +LocalStorageManager.prototype.getBestScore = function () { + return this.storage.getItem(this.bestScoreKey) || 0; +}; + +LocalStorageManager.prototype.setBestScore = function (score) { + this.storage.setItem(this.bestScoreKey, score); +}; + +// Game state getters/setters and clearing +LocalStorageManager.prototype.getGameState = function () { + var stateJSON = this.storage.getItem(this.gameStateKey); + return stateJSON ? JSON.parse(stateJSON) : null; +}; + +LocalStorageManager.prototype.setGameState = function (gameState) { + this.storage.setItem(this.gameStateKey, JSON.stringify(gameState)); +}; + +LocalStorageManager.prototype.clearGameState = function () { + this.storage.removeItem(this.gameStateKey); +}; diff --git a/static/2048/js/tile.js b/static/2048/js/tile.js new file mode 100644 index 0000000..92a670a --- /dev/null +++ b/static/2048/js/tile.js @@ -0,0 +1,27 @@ +function Tile(position, value) { + this.x = position.x; + this.y = position.y; + this.value = value || 2; + + this.previousPosition = null; + this.mergedFrom = null; // Tracks tiles that merged together +} + +Tile.prototype.savePosition = function () { + this.previousPosition = { x: this.x, y: this.y }; +}; + +Tile.prototype.updatePosition = function (position) { + this.x = position.x; + this.y = position.y; +}; + +Tile.prototype.serialize = function () { + return { + position: { + x: this.x, + y: this.y + }, + value: this.value + }; +}; diff --git a/static/2048/meta/apple-touch-icon.png b/static/2048/meta/apple-touch-icon.png new file mode 100644 index 0000000..3fd20f6 Binary files /dev/null and b/static/2048/meta/apple-touch-icon.png differ diff --git a/static/2048/meta/apple-touch-startup-image-640x1096.png b/static/2048/meta/apple-touch-startup-image-640x1096.png new file mode 100644 index 0000000..5a68ba0 Binary files /dev/null and b/static/2048/meta/apple-touch-startup-image-640x1096.png differ diff --git a/static/2048/meta/apple-touch-startup-image-640x920.png b/static/2048/meta/apple-touch-startup-image-640x920.png new file mode 100644 index 0000000..17bc9d8 Binary files /dev/null and b/static/2048/meta/apple-touch-startup-image-640x920.png differ diff --git a/static/2048/style/fonts/ClearSans-Bold-webfont.eot b/static/2048/style/fonts/ClearSans-Bold-webfont.eot new file mode 100644 index 0000000..3678ef2 Binary files /dev/null and b/static/2048/style/fonts/ClearSans-Bold-webfont.eot differ diff --git a/static/2048/style/fonts/ClearSans-Bold-webfont.svg b/static/2048/style/fonts/ClearSans-Bold-webfont.svg new file mode 100644 index 0000000..aa985ae --- /dev/null +++ b/static/2048/style/fonts/ClearSans-Bold-webfont.svg @@ -0,0 +1,640 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/2048/style/fonts/ClearSans-Bold-webfont.woff b/static/2048/style/fonts/ClearSans-Bold-webfont.woff new file mode 100644 index 0000000..184a945 Binary files /dev/null and b/static/2048/style/fonts/ClearSans-Bold-webfont.woff differ diff --git a/static/2048/style/fonts/ClearSans-Light-webfont.eot b/static/2048/style/fonts/ClearSans-Light-webfont.eot new file mode 100644 index 0000000..0dc609d Binary files /dev/null and b/static/2048/style/fonts/ClearSans-Light-webfont.eot differ diff --git a/static/2048/style/fonts/ClearSans-Light-webfont.svg b/static/2048/style/fonts/ClearSans-Light-webfont.svg new file mode 100644 index 0000000..1d5d2ec --- /dev/null +++ b/static/2048/style/fonts/ClearSans-Light-webfont.svg @@ -0,0 +1,670 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/2048/style/fonts/ClearSans-Light-webfont.woff b/static/2048/style/fonts/ClearSans-Light-webfont.woff new file mode 100644 index 0000000..44555e0 Binary files /dev/null and b/static/2048/style/fonts/ClearSans-Light-webfont.woff differ diff --git a/static/2048/style/fonts/ClearSans-Regular-webfont.eot b/static/2048/style/fonts/ClearSans-Regular-webfont.eot new file mode 100644 index 0000000..b020e05 Binary files /dev/null and b/static/2048/style/fonts/ClearSans-Regular-webfont.eot differ diff --git a/static/2048/style/fonts/ClearSans-Regular-webfont.svg b/static/2048/style/fonts/ClearSans-Regular-webfont.svg new file mode 100644 index 0000000..1e2cffd --- /dev/null +++ b/static/2048/style/fonts/ClearSans-Regular-webfont.svg @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/2048/style/fonts/ClearSans-Regular-webfont.woff b/static/2048/style/fonts/ClearSans-Regular-webfont.woff new file mode 100644 index 0000000..9d58858 Binary files /dev/null and b/static/2048/style/fonts/ClearSans-Regular-webfont.woff differ diff --git a/static/2048/style/fonts/clear-sans.css b/static/2048/style/fonts/clear-sans.css new file mode 100644 index 0000000..de2811d --- /dev/null +++ b/static/2048/style/fonts/clear-sans.css @@ -0,0 +1,30 @@ +@font-face { + font-family: "Clear Sans"; + src: url("ClearSans-Light-webfont.eot"); + src: url("ClearSans-Light-webfont.eot?#iefix") format("embedded-opentype"), + url("ClearSans-Light-webfont.svg#clear_sans_lightregular") format("svg"), + url("ClearSans-Light-webfont.woff") format("woff"); + font-weight: 200; + font-style: normal; +} + +@font-face { + font-family: "Clear Sans"; + src: url("ClearSans-Regular-webfont.eot"); + src: url("ClearSans-Regular-webfont.eot?#iefix") format("embedded-opentype"), + url("ClearSans-Regular-webfont.svg#clear_sansregular") format("svg"), + url("ClearSans-Regular-webfont.woff") format("woff"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "Clear Sans"; + src: url("ClearSans-Bold-webfont.eot"); + src: url("ClearSans-Bold-webfont.eot?#iefix") format("embedded-opentype"), + url("ClearSans-Bold-webfont.svg#clear_sansbold") format("svg"), + url("ClearSans-Bold-webfont.woff") format("woff"); + font-weight: 700; + font-style: normal; +} + diff --git a/static/2048/style/helpers.scss b/static/2048/style/helpers.scss new file mode 100644 index 0000000..72693ee --- /dev/null +++ b/static/2048/style/helpers.scss @@ -0,0 +1,82 @@ +// Exponent +// From: https://github.com/Team-Sass/Sassy-math/blob/master/sass/math.scss#L36 + +@function exponent($base, $exponent) { + // reset value + $value: $base; + // positive intergers get multiplied + @if $exponent > 1 { + @for $i from 2 through $exponent { + $value: $value * $base; } } + // negitive intergers get divided. A number divided by itself is 1 + @if $exponent < 1 { + @for $i from 0 through -$exponent { + $value: $value / $base; } } + // return the last value written + @return $value; +} + +@function pow($base, $exponent) { + @return exponent($base, $exponent); +} + +// Transition mixins +@mixin transition($args...) { + -webkit-transition: $args; + -moz-transition: $args; + transition: $args; +} + +@mixin transition-property($args...) { + -webkit-transition-property: $args; + -moz-transition-property: $args; + transition-property: $args; +} + +@mixin animation($args...) { + -webkit-animation: $args; + -moz-animation: $args; + animation: $args; +} + +@mixin animation-fill-mode($args...) { + -webkit-animation-fill-mode: $args; + -moz-animation-fill-mode: $args; + animation-fill-mode: $args; +} + +@mixin transform($args...) { + -webkit-transform: $args; + -moz-transform: $args; + -ms-transform: $args; + transform: $args; +} + +// Keyframe animations +@mixin keyframes($animation-name) { + @-webkit-keyframes $animation-name { + @content; + } + @-moz-keyframes $animation-name { + @content; + } + @keyframes $animation-name { + @content; + } +} + +// Media queries +@mixin smaller($width) { + @media screen and (max-width: $width) { + @content; + } +} + +// Clearfix +@mixin clearfix { + &:after { + content: ""; + display: block; + clear: both; + } +} diff --git a/static/2048/style/main.css b/static/2048/style/main.css new file mode 100644 index 0000000..cbd4b3f --- /dev/null +++ b/static/2048/style/main.css @@ -0,0 +1,779 @@ +@import url(fonts/clear-sans.css); +html, body { + margin: 0; + padding: 0; + background: #faf8ef; + color: #776e65; + font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif; + font-size: 18px; } + +body { + margin: 80px 0; } + +.heading:after { + content: ""; + display: block; + clear: both; } + +h1.title { + font-size: 80px; + font-weight: bold; + margin: 0; + display: block; + float: left; } + +@-webkit-keyframes move-up { + 0% { + top: 25px; + opacity: 1; } + + 100% { + top: -50px; + opacity: 0; } } +@-moz-keyframes move-up { + 0% { + top: 25px; + opacity: 1; } + + 100% { + top: -50px; + opacity: 0; } } +@keyframes move-up { + 0% { + top: 25px; + opacity: 1; } + + 100% { + top: -50px; + opacity: 0; } } +.scores-container { + float: right; + text-align: right; } + +.score-container, .best-container { + position: relative; + display: inline-block; + background: #bbada0; + padding: 15px 25px; + font-size: 25px; + height: 25px; + line-height: 47px; + font-weight: bold; + border-radius: 3px; + color: white; + margin-top: 8px; + text-align: center; } + .score-container:after, .best-container:after { + position: absolute; + width: 100%; + top: 10px; + left: 0; + text-transform: uppercase; + font-size: 13px; + line-height: 13px; + text-align: center; + color: #eee4da; } + .score-container .score-addition, .best-container .score-addition { + position: absolute; + right: 30px; + color: red; + font-size: 25px; + line-height: 25px; + font-weight: bold; + color: rgba(119, 110, 101, 0.9); + z-index: 100; + -webkit-animation: move-up 600ms ease-in; + -moz-animation: move-up 600ms ease-in; + animation: move-up 600ms ease-in; + -webkit-animation-fill-mode: both; + -moz-animation-fill-mode: both; + animation-fill-mode: both; } + +.score-container:after { + content: "Score"; } + +.best-container:after { + content: "Best"; } + +p { + margin-top: 0; + margin-bottom: 10px; + line-height: 1.65; } + +a { + color: #776e65; + font-weight: bold; + text-decoration: underline; + cursor: pointer; } + +strong.important { + text-transform: uppercase; } + +hr { + border: none; + border-bottom: 1px solid #d8d4d0; + margin-top: 20px; + margin-bottom: 30px; } + +.container { + width: 500px; + margin: 0 auto; } + +@-webkit-keyframes fade-in { + 0% { + opacity: 0; } + + 100% { + opacity: 1; } } +@-moz-keyframes fade-in { + 0% { + opacity: 0; } + + 100% { + opacity: 1; } } +@keyframes fade-in { + 0% { + opacity: 0; } + + 100% { + opacity: 1; } } +.game-container { + margin-top: 40px; + position: relative; + padding: 15px; + cursor: default; + -webkit-touch-callout: none; + -ms-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -ms-touch-action: none; + touch-action: none; + background: #bbada0; + border-radius: 6px; + width: 500px; + height: 500px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + .game-container .game-message { + display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(238, 228, 218, 0.5); + z-index: 100; + text-align: center; + -webkit-animation: fade-in 800ms ease 1200ms; + -moz-animation: fade-in 800ms ease 1200ms; + animation: fade-in 800ms ease 1200ms; + -webkit-animation-fill-mode: both; + -moz-animation-fill-mode: both; + animation-fill-mode: both; } + .game-container .game-message p { + font-size: 60px; + font-weight: bold; + height: 60px; + line-height: 60px; + margin-top: 222px; } + .game-container .game-message .lower { + display: block; + margin-top: 59px; } + .game-container .game-message a { + display: inline-block; + background: #8f7a66; + border-radius: 3px; + padding: 0 20px; + text-decoration: none; + color: #f9f6f2; + height: 40px; + line-height: 42px; + margin-left: 9px; } + .game-container .game-message a.keep-playing-button { + display: none; } + .game-container .game-message.game-won { + background: rgba(237, 194, 46, 0.5); + color: #f9f6f2; } + .game-container .game-message.game-won a.keep-playing-button { + display: inline-block; } + .game-container .game-message.game-won, .game-container .game-message.game-over { + display: block; } + +.grid-container { + position: absolute; + z-index: 1; } + +.grid-row { + margin-bottom: 15px; } + .grid-row:last-child { + margin-bottom: 0; } + .grid-row:after { + content: ""; + display: block; + clear: both; } + +.grid-cell { + width: 106.25px; + height: 106.25px; + margin-right: 15px; + float: left; + border-radius: 3px; + background: rgba(238, 228, 218, 0.35); } + .grid-cell:last-child { + margin-right: 0; } + +.tile-container { + position: absolute; + z-index: 2; } + +.tile, .tile .tile-inner { + width: 107px; + height: 107px; + line-height: 107px; } +.tile.tile-position-1-1 { + -webkit-transform: translate(0px, 0px); + -moz-transform: translate(0px, 0px); + -ms-transform: translate(0px, 0px); + transform: translate(0px, 0px); } +.tile.tile-position-1-2 { + -webkit-transform: translate(0px, 121px); + -moz-transform: translate(0px, 121px); + -ms-transform: translate(0px, 121px); + transform: translate(0px, 121px); } +.tile.tile-position-1-3 { + -webkit-transform: translate(0px, 242px); + -moz-transform: translate(0px, 242px); + -ms-transform: translate(0px, 242px); + transform: translate(0px, 242px); } +.tile.tile-position-1-4 { + -webkit-transform: translate(0px, 363px); + -moz-transform: translate(0px, 363px); + -ms-transform: translate(0px, 363px); + transform: translate(0px, 363px); } +.tile.tile-position-2-1 { + -webkit-transform: translate(121px, 0px); + -moz-transform: translate(121px, 0px); + -ms-transform: translate(121px, 0px); + transform: translate(121px, 0px); } +.tile.tile-position-2-2 { + -webkit-transform: translate(121px, 121px); + -moz-transform: translate(121px, 121px); + -ms-transform: translate(121px, 121px); + transform: translate(121px, 121px); } +.tile.tile-position-2-3 { + -webkit-transform: translate(121px, 242px); + -moz-transform: translate(121px, 242px); + -ms-transform: translate(121px, 242px); + transform: translate(121px, 242px); } +.tile.tile-position-2-4 { + -webkit-transform: translate(121px, 363px); + -moz-transform: translate(121px, 363px); + -ms-transform: translate(121px, 363px); + transform: translate(121px, 363px); } +.tile.tile-position-3-1 { + -webkit-transform: translate(242px, 0px); + -moz-transform: translate(242px, 0px); + -ms-transform: translate(242px, 0px); + transform: translate(242px, 0px); } +.tile.tile-position-3-2 { + -webkit-transform: translate(242px, 121px); + -moz-transform: translate(242px, 121px); + -ms-transform: translate(242px, 121px); + transform: translate(242px, 121px); } +.tile.tile-position-3-3 { + -webkit-transform: translate(242px, 242px); + -moz-transform: translate(242px, 242px); + -ms-transform: translate(242px, 242px); + transform: translate(242px, 242px); } +.tile.tile-position-3-4 { + -webkit-transform: translate(242px, 363px); + -moz-transform: translate(242px, 363px); + -ms-transform: translate(242px, 363px); + transform: translate(242px, 363px); } +.tile.tile-position-4-1 { + -webkit-transform: translate(363px, 0px); + -moz-transform: translate(363px, 0px); + -ms-transform: translate(363px, 0px); + transform: translate(363px, 0px); } +.tile.tile-position-4-2 { + -webkit-transform: translate(363px, 121px); + -moz-transform: translate(363px, 121px); + -ms-transform: translate(363px, 121px); + transform: translate(363px, 121px); } +.tile.tile-position-4-3 { + -webkit-transform: translate(363px, 242px); + -moz-transform: translate(363px, 242px); + -ms-transform: translate(363px, 242px); + transform: translate(363px, 242px); } +.tile.tile-position-4-4 { + -webkit-transform: translate(363px, 363px); + -moz-transform: translate(363px, 363px); + -ms-transform: translate(363px, 363px); + transform: translate(363px, 363px); } + +.tile { + position: absolute; + -webkit-transition: 100ms ease-in-out; + -moz-transition: 100ms ease-in-out; + transition: 100ms ease-in-out; + -webkit-transition-property: -webkit-transform; + -moz-transition-property: -moz-transform; + transition-property: transform; } + .tile .tile-inner { + border-radius: 3px; + background: #eee4da; + text-align: center; + font-weight: bold; + z-index: 10; + font-size: 55px; } + .tile.tile-2 .tile-inner { + background: #eee4da; + box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } + .tile.tile-4 .tile-inner { + background: #ede0c8; + box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } + .tile.tile-8 .tile-inner { + color: #f9f6f2; + background: #f2b179; } + .tile.tile-16 .tile-inner { + color: #f9f6f2; + background: #f59563; } + .tile.tile-32 .tile-inner { + color: #f9f6f2; + background: #f67c5f; } + .tile.tile-64 .tile-inner { + color: #f9f6f2; + background: #f65e3b; } + .tile.tile-128 .tile-inner { + color: #f9f6f2; + background: #edcf72; + box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286); + font-size: 45px; } + @media screen and (max-width: 520px) { + .tile.tile-128 .tile-inner { + font-size: 25px; } } + .tile.tile-256 .tile-inner { + color: #f9f6f2; + background: #edcc61; + box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048); + font-size: 45px; } + @media screen and (max-width: 520px) { + .tile.tile-256 .tile-inner { + font-size: 25px; } } + .tile.tile-512 .tile-inner { + color: #f9f6f2; + background: #edc850; + box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381); + font-size: 45px; } + @media screen and (max-width: 520px) { + .tile.tile-512 .tile-inner { + font-size: 25px; } } + .tile.tile-1024 .tile-inner { + color: #f9f6f2; + background: #edc53f; + box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571); + font-size: 35px; } + @media screen and (max-width: 520px) { + .tile.tile-1024 .tile-inner { + font-size: 15px; } } + .tile.tile-2048 .tile-inner { + color: #f9f6f2; + background: #edc22e; + box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333); + font-size: 35px; } + @media screen and (max-width: 520px) { + .tile.tile-2048 .tile-inner { + font-size: 15px; } } + .tile.tile-super .tile-inner { + color: #f9f6f2; + background: #3c3a32; + font-size: 30px; } + @media screen and (max-width: 520px) { + .tile.tile-super .tile-inner { + font-size: 10px; } } + +@-webkit-keyframes appear { + 0% { + opacity: 0; + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); } } +@-moz-keyframes appear { + 0% { + opacity: 0; + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); } } +@keyframes appear { + 0% { + opacity: 0; + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); } } +.tile-new .tile-inner { + -webkit-animation: appear 200ms ease 100ms; + -moz-animation: appear 200ms ease 100ms; + animation: appear 200ms ease 100ms; + -webkit-animation-fill-mode: backwards; + -moz-animation-fill-mode: backwards; + animation-fill-mode: backwards; } + +@-webkit-keyframes pop { + 0% { + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); } + + 50% { + -webkit-transform: scale(1.2); + -moz-transform: scale(1.2); + -ms-transform: scale(1.2); + transform: scale(1.2); } + + 100% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); } } +@-moz-keyframes pop { + 0% { + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); } + + 50% { + -webkit-transform: scale(1.2); + -moz-transform: scale(1.2); + -ms-transform: scale(1.2); + transform: scale(1.2); } + + 100% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); } } +@keyframes pop { + 0% { + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); } + + 50% { + -webkit-transform: scale(1.2); + -moz-transform: scale(1.2); + -ms-transform: scale(1.2); + transform: scale(1.2); } + + 100% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); } } +.tile-merged .tile-inner { + z-index: 20; + -webkit-animation: pop 200ms ease 100ms; + -moz-animation: pop 200ms ease 100ms; + animation: pop 200ms ease 100ms; + -webkit-animation-fill-mode: backwards; + -moz-animation-fill-mode: backwards; + animation-fill-mode: backwards; } + +.above-game:after { + content: ""; + display: block; + clear: both; } + +.game-intro { + float: left; + line-height: 42px; + margin-bottom: 0; } + +.restart-button { + display: inline-block; + background: #8f7a66; + border-radius: 3px; + padding: 0 20px; + text-decoration: none; + color: #f9f6f2; + height: 40px; + line-height: 42px; + display: block; + text-align: center; + float: right; } + +.exit-button { + display: inline-block; + background: red; + border-radius: 3px; + padding: 0 20px; + text-decoration: none; + color: #f9f6f2; + height: 40px; + line-height: 42px; + display: block; + text-align: center; + float: right; } + + +.game-explanation { + margin-top: 50px; } + +@media screen and (max-width: 520px) { + html, body { + font-size: 15px; } + + body { + margin: 20px 0; + padding: 0 20px; } + + h1.title { + font-size: 27px; + margin-top: 15px; } + + .container { + width: 280px; + margin: 0 auto; } + + .score-container, .best-container { + margin-top: 0; + padding: 15px 10px; + min-width: 40px; } + + .heading { + margin-bottom: 10px; } + + .game-intro { + width: 55%; + display: block; + box-sizing: border-box; + line-height: 1.65; } + + .restart-button { + width: 42%; + padding: 0; + display: block; + box-sizing: border-box; + margin-top: 2px; } + + .exit-button { + width: 42%; + padding: 2px; + display: block; + box-sizing: border-box; + margin-top: 2px; } + + .game-container { + margin-top: 17px; + position: relative; + padding: 10px; + cursor: default; + -webkit-touch-callout: none; + -ms-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -ms-touch-action: none; + touch-action: none; + background: #bbada0; + border-radius: 6px; + width: 280px; + height: 280px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + .game-container .game-message { + display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(238, 228, 218, 0.5); + z-index: 100; + text-align: center; + -webkit-animation: fade-in 800ms ease 1200ms; + -moz-animation: fade-in 800ms ease 1200ms; + animation: fade-in 800ms ease 1200ms; + -webkit-animation-fill-mode: both; + -moz-animation-fill-mode: both; + animation-fill-mode: both; } + .game-container .game-message p { + font-size: 60px; + font-weight: bold; + height: 60px; + line-height: 60px; + margin-top: 222px; } + .game-container .game-message .lower { + display: block; + margin-top: 59px; } + .game-container .game-message a { + display: inline-block; + background: #8f7a66; + border-radius: 3px; + padding: 0 20px; + text-decoration: none; + color: #f9f6f2; + height: 40px; + line-height: 42px; + margin-left: 9px; } + .game-container .game-message a.keep-playing-button { + display: none; } + .game-container .game-message.game-won { + background: rgba(237, 194, 46, 0.5); + color: #f9f6f2; } + .game-container .game-message.game-won a.keep-playing-button { + display: inline-block; } + .game-container .game-message.game-won, .game-container .game-message.game-over { + display: block; } + + .grid-container { + position: absolute; + z-index: 1; } + + .grid-row { + margin-bottom: 10px; } + .grid-row:last-child { + margin-bottom: 0; } + .grid-row:after { + content: ""; + display: block; + clear: both; } + + .grid-cell { + width: 57.5px; + height: 57.5px; + margin-right: 10px; + float: left; + border-radius: 3px; + background: rgba(238, 228, 218, 0.35); } + .grid-cell:last-child { + margin-right: 0; } + + .tile-container { + position: absolute; + z-index: 2; } + + .tile, .tile .tile-inner { + width: 58px; + height: 58px; + line-height: 58px; } + .tile.tile-position-1-1 { + -webkit-transform: translate(0px, 0px); + -moz-transform: translate(0px, 0px); + -ms-transform: translate(0px, 0px); + transform: translate(0px, 0px); } + .tile.tile-position-1-2 { + -webkit-transform: translate(0px, 67px); + -moz-transform: translate(0px, 67px); + -ms-transform: translate(0px, 67px); + transform: translate(0px, 67px); } + .tile.tile-position-1-3 { + -webkit-transform: translate(0px, 135px); + -moz-transform: translate(0px, 135px); + -ms-transform: translate(0px, 135px); + transform: translate(0px, 135px); } + .tile.tile-position-1-4 { + -webkit-transform: translate(0px, 202px); + -moz-transform: translate(0px, 202px); + -ms-transform: translate(0px, 202px); + transform: translate(0px, 202px); } + .tile.tile-position-2-1 { + -webkit-transform: translate(67px, 0px); + -moz-transform: translate(67px, 0px); + -ms-transform: translate(67px, 0px); + transform: translate(67px, 0px); } + .tile.tile-position-2-2 { + -webkit-transform: translate(67px, 67px); + -moz-transform: translate(67px, 67px); + -ms-transform: translate(67px, 67px); + transform: translate(67px, 67px); } + .tile.tile-position-2-3 { + -webkit-transform: translate(67px, 135px); + -moz-transform: translate(67px, 135px); + -ms-transform: translate(67px, 135px); + transform: translate(67px, 135px); } + .tile.tile-position-2-4 { + -webkit-transform: translate(67px, 202px); + -moz-transform: translate(67px, 202px); + -ms-transform: translate(67px, 202px); + transform: translate(67px, 202px); } + .tile.tile-position-3-1 { + -webkit-transform: translate(135px, 0px); + -moz-transform: translate(135px, 0px); + -ms-transform: translate(135px, 0px); + transform: translate(135px, 0px); } + .tile.tile-position-3-2 { + -webkit-transform: translate(135px, 67px); + -moz-transform: translate(135px, 67px); + -ms-transform: translate(135px, 67px); + transform: translate(135px, 67px); } + .tile.tile-position-3-3 { + -webkit-transform: translate(135px, 135px); + -moz-transform: translate(135px, 135px); + -ms-transform: translate(135px, 135px); + transform: translate(135px, 135px); } + .tile.tile-position-3-4 { + -webkit-transform: translate(135px, 202px); + -moz-transform: translate(135px, 202px); + -ms-transform: translate(135px, 202px); + transform: translate(135px, 202px); } + .tile.tile-position-4-1 { + -webkit-transform: translate(202px, 0px); + -moz-transform: translate(202px, 0px); + -ms-transform: translate(202px, 0px); + transform: translate(202px, 0px); } + .tile.tile-position-4-2 { + -webkit-transform: translate(202px, 67px); + -moz-transform: translate(202px, 67px); + -ms-transform: translate(202px, 67px); + transform: translate(202px, 67px); } + .tile.tile-position-4-3 { + -webkit-transform: translate(202px, 135px); + -moz-transform: translate(202px, 135px); + -ms-transform: translate(202px, 135px); + transform: translate(202px, 135px); } + .tile.tile-position-4-4 { + -webkit-transform: translate(202px, 202px); + -moz-transform: translate(202px, 202px); + -ms-transform: translate(202px, 202px); + transform: translate(202px, 202px); } + + .tile .tile-inner { + font-size: 35px; } + + .game-message p { + font-size: 30px !important; + height: 30px !important; + line-height: 30px !important; + margin-top: 90px !important; } + .game-message .lower { + margin-top: 30px !important; } } diff --git a/static/2048/style/main.scss b/static/2048/style/main.scss new file mode 100644 index 0000000..b0ec8da --- /dev/null +++ b/static/2048/style/main.scss @@ -0,0 +1,549 @@ +@import "helpers"; +@import "fonts/clear-sans.css"; + +$field-width: 500px; +$grid-spacing: 15px; +$grid-row-cells: 4; +$tile-size: ($field-width - $grid-spacing * ($grid-row-cells + 1)) / $grid-row-cells; +$tile-border-radius: 3px; + +$mobile-threshold: $field-width + 20px; + +$text-color: #776E65; +$bright-text-color: #f9f6f2; + +$tile-color: #eee4da; +$tile-gold-color: #edc22e; +$tile-gold-glow-color: lighten($tile-gold-color, 15%); + +$game-container-margin-top: 40px; +$game-container-background: #bbada0; + +$transition-speed: 100ms; + +html, body { + margin: 0; + padding: 0; + + background: #faf8ef; + color: $text-color; + font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif; + font-size: 18px; +} + +body { + margin: 80px 0; +} + +.heading { + @include clearfix; +} + +h1.title { + font-size: 80px; + font-weight: bold; + margin: 0; + display: block; + float: left; +} + +@include keyframes(move-up) { + 0% { + top: 25px; + opacity: 1; + } + + 100% { + top: -50px; + opacity: 0; + } +} + +.scores-container { + float: right; + text-align: right; +} + +.score-container, .best-container { + $height: 25px; + + position: relative; + display: inline-block; + background: $game-container-background; + padding: 15px 25px; + font-size: $height; + height: $height; + line-height: $height + 22px; + font-weight: bold; + border-radius: 3px; + color: white; + margin-top: 8px; + text-align: center; + + &:after { + position: absolute; + width: 100%; + top: 10px; + left: 0; + text-transform: uppercase; + font-size: 13px; + line-height: 13px; + text-align: center; + color: $tile-color; + } + + .score-addition { + position: absolute; + right: 30px; + color: red; + font-size: $height; + line-height: $height; + font-weight: bold; + color: rgba($text-color, .9); + z-index: 100; + @include animation(move-up 600ms ease-in); + @include animation-fill-mode(both); + } +} + +.score-container:after { + content: "Score"; +} + +.best-container:after { + content: "Best"; +} + +p { + margin-top: 0; + margin-bottom: 10px; + line-height: 1.65; +} + +a { + color: $text-color; + font-weight: bold; + text-decoration: underline; + cursor: pointer; +} + +strong { + &.important { + text-transform: uppercase; + } +} + +hr { + border: none; + border-bottom: 1px solid lighten($text-color, 40%); + margin-top: 20px; + margin-bottom: 30px; +} + +.container { + width: $field-width; + margin: 0 auto; +} + +@include keyframes(fade-in) { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +// Styles for buttons +@mixin button { + display: inline-block; + background: darken($game-container-background, 20%); + border-radius: 3px; + padding: 0 20px; + text-decoration: none; + color: $bright-text-color; + height: 40px; + line-height: 42px; +} + +// Game field mixin used to render CSS at different width +@mixin game-field { + .game-container { + margin-top: $game-container-margin-top; + position: relative; + padding: $grid-spacing; + + cursor: default; + -webkit-touch-callout: none; + -ms-touch-callout: none; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + + -ms-touch-action: none; + touch-action: none; + + background: $game-container-background; + border-radius: $tile-border-radius * 2; + width: $field-width; + height: $field-width; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + .game-message { + display: none; + + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba($tile-color, .5); + z-index: 100; + + text-align: center; + + p { + font-size: 60px; + font-weight: bold; + height: 60px; + line-height: 60px; + margin-top: 222px; + // height: $field-width; + // line-height: $field-width; + } + + .lower { + display: block; + margin-top: 59px; + } + + a { + @include button; + margin-left: 9px; + // margin-top: 59px; + + &.keep-playing-button { + display: none; + } + } + + @include animation(fade-in 800ms ease $transition-speed * 12); + @include animation-fill-mode(both); + + &.game-won { + background: rgba($tile-gold-color, .5); + color: $bright-text-color; + + a.keep-playing-button { + display: inline-block; + } + } + + &.game-won, &.game-over { + display: block; + } + } + } + + .grid-container { + position: absolute; + z-index: 1; + } + + .grid-row { + margin-bottom: $grid-spacing; + + &:last-child { + margin-bottom: 0; + } + + &:after { + content: ""; + display: block; + clear: both; + } + } + + .grid-cell { + width: $tile-size; + height: $tile-size; + margin-right: $grid-spacing; + float: left; + + border-radius: $tile-border-radius; + + background: rgba($tile-color, .35); + + &:last-child { + margin-right: 0; + } + } + + .tile-container { + position: absolute; + z-index: 2; + } + + .tile { + &, .tile-inner { + width: ceil($tile-size); + height: ceil($tile-size); + line-height: ceil($tile-size); + } + + // Build position classes + @for $x from 1 through $grid-row-cells { + @for $y from 1 through $grid-row-cells { + &.tile-position-#{$x}-#{$y} { + $xPos: floor(($tile-size + $grid-spacing) * ($x - 1)); + $yPos: floor(($tile-size + $grid-spacing) * ($y - 1)); + @include transform(translate($xPos, $yPos)); + } + } + } + } +} + +// End of game-field mixin +@include game-field; + +.tile { + position: absolute; // Makes transforms relative to the top-left corner + + .tile-inner { + border-radius: $tile-border-radius; + + background: $tile-color; + text-align: center; + font-weight: bold; + z-index: 10; + + font-size: 55px; + } + + // Movement transition + @include transition($transition-speed ease-in-out); + -webkit-transition-property: -webkit-transform; + -moz-transition-property: -moz-transform; + transition-property: transform; + + $base: 2; + $exponent: 1; + $limit: 11; + + // Colors for all 11 states, false = no special color + $special-colors: false false, // 2 + false false, // 4 + #f78e48 true, // 8 + #fc5e2e true, // 16 + #ff3333 true, // 32 + #ff0000 true, // 64 + false true, // 128 + false true, // 256 + false true, // 512 + false true, // 1024 + false true; // 2048 + + // Build tile colors + @while $exponent <= $limit { + $power: pow($base, $exponent); + + &.tile-#{$power} .tile-inner { + // Calculate base background color + $gold-percent: ($exponent - 1) / ($limit - 1) * 100; + $mixed-background: mix($tile-gold-color, $tile-color, $gold-percent); + + $nth-color: nth($special-colors, $exponent); + + $special-background: nth($nth-color, 1); + $bright-color: nth($nth-color, 2); + + @if $special-background { + $mixed-background: mix($special-background, $mixed-background, 55%); + } + + @if $bright-color { + color: $bright-text-color; + } + + // Set background + background: $mixed-background; + + // Add glow + $glow-opacity: max($exponent - 4, 0) / ($limit - 4); + + @if not $special-background { + box-shadow: 0 0 30px 10px rgba($tile-gold-glow-color, $glow-opacity / 1.8), + inset 0 0 0 1px rgba(white, $glow-opacity / 3); + } + + // Adjust font size for bigger numbers + @if $power >= 100 and $power < 1000 { + font-size: 45px; + + // Media queries placed here to avoid carrying over the rest of the logic + @include smaller($mobile-threshold) { + font-size: 25px; + } + } @else if $power >= 1000 { + font-size: 35px; + + @include smaller($mobile-threshold) { + font-size: 15px; + } + } + } + + $exponent: $exponent + 1; + } + + // Super tiles (above 2048) + &.tile-super .tile-inner { + color: $bright-text-color; + background: mix(#333, $tile-gold-color, 95%); + + font-size: 30px; + + @include smaller($mobile-threshold) { + font-size: 10px; + } + } +} + +@include keyframes(appear) { + 0% { + opacity: 0; + @include transform(scale(0)); + } + + 100% { + opacity: 1; + @include transform(scale(1)); + } +} + +.tile-new .tile-inner { + @include animation(appear 200ms ease $transition-speed); + @include animation-fill-mode(backwards); +} + +@include keyframes(pop) { + 0% { + @include transform(scale(0)); + } + + 50% { + @include transform(scale(1.2)); + } + + 100% { + @include transform(scale(1)); + } +} + +.tile-merged .tile-inner { + z-index: 20; + @include animation(pop 200ms ease $transition-speed); + @include animation-fill-mode(backwards); +} + +.above-game { + @include clearfix; +} + +.game-intro { + float: left; + line-height: 42px; + margin-bottom: 0; +} + +.restart-button { + @include button; + display: block; + text-align: center; + float: right; +} + +.game-explanation { + margin-top: 50px; +} + +@include smaller($mobile-threshold) { + // Redefine variables for smaller screens + $field-width: 280px; + $grid-spacing: 10px; + $grid-row-cells: 4; + $tile-size: ($field-width - $grid-spacing * ($grid-row-cells + 1)) / $grid-row-cells; + $tile-border-radius: 3px; + $game-container-margin-top: 17px; + + html, body { + font-size: 15px; + } + + body { + margin: 20px 0; + padding: 0 20px; + } + + h1.title { + font-size: 27px; + margin-top: 15px; + } + + .container { + width: $field-width; + margin: 0 auto; + } + + .score-container, .best-container { + margin-top: 0; + padding: 15px 10px; + min-width: 40px; + } + + .heading { + margin-bottom: 10px; + } + + // Show intro and restart button side by side + .game-intro { + width: 55%; + display: block; + box-sizing: border-box; + line-height: 1.65; + } + + .restart-button { + width: 42%; + padding: 0; + display: block; + box-sizing: border-box; + margin-top: 2px; + } + + // Render the game field at the right width + @include game-field; + + // Rest of the font-size adjustments in the tile class + .tile .tile-inner { + font-size: 35px; + } + + .game-message { + p { + font-size: 30px !important; + height: 30px !important; + line-height: 30px !important; + margin-top: 90px !important; + } + + .lower { + margin-top: 30px !important; + } + } +} diff --git a/static/Space-Impact-Web/README.md b/static/Space-Impact-Web/README.md new file mode 100644 index 0000000..1b0e8da --- /dev/null +++ b/static/Space-Impact-Web/README.md @@ -0,0 +1,41 @@ +# Space-Impact-Web +## Introduction +This project is the web version of Nokia 3310's classic game "Space Impact". A 2D shooter game, in which the player flies a spaceship and destroys incoming swarms of enemies spanning across 8 levels. Each level at the end has its own level boss. + +### [Live Demo](https://sidsinr.github.io/Space-Impact-Web) + +The objective of the game is to survive and beat the final boss. + +The player has unlimited ammo for the normal bullets. The player also gets one of the three types of different special attacks namely; homing missiles, laser gun and laser wall. These special attacks are limited and can only be replenished from the powerup picked up during a level. The powerup can also provide an extra life. + +All the necessary information during the game is available at the top; which include the lives remaining, the type of special attack and the number of attacks remaining, and the score. +There is also an option to pause the game during the gameplay. + +The game is responsive and can be played on a mobile browser. I've added onscreen buttons that can be toggled on/off. + + + + + +## How to run +Being a complete front-end application, the game can be run on any web broswer. + +Or, download the files and run the **index.html** file in any of the browser. Just make sure the file paths remains the same. + + +## Software Tools Used +- Adobe Photoshop, to create all the game assets. +- HTML, for laying the foundation of the application. +- CSS, for styling the components of the html document and making it responsive. +- Vanilla JS, for the entire game build. + +### Acknowledgement +[OpenGameArt](https://opengameart.org) for the onscreen buttons. + +## Future Plans +1. Addition of sound effects. Currently the game has no audio. +2. A leaderboard for storing the high-scores. +3. A more modern looking colorful art for the game, but preserving the originality of the game. Adding the parallax scrolling to the background for creating an effect of depth. + +## Disclaimer +Space Impact is a trademark of Nokia Inc. All rights reserved with Nokia Inc. This personal project is only for the educational purpose of learning Javascript and HTML Canvas; and it is no way intended for any commercial use or distribution. diff --git a/static/Space-Impact-Web/game.js b/static/Space-Impact-Web/game.js new file mode 100644 index 0000000..c1c1336 --- /dev/null +++ b/static/Space-Impact-Web/game.js @@ -0,0 +1,2960 @@ +const bgCanvas = document.getElementById("canvas-bg"); +const mainCanvas = document.getElementById("canvas-main"); +const bgCtx = bgCanvas.getContext("2d"); +const mainCtx = mainCanvas.getContext("2d"); + +mainCanvas.width = 840; +mainCanvas.height = 480; +bgCanvas.width = 840; +bgCanvas.height = 480; + +const buttonUp = document.getElementById("button-up"); +const buttonDown = document.getElementById("button-down"); +const buttonLeft = document.getElementById("button-left"); +const buttonRight = document.getElementById("button-right"); +const buttonFire = document.getElementById("button-fire"); +const buttonX = document.getElementById("button-x"); + +const mainSprites = new Image(); +mainSprites.src = "./static/Space-Impact-Web/img/gameSprites.png"; +const bossSprites = new Image(); +bossSprites.src = "./static/Space-Impact-Web/img/bossSprites.png"; +const bgSprites1 = new Image(); +bgSprites1.src = "./static/Space-Impact-Web/img/bgSprites1.png"; +const bgSprites2 = new Image(); +bgSprites2.src = "./static/Space-Impact-Web/img/bgSprites2.png"; +//for all images, sprite dimensions and game dimensions of the character is same + +const bgArray2 = [0, 1, 2, 1, 1, 2, 2, 1, 2, 1, 1, 1, 2, 0, 1, 1, 2]; +const bgArray3 = [0, 1, 2, 2, 1, 0, 2, 0, 1, 2, 1, 0, 2, 0, 2, 1, 0, 1, 2, 0, 1]; +const bgArray4 = [0, 1, 2, 2, 1, 0, 2, 0, 1, 2, 1, 0, 2, 0, 2, 1, 0, 1, 2, 2, 1, 0, 2, 0, 1, 0]; +const bgArray5 = [0, 1, 2, 2, 0, 2, 1, 1, 0, 1, 0, 0, 1, 1, 2, 1, 0, 1, 2, 2, 0, 2, 1, 1, 0, 1, 0, 0]; +const bgArray6 = [2, 2, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 2, 1, 2, 2, 1, 1, 0, 0, 1, 1]; +const bgArray7 = [0, 0, 0]; +const bgArray8 = [0]; + +// game helper variables +const keys = { + ArrowLeft: { + pressed: false + }, + ArrowRight: { + pressed: false + }, + ArrowUp: { + pressed: false + }, + ArrowDown: { + pressed: false + }, + space: { + pressed: false + }, + x: { + pressed: false + } +} +let gameOver = false; +let gamePause = false; +let isLevelDark = true; +let specialAtttack = "missile"; //3 spAtk available, "missile", "laser", "wall"; +let specialCount = 3; +let lives = 4; +const maxLives = 7; +let playerScore = 0; + +class InputHandler { + constructor(level) { + this.level = level; + // keyboard events + window.addEventListener("keydown", (e) => { + e.preventDefault(); //prevents the page scrolling + if (gameOver || !level.active || gamePause) return; + switch (e.key) { + case "ArrowLeft": + keys.ArrowLeft.pressed = true; + break; + case "ArrowRight": + keys.ArrowRight.pressed = true; + break; + case "ArrowUp": + keys.ArrowUp.pressed = true; + break; + case "ArrowDown": + keys.ArrowDown.pressed = true; + break; + case " ": + if (!keys.space.pressed) { + this.level.playerProjectiles.push(new Projectile(true, this.level.player)); + keys.space.pressed = true; + } + break; + case "X": + case "x": + if (!keys.x.pressed) { + if (specialCount > 0) { + switch (specialAtttack) { + case "missile": this.level.playerSpecial.push(new Missile(this.level)); + break; + case "laser": this.level.playerSpecial.push(new Laser(this.level)); + break; + case "wall": this.level.playerSpecial.push(new Wall(this.level)); + break; + } + specialCount--; + } + keys.x.pressed = true; + } + break; + } + }); + window.addEventListener("keyup", (e) => { + switch (e.key) { + case "ArrowLeft": + keys.ArrowLeft.pressed = false; + break; + case "ArrowRight": + keys.ArrowRight.pressed = false; + break; + case "ArrowUp": + keys.ArrowUp.pressed = false; + break; + case "ArrowDown": + keys.ArrowDown.pressed = false; + break; + case " ": + keys.space.pressed = false; + break; + case "X": + case "x": + keys.x.pressed = false; + break; + } + }); + + // onscreen-buttons events + buttonLeft.addEventListener("pointerdown", ()=>{ + if (gameOver || !level.active || gamePause) return; + keys.ArrowLeft.pressed = true; + }) + buttonLeft.addEventListener("pointerup", ()=>{ + keys.ArrowLeft.pressed = false; + }) + buttonLeft.addEventListener("pointerout", ()=>{ + keys.ArrowLeft.pressed = false; + }) + + buttonRight.addEventListener("pointerdown", ()=>{ + if (gameOver || !level.active || gamePause) return; + keys.ArrowRight.pressed = true; + }) + buttonRight.addEventListener("pointerup", ()=>{ + keys.ArrowRight.pressed = false; + }) + buttonRight.addEventListener("pointerout", ()=>{ + keys.ArrowRight.pressed = false; + }) + + buttonUp.addEventListener("pointerdown", ()=>{ + if (gameOver || !level.active || gamePause) return; + keys.ArrowUp.pressed = true; + }) + buttonUp.addEventListener("pointerup", ()=>{ + keys.ArrowUp.pressed = false; + }) + buttonUp.addEventListener("pointerout", ()=>{ + keys.ArrowUp.pressed = false; + }) + + buttonDown.addEventListener("pointerdown", ()=>{ + if (gameOver || !level.active || gamePause) return; + keys.ArrowDown.pressed = true; + }) + buttonDown.addEventListener("pointerup", ()=>{ + keys.ArrowDown.pressed = false; + }) + buttonDown.addEventListener("pointerout", ()=>{ + keys.ArrowDown.pressed = false; + }) + // firing button events + buttonFire.addEventListener("pointerdown", ()=>{ + if (gameOver || !level.active || gamePause) return; + if (!keys.space.pressed) { + this.level.playerProjectiles.push(new Projectile(true, this.level.player)); + keys.space.pressed = true; + } + }) + buttonFire.addEventListener("pointerup", ()=>{ + keys.space.pressed = false; + }) + buttonFire.addEventListener("pointerout", ()=>{ + keys.space.pressed = false; + }) + + buttonX.addEventListener("pointerdown", ()=>{ + if (gameOver || !level.active || gamePause) return; + if (!keys.x.pressed) { + if (specialCount > 0) { + switch (specialAtttack) { + case "missile": this.level.playerSpecial.push(new Missile(this.level)); + break; + case "laser": this.level.playerSpecial.push(new Laser(this.level)); + break; + case "wall": this.level.playerSpecial.push(new Wall(this.level)); + break; + } + specialCount--; + } + keys.x.pressed = true; + } + }) + buttonX.addEventListener("pointerup", ()=>{ + keys.x.pressed = false; + }) + buttonX.addEventListener("pointerout", ()=>{ + keys.x.pressed = false; + }) + } +} +// Hitbox class for player, background and boss +class Hitbox { + constructor(x, y, width, height, offsetX, offsetY, immune) { + this.offsetX = offsetX; + this.offsetY = offsetY; + this.x = x + this.offsetX; + this.y = y + this.offsetY; + this.width = width; + this.height = height; + this.immune = immune; + } + update(x, y) { + this.x = x + this.offsetX; + this.y = y + this.offsetY; + //this.draw(); //uncomment for debugging hitbox + } + draw() { + bgCtx.fillStyle = "red"; + if (this.immune) bgCtx.fillStyle = "blue"; + bgCtx.fillRect(this.x, this.y, this.width, this.height); + } +} +class Shield { + constructor(player) { + this.player = player; + this.image = mainSprites + this.x = this.player.x - 20; + this.y = this.player.y - 20; + this.width = 130; + this.height = 110; + this.spriteSize = 150; + this.maxFrames = 2; + this.frameXStart = 2; + this.frameY = 0; + if (isLevelDark) this.frameXStart = 4; + this.frameX = this.frameXStart; + this.spriteTimer = 0; + this.spriteInterval = 150; + } + update(deltaTime) { + this.x = this.player.x - 20; + this.y = this.player.y - 20; + + //sprite animation + if (this.spriteTimer > this.spriteInterval) { + this.frameX++; + if (this.frameX - this.frameXStart >= this.maxFrames) this.frameX = this.frameXStart; + this.spriteTimer = 0; + } else this.spriteTimer += deltaTime; + + this.draw(); + } + draw() { + mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height); + } +} +class Player { + constructor(level) { + this.level = level; + this.image = mainSprites; + this.width = 100; // sprite width and game width is same + this.height = 70; + this.x = 20; + this.y = 220; + this.speedX = 2.5; + this.speedY = 3; + this.frameX = 0; //starting x coordinate of the box in sprite + this.frameY = 0; //starting y coordinate of the box in sprite + this.spriteSize = 150; // size of each box(uniform all over sprite) + if (isLevelDark) this.frameX = 1; + this.hit = false; // is player hit + this.delete = false; + this.hitbox = new Hitbox(this.x, this.y, this.width - 10, this.height - 20, 0, 10, false); + this.shield = new Shield(this); + this.shieldOn = true; + this.shieldInterval = 4000; + this.shieldTimer = 0; + } + update(deltaTime) { + // shield + if (this.shieldTimer <= this.shieldInterval) { + this.shieldTimer += deltaTime; + this.shield.update(deltaTime); + } + else this.shieldOn = false; + + // update hitbox + this.hitbox.update(this.x, this.y); + + // x-axis limits + if (keys.ArrowLeft.pressed && this.x >= 0) this.x -= this.speedX; + if (keys.ArrowRight.pressed && this.x <= mainCanvas.width - this.width) this.x += this.speedX; + else this.x += 0; + // y-axis limits + if (keys.ArrowUp.pressed && this.y >= 50) this.y -= this.speedY; + if (keys.ArrowDown.pressed && this.y <= mainCanvas.height - this.height) this.y += this.speedY; + else this.y += 0; + + this.draw(); + } + draw() { + mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height); + } +} +class Projectile { + constructor(isPlayer, object) { + this.object = object; + this.isPlayer = isPlayer; //boolean to differentiate enemy and player + this.width = 20; + this.height = 10; + if (isPlayer) { + this.x = this.object.x + this.object.width; + this.y = this.object.y + this.object.height * 0.5 - this.height * 0.5; + this.speed = 3; + } else { + this.x = this.object.x; + this.y = this.object.y + this.object.height * 0.5 - this.height * 0.5; + this.speed = -object.speedX - 1; + } + this.delete = false; + } + update() { + this.x += this.speed; + if (this.x >= mainCanvas.width || this.x < 0) this.delete = true; + this.draw(); + } + draw() { + mainCtx.fillStyle = "#282828"; + if (isLevelDark) mainCtx.fillStyle = "#aad69c"; + mainCtx.fillRect(this.x, this.y, this.width, this.height); + } +} +// Special Power Ups +class PowerUp { + constructor(x, y, speedX, speedY, movement, range, xbreak) { + this.image = mainSprites; + this.isPowerUp = true; + this.width = 80; + this.height = 70; + this.x = x; + this.y = y; + this.spriteSize = 150; + this.maxFrames = 2; + this.frameXStart = 4; + this.frameY = 6; + if (isLevelDark) this.frameY = 7; + this.frameX = this.frameXStart; + this.delete = false; // powerUp marked for deletion; + this.speedX = speedX; + this.speedY = speedY; + this.movement = movement; + this.range = range; + this.xbreak = xbreak; + this.angle = 0; + this.spriteTimer = 0; //sprite animation helper + this.spriteInterval = 300; + this.hit = false; // flag for single collision else multiple power added + + //randomizing the power up awarded to the player + this.randomize = Math.random(); + if (this.randomize < 0.25) this.powerup = "life"; + else if (this.randomize < 0.5) this.powerup = "missile"; + else if (this.randomize < 0.75) this.powerup = "laser"; + else this.powerup = "wall"; + } + update(deltaTime) { + //sprite animation + if (this.spriteTimer > this.spriteInterval) { + this.frameX++; + if (this.frameX - this.frameXStart >= this.maxFrames) this.frameX = this.frameXStart; + this.spriteTimer = 0; + } else this.spriteTimer += deltaTime; + + //movement + switch (this.movement) { + case "wave": + this.x -= this.speedX; + this.y = this.xbreak + this.range * Math.sin(this.angle); + this.angle += this.speedY; + break; + + case "linear": + default: + this.x -= this.speedX; + } + + // garbage collection + if (this.x + this.width < 0) this.delete = true; + + this.draw(); + } + draw() { + mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height); + } +} +class Missile { + constructor(level) { + this.image = mainSprites; + this.spriteX = 900; + this.spriteY = 0; + if (isLevelDark) this.spriteY = 150; + this.level = level; + this.width = 50; + this.height = 30; + this.x = this.level.player.x + this.level.player.width; + this.y = this.level.player.y + this.level.player.height * 0.5 - this.height * 0.5; + this.targetSet = false; + this.target = null; + this.speedX = 3; + this.speedY = 0; + this.delete = false; + this.damage = 50; + this.specialType = "missile"; + this.hit = false; + } + update() { + // setting the target + if (!this.targetSet) { + this.level.enemies.forEach(enemy => { + if (enemy.x > this.x + 40 && !this.targetSet) { + this.target = enemy; + this.targetSet = true; + this.speedX = 4; + } + }); + } + + if (this.target != null) { + // unsetting target + if (this.target.delete || this.target.x + this.target.width < this.x) { + this.targetSet = false; + this.speedX = 3; + } + + // following target + if (this.y >= this.target.y + this.target.height * 0.5) this.speedY = -5; + else if (this.y + this.height * 0.5 <= this.target.y) this.speedY = 5; + else this.speedY = 0; + } + + this.x += this.speedX; + this.y += this.speedY; + + if (this.x >= 840) this.delete = true; + + this.draw(); + } + draw() { + mainCtx.drawImage(this.image, this.spriteX, this.spriteY, this.width, this.height, this.x, this.y, this.width, this.height); + } +} +class Laser { + constructor(level) { + this.level = level; + this.x = this.level.player.x + this.level.player.width; + this.y = this.level.player.y + this.level.player.height * 0.5 - 5; + this.width = 600; + this.height = 10; + this.duration = 500; + this.timer = 0; + this.delete = false; + this.hit = false; //helper for score deduction for boss + this.damage = 100; + this.specialType = "laser"; + } + update(deltaTime) { + if (this.timer < this.duration) { + this.draw(); + this.timer += deltaTime; + } + else this.delete = true; + } + draw() { + if (isLevelDark) mainCtx.fillStyle = "#aad69c"; + else mainCtx.fillStyle = "#282828"; + + mainCtx.fillRect(this.x, this.y - 10, 10, 10); + mainCtx.fillRect(this.x, this.y + 10, 10, 10); + mainCtx.fillRect(this.x + 10, this.y, this.width, this.height); + } +} +class Wall { + constructor(level) { + this.level = level; + this.x = this.level.player.x + this.level.player.width; + this.y = 50; + this.width = 10; + this.height = 430; + this.speed = 4; + this.hit = false; + this.delete = false; + this.damage = 100; + this.specialType = "wall"; + } + update() { + this.x += this.speed; + if (this.x > 840) this.delete = true; + this.draw(); + } + draw() { + if (isLevelDark) mainCtx.fillStyle = "#aad69c"; + else mainCtx.fillStyle = "#282828"; + + mainCtx.fillRect(this.x, this.y, this.width, this.height); + } +} +class EnemyMissile { + constructor(boss) { + this.boss = boss; + this.x = this.boss.x; + this.y = this.boss.y + this.boss.height * 0.5; + this.width = 50; + this.height = 30; + this.target = currentLevel.player; + this.speedX = 3; + this.speedY = 0; + this.delete = false; + } + update() { + // seeking missile targeting the player + if (this.y >= this.target.y + this.target.height * 0.5) this.speedY = -3; + else if (this.y + this.height * 0.5 <= this.target.y) this.speedY = 3; + else this.speedY = 0; + + this.x -= this.speedX; + this.y += this.speedY; + + if (this.x + this.width < 0) this.delete = true; + + this.draw(); + } + draw() { + mainCtx.fillStyle = "#282828"; + mainCtx.fillRect(this.x, this.y + 10, 10, 10); + mainCtx.fillRect(this.x + 10, this.y, 20, 30); + mainCtx.fillRect(this.x + 30, this.y + 10, 10, 10); + mainCtx.fillRect(this.x + 40, this.y, 10, 30); + } +} +//Explosion effect +class Explosion { + constructor(x, y) { + this.image = mainSprites; + this.x = x; + this.y = y; + this.width = 70; + this.height = 70; + this.spriteSize = 150; + this.maxFrames = 5; + this.frames = 1; + this.staggeredFrames = 5; + this.timer = 0; + this.frameX = 0; + this.frameY = 8; + if (isLevelDark) this.frameY = 9; + this.delete = false; + } + update() { + if (this.frames % this.staggeredFrames === 0) { + this.frameX++; + this.frames = 1; + } + else this.frames++; + + if (this.frameX >= this.maxFrames) this.delete = true; + + this.draw(); + } + draw() { + mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height); + } +} + +//enemy classes +class Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + this.image = mainSprites; + this.x = x; + this.y = y; + this.spriteSize = 150; + this.hp = hp; + this.score = this.hp; + this.shoots = shoots; // boolean; whether the enemy shoots back + this.speedX = speedX; // enemy speed x direction + this.speedY = speedY; // enemy speed y direction || angle speed for sin + this.movement = movement; // string: type of movement + this.range = range; // y axis range of enemy movement || 2nd break point on x axis for z movement + this.xbreak = xbreak; // x coordinate where the enemy starts z movement || the base on y for sinusoid + this.delete = false; // Enemy marked for Deletion + this.angle = 0; + this.spriteTimer = 0; //sprite animation helper + this.spriteInterval = Math.floor(Math.random() * 80) + 120; + this.fireTimer = 0; // projectile firing timer + this.fireInteval = Math.floor(Math.random() * 2000) + 1000; // projectile random firing interval b/w 1500 and 3500ms + this.hit = false; + } + update(deltaTime) { + //sprite animation + if (this.spriteTimer > this.spriteInterval) { + this.frameX++; + if (this.frameX - this.frameXStart >= this.maxFrames) this.frameX = this.frameXStart; + this.spriteTimer = 0; + } else this.spriteTimer += deltaTime; + + // projectile generation + if (this.shoots) { + if (this.fireTimer > this.fireInteval) { + currentLevel.enemyProjectiles.push(new Projectile(false, this)); + this.fireTimer = 0; + } + else this.fireTimer += deltaTime; + } + //movement + switch (this.movement) { + case "wave": + this.x -= this.speedX; + this.y = this.xbreak + this.range * Math.sin(this.angle); + this.angle += this.speedY; + break; + + case "zigzag": + this.x -= this.speedX; + if (this.x < this.xbreak && this.x >= this.range) this.y += this.speedY; + break; + + case "mini1": // mini boss 1 in level 3 + if (this.x > this.xbreak) this.x -= this.speedX; + else { + if (this.y > 330 || this.y < 70) this.speedY *= -1; + this.y += this.speedY; + } + break; + + case "mini2": // mini boss 2 in level 5 + if (this.x > this.xbreak) this.x -= this.speedX; + else { + if (this.y > 430 || this.y < 150) this.speedY *= -1; + this.y += this.speedY; + } + break; + + case "linear": + default: + this.x -= this.speedX; + } + + // garbage collection + if (this.x + this.width < 0) this.delete = true; + + this.draw(); + } + draw() { + mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height); + } +} +class Meteor extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 100; + this.height = 50; + this.maxFrames = 2; + this.frameXStart = 0; + this.frameX = this.frameXStart; + this.frameY = 1; + } +} +class Triship extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 60; + this.height = 70; + this.maxFrames = 2; + this.frameXStart = 2; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 1; + } +} +class Squid extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 80; + this.height = 50; + this.maxFrames = 1; + this.frameXStart = 0; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 2; + } +} +class Shuttle extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 90; + this.height = 50; + this.maxFrames = 1; + this.frameXStart = 2; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 2; + } +} +class Saucer extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 90; + this.height = 50; + this.maxFrames = 1; + this.frameXStart = 4; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 2; + } +} +class Tadpole extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 90; + this.height = 50; + this.maxFrames = 2; + this.frameXStart = 0; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 3; + } +} +class Kraken extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 70; + this.height = 80; + this.maxFrames = 1; + this.frameXStart = 4; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 3; + } +} +class Marble1 extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 50; + this.height = 50; + this.maxFrames = 1; + this.frameXStart = 0; + this.frameX = this.frameXStart; + this.frameY = 4; + } +} +class Marble2 extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 50; + this.height = 40; + this.maxFrames = 1; + this.frameXStart = 1; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 4; + } +} +class Marble3 extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 50; + this.height = 30; + this.maxFrames = 1; + this.frameXStart = 3; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 4; + } +} +class Beetle extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 80; + this.height = 50; + this.maxFrames = 2; + this.frameXStart = 0; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 5; + } +} +class Rock extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 70; + this.height = 70; + this.maxFrames = 1; + this.frameXStart = 4; + this.frameX = this.frameXStart; + this.frameY = 5; + } +} +class Flipper extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 60; + this.height = 60; + this.maxFrames = 2; + this.frameXStart = 0; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 6; + } +} +class Dragonfly extends Enemy { + constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) { + super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak); + this.width = 100; + this.height = 50; + this.maxFrames = 2; + this.frameXStart = 0; + if (isLevelDark) this.frameXStart += this.maxFrames; + this.frameX = this.frameXStart; + this.frameY = 7; + } +} +class Torpedo { + constructor(x, y, offsetX, offsetY) { + this.image = mainSprites; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.width = 70; + this.height = 50; + this.x = x + offsetX; + this.y = y + offsetY; + this.delete = false; + this.hp = 150; + this.torpToggle = true; // toggling torpedoes up and down + } + update(x) { + if (this.hp <= 0) { + this.x -= 7; + } else { + this.x = x + this.offsetX; + } + if (this.x + this.width < 0) this.delete = true; + + this.draw(); + } + draw() { + mainCtx.drawImage(this.image, 900, 300, this.width, this.height, this.x, this.y, this.width, this.height); + } +} + +// Boss Classes +class Boss { + constructor() { + this.image = bossSprites; + this.isBoss = true; + this.spriteTimer = 0; //sprite animation helper + this.spriteInterval = 200; + this.fireTimer = 0; // projectile firing timer + this.spriteSize = 400; + this.frameX = 0; + this.maxFrames = 2; + this.speedX = 2; + this.speedY = 1.5; + this.supportEnemies = []; + this.chargeType = 0; // 0: no charge, 1: fwd , 2: back and then fwd + this.xMin = 20; + this.charge = false; + this.retreat = false; + this.chargeTimer = 0; + this.chargeInterval = 9000; + this.support = false; + this.delete = false; + } + update(deltaTime) { + //sprite animation + if (this.spriteTimer > this.spriteInterval) { + this.frameX++; + if (this.frameX >= this.maxFrames) this.frameX = 0; + this.spriteTimer = 0; + } else this.spriteTimer += deltaTime; + + // projectile generation + if (this.fireTimer > this.fireInteval) { + currentLevel.enemyProjectiles.push(new Projectile(false, this)); + this.fireTimer = 0; + } + else this.fireTimer += deltaTime; + + + // movement + if (!this.charge && !this.retreat) { + //entry and basic movement; + if (this.x > this.xbreak + 1) this.x -= this.speedX; + else if (this.x < this.xbreak - 1) this.x += this.speedX; + else { + if (this.y + this.height >= this.yMax || this.y <= this.yMin) this.speedY *= -1; + this.y += this.speedY; + } + } + if (this.chargeTimer > this.chargeInterval) { + switch (this.chargeType) { + case 1: this.charge = true; + break; + case 2: this.retreat = true; + break; + case 0: this.charge = false; + break; + } + this.chargeTimer = 0; + } + else this.chargeTimer += deltaTime; + + if (this.retreat) { + this.retreatMovement(); + } + + if (this.charge) { + this.chargeMovement(); + } + + //update hitbox + this.hitbox.forEach(hb => hb.update(this.x, this.y)); + + // support ship generation, collision, and deletion + if (this.support) { + if (this.supportTimer >= this.supportInterval) { + this.supportGen(); + this.supportTimer = 0; + } else this.supportTimer += deltaTime; + + this.supportEnemies.forEach((e, i) => { + currentLevel.playerProjectiles.forEach(pp => { + if (checkCollision(pp, e)) { + e.hp -= 10; + if (e.hp <= 0) e.delete = true; + pp.delete = true; + } + }); + currentLevel.playerSpecial.forEach(sp => { + if (checkCollision(sp, e)) { + e.delete = true; + currentLevel.explosions.push(new Explosion(e.x, e.y)); + if (sp.specialType === "missile") sp.delete = true; + } + }); + // collision detection with player and shield + if (currentLevel.player.shieldOn) { + if (checkCollision(currentLevel.player.shield, e)) { + e.delete = true; + currentLevel.explosions.push(new Explosion(e.x, e.y)); + } + } + else if (checkCollision(currentLevel.player, e)) { + if (!currentLevel.player.hit) { + currentLevel.player.hit = true; + currentLevel.explosions.push(new Explosion(e.x, e.y)); + e.delete = true; + currentLevel.playerDead(); + } + } + e.update(deltaTime); + if (e.delete) this.supportEnemies.splice(i, 1); + }); + } + + // draw + this.draw(); + } + chargeMovement() { + if (this.x > this.xMin) this.x -= 4; + else this.charge = false; + } + retreatMovement() { + if (this.x <= 840) this.x += 2 + else { + this.retreat = false; + this.charge = true; + this.y = this.chargeY; + } + } + draw() { + mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height); + } +} +class Boss1 extends Boss { + constructor() { + super(); + this.width = 200; + this.height = 230; + this.x = 840; + this.y = 120; + this.hitbox = [new Hitbox(this.x, this.y, this.width - 50, this.height - 30, 50, 0, false)]; + this.xbreak = 550; + this.hp = 200; + this.score = 100; + this.delete = false; + this.frameY = 0; + this.yMin = 50; + this.yMax = 480; + this.fireInteval = 1500; + } +} +class Boss2 extends Boss { + constructor() { + super(); + this.width = 230; + this.height = 210; + this.x = 840; + this.y = 120; + this.hitbox = [new Hitbox(this.x, this.y, this.width - 50, this.height - 10, 50, 0, false)]; + this.xbreak = 550; + this.hp = 200; + this.score = 100; + this.delete = false; + this.frameY = 1; + this.yMin = 50; + this.yMax = 480; + this.fireInteval = 1500; + } +} +class Boss3 extends Boss { + constructor() { + super(); + this.width = 220; + this.height = 200; + this.x = 840; + this.y = 120; + this.hitbox = [new Hitbox(this.x, this.y, this.width - 10, this.height - 10, 10, 10, false)]; + this.xbreak = 500; + this.hp = 300; + this.score = 100; + this.delete = false; + this.frameY = 2; + this.yMin = 50; + this.yMax = 340; + this.fireInteval = 1500; + this.chargeType = 1; + } +} +class Boss4 extends Boss { + constructor() { + super(); + this.width = 150; + this.height = 250; + this.x = 840; + this.y = 120; + this.hitbox = [ + new Hitbox(this.x, this.y, this.width, 70, 0, 0, false), + new Hitbox(this.x, this.y, 100, 140, 50, 70, false) + ]; + this.xbreak = 550; + this.hp = 250; + this.score = 100; + this.delete = false; + this.frameY = 3; + this.yMin = 50; + this.yMax = 450; + this.fireInteval = 1500; + this.support = true; + this.supportTimer = 0; + this.supportInterval = 3000; + } + supportGen() { + currentLevel.enemyProjectiles.push(new EnemyMissile(this)); + } +} +class Boss5 extends Boss { + constructor() { + super(); + this.width = 190; + this.height = 210; + this.x = 840; + this.y = 220; + this.hitbox = [new Hitbox(this.x, this.y, this.width - 10, this.height - 10, 10, 10, false)]; + this.xbreak = 500; + this.hp = 350; + this.score = 100; + this.delete = false; + this.frameY = 4; + this.yMin = 210; + this.yMax = 480; + this.fireInteval = 1500; + this.chargeType = 1; + this.support = true; + this.supportTimer = 0; + this.supportInterval = 4000; + } + supportGen() { + this.supportEnemies.push(new Beetle(20, this.x, this.y + Math.random() * (this.height - 50), false, 3, 0, "linear", 0, 0)); + } +} +class Boss6 extends Boss { + constructor() { + super(); + this.width = 200; + this.height = 190; + this.x = 840; + this.y = 220; + this.hitbox = [new Hitbox(this.x, this.y, this.width - 30, this.height, 30, 0, false)]; + this.xbreak = 500; + this.hp = 350; + this.score = 100; + this.delete = false; + this.frameY = 5; + this.yMin = 140; + this.yMax = 480; + this.fireInteval = 1500; + this.speedX = 3; + this.chargeY = 270; + this.chargeInterval = 12000; + this.chargeType = 2; + this.support = true; + this.supportTimer = 0; + this.supportInterval = 4000; + } + supportGen() { + this.supportEnemies.push(new Tadpole(20, this.x, this.y + Math.random() * (this.height - 50), false, 3, 0, "linear", 0, 0)); + } +} +class Boss7 extends Boss { + constructor() { + super(); + this.width = 300; + this.height = 250; + this.x = 840; + this.y = 120; + this.hitbox = [ + new Hitbox(this.x, this.y, this.width - 20, 120, 20, 10, false), + new Hitbox(this.x, this.y, 220, this.height - 20, 80, 10, false), + new Hitbox(this.x, this.y, this.width, 50, 0, 200, false) + ]; + this.xbreak = 550; + this.hp = 300; + this.score = 100; + this.delete = false; + this.frameY = 6; + this.yMin = 50; + this.yMax = 400; + this.fireInteval = 1500; + this.chargeY = 60; + this.chargeInterval = 12000; + this.chargeType = 2; + this.support = true; + this.supportTimer = 0; + this.supportInterval = 4000; + } + supportGen() { + this.supportEnemies.push(new Flipper(20, this.x, this.y + Math.random() * (this.height - 60), false, 3, 0, "linear", 0, 0)); + } +} +class Boss8 extends Boss { + constructor() { + super(); + this.width = 380; + this.height = 380; + this.x = 840; + this.y = 50; + this.hitbox = [ + new Hitbox(this.x, this.y, this.width - 20, 120, 20, 10, true), + new Hitbox(this.x, this.y, 100, 60, 100, 130, true), + new Hitbox(this.x, this.y, 100, 60, 100, 220, true), + new Hitbox(this.x, this.y, 90, 100, 210, 150, false) + ]; + this.xbreak = 460; + this.hp = 250; + this.score = 100; + this.delete = false; + this.frameY = 7; + this.fireTimer = 0; + this.fireInteval = 3000; + this.torpedoes = [ + new Torpedo(this.x, this.y, 50, 150), + new Torpedo(this.x, this.y, 30, 220), + ]; + this.retreat = false; + this.retreatTimer = 0; + this.retreatInterval = 6000; + this.retreatFire = false; // to trigger retreat swarm only once + } + update(deltaTime) { + //sprite animation + if (this.spriteTimer > this.spriteInterval) { + this.frameX++; + + this.torpedoes.forEach(torp => { + if (torp.hp > 0) { + if (torp.torpToggle) { + torp.y += 10; + torp.torpToggle = !torp.torpToggle; + } else { + torp.y -= 10; + torp.torpToggle = !torp.torpToggle; + } + } + }); + + if (this.frameX >= this.maxFrames) this.frameX = 0; + this.spriteTimer = 0; + } else this.spriteTimer += deltaTime; + + // torpedo + this.torpedoes.forEach((torp, i) => { + currentLevel.playerProjectiles.forEach(pp => { + if (checkCollision(pp, torp)) { + torp.hp -= 5; + pp.delete = true; + } + }); + currentLevel.playerSpecial.forEach(sp => { + if (checkCollision(sp, torp)) { + if (!sp.hit) { + sp.hit = true; + if (sp.specialType === "missile") { + sp.delete = true; + torp.hp -= 30; + } + else torp.hp -= 50; + } + } + }); + torp.update(this.x); + if (torp.delete) this.torpedoes.splice(i, 1); + }); + + // projectile generation aka support ships + if (this.fireTimer >= this.fireInteval && !this.retreat) { + this.supportEnemies.push(new Triship(30, this.x, 240, false, 3, 0, "linear", 0, 0)); + this.fireTimer = 0; + } else this.fireTimer += deltaTime; + + this.supportEnemies.forEach((e, i) => { + currentLevel.playerProjectiles.forEach(pp => { + if (checkCollision(pp, e)) { + e.hp -= 10; + if (e.hp <= 0) { + e.delete = true; + currentLevel.explosions.push(new Explosion(e.x, e.y)); + } + pp.delete = true; + } + }); + currentLevel.playerSpecial.forEach(sp => { + if (checkCollision(sp, e)) { + e.delete = true; + currentLevel.explosions.push(new Explosion(e.x, e.y)); + if (sp.specialType === "missile") sp.delete = true; + } + }); + // collision detection with player and shield + if (currentLevel.player.shieldOn) { + if (checkCollision(currentLevel.player.shield, e)) { + e.delete = true; + currentLevel.explosions.push(new Explosion(e.x, e.y)); + } + } + else if (checkCollision(currentLevel.player, e)) { + if (!currentLevel.player.hit) { + currentLevel.player.hit = true; + currentLevel.explosions.push(new Explosion(e.x, e.y)); + e.delete = true; + currentLevel.playerDead(); + } + } + e.update(deltaTime); + if (e.delete) this.supportEnemies.splice(i, 1); + }); + + // forward movement + this.speedX = 0.105 * deltaTime; + if (this.x > this.xbreak && !this.retreat) this.x -= this.speedX; + + // Retreat when both the torpedoes are fired & dragonfly swarm + if (this.torpedoes.length === 0) { + if (this.retreatTimer > this.retreatInterval && !this.retreat) { + this.retreat = true; + this.retreatTimer = 0; + } + if (this.retreat && this.x <= 840) { + this.x += 2; // retreat back out of the screen + } else if (!this.retreatFire && this.retreat) { + this.supportEnemies.push(new Dragonfly(40, 1040, 60, false, 4, 0, "linear", 0, 0)); + this.supportEnemies.push(new Dragonfly(40, 1150, 60, false, 4, 0, "linear", 0, 0)); + this.supportEnemies.push(new Dragonfly(40, 1040, 120, false, 4, 0, "linear", 0, 0)); + this.supportEnemies.push(new Dragonfly(40, 1150, 120, false, 4, 0, "linear", 0, 0)); + this.supportEnemies.push(new Dragonfly(40, 1040, 180, false, 4, 0, "linear", 0, 0)); + this.supportEnemies.push(new Dragonfly(40, 1150, 180, false, 4, 0, "linear", 0, 0)); + this.supportEnemies.push(new Dragonfly(40, 1040, 240, false, 4, 0, "linear", 0, 0)); + this.supportEnemies.push(new Dragonfly(40, 1150, 240, false, 4, 0, "linear", 0, 0)); + this.retreatFire = true; + } + + if (this.retreat && this.retreatTimer > this.retreatInterval) { + this.retreat = false; + this.retreatFire = false; + this.retreatTimer = 0; + } + this.retreatTimer += deltaTime; + } + + //update hitbox + this.hitbox.forEach(hb => hb.update(this.x, this.y)); + + this.draw(); + } +} + +// UI class for displaying lives, spAtk and score; +class UI { + constructor() { + this.image = mainSprites; + this.frameX = 6; // grid x on spritesheet + this.heartY = 3; // grid y on spritesheet + this.missileY = 0; + this.laserY = 5; + this.wallY = 7; + if (isLevelDark) { + this.heartY++; + this.missileY++; + this.laserY++; + this.wallY++; + } + this.spriteSize = 150; + this.width = 50; + this.height = 40; + } + draw() { + // drawing hearts for lives + for (let i = 0; i < lives; i++) { + bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.heartY * this.spriteSize, this.width, this.height, i * 55 + 5, 5, this.width, this.height); + } + + // drawing special Attack and the counts remining; + switch (specialAtttack) { + case "missile": bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.missileY * this.spriteSize, this.width, this.height, 400, 10, this.width, this.height); + break; + case "laser": bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.laserY * this.spriteSize, this.width, this.height, 400, 5, this.width, this.height); + break; + case "wall": bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.wallY * this.spriteSize, this.width, this.height, 400, 5, this.width, this.height); + break; + } + bgCtx.save(); + if (isLevelDark) bgCtx.fillStyle = "#aad69c"; + else bgCtx.fillStyle = "#282828"; + bgCtx.font = "bold 52px Silkscreen"; + bgCtx.fillText(specialCount.toString().padStart(2, "0"), 460, 45); + + //score + bgCtx.fillText(playerScore.toString().padStart(5, "0"), 600, 45); + bgCtx.restore(); + } +} +// Star Particle effect in the home screen +class Particle { + constructor() { + this.initialX = Math.floor(Math.random() * 440) + 200; + this.initialY = Math.floor(Math.random() * 280) + 100; + this.x = this.initialX; + this.y = this.initialY; + this.speedX = Math.abs(420 - this.x) * 0.0025; + this.speedY = Math.random() * 0.3; + if(this.initialX <= bgCanvas.width * 0.5) this.speedX *= -1; + if(this.initialY <= bgCanvas.height * 0.5) this.speedY *= -1; + this.radius = Math.random() * 2; + this.color = "white"; + this.opacity = 0.001; + this.increase = Math.random() * 0.01 + 0.001; + } + update() { + this.x += this.speedX; + this.y += this.speedY; + this.opacity += this.increase; + if(this.x < 0 || this. x > 840 || this.y < 0 || this.y > 480){ + this.x = this.initialX; + this.y = this.initialY; + this.opacity = 0.001; + } + this.draw(); + } + draw() { + bgCtx.save(); + bgCtx.globalAlpha = this.opacity; + bgCtx.beginPath(); + bgCtx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); + bgCtx.fillStyle = this.color; + bgCtx.fill(); + bgCtx.closePath(); + bgCtx.restore(); + } +} +const particles = []; +for(let i = 0; i< 100; i++){ + particles.push(new Particle()); +} + +// Background classes +class Background { + constructor(level, frameX) { + this.level = level; + this.frameX = frameX; + this.speed = 0; + this.hitbox = []; + } + update(deltaTime) { + if (!this.level.bgStop) { + this.speed = 0.105 * deltaTime; // 0.105 comes from 840px in 8000ms. + this.x -= this.speed; + } + this.hitbox.forEach(hb => hb.update(this.x, this.y)); + if (this.x + this.width < 0) this.delete = true; + this.draw(); + } + draw() { + bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.spriteY, this.width, this.height, this.x, this.y, this.width, this.height); + } +} +class Background2 extends Background { + constructor(level, frameX, index) { + super(level, frameX); + this.image = bgSprites1; + this.spriteY = 0; // y position in px on sprite sheet + this.spriteSize = 400; + this.width = 320; + switch (frameX) { + case 0: this.height = 160; + break; + case 1: this.height = 80; + break; + case 2: this.height = 60; + break; + } + this.x = this.width * index; + this.y = bgCanvas.height - this.height; + this.delete = false; // marked for deletion + } +} +class Background3 extends Background { + constructor(level, frameX, index) { + super(level, frameX); + this.image = bgSprites1; + this.spriteY = 200; //y position in px on sprite sheet + this.spriteSize = 400; + this.width = 320; + this.x = this.width * index; + this.hitbox = []; + switch (frameX) { + case 0: this.height = 70; + this.y = bgCanvas.height - this.height; + this.hitbox.push(new Hitbox(this.x, this.y, this.width, this.height - 10, 0, 10, false)); + break; + case 1: this.height = 80; + this.y = bgCanvas.height - this.height; + this.hitbox.push(new Hitbox(this.x, this.y, this.width, this.height - 20, 0, 20, false)); + break; + case 2: this.height = 160; + this.y = bgCanvas.height - this.height; + this.hitbox.push(new Hitbox(this.x, this.y, 60, 60, 0, 100, false)); + this.hitbox.push(new Hitbox(this.x, this.y, 30, 130, 60, 30, false)); + this.hitbox.push(new Hitbox(this.x, this.y, 100, 150, 90, 10, false)); + this.hitbox.push(new Hitbox(this.x, this.y, 60, 120, 230, 40, false)); + break; + } + this.delete = false; // marked for deletion + } +} +class Background4 extends Background { + constructor(level, frameX, index) { + super(level, frameX); + this.image = bgSprites1; + this.spriteY = 400; //y position in px on sprite sheet + this.spriteSize = 400; + this.width = 320; + this.x = this.width * index; + switch (frameX) { + case 0: this.height = 70; + this.y = bgCanvas.height - this.height; + this.hitbox.push(new Hitbox(this.x, this.y, this.width, this.height - 20, 0, 20, false)); + break; + case 1: this.height = 80; + this.y = bgCanvas.height - this.height; + this.hitbox.push(new Hitbox(this.x, this.y, this.width, this.height - 20, 0, 20, false)); + break; + case 2: this.height = 160; + this.y = bgCanvas.height - this.height; + this.hitbox.push(new Hitbox(this.x, this.y, this.width, 60, 0, 100, false)); + this.hitbox.push(new Hitbox(this.x, this.y, 60, 150, 100, 10, false)); + } + this.delete = false; // marked for deletion + } +} +class Background5 extends Background { + constructor(level, frameX, index) { + super(level, frameX); + this.image = bgSprites1; + this.spriteY = 600; //y position in px on sprite sheet + this.spriteSize = 400; + this.width = 320; + this.x = this.width * index; + this.y = 50; + switch (frameX) { + case 0: this.height = 170; + break; + case 1: this.height = 80; + break; + case 2: this.height = 50; + break; + } + this.hitbox.push(new Hitbox(this.x, this.y, this.width - 40, this.height - 5, 20, 0, false)); + this.delete = false; // marked for deletion + } +} +class Background7 extends Background { + constructor(level, frameX, index) { + super(level, frameX); + this.image = bgSprites2; + this.spriteY = 0; //y position in px on sprite sheet + this.spriteSize = 400; + this.width = 2800; + this.height = 160; + this.x = this.width * index; + this.y = bgCanvas.height - this.height; + this.delete = false; // marked for deletion + this.hitbox = [ + new Hitbox(this.x, 430, this.width, 50, 0, 110, false), + new Hitbox(this.x, this.y, 60, 150, 1880, 10, false), + new Hitbox(this.x, this.y, 60, 150, 2200, 10, false) + ]; + } +} +class Background8 extends Background { + constructor(level, frameX, index) { + super(level, frameX); + this.image = bgSprites2; + this.spriteY = 200; //y position in px on sprite sheet + this.spriteSize = 400; + this.width = 2800; + this.height = 160; + this.x = this.width * index; + this.y = bgCanvas.height - this.height; + this.delete = false; // marked for deletion + this.hitbox = [ + new Hitbox(this.x, 430, this.width, 50, 0, 110, false), + new Hitbox(this.x, this.y, 110, 100, 670, 60, false), + new Hitbox(this.x, this.y, 100, 150, 780, 10, false), + new Hitbox(this.x, this.y, 110, 100, 990, 60, false), + new Hitbox(this.x, this.y, 100, 150, 1100, 10, false), + new Hitbox(this.x, this.y, 110, 100, 1950, 60, false), + new Hitbox(this.x, this.y, 100, 150, 2060, 10, false) + ]; + } +} + +class Level { + constructor(level, isDark) { + this.active = true; + this.number = level; // level number + isLevelDark = isDark; + if (isLevelDark) bgCanvas.style.background = "#282828"; + else bgCanvas.style.background = "#aad69c"; + this.player = new Player(this); + this.input = new InputHandler(this); + this.ui = new UI(); + this.sourceEnemyArray = []; + this.enemies = []; + this.playerProjectiles = []; + this.playerSpecial = []; // special weapons + this.enemyProjectiles = []; + this.background = []; + this.explosions = []; + this.i = 0; // index for global level enemy array + this.levelTime = 0; + this.flag = true; // prevents index overflow of enemy array + this.bgStop = false; // stops background + this.levelComplete = false; + + // create background and assigns enemy array of the current level + switch (this.number) { + case 1: this.sourceEnemyArray = enemiesLvl1; + break; + case 2: this.sourceEnemyArray = enemiesLvl2; + bgArray2.forEach((frame, index) => this.background.push(new Background2(this, frame, index))); + break; + case 3: this.sourceEnemyArray = enemiesLvl3; + bgArray3.forEach((frame, index) => this.background.push(new Background3(this, frame, index))); + break; + case 4: this.sourceEnemyArray = enemiesLvl4; + bgArray4.forEach((frame, index) => this.background.push(new Background4(this, frame, index))); + break; + case 5: this.sourceEnemyArray = enemiesLvl5; + bgArray5.forEach((frame, index) => this.background.push(new Background5(this, frame, index))); + break; + case 6: this.sourceEnemyArray = enemiesLvl6; + bgArray6.forEach((frame, index) => this.background.push(new Background5(this, frame, index))); // lvl 5 & 6 have same bg + break; + case 7: this.sourceEnemyArray = enemiesLvl7; + bgArray7.forEach((frame, index) => this.background.push(new Background7(this, frame, index))); + break; + case 8: this.sourceEnemyArray = enemiesLvl8; + bgArray8.forEach((frame, index) => this.background.push(new Background8(this, frame, index))); + break; + } + } + update(deltaTime) { + // ui draw + this.ui.draw(); + + // player update + if (!this.player.delete) this.player.update(deltaTime); + + // player projectiles + this.playerProjectiles.forEach(projectile => projectile.update()); + this.playerProjectiles = this.playerProjectiles.filter(projectile => !projectile.delete); + + // player special attack + this.playerSpecial.forEach(special => special.update(deltaTime)); + this.playerSpecial = this.playerSpecial.filter(special => !special.delete); + + // pushing enemies from source array into the level enemies array + if (this.flag && this.levelTime > this.sourceEnemyArray[this.i].time) { + this.swarm(this.sourceEnemyArray[this.i].object); + this.i++; + if (this.i >= this.sourceEnemyArray.length) { + this.flag = false; + this.bgStop = true; + } + } + + //updating all the enemies, and thier collision detection + this.enemies.forEach(enemy => { + + enemy.update(deltaTime); + + // interaction with player projectile; + this.playerProjectiles.forEach(pp => { + if (enemy.isBoss) { // for boss + enemy.hitbox.forEach(hb => { + if (checkCollision(hb, pp)) { + pp.delete = true; + if (!hb.immune) { + enemy.hp -= 5; + playerScore += 5; + if (enemy.hp <= 0) { + playerScore += enemy.score; + for (let i = 0; i < 3; i++) { //add 3 explosions + this.explosions.push(new Explosion(Math.random() * enemy.width + enemy.x, Math.random() * enemy.height + enemy.y)); + } + this.levelComplete = true; + enemy.delete = true; + } + } + } + }); + } + else if (checkCollision(pp, enemy)) { // for others + if (enemy.isPowerUp) pp.delete = true; + else { + pp.delete = true; + enemy.hp -= 10; + if (enemy.hp <= 0) { + this.explosions.push(new Explosion(enemy.x, enemy.y)); + playerScore += enemy.score; + enemy.delete = true; + } + } + } + }); + + //interaction with player special attack + this.playerSpecial.forEach(sp => { + if (enemy.isBoss) { // for boss + enemy.hitbox.forEach(hb => { + if (checkCollision(hb, sp)) { + if (sp.specialType === "missile") sp.delete = true; + if (!hb.immune) { + if (!sp.hit) { + enemy.hp -= sp.damage; + sp.hit = true; + playerScore += sp.damage; + if (enemy.hp <= 0) { + playerScore += enemy.score; + for (let i = 0; i < 3; i++) { //add 3 explosions + this.explosions.push(new Explosion(Math.random() * enemy.width + enemy.x, Math.random() * enemy.height + enemy.y)); + } + this.levelComplete = true; + enemy.delete = true; + } + } + + } + } + }); + } + else if (!enemy.isPowerUp) { // everyone except powerup + if (checkCollision(sp, enemy)) { + if (!enemy.hit || !sp.hit) { + enemy.hp -= sp.damage; + enemy.hit = true; + sp.hit = true; + if (enemy.hp <= 0) { + this.explosions.push(new Explosion(enemy.x, enemy.y)); + playerScore += enemy.score; + enemy.delete = true; + } + } + if (sp.specialType === "missile") sp.delete = true; + } + } + }); + + //interaction with player and its shield + if (enemy.isPowerUp) { + if (checkCollision(enemy, this.player.hitbox)) { + enemy.delete = true; + if (enemy.powerup === "life") { + if (lives === maxLives) specialCount++; + else lives++; + } + else if (specialAtttack === enemy.powerup) { + if (specialAtttack === "wall") specialCount++; + else specialCount += 3; + } + else { + specialAtttack = enemy.powerup; + if (specialAtttack == "wall") specialCount = 1; + else specialCount = 3; + } + } + } + else if (enemy.isBoss) { + enemy.hitbox.forEach(hb => { + if (this.player.shieldOn) { //when shieldOn check with shield + if (checkCollision(hb, this.player.shield)) { + enemy.hp -= 1; + playerScore += 1; + if (enemy.hp <= 0) { + playerScore += enemy.score; + for (let i = 0; i < 3; i++) { //add 3 explosions + this.explosions.push(new Explosion(Math.random() * enemy.width + enemy.x, Math.random() * enemy.height + enemy.y)); + } + this.levelComplete = true; + enemy.delete = true; + } + } + } + else if (checkCollision(hb, this.player.hitbox)) { + if (!this.player.hit) { + this.player.hit = true; + this.explosions.push(new Explosion(this.player.x, this.player.y)); + this.playerDead(); + } + } + }); + } + else { + if (this.player.shieldOn) { //other enemies, when shield is on + if (checkCollision(this.player.shield, enemy)) { + playerScore += enemy.score; + this.explosions.push(new Explosion(enemy.x, enemy.y)); + enemy.delete = true; + } + } + // other enemies when shield is off + else if (checkCollision(enemy, this.player.hitbox)) { + if (!this.player.hit) { + this.player.hit = true; + this.explosions.push(new Explosion(this.player.x, this.player.y)); + this.explosions.push(new Explosion(enemy.x, enemy.y)); + enemy.delete = true; + this.playerDead(); + } + } + } + }); + this.enemies = this.enemies.filter(enemy => !enemy.delete); + + //enemy projectiles + this.enemyProjectiles.forEach(projectile => { + projectile.update(); + // interaction with player projectile + this.playerProjectiles.forEach(pp => { + if (checkCollision(projectile, pp)) { + this.explosions.push(new Explosion(pp.x, pp.y)); + pp.delete = true; + projectile.delete = true; + } + }); + // interaction with player special attack + this.playerSpecial.forEach(sp => { + if (checkCollision(projectile, sp)) { + projectile.delete = true; + } + }); + // interaction with player + if (!this.player.shieldOn && checkCollision(this.player.hitbox, projectile)) { + if (!this.player.hit && this.active) { + this.player.hit = true; + projectile.delete = true; + this.explosions.push(new Explosion(this.player.x, this.player.y)); + this.playerDead(); + } + } + //interaction with player shield + if (this.player.shieldOn) { + if (checkCollision(this.player.shield, projectile)) { + projectile.delete = true; + } + } + }); + this.enemyProjectiles = this.enemyProjectiles.filter(projectile => !projectile.delete); + + // explosions update + this.explosions.forEach(explosion => explosion.update()); + this.explosions = this.explosions.filter(explosion => !explosion.delete); + + // background and collision detection with player and projectiles + this.background.forEach(bg => { + bg.update(deltaTime); + bg.hitbox.forEach(hb => { + if (checkCollision(this.player.hitbox, hb)) { + if (!this.player.hit) { + this.player.hit = true; + this.explosions.push(new Explosion(this.player.x, this.player.y)); + this.playerDead(); + } + } + + this.playerProjectiles.forEach(pp => { + if (checkCollision(pp, hb)) { + pp.delete = true; + } + }) + }) + }); + this.background = this.background.filter(bg => !bg.delete); + + //level complete; + if (this.levelComplete) { + this.active = false; + if (this.number != 5 && this.number != 6) { + if (this.player.y <= 55) this.player.x += 5; + else this.player.y -= 2; + } + else { + if (this.player.y + this.player.height >= 400) this.player.x += 5; + else this.player.y += 2; + } + //player crosses canvas width and next level + if (this.player.x >= 840) { + if (this.number === 8) { + gameOver = true; + } + else if (this.number != 4 && this.number != 5) { + nextLevel(this.number + 1, false); + } + else { + nextLevel(this.number + 1, true); + } + } + } + + this.levelTime += deltaTime; + } + swarm(array) { + array.forEach(object => { + this.enemies.push(object); + }) + } + playerDead() { + this.player.delete = true; + lives--; + if (lives > 0) setTimeout(() => { this.player = new Player(); }, 500); + else setTimeout(() => { + gameOver = true; + }, 500); + } +} + +// all level enemies source array +const enemiesLvl1 = [ + { + time: 2000, + object: [ + new Meteor(10, 840, 80, false, 3.5, 0, "linear", 0, 0), + new Meteor(10, 1040, 80, false, 3.5, 0, "linear", 0, 0), + new Meteor(10, 1240, 80, false, 3.5, 0, "linear", 0, 0) + ] + }, + { + time: 5000, + object: [ + new Meteor(10, 840, 200, false, 3.5, 0, "linear", 0, 0), + new Meteor(10, 1040, 200, false, 3.5, 0, "linear", 0, 0) + ] + }, + { + time: 8000, + object: [ + new Meteor(10, 840, 200, false, 2, 0, "linear", 0, 0), + new Meteor(10, 1040, 200, false, 2, 0, "linear", 0, 0) + ] + }, + { + time: 12000, + object: [ + new Meteor(10, 840, 300, false, 3, 0, "linear", 0, 0), + new Meteor(10, 1040, 300, false, 3, 0, "linear", 0, 0), + new Meteor(10, 1240, 300, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 15000, + object: [ + new Meteor(25, 840, 80, false, 3, 0, "linear", 0, 0), + new PowerUp(950, 70, 3, 0, "linear", 0, 0), + new Meteor(25, 1120, 80, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 21000, + object: [ + new Triship(15, 840, 50, false, 2, 1.5, "zigzag", 300, 620), + new Triship(15, 960, 200, false, 2, 0.03, "wave", 120, 200), + new Triship(15, 1080, 50, false, 2, 1.5, "zigzag", 300, 620), + new Triship(15, 1200, 200, false, 2, 0.03, "wave", 120, 200), + new Triship(15, 1320, 50, false, 2, 1.5, "zigzag", 300, 620), + new Triship(15, 1440, 200, false, 2, 0.03, "wave", 120, 200), + new Triship(15, 1560, 50, false, 2, 1.5, "zigzag", 300, 620), + new Triship(15, 1680, 200, false, 2, 0.03, "wave", 120, 200) + ] + }, + { + time: 35500, + object: [new Squid(10, 840, 280, false, 3, 0, "linear", 0, 0)] + }, + { + time: 38000, + object: [ + new Squid(10, 840, 70, false, 3, 0, "linear", 0, 0), + new Squid(10, 1000, 70, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 41000, + object: [new Squid(10, 840, 200, true, 2, 0.03, "wave", 120, 200)] + }, + { + time: 42500, + object: [new Squid(10, 840, 200, true, 2, 0.03, "wave", 120, 200)] + }, + { + time: 44000, + object: [new Squid(10, 840, 200, true, 2, 0.03, "wave", 120, 200)] + }, + { + time: 45500, + object: [new Squid(10, 840, 200, true, 2, 0.03, "wave", 120, 200)] + }, + { + time: 49000, + object: [ + new Squid(10, 840, 50, true, 3, 2, "zigzag", 300, 620), + new Squid(10, 1040, 50, true, 3, 2, "zigzag", 300, 620), + new Squid(10, 1240, 50, true, 3, 2, "zigzag", 300, 620), + new Squid(10, 1440, 50, true, 3, 2, "zigzag", 300, 620) + ] + }, + { + time: 57000, + object: [new PowerUp(840, 280, 3, 0, "linear", 0, 0)] + }, + { + time: 61000, + object: [ + new Shuttle(15, 840, 50, false, 3, 0, "linear", 0, 0), + new Shuttle(15, 1000, 110, false, 2, 0, "linear", 0, 0), + new Shuttle(15, 1100, 60, false, 2, 0, "linear", 0, 0), + new Shuttle(15, 1300, 330, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 68000, + object: [new Boss1()] //boss + } +]; +isLevelDark = false;//solves bug, otherwise need to declare level before +const enemiesLvl2 = [ + { + time: 2000, + object: [ + new Triship(15, 840, 50, false, 2, 1, "zigzag", 300, 780), + new Triship(15, 840, 300, false, 2, -1, "zigzag", 300, 780), + new Triship(15, 960, 50, false, 2, 1, "zigzag", 300, 780), + new Triship(15, 960, 300, true, 2, -1, "zigzag", 300, 780), + new Triship(15, 1080, 50, true, 2, 1, "zigzag", 300, 780), + new Triship(15, 1080, 300, false, 2, -1, "zigzag", 300, 780) + ] + }, + { + time: 8000, + object: [ + new Saucer(20, 840, 100, false, 2, 0.03, "wave", 50, 100), + new Saucer(20, 840, 340, false, 2, 0.03, "wave", 50, 340) + ] + }, + { + time: 9000, + object: [ + new Saucer(20, 840, 100, false, 2, 0.03, "wave", 50, 100), + new Saucer(20, 840, 340, true, 2, 0.03, "wave", 50, 340), + new PowerUp(840, 220, 3, 0, "linear", 0, 0) + ] + }, + { + time: 10000, + object: [ + new Saucer(20, 840, 100, true, 2, 0.03, "wave", 50, 100), + new Saucer(20, 840, 340, false, 2, 0.03, "wave", 50, 340) + ] + }, + { + time: 13000, + object: [ + new Squid(15, 840, 50, true, 2, 1, "zigzag", 250, 780), + new Squid(15, 1200, 130, true, 3, 0.07, "wave", 40, 130), + new Squid(15, 1200, 350, true, 2, 0.05, "wave", 40, 350), + new Squid(15, 1800, 240, true, 3, 0, "linear", 0, 0), + new Squid(15, 2000, 120, true, 3, 0, "linear", 0, 0) + ] + }, + { + time: 21000, + object: [ + new Squid(15, 840, 240, false, 3, 0, "linear", 0, 0), + new Squid(15, 940, 170, false, 3, 0, "linear", 0, 0), + new Squid(15, 940, 310, false, 3, 0, "linear", 0, 0), + new Squid(15, 1040, 100, false, 3, 0, "linear", 0, 0), + new Squid(15, 1040, 380, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 24000, + object: [ + new Squid(15, 840, 240, false, 3, 0, "linear", 0, 0), + new Squid(15, 940, 170, false, 3, 0, "linear", 0, 0), + new Squid(15, 940, 310, false, 3, 0, "linear", 0, 0), + new Squid(15, 1040, 100, false, 3, 0, "linear", 0, 0), + new Squid(15, 1040, 380, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 28000, + object: [ + new Shuttle(25, 840, 250, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 960, 250, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 1080, 250, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 1200, 250, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 1320, 250, false, 4, 0, "linear", 0, 0) + ] + }, + { + time: 30000, + object: [ + new Shuttle(25, 840, 70, false, 3, 0, "linear", 0, 0), + new Shuttle(25, 1040, 70, false, 3, 0, "linear", 0, 0), + new Shuttle(25, 1240, 250, false, 3, 0, "linear", 0, 0), + new Shuttle(25, 1440, 220, false, 3, 0, "linear", 0, 0), + new Shuttle(25, 1640, 150, false, 3, 0, "linear", 0, 0), + new Shuttle(25, 1840, 220, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 38000, + object: [new Boss2()] //boss + } +]; +const enemiesLvl3 = [ + { + time: 2000, + object: [ + new Tadpole(15, 840, 50, true, 2, 1, "zigzag", 250, 750), + new Tadpole(15, 1020, 50, true, 2, 1, "zigzag", 250, 750), + new Tadpole(15, 1200, 50, true, 2, 1, "zigzag", 250, 750), + new Tadpole(15, 1380, 50, true, 2, 1, "zigzag", 250, 750), + new Tadpole(15, 1560, 50, true, 2, 1, "zigzag", 250, 750), + new Tadpole(15, 1740, 50, true, 2, 1, "zigzag", 250, 750), + new Tadpole(15, 1920, 50, true, 2, 1, "zigzag", 250, 750), + new Tadpole(15, 2100, 50, true, 2, 1, "zigzag", 250, 750), + new Tadpole(15, 2280, 50, true, 2, 1, "zigzag", 250, 750) + ] + }, + { + time: 16000, + object: [ + new Saucer(15, 840, 280, false, 4, 0, "linear", 0, 0), + new Saucer(15, 1040, 280, false, 4, 0, "linear", 0, 0), + new Saucer(15, 1240, 280, false, 4, 0, "linear", 0, 0), + new PowerUp(1340, 280, 4, 0, "linear", 0, 0) + ] + }, + { + time: 22000, + object: [ + new Squid(20, 840, 180, false, 4, 0, "linear", 0, 0), + new Squid(20, 1000, 120, false, 4, 0, "linear", 0, 0), + new Squid(20, 1000, 240, false, 4, 0, "linear", 0, 0), + new Squid(20, 1160, 60, false, 4, 0, "linear", 0, 0), + new Squid(20, 1160, 300, false, 4, 0, "linear", 0, 0) + ] + }, + { + time: 26000, + object: [ + new Squid(20, 840, 180, false, 4, 0, "linear", 0, 0), + new Squid(20, 1000, 120, false, 4, 0, "linear", 0, 0), + new Squid(20, 1000, 240, false, 4, 0, "linear", 0, 0), + new Squid(20, 1160, 60, false, 4, 0, "linear", 0, 0), + new Squid(20, 1160, 300, false, 4, 0, "linear", 0, 0) + ] + }, + { + time: 30000, + object: [new Marble1(20, 840, 110, true, 3, 0.04, "wave", 50, 110)] + }, + { + time: 30300, + object: [new Marble2(20, 840, 110, true, 3, 0.04, "wave", 50, 110)] + }, + { + time: 30600, + object: [ + new Marble3(20, 840, 110, true, 3, 0.04, "wave", 50, 110), + new Marble1(20, 840, 270, true, 3, 0.04, "wave", 50, 270) + ] + }, + { + time: 30900, + object: [new Marble2(20, 840, 270, true, 3, 0.04, "wave", 50, 270)] + }, + { + time: 31200, + object: [new Marble3(20, 840, 270, true, 3, 0.04, "wave", 50, 270)] + }, + { + time: 33000, + object: [new Kraken(150, 840, 150, true, 3, 2, "mini1", 0, 480)] + }, + { + time: 51000, + object: [new Boss3()] //boss + } +]; +const enemiesLvl4 = [ + { + time: 2000, + object: [ + new Beetle(15, 840, 50, true, 3, 1, "zigzag", 700, 800), + new Beetle(15, 990, 50, true, 3, 1, "zigzag", 700, 800), + new Beetle(15, 1140, 50, false, 3, 1, "zigzag", 700, 800), + new Beetle(15, 1290, 50, true, 3, 1, "zigzag", 700, 800), + new Beetle(15, 1440, 50, false, 3, 1, "zigzag", 700, 800) + ] + }, + { + time: 6000, + object: [ + new Beetle(15, 840, 50, true, 3, 2, "zigzag", 400, 700), + new Beetle(15, 990, 50, false, 3, 2, "zigzag", 400, 700), + new Beetle(15, 1140, 50, true, 3, 2, "zigzag", 400, 700), + new Beetle(15, 1290, 50, false, 3, 2, "zigzag", 400, 700), + new Beetle(15, 1440, 50, true, 3, 2, "zigzag", 400, 700) + ] + }, + { + time: 10000, + object: [ + new Beetle(15, 840, 270, true, 3, -2, "zigzag", 370, 700), + new Beetle(15, 990, 270, true, 3, -2, "zigzag", 370, 700), + new Beetle(15, 1140, 270, false, 3, -2, "zigzag", 370, 700), + new Beetle(15, 1290, 270, false, 3, -2, "zigzag", 370, 700), + new Beetle(15, 1440, 270, true, 3, -2, "zigzag", 370, 700) + ] + }, + { + time: 14500, + object: [new Shuttle(15, 840, 250, false, 3, 0, "linear", 0, 0)] + }, + { + time: 17500, + object: [ + new Shuttle(15, 840, 120, false, 3, 0, "linear", 0, 0), + new Shuttle(15, 980, 120, false, 3, 0, "linear", 0, 0), + new Shuttle(15, 1120, 120, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 20500, + object: [new Shuttle(25, 840, 250, false, 3, 0, "linear", 0, 0)] + }, + { + time: 24000, + object: [ + new Shuttle(15, 840, 180, false, 4, 0, "linear", 0, 0), + new Shuttle(15, 980, 180, false, 4, 0, "linear", 0, 0), + new Shuttle(15, 1120, 180, false, 4, 0, "linear", 0, 0) + ] + }, + { + time: 29300, + object: [new PowerUp(840, 180, 1.788, 0.07, "wave", 80, 180)] + }, + { + time: 36000, + object: [ + new Rock(80, 840, 130, false, 2, 0, "linear", 0, 0), + new Rock(80, 1040, 180, false, 2, 0, "linear", 0, 0), + new Rock(80, 1240, 240, false, 2, 0, "linear", 0, 0), + new Rock(80, 1440, 90, false, 2, 0, "linear", 0, 0), + new Rock(80, 1640, 250, false, 2, 0, "linear", 0, 0) + ] + }, + { + time: 50500, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 51000, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 51500, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 52000, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 52500, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 53000, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 53500, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 54000, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 54500, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)] + }, + { + time: 58000, + object: [ + new Triship(20, 840, 50, true, 3, 3, "zigzag", 600, 800), + new Triship(20, 940, 50, true, 3, 3, "zigzag", 600, 800), + new Triship(20, 1160, 50, true, 3, 3, "zigzag", 600, 800), + new Triship(20, 1260, 50, true, 3, 3, "zigzag", 600, 800), + new Triship(20, 1480, 50, true, 3, 3, "zigzag", 600, 800), + new Triship(20, 1580, 50, true, 3, 3, "zigzag", 600, 800) + ] + }, + { + time: 66500, + object: [new Boss4()] //boss + } +]; +isLevelDark = true; +const enemiesLvl5 = [ + { + time: 2000, + object: [ + new Marble3(15, 840, 440, true, 2.8, -2, "zigzag", 300, 600), + new Marble3(15, 1000, 440, true, 2.8, -2, "zigzag", 300, 600), + new Marble3(15, 1160, 440, true, 2.8, -2, "zigzag", 300, 600), + new Marble3(15, 1320, 440, true, 2.8, -2, "zigzag", 300, 600), + new Marble3(15, 1480, 440, true, 2.8, -2, "zigzag", 300, 600) + ] + }, + { + time: 4000, + object: [ + new Marble3(15, 840, 410, false, 3.5, 0, "linear", 0, 0), + new Marble3(15, 1000, 410, false, 3.5, 0, "linear", 0, 0), + new Marble3(15, 1160, 410, false, 3.5, 0, "linear", 0, 0), + new Marble3(15, 1320, 410, false, 3.5, 0, "linear", 0, 0), + new Marble3(15, 1480, 410, false, 3.5, 0, "linear", 0, 0) + ] + }, + { + time: 9500, + object: [new Kraken(100, 840, 390, true, 2, -3, "zigzag", 500, 600)] + }, + { + time: 16000, + object: [new PowerUp(840, 300, 2, 0.07, "wave", 100, 300)] + }, + { + time: 21000, + object: [ + new Tadpole(15, 840, 320, true, 3, 0.07, "wave", 110, 320), + new Tadpole(15, 940, 320, true, 2, 0.05, "wave", 110, 320), + new Tadpole(15, 1040, 320, false, 2, 0.08, "wave", 110, 320), + new Tadpole(15, 1140, 320, false, 3, 0.03, "wave", 110, 320), + new Tadpole(15, 1240, 320, true, 3, 0.09, "wave", 110, 320), + new Tadpole(15, 1340, 320, false, 2, 0.04, "wave", 110, 320), + new Tadpole(15, 1440, 320, false, 3, 0.06, "wave", 110, 320), + new Tadpole(15, 1540, 320, true, 3, 0.03, "wave", 110, 320), + new Tadpole(15, 1640, 320, false, 3, 0.07, "wave", 110, 320) + ] + }, + { + time: 30000, + object: [ + new Squid(25, 840, 320, false, 4, 0, "linear", 0, 0), + new Squid(25, 980, 230, false, 4, 0, "linear", 0, 0), + new Squid(25, 980, 410, false, 4, 0, "linear", 0, 0) + ] + }, + { + time: 32000, + object: [ + new Squid(25, 840, 320, false, 4, 0, "linear", 0, 0), + new Squid(25, 980, 230, false, 4, 0, "linear", 0, 0), + new Squid(25, 980, 410, false, 4, 0, "linear", 0, 0) + ] + }, + { + time: 34000, + object: [ + new Triship(15, 840, 210, false, 2, 1, "zigzag", 225, 600), + new Triship(15, 840, 410, false, 2, -1, "zigzag", 225, 600), + new Triship(15, 1000, 210, false, 2, 1, "zigzag", 225, 600), + new Triship(15, 1000, 410, true, 2, -1, "zigzag", 225, 600), + new Triship(15, 1160, 210, true, 2, 1, "zigzag", 225, 600), + new Triship(15, 1160, 410, false, 2, -1, "zigzag", 225, 600), + new Triship(15, 1320, 210, false, 2, 1, "zigzag", 225, 600), + new Triship(15, 1320, 410, true, 2, -1, "zigzag", 225, 600), + new Triship(15, 1480, 210, true, 2, 1, "zigzag", 225, 600), + new Triship(15, 1480, 410, false, 2, -1, "zigzag", 225, 600) + ] + }, + { + time: 46000, + object: [ + new Squid(25, 840, 160, true, 3, 2, "zigzag", 400, 760), + new Squid(25, 980, 160, true, 3, 2, "zigzag", 400, 760), + new Squid(25, 1120, 160, true, 3, 2, "zigzag", 400, 760), + new Squid(25, 1260, 160, true, 3, 2, "zigzag", 400, 760), + new Squid(25, 1400, 160, true, 3, 2, "zigzag", 400, 760) + ] + }, + { + time: 53000, + object: [ + new Squid(15, 840, 430, true, 3, -2, "zigzag", 350, 650), + new Squid(15, 980, 430, true, 3, -2, "zigzag", 350, 650), + new Squid(15, 1120, 430, true, 3, -2, "zigzag", 350, 650), + new Squid(15, 1260, 430, true, 3, -2, "zigzag", 350, 650), + new Squid(15, 1400, 430, true, 3, -2, "zigzag", 350, 650) + ] + }, + { + time: 56000, + object: [new Squid(150, 840, 180, true, 1.8, -2, "mini2", 300, 450)] + }, + { + time: 75200, + object: [new Boss5()] //boss + } +]; +const enemiesLvl6 = [ + { + time: 2000, + object: [ + new Triship(15, 840, 350, false, 6, 0, "linear", 0, 0), + new Triship(15, 980, 300, false, 6, 0, "linear", 0, 0), + new Triship(15, 1120, 250, false, 6, 0, "linear", 0, 0), + new Triship(15, 1260, 200, false, 6, 0, "linear", 0, 0), + new Triship(15, 1400, 150, false, 6, 0, "linear", 0, 0) + ] + }, + { + time: 4000, + object: [ + new Squid(15, 840, 220, true, 4, 2, "zigzag", 300, 700), + new Squid(15, 960, 220, true, 4, 2, "zigzag", 300, 700), + new Squid(15, 1080, 220, true, 4, 2, "zigzag", 300, 700), + new Squid(15, 1200, 220, true, 4, 2, "zigzag", 300, 700), + new Squid(15, 1320, 220, true, 4, 2, "zigzag", 300, 700) + ] + }, + { + time: 7500, + object: [ + new Triship(15, 840, 410, false, 5, 0, "linear", 0, 0), + new Triship(15, 980, 360, false, 5, 0, "linear", 0, 0), + new Triship(15, 1120, 310, false, 5, 0, "linear", 0, 0), + new Triship(15, 1260, 260, false, 5, 0, "linear", 0, 0), + new Triship(15, 1400, 210, false, 5, 0, "linear", 0, 0) + ] + }, + { + time: 10000, + object: [new PowerUp(840, 240, 4, 0, "linear", 0, 0)] + }, + { + time: 12000, + object: [new Beetle(15, 840, 350, true, 4, 0.04, "wave", 50, 350)] + }, + { + time: 12500, + object: [new Beetle(15, 840, 350, true, 4, 0.04, "wave", 50, 350)] + }, + { + time: 13000, + object: [new Beetle(15, 840, 350, true, 4, 0.04, "wave", 50, 350)] + }, + { + time: 13500, + object: [new Beetle(15, 840, 350, true, 4, 0.04, "wave", 50, 350)] + }, + { + time: 15000, + object: [ + new Saucer(20, 840, 210, true, 4, 0.05, "linear", 50, 100), + new Saucer(20, 840, 380, true, 4, 0.06, "wave", 50, 380), + new PowerUp(840, 270, 4, 0, "linear", 0, 0) + ] + }, + { + time: 15500, + object: [ + new Saucer(20, 840, 210, true, 4, 0.04, "linear", 50, 100), + new Saucer(20, 840, 380, true, 4, 0.06, "wave", 50, 380) + ] + }, + { + time: 16000, + object: [ + new Saucer(20, 840, 210, true, 4, 0.04, "linear", 50, 100), + new Saucer(20, 840, 380, true, 4, 0.06, "wave", 50, 380) + ] + }, + { + time: 20000, + object: [ + new Triship(15, 840, 210, false, 4, 4, "zigzag", 400, 600), + new Triship(15, 840, 410, false, 4, -4, "zigzag", 400, 600), + new Triship(15, 1000, 210, false, 4, 4, "zigzag", 400, 600), + new Triship(15, 1000, 410, true, 4, -4, "zigzag", 400, 600), + new Triship(15, 1160, 210, true, 4, 4, "zigzag", 400, 600), + new Triship(15, 1160, 410, false, 4, -4, "zigzag", 400, 600) + ] + }, + { + time: 22000, + object: [new Beetle(15, 840, 420, true, 3, 0, "linear", 0, 0)] + }, + { + time: 25000, + object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)] + }, + { + time: 25400, + object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)] + }, + { + time: 25800, + object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)] + }, + { + time: 26200, + object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)] + }, + { + time: 26600, + object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)] + }, + { + time: 27000, + object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)] + }, + { + time: 28000, + object: [new Flipper(25, 840, 320, true, 3, 0.06, "wave", 100, 320)] + }, + { + time: 28700, + object: [new Flipper(25, 840, 320, true, 3, 0.06, "wave", 100, 320)] + }, + { + time: 29400, + object: [new Flipper(25, 840, 320, true, 3, 0.06, "wave", 100, 320)] + }, + { + time: 30100, + object: [new Flipper(25, 840, 320, true, 3, 0.06, "wave", 100, 320)] + }, + { + time: 32300, + object: [ + new Dragonfly(15, 840, 210, true, 3.5, 3, "zigzag", 350, 600), + new Dragonfly(15, 840, 430, true, 3.5, -3, "zigzag", 350, 600), + new Dragonfly(15, 1000, 210, true, 3.5, 3, "zigzag", 350, 600), + new Dragonfly(15, 1000, 430, true, 3.5, -3, "zigzag", 350, 600), + new Dragonfly(15, 1160, 210, true, 3.5, 3, "zigzag", 350, 600), + new Dragonfly(15, 1160, 430, true, 3.5, -3, "zigzag", 350, 600) + ] + }, + { + time: 37000, + object: [new PowerUp(840, 200, 4, 0, "linear", 0, 0)] + }, + { + time: 40000, + object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 50, 200)] + }, + { + time: 43000, + object: [ + new Squid(15, 840, 210, true, 4, 2, "zigzag", 300, 700), + new Squid(15, 1140, 210, true, 4, 2, "zigzag", 300, 700) + ] + }, + { + time: 48000, + object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)] + }, + { + time: 48400, + object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)] + }, + { + time: 48800, + object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)] + }, + { + time: 49200, + object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)] + }, + { + time: 49600, + object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)] + }, + { + time: 51000, + object: [ + new Dragonfly(15, 840, 210, true, 3.5, 3, "zigzag", 350, 600), + new Dragonfly(15, 840, 430, true, 3.5, -3, "zigzag", 350, 600), + new Dragonfly(15, 1000, 210, false, 3.5, 3, "zigzag", 350, 600), + new Dragonfly(15, 1000, 430, false, 3.5, -3, "zigzag", 350, 600), + new Dragonfly(15, 1160, 210, true, 3.5, 3, "zigzag", 350, 600), + new Dragonfly(15, 1160, 430, false, 3.5, -3, "zigzag", 350, 600), + new Dragonfly(15, 1320, 210, true, 3.5, 3, "zigzag", 350, 600), + new Dragonfly(15, 1320, 430, false, 3.5, -3, "zigzag", 350, 600) + ] + }, + { + time: 57000, + object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)] + }, + { + time: 57500, + object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)] + }, + { + time: 58000, + object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)] + }, + { + time: 58500, + object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)] + }, + { + time: 59000, + object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)] + }, + { + time: 59500, + object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)] + }, + { + time: 60000, + object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)] + }, + { + time: 60500, + object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)] + }, + { + time: 61000, + object: [new Boss6()] //boss + }, + { + time: 65000, + object: [] // bg stop trigger + } +]; +isLevelDark = false; +const enemiesLvl7 = [ + { + time: 2000, + object: [ + new Kraken(25, 840, 100, true, 3, 0.05, "wave", 40, 100), + new Kraken(25, 840, 320, true, 5, 0.06, "wave", 40, 320) + ] + }, + { + time: 2500, + object: [ + new Kraken(25, 900, 100, true, 3, 0.05, "wave", 40, 100), + new Kraken(25, 840, 320, true, 5, 0.06, "wave", 40, 320) + ] + }, + { + time: 6000, + object: [ + new Kraken(25, 840, 150, true, 5, 0.06, "wave", 40, 150), + new Kraken(25, 840, 320, true, 3, 0.05, "wave", 40, 320) + ] + }, + { + time: 6500, + object: [ + new Kraken(25, 840, 150, true, 5, 0.06, "wave", 40, 150), + new Kraken(25, 900, 320, true, 3, 0.05, "wave", 40, 320) + ] + }, + { + time: 9000, + object: [ + new Kraken(25, 840, 100, true, 5, 0.06, "wave", 40, 100), + new Kraken(25, 840, 300, true, 3, -3, "zigzag", 400, 650), + new Kraken(25, 980, 300, true, 3, -3, "zigzag", 400, 650) + ] + }, + { + time: 9500, + object: [new Kraken(25, 840, 100, true, 5, 0.06, "wave", 40, 100)] + }, + { + time: 13000, + object: [ + new Beetle(15, 840, 180, true, 6, 0, "linear", 0, 0), + new Beetle(15, 980, 180, true, 6, 0, "linear", 0, 0), + new Beetle(15, 1120, 180, true, 6, 0, "linear", 0, 0), + new Beetle(15, 1260, 180, true, 6, 0, "linear", 0, 0) + ] + }, + { + time: 15000, + object: [ + new Beetle(15, 840, 310, true, 6, 0, "linear", 0, 0), + new Beetle(15, 980, 310, true, 6, 0, "linear", 0, 0), + new Beetle(15, 1120, 310, true, 6, 0, "linear", 0, 0), + new Beetle(15, 1260, 310, true, 6, 0, "linear", 0, 0) + ] + }, + { + time: 16000, + object: [ + new Beetle(15, 840, 50, true, 5, 4, "zigzag", 300, 600), + new Beetle(15, 980, 50, true, 5, 4, "zigzag", 300, 600), + new Beetle(15, 1120, 50, true, 5, 4, "zigzag", 300, 600), + new Beetle(15, 1260, 50, true, 5, 4, "zigzag", 300, 600), + new Beetle(15, 1400, 50, true, 5, 4, "zigzag", 300, 600) + ] + }, + { + time: 18500, + object: [ + new Beetle(15, 840, 180, true, 5, 0, "linear", 0, 0), + new Beetle(15, 980, 180, true, 5, 0, "linear", 0, 0), + new Beetle(15, 1120, 180, true, 5, 0, "linear", 0, 0), + new Beetle(15, 1260, 180, true, 5, 0, "linear", 0, 0) + ] + }, + { + time: 20500, + object: [ + new Triship(25, 840, 50, true, 2, 1.5, "zigzag", 450, 800), + new Triship(25, 980, 50, true, 2, 1.5, "zigzag", 450, 800), + new Triship(25, 1120, 50, true, 2, 1.5, "zigzag", 450, 800), + new Triship(25, 1260, 50, true, 2, 1.5, "zigzag", 450, 800), + new Triship(25, 1400, 50, true, 2, 1.5, "zigzag", 450, 800), + new Triship(25, 1540, 50, true, 2, 1.5, "zigzag", 450, 800) + ] + }, + { + time: 22000, + object: [ + new Triship(25, 840, 300, true, 4, -4, "zigzag", 350, 600), + new Triship(25, 980, 300, true, 4, -4, "zigzag", 350, 600), + new Triship(25, 1120, 300, true, 4, -4, "zigzag", 350, 600), + new Triship(25, 1260, 300, true, 4, -4, "zigzag", 350, 600), + new Triship(25, 1400, 300, true, 4, -4, "zigzag", 350, 600), + new Triship(25, 1540, 300, true, 4, -4, "zigzag", 350, 600) + ] + }, + { + time: 30500, + object: [ + new Shuttle(25, 1120, 50, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 980, 120, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 840, 190, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 980, 260, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 1120, 330, false, 4, 0, "linear", 0, 0) + ] + }, + { + time: 35000, + object: [ + new Shuttle(25, 1120, 50, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 980, 120, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 840, 190, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 980, 260, false, 4, 0, "linear", 0, 0), + new Shuttle(25, 1120, 330, false, 4, 0, "linear", 0, 0) + ] + }, + { + time: 40000, + object: [ + new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100), + new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170) + ] + }, + { + time: 40700, + object: [ + new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100), + new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170) + ] + }, + { + time: 41400, + object: [ + new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100), + new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170) + ] + }, + { + time: 42100, + object: [ + new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100), + new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170) + ] + }, + { + time: 42800, + object: [ + new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100), + new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170) + ] + }, + { + time: 44000, + object: [ + new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170), + new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240) + ] + }, + { + time: 44500, + object: [ + new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170), + new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240) + ] + }, + { + time: 45000, + object: [ + new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170), + new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240) + ] + }, + { + time: 45500, + object: [ + new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170), + new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240) + ] + }, + { + time: 46000, + object: [ + new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170), + new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240) + ] + }, + { + time: 52000, + object: [new Boss7()] //boss + } +]; +const enemiesLvl8 = [ + { + time: 2000, + object: [ + new Squid(70, 840, 70, false, 3, 0, "linear", 0, 0), + new Squid(70, 840, 170, false, 3, 0, "linear", 0, 0), + new Squid(70, 840, 270, false, 3, 0, "linear", 0, 0) + ] + }, + { + time: 4000, + object: [ + new Dragonfly(25, 840, 50, true, 2, 2, "zigzag", 350, 600), + new Dragonfly(25, 940, 200, true, 2, 0.04, "wave", 130, 180), + new Dragonfly(25, 1040, 50, true, 2, 2, "zigzag", 350, 600), + new Dragonfly(25, 1140, 200, true, 2, 0.06, "wave", 130, 180), + new Dragonfly(25, 1240, 50, true, 2, 2, "zigzag", 350, 600), + new Dragonfly(25, 1340, 200, true, 2, 0.03, "wave", 130, 180), + new Dragonfly(25, 1440, 50, true, 2, 2, "zigzag", 350, 600), + new Dragonfly(25, 1540, 200, true, 2, 0.05, "wave", 130, 180) + ] + }, + { + time: 12050, + object: [new Boss8()] + }, + { + time: 15700, + object: [] + // bg stop trigger component so blank object + } +]; + +function checkCollision(rect1, rect2) { + return (rect1.x < rect2.x + rect2.width && + rect1.x + rect1.width > rect2.x && + rect1.y < rect2.y + rect2.height && + rect1.height + rect1.y > rect2.y); +} + +let currentLevel = new Level(1, true); +let lastTime = 0; // previous time stamp +const logo = document.getElementById("logo"); +const playButton = document.getElementById("play"); +const exitButton = document.getElementById("exit"); +const pauseButton = document.getElementById("pause-button"); +let isMainScreen = true; // game doesn't start in main screen +let newGame = false; + +function nextLevel(num, isDark) { + currentLevel = new Level(num, isDark); +} +function gameWin() { + bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height); + mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height); + particles.forEach(particle => particle.update()); + + mainCtx.save(); + mainCtx.font = "bold 60px Silkscreen" + bgCanvas.style.background = "#282828"; + mainCtx.textAlign = "center"; + mainCtx.fillStyle = "#aad69c"; + mainCtx.fillText("You Win!", 420, 150); + mainCtx.fillText("High Score: " + playerScore.toString().padStart(5, "0"), 420, 250); + mainCtx.restore(); + exitButton.style.display = "block"; + pauseButton.style.visibility = "hidden"; +} +function gameLose() { + bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height); + mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height); + particles.forEach(particle => particle.update()); + + mainCtx.save(); + bgCanvas.style.background = "#282828"; + mainCtx.fillStyle = "#aad69c"; + mainCtx.font = "bold 60px Silkscreen" + mainCtx.textAlign = "center"; + mainCtx.fillText("Game Over!", 420, 120); + mainCtx.fillText("Score: " + playerScore.toString().padStart(5, "0"), 420, 220); + mainCtx.font = "bold 50px Silkscreen" + mainCtx.fillText("Better Luck Next Time", 420, 320); + mainCtx.restore(); + exitButton.style.display = "block"; + pauseButton.style.visibility = "hidden"; +} +function gameStart() { + playButton.style.display = "none"; + pauseButton.style.visibility = "visible"; + isMainScreen = false; +} +function mainScreen() { + mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height); + bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height); + particles.forEach(particle => particle.update()); + bgCanvas.style.background = "#282828"; + mainCtx.drawImage(logo, 0, 0, 1190, 430, 100, 100, 640, 245); +} + +function animate(timestamp) { + let deltaTime = timestamp - lastTime; + lastTime = timestamp; + + if (isMainScreen) mainScreen(); + else if (!gamePause && !gameOver) { + mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height); + bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height); + currentLevel.update(deltaTime); + } + else { + mainCtx.save(); + mainCtx.fillStyle = "red"; + mainCtx.textAlign = "center"; + mainCtx.font = "bold 60px Silkscreen" + mainCtx.fillText("|| Game Paused", 420, 260); + mainCtx.restore(); + } + if (gameOver) { + lives > 0 ? gameWin() : gameLose(); + } + + requestAnimationFrame(animate); +} +animate(0); + +playButton.style.top = `${bgCanvas.getBoundingClientRect().height - 70}`+"px"; +exitButton.style.top = `${bgCanvas.getBoundingClientRect().height - 70}`+"px"; + +exitButton.addEventListener("click", () => { + document.location.reload(); // !!! reloading this because game not loading properly second time, tried everything, still unknown bug +}); +pauseButton.addEventListener("click", () => { + gamePause = !gamePause; + if (gamePause) pauseButton.innerHTML = "Resume"; + else pauseButton.innerHTML = "Pause"; + pauseButton.blur(); +}); + +playButton.addEventListener("click", gameStart); +exitButton.addEventListener("click", gameStart); +window.addEventListener("resize", ()=>{ + playButton.style.top = `${bgCanvas.getBoundingClientRect().height - 70}`+"px"; + exitButton.style.top = `${bgCanvas.getBoundingClientRect().height - 70}`+"px"; + +}) diff --git a/static/Space-Impact-Web/img/arrowKeys.png b/static/Space-Impact-Web/img/arrowKeys.png new file mode 100644 index 0000000..2186f04 Binary files /dev/null and b/static/Space-Impact-Web/img/arrowKeys.png differ diff --git a/static/Space-Impact-Web/img/bgSprites1.png b/static/Space-Impact-Web/img/bgSprites1.png new file mode 100644 index 0000000..8df8e38 Binary files /dev/null and b/static/Space-Impact-Web/img/bgSprites1.png differ diff --git a/static/Space-Impact-Web/img/bgSprites2.png b/static/Space-Impact-Web/img/bgSprites2.png new file mode 100644 index 0000000..fe6730b Binary files /dev/null and b/static/Space-Impact-Web/img/bgSprites2.png differ diff --git a/static/Space-Impact-Web/img/bossSprites.png b/static/Space-Impact-Web/img/bossSprites.png new file mode 100644 index 0000000..7990f71 Binary files /dev/null and b/static/Space-Impact-Web/img/bossSprites.png differ diff --git a/static/Space-Impact-Web/img/buttonDown.png b/static/Space-Impact-Web/img/buttonDown.png new file mode 100644 index 0000000..0b17e7d Binary files /dev/null and b/static/Space-Impact-Web/img/buttonDown.png differ diff --git a/static/Space-Impact-Web/img/buttonFire.png b/static/Space-Impact-Web/img/buttonFire.png new file mode 100644 index 0000000..6baede6 Binary files /dev/null and b/static/Space-Impact-Web/img/buttonFire.png differ diff --git a/static/Space-Impact-Web/img/buttonLeft.png b/static/Space-Impact-Web/img/buttonLeft.png new file mode 100644 index 0000000..f2ce1a8 Binary files /dev/null and b/static/Space-Impact-Web/img/buttonLeft.png differ diff --git a/static/Space-Impact-Web/img/buttonRight.png b/static/Space-Impact-Web/img/buttonRight.png new file mode 100644 index 0000000..3532ffc Binary files /dev/null and b/static/Space-Impact-Web/img/buttonRight.png differ diff --git a/static/Space-Impact-Web/img/buttonUp.png b/static/Space-Impact-Web/img/buttonUp.png new file mode 100644 index 0000000..d0bcbfa Binary files /dev/null and b/static/Space-Impact-Web/img/buttonUp.png differ diff --git a/static/Space-Impact-Web/img/buttonX.png b/static/Space-Impact-Web/img/buttonX.png new file mode 100644 index 0000000..b2cc1c2 Binary files /dev/null and b/static/Space-Impact-Web/img/buttonX.png differ diff --git a/static/Space-Impact-Web/img/gameSprites.png b/static/Space-Impact-Web/img/gameSprites.png new file mode 100644 index 0000000..05bf799 Binary files /dev/null and b/static/Space-Impact-Web/img/gameSprites.png differ diff --git a/static/Space-Impact-Web/img/logo.png b/static/Space-Impact-Web/img/logo.png new file mode 100644 index 0000000..a0ef694 Binary files /dev/null and b/static/Space-Impact-Web/img/logo.png differ diff --git a/static/Space-Impact-Web/img/spacebar.png b/static/Space-Impact-Web/img/spacebar.png new file mode 100644 index 0000000..4cacae0 Binary files /dev/null and b/static/Space-Impact-Web/img/spacebar.png differ diff --git a/static/Space-Impact-Web/img/special.png b/static/Space-Impact-Web/img/special.png new file mode 100644 index 0000000..a48b619 Binary files /dev/null and b/static/Space-Impact-Web/img/special.png differ diff --git a/static/Space-Impact-Web/style.css b/static/Space-Impact-Web/style.css new file mode 100644 index 0000000..1a58ce1 --- /dev/null +++ b/static/Space-Impact-Web/style.css @@ -0,0 +1,256 @@ +* { + padding: 0; + margin: 0; + box-sizing: border-box; +} +body{ + background-color: #2d3641; + overflow-x: hidden; +} + +/* ----------------Nav bar --------------*/ +nav{ + width: 100%; + height: 50px; + background-color: #2b2e32; +} +#logo{ + width: 130px; + height: 50px; + padding: 5px 10px; +} + +/*----------------Main Game Div------------*/ +#game{ + position: relative; + left: 0; + right: 0; + margin: auto; +} + +canvas { + left: 50%; + transform: translateX(-50%); + max-width: 100%; + max-height: 100%; +} +#canvas-bg{ + position: absolute; +} +#canvas-main{ + position: relative; +} + /* ------------- Game-Buttons-pause and others ---------*/ +#pause-button { + display: block; + position: relative; + left: 50%; + transform: translateX(-50%); + cursor: pointer; + font-family: 'Silkscreen', monospace; + font-weight: bold; + font-size: 20px; + padding: 5px 10px; + border-radius: 5px; + visibility: hidden; +} +#pause-button:hover{ + background-color: gainsboro; +} + +.ingame-button{ + padding: 5px 10px; + font-size: 2em; + font-family: 'Silkscreen', monospace; + font-weight: bold; + cursor: pointer; + border-radius: 5px; + position: absolute; + left: 50%; + transform: translateX(-50%); + display: block; +} +.ingame-button:hover{ + background-color: gainsboro; +} +#exit { + display: none; +} + +/* ---------------------Info --------------*/ +#info{ + font-family: 'VT323', monospace; + color: white; + position: relative; + left: calc(50% - 420px); + display: inline-block; + transition: all 0.3s ease-in-out; +} +.info-head{ + padding: 0 10px; + font-size: 2rem; +} +.controls { + display: flex; + align-items: center; + justify-content: center; + gap: 30px; +} +.action { + padding: 10px; + margin-left: 20px; + font-size: 1.8rem; +} +.control-wrapper{ + display:flex; + align-items: center; +} +#arrows{ + width: 50px; +} +#space{ + width: 100px; +} +#xKey { + width: 30px; +} + +.desc-head { + padding: 5px 10px; + font-size: 2rem; +} +.desc{ + padding-left: 10px; + font-size: 1.5rem; + margin-bottom: 20px; +} + +/*-------------toggle-------------*/ +.toggle-container{ + position: relative; + display: flex; + align-items: center; + gap: 10px; + width: fit-content; + left: 50%; + transform: translateX(-50%); + color: white; + font-size: 1.5em; + font-weight: bold; + font-family: 'VT323', monospace; + margin: 10px 0px; +} +.toggle{ + width: 40px; + height: 15px; + border-radius: 10px; + background-color: lightgray; + cursor: pointer; + position: relative; +} +.toggle-ball{ + width: 20px; + height: 20px; + border-radius: 50%; + background-color: gray; + position: absolute; + top: -2.5px; + transition: all 0.3s ease-in-out; +} +.toggle-on{ + background-color: gold; +} +.toggle-ball-on{ + background-color: goldenrod; + transform: translateX(20px); +} + +/*----------- Onscreen Buttons ------------*/ +.buttons-onscreen{ + display: none; +} + +.button{ + border: none; + background-color: transparent; + cursor: pointer; + user-select: none; + -webkit-user-drag: none; +} +.button img{ + width: 60px; + height: 60px; +} + +#button-left{ + position: absolute; + left: 10px; + bottom: 120px; +} +#button-up{ + position: absolute; + left: 70px; + bottom: 180px; +} +#button-right{ + position: absolute; + left: 130px; + bottom: 120px; +} +#button-down{ + position: absolute; + left: 70px; + bottom: 60px; +} +#button-fire{ + position: absolute; + right: 70px; + bottom: 80px; +} +#button-x{ + position: absolute; + right: 10px; + bottom: 150px; +} + + +/* ------------ RWD -----------*/ +@media only screen and (max-width:840px) { + #info{ + left:0; + } +} + +@media only screen and (max-width:600px ) { + .ingame-button{ + font-size: 1em; + transform: translate(-50%, 30px); + } + .info{ + width:100%; + } + .controls{ + flex-direction: column; + } + .button img{ + width: 50px; + height: 50px; + } + + #button-left{ + left: 10px; + bottom: 110px; + } + #button-up{ + left: 60px; + bottom: 160px; + } + #button-right{ + left: 110px; + bottom: 110px; + } + #button-down{ + left: 60px; + bottom: 60px; + } +} diff --git a/static/css/styles.css b/static/css/styles.css new file mode 100644 index 0000000..ae81037 --- /dev/null +++ b/static/css/styles.css @@ -0,0 +1,496 @@ +/*===== GOOGLE FONTS =====*/ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap"); +/*===== VARIABLES CSS =====*/ +:root { + --header-height: 3rem; + --font-semi: 600; + /*===== Colores =====*/ + /*Purple 260 - Red 355 - Blue 224 - Pink 340*/ + /* HSL color mode */ + --hue-color: 224; + --first-color: hsl(var(--hue-color), 89%, 60%); + --second-color: hsl(var(--hue-color), 56%, 12%); + /*===== Fuente y tipografia =====*/ + --body-font: "Poppins", sans-serif; + --big-font-size: 2rem; + --h2-font-size: 1.25rem; + --normal-font-size: .938rem; + --smaller-font-size: .75rem; + /*===== Margenes =====*/ + --mb-2: 1rem; + --mb-4: 2rem; + --mb-5: 2.5rem; + --mb-6: 3rem; + /*===== z index =====*/ + --z-back: -10; + --z-fixed: 100; +} +@media screen and (min-width: 968px) { + :root { + --big-font-size: 3.5rem; + --h2-font-size: 2rem; + --normal-font-size: 1rem; + --smaller-font-size: .875rem; + } +} + +/*===== BASE =====*/ +*, ::before, ::after { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: var(--header-height) 0 0 0; + font-family: var(--body-font); + font-size: var(--normal-font-size); + color: var(--second-color); +} + +h1, h2, p { + margin: 0; +} + +ul { + margin: 0; + padding: 0; + list-style: none; +} + +a { + text-decoration: none; +} + +img { + max-width: 100%; + height: auto; + display: block; +} + +/*===== CLASS CSS ===== */ +.section-title { + position: relative; + margin-top: var(--mb-2); + margin-bottom: var(--mb-4); + text-align: center; +} +.section-title::after { + position: absolute; + content: ""; + width: 64px; + height: 0.18rem; + left: 0; + right: 0; + margin: auto; + top: 2rem; + background-color: var(--first-color); +} + +.section { + padding-top: 3rem; + padding-bottom: 2rem; +} + +/*===== LAYOUT =====*/ +.bd-grid { + max-width: 1024px; + display: grid; + margin-left: var(--mb-2); + margin-right: var(--mb-2); +} + +.logo { + width: 220px; + font-size: 1.5rem; + font-weight: 700; + + display: flex; + align-items: center; +} + +.l-header { + width: 100%; + position: fixed; + top: 0; + left: 0; + z-index: var(--z-fixed); + background-color: #fff; + box-shadow: 0 1px 4px rgba(146, 161, 176, 0.15); +} + +/*===== NAV =====*/ +.nav { + height: var(--header-height); + display: flex; + justify-content: space-between; + align-items: center; + font-weight: var(--font-semi); +} +@media screen and (max-width: 767px) { + .nav__menu { + position: fixed; + top: var(--header-height); + right: -100%; + width: 80%; + height: 100%; + padding: 2rem; + background-color: var(--second-color); + transition: 0.5s; + } +} +.nav__item { + margin-bottom: var(--mb-4); +} +.nav__link { + position: relative; + color: #fff; +} +.nav__link:hover { + position: relative; +} +.nav__link:hover::after { + position: absolute; + content: ""; + width: 100%; + height: 0.18rem; + left: 0; + top: 2rem; + background-color: var(--first-color); +} +.nav__logo { + color: var(--second-color); +} +.nav__toggle { + color: var(--second-color); + font-size: 1.5rem; + cursor: pointer; +} + +/*Active menu*/ +.active-link::after { + position: absolute; + content: ""; + width: 100%; + height: 0.18rem; + left: 0; + top: 2rem; + background-color: var(--first-color); +} + +.mid-title { + color: blueviolet; + margin-bottom: var(--mb-6); +} + +/*=== Show menu ===*/ +.show { + right: 0; +} + +/*===== HOME =====*/ +.home { + position: relative; + row-gap: 5rem; + padding: 4rem 0 5rem; +} +.home__data { + align-self: center; +} +/* .home__title { + font-size: var(--big-font-size); + margin-bottom: var(--mb-5); +} */ +.home__title-color { + color: var(--first-color); +} +.home__social { + display: flex; + flex-direction: column; +} +.home__social-icon { + width: max-content; + margin-bottom: var(--mb-2); + font-size: 1.5rem; + color: var(--second-color); +} +.home__social-icon:hover { + color: var(--first-color); +} + +.home__img { + display: flex; + justify-content: center; /* Centers the blob horizontally */ + align-items: center; /* Centers the blob vertically */ + overflow: hidden; /* Prevents anything from sticking out */ +} + +.home__blob { + width: 300px; /* Adjust this value to make it smaller or larger */ + height: auto; /* Maintains the correct shape */ + transition: .3s; +} + +/* Optional: Make it smaller on mobile screens */ +@media screen and (max-width: 768px) { + .home__blob { + width: 200px; + } +} + +/* ===== ABOUT =====*/ +.about__container { + row-gap: 2rem; + text-align: center; +} +.about__subtitle { + margin-bottom: var(--mb-2); +} +.about__img { + justify-self: center; +} +.about__img img { + width: 200px; + border-radius: 0.5rem; +} + +/* ===== SKILLS =====*/ +.skills__container { + row-gap: 2rem; + text-align: center; +} +.skills__subtitle { + margin-bottom: var(--mb-2); +} +.skills__text { + margin-bottom: var(--mb-4); +} +.skills__data { + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + font-weight: var(--font-semi); + padding: 0.5rem 1rem; + margin-bottom: var(--mb-4); + border-radius: 0.5rem; + box-shadow: 0px 4px 25px rgba(14, 36, 49, 0.15); +} +.skills__icon { + font-size: 2rem; + margin-right: var(--mb-2); + color: var(--first-color); +} +.skills__names { + display: flex; + align-items: center; +} +.skills__bar { + position: absolute; + left: 0; + bottom: 0; + background-color: var(--first-color); + height: 0.25rem; + border-radius: 0.5rem; + z-index: var(--z-back); +} +.skills__cpp { + width: 75%; +} +.skills__c { + width: 70%; +} +.skills__python { + width: 55%; +} +.skills__algorithms { + width: 72%; +} +.skills__img { + border-radius: 0.5rem; +} + +/* ===== WORK =====*/ +.work__container { + row-gap: 2rem; +} +.work__img { + box-shadow: 0px 4px 25px rgba(14, 36, 49, 0.15); + border-radius: 0.5rem; + overflow: hidden; +} +.work__img img { + transition: 1s; +} +.work__img img:hover { + transform: scale(1.1); +} + +/* ===== CONTACT =====*/ +.contact__input { + width: 100%; + font-size: var(--normal-font-size); + font-weight: var(--font-semi); + padding: 1rem; + border-radius: 0.5rem; + border: 1.5px solid var(--second-color); + outline: none; + margin-bottom: var(--mb-4); +} +.contact__button { + display: block; + border: none; + outline: none; + font-size: var(--normal-font-size); + cursor: pointer; + margin-left: auto; +} + +/* ===== FOOTER =====*/ +.footer { + background-color: #111827; + color: #fff; + text-align: center; + font-weight: var(--font-semi); + padding: 2rem 0; +} +.footer__title { + font-size: 2rem; + margin-bottom: var(--mb-4); +} +.footer__social { + margin-bottom: var(--mb-4); +} +.footer__icon { + font-size: 1.5rem; + color: #fff; + margin: 0 var(--mb-2); +} +.footer__copy { + font-size: var(--smaller-font-size); +} + +/* ===== MEDIA QUERIES=====*/ +@media screen and (max-width: 320px) { + .home { + row-gap: 2rem; + } + .home__img { + width: 200px; + } +} +@media screen and (min-width: 576px) { + .home { + padding: 4rem 0 2rem; + } + .home__social { + padding-top: 0; + padding-bottom: 2.5rem; + flex-direction: row; + align-self: flex-end; + } + .home__social-icon { + margin-bottom: 0; + margin-right: var(--mb-4); + } + .home__img { + width: 300px; + bottom: 25%; + } + .about__container { + grid-template-columns: repeat(2, 1fr); + align-items: center; + text-align: initial; + } + .skills__container { + grid-template-columns: 0.7fr; + justify-content: center; + column-gap: 1rem; + } + .work__container { + grid-template-columns: repeat(2, 1fr); + column-gap: 2rem; + padding-top: 2rem; + } + .contact__form { + width: 360px; + padding-top: 2rem; + } + .contact__container { + justify-items: center; + } +} +@media screen and (min-width: 768px) { + body { + margin: 0; + } + .section { + padding-top: 4rem; + padding-bottom: 3rem; + } + .section-title { + margin-bottom: var(--mb-6); + } + .section-title::after { + width: 80px; + top: 3rem; + } + + /*NavNavNAV*/ + .nav { + height: calc(var(--header-height) + 1.5rem); + } + .nav__list { + display: flex; + padding-top: 0; + } + .nav__item { + margin-left: var(--mb-6); + margin-bottom: 0; + } + .nav__toggle { + display: none; + } + .nav__link { + color: var(--second-color); + } + + + .home { + padding: 8rem 0 2rem; + } + .home__img { + width: 400px; + bottom: 10%; + } + .about__container { + padding-top: 2rem; + } + .about__img img { + width: 300px; + } + .skills__container { + grid-template-columns: repeat(2, 1fr); + column-gap: 2rem; + align-items: center; + text-align: initial; + } + .work__container { + grid-template-columns: repeat(3, 1fr); + column-gap: 2rem; + } +} +@media screen and (min-width: 992px) { + .bd-grid { + margin-left: auto; + margin-right: auto; + } + .home { + padding: 10rem 0 2rem; + } + .home__img { + width: 450px; + } +} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..0980f6a --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,74 @@ +// Nav Burger toggle +document.addEventListener('DOMContentLoaded', () => { + // Get all "navbar-burger" elements + const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); + // Add a click event on each of them + $navbarBurgers.forEach( el => { + el.addEventListener('click', () => { + // Get the target from the "data-target" attribute + const target = el.dataset.target; + const $target = document.getElementById(target); + // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" + el.classList.toggle('is-active'); + $target.classList.toggle('is-active'); + }); + }); +}); + +function hide_nav() +{ + const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); + $navbarBurgers.forEach(el => { + if(el.classList.contains('is-active')) { + el.classList.remove('is-active'); + const target = el.dataset.target; + const $target = document.getElementById(target); + if($target) { + $target.classList.remove('is-active'); + } + } + }); +} + +/*===== MENU SHOW =====*/ +const showMenu = (toggleId, navId) =>{ + const toggle = document.getElementById(toggleId), + nav = document.getElementById(navId) + + if(toggle && nav){ + toggle.addEventListener('click', ()=>{ + nav.classList.toggle('show') + }) + } +} +showMenu('nav-toggle','nav-menu') + +/*==================== SCROLL SECTIONS ACTIVE LINK ====================*/ +const sections = document.querySelectorAll('section[id]') + +const scrollActive = () =>{ + const scrollDown = window.scrollY + sections.forEach(current =>{ + const sectionHeight = current.offsetHeight, + sectionTop = current.offsetTop - 58, + sectionId = current.getAttribute('id'), + sectionsClass = document.querySelector('.nav__menu a[href*=' + sectionId + ']') + }) +} +window.addEventListener('scroll', scrollActive) + +/*===== SCROLL REVEAL ANIMATION =====*/ +const sr = ScrollReveal({ + origin: 'top', + distance: '60px', + duration: 2000, + delay: 200, + reset: true +}); + +sr.reveal('.home__data, .about__img, .skills__subtitle, .skills__text',{}); +sr.reveal('.home__img, .about__subtitle, .about__text, .skills__img',{delay: 400}); +sr.reveal('.home__social-icon',{ interval: 200}); +sr.reveal('.skills__data, .work__img, .contact__input',{interval: 200}); + + diff --git a/static/scss/styles.scss b/static/scss/styles.scss new file mode 100644 index 0000000..76ff096 --- /dev/null +++ b/static/scss/styles.scss @@ -0,0 +1,525 @@ +/*===== GOOGLE FONTS =====*/ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap'); + +/*===== VARIABLES CSS =====*/ +:root{ + --header-height: 3rem; + --font-semi: 600; + + /*===== Colores =====*/ + /*Purple 260 - Red 355 - Blue 224 - Pink 340*/ + /* HSL color mode */ + --hue-color: 224; + --first-color: hsl(var(--hue-color), 89%, 60%); + --second-color: hsl(var(--hue-color), 56%, 12%); + + /*===== Fuente y tipografia =====*/ + --body-font: 'Poppins', sans-serif; + + --big-font-size: 2rem; + --h2-font-size: 1.25rem; + --normal-font-size: .938rem; + --smaller-font-size: .75rem; + + /*===== Margenes =====*/ + --mb-2: 1rem; + --mb-4: 2rem; + --mb-5: 2.5rem; + --mb-6: 3rem; + + /*===== z index =====*/ + --z-back: -10; + --z-fixed: 100; + + @media screen and (min-width: 968px){ + --big-font-size: 3.5rem; + --h2-font-size: 2rem; + --normal-font-size: 1rem; + --smaller-font-size: .875rem; + } +} + +/*===== BASE =====*/ +*,::before,::after{ + box-sizing: border-box; +} +html{ + scroll-behavior: smooth; +} +body{ + margin: var(--header-height) 0 0 0; + font-family: var(--body-font); + font-size: var(--normal-font-size); + color: var(--second-color); +} +h1,h2,p{ + margin: 0; +} +ul{ + margin: 0; + padding: 0; + list-style: none; +} +a{ + text-decoration: none; +} +img{ + max-width: 100%; + height: auto; + display: block; +} + +/*===== CLASS CSS ===== */ +.section-title{ + position: relative; + font-size: var(--h2-font-size); + color: var(--first-color); + margin-top: var(--mb-2); + margin-bottom: var(--mb-4); + text-align: center; + + &::after{ + position: absolute; + content: ''; + width: 64px; + height: 0.18rem; + left: 0; + right: 0; + margin: auto; + top: 2rem; + background-color: var(--first-color); + } +} +.section{ + padding-top: 3rem; + padding-bottom: 2rem; +} + +/*===== LAYOUT =====*/ +.bd-grid{ + max-width: 1024px; + display: grid; + margin-left: var(--mb-2); + margin-right: var(--mb-2); +} +.l-header{ + width: 100%; + position: fixed; + top: 0; + left: 0; + z-index: var(--z-fixed); + background-color: #fff; + box-shadow: 0 1px 4px rgba(146,161,176,.15); +} + +/*===== NAV =====*/ +.nav{ + height: var(--header-height); + display: flex; + justify-content: space-between; + align-items: center; + font-weight: var(--font-semi); + + &__menu{ + @media screen and (max-width: 767px){ + position: fixed; + top: var(--header-height); + right: -100%; + width: 80%; + height: 100%; + padding: 2rem; + background-color: var(--second-color); + transition: .5s; + } + } + &__item{ + margin-bottom: var(--mb-4); + } + &__link{ + position: relative; + color: #fff; + + &:hover{ + position: relative; + + &::after{ + position: absolute; + content: ''; + width: 100%; + height: 0.18rem; + left: 0; + top: 2rem; + background-color: var(--first-color); + } + } + } + &__logo{ + color: var(--second-color); + } + &__toggle{ + color: var(--second-color); + font-size: 1.5rem; + cursor: pointer; + } +} +/*Active menu*/ +.active-link::after{ + position: absolute; + content: ''; + width: 100%; + height: 0.18rem; + left: 0; + top: 2rem; + background-color: var(--first-color); +} + +/*=== Show menu ===*/ +.show{ + right: 0; +} + +/*===== HOME =====*/ +.home{ + position: relative; + row-gap: 5rem; + padding: 4rem 0 5rem; + + &__data{ + align-self: center; + } + &__title{ + font-size: var(--big-font-size); + margin-bottom: var(--mb-5); + + &-color{ + color: var(--first-color); + } + } + &__social{ + display: flex; + flex-direction: column; + &-icon{ + width: max-content; + margin-bottom: var(--mb-2); + font-size: 1.5rem; + color: var(--second-color); + + &:hover{ + color: var(--first-color); + } + } + } + + &__img{ + position: absolute; + right: 0; + bottom: 0; + width: 260px; + } + &__blob{ + fill: var(--first-color); + + &-img{ + width: 360px; + } + } +} + +/*BUTTONS*/ +.button{ + display: inline-block; + background-color: var(--first-color); + color: #fff; + padding: .75rem 2.5rem; + font-weight: var(--font-semi); + border-radius: .5rem; + transition: .3s; + + &:hover{ + box-shadow: 0px 10px 36px rgba(0,0,0,.15); + } +} + +/* ===== ABOUT =====*/ +.about{ + &__container{ + row-gap: 2rem; + text-align: center; + } + &__subtitle{ + margin-bottom: var(--mb-2); + } + + &__img{ + justify-self: center; + + & img{ + width: 200px; + border-radius: .5rem; + } + } +} + +/* ===== SKILLS =====*/ +.skills{ + &__container{ + row-gap: 2rem; + text-align: center; + } + &__subtitle{ + margin-bottom: var(--mb-2); + } + &__text{ + margin-bottom: var(--mb-4); + } + &__data{ + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + font-weight: var(--font-semi); + padding: .5rem 1rem; + margin-bottom: var(--mb-4); + border-radius: .5rem; + box-shadow: 0px 4px 25px rgba(14, 36, 49, 0.15); + } + &__icon{ + font-size: 2rem; + margin-right: var(--mb-2); + color: var(--first-color); + } + &__names{ + display: flex; + align-items: center; + } + &__bar{ + position: absolute; + left: 0; + bottom: 0; + background-color: var(--first-color); + height: .25rem; + border-radius: .5rem; + z-index: var(--z-back); + } + &__html{ + width: 95%; + } + &__css{ + width: 85%; + } + &__js{ + width: 65%; + } + &__ux{ + width: 85%; + } + &__img{ + border-radius: .5rem; + } +} +/* ===== WORK =====*/ +.work{ + &__container{ + row-gap: 2rem; + } + &__img{ + box-shadow: 0px 4px 25px rgba(14, 36, 49, 0.15); + border-radius: .5rem; + overflow: hidden; + + & img{ + transition: 1s; + + &:hover{ + transform: scale(1.1); + } + } + } +} + +/* ===== CONTACT =====*/ +.contact{ + &__input{ + width: 100%; + font-size: var(--normal-font-size); + font-weight: var(--font-semi); + padding: 1rem; + border-radius: .5rem; + border: 1.5px solid var(--second-color); + outline: none; + margin-bottom: var(--mb-4); + } + &__button{ + display: block; + border: none; + outline: none; + font-size: var(--normal-font-size); + cursor: pointer; + margin-left: auto; + } +} + +/* ===== FOOTER =====*/ +.footer{ + background-color: var(--second-color); + color: #fff; + text-align: center; + font-weight: var(--font-semi); + padding: 2rem 0; + + &__title{ + font-size: 2rem; + margin-bottom: var(--mb-4); + } + &__social{ + margin-bottom: var(--mb-4); + } + &__icon{ + font-size: 1.5rem; + color: #fff; + margin: 0 var(--mb-2); + } + &__copy{ + font-size: var(--smaller-font-size); + } +} + +/* ===== MEDIA QUERIES=====*/ +@media screen and (max-width: 320px){ + .home{ + row-gap: 2rem; + + &__img{ + width: 200px; + } + } +} + +@media screen and (min-width: 576px){ + .home{ + padding: 4rem 0 2rem; + &__social{ + padding-top: 0; + padding-bottom: 2.5rem; + flex-direction: row; + align-self: flex-end; + + &-icon{ + margin-bottom: 0; + margin-right: var(--mb-4); + } + } + &__img{ + width: 300px; + bottom: 25%; + } + } + + .about__container{ + grid-template-columns: repeat(2,1fr); + align-items: center; + text-align: initial; + } + + .skills__container{ + grid-template-columns: .7fr; + justify-content: center; + column-gap: 1rem; + } + + .work__container{ + grid-template-columns: repeat(2,1fr); + column-gap: 2rem; + padding-top: 2rem; + } + + .contact{ + &__form{ + width: 360px; + padding-top: 2rem ; + } + &__container{ + justify-items: center; + } + } +} + +@media screen and (min-width: 768px){ + body{ + margin: 0; + } + .section{ + padding-top: 4rem; + padding-bottom: 3rem; + } + + .section-title{ + margin-bottom: var(--mb-6); + + &::after{ + width: 80px; + top: 3rem; + } + } + + .nav{ + height: calc(var(--header-height) + 1.5rem); + &__list{ + display: flex; + padding-top: 0; + } + &__item{ + margin-left: var(--mb-6); + margin-bottom: 0; + } + &__toggle{ + display: none; + } + &__link{ + color: var(--second-color); + } + } + .home{ + padding: 8rem 0 2rem; + + &__img{ + width: 400px; + bottom: 10%; + } + } + + .about{ + &__container{ + padding-top: 2rem; + } + &__img{ + & img{ + width: 300px; + } + } + } + .skills__container{ + grid-template-columns: repeat(2,1fr); + column-gap: 2rem; + align-items: center; + text-align: initial; + } + .work__container{ + grid-template-columns: repeat(3,1fr); + column-gap: 2rem; + } +} + +@media screen and (min-width: 992px){ + .bd-grid{ + margin-left: auto; + margin-right: auto; + } + .home{ + padding: 10rem 0 2rem; + + &__img{ + width: 450px; + } + } +} diff --git a/templates/2048.html b/templates/2048.html new file mode 100644 index 0000000..e106697 --- /dev/null +++ b/templates/2048.html @@ -0,0 +1,104 @@ + + + + + 2048 + + + + + + + + + + + + + + + + + +
+
+

2048

+
+
0
+
0
+
+
+ +
+

Join the numbers and get to the 2048 tile!

+ New Game + Exit +
+ +
+
+

+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ +

+ How to play: Use your arrow keys to move the tiles. When two tiles with the same number touch, they merge into one! +

+
+

+ Note: This site is the official version of 2048. You can play it on your phone via http://git.io/2048. All other apps or sites are derivatives or fakes, and should be used with caution. +

+
+

+ Created by Gabriele Cirulli. Based on 1024 by Veewo Studio and conceptually similar to Threes by Asher Vollmer. +

+
+ + + + + + + + + + + + + + diff --git a/templates/Space-Impact-Web.html b/templates/Space-Impact-Web.html new file mode 100644 index 0000000..b6cb75b --- /dev/null +++ b/templates/Space-Impact-Web.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + + Space Impact Web + + + + + +
+ Your browswer doesn't support HTML Canvas + + + + +
+
+ On-screen Buttons: +
+
+
+
+
+

Controls:

+
+
+
Move:
+ + arrow keys +
+
+
Fire:
+ + spacebar +
+
+
Special:
+ + X key +
+
+

Description:

+

Shoot down the enemy ships and survive all 8 levels.

+
+
+ + + + + + +
+ + + + + + + diff --git a/templates/games.html b/templates/games.html new file mode 100644 index 0000000..f567538 --- /dev/null +++ b/templates/games.html @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + +
+ +
+
+
+
+ +
+
+
+
+
+ + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..9b81e40 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + My Portfolio + + + + + +
+ +
+
+
+
+

+ Hi, I'm
+ Ace Lorenz Gallo +

+

+ Computer Engineering Student. +

+ +
+
+ + + + + + + + + +
+
+
+
+ + +
+ +
+
+ +
+ +
+

I’m Ace Lorenz Gallo,

+

I am a Computer Engineering student with a strong passion for coding and problem-solving. I enjoy tackling complex challenges and continuously improving my skills, which has led me to win a coding competition. I’m currently focused on expanding my knowledge in software development and exploring new technologies to grow as a developer. My goal is to become a successful software engineer, and I bring dedication, curiosity, and a driven mindset to every project I take on.

+
+
+
+ + +
+ + +
+
+

My Skills

+

I work with a range of programming languages, with my strongest skills centered around problem-solving and coding.

+
+
+ C++ +
+
+
+ 75% +
+
+
+
+ C +
+
+
+ 70% +
+
+ +
+
+ PYTHON +
+
+
+ 55% +
+
+ +
+
+ ALGORITHMS +
+
+
+ 72% +
+
+
+
+ +
+
+
+ + +
+ + + +
+
+ + + + + + + + + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..49e3116 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + +
+ +
+
+
+
+
+
+
+ +
+ + + + + + +
+
+
+ +
+ + + + + + +
+
+
+ + {% if error %} +

Wrong Password or Username

+ {% endif %} +
+
+
+
+
+
+
+
+ + + \ No newline at end of file