DEV Community

Cover image for Azure Arcade: Deploy & Play a Modern Snake Game on App Service
Oladosu Ibrahim
Oladosu Ibrahim

Posted on • Edited on

Azure Arcade: Deploy & Play a Modern Snake Game on App Service

Introduction

Azure App Service makes it ridiculously easy to host web apps without managing servers. In this hands-on guide, you’ll create a Windows-based App Service, deploy a modern Snake game directly from your browser using Advanced Tools (Kudu), and then practice Deployment Slots with traffic routing and swap, just like a real production rollout.

By the end, your app will be live on a public URL, you’ll have a safe “New Feature” slot for testing, and you’ll understand how to gradually send users to the new version before swapping it into production.

Skilling Objectives

  • Create and configure an Azure App Service (Windows) and Plan.
  • Use Advanced Tools (Kudu) to upload/edit web content in wwwroot.
  • Publish a modern HTML/CSS/JS game as your website.
  • Create a Deployment Slot, send 6% traffic to it, and swap safely.

Step 1: Create the Web App

What is App Service?
Azure App Service is a PaaS offering for hosting web apps, APIs, and static sites with built-in scaling, deployment slots, and SSL.

Steps

  1. Log in to the Azure Portal.
  2. Search for App ServicesCreateWebApp
    Image 1
    Image 2

  3. Resource Group: IbrahimRG (create new if it doesn’t exist).

  4. Name: Console (or any globally unique name).

  5. Publish: Code

  6. Runtime stack: .NET 9 (STS)

  7. Operating System: Windows

  8. Region: your nearest region.
    Image 3

  9. App Service Plan: Create new → name it ConsolePlan.

  10. Click Review + createCreate.
    Image 4

  11. After deployment, click Go to resource.
    Image 5

Your App Service is live with a default domain.

Step 2: Generate the Game Code

You’ll deploy a single-page app (index.html) that includes HTML, CSS, and JavaScript for an intuitive, interactive, and modern Snake game: keyboard & touch controls, pause, levels, high score, and a sleek UI.

