Advertisement
Guest User

Chapter 4: Tank Game Course

a guest
May 21st, 2022
55
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. var TankGame = (function() {
  2.   var TARGET_WIDTH = 801; // Desired width
  3.   var TARGET_HEIGHT = 601;
  4.   var WIDTH; // Actual width of game, scaled to fit screen
  5.   var HEIGHT;
  6.   var GUI_HEIGHT = 150;
  7.   var DEBUG = false;
  8.   // WASD
  9.   var P1_UP = 87;
  10.   var P1_DOWN = 83;
  11.   var P1_LEFT = 65;
  12.   var P1_RIGHT = 68;
  13.   var P1_FIRE = 49;
  14.  
  15.   // Arrow keys
  16.   var P2_UP = 38;
  17.   var P2_DOWN = 40;
  18.   var P2_LEFT = 37;
  19.   var P2_RIGHT = 39;
  20.   var P2_FIRE = 189;
  21.  
  22.   // Other settings
  23.   var TANK_SIZE = 15;
  24.   var TANK_SPEED = 1;
  25.   var TANK_TURN_SPEED = 5;
  26.   var WALL_WIDTH = 2;
  27.   var CELL_SIZE = 50;
  28.   var RESET_COUNTER_MAX = 200;
  29.   var EPSILON = 0.001;
  30.  
  31.  
  32.   var MAX_DIST_FOR_COLLISIONS = 2; // (this is multiplied by CELL_SIZE)
  33.   var TANK_P1, TANK_P2;
  34.   var CELLS_X, CELLS_Y;
  35.   var CANVAS, CTX, KEYSTATE, GAME_OBJECTS;
  36.   var PRERENDERED_CANVAS, PRERENDERED_CTX, PRERENDERED_REDRAW_NEEDED;
  37.   var GUI_REDRAW_NEEDED;
  38.   var END_ROUND = false;
  39.   var RESET_COUNTER;
  40.  
  41.   var P1 = 1;
  42.   var P2 = 2;
  43.   var P1_SCORE = 0;
  44.   var P2_SCORE = 0;
  45.  
  46.  
  47.  
  48.  
  49.   /*
  50.   ===============================================================================
  51.   -------------------------------------CLASSES-----------------------------------
  52.   ===============================================================================
  53.   */
  54.  
  55.   function deg2rad(degrees) {
  56.     return degrees * (Math.PI/180);
  57.   }
  58.  
  59.   class Vector2d {
  60.     constructor(x, y) {
  61.       if (typeof x == 'undefined' || typeof  y == 'undefined') {
  62.         throw "Invalid arguments";
  63.       }
  64.       this.x = x;
  65.       this.y = y;
  66.     }
  67.  
  68.     rotate(radians) {
  69.       // Rotates coordinates counterclockwise
  70.       //radians=-radians
  71.       if (radians != 0) {
  72.         var x = this.x;
  73.         var y = this.y;
  74.         this.x = x * Math.cos(radians) - y * Math.sin(radians);
  75.         this.y = x * Math.sin(radians) + y * Math.cos(radians);
  76.       }
  77.       return this;
  78.     }
  79.  
  80.     add(vector) {
  81.       if (vector instanceof Vector2d) {
  82.         this.x += vector.x;
  83.         this.y += vector.y;
  84.       }
  85.       else throw "Invalid argument";
  86.       return this;
  87.     }
  88.  
  89.     subtract(vector) {
  90.       if (vector instanceof Vector2d) {
  91.         this.x -= vector.x;
  92.         this.y -= vector.y;
  93.       }
  94.       else throw "Invalid argument";
  95.       return this;
  96.     }
  97.  
  98.     multiply(value) {
  99.       if (isNaN(value) === false) {
  100.         this.x *= value;
  101.         this.y *= value;
  102.       }
  103.       else throw "Invalid argument";
  104.       return this;
  105.     }
  106.  
  107.     get_dot_product(other) {
  108.       if (other instanceof Vector2d) return this.x*other.x + this.y*other.y;
  109.       else throw "Invalid argument";
  110.     }
  111.  
  112.     get_unit_vector() {
  113.       var c = this.get_magnitude();
  114.       var unit_vec = new Vector2d(this.x/c, this.y/c);
  115.       return unit_vec;
  116.     }
  117.  
  118.     get_right_normal() {
  119.       return new Vector2d(this.y, -this.x);
  120.     }
  121.  
  122.     get_magnitude() {
  123.       return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
  124.     }
  125.  
  126.     get_magnitude_squared() {
  127.       return Math.pow(this.x, 2) + Math.pow(this.y, 2);
  128.     }
  129.  
  130.     get_inverted() {
  131.       return new Vector2d(-this.x, -this.y);
  132.     }
  133.  
  134.     clone() {
  135.       return new Vector2d(this.x, this.y);
  136.     }
  137.  
  138.     reflect(vector) {
  139.       if (vector instanceof Vector2d) {
  140.         var vec2 = vector.clone(); // Avoid modifying given vector
  141.         vec2.multiply(vec2.get_dot_product(this));
  142.         vec2.multiply(2);
  143.         this.subtract(vec2);
  144.         return this;
  145.       }
  146.       else throw "Invalid argument";
  147.     }
  148.   };
  149.  
  150.   class GameObject {
  151.     constructor(x, y, width, height, movable = false) {
  152.       this.destructible = true; // Can this object be destroyed?
  153.       this.max_hp = 1000;
  154.       this.hp = this.max_hp;
  155.       this.pos = new Vector2d(x, y);
  156.       this.width = width;
  157.       this.height = height;
  158.       this.rotation = 0; // In degrees
  159.       this.velocity = new Vector2d(0, 0);
  160.       this.movable = movable; // Can be moved by collisions
  161.       this.color = {};
  162.       this.color.r = 0;
  163.       this.color.g = 0;
  164.       this.color.b = 0;
  165.       this.verts = [];
  166.       this.rotated_verts = [];
  167.       this.ignored_collision_objs = [this];
  168.       this.circle = false;
  169.       this.radius = 0;
  170.       this.unstoppable = false; // Lets object move through destructible objects
  171.  
  172.       var w = this.width / 2;
  173.       var h = this.height / 2;
  174.       this.verts.push(new Vector2d(-w, -h));
  175.       this.verts.push(new Vector2d(-w, h));
  176.       this.verts.push(new Vector2d(w, h));
  177.       this.verts.push(new Vector2d(w, -h));
  178.  
  179.       GAME_OBJECTS.push(this);
  180.       if (this.movable == false) {
  181.         PRERENDERED_REDRAW_NEEDED = true;
  182.       }
  183.  
  184.     }
  185.  
  186.     damage(amount) {
  187.       if (this.destructible && this.hp > 0) {
  188.         if (amount > 0) {
  189.           this.hp -= amount;
  190.           this.color_by_damage();
  191.         }
  192.         else console.log("WARNING: Attempted to damage by negative amount!!!");
  193.       }
  194.     }
  195.  
  196.     set_unstoppable(value) {
  197.       this.unstoppable = value;
  198.     }
  199.  
  200.     set_destructible(value) {
  201.       this.destructible = value;
  202.     }
  203.  
  204.     color_by_damage() {
  205.       var red = Math.round(255 - (255 * (this.hp/this.max_hp)));
  206.       this.color.r = red;
  207.     }
  208.  
  209.     rotate(degrees) {
  210.       if (degrees != 0) {
  211.         this.rotation += degrees;
  212.         while (this.rotation < 0) this.rotation += 360;
  213.         while (this.rotation > 360) this.rotation -= 360;
  214.         this.calculate_rotated_verts();
  215.       }
  216.     }
  217.  
  218.     move(vector) {
  219.       if (vector instanceof Vector2d) {
  220.         this.pos.add(vector);
  221.       }
  222.       else throw "Invalid argument";
  223.     }
  224.  
  225.     get_rect(local_coordinates) {
  226.       var x = this.pos.x;
  227.       var y = this.pos.y;
  228.       var w = this.width / 2;
  229.       var h = this.height / 2;
  230.       if (local_coordinates) return [-w, -h, w*2, h*2];
  231.       else return [x-w, y-h, w*2, h*2];
  232.     }
  233.  
  234.     calculate_rotated_verts() {
  235.       var radians = deg2rad(this.rotation)
  236.       this.rotated_verts = [];
  237.       for (var vert of this.verts) {
  238.         this.rotated_verts.push(vert.clone().rotate(radians));
  239.       }
  240.       this.rotated_verts;
  241.     }
  242.  
  243.     get_verts() {
  244.       if (this.rotated_verts.length === 0) this.calculate_rotated_verts();
  245.       return this.rotated_verts;
  246.     }
  247.  
  248.     on_collision(obj) {
  249.       /*
  250.         Default GameObjects do nothing on collision. Child casses can use this
  251.         for example to damage or give powerups to tanks on collision.
  252.       */
  253.       //console.log(typeof(obj));
  254.     }
  255.  
  256.     destroy() {
  257.       var i = GAME_OBJECTS.indexOf(this);
  258.       delete GAME_OBJECTS[i];
  259.       if (this.movable == false) {
  260.         PRERENDERED_REDRAW_NEEDED = true;
  261.       }
  262.     }
  263.  
  264.     update() {
  265.       /*
  266.         Moves GameObject by its velocity and checks for collisions.
  267.         If collisions are found, attempts to solve them by moving itself.
  268.       */
  269.       if (this.hp <= 0) {
  270.         this.destroy();
  271.         return;
  272.       }
  273.       if (this.movable) {
  274.         // Move by velocity, if it has any
  275.         this.move(this.velocity);
  276.  
  277.         // Get all colliding objects
  278.         var collisions = GetCollisions(this);
  279.  
  280.         var attempts = 0; // Track attempts to prevent infinite loops
  281.         var done = false;
  282.         var max_attempts = 5;
  283.         while(done === false && attempts < max_attempts) {
  284.           var prev_pos = this.pos.clone();
  285.           var prev_velo = this.velocity.clone();
  286.           done = true;
  287.           if (collisions.length > 0) {
  288.             //console.log(collisions);
  289.           }
  290.           for (var i = 0; i < collisions.length; i++) {
  291.             // Loop over all collisions one at a time
  292.             var collision = collisions[i];
  293.             var obj1 = collision.obj1; // This object (redundant)
  294.             var obj2 = collision.obj2; // The colliding object
  295.             var dir = collision.direction.clone();
  296.             if (this.unstoppable && obj2.destructible == true) {
  297.               // Don't attempt to solve collisions for unstoppable objects
  298.               // unstoppable objects can go through almost anything.
  299.               obj1.on_collision(obj2);
  300.               obj2.on_collision(obj1);
  301.               break;
  302.             }
  303.             this.move(dir.clone().multiply(collision.magnitude));
  304.             this.velocity.reflect(dir);
  305.  
  306.  
  307.             // Get all new collisions after moving
  308.             var new_collisions = GetCollisions(this);
  309.             if (new_collisions.length === 0) {
  310.               // Success! No new collisions found
  311.               obj1.on_collision(obj2);
  312.               obj2.on_collision(obj1);
  313.               break; // Don't check any other collisions
  314.             }
  315.             else if (i < collisions.length-1) {
  316.               // Fail! Move back to original position and attempt to solve the next collision
  317.               this.pos = prev_pos;
  318.               this.velocity = prev_velo;
  319.             }
  320.             else {
  321.               // Fail! No collisions remaining. Try to resolve collisions from the new position
  322.               obj1.on_collision(obj2);
  323.               obj2.on_collision(obj1);
  324.               collisions = new_collisions;
  325.               done = false;
  326.               attempts++;
  327.             }
  328.           }
  329.  
  330.         }
  331.         if(attempts > 1) {
  332.           console.log("Attempted to resolve collisions " + attempts + " times");
  333.         }
  334.       }
  335.     }
  336.  
  337.     draw(context) {
  338.       context.save();
  339.       let color = "rgb(" + this.color.r + "," + this.color.g + "," + this.color.b + ")";
  340.       context.fillStyle = color;
  341.       context.translate(this.pos.x, this.pos.y);
  342.       context.beginPath();
  343.       if (this.circle === true) {
  344.         context.arc(0, 0, this.radius, 0, 2 * Math.PI);
  345.       }
  346.       else {
  347.         var verts = this.get_verts();
  348.         context.moveTo(verts[0].x, verts[0].y);
  349.         for (var vert of verts) {
  350.           context.lineTo(vert.x, vert.y);
  351.         }
  352.         context.lineTo(vert.x, vert.y);
  353.       }
  354.       context.fill();
  355.       context.restore();
  356.     }
  357.   };
  358.  
  359.   var GunTypes = {
  360.     // Enumeration for different types of guns
  361.     'normal' : 1,
  362.     'machinegun' : 2,
  363.     'heavy' : 3,
  364.   }
  365.  
  366.   class Gun {
  367.     /*
  368.       Handles the firing of tank guns.
  369.     */
  370.     constructor(tank) {
  371.       this.bullet_size = 5;
  372.       this.bullet_speed = 1.5;
  373.       this.ammo = 1000; // Max shots for current gun
  374.       this.clip = 5; // Max simultaenous shots
  375.       this.fire_delay = 0.5;
  376.       this.last_shot = Date.now();
  377.       this.tank = tank; // Used for ignoring collisions when firing
  378.       this.type = GunTypes.normal;
  379.       this.damage_amount = 4;
  380.       this.randomize_direction = false;
  381.     }
  382.  
  383.     fire(x, y, direction) {
  384.       var bullet;
  385.       if (this.randomize_direction) {
  386.         direction += Math.random() * 10 - 5; // Add a random offset of +-5 degrees
  387.       }
  388.       if (this.clip > 0 && this.ammo > 0) {
  389.         if (Date.now() - this.last_shot > this.fire_delay * 1000) {
  390.           GUI_REDRAW_NEEDED = true; // GUI has info about remaining ammo
  391.           this.clip--;
  392.           this.ammo--;
  393.           this.last_shot = Date.now();
  394.           bullet = new Bullet(x, y, direction, this.damage_amount, this, this.bullet_size, this.bullet_speed);
  395.         }
  396.       }
  397.       return bullet;
  398.     }
  399.  
  400.     reload() {
  401.       // Adds one bullet to clip.
  402.       this.clip++;
  403.     }
  404.  
  405.     get_name() {
  406.       return "40mm gun";
  407.     }
  408.  
  409.     get_ammo_str() {
  410.       switch (this.type) {
  411.         case GunTypes.normal:
  412.           return "infinite";
  413.           break;
  414.         default:
  415.           return this.ammo;
  416.       }
  417.     }
  418.   };
  419.  
  420.   class Machinegun extends Gun {
  421.     constructor(tank) {
  422.       super(tank);
  423.       this.bullet_size = 2;
  424.       this.bullet_speed = 2;
  425.       this.ammo = 75;
  426.       this.clip = 25;
  427.       this.fire_delay = 0.1;
  428.       this.damage_amount = 1;
  429.       this.randomize_direction = true;
  430.       this.type = GunTypes.machinegun;
  431.     }
  432.  
  433.     get_name() {
  434.       return ".50 caliber machine gun";
  435.     }
  436.   };
  437.  
  438.   class Heavygun extends Gun {
  439.     constructor(tank) {
  440.       super(tank);
  441.       this.bullet_size = 20;
  442.       this.bullet_speed = 1.5;
  443.       this.ammo = 3;
  444.       this.clip = 3;
  445.       this.fire_delay = 1;
  446.       this.damage_amount = 1000;
  447.       this.randomize_direction = false;
  448.       this.type = GunTypes.heavy;
  449.     }
  450.  
  451.     fire(x, y, direction) {
  452.       // Override firing to make the bullet unstoppable
  453.       var bullet = super.fire(x, y, direction);
  454.       if (bullet)
  455.         bullet.set_unstoppable(true);
  456.     }
  457.  
  458.     get_name() {
  459.       return "155mm heavy gun";
  460.     }
  461.   };
  462.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement