main 230dfd2005f0 cached
20 files
30.0 KB
9.2k tokens
57 symbols
1 requests
Download .txt
Repository: chenhunghan/lens-ext-invaders
Branch: main
Commit: 230dfd2005f0
Files: 20
Total size: 30.0 KB

Directory structure:
gitextract_hjnyx861/

├── .eslintrc
├── .github/
│   └── workflows/
│       └── pr_release.yaml
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── components/
│   ├── Alien.ts
│   ├── AlienBullet.ts
│   ├── Bullet.ts
│   ├── ClusterPage.tsx
│   ├── Game.tsx
│   ├── Invaders.ts
│   ├── Particle.ts
│   ├── Player.ts
│   └── PlayerBullet.ts
├── main.ts
├── package.json
├── renderer.tsx
├── tsconfig.json
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc
================================================
{
    // `root: true` prevent eslint to combine .eslintrc in parent folder with this one
    // ext developer can remove this line if desire to let eslint to traverses eslint config 
    // see <http://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy>
    "root": true,
    // require to install @typescript-eslint/parser + typescript
    "parser": "@typescript-eslint/parser",
    // Tells ESLint to load the plugin. this allows you to use 
    // the rules within your codebase
    "plugins": [
        "@typescript-eslint",
        "react-hooks"
    ],
    "extends": [
        // ESLint's inbuilt "recommended" config
        "eslint:recommended",
        // require to install @typescript-eslint/eslint-plugin
        // just like eslint:recommended, except it only turns on rules from TypeScript-specific plugin
        "plugin:@typescript-eslint/recommended"
    ],
    "env": {
      // this tells eslint it's safe to use `require`/`module.exports`/`__dirname`...
      // that are only legal in node.js, we use these in `webpack.config.js`
      "node": true,
      "jest": true
    },
    "rules": {
      "indent": ["error", 2],
      "linebreak-style": ["error", "unix"],
      "quotes": ["error", "double"],
      "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
     "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
    }
}


================================================
FILE: .github/workflows/pr_release.yaml
================================================
on:
  pull_request:
    types: [closed]

jobs:
  versioning:
    name: Auto versioning/tag => Create a release with .tgz
    # github does not have PR event type 'merged' for github action
    # so we use 'closed' instead, and add condition:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest

    steps:

    - uses: actions/checkout@v2
      with:
        persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
        fetch-depth: 0 # otherwise, you will failed to push refs to dest repo

    # same node.js version as in package.json > engines
    - uses: actions/setup-node@v2-beta
      with:
        node-version: '12'
    
    # npm version patch will automatically git commit/tag
    - name: Run 'npm version patch'
      run: |
        git config --local user.email "github-action@github.com"
        git config --local user.name "Github Action Bot"
        npm version patch

    - name: Push commits
      uses: ad-m/github-push-action@master
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        branch: ${{ github.ref }}
        tags: true

    - name: Install dependencies and build extension
      run: |
        yarn
        yarn build

    - name: Create tarball from package
      run: |
        npm pack

    - name: Get version
      id: version
      run: |
        VERSION=$(node -p -e "require('./package.json').version")
        echo "::set-output name=value::$VERSION"

    - name: Create GitHub release
      uses: actions/create-release@v1
      id: create_release
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: "v${{ steps.version.outputs.value }}"
        release_name: "v${{ steps.version.outputs.value }}"
        draft: false

    - name: Upload GitHub assets
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: lensapp-lens-ext-invaders-${{ steps.version.outputs.value }}.tgz
        asset_name: lensapp-lens-ext-invaders-${{ steps.version.outputs.value }}.tgz
        asset_content_type: application/octet-stream


================================================
FILE: .gitignore
================================================
node_modules
dist
.DS_Store
lens.log
*.tgz


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 chh

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.


================================================
FILE: README.md
================================================
# Start Invading Clusters

⚠️⚠️⚠️
Please don't play this game on your production cluster. 😅
Alien 👾 = pod on cluster.
⚠️⚠️⚠️

![screencast](screencast_256.gif)

## Install

```bash
cd ~ && git clone https://github.com/chenhunghan/lens-ext-invaders.git
cd lens-ext-invaders && yarn && yarn build
ln -s ~/lens-ext-invaders ~/.k8slens/extensions/lens-ext-invaders
```

OR

