Google Calendar Breakout Game

Play Breakout on your calendar - events become bricks that temporarily hide for 10 minutes when hit

Size

11.6 KB

Version

1.0.1

Created

Oct 22, 2025

Updated

1 day ago

1// ==UserScript==
2// @name		Google Calendar Breakout Game
3// @description		Play Breakout on your calendar - events become bricks that temporarily hide for 10 minutes when hit
4// @version		1.0.1
5// @match		https://*.calendar.google.com/*
6// ==/UserScript==
7(function() {
8    'use strict';
9
10    console.log('Google Calendar Breakout Game initialized');
11
12    // Game state
13    let gameActive = false;
14    let canvas, ctx;
15    let paddle, ball;
16    let hiddenEvents = new Map(); // Track hidden events with timestamps
17    let animationFrameId;
18
19    // Game constants
20    const PADDLE_WIDTH = 100;
21    const PADDLE_HEIGHT = 15;
22    const BALL_RADIUS = 8;
23    const BALL_SPEED = 4;
24    const HIDE_DURATION = 10 * 60 * 1000; // 10 minutes in milliseconds
25
26    // Paddle object
27    class Paddle {
28        constructor(canvasWidth, canvasHeight) {
29            this.width = PADDLE_WIDTH;
30            this.height = PADDLE_HEIGHT;
31            this.x = canvasWidth / 2 - this.width / 2;
32            this.y = canvasHeight - 30;
33            this.speed = 8;
34            this.canvasWidth = canvasWidth;
35        }
36
37        draw(ctx) {
38            ctx.fillStyle = '#4285f4';
39            ctx.fillRect(this.x, this.y, this.width, this.height);
40        }
41
42        moveLeft() {
43            this.x = Math.max(0, this.x - this.speed);
44        }
45
46        moveRight() {
47            this.x = Math.min(this.canvasWidth - this.width, this.x + this.speed);
48        }
49    }
50
51    // Ball object
52    class Ball {
53        constructor(canvasWidth, canvasHeight) {
54            this.radius = BALL_RADIUS;
55            this.x = canvasWidth / 2;
56            this.y = canvasHeight - 50;
57            this.dx = BALL_SPEED * (Math.random() > 0.5 ? 1 : -1);
58            this.dy = -BALL_SPEED;
59            this.canvasWidth = canvasWidth;
60            this.canvasHeight = canvasHeight;
61        }
62
63        draw(ctx) {
64            ctx.beginPath();
65            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
66            ctx.fillStyle = '#ea4335';
67            ctx.fill();
68            ctx.closePath();
69        }
70
71        update(paddle) {
72            this.x += this.dx;
73            this.y += this.dy;
74
75            // Wall collision
76            if (this.x + this.radius > this.canvasWidth || this.x - this.radius < 0) {
77                this.dx = -this.dx;
78            }
79            if (this.y - this.radius < 0) {
80                this.dy = -this.dy;
81            }
82
83            // Paddle collision
84            if (this.y + this.radius > paddle.y &&
85                this.x > paddle.x &&
86                this.x < paddle.x + paddle.width) {
87                this.dy = -Math.abs(this.dy);
88                // Add some angle variation based on where ball hits paddle
89                const hitPos = (this.x - paddle.x) / paddle.width;
90                this.dx = BALL_SPEED * (hitPos - 0.5) * 2;
91            }
92
93            // Ball falls off bottom - reset
94            if (this.y - this.radius > this.canvasHeight) {
95                this.reset();
96            }
97        }
98
99        reset() {
100            this.x = this.canvasWidth / 2;
101            this.y = this.canvasHeight - 50;
102            this.dx = BALL_SPEED * (Math.random() > 0.5 ? 1 : -1);
103            this.dy = -BALL_SPEED;
104        }
105    }
106
107    // Get all calendar event elements
108    function getCalendarEvents() {
109        return Array.from(document.querySelectorAll('div[data-eventchip][role="button"]'));
110    }
111
112    // Hide an event temporarily
113    function hideEvent(eventElement) {
114        if (!eventElement || hiddenEvents.has(eventElement)) return;
115
116        console.log('Hiding event temporarily');
117        eventElement.style.opacity = '0';
118        eventElement.style.pointerEvents = 'none';
119        
120        const hideTime = Date.now();
121        hiddenEvents.set(eventElement, hideTime);
122
123        // Schedule reappearance after 10 minutes
124        setTimeout(() => {
125            showEvent(eventElement);
126        }, HIDE_DURATION);
127    }
128
129    // Show an event again
130    function showEvent(eventElement) {
131        if (!eventElement) return;
132
133        console.log('Showing event again');
134        eventElement.style.opacity = '';
135        eventElement.style.pointerEvents = '';
136        hiddenEvents.delete(eventElement);
137    }
138
139    // Check collision between ball and events
140    function checkEventCollisions() {
141        const events = getCalendarEvents();
142        const canvasRect = canvas.getBoundingClientRect();
143
144        events.forEach(event => {
145            if (hiddenEvents.has(event)) return; // Skip already hidden events
146
147            const eventRect = event.getBoundingClientRect();
148            
149            // Convert event position to canvas coordinates
150            const eventX = eventRect.left - canvasRect.left;
151            const eventY = eventRect.top - canvasRect.top;
152            const eventWidth = eventRect.width;
153            const eventHeight = eventRect.height;
154
155            // Check if ball intersects with event
156            const closestX = Math.max(eventX, Math.min(ball.x, eventX + eventWidth));
157            const closestY = Math.max(eventY, Math.min(ball.y, eventY + eventHeight));
158
159            const distanceX = ball.x - closestX;
160            const distanceY = ball.y - closestY;
161            const distanceSquared = distanceX * distanceX + distanceY * distanceY;
162
163            if (distanceSquared < ball.radius * ball.radius) {
164                // Collision detected!
165                hideEvent(event);
166                
167                // Bounce ball
168                if (Math.abs(distanceX) > Math.abs(distanceY)) {
169                    ball.dx = -ball.dx;
170                } else {
171                    ball.dy = -ball.dy;
172                }
173            }
174        });
175    }
176
177    // Game loop
178    function gameLoop() {
179        if (!gameActive) return;
180
181        // Clear canvas
182        ctx.clearRect(0, 0, canvas.width, canvas.height);
183
184        // Update and draw
185        ball.update(paddle);
186        checkEventCollisions();
187        
188        paddle.draw(ctx);
189        ball.draw(ctx);
190
191        animationFrameId = requestAnimationFrame(gameLoop);
192    }
193
194    // Keyboard controls
195    const keys = {};
196    function handleKeyDown(e) {
197        if (!gameActive) return;
198        keys[e.key] = true;
199
200        if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
201            paddle.moveLeft();
202        }
203        if (keys['ArrowRight'] || keys['d'] || keys['D']) {
204            paddle.moveRight();
205        }
206    }
207
208    function handleKeyUp(e) {
209        keys[e.key] = false;
210    }
211
212    // Mouse controls
213    function handleMouseMove(e) {
214        if (!gameActive) return;
215        const rect = canvas.getBoundingClientRect();
216        const mouseX = e.clientX - rect.left;
217        paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, mouseX - paddle.width / 2));
218    }
219
220    // Create game canvas overlay
221    function createGameCanvas() {
222        canvas = document.createElement('canvas');
223        canvas.id = 'breakout-game-canvas';
224        canvas.style.cssText = `
225            position: fixed;
226            top: 0;
227            left: 0;
228            width: 100%;
229            height: 100%;
230            z-index: 9998;
231            pointer-events: none;
232        `;
233        document.body.appendChild(canvas);
234
235        // Set canvas size
236        canvas.width = window.innerWidth;
237        canvas.height = window.innerHeight;
238        ctx = canvas.getContext('2d');
239
240        // Initialize game objects
241        paddle = new Paddle(canvas.width, canvas.height);
242        ball = new Ball(canvas.width, canvas.height);
243
244        // Handle window resize
245        window.addEventListener('resize', () => {
246            canvas.width = window.innerWidth;
247            canvas.height = window.innerHeight;
248            paddle.canvasWidth = canvas.width;
249            ball.canvasWidth = canvas.width;
250            ball.canvasHeight = canvas.height;
251        });
252
253        console.log('Game canvas created');
254    }
255
256    // Create toggle button
257    function createToggleButton() {
258        const button = document.createElement('button');
259        button.id = 'breakout-toggle-btn';
260        button.textContent = '🎮 Start Breakout';
261        button.style.cssText = `
262            position: fixed;
263            top: 20px;
264            right: 20px;
265            z-index: 9999;
266            padding: 12px 20px;
267            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
268            color: white;
269            border: none;
270            border-radius: 8px;
271            font-size: 16px;
272            font-weight: bold;
273            cursor: pointer;
274            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
275            transition: all 0.3s ease;
276            font-family: 'Google Sans', Roboto, Arial, sans-serif;
277        `;
278
279        button.addEventListener('mouseenter', () => {
280            button.style.transform = 'translateY(-2px)';
281            button.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.3)';
282        });
283
284        button.addEventListener('mouseleave', () => {
285            button.style.transform = 'translateY(0)';
286            button.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)';
287        });
288
289        button.addEventListener('click', toggleGame);
290        document.body.appendChild(button);
291
292        console.log('Toggle button created');
293    }
294
295    // Toggle game on/off
296    function toggleGame() {
297        gameActive = !gameActive;
298        const button = document.getElementById('breakout-toggle-btn');
299
300        if (gameActive) {
301            button.textContent = '⏸️ Stop Breakout';
302            button.style.background = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)';
303            canvas.style.pointerEvents = 'auto';
304            
305            // Add event listeners
306            document.addEventListener('keydown', handleKeyDown);
307            document.addEventListener('keyup', handleKeyUp);
308            canvas.addEventListener('mousemove', handleMouseMove);
309            
310            // Reset ball position
311            ball.reset();
312            
313            // Start game loop
314            gameLoop();
315            
316            console.log('Game started');
317        } else {
318            button.textContent = '🎮 Start Breakout';
319            button.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
320            canvas.style.pointerEvents = 'none';
321            
322            // Remove event listeners
323            document.removeEventListener('keydown', handleKeyDown);
324            document.removeEventListener('keyup', handleKeyUp);
325            canvas.removeEventListener('mousemove', handleMouseMove);
326            
327            // Stop game loop
328            if (animationFrameId) {
329                cancelAnimationFrame(animationFrameId);
330            }
331            
332            // Clear canvas
333            ctx.clearRect(0, 0, canvas.width, canvas.height);
334            
335            console.log('Game stopped');
336        }
337    }
338
339    // Restore any hidden events on page unload
340    window.addEventListener('beforeunload', () => {
341        hiddenEvents.forEach((time, event) => {
342            showEvent(event);
343        });
344    });
345
346    // Initialize game when calendar is ready
347    function init() {
348        // Wait for calendar to load
349        const checkCalendar = setInterval(() => {
350            const events = getCalendarEvents();
351            if (events.length > 0) {
352                clearInterval(checkCalendar);
353                console.log(`Found ${events.length} calendar events`);
354                
355                createGameCanvas();
356                createToggleButton();
357                
358                console.log('Breakout game ready! Click the button to start playing.');
359                console.log('Controls: Arrow keys or A/D to move paddle, or use your mouse!');
360            }
361        }, 1000);
362    }
363
364    // Start initialization
365    if (document.readyState === 'loading') {
366        document.addEventListener('DOMContentLoaded', init);
367    } else {
368        init();
369    }
370})();
Google Calendar Breakout Game | Robomonkey