I'm doing a project for school. A memory game that uses the pokéAPI to get images.
I have three buttons depending on the game level (easy, medium, hard) and it generates the amount of cards.
When I click a button it generates the pictures but when I click it again it append the new data into the same selector.
I have tried with: $('#output').html(''), $('#output').append(''), $('#output').remove(), $('#output').empty()
// Settings for game level. Each integer represents number of cards * 2.
let easy = 4;
let medium = 6;
let hard = 8;
// Arrays for PokemonImgUrl.
let originalPokemonImgUrl = [];
let duplicateAllPokemonImgUrl = [];
let allPokemonImgUrl = [];
// PokéAPI URL.
const pokemonDataUrl = 'https://pokeapi.co/api/v2/pokemon/';
// This function creates a random number depending on the settings below.
function randomNumber() {
// Settings for max randomnumbers starting from index 1.
let randomNumberMax = 500;
let fromIndex = 1;
// Math random function with values from randomnumbers.
return Math.floor(Math.random() * randomNumberMax) + fromIndex;
}
// Function for getting data from PokéAPI.
function getData() {
$.ajax ({
type: 'GET',
url: pokemonDataUrl + randomNumber(), // Calling randomnnumber to get a random pokémon.
success: function(pokemonData) {
var pokemonImgUrl = pokemonData.sprites.front_default; // Store and extract pokemon images.
originalPokemonImgUrl.push(pokemonImgUrl); // store ImagesURL to a global array called allPokemonImgUrl.
}
})
}
// Shuffle code from css-tricks.com.
function Shuffle(cards) {
for(var j, x, i = cards.length; i; j = parseInt(Math.random() * i), x = cards[--i], cards[i] = cards[j], cards[j] = x);
return cards;
};
// function iterates through allPokemonImgUrl array and outputs it into the DOM.
function output() {
allPokemonImgUrl.forEach(function (i) {
$('#output').append('<img src="'+ [i] +'">');
}
)}
/* This function copies the array so that we always have two of the same cards.
Then concat into a new array and shuffles it. After that it outputs the result.*/
function startGame(){
setTimeout( function(){
duplicateAllPokemonImgUrl = originalPokemonImgUrl.slice();
}, 1000 );
setTimeout( function(){
allPokemonImgUrl = originalPokemonImgUrl.concat(duplicateAllPokemonImgUrl);
}, 1500 );
setTimeout( function(){
Shuffle(allPokemonImgUrl)
}, 2000 );
setTimeout( function(){
output();
}, 2500 );
}
/* Events for clicking on game levels. It iterates to check how many cards it needs
and calls the function getData accordingly. */
$(document).on('click', '#easy', function() {
for (var cards = 0; cards < easy; cards++) {
getData();
}
startGame();
})
$(document).on('click', '#medium', function() {
for (var cards = 0; cards < medium; cards++) {
getData();
}
startGame();
})
$(document).on('click', '#hard', function() {
for (var cards = 0; cards < hard; cards++) {
getData();
}
startGame();
})
When I click a button I get the images as expected.
But when I click it again it appends the new data after the old data. I want to remove the old ajax data first and then append the new data.
I found the solution:
Adding a clear function:
function clear() {
originalPokemonImgUrl = [];
duplicateAllPokemonImgUrl = [];
allPokemonImgUrl = [];
$('#output').empty();
}
Then just call it:
$(document).on('click', '#easy', function() {
for (var cards = 0; cards < easy; cards++) {
getData();
}
clear();
startGame();
})
Related
I have a course where I learn JS+JQuery and I have to write a memory game. I want to check if the turned cards are in the same classes so I can delete them and count the points, but I just cannot do it. I tried a lot of ways but I cannot figure it out.
I tried with .is(), === but nothing...
Here's my code:
let gameArea;
let size = 6;
let card_size = 600 / size;
let images = ['arbalest', 'armored', 'arms', 'cannoner', 'cataphract', 'cavalier', 'centurion', 'champion', 'composite',
'conquistador', 'eagle', 'heavy', 'heavyhorse', 'hussar', 'knight', 'legion', 'paladin', 'skirmisher'
];
$(function() {
gameArea = $('<div></div>');
gameArea.appendTo('body');
gameArea.attr('id', 'gameArea');
drawMap();
gameArea.on("click", "div", function(e) {
if ($(e.target).is(".unturned")) {
$(e.target).removeClass("unturned");
}
let unturnedCardNum = $("#gameArea").find("div:not(.unturned)").length;
if (unturnedCardNum === 3) {
$("#gameArea").find("div:not(.unturned)").addClass("unturned");
}
//I'd like to check here
});
});
function generate() {
let generatedImage = images[Math.floor(Math.random() * images.length)];
if ($("#gameArea").find(`.${generatedImage}`).length === 2) {
images = images.filter(function(e) {
return e !== generatedImage
});
generatedImage = generate();
}
return generatedImage;
}
function drawMap() {
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
let pic = $('<div></div>');
let shuffledImages = generate();
pic.addClass(shuffledImages);
pic.addClass("unturned");
pic.css({
width: card_size,
height: card_size,
top: i * card_size,
left: j * card_size
});
pic.appendTo(gameArea);
}
}
}
Assuming that the string in the images array will have been assigned to the associated card as a class name and that, after removing the "unturned" class, this will be the only class name left then maybe the following might help you:
Inside the $("#gameArea").on("click"...) function replace the lines
let unturnedCardNum = $("#gameArea").find("div:not(.unturned)").length;
if (unturnedCardNum === 3) {
$("#gameArea").find("div:not(.unturned)").addClass("unturned");
}
with
let unturnedCards = $("#gameArea").find("div:not(.unturned)");
switch(unturnedCards.length){
case 3:
unturnedCards.addClass("unturned");
break;
case 2:
if (unturnedCards[0].className===
unturnedCards[1].className){
console.log("you found a pair!");
// score[currentPlayer]++;
}
}
Further hint:
Instead of calling the generate() function recursively, you could simply shuffle an array made up of two copies of the images array by using a Fisher-Yates algorithm:
function shuffle(a,n){ // shuffle array a in place (Fisher-Yates)
let m=a.length;
n=n||m-1;
for(let i=0,j;i<n;i++){
j=Math.floor(Math.random()*(m-i)+i);
if (j-i) [ a[i],a[j] ] = [ a[j],a[i] ]; // swap 2 array elements
}
}
I'm using the 'timeupdate' event listener to sync a subtitle file with audio.
It is working currently, but I'd like to adjust it to where it is just highlighting the corresponding sentence in a large paragraph instead of deleting the entire span and replacing it with just the current sentence. This is the sort of functionality I am trying to replicate: https://j.hn/lab/html5karaoke/dream.html (see how it only highlights the section that it is currently on).
This is made complicated due to timeupdate constantly checking multiple times a second.
Here is the code:
var audioSync = function (options) {
var audioPlayer = document.getElementById(options.audioPlayer);
var subtitles = document.getElementById(options.subtitlesContainer);
var syncData = [];
var init = (function () {
return fetch(new Request(options.subtitlesFile))
.then((response) => response.text())
.then(createSubtitle);
})();
function createSubtitle(text) {
var rawSubTitle = text;
convertVttToJson(text).then((result) => {
var x = 0;
for (var i = 0; i < result.length; i++) {
if (result[i].part && result[i].part.trim() != '') {
syncData[x] = result[i];
x++;
}
}
});
}
audioPlayer.addEventListener('timeupdate', function (e) {
syncData.forEach(function (element, index, array) {
if (
audioPlayer.currentTime * 1000 >= element.start &&
audioPlayer.currentTime * 1000 <= element.end
) {
while (subtitles.hasChildNodes()) {
subtitles.removeChild(subtitles.firstChild);
}
var el = document.createElement('span');
el.setAttribute('id', 'c_' + index);
el.innerText = syncData[index].part + '\n';
el.style.background = 'yellow';
subtitles.appendChild(el);
}
});
});
};
new audioSync({
audioPlayer: 'audiofile', // the id of the audio tag
subtitlesContainer: 'subtitles', // the id where subtitles should show
subtitlesFile: './sample.vtt', // the path to the vtt file
});
I have a little game I play around with to learn Javascript better.
The player gets to choose
So i'm doing a little dice game in javascript/jquery. I get some users (In this case players) from a REST API that i get with AJAX Like this:
var APICaller = (function () {
let endpoint = "https://jsonplaceholder.typicode.com/";
function api_call(method, url, data, callback) {
$.ajax({
url: url,
method: method,
data: data,
success: callback
});
}
function get_users(callback) {
let method = "GET";
let url = endpoint + "users";
let data = {};
api_call(method, url, data, callback);
}
return {
get_users: get_users,
};
})();
The person playing the game can choose a player from the user list and then press the play button.
When the play button is pressed the dice should roll 3 numbers for each player and display all the numbers at the side of each player. And then the dice value list should change after a time and only the total amount of the three dice values should be in that list, and also the user list should be in points order with the highest total points on the top.
I have manage to solve all of those problems but I just need guidance of how to make the player I choose to have a green background color in the final score board.
This is how my code looks like:
var players = [];
var currentPlayer;
function add_scoreboard() {
$("#my-list").css({
display: 'none'
});
$("#my-list2").css({
display: 'block'
});
// currentPlayer.style.background("green");
let elements = []
let container = document.querySelector('#main_gameDiv')
//Add each row to the array
container.querySelectorAll('.list-li2').forEach(el => elements.push(el))
//Clear the container
container.innerHTML = ''
//Sort the array from highest to lowest
elements.sort((a, b) => b.querySelector('.score').textContent - a.querySelector('.score').textContent)
//Put the elements back into the container
elements.forEach(e => container.appendChild(e))
if(elements.firstchild.data == currentPlayer.firstchild.data){
$(this).css({background: 'green'});
}
console.log(elements);
}
var EventHandlers = (function () {
function init() {
APICaller.get_users(on_get_users_success);
function on_get_users_success(response) {
//For each user in the API
$.each(response, function (i, user) {
$("#my-list").append('<li class="list-li"><a class="list-a">' + user.name + '</a><span class="score"></span></li>');
$("#my-list2").append('<li class="list-li2">' + user.name + '<span class="score"></span></li>');
var playerObject = {
id: user.id,
name: user.name,
score: 0
};
//Add all objects to the array
players.push(playerObject);
});
$("#my-list2").css({
display: 'none'
});
//change information
$("#info-txt").text("Välj en spelare!");
}
// On klick on a user make klicked user your own player.
$("#my-list").on('click', '.list-a', function () {
currentPlayer = this;
$("#info-txt").text("Tryck på spela knappen för att börja spelet!");
$("#currentPlayer-div").animate({
height: '300px',
opacity: '1'
});
$("#currentPlayer-h3").text(this.text);
});
// On klick of the play button
$("#startGame-button").click(function () {
$("#info-txt").text("Vänta 5 sekunder tills scoreboarden visas :)");
setTimeout(function () {
add_scoreboard();
}, 5000);
$("#my-list2").css({
display: 'none'
});
console.log(players);//Show players in console just for extra info
$(".score").animate({
opacity: '1'
});
$("#currentPlayer-div").animate({
height: '150px'
});
$("#startGame-button").animate({
opacity: '0'
});
$("#dice_value").animate({
opacity: '1'
});
$("#my-list li").each(function (index) {
var numbers = [];
var v1 = Math.floor(Math.random() * 6) + 1;
var v2 = Math.floor(Math.random() * 6) + 1;
var v3 = Math.floor(Math.random() * 6) + 1;
//Add the numbers to the array
numbers.push(v1, v2, v3);
var totalScore = numbers.reduce(function (a, b) {
return a + b;
}, 0); //Count the sum of the array
//Set the players points to the sum of the array
players[index].score = totalScore;
//Find every list item of type span and set HTML to the content of the array
$(this).find(".score").html(numbers.toString());
});
$("#my-list2 li").each(function (index) {
var numbers = [];
var v1 = Math.floor(Math.random() * 6) + 1;
var v2 = Math.floor(Math.random() * 6) + 1;
var v3 = Math.floor(Math.random() * 6) + 1;
//Add the numbers to the array
numbers.push(v1, v2, v3);
var totalScore = numbers.reduce(function (a, b) {
return a + b;
}, 0); //Count the sum of the array
//Set the players points to the sum of the array
players[index].score = totalScore;
//Find every list item of type span and set HTML to the content of the array
$(this).find(".score").html(totalScore.toString());
});
});
}
return {
init: init,
}
})();
$(document).ready(function () {
EventHandlers.init();
});
This is where i choose the player:
// On klick on a user make klicked user your own player.
$("#my-list").on('click', '.list-a', function () {
currentPlayer = this;
$("#info-txt").text("Tryck på spela knappen för att börja spelet!");
$("#currentPlayer-div").animate({
height: '300px',
opacity: '1'
});
$("#currentPlayer-h3").text(this.text);
});
And this is where I try to set the color on that:
function add_scoreboard() {
$("#my-list").css({
display: 'none'
});
$("#my-list2").css({
display: 'block'
});
// currentPlayer.style.background("green");
let elements = []
let container = document.querySelector('#main_gameDiv')
//Add each row to the array
container.querySelectorAll('.list-li2').forEach(el => elements.push(el))
//Clear the container
container.innerHTML = ''
//Sort the array from highest to lowest
elements.sort((a, b) => b.querySelector('.score').textContent - a.querySelector('.score').textContent)
//Put the elements back into the container
elements.forEach(e => container.appendChild(e))
if(elements.firstchild.data == currentPlayer.firstchild.data){
$(this).css({background: 'green'});
}
console.log(elements);
}
I think it is if(elements.firstchild.data == currentPlayer.firstchild.data){ $(this).css({background: 'green'}); } and i know there is a problem because it is not changing background color.
Hope that describes it enough.
Thanks in advance!
Since elements is an array it doesn't have a property firstchild so that is why you get Uncaught TypeError: Cannot read property 'data' of undefined. It seems like what you want is to include that code in your elements.forEach function.
For example:
//Put the elements back into the container
elements.forEach(e => {
container.appendChild(e);
if (e.firstchild.data == currentPlayer.firstchild.data) {
$(e).css({
background: 'green'
});
}
});
I have two functions. In the first one I increase a variable by adding 100 to it and I put a setInterval so the funcion repeats itself after some time. The other function is a class, a contrusctor to create an object. I want this.x_origen to get increased by adding aumento to it after some time and repeat it. However what I'm getting here is that the first function increases aument and then it finishes and then the second function starts. How can I solve this?
var aument = 0;
function aumento(){
aument = aument + 100;
return aument;
}
setInterval(function () {aumento()}, 1000/50);
function create_class_brick (x_origen_in, y_origen_in, x_final_in, y_final_in, mi_estado, mi_velocidad, mi_id){
this.x_origen = x_origen_in + aumento();
this.y_origen = y_origen_in;
this.x_final = x_final_in + aumento();
this.y_final = y_final_in;
this.estado = mi_estado;
this.velocidad = mi_velocidad;
this.id_elemento = mi_id;
this.DESPLAZAR_LADRILLO = desplazar_ladrillo;
this.F0 = f0;
this.F2 = f2;
this.crear_ladrillo = crear_ladrillo;
this.obtener_x_origen_ladrillo = obtener_x_origen_ladrillo;
this.obtener_y_origen_ladrillo = obtener_y_origen_ladrillo;
this.obtener_x_final_ladrillo = obtener_x_final_ladrillo;
this.obtener_y_final_ladrillo = obtener_y_final_ladrillo;
}
An example on how to wait for the initial call:
function brick (x_origen_in){
this.x_origen = x_origen_in;
}
function aumento(brick){
console.log(brick.x_origen);
brick.x_origen += 100;
setTimeout(aumento.bind(this, brick), 500);
}
var brick = new brick(100);
aumento(brick);
http://jsfiddle.net/x6c08u39/
You can use Object.defineProperty to dynamically generate the value whenever it is accessed.
First, lets simplify the auto-incrementing of aument:
var aument = 0;
function aumento(){
aument += 100;
}
// The first argument for setInterval is the function to execute
// No need to figure out the interval value at runtime as there are no dynamic values
setInterval(aumento, 20); // 1000/50 === 20
Now lets make an object that will have a the correct value:
function create_class_brick (x_origen_in, y_origen_in, x_final_in, y_final_in, mi_estado, mi_velocidad, mi_id){
Object.defineProperty(this, 'x_origen', {
get: function () { return x_origen_in + aument; }
});
// Other stuff
// ...
}
A quick test:
> aument
34100
> var obj = new create_class_brick(23);
undefined
> obj.x_origen
161523
> obj.x_origen
167223
> obj.x_origen
172423
I understand that in order to remove an interval you need a stored reference to it, so I figured I'll store function returns in a global array.
But why when I click on a cell the intervals keep going and only one stops (I saw first and last cell to stop flashing)?
What I wanted it to do is append a continuous fadeTo on all cells of a table row (excluding first one), and a listener that on clicking any of these cells would stop animations on all of them.
Here's my best effort so far (jsfiddle):
var intervals;
var target = $('#table');
var qty = target.find('td:contains(Price)');
var row = qty.closest('tr');
arr = new Array();
row.find('td').each( function() {
if ($(this).text() !== "Price" ) {
intervals = new Array();
addAnimation($(this));
}
});
function addAnimation(cell) {
var intv = setInterval(function() {
cell.fadeTo("slow", 0.3);
cell.fadeTo("slow", 1);
}, 1000);
intervals.push(intv);
cell.click(function() {
for (var i = 0; i < intervals.length; intervals++) {
window.clearInterval(intervals[i]);
}
});
}
You are instantiating the intervals array multiple times and incrementing the wrong parameter in the for loop:
var intervals = [],
target = $('#table'),
qty = target.find('td:contains(Price)'),
row = qty.closest('tr');
row.find('td').each( function() {
if ($(this).text() !== "Price" ) {
addAnimation($(this));
}
});
function addAnimation(cell) {
var intv = setInterval(function() {
cell.fadeTo("slow", 0.3);
cell.fadeTo("slow", 1);
}, 1000);
intervals.push(intv);
cell.click(function() {
for (var i = 0; i < intervals.length; i++) {
window.clearInterval(intervals[i]);
}
$(this).stop();
});
}
See: fiddle
Your other problem is here:
var intervals;
...
if ($(this).text() !== "Price" ) {
intervals = new Array();
addAnimation($(this));
That creates a new array each time. You should be initialising intervals when you declare it and delete the line creating a new array in the if block:
var intervals = [];
...
if ($(this).text() !== "Price" ) {
addAnimation($(this));
}
However, you may wish to run this more than once, so you should clear out the array when you clear the intervals, something like:
function addAnimation(cell) {
var intv = setInterval(function() {
cell.fadeTo("slow", 0.3);
cell.fadeTo("slow", 1);
}, 1000);
intervals.push(intv);
cell.click(function() {
for (var i = 0; i < intervals.length; intervals++) {
window.clearInterval(intervals[i]);
}
// reset the array
intervals = [];
});
}
or replace the for loop with something like:
while (intervals.length) {
window.clearInterval(intervals.pop());
}
which stops the intervals and clears the array in one go. :-)