Josh Bruce Online

Sirtet - Speedrunning Tetris Game Documentation

Overview

“Sirtet” is a competitive speedrunning Tetris game with a unique reverse scoring system and comprehensive Firebase leaderboard integration. The game features a time-based challenge where players must clear lines to reduce their score to zero as quickly as possible, creating an innovative twist on the classic Tetris formula. Built with Google Analytics tracking and administrative controls, it provides a complete competitive gaming experience.

Technical Architecture

Core Technologies

File Structure

sirtet/
├── index.html              # Complete Tetris game implementation (881 lines)
├── sirtetCnfg.js          # Firebase configuration file (referenced but not analyzed)
├── tetrisfavicon.ico      # Tetris-themed favicon (referenced but not analyzed)
└── loganWseal.png         # Logan W seal image (referenced but not analyzed)

External Dependencies

<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-WPNF0DNSMX"></script>

<!-- Firebase SDK -->
<script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-database.js"></script>

Unique Game Mechanics

Reverse Scoring System

let score = 750;                    /* Starting score */

// Scoring based on lines cleared
if (linesCleared > 0) {
  if (linesCleared === 1) {
    score -= 100;                   /* Single line: -100 points */
  } else if (linesCleared === 2) {
    score -= 250;                   /* Double line: -250 points */
  } else if (linesCleared === 3) {
    score -= 500;                   /* Triple line: -500 points */
  }
}

// Win condition: reach score of 0
if (score <= 0) {
  showGameOver()
}

Speedrun Timer System

let totalSecondsPassed = 0;
let formattedTime = `00:00`;

function updateTimer() {
  const minutes = Math.floor(totalSecondsPassed / 60);
  const seconds = totalSecondsPassed % 60;
  
  // Format as MM:SS
  formattedTime = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  
  document.getElementById('timer').textContent = formattedTime;
}

function startTimer() {
  timerInterval = setInterval(() => {
    totalSecondsPassed++;
    updateTimer();
  }, 1000);
}

Tetris Game Engine

Standard Tetromino Implementation

const tetrominos = {
  'I': [
    [0,0,0,0],
    [1,1,1,1],
    [0,0,0,0],
    [0,0,0,0]
  ],
  'J': [
    [1,0,0],
    [1,1,1],
    [0,0,0],
  ],
  'L': [
    [0,0,1],
    [1,1,1],
    [0,0,0],
  ],
  'O': [
    [1,1],
    [1,1],
  ],
  'S': [
    [0,1,1],
    [1,1,0],
    [0,0,0],
  ],
  'Z': [
    [1,1,0],
    [0,1,1],
    [0,0,0],
  ],
  'T': [
    [0,1,0],
    [1,1,1],
    [0,0,0],
  ]
};

Color Scheme

const colors = {
  'I': '#43A3B7',              /* Cyan for I-piece */
  'O': '#F6BD44',              /* Yellow for O-piece */
  'T': '#6F41B1',              /* Purple for T-piece */
  'S': '#4CA84C',              /* Green for S-piece */
  'Z': '#E14331',              /* Red for Z-piece */
  'J': '#4D86F7',              /* Blue for J-piece */
  'L': '#DC6C33'               /* Orange for L-piece */
};

Game Physics

function loop() {
  rAF = requestAnimationFrame(loop);
  context.clearRect(0, 0, canvas.width, canvas.height);

  // Tetromino falls every 35 frames (approximately 0.583 seconds at 60 FPS)
  if (++count > 35) {
    tetromino.row++;
    count = 0;

    // Check for collision and place piece
    if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) {
      tetromino.row--;
      placeTetromino();
    }
  }
}

Firebase Leaderboard System

Dual Firebase Architecture

// Primary game database
firebase.initializeApp(firebaseConfig);
const leaderboardRef = firebase.database().ref("leaderboard");

// Administrative control database  
const switchFirebaseConfig = {
  apiKey: "AIzaSyCRPBjKJfKoLO52TqROpQ3I9X-nEL-0btE",
  databaseURL: "https://joshbruceonline-switch-default-rtdb.europe-west1.firebasedatabase.app/",
  // ... additional configuration
};

