I had written a "KrazyKeno" game via Node-Red.
I should clarify, it's actually a Javascript game based on the classic video keno you see in Vegas.
It's served in a Node-Red http endpoint. I wanted to do different versions of Keno games similar to Cleopatra, Power Keno, Caveman, etc just for giggles. Also I was thinking of eventually doing a multiplayer tournament style, or a high score system, where you can log a highscore like old arcades, but more than 3 character.
Working example online... if it's online.
https://sidthetech.mooo.com:18800/krazykeno
[{"id":"d665993.ab87268","type":"http in","z":"6ca0c686.7d00d8","name":"","url":"/krazykeno","method":"get","upload":false,"swaggerDoc":"","x":136,"y":150,"wires":[["43bba2f9.f8188c"]]},{"id":"3ef5b8e3.3c9428","type":"http response","z":"6ca0c686.7d00d8","name":"","statusCode":"","headers":{},"x":578,"y":165,"wires":[]},{"id":"43bba2f9.f8188c","type":"template","z":"6ca0c686.7d00d8","name":"KRAZYKENO","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!--\n\nKrazyKeno - written by MrSiDX \nsidthetech@gmail.com\n\n-->\n<link href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T\" crossorigin=\"anonymous\">\n<script src=\"https://code.jquery.com/jquery-3.4.1.min.js\" integrity=\"sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=\" crossorigin=\"anonymous\"></script>\n<link href=\"https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css\" rel=\"stylesheet\" integrity=\"sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN\" crossorigin=\"anonymous\">\n<style>\nbody {\n background-color: black;\n color : white;\n} \n\ntable {\n border-width: 3px;\n border-color: yellow;\n border-style: solid;\n font-size: 25pt;\n text-shadow: 2px 2px #000000;\n margin: 15px;\n margin-bottom: 25px;\n cursor: pointer;\n}\n\ntd {\n border-right: 3px;\n border-style: solid;\n border-color: yellow;\n text-align: center;\n padding : 3px;\n}\n\n.paytable{\n border: 3px;\n border-style: solid;\n border-color: yellow;\n text-align: center;\n font-size: small;\n padding : 3px;\n /*overflow: fixed;*/\n}\n\n.tables {\n /*width: 80%;*/\n /*float:right;*/\n /*overflow: fixed;*/\n}\n\n.paytablerow {\n font-size:14px;\n}\n\n.wager {\n float:left;\n padding: 10px;\n}\n\n.credits {\n float:right;\n padding:10px;\n}\n\n\n.neon1 {\n color: #fff;\n font-family: Monoton;\n -webkit-animation: neon1 1.5s ease-in-out infinite alternate;\n -moz-animation: neon1 1.5s ease-in-out infinite alternate;\n animation: neon1 1.5s ease-in-out infinite alternate;\n}\n \n@-webkit-keyframes spot-match {\n from {\n text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #FF1177, 0 0 70px #FF1177, 0 0 80px #FF1177, 0 0 100px #FF1177, 0 0 150px #FF1177;\n }\n to {\n text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #FF1177, 0 0 35px #FF1177, 0 0 40px #FF1177, 0 0 50px #FF1177, 0 0 75px #FF1177;\n }\n} \n\n@-webkit-keyframes neon1 {\n from {\n text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #FF1177, 0 0 70px #FF1177, 0 0 80px #FF1177, 0 0 100px #FF1177, 0 0 150px #FF1177;\n }\n to {\n text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #FF1177, 0 0 35px #FF1177, 0 0 40px #FF1177, 0 0 50px #FF1177, 0 0 75px #FF1177;\n }\n}\n\n\n</style>\n\n\n<button onclick=\"function (){return 'value';}\">test</button>\n<div class=\"col-sm-12 col-md-12 col-lg-12 col-xl-12 neon1\" style=\"text-align:center; width:100%; font-size: large;\" >KRAZY KENO</div>\n\n<div class=\"container\" style=\"width:100%;\">\n <div class=\"row\">\n \n \n {{!--PAYTABLES--}}\n <div id=\"paytable\" style=\"\"class=\"payouts col-sm-3 col-md-3 col-lg-3 col-xl-3 float-left\">\n </div>\n {{!--END PAYTABLES--}}\n \n <div class=\"board col-sm-9 col-md-9 col-lg-9 col-xl-9\" >\n {{!--THE PLAYING BOARD--}}\n <table class=\"col-sm-12 col-md-12 col-lg-12 col-xl-12\" style=\"margin-bottom: 0;\">\n <tr><td id=\"spot1\">1</td><td id=\"spot2\">2</td><td id=\"spot3\">3</td><td id=\"spot4\">4</td><td id=\"spot5\">5</td><td id=\"spot6\">6</td><td id=\"spot7\">7</td><td id=\"spot8\">8</td><td id=\"spot9\">9</td><td id=\"spot10\">10</td></tr>\n <tr><td id=\"spot11\">11</td><td id=\"spot12\">12</td><td id=\"spot13\">13</td><td id=\"spot14\">14</td><td id=\"spot15\">15</td><td id=\"spot16\">16</td><td id=\"spot17\">17</td><td id=\"spot18\">18</td><td id=\"spot19\">19</td><td id=\"spot20\">20</td></tr>\n <tr><td id=\"spot21\">21</td><td id=\"spot22\">22</td><td id=\"spot23\">23</td><td id=\"spot24\">24</td><td id=\"spot25\">25</td><td id=\"spot26\">26</td><td id=\"spot27\">27</td><td id=\"spot28\">28</td><td id=\"spot29\">29</td><td id=\"spot30\">30</td></tr>\n <tr><td id=\"spot31\">31</td><td id=\"spot32\">32</td><td id=\"spot33\">33</td><td id=\"spot34\">34</td><td id=\"spot35\">35</td><td id=\"spot36\">36</td><td id=\"spot37\">37</td><td id=\"spot38\">38</td><td id=\"spot39\">39</td><td id=\"spot40\">40</td></tr>\n </table> \n \n <center> <div class=\"col-sm-12 col-md-12 col-lg-12 col-xl-12\" style = \"height:30px; padding: 0px;\" id=\"middlegamestatus\"></div></center>\n <table class=\"col-sm-12 col-md-12 col-lg-12 col-xl-12\" style=\"margin-top: 0;\">\n <tr><td id=\"spot41\">41</td><td id=\"spot42\">42</td><td id=\"spot43\">43</td><td id=\"spot44\">44</td><td id=\"spot45\">45</td><td id=\"spot46\">46</td><td id=\"spot47\">47</td><td id=\"spot48\">48</td><td id=\"spot49\">49</td><td id=\"spot50\">50</td></tr>\n <tr><td id=\"spot51\">51</td><td id=\"spot52\">52</td><td id=\"spot53\">53</td><td id=\"spot54\">54</td><td id=\"spot55\">55</td><td id=\"spot56\">56</td><td id=\"spot57\">57</td><td id=\"spot58\">58</td><td id=\"spot59\">59</td><td id=\"spot60\">60</td></tr>\n <tr><td id=\"spot61\">61</td><td id=\"spot62\">62</td><td id=\"spot63\">63</td><td id=\"spot64\">64</td><td id=\"spot65\">65</td><td id=\"spot66\">66</td><td id=\"spot67\">67</td><td id=\"spot68\">68</td><td id=\"spot69\">69</td><td id=\"spot70\">70</td></tr>\n <tr><td id=\"spot71\">71</td><td id=\"spot72\">72</td><td id=\"spot73\">73</td><td id=\"spot74\">74</td><td id=\"spot75\">75</td><td id=\"spot76\">76</td><td id=\"spot77\">77</td><td id=\"spot78\">78</td><td id=\"spot79\">79</td><td id=\"spot80\">80</td></tr>\n </table>\n </div>\n \n</div>\n\n \n <div id=\"controls\" class=\"controls col-sm-12 col-md-12 col-lg-12 col-xl-12\">\n <div class=\"\">\n <div id=\"status_wager\" class=\"wager\"></div>\n <div id=\"status_credits\" class=\"credits\"></div>\n </div>\n </div>\n \n {{!--CONTROLS--}}\n <div id=\"controls\" class=\"controls col-sm-12 col-md-12 col-lg-12 col-xl-12\">\n \n <button class=\"btn btn-primary btn-lg inline-block\" id=\"betmax\" onclick=\"betMax()\">Bet Max</button>\n <button class=\"btn btn-primary btn-lg inline-block\" id=\"BetOne\" onclick=\"betOne()\">Bet One</button>\n {{!--<button class=\"btn btn-primary btn-lg inline-block \" id=\"Clear Drawn\" onclick=\"clearDrawn()\">Clear Drawn</button>--}}\n <button class=\"btn btn-primary btn-lg inline-block\" id=\"clearPicks\" onclick=\"clearPicks()\">Erase</button>\n <button class=\"btn btn-primary btn-lg inline-block\" id=\"draw\" onclick=\"Draw()\">Start</button>\n \n <div id=\"betInfo\"></div>\n </div>\n \n</div>\n\n \n\nNumbers: <div id=\"numbers\"></div>\nMatched: <div id=\"matched\">{{matched}}</div>\n\n\n<script>\n\n\n//init:\nvar denom = 1;\nvar maxBet = 400;\nvar credits = 1000;\nvar currentBet = 0;\nvar minBet = denom;\nvar picked = [];\nvar paytable = [];\nvar current_paytable = [];\nvar readyToPick = true;\nvar matched = [];\nvar drawSpeed = 1000;\nvar matchCount = 0;\nvar clear_board_between_picks = false;\nvar max_pick_count = 10;\nvar wager = 0;\nvar last_wager = 0;\n\nvar spot_pick_color = 'lightblue'\nvar table_background_color = 'blue';\nvar matched_spot_color = 'purple';\nvar spot_missed_color = 'red';\n\nclearBoard();\nupdateCreditStatus();\n\nfunction betMax(){\n \n \n if(credits <= maxBet){\n wager = credits;\n } else{\n wager = maxBet;\n }\n \n updateCreditStatus();\n}\n\nfunction updateButtonStatus(){\n if (picked.length < 1){\n document.getElementById('draw').disabled = true;\n document.getElementById('draw').setAttribute('style', \"background-color: grey;\");\n document.getElementById('clearPicks').disabled = true;\n document.getElementById('clearPicks').setAttribute('style', \"background-color: grey;\");\n \n }else{\n document.getElementById('draw').disabled = false;\n document.getElementById('clearPicks').disabled = false;\n }\n}\n\nfunction updateCreditStatus(){\n document.getElementById('status_credits').innerHTML=\"Credit \" + credits;\n document.getElementById('status_wager').innerHTML = \"Bet \" + wager ;\n}\n\nfunction betOne(){\n \n if (wager <= maxBet && wager <= credits){\n wager = wager + 1 \n }\n // if (wager != 0 ){\n // wager = wager + 1;\n // }else {\n // if(credits > last_wager && last_wager != 0){\n // wager = last_wager;\n // }else{\n // wager = 1;\n // }\n // }\n \n // //credits = credits - 1;\n // } \n updateCreditStatus();\n}\n\n\nfunction displayPaytable(pt){\n \n //example\n //[0,1,2,3,4,5,6,7,8,9,10....]\n var inject = \"\";\n for(var i =0; i<pt.length;i++){\n if(pt[i] > 0){\n if( i == matched.length){\n inject = inject + '<tr style=\"margin:0; padding:5px; background-color:red;\" class=\"paytablerow\" id=\"paytablerow' + i + '\"><td>' + i + '</td><td>' + pt[i] + '</td></tr>';\n \n } else {\n inject = inject + '<tr style=\"margin:0; padding:5px; background-color: black;\" class=\"paytablerow\" id=\"paytablerow' + i + '\"><td>' + i + '</td><td>' + pt[i] + '</td></tr>';\n }\n \n }\n }\n \n \n document.getElementById('paytable').innerHTML = `\n <table class=\"\" id=\"paytable_inner\">\n <tr class=\"paytablerow\"><td>Hits</td><td>Win</td></tr>` + inject + `\n </table>\n `\n \n}\n\nfunction gameDone(){\n // check winnings and pay\n credits = credits + (current_paytable[matched.length] * wager);\n updateCreditStatus();\n last_wager = wager;\n document.getElementById('middlegamestatus').innerHTML = '<div style=\"width:100%; text-align: center; margin: 0;\">Game Over</div>';\n document.getElementById('controls').disabled = false;\n document.getElementById('controls').setAttribute('style', 'disabled: false');\n\n}\n\nfunction updatePaytable(){\n \n var spots = picked.length;\n // spots\n //1\n var r = [];\n if (spots == 1){\n r= [0,3]\n }\n if(spots == 2){\n r=[0,1,15]\n }\n if(spots == 3){\n r=[0,0,3,21]\n }\n if(spots == 4){\n r=[0,0,1,8,96]\n }\n if(spots == 5 ){\n r= [0,0,0,1,12,810]\n }\n if(spots == 6){\n r=[0,0,0,1,3,54,1600]\n }\n if(spots == 7){\n r= [0,0,0,1,15,21,46,3200]\n }\n if(spots == 8){\n r=[0,0,0,1,15,21,46,3200,6400]\n }\n if(spots == 9){\n r= [0,0,0,1,15,21,46,3200,6400,10000]\n }\n if(spots == 10){\n r= [0,0,0,1,15,21,46,200,1600,4260,10000]\n }\n current_paytable = r;\n displayPaytable(r);\n \n}\n\n\nfunction clearPicks(){\n if(readyToPick == false){\n }else{\n \n for( var i = 1; i< 81;i++){\n document.getElementById(\"spot\" + i).setAttribute('style', 'background-color: ' + table_background_color);\n }\n document.getElementById('middlegamestatus').innerHTML = \"\";\n picked = [];\n matched = [];\n wager = 0;\n updateCreditStatus();\n }\n }\n\nfunction clearBoard(){\n if(readyToPick == false){\n }else{\n for( var i = 1; i< 81;i++){\n document.getElementById(\"spot\" + i).setAttribute('style', 'background-color: ' + table_background_color);\n } \n }\n }\n \nfunction clearDrawn(){\n if(readyToPick == false){\n }else{\n \n for( var i = 1; i< 81;i++){\n document.getElementById(\"spot\" + i).setAttribute('style', 'background-color: ' + table_background_color);\n document.getElementById(\"spot\" + i).innerHTML = i;\n \n }\n for( var i = 0; i< picked.length;i++){\n document.getElementById(\"spot\" + picked[i]).setAttribute('style', 'background-color: ' + spot_pick_color);\n }\n }\n\n}\n \n function clearAll(){\n \n if(readyToPick == false){\n }else{\n for( var i = 1; i< 81;i++){\n document.getElementById(\"spot\" + i).setAttribute('style', 'background-color: ' + table_background_color);\n document.getElementById(\"spot\" + i).text = i;\n matched=[];\n picked=[];\n }\n }\n\n }\n \n \n\nfunction dabBoard(arr){\n // if(clear_board_between_picks == true){\n // clearDrawn();\n // }\n matched = [];\n //document.getElementById('numbers').innerHTML = arr;\n var i = 0;\n dly = setInterval(function (){\n if(i >= arr.length){\n readyToPick = true;\n clearInterval(dly);\n gameDone();\n \n }\n var nomatch = true;\n\n for(var s = 0; s < picked.length; s++){\n if ( arr[i] == picked[s] ){\n //match!\n nomatch = false;\n matched.push(picked[s]);\n document.getElementById(\"matched\").innerHTML = matched;\n document.getElementById(\"spot\" + picked[s]).setAttribute('style', 'background-color: ' + matched_spot_color);\n document.getElementById(\"spot\" + picked[s]).innerHTML = '<span><i class=\"fa fa-check-circle spot-match\"></i></span>'\n \n }\n }\n\n if ( nomatch ){\n document.getElementById(\"spot\" + arr[i]).setAttribute('style','background-color: ' + spot_missed_color);\n nomatch = false;\n }\n updatePaytable();\n i++;\n }, 100);\n \n}\n\n\n //keno draw numbers\nfunction drawNumbers(){\n var arr = [];\n var s = 0;\n var returnAllAtOnce = true;\n var amountToPick = 20;\n \n while(arr.length < amountToPick){\n var r = Math.floor(Math.random()*80) + 1;\n if(arr.indexOf(r) === -1) arr.push(r);\n }\n //alert(arr);\n return arr;\n}\n\n\n</script>\n\n\n<script>\n\n\n$( document ).ready(function() {\n \n $('td').click(function (){\n if( readyToPick == true){\n \n var spot = this.id.replace('spot','');\n var exists = false;\n //check if in picked list:\n for (var i = 0 ; i < picked.length; i++){\n if (spot == picked[i]){\n //unpick\n exists = true;\n picked.splice(i, 1);\n document.getElementById(\"spot\" + spot).setAttribute('style', 'background-color: ' + table_background_color);\n \n \n }\n }\n if (!exists){\n if(picked.length < max_pick_count){\n picked.push(spot);\n document.getElementById(\"spot\" + spot).setAttribute('style', 'background-color: ' + spot_pick_color);\n }\n }\n updatePaytable()\n updateButtonStatus();\n } \n });\n});\n \nfunction Draw(){\n\n if(readyToPick == false){\n }else{\n if(picked.length > 0 && (credits - wager) >= 0 && credits > 0 && wager > 0){\n updateCreditStatus()\n document.getElementById('middlegamestatus').innerHTML = \"\";\n document.getElementById('controls').disabled = true;\n clearDrawn();\n readyToPick = false;\n credits = credits - wager;\n updateCreditStatus();\n dabBoard(drawNumbers());\n }\n }\n}\n</script>","output":"str","x":316,"y":150,"wires":[["3ef5b8e3.3c9428","d8c921c9.74195"]]},{"id":"d8c921c9.74195","type":"debug","z":"6ca0c686.7d00d8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":517,"y":264,"wires":[]}]