@manufosela/game-tetris

@manufosela/game-tetris

Complete Tetris game implementation as a Lit 3 web component with full keyboard controls

game-tetris Demo

A complete Tetris game implementation as a web component. Use keyboard controls to play!

Keyboard Controls

Left / A Move left
Right / D Move right
Down / S Soft drop
Up / W / X Rotate
Space Hard drop
P / Esc Pause

Event Log

Demo code (CodePen-ready HTML, CSS, JS)
HTML (html)
<h1>game-tetris Demo</h1>
  <p class="description">A complete Tetris game implementation as a web component. Use keyboard controls to play!</p>

  <div class="settings">
    <div class="setting">
      <label for="themeSelect">Theme:</label>
      <select id="themeSelect">
        <option value="">Default</option>
        <option value="theme-neon">Neon</option>
        <option value="theme-retro">Retro</option>
      </select>
    </div>
    <div class="setting">
      <label for="ghostToggle">Ghost Piece:</label>
      <input type="checkbox" id="ghostToggle" checked>
    </div>
    <div class="setting">
      <label for="gridToggle">Show Grid:</label>
      <input type="checkbox" id="gridToggle" checked>
    </div>
  </div>

  <div class="game-wrapper" id="gameWrapper">
    <game-tetris id="tetris"></game-tetris>
  </div>

  <div class="controls-info">
    <h3>Keyboard Controls</h3>
    <div class="controls-grid">
      <div class="control-item">
        <kbd>Left / A</kbd>
        <span>Move left</span>
      </div>
      <div class="control-item">
        <kbd>Right / D</kbd>
        <span>Move right</span>
      </div>
      <div class="control-item">
        <kbd>Down / S</kbd>
        <span>Soft drop</span>
      </div>
      <div class="control-item">
        <kbd>Up / W / X</kbd>
        <span>Rotate</span>
      </div>
      <div class="control-item">
        <kbd>Space</kbd>
        <span>Hard drop</span>
      </div>
      <div class="control-item">
        <kbd>P / Esc</kbd>
        <span>Pause</span>
      </div>
    </div>
  </div>

  <div class="event-log">
    <h3>Event Log</h3>
    <div class="events" id="events"></div>
  </div>
CSS (css)
:root {
  --bg: #0c0f14;
  --bg-elevated: #141923;
  --bg-panel: #171d28;
  --border: #262f3f;
  --text: #f4f6fb;
  --text-muted: #a7b0c2;
  --text-dim: #7d879b;
  --accent: #ff8a3d;
  --accent-strong: #ff6a00;
  --accent-soft: rgba(255, 138, 61, 0.16);
  --shadow: 0 20px 50px rgba(5, 8, 14, 0.45);
  --radius-lg: 22px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --max-width: 1160px;
}

