Skip to content

Commit

Permalink
Allow twitch to play games (#12)
Browse files Browse the repository at this point in the history
* First draft of turtle

* It listens for twitch now

* Move some text around

* MVP

* Dorito failed

* change label

* Add bsg sprites

* cleanup
  • Loading branch information
duncte123 authored Mar 28, 2024
1 parent 3eb47d5 commit 6a7423e
Show file tree
Hide file tree
Showing 32 changed files with 950 additions and 10 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@esamarathon/mq-events": "^1.0.1",
"animate.css": "^4.1.1",
"clone": "^2.1.2",
"comfy.js": "^1.1.16",
"discord.js": "^13.17.1",
"lodash": "^4.17.21",
"module-alias": "^2.2.3",
Expand Down
26 changes: 26 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/dashboard/obs-control/main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
@click="startIntermission"
:disabled="disableIntermission"
>
Start Intermission
Transition to Intermission (<strong><em>ADS</em></strong>)
<template v-if="currentRunDelay.audio">
({{ (currentRunDelay.audio / 1000).toFixed(1) }}s delay)
</template>
Expand Down
13 changes: 13 additions & 0 deletions src/graphics/_misc/themes/bsg.theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@
background-image: url('./bsg/intermission.png');
}

.textOrangeShadow {
text-shadow: 5px 5px 0px var(--bsg-color), 5px 7px 3px rgba(0, 0, 0, 0.5);
}

/*#Countdown #Background,
#Intermission #Background {
position: absolute;
width: 100%;
height: 100%;
background-image: url('./bsg/dorito.png');
!*background: red;*!
}*/

.countdownLayout {
background-image: url('./bsg/countdown.png');
}
Expand Down
Binary file modified src/graphics/_misc/themes/bsg/countdown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/graphics/_misc/themes/bsg/dorito.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/graphics/_misc/themes/default.theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ body {
}

#Background {
background-color: #19171c;
background-color: rgba(0, 0, 0, 0.76);
display: none;
}

Expand Down
43 changes: 43 additions & 0 deletions src/graphics/countdown/game/Animator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export default class Animator {
private timer = 0;
private index = 0;

constructor(
private readonly playSpeed: number,
private readonly showTime: number,
private readonly images: HTMLImageElement[],
) {
//
}

// A method used to update the animation over time.
update() {
this.timer += this.playSpeed;
if (this.timer >= this.showTime) {
this.timer = 0;
this.index = (this.index + 1) % this.images.length;
}
}

// A method that returns the current image of the animation.
getImage() {
return this.images[this.index];
}

// A method that reset the animation to the first image.
reset() {
this.index = 0;
}

// A method that can be used to create an instance of an animator
// by specifying images' locations instead of instances of HTMLImageElements.
static create(playSpeed: number, showTime: number, imageSelectors: string[]) {
const images: HTMLImageElement[] = [];

imageSelectors.forEach((selector) => {
images.push(document.querySelector(selector) as HTMLImageElement);
});

return new Animator(playSpeed, showTime, images);
}
}
28 changes: 28 additions & 0 deletions src/graphics/countdown/game/Background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export default class Background {
private current: number;

constructor(
private readonly max: number,
private readonly w: number,
private readonly h: number,
) {
this.current = max;
}

draw(ctx: CanvasRenderingContext2D) {
// Draw background color.
// ctx.beginPath();
// ctx.fillStyle = 'rgb(0, 0, 0)';
// ctx.rect(0, 0, this.w, this.h);
// ctx.fill();
// ctx.closePath();

// Set inverse colors for other objects.
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.strokeStyle = 'rgb(255, 255, 255)';
}

update(): void {
// Does not need to do anything anymore
}
}
33 changes: 33 additions & 0 deletions src/graphics/countdown/game/Collider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Point2D from '@esa-layouts/countdown/game/Point2D';
import { IObstacle } from '@esa-layouts/countdown/game/types/options';

export default class Collider {
constructor(
public readonly position: Point2D,
public readonly w: number,
public readonly h: number,
) {
//
}

// A method that can be used to check if the
// collider overlaps with another collider.
overlaps(other: Collider | IObstacle) {
return this.position.x < other.position.x + other.w
&& this.position.x + this.w > other.position.x
&& this.position.y < other.position.y + other.h
&& this.position.y + this.h > other.position.y;
}

// A method that returns true if the collider
// overlaps with one in the list of colliders.
overlapsWithOthers(others: Collider[] | IObstacle[]): boolean {
for (const other of others) {
if (this.overlaps(other)) {
return true;
}
}

return false;
}
}
182 changes: 182 additions & 0 deletions src/graphics/countdown/game/EndlessRunnerGame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { DifficultyOptions, PlayerOptions, SpawnerOptions } from '@esa-layouts/countdown/game/types/options';
import RunnerPlayer from '@esa-layouts/countdown/game/RunnerPlayer';
import Background from '@esa-layouts/countdown/game/Background';
import Spawner from '@esa-layouts/countdown/game/Spawner';
import InputHandler from '@esa-layouts/countdown/game/InputHandler';

