<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: RitzaCo</title>
    <description>The latest articles on DEV Community by RitzaCo (@ritzaco).</description>
    <link>https://dev.to/ritzaco</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F509329%2Ff5b1a3db-bdb1-4f41-a222-770a118a8156.jpg</url>
      <title>DEV Community: RitzaCo</title>
      <link>https://dev.to/ritzaco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ritzaco"/>
    <language>en</language>
    <item>
      <title>Best SERP API Comparison 2025: SerpAPI vs Exa vs Tavily vs ScrapingDog vs ScrapingBee</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Wed, 03 Dec 2025 13:49:46 +0000</pubDate>
      <link>https://dev.to/ritza/best-serp-api-comparison-2025-serpapi-vs-exa-vs-tavily-vs-scrapingdog-vs-scrapingbee-2jci</link>
      <guid>https://dev.to/ritza/best-serp-api-comparison-2025-serpapi-vs-exa-vs-tavily-vs-scrapingdog-vs-scrapingbee-2jci</guid>
      <description>&lt;p&gt;We tested five SERP APIs with standardized benchmarks to find which performs best for different use cases. &lt;a href="https://exa.ai" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; delivered the fastest responses at 1.180 seconds with semantic search over a proprietary index at $5 per 1,000 queries. &lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; aggregates and parses content for LLM applications. &lt;a href="https://www.scrapingdog.com" rel="noopener noreferrer"&gt;Scrapingdog&lt;/a&gt; offers the lowest per-request pricing at under $0.001 at scale. &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; provides the broadest coverage with 20+ search engines. &lt;a href="https://www.scrapingbee.com" rel="noopener noreferrer"&gt;Scrapingbee&lt;/a&gt; bundles SERP access with general web scraping.&lt;/p&gt;

&lt;p&gt;This comparison helps you choose a SERP API for SEO monitoring, AI agents, RAG applications, or price comparison tools. Each section includes benchmark data, pricing breakdowns, and code examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview Comparison
&lt;/h2&gt;

&lt;p&gt;We tested five SERP APIs using this Python benchmark script that measured response time, success rate, cost, and free tier across 50 real-world queries.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Avg Response Time&lt;/th&gt;
&lt;th&gt;Success Rate&lt;/th&gt;
&lt;th&gt;Cost per 1,000 Searches&lt;/th&gt;
&lt;th&gt;Free Tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Exa&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.180s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;2,000 one-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scrapingdog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.700s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$0.20-$0.06&lt;/td&gt;
&lt;td&gt;1,000+ one-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tavily&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.885s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$5-$8&lt;/td&gt;
&lt;td&gt;1,000/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SerpAPI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.972s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;td&gt;250/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scrapingbee&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.257s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$0.56-$1.44&lt;/td&gt;
&lt;td&gt;66 one-time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3fkbi82wz3h900rf7j4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3fkbi82wz3h900rf7j4.png" alt="SERP API Comparison Spider Chart" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Response times vary based on what each API delivers: &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; returns raw SERP data, &lt;a href="https://exa.ai" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; performs semantic search with embeddings generation, and &lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; aggregates content from multiple sources with AI ranking. These tests used free tiers, but some providers offer faster speeds for additional cost (for example, &lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;SerpAPI's Ludicrous Speed tiers&lt;/a&gt; claim 2.2x faster responses).&lt;/p&gt;

&lt;h1&gt;
  
  
  SerpAPI
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; scrapes 20+ search engines and returns structured JSON. The service handles CAPTCHA solving and proxy rotation automatically, so you make a request and get back parsed data without dealing with blocks or rate limits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftojd6ad9na8mghn75grp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftojd6ad9na8mghn75grp.png" alt="SerpAPI Spider Chart" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 2.972s average response time with 100% success rate. Response times ranged from 0.070s to 13.308s, showing high variability. The free tier uses "Best Effort" speed with automatic refunds for failed searches. &lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;Paid tiers offer Ludicrous Speed&lt;/a&gt; claiming 2.2x faster responses for time-sensitive applications. &lt;a href="https://serpapi.com/status" rel="noopener noreferrer"&gt;SerpAPI's status page&lt;/a&gt; reports 99.911% uptime over the last 30 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (6.0/10):&lt;/strong&gt; Mid-tier performance at 2.972s average places SerpAPI fourth out of five tested APIs, though the wide variance suggests some queries complete in milliseconds while others take 10+ seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;SerpAPI's pricing&lt;/a&gt; uses monthly subscriptions based on search volume. Plans start at $75/month for 5,000 searches, which works out to $15 per 1,000 searches or $0.015 per search. Production tier and above include &lt;a href="https://serpapi.com/legal-shield" rel="noopener noreferrer"&gt;U.S. Legal Shield&lt;/a&gt;, providing legal defense for crawling public search data under First Amendment rights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (3.0/10):&lt;/strong&gt; At $15 per 1,000 searches, SerpAPI costs 3-15x more than alternatives like Scrapingdog ($0.20) or Exa ($5), earning the lowest pricing score among tested APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://serpapi.com/integrations" rel="noopener noreferrer"&gt;SerpAPI provides SDKs&lt;/a&gt; for Ruby, Python, JavaScript, Golang, PHP, Java, Rust, and .NET. Setup takes about 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;serpapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GoogleSearch&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Austin, Texas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://serpapi.com/playground" rel="noopener noreferrer"&gt;SerpAPI's Playground&lt;/a&gt; lets you test queries through a web interface before writing code. You modify parameters through form controls and see both the rendered SERP and JSON response. The dashboard tracks your usage with response time graphs and search history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kyiq6nsz1g138oni1t1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kyiq6nsz1g138oni1t1.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience Score (10.0/10):&lt;/strong&gt; Eight official SDKs, an interactive playground, comprehensive documentation, and real-time usage dashboards make SerpAPI the strongest developer experience among tested APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;SerpAPI covers Google (including AI Mode and AI Overview), Bing, Yahoo, Baidu, DuckDuckGo, Yandex, Amazon, eBay, Walmart, Home Depot, YouTube, Google Scholar, Google Patents, TripAdvisor, and Yelp. Each platform has its own API endpoint with consistent JSON formatting. Most competitors support 3-5 search engines at most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (10.0/10):&lt;/strong&gt; Coverage of 20+ search engines and specialized platforms far exceeds any competitor, making SerpAPI the only choice for applications needing multi-engine support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides 250 searches per month with no credit card required. All API endpoints work on the free tier, though you're limited to Best Effort speed without access to Ludicrous Speed tiers. Failed searches receive automatic refunds, so the 250-search limit only counts successful requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (6.0/10):&lt;/strong&gt; The 250 searches per month places SerpAPI mid-tier, more generous than Scrapingbee (66 total) but less than Tavily (1,000/month), Exa (2,000 one-time), or Scrapingdog (11,000 with promotions).&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose SerpAPI when you need breadth of coverage
&lt;/h2&gt;

&lt;p&gt;SerpAPI works best when you need access to multiple search engines or specialized platforms through one provider. SEO tools monitoring rankings across Google, Bing, and Yahoo avoid maintaining separate integrations. E-commerce price monitoring pulls structured data from Amazon, Walmart, and eBay. Academic research platforms integrate Google Scholar without parsing citation formats. The $15 per 1,000 searches pays for itself when infrastructure complexity is the bottleneck.&lt;/p&gt;

&lt;p&gt;Skip SerpAPI if you only need Google search on a tight budget. At $0.015 per search, you're paying 3-15x more than alternatives. AI applications requiring semantic search or embeddings should use Exa or Tavily instead.&lt;/p&gt;

&lt;h1&gt;
  
  
  Exa
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://exa.ai" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; is a search API built for AI applications using a proprietary embeddings-based index. Instead of scraping Google or Bing, Exa maintains its own crawl optimized for semantic search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywh3afpkf3nthiczelax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywh3afpkf3nthiczelax.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 1.180s average response time with 100% success rate. Response times ranged from 1.003s to 2.120s. &lt;a href="https://exa.ai/blog/fastest-search-api" rel="noopener noreferrer"&gt;Exa's published benchmarks&lt;/a&gt; claim P50 latency below 425ms, though our numbers likely include network overhead and full result retrieval while their metrics measure API response only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (10.0/10):&lt;/strong&gt; At 1.180s average, Exa delivered the fastest response times among all tested APIs, making it ideal for latency-sensitive applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://exa.ai/pricing" rel="noopener noreferrer"&gt;Exa's pricing&lt;/a&gt; charges $5 per 1,000 searches for 1-25 results ($0.005 per search). Requesting 26-100 results jumps to $25 per 1,000 searches ($0.025 per search), a 5x increase. Additional endpoints cost separately: contents retrieval runs $1 per 1,000 pages, answers and research tasks run $5 per 1,000 calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (7.5/10):&lt;/strong&gt; At $5 per 1,000 searches, Exa sits in the affordable mid-tier, significantly cheaper than SerpAPI ($15) but more expensive than Scrapingdog ($0.20).&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.exa.ai/reference/getting-started" rel="noopener noreferrer"&gt;Exa provides SDKs&lt;/a&gt; for Python (&lt;code&gt;exa-py&lt;/code&gt;) and JavaScript (&lt;code&gt;exa-js&lt;/code&gt;), both with full TypeScript definitions. Setup takes about 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;exa_py&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Exa&lt;/span&gt;

&lt;span class="n"&gt;exa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Exa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://dashboard.exa.ai" rel="noopener noreferrer"&gt;Playground&lt;/a&gt; lets you test queries with controls for search type, filters, and AI features while generating code snippets. &lt;a href="https://docs.exa.ai" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt; includes integration examples for LangChain, LlamaIndex, CrewAI, Anthropic, and OpenAI, plus an &lt;a href="https://docs.exa.ai/mcp" rel="noopener noreferrer"&gt;MCP integration&lt;/a&gt; for Cursor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhniscvv9bxrz1aeafufu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhniscvv9bxrz1aeafufu.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience Score (9.0/10):&lt;/strong&gt; Two official SDKs with TypeScript support, interactive playground, comprehensive docs, and MCP integration provide excellent developer experience, just slightly behind SerpAPI's eight-language SDK coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;Exa maintains a &lt;a href="https://exa.ai/exa-api" rel="noopener noreferrer"&gt;proprietary search index&lt;/a&gt; built for AI applications rather than scraping Google or Bing. The embeddings-based index covers web content, research papers, news, GitHub repositories, tweets, and PDFs with &lt;a href="https://docs.exa.ai/reference/search" rel="noopener noreferrer"&gt;category filters&lt;/a&gt; and date filtering. The &lt;a href="https://docs.exa.ai/reference/search" rel="noopener noreferrer"&gt;API&lt;/a&gt; provides multiple search modes (neural for semantic matching, keyword for traditional results, auto for intelligent combination) plus endpoints for content retrieval, similarity search, and automated research.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (8.0/10):&lt;/strong&gt; Proprietary AI-powered semantic search offers unique capabilities for meaning-based queries, though it can't replicate specific search engine results like SerpAPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides $10 in credits with no expiration and no credit card requirement. At $5 per 1,000 searches for standard queries (1-25 results), this translates to 2,000 free searches. You can allocate credits across other endpoints (research/answers at 2,000 calls, content retrieval at 10,000 pages). All features work on the free tier including semantic search, similarity search, and AI-powered summaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (9.0/10):&lt;/strong&gt; 2,000 one-time free searches with no expiration places Exa among the most generous free tiers, second only to Scrapingdog's 11,000 credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Exa when you need semantic search
&lt;/h2&gt;

&lt;p&gt;Exa works best for AI agents, RAG systems, or LLM-powered research tools that benefit from meaning-based search. Voice AI companions leverage sub-2-second response times for fast web retrieval. Research platforms use the structured research endpoint to generate reports with citations. Chatbots get clean, parsed HTML without dealing with CAPTCHA.&lt;/p&gt;

&lt;p&gt;Skip Exa if you need exact Google SERP replication for SEO monitoring or Google Shopping data for price comparison. Watch the 5x price jump when requesting 26-100 results per query ($0.025 per search approaches premium pricing without multi-engine coverage).&lt;/p&gt;

&lt;h1&gt;
  
  
  Tavily
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; is a search API optimized for LLMs and RAG applications. Instead of returning URLs with snippets, Tavily aggregates content from up to 20 sources per query, ranks them using proprietary AI, and delivers parsed content ready for LLM consumption.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjuhcznnyyfpfabxeh88y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjuhcznnyyfpfabxeh88y.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 1.885s average response time with 100% success rate. Response times ranged from 1.583s to 2.897s. The response time reflects that Tavily fetches, parses, and ranks content before responding, while traditional SERP APIs return URLs and leave content extraction to you. &lt;a href="https://blog.tavily.com/tavily-evaluation-part-1-tavily-achieves-sota-on-simpleqa-benchmark/" rel="noopener noreferrer"&gt;Tavily's published benchmarks&lt;/a&gt; focus on accuracy (93.3% on SimpleQA) rather than raw speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (8.5/10):&lt;/strong&gt; At 1.885s average, Tavily ranks third among tested APIs, delivering strong performance considering it aggregates and parses content from multiple sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tavily.com/#pricing" rel="noopener noreferrer"&gt;Tavily's pricing&lt;/a&gt; uses a credit-based system. Pay-as-you-go charges $0.008 per credit. Basic search consumes 1 credit per request ($8 per 1,000 searches), while advanced search uses 2 credits. Subscription tiers start at $30/month for 4,000 credits ($0.0075 per credit) and scale down to $0.005 per credit on the Growth plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (7.0/10):&lt;/strong&gt; At $8 per 1,000 searches on pay-as-you-go, Tavily offers mid-tier pricing between premium options like SerpAPI ($15) and budget options like Scrapingdog ($0.20).&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.tavily.com" rel="noopener noreferrer"&gt;Tavily provides SDKs&lt;/a&gt; for Python (&lt;code&gt;tavily-python&lt;/code&gt;) and JavaScript/TypeScript (&lt;code&gt;@tavily/core&lt;/code&gt;). Setup takes about 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tavily&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TavilyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TavilyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tvly-YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://app.tavily.com" rel="noopener noreferrer"&gt;Tavily's Playground&lt;/a&gt; generates real-time code as you build queries. Adjust parameters like search depth and result count, and the right panel updates with Python, JavaScript, or cURL code. &lt;a href="https://docs.tavily.com" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt; includes integration guides for LangChain, LlamaIndex, OpenAI, Anthropic, Vercel AI, and Zapier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm4ch5gbrf6ee0wrwwb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm4ch5gbrf6ee0wrwwb6.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience Score (8.5/10):&lt;/strong&gt; Two official SDKs, real-time code generation in the playground, comprehensive integration guides, and MCP support provide strong developer experience, comparable to Exa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;Tavily uses &lt;a href="https://docs.tavily.com/documentation/about" rel="noopener noreferrer"&gt;proprietary AI ranking&lt;/a&gt; to aggregate content from up to 20 sources per query. The API provides &lt;a href="https://docs.tavily.com" rel="noopener noreferrer"&gt;four endpoints&lt;/a&gt;: search (factual queries with AI ranking), extract (clean content from URLs), map (page discovery), and crawl (combined mapping and extraction). Search supports &lt;a href="https://docs.tavily.com/documentation/api-reference/endpoint/search" rel="noopener noreferrer"&gt;topic filters&lt;/a&gt; (general, news, finance), time range filters (day, week, month, year), and domain inclusion/exclusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (7.5/10):&lt;/strong&gt; AI-powered content aggregation with 93.3% accuracy on SimpleQA benchmarks provides strong factual search, though it can't replicate specific search engine results like SerpAPI or offer semantic search like Exa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides 1,000 credits per month with no credit card requirement. At 1 credit per basic search, this translates to 1,000 free searches monthly. You can allocate credits across advanced searches (2 credits), content extraction (1 credit per 5 URLs), or mapping (1 credit per 10 pages). All features work on the free tier with 100 requests per minute rate limiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (8.0/10):&lt;/strong&gt; 1,000 recurring monthly credits places Tavily among the more generous free tiers, better than SerpAPI (250/month) or Scrapingbee (66 total) but less than Exa (2,000 one-time) or Scrapingdog (11,000).&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Tavily for AI-optimized search
&lt;/h2&gt;

&lt;p&gt;Tavily fits RAG applications, AI agents, or LLM-powered assistants that need factual, grounded web information. Chatbots get ranked content with source citations, research automation tools use the crawl endpoint to map entire domains, and content aggregation platforms pull clean text without handling CAPTCHA. The 93.3% accuracy on factual benchmarks and automatic content filtering reduce manual validation needs.&lt;/p&gt;

&lt;p&gt;Skip Tavily for exact Google or Bing SERP layouts needed in SEO monitoring or structured shopping results for price comparison. Applications needing only URLs without content extraction should consider lighter-weight SERP APIs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scrapingdog
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com" rel="noopener noreferrer"&gt;Scrapingdog&lt;/a&gt; is a web scraping service offering SERP API access alongside scrapers for Amazon, LinkedIn Jobs, Instagram, and other platforms. The service provides a &lt;a href="https://www.scrapingdog.com/google-search-api/" rel="noopener noreferrer"&gt;Google Search API&lt;/a&gt; and a &lt;a href="https://www.scrapingdog.com/universal-search-api/" rel="noopener noreferrer"&gt;Universal Search API&lt;/a&gt; that aggregates and de-duplicates results across Google, Bing, Yahoo, and DuckDuckGo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzeeligc1zn5tmq6gop4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzeeligc1zn5tmq6gop4z.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 1.700s average response time with 100% success rate. Response times ranged from 0.709s to 4.432s. The measured performance contradicts &lt;a href="https://www.scrapingdog.com/blog/serpapi-vs-serper-vs-scrapingdog/" rel="noopener noreferrer"&gt;Scrapingdog's published benchmarks&lt;/a&gt;, which claim 1.25 second response times for Google SERP requests, though our 1.700s average is reasonably close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (9.0/10):&lt;/strong&gt; At 1.700s average, Scrapingdog ranks second among tested APIs, delivering strong performance at the lowest price point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com/pricing" rel="noopener noreferrer"&gt;Scrapingdog's pricing&lt;/a&gt; uses a credit-based system with monthly subscriptions. Plans start at $40/month for 200,000 credits (Light) and scale to $30,000/month for 1 billion credits (Nova Pro). At 1 credit per Google search, the cost per 1,000 searches ranges from $0.20 (Light) to under $0.06 at high-volume tiers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (10.0/10):&lt;/strong&gt; At $0.20-$0.06 per 1,000 searches, Scrapingdog offers the cheapest pricing among all tested APIs, making it ideal for high-volume projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com/documentation" rel="noopener noreferrer"&gt;Scrapingdog's documentation&lt;/a&gt; covers authentication, error codes, and endpoint references, though some pages returned 404 errors during testing. The service lacks official SDKs with helper functions found in competing APIs, requiring direct REST API calls using standard HTTP libraries. The dashboard's request builder generates code in cURL, Python, Java, Node.js, Ruby, PHP, Go, and C# as you configure parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1u4e576lo7s8ayq6z3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1u4e576lo7s8ayq6z3.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setup takes approximately 5-10 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.scrapingdog.com/google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Developer Experience Score (5.0/10):&lt;/strong&gt; No official SDKs, documentation gaps with 404 errors, and REST-only integration give Scrapingdog the weakest developer experience among tested APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com/google-search-api/" rel="noopener noreferrer"&gt;Scrapingdog's Google Search API&lt;/a&gt; provides access to Google web search with organic results, People Also Ask sections, related searches, and multiple search types (web, images, news, shopping). The &lt;a href="https://www.scrapingdog.com/universal-search-api/" rel="noopener noreferrer"&gt;Universal Search API&lt;/a&gt; aggregates and de-duplicates results from Google, Bing, Yahoo, and DuckDuckGo in a single request. Beyond search engines, Scrapingdog provides dedicated scrapers for Amazon, Walmart, eBay, LinkedIn Jobs, Instagram, and other platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (6.0/10):&lt;/strong&gt; Coverage of 4 search engines via Universal Search API with de-duplication provides decent breadth, though significantly less than SerpAPI's 20+ platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides 1,000 request credits with no expiration and no credit card requirement. Optional &lt;a href="https://www.scrapingdog.com/dashboard" rel="noopener noreferrer"&gt;social media promotions&lt;/a&gt; add 4,000 credits for following on X and 6,000 for posting a LinkedIn review, bringing the total to 11,000 credits. Free tier users access all API endpoints including the Universal Search API, JavaScript rendering, geo-targeting, and AI extraction features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (10.0/10):&lt;/strong&gt; 11,000 total free credits (with promotions) is the most generous free tier among all tested APIs, ideal for extensive evaluation and small projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Scrapingdog for cost-optimized scraping
&lt;/h2&gt;

&lt;p&gt;Scrapingdog fits high-volume projects where per-request cost matters more than response time. Data collection pipelines requiring millions of monthly searches benefit from pricing that drops to $0.00006 per request at scale. Lead generation tools can consolidate Google search results alongside LinkedIn profiles, Instagram accounts, or e-commerce platforms through a single provider. The Universal Search API's de-duplication feature benefits SEO tools comparing rankings across multiple search engines without manual result matching.&lt;/p&gt;

&lt;p&gt;Skip Scrapingdog for projects requiring official SDKs and comprehensive documentation. The REST-only API and documentation gaps observed during testing make integration more challenging than competitors with polished SDKs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scrapingbee
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com/" rel="noopener noreferrer"&gt;Scrapingbee&lt;/a&gt; is a general-purpose web scraping service that includes Google Search API functionality. Acquired by &lt;a href="https://www.scrapingbee.com/blog/scrapingbee-acquisition/" rel="noopener noreferrer"&gt;Oxylabs in June 2025&lt;/a&gt;, it targets developers needing headless browser rendering, JavaScript execution, and proxy rotation across multiple websites including e-commerce platforms, social media sites, and search engines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e1eoslatihryqwhrv7f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e1eoslatihryqwhrv7f.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 7.257s average response time with 100% success rate. Response times ranged from 1.453s to 22.978s, creating a 16x performance spread that makes response time prediction difficult. &lt;a href="https://www.scrapingdog.com/blog/scrapingbee-vs-scraperapi-vs-scrapingdog/" rel="noopener noreferrer"&gt;Competitor benchmarks from Scrapingdog&lt;/a&gt; show 90% success rate with 16.28 second average response time for Google SERP requests, confirming high variability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (2.0/10):&lt;/strong&gt; At 7.257s average, Scrapingbee delivered the slowest response times among all tested APIs, with extreme variability reflecting its positioning as a general web scraper rather than SERP-optimized service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com/pricing/" rel="noopener noreferrer"&gt;Scrapingbee's pricing&lt;/a&gt; uses a credit-based system where Google Search API requests cost 15 credits per call. Monthly plans start at $49.99 for 250,000 credits. For 1,000 Google searches at 15 credits per request, costs range from $0.56 to $1.44 depending on tier. Variable credit costs create billing unpredictability as enabling premium proxies increases request cost from 5 to 25 credits (5x multiplier).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (8.5/10):&lt;/strong&gt; At $0.56-$1.44 per 1,000 searches, Scrapingbee offers competitive pricing in the mid-to-low range, more affordable than SerpAPI ($15) or Exa ($5) but slightly more expensive than Scrapingdog ($0.20).&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com/documentation/" rel="noopener noreferrer"&gt;Scrapingbee's documentation&lt;/a&gt; provides endpoint references, authentication guides, and code examples across Python, Node.js, Java, Ruby, PHP, Go, and C#. The request builder interface shows real-time credit costs and generates code in multiple languages as you configure parameters. Scrapingbee provides &lt;a href="https://www.scrapingbee.com/documentation/" rel="noopener noreferrer"&gt;official Python and Node.js SDKs&lt;/a&gt; that wrap authentication and parameter formatting.&lt;/p&gt;

&lt;p&gt;Setup takes approximately 5-10 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://app.scrapingbee.com/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Developer Experience Score (7.0/10):&lt;/strong&gt; Two official SDKs, comprehensive documentation, and a request builder with real-time cost estimation provide decent developer experience, though credits charged for failed requests create cost uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com/features/google/" rel="noopener noreferrer"&gt;Scrapingbee's Google Search API&lt;/a&gt; provides access to Google web search only with structured JSON including organic results, local results, ads, related queries, images, news results, knowledge graph data, and top stories. Scrapingbee supports &lt;a href="https://www.scrapingbee.com/documentation/google/" rel="noopener noreferrer"&gt;multiple Google search types&lt;/a&gt; including web, news, Maps, and image search. The platform provides dedicated scrapers for Amazon, Walmart, and ChatGPT API alongside general HTML scraping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (4.0/10):&lt;/strong&gt; Google-only coverage limits Scrapingbee to a single search engine, significantly less than SerpAPI (20+ engines), Scrapingdog (4 engines), or the proprietary search capabilities of Exa and Tavily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides 1,000 API credits with no credit card required and no time expiration. At 15 credits per Google search, this translates to approximately 66 free searches. The 1,000 credits cover diverse use cases including HTML scraping (5 credits), Amazon scraping (15 credits), and Walmart scraping (15 credits), with all platform features available including JavaScript rendering, geo-targeting, and premium proxies at higher credit costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (2.0/10):&lt;/strong&gt; 66 total free searches is the least generous free tier among all tested APIs, limiting evaluation and making it difficult to thoroughly test the service before committing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Scrapingbee for unified web scraping
&lt;/h2&gt;

&lt;p&gt;Scrapingbee fits projects requiring both SERP data and general web scraping in a unified platform. Teams already scraping e-commerce sites, social media profiles, or JavaScript-heavy web applications can consolidate Google search results through the same service. Projects with flexible latency requirements can tolerate 7.257-second average response times and high variability for data collection pipelines, batch processing systems, or research workflows prioritizing breadth over speed.&lt;/p&gt;

&lt;p&gt;Skip Scrapingbee for dedicated SERP API needs. The 66-search free tier limits evaluation, Google-only coverage restricts multi-engine analysis, and 7.257-second average response times with 23-second worst-case performance make it unsuitable for real-time voice assistants or live autocomplete suggestions.&lt;/p&gt;

&lt;h1&gt;
  
  
  SerpAPI vs Tavily
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; and &lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; represent different approaches to SERP data. SerpAPI scrapes existing search engines like Google and Bing, returning their results as structured JSON. Tavily runs its own search engine that aggregates content from up to 20 sites and ranks it using proprietary AI. This architectural difference determines which one fits your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mirror Google or aggregate multiple sources?
&lt;/h2&gt;

&lt;p&gt;When you query &lt;a href="https://serpapi.com/search-api" rel="noopener noreferrer"&gt;SerpAPI for Google results&lt;/a&gt;, you get exactly what appears on Google with all its ranking signals, personalization, and algorithmic biases. When you query Amazon, you get Amazon's product listings ranked by Amazon's algorithm. This matters for SEO monitoring, competitor analysis, and applications that need to replicate what users see on specific platforms. &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI supports 20+ search engines&lt;/a&gt; with dedicated endpoints for each.&lt;/p&gt;

&lt;p&gt;Tavily doesn't scrape existing engines. It &lt;a href="https://docs.tavily.com/documentation/about" rel="noopener noreferrer"&gt;aggregates and ranks content&lt;/a&gt; from multiple sources per query, scoring relevance with AI and formatting results for LLM consumption. You can't get "Bing results" from Tavily because Tavily is the search engine. This matters for AI agents and RAG applications where answer accuracy matters more than which search engine provided it.&lt;/p&gt;

&lt;p&gt;Our benchmarks measured SerpAPI at 0.072 seconds average response time versus Tavily's 2.327 seconds. The 32x speed difference reflects Tavily's extra processing: searching up to 20 sites, applying AI ranking, extracting content, and formatting for LLM context windows. SerpAPI returns Google's results as-is with minimal overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose SerpAPI when you need specific search engines
&lt;/h2&gt;

&lt;p&gt;Build with &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; when you need to replicate what users see on specific platforms. SEO monitoring tools that track keyword rankings across Google, Bing, and other engines require SerpAPI's multi-engine support. Price comparison tools need Amazon, eBay, and Walmart product listings. Academic research applications can use &lt;a href="https://serpapi.com/google-scholar-api" rel="noopener noreferrer"&gt;SerpAPI's Scholar endpoint&lt;/a&gt; instead of building separate scrapers.&lt;/p&gt;

&lt;p&gt;The 0.072-second response time fits latency-sensitive applications like browser extensions, real-time dashboards, and user-facing search features. When your application needs to feel instant, the 32x speed advantage matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Tavily when you're feeding LLMs
&lt;/h2&gt;

&lt;p&gt;Build with &lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; when creating AI agents, RAG applications, or LLM-powered tools that need accurate information rather than specific search engine results. &lt;a href="https://blog.tavily.com/tavily-evaluation-part-1-tavily-achieves-sota-on-simpleqa-benchmark/" rel="noopener noreferrer"&gt;Tavily's 93.3% accuracy on SimpleQA&lt;/a&gt; and LLM-optimized formatting reduce the post-processing needed to integrate search into your agent's workflow.&lt;/p&gt;

&lt;p&gt;The multi-source aggregation helps when you care about answer quality over result provenance. Instead of scraping Google's top result and hoping it's accurate, Tavily retrieves up to 20 sources and ranks them by relevance. If your application shows users "here's what I found about X" rather than "here are Google's top 10 results for X," Tavily's AI-scored relevance provides better raw material.&lt;/p&gt;

&lt;p&gt;The 2.327-second response time becomes acceptable when the alternative is making multiple follow-up requests to scrape and parse content from traditional SERP results.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Choose the Right SERP API
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; works best when you need exact Google (or Bing, or Amazon) results with sub-100ms response times. The $15 per 1,000 searches pays for 20+ search engine coverage and infrastructure that handles CAPTCHA solving automatically. SEO monitoring tools and price comparison platforms that need traditional SERP layouts benefit from the 0.072-second average we measured.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://exa.ai" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; fits AI applications needing semantic search over a proprietary index. The $5 per 1,000 searches includes embeddings and meaning-based matching that keyword APIs don't provide. At 1.189 seconds average, Exa delivers the fastest semantic search among tested options. The 2,000 free searches make evaluation straightforward.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; targets RAG pipelines requiring factual, multi-source content. The service aggregates up to 20 sites per query and formats results for LLM consumption at $5-$8 per 1,000 searches. The 1,000 monthly free credits that reset support sustained prototyping. Choose Tavily when accuracy matters more than speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com" rel="noopener noreferrer"&gt;Scrapingdog&lt;/a&gt; offers the lowest per-request pricing at scale, dropping to $0.000058 per request for high-volume users. Our 3.762-second average contradicted their claimed 1.25-second benchmark, but the cost advantage persists for budget-focused projects. The Universal Search API de-duplicates across four engines in single requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com" rel="noopener noreferrer"&gt;Scrapingbee&lt;/a&gt; makes sense for teams already scraping e-commerce or general websites who want to consolidate SERP access through one provider. The 5.411-second average and high variability make it unsuitable for latency-sensitive applications. At 66 free searches versus competitors offering 250-2,000, the free tier limits evaluation depth.&lt;/p&gt;

&lt;h1&gt;
  
  
  Frequently Asked Questions
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is a SERP API?
&lt;/h2&gt;

&lt;p&gt;A SERP API provides programmatic access to search engine results through structured JSON responses. Instead of manually searching Google and scraping HTML, you send an HTTP request with query parameters and receive formatted data including organic results, ads, knowledge panels, and related searches. These APIs handle CAPTCHA solving and proxy rotation automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are SERP APIs legal?
&lt;/h2&gt;

&lt;p&gt;SERP APIs operate in a legal gray area. In the United States, &lt;a href="https://en.wikipedia.org/wiki/HiQ_Labs_v._LinkedIn" rel="noopener noreferrer"&gt;hiQ Labs v. LinkedIn (2019)&lt;/a&gt; established that scraping publicly accessible data doesn't violate the Computer Fraud and Abuse Act. Search engine terms of service typically prohibit automated access, but enforcement focuses more on technical countermeasures than legal action. &lt;a href="https://serpapi.com/legal-shield" rel="noopener noreferrer"&gt;SerpAPI offers U.S. Legal Shield&lt;/a&gt; on paid plans, covering legal fees if you face action for scraping public search data.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do SERP APIs avoid getting blocked?
&lt;/h2&gt;

&lt;p&gt;SERP APIs use rotating residential proxies, CAPTCHA solving services, and request pattern randomization to avoid detection. They distribute requests across thousands of IP addresses that appear as regular user traffic. When search engines implement new anti-bot measures, the APIs update their infrastructure to maintain access. This is why using an API makes more sense than building your own scraping system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I use SERP APIs for commercial projects?
&lt;/h2&gt;

&lt;p&gt;Yes, all five APIs support commercial use. Check your plan's terms of service for specific restrictions. Most APIs prohibit reselling raw SERP data to competitors but allow using the data in your own products. &lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;SerpAPI's pricing page&lt;/a&gt; and &lt;a href="https://docs.tavily.com" rel="noopener noreferrer"&gt;Tavily's documentation&lt;/a&gt; specify commercial terms. Free tiers typically allow commercial use with volume limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Methodology
&lt;/h2&gt;

&lt;p&gt;Back to comparison results&lt;/p&gt;

&lt;p&gt;We tested each API with fifty standardized queries: "python tutorials", "best coffee makers 2025", "machine learning", "weather forecast" etc. The Python script measured response time from request to complete JSON parsing and verified successful responses. Each test ran once per API to simulate real-world usage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;quote_plus&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_serpapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://serpapi.com/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SERPAPI_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_exa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.exa.ai/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EXA_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;numResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_tavily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.tavily.com/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TAVILY_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scrapingdog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.scrapingdog.com/google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SCRAPINGDOG_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scrapingbee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;encoded_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;quote_plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://app.scrapingbee.com/api/v1/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SCRAPINGBEE_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.google.com/search?q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;render_js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom_google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python tutorials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best coffee makers 2025&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;machine learning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather forecast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;electric cars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;react hooks tutorial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best restaurants near me&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data science courses&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yoga poses&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home workout routines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;javascript async await&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docker compose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes tutorial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws lambda pricing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terraform vs ansible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best noise cancelling headphones&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meal prep ideas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sourdough recipe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running shoes 2025&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meditation techniques&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;typescript generics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres indexing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis caching&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mongodb aggregation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git rebase vs merge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vim shortcuts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linux commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;css flexbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tailwind css&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nextjs routing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;graphql vs rest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;microservices architecture&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api gateway&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oauth2 flow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jwt authentication&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password hashing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sql injection prevention&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xss attack&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csrf protection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seo best practices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sitemap xml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google analytics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversion rate optimization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a/b testing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email marketing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;social media strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content marketing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project management tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agile methodology&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scrum vs kanban&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;apis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SerpAPI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_serpapi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_exa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tavily&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_tavily&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scrapingdog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_scrapingdog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scrapingbee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_scrapingbee&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_func&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;apis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Testing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;successes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;successes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/50: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;✓&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;✗&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/50: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - ERROR: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;avg_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;success_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;successes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;fastest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;slowest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;success_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fastest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fastest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slowest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slowest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;successful_queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;successes&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Results:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Average: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;avg_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Success Rate: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;success_rate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Fastest: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fastest&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Slowest: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;slowest&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Save results to JSON
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_results.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Print summary
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FINAL SUMMARY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;avg_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s avg, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;success_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;5.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We ran tests with a 100Mbps connection. Network latency impacts absolute times but relative performance remains consistent. Your results may vary based on location, query complexity, and API server load.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How to create a timeline: Excel, Word, PowerPoint, Google Sheets, Preceden, and JavaScript</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 13 Nov 2025 07:54:12 +0000</pubDate>
      <link>https://dev.to/ritza/how-to-create-a-timeline-excel-word-powerpoint-google-sheets-preceden-and-javascript-3126</link>
      <guid>https://dev.to/ritza/how-to-create-a-timeline-excel-word-powerpoint-google-sheets-preceden-and-javascript-3126</guid>
      <description>&lt;p&gt;A timeline visualizes events chronologically, making it easier to understand project progress, historical sequences, or future plans. Whether you're tracking project milestones, creating a presentation, or documenting company history, you need the right tool to build your timeline.&lt;/p&gt;

&lt;p&gt;This guide shows you how to create timelines using tools you likely already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.microsoft.com/en-us/microsoft-365/excel" rel="noopener noreferrer"&gt;Excel&lt;/a&gt;: Using SmartArt for basic document timelines.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.microsoft.com/en-us/microsoft-365/word" rel="noopener noreferrer"&gt;Word&lt;/a&gt;: Using SmartArt for report and document timelines.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.microsoft.com/en-us/microsoft-365/powerpoint" rel="noopener noreferrer"&gt;PowerPoint&lt;/a&gt;: Using SmartArt for presentation timelines.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.google.com/sheets/about/" rel="noopener noreferrer"&gt;Google Sheets&lt;/a&gt;/&lt;a href="https://www.google.com/docs/about/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;: Using Timeline view for collaborative projects (requires &lt;a href="https://workspace.google.com/" rel="noopener noreferrer"&gt;Google Workspace&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;Preceden&lt;/a&gt;: Using dedicated timeline software for professional results.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt;: Using &lt;a href="https://bryntum.com/" rel="noopener noreferrer"&gt;Bryntum&lt;/a&gt; to build custom timeline charts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each section is self-contained, so you can jump directly to the tool you want to use. Before diving into the how-to sections, you'll learn the key differences between timelines and Gantt charts to choose the proper visualization for your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are the Differences Between a Timeline and a Gantt?
&lt;/h2&gt;

&lt;p&gt;Timelines and Gantt charts both visualize events over time, but they serve different purposes. Understanding the difference helps you choose the right tool for your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's a Timeline?
&lt;/h3&gt;

&lt;p&gt;A timeline is a linear visualization that shows events in chronological order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91bejph36obbiebl72tj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91bejph36obbiebl72tj.png" alt=" " width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Timelines focus on dates and milestones, making them ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Historical events such as company founding, product launches, and industry evolution.&lt;/li&gt;
&lt;li&gt;Project milestones like major deadlines and deliverables.&lt;/li&gt;
&lt;li&gt;Event planning, like conference agendas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A timeline answers the question: &lt;strong&gt;What happened when?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's a Gantt Chart?
&lt;/h3&gt;

&lt;p&gt;A Gantt chart shows when events happen, how long they take, and how they relate to each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5l9xz1lx2ozqcsajvi2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5l9xz1lx2ozqcsajvi2.png" alt=" " width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gantt charts display:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Task start and end dates.&lt;/li&gt;
&lt;li&gt;Task duration.&lt;/li&gt;
&lt;li&gt;Overlapping tasks.&lt;/li&gt;
&lt;li&gt;Task dependencies – which tasks must finish before others can start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Gantt chart answers the questions: &lt;strong&gt;When does this happen, how long does it take, and what depends on it?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeline vs Gantt Chart: Which One Should You Use?
&lt;/h3&gt;

&lt;p&gt;Choose a &lt;strong&gt;timeline&lt;/strong&gt; when you want to communicate a sequence of events to an audience. Timelines work best for presentations, reports, and documentation where clarity matters more than detailed project management.&lt;/p&gt;

&lt;p&gt;Choose a &lt;strong&gt;Gantt chart&lt;/strong&gt; when you need to plan and track complex projects with multiple tasks, dependencies, and resources. Gantt charts work best for project managers coordinating teams and deliverables.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in Excel?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.microsoft.com/en-us/microsoft-365/excel" rel="noopener noreferrer"&gt;Excel&lt;/a&gt; doesn't have a dedicated timeline chart. You can create a basic timeline using &lt;a href="https://support.microsoft.com/en-us/office/create-a-smartart-graphic-from-scratch-fac94c93-500b-4a0a-97af-124040594842" rel="noopener noreferrer"&gt;SmartArt&lt;/a&gt; graphics, which work in Excel desktop applications but not in &lt;a href="https://www.microsoft365.com/launch/excel" rel="noopener noreferrer"&gt;Excel Online&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Insert SmartArt
&lt;/h3&gt;

&lt;p&gt;On the Excel application, follow these steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Insert tab.&lt;/li&gt;
&lt;li&gt;Select the SmartArt option.&lt;/li&gt;
&lt;li&gt;Then select "Process". You will see many options to describe a process, a timeline. &lt;/li&gt;
&lt;li&gt;Choose the one that works for you, but for this tutorial, we will use the first one, "Basic process".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0qv6y3f526md4p7kpom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0qv6y3f526md4p7kpom.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Your Events
&lt;/h3&gt;

&lt;p&gt;Excel displays a timeline with placeholder text. Click each text box and type your event name and date.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjai6ip5rznjwkrsgcb7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjai6ip5rznjwkrsgcb7.png" alt=" " width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add more events, you can use the SmartArt text utility, by typing on the "Enter" button for a new line, or the "+" button. A new item will appear on the right of your timeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Format Your Timeline
&lt;/h3&gt;

&lt;p&gt;Customize your timeline using the SmartArt Design tab. You can change the colors, apply SmartArt styles or change the layout. &lt;/p&gt;

&lt;p&gt;For detailed formatting, select individual text boxes and use the Format tab to change fill colors, borders, or text properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in PowerPoint
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.microsoft.com/en-us/microsoft-365/powerpoint" rel="noopener noreferrer"&gt;PowerPoint&lt;/a&gt; provides &lt;a href="https://support.microsoft.com/en-us/office/create-a-smartart-graphic-from-scratch-fac94c93-500b-4a0a-97af-124040594842" rel="noopener noreferrer"&gt;SmartArt&lt;/a&gt; graphics that make timeline creation straightforward. This feature is available in both PowerPoint desktop and &lt;a href="https://www.microsoft365.com/launch/powerpoint" rel="noopener noreferrer"&gt;PowerPoint Online&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Insert SmartArt
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In your PowerPoint presentation, follow these steps:

&lt;ol&gt;
&lt;li&gt;Click the Insert tab.&lt;/li&gt;
&lt;li&gt;Click SmartArt.&lt;/li&gt;
&lt;li&gt;Select "Process" from the left panel. You'll see various process diagrams, including timeline options.&lt;/li&gt;
&lt;li&gt;Select the timeline style you prefer. For this guide, we'll use "Basic Process".&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahnoox126qtn39gn4hsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahnoox126qtn39gn4hsb.png" alt=" " width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Your Events
&lt;/h3&gt;

&lt;p&gt;A timeline graphic will appear with text placeholders. Click on each text box and type your event information. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fithuvw9uvpgqauayxzld.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fithuvw9uvpgqauayxzld.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add more events, you can use the SmartArt text utility by typing on the "Enter" button for a new line or the "+" button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Format Your Timeline
&lt;/h3&gt;

&lt;p&gt;Customize your timeline using the SmartArt Design tab. You can change the colors, apply SmartArt styles, or change the layout. &lt;/p&gt;

&lt;p&gt;For detailed formatting, select individual text boxes and use the Format tab to change fill colors, borders, or text properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in Word?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.microsoft.com/en-us/microsoft-365/word" rel="noopener noreferrer"&gt;Word&lt;/a&gt; provides &lt;a href="https://support.microsoft.com/en-us/office/create-a-smartart-graphic-from-scratch-fac94c93-500b-4a0a-97af-124040594842" rel="noopener noreferrer"&gt;SmartArt&lt;/a&gt; graphics for creating basic timelines in your documents. This method works in Word desktop applications (not &lt;a href="https://www.microsoft365.com/launch/word" rel="noopener noreferrer"&gt;Word Online&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Insert SmartArt
&lt;/h3&gt;

&lt;p&gt;On the Word application, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Insert tab.&lt;/li&gt;
&lt;li&gt;Select the SmartArt option.&lt;/li&gt;
&lt;li&gt;Then select "Process". You will see many options to describe a process, including timeline layouts.&lt;/li&gt;
&lt;li&gt;Choose the one that works for you, but for this tutorial, we will use "Basic process".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5n491xoda2nbaihxpxo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5n491xoda2nbaihxpxo.png" alt=" " width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Your Events
&lt;/h3&gt;

&lt;p&gt;A timeline graphic will appear with text placeholders. Click on each text box and type your event information - include both the event name and date.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiamrczjlx2vinzqtsjdn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiamrczjlx2vinzqtsjdn.png" alt=" " width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add more events, click the SmartArt border, then click the SmartArt Design tab &amp;gt; Add Shape. New boxes appear on your timeline.&lt;/p&gt;

&lt;p&gt;To remove events, click a box and press Delete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Format Your Timeline
&lt;/h3&gt;

&lt;p&gt;Customize your timeline using the SmartArt Design tab. You can change the colors, apply SmartArt styles, or change the layout. &lt;/p&gt;

&lt;p&gt;For detailed formatting, select individual text boxes and use the Format tab to change fill colors, borders, or text properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in Google Docs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.google.com/docs/about/" rel="noopener noreferrer"&gt;Google Docs&lt;/a&gt; doesn't have a built-in timeline feature. You can create a timeline in &lt;a href="https://www.google.com/sheets/about/" rel="noopener noreferrer"&gt;Google Sheets&lt;/a&gt; using Timeline view, then add it to your document as an image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Timeline view is only available for &lt;a href="https://workspace.google.com/" rel="noopener noreferrer"&gt;Google Workspace&lt;/a&gt; accounts (Business Starter, Business Standard, Business Plus, Enterprise, Education editions). Free Google accounts do not have access to this feature.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 1: Prepare Your Data in Google Sheets
&lt;/h3&gt;

&lt;p&gt;Create a new Google Sheet and set up your timeline data in columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Card Title,Start Date,End Date,Card Detail
Research &amp;amp; Planning,1/15/2025,2/1/2025,Initial research phase
Design Phase,2/15/2025,3/15/2025,UI/UX design
Development,4/15/2025,6/15/2025,Build features
Testing,6/15/2025,7/1/2025,QA and bug fixes
Launch,7/15/2025,7/15/2025,Product launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr4z6vdb8ql6ne8huc0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr4z6vdb8ql6ne8huc0x.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter your dates in a standard format like 1/15/2025 or 01/15/2025. Google Sheets recognizes these as dates automatically and right-aligns them in the cells.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Insert Timeline View
&lt;/h3&gt;

&lt;p&gt;In your Google Sheet, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Insert menu.&lt;/li&gt;
&lt;li&gt;Click Timeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5ucaim7fpfplm5u6t1k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5ucaim7fpfplm5u6t1k.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select your data range, including all columns with headers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jl1lgwr11djv0h3evbi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jl1lgwr11djv0h3evbi.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click Ok after validating the selection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Google Sheets creates an interactive timeline view showing your tasks as cards on a horizontal timeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21stnabvjgdy5y3ltbzs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21stnabvjgdy5y3ltbzs.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the right panel, you can customize the timeline by modifying the card title, the card color, or the display. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Embed in Google Docs
&lt;/h3&gt;

&lt;p&gt;Google Docs doesn't support embedding timelines directly from Google Sheets. To add your timeline to a document:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Google Sheets, click File -&amp;gt; Download -&amp;gt; PDF Document.&lt;/li&gt;
&lt;li&gt;Convert the PDF to an image using a tool like &lt;a href="https://pdf2png.com" rel="noopener noreferrer"&gt;pdf2png.com&lt;/a&gt; or &lt;a href="https://cloudconvert.com/" rel="noopener noreferrer"&gt;CloudConvert&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In Google Docs, click Insert -&amp;gt; Image -&amp;gt; Upload from computer.&lt;/li&gt;
&lt;li&gt;Select your converted timeline image.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in Preceden?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;Preceden&lt;/a&gt; is a dedicated timeline software explicitly designed for creating professional timelines. The free plan lets you create timelines manually, while paid plans allow you to import spreadsheet data for faster timeline creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a new timeline
&lt;/h3&gt;

&lt;p&gt;Visit &lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;preceden.com&lt;/a&gt; and sign up for an account. On the dashboard, click the "+Create Manually" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3j1noowdreuod3ypv5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3j1noowdreuod3ypv5o.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter a name for your timeline and click "Create Timeline →".&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add events to your timeline
&lt;/h3&gt;

&lt;p&gt;Preceden displays your blank timeline. Click the "+ Add Event" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceduxerzaci0zdzmhzpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceduxerzaci0zdzmhzpf.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter your event details, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event name.&lt;/li&gt;
&lt;li&gt;Start date.&lt;/li&gt;
&lt;li&gt;End date (optional for milestone events).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgra2pk50rs6hsm621yap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgra2pk50rs6hsm621yap.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Save. Your event appears on the timeline immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayv7vt4cbq72ucd6dwzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayv7vt4cbq72ucd6dwzb.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repeat this process to add more events. Alternatively, click "Bulk Add" to enter multiple events at once using a spreadsheet-style interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Use the timeline
&lt;/h3&gt;

&lt;p&gt;Preceden allows you to export the timeline in many formats, such as PDF or Images, or embed it if you need to display the chart on a website. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89d8fysqoohtwzvk2l4s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89d8fysqoohtwzvk2l4s.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in JavaScript
&lt;/h2&gt;

&lt;p&gt;Creating timelines in web applications requires a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt; library that handles rendering, interactivity, and data management. While you could build a timeline from scratch using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" rel="noopener noreferrer"&gt;HTML canvas&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG" rel="noopener noreferrer"&gt;SVG&lt;/a&gt;, production-quality timelines need features like drag-and-drop, zoom controls, and responsive design.&lt;/p&gt;

&lt;p&gt;For this guide, you will learn how to create a timeline using the &lt;a href="https://bryntum.com/products/scheduler-pro/" rel="noopener noreferrer"&gt;Bryntum Scheduler Pro&lt;/a&gt; component, which has easy setup and provides complete timeline functionality. &lt;a href="https://bryntum.com/" rel="noopener noreferrer"&gt;Bryntum&lt;/a&gt; offers trial packages so you can test the library before purchasing. If you're interested in other JavaScript timeline options, you can also check &lt;a href="https://visjs.github.io/vis-timeline/docs/timeline/" rel="noopener noreferrer"&gt;vis-timeline&lt;/a&gt; (open source) and &lt;a href="https://timeline.knightlab.com/" rel="noopener noreferrer"&gt;Timeline.js&lt;/a&gt; (free for storytelling timelines).&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before creating your timeline, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; and &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt; are installed on your computer.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://bryntum.com/" rel="noopener noreferrer"&gt;Bryntum&lt;/a&gt; account (free trial available).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Initialize your project
&lt;/h3&gt;

&lt;p&gt;Create a new project directory and initialize an npm project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;timeline-project
&lt;span class="nb"&gt;cd &lt;/span&gt;timeline-project
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Set up npm authentication
&lt;/h3&gt;

&lt;p&gt;Bryntum packages require authentication. Follow the &lt;a href="https://bryntum.com/products/scheduler/docs/guide/Scheduler/npm-repository#login" rel="noopener noreferrer"&gt;npm repository login guide&lt;/a&gt; to authenticate your npm client.&lt;/p&gt;

&lt;p&gt;Then configure npm to access Bryntum packages by following the &lt;a href="https://bryntum.com/products/scheduler/docs/guide/Scheduler/npm-repository#configure-npm" rel="noopener noreferrer"&gt;npm configuration guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Install Bryntum Scheduler
&lt;/h3&gt;

&lt;p&gt;Install Bryntum Scheduler. You can use the trial version or purchase a license. Follow the &lt;a href="https://bryntum.com/products/scheduler/docs/guide/Scheduler/npm-repository#installing-trial-packages" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; for your chosen version.&lt;/p&gt;

&lt;p&gt;For the trial version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @bryntum/scheduler@npm:@bryntum/scheduler-trial@6.3.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Create your HTML file
&lt;/h3&gt;

&lt;p&gt;Inside the project directory, create an &lt;code&gt;index.html&lt;/code&gt; file with the timeline container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nx"&gt;DOCTYPE&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="nx"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTF-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viewport&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width=device-width, initial-scale=1.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Timeline&lt;/span&gt; &lt;span class="nx"&gt;Chart&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stylesheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node_modules/@bryntum/scheduler/scheduler.stockholm.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;apple&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BlinkMacSystemFont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Segoe UI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sans&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;timeline&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="nf"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/style&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/head&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeline-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Create your JavaScript timeline
&lt;/h3&gt;

&lt;p&gt;Create an &lt;code&gt;app,js&lt;/code&gt; file with the following timeline configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Scheduler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DateHelper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./node_modules/@bryntum/scheduler/scheduler.module.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Define your timeline events&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Research &amp;amp; Planning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Design Phase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Launch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Create a single resource (timeline row)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Project Timeline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the timeline&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Scheduler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timeline-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;viewPreset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekAndMonth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Timeline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;eventDrag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;eventResize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;eventEdit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;eventTooltip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rowHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;barMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Run your timeline
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;index.html&lt;/code&gt; in a web browser. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhtawheu2bqlavj896sm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhtawheu2bqlavj896sm.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see an interactive timeline where you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag events to change their dates.&lt;/li&gt;
&lt;li&gt;Resize events to adjust duration.&lt;/li&gt;
&lt;li&gt;Click events to edit details.&lt;/li&gt;
&lt;li&gt;Hover over events to see tooltips.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This guide showed you how to create timelines in &lt;a href="https://www.microsoft.com/en-us/microsoft-365/excel" rel="noopener noreferrer"&gt;Excel&lt;/a&gt;, &lt;a href="https://www.microsoft.com/en-us/microsoft-365/word" rel="noopener noreferrer"&gt;Word&lt;/a&gt;, &lt;a href="https://www.microsoft.com/en-us/microsoft-365/powerpoint" rel="noopener noreferrer"&gt;PowerPoint&lt;/a&gt;, &lt;a href="https://www.google.com/docs/about/" rel="noopener noreferrer"&gt;Google Docs&lt;/a&gt;, &lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;Preceden&lt;/a&gt;, and JavaScript using &lt;a href="https://bryntum.com/products/scheduler-pro/" rel="noopener noreferrer"&gt;Bryntum Scheduler Pro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.google.com/sheets/about/" rel="noopener noreferrer"&gt;Google Sheets&lt;/a&gt; and &lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;Preceden&lt;/a&gt; offer the best experience because they use your spreadsheet data directly and let you easily customize colors. Microsoft tools use &lt;a href="https://support.microsoft.com/en-us/office/create-a-smartart-graphic-from-scratch-fac94c93-500b-4a0a-97af-124040594842" rel="noopener noreferrer"&gt;SmartArt&lt;/a&gt;, which is simple to use but requires manual updates for each event.&lt;/p&gt;

&lt;p&gt;For occasional timelines in documents or presentations, SmartArt works fine. For professional timelines with many events or frequent updates, use Preceden. And if you're a developer building a dashboard or tool that needs embedded timelines, use &lt;a href="https://bryntum.com/products/scheduler-pro/" rel="noopener noreferrer"&gt;Bryntum Scheduler Pro&lt;/a&gt; for complete control over appearance and functionality.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>CRM app with Node.js, Replit, and MongoDB</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Tue, 20 Sep 2022 09:09:47 +0000</pubDate>
      <link>https://dev.to/ritza/crm-app-with-nodejs-replit-and-mongodb-3p48</link>
      <guid>https://dev.to/ritza/crm-app-with-nodejs-replit-and-mongodb-3p48</guid>
      <description>&lt;p&gt;In this tutorial, we'll use Node.js on Replit and a MongoDB database to build a basic &lt;a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt; (Create, Read, Update, Delete) &lt;a href="https://en.wikipedia.org/wiki/Customer_relationship_management"&gt;CRM&lt;/a&gt; (Customer Relationship Management) application. A CRM lets you store information about customers to help you track the status of every customer relationship. The application will be able to store and edit customer details, as well as keep notes about them.&lt;/p&gt;

&lt;p&gt;This tutorial won't be covering the basics of Node.js, but each line of code will be explained in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up
&lt;/h2&gt;

&lt;p&gt;All the code for our application will be written and hosted on Replit, so you won't need to install any additional software on your computer.&lt;/p&gt;

&lt;p&gt;If you don't already have a &lt;a href="https://replit.com/signup"&gt;Replit&lt;/a&gt; account, create one now.&lt;/p&gt;

&lt;p&gt;Next, head over to &lt;a href="https://www.mongodb.com/cloud/atlas"&gt;MongoDB Atlas&lt;/a&gt; and hit the "Try free" button. You should then sign up, clicking the "Get started free" button to complete the process.&lt;/p&gt;

&lt;p&gt;MongoDB Atlas is a fully managed Database-as-a-Service. It provides a document database (often referred to as NoSQL), as opposed to a more traditional relational database like PostgreSQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a cluster
&lt;/h2&gt;

&lt;p&gt;Once you've signed up with MongoDB Atlas, under "Shared Clusters", press the "Create a Cluster" button.&lt;/p&gt;

&lt;p&gt;Select a provider and a region. For the purposes of this tutorial, we'll choose "Google Cloud Platform" as the provider and "Iowa (us-central1)" as the region, although it should work regardless of the provider and region. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tL99OwnB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/mongo-setup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tL99OwnB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/mongo-setup.png" alt="Cluster Region" width="880" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your cluster a name. Note that once you've created your cluster, you won't be able to change the name. Click "Create Cluster". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wdIkS2PL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/cluster-name.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wdIkS2PL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/cluster-name.png" alt="Cluster Name" width="880" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a bit of time, your cluster will be created. Once it's available, click on “Database Access” under the Security heading in the left-hand column and then click "Add New Database User". You need a database user to store and retrieve data. Enter a username and password for the user and make a note of those details - you'll need them later. Select “Read and write to any database” as the user privilege. Hit "Add User" to complete this step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TPgZ7sUp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/add-db-user.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TPgZ7sUp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/add-db-user.png" alt="Adding a New Database User" width="880" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you need to allow network access to the database. Click on "Network Access" in the left-hand column, and “Add IP Address”. Because we won't have a static IP from Replit, we're just going to allow access from anywhere - don't worry, the database is still secured with the username and password you created earlier. In the pop-up, click "Allow Access From Anywhere" and then "Confirm". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---69Iyyut--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/whitelist-entry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---69Iyyut--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/whitelist-entry.png" alt="Allow Access From Anywhere" width="880" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now select "Database" under "Deployment" in the left-hand column. Click on "Connect" and select “Connect Your Application”. This will change the pop-up view. Copy the "Connection String" as you will need it shortly to connect to your database from Replit. It will look something like this: &lt;code&gt;mongodb+srv://&amp;lt;username&amp;gt;:&amp;lt;password&amp;gt;@cluster0-zrtwi.gcp.mongodb.net/test?retryWrites=true&amp;amp;w=majority&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g1jmdi4U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/db-connect-string.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g1jmdi4U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/db-connect-string.png" alt="Retrieve Your Connection String" width="880" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a repl and connecting to the database
&lt;/h2&gt;

&lt;p&gt;Now we need a Node.js repl to write the code necessary to connect to our shiny new database. Navigate to Replit and create a new repl, selecting "Node.js" as the language.&lt;/p&gt;

&lt;p&gt;A great thing about Replit is that it makes projects public by default. This makes it easy to share and it's great for collaboration and learning, but we have to be careful not to make our database credentials available on the open internet.&lt;/p&gt;

&lt;p&gt;To solve this problem, we'll use Replit's &lt;code&gt;environment variables&lt;/code&gt;, as we have done in previous tutorials. Replit allows us to set secret environment variables through the "Secrets (Environment variables)" menu option. &lt;/p&gt;

&lt;p&gt;Open the "Secrets" menu option. There you will be able to set environment variables for your repl. Set the key as the name of your environment variable to &lt;code&gt;MONGO_USERNAME&lt;/code&gt;. Set the value as your database username, then click "Add new secret". Then create a new key called &lt;code&gt;MONGO_PASSWORD&lt;/code&gt; with its value set to your database password, and click "Add new secret". It should look something like: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TD4F-tPv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/replit-secrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TD4F-tPv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/replit-secrets.png" alt="Set Secrets Key Value" width="514" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replace&lt;/strong&gt; &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; with your database username and password.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have credentials set up for the database, we can move on to connecting to it in our code.&lt;/p&gt;

&lt;p&gt;MongoDB is kind enough to provide a client that we can use. To test out our database connection, we're going to insert some customer data into our database. In your &lt;code&gt;index.js&lt;/code&gt; file (created automatically and found under the Files pane), add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MongoClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongodb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;MongoClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongo_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MONGO_USERNAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongo_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MONGO_PASSWORD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`mongodb+srv://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mongo_username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mongo_password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@cluster0-zrtwi.gcp.mongodb.net/crmdb?retryWrites=true&amp;amp;w=majority`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MongoClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;useNewUrlParser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down to see what is going on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; adds the dependency for the MongoDB Client. Replit makes things easy by installing all the dependencies for us, so we don't have to use something like npm to do it manually.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Line 2 &amp;amp; 3&lt;/strong&gt;, we retrieve our MongoDB username and password from the environment variables that we set up earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5&lt;/strong&gt; has a few very important details that we need to get right:

&lt;ul&gt;
&lt;li&gt;Replace the section between the &lt;code&gt;@&lt;/code&gt; and the next &lt;code&gt;/&lt;/code&gt; with the same section of your connection string from MongoDB that we copied earlier. You may notice the &lt;code&gt;${mongo_username}&lt;/code&gt; and &lt;code&gt;${mongo_password}&lt;/code&gt; before and after the colon near the beginning of the string. These are called template literals. Template literals allow us to put variables in a string, which Node.js will then replace with the actual values of the variables.&lt;/li&gt;
&lt;li&gt;Note &lt;code&gt;crmdb&lt;/code&gt; after the &lt;code&gt;/&lt;/code&gt; and before the &lt;code&gt;?&lt;/code&gt;. This will be the name of the database that we will be using. MongoDB creates the database for us if it doesn't exist. You can change this to whatever you want to name the database, but remember what you changed it to for future sections of this tutorial.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 6&lt;/strong&gt; creates the client that we will use to connect to the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Making a user interface to insert customer data
&lt;/h2&gt;

&lt;p&gt;We're going to make an HTML form that will capture the customer data and send it to our Replit code, which will then insert it into our database.&lt;/p&gt;

&lt;p&gt;In order to actually present and handle an HTML form, we need a way to process HTTP GET and POST requests. The easiest way to do this is to use a web application framework. A web application framework is designed to support the development of web applications - it gives you a standard way to build your application and lets you get to building your application fast without having to do the boilerplate code.&lt;/p&gt;

&lt;p&gt;A really simple, fast, and flexible Node.js web application framework is &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt;, which provides a robust set of features for the development of web applications.&lt;/p&gt;

&lt;p&gt;The first thing we need to do is add the dependencies we need. Right at the top of your &lt;code&gt;index.js&lt;/code&gt; file (above the MongoDB code), add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bodyParser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body-parser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; adds the dependency for Express. Replit will take care of installing it for us.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 2&lt;/strong&gt; creates a new Express app that will handle incoming requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 3&lt;/strong&gt; adds a dependency for &lt;code&gt;'body-parser'&lt;/code&gt;. This is needed for the Express server to be able to handle the data that the form will send, and give it to us in a useful format to use in the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 4&lt;/strong&gt; adds a dependency for a basic HTTP server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 6 &amp;amp; 7&lt;/strong&gt; tell the Express app which parsers to use on incoming data. This is needed to handle form data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we need to add a way for the Express app to handle an incoming request and give it to us in the form we need. Add the following lines of code below the code you added above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/create.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app.get&lt;/code&gt; tells Express that we want it to handle a GET request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'/'&lt;/code&gt; tells Express that it should respond to GET requests sent to the root URL. A root URL looks something like &lt;code&gt;https://crm.hawkiesza.repl.co&lt;/code&gt; - note that there are no slashes after the URL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'/create'&lt;/code&gt; tells Express that it should respond to GET requests sent to the &lt;code&gt;/create&lt;/code&gt; endpoint after the root URL, i.e. &lt;code&gt;https://crm.hawkiesza.repl.co/create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;res.sendFile&lt;/code&gt; tells Express to send the given file as a response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before the server will start receiving requests and sending responses, we need to tell it to run. Add the following code below the previous line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listening on port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; tells Express to set the port number to either a number defined as an environment variable, or 5000 if no definition was made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 2-4&lt;/strong&gt; tells the server to start listening for requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we have an Express server listening for requests, but we haven't yet built the form that it needs to send back if it receives a request.&lt;/p&gt;

&lt;p&gt;Make a new file called &lt;code&gt;index.html&lt;/code&gt; and paste the following code into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/create"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Create"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a straightforward bit of HTML that puts a single button on the page. When this button is clicked, it sends a GET request to &lt;code&gt;/create&lt;/code&gt;, which the server will then respond to according to the code that we wrote above - in our case, it will send back the &lt;code&gt;create.html&lt;/code&gt; file which we will define now.&lt;/p&gt;

&lt;p&gt;Make a new file called &lt;code&gt;create.html&lt;/code&gt; and paste the following into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Customer details&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/create"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer name *&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"John Smith"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"address"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer address *&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"address"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"42 Wallaby Way, Sydney"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"telephone"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer telephone *&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"telephone"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"+275554202"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"note"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer note&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"note"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Needs a new pair of shoes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won't go in-depth into the above HTML. It is a very basic form with four fields (name, address, telephone, note) and a submit button, which creates an interface that will look like the one below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3O1MQpO_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-details.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3O1MQpO_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-details.png" alt="Customer Details" width="344" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the user presses the submit button, a POST request is made to &lt;code&gt;/create&lt;/code&gt; with the data in the form - we still have to handle this request in our code as we're currently only handling a GET request to &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you click the "Run" button now, a new window should appear on the right that displays the "Create" button we defined just now in &lt;code&gt;index.html&lt;/code&gt;. To see the form, you can also navigate to &lt;code&gt;https://&amp;lt;repl_name&amp;gt;.&amp;lt;your_username&amp;gt;.repl.co&lt;/code&gt;, replacing  with whatever you named your repl (but with no underscores or spaces) and  with your repl username. You will be able to see this URL in the repl itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UFxApidL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/first-run.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UFxApidL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/first-run.png" alt="Run Your Application" width="880" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you select "Create" and then fill in the form and hit submit, you'll get a response that says&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;Cannot POST /create&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
. This is because we haven't added the code that handles the form POST request, so let's do that. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nlqFJSmw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/cannot-post.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nlqFJSmw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/cannot-post.png" alt="*Cannot POST/create*" width="880" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following code into your &lt;code&gt;index.js&lt;/code&gt; file, below the &lt;code&gt;app.get&lt;/code&gt; entry that we made above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 customer inserted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Customer created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; defines a new route that listens for an HTTP POST request at &lt;code&gt;/create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 2&lt;/strong&gt; connects to the database. This happens asynchronously, so we define a callback function that will be called once the connection is made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 3&lt;/strong&gt; creates a new collection of customers. Collections in MongoDB are similar to tables in SQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5&lt;/strong&gt; defines customer data that will be inserted into the collection. This is taken from the incoming request. The form data is parsed using the parsers that we defined earlier and is then placed in the &lt;code&gt;req.body&lt;/code&gt; variable for us to use in the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 6&lt;/strong&gt; inserts the customer data into the collection. This also happens asynchronously, and so we define another callback function that will get an error if an error occurred, or the response if everything happened successfully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 7&lt;/strong&gt; throws an error if the above insert had a problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 8&lt;/strong&gt; gives us some feedback that the insert happened successfully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run the repl now (you may need to refresh it) and submit the filled-in form, you'll get a message back that says "Customer created". If you look in your cluster in MongoDB and select the "Collections" button, you'll see a document has been created with the details that we submitted in the form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TFoB1huy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TFoB1huy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-created.png" alt="Customer Created" width="880" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating and deleting database entries
&lt;/h2&gt;

&lt;p&gt;As a final step in this tutorial, we want to be able to update and delete database documents in our collection. To make things simpler, we're going to make a new HTML page where we can request a document and then update or delete it.&lt;/p&gt;

&lt;p&gt;First, let's make the routes to our new page. In your &lt;code&gt;index.js&lt;/code&gt;, add the following code below the rest of your routing code (i.e. before the MongoDB code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/get.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/get-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;oldname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldaddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldtelephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldnote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1-3&lt;/strong&gt; tells Express to respond to incoming GET requests on &lt;code&gt;/get&lt;/code&gt; by sending the &lt;code&gt;get.html&lt;/code&gt; file which we will define below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5-12&lt;/strong&gt; tells Express to respond to incoming GET requests on &lt;code&gt;/get-client&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 7&lt;/strong&gt; makes a call to the database to fetch a customer by name. If there is more than one customer with the same name, then the first one found will be returned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 9&lt;/strong&gt; tells Express to render the &lt;code&gt;update&lt;/code&gt; template, replacing variables with the given values as it goes. Important to note here is that we are also replacing values in the hidden form fields we created earlier with the current values of the customer details. This is to ensure that we update or delete the correct customer.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In your &lt;code&gt;index.html&lt;/code&gt; file, add the following code after the &lt;code&gt;&amp;lt;/form&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/get"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Update/Delete"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds a new button that will make a GET request to &lt;code&gt;/get&lt;/code&gt;, which will then return &lt;code&gt;get.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--14QLuuuN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/buttons.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--14QLuuuN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/buttons.png" alt="Index" width="880" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make a new file called &lt;code&gt;get.html&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt;  &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/get-client"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer name *&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"John Smith"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Get customer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes a simple form with an input for the customer's name and a button. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0gx6EYW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/get-customer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0gx6EYW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/get-customer.png" alt="Get Customer" width="880" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking this button will make a GET request to &lt;code&gt;/get-client&lt;/code&gt;, which will respond with the client details, and we will be able to update or delete them.&lt;/p&gt;

&lt;p&gt;To see the customer details on a form after requesting them, we need a templating engine to render them onto the HTML page and send the rendered page back to us. With a templating engine, you define a template - a page with variables in it - and then give it the values you want to fill into the variables. In our case, we're going to request the customer details from the database and tell the templating engine to render them onto the page.&lt;/p&gt;

&lt;p&gt;We're going to use a templating engine called &lt;a href="https://pugjs.org/api/getting-started.html"&gt;Pug&lt;/a&gt;. Pug is a simple templating engine that integrates fully with Express. The syntax that Pug uses is very similar to HTML. One important difference in the syntax is that spacing is very important as it determines your parent/child hierarchy.&lt;/p&gt;

&lt;p&gt;First, we need to tell Express which templating engine to use and where to find our templates. Put the following line above your route definitions (i.e. after the other &lt;code&gt;app.use&lt;/code&gt; lines in &lt;code&gt;index.js&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;__express&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;views&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;view engine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new file called &lt;code&gt;update.pug&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;html
  body
    p #{message}
    h2= 'Customer details'
    form(method='POST' action='/update')
      input(type='hidden' id='oldname' name='oldname' value=oldname)
      input(type='hidden' id='oldaddress' name='oldaddress' value=oldaddress)
      input(type='hidden' id='oldtelephone' name='oldtelephone' value=oldtelephone)
      input(type='hidden' id='oldnote' name='oldnote' value=oldnote)
      label(for='name') Customer name:
      br
      input(type='text', placeholder='John Smith' name='name' value=name)
      br
      label(for='address') Customer address:
      br
      input(type='text', placeholder='42 Wallaby Way, Sydney' name='address' value=address)
      br
      label(for='telephone') Customer telephone:
      br
      input(type='text', placeholder='+275554202' name='telephone' value=telephone)
      br
      label(for='note') Customer note:
      br
      input(type='text', placeholder='Likes unicorns' name='note' value=note)
      br
      button(type='submit' formaction="/update") Update
      button(type='submit' formaction="/delete") Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is very similar to the HTML form we created previously for &lt;code&gt;create.html&lt;/code&gt;, however this is written in the Pug templating language. We're creating a hidden element to store the "old" name, telephone, address, and note of the customer - this is for when we want to do an update.&lt;/p&gt;

&lt;p&gt;Using the old details to update the customer is an easy solution, but not the best solution as it makes the query cumbersome and slow. If you add extra fields into your database, you would have to remember to update your query as well, otherwise it could lead to updating or deleting the wrong customer if they have the same information. A better but more complicated way is to use the unique ID of the database document, as that will only ever refer to one customer.&lt;/p&gt;

&lt;p&gt;We have also put in placeholder variables for name, address, telephone, and note, and we have given the form two buttons with different actions.&lt;/p&gt;

&lt;p&gt;If you now run the code, you will have an index page with two buttons. Pressing the "Update/Delete" button will take you to a new page that asks for a customer name. If you fill in the customer name and press "Get customer", a page will load with the customer's details and two buttons below, "Update" and "Delete". Make sure you enter a customer name you have entered before.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1oPAnO0e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-details-final.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1oPAnO0e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-details-final.png" alt="Update-Delete" width="880" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our next step is to add the "Update" and "Delete" functionality. Add the following code below your routes in &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldaddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldtelephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldnote&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;newvalues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newvalues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 document updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Customer updated!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldaddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldtelephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldnote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;deleteOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 document deleted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Customer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; deleted`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This introduces two new POST handlers - one for &lt;code&gt;/update&lt;/code&gt;, and one for &lt;code&gt;/delete&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 2&lt;/strong&gt; connects to our MongoDB database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 3&lt;/strong&gt; throws an error if there was a problem connecting to the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 4&lt;/strong&gt; defines a query that we will use to find the document to update. In this case, we'll use the details of the customer &lt;em&gt;before&lt;/em&gt; it was updated. We saved this name earlier in a hidden field in the HTML. Trying to find the customer by its updated name obviously won't work, because it hasn't been updated yet. Also, note that we are setting some of the fields to null if they are empty. This is so that the database returns the correct document when we update or delete - if we search for a document that has no address with an address of '' (empty string), then our query won't return anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5&lt;/strong&gt; defines the new values that we want to update our customer with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 6&lt;/strong&gt; updates the customer with the new values using the query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 7&lt;/strong&gt; throws an error if there was a problem with the update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 8&lt;/strong&gt; logs that a document was updated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 9&lt;/strong&gt; re-renders the update page with a message saying that the customer was updated, and displays the new values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 15&lt;/strong&gt; connects to our MongoDB database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 16&lt;/strong&gt; throws an error if there was a problem connecting to the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 17&lt;/strong&gt; defines a query that we will use to find the document to delete. In this case, we use all the details of the customer &lt;em&gt;before&lt;/em&gt; any changes were made on the form to make sure we delete the correct customer.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Line 18&lt;/strong&gt;, we connect to the database and delete the customer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 19&lt;/strong&gt; throws an error if there was a problem with the delete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 20&lt;/strong&gt; logs that a document was deleted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 21&lt;/strong&gt; sends a response to say that the customer was deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;If you run your application now, you'll be able to create, update, and delete documents in a MongoDB database. This is a very basic CRUD application, with a very basic and unstyled UI, but it should give you the foundation to build much more sophisticated applications.&lt;/p&gt;

&lt;p&gt;Some ideas for this are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You could add fields to the database to classify customers according to which stage they are in your sales &lt;a href="https://www.bitrix24.com/glossary/what-is-pipeline-management-definition-crm.php"&gt;pipeline&lt;/a&gt; so that you can track if a customer is potentially stuck somewhere and contact them to re-engage.&lt;/li&gt;
&lt;li&gt;You could integrate some basic marketing automation with a page allowing you to send an email or SMS to customers (though don't spam clients!).&lt;/li&gt;
&lt;li&gt;You could also add fields to keep track of customer purchasing information so that you can see which products do well with which customers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to start from where this tutorial leaves off, fork the repl at &lt;a href="https://replit.com/@ritza/replcrm"&gt;https://replit.com/@ritza/replcrm&lt;/a&gt;.&lt;br&gt;
To get additional guidance from the Replit community, you can join Replit's Discord server by using this invite link &lt;a href="https://replit.com/discord"&gt;https://replit.com/discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://replit.com/@ritza/replcrm"&gt;https://replit.com/@ritza/replcrm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>replit</category>
      <category>crm</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Discord bot with Node.js and Replit</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Mon, 12 Sep 2022 08:31:21 +0000</pubDate>
      <link>https://dev.to/ritza/building-a-discord-bot-with-nodejs-and-replit-3i7f</link>
      <guid>https://dev.to/ritza/building-a-discord-bot-with-nodejs-and-replit-3i7f</guid>
      <description>&lt;p&gt;In this tutorial, we'll use &lt;a href="https://replit.com"&gt;Replit&lt;/a&gt; and Node.js to build a Discord chatbot. The bot will be able to join a Discord server and respond to messages.&lt;/p&gt;

&lt;p&gt;If you prefer Python, here's a &lt;a href="https://docs.replit.com/tutorials/discord-role-bot"&gt;Python Discord bot tutorial&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You'll find it easier to follow along if you have some JavaScript knowledge and you should have used Discord or a similar app such as Skype or Telegram before. We won't be covering the very basics of Node.js, but we will explain each line of code in detail, so if you have any experience with programming, you should be able to follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview and requirements
&lt;/h2&gt;

&lt;p&gt;We'll be doing all of our coding through the Replit web IDE, and we'll host our bot with Replit too, so you won't need to install any additional software on your machine. For this tutorial you will need to create a &lt;a href="https://discordapp.com/"&gt;Discord&lt;/a&gt; account (if you already have one, you can skip this).&lt;/p&gt;

&lt;p&gt;We will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating an application and a bot user in your Discord account.&lt;/li&gt;
&lt;li&gt;Creating a server on Discord.&lt;/li&gt;
&lt;li&gt;Adding our bot to our Discord server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get through these admin steps first and then we can get to the fun part of coding our bot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a bot in Discord and getting a token
&lt;/h3&gt;

&lt;p&gt;You can sign up for a free account over at &lt;a href="https://discordapp.com/register"&gt;Discord&lt;/a&gt;, and you can download one of their desktop or mobile applications from &lt;a href="https://discordapp.com/"&gt;the Discord homepage&lt;/a&gt;. You can also use Discord in the browser.&lt;/p&gt;

&lt;p&gt;Once you have an account, you'll want to create a Discord application. Visit &lt;a href="https://discordapp.com/developers/applications/"&gt;the Discord developer's page&lt;/a&gt; and press the "New application" button, as in the image below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mP-eLC6W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/new-discord-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mP-eLC6W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/new-discord-app.png" alt="Creating a new Discord application" width="880" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill out a name for your bot and select "Create".&lt;/p&gt;

&lt;p&gt;The first thing to do on the next page is to note your Application ID, which you'll need to add the bot to the server. You can come back later and get it from this page, or copy it somewhere so you can find it when you need it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---7Hh2W6u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/app-id.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---7Hh2W6u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/app-id.png" alt="Record your Client ID" width="880" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also rename the application and provide a description for your bot, then press "Save Changes".&lt;/p&gt;

&lt;p&gt;You have now created a Discord application. The next step is to add a bot to this application, so head over to the "Bot" tab using the menu on the left and press the "Add Bot" button, as shown below. Click "Yes, do it" when Discord asks if you're sure about bringing a new bot to life.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TjsRfs4---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/add-bot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TjsRfs4---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/add-bot.png" alt="Adding a bot to our Discord Application" width="880" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last thing we'll need from our bot is a token. Anyone who has the bot's token can prove that they own the bot, so you'll need to be careful not to share this with anyone. You can get the token by pressing "Reset Token", and then copy it to your clipboard by pressing "Copy".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lmAN3B4p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/get-token.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lmAN3B4p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/get-token.png" alt="Generating a token for our Discord bot" width="880" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--66ITfYqh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/copy-token.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--66ITfYqh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/copy-token.png" alt="Copying a token for our Discord bot" width="880" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take note of your token or copy it to your clipboard, as we'll need to add it to our code soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Discord server
&lt;/h3&gt;

&lt;p&gt;If you don't have a Discord server to add your bot to, you can create one by either opening the desktop Discord application that you downloaded earlier or returning to the Discord home page in your browser. Press the "+" icon, as shown below, to create a server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bgu1mlYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/create-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bgu1mlYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/create-server.png" alt="Creating a Discord server" width="880" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press "Create a server" in the screen that follows, and then give your server a name. Once the server is up and running, you can chat with yourself, or invite some friends to chat with you. Soon we'll invite our bot to chat with us as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding your Discord bot to your Discord server
&lt;/h3&gt;

&lt;p&gt;Our Discord bot is still just a shell at this stage as we haven't written any code to allow it to do anything, but let's go ahead and add the bot to our Discord server anyway. To add a bot to your server, you'll need the Application ID from the "General Information" page that we looked at before when we created our ReplBotApplication (ie. the application ID, not the secret bot token).&lt;/p&gt;

&lt;p&gt;Create a URL that looks as follows, but using your application ID instead of mine at the end (the link calls the application ID "client_id"):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discordapp.com/api/oauth2/authorize?scope=bot&amp;amp;client_id=746269162917331028"&gt;https://discordapp.com/api/oauth2/authorize?scope=bot&amp;amp;client_id=746269162917331028&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visit the URL that you created in your web browser and you'll see a page similar to the following where you can choose which server to add your bot to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hfhc6xXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/add-bot-to-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hfhc6xXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/add-bot-to-server.png" alt="Authorizing our bot to join our server" width="880" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the server we created in the step before this and hit the "Authorize" button. After completing the captcha, you should get an in-app Discord notification telling you that your bot has joined your server.&lt;/p&gt;

&lt;p&gt;Now we can get to the fun part of building a brain for our bot!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a repl and installing our Discord dependencies
&lt;/h2&gt;

&lt;p&gt;The first thing we need to do is create a Node.js repl to write the code for our Discord bot. Over at &lt;a href="https://replit.com"&gt;Replit&lt;/a&gt;, create a new repl, choosing "Node.js" as your language.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---KovBJkt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/new-repl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---KovBJkt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/new-repl.png" alt="Create a new repl" width="880" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We don't need to reinvent the wheel as there is already a great Node wrapper for the Discord bot API called &lt;a href="https://discord.js.org/"&gt;discord.js&lt;/a&gt;. Normally we would install this third-party library through &lt;a href="https://www.npmjs.com/"&gt;npm&lt;/a&gt;, but because we're using Replit, we can skip the installation. Our repl will automatically pull in all dependencies. &lt;/p&gt;

&lt;p&gt;In the default &lt;code&gt;index.js&lt;/code&gt; file that is included with your new repl, add the following line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Discord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press the "Run" button and you should see Replit installing the Discord library in the output pane on the right, as in the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nY9RI2yy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/install-discord-js.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nY9RI2yy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/install-discord-js.png" alt="Installing Discord.js in our Repl" width="880" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our bot is nearly ready to go - but we still need to plug in our secret token. This will authorize our code to control our bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up authorization for our bot
&lt;/h2&gt;

&lt;p&gt;By default, Replit code is public. This is great as it encourages collaboration and learning, but we need to be careful not to share our secret bot token (which gives anyone who has access to it full control of our bot).&lt;/p&gt;

&lt;p&gt;To get around the problem of needing to give our &lt;em&gt;code&lt;/em&gt; access to the token while allowing others to access our code but &lt;em&gt;not&lt;/em&gt; our token, we'll be using &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-read-and-set-environmental-and-shell-variables-on-a-linux-vps"&gt;environment variables&lt;/a&gt;. Replit allows us to set secret environment variables through the "Secrets (Environment variables)" menu option. &lt;/p&gt;

&lt;p&gt;Open the "Secrets" menu option. There you will be able to set environment variables for your repl. Set the key as the name of your environment variable to &lt;code&gt;DISCORD_BOT_SECRET&lt;/code&gt;. Set the value as your bot's secret token (note that this is the second token that we got while setting up the bot -- different from the Application ID that we used to add our bot to our server). It should look something like: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A3Aj3xQj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/replit-secrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A3Aj3xQj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/replit-secrets.png" alt="Set Secrets Key Value" width="880" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's make a Discord bot that repeats everything we say but in reverse. We can do this in just a few lines of code. In your &lt;code&gt;index.js&lt;/code&gt; file, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Discord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Discord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GUILDS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GUILD_MESSAGES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DISCORD_BOT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messageCreate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's tear this apart line by line to see what it does.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; is the line we added earlier. This line tells Replit to install the third party library and brings it into this file so that we can use it.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;line 2&lt;/strong&gt;, we create a Discord &lt;code&gt;Client&lt;/code&gt;. We'll use this client to send commands to the Discord &lt;em&gt;server&lt;/em&gt; to control our bot and send it commands. &lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;line 3-8&lt;/strong&gt;, we provide the &lt;code&gt;intents&lt;/code&gt; of our &lt;code&gt;Client&lt;/code&gt;, these are provided to designate which events our bot will be able to receive.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;line 9&lt;/strong&gt;, we retrieve our secret token from the environment variables (which Replit sets from the "Secrets" menu). &lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;line 11&lt;/strong&gt;, we define an &lt;code&gt;event&lt;/code&gt; for our client, which defines how our bot should react to the &lt;code&gt;ready&lt;/code&gt; event. The Discord bot is going to run &lt;em&gt;asynchronously&lt;/em&gt;, which might be a bit confusing if you're used to running standard synchronous code. We won't go into asynchronous coding in-depth here, but if you're interested in what this is and why it's used, there's a good guide over at &lt;a href="https://blog.risingstack.com/node-hero-async-programming-in-node-js/"&gt;RisingStack&lt;/a&gt;. In short, instead of running the code in our file from top to bottom, we'll be running pieces of code in response to specific events.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;lines 12-14&lt;/strong&gt;, we define how our bot should respond to the &lt;code&gt;ready&lt;/code&gt; event, which is fired when our bot successfully joins a server. We instruct our bot to output some information server-side (i.e. it will be displayed in our repl's output, but not sent as a message through to Discord). We'll print a simple "I'm in" message to see that the bot is there and print our bot's username (if you're running multiple bots, this will make it easier to work out who's doing what). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lines 16-20&lt;/strong&gt; are similar, but instead of responding to a &lt;code&gt;ready&lt;/code&gt; event, we tell our bot how to handle new messages. &lt;strong&gt;Line 17&lt;/strong&gt; says we only want to respond to messages that aren't from us (otherwise our bot will keep responding to himself -- you can remove this line to see why that's a problem), and &lt;strong&gt;line 18&lt;/strong&gt; says we'll send a new message to the same channel we received a message in (&lt;code&gt;msg.channel&lt;/code&gt;) and the content we'll send will be the same message that we received, but backwards. To reverse a string, we split it into its individual characters, reverse the resulting array, and then join it all back into a string again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last line fires up our bot and uses the token we loaded earlier to log into Discord.&lt;/p&gt;

&lt;p&gt;Press the "Run" button again and you should see your bot reporting a successful channel join in the repl output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YpQTLuT0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/bot-join.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YpQTLuT0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/bot-join.png" alt="Repl output showing channel join" width="880" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open Discord, and from within the server we created earlier, you will be able to send a message (by typing into the box highlighted below) and see your bot respond!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BgOXMgH1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/message-repl-bot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BgOXMgH1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/message-repl-bot.png" alt="Send a message to your bot" width="880" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bot responds each time, reversing the text we enter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6Z01OpuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/repl-bot-response.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6Z01OpuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/repl-bot-response.png" alt="Our bot can talk!" width="880" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping our bot alive
&lt;/h2&gt;

&lt;p&gt;Your bot can now respond to messages, but only for as long as your repl is running. If you close your browser tab or shut down your computer, your bot will stop and no longer respond to messages on Discord. &lt;/p&gt;

&lt;p&gt;Replit will keep your code running after you close the browser tab only if you are running a web server. Our bot doesn't require an explicit web server to run, but we can create a server and run it in the background just to keep our repl alive. &lt;/p&gt;

&lt;p&gt;Create a new file in your project called &lt;code&gt;keep_alive.js&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm alive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won't go over this in detail as it's not central to our bot, but here we start a web server that will return "I'm alive" if anyone visits it.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;index.js&lt;/code&gt; file, we need to add a &lt;code&gt;require&lt;/code&gt; statement for this server at the top. Add the following line near the top of &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keep_alive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./keep_alive.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After doing this and hitting the green "Run" button again, you should see some changes to your repl. For one, you'll see a new pane in the top right that shows the web output from your server. We can see that visiting our repl now returns a basic web page showing the "I'm alive" string that we told our web server to return by default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--94hOG4Fj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/keep-alive.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--94hOG4Fj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/keep-alive.png" alt="Running a Node server in the background" width="880" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now your bot will stay alive even after closing your browser or shutting down your development machine. Replit will still clean up your server and kill your bot after about one hour of inactivity, so if you don't use your bot for a while, you'll have to log into your repl and start the bot up again. Alternatively, you can set up a third-party (free!) service like &lt;a href="https://uptimerobot.com/"&gt;Uptime Robot&lt;/a&gt;. Uptime Robot pings your site every five minutes to make sure it's still working -- usually to notify you of unexpected downtime, but in this case the constant pings have the side effect of keeping our repl alive as it will never go more than an hour without receiving any activity. Note that you need to select the "HTTP" option instead of the "Ping" option when setting up Uptime Robot, as Replit requires regular HTTP requests to keep your chatbot alive, or you can use an &lt;a href="https://docs.replit.com/hosting/enabling-always-on"&gt;Always-on&lt;/a&gt; repl to keep it running 24/7.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forking and extending our basic bot
&lt;/h2&gt;

&lt;p&gt;This is not a very useful bot as-is, but the possibilities are only limited by your creativity now! You can have your bot receive input from a user, process the input, and respond in any way you choose. In fact, with the basic input and output that we've demonstrated, we have most of the components of any modern computer, all of which are based on the &lt;a href="https://en.wikipedia.org/wiki/Von_Neumann_architecture"&gt;Von Neumann architecture&lt;/a&gt; (we could easily add the missing memory by having our bot write to a file, or with a bit more effort link in a &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite database&lt;/a&gt; for persistent storage).&lt;/p&gt;

&lt;p&gt;If you followed along with this tutorial, you'll have your own basic repl bot to play around with and extend. If you were only reading, you can easily fork my bot at &lt;a href="https://replit.com/@ritza/discord-bot-node-1"&gt;https://replit.com/@ritza/discord-bot-node-1&lt;/a&gt; and extend it however you like (you'll need to add your own token and reset the secrets still). Happy hacking!&lt;/p&gt;

&lt;p&gt;If you're stuck for ideas, you could try link up your Discord bot to the &lt;a href="https://dev.twitch.tv/"&gt;Twitch API&lt;/a&gt; to get notified when your favorite streamers are online, or build a &lt;a href="https://en.wikipedia.org/wiki/Interactive_fiction"&gt;text adventure&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading:
&lt;/h2&gt;

&lt;p&gt;If you are interested, here's another Discord bot tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.replit.com/tutorials/discord-role-bot"&gt;Role assignment bot with Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://replit.com/@ritza/discord-bot-node-1"&gt;https://replit.com/@ritza/discord-bot-node-1&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discord</category>
      <category>node</category>
      <category>replit</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Card game with pygame</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Wed, 07 Sep 2022 07:59:11 +0000</pubDate>
      <link>https://dev.to/ritza/card-game-with-pygame-3jc6</link>
      <guid>https://dev.to/ritza/card-game-with-pygame-3jc6</guid>
      <description>&lt;p&gt;Card games are a great way to learn how to program. We get to build a model of the game, game logic, and a visual interface. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mOSvFTBF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/gameplay.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mOSvFTBF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/gameplay.gif" alt="Playing SnaPy" width="880" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A classic card game is Snap. The rules are pretty simple, which makes it great for building a first card game. We can spend more time on the modelling and visualization, which can be used on other card games too. &lt;/p&gt;

&lt;h2&gt;
  
  
  The game rules
&lt;/h2&gt;

&lt;p&gt;To simplify this tutorial, we'll limit our game to 2 players. Here's the rules for our 2 player game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The deck is shuffled, and dealt evenly between the two players.&lt;/li&gt;
&lt;li&gt;Each player takes turns drawing a card and places it face up on top of the last card drawn.&lt;/li&gt;
&lt;li&gt;If the card placed is a match (by value) of the previous card, the first player to call "Snap!" wins the entire pile of cards. These cards are added to their hand. &lt;/li&gt;
&lt;li&gt;If a player calls "Snap!" and the card placed is not a match (i.e. falsely calls "Snap!"), then the other player takes the pile of cards.&lt;/li&gt;
&lt;li&gt;The first player to run out of cards loses, and the other player wins.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a new project
&lt;/h2&gt;

&lt;p&gt;Let's head over to &lt;a href="https://replit.com"&gt;Replit&lt;/a&gt; and create a new repl. Choose &lt;strong&gt;Pygame&lt;/strong&gt; as the template to create a repl from. Now, give this repl a name, like "SnaPy".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EueKkEkk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/new-repl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EueKkEkk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/new-repl.png" alt="Creating a new repl" width="880" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the repl has booted up, you should see a &lt;code&gt;main.py&lt;/code&gt; file. We'll use this file for the main game loop, but we'll create other files for the game models and logic. &lt;/p&gt;

&lt;p&gt;For our Snap game, we'll need some images of cards to display. Create a folder called &lt;code&gt;images&lt;/code&gt; in the file explorer of your repl. &lt;/p&gt;

&lt;p&gt;Now download &lt;a href="///tutorial-files/card-game-pygame/snapy-resources.zip"&gt;these card images&lt;/a&gt; to your computer. Unzip the file, and drag and drop the images it contains to the &lt;code&gt;images&lt;/code&gt; folder in your repl. &lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started with pygame
&lt;/h2&gt;

&lt;p&gt;A popular game framework in Python is &lt;a href="https://www.pygame.org/"&gt;pygame&lt;/a&gt;. It has functionality to draw shapes and images to the screen, get user input, play sounds and more. We'll use some of the basic functionality in this game to see how it works.&lt;/p&gt;

&lt;p&gt;We can import it into our project by adding the following line to our &lt;code&gt;main.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pygame&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the pygame framework started, we need to add some initialization code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_caption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SnaPy"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; starts up the pygame system, by &lt;a href="https://www.pygame.org/docs/ref/pygame.html?highlight=init#pygame.init"&gt;initializing&lt;/a&gt; its modules (for example, the font, sound and graphics code).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 2&lt;/strong&gt; creates a new tuple called &lt;code&gt;bounds&lt;/code&gt;. This tuple contains the dimensions of the window that we'll run our Snap game in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 3&lt;/strong&gt; creates a new window for us to display our game in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 4&lt;/strong&gt; gives the window a caption, or title. This can be whatever you'd like to call the game.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run the project, using the "Run" button at the top center of the Repl, you should see a small blank window come up. That means everything is initialized and working so far. Not much, but it's our blank canvas to get started with!&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the game model
&lt;/h2&gt;

&lt;p&gt;Python is an object-oriented language. With object-oriented programming, we identify different parts and entities of the game and model them as classes and objects. Let's draw the game, and see what parts we need to model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CHJbQz24--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/game-objects.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CHJbQz24--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/game-objects.png" alt="Game objects" width="880" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the above image, we can see that we can model the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Individual cards&lt;/li&gt;
&lt;li&gt;A deck of cards, which is a collection of all the cards&lt;/li&gt;
&lt;li&gt;Players, with their hand of cards and name&lt;/li&gt;
&lt;li&gt;A pile of cards, which is a collection of cards that are face up on the table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that this is one way to group the game parts, but it is not the only way. There are many ways to identify individual objects, and a big factor is how granular you need to be, and how responsibilities are assigned. This is part art, part experience and part computer science. Often we start with a model design, and then refine it over time as we learn more about the game and the dynamics of the model.&lt;/p&gt;

&lt;p&gt;We'll also need logic to control the rules and the state of the game. Often this part of the program is known as the "game engine". We'll model this as a class as well.&lt;/p&gt;

&lt;p&gt;Here is a diagram of all the classes we'll build. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UkSwPWJY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/class-model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UkSwPWJY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/class-model.png" alt="Class model" width="880" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The classes have some of the key properties and methods that they will need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the game model
&lt;/h2&gt;

&lt;p&gt;Let's start by building the Card class. This class will represent a single card. &lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;models.py&lt;/code&gt; in Replit. Inside this file we can define the Card class.&lt;/p&gt;

&lt;p&gt;At the top of the file, add in some package imports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Enum&lt;/code&gt; for enumerations (which we'll use for defining the card suits).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pygame&lt;/code&gt;, to load up the card images.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;random&lt;/code&gt;, to use when we shuffle the deck.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pygame&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's add in the suits we'll need for the cards. We'll use the &lt;a href="https://docs.python.org/3/library/enum.html"&gt;Enum&lt;/a&gt; class to define the different suits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Suits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;CLUB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;SPADE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;HEART&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="n"&gt;DIAMOND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, the boilerplate is out of the way. Now, lets define the Card class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'images/'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;'-'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;'.svg'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this small class, we can see the various parts of a Python class. The first line is the class definition. The next 3 lines are the class properties (variables that are controlled by objects made from the class). Then we have the &lt;code&gt;__init__&lt;/code&gt; method, which is called the constructor method. This is where we initialize the class. In this case, we'll initialize the suit and value properties for the card. We'll also load the image for the card using Pygame's &lt;a href="https://www.pygame.org/docs/ref/image.html?highlight=load#pygame.image.load"&gt;&lt;code&gt;image.load&lt;/code&gt;&lt;/a&gt; function, with the filepath constructed from the suit and value. This implies that all the card images are all actually named according to the suit and value. &lt;/p&gt;

&lt;p&gt;Now that we have suits and cards, let's create a Deck of cards.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Deck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Suits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Deck class is a collection of cards. It has a list of &lt;code&gt;cards&lt;/code&gt;, and a few methods to manipulate the list. &lt;/p&gt;

&lt;p&gt;The constructor, &lt;code&gt;__init__&lt;/code&gt; initializes the &lt;code&gt;cards&lt;/code&gt; list, and populates it with all the cards in a deck, using 2 &lt;code&gt;for&lt;/code&gt; loops. The first loop iterates over the &lt;code&gt;Suits&lt;/code&gt; enum, and the second loop iterates over the values 1 through 13, which is all the cards in a deck.&lt;/p&gt;

&lt;p&gt;Every deck of cards needs to be shuffled before it is used, so we've defined a &lt;code&gt;shuffle&lt;/code&gt; method. Python's &lt;a href="https://docs.python.org/3/library/random.html"&gt;random&lt;/a&gt; module has a very handy &lt;code&gt;shuffle&lt;/code&gt; function built in that we can use. &lt;code&gt;shuffle&lt;/code&gt; takes a list and rearranges the contents of that list in place (i.e. it doesn't return a new list). &lt;/p&gt;

&lt;p&gt;Then we have the &lt;code&gt;deal&lt;/code&gt; method, which removes the last card from the list and returns it. We'll use this to deal the cards to the players.&lt;/p&gt;

&lt;p&gt;We have a &lt;code&gt;length&lt;/code&gt; method that we'll use to determine if there are any cards left in the deck. This will be useful when dealing out cards to know when to stop. &lt;/p&gt;

&lt;p&gt;The Deck class is now complete.&lt;/p&gt;

&lt;p&gt;Let's move to the &lt;code&gt;Pile&lt;/code&gt; class. This class models the pile of cards face up on the table, that each player adds to when playing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;popAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isSnap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Pile&lt;/code&gt; class has one main property: a list of cards, which is initialized in the constructor. &lt;/p&gt;

&lt;p&gt;The  &lt;code&gt;add&lt;/code&gt; method is used when a player plays a card, i.e. adds it to the pile. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;peek&lt;/code&gt; method returns the top card of the pile, while still keeping it on the pile. We'll use this to draw the top card, as it is face up. If there are no cards on the pile, indicated by the length of the card array being 0, we'll return &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When a player wins, they get all the cards on the pile. The &lt;code&gt;popAll&lt;/code&gt; method handles this by returning the list of cards. Then we can call the &lt;code&gt;clear&lt;/code&gt; method to remove all the cards from the pile.&lt;/p&gt;

&lt;p&gt;If a player calls "Snap!", we need to check if the top two cards are the same value. We'll use the &lt;code&gt;isSnap&lt;/code&gt; method to check this. Python has a handy feature of negative indices. This means an index of &lt;code&gt;-1&lt;/code&gt; returns the last element in the list, &lt;code&gt;-2&lt;/code&gt; returns the second last, and so on. This allows us to easily get the last 2 cards added to the pile, and check if they are the same value. Note that in the rules of Snap, only the value is important - the suit is not used. &lt;/p&gt;

&lt;p&gt;The last of the models is the &lt;code&gt;Player&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;hand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;flipKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;snapKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flipKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snapKey&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flipKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flipKey&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snapKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapKey&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Player&lt;/code&gt; class has a few properties. The &lt;code&gt;hand&lt;/code&gt; property is a list of cards that the player has. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;flipKey&lt;/code&gt; and &lt;code&gt;snapKey&lt;/code&gt; properties are the keys assigned to player that they use to flip and snap cards.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;name&lt;/code&gt; property is the name of the player.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;draw&lt;/code&gt; method is used to draw a card from the &lt;code&gt;deck&lt;/code&gt; passed in the arguments. The &lt;code&gt;draw&lt;/code&gt; method calls the &lt;code&gt;deal&lt;/code&gt; method on the &lt;code&gt;deck&lt;/code&gt;, and adds the card to the &lt;code&gt;hand&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;play&lt;/code&gt; method &lt;a href="https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types"&gt;&lt;code&gt;pop&lt;/code&gt;&lt;/a&gt;s a card off the hand list, used when the player plays a card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the game engine
&lt;/h2&gt;

&lt;p&gt;Ok, time to build the game engine that will coordinate the interactions between the models we created.&lt;/p&gt;

&lt;p&gt;Make a new file called &lt;code&gt;engine.py&lt;/code&gt;. Let's start by adding the libraries we'll need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pygame&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides the &lt;code&gt;Enum&lt;/code&gt; and &lt;code&gt;pygame&lt;/code&gt; libraries, we import everything, &lt;code&gt;*&lt;/code&gt;, from our &lt;code&gt;models.py&lt;/code&gt; file. This way we can use all the classes that we defined in models.&lt;/p&gt;

&lt;p&gt;A common element in game engines is keeping track of the state of the game. We'll define a &lt;code&gt;GameState&lt;/code&gt; enumeration to keep track of the state of the game.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;PLAYING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;SNAPPING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;ENDED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We only have 3 states, or phases, of the game that we will track. The first is &lt;code&gt;PLAYING&lt;/code&gt;, which is the main phase of the game, where the players take turns putting down cards. The second is &lt;code&gt;SNAPPING&lt;/code&gt;. This is the state the game is in when a player calls "Snap!". In this state, we check if the snap is valid, and also wait until the players are ready to resume playing. The third is &lt;code&gt;ENDED&lt;/code&gt;, which is the phase where the game is over, i.e. one player has no more cards to play. &lt;/p&gt;

&lt;p&gt;Now, let's start with the engine itself. First, let's add the definition, properties and constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SnapEngine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;deck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;player1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;player2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;pile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;currentPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Deck&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Player 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;K_q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;K_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Player 2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;K_o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;K_p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PLAYING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the properties, we define the &lt;code&gt;deck&lt;/code&gt; of cards, &lt;code&gt;players&lt;/code&gt;, &lt;code&gt;pile&lt;/code&gt; of cards, and the &lt;code&gt;state&lt;/code&gt; of the game. We also have a &lt;code&gt;currentPlayer&lt;/code&gt; property, which keeps track of the player whose turn it is. The &lt;code&gt;result&lt;/code&gt; property is used to communicate the outcome of a round, or the entire game. &lt;/p&gt;

&lt;p&gt;The constructor initializes objects and stores them in the properties. Note that we assign default names and keys to the players. &lt;/p&gt;

&lt;p&gt;We also call a method we have yet to define, &lt;code&gt;deal&lt;/code&gt;, to deal the cards to the players. &lt;/p&gt;

&lt;p&gt;We start off the game by setting the &lt;code&gt;state&lt;/code&gt; to &lt;code&gt;PLAYING&lt;/code&gt;, and the &lt;code&gt;currentPlayer&lt;/code&gt; to &lt;code&gt;player1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's add some more methods and logic to the engine. To start, the &lt;code&gt;deal&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;half&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deal method is in charge of making sure each player gets half the deck of cards. There are a few ways to do this - this code takes a bit of a literal translation of dealing, by dealing one card to each player alternately. In practice, we'll use a for loop to do this, and in the loop we'll call the &lt;code&gt;draw&lt;/code&gt; method on each of the &lt;code&gt;player&lt;/code&gt;s to draw a card.&lt;/p&gt;

&lt;p&gt;Since each round of the &lt;code&gt;for&lt;/code&gt; loop takes a card for each player, 2 cards per loop, we only need to loop for half the number of cards in the deck.&lt;/p&gt;

&lt;p&gt;To find the middle of the deck, we get the length of the deck, and divide it by 2. Note that we use the &lt;code&gt;//&lt;/code&gt; integer division operator to get back an integer after division, as the meaning of the middle is the index of the middle card. A number with decimals wouldn't make any sense here. &lt;/p&gt;

&lt;p&gt;Next, we can add a helper method to switch the current player. We'll use this after each player plays a card to indicate that it's the next player's turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;switchPlayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentPlayer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we check which player is the current player and switch &lt;code&gt;currentPlayer&lt;/code&gt; to the other player.&lt;/p&gt;

&lt;p&gt;The last helper method we need on the engine is one that handles a player winning a round (by calling "Snap!" correctly, or the other player falsely calling "Snap!"). This method will change the state of the game. It will also add all the cards on the pile to the winner's hand. Then it will clear out the pile so the next round can start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;winRound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SNAPPING&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popAll&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we get to the main logic of the engine. This method will be called from our main game loop, which we will define later. Let's start with the method definition and some basic checks. Then we'll add the logic in sections. Start by adding this method to the engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
      &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENDED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll call this main logic method &lt;code&gt;play&lt;/code&gt;. It takes whatever key is currently pressed, and processes the logic for that. The first thing we check is if a key has actually been pressed. If it hasn't, we return, as there is nothing to update with the game state. &lt;/p&gt;

&lt;p&gt;The next check to make is if the game is over. If it is, we return, as again, there is nothing to do. If you want to improve the game, you could listen for a key press to restart the game.&lt;/p&gt;

&lt;p&gt;Now let's add some of the logic. The first thing is to check if the current player has pressed the key to play, or flip a card onto the pile. If they have pressed their &lt;code&gt;flipKey&lt;/code&gt;, we call their play method and add the returned card to the pile. Then we switch turn to the next player, by calling our &lt;code&gt;switchPlayer&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flipKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;switchPlayer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's check if any of the players have called "Snap!". We'll need to keep track of a few things: who called "Snap!", who didn't call "Snap!", and if there is a valid snap condition on the pile. Add this logic to the &lt;code&gt;play&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;snapCaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;nonSnapCaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;isSnap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSnap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snapKey&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="n"&gt;snapCaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;
      &lt;span class="n"&gt;nonSnapCaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snapKey&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="n"&gt;snapCaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt;
      &lt;span class="n"&gt;nonSnapCaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create two variables, &lt;code&gt;snapCaller&lt;/code&gt; and &lt;code&gt;nonSnapCaller&lt;/code&gt;, which keep track of the player who called "Snap!" and the player who didn't call "Snap!". We also create a variable &lt;code&gt;isSnap&lt;/code&gt; to keep track of whether there is a valid snap condition on the pile. Then we check if either of the players has called "Snap!". If they have, then we set the &lt;code&gt;snapCaller&lt;/code&gt; and &lt;code&gt;nonSnapCaller&lt;/code&gt; variables as applicable.&lt;/p&gt;

&lt;p&gt;We now know if "Snap!" has been called and which player called it. Let's add the logic to see if the player that called "Snap!" wins or loses. Add this logic to the &lt;code&gt;play&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isSnap&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;snapCaller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;winRound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapCaller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"winner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;snapCaller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"isSnap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"snapCaller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;snapCaller&lt;/span&gt; 
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;winRound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapCaller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;isSnap&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;snapCaller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"winner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nonSnapCaller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"isSnap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"snapCaller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;snapCaller&lt;/span&gt; 
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;winRound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonSnapCaller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have two cases: one for a valid snap, and one for an invalid snap. &lt;/p&gt;

&lt;p&gt;If the pile is a valid snap, we call the &lt;code&gt;winRound&lt;/code&gt; method on the player who called "Snap!". Then we set the &lt;code&gt;result&lt;/code&gt; property to a dictionary with the winner, whether it was a valid snap, and the player who called "Snap!". This will be used for information when we make the game user interface (UI). &lt;/p&gt;

&lt;p&gt;Likewise, for an invalid snap, we call the &lt;code&gt;winRound&lt;/code&gt; method on the player who didn't call "Snap!". Then we set the &lt;code&gt;result&lt;/code&gt; property to a dictionary with the winner as the &lt;code&gt;nonSnapCaller&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In both cases, we call the &lt;code&gt;winRound&lt;/code&gt; method with whichever player won the cards. Recall in the &lt;code&gt;winRound&lt;/code&gt; method we assign the pile to the player's hand. Then we clear the pile, and set the &lt;code&gt;gameState&lt;/code&gt; to &lt;code&gt;SNAPPING&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We've got just one last thing to check: if any player has run out of cards. If they have, then it means the other player wins. Add this logic to the &lt;code&gt;play&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"winner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENDED&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"winner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENDED&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one of the players has run out of cards, then we set the &lt;code&gt;result&lt;/code&gt; property to a dictionary with the winner as the other player. Then we set the &lt;code&gt;state&lt;/code&gt; property to &lt;code&gt;GameState.ENDED&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the game loop
&lt;/h2&gt;

&lt;p&gt;We've built the model and the logic in the engine to play a game of Snap. Now we need to define the game loop to run the game. &lt;/p&gt;

&lt;p&gt;Open up the &lt;code&gt;main.py&lt;/code&gt; file again. We'll start by adding references to the models and engine, so that we can access them when building the UI. Add the following imports right under the &lt;code&gt;import pygame&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;engine&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll start by creating a new game engine object. Add this to the &lt;code&gt;main.py&lt;/code&gt; file, underneath the Pygame initialization code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;gameEngine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SnapEngine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the game loop. The game loop's job is to listen for user input, call the engine's &lt;code&gt;play&lt;/code&gt; method to process that input, and update the UI with the result.  Add this to the &lt;code&gt;main.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QUIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEYDOWN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;

  &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;renderGame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SNAPPING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PLAYING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start off by defining a variable &lt;code&gt;run&lt;/code&gt;. Then we use this as a condition for a &lt;code&gt;while&lt;/code&gt; loop. As long as &lt;code&gt;run&lt;/code&gt; is true, the loop will continuously run the code inside.&lt;/p&gt;

&lt;p&gt;In Pygame, we listen for events on the event queue. We use the &lt;code&gt;pygame.event.get()&lt;/code&gt; method to get all the events that have happened since the last time we checked. We then iterate through the events, checking if any of them is a &lt;code&gt;QUIT&lt;/code&gt; event. If they are, we set &lt;code&gt;run&lt;/code&gt; to false, and break out of the loop to end the program. A &lt;code&gt;QUIT&lt;/code&gt; event is sent if the user clicks the close button on the window.&lt;/p&gt;

&lt;p&gt;If any of the events is a &lt;code&gt;KEYDOWN&lt;/code&gt; event, we set the &lt;code&gt;key&lt;/code&gt; variable to the key that was pressed.&lt;/p&gt;

&lt;p&gt;Then we call out to the game engine to process the key that was pressed. &lt;/p&gt;

&lt;p&gt;Once that is done, we can update the UI. We call the &lt;code&gt;renderGame&lt;/code&gt; method, which we'll get to next. &lt;/p&gt;

&lt;p&gt;After the UI has been updated, we call the &lt;code&gt;pygame.display.update()&lt;/code&gt; method to draw it to the screen.&lt;/p&gt;

&lt;p&gt;The final check is to see if the game is in the &lt;code&gt;SNAPPING&lt;/code&gt; state. If it is, we wait 3 seconds before switching back to the &lt;code&gt;PLAYING&lt;/code&gt; state. This is so that there is enough time for the players to pause and see what happened. Remember the game loop will go around very quickly, so the delay will help them to see any messages we display in the &lt;code&gt;renderGame&lt;/code&gt; method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering the game
&lt;/h2&gt;

&lt;p&gt;The last main task is to render the game through a UI. This is where we'll use Pygame to draw the game to the screen.&lt;/p&gt;

&lt;p&gt;In the game loop, we call the &lt;code&gt;renderGame&lt;/code&gt; method. This method takes a &lt;code&gt;window&lt;/code&gt; parameter, which is the Pygame window we created in the &lt;code&gt;main.py&lt;/code&gt; file. The window is the graphics surface that we will draw to. The &lt;code&gt;renderGame&lt;/code&gt; method will look at the state and result of the &lt;code&gt;gameEngine&lt;/code&gt;, as well as the players, and draw the appropriate UI to the window.&lt;/p&gt;

&lt;p&gt;Let's implement that method now. We'll start by clearing the window, and drawing some fixed UI elements. Add the following code to the &lt;code&gt;main.py&lt;/code&gt; file, above the game loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cardBack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'images/BACK.png'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cardBack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cardBack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;238&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;332&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;renderGame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;169&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pygame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SysFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'comicsans'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cardBack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cardBack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" cards"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" cards"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="n"&gt;topCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topCard&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topCard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firstly we load an image of the back of a card outside the function. We do this to avoid loading it each time the screen is rendered - this way it is only loaded once. We will use this image to represent the cards in the players' hands, which are face down. pygame's &lt;code&gt;transform.scale&lt;/code&gt; function is used to scale the image to &lt;code&gt;0.8&lt;/code&gt; the size of the regular card size. This is just to indicate visually that they are in the background to the main part of the UI, which will be the card at top of the pile. &lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;renderGame&lt;/code&gt; function, we first clear the window. We use the &lt;code&gt;fill&lt;/code&gt; method to fill the window with a color, &lt;code&gt;(15,0,169)&lt;/code&gt; in RGB (Red Green Blue) color notation, which is a dark blue color.&lt;/p&gt;

&lt;p&gt;Then we choose a font to use. We use the &lt;code&gt;SysFont&lt;/code&gt; method to load a font from the system. We pass in the font name, the font size, and a boolean value indicating whether the font should be bold. &lt;/p&gt;

&lt;p&gt;Next is to draw the card backs representing the players' hands. We use the &lt;a href="https://www.pygame.org/docs/ref/surface.html?highlight=blit#pygame.Surface.blit"&gt;&lt;code&gt;blit&lt;/code&gt;&lt;/a&gt; method to draw the card back to the window. We pass in the card back image we loaded outside the function, and the screen coordinates we want to draw it at. We draw two card backs, one for each player, one on the far left of the screen, and the other on the far right. We'll draw the pile between these two hands.&lt;/p&gt;

&lt;p&gt;To indicate the current score of the game, we render text showing the number of cards each player currently holds. First, we construct a text object using the font's &lt;a href="https://www.pygame.org/docs/ref/font.html?highlight=font.sysfont#pygame.font.SysFont"&gt;&lt;code&gt;render&lt;/code&gt;&lt;/a&gt; method. We pass in the text we want to render (which is the player's card count and the word 'cards'), and the color we want to use. We then draw the text to the screen by blitting it to the window. &lt;/p&gt;

&lt;p&gt;The last part of the code draws the card at the top of the pile. We use the &lt;code&gt;peek&lt;/code&gt; method we implemented on the &lt;code&gt;Pile&lt;/code&gt; class to get the top card. If there is no top card, we don't draw anything. Recall that for each card, we loaded its image in the constructor. This means we can get the image object from the &lt;code&gt;image&lt;/code&gt; property on the card, without loading it here. Once again we blit the card image to the screen, this time passing in coordinates between the two hands.&lt;/p&gt;

&lt;p&gt;The next part of the UI to draw is some indication of the current state of the game, and whose turn it is. We'll use the &lt;code&gt;state&lt;/code&gt; property of the &lt;code&gt;gameEngine&lt;/code&gt; to determine what to draw. We'll use &lt;code&gt;if&lt;/code&gt; statements to draw the appropriate UI, based on the game state. There are three states to consider: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PLAYING&lt;/strong&gt; The game is in progress, and we are waiting for the current player to flip a card, or either player to call "Snap!". In this case, we render the current player's name and a message to indicate that it is their turn to flip. This message is written in white &lt;code&gt;(255,255,255)&lt;/code&gt; near the top left of the window. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SNAPPING&lt;/strong&gt; A player has called snap. In this case, the message rendered depends on if the snap was valid or not. We can get this information from the &lt;code&gt;result&lt;/code&gt; property of the &lt;code&gt;gameEngine&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ENDING&lt;/strong&gt; The game is over. In this case, we render the  winner's name, and a message to indicate that they won. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add this code to the &lt;code&gt;renderGame&lt;/code&gt; function, to implement the above logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PLAYING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" to flip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SNAPPING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"isSnap"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Winning Snap! by "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"winner"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"False Snap! by "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"snapCaller"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;". "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"winner"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" wins!"&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENDED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gameEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Game Over! "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"winner"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" wins!"&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the game
&lt;/h2&gt;

&lt;p&gt;Now that we have implemented the game, we can give it a test run. Click the big green "Run" button at the top of your repl. You should see the game start up, prompting "Player 1" to flip a card.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mOSvFTBF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/gameplay.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mOSvFTBF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/card-game-pygame/gameplay.gif" alt="Playing SnaPy" width="880" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Player 1's keys are &lt;code&gt;q&lt;/code&gt; to flip a card, and &lt;code&gt;w&lt;/code&gt; to call snap. &lt;br&gt;
Player 2's keys are &lt;code&gt;o&lt;/code&gt; to flip a card, and &lt;code&gt;p&lt;/code&gt; to call snap.&lt;/p&gt;

&lt;p&gt;Call over a friend and see who's the quickest finger on calling "Snap!".&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Congratulations! You've implemented a card game. You can use the ideas and principles from this tutorial to implement a card game of your own.&lt;/p&gt;

&lt;p&gt;Some ideas to add features to this game are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a timer to the game, so that the player has a limited amount of time to flip a card.&lt;/li&gt;
&lt;li&gt;Add some sound effects, using the Pygame &lt;a href="https://www.pygame.org/docs/ref/mixer.html"&gt;&lt;code&gt;mixer&lt;/code&gt;&lt;/a&gt; module.&lt;/li&gt;
&lt;li&gt;Add a key to restart the game after it ends. Currently, at the end of the game, the players would need to close the window and start the game to play again.&lt;/li&gt;
&lt;li&gt;Try with different cards. For example, you could use a simplified card set, perhaps themed on something you love, like Pokemons, dinosaurs, cars, celebrities, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find our repl below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://replit.com/@ritza/SnaPy"&gt;https://replit.com/@ritza/SnaPy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>pygame</category>
      <category>gamedev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Packaging software with Nix</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Fri, 02 Sep 2022 08:48:51 +0000</pubDate>
      <link>https://dev.to/ritza/packaging-software-with-nix-50ph</link>
      <guid>https://dev.to/ritza/packaging-software-with-nix-50ph</guid>
      <description>&lt;p&gt;Replit's repls are powered by &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt;. Nix is a tool for managing software packages and system configurations. The Nix community also maintains the &lt;a href="https://github.com/NixOS/nixpkgs"&gt;Nix Packages Collection&lt;/a&gt; (Nixpkgs), Nix's official package repository. It contains &lt;a href="https://repology.org/repository/nix_stable"&gt;over 50,000 projects&lt;/a&gt;. Chances are high that the software you need is already in there! You can search for packages on the &lt;a href="https://search.nixos.org/packages"&gt;NixOS website&lt;/a&gt; (DuckDuckGo users can use the &lt;a href="https://duckduckgo.com/bang?q=nixpkg"&gt;&lt;code&gt;!nixpkg&lt;/code&gt; bang command&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;However, despite being the largest repository of packaged software (at the &lt;span title="July 2022"&gt;time of writing&lt;/span&gt;), there is software that has not been packaged in the Nix Package Set. If you need something packaged in order to use it in Nix, you can &lt;a href="https://github.com/NixOS/nixpkgs/issues/new?labels=0.kind%3A+packaging+request&amp;amp;template=packaging_request.md"&gt;make a packaging request&lt;/a&gt;, but then you have to wait for a volunteer to package it for you. It's much quicker to learn how to package it yourself!&lt;/p&gt;

&lt;p&gt;Packaging software with Nix is easier than for most other repositories. The Nix language is a DSL specifically designed for packaging software. It's partly why Nixpkgs has the most packages despite having only about a sixth of the maintainers of the repository with the most maintainers, AUR (2182 vs 12292).&lt;/p&gt;

&lt;p&gt;An exhaustive breakdown of all the ins and outs of packaging for Nix is out of the scope of this article, but we'll take a look at the principles of packaging software and show you how to package some simple examples.&lt;/p&gt;

&lt;p&gt;To begin with, let's talk about fetching software. Before we can package anything, we need to get a copy of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching software
&lt;/h2&gt;

&lt;p&gt;Before we can build a package, we need to fetch either the source code, or if the source code is not available, the binary file. Nix has &lt;a href="https://nixos.org/manual/nixpkgs/stable/#chap-pkgs-fetchers"&gt;many different fetchers&lt;/a&gt; we can use to fetch files.&lt;/p&gt;

&lt;p&gt;Nix tries hard to be reproducible, that is, to ensure that whenever you build a package with the same inputs, it gives you the exact same result. When we're fetching things from the network, we need to be sure that what we've received is the thing we expected. That's why Nix fetchers all require you to supply a hash of the expected file. A hash is a short, distinctive representation of some data. It's like a digital fingerprint. If the hash of the file we downloaded matches the hash we expected, we can be sure we got the exact file we expected. Nix supports &lt;code&gt;md5&lt;/code&gt;, &lt;code&gt;sha1&lt;/code&gt;, &lt;code&gt;sha256&lt;/code&gt;, and &lt;code&gt;sha512&lt;/code&gt; hashes, though the first two are deprecated and should no longer be used.&lt;/p&gt;

&lt;p&gt;How do you get the hash of the file? Some projects will list hashes or checksums of their downloads on their website. If they do, use what they give you. If not, you could download it yourself, and then run a program like &lt;code&gt;sha256sum&lt;/code&gt; to get the hash. This works well for simple files, but becomes a bit harder when you're using a fetcher like &lt;code&gt;fetchFromGitHub&lt;/code&gt; to fetch a specific version of a Git repository. An easy workaround is to fill in a nonsensical value for the hash, and then attempt to build the package. Nix will abort the build after fetching because the hash won't match, but it will print the hash of what it fetched to the console. You can copy this value and use it as the correct hash. Re-running the build should now succeed.&lt;/p&gt;

&lt;p&gt;Alternatively, there are also some command-line tools that can prefetch a file for you. They will fetch the file, put it in the Nix store, and print out the hash to the console. The &lt;a href="https://nixos.org/manual/nix/stable/command-ref/nix-prefetch-url.html"&gt;&lt;code&gt;nix-prefetch-url&lt;/code&gt;&lt;/a&gt; command comes installed in a standard Nix environment, including Replit repls. Both &lt;code&gt;nix-prefetch-git&lt;/code&gt; and &lt;code&gt;nix-prefetch-github&lt;/code&gt; are in Nixpkgs (so they can be installed easily using &lt;code&gt;replit.nix&lt;/code&gt;) and work similarly. For more advanced uses, consider using &lt;a href="https://github.com/msteen/nix-prefetch"&gt;&lt;code&gt;nix-prefetch&lt;/code&gt;&lt;/a&gt;, also in Nixpkgs, which can prefetch using any Nix fetcher.&lt;/p&gt;

&lt;p&gt;When updating the version of something that's fetched, be sure to change the hash, too. Internally, Nix uses the hashes to identify things, and if it hasn't changed and Nix has already got a copy downloaded from before, Nix may silently use that instead of actually attempting to fetch the file again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetch files and archives with &lt;code&gt;fetchurl&lt;/code&gt; and &lt;code&gt;fetchzip&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The simplest fetcher is &lt;code&gt;fetchurl&lt;/code&gt;. It will fetch a file and put it in the Nix store. The &lt;code&gt;fetchzip&lt;/code&gt; fetcher is very similar, but it will unpack the archive before storing it in the Nix store. This is useful if you need to refer to specific files from the archive, rather than the download as a whole. Despite its name, it can handle many different archive formats, not just zip files. In general, unless you need to refer to a specific file in an archive in a Nix expression, use &lt;code&gt;fetchurl&lt;/code&gt;, which will not unpack the archive before putting it in the Nix store. This will save space, and, as we will cover in more detail later, archives are automatically unpacked during the build process in any case.&lt;/p&gt;

&lt;p&gt;Sign in to &lt;a href="https://replit.com"&gt;Replit&lt;/a&gt; or &lt;a href="https://replit.com/signup"&gt;create an account&lt;/a&gt; if you haven't already. Once logged in, create a new &lt;a href="https://replit.com/@replit/Blank-Repl"&gt;Blank Repl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NqD96IkB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/create-repl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NqD96IkB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/create-repl.png" alt="Create repl" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Find the &lt;code&gt;replit.nix&lt;/code&gt; file in your repl. If you don't see it in the Files sidebar, select "Show hidden files" from the menu. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FfreGFRK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/show-hidden.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FfreGFRK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/show-hidden.png" alt="show-hidden" width="560" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;replit.nix&lt;/code&gt; to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt;
  &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2.12"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fetchurl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mirror://gnu/hello/hello-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1111111111111111111111111111111111111111111111111111"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;hello&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've made this change to &lt;code&gt;replit.nix&lt;/code&gt;, open the Console tab, and press enter. The output should be similar to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6viasSxQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/wrong-hash.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6viasSxQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/wrong-hash.png" alt="Console output using a wrong hash" width="880" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nix is telling us what the hash of the file it fetched actually is, in this case: &lt;code&gt;1ayhp9v4m4rdhjmnl2bq3cibrbqqkgjbl3s7yk2nhlh8vj3ay16g&lt;/code&gt;. Your output might be different, that just means that you're using an updated version of Nixpkgs, so &lt;code&gt;stdenv&lt;/code&gt; has been updated; since that's a dependency of the derivation that &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; produces and since derivation hashes depend on their input derivation hashes, your derivation's hash will have changed, too.&lt;/p&gt;

&lt;p&gt;Now that we have the real hash, copy it to the &lt;code&gt;sha256&lt;/code&gt; attribute in the derivation, press enter in the console, which will then pick up on the change, and Nix will successfully build the &lt;code&gt;hello&lt;/code&gt; package — though it won't output anything when it succeeds. The &lt;code&gt;hello&lt;/code&gt; command will be added to your environment. If you run it, the &lt;code&gt;hello&lt;/code&gt; program just echoes "Hello, world!" to the console. At first glance, that doesn't seem like a very useful program, but &lt;a href="https://www.gnu.org/software/hello/manual/html_node/Overview.html"&gt;its purpose&lt;/a&gt; is actually to help test packaging systems. Now we know ours is working!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TNjMvIAX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/hello-command.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TNjMvIAX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/hello-command.png" alt="Successfully installed hello" width="880" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have been paying particularly close attention, you may have spotted that Nix is telling us that two derivations will be built, even though we're only asking it to build one. This is because each fetcher actually builds a derivation: the fetched file that's added to the Nix store. Nix packages can only depend on things that are in the Nix Store, so in order to build &lt;code&gt;hello&lt;/code&gt;, we need its source code to be put in the Nix Store, too. Since we don't have it yet, it needs to be "built" first (fetched, in this case). A fetcher is actually a special type of builder.&lt;/p&gt;

&lt;p&gt;We could also have gotten the same hash with &lt;code&gt;nix-prefetch-url&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix-prefetch-url https://ftpmirror.gnu.org/hello/hello-2.12.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Zh8Ec9A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/prefetch-command.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Zh8Ec9A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/nix-packaging/prefetch-command.png" alt="Prefetch command" width="880" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can copy the string &lt;code&gt;1ayhp9v4m4rdhjmnl2bq3cibrbqqkgjbl3s7yk2nhlh8vj3ay16g&lt;/code&gt; to the &lt;code&gt;sha256&lt;/code&gt; field in the derivation and it will also work (again, your actual hash might be different). Sometimes the hashes are in a different format, but that's not a problem. Nix fetchers understand different types of hashes presented in a variety of formats.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some notes on hashes
&lt;/h3&gt;

&lt;p&gt;In our first example, we initially provided a hash that we knew was incorrect in order to have Nix output the correct hash in the hash mismatch error message. Trusting the hash that Nix gives back to us is an approach called &lt;a href="https://en.wikipedia.org/wiki/Trust_on_first_use"&gt;trust on first use&lt;/a&gt;. We're assuming that the file Nix actually fetched is correct. For many purposes, this is fine.&lt;/p&gt;

&lt;p&gt;If you have a real hash, not a dummy hash, in your derivation and you still get this error, carefully try to find out what might have happened, as this means that Nix is telling you that the file it actually fetched is not what you expected. It could be that the download was corrupted due to network problems, in which case re-attempting it later might succeed; or that the target file has actually been changed on the server. This might happen if you're trying to download from a URL that does not provide a stable version, or if an attacker has surreptitiously replaced the file. If you're unsure, try to determine with a trusted party, such as the publishers of the code, whether everything is OK.&lt;/p&gt;

&lt;p&gt;You may notice that the hash Nix calculates for a file doesn't match the hash that &lt;code&gt;sha256sum&lt;/code&gt; calculates. They are actually the same hash, but they're encoded differently. Most hash tools print out the hash in a hexadecimal (base 16) format, but Nix prefers its own non-standard base 32 format, because it's shorter (it's base 32, but it doesn't use the characters &lt;code&gt;e&lt;/code&gt;, &lt;code&gt;o&lt;/code&gt;, &lt;code&gt;u&lt;/code&gt;, and &lt;code&gt;t&lt;/code&gt; to reduce the chances of offensive letter sequences). To convert a base 16 hash to a base 32 hash or vice versa, use &lt;code&gt;nix-hash&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix-hash &lt;span class="nt"&gt;--to-base32&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; sha256 cf04af86dc085268c5f4470fbae49b18afbc221b78096aab842d934a76bad0ab
1ayhp9v4m4rdhjmnl2bq3cibrbqqkgjbl3s7yk2nhlh8vj3ay16g
&lt;span class="nv"&gt;$ &lt;/span&gt;nix-hash &lt;span class="nt"&gt;--to-base16&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; sha256 1ayhp9v4m4rdhjmnl2bq3cibrbqqkgjbl3s7yk2nhlh8vj3ay16g
cf04af86dc085268c5f4470fbae49b18afbc221b78096aab842d934a76bad0ab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need to encode hashes in Nix's custom base 32 format before using them. The normal base 16 format works just fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetch Git repositories with &lt;code&gt;fetchgit&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If the source code you need is in a Git repository, you can use &lt;a href="https://nixos.org/manual/nixpkgs/stable/#fetchgit"&gt;&lt;code&gt;fetchgit&lt;/code&gt;&lt;/a&gt;. It expects at least one extra attribute compared to &lt;code&gt;fetchurl&lt;/code&gt;, the attribute &lt;code&gt;rev&lt;/code&gt;. This can be the full Git commit ID (the &lt;code&gt;sha1&lt;/code&gt; hash) or a tag name, like &lt;code&gt;v2.12&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Try the following &lt;code&gt;replit.nix&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt;
  &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2.12"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fetchgit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/ritza-co/simple-hello-world-demo.git"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1111111111111111111111111111111111111111111111111111"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;hello&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, after changing &lt;code&gt;replit.nix&lt;/code&gt;, open the Console tab, and press enter. The output should be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Detected change in environment, reloading shell...
nix error: building nix env: exit status 102
Output has been trimmed to the last 20 lines
executing builder '/nix/store/bm7jr70d9ghn5cczb3q0w90apsm05p54-bash-5.1-p8/bin/bash'
building '/nix/store/6drp4pmqj67b3ysy88fhawz4fdf2kwkb-simple-hello-world-demo.drv'...
exporting https://github.com/ritza-co/simple-hello-world-demo.git (rev v2.12) into /nix/store/hmmp6c24wg43d9cbslmhgrrccvhhaxac-simple-hello-world-demo
Initialized empty Git repository in /nix/store/hmmp6c24wg43d9cbslmhgrrccvhhaxac-simple-hello-world-demo/.git/
remote: Enumerating objects: 462, done.
remote: Counting objects: 100% (462/462), done.
remote: Compressing objects: 100% (333/333), done.
remote: Total 462 (delta 127), reused 462 (delta 127), pack-reused 0
Receiving objects: 100% (462/462), 1.09 MiB | 7.15 MiB/s, done.
Resolving deltas: 100% (127/127), done.
From https://github.com/ritza-co/simple-hello-world-demo
 * tag               v2.12      -&amp;gt; FETCH_HEAD
Switched to a new branch 'fetchgit'
removing `.git'...
hash mismatch in fixed-output derivation '/nix/store/bj8zf0n8xdfrkj5mndmlhg34ac3pd91l-simple-hello-world-demo':
  wanted: sha256:1111111111111111111111111111111111111111111111111111
  got:    sha256:1mc1vrixpkzkdnvpzn3b01awvha6z7k2dnpai3c6g89in8l1wr70
cannot build derivation '/nix/store/gw50rkh9d2m4hxiyyldcz6wk0hf76pkx-hello.drv': 1 dependencies couldn't be built
error: build of '/nix/store/gw50rkh9d2m4hxiyyldcz6wk0hf76pkx-hello.drv' failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replacing the &lt;code&gt;sha256&lt;/code&gt; in the derivation with &lt;code&gt;1mc1vrixpkzkdnvpzn3b01awvha6z7k2dnpai3c6g89in8l1wr70&lt;/code&gt; will allow the build to succeed (your hash might be different, check the &lt;code&gt;got: sha256:&lt;/code&gt; line).&lt;/p&gt;

&lt;p&gt;There are also fetchers for other version control repositories, such as &lt;a href="https://nixos.org/manual/nixpkgs/stable/#fetchsvn"&gt;&lt;code&gt;fetchsvn&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://nixos.org/manual/nixpkgs/stable/#fetchhg"&gt;&lt;code&gt;fetchhg&lt;/code&gt;&lt;/a&gt;, or &lt;a href="https://nixos.org/manual/nixpkgs/stable/#fetchcvs"&gt;&lt;code&gt;fetchcvs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetch code from GitHub with &lt;code&gt;fetchFromGitHub&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;fetchFromGitHub&lt;/code&gt; fetcher doesn't take a &lt;code&gt;url&lt;/code&gt; attribute, instead replacing it with &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;repo&lt;/code&gt; attributes.&lt;/p&gt;

&lt;p&gt;Try the following &lt;code&gt;replit.nix&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt;
  &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2.12"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ritza-co"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"simple-hello-world-demo"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1111111111111111111111111111111111111111111111111111"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;hello&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, if you switch to the Console tab and pressing enter, the build will fail because we haven't provided the correct hash. Nix will output the correct hash. Use this to get the build to succeed.&lt;/p&gt;

&lt;p&gt;There are also fetchers for other Git forges, such as &lt;a href="https://nixos.org/manual/nixpkgs/stable/#fetchfromgitlab"&gt;&lt;code&gt;fetchFromGitLab&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://nixos.org/manual/nixpkgs/stable/#fetchfrombitbucket"&gt;&lt;code&gt;fetchFromBitbucket&lt;/code&gt;&lt;/a&gt;, or &lt;a href="https://nixos.org/manual/nixpkgs/stable/#fetchfromsavannah"&gt;&lt;code&gt;fetchFromSavannah&lt;/code&gt;&lt;/a&gt;. Most work just like &lt;code&gt;fetchFromGitHub&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a package
&lt;/h2&gt;

&lt;p&gt;Now that we know how to fetch source code, we need to know how to build it into actual software. Our little &lt;code&gt;hello&lt;/code&gt; program is built in a stock-standard way, and the default options used by &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; are perfect for it. Most software is not quite so simple, and will require us to tweak some of these options.&lt;/p&gt;

&lt;p&gt;For some common types of software, Nix has &lt;a href="https://nixos.org/manual/nixpkgs/stable/#chap-language-support"&gt;dedicated builders&lt;/a&gt; that make those types of software easier to build. Some examples are Python programs or C# programs.&lt;/p&gt;

&lt;p&gt;The "standard" builder is &lt;code&gt;stdenv.mkDerivation&lt;/code&gt;. The dedicated builders are usually wrappers around &lt;code&gt;stdenv.mkDerivation&lt;/code&gt;, so they inherit much from it. If the software you need to build is written in C or you can't find a dedicated builder for it, use &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; to build it.&lt;/p&gt;

&lt;p&gt;Even if there is a dedicated builder for your software, it's probably a good idea to at least read this next section on &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; so you understand the fundamentals of building a package in Nix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building with &lt;code&gt;stdenv.mkDerivation&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;So far, we've not really explained what a derivation means in Nix. A derivation is an attribute set that tells Nix what it needs to know to build a package.&lt;/p&gt;

&lt;p&gt;It contains information about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What other derivations, if any, a derivation depends on;&lt;/li&gt;
&lt;li&gt;What build script to use;&lt;/li&gt;
&lt;li&gt;What platform to build for;&lt;/li&gt;
&lt;li&gt;What arguments and environment variables should be available to the builder; and&lt;/li&gt;
&lt;li&gt;Where Nix should put the results of the build.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the final part of evaluating a Nix expression that produces a derivation, Nix will save that derivation attribute set to disk in the Nix store as a &lt;code&gt;.drv&lt;/code&gt; file. It represents the build action. Later, in the build phase, Nix will use this &lt;code&gt;.drv&lt;/code&gt; file to build the package it describes. If you want to see what one looks like, run the following command in the Console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix show-derivation &lt;span class="si"&gt;$(&lt;/span&gt;which hello&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will show you &lt;code&gt;hello&lt;/code&gt;'s derivation file, which was used by Nix to build it.&lt;/p&gt;

&lt;p&gt;Before Nix builds a derivation, it will ensure that all the input derivation paths are valid. That is, that those derivations have been built and their outputs exist in the Nix store. If not, it will fetch them from the binary cache, or build those first.&lt;/p&gt;

&lt;p&gt;Nix actually has a built-in function called &lt;code&gt;derivation&lt;/code&gt; that produces derivation attribute sets, but we usually use &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; instead, which is easier to use. The &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; builder is not built into the Nix language itself. It's part of Nixpkgs, and evolved over time as the community built more and more packages, incorporating their learnings as useful abstractions.&lt;/p&gt;

&lt;p&gt;The minimum attributes that &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; needs are &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;src&lt;/code&gt;. If &lt;code&gt;name&lt;/code&gt; is not provided, it will attempt to construct it from &lt;code&gt;pname&lt;/code&gt; + &lt;code&gt;version&lt;/code&gt;: &lt;code&gt;"${pname}-${version}"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; builder automates common build tasks and provides common Unix tools: &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;coreutils&lt;/code&gt;/&lt;code&gt;findutils&lt;/code&gt;/&lt;code&gt;diffutils&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;/&lt;code&gt;grep&lt;/code&gt;/&lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;tar&lt;/code&gt;/&lt;code&gt;gzip&lt;/code&gt;/&lt;code&gt;bzip2&lt;/code&gt;/&lt;code&gt;xz&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;, and &lt;code&gt;patch&lt;/code&gt;. If your package uses the Unix standard &lt;code&gt;./configure; make; make install&lt;/code&gt; build procedure (like our earlier &lt;code&gt;hello&lt;/code&gt; example), you don't even need to tweak the standard build at all. If &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; doesn't work automatically, you can easily customize or override the various build phases.&lt;/p&gt;

&lt;p&gt;Let's take a look at a more complex derivation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt;
  &lt;span class="nv"&gt;mle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mle"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.5.0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"adsr"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mle"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1nhd00lsx9v12zdmps92magz76c2d8zzln3lxvzl4ng73gbvq3n0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c"&gt;# Bug fixes found after v1.5.0 release&lt;/span&gt;
    &lt;span class="nv"&gt;patches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fetchpatch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"skip_locale_dep_test.patch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/adsr/mle/commit/e4dc4314b02a324701d9ae9873461d34cce041e5.patch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sha256-j3Z/n+2LqB9vEkWzvRVSOrF6yE+hk6f0dvEsTQ74erw="&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fetchpatch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fix_input_trail.patch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/adsr/mle/commit/bc05ec0eee4143d824010c6688fce526550ed508.patch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sha256-dM63EBDQfHLAqGZk3C5NtNAv23nCTxXVW8XpLkAeEyQ="&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c"&gt;# Fix location of Lua 5.4 header and library&lt;/span&gt;
    &lt;span class="nv"&gt;postPatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      substituteInPlace Makefile --replace "-llua5.4" "-llua";&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      substituteInPlace mle.h    --replace "&amp;lt;lua5.4/" "&amp;lt;";&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      patchShebangs tests/*&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# Use select(2) instead of poll(2) (poll is returning POLLINVAL on macOS)&lt;/span&gt;
    &lt;span class="c"&gt;# Enable compiler optimization&lt;/span&gt;
    &lt;span class="nv"&gt;CFLAGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-DTB_OPT_SELECT -O2"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;makeWrapper&lt;/span&gt; &lt;span class="nv"&gt;installShellFiles&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;pcre&lt;/span&gt; &lt;span class="nv"&gt;uthash&lt;/span&gt; &lt;span class="nv"&gt;lua5_4&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nv"&gt;doCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;installFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"prefix=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;placeholder&lt;/span&gt; &lt;span class="s2"&gt;"out"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nv"&gt;postInstall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      installManPage mle.1&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;mle&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was adapted from &lt;a href="https://github.com/adsr/mle"&gt;mle&lt;/a&gt;'s &lt;a href="https://github.com/NixOS/nixpkgs/blob/c5edc1d38aaa960a04ad2c675af102debde12462/pkgs/applications/editors/mle/default.nix"&gt;derivation in Nixpgks&lt;/a&gt;. Let's break down some of the new concepts in here.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dependencies
&lt;/h4&gt;

&lt;p&gt;If your software depends on other packages that are not part of the standard environment, you can provide them using the &lt;code&gt;buildInputs&lt;/code&gt; attribute as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"foo-1.2.3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;libbar&lt;/span&gt; &lt;span class="nv"&gt;perl&lt;/span&gt; &lt;span class="nv"&gt;ncurses&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may also see &lt;code&gt;nativeBuildInputs&lt;/code&gt;, which you can think of as being for build-time dependencies, whereas &lt;code&gt;buildInputs&lt;/code&gt; is for run-time dependencies. That is an oversimplification, but it's a good approximation.&lt;/p&gt;

&lt;p&gt;If you're unsure where to put a dependency, use &lt;code&gt;buildInputs&lt;/code&gt;. Even if you accidentally put a build-time dependency there, the derivation should still build. It's not ideal, but if you're just trying to get some software into your repl, it'll do just fine.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://nixos.org/manual/nixpkgs/stable/#variables-specifying-dependencies"&gt;even more ways to specify dependencies&lt;/a&gt;, but unless you're cross-compiling they're unlikely to matter to you.&lt;/p&gt;

&lt;h4&gt;
  
  
  Patches
&lt;/h4&gt;

&lt;p&gt;One of the great things about Nix is that since it's source built, it has first-class support for applying patches to software.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"foo-1.2.3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="nv"&gt;patches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fetchpatch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://example.com/patches/001_arches_align.patch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0i3qclm2mh98c04rqpx1r4qagd3wpxlkj7lvq0ddpkmr8bm0fh0m"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fetchpatch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://example.com/patches/002_no_remove_static_const.patch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0zfjqmjsj0y1kfzxbp29v6nxq5qwgazhb9clqc544sm5zn0bdp8n"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fetchpatch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://example.com/patches/003_64_bit_clean.patch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0mda9fkaqf2s1xl6vlbkbq20362h3is9dpml9kfmacpbifl4dx3n"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Patches are applied in the order that they are listed. They must be in the format accepted by the &lt;code&gt;patch&lt;/code&gt; command, and may optionally be compressed using &lt;code&gt;gzip&lt;/code&gt;, &lt;code&gt;bzip2&lt;/code&gt;, or &lt;code&gt;xz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They can be local files, or you can fetch them. Prefer using &lt;code&gt;fetchpatch&lt;/code&gt; rather than &lt;code&gt;fetchurl&lt;/code&gt; to fetch patches. It works similarly, but it performs normalization on the patches before computing the hash, such as removing comments and unstable parts sometimes added by version control systems that change over time (and which would otherwise cause the hash to change).&lt;/p&gt;

&lt;h4&gt;
  
  
  Environment variables
&lt;/h4&gt;

&lt;p&gt;Any attributes that you set on &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; will also be made available in the build environment as environment variables. Be aware that they'll be set in all phases if you set them this way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build phases
&lt;/h3&gt;

&lt;p&gt;The standard builder script has several phases, which are defined as bash functions. You can influence the behaviour of each phase by setting specific attributes on &lt;code&gt;stdenv.mkDerivation&lt;/code&gt;, some of which are detailed below. The most common phases are as follows, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://nixos.org/manual/nixpkgs/stable/#ssec-unpack-phase"&gt;&lt;code&gt;unpackPhase&lt;/code&gt;&lt;/a&gt;: This should unpack the source into the current directory. The default implementation simply unpacks the derivation's &lt;code&gt;src&lt;/code&gt; files.

&lt;ul&gt;
&lt;li&gt;By default it supports plain tar archives, or those compressed by gzip (&lt;code&gt;*.tar.gz&lt;/code&gt;, &lt;code&gt;*.tgz&lt;/code&gt; or &lt;code&gt;*.tar.Z&lt;/code&gt;), bzip2 (&lt;code&gt;*.tar.bz2&lt;/code&gt;, &lt;code&gt;*.tbz2&lt;/code&gt; or &lt;code&gt;*.tbz&lt;/code&gt;) or xz (&lt;code&gt;*.tar.xz&lt;/code&gt;, &lt;code&gt;*.tar.lzma&lt;/code&gt; or &lt;code&gt;*.txz&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;It can also automatically unpack zip files. Zip files are unpacked using the &lt;code&gt;unzip&lt;/code&gt; package, which is not in the standard environment. Add it to &lt;code&gt;nativeBuildInputs&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;dontUnpack&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; will skip this phase.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nixos.org/manual/nixpkgs/stable/#ssec-patch-phase"&gt;&lt;code&gt;patchPhase&lt;/code&gt;&lt;/a&gt;: The patch phase applies any supplied &lt;code&gt;patches&lt;/code&gt; to the unpacked source code.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;patchFlags&lt;/code&gt; sets flags to be passed to &lt;code&gt;patch&lt;/code&gt;. If not set, the argument &lt;code&gt;-p1&lt;/code&gt; is used, which causes the leading directory component to be stripped from the file names in each patch.
&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;dontPatch&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; skips this phase, but if &lt;code&gt;patches&lt;/code&gt; is not set it's also skipped.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nixos.org/manual/nixpkgs/stable/#ssec-configure-phase"&gt;&lt;code&gt;configurePhase&lt;/code&gt;&lt;/a&gt;: Prepares the source tree for building. By default, it runs &lt;code&gt;./configure&lt;/code&gt; if it exists.

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;configureScript&lt;/code&gt; to change the configure script. Defaults to &lt;code&gt;./configure&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;configureFlags&lt;/code&gt; to pass additional arguments to the configure script.
&lt;/li&gt;
&lt;li&gt;By default, &lt;code&gt;--prefix=$prefix&lt;/code&gt; is added to the configure flags. Set &lt;code&gt;dontAddPrefix&lt;/code&gt; to disable this.
&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;prefix&lt;/code&gt; to set the &lt;code&gt;$prefix&lt;/code&gt; variable above (remember how attributes defined here are also exposed as environment variables). By default this is set to &lt;code&gt;$out&lt;/code&gt;, which is the output directory that Nix creates for your package in the Nix store.
&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;dontConfigure&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; will skip this phase.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nixos.org/manual/nixpkgs/stable/#build-phase"&gt;&lt;code&gt;buildPhase&lt;/code&gt;&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;makeFile&lt;/code&gt; to change the name of the MakeFile.
&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;makeFlags&lt;/code&gt; to pass additional flags to &lt;code&gt;make&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;buildFlags&lt;/code&gt; to pass additional flags to &lt;code&gt;make&lt;/code&gt;, but only called during the build phase.
&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;dontBuild&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; will skip this phase.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nixos.org/manual/nixpkgs/stable/#ssec-install-phase"&gt;&lt;code&gt;installPhase&lt;/code&gt;&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;installFlags&lt;/code&gt; to pass additional flags to &lt;code&gt;make&lt;/code&gt;, that will only be called during the install phase.
&lt;/li&gt;
&lt;li&gt;Remember that &lt;code&gt;makeFlags&lt;/code&gt; will also affect this phase.
&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;dontInstall&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; will skip this phase.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can override any of these phases by setting an attribute with the phase's name. It will be executed as a bash script in the standard environment.&lt;/p&gt;

&lt;p&gt;You can also adjust the build by running code before and after each phase. Set at attribute with either &lt;code&gt;pre&lt;/code&gt; or &lt;code&gt;post&lt;/code&gt; and the phase's name (capitalized), e.g. &lt;code&gt;preConfigure&lt;/code&gt; or &lt;code&gt;postBuild&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can add yet more phases before or after each of these phases, or even reorder them completely. Read the &lt;a href="https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases"&gt;documentation for build phases&lt;/a&gt; for details on how to do that.&lt;/p&gt;

&lt;p&gt;Here we've covered the basics. Read &lt;a href="https://nixos.org/manual/nixpkgs/stable/#chap-stdenv"&gt;&lt;code&gt;stdenv.mkDerivation&lt;/code&gt;'s documentation&lt;/a&gt; for more. Remember that this is the basis for the largest software repository, and as such there's a lot it is capable of.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shell functions and utilities
&lt;/h3&gt;

&lt;p&gt;The standard environment also has a few &lt;a href="https://nixos.org/manual/nixpkgs/stable/#ssec-stdenv-functions"&gt;shell functions and utilities&lt;/a&gt; that may be helpful if you're making adjustments to the phases above, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nixos.org/manual/nixpkgs/stable/#fun-substituteInPlace"&gt;&lt;code&gt;substituteInPlace&lt;/code&gt;&lt;/a&gt;, which performs substitutions in files, especially handy to replace references in scripts to executables in nix derivations to get them to work correctly:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  substituteInPlace ./foo.sh \
    --replace /usr/bin/bar $bar/bin/bar \
    --replace /usr/bin/baz $bar/bin/baz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dedicated builders
&lt;/h3&gt;

&lt;p&gt;As we touched on before, &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; is not the only builder. Many frameworks, programming languages, or build systems have dedicated builders that provide conveniences and abstractions for their idiosyncrasies. Be sure to have a look at &lt;a href="https://nixos.org/manual/nixpkgs/stable/#chap-language-support"&gt;builders for specific languages and frameworks&lt;/a&gt; in the Nixpkgs manual.&lt;/p&gt;

&lt;p&gt;It is not necessarily an exhaustive list. Even if you don't see a dedicated builder for your use case listed in the manual, there may still be one. The best way to find out is to have a look at &lt;a href="https://github.com/NixOS/nixpkgs"&gt;source code in Nixpkgs&lt;/a&gt;, which is hosted on GitHub.&lt;/p&gt;

&lt;p&gt;Look for terms, tools, and commands that are specific to your ecosystem. You can use GitHub's web search, or make a local clone of the repo to &lt;code&gt;grep&lt;/code&gt; (consider making a shallow clone using git's &lt;code&gt;--depth=1&lt;/code&gt; flag, as it's quite a big repository with many branches and a long history).&lt;/p&gt;

&lt;h3&gt;
  
  
  Package setup hooks
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nixos.org/manual/nixpkgs/stable/#ssec-setup-hooks"&gt;Hooks&lt;/a&gt; are scripts that are run during the build if a dependency uses them. If a dependency of your derivation uses a hook, that hook will be run during the build of your derivation.&lt;/p&gt;

&lt;p&gt;You should not use hooks directly, but be aware that depending on packages that use hooks may change the way your build works. Even &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; includes some hooks, for example, to place docs in the right place, to remove debug symbols, or to compress man pages.&lt;/p&gt;

&lt;p&gt;There are hooks for specific technologies, for example, the &lt;code&gt;cmake&lt;/code&gt; derivation uses the &lt;a href="https://nixos.org/manual/nixpkgs/stable/#cmake"&gt;&lt;code&gt;cmake&lt;/code&gt; hook&lt;/a&gt;, which makes some changes to &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; for Cmake projects. Thus, including &lt;code&gt;cmake&lt;/code&gt; (the derivation) in the &lt;code&gt;nativeBuildInputs&lt;/code&gt; of your derivation will change the way it works. You can use the &lt;code&gt;cmakeFlags&lt;/code&gt; attribute in this situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;For more information, be sure to take a look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nix Pills &lt;a href="https://nixos.org/guides/nix-pills/our-first-derivation.html"&gt;6: Our First Derivation&lt;/a&gt;, &lt;a href="https://nixos.org/guides/nix-pills/working-derivation.html"&gt;7: Working Derivation&lt;/a&gt;, and &lt;a href="https://nixos.org/guides/nix-pills/generic-builders.html"&gt;8: Generic Builders&lt;/a&gt; for a gradual build up to &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; from first principles.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://nixos.org/manual/nixpkgs/stable/"&gt;Nixpkgs manual&lt;/a&gt;, the authoritative reference, which includes further information, such as advanced features we haven't gotten into here. Of particular interest might be the section on &lt;a href="https://nixos.org/manual/nixpkgs/stable/#chap-language-support"&gt;trivial builders&lt;/a&gt; for creating things like text files or shell scripts.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nixos.wiki/wiki/Packaging/Binaries"&gt;Packaging/Binaries on the NixOS Wiki&lt;/a&gt;. You may need to use a pre-compiled binary file. This is most common with proprietary software.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>replit</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Learning HTML and CSS by building a 90s-inspired website</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 25 Aug 2022 09:47:00 +0000</pubDate>
      <link>https://dev.to/ritza/learning-html-and-css-by-building-a-90s-inspired-website-87a</link>
      <guid>https://dev.to/ritza/learning-html-and-css-by-building-a-90s-inspired-website-87a</guid>
      <description>&lt;p&gt;In this tutorial, we will make a 90s-inspired website using HTML and a little CSS.&lt;/p&gt;

&lt;p&gt;Nineties web design is pretty iconic. With backgrounds that can make your eyes water, animated GIFs and cursors, and an overload of text, graphics, and ads, there is only one word to describe 90s web design - garish!&lt;/p&gt;

&lt;p&gt;Yet for those who witnessed the birth of the world wide web, 90s web design brings back fond memories.&lt;/p&gt;

&lt;p&gt;As most of the websites built in the 90s didn't require complicated CSS and JavaScript, building a 90s-inspired website can be a fun first project to learn web development.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML and CSS
&lt;/h2&gt;

&lt;p&gt;Every website you see on the web has two main building blocks: &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics" rel="noopener noreferrer"&gt;HTML&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics" rel="noopener noreferrer"&gt;CSS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hyper Text Markup Language (HTML) is not a programming language. It is, as the name suggests, a markup language. To understand what a markup language is, we need to travel back in history.&lt;/p&gt;

&lt;p&gt;Imagine you are an editor in the olden days and you receive a manuscript to revise. You want to write a bunch of revision instructions for the author on the manuscript. Or you may want to write instructions for the copy editor (write certain lines in a bigger font, make certain words bold, and so on).&lt;/p&gt;

&lt;p&gt;To keep your instructions distinct from the content of the manuscript itself, you grab a &lt;a href="https://en.wikipedia.org/wiki/Blue_pencil_(editing)" rel="noopener noreferrer"&gt;red or blue pencil&lt;/a&gt; and write your instructions. In other words, you "mark up" the manuscript.&lt;/p&gt;

&lt;p&gt;Fast forward to the electronic age. You are a website developer. You want your website to look a certain way. You need to "mark up" the content for the browser, which is responsible for displaying the content on the screen.&lt;/p&gt;

&lt;p&gt;Enter HTML!&lt;/p&gt;

&lt;p&gt;Rather than marking up our content with red and blue pencils, we will break our content into elements and wrap each element inside an opening and closing HTML tag.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;HTML is a &lt;em&gt;markup language&lt;/em&gt; that defines the structure of your content. HTML consists of a series of &lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Element" rel="noopener noreferrer"&gt;elements&lt;/a&gt;&lt;/strong&gt;, which you use to enclose, or wrap, different parts of the content to make it appear a certain way, or act a certain way. The enclosing &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Tag" rel="noopener noreferrer"&gt;tags&lt;/a&gt; can make a word or image hyperlink to somewhere else, can italicize words, can make the font bigger or smaller, and so on. ~ &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HTML tags are written using angle brackets, like these: &lt;code&gt;&amp;lt; &amp;gt;&lt;/code&gt;. A closing tag also has a forward slash: &lt;code&gt;&amp;lt;/ &amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's say we have some text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I love 90s design
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we want to instruct the browser to display this text as a heading. We can simply wrap the text between the opening and closing tags, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;I love 90s design&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fhtml-tags.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fhtml-tags.png" alt="Anatomy of an HTML tag"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are tons of HTML elements. But don't worry! You don't have to memorize everything. Once you understand how elements and tags work, you can always refer to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; and find relevant elements and their usage.&lt;/p&gt;

&lt;p&gt;HTML is the skeleton of our website. But CSS or Cascading Style Sheets is what adds interest to a website. CSS is used to style and alter the layout of HTML elements. We use CSS to specify fonts, change colors and size, add animations, and control other decorative features of our website.&lt;/p&gt;

&lt;p&gt;We will learn more about HTML and CSS as we make our website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;In this tutorial we are going to use &lt;a href="https://replit.com/" rel="noopener noreferrer"&gt;Replit&lt;/a&gt; to make our website. The cool thing about Replit is that you don't need to install anything. All you need is a computer with a browser and internet connection.&lt;/p&gt;

&lt;p&gt;Create an account on &lt;a href="https://replit.com/" rel="noopener noreferrer"&gt;Replit&lt;/a&gt; if you haven't already. Create a new Repl and choose &lt;strong&gt;HTML, CSS, JS&lt;/strong&gt; from the template dropdown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fcreate-a-new-repl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fcreate-a-new-repl.png" alt="create a new repl"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see that we have three files added to our project. The &lt;code&gt;index.html&lt;/code&gt; file is where we will write our HTML. The &lt;code&gt;style.css&lt;/code&gt; file is where we will write our CSS. You can delete the &lt;code&gt;script.js&lt;/code&gt; file, as we won't write any JavaScript in this tutorial.&lt;/p&gt;

&lt;p&gt;Replit prepopulates the &lt;code&gt;index.html&lt;/code&gt; file with a bunch of code. Delete everything in there as we will start from scratch.&lt;/p&gt;

&lt;p&gt;Copy the following line in your &lt;code&gt;index.html&lt;/code&gt; file, and you will see it formatted as a heading in the browser preview window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Web Design in the 90s&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Freplit-interface.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Freplit-interface.png" alt="replit interface"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! You just made your very own web page 🎉🎉🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Building structure
&lt;/h2&gt;

&lt;p&gt;While writing individual HTML tags does work, to create a real website we need to combine individual elements in a structured way. We will also need to add some extra information and metadata about our site for SEO and other purposes.&lt;/p&gt;

&lt;p&gt;In your code editor, erase everything.&lt;/p&gt;

&lt;p&gt;The very first piece of HTML we will write is &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/code&gt; tag is a &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Doctype" rel="noopener noreferrer"&gt;doctype&lt;/a&gt; tag. It is a required tag that is added at the top of all HTML documents. This tag makes sure everything behaves as expected. Note that there is no closing tag as we are not wrapping this tag around any content.&lt;/p&gt;

&lt;p&gt;Next we will add an &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag wraps around all the content and is often called the root element. This tag tells the browser that everything enclosed inside this tag is HTML. HTML elements can also be nested inside each other. All further elements we add will be nested inside the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;Also, notice how we set the language to USA English by setting the value of the &lt;code&gt;lang&lt;/code&gt; attribute to &lt;code&gt;"en-US"&lt;/code&gt;. We can pass extra information to an HTML element using attributes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element
&lt;/h2&gt;

&lt;p&gt;Inside the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element, the content is divided into a &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element and a &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The head element is a container where we include all the meta information. We can also include extra information about the website, which we don't want to render, or show, on the website itself. This extra information can include data to improve our website's SEO ranking, links to other files, and so on.&lt;/p&gt;

&lt;p&gt;Let's add these elements to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My 90s website&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To display an HTML page correctly, a web browser must know which character set to use. Adding the line &lt;code&gt;&amp;lt;meta charset="utf-8"&amp;gt;&lt;/code&gt; tells our browser to use the &lt;a href="https://en.wikipedia.org/wiki/UTF-8" rel="noopener noreferrer"&gt;UTF-8&lt;/a&gt; character set. UTF-8 is the recommended character set as it covers almost all of the characters and symbols in the world!&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; element contains the title of our website. This title appears in the browser tab. It is also used to describe the page when you bookmark it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;&lt;br&gt;
In Replit you can generate this boilerplate code by typing &lt;code&gt;!&lt;/code&gt; followed by the tab key in an empty HTML file.&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element
&lt;/h2&gt;

&lt;p&gt;Everything we want to display on our website will be nested within the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element. We will spend most of our time as web developers in this element.&lt;/p&gt;

&lt;p&gt;Let's add a &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; element and a heading to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of our site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My 90s Website&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My 90s Website&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; element usually contains introductory and/or navigational content. Don't confuse the &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; with the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; element is the level one heading element. Heading elements allow us to specify that certain parts of our content are headings or subheadings. There are six heading levels in HTML, &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Info Indentation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The indentation in the HTML code is not required. The only reason we indent our code is to make it easier to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding more content
&lt;/h2&gt;

&lt;p&gt;Let's add some more content to our site. We already have a heading, so let's add a &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element to our web page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My 90s Website&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element represents the dominant content of the &lt;code&gt;body&lt;/code&gt; of a document.&lt;/p&gt;

&lt;p&gt;Next we will add some text or paragraph elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My 90s Website&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      This is a &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;90s inspired&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; website. Today, 90s web design is
      more likely to raise a smile than admiration.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      See what Apple, Microsoft, Yahoo or Pepsi websites looked like in the
      mid-1990s.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have added two paragraph elements under the heading. According to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;&lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML" rel="noopener noreferrer"&gt;HTML&lt;/a&gt; element represents a paragraph. Paragraphs are usually represented in visual media as blocks of text separated from adjacent blocks by blank lines and/or first-line indentation, but HTML paragraphs can be any structural grouping of related content, such as images or form fields.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have also wrapped the text "90s inspired" with the &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; tag. The &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; element indicates that its contents have strong importance. The browser will render the contents of &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; element in bold type.&lt;/p&gt;

&lt;p&gt;Also, notice the &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; element which is adding a horizontal separator between the two paragraphs. There are certain elements like the &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; element that do not require a closing tag as they do not wrap around any content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which other element have we used that didn't require a closing tag?&lt;/p&gt;

&lt;p&gt;Hit the "Run" button on your Replit site and you will see the new text appear in the browser preview window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fparagraphs-added.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fparagraphs-added.png" alt="paragraphs added"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding links
&lt;/h2&gt;

&lt;p&gt;Next, we will add a link to &lt;a href="https://www.webdesignmuseum.org/exhibitions/web-design-in-the-90s" rel="noopener noreferrer"&gt;the web design museum&lt;/a&gt; so our readers can see some actual 90s websites. We want to make some of our text clickable. We can do that by using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a" rel="noopener noreferrer"&gt;the anchor tag &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First wrap the text that you want to make clickable with the &lt;code&gt;&amp;lt;a&amp;gt;&amp;lt;/a&amp;gt;&lt;/code&gt; tags, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
  See what Apple, Microsoft, Yahoo or Pepsi websites
  &lt;span class="nt"&gt;&amp;lt;a&amp;gt;&lt;/span&gt;looked like in the mid-1990s&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;.
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The anchor element requires an &lt;code&gt;href&lt;/code&gt; or hyperlink reference attribute, which is the URL that the hyperlink points to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
  See what Apple, Microsoft, Yahoo or Pepsi websites
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.webdesignmuseum.org/exhibitions/web-design-in-the-90s"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;looked like in the mid-1990s&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit "Run" and you should see the link in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fhyperlink-added.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fhyperlink-added.png" alt="hyperlink-added.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding images
&lt;/h2&gt;

&lt;p&gt;You can't have a 90s-inspired website without the animated GIF of the dancing baby!&lt;/p&gt;

&lt;p&gt;Create a new folder in the "Files" section in your repl, and name it "img". Download the images from &lt;a href="///tutorial-files/nineties-website/assets.zip"&gt;here&lt;/a&gt; and upload them to the "img" folder.&lt;/p&gt;

&lt;p&gt;Images are added with an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img" rel="noopener noreferrer"&gt;image element &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;&lt;/a&gt;. Inside this tag we need to give the image location, or &lt;em&gt;source&lt;/em&gt;, with a &lt;code&gt;src&lt;/code&gt; attribute, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"img/dancing-baby.gif"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will add the image within the &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; element. We will also add a description of the image with an &lt;code&gt;alt&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My 90s Website&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"img/dancing-baby.gif"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Animated GIF of a dancing baby"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      This is a &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;90s inspired&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; website. Today, 90s web design is
      more likely to raise a smile than admiration.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      See what Apple, Microsoft, Yahoo or Pepsi websites
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
        &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.webdesignmuseum.org/exhibitions/web-design-in-the-90s"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;looked like in the mid-1990s&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how our website looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fimage-added.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fimage-added.gif" alt="image added"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element is like an empty box. We can use it when we need to break the content on our website into different sections, to control styling, and more.&lt;/p&gt;

&lt;p&gt;For our site, we will use the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element to add a background image to our site later.&lt;/p&gt;

&lt;p&gt;Let's add a new &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element to our "index.html" file and nest everything else inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- This is the new addition. --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My 90s Website&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"img/dancing-baby.gif"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        This is a &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;90s inspired&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; website. Today, 90s web design
        is more likely to raise a smile than admiration.
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        See what Apple, Microsoft, Yahoo or Pepsi websites
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
          &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.webdesignmuseum.org/exhibitions/web-design-in-the-90s"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;looked like in the mid-1990s&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- Don't forget to close the div element --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Info Comments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;!-- --&amp;gt;&lt;/code&gt; characters mark a comment. Anything written between these two tags will be ignored by the browser. We add comments to explain our code to other humans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linking files
&lt;/h2&gt;

&lt;p&gt;The website has all the content that we need. Now it's time to add some CSS magic.&lt;/p&gt;

&lt;p&gt;Before we write any CSS, we need to leave instructions for our browser so it knows where to look for our CSS file.&lt;/p&gt;

&lt;p&gt;To link the CSS file to the HTML file, we will use a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link" rel="noopener noreferrer"&gt;link element &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;&lt;/a&gt;. The &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element is used to specify relationships between the current document and an external resource. This element is most commonly used to link to CSS, but it can be used to link other files.&lt;/p&gt;

&lt;p&gt;We will add the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element within the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element of our site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"style.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My 90s Website&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element needs a &lt;code&gt;rel&lt;/code&gt; or relationship attribute and an &lt;code&gt;href&lt;/code&gt; attribute. We set the &lt;code&gt;rel&lt;/code&gt; attribute to "stylesheet" to tell our browser what kind of document are we linking. We will add the name of our stylesheet as the value of the &lt;code&gt;href&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your CSS file is in another folder or directory, you will need to provide the complete path to the CSS sheet.&lt;/p&gt;

&lt;p&gt;Notice we have also added a new &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag. This gives the browser instructions on how to control the page's dimensions and scaling. You can read more about it on &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag" rel="noopener noreferrer"&gt;MDN documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic styling
&lt;/h2&gt;

&lt;p&gt;In our CSS file, we can list the HTML elements we’ve used and say how we want them to look. We use CSS properties and values to control the look of our elements.&lt;/p&gt;

&lt;p&gt;There are many ways to point to the elements we want. The easiest way is to use the element's name.&lt;/p&gt;

&lt;p&gt;For example, the following code will select the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element, and change its &lt;code&gt;background-color&lt;/code&gt; property to black:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also select multiple elements at once. For example, the following code will change the text color of both &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blueviolet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also declare more than one rule at once. For example, the following group of rules will alter the size and the extra spacing (padding) at the top of our &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fcss-applied-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fcss-applied-1.png" alt="css applied"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a lot of CSS properties. Covering them all is beyond the scope of this tutorial. You can look up all the CSS properties on &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference" rel="noopener noreferrer"&gt;MDN documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another good way to find CSS properties and their values for your use case is searching for them using your favorite search engine. For example, if we want to learn how to align the text on our web page to the center, we can search "CSS text align".&lt;/p&gt;

&lt;p&gt;The first result was this &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-align" rel="noopener noreferrer"&gt;MDN page&lt;/a&gt; which explains the usage of &lt;code&gt;text-align&lt;/code&gt; property. Add this new property to your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A good way to learn CSS is by playing around with different properties and their values.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML classes
&lt;/h2&gt;

&lt;p&gt;Selecting elements by name comes in handy when we want to select all elements of the same type on a page at once. But for better control, we may want to select and make changes to individual elements. One way to select individual elements is using &lt;em&gt;classes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;HTML classes&lt;/em&gt; are attributes that we can add to elements. Once an element has a class name, we can use this in our CSS. You can think of classes as custom labels that we attach to our elements.&lt;/p&gt;

&lt;p&gt;Let's add a class &lt;code&gt;bigger-text&lt;/code&gt; to our first &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bigger-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  This is a &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;90s inspired&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; website. Today, 90s web design is more
  likely to raise a smile than admiration.
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Naming the classes is totally up to you. You can name the classes whatever way you want. But it's a good practice to give classes a descriptive name.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;style.css&lt;/code&gt; file we can select the element with the class &lt;code&gt;bigger-text&lt;/code&gt;. To tell the CSS that we are selecting a class, and not a built-in HTML element, we have to add a dot &lt;code&gt;.&lt;/code&gt; before the class name. For example, we can select the &lt;code&gt;bigger-text&lt;/code&gt; class and change its fonts to a different size like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.bigger-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CSS has several different units for expressing length. We are using a relative size unit &lt;code&gt;rem&lt;/code&gt; here. But we can also use absolute units like &lt;code&gt;px&lt;/code&gt;, which stands for pixels.&lt;/p&gt;

&lt;p&gt;Let's also add a class &lt;code&gt;container&lt;/code&gt; to our &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element and use it to add a background image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upload the &lt;code&gt;window.png&lt;/code&gt; file to your image folder, if you haven't already. Now add the following CSS code to your &lt;code&gt;style.css&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("img/window.png")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;no-repeat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;800px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;800px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSS properties are pretty self-explanatory. In the code above, we use an image as a background with a &lt;code&gt;url()&lt;/code&gt; function. This is just like passing the URL of an image to the &lt;code&gt;href&lt;/code&gt; attribute in HTML.&lt;/p&gt;

&lt;p&gt;Next, we tell the browser to position this image in the center and not to repeat it multiple times. We also set the height and width. This time we use absolute length units &lt;code&gt;px&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;margin&lt;/code&gt; property controls the extra space around an element. Setting it to &lt;code&gt;auto&lt;/code&gt; means we want equal space all around an element, which is a good way to center an element.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fcss-applied-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fcss-applied-2.png" alt="css applied"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's add our emoji image to the &lt;code&gt;body&lt;/code&gt; element to make our website as eye-watering as possible 😀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt; &lt;span class="sx"&gt;url("img/emoji.png")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we changed the &lt;code&gt;background-color&lt;/code&gt; property to &lt;code&gt;background&lt;/code&gt; and added both a color and an image to it in a single line.&lt;/p&gt;

&lt;p&gt;Also notice that this time we didn't use the &lt;code&gt;background-repeat: no-repeat&lt;/code&gt; which resulted in our image repeating multiple times to fill the whole &lt;code&gt;body&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fcss-applied-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fcss-applied-3.png" alt="css applied 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom domain
&lt;/h2&gt;

&lt;p&gt;Our website is done! We can share it with the world by sharing the URL of our repl.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Freplit-url.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Freplit-url.png" alt="replit url"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is one problem, however. The URL is not very reader-friendly. We want something easier like "ilove90sdesign.com".&lt;/p&gt;

&lt;p&gt;There are many places you can buy a custom URL or domain. Some of them are &lt;a href="https://domains.google/v2/" rel="noopener noreferrer"&gt;Google Domains&lt;/a&gt;, &lt;a href="https://www.godaddy.com" rel="noopener noreferrer"&gt;GoDaddy&lt;/a&gt;, and &lt;a href="https://www.namecheap.com/" rel="noopener noreferrer"&gt;Namecheap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's walk through the process of connecting our repl to a custom domain from Namecheap.&lt;/p&gt;

&lt;p&gt;Buy a domain on Namecheap.&lt;/p&gt;

&lt;p&gt;Next, in your Replit site, click on the pencil icon in the browser preview window and click on "Create Domain Link". Enter your domain and the domain linking window will open:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fdomain-linking.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fnineties-website%2Fdomain-linking.png" alt="domain linking"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the IP address from the "Domain Linking" dialog.&lt;/p&gt;

&lt;p&gt;Sign in to your Namecheap account. Follow the instructions from step 3 of &lt;a href="https://www.namecheap.com/support/knowledgebase/article.aspx/9837/46/how-to-connect-a-domain-to-a-server-or-hosting/" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;. Select "@" for "Host" and enter the IP address that you have copied above in the "IP Address" field.&lt;/p&gt;

&lt;p&gt;Click "Save changes". Back in your Replit site, click the "Next" button. Replit will verify the domain and you are done!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can take some time before the changes take effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where next?
&lt;/h2&gt;

&lt;p&gt;That's it for this tutorial, but you can build your site further. Try adding more sections to your website, or play around with CSS properties and selectors to give your site a different look.&lt;/p&gt;

&lt;p&gt;You can find our repl below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://replit.com/@ritza/My-90s-website" rel="noopener noreferrer"&gt;https://replit.com/@ritza/My-90s-website&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>html</category>
    </item>
    <item>
      <title>Phaser vs PixiJS for making 2D games</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 18 Aug 2022 07:53:00 +0000</pubDate>
      <link>https://dev.to/ritza/phaser-vs-pixijs-for-making-2d-games-2j8c</link>
      <guid>https://dev.to/ritza/phaser-vs-pixijs-for-making-2d-games-2j8c</guid>
      <description>&lt;p&gt;&lt;a href="https://phaser.io/"&gt;Phaser&lt;/a&gt; and &lt;a href="https://pixijs.com/"&gt;PixiJS&lt;/a&gt; are &lt;a href="https://github.com/collections/javascript-game-engines"&gt;popular&lt;/a&gt; open-source JavaScript &lt;a href="https://en.wikipedia.org/wiki/WebGL"&gt;WebGL&lt;/a&gt; libraries that are used to create 2D browser games. They render graphics inside an HTML &lt;code&gt;canvas&lt;/code&gt; element. Games can be developed using JavaScript or TypeScript. These libraries use WebGL for creating high-performance, complex visual effects. You can create simple browser games using vanilla JavaScript, but if you want to have performant rendering or advanced features in your game such as physics or realistic lighting, vanilla JavaScript is not a good choice. These libraries speed up game development by providing useful functionality and they improve performance.&lt;/p&gt;

&lt;p&gt;Let's compare Phaser and PixiJS for making 2D games. We'll use code snippets of a simple version of Flappy Bird, created using each library, as practical examples of the differences and similarities between the two libraries.&lt;/p&gt;

&lt;p&gt;Here are the links to the Flappy Bird games:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flappy-bird-phaser.ritza.repl.co/"&gt;Phaser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flappy-bird-pixijs.ritza.repl.co/"&gt;PixiJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To move the bird, press up, click or touch the screen. There is no restart; you'll need to refresh the page to play again.&lt;/p&gt;

&lt;p&gt;Here are the links to the code for the Flappy Bird games:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://replit.com/@ritza/Flappy-Bird-Phaser#script.js"&gt;Phaser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://replit.com/@ritza/Flappy-Bird-PixiJS#script.js"&gt;PixiJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Phaser basics
&lt;/h2&gt;

&lt;p&gt;Phaser is a beginner-friendly 2D game framework that is used to create HTML5 games. It uses WebGL for rendering, and it has a Canvas fallback for when WebGL is not supported. It has all the functionality that you need to build a game. Its features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pre-loader to load game assets such as images and sounds.&lt;/li&gt;
&lt;li&gt;Three physics systems to choose from. They allow you to add gravity and collision detection to games.&lt;/li&gt;
&lt;li&gt;Animation, including &lt;a href="https://phaser.io/examples/v3/view/animation/60fps-animation-test"&gt;animation using sprite sheets&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A particle system for creating effects such as moving stars.&lt;/li&gt;
&lt;li&gt;User input functions to control user input.&lt;/li&gt;
&lt;li&gt;Sound: Web Audio with legacy HTML Audio support.&lt;/li&gt;
&lt;li&gt;Scale Manager to control responsive game layout.&lt;/li&gt;
&lt;li&gt;Multi-camera support.&lt;/li&gt;
&lt;li&gt;Plugin system for extra functionality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To see some examples of games created using Phaser, check &lt;a href="https://phaser.io/examples"&gt;this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following code example shows the basic structure of a Phaser game:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Phaser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;physics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arcade&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;arcade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;gravity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Phaser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make a game, a &lt;code&gt;Phaser.Game&lt;/code&gt; instance is created and a &lt;code&gt;config&lt;/code&gt; object is passed in as an argument. There are many configuration options available; this example only shows a few of them. The &lt;code&gt;type&lt;/code&gt; property is the rendering context, the recommended value is &lt;code&gt;Phaser.AUTO&lt;/code&gt;. This uses WebGL for rendering and if the browser or device does not support it, it uses Canvas as a fallback.&lt;/p&gt;

&lt;p&gt;Three functions are used to create a game scene and update it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;preload()&lt;/code&gt;&lt;/strong&gt; - Load assets such as images and sounds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;create()&lt;/code&gt;&lt;/strong&gt; - Set up a scene: create sprites and position them. Set up user input event listeners and collision detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;update()&lt;/code&gt;&lt;/strong&gt; - Game loop: update the scene on every frame. For example, moving sprites and checking their current position.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  PixiJS basics
&lt;/h2&gt;

&lt;p&gt;PixiJS is a fast, lightweight 2D graphics library that is used to create interactive graphics and games. It's not a game framework; it's a library for 2D rendering. It does not have many of the built-in game functions that Phaser has, such as collision detection. To create a game, you need to build a lot of the game functionality yourself, unless additional libraries are used. The features of PixiJS, which focus on rendering graphics, include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asset loader to load assets such as images, fonts and animation data.&lt;/li&gt;
&lt;li&gt;Animation, including &lt;a href="https://pixijs.io/examples/#/sprite/animatedsprite-explosion.js"&gt;animation using sprite sheets&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pixijs.io/examples/#/demos-basic/tinting.js"&gt;Tinting&lt;/a&gt; and &lt;a href="https://pixijs.io/examples/#/demos-basic/blendmodes.js"&gt;blending&lt;/a&gt; modes.&lt;/li&gt;
&lt;li&gt;WebGL &lt;a href="https://pixijs.io/examples/#/filters-advanced/shadertoy-filter-rendertexture.js"&gt;filters&lt;/a&gt; and &lt;a href="https://pixijs.io/examples/#/mesh-and-shaders/textured-mesh-advanced.js"&gt;shaders&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Accessibility: All content can easily be made to be screen-reader accessible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To see some examples of 2D graphics created using PixiJS, check &lt;a href="https://pixijs.io/examples"&gt;this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following code example shows the basic structure of a PixiJS game:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PIXI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;antialias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assets/images/cat.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Using a game state helps modularize your code&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Start the game loop&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;gameLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;gameLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Update the current game state&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The game logic goes here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// All the code that should run at the end of the game&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Add your game helper functions:&lt;/span&gt;
&lt;span class="c1"&gt;//   such as a collision detecting function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make a game, a &lt;code&gt;PIXI.Application&lt;/code&gt; instance is created and an &lt;code&gt;options&lt;/code&gt; object is passed in as an argument. The &lt;code&gt;options&lt;/code&gt; are the optional renderer parameters such as the width and height of the renderer's view. The renderer uses the canvas element, &lt;code&gt;app.view&lt;/code&gt;, that PixiJS automatically creates for you. It is added to the HTML document using the &lt;code&gt;appendChild&lt;/code&gt; method. The assets for the game are loaded using the &lt;code&gt;loader&lt;/code&gt; instance. This is like the &lt;code&gt;preload&lt;/code&gt; function used in Phaser. Once the assets are loaded, the &lt;code&gt;setup&lt;/code&gt; function is called.&lt;/p&gt;

&lt;p&gt;Four functions are used to create a game scene and update it, after the game assets are loaded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;setup()&lt;/code&gt;&lt;/strong&gt; - Set up a scene: Create sprites and position them. Set up user input event listeners and collision detection. Set up the game state and start the game loop. The method &lt;a href="https://pixijs.download/dev/docs/PIXI.Ticker.html#add"&gt;&lt;code&gt;ticker.add&lt;/code&gt;&lt;/a&gt; starts the &lt;code&gt;gameLoop&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gameLoop()&lt;/code&gt;&lt;/strong&gt; - Update the scene on every frame. The &lt;code&gt;play()&lt;/code&gt; function is called to handle the game logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;play()&lt;/code&gt;&lt;/strong&gt; - Handle game updates, for example moving sprites and checking their current position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;end()&lt;/code&gt;&lt;/strong&gt; - All the code that should run at the end of the game, such as changing the game state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Documentation and ease of use
&lt;/h2&gt;

&lt;p&gt;Phaser and PixiJS have good documentation for their APIs. They also provide examples and tutorials for making games. Phaser has more game-specific examples, whereas the PixiJS examples focus on graphics rendering. It is easier to make a game using Phaser as it is a game framework. It provides useful functionality for games and the code structure is more organized for games by default. PixiJS is less opinionated about how you structure a game.&lt;/p&gt;

&lt;p&gt;When you publish a game, it's best to bundle your files to make them as small as possible. Both Phaser and PixiJS provide starter templates to bundle your game using Webpack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding game physics
&lt;/h2&gt;

&lt;p&gt;Phaser comes with built-in game physics, whereas PixiJS does not. Phaser has three types of built-in physics: Arcade Physics, Impact Physics, and Matter.js. For example, if you wanted to add Arcade Physics, which has dynamic and static bodies, you add it to the &lt;code&gt;config&lt;/code&gt; as can be seen in the code example in the &lt;strong&gt;Phaser basics&lt;/strong&gt; section above. A dynamic body is a body that can move around via forces such as velocity and acceleration. These bodies can bounce and collide. There is also a &lt;code&gt;debug&lt;/code&gt; property that, when set to true, displays the outline of physics bodies and shows the effect of forces as can be seen in the &lt;a href="https://flappy-bird-phaser.ritza.repl.co/"&gt;Phaser Flappy Bird game&lt;/a&gt;. There is a performance cost in drawing debug displays. It should not be used in production. The &lt;code&gt;gravity&lt;/code&gt; property allows us to set up the gravitational force.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;collider&lt;/code&gt; method is used to detect a collision between the bird and the pipes in the &lt;a href="https://replit.com/@ritza/Flappy-Bird-Phaser#script.js"&gt;Flappy Bird game&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;collider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;physics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;collisionCallback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;collisionCallback&lt;/code&gt; function is run when a collision is detected. It can be used to run all of the code that should run when the game is over:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;collisionCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;gameOver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;collider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// turn off so that the bird falls to the ground on collision&lt;/span&gt;
  &lt;span class="nx"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setFlipY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crashSound&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting up physics in PixiJS requires building the gravity and collision detection yourself. Making the bird fall in &lt;a href="https://replit.com/@ritza/Flappy-Bird-PixiJS#script.js"&gt;Flappy Bird&lt;/a&gt; by simulating gravity with PixiJS only requires two lines of code added to the &lt;code&gt;play()&lt;/code&gt; loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bird's velocity is increased on each game loop and then its y position is updated. Collision detection requires writing your own collision detector, for example the &lt;code&gt;hitTestRectangle&lt;/code&gt; function in the PixiJS Flappy Bird game.&lt;/p&gt;

&lt;p&gt;Another option is to add a third-party physics library such as &lt;a href="https://github.com/liabru/matter-js"&gt;Matter.js&lt;/a&gt;, although setting up and integrating the physics library can be tricky. If you need physics in your game, especially more complex physics effects, Phaser is a better choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding animation
&lt;/h2&gt;

&lt;p&gt;PixiJS and Phaser have basic animation functionality for sprite sheet animation, &lt;a href="https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Animations_and_tweens#tweens"&gt;tweens&lt;/a&gt; for smooth animation and they have particle systems to create effects such as an explosion. The particle system needs to be installed as a &lt;a href="https://github.com/pixijs/particle-emitter"&gt;separate library&lt;/a&gt; for PixiJS.&lt;/p&gt;

&lt;p&gt;For more advanced graphics effects, PixiJS is a better choice as it includes many examples of how to create advanced graphics features such as WebGL &lt;a href="https://pixijs.io/examples/#/filters-advanced/shadertoy-filter-rendertexture.js"&gt;filters&lt;/a&gt; and &lt;a href="https://pixijs.io/examples/#/mesh-and-shaders/textured-mesh-advanced.js"&gt;shaders&lt;/a&gt;. The &lt;a href="https://pixijs.com/"&gt;PixiJS website&lt;/a&gt; shows some real-world examples of websites that were created using PixiJS. It has also been used by many well-known global brands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding user input
&lt;/h2&gt;

&lt;p&gt;Phaser provides many methods to handle user input, including more advanced use cases such as drag and drop. PixiJS requires more manual setup of user input. For example, in the &lt;a href="https://replit.com/@ritza/Flappy-Bird-Phaser#script.js"&gt;Phaser Flappy Bird game&lt;/a&gt;, making the bird fly up when the user presses the up arrow, clicks the screen or touches the screen requires three lines of code.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;create&lt;/code&gt; function, the &lt;code&gt;createCursorKeys&lt;/code&gt; method creates and returns an object containing four hotkeys for up, down, left, and right as well as space bar and shift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cursors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createCursorKeys&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;update&lt;/code&gt; function, we listen for an arrow up press, a mouse click or a screen touch and then move the bird up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDown&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activePointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setVelocityY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make the bird move up in the PixiJS Flappy Bird game requires a lot more code. A &lt;code&gt;keyboard&lt;/code&gt; helper function is created, which is used in the &lt;code&gt;setup&lt;/code&gt; function to listen for the specific input events and set the bird's velocity when the event occurs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing a responsive layout
&lt;/h2&gt;

&lt;p&gt;Browser games can be played on desktop or mobile devices; they may also be compiled to native mobile apps by using third-party tools such as &lt;a href="https://cordova.apache.org/"&gt;Cordova&lt;/a&gt;. It's important that your 2D game has a responsive layout. Phaser has a &lt;a href="https://photonstorm.github.io/phaser3-docs/Phaser.Scale.ScaleManager.html"&gt;Scale Manager&lt;/a&gt; that handles scaling, resizing, and alignment; it also has a full-screen mode. When using PixiJS, you need to create a responsive layout manually. This can be tricky with more complex layouts.&lt;/p&gt;

&lt;p&gt;Let's compare the responsive layout implementation of the &lt;a href="https://replit.com/@ritza/Flappy-Bird-Phaser#script.js"&gt;Phaser&lt;/a&gt; and &lt;a href="https://replit.com/@ritza/Flappy-Bird-PixiJS#script.js"&gt;PixiJS&lt;/a&gt; Flappy Bird games. For both games, the game canvas width is set at 800px, the height is set at 600px and it is centered in the window. The canvas is scaled to fill the browser window as much as possible while maintaining its aspect ratio.&lt;/p&gt;

&lt;p&gt;In the Phaser implementation, this requires setting some &lt;code&gt;scale&lt;/code&gt; properties in the &lt;code&gt;config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Phaser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;autoCenter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Phaser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CENTER_BOTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CONFIG_WIDTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CONFIG_HEIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the PixiJS implementation, this requires more code. In the &lt;code&gt;setup&lt;/code&gt; function, a "resize" event listener was created with a callback function called &lt;code&gt;resize&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// call it manually once - make sure the stage is correctly sized on page load&lt;/span&gt;
&lt;span class="nx"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;resize&lt;/code&gt; function is a helper function that determines the current screen size and what scale factor is needed to make the game canvas fill the screen while maintaining its aspect ratio. It then uses the scale factor to change the CSS styling of the game canvas to scale the canvas size up or down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// current screen size&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;screenWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;screenHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// scale factor for our game canvas&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;screenWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;CONFIG_WIDTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;screenHeight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;CONFIG_HEIGHT&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// scaled width and height&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enlargedWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;CONFIG_WIDTH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enlargedHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;CONFIG_HEIGHT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// margins for centering&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;horizontalMargin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screenWidth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;enlargedWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verticalMargin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screenHeight&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;enlargedHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// CSS to set the sizes and margins&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;enlargedWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;enlargedHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;marginLeft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;marginRight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;horizontalMargin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;marginTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;marginBottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;verticalMargin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding sounds
&lt;/h2&gt;

&lt;p&gt;Both Phaser and PixiJS provide support for sound functionality. They provide a beginner-friendly interface for the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API"&gt;Web Audio API&lt;/a&gt;. Phaser also supports legacy HTML Audio. For PixiJS, the &lt;a href="https://github.com/pixijs/sound"&gt;PixiJS Sound library&lt;/a&gt; needs to be added. This library is a Web Audio API playback library that is built for modern browsers. It has a &lt;a href="https://bundlephobia.com/package/@pixi/sound@4.2.0"&gt;small bundle size&lt;/a&gt; (8.7 kB minified and GZipped) with no dependencies and is tree-shakable so you can remove unused modules in your production build.&lt;/p&gt;

&lt;p&gt;Let's compare how sound was added to the &lt;a href="https://replit.com/@ritza/Flappy-Bird-Phaser#script.js"&gt;Phaser&lt;/a&gt; and &lt;a href="https://replit.com/@ritza/Flappy-Bird-PixiJS#script.js"&gt;PixiJS&lt;/a&gt; Flappy Bird games. You'll see that the implementation is similar.&lt;/p&gt;

&lt;p&gt;In the Phaser Flappy Bird game, the "woosh" sound that the bird makes when it moves up is loaded into the game in the &lt;code&gt;preload&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;birdSound&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets/sounds/woosh.mp3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sound.play&lt;/code&gt; method is called to play the bird sound:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;birdSound&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the PixiJS Flappy Bird game, the PixiJS Sound library is imported using a script tag in the &lt;code&gt;index.html&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/@pixi/sound/dist/pixi-sound.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sound is loaded into the game using the &lt;code&gt;add&lt;/code&gt; method in the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bird-sound&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assets/sounds/woosh.mp3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sound.play&lt;/code&gt; method is called to play the bird sound:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;PIXI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bird-sound&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bundle size comparison
&lt;/h2&gt;

&lt;p&gt;The Flappy Bird game examples import the libraries using a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag for ease of use. When building a production-ready game, you can use the Webpack starter templates that are available for each of the libraries to bundle the game files so that the file size is as small as possible. To compare the bundle sizes, the two Flappy Bird games were bundled using the following Webpack starter templates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/photonstorm/phaser3-project-template"&gt;Phaser Webpack starter template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/yordan-kanchelov/pixi-typescript-boilerplate"&gt;PixiJS TypeScript Webpack starter template&lt;/a&gt;: TypeScript was disabled using &lt;code&gt;// @ts-nocheck&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following table compares the bundle sizes. The &lt;a href="https://bundlephobia.com/package/phaser@3.55.2"&gt;bundle size of PixiJS&lt;/a&gt; is less than half the size of the &lt;a href="https://bundlephobia.com/package/pixi.js@6.4.2"&gt;bundle size of Phaser&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Phaser&lt;/th&gt;
&lt;th&gt;PixiJS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;npm package minified bundle size&lt;/td&gt;
&lt;td&gt;1 MB&lt;/td&gt;
&lt;td&gt;478.6 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm package minified and GZipped bundle size&lt;/td&gt;
&lt;td&gt;274.1 kB&lt;/td&gt;
&lt;td&gt;125.4 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flappy Bird game minified bundle size&lt;/td&gt;
&lt;td&gt;1.02 MB&lt;/td&gt;
&lt;td&gt;424 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PixiJS is also tree-shakable, so you can remove unused modules in your production build. This will further reduce the bundle size. The PixiJS Flappy Bird game includes the &lt;a href="https://github.com/pixijs/sound"&gt;PixiJS Sound library&lt;/a&gt;. The Flappy Bird game bundle size can be further reduced in the browser using &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/GZip_compression"&gt;GZip compression&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If bundle size is critical, for example if you are building a mobile game for users with a poor internet connection, then PixiJS is a good choice, as long as you are willing to build a lot of the game functionality yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion - which one is the best?
&lt;/h2&gt;

&lt;p&gt;The following table shows a comparison between Phaser and PixiJS for 2D browser game development. It shows which library is the best for each category.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Phaser&lt;/th&gt;
&lt;th&gt;PixiJS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Beginner-friendly&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Game physics&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animations / visual effects&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handling user input&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Responsive layout&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sounds&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;✔️ *&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* &lt;small&gt;&lt;em&gt;needs an extra library&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Phaser is a framework for making games and is more beginner-friendly than PixiJS. If you compare the code for the &lt;a href="https://replit.com/@ritza/Flappy-Bird-Phaser#script.js"&gt;Phaser&lt;/a&gt; and &lt;a href="https://replit.com/@ritza/Flappy-Bird-PixiJS#script.js"&gt;PixiJS&lt;/a&gt; Flappy Bird games, you'll see that the number of lines in the PixiJS implementation is more than double the number of lines in the Phaser implementation. This is because some functionality such as collision detection and containing the bird in the visible area of the game canvas required creating custom functions.&lt;/p&gt;

&lt;p&gt;PixiJS can also be good for beginners looking to understand game development more deeply as they will have to implement a lot of functionality themselves.&lt;/p&gt;

&lt;p&gt;For more complex games, Phaser is a better choice because of the built-in game functionality. Although you can add extra libraries to PixiJS to compensate for its lack of built-in functionality, the problem is that the bundle size and codebase complexity will increase, which may make the bundle size advantage that PixiJS has insignificant.&lt;/p&gt;

&lt;p&gt;If your game needs to be screen-reader accessible or if you want to create complex interactive graphics for games or other types of digital content, PixiJS is the best option.&lt;/p&gt;

&lt;p&gt;For 2D browser games, Phaser is the best choice as it has many useful game functions built in. If the bundle size of your game is critical and you can build a lot of the game functionality yourself, then PixiJS is a better choice.&lt;/p&gt;

&lt;p&gt;Another option for a beginner-friendly game framework is &lt;a href="https://kaboomjs.com/"&gt;Kaboom.js&lt;/a&gt;. Here's a tutorial where you can learn how to &lt;a href="https://docs.replit.com/tutorials/build-flappy-bird-with-kaboom"&gt;build Flappy Bird with Kaboom.js&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>pixijs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Create a Discord RPG bot with Python</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Fri, 12 Aug 2022 10:14:00 +0000</pubDate>
      <link>https://dev.to/ritza/create-a-discord-rpg-bot-with-python-4nab</link>
      <guid>https://dev.to/ritza/create-a-discord-rpg-bot-with-python-4nab</guid>
      <description>&lt;p&gt;In this tutorial, we'll create a text-based RPG that users of a &lt;a href="https://discord.com" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; server can play by entering special commands. Users will be able to create characters, fight enemies, and earn experience and gold, which they can use to develop their skills and buy powerful items.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you will have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used &lt;a href="https://discordpy.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;discord.py&lt;/a&gt; to create an RPG that can be added to any Discord server, and which can be expanded as you see fit.&lt;/li&gt;
&lt;li&gt;Used Replit Database to store information about the game world through serialization.&lt;/li&gt;
&lt;li&gt;Created a custom Discord message embed.&lt;/li&gt;
&lt;li&gt;Hosted your own Discord bot on Replit!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Sign in to &lt;a href="https://replit.com" rel="noopener noreferrer"&gt;Replit&lt;/a&gt; or &lt;a href="https://replit.com/signup" rel="noopener noreferrer"&gt;create an account&lt;/a&gt; if you haven't already. Once logged in, create a Python repl.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-rpg-bot%2Fcreate-repl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-rpg-bot%2Fcreate-repl.png" alt="Create repl"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Game design
&lt;/h2&gt;

&lt;p&gt;Before we dive into writing code, it's worth taking a moment to think about how our game is going to work and what options we're going to give our players. Our game will be text-based and played on a Discord server. Players will take actions by issuing commands to a Discord bot, and the bot will respond with the results of those actions.&lt;/p&gt;

&lt;p&gt;Players will start the game by creating a character, whom they can name. This character will have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hitpoints to determine how much damage they can take.&lt;/li&gt;
&lt;li&gt;Attack and defense skills to determine how they fare in battle.&lt;/li&gt;
&lt;li&gt;A level and experience points.&lt;/li&gt;
&lt;li&gt;Mana for casting spells.&lt;/li&gt;
&lt;li&gt;An inventory for collecting items.&lt;/li&gt;
&lt;li&gt;Gold for purchasing items.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characters will gain experience and gold for fighting and defeating enemies. They will be able to seek out enemies and battle them. In battle mode, they will be able to fight, use an item, or flee if it doesn't look like they're going to win. Battles will continue until one participant runs out of hitpoints, or until the player's character flees.&lt;/p&gt;

&lt;p&gt;At the end of a successful battle, characters will gain experience points and gold, based on the strength of the defeated enemy. If a character is defeated in battle, they will die and the player will have to create a new character.&lt;/p&gt;

&lt;p&gt;At certain thresholds of experience points, characters will level up. Every time a player levels up their character, they will be able to choose to increase the character's hitpoints, attack, defense, or mana.&lt;/p&gt;

&lt;p&gt;While we've included gold, mana, and an inventory in our design, the implementation of these aspects will be left as an exercise for the reader. For this tutorial, we'll focus on implementing battling enemies and leveling up.&lt;/p&gt;

&lt;p&gt;To facilitate the above, our game will need two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adventure mode, in which players can hunt for enemies and level up.&lt;/li&gt;
&lt;li&gt;Battle mode, in which players can fight enemies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will also need a few commands for players to take game actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;!create &amp;lt;name&amp;gt;&lt;/code&gt;: Create a new character with the name provided.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!status&lt;/code&gt;: Display the character's current hitpoints, level, carried items, and other information.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!die&lt;/code&gt;: Destroy the current character.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!hunt&lt;/code&gt;: (In adventure mode) seek out an enemy to fight.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!fight&lt;/code&gt;: (In battle mode) attack an enemy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!flee&lt;/code&gt;: (In battle mode) flee from a battle.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!levelup &amp;lt;stat&amp;gt;&lt;/code&gt;: (In adventure mode) advance to the next level if enough XP is available, increasing the provided stat (hitpoints, attack, defense, mana).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Game classes
&lt;/h2&gt;

&lt;p&gt;Before we start on the Discord code, let's create some classes to represent the characters, enemies, modes, and items in our game. In your repl, create a new file named &lt;code&gt;game.py&lt;/code&gt; and add the following import code to it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;replit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deepcopy&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-rpg-bot%2Fgamedotpy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-rpg-bot%2Fgamedotpy.png" alt="Game file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're pulling in the &lt;a href="https://replit-py.readthedocs.io/en/latest/db_tutorial.html" rel="noopener noreferrer"&gt;Python library&lt;/a&gt; for &lt;a href="https://docs.replit.com/hosting/database-faq" rel="noopener noreferrer"&gt;Replit Database&lt;/a&gt;, a persistent key-value store attached to every repl. If you haven't used this before, think of it as a Python dictionary with contents that persist between restarts of your repl. We'll be using it to store the state of our game and its characters as they fight enemies and level up.&lt;/p&gt;

&lt;p&gt;Additionally, we're importing the Python built-in libraries &lt;a href="https://docs.python.org/3/library/enum.html" rel="noopener noreferrer"&gt;&lt;code&gt;enum&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://docs.python.org/3/library/random.html" rel="noopener noreferrer"&gt;&lt;code&gt;random&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://docs.python.org/3/library/sys.html" rel="noopener noreferrer"&gt;&lt;code&gt;sys&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://docs.python.org/3/library/copy.html#copy.deepcopy" rel="noopener noreferrer"&gt;&lt;code&gt;deepcopy()&lt;/code&gt;&lt;/a&gt; from &lt;a href="https://docs.python.org/3/library/copy.html" rel="noopener noreferrer"&gt;&lt;code&gt;copy&lt;/code&gt;&lt;/a&gt;. These will provide a number of helpful utilities we'll use while building our game.&lt;/p&gt;

&lt;p&gt;First off, we'll use &lt;a href="https://docs.python.org/3/library/enum.html#enum.IntEnum" rel="noopener noreferrer"&gt;&lt;code&gt;enum.IntEnum&lt;/code&gt;&lt;/a&gt; to enumerate our game modes. Add the following code below your imports:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Game modes
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GameMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ADVENTURE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;BATTLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Whenever we refer to a game mode in our code, we'll be able to write &lt;code&gt;GameMode.ADVENTURE&lt;/code&gt; or &lt;code&gt;GameMode.BATTLE&lt;/code&gt;, rather than the meaningless numbers &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;2&lt;/code&gt;, but our code and database will see these game modes as &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt;. This saves us from having to remember which game mode is which and will make our code clearer.&lt;/p&gt;

&lt;p&gt;Next, we'll create classes for living creatures, such as players' characters and the enemies they fight. As these will all share several properties and behaviors, such as hitpoints and the ability to fight, we'll use inheritance to save ourselves some repetition. Our class hierarchy will look like this (but with a few more enemy types):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

       ,-----.             
       |Actor|             
       |-----|             
       {% raw %}`-----'             
         / \               
        /   \              
,---------.  ,-----.       
|Character|  |Enemy|       
|---------|  |-----|       
`---------'  `-----'       
              /    \       
      ,--------.   ,------.
      |GiantRat|   |Dragon|
      |--------|   |------|
      `--------'   `------'
```

Let's start by implementing our parent class, `Actor`. This class will define all of the attributes that characters and enemies have in common and implement a `fight()` method. Add the following code at the bottom of `game.py`:

```python
# Living creatures
class Actor:

    def __init__(self, name, hp, max_hp, attack, defense, xp, gold):
        self.name = name
        self.hp = hp
        self.max_hp = max_hp
        self.attack = attack
        self.defense = defense
        self.xp = xp
        self.gold = gold

    def fight(self, other):
        defense = min(other.defense, 19) # cap defense value
        chance_to_hit = random.randint(0, 20-defense)
        if chance_to_hit: 
            damage = self.attack
        else:
            damage = 0

        other.hp -= damage

        return (self.attack, other.hp &amp;lt;= 0) #(damage, fatal)
```

Our `__init__()` method defines several variables per our game design specification above. Note that we've defined both `hp` and `max_hp`: these should be the same value when we first create a character or enemy class, but will diverge for characters and enemies we read from the database. Once we get into the game logic, we will be recreating instances of these classes from the database constantly.

Also note that `self.xp` will represent something slightly different in characters and enemies: For characters, it will be the cumulative experience points earned, whereas for enemies it will be the amount of XP rewarded to characters when the enemy is defeated. A more complex design might instead enable enemies to gain experience points and level up like player characters.

The `fight()` method takes `other`, an instance of a class that inherits from `Actor`. The attack is simulated by first calculating the chance to hit based on the opponent's `defense` attribute. If `chance_to_hit` is 0, the attack will miss. The likelihood of a miss happening increases in probability as the `defense` value increases. For example, an attack will have a 95% chance of succeeding against an enemy with a `defense` of 1, but only a 50% chance of succeeding against an enemy with `defense` of 19. We use Python's built-in `min()` function to cap the value of `defense` at 19, to avoid creating a completely invulnerable character.

The damage done on a successful hit is determined by the `Actor`'s `attack` attribute. The function returns a tuple of the amount of damage dealt by the attack and whether it was the fatal blow.

Our `Actor` class is complete for now. Let's create a `Character` class that inherits from it and will represent players. Add the following code to the bottom of the `game.py` file:

```python
class Character(Actor):

    level_cap = 10

    def __init__(self, name, hp, max_hp, attack, defense, mana, level, xp, gold, inventory, mode, battling, user_id):
        super().__init__(name, hp, max_hp, attack, defense, xp, gold)
        self.mana = mana
        self.level = level

        self.inventory = inventory 

        self.mode = mode
        self.battling = battling
        self.user_id = user_id
```

Our `Character` class has all the same attributes as `Actor`, plus some additional ones:

* `mana` represents the character's capacity for casting spells.
* `level` is the character's level. This will start at 1 and increase with every 10 XP the player earns.
* `inventory` is a list of items carried by the character.
* `mode` is the game mode we defined an enum for above. Two characters will be in different modes depending on their actions.
* `battling` is a variable we'll use to store the enemy object that the character is currently fighting. This will be `None` outside of battle mode.
* `user_id` is the Discord user ID of the user who created this character.

Also, note the [class variable](https://www.digitalocean.com/community/tutorials/understanding-class-and-instance-variables-in-python-3) `level_cap`: this will be the highest possible value of `level`.

We'll come back to the `Character` class and use it to implement gameplay actions later, but first let's define the enemy classes, starting with `Enemy`.

```python
class Enemy(Actor):

    def __init__(self, name, max_hp, attack, defense, xp, gold):
        super().__init__(name, max_hp, max_hp, attack, defense, xp, gold)
```

For now, this is a straightforward subclass of `Actor` that uses the same value for `hp` and `max_hp` but doesn't define anything extra. We'll specify the actual stats of individual enemy types in the `Enemy` subclasses, of which we'll define ten. Copy-paste the code below at the bottom of `game.py` to implement those classes:

```python
class GiantRat(Enemy):
    min_level = 1
    def __init__(self):
        super().__init__("🐀 Giant Rat", 2, 1, 1, 1, 1) # HP, attack, defense, XP, gold

class GiantSpider(Enemy):
    min_level = 1
    def __init__(self):
        super().__init__("🕷️ Giant Spider", 3, 2, 1, 1, 2) # HP, attack, defense, XP, gold

class Bat(Enemy):
    min_level = 1
    def __init__(self):
        super().__init__("🦇 Bat", 4, 2, 1, 2, 1) # HP, attack, defense, XP, gold

class Crocodile(Enemy):
    min_level = 2
    def __init__(self):
        super().__init__("🐊 Crocodile", 5, 3, 1, 2, 2) # HP, attack, defense, XP, gold

class Wolf(Enemy):
    min_level = 2
    def __init__(self):
        super().__init__("🐺 Wolf", 6, 3, 2, 2, 2) # HP, attack, defense, XP, gold

class Poodle(Enemy):
    min_level = 3
    def __init__(self):
        super().__init__("🐩 Poodle", 7, 4, 1, 3, 3) # HP, attack, defense, XP, gold

class Snake(Enemy):
    min_level = 3
    def __init__(self):
        super().__init__("🐍 Snake", 8, 4, 2, 3, 3) # HP, attack, defense, XP, gold

class Lion(Enemy):
    min_level = 4
    def __init__(self):
        super().__init__("🦁 Lion", 9, 5, 1, 4, 4) # HP, attack, defense, XP, gold

class Dragon(Enemy):
    min_level = 5
    def __init__(self):
        super().__init__("🐉 Dragon", 10, 6, 2, 5, 5) # HP, attack, defense, XP, gold
```

In addition to providing names and hardcoded HP, attack, defense, XP, and gold values, we've implemented `min_level` as a class variable. This specifies the minimum level the player must be to face this enemy. By implementing this, we avoid having low-level players die instantly against too-powerful enemies and ensure that new enemies will show up as the player levels up, creating a sense of progression. Feel free to change any of these enemies, or add your own.

## Saving and loading from the database

We've now defined our player and enemy classes, though they don't do much. But before we implement any actual gameplay, we need a way to ensure that our characters and enemies maintain their state from one moment to the next. Presently, any `Actor` instances we create will disappear as soon as our repl finishes running or is stopped. We need to use our repl's database to ensure that players can keep the characters they create and that changes such as losing and gaining HP and leveling up will persist.

The most important objects in our game are the instances of `Character`. These objects store character stats, inventory, and even game mode and the enemy currently being fought. So as long as we can save and load the state of individual characters from our database, our game world will feel permanent. For now, we don't need to worry about storing anything else.

Return to the definition of `Character` and add the following `save_to_db()` method below the `__init__()` method:

```python
class Character(Actor):

    # ...

    def save_to_db(self):
        character_dict = deepcopy(vars(self))
        if self.battling != None:
            character_dict["battling"] = deepcopy(vars(self.battling))

        db["characters"][self.user_id] = character_dict
```

At the top of `game.py`, we imported `db` from the `replit` Python library – this object provides an interface to our repl's database. The `db` object is designed to be used like a dictionary, so we can create keys and values as we would with any other dictionary. Our database layout will look like this:

```
{
    "characters": {
        "123456789012345678": {
            "name": "Bob the Dwarf"
            "hp": 10,
            ...
        },
        "823486788042375673": {
            "name": "Eric the Human"
            "hp": 8,
            ...
        },
        ...
    }
}
```

The [`vars()` function](https://docs.python.org/3/library/functions.html#vars) is a Python built-in that returns the value of `__dict__` for any class, module, or instance we pass to it. For most instances, this will be a dictionary containing that instance's attributes. In the case of our `Character` object, the dictionary will contain all of the attributes we defined in `__init__`. We use [`deepcopy()`](https://docs.python.org/3/library/copy.html#copy.deepcopy) to make a full copy of this dictionary. 

Any attributes that contain strings, numbers, boolean values, or even lists or dictionaries can be easily and meaningfully stored in our repl's database. Attributes that reference instances of our custom classes cannot be usefully stored, as the referenced instance may not exist the next time the data is loaded. The `deepcopy()` operation alone does not solve this problem. Thus, we need to store the object referenced by `battling` as a dictionary of its attributes using `vars()`, just like we did for the `Character` instance.

However, we have a slight problem: While we can store the attributes of the enemy, this way we're not storing its class. There are a few ways we could resolve this issue – the easiest one is to store the instance's class name as an attribute. Go to your `Enemy` class's `__init__()` method and add the following line:

```python
class Enemy(Actor):

    def __init__(self, name, max_hp, attack, defense, xp, gold):
        super().__init__(name, max_hp, max_hp, attack, defense, xp, gold)
        # NEW LINE BELOW
        self.enemy = self.__class__.__name__
```

This new line will store the name of the class as a string, which we can write to the database.

We've now written all the code we'll need to save player characters to the database. Now we need a way to load them back into the game. Fortunately, we already have most of what we need to do this in our `Character.__init__()` method. A line of code like the following will initialize a new `Character` with details from our database:

```python
Character(**db["characters"]["123456789012345678"])
```

The only problem here is that the value of `battling` will be a dictionary rather than a subclass of `Enemy`. We can fix this by making some changes to `Character.__init__()`. Find the method and replace the line `self.battling = battling` with the new code below:

```python
class Character(Actor):

    level_cap = 10

    def __init__(self, name, hp, max_hp, attack, defense, mana, level, xp, gold, inventory, mode, battling, user_id):
        super().__init__(name, hp, max_hp, attack, defense, xp, gold)
        self.mana = mana
        self.level = level

        self.inventory = inventory 

        self.mode = mode
        # NEW CODE BELOW THIS LINE
        if battling != None:
            enemy_class = str_to_class(battling["enemy"])
            self.battling = enemy_class()
            self.battling.rehydrate(**battling)
        else:
            self.battling = None
        # NEW CODE ABOVE THIS LINE

        self.user_id = user_id
```

This code converts the value of the `enemy` attribute we created above from a string into a class, initializes a copy of that class, and then calls `rehydrate`, unpacking the `battling` dictionary as its arguments. We'll write both the `str_to_class` function and the `Enemy.rehydrate()` method shortly.

The `str_to_class` function will take a `string` and return the `class` with its name.

The `rehydrate` method will set all attributes of the instance to those provided. While we could do this with the `__init__()` method as we did with `Character`, this would force us to specify all our attribute values every time we initialize any subclass of `Enemy`, defeating the point of having subclasses in the first place.

Navigate to the top of `game.py` and create the `str_to_class` function just below your imports, as below:

```python
# Helper functions
def str_to_class(classname):
    return getattr(sys.modules[__name__], classname)
```

This function uses the useful Python built-in [`getattr`](https://docs.python.org/3/library/functions.html#getattr) to retrieve the class corresponding to the string provided as `classname`. Note that this function will only work for the classes we define.

Next, return to your `Enemy` class and create the `rehydrate()` method just below the `__init__()` method.

```python
class Enemy(Actor):

    def __init__(self, name, max_hp, attack, defense, xp, gold):
        super().__init__(name, max_hp, max_hp, attack, defense, xp, gold)
        self.enemy = self.__class__.__name__

    # NEW METHOD
    def rehydrate(self, name, hp, max_hp, attack, defense, xp, gold, enemy):
        self.name = name
        self.hp = hp
        self.max_hp = max_hp
        self.attack = attack
        self.defense = defense
        self.xp = xp
        self.gold = gold
```

Note that we're accepting `enemy` as an argument without using it. This is to prevent errors when unpacking the `battling` dictionary.

We will now be able to save characters to the database by calling `save_to_db()` and load characters from the database by passing a database entry to `Character()`. As our characters store information about the enemies they're fighting, this is all we need for a persistent game world.

## Game actions

Let's implement the player actions from our design specification. We'll start with hunting, the action that enables characters to seek out enemies to fight. Add the following code to the class definition of `Character`:

```python
    def hunt(self):
        # Generate random enemy to fight
        while True:
            enemy_type = random.choice(Enemy.__subclasses__())

            if enemy_type.min_level &amp;lt;= self.level:
                break

        enemy = enemy_type()

        # Enter battle mode
        self.mode = GameMode.BATTLE
        self.battling = enemy

        # Save changes to DB after state change
        self.save_to_db()

        return enemy
```

First, we use [`random.choice()`](https://docs.python.org/3/library/random.html#random.choice) to choose one of the subclasses of `Enemy` at random. This random selection will be repeated until we chose an enemy with a minimum level less than or equal to our player character's level.

Once the enemy is chosen, we initialize an instance of it, switch the game mode, and save a reference to it in `battling`. We then update the player object in the database and return the enemy object.

We will need to call `save_to_db()` at the end of every method that changes the character's state. This includes the `fight()` method defined in `Actor`. Add the following method to the `Character` class to accomplish this:

```python
   def fight(self, enemy):
        outcome = super().fight(enemy)

        # Save changes to DB after state change
        self.save_to_db()

        return outcome
```

This method will call `Actor.fight()`, store its result, update the database, and then return the result.

Next, we'll define a `flee()` method for escaping from a battle the player character is unlikely to win. Add the following method at the bottom of `Character`:

```python
    def flee(self, enemy):
        if random.randint(0,1+self.defense): # flee unscathed
            damage = 0
        else: # take damage
            damage = enemy.attack/2
            self.hp -= damage

        # Exit battle mode
        self.battling = None
        self.mode = GameMode.ADVENTURE

        # Save to DB after state change
        self.save_to_db()

        return (damage, self.hp &amp;lt;= 0) #(damage, killed)
```

To add some uncertainty to the flee action, as well as an additional use for the defense stat, we've implemented a random chance that the player will take a small amount of damage on fleeing. We then empty `battling`, change the game mode, save the state, and return a tuple of the action outcome, similar to the one returned in `Actor.fight()`.

Next, we'll need a method to call after an enemy is defeated, to give the player character their gold and XP. Add the following method definition:

```python
    def defeat(self, enemy):
        if self.level &amp;lt; self.level_cap: # no more XP after hitting level cap
            self.xp += enemy.xp

        self.gold += enemy.gold # loot enemy

        # Exit battle mode
        self.battling = None
        self.mode = GameMode.ADVENTURE

        # Check if ready to level up after earning XP
        ready, _ = self.ready_to_level_up()

        # Save to DB after state change
        self.save_to_db()

        return (enemy.xp, enemy.gold, ready)
```

If the player's level is below the cap, we add the enemy's XP onto their own. We then add the enemy's gold to their coin-purse, exit battle mode, and check if they've gained enough XP to level up (using a method we'll implement later). Finally, we save the character's state to the database and return a tuple of the results of this action.

Let's define `ready_to_level_up()` next:

```python
    def ready_to_level_up(self):
        if self.level == self.level_cap: # zero values if we've ready the level cap
            return (False, 0)

        xp_needed = (self.level)*10
        return (self.xp &amp;gt;= xp_needed, xp_needed-self.xp) #(ready, XP needed)
```

This method merely checks whether the current XP is greater than or equal to ten times the character's level. Characters will need 10 XP to advance to level 2, 20 XP to advance to level 3, etc. The method returns a tuple containing a Boolean that indicates whether the character is ready to level up and the amount of XP still needed. As it does not change the character's state, we do not need a call to `save_to_db`.

Now that we're increasing the player's XP and checking whether they're ready to level up, we need a method to level them up. Add the following method:

```python
    def level_up(self, increase):
        ready, _ = self.ready_to_level_up()
        if not ready:
            return (False, self.level) # (not leveled up, current level)

        self.level += 1 # increase level
        setattr(self, increase, getattr(self, increase)+1) # increase chosen stat

        self.hp = self.max_hp #refill HP

        # Save to DB after state change
        self.save_to_db()

        return (True, self.level) # (leveled up, new level)
```

After ensuring that the player is ready to level up, we increase their level and use Python built-ins [`setattr`](https://docs.python.org/3/library/functions.html#setattr) and [`getattr`](https://docs.python.org/3/library/functions.html#getattr) to increment one of the character's stats. We reset their HP to the max value, save the state, and finally return the outcome of the action (a tuple indicating whether leveling up succeeded and what the character's new level is).

The last method we need is `die()`, which will be called when a character is defeated in battle. We could handle player death in a few different ways, but for the sake of simplicity, we'll just delete the character from the database.

```python
    def die(self, player_id):
        if self.user_id in db["characters"].keys():
            del db["characters"][self.user_id]
```

That's a wrap for our game's logic. Next, we'll integrate with Discord and make our game playable.

## Creating a Discord application

Discord will serve as the display layer for our game, allowing players to issue commands and see the results of those commands.

Open another browser tab and visit the [Discord Developer Portal](http://discordapp.com/developers/applications). Log in with your Discord account, or create one if you haven't already. Keep your repl open – we'll return to it soon.

Once you're logged in, create a new application. Give it a name like "MyRPG".

![Discord create app](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/discord-create-app.png)

Discord applications can interact with Discord in several different ways, not all of which require bots, so creating one is optional. That said, we'll need one for this project. Let's create a bot.

1. Click on **Bot** in the menu on the left-hand side of the page.
2. Click **Add Bot**.
3. Give your bot a username (such as "RPGBot").
4. Click **Reset Token** and then **Yes, do it!**.
4. Copy the token that appears just under your bot's username.

![Discord create bot](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/discord-create-bot.png)

The token you just copied is required for the code in our repl to interface with Discord's API. Return to your repl and open the Secrets tab in the left sidebar. Create a new secret with `DISCORD_TOKEN` as its key and the token you copied as its value.


![secret token](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/secret-token.png)


Once you've done that, return to the Discord developer panel. We need to finish setting up our bot.

You can leave the **Public Bot** option enabled or disable it, depending on whether you'd like other people to be able to find and install your bot on their server. Keep in mind that bots on 100 or more servers have to go through a special verification and approval process.

![Public Bot option](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/public-bot.png)

Next, we need to configure access to privileged [Gateway Intents](https://discord.com/developers/docs/topics/gateway#gateway-intents). Depending on a bot's functionality, it will require access to different events and sources of data. Events involving users' actions and the content of their messages are considered more sensitive and need to be explicitly enabled.

For this bot to work, we'll need the **Message Content Intent**, which will allow our bot to see the content of users' messages. Toggle it to the on position and save changes when prompted.

![Bot intents](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/bot-intents.png)

Now that we've created our application and its bot, we need to add it to a server. We'll walk you through creating a test server for this tutorial, but you can also use any server you've created in the past, as long as the other members won't get too annoyed about it becoming a bot testing ground. You can't use a server that you're just a normal user on, as adding bots requires special privileges.

Open [Discord](http://discord.com) in your browser. You should already be logged in. Then click on the **+** icon in the leftmost panel to create a new server. Alternatively, open an existing server you own.

In a separate tab, return to the [Discord Dev Portal](https://discord.com/developers/applications) and open your application. Then follow these steps to add your bot to your server:

1. Click on **OAuth2** in the left sidebar.
2. In the menu that appears under **OAuth2**, select **URL Generator**.
3. Under **Scopes**, mark the checkbox labelled *bot*.
4. Under **Bot Permissions**, mark the checkboxes labelled *Read Messages/View Channels* and *Send Messages*.
    ![Bot permissions](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/bot-permissions.png)

5. Scroll down and copy the URL under **Generated URL**.
6. Paste the URL in your browser's navigation bar and hit enter.
7. On the page that appears, select your server from the drop-down box and click **Continue**.
8. When prompted about permissions, click **Authorize**, and complete the CAPTCHA.
    ![Bot connect](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/bot-connect.png)

9. Return to your Discord server. You should see that your bot has just joined.

Now that we've done the preparatory work, it's time to write some code. Return to your repl for the next section.

## Writing the Discord bot code 

We'll be using [discord.py](https://discordpy.readthedocs.io/en/stable/) to interface with Discord's API using Python. Open `main.py` in your repl and add the following code:

```python
import os, discord
from discord.ext import commands

from replit import db
from game import *

DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")

bot = commands.Bot(command_prefix="!")

@bot.event
async def on_ready():
    print(f"{bot.user} has connected to Discord!")

bot.run(DISCORD_TOKEN)
```

First, we import the Python libraries we'll need, including discord.py and its [commands extension](https://discordpy.readthedocs.io/en/stable/ext/commands/commands.html), as well as our database and the contents of `game.py`.

We then retrieve the value of the `DISCORD_TOKEN` environment variable, which we set in our repl's Secrets tab above. Following that, we instantiate a [`Bot` object](https://discordpy.readthedocs.io/en/stable/ext/commands/api.html#discord.ext.commands.Bot). We'll use this object to listen for Discord events and respond to them. For the most part, we'll be responding to *commands*: messages from users which start with `!` (the `command_prefix` we specified when creating our `Bot` object).

However, the first event we're interested in is not a command. The [`on_ready()`](https://discordpy.readthedocs.io/en/stable/api.html#discord.on_ready) event will trigger when our bot logs onto Discord (the `@bot.event` [decorator](https://realpython.com/primer-on-python-decorators/) ensures this). All this event will do is print a message to our repl's console, telling us that the bot has connected.

Note that we've prepended `async` to the function definition – this makes our `on_ready()` function into a [coroutine](https://docs.python.org/3/library/asyncio-task.html). Coroutines are largely similar to functions, but may not execute immediately, and must be invoked with the `await` keyword. Using coroutines makes our program [asynchronous](https://realpython.com/async-io-python/#the-10000-foot-view-of-async-io), which means it can continue executing code while waiting for the results of a long-running function, usually one that depends on input or output. If you've used JavaScript before, you'll recognize this style of programming.

The final line in our file starts the bot, providing `DISCORD_TOKEN` to authenticate it. Run your repl now to see it in action. Once it's started, return to your Discord server. You should see that your bot user is now online.

![Online bot](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/online-bot.png)

## Handling user commands

Now we can start writing the handlers for our game's actions, such as `!create`, `!hunt`, and `!fight`.

The discord.py commands extension allows us to define command handlers using the `@bot.command` decorator. Without this, we'd have to manually parse the content of all user messages to determine whether a command has been issued, as was necessary for our [role assignment bot tutorial](https://docs.replit.com/tutorials/discord-role-bot).

### Character creation

First, we'll implement our character creation command, `!create`. Add the following code to `main.py` below the definition of `on_ready()`:

```python
# Commands
@bot.command(name="create", help="Create a character.")
async def create(ctx, name=None):
    user_id = ctx.message.author.id

    # if no name is specified, use the creator's nickname
    if not name:
        name = ctx.message.author.name

```

The `@bot.command` decorator will ensure that our function is invoked when a user types a message starting with `!create`. We also use it to define some help text – the commands extension provides a default `!help` command, and each command we define can have two types of explanatory text:

* `brief`: A short description of the command that will show alongside other defined commands when the user types `!help`.
* `help`: A longer description of the command that will show when the user types `!help name_of_command`. 

In the absence of `brief`, the `help` text will be used in both cases, though it may be truncated for the output of `!help`.

Our `create` function takes two parameters:

* `ctx`: This is the *invocation context*, a special object containing information such as the user who called the command, the server it was called in, and the files attached to the calling message if any. All commands must take this parameter.
* `name`: This will be the name for the character we're creating and is an optional parameter.

The function body retrieves the Discord user ID of the user who issued the `!create` command. It then checks whether a `name` parameter was provided. If not, it sets `name` to the name of the user.

Next, we'll create an instance of `Character` with some starter stats and save it to our repl's database. Add the following code to the body of `create`:

```python
    # create characters dictionary if it does not exist
    if "characters" not in db.keys():
        db["characters"] = {}

    # only create a new character if the user does not already have one
    if user_id not in db["characters"] or not db["characters"][user_id]:
        character = Character(**{
            "name": name,
            "hp": 16,
            "max_hp": 16,
            "attack": 2,
            "defense": 1,
            "mana": 0,
            "level": 1,
            "xp": 0,
            "gold": 0,
            "inventory": [],
            "mode": GameMode.ADVENTURE,
            "battling": None,
            "user_id": user_id
        })
        character.save_to_db()
        await ctx.message.reply(f"New level 1 character created: {name}. Enter `!status` to see your stats.")
    else:
        await ctx.message.reply("You have already created your character.")
```

After creating and saving a new character, or failing to do so, this code sends a [reply](https://support.discord.com/hc/en-us/articles/360057382374-Replies-FAQ) to the message that invoked it. As our game logic primarily resides in the `game.py` file, constructing these commands will largely be a matter of constructing and sending replies informing the player of what's happened in the game world. 

### Character status

Next, we'll implement the `!status` command, which players will use to view their character's current statistics, inventory, and game mode. To convey this information compactly and attractively, we'll use an [embed](https://python.plainenglish.io/send-an-embed-with-a-discord-bot-in-python-61d34c711046) rather than a plain Discord message.

Embeds are usually used to provide link previews, but can also be constructed from scratch, providing a powerful tool for bots to display richly formatted information of any kind. This is what our embed will look like:

![Status embed](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/status-embed.png)

Add the following function below the definition `create()`:

```python
@bot.command(name="status", help="Get information about your character.")
async def status(ctx):
    character = load_character(ctx.message.author.id)

    embed = status_embed(ctx, character)
    await ctx.message.reply(embed=embed)
```

This function retrieves the player's character from the database, passes it and the current context to a function that will construct and return an embed, and then replies with the embed. We'll implement the two methods we've used just above the definition of `on_ready()`. Go there now.

First, `load_character()`, which reads from the database and creates an instance of `Character` using the results:

```python
# Helper functions
def load_character(user_id):
    return Character(**db["characters"][str(user_id)])
```

Second, `status_embed()`:

```python
MODE_COLOR = {
    GameMode.BATTLE: 0xDC143C,
    GameMode.ADVENTURE: 0x005EB8,
}
def status_embed(ctx, character):

    # Current mode
    if character.mode == GameMode.BATTLE:
        mode_text = f"Currently battling a {character.battling.name}."
    elif character.mode == GameMode.ADVENTURE:
        mode_text = "Currently adventuring."

    # Create embed with description as current mode
    embed = discord.Embed(title=f"{character.name} status", description=mode_text, color=MODE_COLOR[character.mode])
    embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar_url)
```

Just above the function definition, we've created a dictionary that maps game modes to colors. We'll use this to change the color of the vertical bar on the left side of the embed.

In the function itself, we first check the game mode. This will determine the embed's description text, a paragraph that appears just below the embed's title. Once that's done, we create the embed with [`discord.Embed`](https://discordpy.readthedocs.io/en/stable/api.html#discord.Embed), setting the title, description, and color. We then use [`set_author()`](https://discordpy.readthedocs.io/en/stable/api.html#discord.Embed.set_author) to include the calling user's name and profile picture at the top of the embed.

Next, we will construct the embed's [`fields`](https://discordpy.readthedocs.io/en/stable/api.html#discord.Embed.fields). You can think of these as individual text boxes, which will be displayed below the description. We'll start with a stats field:

```python
    # Stats field
    _, xp_needed = character.ready_to_level_up()

    embed.add_field(name="Stats", value=f"""
**HP:**    {character.hp}/{character.max_hp}
**ATTACK:**   {character.attack}
**DEFENSE:**   {character.defense}
**MANA:**  {character.mana}
**LEVEL:** {character.level}
**XP:**    {character.xp}/{character.xp+xp_needed}
    """, inline=True)
```

We've used [`add_field()`](https://discordpy.readthedocs.io/en/stable/api.html#discord.Embed.add_field) to create a field with the title "Stats" that contains a listing of all the player's important stats. Note the call to `character.ready_to_level_up()`, so that we can show the player how much XP they need to advance to the next level. We've also set `inline=True`, which allows us to display fields as columns.

Our next column will show the player's inventory:

```python
    # Inventory field
    inventory_text = f"Gold: {character.gold}\n"
    if character.inventory:
        inventory_text += "\n".join(character.inventory)

    embed.add_field(name="Inventory", value=inventory_text, inline=True)
```

There's currently no logic in our game to put items into characters' inventories, but you should see characters' gold increase as they defeat enemies.

Finally, we'll return the embed.

```python
    return embed 
```

Run your repl now, and then switch tabs to your Discord server. Create a character with `!create` and view its status with `!status`.

![Create character](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/create-character.png)

### Battles

Next, let's implement our battle commands, starting with `!hunt`. Add the following function definition below the body of `status()`:

```python
@bot.command(name="hunt", help="Look for an enemy to fight.")
async def hunt(ctx):
    character = load_character(ctx.message.author.id)

    if character.mode != GameMode.ADVENTURE:
        await ctx.message.reply("Can only call this command outside of battle!")
        return

    enemy = character.hunt()

    # Send reply
    await ctx.message.reply(f"You encounter a {enemy.name}. Do you `!fight` or `!flee`?")
```

This function is fairly simple: We load the character, ensure they're not currently in a battle, call `hunt()` to generate a random enemy, and reply to the player with a message about what they're fighting and which commands they can use.

Next, we'll implement `!fight`:

```python
@bot.command(name="fight", help="Fight the current enemy.")
async def fight(ctx):
    character = load_character(ctx.message.author.id)

    if character.mode != GameMode.BATTLE:
        await ctx.message.reply("Can only call this command in battle!")
        return

    # Simulate battle
    enemy = character.battling

    # Character attacks
    damage, killed = character.fight(enemy)
    if damage:
        await ctx.message.reply(f"{character.name} attacks {enemy.name}, dealing {damage} damage!")
    else:
        await ctx.message.reply(f"{character.name} swings at {enemy.name}, but misses!")
```

Here we load the character, ensure they're currently in battle mode, and then have them fight the enemy, returning a message about the damage inflicted. We also account for the small chance that they will inflict no damage.

Next, we need to check if the enemy was killed by the attack. Add the following code to the bottom of the function:

```python
    # End battle in victory if enemy killed
    if killed:
        xp, gold, ready_to_level_up = character.defeat(enemy)

        await ctx.message.reply(f"{character.name} vanquished the {enemy.name}, earning {xp} XP and {gold} GOLD. HP: {character.hp}/{character.max_hp}.")

        if ready_to_level_up:
            await ctx.message.reply(f"{character.name} has earned enough XP to advance to level {character.level+1}. Enter `!levelup` with the stat (HP, ATTACK, DEFENSE) you would like to increase. e.g. `!levelup hp` or `!levelup attack`.")

        return
```

Here we call `character.defeat()` to handle the enemy's death and return appropriate replies. Again, we've already written all the game logic, so all this code needs to do is display it to the player. Once we've sent the reply, we return from the function.

After the player character attacks, we need to have the enemy fight back. Add the following code below the `if killed` block:

```python
    # Enemy attacks
    damage, killed = enemy.fight(character)
    if damage:
        await ctx.message.reply(f"{enemy.name} attacks {character.name}, dealing {damage} damage!")
    else:
        await ctx.message.reply(f"{enemy.name} tries to attack {character.name}, but misses!")

    character.save_to_db() #enemy.fight() does not save automatically
```

This is almost identical to the player's attack code, but with `enemy.fight(character)` instead of `character.fight(enemy)`. But because `enemy.fight()` does not save to the database after changing the game state, we must do this manually.

Next, we need some code to check whether the player character was killed in the attack. Add the following lines to your function:

```python
    # End battle in death if character killed
    if killed:
        character.die()

        await ctx.message.reply(f"{character.name} was defeated by a {enemy.name} and is no more. Rest in peace, brave adventurer.")
        return
```

Here we delete the character from the database, send a message of condolences, and return from the function.

The last case we need to handle is the most common one, where neither the player nor their enemy has died. We'll deal with this by sending a final message to close out this round of fighting.

```python
    # No deaths, battle continues
    await ctx.message.reply(f"The battle rages on! Do you `!fight` or `!flee`?")
```

That's it for `!fight` – now we need `!flee`! Add the following function below the one you just finished:

```python
@bot.command(name="flee", help="Flee the current enemy.")
async def flee(ctx):
    character = load_character(ctx.message.author.id)

    if character.mode != GameMode.BATTLE:
        await ctx.message.reply("Can only call this command in battle!")
        return

    enemy = character.battling
    damage, killed = character.flee(enemy)

    if killed:
        character.die()
        await ctx.message.reply(f"{character.name} was killed fleeing the {enemy.name}, and is no more. Rest in peace, brave adventurer.")
    elif damage:
        await ctx.message.reply(f"{character.name} flees the {enemy.name}, taking {damage} damage. HP: {character.hp}/{character.max_hp}")
    else:
        await ctx.message.reply(f"{character.name} flees the {enemy.name} with their life but not their dignity intact. HP: {character.hp}/{character.max_hp}")
```

Once again, this function loads the character, checks that the game mode is appropriate for the invoked command, and then invokes the appropriate method in `Character`. We finish off the function by providing for three possible outcomes of an attempt to flee: the character dies, the character flees taking damage, and the character flees unscathed.

Rerun your repl and try hunting, fighting, and fleeing.

![Hunt and fight](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/hunt-and-fight.png)

### Leveling up

Next, we need to implement `!levelup`. Add the following code below the definition of `!flee`:

```python
@bot.command(name="levelup", help="Advance to the next level. Specify a stat to increase (HP, ATTACK, DEFENSE).")
async def levelup(ctx, increase):
    character = load_character(ctx.message.author.id)

    if character.mode != GameMode.ADVENTURE:
        await ctx.message.reply("Can only call this command outside of battle!")
        return

    ready, xp_needed = character.ready_to_level_up()
    if not ready:
        await ctx.message.reply(f"You need another {xp_needed} to advance to level {character.level+1}")
        return

    if not increase:
        await ctx.message.reply("Please specify a stat to increase (HP, ATTACK, DEFENSE)")
        return
```

This function takes `increase`, which will be a string containing the stat to increase. After our standard character load and mode check, we do some error handling. First, we reject the command if the character does not have enough XP to level up, and then we reject the command if the player has not specified a stat to increase.

Next, we need to parse the value of `increase`. Add the following code to your function:

```python
    increase = increase.lower()
    if increase == "hp" or increase == "hitpoints" or increase == "max_hp" or increase == "maxhp":
        increase = "max_hp"
    elif increase == "attack" or increase == "att":
        increase = "attack"
    elif increase == "defense" or increase == "def" or increase == "defence":
        increase = "defense"
```

We're allowing players to increase their characters' HP, attack, or defense stats only. To make our game as user-friendly as possible, we accept a few different words for each of those stats.

Finally, we call the character's `level_up()` method and report on its results:

```python
    success, new_level = character.level_up(increase)
    if success:
        await ctx.message.reply(f"{character.name} advanced to level {new_level}, gaining 1 {increase.upper().replace('_', ' ')}.")
    else:
        await ctx.message.reply(f"{character.name} failed to level up.")
```

Rerun your repl and test this out. If you'd prefer to avoid grinding, edit your character creation code temporarily to increase the initial amount of XP.

![Level up](https://replit-docs-images.bardia.repl.co/images/tutorials/discord-rpg-bot/levelup.png)

### Character death

There's one more command in our design specification that we have not yet implemented: `!die`. Players will use this command if they want to start the game over with a new character. Add the following code to define it:

```python
@bot.command(name="die", help="Destroy current character.")
async def die(ctx):
    character = load_character(ctx.message.author.id)

    character.die()

    await ctx.message.reply(f"Character {character.name} is no more. Create a new one with `!create`.")
```

### Character reset

Before we finish up, we're going to implement one last, special command: `!reset`. This command will delete a player's character and then immediately create a new character. Unlike the commands above, this will be a testing command, for use by the developer rather than players. Add the following code below the definition of `die()`:

```python
@bot.command(name="reset", help="[DEV] Destroy and recreate current character.")
async def reset(ctx):
    user_id = str(ctx.message.author.id)

    if user_id in db["characters"].keys():
        del db["characters"][user_id]

    await ctx.message.reply(f"Character deleted.")
    await create(ctx)
```

Unlike in `die()`, we're deleting from the database directly rather than using `character.die()` method. This is useful because further development of the game might cause errors in `Character.__init__()`, rendering the `die()` method temporarily unusable.

## Where next?

We've created a text-based RPG that can be played on a Discord server, but our game is pretty barebones. We could expand it in a few different ways:

* Implement a magic system using the `mana` attribute.
* Implement an economy in which characters can buy and sell items, such as health potions.
* Flesh out the battle system, with multiple attack types, multiple enemies per battle, and the ability to use items that weaken enemies or temporarily strengthen characters.
* Create a game world with different areas the player can travel to, containing different enemies.
* Implement NPCs the player can talk to and receive quests from.
* Enable player versus player combat.

Discord bot code can be hosted on Replit permanently, but you'll need to use an [Always-on](https://docs.replit.com/hosting/enabling-always-on) repl to keep it running 24/7.

You can find our repl below:
https://replit.com/@ritza/DiscordRPG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>python</category>
      <category>discord</category>
      <category>rpg</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Make Snake with vanilla JavaScript</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Wed, 13 Jul 2022 11:02:00 +0000</pubDate>
      <link>https://dev.to/ritza/make-snake-with-vanilla-javascript-2h2f</link>
      <guid>https://dev.to/ritza/make-snake-with-vanilla-javascript-2h2f</guid>
      <description>&lt;p&gt;Snake is a simple game that is great for learning the basics of game development. It can be basic, like the classic Nokia cellphone Snake game, or it can be more complex with added features such as obstacles or making it a two-player game. &lt;/p&gt;

&lt;p&gt;In this tutorial, we'll implement a simple version of Snake, using HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EYj9xb0B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/snake-game.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EYj9xb0B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/snake-game.gif" alt="Snake game" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;We'll use the &lt;a href="https://replit.com/"&gt;Replit&lt;/a&gt; web IDE to create our Snake game. This means that you can do this tutorial in the browser, and it will be easy to share your game online. If you don't already have a Replit account, &lt;a href="https://replit.com/signup"&gt;create one now&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the Snake game, we'll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a grid with cells - the game grid where the snake and food will be.&lt;/li&gt;
&lt;li&gt;Move the snake from cell to cell.&lt;/li&gt;
&lt;li&gt;Control the snake's direction.&lt;/li&gt;
&lt;li&gt;Randomly place food items.&lt;/li&gt;
&lt;li&gt;Detect when the snake's head touches the food.&lt;/li&gt;
&lt;li&gt;Detect when the snake hits the wall of the game grid.&lt;/li&gt;
&lt;li&gt;Detect when the snake hits itself.&lt;/li&gt;
&lt;li&gt;Keep track of the score - how many food items are eaten.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started and create a new project in &lt;a href="https://replit.com/"&gt;Replit&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a new project in Replit
&lt;/h2&gt;

&lt;p&gt;Head over to &lt;a href="https://replit.com/"&gt;Replit&lt;/a&gt; and create a new repl. Choose &lt;strong&gt;HTML, CSS, JS&lt;/strong&gt; as your project type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CHRZkbRg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/replit-how-to-use.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CHRZkbRg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/replit-how-to-use.png" alt="Replit - how to create a new project" width="880" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the folder structure
&lt;/h2&gt;

&lt;p&gt;Our main JavaScript module will be the &lt;code&gt;script.js&lt;/code&gt; file. We'll have a utility module, &lt;code&gt;utils.js&lt;/code&gt; for some utility functions. A utility function is a generic function that performs a task that we could reuse in another project. We'll create two utility functions: A wait function that pauses an async function for a defined amount of time and a function to get a random element from an array.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;utils.js&lt;/code&gt; file so that you have the following files in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|__ index.html
|__ style.css
|__ script.js
|__ utils.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding the HTML
&lt;/h2&gt;

&lt;p&gt;Let's add the HTML needed for the Snake game. Replace the code in your &lt;code&gt;index.html&lt;/code&gt; file with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"style.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"script.js"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Snake - vanilla JavaScript&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"start-btn"&lt;/span&gt; &lt;span class="na"&gt;data-content=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Start&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Score: &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"keys-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ArrowUp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"rotate(0, 5, 5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5,4 L7,6 L3,6 L5,4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ArrowLeft"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"rotate(-90, 5, 5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5,4 L7,6 L3,6 L5,4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ArrowDown"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"rotate(180, 5, 5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5,4 L7,6 L3,6 L5,4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ArrowRight"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"rotate(90, 5, 5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5,4 L7,6 L3,6 L5,4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;type=module&lt;/code&gt; attribute on the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag for the &lt;code&gt;script.js&lt;/code&gt; file allows us to use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"&gt;module&lt;/a&gt; features in our script. The &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with a &lt;code&gt;class&lt;/code&gt; of &lt;code&gt;grid&lt;/code&gt; is the game grid. We'll create the cells of the game grid using JavaScript. The &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with an &lt;code&gt;id&lt;/code&gt; of &lt;code&gt;keys&lt;/code&gt; contains the up, down, left, and right buttons that we'll use to change the snake's direction on a mobile device. The icons for the keys are created using SVGs. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the CSS
&lt;/h2&gt;

&lt;p&gt;Now let's add some basic styling. Replace the code in your &lt;code&gt;style.css&lt;/code&gt; file with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f78400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-button-after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#424246&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#373737&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Trebuchet MS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;'Lucida Sans Unicode'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;'Lucida Grande'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;'Lucida Sans'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-border&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.start-btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.15rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-button&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.6rem&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.start-btn&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data-content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inset&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-border&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-button-after&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-0.2rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;-0.2rem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.start-btn&lt;/span&gt;&lt;span class="nd"&gt;:hover::after&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.start-btn&lt;/span&gt;&lt;span class="nd"&gt;:focus::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.grid&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.4s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.food-item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.keys-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s2"&gt;'.    up   .'&lt;/span&gt;
    &lt;span class="s2"&gt;'left down right'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.keys-container&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-button-after&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.keys-container&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#ArrowUp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#ArrowDown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#ArrowLeft&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#ArrowRight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.shake&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shake&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;shake&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;10&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;90&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate3d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;20&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;80&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate3d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;30&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;70&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate3d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-4px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;40&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;60&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate3d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties"&gt;CSS variables&lt;/a&gt; to store the colors that we'll use. The &lt;code&gt;body&lt;/code&gt; selector uses Flexbox to center the snake game grid on the screen. The start button, which has a &lt;code&gt;class&lt;/code&gt; of &lt;code&gt;start-btn&lt;/code&gt;, is styled using the &lt;code&gt;::after&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements"&gt;pseudo-element&lt;/a&gt;. This is done to give the button a layered effect. The &lt;code&gt;::after&lt;/code&gt; pseudo-element is on top of the button, the text is set using the &lt;code&gt;content&lt;/code&gt; CSS property. We use a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*"&gt;data attribute&lt;/a&gt; to label the button. The data attribute &lt;code&gt;data-content&lt;/code&gt; was added to the start button in the &lt;code&gt;index.html&lt;/code&gt; file.  The shake &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes"&gt;keyframe animation&lt;/a&gt; will be used to shake the game grid when the snake hits the walls or itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the JavaScript structure
&lt;/h2&gt;

&lt;p&gt;Replace the code in your &lt;code&gt;script.js&lt;/code&gt; file with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomElementFromArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foodItemsArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🐁&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍇&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍉&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍈&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍓&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍍&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍌&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🥝&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍏&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍎&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍔&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🍅&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🥚&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We import the utility functions that we'll soon create. The &lt;code&gt;foodItemsArray&lt;/code&gt; variable contains the food items that will be randomly selected and added to the game grid. You can change the food item emojis if you want to. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding utility functions
&lt;/h2&gt;

&lt;p&gt;Add the following lines to the &lt;code&gt;utils.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;randomElementFromArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;wait&lt;/code&gt; function takes in a time, in milliseconds, as an argument. It returns a promise that resolves after the time passed in has elapsed. We'll use it to pause an async function that will be used in the game. The &lt;code&gt;randomElementFromArray&lt;/code&gt; function takes in an array as an argument and returns a randomly selected element from the array. We'll use it to randomly select a food item from the &lt;code&gt;foodItemsArray&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;Now try running the code, by pushing the &lt;code&gt;Run&lt;/code&gt; button at the top of the Replit window. You should see the start and arrow buttons:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j0sNAnT_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/basic-code.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j0sNAnT_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/basic-code.png" alt="Basic code - view in the browser" width="871" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the game logic
&lt;/h2&gt;

&lt;p&gt;Before we start coding the game logic, let's look at an overview of how we'll design the game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game overview
&lt;/h2&gt;

&lt;p&gt;There are four main functions we'll use to make the game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;startGame&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gameLoop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createFood&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;moveSnake&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll run the &lt;code&gt;startGame&lt;/code&gt; function when the user clicks start button. In this function, the game grid cells are cleared, the snake and a randomly positioned food item are added to the game grid, and the &lt;code&gt;gameLoop&lt;/code&gt; function is run. We run &lt;code&gt;gameLoop&lt;/code&gt; every x milliseconds by using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/setInterval"&gt;&lt;code&gt;setInterval&lt;/code&gt;&lt;/a&gt;. The snake moves one cell in each game loop.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;gameLoop&lt;/code&gt; function, we check for collisions with the wall of the game grid or the snake itself. We also check if the snake head is in the same cell as a food item. If it is, we remove the food item and create a new randomly positioned food item using the &lt;code&gt;createFood&lt;/code&gt; function. The snake length is increased by adding a new tail index to the &lt;code&gt;currentSnake&lt;/code&gt; array. We also increase the score.&lt;/p&gt;

&lt;p&gt;The snake's body, stored in the &lt;code&gt;currentSnake&lt;/code&gt; variable, is represented as an array indicating the cell indexes of the snake. The first element in the array is the snake's head, and the last element in the array is the snake's tail. The game grid cells containing the snake are given a &lt;code&gt;class&lt;/code&gt; of &lt;code&gt;snake&lt;/code&gt; so that we can detect collisions. We use the cell index positions to style the snake: giving the snake cells a background color and giving the snake head two eyes. &lt;/p&gt;

&lt;p&gt;The game grid cell containing the food item is given a &lt;code&gt;class&lt;/code&gt; of &lt;code&gt;food-item&lt;/code&gt; so that we can detect when the snake collides with this (and eats the food). The cell index of the food item is stored so that we can find the food item cell and place the food item emoji in it. When the game grid is cleared at the start of the game, these added classes are removed from each cell and the cell's inner text content is cleared.&lt;/p&gt;

&lt;p&gt;The user can move the snake using the keyboard arrow keys or by clicking the arrow keys on the screen. We listen for the "keydown", "mousedown" or "touchstart" events on the arrow keys and use the &lt;code&gt;moveSnake&lt;/code&gt; function to change the direction of the snake. These events change the &lt;code&gt;direction&lt;/code&gt; variable value. This variable determines which direction the snake moves. In each game loop, the &lt;code&gt;direction&lt;/code&gt; value will be added to the cell index position of the snake's head to give the snake a new head position. The snake's tail will be removed. The &lt;code&gt;direction&lt;/code&gt; value indicates the change in index position required for the snake's head to move one cell in the current direction, The &lt;code&gt;direction&lt;/code&gt; values are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; right: +1&lt;/li&gt;
&lt;li&gt;left: -1&lt;/li&gt;
&lt;li&gt;up: -10&lt;/li&gt;
&lt;li&gt;down: +10&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jD_I1Liz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/vanilla-js-snake-game-logic-outline.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jD_I1Liz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/vanilla-js-snake-game-logic-outline.png" alt="Vanilla JS snake game logic outline" width="880" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's create the game. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the game grid
&lt;/h2&gt;

&lt;p&gt;Add the following lines to the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// game display elements&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.grid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scoreDisplay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.start-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyBtns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.keys-container button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// game variables&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numCells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// create grid cells&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.grid div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first get the game display HTML elements from the DOM. We'll only use the &lt;code&gt;grid&lt;/code&gt; element now. We then create some of the game variables that we'll need. We set the &lt;code&gt;width&lt;/code&gt; to 10 cells and set the game grid's width and height based on the &lt;code&gt;width&lt;/code&gt; variable. The square grid is 200 px wide.&lt;/p&gt;

&lt;p&gt;We then create the grid cells using a for loop. The number of cells is equal to the &lt;code&gt;width&lt;/code&gt; squared.  We create each cell using the &lt;code&gt;createElement&lt;/code&gt; method. Each square cell is 20 px wide. We then append each cell to the grid. The grid is a flex container and the cells, which are flex items, wrap after each row, which is 10 cells long. We then select the cells using the &lt;code&gt;querySelectorAll&lt;/code&gt; method for later use.&lt;/p&gt;

&lt;p&gt;Run your code again and you should now be able to see our game grid as a square below the "Start" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JcKwGf0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/game-grid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JcKwGf0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/game-grid.png" alt="Game grid" width="681" height="685"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the snake
&lt;/h2&gt;

&lt;p&gt;Add the following game variables in the &lt;code&gt;// game variables&lt;/code&gt; section in the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentSnake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;snakeColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;snakeColorIncrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add the following function and event listener to the bottom of the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;startGame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;currentSnake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;snakeColor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;snakeColorIncrement&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`hsl(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;snakeColor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, 100%, 50%)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;startBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startGame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define the snake's position on the game grid as an array called &lt;code&gt;currentSnake&lt;/code&gt;. This indicates the index position of the snake's head [2], body [1] and tail [0] in the game grid. All of the middle elements are the snake's body. At the start of the game, the snake only has one body segment. We also style the snake's body segments using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsla"&gt;&lt;code&gt;hsla()&lt;/code&gt;functional notation&lt;/a&gt;. This is done to give our snake a "rainbow" color that changes as it moves. We create a random color value for the hue called &lt;code&gt;snakeColor&lt;/code&gt;. This has a value between 0 degrees to 360 degrees. It represents an angle of the color circle: Red = 0 degrees = 360 degrees,  green = 120 degrees, blue = 240 degrees, etc. &lt;/p&gt;

&lt;p&gt;We loop through the snake segments and give each cell that contains the snake a color. The color changes for each segment as the &lt;code&gt;snakeColor&lt;/code&gt; increases in each loop. We also add the &lt;code&gt;class&lt;/code&gt; of &lt;code&gt;snake&lt;/code&gt; to the game grid cells containing the snake.  &lt;/p&gt;

&lt;p&gt;Now run the code again and then press the "Start" button in the game UI to see the snake on the game grid:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NmfRGcNS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/adding-snake.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NmfRGcNS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/adding-snake.png" alt="Adding snake" width="721" height="885"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each time you press the "Start" button, the snake's color will change. Inspect the snake cells in your browser dev tools. You will see the added &lt;code&gt;snake&lt;/code&gt; class and the added &lt;code&gt;background&lt;/code&gt; color.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving the snake
&lt;/h2&gt;

&lt;p&gt;Let's get the snake moving by adding the &lt;code&gt;gameLoop&lt;/code&gt; function. Add the following game variables in the &lt;code&gt;// game variables&lt;/code&gt; section in the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;intervalTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// determines speed - frequency of game loop calls&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We set the initial &lt;code&gt;direction&lt;/code&gt; to one. In each game loop, the snake's index position in the game grid will increase by one, so the snake will move to the right. The game loop will run five times per second as the interval time is set to 200 ms. The &lt;code&gt;interval&lt;/code&gt; variable will be the returned &lt;code&gt;intervalID&lt;/code&gt; from the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/setInterval"&gt;&lt;code&gt;setInterval&lt;/code&gt;&lt;/a&gt; function that we'll use to run the game loop continuously. The &lt;code&gt;interval&lt;/code&gt; variable will be used to cancel the interval so that the game loop is stopped when there is a collision. &lt;/p&gt;

&lt;p&gt;Add the following code inside the &lt;code&gt;startGame&lt;/code&gt; function, at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the start of a game, we remove the snake from the game grid cells. We clear the interval to remove any previous interval and set the direction to it's initial value.&lt;/p&gt;

&lt;p&gt;Now add the &lt;code&gt;gameLoop&lt;/code&gt; function below the &lt;code&gt;startGame&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;gameLoop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unshift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// gives direction to the head&lt;/span&gt;
  &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👀&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;snakeColor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;snakeColorIncrement&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`hsl(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;snakeColor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, 100%, 50%)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first clear the &lt;code&gt;innerText&lt;/code&gt; of the current snake head to remove the emoji eyes from the current snake head. We then remove the tail and add a new head. To get the index of the new head position in the game grid, we add a new head that has an index of the current snake head plus the current direction. We then style the new head by giving it emoji eyes and a background color.&lt;/p&gt;

&lt;p&gt;We also need to call the game loop at the start of the game. Add the following to the &lt;code&gt;startGame&lt;/code&gt; function, at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gameLoop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;intervalTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now press the start button to see the snake move along the game grid:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0ZG-e6Ba--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/moving-snake.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0ZG-e6Ba--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/moving-snake.gif" alt="Moving snake" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The snake moves from index 0 to 99, incrementing its position by one in each game loop. &lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling the snake
&lt;/h2&gt;

&lt;p&gt;We'll add a "keydown" event listener and an event handler function to control the snake movement with our keyboard arrow keys. Add the following code below the &lt;code&gt;gameLoop&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;moveSnake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowRight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowLeft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowDown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleKeyMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowRight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowLeft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowDown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;moveSnake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleKeyMove&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added a "keydown" event listener that is handled by the &lt;code&gt;handleKeyMove&lt;/code&gt; function. The &lt;code&gt;handleKeyMove&lt;/code&gt; function calls the &lt;code&gt;moveSnake&lt;/code&gt; function if one of the arrow keys is pressed down. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;moveSnake&lt;/code&gt; function changes the snake's direction, depending on which arrow key was pressed.&lt;/p&gt;

&lt;p&gt;You will now be able to change the snake's direction using the arrow keys on your keyboard. However, the snake will be able to move through the walls and move through self. You will also get an error in your console if you go outside of the bounds of the grid by hitting the top or bottom wall. We'll fix this by detecting collisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting collisions with the walls and itself
&lt;/h2&gt;

&lt;p&gt;In the &lt;code&gt;gameLoop&lt;/code&gt; function, add the following code below the &lt;code&gt;cells[currentSnake[0]].innerText = '';&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="c1"&gt;// hits bottom wall&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="c1"&gt;// hits right wall&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="c1"&gt;// hits left wall&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="c1"&gt;// hits the top wall&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// hits itself&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This if statement checks if the current snake index is outside the bounds of the game grid. It also checks if the snake hit itself by checking if the next cell that the snake head will move to has the &lt;code&gt;snake&lt;/code&gt; class. If there is a collision, the game loop is stopped by clearing the interval and a shake animation is added by adding the &lt;code&gt;shake&lt;/code&gt; class to the game grid. This will make the game grid shake when there is a collision. This uses the shake keyframe animation that defined earlier in our &lt;code&gt;style.css&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;For the shake animation to work each time we restart a game, we need to remove the &lt;code&gt;shake&lt;/code&gt; class from the game grid at the start of each game. Do this by adding the following line inside of the &lt;code&gt;startGame()&lt;/code&gt; function, at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if your snake hits the wall or itself, it will stop the game loop and the game grid will shake. There are two bugs with the snake direction changes. If you are moving in one direction, then press the arrow key to move in the opposite direction, the snake will hit itself. Also, if your snake is going in one direction and then you make two quick 90-degree turns so that the snake moves in the opposite direction, the snake will hit itself. Let's change the &lt;code&gt;moveSnake&lt;/code&gt; function to fix these issues. Replace your &lt;code&gt;moveSnake&lt;/code&gt; function with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;moveSnake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowRight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;directionVal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowLeft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;directionVal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;directionVal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDirection&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowDown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;directionVal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;directionVal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each direction change, we now check that the direction change is not the opposite of the current direction. We also check that the direction change does not cause the snake's head to hit the first segment of its body.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding and eating food
&lt;/h2&gt;

&lt;p&gt;Let's add food items to the game grid. When the snake eats a food item,  the score will be increased by one and a new food item will be created. Add the following game variables in the &lt;code&gt;// game variables&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;foodItemIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// first cell&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;foodItemIndex&lt;/code&gt; is the cell index in the game grid that the food item will be added to. We'll randomly position it. &lt;/p&gt;

&lt;p&gt;Now let's define the &lt;code&gt;createFood&lt;/code&gt; function that we'll use to randomly add food items to our game grid. Add the following &lt;code&gt;createFood&lt;/code&gt; function above the &lt;code&gt;startGame&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createFood&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;foodItemIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;numCells&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foodItemIndex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;createFood&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;foodItemIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;food-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;foodItemIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;randomElementFromArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foodItemsArray&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this recursive async function, we create a random food item index position and add the food item to the game grid. This function is recursive to prevent the food item from being added in a position occupied by the snake. If the random index is on the snake, the &lt;code&gt;createFood&lt;/code&gt; function is called again to get another random index position. Our &lt;code&gt;wait&lt;/code&gt; utility function prevents the recursive function from being called too often by pausing the function for 100 ms before each recursive function call. &lt;/p&gt;

&lt;p&gt;Let's modify our &lt;code&gt;startGame&lt;/code&gt; function so that the food items are cleared at the start of the game, the score is reset and a food item is created. Add the following lines to the &lt;code&gt;startGame&lt;/code&gt; function, above the &lt;code&gt;interval = setInterval(gameLoop, intervalTime);&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;foodItemIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;food-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;foodItemIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;createFood&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;scoreDisplay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to detect if the food item is eaten and if so, add a new food item to the game grid. Add the following lines to the &lt;code&gt;gameLoop&lt;/code&gt; function, above the &lt;code&gt;cells[currentSnake[0]].classList.add('snake');&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;food-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;food-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;snakeColor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;snakeColorIncrement&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`hsl(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;snakeColor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, 100%, 50%)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;currentSnake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;scoreDisplay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;createFood&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The food item is 'eaten' by checking if the snakes head position cell contains the food item. If it does, we remove the 'food-item' class to remove the food item. We increase the snake's length by adding a new segment to the tail end of the snake. Then we increase the score, and create a new randomly positioned food item. &lt;/p&gt;

&lt;p&gt;The food items increase in size when they are added as we added a scale transform to the &lt;code&gt;food-item&lt;/code&gt; class in the &lt;code&gt;style.css&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Our snake game is almost complete! The last thing we need to do is make it mobile-friendly by making the on-screen arrow buttons functional. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8AEmWyzL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/snake-eating.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8AEmWyzL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/make-snake-game-vanilla-javascript/snake-eating.gif" alt="Snake eating food items" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the game mobile-friendly
&lt;/h2&gt;

&lt;p&gt;Add the following lines near the bottom of the &lt;code&gt;script.js&lt;/code&gt; file, just above the existing event listeners.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleButtonKeyMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;moveSnake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;keyBtns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyBtn&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousedown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleButtonKeyMove&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;touchstart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleButtonKeyMove&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each key button, we add two event listeners: "mousedown" and "touchstart". We use the &lt;code&gt;handleButtonKeyMove&lt;/code&gt; function to handle these events. We get the &lt;code&gt;id&lt;/code&gt; attributes of the on-screen buttons from the HTML &lt;code&gt;id&lt;/code&gt; attributes that we added in the &lt;code&gt;index.html&lt;/code&gt; file. These &lt;code&gt;id&lt;/code&gt; attributes match the corresponding event key strings of the arrow keys on a keyboard. This makes it easy to pass the id string to the &lt;code&gt;moveSnake&lt;/code&gt; function to handle the direction change. &lt;/p&gt;

&lt;p&gt;Run your project. Our game is now complete. See how high you can get your score using either your keyboard or the on-screen buttons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Now that you have built a simple snake game with some basic styling, you might want to improve it or add extra features.&lt;/p&gt;

&lt;p&gt;You can build your own by copying the code above, or by forking our example repl at &lt;a href="https://replit.com/@ritza/snake-game-vanilla-javascript"&gt;replit.com/@ritza/snake-game-vanilla-javascript&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are some suggestions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make the snake transition from cell to cell a smooth animation.&lt;/li&gt;
&lt;li&gt;Persist the top five highest scores by storing them in local storage. Create a pop dialog menu to view these high scores.&lt;/li&gt;
&lt;li&gt;Increase the speed of the snake as the game progresses to make it harder.&lt;/li&gt;
&lt;li&gt;Add obstacles.&lt;/li&gt;
&lt;li&gt;Add sound effects.&lt;/li&gt;
&lt;li&gt;Make different food items have different points.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also &lt;a href="https://docs.replit.com/tutorials/build-snake-with-kaboom"&gt;learn how to make Snake with Kaboom&lt;/a&gt;. &lt;a href="https://kaboomjs.com/"&gt;Kaboom.js&lt;/a&gt; is a JavaScript game programming library that makes creating games like this easy. &lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Creating a Discord meme-maker bot with Python</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Tue, 12 Jul 2022 08:17:23 +0000</pubDate>
      <link>https://dev.to/ritza/creating-a-discord-meme-maker-bot-with-python-5eeh</link>
      <guid>https://dev.to/ritza/creating-a-discord-meme-maker-bot-with-python-5eeh</guid>
      <description>&lt;p&gt;In this tutorial, we'll create a &lt;a href="https://discord.com" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; bot that adds captions to images, allowing server members to create memes. Users will call the bot using a command and supply it with an image file and a caption. The bot will reply with a new image file that includes the caption.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you will: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;a href="https://discordpy.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;discord.py&lt;/a&gt; to create a bot that can be added to any Discord server.&lt;/li&gt;
&lt;li&gt;Understand the basics of image processing and dealing with files in memory using Python.&lt;/li&gt;
&lt;li&gt;Know how to host Discord bots on Replit!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Sign in to &lt;a href="https://replit.com" rel="noopener noreferrer"&gt;Replit&lt;/a&gt; or &lt;a href="https://replit.com/signup" rel="noopener noreferrer"&gt;create an account&lt;/a&gt; if you haven't already. Once logged in, create a Python repl.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fcreate-repl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fcreate-repl.png" alt="Creating a new repl"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Discord application
&lt;/h2&gt;

&lt;p&gt;Open another browser tab and visit the &lt;a href="http://discordapp.com/developers/applications" rel="noopener noreferrer"&gt;Discord Developer Portal&lt;/a&gt;. Log in with your Discord account, or create one if you haven't already. Keep your repl open – we'll return to it soon.&lt;/p&gt;

&lt;p&gt;Once you're logged in, create a new application. Give it a name, like "ImageCaptioner".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fdiscord-create-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fdiscord-create-app.png" alt="Discord creating an app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Discord applications can interact with Discord in several different ways, not all of which require bots, so creating one is optional. That said, we'll need one for this project. Let's create a bot.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on &lt;strong&gt;Bot&lt;/strong&gt; in the menu on the left-hand side of the page.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Bot&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give your bot a username (such as "ImageCaptionBot").&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Reset Token&lt;/strong&gt; and then &lt;strong&gt;Yes, do it!&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Copy the token that appears just under your bot's username.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fdiscord-create-bot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fdiscord-create-bot.png" alt="Creating a discord bot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The token you just copied is required for the code in our repl to interface with Discord's API. Return to your repl and open the Secrets tab in the left sidebar. Create a new secret with &lt;code&gt;DISCORD_TOKEN&lt;/code&gt; as its key and the token you copied as its value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fsecret-token.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fsecret-token.png" alt="Secret token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once, you've done that, return to the Discord developer panel. We need to finish setting up our bot.&lt;/p&gt;

&lt;p&gt;First, disable the Public Bot option – the functionality we're building for this bot will be highly specific to our server, so we don't want anyone else to try to add it to their server. What's more, bots on 100 or more servers have to go through a special verification and approval process, and we don't want to worry about that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fpublic-bot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fpublic-bot.png" alt="Disable public bot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second, we need to configure access to privileged &lt;a href="https://discord.com/developers/docs/topics/gateway#gateway-intents" rel="noopener noreferrer"&gt;Gateway Intents&lt;/a&gt;. Depending on a bot's functionality, it will require access to different events and sources of data. Events involving users' actions and the content of their messages are considered more sensitive and need to be explicitly enabled.&lt;/p&gt;

&lt;p&gt;For this bot to work, we'll need the "Message Content Intent", which will allow our bot to see the content of users' messages. Toggle it to the on position and save changes when prompted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fbot-intents.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fbot-intents.png" alt="Priviledged bot intents"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we've created our application and its bot, we need to add it to a server. We'll walk you through creating a test server for this tutorial, but you can also use any server you've created in the past, as long as the other members won't get too annoyed about it becoming a bot testing ground. You can't use a server that you're just a normal user on, as adding bots requires special privileges.&lt;/p&gt;

&lt;p&gt;Open &lt;a href="http://discord.com" rel="noopener noreferrer"&gt;Discord.com&lt;/a&gt; in your browser. You should already be logged in. Then click on the &lt;strong&gt;+&lt;/strong&gt; icon in the leftmost panel to create a new server. Alternatively, open an existing server you own.&lt;/p&gt;

&lt;p&gt;In a separate tab, return to the &lt;a href="https://discord.com/developers/applications" rel="noopener noreferrer"&gt;Discord Developer Portal&lt;/a&gt; and open your application. Then follow these steps to add your bot to your server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on &lt;strong&gt;OAuth2&lt;/strong&gt; in the left sidebar.&lt;/li&gt;
&lt;li&gt;In the menu that appears under &lt;strong&gt;OAuth2&lt;/strong&gt;, select &lt;strong&gt;URL Generator&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Scopes&lt;/strong&gt;, mark the checkbox labelled &lt;em&gt;bot&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Bot Permissions&lt;/strong&gt;, mark the checkboxes labelled &lt;em&gt;Read Messages/View Channels&lt;/em&gt;, &lt;em&gt;Send Messages&lt;/em&gt;, and &lt;em&gt;Attach Files&lt;/em&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fbot-permissions.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fbot-permissions.png" alt="Bot permissions"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scroll down and copy the URL under &lt;strong&gt;Generated URL&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paste the URL in your browser's navigation bar and hit Enter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the page that appears, select your server from the drop-down box and click &lt;strong&gt;Continue&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When prompted about permissions, click &lt;strong&gt;Authorize&lt;/strong&gt;, and complete the CAPTCHA.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fbot-connect.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fbot-connect.png" alt="Bot connect"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return to your Discord server. You should see that your bot has just joined.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now that we've done the preparatory work, it's time to write some code. Return to your repl for the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the Discord bot code
&lt;/h2&gt;

&lt;p&gt;We'll be using &lt;a href="https://discordpy.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;Discord.py&lt;/a&gt; to interface with Discord's API using Python. Add the following code scaffold to &lt;code&gt;main.py&lt;/code&gt; in your repl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;discord&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;discord.ext&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;commands&lt;/span&gt;

&lt;span class="n"&gt;DISCORD_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DISCORD_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command_prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@bot.event&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_ready&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; has connected to Discord!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DISCORD_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we import some Python libraries we'll need, including Discord.py and its &lt;a href="https://discordpy.readthedocs.io/en/stable/ext/commands/commands.html" rel="noopener noreferrer"&gt;commands extension&lt;/a&gt;. We then retrieve the value of the &lt;code&gt;DISCORD_TOKEN&lt;/code&gt; environment variable, which we set in our repl's secrets tab above. Following that, we instantiate a &lt;a href="https://discordpy.readthedocs.io/en/stable/ext/commands/api.html#discord.ext.commands.Bot" rel="noopener noreferrer"&gt;&lt;code&gt;Bot&lt;/code&gt; object&lt;/a&gt;. We'll use this object to listen for Discord events and respond to them. For the most part, we'll be responding to &lt;em&gt;commands&lt;/em&gt; - messages from users which start with &lt;code&gt;!&lt;/code&gt; (the &lt;code&gt;command_prefix&lt;/code&gt; we specified when creating our &lt;code&gt;Bot&lt;/code&gt; object).&lt;/p&gt;

&lt;p&gt;However, the first event we're interested in is not a command. The &lt;a href="https://discordpy.readthedocs.io/en/stable/api.html#discord.on_ready" rel="noopener noreferrer"&gt;&lt;code&gt;on_ready()&lt;/code&gt;&lt;/a&gt; event will trigger when our bot logs on to Discord (the &lt;code&gt;@bot.event&lt;/code&gt; &lt;a href="https://realpython.com/primer-on-python-decorators/" rel="noopener noreferrer"&gt;decorator&lt;/a&gt; ensures this). All this event will do is print a message to our repl's console, telling us that the bot has connected.&lt;/p&gt;

&lt;p&gt;Note that we've prepended &lt;code&gt;async&lt;/code&gt; to the function definition – this makes our &lt;code&gt;on_ready()&lt;/code&gt; function into a &lt;a href="https://docs.python.org/3/library/asyncio-task.html" rel="noopener noreferrer"&gt;coroutine&lt;/a&gt;. Coroutines are largely similar to functions, but may not execute immediately, and must be invoked with the &lt;code&gt;await&lt;/code&gt; keyword. Using coroutines makes our program &lt;a href="https://realpython.com/async-io-python/#the-10000-foot-view-of-async-io" rel="noopener noreferrer"&gt;asynchronous&lt;/a&gt;, which means it can continue executing code while waiting for the results of a long-running function, usually one that depends on input or output. If you've used JavaScript before, you'll recognize this style of programming.&lt;/p&gt;

&lt;p&gt;The final line in our file starts the bot, providing &lt;code&gt;DISCORD_TOKEN&lt;/code&gt; to authenticate it. Run your repl now to see it in action. Once it's started, return to your Discord server. You should see that your bot user is now online.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fonline-bot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fonline-bot.png" alt="Online bot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Receiving user input
&lt;/h2&gt;

&lt;p&gt;The Discord.py commands extension allows us to define command handlers using the &lt;code&gt;@bot.command&lt;/code&gt; decorator. Without this, we'd have to manually parse the content of all user messages to determine whether a command had been issued, as was necessary for our &lt;a href="https://docs.replit.com/tutorials/discord-role-bot" rel="noopener noreferrer"&gt;role assignment bot tutorial&lt;/a&gt;. Let's define our main command now, &lt;code&gt;!caption&lt;/code&gt;. Enter the following code below the definition of &lt;code&gt;on_ready()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@bot.command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caption&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brief&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Add a caption to an image.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add a caption to an attached image. Example:

!caption &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello world!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;attached image&amp;gt;

Supported image types: PNG, JPEG, WebP
             &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;caption_text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@bot.command&lt;/code&gt; decorator will ensure that our function is invoked when a user types a message starting with &lt;code&gt;!caption&lt;/code&gt;. We also use it to define some help text – the commands extension provides a default &lt;code&gt;!help&lt;/code&gt; command, and each command we define can have two types of explanatory text:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;brief&lt;/code&gt;: A short description of the command that will show alongside other defined commands when the user types &lt;code&gt;!help&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;help&lt;/code&gt;: A longer description of the command that will show when the user types &lt;code&gt;!help name_of_command&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our &lt;code&gt;caption&lt;/code&gt; function takes two parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ctx&lt;/code&gt;: This is the &lt;em&gt;invocation context&lt;/em&gt;, a special object containing information such as the user who called the command, the server it was called in, and the files attached to the calling message, if any. All commands must take this parameter.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;caption_text&lt;/code&gt;: This will be a string containing the caption the user wants to add to their image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll start the body of the function with some user-friendly error handling. Replace &lt;code&gt;pass&lt;/code&gt; with the function body shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;caption_text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Must have caption text
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;caption_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please include some caption text after the `!caption` command. For example `!caption &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;Hello world!&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user forgets to include a caption in their command, they'll receive a &lt;a href="https://support.discord.com/hc/en-us/articles/360057382374-Replies-FAQ" rel="noopener noreferrer"&gt;reply&lt;/a&gt; informing them of this omission.&lt;/p&gt;

&lt;p&gt;Next, we'll make sure they've attached a file to their command message with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Must have a file attached
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;image_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please attach an image for me to caption.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no file is attached, &lt;code&gt;ctx.message.attachments&lt;/code&gt; will be an empty list and we'll send an error message to the user and abort the function. Otherwise, we'll store the URL of the first file attached to the message in &lt;code&gt;image_url&lt;/code&gt; and continue execution. It's possible to attach multiple files to a single Discord message, but for this bot, we're going to ignore all but the first one.&lt;/p&gt;

&lt;p&gt;Next, we need to check whether the attached file is an image. We'll do this by determining the file's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" rel="noopener noreferrer"&gt;MIME type&lt;/a&gt; using Python's built-in &lt;code&gt;mimetypes&lt;/code&gt; library. Add the following line to the top of your &lt;code&gt;main.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now need to specify which MIME types we'll support. According to our help text, images for captioning should be PNGs, JPEGs, or WebPs. These are the most common non-animating image types on the web, so we shouldn't need to support any more for the moment. Add the following code below the definition of &lt;code&gt;DISCORD_TOKEN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SUPPORTED_MIMETYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/webp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return to the &lt;code&gt;caption()&lt;/code&gt; function. Add the following code below the block of code that checks whether a file is attached to the user's message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# File must be an image
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;guess_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SUPPORTED_MIMETYPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sorry, the file you attached is not a supported image format. Please upload a PNG, JPEG or WebP image.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.python.org/3/library/mimetypes.html#mimetypes.guess_type" rel="noopener noreferrer"&gt;&lt;code&gt;mimetypes.guess_type&lt;/code&gt;&lt;/a&gt; function will determine what kind of file we're dealing with from its URL. If we don't find its filetype in our list of supported types, we'll send an error message to the user and cease execution.&lt;/p&gt;

&lt;p&gt;Now that we've dealt with the most likely error cases, it's time to handle a correctly formatted &lt;code&gt;!caption&lt;/code&gt; command. First, we need to fetch the image file – we have a URL for it, so we can use Python's &lt;a href="https://pypi.org/project/requests/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; library for this. Add the following line to the top of your &lt;code&gt;main.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then return to the bottom of the &lt;code&gt;caption&lt;/code&gt; function and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Fetch image file
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Store image file name
&lt;/span&gt;    &lt;span class="n"&gt;image_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use a &lt;a href="https://requests.readthedocs.io/en/latest/api/#requests.get" rel="noopener noreferrer"&gt;GET request&lt;/a&gt; to retrieve the user's image and store its filename in another variable.&lt;/p&gt;

&lt;p&gt;It would be convenient if we could deal with this image completely in memory, without saving it to a file. We can do this by treating the image as a binary stream, provided by Python's built-in &lt;a href="https://docs.python.org/3/library/io.html" rel="noopener noreferrer"&gt;&lt;code&gt;io&lt;/code&gt;&lt;/a&gt; library. Add the following line to the top of &lt;code&gt;main.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return to the bottom of the &lt;code&gt;caption&lt;/code&gt; function and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Caption image
&lt;/span&gt;    &lt;span class="n"&gt;final_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;caption_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;caption_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.python.org/3/library/io.html#io.BytesIO" rel="noopener noreferrer"&gt;&lt;code&gt;BytesIO&lt;/code&gt;&lt;/a&gt; will convert the image from HTTP response content into a binary stream, which is similar to the file object returned by &lt;a href="https://docs.python.org/3/library/functions.html#open" rel="noopener noreferrer"&gt;&lt;code&gt;open()&lt;/code&gt;&lt;/a&gt;. This allows us to work with the image as though it were a file without ever saving it to disk.&lt;/p&gt;

&lt;p&gt;We'll write the &lt;code&gt;caption_image()&lt;/code&gt; function in the next section. It takes an image and a caption and returns an image with the caption applied. Once we've got that implemented, we'll return to this function and send the finished image back to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Captioning images
&lt;/h2&gt;

&lt;p&gt;The code we're going to write in this section is quite different from the Discord API code above, so we'll put it in a separate file. That way, we can easily reuse it for other applications – for example, we might also want to write a Telegram bot, a command-line application, or even a small website that adds captions to images. By separating our code, we can implement any of these later on without having to change or even think about the image captioning logic.&lt;/p&gt;

&lt;p&gt;Create a new file named &lt;code&gt;caption.py&lt;/code&gt; and populate it with the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;caption_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;impact.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're using the &lt;a href="https://pypi.org/project/Pillow/" rel="noopener noreferrer"&gt;Pillow&lt;/a&gt; library for image processing. This library is imported as PIL because it's a fork of &lt;a href="https://en.wikipedia.org/wiki/Python_Imaging_Library" rel="noopener noreferrer"&gt;an earlier, discontinued project of that name&lt;/a&gt;. This library has &lt;a href="https://pillow.readthedocs.io/" rel="noopener noreferrer"&gt;a wealth of image manipulation features&lt;/a&gt;, one of which is adding text to images.&lt;/p&gt;

&lt;p&gt;To install Pillow, open &lt;code&gt;pyproject.toml&lt;/code&gt; and add it to &lt;code&gt;[tool.poetry.dependencies]&lt;/code&gt; as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.poetry.dependencies]&lt;/span&gt;
&lt;span class="py"&gt;python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;3.9&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="py"&gt;numpy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^1.22.2"&lt;/span&gt;
&lt;span class="py"&gt;replit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^3.2.4"&lt;/span&gt;
&lt;span class="py"&gt;discord&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^1.7.3"&lt;/span&gt;
&lt;span class="py"&gt;pillow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^9.1.1"&lt;/span&gt; &lt;span class="c"&gt;# &amp;lt;-- new line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stop and run your repl to update its dependencies and install the new package. Normally we would let Replit do this for us, but later in this section, we're going to test &lt;code&gt;caption.py&lt;/code&gt; without linking it to the Discord code in &lt;code&gt;main.py&lt;/code&gt;, so Replit won't have a chance to detect and install our new package on its own.&lt;/p&gt;

&lt;p&gt;Return to &lt;code&gt;caption.py&lt;/code&gt;. Below our imports, we define the &lt;code&gt;caption_image()&lt;/code&gt; function used in &lt;code&gt;main.py&lt;/code&gt;. In addition to the image file and caption parameters, we'll add the ability to optionally specify a font, defaulting to Impact, a popular font for making memes. Download the &lt;a href="///tutorial-files/discord-meme-maker-bot/captionbot-resources.zip"&gt;font file now&lt;/a&gt; and upload it to your repl.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fimpact-font.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fimpact-font.gif" alt="Upload impact font"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We start the &lt;code&gt;caption_image()&lt;/code&gt; function by &lt;a href="https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.open" rel="noopener noreferrer"&gt;creating an &lt;code&gt;Image&lt;/code&gt; object&lt;/a&gt; from the file provided as an argument. Next, we'll need to convert our &lt;code&gt;Image&lt;/code&gt; into an &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html" rel="noopener noreferrer"&gt;&lt;code&gt;ImageDraw&lt;/code&gt;&lt;/a&gt; so that we can add text to it. We'll do that with the following line of code, inserted below the definition of &lt;code&gt;img&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to load our font:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;font_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;impact.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.truetype" rel="noopener noreferrer"&gt;&lt;code&gt;ImageFont.truetype()&lt;/code&gt;&lt;/a&gt; to load a TrueType font from a file. We specify the font size in the second argument. Users will be able to upload different-sized images, so rather than hardcoding a specific font size, we've expressed the size in terms of the image's width, allowing it to scale appropriately. Feel free to experiment with this value.&lt;/p&gt;

&lt;p&gt;Now that we've got our image and font, we can start drawing. Extend your function as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;caption_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;caption_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;caption_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;caption_h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# position
&lt;/span&gt;              &lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# text
&lt;/span&gt;              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# color
&lt;/span&gt;              &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# font
&lt;/span&gt;              &lt;span class="n"&gt;stroke_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# text outline width
&lt;/span&gt;              &lt;span class="n"&gt;stroke_fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# text outline color
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.textsize" rel="noopener noreferrer"&gt;&lt;code&gt;ImageDraw.textsize()&lt;/code&gt;&lt;/a&gt; method returns the height and width of a given string in a given font. We need this information to place our caption in the upper center of the image.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.text" rel="noopener noreferrer"&gt;&lt;code&gt;ImageDraw.text()&lt;/code&gt;&lt;/a&gt; method actually draws our text. We provide it with our text and the necessary positioning and coloring information. To avoid situations where text blends in with the background, we make our text white with a black outline. Again, feel free to experiment with different colors, outlines and positions. &lt;/p&gt;

&lt;p&gt;Our work here is done, and all that remains is to return the image. But before we do that, let's test this code out on its own. Add the following line to the bottom of your function to &lt;a href="https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.save" rel="noopener noreferrer"&gt;save the image to a file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find an image you'd like to add a caption to. If you don't have one on hand, &lt;a href="///tutorial-files/discord-meme-maker-bot/captionbot-resources.zip"&gt;use this picture of a robot&lt;/a&gt;. Upload your chosen image to your repl.&lt;/p&gt;

&lt;p&gt;At the bottom of &lt;code&gt;caption.py&lt;/code&gt;, outside of the definition of &lt;code&gt;caption_image()&lt;/code&gt;, add the following line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;caption_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replbot.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello world!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the image file to your file's name and the caption to whatever you want. Then, in your repl's shell, type &lt;code&gt;python caption.py&lt;/code&gt; and press Enter. A file named &lt;code&gt;output.png&lt;/code&gt; should appear in your repl's file pane. Click on it to see the results of the code we've just written. Our version looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Freplbot-captioned.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Freplbot-captioned.png" alt="Captioned image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Returning captioned images
&lt;/h2&gt;

&lt;p&gt;Now we'll tie everything together. Once you're happy with the text size and colors of your captions, delete the &lt;code&gt;caption_image()&lt;/code&gt; function invocation from the bottom of &lt;code&gt;caption.py&lt;/code&gt; and replace the line that reads &lt;code&gt;img.save("output.png")&lt;/code&gt; with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;img_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getvalue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of saving our image to a file, we save it to a binary stream object, much like we did when fetching the original image from Discord. We keep the image format the same as the original using Pillow's &lt;a href="https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.format" rel="noopener noreferrer"&gt;&lt;code&gt;Image.format&lt;/code&gt;&lt;/a&gt; attribute. We then convert our &lt;code&gt;BytesIO&lt;/code&gt; stream into a &lt;code&gt;bytes&lt;/code&gt; object with &lt;a href="https://docs.python.org/3/library/io.html#io.BytesIO.getvalue" rel="noopener noreferrer"&gt;&lt;code&gt;.getvalue()&lt;/code&gt;&lt;/a&gt; and return it to the caller.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;main.py&lt;/code&gt; and import &lt;code&gt;caption_image()&lt;/code&gt; from &lt;code&gt;caption.py&lt;/code&gt; with the following line near the top of the file, below your other imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;caption&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;caption_image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return to the bottom of the &lt;code&gt;caption()&lt;/code&gt; function definition. Add the following code below the definition of &lt;code&gt;final_image&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Send reply
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;discord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_image&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;captioned-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image_filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will reply to the user with a message containing the image we created above.&lt;/p&gt;

&lt;p&gt;Stop and run your repl, and then switch to your Discord server. Enter a message such as &lt;code&gt;!caption "Hello world!"&lt;/code&gt; and attach an image to it. You should receive a reply something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fcaption-in-discord.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fcaption-in-discord.png" alt="Image caption in discord"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Text wrapping
&lt;/h2&gt;

&lt;p&gt;Our bot works reasonably well for short captions, but anything longer than about five words goes off the edges of the image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fnowrap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fnowrap.png" alt="Caption text wrapping"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can fix this by splitting caption text into multiple lines. We'll use Python's &lt;a href="https://docs.python.org/3/library/textwrap.html" rel="noopener noreferrer"&gt;textwrap&lt;/a&gt; library to do this. Open &lt;code&gt;caption.py&lt;/code&gt; and add the following line to the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;textwrap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the new line shown below to the &lt;code&gt;caption_image()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;font_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;impact.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# NEW LINE BELOW
&lt;/span&gt;    &lt;span class="n"&gt;caption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textwrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# NEW LINE ABOVE
&lt;/span&gt;
    &lt;span class="n"&gt;caption_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;caption_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;caption_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;caption_h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# position
&lt;/span&gt;              &lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# text
&lt;/span&gt;              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# color
&lt;/span&gt;              &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# font
&lt;/span&gt;              &lt;span class="n"&gt;stroke_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# text outline width
&lt;/span&gt;              &lt;span class="n"&gt;stroke_fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# text outline color
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;a href="https://docs.python.org/3/library/textwrap.html#textwrap.fill" rel="noopener noreferrer"&gt;&lt;code&gt;textwrap.fill()&lt;/code&gt;&lt;/a&gt; will return a new version of our caption string with newline characters (&lt;code&gt;\n&lt;/code&gt;) inserted in appropriate places to ensure that each line of the text contains no more than &lt;code&gt;width&lt;/code&gt; characters. It will do this without splitting words over multiple lines.&lt;/p&gt;

&lt;p&gt;We use the following calculation to get &lt;code&gt;width&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;img.width / (font_size / 2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the width of the image divided by half the font size. Most of the characters in the Impact font are taller than they are wide, so dividing the font size by two gives us reasonably good results for most text. It should also work for other fonts that aren't extremely wide. Try experimenting with different maximum line lengths.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fwrapped.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2Fdiscord-meme-maker-bot%2Fwrapped.png" alt="Wrapped text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rerun your repl now and return to Discord to try out some different long captions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where next?
&lt;/h2&gt;

&lt;p&gt;Our image captioning bot is functional but quite rudimentary. If you'd like to continue working on it, here are some ideas you might want to try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More font and text color options. You could implement these as additional bot commands.&lt;/li&gt;
&lt;li&gt;The ability to add two captions to an image, on both the top and bottom.&lt;/li&gt;
&lt;li&gt;A different interface for your image captioning code, such as a Telegram bot or a simple website.&lt;/li&gt;
&lt;li&gt;Pillow provides a number of &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageFilter.html" rel="noopener noreferrer"&gt;filters&lt;/a&gt; that you could apply to images in addition to captioning them.&lt;/li&gt;
&lt;li&gt;If you would like to make the text wrapping more robust for different fonts, you could try rewriting that part of &lt;code&gt;caption.py&lt;/code&gt;. You might find &lt;a href="https://gist.github.com/turicas/1455973" rel="noopener noreferrer"&gt;this gist&lt;/a&gt; and this &lt;a href="https://levelup.gitconnected.com/how-to-properly-calculate-text-size-in-pil-images-17a2cc6f51fd" rel="noopener noreferrer"&gt;article&lt;/a&gt; helpful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Discord bot code can be hosted on Replit permanently, but you'll need to use an &lt;a href="https://docs.replit.com/hosting/enabling-always-on" rel="noopener noreferrer"&gt;Always-on&lt;/a&gt; repl to keep it running 24/7.&lt;/p&gt;

&lt;p&gt;You can find our repl below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://replit.com/@ritza/ImageCaptionBot" rel="noopener noreferrer"&gt;https://replit.com/@ritza/ImageCaptionBot&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>discord</category>
      <category>tutorial</category>
      <category>meme</category>
    </item>
    <item>
      <title>Building a 3D obstacle avoiding game with three.js</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Mon, 11 Jul 2022 12:30:00 +0000</pubDate>
      <link>https://dev.to/ritza/building-a-3d-obstacle-avoiding-game-with-threejs-19da</link>
      <guid>https://dev.to/ritza/building-a-3d-obstacle-avoiding-game-with-threejs-19da</guid>
      <description>&lt;p&gt;&lt;a href="https://threejs.org/" rel="noopener noreferrer"&gt;Three.js&lt;/a&gt; is a general-purpose 3D library for browsers. You can use it to create 3D objects, animations, and games. Take a look at the three.js &lt;a href="https://threejs.org/examples" rel="noopener noreferrer"&gt;examples page&lt;/a&gt; in the documentation to see what kind of things you can make.&lt;/p&gt;

&lt;p&gt;In this tutorial, you will learn the basics of three.js and its game creation capabilities by making a simple obstacle avoiding 3D game. In the game, you will control a box that moves through a 3D course. The goal of the game is to avoid the obstacles and get to the end of the course.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fgameplay.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fgameplay.gif" alt="Game play"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will create the game in Replit, which is an online integrated development environment (IDE). This means that you can do this tutorial in the browser, and it will be easy to share your game online.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a new project in Replit
&lt;/h2&gt;

&lt;p&gt;Head over to &lt;a href="https://replit.com/" rel="noopener noreferrer"&gt;Replit&lt;/a&gt; and create a new repl. Choose &lt;strong&gt;HTML, CSS, JS&lt;/strong&gt; as your project type. Give this repl a name, like "3D obstacle avoiding game".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fnew_repl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fnew_repl.png" alt="creating a new Replit project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing three.js to the project
&lt;/h2&gt;

&lt;p&gt;Open the &lt;code&gt;script.js&lt;/code&gt; file in your repl. We'll import three.js by referencing it from a content distribution network (CDN) to get us up and running quickly. Add the following line to the &lt;code&gt;script.js&lt;/code&gt; file to import three.js from the Skypack CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.skypack.dev/three@0.140.2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import" rel="noopener noreferrer"&gt;&lt;code&gt;import&lt;/code&gt;&lt;/a&gt; keyword is used to import a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules" rel="noopener noreferrer"&gt;JavaScript &lt;code&gt;module&lt;/code&gt;&lt;/a&gt;. It will not work as is – we need to indicate that this script file is a module. To make this work, change the default &lt;code&gt;script&lt;/code&gt; tag in the &lt;code&gt;index.html&lt;/code&gt; file to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;type=module&lt;/code&gt; attribute allows us to use module features in our script.&lt;/p&gt;

&lt;p&gt;Now we are ready to use three.js in our project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a scene with the player box
&lt;/h2&gt;

&lt;p&gt;To display 3D objects on the screen, we need three things: a scene, a camera, and a renderer. Then we will place 3D objects in the scene. Most objects also require lighting to be added to see them. &lt;/p&gt;

&lt;p&gt;Let's start by creating a scene and adding a camera. Add the following lines to the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Scene&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PerspectiveCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a scene. The scene determines what will be rendered and where it will be rendered. We will add objects, a camera, and lights to our scene.&lt;/p&gt;

&lt;p&gt;We are using a perspective camera, which is the most commonly used camera type for 3D scenes. Perspective cameras use &lt;a href="https://en.wikipedia.org/wiki/Perspective_(graphical)" rel="noopener noreferrer"&gt;perspective projection&lt;/a&gt;, which mimics the way human eyes see. The further away objects are from the camera, the smaller they appear. &lt;/p&gt;

&lt;p&gt;The four parameters of the &lt;code&gt;PerspectiveCamera&lt;/code&gt; constructor function define the camera's viewing frustum, which is the field of view of our camera in the 3D world. The parameters are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;fov&lt;/strong&gt; - Field of view, the camera frustum vertical field of view, in degrees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aspect&lt;/strong&gt; - Camera frustum aspect ratio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;near&lt;/strong&gt; - Camera frustum near plane, in world units.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;far&lt;/strong&gt; - Camera frustum far plane, in world units.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fperspective-camera-frustum.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fperspective-camera-frustum.png" alt="creating a new Replit project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image shows the camera's viewing frustum. The viewable objects are between the near and far plane. There are two objects in the frustum, a purple stick and an orange stick. The sticks are the same size. The dotted lines show how the sticks are projected onto the near plane, which shows how they will be seen. Their size on the near plane is different. The orange stick, which is further away from the camera, appears smaller. The units used are world units, it can be mapped to any defined unit but it is usually in meters.&lt;/p&gt;

&lt;p&gt;Next, we will position our camera in the 3D world and make it look at a specific point. The parameters are the x, y, and z points in the 3D world.  Add the following lines of code to the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add a renderer so that we can display the scene. We will attach it to a DOM element on our web page. Add the following lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebGLRenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;antialias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new renderer that will display our scene using &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/WebGL" rel="noopener noreferrer"&gt;WebGL&lt;/a&gt;. WebGL (Web Graphics Library) is used for rendering complex graphics, such as 3D scenes, on the web. It does this by accessing the graphics card on the user's device. The &lt;code&gt;antialias&lt;/code&gt; property is used to determine if anti-aliasing will be used, which is a method that smooths jagged edges on objects. This makes our 3D world look better. The size of the renderer is set to the browser width and height using the &lt;code&gt;setSize&lt;/code&gt; method so that our scene will take up the entire browser window. We then call the renderer's &lt;code&gt;render&lt;/code&gt; method to tell the renderer to draw the scene using our created scene and camera. We then add the renderer DOM element, which is a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element, to the HTML document. The renderer uses the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element to display the scene.&lt;/p&gt;

&lt;p&gt;Now let's create our first 3D object, the player box. This will be the box that we move around in the game. In three.js, we need three things to create an object:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Geometry - An object contains the x, y, z points that make up a shape.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Material - The surface of the geometry. Gives the geometry color and texture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mesh - Geometry + material. This is what we will add to our scene.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add the following lines above the &lt;code&gt;renderer&lt;/code&gt; declaration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BoxGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MeshBasicMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xe56956&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mesh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;0x&lt;/code&gt; in front of the color property value means that it is a hexadecimal value. Most materials require a light source to bounce off of them so that they can be seen. The &lt;code&gt;MeshBasicMaterial&lt;/code&gt; does not.&lt;/p&gt;

&lt;p&gt;Now let's add the mesh to the scene:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the code now by pushing the "Run" button at the top of the Replit window. You should see your first scene, an orange cube:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fplayer_box.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fplayer_box.png" alt="creating a new Replit project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MeshBasicMaterial&lt;/code&gt; does not look 3D. It would be better to have a material that light can interact with so that we can get some depth to our player box. Replace your material with the following material:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MeshLambertMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xe56956&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;MeshLambertMaterial&lt;/code&gt; is a relatively simple material that can reflect light. Your player box will not be visible now, we need to add a light source. We will add an ambient light and a directional light.&lt;/p&gt;

&lt;p&gt;Add the following lines to the &lt;code&gt;script.js&lt;/code&gt; file, above the &lt;code&gt;renderer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ambientLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AmbientLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xffffff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directionalLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DirectionalLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xffffff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;directionalLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ambientLight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;directionalLight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add a white directional light that is set along the x-, y-, and z-axes so that it shines on the top and side of the box. The ambient light allows us to see the box better from all angles. The first parameter for the light constructor functions is the color, and the second parameter is the light intensity, which ranges from 0 to 1. You should be able to see your player box now. It will look more 3D, as each side has different lighting. You can use your browser dev tools to check the console logs if you encounter any errors.&lt;/p&gt;

&lt;p&gt;Let's also get rid of the window scrollbars. Add the following to the &lt;code&gt;style.css&lt;/code&gt; file in the &lt;code&gt;body&lt;/code&gt; selector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;overflow&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;hidden&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;margin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your player box should now look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fplayer_box_light.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fplayer_box_light.png" alt="Player box with light"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try changing the material color, light intensity, and camera position to see what happens. &lt;/p&gt;

&lt;p&gt;Before we get our player box moving, let's change our code so that it is nicely structured for the game logic that we will add. Add the following lines to the top of the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boxSideLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;camera&lt;/code&gt;, &lt;code&gt;scene&lt;/code&gt;, and &lt;code&gt;renderer&lt;/code&gt; variables are global variables so that they are available throughout our script. They will be defined in functions we create. Remove the &lt;code&gt;const&lt;/code&gt; keyword in front of their declarations that we already added.&lt;/p&gt;

&lt;p&gt;We also initialize a &lt;code&gt;player&lt;/code&gt; variable for our created box.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;boxSideLength&lt;/code&gt; variable is for the x, y and z lengths of our box. Most of our boxes will be squares.&lt;/p&gt;

&lt;p&gt;Now let's define the function called &lt;code&gt;init&lt;/code&gt;. This function will be used to initialize the game, by rendering and setting up the scene and creating our objects. Define an &lt;code&gt;init&lt;/code&gt; function and move the camera, light, and renderer code into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Scene&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PerspectiveCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ambientLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AmbientLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xffffff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directionalLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DirectionalLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xffffff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;directionalLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ambientLight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;directionalLight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;initializeBoxes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebGLRenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;antialias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also call the &lt;code&gt;initializeBoxes&lt;/code&gt; function, which we will soon define. &lt;/p&gt;

&lt;p&gt;Now let's make a &lt;code&gt;createBox&lt;/code&gt; function. We will use this function to create our player box, and later to create obstacles. Define the &lt;code&gt;createBox&lt;/code&gt; function and move the code for creating your player box inside of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BoxGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;boxSideLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;boxSideLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;boxSideLength&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MeshLambertMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xe56956&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mesh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;geometry&lt;/code&gt; definition now uses the &lt;code&gt;boxSideLength&lt;/code&gt; variable to set the lengths of the sides. There is one extra line of code here, the call to the &lt;code&gt;mesh.position.set&lt;/code&gt; function. This will set the position of the created box in the 3D world based on the arguments passed to the function. This will be useful when we create randomly positioned obstacles.&lt;/p&gt;

&lt;p&gt;Now let's create an &lt;code&gt;initializeBoxes&lt;/code&gt; function that will be used to create all of the boxes at the start of the game. Add the following lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initializeBoxes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All this function currently does is create a player box that is positioned in the center of our 3D world. Its x, y, and z positions will be 0. If you run your repl code, you should still be able to see your box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Animating the player box
&lt;/h2&gt;

&lt;p&gt;In the final game, the player box moves continuously along the z-axis. We will make use of an animation loop to continuously move the player box and re-render the scene to see the movement. At the bottom of the &lt;code&gt;script.js&lt;/code&gt; file, add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;animate&lt;/code&gt; function will be called on every frame by using the &lt;code&gt;requestAnimationFrame&lt;/code&gt; function, which is a web API that is used to create animations. It will call the &lt;code&gt;animate&lt;/code&gt; function before each repaint of the screen by the browser. The number of function calls is usually 60 per second.   &lt;/p&gt;

&lt;p&gt;For each &lt;code&gt;animate&lt;/code&gt; function call, we move the player box and the camera by changing their position along the z-axis. The camera will follow the player box. Let's add a global speed variable at the top of our &lt;code&gt;script.js&lt;/code&gt; file so that we can easily change it later if we want to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To access the player box's &lt;code&gt;mesh&lt;/code&gt; property, let's return the &lt;code&gt;mesh&lt;/code&gt; property from the &lt;code&gt;createBox&lt;/code&gt; function. Add the following to the end of the &lt;code&gt;createBox&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to call our &lt;code&gt;animate&lt;/code&gt; function initially to get it started. In the &lt;code&gt;init&lt;/code&gt; function, add the following line below &lt;code&gt;renderer.render(scene, camera)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our player box will now be moving, but we won't be able to see the movement. We are going to add a &lt;a href="https://threejs.org/docs/index.html?q=grid#api/en/helpers/GridHelper" rel="noopener noreferrer"&gt;&lt;code&gt;GridHelper&lt;/code&gt;&lt;/a&gt; so that we can see the movement. A &lt;code&gt;GridHelper&lt;/code&gt; is an object that defines a grid, which is a two-dimensional array of lines along the x- and y-axes. This will give our 3D world a 2D grid surface. In the &lt;code&gt;init&lt;/code&gt; function, add the following lines below &lt;code&gt;initializeBoxes()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gridHelper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GridHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gridHelper&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will now be able to see your player box move through the 3D world.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fanimating-the-player-box.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fanimating-the-player-box.gif" alt="Animated player box"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you wait long enough, the box will move off the grid. Let's restrict the movement of the box so that it can't move off the grid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a game course
&lt;/h2&gt;

&lt;p&gt;The first thing we will do to prevent the player box from moving off the grid will be to add another box, the finishing line box. The finishing line box will be added at the boundary of the grid. Our game course will be from the center of the grid to the end of the grid, along the z-axis. Later, we will add collision detection to check if our player box has hit the finishing line box so that we can end the game before the player box leaves the grid.&lt;/p&gt;

&lt;p&gt;We will create a new global variable called &lt;code&gt;courseLength&lt;/code&gt; that will define the distance to the edge of the grid. Our player box starts moving from the center of the grid (x = 0, y = 0, z = 0) so our square grid's length should be double the course length. We will also restrict the movement of the box along the x- and y-axes. Add the following global variables to the top of the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;courseLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gridHelperSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;courseLength&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// limit movement of player box on x and y-axis &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xBoundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;boxSideLength&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yBoundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;xBoundary&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We set the &lt;code&gt;courseLength&lt;/code&gt; to &lt;code&gt;100&lt;/code&gt; world units. We use the &lt;code&gt;gridHelperSize&lt;/code&gt; to define the length of our square grid along the x- and z-axes. The &lt;code&gt;xBoundary&lt;/code&gt; and &lt;code&gt;yBoundary&lt;/code&gt; variables are used to limit the movement of our player box along the x- and y-axes. &lt;/p&gt;

&lt;p&gt;Let's update the &lt;code&gt;gridHelper&lt;/code&gt; to use the &lt;code&gt;gridHelperSize&lt;/code&gt; variable for its size and number of divisions parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gridHelper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GridHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gridHelperSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gridHelperSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will add the finish line box. Add the following to the &lt;code&gt;initializeBoxes&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// create finish line box&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BoxGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;xBoundary&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;yBoundary&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;boxSideLength&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MeshLambertMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mesh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;courseLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a green box that is positioned at the end of the grid. It marks the end of the game course. It's positioned at the end of the course where the z-axis value is equal to the course length. The size of the box along its x- and y-axes marks the x and y boundary. We will restrict the player box's movement so that it can't move past the finish line box. Its size along the x- and y-axes is double the boundary length, because it needs to mark the negative and positive axis boundaries. &lt;/p&gt;

&lt;p&gt;You will now be able to see the finish line box at the end of the grid. To reach the end of the grid sooner, you can change the &lt;code&gt;speed&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fcreating-a-game-course.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fcreating-a-game-course.png" alt="Game course finish line box"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling the player box
&lt;/h2&gt;

&lt;p&gt;Let's add some controls so that we can move our player box up, down, left, and right. Add the following lines to the bottom of the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// moving player box with arrow keys&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArrowLeft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArrowRight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArrowDown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an event listener that listens for a key press event. The player box's mesh (geometry + material) position is increased or decreased along the x- or y-axis, depending on which arrow button is pressed. Run your repl now and you should be able to move your player box with the arrow keys. You will notice that you can move off the screen, and when you reach the edge of the grid, you can avoid hitting the finish line box. Let's restrict the movement of the player box so that it always hits the finish line box. Replace the event listener that you just added with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currXPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currYPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArrowLeft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currXPos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;xBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArrowRight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currXPos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;xBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currYPos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;yBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArrowDown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currYPos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;yBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we add some extra lines of code to restrict the movement of the player box along the x- and y-axes by getting the current x and y position of the player box and then preventing movement if the position exceeds the current boundary values that we set using our global variables &lt;code&gt;xBoundary&lt;/code&gt; and &lt;code&gt;yBoundary&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Note that if you increased the speed variable to reach the end of the course sooner, you may need to increase the width and height of the finish line box to ensure that the player box always hits it, as the player box will be able to move a bit more along the x- and y-axes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting collisions
&lt;/h2&gt;

&lt;p&gt;To determine that we have reached the end of the course, we need to be able to detect collisions. Once the player box has collided with the finish line box, the game is over. Add the following global variables to the top of your &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;gameOver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numOfObstacles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;obstaclesBoundingBoxes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;gameOver&lt;/code&gt; flag variable will be used to determine when the game is over. This will occur once the finish line is reached, or if the player box collides with an obstacle (we will add obstacles later). The number of obstacles is currently zero. The &lt;code&gt;obstaclesBoundingBoxes&lt;/code&gt; will store a set of x, y, and z positions of bounding boxes that describe the positions of all of our objects, excluding the player box. We will use these bounding boxes to detect collisions.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;initializeBoxes&lt;/code&gt; function, add the following lines at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boundingBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Box3&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setFromObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;obstaclesBoundingBoxes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;boundingBox&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a bounding box for the finish line box &lt;code&gt;mesh&lt;/code&gt; object. We add this to the  &lt;code&gt;obstaclesBoundingBoxes&lt;/code&gt; array. The &lt;a href="https://threejs.org/docs/index.html?q=box3#api/en/math/Box3" rel="noopener noreferrer"&gt;&lt;code&gt;Box3&lt;/code&gt;&lt;/a&gt; object represents a bounding box in 3D space. It describes a set of coordinates, it does not appear in our 3D world. We use the &lt;code&gt;setFromObject&lt;/code&gt; method to calculate the bounding box of the finish line box, using the finish line box's &lt;code&gt;mesh&lt;/code&gt;. Within the bounding box object, there is a &lt;code&gt;max&lt;/code&gt; and &lt;code&gt;min&lt;/code&gt; property that describes the upper and lower x, y, and z boundaries of the box. For example, the bounding box of the finish line box contains the following &lt;code&gt;max&lt;/code&gt; and &lt;code&gt;min&lt;/code&gt; properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;max:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;y:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.9375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;z:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;100.25&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;min:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-3.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;y:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-0.9375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;z:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;99.75&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's create a function to detect collisions. Add the following function below the &lt;code&gt;createBox&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectCollisions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playerBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Box3&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setFromObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Check each object to detect if there is a collision&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numOfObstacles&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// an object was hit&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obstaclesBoundingBoxes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;intersectsBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playerBox&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;gameOver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You win!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To detect a collision, we first create a bounding box for the player box by creating a &lt;code&gt;Box3&lt;/code&gt; object. We then loop through the &lt;code&gt;obstaclesBoundingBoxes&lt;/code&gt; array and use the &lt;code&gt;intersectsBox&lt;/code&gt; method to check for an intersection – a collision – between the &lt;code&gt;playerBox&lt;/code&gt; and each obstacle. We currently don't have any obstacles, so we can only check for a collision with the finish line box. Given that we will always hit the finish line box, we set &lt;code&gt;gameOver&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; once it is hit. We will use an alert to let the player know that they have won when they reach the end of the course.&lt;/p&gt;

&lt;p&gt;We need to call the &lt;code&gt;detectCollisions&lt;/code&gt; function in the game loop &lt;code&gt;animate&lt;/code&gt; function so that we constantly check for a collision. Change your &lt;code&gt;animate&lt;/code&gt; function so that it is the same as the &lt;code&gt;animate&lt;/code&gt; function below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gameOver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;detectCollisions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The if statement at the start of the &lt;code&gt;animate&lt;/code&gt; function prevents the function from running if the game is over.&lt;/p&gt;

&lt;p&gt;Let's also add &lt;code&gt;if (gameOver) return;&lt;/code&gt; to the start of the "keydown" &lt;code&gt;window.addEventListener&lt;/code&gt; callback function to disable the keyboard event if the game is over.&lt;/p&gt;

&lt;p&gt;You will now get a "You win!" alert message once you reach the finish line box at the end of the grid. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fdetecting-collisions.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fdetecting-collisions.png" alt="Game course finish line box"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating obstacles
&lt;/h2&gt;

&lt;p&gt;Now let's add some obstacles for the player box to avoid to make it a game. Set the &lt;code&gt;numOfObstacles&lt;/code&gt; global variable to 50. We are going to write a &lt;code&gt;createObstacle&lt;/code&gt; function to generate 50 randomly positioned obstacles. Add the following function below the &lt;code&gt;createBox&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createObstacle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MathUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randFloatSpread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xBoundary&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MathUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randFloatSpread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yBoundary&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MathUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;courseLength&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;boxSideLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obstacle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boundingBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Box3&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setFromObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obstacle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;obstaclesBoundingBoxes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;boundingBox&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We make use of some math utility functions of the three.js &lt;a href="https://threejs.org/docs/index.html?q=MathUtils#api/en/math/MathUtils" rel="noopener noreferrer"&gt;&lt;code&gt;MathUtils&lt;/code&gt;&lt;/a&gt; object to get random x, y, and z points along our course. These will be used to randomly position obstacles. We get random x and y points using our x and y boundaries. The &lt;code&gt;randFloatSpread&lt;/code&gt; function takes in a range parameter and returns a random float in the interval [- range / 2, range / 2]. We get a random z value using the &lt;code&gt;randFloat&lt;/code&gt; function. It takes in a low and high parameter and returns a random float in the interval [low, high]. This value is always positive, as our course is positioned on the positive side of the z-axis. The low value starts at 10, as we don't want to place obstacles right in front of or on the player box. The high value is the &lt;code&gt;courseLength&lt;/code&gt;, so that the obstacles can be positioned all along our course. We subtract the &lt;code&gt;boxSideLength&lt;/code&gt; to prevent the obstacles from being placed on the finish line box. We then pass these x, y, and z points to the &lt;code&gt;createBox&lt;/code&gt; function to create an obstacle box. A bounding box is created for each created obstacle, and added to the &lt;code&gt;obstaclesBoundingBoxes&lt;/code&gt; array so that we can detect collisions between our player box and the obstacles.&lt;/p&gt;

&lt;p&gt;We need to call the &lt;code&gt;createObstacle&lt;/code&gt; function to create the obstacles. In the &lt;code&gt;initializeBoxes&lt;/code&gt; function, let's add a for loop to create the obstacles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initializeBoxes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numOfObstacles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;createObstacle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create 50 randomly positioned obstacles. Run your repl to see them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fcreating-random-obstacles.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fcreating-random-obstacles.png" alt="Creating random obstacles"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each time you reload the page, the obstacles will be randomly positioned. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding win-or-lose logic
&lt;/h2&gt;

&lt;p&gt;Try to collide with an obstacle, and you will see that the alert message is always "You win!". Let's fix that by adding some win-or-lose logic. In the &lt;code&gt;detectCollisions&lt;/code&gt; function, replace the for loop with the following for loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numOfObstacles&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// an object was hit&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obstaclesBoundingBoxes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;intersectsBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playerBox&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;gameOver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;numOfObstacles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You lose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// the last box is the finish line box&lt;/span&gt;
        &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You win!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last object bounding box in the &lt;code&gt;obstaclesBoundingBoxes&lt;/code&gt; array is the finish line box. Knowing this, we can determine when the player box has reached the finish line without hitting an obstacle. If the player box intersects with the last item in the &lt;code&gt;obstaclesBoundingBoxes&lt;/code&gt; array, you win. If the player box intersects with any other item, you lose.&lt;/p&gt;

&lt;p&gt;The game is now playable. Try make it to the end of the obstacle course.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding restart
&lt;/h2&gt;

&lt;p&gt;There is one big problem with our game: once the game is over we need to refresh the page to play again. That's not a very good user experience. Let's add a "Play" button that will show on page load and when the game is over. It will start the game and reset everything that needs to be reset at the start of a game.&lt;/p&gt;

&lt;p&gt;Add the following to the &lt;code&gt;index.html&lt;/code&gt; file inside of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag to create a button and a screen that covers the window behind the button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"play-btn-screen"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"play-btn-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Use the up, down, left and right arrow keys to move&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"play-btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Play&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the &lt;code&gt;style.css&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#play-btn-screen&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.play-btn-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#play-btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a play button in the middle of the screen.&lt;/p&gt;

&lt;p&gt;Now let's make the button work. Add the following global variables to the top of the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playBtnScreen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;play-btn-screen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;playBtnScreen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#play-btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;allObjs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets the play button screen (background cover behind the button) and play button element from the DOM and stores them in variables. The &lt;code&gt;allObjs&lt;/code&gt; array will store all of the created objects. We need this array to clear the scene by removing all of the objects at the start of each game. We do this so that we can place new randomly positioned objects in the scene and not have objects in the scene from previous rounds. &lt;/p&gt;

&lt;p&gt;Next, add a new click event listener on the &lt;code&gt;playBtn&lt;/code&gt;. Add the following to the bottom of the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;playBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;allObjs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;initializeBoxes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;gameOver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;playBtnScreen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The play button is used to start and also re-start the game. The first thing we do when the play button is clicked is to remove all the objects in the scene using the &lt;code&gt;scene.remove&lt;/code&gt; method. We then reset the camera position and call the &lt;code&gt;initializeBoxes&lt;/code&gt; function, which creates and positions all of the objects. We set &lt;code&gt;gameOver&lt;/code&gt; to false so that our &lt;code&gt;animate&lt;/code&gt; function and &lt;code&gt;keydown&lt;/code&gt; event listener will work. We then call the &lt;code&gt;animate&lt;/code&gt; function to start the animation loop, and then we hide the play button screen, which hides the play button as well. We need to make a few more changes in our &lt;code&gt;script.js&lt;/code&gt; file for the play button to work properly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Delete the &lt;code&gt;animate()&lt;/code&gt; function call in the &lt;code&gt;init&lt;/code&gt; function. We now call it when the play button is clicked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add &lt;code&gt;allObjs.push(mesh);&lt;/code&gt; in the &lt;code&gt;createBox&lt;/code&gt; function above &lt;code&gt;scene.add(mesh);&lt;/code&gt;. This adds the created box mesh (object) to the &lt;code&gt;allObjs&lt;/code&gt; array.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the following lines in the &lt;code&gt;detectCollisions&lt;/code&gt; function below &lt;code&gt;gameOver = true;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;playBtnScreen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;playBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make our play button visible at the end of the game and focus the button so that you can easily restart the game by pressing the spacebar or enter key.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the following lines at the start of the &lt;code&gt;initializeBoxes&lt;/code&gt; function:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// make empty at start of a game&lt;/span&gt;
    &lt;span class="nx"&gt;allObjs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nx"&gt;obstaclesBoundingBoxes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This clears the &lt;code&gt;allObjs&lt;/code&gt; array so that it only contains objects created in the current game. We also clear the &lt;code&gt;obstaclesBoundingBoxes&lt;/code&gt; as we only want to detect collisions with objects in the current game.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;allObjs.push(mesh);&lt;/code&gt; in the &lt;code&gt;initializeBoxes&lt;/code&gt; function above &lt;code&gt;scene.add(mesh);&lt;/code&gt;. This adds the finish line box to the &lt;code&gt;allObjs&lt;/code&gt; array.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our game is almost complete, all we need to do now is make it mobile-friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the game mobile friendly: Adding on-screen arrow buttons
&lt;/h2&gt;

&lt;p&gt;To make the game mobile friendly, we will add up, down, left, and right buttons to the bottom of the screen.&lt;/p&gt;

&lt;p&gt;Add the following to the &lt;code&gt;index.html&lt;/code&gt; file inside the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag, just above the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to create the buttons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"keys"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"keys-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"up"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"rotate(0, 5,5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5,4 L7,6 L3,6 L5,4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt; 
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"left"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"rotate(-90, 5,5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5,4 L7,6 L3,6 L5,4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt; 
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"down"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"rotate(180, 5,5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5,4 L7,6 L3,6 L5,4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt; 
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"right"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"rotate(90, 5,5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5,4 L7,6 L3,6 L5,4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The up, down, left, and right icons are created using SVGs.&lt;/p&gt;

&lt;p&gt;Now let's add some basic styling to our buttons. Add the following to the &lt;code&gt;style.css&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#keys&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.keys-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;".    up   ."&lt;/span&gt;
    &lt;span class="s1"&gt;"left down right"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#keys&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#up&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#down&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#left&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see the arrows, click the "Open in a new tab" button in the repl Output tab. This opens the link to the repl in a new tab. You can copy this link to view your repl on your phone or to share it with your friends.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fopen_in_new_tab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fopen_in_new_tab.png" alt="Replit - open in new tab link"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now be able to see the screen arrow buttons.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fscreen_buttons.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplit-docs-images.bardia.repl.co%2Fimages%2Ftutorials%2F47-3dgamethreejs%2Fscreen_buttons.png" alt="Screen arrow buttons"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try clicking a button before pressing "Play". You will notice that you can't click the arrow buttons. This is because the play button screen, which covers the whole screen, has a CSS &lt;code&gt;z-index&lt;/code&gt; property of &lt;code&gt;1&lt;/code&gt;. Once you click "Play", the play button screen CSS &lt;code&gt;visibility&lt;/code&gt; property is set to hidden, and you will be able to press the arrow buttons. &lt;/p&gt;

&lt;p&gt;Now let's add some JavaScript click event listeners and some functions to make our arrow buttons work on desktop and mobile. Our code is going to look quite complex because we use some extra functions and &lt;code&gt;setTimeout&lt;/code&gt; to allow the player box to continuously move when the arrow button is held down. This makes for a better user experience than having to continuously tap or click the button to move in one direction. Add the following global variable to the top of the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyBtns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.keys-container button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets all the key buttons from the DOM and stores them in a variable. &lt;/p&gt;

&lt;p&gt;Now add the following to the bottom of the &lt;code&gt;script.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;moveLeft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currXPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currXPos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;xBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveLeft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;moveRight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currXPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currXPos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;xBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveRight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;moveUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currYPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currYPos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;yBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveUp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;moveDown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currYPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currYPos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;yBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveDown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleKeyDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gameOver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;moveLeft&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;moveRight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;moveUp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;moveDown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// moving box - mobile - using screen btns&lt;/span&gt;
&lt;span class="nx"&gt;keyBtns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mousedown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleKeyDown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;touchstart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleKeyDown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mouseup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mouseleave&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;touchend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;keyBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;touchcancel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;timeoutID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each key button, we add &lt;code&gt;"mousedown"&lt;/code&gt; and &lt;code&gt;"touchstart"&lt;/code&gt; event listeners. When the button is clicked or touched, the &lt;code&gt;handleKeyDown&lt;/code&gt; function is called. This function determines which button was clicked or touched by checking the ID of the event's &lt;code&gt;currentTarget&lt;/code&gt; property. Different functions are called depending on the ID of the button. For each function that handles the movement in a particular direction, we get the current position of the player box, check if it is within the set boundaries, and increase or decrease its position by the &lt;code&gt;speed&lt;/code&gt; variable value. We then recursively call the function again after 50 ms using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/setTimeout" rel="noopener noreferrer"&gt;&lt;code&gt;setTimeout&lt;/code&gt;&lt;/a&gt; so that the movement is continuous when the button is held down.  &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;setTimeout&lt;/code&gt; function returns a &lt;code&gt;timeoutID&lt;/code&gt; that is a positive integer value. It identifies the timer created by the call to &lt;code&gt;setTimeout()&lt;/code&gt;. This value is passed to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout" rel="noopener noreferrer"&gt;&lt;code&gt;clearTimeout()&lt;/code&gt;&lt;/a&gt; to cancel the timeout after each recursive function call so that we don't create unnecessary timeouts. &lt;/p&gt;

&lt;p&gt;For each key button, we also add &lt;code&gt;"mouseleave"&lt;/code&gt;, &lt;code&gt;"touchend"&lt;/code&gt;, and &lt;code&gt;"touchcancel"&lt;/code&gt; event listeners. These clear the timeouts when the button is not held down anymore.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A little cheat in the game: If you have a touchscreen laptop, pressing the arrow key on your keyboard and on the screen will make it move faster than normal!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Save and run your project. Our game is complete! &lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;We learnt the basics of three.js and built a simple 3D game. There are many things that you can do to improve the game. Here are some you might want to try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace the alert with a nicely styled modal.&lt;/li&gt;
&lt;li&gt;Style the objects, or import or create 3D models. See &lt;a href="https://docs.replit.com/tutorials/3D-rendering-with-threejs" rel="noopener noreferrer"&gt;rendering 3D scenes with three.js&lt;/a&gt; for more information.&lt;/li&gt;
&lt;li&gt;Add a nicer surface instead of the grid.&lt;/li&gt;
&lt;li&gt;Add a loading screen while the 3D scene is loading.&lt;/li&gt;
&lt;li&gt;Make the game more challenging by increasing the speed the player box moves at as the game progresses, adding more obstacles, or by making the obstacles move.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a points system. Change the game logic so that you have to hit the boxes to get points.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store the points in local storage.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Add physics to the collisions using &lt;a href="https://github.com/pmndrs/cannon-es" rel="noopener noreferrer"&gt;cannon-es&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Or for a more advanced challenge: Make a bigger course, try making infinite movement within finite bounds so that you don't use too much of your computer's memory.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;You can find our repl below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://replit.com/@ritza/3D-obstacle-avoiding-game" rel="noopener noreferrer"&gt;https://replit.com/@ritza/3D-obstacle-avoiding-game&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
