<?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: Fabiano Salles</title>
    <description>The latest articles on DEV Community by Fabiano Salles (@fabianosalles).</description>
    <link>https://dev.to/fabianosalles</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%2F247216%2Fe497d666-96ed-4205-9e9d-ae3755d31763.jpg</url>
      <title>DEV Community: Fabiano Salles</title>
      <link>https://dev.to/fabianosalles</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fabianosalles"/>
    <language>en</language>
    <item>
      <title>I built a zero-friction SQLite Playground for my students</title>
      <dc:creator>Fabiano Salles</dc:creator>
      <pubDate>Wed, 11 Mar 2026 18:13:03 +0000</pubDate>
      <link>https://dev.to/fabianosalles/i-built-a-zero-friction-sqlite-playground-for-my-students-58i1</link>
      <guid>https://dev.to/fabianosalles/i-built-a-zero-friction-sqlite-playground-for-my-students-58i1</guid>
      <description>&lt;p&gt;Hi everyone!&lt;/p&gt;

&lt;p&gt;I'm a teacher, and in my SQL classes, I've always felt that many online tools are either too "heavy," full of distractions, or have some cost/signup friction that gets in the way of a beginner's first steps.&lt;/p&gt;

&lt;p&gt;Students just want to write their first &lt;code&gt;CREATE TABLE&lt;/code&gt; and see the results instantly. They don't want to manage servers or deal with bloated interfaces.&lt;/p&gt;

&lt;p&gt;That's why I decided to build a simple, lightweight &lt;a href="https://fasttools.dev/en/sql-playground" rel="noopener noreferrer"&gt;SQLite Playground&lt;/a&gt;.&lt;br&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%2F7ontir20yiywbw8yj6km.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%2F7ontir20yiywbw8yj6km.png" alt=" " width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Motivation: Focus on Learning
&lt;/h2&gt;

&lt;p&gt;My main goal was to provide a didactic and 100% free tool for students. I wanted an environment where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Setup is Required&lt;/strong&gt;: You just open the link and start typing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is Guaranteed&lt;/strong&gt;: The database runs entirely in the browser via &lt;code&gt;WASM&lt;/code&gt;. No data ever leaves the user's computer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Feedback&lt;/strong&gt;: It has a built-in Database Explorer so learners can actually see the schema they are building in real-time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;Being a dev-to-dev community, here is what’s under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vue 3 + Vite + TypeScript&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite WASM&lt;/strong&gt;: To run the database engine client-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monaco Editor&lt;/strong&gt;: For that professional IDE feel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Storage&lt;/strong&gt;: To remember your SQL and preferences (like if you prefer the explorer hidden or visible).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I need your feedback!&lt;br&gt;
I’m providing this as a free educational tool, and I’d honestly love to hear from this community.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any missing functionality in this beta?&lt;/li&gt;
&lt;li&gt;How is the performance on your end?&lt;/li&gt;
&lt;li&gt;Any UI/UX tweaks that would make it easier for an absolute beginner?
You can try it here: fasttools.dev/sql-playground&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm building this as part of a larger project called &lt;em&gt;FastTools&lt;/em&gt;, which focuses on fast, private, browser-native utilities.&lt;/p&gt;

&lt;p&gt;Any feedback, critique, or suggestion is more than welcome!&lt;/p&gt;

</description>
      <category>sql</category>
      <category>sqlite</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Why most SVG to PNG converters are overkill (and what I actually needed)</title>
      <dc:creator>Fabiano Salles</dc:creator>
      <pubDate>Sun, 25 Jan 2026 21:36:07 +0000</pubDate>
      <link>https://dev.to/fabianosalles/why-most-svg-to-png-converters-are-overkill-and-what-i-actually-needed-2j52</link>
      <guid>https://dev.to/fabianosalles/why-most-svg-to-png-converters-are-overkill-and-what-i-actually-needed-2j52</guid>
      <description>&lt;p&gt;I work a lot with small UI assets — icons, simple illustrations, and design system components — and SVG is usually the starting point.&lt;br&gt;&lt;br&gt;
At some point, though, you almost always need PNGs: previews, exports for mobile, different scales, quick handoff, etc.&lt;/p&gt;