export default class EndlessRunnerGame {
private readonly ctx: CanvasRenderingContext2D;
private readonly groundY: number;

private speed = 0;
public score = 0;
public highScore = parseInt(localStorage.getItem('highScore') || '0', 10);
private player: RunnerPlayer | null = null;
private spawner: Spawner | null = null;
private gameOver = false;

private readonly inputHandler = new InputHandler();
private background: Background = new Background(0, 0, 0);

constructor(
private readonly canvas: HTMLCanvasElement,
private readonly frameRate: number,
groundOffset: number,
private readonly playerOptions: PlayerOptions,
private readonly spawnerOptions: SpawnerOptions,
private readonly difficulty: DifficultyOptions,
) {
this.groundY = this.canvas.height - groundOffset;

const ctx = canvas.getContext('2d');

if (ctx === null) {
throw new Error('Could not get 2d context from canvas');
}

this.ctx = ctx;

this.initialize();
}

initialize(): void {
this.background = new Background(0, this.canvas.width, this.canvas.height);
this.player = RunnerPlayer.create(this.playerOptions, this.groundY);
this.spawner = Spawner.create(this.spawnerOptions, this.canvas.width, this.groundY);
this.speed = 0;
this.score = 0;
this.gameOver = false;
}

start(): void {
setInterval(() => {
this.update();
}, this.frameRate);
}

handleInput(): void {
if (this.inputHandler.isJump) {
// If the game is ended,
// restart the game.
if (this.gameOver) {
// this.initialize();
// Do nothing.
} else {
// otherwise, execute the
// player's jump behaviour.
this.player?.jump();
}
}
}

update(): void {
this.handleInput();

if (this.player === null || this.spawner === null) {
return;
}

// Clear the canvas.
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

// Draw game objects
this.background.draw(this.ctx);
this.drawGround();
// this.drawScore();
this.player?.draw(this.ctx, this.gameOver);
this.spawner?.draw(this.ctx);

if (this.gameOver) {
// Draw game over elements.
this.drawGameOver();
this.updateHighScore(this.score);

// otherwise, execute game behaviour.
} else {
this.increaseDifficulty();

// Execute update.
this.background.update();
this.player.update();
this.spawner.update();

// Check for collisions.
this.gameOver = this.player.overlapsWithOthers(this.spawner.activeObstacles);

if (this.gameOver) {
setTimeout(() => {
this.initialize();
}, 5 * 1000);
}

// Increase score.
// eslint-disable-next-line no-plusplus
this.score++;
}

// if (this.highScore > 0) {
// this.drawHighScore();
// }
}

increaseDifficulty() {
if (this.player === null || this.spawner === null) {
return;
}

if (this.speed < this.difficulty.maxIncreasement) {
this.speed += this.difficulty.speedIncreasement;
this.player.movement.jumpPower += this.difficulty.speedIncreasement;
this.player.movement.gravity += this.difficulty.speedIncreasement;
this.spawner.speed += this.difficulty.speedIncreasement;
}
}

drawGameOver(): void {
this.ctx.font = '40px sans-serif';
this.ctx.beginPath();
this.ctx.fillText(
'Type "jump" to jump',
(this.canvas.width / 2) - 260,
(this.canvas.height / 2) - 50,
);
this.ctx.closePath();

this.ctx.beginPath();
this.ctx.fillText('GAME OVER', (this.canvas.width / 2) - 190, this.canvas.height / 2);
this.ctx.closePath();

this.ctx.font = '30px sans-serif';
this.ctx.beginPath();
this.ctx.fillText(
'Game restarts in 5 seconds',
(this.canvas.width / 2) - 290,
(this.canvas.height / 2) + 50,
);
this.ctx.closePath();
}

drawScore(): void {
this.ctx.beginPath();
this.ctx.fillText(`score: ${this.score}`, 10, this.highScore > 0 ? 40 : 20);
this.ctx.closePath();
}

drawHighScore(): void {
this.ctx.beginPath();
this.ctx.fillText(`HI score: ${this.highScore}`, 10, 20);
this.ctx.closePath();
}

updateHighScore(newHigh: number): void {
this.highScore = Math.max(this.highScore, newHigh);
// localStorage.setItem('highScore', this.highScore.toString());
}

drawGround(): void {
this.ctx.beginPath();
this.ctx.rect(0, this.groundY, this.canvas.width, 3);
this.ctx.fill();
this.ctx.closePath();
}
}
Loading

0 comments on commit 6a7423e

Please sign in to comment.