const switchApp = firebase.initializeApp(switchFirebaseConfig, 'switch');
const switchPageRef = switchApp.database().ref('sirtet');

Leaderboard Data Management

leaderboardRef.orderByChild("score").on("value", function (snapshot) {
  const leaderboardEntries = {};

  snapshot.forEach(function (childSnapshot) {
    const entry = childSnapshot.val();
    const playerName = entry.name;
    const playerScore = entry.score;

    // Keep only the best (lowest) score for each player
    if (playerName in leaderboardEntries) {
      if (playerScore < leaderboardEntries[playerName]) {
        leaderboardEntries[playerName] = playerScore;
      }
    } else {
      leaderboardEntries[playerName] = playerScore;
    }
  });
});

Medal System Implementation

// Display leaderboard with medal emojis
sortedEntries.forEach(function (entry, index) {
  const listItem = document.createElement("li");
  const playerName = entry.name;
  const playerScore = entry.score;

  if (index === 0) {
    listItem.innerHTML = `${playerName}: ${playerScore} 🥇`;      /* Gold medal */
  } else if (index === 1) {
    listItem.innerHTML = `${playerName}: ${playerScore} 🥈`;      /* Silver medal */
  } else if (index === 2) {
    listItem.innerHTML = `${playerName}: ${playerScore} 🥉`;      /* Bronze medal */
  } else {
    listItem.innerHTML = `${playerName}: ${playerScore}`;
  }

  leaderboardList.appendChild(listItem);
});

Content Moderation System

Comprehensive Name Filtering

function nameInput() {
  const name = prompt("Game Over. Enter your name:");
  
  const bannedNamesString = "cock,balls,ballz,pussy,fuck,shit,cunt,suck,bitch,nig,penis,france,french,fag,kill,uh,ass,arse,nonce,piss,pedo,slut,whore,twat,ret,ard,hate,gay,ligma,slay,tran,gun";
  const bannedNames = bannedNamesString.split(",");

  function isNameBanned(name, bannedNames) {
    const lowercaseName = name.toLowerCase();
    for (const bannedName of bannedNames) {
      if (lowercaseName.includes(bannedName)) {
        return true;
      }
    }
    return false;
  }

  // Multiple validation layers
  if (name.length > 5 && name !== "Logan W") {
    alert("Names can't be more than 5 characters in length!");
    nameInput();
    return;
  }

  if (isNameBanned(name, bannedNames)) {
    alert("Names will be displayed on the leaderboard. This name includes restricted words! Please enter another name.");
    nameInput();
    return;
  }
}

Automated Content Cleanup

// Delete entries with names longer than 5 characters
function deleteLongNames() {
  const leaderboardRef = firebase.database().ref("leaderboard");

  leaderboardRef.once("value", function (snapshot) {
    snapshot.forEach(function (childSnapshot) {
      const entry = childSnapshot.val();
      const entryKey = childSnapshot.key;

      if (entry.name.length > 5 && entry.name != 'Logan W') {
        leaderboardRef.child(entryKey).remove();
      }
    });
  });
}

Game Modes and Features

Nat Mode Toggle System

let isToggled = false;

function togglePage() {
  // Visual theme toggle
  if (isToggled) {
    document.body.style.backgroundColor = 'black';
  } else {
    document.body.style.backgroundColor = 'white';
  }

  // Modify tetromino shapes for increased difficulty
  if (!isToggled) {
    // Altered Z-piece becomes more complex
    tetrominos['Z'] = [
      [0, 1, 1, 1],
      [1, 1, 0, 0],
      [1, 1, 1, 1],
      [0, 1, 0, 1],
    ];

    // Altered O-piece becomes asymmetric
    tetrominos['O'] = [
      [0, 0],
      [0, 1],
    ];
  }
}

Keyboard Controls