&lt;p&gt;That’s when I started noticing how frustrating most SVG to PNG converters are.&lt;/p&gt;

&lt;p&gt;Common issues I kept running into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;usage limits or queues
&lt;/li&gt;
&lt;li&gt;mandatory signups
&lt;/li&gt;
&lt;li&gt;uploading files to a server for a very simple task
&lt;/li&gt;
&lt;li&gt;bloated interfaces for something that should be quick
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is inherently bad, but it felt unnecessary for my day-to-day workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually needed
&lt;/h2&gt;

&lt;p&gt;My requirements were intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;convert SVG to PNG
&lt;/li&gt;
&lt;li&gt;export at different scales (1x, 2x, 3x)
&lt;/li&gt;
&lt;li&gt;no limits
&lt;/li&gt;
&lt;li&gt;no account
&lt;/li&gt;
&lt;li&gt;files never leave my machine
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically: drop the file, export, move on.&lt;/p&gt;

&lt;p&gt;After trying a few tools, I realized it would be faster to just build a small one myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  A browser-based approach
&lt;/h2&gt;

&lt;p&gt;The tool I ended up building runs entirely in the browser. There’s no backend involved — everything happens client-side using standard web APIs.&lt;/p&gt;

&lt;p&gt;That comes with a few nice side effects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unlimited usage by default
&lt;/li&gt;
&lt;li&gt;works offline once it’s loaded
&lt;/li&gt;
&lt;li&gt;no privacy concerns
&lt;/li&gt;
&lt;li&gt;very fast for small assets
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It started as something I built purely for my own use, but after a while I cleaned it up and made it public.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping it intentionally simple
&lt;/h2&gt;

&lt;p&gt;I deliberately avoided adding features “just because”.&lt;/p&gt;

&lt;p&gt;No batch processing (for now), no presets, no advanced options panel.&lt;br&gt;&lt;br&gt;
The goal is for it to feel closer to a small utility than a full-blown web app.&lt;/p&gt;

&lt;p&gt;If you work with SVGs regularly, you probably already have heavier tools in your stack. This is meant to cover the opposite end of that spectrum.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;If you’re curious, the converter is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fasttools.dev/svg-para-png" rel="noopener noreferrer"&gt;https://fasttools.dev/svg-para-png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No signup, no usage limits, and no file uploads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback welcome
&lt;/h2&gt;

&lt;p&gt;I’m mainly interested in feedback from people who actually work with SVGs in real projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frontend developers
&lt;/li&gt;
&lt;li&gt;designers
&lt;/li&gt;
&lt;li&gt;anyone maintaining icon libraries or design systems
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there’s a small feature you genuinely miss in tools like this, I’m open to ideas — as long as it keeps the tool lightweight.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Using KNN Algorithm for Player Matching in Online Games</title>
      <dc:creator>Fabiano Salles</dc:creator>
      <pubDate>Mon, 24 Apr 2023 14:55:34 +0000</pubDate>
      <link>https://dev.to/fabianosalles/using-knn-algorithm-for-player-matching-in-online-games-3d40</link>
      <guid>https://dev.to/fabianosalles/using-knn-algorithm-for-player-matching-in-online-games-3d40</guid>
      <description>&lt;h2&gt;
  
  
  A brief history of a real-world experience
&lt;/h2&gt;

&lt;p&gt;Recently I had the opportunity to work on the backend services of an online game where two players can mount a deck of summonable creatures and fight against each other in real time on their mobile devices. The game is fun and as the user base begins to grow a problem arises: how can we join two players in a fair fight?&lt;/p&gt;

&lt;p&gt;Imagine that we have, at a given moment, 1200 players searching for an opponent to fight. Some of them may have 10 hours of gaming while others may have 200 hours. Some are in early levels, others, way more advanced. Some may have a long stream of wins while others barely win a couple of fights. How do we measure who is a good opponent to who?&lt;/p&gt;