Copy the `.tgz` link in [Release](https://github.com/chenhunghan/lens-ext-invaders/releases)

Add install the extension by paste the link in Lens -> Extension

![intstall](https://i.imgur.com/oFzyvGG.png)


## Acknowledgements

This project is largely inspired by <https://codeheir.com/2019/03/17/how-to-code-space-invaders-1978-7/>


================================================
FILE: babel.config.js
================================================
// the babel config is for jest to parse jsx in test/spec files
const presets = [
  "@babel/preset-env",
  "@babel/preset-react",
  "@babel/preset-typescript"
];

module.exports = { presets };


================================================
FILE: components/Alien.ts
================================================
import p5 from "p5";
import { K8sApi } from "@k8slens/extensions";
import { AlienImages } from "./Invaders";

class Alien {

  x: number
  y: number
  alienImages: AlienImages
  p5: p5
  pod: K8sApi.Pod;
  hits: number;
  width: number;
  height: number;

  constructor(x: number, y: number, alienImages: AlienImages, p5: p5, pod: K8sApi.Pod) {
    this.x = x;
    this.y = y;
    this.alienImages = alienImages;
    this.p5 = p5;
    this.pod = pod
    this.hits = 0;
    this.width = alienImages.greenAlien.width / 20;
    this.height = alienImages.greenAlien.height / 20;
  }

  bulletHit(): number {
    this.pod.delete();
    return this.hits += 1;
  }

  draw(): void {
    let image: p5.Image;
    this.p5.textSize(8);

    const status = this.pod.getStatus();
    if (this.pod.metadata.deletionTimestamp || this.hits > 0) {
      image = this.alienImages.redAlien;
    } else if (status === "Running") {
      image = this.alienImages.greenAlien;
    } else if (status === "Pending") {
      image = this.alienImages.yellowAlien
    } else {
      image = this.alienImages.orangeAlien
    }
    image && this.p5.image(image, this.x, this.y, this.width, this.height);

    const name = this.pod.getName();
    if (name && name.length > 7) {
      this.p5.text(`${name.substring(0, 7)}..`, this.x, this.y + 40);
    } else {
      this.p5.text(`${name}`, this.x, this.y + 40);
    }
  }
}

export default Alien


================================================
FILE: components/AlienBullet.ts
================================================
import Bullet from "./Bullet";
import p5 from "p5";

class AlienBullet extends Bullet {
  x: number;
  y: number;

  constructor(x: number, y: number, p5: p5) {
    super(x, y, p5);
  }

  update(): void {
    this.y += 2;
  }
}

export default AlienBullet


================================================
FILE: components/Bullet.ts
================================================
import p5 from "p5";
import Player from "./Player";

class Bullet {
  p5: p5;
  x: number;
  y: number;

  constructor(x: number, y: number, p5: p5) {
    this.x = x;
    this.y = y;
    this.p5 = p5;
  }

  isOffScreen(): boolean {
    return this.y <= 0;
  }
    
  draw(): void {
    this.p5.fill(255);
    this.p5.rect(this.x, this.y, 3, 15);
  }

  hasHit(player: Player): boolean {
    return this.p5.dist(this.x, this.y, player.x + 10, player.y + 10) < 20;
  }
}

export default Bullet;


================================================
FILE: components/ClusterPage.tsx
================================================
import React, { useEffect, useState } from "react"
import Game from "./Game"
import { K8sApi } from "@k8slens/extensions";

const ClusterPage = (): JSX.Element => {

  console.info("🔥 Cluster page rendered");

  const [podsStore] = useState(K8sApi.apiManager.getStore(K8sApi.podsApi))

  useEffect(() => {
    const ensure = async () => {
      if (!podsStore.isLoaded) {
        await podsStore.loadAll();
        podsStore.subscribe();
      }
    }
    ensure();
  }, [podsStore])

  return (
    <div className="TabLayout">
      <main>
        <div className="ItemListLayout flex column">
          <div className="header flex gaps align-center">
            <h5>Space Invaders</h5>
          </div>
          <Game pods={podsStore.items} />
        </div>
      </main>
    </div>
  )
}

export default ClusterPage


================================================
FILE: components/Game.tsx
================================================
import React, { memo, useState, useLayoutEffect } from "react"
import { K8sApi } from "@k8slens/extensions";
import p5 from "p5";
import Invaders from "./Invaders";
import Player from "./Player";
import { IObservableArray } from "mobx";
import Particle from "./Particle";
import { configMapsStore } from "@k8slens/extensions/dist/src/renderer/components/+config-maps/config-maps.store";

type Props = { pods: IObservableArray<K8sApi.Pod> }

let keyboardEvenListener: (ev: KeyboardEvent) => void
let mouseEvenListener: (ev: Event) => void;
let windowResizeEvenListener: (ev: Event) => void;

// an array to add multiple particles
const particles: Array<Particle> = [];

const sketch = (pods: IObservableArray<K8sApi.Pod>) => (p: p5) => {
  let invaders: Invaders;
  let player: Player;
  let enableParticles = false;
  let lastMouseTarget: EventTarget;
  let started = false;
  const gameImage = p.loadImage("https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/11a10a01-ac23-4fea-ad5a-b51f53084159/d5eu5dw-11a48688-3762-4f92-ba46-ebf94abe51b1.png/v1/fill/w_900,h_389,strp/space_invaders_logo__us__by_ringostarr39_d5eu5dw-fullview.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD0zODkiLCJwYXRoIjoiXC9mXC8xMWExMGEwMS1hYzIzLTRmZWEtYWQ1YS1iNTFmNTMwODQxNTlcL2Q1ZXU1ZHctMTFhNDg2ODgtMzc2Mi00ZjkyLWJhNDYtZWJmOTRhYmU1MWIxLnBuZyIsIndpZHRoIjoiPD05MDAifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.0wP1LBCQjWzqUQ5czHR2NjUv9iaiB2lUp_y-d9FuX-8");

  const setup = () => {
    keyboardEvenListener && document.removeEventListener("keydown", keyboardEvenListener);
    mouseEvenListener && document.removeEventListener("mousedown", mouseEvenListener);
    windowResizeEvenListener && document.removeEventListener("resize", windowResizeEvenListener);
    const playerImage = p.loadImage("https://i.imgur.com/cCmEvHN.png");
    const greenAlien = p.loadImage("https://i.imgur.com/fqeDYa0.png");
    const redAlien = p.loadImage("https://i.imgur.com/iHKEnRq.png");
    const yellowAlien = p.loadImage("https://i.imgur.com/lVEg9GG.png");
    const orangeAlien = p.loadImage("https://i.imgur.com/LRYWNG0.png");
    const container = document.getElementById("p5_canvas_container");
    const canvas = p.createCanvas(container.offsetWidth, container.clientHeight + 100);
    canvas.parent(container);
    canvas.style("position", "relative");
    canvas.attribute("tabindex", "1");
    p.frameRate(24);
    invaders = new Invaders({
      greenAlien, yellowAlien, orangeAlien, redAlien
    }, p, pods);
    player = new Player(playerImage, p, invaders);

    const starter = (event: Event) => {
      lastMouseTarget = event.target;
      if (event.target === canvas.elt) {
        started = true;
      }
    }
    document.addEventListener("mousedown", starter, false);
    mouseEvenListener = starter;

    const bind = ({ code }: { code: string }) => {
      if (code == "Backquote") {
        // for easier screen record w/o mouse
        started = !started;
        return;
      }
      if (lastMouseTarget !== canvas.elt) {
        return;
      }
      switch (code) {
      case "ArrowRight":
        player.moveRight()
        break;
      case "ArrowLeft":
        player.moveLeft()
        break;
      case "Space":
        console.info("🔫🔫🔫 You humans!")
        player.shoot()
        break;
      case "KeyS":
        enableParticles = !enableParticles;
        console.info("enableParticles", enableParticles)
        break;
      default:
        break;
      }
      
    };

    document.addEventListener("keydown", bind);
    keyboardEvenListener = bind;
    
    for (let i = 0; i < p.width / 20; i++) {
      particles.push(new Particle(p));
    }
  }

  p.setup = () => {
    setup();
  };

  const draw = () => {
    p.background(0);

    if (started) {
      invaders.update(player);
      invaders.draw();
      player.update();
      player.draw();
    } else {
      const imageWidth = p.width / 3;
      const imageHeight = p.width / 6;
      const x = p.width / 2;
      const y = p.width / 2;
      const textSize = p.width / 50;
      p.image(gameImage, x - imageWidth / 2, y - imageHeight - textSize * 5, imageWidth, imageHeight);
      p.textSize(textSize);
      p.fill("rgba(0,255,0,1)");
      p.textFont("Courier New");
      p.textAlign(p.CENTER)
      p.text("CLICK TO START", x, y - textSize * 3);
    }

    if (player.score > (invaders.aliens.length * player.level)) {
      invaders.aliens = [];
      invaders.speed += 0.4;
      player.level += 1;
    }

    if (player.lives == 0) {
      p.textFont("Courier New");
      p.textSize(p.width / 20);
      p.fill("rgba(0,255,0,1)");
      const x = p.width / 2;
      const y = p.width / 2.5;
      p.text("GAME OVER ", x, y);
      p.fill("rgba(255,255,255,1)");
      p.textSize(p.width / 80);
      p.text("CMD/Ctrl + R to Restart", x - p.width / 80, y + p.width / 40);
      player.stop();
    }

    if (enableParticles) {
      for (let i = 0; i < particles.length; i++) {
        particles[i].createParticle();
        particles[i].moveParticle();
        particles[i].joinParticles(particles.slice(i));
      }
    }
  }
  p.draw = () => {
    draw();
  };

  const resizer = () => {
    const container = document.getElementById("p5_canvas_container");
    p.resizeCanvas(container.clientWidth, container.clientHeight);
    draw();
  }

  window.addEventListener("resize", resizer);
  windowResizeEvenListener = resizer;
};

const Game = memo(({ pods }: Props): JSX.Element => {

  const [init, setInit] = useState(false);
  useLayoutEffect(() => {
    if (!init) {
      new p5(sketch(pods));
      console.info("👾 P5 Canvas Injected");
      setInit(true)
    }
  }, [init, pods]);


  return (
    <div id='p5_canvas_container' className="flex box column grow"></div>
  )
})

export default Game


================================================
FILE: components/Invaders.ts
================================================
import Alien from "./Alien";
import p5 from "p5";
import AlienBullet from "./AlienBullet";
import Bullet from "./Bullet";
import Player from "./Player";
import { K8sApi } from "@k8slens/extensions";
import { IObservableArray } from "mobx";

type AlienImages = {
  greenAlien: p5.Image, yellowAlien: p5.Image, orangeAlien: p5.Image, redAlien: p5.Image
}

class Invaders {

    alienImages: AlienImages;
    direction: number;
    y: number;
    aliens: Array<Alien> = [];
    bullets: Array<Bullet>;
    speed: number;
    timeSinceLastBullet: number;
    p5: p5;
    pods: Array<K8sApi.Pod>;

    constructor(alienImages: AlienImages, p5: p5, pods: IObservableArray<K8sApi.Pod>) {
      this.alienImages = alienImages;
      this.direction = 0;
      this.y = 40;
      this.p5 = p5;
      this.bullets = [];
      this.speed = 0.2;
      this.pods = pods;

      // to make sure the aliens dont spam
      this.timeSinceLastBullet = 0;
    }

    update(player: Player): void {
      this.updateAliens();

      for (const alien of this.aliens) {
        if (this.direction == 0) {
          alien.x += this.speed;
        } else if (this.direction == 1) {
          alien.x -= this.speed;
        }
      }
      this.updateBullets(player);

      if (this.hasChangedDirection()) {
        this.moveAlienDown();
      }

      if (this.timeSinceLastBullet >= 40) {
        const bottomAliens = this.getBottomAliens();
        if (bottomAliens.length) {
          this.makeABottomAlienShoot(bottomAliens);
        }
      }

      this.timeSinceLastBullet++;
    }

    hasChangedDirection(): boolean {
      for (const alien of this.aliens) {
        if (alien.x >= this.p5.width - 40) {
          this.direction = 1;
          return true;
        } else if (alien.x <= 20) {
          this.direction = 0;
          return true;
        }
      }
      return false;
    }

    moveAlienDown(): void {
      for (const alien of this.aliens) {
        alien.y += 10;
      }
    }

    // to make sure only the bottom row will shoot
    getBottomAliens(): Array<Alien> {
      const allXPositions = this.getAllXPositions();

      const aliensAtTheBottom = [];
      for (const alienAtX of allXPositions) {
        let bestYPosition = 0;
        let lowestAlien;
        for (const alien of this.aliens) {
          if (alien.x == alienAtX) {
            if (alien.y > bestYPosition) {
              bestYPosition = alien.y;
              lowestAlien = alien;
            }
          }
        }
        aliensAtTheBottom.push(lowestAlien);
      }
      return aliensAtTheBottom;
    }

    nextLevel(): void {
      this.speed += 0.5;
      this.updateAliens();
    }

    // get all the x positions for a single frame
    getAllXPositions(): Set<unknown> {
      const allXPositions = new Set();
      for (const alien of this.aliens) {
        allXPositions.add(alien.x);
      }
      return allXPositions
    }

    updateAliens(): void {
      const maxX = this.p5.width - 100;
      const minX = 100;
      const gapX = 50;

      const newPods: K8sApi.Pod[] = []
      this.pods.forEach((pod: K8sApi.Pod) => {
        const alien = this.aliens.find((a) => a.pod.getId() === pod.getId())
        if(!alien) {
          newPods.push(pod);
        } else {
          alien.pod = pod;
        }
      })

      const podIds = this.pods.map((p) => p.getId());
      this.aliens.forEach((alien, index) => {
        if (!podIds.includes(alien.pod.getId())) {
          this.aliens.splice(index, 1);
        }
      })

      const empty = this.aliens.length === 0;
      newPods.sort(() => Math.random() - 0.5);
      if (empty) {
        newPods.forEach((pod) => {
          let y = 80;
          let x = minX;
          const lastAlien = this.aliens[this.aliens.length - 1];
          if (lastAlien) {
            x = lastAlien.x + gapX;
            y = lastAlien.y;
            if (x >= maxX) {
              x = minX;
              y = lastAlien.y + gapX;
            }
          }
          if (this.aliens.length < 100) {
            this.aliens.push(new Alien(x, y, this.alienImages, this.p5, pod))
          }
        })
      } else {
        const pod = newPods[0]
        if (!pod) {
          return
        }
        const alien = this.aliens[Math.floor(Math.random() * this.aliens.length)];
        const aliensOnSameRow = this.aliens.filter((a) => a.y === alien.y)
        if (!aliensOnSameRow.find((a) => a.x >= (alien.x - (gapX + 3)) && a.x < alien.x && (alien.x - gapX) > minX)) {
          if (alien.x - gapX > minX) {
            this.aliens.push(new Alien(alien.x - gapX, alien.y, this.alienImages, this.p5, pod))
          }
        }
      }
    }

    checkCollision(x: number, y: number): boolean {
      for (let i = this.aliens.length - 1; i >= 0; i--) {
        const currentAlien = this.aliens[i];
        // the numbers are hard-coded for the width of the image
        if (this.p5.dist(x, y, currentAlien.x + (currentAlien.width / 2), currentAlien.y + (currentAlien.height / 2)) < currentAlien.width / 2) {
          currentAlien.bulletHit();
          return true;
        }
      }
      return false;
    }

    makeABottomAlienShoot(bottomAliens: Array<Alien>): void {
      const shootingAlien = this.p5.random(bottomAliens.filter((a) => a.hits === 0));
      const bullet = new AlienBullet(shootingAlien.x + 10, shootingAlien.y + 10, this.p5);
      this.bullets.push(bullet);
      this.timeSinceLastBullet = 0;
    }

    updateBullets(player: Player): void {
      for (let i = this.bullets.length - 1; i >= 0; i--) {
        this.bullets[i].y += 4;
        if (this.bullets[i].hasHit(player)) {
          player.bulletHit();
          this.bullets.splice(i, 1);
        }
      }
    }

    draw(): void {
      for (const alien of this.aliens) {
        alien.draw();
      }
      for (const bullet of this.bullets) {
        this.p5.rect(bullet.x, bullet.y, 3, 10);
      }
    }
}

export { AlienImages }
export default Invaders


================================================
FILE: components/Particle.ts
================================================
import p5 from "p5";

// this class describes the properties of a single particle.
class Particle {
  // setting the co-ordinates, radius and the
  // speed of a particle in both the co-ordinates axes.
  x: number;
  y: number;
  r: number;
  xSpeed: number;
  ySpeed: number;
  p5: p5;

  constructor(p5: p5) {
    this.p5 = p5;
    this.x = this.p5.random(0, this.p5.windowWidth);
    this.y = this.p5.random(0, this.p5.windowHeight);
    this.r = this.p5.random(1, 8);
    this.xSpeed = this.p5.random(-2, 2);
    this.ySpeed = this.p5.random(-1, 1.5);
  }

  // creation of a particle.
  createParticle(): void {
    this.p5.noStroke();
    this.p5.fill("rgba(200,169,169,0.5)");
    this.p5.circle(this.x, this.y, this.r);
  }

  // setting the particle in motion.
  moveParticle(): void {
    if (this.x < 0 || this.x > this.p5.windowWidth)
      this.xSpeed *= -1;
    if (this.y < 0 || this.y > this.p5.windowHeight)
      this.ySpeed *= -1;
    this.x += this.xSpeed;
    this.y += this.ySpeed;
  }

  // this function creates the connections(lines)
  // between particles which are less than a certain distance apart
  joinParticles(particles: Array<Particle>): void {
    particles.forEach((element: Particle) => {
      const dis = this.p5.dist(this.x, this.y, element.x, element.y);
      if (dis < 85) {
        this.p5.stroke("rgba(255,255,255,0.08)");
        this.p5.line(this.x, this.y, element.x, element.y);
      }
    });
  }
}

export default Particle;


================================================
FILE: components/Player.ts
================================================
import p5 from "p5";
import PlayerBullet from "./PlayerBullet"
import Invaders from "./Invaders";

class Player {
  image: p5.Image;
  x: number;
  y: number;
  isMovingLeft: boolean;
  isMovingRight: boolean;
  bullets: Array<PlayerBullet>;
  p5: p5;
  invaders: Invaders;
  lives: number;
  level: number;
  score: number;

  constructor(shooterImage: p5.Image, p5: p5, invaders: Invaders) {
    this.image = shooterImage;
    this.isMovingLeft = false;
    this.isMovingRight = false;
    this.bullets = [];
    this.p5 = p5;
    this.x = this.p5.width / 2;
    // there seems to be a bug of p5.height which won't update when window is being
    // resize, so we use windowHeight to cal y instead. 
    this.y = this.p5.windowHeight - Math.max(210, this.p5.windowHeight / 3.8);
    this.invaders = invaders;
    this.lives = 3;
    this.score = 0;
    this.level = 1;
  }

  update(): void {
    if (this.isMovingRight) {
      this.x += 5;
    } else if (this.isMovingLeft) {
      this.x -= 5;
    } else {
      this.x = this.p5.width / 2;
    }
    this.y = this.p5.windowHeight - Math.max(210, this.p5.windowHeight / 3.8);
    if(this.invaders.aliens.find((a) => a.y >= this.p5.height)) {
      this.lives = 0;
    }
    this.constrain();
    this.updateBullets();
  }

  addScore(): void {
    this.score = this.score + 1;
  }

  updateBullets(): void {

    for (let i = this.bullets.length - 1; i >= 0; i--) {
      this.bullets[i].update();
      if (this.hasHitAlien(this.bullets[i])) {
        this.bullets.splice(i, 1);
        this.addScore();
        break;
      } else if (this.bullets[i].isOffScreen()) {
        this.bullets.splice(i, 1);
        break;
      }
    }
  }

  hasHitAlien(bullet: PlayerBullet): boolean {
    return this.invaders.checkCollision(bullet.x, bullet.y);
  }

  bulletHit(): void {
    if (this.lives > 0) {
      this.lives--;
    }
  }

  constrain(): void {
    if (this.x <= 0) {
      this.x = 0;
    } else if (this.x > this.p5.width - 23) {
      this.x = this.p5.width - 23;
    }
  }

  draw(): void {
    this.p5.image(this.image, this.x, this.y, this.image.width / 10, this.image.height / 10);
    this.drawBullets();
    this.drawLives();
    this.drawLevel();
    this.drawScore();
  }

  drawBullets(): void {
    for (const bullet of this.bullets) {
      bullet.draw();
    }
  }

  drawLives(): void {
    this.p5.fill(255);
    this.p5.textSize(15);
    this.p5.text("LIVES", 50, 25);
    for (let i = 0; i < this.lives; i++) {
      this.p5.image(this.image, 100 + i * 30, 10, this.image.width / 20, this.image.height / 20);
    }
  }

  drawLevel(): void {
    this.p5.text("LEVEL", this.p5.width - 200, 25);
    this.p5.push();
    this.p5.fill(100, 255, 100);
    this.p5.text(this.level, this.p5.width - 150, 25);
    this.p5.pop();
  }

  drawScore(): void {
    this.p5.text("SCORE", this.p5.width - 120, 25);
    this.p5.push();
    this.p5.fill(100, 255, 100);
    this.p5.text(this.score, this.p5.width - 50, 25);
    this.p5.pop();
  }

  moveLeft(): void {
    this.isMovingRight = false;
    this.isMovingLeft = true;
  }
  moveRight(): void {
    this.isMovingLeft = false;
    this.isMovingRight = true;
  }

  stop(): void {
    this.isMovingLeft = false;
    this.isMovingRight = false;
  }

  shoot(): void {
    this.bullets.push(new PlayerBullet(this.x + 12, this.y, this.p5));
  }
}

export default Player;


================================================
FILE: components/PlayerBullet.ts
================================================
import Bullet from "./Bullet"
import p5 from "p5";

class PlayerBullet extends Bullet {
  constructor(x: number, y: number, p5: p5) {
    super(x, y, p5);
  }

  update(): void {
    this.y -= 12;
  }
}

export default PlayerBullet;


================================================
FILE: main.ts
================================================
import { LensMainExtension } from "@k8slens/extensions";

export default class MainExtension extends LensMainExtension {
    
  onActivate(): void {
    console.log("activated");
  }

  onDeactivate(): void {
    console.log("deactivated");
  }

  appMenus = [
    {
      parentId: "help",
      label: "Invade",
      click(): void {
        this.navigate();
      }
    }
  ]
}


================================================
FILE: package.json
================================================
{
	"name": "@lensapp/lens-ext-invaders",
	"version": "0.0.7",
	"description": "Space Invaders for Lens",
	"publisher": "IRATA Inc",
	"main": "dist/main.js",
	"renderer": "dist/renderer.js",
	"engines": {
		"node": ">=12.0 <13.0"
	},
	"contributes": {
		"crds": [],
		"cloudProviders": [],
		"kubernetesDistros": []
	},
	"keywords": [
		"lens",
		"extension",
		"k8slens",
		"kubernetes"
	],
	"scripts": {
		"start": "webpack --watch",
		"build": "npm run clean && webpack",
		"clean": "rm -rf ./dist",
		"test": "jest"
	},
	"dependencies": {
		"@types/p5": "^0.9.1",
		"p5": "^1.1.9"
	},
	"devDependencies": {
		"@babel/preset-env": "^7.12.7",
		"@babel/preset-react": "^7.12.7",
		"@babel/preset-typescript": "^7.12.7",
		"@jest-runner/electron": "^3.0.0",
		"@k8slens/extensions": "4.2.1",
		"@testing-library/jest-dom": "^5.11.6",
		"@testing-library/react": "^11.2.2",
		"@types/jest": "^26.0.15",
		"@types/node": "^12.12.9",
		"@types/react": "^17.0.0",
		"@typescript-eslint/eslint-plugin": "^4.8.1",
		"@typescript-eslint/parser": "^4.8.1",
		"electron": "^11.0.3",
		"eslint": "^7.13.0",
		"eslint-plugin-react-hooks": "^4.2.0",
		"jest": "^26.6.3",
		"react": "^17.0.1",
		"react-dom": "^17.0.1",
		"mobx": "^5.0.0",
		"mobx-react": "^6.2.2",
		"ts-loader": "^8.0.11",
		"typescript": "^4.1.2",
		"webpack": "^4.44.2",
		"webpack-cli": "^3.3.11"
	},
	"jest": {
		"runner": "@jest-runner/electron",
		"testEnvironment": "@jest-runner/electron/environment"
	},
	"lingui": {
		"locales": [
			"en"
		]
	}
}


================================================
FILE: renderer.tsx
================================================
import { LensRendererExtension, Component } from "@k8slens/extensions";
import React from "react"

import ClusterPage from "./components/ClusterPage";

const { Icon } = Component;

export default class RendererExtension extends LensRendererExtension {

  #clusterPageId = "space_invader_clusters_page";
  clusterPages = [
    {
      id: this.#clusterPageId,
      title: "Space Invaders",
      components: {
        Page: ClusterPage
      }
    },
  ]

  clusterPageMenus = [
    // a cluster menu item which links to a cluster page
    {
      title: "Space Invaders",
      target: {
        pageId: this.#clusterPageId,
        params: {}
      },
      components: {
        Icon: (): JSX.Element => <Icon material="my_location" />,
      }
    },
  ]
}


================================================
FILE: tsconfig.json
================================================
{
	"compilerOptions": {
		"outDir": "dist",
		"module": "CommonJS",
		"target": "ES2017",
		"lib": [
			"ESNext",
			"DOM",
			"DOM.Iterable"
		],
		"moduleResolution": "Node",
		"sourceMap": false,
		"declaration": false,
		"strict": false,
		"noImplicitAny": true,
		"skipLibCheck": true,
		"esModuleInterop": true,
		"allowSyntheticDefaultImports": true,
		"experimentalDecorators": true,
		"jsx": "react"
	},
	"include": [
		"./*.ts",
		"./*.tsx"
	],
	"exclude": [
		"node_modules",
		"*.js"
	]
}


================================================
FILE: webpack.config.js
================================================
/* eslint @typescript-eslint/no-var-requires: "off" */

const path = require("path");

module.exports = [
  {
    entry: "./main.ts",
    context: __dirname,
    target: "electron-main",
    mode: "development",
    devtool: "eval-source-map",
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          use: [
            {
              loader: "ts-loader",
              options: {
                transpileOnly: true,
              }
            }
          ],
          exclude: /node_modules/,

        },
      ],
    },
    externals: [
      {
        "@k8slens/extensions": "var global.LensExtensions",
        "mobx": "var global.Mobx",
        "react": "var global.React",
        "mobx-react": "var global.MobxReact"
      }
    ],
    optimization: {
      minimize: false,
    },
    resolve: {
      extensions: [".tsx", ".ts", ".js"],
    },
    output: {
      libraryTarget: "commonjs2",
      filename: "main.js",
      path: path.resolve(__dirname, "dist"),
    },
  },
  {
    entry: "./renderer.tsx",
    context: __dirname,
    target: "electron-renderer",
    mode: "development",
    devtool: "eval-source-map",
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          use: [
            {
              loader: "ts-loader",
              options: {
                transpileOnly: true,
              }
            }
          ],
          exclude: /node_modules/,
        },
      ],
    },
    optimization: {
      minimize: false,
    },
    externals: [
      {
        "@k8slens/extensions": "var global.LensExtensions",
        "react": "var global.React",
        "mobx": "var global.Mobx"
      }
    ],
    resolve: {
      extensions: [".tsx", ".ts", ".js"],
    },
    output: {
      libraryTarget: "commonjs2",
      globalObject: "this",
      filename: "renderer.js",
      path: path.resolve(__dirname, "dist"),
    },
    node: {
      __dirname: false,
      __filename: false
    }
  },
];
Download .txt
gitextract_hjnyx861/