document.addEventListener('keydown', function(e) {
  if (gameOver) return;

  // Left and right arrow keys + A/D keys (move)
  if (e.which === 37 || e.which === 65) {  /* Left arrow or A */
    const col = tetromino.col - 1;
    if (isValidMove(tetromino.matrix, tetromino.row, col)) {
      tetromino.col = col;
    }
  }

  if (e.which === 39 || e.which === 68) {  /* Right arrow or D */
    const col = tetromino.col + 1;
    if (isValidMove(tetromino.matrix, tetromino.row, col)) {
      tetromino.col = col;
    }
  }

  // Up arrow key + W key (rotate)
  if (e.which === 38 || e.which === 87) {
    const matrix = rotate(tetromino.matrix);
    if (isValidMove(matrix, tetromino.row, tetromino.col)) {
      tetromino.matrix = matrix;
    }
  }

  // Down arrow key + S key (drop)
  if(e.which === 40 || e.which === 83) {
    const row = tetromino.row + 1;
    if (!isValidMove(tetromino.matrix, row, tetromino.col)) {
      tetromino.row = row - 1;
      placeTetromino();
      return;
    }
    tetromino.row = row;
  }
});

Visual Design and Layout

Game Interface Layout

canvas {
  border: 3px solid white;
  position: fixed;
  top: 100px;
  right: 120px;
}

.main-title {
  text-align: center;
  position: fixed;
  top: 0px;
  left: 0;
  width: 100%;
  font-size: 25px;
  text-decoration: underline;
}

.leaderboard {
  position: fixed;
  top: 70px;
  left: 80px;
  width: 20%;
  text-align: center;
}

Timer Display

.timer {
  text-align: center;
  position: fixed;
  top: 10vh;
  left: 0;
  width: 100%;
  font-size: 35px;
  font-weight: bold;
}

Analytics and Tracking

Google Analytics Integration

<script async src="https://www.googletagmanager.com/gtag/js?id=G-WPNF0DNSMX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-WPNF0DNSMX');
</script>

User Activity Monitoring

Administrative Control System

Remote Game Management

switchPageRef.on('value', (snapshot) => {
  const isPageActive = snapshot.val();
  if (isPageActive === false) {
    redirectLinkRef.once('value', (snapshot) => {
      const redirectLink = snapshot.val();
      if (redirectLink) {
        window.location.href = redirectLink;
      } else {
        window.location.href = 'https://joshbruce.online/backrooms';
      }
    });
  }
});

Administrative Features

Competitive Gaming Features

Speedrun Optimization

Special Player Privileges

if (name === "Logan W") {
  // Special handling for Logan W - bypass length restrictions
  database.ref("leaderboard").push({
    name: name,
    score: formattedTime,
    timestamp: serverTimestamp,
  });
  return;
}

Security Features

Right-Click Protection

document.addEventListener('contextmenu', function(e) {
  e.preventDefault();
});

Data Validation

Performance Optimization

Efficient Game Loop

// 60 FPS game loop with RequestAnimationFrame
function loop() {
  rAF = requestAnimationFrame(loop);
  context.clearRect(0, 0, canvas.width, canvas.height);
  
  // Optimized rendering
  for (let row = 0; row < 20; row++) {
    for (let col = 0; col < 10; col++) {
      if (playfield[row][col]) {
        const name = playfield[row][col];
        context.fillStyle = colors[name];
        context.fillRect(col * grid, row * grid, grid - 1, grid - 1);
      }
    }
  }
}

Memory Management

Future Enhancement Opportunities

Gameplay Features

  1. Multiple Game Modes: Marathon, Sprint, Ultra variants
  2. Power-ups: Special abilities and bonus items
  3. Multiplayer: Real-time competitive matches
  4. Replay System: Record and share gameplay sessions
  5. Achievement System: Unlock badges and rewards

Technical Improvements

  1. Mobile Support: Touch controls for mobile devices
  2. Sound System: Audio effects and background music
  3. Visual Effects: Particle systems and animations
  4. Save System: Progress and settings persistence
  5. Offline Mode: Local play without internet connection

Code Quality Assessment

Strengths

Areas for Enhancement

Conclusion

Sirtet represents an innovative take on classic Tetris gameplay, successfully combining competitive speedrunning mechanics with comprehensive online infrastructure. The reverse scoring system creates unique strategic challenges while the Firebase integration provides professional-grade leaderboard management.

Technical Rating: 8.7/10

The application successfully demonstrates how classic games can be enhanced with modern competitive features while maintaining the core gameplay that makes them timeless.