&lt;h2&gt;
  
  
  The KNN Algorithm
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm" rel="noopener noreferrer"&gt;K-Nearest Neighbors (KNN)&lt;/a&gt; is, probably, one of the first algorithms we learn when studying machine learning or applied statistics due to its simplicity and easy-to-grasp concepts. It is a distance-based machine-learning algorithm because it relies on the idea that related data should appear in the same region so, if two samples have a correlation, they should appear near each other in their projected space.&lt;/p&gt;

&lt;p&gt;Look at the graphics:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqj3yj1h9of7h0645we8n.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqj3yj1h9of7h0645we8n.png" alt="graphic 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the left graphic, there are 3 classes: yellow, green, and blue. We also have an unknown class point, represented by the white color, which we wish to classify based on the probability that it pertains to one of their “neighbor” classes.&lt;/p&gt;

&lt;p&gt;To achieve that, we calculate the distance between the white point to all the other points in the population as exemplified in the right image, and then choose the &lt;code&gt;k&lt;/code&gt; nearest points. If &lt;code&gt;k=3&lt;/code&gt;, for example, we have &lt;code&gt;2&lt;/code&gt; yellow points and 1 blue point selected, so there is a &lt;code&gt;66,6%&lt;/code&gt; of chance that this point is of yellow class, and... that's it. This is the basic idea of the &lt;a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm" rel="noopener noreferrer"&gt;K-Nearest Neighbors (KNN)&lt;/a&gt; algorithm. Really! It's just that.&lt;/p&gt;

&lt;p&gt;You can see the three steps illustrated in the following graphics:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5y0sxdls151nwgcffix.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5y0sxdls151nwgcffix.png" alt="graphic 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying it!
&lt;/h2&gt;

&lt;p&gt;So, the problem to solve is to choose a fair opponent for a given player among the universe of online players. I can’t publish real data here, but we can simulate and illustrate the problem with our own generated data. So let’s start with basic data creation on SQLServer.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tempdb..#PlayerData'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
   &lt;span class="k"&gt;drop&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;PlayerData&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;PlayerData&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;PlayerID&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;WinLossRatio&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Accuracy&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;PingTimeMS&lt;/span&gt; &lt;span class="nb"&gt;int&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 population of 100,000 random players&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="nb"&gt;int&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;while&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;PlayerData&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WinLossRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Accuracy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PingTimeMS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&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;set&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;PlayerData&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PlayerID&lt;/th&gt;
&lt;th&gt;WinLossRatio&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;PingTimeMS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0,907362409918751&lt;/td&gt;
&lt;td&gt;0,796193089617112&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0,415995706035458&lt;/td&gt;
&lt;td&gt;0,737326135001008&lt;/td&gt;
&lt;td&gt;374&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0,006123595106616&lt;/td&gt;
&lt;td&gt;0,502084640966905&lt;/td&gt;
&lt;td&gt;873&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0,167724872854256&lt;/td&gt;
&lt;td&gt;0,375710986550611&lt;/td&gt;
&lt;td&gt;906&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0,0498535991264818&lt;/td&gt;
&lt;td&gt;0,236891712930946&lt;/td&gt;
&lt;td&gt;996&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Good! We have a PlayerData table with 100,000 random players and 3 relevant attributes: &lt;code&gt;WinLossRatio&lt;/code&gt;, &lt;code&gt;Accuracy&lt;/code&gt;, and &lt;code&gt;PingTimeMS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While the first two are related to the player’s in-game performance, we also have available on our database, the last known user’s ping information. This is pretty useful to move away players with bad connection conditions and to show that we can do multi-dimensional projections on KNN implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating Distances
&lt;/h2&gt;

&lt;p&gt;There are several &lt;a href="https://www.ibm.com/topics/knn#:~:text=The%20k%2Dnearest%20neighbors%20algorithm%2C%20also%20known%20as%20KNN%20or,of%20an%20individual%20data%20point" rel="noopener noreferrer"&gt;distance calculations&lt;/a&gt; we can use with distance-based machine learning algorithms. We’ll stick with the most common: the &lt;a href="https://en.wikipedia.org/wiki/Euclidean_distance" rel="noopener noreferrer"&gt;Euclidean distance&lt;/a&gt;. So:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="cm"&gt;/* 
we should normalize the PingTimeMS data to the (0..1) range, considering 
the max(PingTimeMS) as the limit, so lets store it in @MaxPingTimeMS
*/&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;MaxPingTimeMS&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PingTimeMS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;PlayerData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;    &lt;span class="cm"&gt;/* K hyper parameter of KNN  */&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;TargetPlayerID&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; 

&lt;span class="c1"&gt;-- query to find the K-nearest neighbors of the TargetPlayerID&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;TIES&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;PlayerID&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="n"&gt;WinLossRatio&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="n"&gt;Accuracy&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;PingTimeMS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;MaxPingTimeMS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;NormalizedPing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
       &lt;span class="cm"&gt;/* euclidean distance between attributes */&lt;/span&gt;
       &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;power&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="n"&gt;WinLossRatio&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;opponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WinLossRatio&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="o"&gt;+&lt;/span&gt; 
         &lt;span class="n"&gt;power&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="n"&gt;Accuracy&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;opponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accuracy&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="o"&gt;+&lt;/span&gt;            
         &lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&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="n"&gt;PingTimeMS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;MaxPingTimeMS&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="n"&gt;opponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PingTimeMS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;MaxPingTimeMS&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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;Distance&lt;/span&gt;
 &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;PlayerData&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;PlayerData&lt;/span&gt; 
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;PlayerID&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;TargetPlayerID&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;opponent&lt;/span&gt;
 &lt;span class="k"&gt;where&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;PlayerID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;TargetPlayerID&lt;/span&gt;
   &lt;span class="k"&gt;and&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;PingTimeMS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="cm"&gt;/* minimum acceptable latency */&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;Distance&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PlayerID&lt;/th&gt;
&lt;th&gt;WinLossRatio&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Ping&lt;/th&gt;
&lt;th&gt;Distance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;33765&lt;/td&gt;
&lt;td&gt;0,9146020009807&lt;/td&gt;
&lt;td&gt;0,7903267476986&lt;/td&gt;
&lt;td&gt;0,053&lt;/td&gt;
&lt;td&gt;0,0110826732446677&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28785&lt;/td&gt;
&lt;td&gt;0,9161540072527&lt;/td&gt;
&lt;td&gt;0,7929262921270&lt;/td&gt;
&lt;td&gt;0,048&lt;/td&gt;
&lt;td&gt;0,0144555923270013&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17993&lt;/td&gt;
&lt;td&gt;0,9177007361853&lt;/td&gt;
&lt;td&gt;0,7932708847486&lt;/td&gt;
&lt;td&gt;0,071&lt;/td&gt;
&lt;td&gt;0,0161065288404087&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bingo! We had, with a single query, calculated the &lt;code&gt;K&lt;/code&gt;better fit for a &lt;code&gt;@TargetPlayerID&lt;/code&gt; player fight considering all the &lt;code&gt;3&lt;/code&gt; selected opponents' attributes using machine learning techniques! We can generalize this query in a &lt;code&gt;stored procedure&lt;/code&gt; and execute it from the backend controller code inside a REST API endpoint and consume it on our devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations
&lt;/h2&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25t8m02iw4d9rx0grujw.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25t8m02iw4d9rx0grujw.jpg" alt="KISS Principle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is, obviously, a pretty naive implementation with the purpose of exemplifying how we could rapidly implement and test machine learning techniques in this scenario. The KNN is known for its poor performance with large datasets but, if we can keep our problem’s population short enough, and choose wisely the attributes, we can use KNN for near real-time match-making in online gaming.&lt;/p&gt;

&lt;p&gt;On the project where we test this idea, we have around &lt;code&gt;2,500,000&lt;/code&gt; fights per day, &lt;code&gt;~60..80&lt;/code&gt; fights starting per second on peaks and the database server barely reaches 30% of CPU load which means we can still support a good amount of player base increase before we reach the hardware limit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/KISS_principle" rel="noopener noreferrer"&gt;Keep it simple&lt;/a&gt;, stupid, they say!&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;




&lt;p&gt;Originally written for &lt;a href="http://programmingdrops.com/" rel="noopener noreferrer"&gt;http://programmingdrops.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>tutorial</category>
      <category>sql</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
