Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack"
"build": "webpack --mode production"
},
"devDependencies": {
"@types/copy-webpack-plugin": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^4.1.1",
"@typescript-eslint/parser": "^4.1.1",
"copy-webpack-plugin": "^6.1.1",
"css-loader": "^4.3.0",
"eslint": "^7.9.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"prettier": "^2.1.2",
"style-loader": "^1.2.1",
"ts-loader": "^8.0.4",
"ts-node": "^9.0.0",
"typescript": "^4.0.3",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"@types/chrome": "^0.0.123"
"@types/chrome": "^0.0.279",
"@types/copy-webpack-plugin": "^10.1.3",
"@typescript-eslint/eslint-plugin": "^8.12.2",
"@typescript-eslint/parser": "^8.12.2",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"eslint": "^9.13.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"prettier": "^3.3.3",
"style-loader": "^4.0.0",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typescript": "^5.6.3",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4"
}
}
16 changes: 11 additions & 5 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
{
"name": "GitHub Breakout",
"version": "1.1.1",
"manifest_version": 2,
"manifest_version": 3,
"description": "Breakout!",
"permissions": ["storage"],
"permissions": [
"storage"
],
"content_scripts": [
{
"matches": ["https://github.com/*"],
"js": ["main.js"]
"matches": [
"https://github.com/*"
],
"js": [
"main.js"
]
}
],
"icons": {
Expand All @@ -17,4 +23,4 @@
"128": "icons/128.png",
"512": "icons/512.png"
}
}
}
88 changes: 39 additions & 49 deletions src/game/breakout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Block } from '../objects/block'
import { Game } from './game'
import { Direction, intersectDirection } from '../utils/intersect'
import { Player } from '../objects/player'
import { createButton, createDivElement, createShape } from '../utils/domUtils'
import { createButton, createDivElement, createSVGElement } from '../utils/domUtils'
import { getHighScore, saveScore } from '../utils/score'

