diff --git a/nopixel_minigame/4.0/index.html b/nopixel_minigame/4.0/index.html
index 53a4856..1c8fa4b 100644
--- a/nopixel_minigame/4.0/index.html
+++ b/nopixel_minigame/4.0/index.html
@@ -40,6 +40,7 @@
+
Laundromat Minigame
Roof Running Minigame
NoPixel 3.0 Minigames
Whisper me on Twitch
@Sh4rkill3r or Discord
sharkiller
diff --git a/nopixel_minigame/4.0/laundromat/favicon.ico b/nopixel_minigame/4.0/laundromat/favicon.ico
new file mode 100644
index 0000000..46c085c
Binary files /dev/null and b/nopixel_minigame/4.0/laundromat/favicon.ico differ
diff --git a/nopixel_minigame/4.0/laundromat/index.html b/nopixel_minigame/4.0/laundromat/index.html
new file mode 100644
index 0000000..0004ec1
--- /dev/null
+++ b/nopixel_minigame/4.0/laundromat/index.html
@@ -0,0 +1,69 @@
+
+
+
+
+
NoPixel Laundromat Minigame
+
+
+
+
+
+
+
+
+
+
+
+
+
NoPixel Laundromat Minigame
+
+ Play the NoPixel Laundromat Minigame and match the correct colors.
+
+ How to play: Rotate the dots with ← and → or A and D to match the outer circle color.
+
+
+ STREAK: 0 MAX STREAK: 0
+ TIME: 0.000 BEST TIME: 0.000
+
+
+
+
Lockpick... Unlock each lock...
+
+
+
AGAIN!
+
+ Whisper me on Twitch
@Sh4rkill3r or Discord
sharkiller
+
+
+ I maintain these minigames in my spare time, free of ads and available to everyone.
+ If you wish to donate to dedicate more time is very appreciated.
+
+
+
+
+
+ See also:
+
>Laundromat Minigame<
+
Roof Running Minigame
+
NoPixel 3.0 Minigames
+
+
+
+
\ No newline at end of file
diff --git a/nopixel_minigame/4.0/laundromat/minigame.css b/nopixel_minigame/4.0/laundromat/minigame.css
new file mode 100644
index 0000000..189a51b
--- /dev/null
+++ b/nopixel_minigame/4.0/laundromat/minigame.css
@@ -0,0 +1,158 @@
+/*!
+ * Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face {
+ font-family: "Font Awesome 5 Free";
+ font-style: normal;
+ font-weight: 900;
+ font-display: block;
+ src: url(https://use.fontawesome.com/releases/v5.15.2/webfonts/fa-solid-900.eot);
+ src: url(https://use.fontawesome.com/releases/v5.15.2/webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),
+ url(https://use.fontawesome.com/releases/v5.15.2/webfonts/fa-solid-900.woff2) format("woff2"),
+ url(https://use.fontawesome.com/releases/v5.15.2/webfonts/fa-solid-900.woff) format("woff"),
+ url(https://use.fontawesome.com/releases/v5.15.2/webfonts/fa-solid-900.ttf) format("truetype"),
+ url(https://use.fontawesome.com/releases/v5.15.2/webfonts/fa-solid-900.svg#fontawesome) format("svg")
+}
+
+.fa, .fas {
+ font-family: "Font Awesome 5 Free", sans-serif;
+ font-weight: 900
+}
+
+/*!
+ * Game
+ */
+
+body{
+ background-color: #15181d;
+ font-family: sans-serif;
+}
+
+.title{
+ text-align: center;
+ color: white;
+}
+.description{
+ text-align: center;
+ color: gray;
+}
+.description sub{
+ font-style: italic;
+}
+
+.streaks {
+ display: block;
+ margin: 40px auto;
+ text-align: center;
+ font-size: 30px;
+ font-weight: bold;
+ text-transform: uppercase;
+ color: white;
+ width: -moz-fit-content;
+ width: fit-content;
+ background-color: #028000;
+ padding: 12px;
+ border-radius: 18px;
+}
+.best_time,
+.time {
+ display: inline-block;
+ width: 100px;
+}
+.streaks .fa {
+ padding: 0 10px;
+}
+.options{
+ font-weight: bold;
+ text-align: center;
+ margin: 0 auto 10px;
+}
+.option{
+ display: inline-block;
+ color: white;
+ font-weight: bold;
+ text-align: center;
+ width: -moz-fit-content;
+ width: fit-content;
+ height: 20px;
+ background-color: #20282e;
+ padding: 10px 20px;
+ margin: 0 auto 10px;
+ border-radius: 6px;
+}
+.option.speed input{
+ vertical-align: text-top;
+}
+.speed_value{
+ display: inline-block;
+ width: 18px;
+ text-align: right;
+}
+.dots_value{
+ display: inline-block;
+ width: 40px;
+ text-align: center;
+}
+.minigame{
+ margin: 0 auto 20px;
+ width: 540px;
+ min-width: 540px;
+ max-width: 540px;
+ height: 540px;
+ min-height: 540px;
+ max-height: 540px;
+ background-color: #232832;
+}
+.splash{
+ display: inline-block;
+ width: 100%;
+ margin: 220px auto;
+ text-align: center;
+ color: white;
+ font-size: 16px;
+ user-select: none;
+}
+.splash .hacker{
+ font-size: 65px;
+ margin-bottom: 30px;
+}
+.game-canvas{
+ margin: 0;
+ background-color: #0c2332;
+}
+.hidden {
+ display: none;
+}
+.restart{
+ text-align: center;
+}
+.btn_again {
+ padding: 6px 15px;
+ font-weight: bold;
+}
+.credits{
+ text-align: center;
+ color: gray;
+ margin-top: 40px;
+}
+.credits a{
+ color: #ccc;
+}
+.credits b{
+ color: white;
+}
+.credits .fas{
+ color: red;
+}
+.credits .donate{
+ margin: 20px 0;
+}
+.credits .coin{
+ font-weight: bold;
+ color: white;
+}
+.credits .wallet{
+ font-family: monospace;
+ font-size: 20px;
+}
diff --git a/nopixel_minigame/4.0/laundromat/minigame.js b/nopixel_minigame/4.0/laundromat/minigame.js
new file mode 100644
index 0000000..9f098a2
--- /dev/null
+++ b/nopixel_minigame/4.0/laundromat/minigame.js
@@ -0,0 +1,292 @@
+let timer_start, timer_finish, timer_hide, timer_time, wrong, speed, timerStart;
+let game_started = false;
+let streak = 0;
+let max_streak = 0;
+let best_time = 99.999;
+
+const random = (min, max) => {
+ return Math.floor(Math.random() * (max - min)) + min;
+}
+
+let stage = false
+
+let colors = [];
+let lastRotation = 0;
+let currentCircle = 1;
+let currentCirclePos = [];
+
+const getCookieValue = (name) => (
+ document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || ''
+)
+let getMaxStreakFromCookie = () => {
+ let str = getCookieValue('max-streak_laundromat');
+ if(str !== '')
+ return parseInt(str, 10);
+ else
+ return 0;
+}
+let getBestTimeFromCookie = () => {
+ let str = getCookieValue('best-time_laundromat');
+ if(str !== '')
+ return parseFloat(str);
+ else
+ return 99.999;
+}
+max_streak = getMaxStreakFromCookie();
+best_time = getBestTimeFromCookie();
+
+const sleep = (ms, fn) => {return setTimeout(fn, ms)};
+
+function addListeners(){
+ // Options
+ document.querySelector('#speed').addEventListener('input', function(ev){
+ document.querySelector('.speed_value').innerHTML = ev.target.value + 's';
+ streak = 0;
+ reset();
+ });
+ // Resets
+ document.querySelector('.btn_again').addEventListener('click', function(){
+ streak = 0;
+ reset();
+ });
+
+}
+
+function fillColor(color){
+ let dots = stage.find('#dots-'+(45 * currentCircle))[0];
+ dots.getChildren().forEach(dot => {
+ dot.fill(color);
+ });
+ let sections = stage.find('#section-'+(45 * currentCircle))[0];
+ sections.getChildren().forEach(section => {
+ section.fill(color);
+ });
+}
+
+function check(timeout){
+ if (timeout) {
+ streak = 0;
+ stopTimer();
+ }else if(currentCirclePos[45 * currentCircle] === 0){
+ if(currentCircle === 5){
+ streak++;
+ if(streak > max_streak){
+ max_streak = streak;
+ document.cookie = "max-streak_laundromat="+max_streak;
+ }
+ let time = document.querySelector('.streaks .time').innerHTML;
+ if(parseFloat(time) < best_time){
+ best_time = parseFloat(time);
+ document.cookie = "best-time_laundromat="+best_time;
+ }
+ if (!timeout) {
+ stopTimer();
+ reset();
+ }
+ }else{
+ fillColor('#08AF93DB');
+ currentCircle++;
+ dots = stage.find('#dots-'+(45 * currentCircle))[0];
+ lastRotation = dots.rotation();
+ }
+ }else{
+ fillColor('rgba(234,6,6,0.8)');
+ streak = 0;
+ stopTimer();
+ }
+}
+
+function reset(){
+
+ resetTimer();
+ clearTimeout(timer_start);
+ clearTimeout(timer_hide);
+ clearTimeout(timer_finish);
+
+ max_streak = getMaxStreakFromCookie();
+ best_time = getBestTimeFromCookie();
+
+ document.querySelector('.splash').classList.remove('hidden');
+ document.querySelector('.game-canvas').classList.add('hidden');
+
+ start();
+}
+
+document.addEventListener("keydown", function(ev) {
+ let key_pressed = ev.key;
+ let valid_keys = ['a','d','ArrowRight','ArrowLeft','Enter',' '];
+
+ if(key_pressed === 'r'){
+ streak = 0;
+ stopTimer();
+ reset();
+ return;
+ }
+ if(game_started && valid_keys.includes(key_pressed) ){
+ ev.preventDefault();
+ let rotation = false;
+ switch(key_pressed){
+ case 'a':
+ case 'ArrowLeft':
+ rotation = -1;
+ break;
+ case 'd':
+ case 'ArrowRight':
+ rotation = 1;
+ break;
+ case 'Enter':
+ case ' ':
+ check();
+ return;
+ }
+ let dots = stage.find('#dots-'+(45 * currentCircle))[0];
+ let currentRotation = dots.rotation() - lastRotation;
+
+ if(rotation !== false && currentRotation > -30 && currentRotation < 30){
+ currentCirclePos[45 * currentCircle] += rotation;
+ if(currentCirclePos[45 * currentCircle] < 0) currentCirclePos[45 * currentCircle] = 11;
+ if(currentCirclePos[45 * currentCircle] > 11) currentCirclePos[45 * currentCircle] = 0;
+ lastRotation += 30 * rotation;
+ new Konva.Tween({node: dots, duration: 0.2, rotation: lastRotation}).play();
+ }
+ }
+});
+
+function addDots(radius){
+
+ let dotsGroup = new Konva.Group({id: 'dots-'+radius, x: 270, y: 270});
+
+ let cols = ['#dd2169', '#258fe6', '#ffc30a'];
+ colors[radius] = [];
+
+ for(let i = 0; i < 12; i++) {
+ let randomColor = cols[Math.floor(Math.random() * cols.length)];
+ colors[radius][i] = randomColor;
+
+ if(Math.random() > 0.2) {
+ dotsGroup.add(new Konva.Circle({
+ x: Math.floor(radius * Math.cos(2 * Math.PI * i / 12)),
+ y: Math.floor(radius * Math.sin(2 * Math.PI * i / 12)),
+ radius: 8,
+ fill: randomColor,
+ }));
+ }
+ }
+
+ return dotsGroup;
+}
+
+function addSections(radius){
+ let sectionsGroup = new Konva.Group({id: 'section-'+radius, x: 270, y: 270});
+
+ for(let i = 0; i < 12; i++) {
+ let randomColor = colors[radius][i];
+
+ if(Math.random() > 0.4) {
+ sectionsGroup.add(new Konva.Arc({
+ fill: randomColor,
+ innerRadius: radius + 15,
+ outerRadius: radius + 20,
+ angle: 30,
+ rotation: ((30 * (i+1)) - 45)
+ }));
+ }
+ }
+
+ return sectionsGroup;
+}
+
+function shuffleDots(){
+ for(let i=1; i<=5; i++){
+ let randomPos = random(1,11);
+ currentCirclePos[45*i] = randomPos;
+ let dots = stage.find('#dots-'+(45 * i))[0];
+ dots.rotation( 30 * randomPos );
+ if(i === 1) lastRotation = 30 * randomPos;
+ }
+}
+
+
+function start(){
+ if(stage !== false){
+ stage.destroy();
+ }
+ stage = new Konva.Stage({
+ container: 'game-canvas',
+ width: 540,
+ height: 540,
+ });
+ colors = [];
+ lastRotation = 0;
+ currentCircle = 1;
+ currentCirclePos = [];
+
+ let staticLayer = new Konva.Layer();
+ for(let i=1; i<=5; i++){
+ staticLayer.add( new Konva.Circle({x: 270, y: 270, radius: 45*i, stroke: 'white', strokeWidth: 2}) );
+ }
+ for(let i=0; i<=5; i++){
+ staticLayer.add( new Konva.Line({x:270,y:270,points: [-240, 0, 240, 0], stroke: '#ffffffa0', strokeWidth: 1, rotation: 30*i}) );
+ }
+ staticLayer.add( new Konva.Line({points: [0, 540, 540, 540], stroke: '#26303e', strokeWidth: 15}) );
+ staticLayer.add( new Konva.Line({id: 'progress',points: [0, 540, 540, 540], stroke: '#ff4e00', strokeWidth: 15}) );
+ stage.add(staticLayer);
+
+ let gameLayer = new Konva.Layer({id:'game'});
+ for(let i=1; i<=5; i++){
+ gameLayer.add(addDots(45*i));
+ gameLayer.add(addSections(45*i));
+ }
+ stage.add(gameLayer);
+
+ shuffleDots();
+
+ document.querySelector('.streak').innerHTML = streak;
+ document.querySelector('.max_streak').innerHTML = max_streak;
+ document.querySelector('.best_time').innerHTML = best_time;
+
+ timer_start = sleep(500, function(){
+ document.querySelector('.splash').classList.add('hidden');
+ document.querySelector('.game-canvas').classList.remove('hidden');
+
+ game_started = true;
+
+ startTimer();
+ speed = document.querySelector('#speed').value;
+
+ let progress = stage.find('#progress')[0];
+ new Konva.Tween({node: progress, duration: speed, points: [0, 540, 0, 540]}).play();
+
+ timer_finish = sleep((speed * 1000), function(){
+ check(true);
+ });
+ });
+}
+
+function startTimer(){
+ timerStart = new Date();
+ timer_time = setInterval(timer,1);
+}
+function timer(){
+ let timerNow = new Date();
+ let timerDiff = new Date();
+ timerDiff.setTime(timerNow - timerStart);
+ let ms = timerDiff.getMilliseconds();
+ let sec = timerDiff.getSeconds();
+ if (ms < 10) {ms = "00"+ms;}else if (ms < 100) {ms = "0"+ms;}
+ document.querySelector('.streaks .time').innerHTML = sec+"."+ms;
+}
+function stopTimer(){
+ clearInterval(timer_time);
+ game_started = false;
+}
+function resetTimer(){
+ clearInterval(timer_time);
+ document.querySelector('.streaks .time').innerHTML = '0.000';
+}
+
+addListeners();
+
+start();
+
+
diff --git a/nopixel_minigame/4.0/roof_running/index.html b/nopixel_minigame/4.0/roof_running/index.html
index f53f4ff..354739a 100644
--- a/nopixel_minigame/4.0/roof_running/index.html
+++ b/nopixel_minigame/4.0/roof_running/index.html
@@ -53,6 +53,7 @@
See also: