// Cloned by Toma on 2 Dec 2023 from World "Rythm of the night" by Enhanced
// Please leave this clone trail here.
// Cloned by Enhanced on 2 Aug 2018 from World "Rythm of the night" by SinfulSalad
// Please leave this clone trail here.
// ==== Rythm of the night ===============================================================================================
// (c) Ancient Brain Ltd. All rights reserved.
// This code is only for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// This code may not be copied, re-published or used on any other website.
// To include a run of this code on another website, see the "Embed code" links provided on the Ancient Brain site.
// ==================================================================================================================
// ===================================================================================================================
// === Start of tweaker's box ========================================================================================
// ===================================================================================================================
// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?
document.write(`
<div>
<button id="startRecording">Start Recording</button>
<button id="stopRecording" disabled>Stop Recording</button>
<button id="useAudioButton" disabled>Use Audio</button>
</div>
`);
AB.clockTick = 20; // many frames per second, with small moves, looks best
AB.screenshotStep = 1500;
AB.maxSteps = 100000;
const SKYCOLOR = "#000000";
//the three images that represents the player (in this version it's three smileys)
var PLAYER_IMG = ['/uploads/sinfulsalad/neutralplayer.png',
'/uploads/sinfulsalad/okplayer.png',
'/uploads/sinfulsalad/badplayer.png'];
//the layers that constitute the background
const BACKGROUND_IMG = ['/uploads/sinfulsalad/neoncity.jpg',
'/uploads/sinfulsalad/citycropped.png',
'/uploads/sinfulsalad/neonground.png'];
//the scrolling speed of each layer.
//For more on how the background works, check out the "Scrolling Background"
//world of Enhanced
const BACKGROUND_SPEED = [1, 2, 4];
//the images for the 4 arrows
var OBSTACLES_IMG = ['/uploads/sinfulsalad/left.png',
'/uploads/sinfulsalad/up.png',
'/uploads/sinfulsalad/right.png',
'/uploads/sinfulsalad/down.png',];
//the image of the heart
var LIFE_IMG = ['/uploads/sinfulsalad/heart.png'];
// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================
// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.
var apiKey = "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";
//generate a random float between [ A ; B [
function randomfloatAtoB ( A, B )
{
return ( A + ( Math.random() * (B-A) ) );
}
//returns a whole number in from the [A,B[ range
function randomintAtoB ( A, B )
{
return ( Math.floor ( randomfloatAtoB ( A, B ) ) );
}
function World()
{
//------------------------ GLOBAL VARIABLES --------------------------------
//will contain the context of the canvas
var ctx;
//will keep track of how to draw the background
var backgroundPos = [];
var positionOffset;
//here is stored the sequence of arrows of the level
var obstacles = [];
//the length of the sequence
var nbObstacles;
//the variable level is always worth 5 more than the level the player
//is actually playing. For exemple, when he is playing level 1, this variable
//contains the number 6.
var level;
//the movement speed of the level
var speedMult;
//describes which type of arrows will be present in the level.
//it can take the values 38 to 41. The html keycode of the arrow keys goes
//from 37 to 40. 'actionsPossible = 38' means that only the arrowkey 37 will
//be present, 'actionsPossible = 39' means that both 37 and 38 will be present
//'actionsPossible = 40' means that 37 38 and 39 will be present, etc...
var actionsPossible;
//used to code what happens when the game ends.
//It can take the values 'going' and 'end'
var gameState;
//keep track of what input is needed from the player at any given moment
var actionRequired;
//keep track of wether the player did what was expected from him
var success;
//number of lives of the player
var life;
//serves only to keep track of when to display the sad smiley.
//unhappy is a whole number that is decremented at each step, down to zero.
//whenever 'unhappy > 0', the sad face is displayed
var unhappy;
//------------------------- INIT FUNCTIONS ---------------------------------
//properly load all the images
function initIMG()
{
var img;
//background
for (var i = 0; i<BACKGROUND_IMG.length ; i++)
{
img = new Image();
img.src = BACKGROUND_IMG[i];
BACKGROUND_IMG[i] = img;
backgroundPos.push(0);
}
//arrows
for (i = 0; i<OBSTACLES_IMG.length ; i++)
{
img = new Image();
img.src = OBSTACLES_IMG[i];
OBSTACLES_IMG[i] = img;
}
//player's smiley
for (i = 0; i<PLAYER_IMG.length ; i++)
{
img = new Image();
img.src = PLAYER_IMG[i];
PLAYER_IMG[i] = img;
}
//heart
img = new Image();
img.src = LIFE_IMG;
LIFE_IMG = img;
}
//setup the program so it listens to the player's input on the arrow keys
function keyDownHandler(e)
{
if (e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40)
{
if (actionRequired !== undefined && e.keyCode == obstacles[actionRequired])
success = true;
else unhappy = 10;
}
}
//-------------------------- ENGINE FUNCTIONS ------------------------------
function setupLevel()
{
life = 3;
//resets the drawing position of the obstacles
positionOffset = 0;
level++;
var speedDown = 0;
//on level 1, allows 2 types of arrows to be displayed
if (level >= 6)
{
actionsPossible = 39;
}
//on level 10, slows the game a little bit, and allows 3 types of arrows
//to be displayed
if (level >= 15)
{
speedDown = -0.6;
actionsPossible = 40;
}
//on level 20, slows the game a little bit, and allows the 4 types of arrows
//to be displayed
if (level >= 25)
{
speedDown = -1.2;
actionsPossible = 41;
}
//sets the right speed for the current level
speedMult = (1+level/8)+speedDown;
//generate the obstacles that will be part of this level
generateLevel();
}
//generate the obstacles that will be part of this level, and store them into
//obstacles[].
//the levels are generated procedurally.
function generateLevel()
{
//calculate the length of the sequence depending on the level
nbObstacles = 10+Math.floor(level/3);
//resets the array
obstacles = [];
//link each step of the sequence to either a specific arrow (represented
//by the keycode of that arrow)(70% of the time) either nothing (represented
//by the number -1)(30% of the time)
for (var i = 0 ; i<nbObstacles ; i++)
{
if (randomintAtoB(0,10)>2)
obstacles.push(randomintAtoB(37, actionsPossible));
else obstacles.push(-1);
//this additional -1 push is here to put a little bit of space between
//each obstacle
obstacles.push(-1);
}
}
function drawBackground()
{
//skyscrapers
backgroundPos[0] -= speedMult*BACKGROUND_SPEED[0]*AB.clockTick/30;
if (backgroundPos[0] < -threeworld.canvas.width)
backgroundPos[0] = 0;
ctx.drawImage( BACKGROUND_IMG[0], backgroundPos[0], 0, threeworld.canvas.width, 400 );
ctx.drawImage( BACKGROUND_IMG[0], threeworld.canvas.width+backgroundPos[0], 0, threeworld.canvas.width, 400 );
//low city
backgroundPos[1] -= speedMult*BACKGROUND_SPEED[1]*AB.clockTick/30;
if (backgroundPos[1] < -threeworld.canvas.width)
backgroundPos[1] = 0;
ctx.drawImage( BACKGROUND_IMG[1], backgroundPos[1], threeworld.canvas.height-500, threeworld.canvas.width, 280 );
ctx.drawImage( BACKGROUND_IMG[1], threeworld.canvas.width+backgroundPos[1], threeworld.canvas.height-500, threeworld.canvas.width, 280 );
//floor
backgroundPos[2] -= speedMult*BACKGROUND_SPEED[2]*AB.clockTick/30;
if (backgroundPos[2] < -threeworld.canvas.width)
backgroundPos[2] = 0;
ctx.drawImage( BACKGROUND_IMG[2], backgroundPos[2], threeworld.canvas.height-250, threeworld.canvas.width, 250 );
ctx.drawImage( BACKGROUND_IMG[2], threeworld.canvas.width+backgroundPos[2], threeworld.canvas.height-250, threeworld.canvas.width, 250 );
}
function drawPlayer()
{
var image;
//if the player did what whas expected of him, display the happy smiley
if (success)
image = PLAYER_IMG[1];
//display the sad face whenever the player does an unexpected input
else if (unhappy > 0)
image = PLAYER_IMG[2];
//display the neutral smiley
else
image = PLAYER_IMG[0];
ctx.drawImage( image, 220, threeworld.canvas.height-210, 100, 100);
ctx.fillRect(430, threeworld.canvas.height-195, 5, 70);
}
//draw the arrows AND calculate new position
function drawObstacles()
{
for (var i = 0 ; i < obstacles.length ; i++)
{
//if that step of the sequence corresponds to an arrow
if(obstacles[i] >= 0)
{
ctx.drawImage(OBSTACLES_IMG[obstacles[i]-37], threeworld.canvas.width-positionOffset+50*i-10, threeworld.canvas.height-180, 70, 50);
//if that step is finished
if(threeworld.canvas.width-positionOffset+50*i+10 < 430-50)
{
//if that step was the 'current step' at the previous frame
if (actionRequired == i)
{
//since this step just ended, the action expected from the
//player is reseted, so is the fact that he did what was needed.
//Plus he looses a heart if he missed the arrow
if (!success)
loseLife();
else success = false;
actionRequired = undefined;
}
}
//else if this is the current step
else if(threeworld.canvas.width-positionOffset+50*i < 435)
actionRequired = i;
}
//the value of an obstacle is set to -2 if the player got it wrong
//if that value is -2, replace the arrow by a red square
if(obstacles[i] == -2)
{
ctx.fillRect(threeworld.canvas.width-positionOffset+50*i, threeworld.canvas.height-180, 50, 50);
}
}
}
function drawLife()
{
for (var i = 0 ; i < life ; i++)
ctx.drawImage(LIFE_IMG, 150, threeworld.canvas.height-140-50*i, 50, 50);
}
function loseLife()
{
life--;
obstacles[actionRequired] = -2;
//draws a red splash screen for a frame
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (life === 0)
gameState = 'end';
}
//---------------------------- RUN FUNCTIONS -------------------------------
this.newRun = function()
{
//init world and context
threeworld.init ( SKYCOLOR );
ctx = threeworld.getContext ( "2d" );
initIMG();
//initialisation of the global variables
gameState = 'going';
unhappy = 0;
positionOffset = 0;
actionRequired = undefined;
success = false;
life = 3;
//since the id of the level played by the player is always the value of the level variable
//minus 5, and since the level variable is incremented at the start of generateLevel(),
//initialising this variable with the value of 4 means that the first level played
//will be level 0
level = 4;
speed = 0;
actionsPossible = 38;
setupLevel();
//initialisation of the font
ctx.fillStyle = '#FF2222';
ctx.textAlign = 'center';
ctx.font = 'bold 70px Arial';
document.onkeydown = keyDownHandler;
//initialisation of the save&load feature
if ( AB.runloggedin )
{
// Definitely can save, not sure if can restore:
$("#user_span2").html ( " <button onclick='AB.saveData();' >Save your work</button> " );
// Check if any data exists, if so make restore button:
AB.queryDataExists(); // will call World.queryDataExists when done
}
else
$("#user_span2").html( " <p align=\"center\"> <b> To save your work, go to <br>the World page and run this \"logged in\". </b> </p> " );
};
this.nextStep = function()
{
$("#user_span3").html("<p align=\"center\">hit the correct arrow key when <br> the arrow reaches the red line</p>");
const myImage = document.getElementById("myImage");
const useAudioButton = document.getElementById("useAudioButton");
const startRecordingButton = document.getElementById("startRecording");
const stopRecordingButton = document.getElementById("stopRecording");
const isLoading = document.getElementById("isLoading");
const transcribedText = document.getElementById("transcribedText");
let mediaRecorder;
let audioChunks = [];
useAudioButton.disabled = true;
startRecordingButton.addEventListener("click", startRecording);
stopRecordingButton.addEventListener("click", stopRecording);
useAudioButton.addEventListener("click", function () {
isLoading.innerHTML = "Loading........";
const audioBlob = new Blob(audioChunks, { type: "audio/mpeg" });
transcribeAudio(audioBlob);
});
// $("#user_span4").html ( "<button onclick='startRecording();' >Start Recording</button> " );
// $("#user_span4").html("<p align=\"center\">hit the correct ar <br> the arrow reaches the red line</p>");
if (gameState == 'going')
{
drawBackground();
drawObstacles();
if (positionOffset > threeworld.canvas.width-430+100+nbObstacles*100)
{
setupLevel();
}
drawPlayer();
drawLife();
if (unhappy > 0)
unhappy -= 1;
positionOffset += speedMult*BACKGROUND_SPEED[BACKGROUND_SPEED.length-1]*AB.clockTick/30;
ctx.fillText('level '+(level-5), 500, 100);
}
else if (gameState == 'end')
{
drawBackground();
ctx.fillText('GAME OVER', threeworld.canvas.width/2+100, 180);
}
};
function startRecording() {
navigator.mediaDevices
.getUserMedia({ audio: true })
.then((stream) => {
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = () => {
const audioBlob = new Blob(audioChunks, { type: "audio/mpeg" });
const audioUrl = URL.createObjectURL(audioBlob);
document.getElementById("audioPlayback").src = audioUrl;
};
audioChunks = [];
mediaRecorder.start();
startRecordingButton.disabled = true;
useAudioButton.disabled = true;
stopRecordingButton.disabled = false;
})
.catch((e) => console.error(e));
}
function stopRecording() {
mediaRecorder.stop();
startRecordingButton.disabled = false;
stopRecordingButton.disabled = true;
useAudioButton.disabled = false;
}
function transcribeAudio(audioBlob) {
const formData = new FormData();
formData.append("file", audioBlob, "recording.mp3");
formData.append("model", "whisper-1");
formData.append("language", "en");
fetch("https://api.openai.com/v1/audio/transcriptions", {
method: "POST",
body: formData,
headers: {
Authorization: `Bearer ${apiKey}`,
},
})
.then((response) => response.json())
.then((data) => {
console.log("Transcription:", data);
generateImage(data.text);
})
.catch((error) => {
console.error("Error:", error);
isLoading.innerHTML = "";
});
}
function generateImage(text) {
fetch("https://api.openai.com/v1/images/generations", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: text,
}),
})
.then((response) => response.json())
.then((data) => {
isLoading.innerHTML = "";
transcribedText.innerHTML = text;
var imageUrl = data.data[0].url;
BACKGROUND_IMG[0].src = imageUrl;
// myImage.src = imageUrl;
})
.catch((error) => {
console.error("Error calling DALLĀ·E API", error);
isLoading.innerHTML = "";
});
}
//----------------------- SAVE AND LOAD FUNCTIONS --------------------------
//this function saves the more advanced level you reached.
//Since the saveDate don't seem to work with single digit numbers, I add 10
//to the level, and then substract 10 when I load the data from the server.
//Finally, I substract 1, because the level variable is incremented at the start
//of the generateLevel() function
this.saveData = function()
{
// if no restore button, can make one now
$("#user_span1").html ( " <button onclick='AB.restoreData();' >Restore your work</button> " );
// console.log ( "Saving " + BLOCKARRAY.length + " blocks to server" );
return ( level+10 );
};
this.restoreData = function ( a )
{
level = a-11;
setupLevel();
};
this.queryDataExists = function ( exists )
{
if ( exists )
$("#user_span1").html ( " <button onclick='AB.restoreData();' >Restore your work</button> " );
};
}