enum State {
Expand All @@ -20,58 +20,63 @@ export class Breakout extends Game {
blocks!: Block[]
player!: Player

svgElement: SVGElement
headerElement!: HTMLElement | null
parentElement!: HTMLDivElement
tableElement!: HTMLTableElement
svgElement!: SVGElement
headerElement!: HTMLElement
originalHeaderContent!: string
button!: HTMLButtonElement
footerElement!: HTMLDivElement
scoreElement!: SVGElement

constructor(svgElement: SVGElement) {
constructor(parent: HTMLDivElement) {
super()
this.svgElement = svgElement
;(async () => {
this.parentElement = parent;
(async () => {
this.initGameObject()
await this.initUI()
this.startGameLoop()
})()
}

initGameObject() {
this.tableElement = this.parentElement.querySelectorAll('.js-calendar-graph-table')[0] as HTMLTableElement

this.parentElement.style.position = 'relative'
this.svgElement = this.parentElement.appendChild(
createSVGElement()
) as SVGElement

this.ball = new Ball(this.svgElement)
this.player = new Player(this.svgElement)
this.blocks = [...this.svgElement.querySelectorAll('rect')]
this.blocks = [...this.tableElement.querySelectorAll('td')]
.filter((e) => e.getAttribute('data-count') !== '0')
.map((e) => new Block(this.svgElement, e))
.map((e) => new Block(this.tableElement, e))
}

async initUI() {
this.scoreElement = this.svgElement.appendChild(
createShape(
'text',
{ x: 20, y: 130, fill: 'var(--color-text-primary)' },
''
)
)

this.headerElement = document.querySelector<HTMLElement>(
'.js-yearly-contributions h2'
)
) as HTMLElement
this.originalHeaderContent = this.headerElement.textContent || ''

const uiContainer = document.querySelector('.js-calendar-graph')
const uiContainer = this.parentElement.parentElement
if (!uiContainer) return

this.button = uiContainer.appendChild(
createButton(this.blocks.length > 0 ? `Play!` : '🥺', () =>
this.onButtonClick()
)
)

const hs = await getHighScore()
this.footerElement = uiContainer.appendChild(
this.footerElement = uiContainer.insertBefore(
createDivElement(
hs > 0 ? `HighScore: ${hs}` : 'Press the arrow keys to play ←→'
)
),
uiContainer.childNodes[3]
)

this.button = uiContainer.insertBefore(
createButton(this.blocks.length > 0 ? `Play!` : '🥺', () =>
this.onButtonClick()
),
this.footerElement
)

}

/**
Expand All @@ -87,7 +92,7 @@ export class Breakout extends Game {
this.blocks.forEach((b) => b.update(delta))

// TODO reduce the computational cost
let remainingContributions = 0
let remainingScore = 0
let collideFlagX = false
let collideFlagY = false
this.blocks
Expand All @@ -97,11 +102,11 @@ export class Breakout extends Game {
// the ball hit the block
if (d !== Direction.None) {
b.onCollide()
this.score += b.origianlLife
this.score += 1
collideFlagX ||= d == Direction.X || d == Direction.XY
collideFlagY ||= d == Direction.Y || d == Direction.XY
}
remainingContributions += b.life
remainingScore += b.life
})

if (collideFlagX && collideFlagY) this.ball.onCollide(Direction.XY)
Expand All @@ -112,7 +117,7 @@ export class Breakout extends Game {
this.ball.onCollide(intersectDirection(this.ball, this.player))

// update score label
this.updateLabel(remainingContributions)
this.headerElement.textContent = `score: ${this.score}`

// gameover
if (this.ball.y > 220) {
Expand All @@ -122,27 +127,13 @@ export class Breakout extends Game {
}

// clear
if (remainingContributions === 0) {
if (remainingScore === 0) {
this.state = State.Done
this.button.textContent = 'Clear!'
saveScore(this.score)
}
}

/**
* update score and contribution label
* @param contributons remaining contributions
*/
updateLabel(contributons: number) {
const tmp = this.headerElement?.textContent?.match(/.*?[0-9,]+([\s\S]*)/m)
if (this.headerElement && tmp)
this.headerElement.textContent = `${contributons.toLocaleString()}${tmp[1].replace(
/\n/,
''
)}`
this.scoreElement.textContent = `score: ${this.score}`
}

/**
* footer button
*/
Expand Down Expand Up @@ -175,14 +166,13 @@ export class Breakout extends Game {
let life = 0
this.blocks.forEach((b) => {
b.reset()
life += b.origianlLife
life += b.originalColorLevel
})
this.player.reset()
this.ball.reset()
this.score = 0
this.button.textContent = 'Play!'
this.scoreElement.textContent = ''
this.headerElement.textContent = this.originalHeaderContent
this.footerElement.textContent = `HighScore: ${await getHighScore()}`
this.updateLabel(life)
}
}
43 changes: 39 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,48 @@
import { Breakout } from './game/breakout'


const callback = (mutationList: MutationRecord[], observer: MutationObserver) => {
for (const mutation of mutationList) {
if (mutation.type === "childList") {
mutation.addedNodes.forEach((node) => {
if (!(node instanceof HTMLDivElement)) return

node.childNodes.forEach((node) => {
if (!(node instanceof HTMLDivElement)) return

node.childNodes.forEach((node) => {
if (!(node instanceof HTMLDivElement)) {
return
}

if (node.classList.contains('js-yearly-contributions')) {
const parent = document.getElementsByClassName('js-calendar-graph')[0].childNodes[1] as HTMLDivElement
if (!parent) return
parent.style.height = '220px'
new Breakout(parent)
observer.disconnect()
}
})
})
})
}
}
}

/**
* called on github page loaded
*/
function main() {
const svg = document.querySelector<SVGElement>('.js-calendar-graph-svg')
if (!svg) return
svg.setAttribute('height', '220')
new Breakout(svg) // initialize the game
const observer = new MutationObserver(callback);

const targetNode = document.getElementById("user-profile-frame");
if (!targetNode) return

observer.observe(targetNode, {
childList: true,
subtree: true // needed if the node you're targeting is not the direct parent
});
}

main()

27 changes: 11 additions & 16 deletions src/objects/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,44 @@ import { GameObject } from './gameObject'
import { Rect } from './shape'

export class Block implements GameObject, Rect {
blockElement: SVGElement
blockElement: HTMLTableCellElement

left: number
right: number
top: number
bottom: number

origianlLife: number
originalColorLevel: string

life: number
originalColorLevel: number

constructor(svgElement: SVGElement, el: SVGElement) {
constructor(parent: HTMLTableElement, el: HTMLTableCellElement) {
this.blockElement = el

// calculating coordinates in SVG
const r = el.getBoundingClientRect()
const rr = svgElement.getBoundingClientRect()
const rr = parent.getBoundingClientRect()
this.right = r.right - rr.left
this.left = r.left - rr.left
this.top = r.top - rr.top
this.bottom = r.bottom - rr.top

this.origianlLife = Number(el.getAttribute('data-count'))
this.life = this.origianlLife
this.originalColorLevel = el.getAttribute('data-level') || '0'
this.originalColorLevel = Number(el.getAttribute('data-level'))
this.life = this.originalColorLevel
}

update(delta: number) {}
update(delta: number) { }

/**
* called when hit the ball
*/
onCollide() {
this.life = 0 // breaks at once
this.life -= 1
this.blockElement.setAttribute('fill', 'var(--color-calendar-graph-day-bg)')
this.blockElement.setAttribute('data-count', '0')
this.blockElement.setAttribute('data-level', '0')
this.blockElement.setAttribute('data-level', this.life.toString())
}

reset() {
this.life = this.origianlLife
this.blockElement.setAttribute('data-level', this.originalColorLevel)
this.blockElement.setAttribute('data-count', this.origianlLife.toString())
this.life = this.originalColorLevel
this.blockElement.setAttribute('data-level', this.originalColorLevel.toString())
}
}
14 changes: 14 additions & 0 deletions src/utils/domUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,17 @@ export function createDivElement(text: string) {
el.textContent = text
return el
}

/**
* create svg element
*/

export function createSVGElement() {
const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
el.style.position = 'absolute'
el.style.top = '0'
el.style.left = '0'
el.style.width = '100%'
el.style.height = '100%'
return el
}
7 changes: 3 additions & 4 deletions src/utils/score.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
* @param score
*/
export async function saveScore(score: number) {
if ((await getHighScore()) > score) return Promise.resolve()
if ((await getHighScore()) > score)
return Promise.resolve()
else
return new Promise((resolve) =>
chrome.storage.sync.set({ [`score:${location.href}`]: score }, resolve)
)
await chrome.storage.sync.set({ [`score:${location.href}`]: score })
}

/**
Expand Down
Loading