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
}
}
Related
The thing that i wanna do is similiar with this video. In p5.js, I am using get() function. After use this, I will create small images (by dividing big tileset) and push them into an array. But in my code get() returns an empty pixels array. Here is a part of my code:
tilesImages = []; // tiles array
function preload() {
let tilesImage = loadImage(TILEMAP_PATH + "tiles.png", () => {
console.log("Tiles loaded successfully"); // It logs this
}, () => {
console.log("An error occured when tiles loaded");
});
for (let i = 0; i < TILE_HORIZONTAL; i++) {
for (let j = 0; j < TILE_VERTICAL; j++) {
let x = i * TILE_SIZE + TILES_SPACE;
let y = j * TILE_SIZE + TILES_SPACE;
if (i == 0) {
x = 0;
}
if (j == 0) {
y = 0;
}
var img = tilesImage.get(x, y, TILE_SIZE, TILE_SIZE); // get tiles from tileset
tilesImages.push(img);
}
}
}
function setup() {
console.log(tilesImages[0].pixels); // returns empty
}
I tried to use this but it just draws vertical pink lines to my small image.
My tileset: https://www.kenney.nl/assets/pixel-shmup (on right)
I am using single images now but I want to know the solution of this problem. Thanks
I believe the issue here is that you are not waiting for the image to actually be loaded. The loadImage function is asynchronous. That is, it only starts the process of loading the image, but it returns before it actually finish loading the image. So any code that comes after that will run immediately, while the loading is still in progress. You can use the callback function to run code after loading is complete. Here's a modified version of your code that should alleviate the problem:
tilesImages = []; // tiles array
function preload() {
let tilesImage = loadImage(
TILEMAP_PATH + "tiles.png",
() => {
console.log("Tiles loaded successfully"); // It logs this
// Wait until loading is complete to use tilesImage.get()
for (let i = 0; i < TILE_HORIZONTAL; i++) {
for (let j = 0; j < TILE_VERTICAL; j++) {
let x = i * TILE_SIZE + TILES_SPACE;
let y = j * TILE_SIZE + TILES_SPACE;
if (i == 0) {
x = 0;
}
if (j == 0) {
y = 0;
}
var img = tilesImage.get(x, y, TILE_SIZE, TILE_SIZE); // get tiles from tileset
tilesImages.push(img);
}
}
},
() => {
console.log("An error occured when tiles loaded");
}
);
}
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();
})
I have a slideshow on my website with left and right buttons.
Like this (http://i.prntscr.com/863ad10cfd4e4f1ea9b90721cc6582e8.png).
I am using angular to change the image on left and right.
As you can see in the function I increase the value
/*SlideShow Pictures*/
$scope.picture_1 = "./images/photos/watch.jpg";
$scope.picture_2 = "./images/photos/watch.jpg";
$scope.picture_3 = "./images/photos/watch.jpg";
$scope.picture_4 = "./images/photos/watch.jpg";
$scope.picture = $scope.picture_1;
$scope.picture_value = 1;
$scope.image_change_right = function () {
if ($scope.picture_value < 4)
{
$scope.picture_value = $scope.picture_value + 1;
$scope.picture = ('$scope.picture_' + $scope.picture_value);
console.log($scope.picture_value);
}
else{
$scope.picture_value = 1;
$scope.picture = ('$scope.picture_' + $scope.picture_value);
console.log($scope.picture_value);
}
}
Above is the function called for button right press.
The function increases the variable by 1 and adds it to the string to call the new variable. In the console log it looks great! However I think it is only showing as a string --- it is not actually setting the value of scope.picture to the variable.
How can I set this to not be a string but as a valid variable?
Thanks everyone!
A better way would be like this:
The Controller:
// The array of picture links.
$scope.pictures = [
"./images/photos/watch.jpg",
"./images/photos/watch.jpg",
"./images/photos/watch.jpg",
"./images/photos/watch.jpg"
];
$scope.current = 0; // Initialize the current pictures place in the array.
$scope.picture = $scope.pictures[$scope.current]; // Set the current picture.
// The direction is either 1 or -1;
$scope.changePicture = function (direction) {
$scope.current += direction; // add or remove one depending on direction.
$scope.current %= $scope.pictures.length; // Normalize the number based on the length of the pictures array.
console.log($scope.picture);
}
The Html:
<img src="{{picture}}">
<button ng-click="changePicture(1)">Next</button>
<button ng-click="changePicture(-1)">Previous</button>
Why don't you use an array with image links like this?
/*SlideShow Pictures*/
$scope.pictures = ["./images/photos/watch.jpg", "./images/photos/watch.jpg", "./images/photos/watch.jpg", "./images/photos/watch.jpg"];
$scope.picture = $scope.pictures[0];
$scope.picture_value = 0;
$scope.image_change_right = function () {
if ($scope.picture_value < 4)
{
$scope.picture_value = $scope.picture_value + 1;
$scope.picture = $scope.pictures[$scope.picture_value];
console.log($scope.picture_value);
}
else{
$scope.picture_value = 0;
$scope.picture = $scope.pictures[$scope.picture_value];
console.log($scope.picture_value);
}
}
Basically below is my script for a prototype which uses 128x128 tiles to draw a map on a canvas which user can drag to move around.
Script does work. However I have a few problems to be solved:
1. Poor performance and I can't figure out why.
2. I am missing a method to buffer the tiles before the actual drawing.
3. If you notice any other issues also that could help me to make things run more smoothly it would be fantastic.
Some explanations for the script:
variables
coordinates - Defines the actual images to be displayed. Image file names are type of '0_1.jpg', where 0 is Y and 1 is X.
mouse_position - As name says, is keeping record of mouse position.
position - This is a poorly named variable. It defines the position of the context drawn on canvas. This changes when user drags the view.
Any assistance would be appreciated greatly. Thank you.
var coordinates = [0, 0];
var mouse_position = [0, 0];
var position = [-128, -128];
var canvas = document.getElementById('map_canvas');
var context = canvas.getContext('2d');
var buffer = [];
var buffer_x = Math.floor(window.innerWidth/128)+4;
var buffer_y = Math.floor(window.innerHeight/128)+4;
var animation_frame_request = function() {
var a = window.requestAnimationFrame;
var b = window.webkitRequestAnimationFrame;
var c = window.mozRequestAnimationFrame;
var d = function(callback) {
window.setTimeout(callback, 1000/60);
}
return a || b || c || d;
}
var resizeCanvas = function() {
window.canvas.width = window.innerWidth;
window.canvas.height = window.innerHeight;
window.buffer_x = Math.floor(window.innerWidth/128)+4;
window.buffer_y = Math.floor(window.innerHeight/128)+4;
window.buffer = [];
for (row = 0; row < window.buffer_y; row++) {
x = [];
for (col = 0; col < window.buffer_x; col++) {
x.push(new Image());
}
window.buffer.push(x);
}
}
var render = function() {
animation_frame_request(render);
for (row = 0; row < window.buffer_y; row++) {
for (col = 0; col < window.buffer_x; col++) {
cy = window.coordinates[1]+row;
cx = window.coordinates[0]+col;
window.buffer[row][col].src = 'map/'+cy+'_'+cx+'.jpg';
}
}
for (row = 0; row < window.buffer_y; row++) {
for (col = 0; col < window.buffer_x; col++) {
window.context.drawImage(window.buffer[row][col],
window.position[0]+col*128,
window.position[1]+row*128, 128, 128);
}
}
}
var events = function() {
window.canvas.onmousemove = function(e) {
if (e['buttons'] == 1) {
window.position[0] += (e.clientX-window.mouse_position[0]);
window.position[1] += (e.clientY-window.mouse_position[1]);
if (window.position[0] >= 0) {
window.position[0] = -128;
window.coordinates[0] -= 1;
} else if (window.position[0] < -128) {
window.position[0] = 0;
window.coordinates[0] += 1;
}
if (window.position[1] >= 0) {
window.position[1] = -128;
window.coordinates[1] -= 1;
} else if (window.position[1] < -128) {
window.position[1] = 0;
window.coordinates[1] += 1;
}
render();
}
window.mouse_position[0] = e.clientX;
window.mouse_position[1] = e.clientY;
}
}
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('load', resizeCanvas, false);
window.addEventListener('mousemove', events, false);
resizeCanvas();
To get better performance you should avoid changing the src of img nodes and move them around instead.
A simple way to minimize the number of img nodes handled and modified (except for screen positioning) is to use an LRU (Least Recently Used) cache.
Basically you keep a cache of last say 100 image nodes (they must be enough to cover at least one screen) by using a dictionary mapping the src url to a node object and also keeping them all in a doubly-linked list.
When a tile is required you first check in the cache, and if it's already there just move it to the front of LRU list and move the img coordinates, otherwise create a new node and set the source or, if you already hit the cache limit, reuse the last node in the doubly-linked list instead. In code:
function setTile(x, y, src) {
var t = cache[src];
if (!t) {
if (cache_count == MAXCACHE) {
t = lru_last;
t.prev.next = null;
lru_last = t.prev;
t.prev = t.next = null;
delete cache[t.src]
t.src = src;
t.img.src = src;
cache[t.src] = t;
} else {
t = { prev: null,
next: null,
img: document.createElement("img") };
t.src = src;
t.img.src = src;
t.img.className = "tile";
scr.appendChild(t.img);
cache[t.src] = t;
cache_count += 1;
}
} else {
if (t.prev) t.prev.next = t.next; else lru_first = t.next;
if (t.next) t.next.prev = t.prev; else lru_last = t.prev;
}
t.prev = null; t.next = lru_first;
if (t.next) t.next.prev = t; else lru_last = t;
lru_first = t;
t.img.style.left = x + "px";
t.img.style.top = y + "px";
scr.appendChild(t.img);
}
I'm also always appending the requested tile to the container so that it goes in front of all other existing tiles; this way I don't need to remove old tiles and they're simply left behind.
To update the screen I just iterate over all the tiles I need and request them:
function setView(x0, y0) {
var w = scr.offsetWidth;
var h = scr.offsetHeight;
var iy0 = y0 >> 7;
var ix0 = x0 >> 7;
for (var y=iy0; y*128 < y0+h; y++) {
for (var x=ix0; x*128 < x0+w; x++) {
setTile(x*128-x0, y*128-y0, "tile_" + y + "_" + x + ".jpg");
}
}
}
most of the time the setTile request will just update the x and y coordinates of an existing img tag, without changing anything else. At the same time no more than MAXCACHE image nodes will be present on the screen.
You can see a full working example in
http://raksy.dyndns.org/tiles/tiles.html
I'm using this JS code to make a banner:
var images = ["../../images/g11.jpg","../../images/g9.jpg","../../images/g10.jpg"];
var titulos = ["title1","title2","title3"];
var resumos = ["ddd","aaa","bbb"];
var noticias = ["190","204","200"];
var total = 3;
var indice = 0;
function rotate() {
document.getElementById('imageb').src = images[indice];
document.getElementById('titulob').innerHTML = titulos[indice];
document.getElementById('resumob').innerHTML = resumos[indice];
document.getElementById('noticiab').value = noticias[indice];
indice++;
if (indice > total - 1) indice = 0;
}
function banner() {
rotate();
setTimeout(banner, 5000);
}
It works how expected, but after some loops it freezes the browser. Pretty sure I'm not using setTimeout properly. Any ideas?
Edit:
Working so far:
function rotate(indice) {
document.getElementById('imageb').src = images[indice];
document.getElementById('titulob').innerHTML = titulos[indice];
document.getElementById('resumob').innerHTML = resumos[indice];
document.getElementById('noticiab').value = noticias[indice];
}
function banner(indice) {
var f1 = function() { banner(indice); };
var total = 3;
rotate(indice);
indice++;
if (indice > total - 1) indice = 0;
setTimeout(f1, 5000);
}
I'm posting this as a CW because it's a total guess.
Completely FWIW, here's how I'd minimally change that code: Live Copy | Live Source
(function() {
var entries = [
{
img: "../../images/g11.jpg",
titulo: "title1",
resumo: "ddd",
noticia: "190"
},
{
img: "../../images/g9.jpg",
titulo: "title2",
resumo: "aaa",
noticia: "204"
},
{
img: "../../images/g10.jpg",
titulo: "title3",
resumo: "bbb",
noticia: "200"
}
];
var indice = 0;
function rotate() {
var entry = entries[indice];
document.getElementById('imageb').src = entry.img;
document.getElementById('titulob').innerHTML = entry.titulo;
document.getElementById('resumob').innerHTML = entry.resumo;
document.getElementById('noticiab').value = entry.noticia;
indice = (indice + 1) % data.length;
}
function banner() {
rotate();
setTimeout(banner, 5000);
}
banner();
})();
Changes:
Put everything in a scoping function to avoid creating global variables.
Use an array of objects rather than parallel arrays.
Use the array's length rather than a separate total variable.
Use the remainder trick for getting the wrap-around on the indice variable.
I added a call to banner(); at the end to get things started, but I assume you have that and just didn't show it.
But again, I don't see any reason your code shouldn't be working as is, other than the possibility of some weird global variable conflict.