I am building a game, where the computer makes a patter of four colors and you have to remember the pattern and repeat it.
And it works almost fine, but there is one error which i cant figure out.
When i played a round and lost and the game restarts the level value goes to 2 instead of 1.
And when the new game starts the first two buttons get pressed at the same time.
At the first round everything works fine but the next round not.
var gamePattern = [];
var playerPattern = [];
var buttonColors = ["red", "blue", "green", "yellow"]
var level = 0;
$(".btn").click(function () {
if (patternDone) {
pressButton(this.id);
playerPattern.push(this.id);
checkAnswer();
}
});
function resetGame() {
gamePattern = [];
playerPattern = [];
gamePattern.length = 0;
playerPattern.length = 0;
patternDone = false;
level = 0;
$("h1").html("Press A Key to Start")
$(document).keypress(startGame);
}
//start
$(document).keypress(startGame);
//start Game
function startGame() {
level = level + 1;
console.log("level " + level);
console.log(level);
gamePattern = [];
playerPattern = [];
createLevel();
console.log(gamePattern)
playPattern();
patternDone = true;
$("h1").html("Level " + level)
}
//play the patter
async function playPattern() {
for (k = -1; k < level; k++) {
d = level - k;
// console.log(gamePattern);
abcColor = gamePattern[gamePattern.length - d];
console.log(abcColor);
await delay(1000);
pressButton(abcColor);
d = 0;
}
}
//create the level
function createLevel() {
for (y = 0; y <= level; y++) {
var randomColor = buttonColors[Math.floor(Math.random() * buttonColors.length)];
gamePattern.push(randomColor);
}
}
//update h1
function h1Level() {
levelCopy = level + 1;
$("h1").html("level " + levelCopy);
}
//pressButton
function pressButton(colord) {
// console.log(colord);
animatePress(colord);
playSound(nameSound = colord);
}
// Sound
function playSound(nameSound) {
var audio = new Audio("sounds/" + nameSound + ".mp3");
audio.play();
}
// animateClick
function animatePress(currentColor) {
$("#" + currentColor).addClass("pressed");
// console.log(currentColor);
setTimeout(function () {
$("#" + currentColor).removeClass("pressed");
}, 100);
}
//delay
const delay = millis => new Promise((resolve, reject) => {
setTimeout(_ => resolve(), millis)
});
//Button click and deciding if its right
async function checkAnswer() {
if (playerPattern.length === gamePattern.length) {
if (playerPattern.join(',') === gamePattern.join(',')) {
$("h1").html("Correct!");
console.log("correct");
await delay(1000);
startGame();
} else if (playerPattern.join(',') !== gamePattern.join(',')) {
$("h1").html("Wrong!");
level = 0;
console.log("wrong");
await delay(3000);
resetGame();
}
}
}
I would like to setTimeout to every step/item from the GamePattern array. Suppose I am at LEVEL 4:
gamePattern [ 'red', 'blue', 'blue ] then Next Sequence.
But I need to run the sequence one-second gap between them, like 1sec to 'red', then 1sec to 'blue', 1sec to 'blue'...
Here is my entire code.
let userPattern = [];
const gameColors = ["green", "red", "yellow", "blue"];
let level = 0;
const sound = new Audio(`sounds/green.mp3`);
const wrong = new Audio("sounds/wrong.mp3");
//PLAYGAME
function playGame() {
document.querySelector("#overlay").style.display = "none";
level = 0;
gamePattern = [];
nextSequence();
}
//NEXT SEQUENCE
const nextSequence = function () {
document.querySelector(
"h2"
).innerHTML = `<h2 class="detach">LEVEL ${level}</span></h2>`;
userPattern = [];
let index = Math.round(Math.random() * 3);
let randomChosenColour = gameColors[index];
gamePattern.push(randomChosenColour);
animatePress(randomChosenColour);
};
function animatePress(buttoncolor) {
let button = document.querySelector(`#${buttoncolor}`);
button.classList.remove("hidden");
sound.play();
setTimeout(() => button.classList.add("hidden"), 200);
}
function userChoice(e) {
let colorClicked = e.target.classList[1];
animatePress(colorClicked);
userPattern.push(colorClicked);
checkAnswer(userPattern.length - 1);
}
for (let button of gameColors) {
document.querySelector(`.${button}`).addEventListener("click", userChoice);
}
//CHECK
function checkAnswer(currentlevel) {
if (userPattern[currentlevel] === gamePattern[currentlevel]) {
if (userPattern.length === gamePattern.length) {
previousPattern();
level++;
}
} else {
return gameOver();
}
}
//GAMEOVER
function gameOver() {
wrong.play();
//add eventlistener to restart the game
document.body.addEventListener("keypress", playGame);
//display an overlay GAME OVER SETUP.
document.querySelector("#overlay").style.display = "block";
document.querySelector("h2").innerHTML =
'<h2><span class="detach"> Lost at level: ' + level + "</span></h2>";
}
//START
document.body.addEventListener("keypress", playGame);
function previousPattern() {
//loop over the gamePatter in order to show the iteration 0.5 second gap.
for (let i = 1; i <= gamePattern.length; i++) {
setTimeout(() => {
animatePress(gamePattern[i - 1]);
}, 500 * i);
console.log(gamePattern.length, gamePattern);
if (i === gamePattern.length) {
return setTimeout(nextSequence, 1000 * i);
}
}
}
The thing is, The code doesn't show any improvement, and button animations overlap, and I don't know If I've messed up with so much setTimeout added.
Any hint? Thanks in advance
I'm on Firefox 84.0.1, Windows 10, x86_64. I have a very basic WebAudioWorklet synthesiser that maps keys to the frequencies of musical notes. It is generating very choppy audio when a key is held down. This makes me think that there is not enough audio samples being queued for the speaker to play, hence the audio dropping in and out. However, in audio processing terms, I'm performing a very low-intensive task. As a result, I feel like the default Worklet setup should be able to handle this. Here is my code:
syn.js
(async() => {
let a2_hertz = 110.0;
let twelfth_root_of_two = Math.pow(2.0, 1.0 / 12.0);
let audio_cxt = new AudioContext();
await audio_cxt.audioWorklet.addModule("syn-worklet.js", {credentials: "omit"});
let audio_worklet_options = {
numberOfInputs: 0,
numberOfOutputs: 1,
outputChannelCount: [audio_cxt.destination.channelCount]
};
let audio_worklet = new AudioWorkletNode(audio_cxt, "synthesiser", audio_worklet_options);
audio_worklet.connect(audio_cxt.destination);
document.addEventListener("keydown", (evt) => {
for (let key = 0; key < 12; ++key) {
if (evt.code == "Key" + "QWERTYUIOPAS"[key]) {
audio_worklet.port.postMessage(a2_hertz * Math.pow(twelfth_root_of_two, key));
}
}
});
document.addEventListener("keyup", (evt) => {
audio_worklet.port.postMessage(0.0);
});
})();
syn-worklet.js
function angular_frequency(hertz) {
return hertz * 2 * Math.PI;
}
let OSC_TYPES = {"sine": 0, "square": 1, "triangle": 2};
function oscillator(hertz, osc_type) {
switch (osc_type) {
case OSC_TYPES.sine: {
return Math.sin(angular_frequency(hertz) * currentTime);
} break;
case OSC_TYPES.square: {
return Math.sin(angular_frequency(hertz) * currentTime) > 0.0 ? 1.0 : -1.0;
} break;
case OSC_TYPES.triangle: {
return Math.asin(Math.sin(angular_frequency(hertz) * currentTime)) * (2.0 / Math.PI);
} break;
default: {
return 0.0;
}
}
}
class Synthesiser extends AudioWorkletProcessor {
constructor() {
super();
this.hertz = 0.0;
this.port.onmessage = (evt) => {
this.hertz = evt.data;
};
}
process(inputs, outputs) {
let channels = outputs[0];
let num_samples_per_channel = channels[0].length;
for (let pcm_i = 0; pcm_i < num_samples_per_channel; ++pcm_i) {
let volume = 0.1;
let pcm_value = volume * oscillator(this.hertz, OSC_TYPES.sine);
for (let channel_i = 0; channel_i < channels.length; ++channel_i) {
channels[channel_i][pcm_i] = pcm_value;
}
}
return true;
}
}
registerProcessor("synthesiser", Synthesiser);
I think the problem is that currentTime seems to be the only thing which influences the output of your oscillator() function. But currentTime doesn't change during the invocation of the process() function.
I would recommend using currentFrame instead. It will give you an integer value which represents the currentTime in frames. If you combine that with pcm_i you get the actual index of the sample that you're processing.
const currentSample = currentFrame + pcm_i;
const currentSampleInSeconds = (currentFrame + pcm_i) / sampleRate;
I have a problem with a countdown function I've created for an idiotic fight game I'm developing as an assignment.
I have the following method:
/* Countdown timer before fight */
startTimer() {
this.countDownPanel = true;
if (this.countDownNumbers > 0) {
this.countDown = setInterval(() => {
this.countDownNumbers--;
}, 1000);
this.textDownsizer = setInterval(() => {
this.countDownTextSize--;
}, 10);
}
},
And the following watcher that stops the countdown:
/* Countdown Watcher */
countDownNumbers() {
this.countDownTextSize = 100;
if (this.countDownNumbers < 0) {
clearInterval(this.textDownsizer);
this.textDownsizer = null;
clearInterval(this.countDown);
this.countDown = null;
this.countDownPanel = false;
}
}
},
The problem is that even after I use the clearInterval() through the Vue DevTools I can see the timer still running until countDownNumbers reaches 0. But the case is that it happens a while after the actual numbers on the screen are down to 0. And when I reset the game, running the same instance, the countdown goes nuts and causes a memory leak. Is there a better way of doing it?
Here's the complete Js code:
new Vue({
el: '#app',
data: {
gameStatus: 'disclaimer', //Status of the app, it can be (so far) 'disclaimer', 'startScreen', 'playStage', 'looseScreen', 'wonScreen', 'menuScreen'
backgroundAudio: new Array(), //Array of current playing background sounds
blurFilter: true, //Blur shown at the start of the app
audioTracks: ["sound/battle.mp3", "sound/dungeon.wav", "sound/echo.ogg"], //List of file in the folder 'sound'
playList: new Array(), //Array with a collection of Audio objects that are playable
/*
The health points function in an inverse way. The subject dies when it reaches 100
The health bar div width % is calculated subtracting the current health of the avatar of 100.
*/
healthHuman: 0,
healthComputer: 0,
muted: false, //Obviously determines if the audio is activated or not
humanStatus: true, //Defines if the human player is dead or alive
computerStatus: true, //Defines if the computer player is dead or alive
/* Collection of string that are outputed when a avatar dies */
humanDiesPhrases: [
"You've just been killed and that's why you're seeing your own life flash in front of you.",
"It's almost as if your soul has taken over your body in a frantic attempt to preserve the memory of what you love.",
"The problem is that when your soul leaves your body, it's physically incapable of going back in and if it doesn't try to go back in and make the same decision, it could start experiencing the same things all over again.",
"That was sad and gruesome and an almost poetic way. Anyway, you're dead. The rats are anxious to eat your remains.",
"Looks like the monster will be eating something besides rats today",
"Woow! That must have hurted! Dude! Are you okay? Dude? Dude... Ohh, you're dead. Sorry about that",
"Your death looks like an abstract painting. To me, not for you. For you that must have hurted like hell.",
"Yep! Looks like you're dead. Can I have your sowrd?",
"You died! Was killed to be more exact. Now your opponent drags you to one of many dark pits inside the dungeon.",
"Oh my god, how can you suck in such idiotic game? I know that the game is lame, but you just raised the bar fella.",
"You're not very good at this are you? Have you ever considered trying gardening?"
],
computerDiesPhrases: [
"You've just killed it! Or should I say him? Or her? You didn't ask, did you? How considerate of you.",
"GG mah boy, keep it like this and I'm sure you're going places. Maybe...",
"Wow that was 2 minutes less of your life, how does it feel? You're not having those back... Are you still reading?",
"Oh my, my... Why such violence? Have you considered petting it? Maybe it was just saying hello. Weirdo.",
"Well, it's dead. Now what? Are you going to eat it? Of course not silly, this is just a game. Back to being productive",
"AM I being nosy or you should be doind something else instead? I mean, killing virtual monsters?",
"Good kill dude! But, have you considered that it maybe was the last of its kind? That's disturbing.",
"You've just killed the creature! Good for you girl!",
"Wait! Have you considered trying to... Forget it, it's dead already",
"Oh man! (or woman!). That was gruesome. Wait a minute, have you just farted? Ewww! Ohh sorry, that one was mine.",
"Why did you do that? Such violence. Well you killed it, just like that. That's aliens haven't made contact with us yet...Savage!"
],
humanParts: [
"groin", "left eye", "upper lip", "right ear", "left leg", "tiny finger", "balls", "right knee", "face", "head", "lung", "chest", "chin", "middle finger", "you know where", "neck", "nose", "foot", "ribs", "teeth",
],
monsterParts: [
"center most tentacle", "foremost tooth", "tiny tentacle", "lady parts", "boy parts", "gum", "ass", "iris", "eyeball", "feelings", "wise tooth"
],
fightingMovements: [
"bite", "slapped", "punched", "kicked", "headbutted", "scratched", "hit", "punctured", "fingered", "poundded", "bashed"
],
countDownNumbers: 5, //Countdown counter =)
countDown: '', //Stores the main countdown
countDownTextSize: 100, //Obvious
textDownsizer: '', //Stores the timer responsible to downsize the font of the countdown
logger: new Array(), //Array responsible for storing all messages that will be shown to the player
countDownPanel: false, //Determines if the countdown is to be shown or not
},
computed: {
/* Unset the blur when the disclaimer modal goes away */
flipBlur() {
if (this.gameStatus != 'disclaimer') {
return this.blurFilter = false;
} else {
return true;
}
},
},
watch: {
//Wacth the gameStatus and make changes accordingly
gameStatus() {
//Background Audio Controler
//Tracks
echoSound = this.playList['echo'];
battleSound = this.playList['battle'];
dungeonSound = this.playList['dungeon'];
//Game status DJ
if (this.gameStatus == 'startScreen') {
this.loopPlay(echoSound);
this.loopPlay(dungeonSound);
} else if (this.gameStatus == 'playStage') {
//Add battle song to the background
this.loopPlay(battleSound);
battleSound.volume = 0.25;
//Trigger the coundown
this.startTimer();
}
},
muted() {
if (this.muted == true) {
this.backgroundAudio.forEach(element => {
element.pause();
});
} else {
this.backgroundAudio.forEach(element => {
element.play();
});
}
},
/* Health checkers // Killer */
healthHuman() {
if (this.healthHuman >= 100) {
this.healthHuman = 100;
this.humanStatus = false;
this.humanDied();
}
},
healthComputer() {
if (this.healthComputer >= 100) {
this.healthComputer = 100;
this.computerStatus = false;
this.computerDied();
}
},
/* Logger Wachter - Maintain only the last 3 elements of the logger */
logger() {
if (this.logger.length > 3) {
this.logger.pop();
}
},
/* Countdown Watcher */
countDownNumbers() {
this.countDownTextSize = 100;
if (this.countDownNumbers < 0) {
clearInterval(this.textDownsizer);
this.textDownsizer = null;
clearInterval(this.countDown);
this.countDown = null;
this.countDownPanel = false;
}
}
},
beforeDestroy() {
clearInterval(this.textDownsizer);
},
created: function () {
this.buildPlayList();
},
methods: {
/* Background soundtracks and sound effects */
loopPlay(audioTrack) {
if (audioTrack) {
audioTrack.play();
audioTrack.loop = true;
this.backgroundAudio.push(audioTrack);
}
},
buildPlayList() {
this.audioTracks.forEach(element => {
trackName = element.substring(element.indexOf('/') + 1);
trackName = trackName.substring(0, trackName.indexOf('.'));
this.playList[trackName] = new Audio(element);
});
},
/*
HP monitoring and controller
These two functions return the css property 'width' of the health bars.
*/
heartMonitorHuman() {
return {
width: this.healthHuman + '%'
}
},
heartMonitorComputer() {
return {
width: this.healthComputer + '%'
}
},
/* Generates random hit points */
hitGenerator() {
var hit = Math.round(Math.random() * 20);
return hit;
},
/* Controlls the timer before the fight */
counterFontSize() {
return {
'fontSize': this.countDownTextSize + 'vw'
}
},
/* Hits the human */
hitHuman() {
if (this.healthHuman < 100) {
var willHitPoints = Math.floor(this.hitGenerator() * 1.1); //Gives the monster 10% more attack power on average
this.healthHuman += willHitPoints;
this.logHandler('You got ' + this.randomFightingMoves() + ' on the ' + this.humanHitDesc() + ' and lost ' + willHitPoints + ' HP');
} else {
this.humanDied();
}
},
/* Hits the computer */
hitComputer() {
if (this.healthComputer < 100 && this.countDownNumbers <= 0) {
var willHitPoints = this.hitGenerator();
this.healthComputer += willHitPoints;
this.logHandler('You ' + this.randomFightingMoves() + ' the monster\'s ' + this.monsterHitDesc() + ' and it lost ' + willHitPoints + ' HP');
//Hits the human player back
this.hitHuman();
} else {
this.computerDied();
}
},
/* Shows phrases regarding the avatar's deaths */
humanDied() {
if (this.humanStatus == false) {
var deathMessage = Math.round(Math.random() * 10);
this.logHandler(this.humanDiesPhrases[deathMessage]);
this.logHandler("You died!");
setTimeout(() => {
this.resetGame();
}, 3000);
}
},
computerDied() {
if (this.computerStatus == false) {
var deathMessage = Math.round(Math.random() * 10);
this.logHandler(this.computerDiesPhrases[deathMessage]);
this.logHandler("You win?");
setTimeout(() => {
this.resetGame();
}, 3000);
}
},
/* Generates a random key to be used with a dictionary of log messages */
keyGen() {
return '_' + Math.random().toString(36).substr(2, 9);
},
/* Creates a dictionary with all log messages generated by the app */
logHandler(msg) {
var newLog = {
id: this.keyGen(),
message: msg,
};
this.logger.unshift(newLog);
},
/* Random hits generators */
humanHitDesc() {
randomNumber = Math.floor(Math.random() * 20);
return this.humanParts[randomNumber];
},
monsterHitDesc() {
randomNumber = Math.floor(Math.random() * 10);
return this.monsterParts[randomNumber];
},
randomFightingMoves() {
randomNumber = Math.floor(Math.random() * 10);
return this.fightingMovements[randomNumber];
},
resetGame() {
this.logger = [];
this.healthHuman = 0;
this.humanStatus = true;
this.healthComputer = 0;
this.computerStatus = true;
this.countDownNumbers = 5;
this.startTimer();
},
/* Countdown timer before fight */
startTimer() {
this.countDownPanel = true;
if (this.countDownNumbers > 0) {
this.countDown = setInterval(() => {
this.countDownNumbers--;
}, 1000);
this.textDownsizer = setInterval(() => {
this.countDownTextSize--;
}, 10);
}
},
}
});
The GitHub repo is: https://github.com/Saiuke/MonsterSlayer
Here's a screen capture of the game:
edited: sorry for the mistake. its fixed now.
Have you tried changing if (this.countDownNumbers < 0) to if (this.countDownNumbers === 0)? That might work. If that does not work then why not just make a simple countdown like this:
let num = 10;
let timer = setInterval(function(){
num = num - 1;
console.log(num);
if(num <= 0){
clearInterval(timer);
}
},1000);
The main issue is that startTimer() does not stop any existing timers before starting new ones. And since startTimer() is indirectly invoked by the user (i.e., humanDied() -> resetGame() -> startTimer(), computerDied() -> resetGame() -> startTimer(), or gameStatus change -> startTimer()), multiple timers can be started unpredictably.
The solution is to stop any existing timers before starting new ones, and to move the expiration logic from the countdownTimer watcher into startTimer():
export default {
watch: {
countDownNumbers() {
this.countDownTextSize = 100;
// XXX: DON'T DO THIS HERE
// if (this.countDownNumbers < 0) {
// clearInterval(this.textDownsizer);
// this.textDownsizer = null;
// clearInterval(this.countDown);
// this.countDown = null;
// this.countDownPanel = false;
// }
}
},
methods: {
startTimer() {
clearInterval(this.countDown);
this.countDown = null;
clearInterval(this.textDownsizer);
this.textDownsizer = null;
if (this.countDownNumbers > 0) {
this.countDown = setInterval(() => {
this.countDownNumbers--;
if (countDownNumbers <= 0) {
this.countDownPanel = false;
}
}, 1000);
this.textDownsizer = setInterval(() => {
this.countDownTextSize--;
}, 10);
}
}
}
}
you can do by this way it is so easy. this code is compatible with Vue-Js 3.
...... youe code......
,mounted() {
this.intervalId = setInterval(this.callStatusFn, 200)
console.log("intervalId "+ this.intervalId)
},
unmounted() {
clearInterval(this.intervalId)
}
I have game Server-side, update logic with 60 FPS and Send data with 10 FPS
var PLAYER_LIST = {};
class Player {
constructor(socket) {
this.id = Math.random() * 9999;
this.pressKeyAttack = 0;
this.socket = socket;
PLAYER_LIST[this.id] = this;
}
setupSocket() {
this.socket.on("keypress", (data) => {
if (data.key == 1) this.pressKeyAttack = data.value;
});
}
send() {
//Simple data
//Send to this player all data from PLAYER_LIST
var data = {};
for (var id in PLAYER_LIST) {
data[id] = PLAYER_LIST[id].pressKeyAttack;
}
this.socket.emit("data", data);
}
update() {
if (this.pressKeyAttack == 1) {
//do something
}
}
}
io.sockets.on("connection", (socket) => {
new Player(socket);
});
setInterval(() => {
//Logic update
for (var id in PLAYER_LIST) {
PLAYER_LIST[id].update();
}
}, 1000 / 60);
setInterval(() => {
//Send data
for (var id in PLAYER_LIST) {
PLAYER_LIST[id].send();
}
}, 1000 / 10);
Example Client-side, when any player press [Space] then all other player will see animation from that Player.
var PLAYER_LIST = {}; //all player
for (var id in PLAYER_LIST) {
if (PLAYER_LIST[id].pressKeyAttack == 1) {
//run animation
}
}
socket.on("data", (data) => {
for (var id in data) {
PLAYER_LIST[id].pressKeyAttack = data[id];
}
})
document.onkeydown = function(e) {
// Player.pressKeyAttack = 1
if (e.keyCode == 32) socket.emit("keypress", { key: 1, value: 1 });
}
document.onkeyup = function(e) {
// Player.pressKeyAttack = 0
if (e.keyCode == 32) socket.emit("keypress", { key: 1, value: 0 });
}
My problem:
Ex: I'm a player in game (called Player_A) and some other players. When Player_A press [Space] down and up so fast, other players can't receive data (pressKeyAttack = 1) from Player_A, because Loop Send Data run 10 FPS (send every 100ms), Player_A.pressKeyAttack equal 1 and back to 0 in 50ms.
Look like:
at 0ms: Player_A.pressKeyAttack = 0;
at 20ms: Player_A.pressKeyAttack = 1; //Player press down
at 50ms: Player_A.pressKeyAttack = 0; //Player press up
at 100ms: Loop Data Run - Player_A.pressKeyAttack = 0.
In fact it used to be 1. How can I fix this?