├── .eslintrc
├── .github/
│   └── workflows/
│       └── pr_release.yaml
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── components/
│   ├── Alien.ts
│   ├── AlienBullet.ts
│   ├── Bullet.ts
│   ├── ClusterPage.tsx
│   ├── Game.tsx
│   ├── Invaders.ts
│   ├── Particle.ts
│   ├── Player.ts
│   └── PlayerBullet.ts
├── main.ts
├── package.json
├── renderer.tsx
├── tsconfig.json
└── webpack.config.js
Download .txt
SYMBOL INDEX (57 symbols across 10 files)

FILE: components/Alien.ts
  class Alien (line 5) | class Alien {
    method constructor (line 16) | constructor(x: number, y: number, alienImages: AlienImages, p5: p5, po...
    method bulletHit (line 27) | bulletHit(): number {
    method draw (line 32) | draw(): void {

FILE: components/AlienBullet.ts
  class AlienBullet (line 4) | class AlienBullet extends Bullet {
    method constructor (line 8) | constructor(x: number, y: number, p5: p5) {
    method update (line 12) | update(): void {

FILE: components/Bullet.ts
  class Bullet (line 4) | class Bullet {
    method constructor (line 9) | constructor(x: number, y: number, p5: p5) {
    method isOffScreen (line 15) | isOffScreen(): boolean {
    method draw (line 19) | draw(): void {
    method hasHit (line 24) | hasHit(player: Player): boolean {

FILE: components/Game.tsx
  type Props (line 10) | type Props = { pods: IObservableArray<K8sApi.Pod> }

FILE: components/Invaders.ts
  type AlienImages (line 9) | type AlienImages = {
  class Invaders (line 13) | class Invaders {
    method constructor (line 25) | constructor(alienImages: AlienImages, p5: p5, pods: IObservableArray<K...
    method update (line 38) | update(player: Player): void {
    method hasChangedDirection (line 64) | hasChangedDirection(): boolean {
    method moveAlienDown (line 77) | moveAlienDown(): void {
    method getBottomAliens (line 84) | getBottomAliens(): Array<Alien> {
    method nextLevel (line 104) | nextLevel(): void {
    method getAllXPositions (line 110) | getAllXPositions(): Set<unknown> {
    method updateAliens (line 118) | updateAliens(): void {
    method checkCollision (line 174) | checkCollision(x: number, y: number): boolean {
    method makeABottomAlienShoot (line 186) | makeABottomAlienShoot(bottomAliens: Array<Alien>): void {
    method updateBullets (line 193) | updateBullets(player: Player): void {
    method draw (line 203) | draw(): void {

FILE: components/Particle.ts
  class Particle (line 4) | class Particle {
    method constructor (line 14) | constructor(p5: p5) {
    method createParticle (line 24) | createParticle(): void {
    method moveParticle (line 31) | moveParticle(): void {
    method joinParticles (line 42) | joinParticles(particles: Array<Particle>): void {

FILE: components/Player.ts
  class Player (line 5) | class Player {
    method constructor (line 18) | constructor(shooterImage: p5.Image, p5: p5, invaders: Invaders) {
    method update (line 34) | update(): void {
    method addScore (line 50) | addScore(): void {
    method updateBullets (line 54) | updateBullets(): void {
    method hasHitAlien (line 69) | hasHitAlien(bullet: PlayerBullet): boolean {
    method bulletHit (line 73) | bulletHit(): void {
    method constrain (line 79) | constrain(): void {
    method draw (line 87) | draw(): void {
    method drawBullets (line 95) | drawBullets(): void {
    method drawLives (line 101) | drawLives(): void {
    method drawLevel (line 110) | drawLevel(): void {
    method drawScore (line 118) | drawScore(): void {
    method moveLeft (line 126) | moveLeft(): void {
    method moveRight (line 130) | moveRight(): void {
    method stop (line 135) | stop(): void {
    method shoot (line 140) | shoot(): void {

FILE: components/PlayerBullet.ts
  class PlayerBullet (line 4) | class PlayerBullet extends Bullet {
    method constructor (line 5) | constructor(x: number, y: number, p5: p5) {
    method update (line 9) | update(): void {

FILE: main.ts
  class MainExtension (line 3) | class MainExtension extends LensMainExtension {
    method onActivate (line 5) | onActivate(): void {
    method onDeactivate (line 9) | onDeactivate(): void {
    method click (line 17) | click(): void {

FILE: renderer.tsx
  class RendererExtension (line 8) | class RendererExtension extends LensRendererExtension {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (34K chars).
[
  {
    "path": ".eslintrc",
    "chars": 1400,
    "preview": "{\n    // `root: true` prevent eslint to combine .eslintrc in parent folder with this one\n    // ext developer can remove"
  },
  {
    "path": ".github/workflows/pr_release.yaml",
    "chars": 2237,
    "preview": "on:\n  pull_request:\n    types: [closed]\n\njobs:\n  versioning:\n    name: Auto versioning/tag => Create a release with .tgz"
  },
  {
    "path": ".gitignore",
    "chars": 43,
    "preview": "node_modules\ndist\n.DS_Store\nlens.log\n*.tgz\n"
  },
  {
    "path": "LICENSE",
    "chars": 1060,
    "preview": "MIT License\n\nCopyright (c) 2020 chh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
  },
  {
    "path": "README.md",
    "chars": 703,
    "preview": "# Start Invading Clusters\n\n⚠️⚠️⚠️\nPlease don't play this game on your production cluster. 😅\nAlien 👾 = pod on cluster.\n⚠️"
  },
  {
    "path": "babel.config.js",
    "chars": 193,
    "preview": "// the babel config is for jest to parse jsx in test/spec files\nconst presets = [\n  \"@babel/preset-env\",\n  \"@babel/prese"
  },
  {
    "path": "components/Alien.ts",
    "chars": 1417,
    "preview": "import p5 from \"p5\";\nimport { K8sApi } from \"@k8slens/extensions\";\nimport { AlienImages } from \"./Invaders\";\n\nclass Alie"
  },
  {
    "path": "components/AlienBullet.ts",
    "chars": 257,
    "preview": "import Bullet from \"./Bullet\";\nimport p5 from \"p5\";\n\nclass AlienBullet extends Bullet {\n  x: number;\n  y: number;\n\n  con"
  },
  {
    "path": "components/Bullet.ts",
    "chars": 494,
    "preview": "import p5 from \"p5\";\nimport Player from \"./Player\";\n\nclass Bullet {\n  p5: p5;\n  x: number;\n  y: number;\n\n  constructor(x"
  },
  {
    "path": "components/ClusterPage.tsx",
    "chars": 821,
    "preview": "import React, { useEffect, useState } from \"react\"\nimport Game from \"./Game\"\nimport { K8sApi } from \"@k8slens/extensions"
  },
  {
    "path": "components/Game.tsx",
    "chars": 5873,
    "preview": "import React, { memo, useState, useLayoutEffect } from \"react\"\nimport { K8sApi } from \"@k8slens/extensions\";\nimport p5 f"
  },
  {
    "path": "components/Invaders.ts",
    "chars": 5978,
    "preview": "import Alien from \"./Alien\";\nimport p5 from \"p5\";\nimport AlienBullet from \"./AlienBullet\";\nimport Bullet from \"./Bullet\""
  },
  {
    "path": "components/Particle.ts",
    "chars": 1476,
    "preview": "import p5 from \"p5\";\n\n// this class describes the properties of a single particle.\nclass Particle {\n  // setting the co-"
  },
  {
    "path": "components/Player.ts",
    "chars": 3393,
    "preview": "import p5 from \"p5\";\nimport PlayerBullet from \"./PlayerBullet\"\nimport Invaders from \"./Invaders\";\n\nclass Player {\n  imag"
  },
  {
    "path": "components/PlayerBullet.ts",
    "chars": 233,
    "preview": "import Bullet from \"./Bullet\"\nimport p5 from \"p5\";\n\nclass PlayerBullet extends Bullet {\n  constructor(x: number, y: numb"
  },
  {
    "path": "main.ts",
    "chars": 381,
    "preview": "import { LensMainExtension } from \"@k8slens/extensions\";\n\nexport default class MainExtension extends LensMainExtension {"
  },
  {
    "path": "package.json",
    "chars": 1514,
    "preview": "{\n\t\"name\": \"@lensapp/lens-ext-invaders\",\n\t\"version\": \"0.0.7\",\n\t\"description\": \"Space Invaders for Lens\",\n\t\"publisher\": \""
  },
  {
    "path": "renderer.tsx",
    "chars": 761,
    "preview": "import { LensRendererExtension, Component } from \"@k8slens/extensions\";\nimport React from \"react\"\n\nimport ClusterPage fr"
  },
  {
    "path": "tsconfig.json",
    "chars": 501,
    "preview": "{\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"dist\",\n\t\t\"module\": \"CommonJS\",\n\t\t\"target\": \"ES2017\",\n\t\t\"lib\": [\n\t\t\t\"ESNext\",\n\t\t\t\"DO"
  },
  {
    "path": "webpack.config.js",
    "chars": 1971,
    "preview": "/* eslint @typescript-eslint/no-var-requires: \"off\" */\n\nconst path = require(\"path\");\n\nmodule.exports = [\n  {\n    entry:"
  }
]

About this extraction

This page contains the full source code of the chenhunghan/lens-ext-invaders GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (30.0 KB), approximately 9.2k tokens, and a symbol index with 57 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!