Copy everything below into a file named index.html (you’ll paste it in Step 3).

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Snake • Azure Arcade</title> <style> :root{ --bg:#0f1222; --panel:#151935; --panel-2:#0b0e1e; --text:#e8ecff; --muted:#9aa3c7; --accent:#6ee7ff; --accent-2:#a78bfa; --good:#34d399; --bad:#ef4444; --grid: 24; /* tile size */ } *{box-sizing:border-box} html,body{height:100%;margin:0;background:radial-gradient(1200px 700px at 20% -10%, #1b2046 0%, #0f1222 55%) fixed;color:var(--text);font-family:Inter,system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif} .wrap{max-width:1100px;margin:0 auto;padding:24px} header{display:flex;gap:12px;align-items:center;justify-content:space-between;margin-bottom:16px} .title{font-weight:800;letter-spacing:.3px;font-size:clamp(20px,3.5vw,28px)} .card{background:linear-gradient(180deg,var(--panel),var(--panel-2));border:1px solid rgba(255,255,255,.06);border-radius:18px;box-shadow:0 10px 30px rgba(0,0,0,.35)} .board{display:grid;grid-template-columns:1fr 360px;gap:18px} @media (max-width:960px){.board{grid-template-columns:1fr}} .canvasWrap{position:relative;padding:16px} canvas{display:block;margin:0 auto;background:linear-gradient(180deg,#0b1228,#0a0e20);border:1px solid rgba(255,255,255,.06);border-radius:14px} .hud{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin:12px 0} .pill{padding:8px 12px;border-radius:999px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);font-weight:600} .accent{background:linear-gradient(90deg, rgba(110,231,255,.22), rgba(167,139,250,.22));border-color:rgba(255,255,255,.18)} .side{padding:18px} .side h3{margin:6px 0 10px;font-size:14px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em} .btn{cursor:pointer;user-select:none;display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:12px;border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.04);font-weight:700;transition:transform .05s} .btn:hover{background:rgba(255,255,255,.08)} .btn:active{transform:translateY(1px)} .btn.primary{background:linear-gradient(90deg,#5dd8ff,#9b85ff);color:#051018;border:none} .grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px} .note{color:var(--muted);font-size:13px;line-height:1.5} .controls{display:grid;grid-template-columns:repeat(3,64px);gap:8px;justify-content:center;margin-top:10px} .pad{height:64px} .pad:nth-child(2){grid-column:2} .pad:nth-child(3){grid-column:1} .pad:nth-child(4){grid-column:2} .pad:nth-child(5){grid-column:3} .legend{display:flex;gap:10px;justify-content:center;font-size:12px;color:var(--muted);margin-top:8px} .foodDot,.snakeDot,.wallDot{width:10px;height:10px;border-radius:3px} .foodDot{background:var(--good)} .snakeDot{background:var(--accent)} .wallDot{background:#3b82f6} </style> </head> <body> <div class="wrap"> <header> <div class="title">Azure Arcade — Advanced Snake</div> <div class="hud"> <div class="pill accent">Score: <span id="score">0</span></div> <div class="pill">High: <span id="high">0</span></div> <div class="pill">Level: <span id="level">1</span></div> <button id="pauseBtn" class="btn">Pause</button> <button id="restartBtn" class="btn">Restart</button> </div> </header> <div class="board"> <div class="card canvasWrap"> <canvas id="game" width="720" height="480" aria-label="Snake game area"></canvas> <div class="legend"> <div class="snakeDot"></div> Snake <div class="foodDot"></div> Food <div class="wallDot"></div> Wall </div> </div> <aside class="card side"> <h3>How to play</h3> <div class="note">Use <b>Arrow keys / WASD</b> to move. Press <b>Space</b> to pause. On mobile, use the on-screen pad.</div> <div class="controls"> <button class="btn pad" data-dir="up"></button> <button class="btn pad" data-dir="left"></button> <button class="btn pad" data-dir="down"></button> <button class="btn pad" data-dir="right"></button> </div> <h3>Game rules</h3> <ul class="note" style="margin:0 0 10px 18px"> <li>Eat food to grow and score.</li> <li>Don’t hit walls or yourself.</li> <li>Speed increases every level (every 5 foods).</li> </ul> <div class="grid"> <button id="slowBtn" class="btn">Slow</button> <button id="fastBtn" class="btn">Fast</button> <button id="toggleWalls" class="btn">Toggle Walls</button> <button id="resetHigh" class="btn">Reset High</button> </div> </aside> </div> </div> <script> // --- Config --- const tile = 24; // px per tile const cols = 28; // 28 * 24 = 672 wide area const rows = 18; // 18 * 24 = 432 high area const border = 2 * tile; // inner padding for walls const baseSpeed = 120; // ms per tick (gets faster) const levelEvery = 5; // foods per level // --- Canvas setup --- const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); canvas.width = cols * tile + border * 2; canvas.height = rows * tile + border * 2; // --- UI refs --- const scoreEl = document.getElementById('score'); const highEl = document.getElementById('high'); const levelEl = document.getElementById('level'); const pauseBtn = document.getElementById('pauseBtn'); const restartBtn = document.getElementById('restartBtn'); const slowBtn = document.getElementById('slowBtn'); const fastBtn = document.getElementById('fastBtn'); const toggleWallsBtn = document.getElementById('toggleWalls'); const resetHighBtn = document.getElementById('resetHigh'); document.querySelectorAll('[data-dir]').forEach(b => b.addEventListener('click', () => onDirection(b.dataset.dir))); // --- State --- let snake, dir, nextDir, food, score, level, wallsOn, tick, speed, paused, over; const highScore = () => Number(localStorage.getItem('snakeHigh') || 0); const setHighScore = (v) => localStorage.setItem('snakeHigh', String(v)); function reset() { snake = [ {x: Math.floor(cols/2), y: Math.floor(rows/2)}, {x: Math.floor(cols/2)-1, y: Math.floor(rows/2)}, ]; dir = {x:1,y:0}; nextDir = {...dir}; score = 0; level = 1; speed = baseSpeed; wallsOn = true; paused = false; over = false; spawnFood(); updateHUD(); loop(); } function updateHUD(){ scoreEl.textContent = score; highEl.textContent = highScore(); levelEl.textContent = level; pauseBtn.textContent = paused ? 'Resume' : 'Pause'; toggleWallsBtn.textContent = wallsOn ? 'Disable Walls' : 'Enable Walls'; } function spawnFood(){ do { food = { x: Math.floor(Math.random()*cols), y: Math.floor(Math.random()*rows) }; } while (snake.some(s => s.x===food.x && s.y===food.y)); } function drawBoard(){ // outer background already stylish; draw inner playfield const x0 = border, y0 = border, w = cols*tile, h = rows*tile; // playfield const grd = ctx.createLinearGradient(0,y0,0,y0+h); grd.addColorStop(0, '#0c1530'); grd.addColorStop(1, '#0a1026'); ctx.fillStyle = grd; ctx.fillRect(x0, y0, w, h); // decorative grid dots ctx.fillStyle = 'rgba(255,255,255,.04)'; for(let y=0;y<rows;y++){ for(let x=0;x<cols;x++){ ctx.fillRect(x0 + x*tile + tile/2 - 1, y0 + y*tile + tile/2 - 1, 2, 2); } } // walls if (wallsOn){ ctx.fillStyle = '#3b82f6'; ctx.fillRect(x0-4, y0-4, w+8, 4); // top ctx.fillRect(x0-4, y0+h, w+8, 4); // bottom ctx.fillRect(x0-4, y0, 4, h); // left ctx.fillRect(x0+w, y0, 4, h); // right } } function drawSnake(){ for(let i=0;i<snake.length;i++){ const s = snake[i]; const px = border + s.x*tile; const py = border + s.y*tile; const head = i===0; if(head){ // head gradient const g = ctx.createLinearGradient(px, py, px, py+tile); g.addColorStop(0, '#6ee7ff'); g.addColorStop(1, '#a78bfa'); ctx.fillStyle = g; } else { ctx.fillStyle = 'rgba(167,139,250,.85)'; } ctx.beginPath(); ctx.roundRect(px+3, py+3, tile-6, tile-6, 6); ctx.fill(); } } function drawFood(){ const px = border + food.x*tile + tile/2; const py = border + food.y*tile + tile/2; ctx.fillStyle = '#34d399'; ctx.beginPath(); ctx.arc(px, py, tile*0.32, 0, Math.PI*2); ctx.fill(); // glow ctx.beginPath(); ctx.arc(px, py, tile*0.55, 0, Math.PI*2); ctx.fillStyle = 'rgba(52,211,153,.14)'; ctx.fill(); } function step(){ if (paused || over) return; // apply nextDir (prevent instant reversal) if ((nextDir.x !== -dir.x) || (nextDir.y !== -dir.y)) dir = nextDir; // new head const head = {x: snake[0].x + dir.x, y: snake[0].y + dir.y}; // wrap or collide with walls if (wallsOn){ if (head.x<0 || head.x>=cols || head.y<0 || head.y>=rows){ return gameOver(); } } else { head.x = (head.x + cols) % cols; head.y = (head.y + rows) % rows; } // self-collision if (snake.some((s, i) => i>0 && s.x===head.x && s.y===head.y)) return gameOver(); snake.unshift(head); // eat or move if (head.x===food.x && head.y===food.y){ score++; if (score > highScore()) setHighScore(score); // level up if (score % levelEvery === 0){ level++; speed = Math.max(50, speed - 10); } spawnFood(); } else { snake.pop(); } draw(); tick = setTimeout(step, speed); } function draw(){ ctx.clearRect(0,0,canvas.width,canvas.height); drawBoard(); drawFood(); drawSnake(); updateHUD(); } function loop(){ clearTimeout(tick); draw(); tick = setTimeout(step, speed); } function onDirection(which){ const map = {up:{x:0,y:-1},down:{x:0,y:1},left:{x:-1,y:0},right:{x:1,y:0}}; nextDir = map[which] || nextDir; } function gameOver(){ over = true; updateHUD(); // overlay ctx.fillStyle = 'rgba(0,0,0,.55)'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.fillStyle = '#fff'; ctx.font = '700 28px Inter, system-ui, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Game Over', canvas.width/2, canvas.height/2 - 8); ctx.font = '500 16px Inter, system-ui, sans-serif'; ctx.fillText('Press Restart to play again', canvas.width/2, canvas.height/2 + 18); } // --- Input --- window.addEventListener('keydown', (e)=>{ const k = e.key.toLowerCase(); if (k==='arrowup' || k==='w') onDirection('up'); else if (k==='arrowdown' || k==='s') onDirection('down'); else if (k==='arrowleft' || k==='a') onDirection('left'); else if (k==='arrowright' || k==='d') onDirection('right'); else if (k===' ') { paused = !paused; updateHUD(); if (!paused) loop(); } }); pauseBtn.addEventListener('click', ()=>{ paused = !paused; updateHUD(); if (!paused) loop(); }); restartBtn.addEventListener('click', ()=> reset()); slowBtn.addEventListener('click', ()=>{ speed = Math.min(220, speed+20); }); fastBtn.addEventListener('click', ()=>{ speed = Math.max(40, speed-20); }); toggleWallsBtn.addEventListener('click', ()=>{ wallsOn = !wallsOn; updateHUD(); }); resetHighBtn.addEventListener('click', ()=>{ setHighScore(0); updateHUD(); }); // Polyfill for roundRect on older browsers if (!CanvasRenderingContext2D.prototype.roundRect) { CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); return this; } } reset(); </script> </body> </html> 
Enter fullscreen mode Exit fullscreen mode

Step 3: Deploy via Advanced Tools (Kudu)

Why Kudu?
Kudu is the engine behind App Service that lets you browse/edit the site file system, run commands, and view logs—right from the browser.

Steps

  1. In your App Service, go to Development ToolsAdvanced ToolsGo
    Image 6

  2. A new Kudu tab opens. Select Debug consoleCMD
    Image 7

  3. Navigate: sitewwwroot
    Image 8
    Image 9

  4. If you see hostingstart.html, delete it (otherwise it may override index.html).

  5. Click the pencil icon to create/edit a file named index.html
    Image 10

  6. Paste the entire game code from Step 2, then Save.
    Image 11

Your website now serves the Snake game from wwwroot/index.html.

Step 4: Open Your Public URL

  1. Go back to your App Service Overview page.
  2. Under Default domain, click the link (e.g., https://console.azurewebsites.net or similar)
    Image 12

  3. Your Snake game should load instantly. Play away!
    Image 13

Troubleshooting

  • If you still see the Azure default page, make sure hostingstart.html is deleted and your file is named exactly index.html.
  • Clear cache or open in a private window if changes don’t appear.

Step 5: Use Deployment Slots (Blue-Green Lite)

What are Deployment Slots?
Slots let you have a staging site running alongside production. You can route a small percentage of real traffic to test your changes, then swap when you’re confident.

Steps

  1. In your App Service, go to Deployment slots

  2. Click Add slot → Name: New FeatureAdd
    Image 14

  3. Back on Deployment slots, select Traffic and assign 6% to New Feature. Save

  • Now 6% of users see the slot version; the rest stay on production. Image 15
  1. When satisfied, choose Swap → source: New Feature → target: ConsoleStart swap Image 16 Image 17

You safely validated the change, routed a small slice of traffic, and swapped the update into production without downtime.

Conclusion

You just:

  • Created a Windows App Service with .NET 9 (STS) and a custom plan.
  • Deployed a modern HTML/CSS/JS Snake game via Kudu.
  • Practiced progressive exposure using Deployment Slots, sending 6% of traffic before a swap.

This is the same muscle memory you’ll use for real apps—feature testing, safe rollouts, and fast rollbacks all without touching servers.

Key takeaway: With Azure App Service + Kudu + Deployment Slots, you ship faster and safer experiment in a slot, route a trickle of traffic, then swap with confidence.

Top comments (0)