* {
      box-sizing: border-box;
    }

    h1 {
      color: var(--accent);
      margin-bottom: 10px;
      text-align: center;
    }

    .description {
      color: var(--text-muted);
      margin-bottom: 30px;
      text-align: center;
      max-width: 600px;
    }

    .game-wrapper {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 20px;
    }

    .settings {
      display: flex;
      gap: 15px;
      flex-wrap: wrap;
      justify-content: center;
      margin-bottom: 20px;
    }

    .setting {
      display: flex;
      align-items: center;
      gap: 8px;
      background: var(--bg-elevated);
      padding: 10px 15px;
      border-radius: 8px;
    }

    .setting label {
      font-size: 12px;
      color: var(--text-muted);
    }

    .setting input[type="checkbox"] {
      width: 18px;
      height: 18px;
      cursor: pointer;
    }

    .setting select {
      background: var(--bg-panel);
      color: var(--text);
      border: 1px solid var(--accent);
      padding: 5px 10px;
      border-radius: 4px;
      cursor: pointer;
    }

    .event-log {
      width: 100%;
      max-width: 600px;
      background: var(--bg-elevated);
      border-radius: 8px;
      padding: 15px;
      margin-top: 20px;
    }

    .event-log h3 {
      color: var(--accent);
      margin: 0 0 10px 0;
      font-size: 14px;
    }

    .events {
      max-height: 150px;
      overflow-y: auto;
      font-family: monospace;
      font-size: 11px;
    }

    .events p {
      margin: 5px 0;
      padding: 5px 10px;
      background: var(--bg-panel);
      border-radius: 4px;
    }

    .events p.game-start {
      border-left: 3px solid #2ecc71;
    }

    .events p.game-over {
      border-left: 3px solid #e74c3c;
    }

    .events p.line-clear {
      border-left: 3px solid #f39c12;
    }

    .events p.level-up {
      border-left: 3px solid #9b59b6;
    }

    .events p.score-update {
      border-left: 3px solid #3498db;
    }

    /* Custom theme for tetris */
    game-tetris {
      --game-tetris-bg: var(--bg-panel);
      --game-tetris-board-bg: var(--bg);
      --game-tetris-board-border: 3px solid var(--accent);
      --game-tetris-panel-bg: var(--bg-elevated);
    }

    .theme-neon game-tetris {
      --game-tetris-bg: #0a0a0a;
      --game-tetris-board-bg: #000;
      --game-tetris-board-border: 3px solid #0ff;
      --game-tetris-panel-bg: #111;
      --game-tetris-label-color: #0ff;
      --game-tetris-score-color: #0f0;
      --game-tetris-button-bg: #0ff;
      --game-tetris-button-hover-bg: #0aa;
    }

    .theme-retro game-tetris {
      --game-tetris-bg: #2d2d2d;
      --game-tetris-board-bg: #1a1a1a;
      --game-tetris-board-border: 3px solid #ff6b35;
      --game-tetris-panel-bg: #3d3d3d;
      --game-tetris-label-color: #ff6b35;
      --game-tetris-score-color: #ffd700;
      --game-tetris-button-bg: #ff6b35;
    }

    .controls-info {
      background: var(--bg-elevated);
      padding: 15px 20px;
      border-radius: 8px;
      margin-top: 20px;
      max-width: 600px;
    }

    .controls-info h3 {
      color: var(--accent);
      margin: 0 0 15px 0;
      font-size: 14px;
    }

    .controls-grid {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 10px;
      font-size: 12px;
    }

    .control-item {
      display: flex;
      gap: 10px;
    }

    .control-item kbd {
      background: var(--bg-panel);
      padding: 4px 8px;
      border-radius: 4px;
      border: 1px solid var(--border);
      font-family: monospace;
      min-width: 60px;
      text-align: center;
    }
JS (js)
import "https://esm.sh/@manufosela/game-tetris";

    const tetris = document.getElementById('tetris');
    const gameWrapper = document.getElementById('gameWrapper');
    const events = document.getElementById('events');

    // Log event helper
    function logEvent(message, type) {
      const p = document.createElement('p');
      p.className = type;
      p.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
      events.insertBefore(p, events.firstChild);
      if (events.children.length > 20) {
        events.removeChild(events.lastChild);
      }
    }

    // Event listeners
    tetris.addEventListener('game-start', (e) => {
      logEvent(`Game started! Level: ${e.detail.level}, Speed: ${e.detail.speed}ms`, 'game-start');
    });

    tetris.addEventListener('game-over', (e) => {
      logEvent(`Game Over! Score: ${e.detail.score}, Level: ${e.detail.level}, Lines: ${e.detail.lines}`, 'game-over');
    });

    tetris.addEventListener('line-clear', (e) => {
      const linesText = e.detail.lines === 1 ? 'line' : 'lines';
      logEvent(`Cleared ${e.detail.lines} ${linesText}! Total: ${e.detail.totalLines}`, 'line-clear');
    });

    tetris.addEventListener('level-up', (e) => {
      logEvent(`Level up! Now level ${e.detail.level}, Speed: ${e.detail.speed}ms`, 'level-up');
    });

    tetris.addEventListener('score-update', (e) => {
      if (e.detail.pointsAdded > 10) {
        logEvent(`+${e.detail.pointsAdded} points! Total: ${e.detail.score}`, 'score-update');
      }
    });

    // Settings controls
    document.getElementById('themeSelect').addEventListener('change', (e) => {
      gameWrapper.className = 'game-wrapper ' + e.target.value;
    });

    document.getElementById('ghostToggle').addEventListener('change', (e) => {
      tetris.showGhost = e.target.checked;
    });

    document.getElementById('gridToggle').addEventListener('change', (e) => {
      tetris.showGrid = e.target.checked;
    });