Im trying to create a program for one of my games(details not needed) nevertheless I can move around aside elements written in the code
<aside draggable="true" class="dragme" data-item="0">One</aside>
but if I create it at runtime(via button click) it gives me this error in the console
Uncaught TypeError: Cannot read property 'style' of undefined
Here is my full code anybody have ideas?
<!DOCTYPE HTML>
<html>
<head>
<style>
aside {
position: absolute;
left: 0;
top: 0;
width: 200px;
background: rgba(255, 255, 255, 1);
border: 2px solid rgba(0, 0, 0, 1);
border-radius: 4px;
padding: 8px;
}
.second {
left: 100px;
top: 100px;
}
body, html {
min-height: 100vh;
}
body{
width:700px;
height:700px;
}
</style>
</head>
<body ondragover="drag_over(event)" ondrop="drop(event)">
<div class="ControlPanel">
<button onclick="CreateNew('item1')">Item1</button>
</div>
<aside draggable="true" class="dragme" data-item="0">One</aside>
<script>
var dataNum = 0;
function CreateNew(item){
if(item == "item1"){
dataNum += 1;
var asi = document.createElement("ASIDE");
asi.setAttribute("draggable",true);
asi.setAttribute("class","dragme second");
asi.setAttribute("data-item",dataNum);
asi.setAttribute("style","left: 347px; top: 82px;");
document.body.appendChild(asi);
}
}
function drag_start(event) {
var style = window.getComputedStyle(event.target, null);
event.dataTransfer.setData("text/plain", (parseInt(style.getPropertyValue("left"), 10) - event.clientX) + ',' + (parseInt(style.getPropertyValue("top"), 10) - event.clientY) + ',' + event.target.getAttribute('data-item'));
}
function drag_over(event) {
event.preventDefault();
return false;
}
function drop(event) {
var offset = event.dataTransfer.getData("text/plain").split(',');
var dm = document.getElementsByClassName('dragme');
dm[parseInt(offset[2])].style.left = (event.clientX + parseInt(offset[0], 10)) + 'px';
dm[parseInt(offset[2])].style.top = (event.clientY + parseInt(offset[1], 10)) + 'px';
event.preventDefault();
return false;
}
var dm = document.getElementsByClassName('dragme');
for (var i = 0; i < dm.length; i++) {
dm[i].addEventListener('dragstart', drag_start, false);
document.body.addEventListener('dragover', drag_over, false);
document.body.addEventListener('drop', drop, false);
}
</script>
</body>
</html>
You are registering the event listener only once during the page load. Therefore, when you create a new element, you need to re-register the events for the newly created elements too.
<script>
var dataNum = 0;
function CreateNew(item){
if(item == "item1"){
dataNum += 1;
var asi = document.createElement("ASIDE");
asi.setAttribute("draggable",true);
asi.setAttribute("class","dragme second");
asi.setAttribute("data-item",dataNum);
asi.setAttribute("style","left: 347px; top: 82px;");
document.body.appendChild(asi);
registerDragMe(); // --> Add this
}
}
function drag_start(event) {
var style = window.getComputedStyle(event.target, null);
event.dataTransfer.setData("text/plain", (parseInt(style.getPropertyValue("left"), 10) - event.clientX) + ',' + (parseInt(style.getPropertyValue("top"), 10) - event.clientY) + ',' + event.target.getAttribute('data-item'));
}
function drag_over(event) {
event.preventDefault();
return false;
}
function drop(event) {
var offset = event.dataTransfer.getData("text/plain").split(',');
var dm = document.getElementsByClassName('dragme');
dm[parseInt(offset[2])].style.left = (event.clientX + parseInt(offset[0], 10)) + 'px';
dm[parseInt(offset[2])].style.top = (event.clientY + parseInt(offset[1], 10)) + 'px';
event.preventDefault();
return false;
}
// create a wrapper function
function registerDragMe(){
var dm = document.getElementsByClassName('dragme');
for (var i = 0; i < dm.length; i++) {
dm[i].addEventListener('dragstart', drag_start, false);
document.body.addEventListener('dragover', drag_over, false);
document.body.addEventListener('drop', drop, false);
}
}
registerDragMe();
</script>
Working example : https://jsfiddle.net/jcLjr70y/
Related
Hello Stackoverflow Users, so I am making a game with HTML, I have to eat berries, but when I collect them, the more I collect (the max is 5), the more offset the extra body parts add to all of the body parts (minus the main one).
I have tried changing offset with the variables and loop I used, but nothing worked, no matter the math operator.
All of jQuery code:
// Null Variables \\
/* These variables have no value. */
var bound;
var newFood;
var horizontalMatch;
var vertialMatch;
// Main Variables \\
/* These variables keep values in store. */
var background = $(".game-background");
var player = $(".game-player");
var verticalNum = 0;
var horizontalNum = 0;
var score = 0;
var speed = 20;
var updatePos = 0;
var numOfSnakes = 0;
var snakesArray = [];
// Main Functions \\
/* These are all of the function being called, and created. */
updateSnakeCSS = function (snakes) {
if (snakes.length > -1) {
//for (var i = 0; i < snakes.length; i++) {
console.log(snakes[numOfSnakes - 1]);
$("." + snakes[numOfSnakes - 1]).css("position", "absolute");
$("." + snakes[numOfSnakes - 1]).css("left", player.offset().left - 20);
$("." + snakes[numOfSnakes - 1]).css("top", player.offset().top);
$("." + snakes[numOfSnakes - 1]).css("border", "black");
$("." + snakes[numOfSnakes - 1]).css("border-style", "outset");
$("." + snakes[numOfSnakes - 1]).css("border-width", 2 + "px");
$("." + snakes[numOfSnakes - 1]).css("width", player.width());
$("." + snakes[numOfSnakes - 1]).css("height", player.height());
$("." + snakes[numOfSnakes - 1]).css("background-color", "red");
//}
}
};
addSnake = function (snakes) {
if (numOfSnakes < 5) {
var body = document.getElementsByTagName("body")[0];
var newBodyPart = document.createElement("div");
newBodyPart.className = "game-player" + numOfSnakes;
body.appendChild(newBodyPart);
snakes.push("game-player" + numOfSnakes);
console.log(updatePos);
numOfSnakes++;
}
};
updateScore = function () {
score++;
$(".game-scoreboard").text("Berries: " + score);
};
createFood = function ($parentDiv) {
if (newFood == null) {
var x = WMath.random(220, background.width() * 1.5 - 100);
var y = WMath.random(100, background.height());
var body = document.getElementsByTagName("body")[0];
newFood = document.createElement("div");
newFood.className = "game-food";
body.appendChild(newFood);
$(".game-food").css("width", player.width() / 2);
$(".game-food").css("border", "black");
$(".game-food").css("border-style", "outset");
$(".game-food").css("border-width", 1 + "px");
$(".game-food").css("height", player.width() / 2);
$(".game-food").css("background-color", "red");
$(".game-food").css("position", "absolute");
$(".game-food").css("left", x);
$(".game-food").css("top", y);
}
};
var WMath = {
random: function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min); //The maximum is inclusive and the minimum is inclusive
},
};
var WObject = {
isTouching: function ($div1, $div2) {
var x1 = $div1.offset().left;
var y1 = $div1.offset().top;
var h1 = $div1.outerHeight(true);
var w1 = $div1.outerWidth(true);
var b1 = y1 + h1;
var r1 = x1 + w1;
var x2 = $div2.offset().left;
var y2 = $div2.offset().top;
var h2 = $div2.outerHeight(true);
var w2 = $div2.outerWidth(true);
var b2 = y2 + h2;
var r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
console.log("Touching WObject");
newFood = null;
createFood(document.body);
updateScore();
addSnake(snakesArray);
updateSnakeCSS(snakesArray);
speed += 2;
$div2.remove();
return true;
},
};
createFood(document.body);
$(document).keyup(function (e) {
var key = e.keyCode || e.which;
const keys = {
UP: 38,
DOWN: 40,
LEFT: 37,
RIGHT: 39,
};
if (key === keys.UP && verticalNum > 0 && !String(verticalNum).includes("e")) {
verticalNum -= speed;
if (verticalNum < 0) {
verticalNum = 0;
}
if (snakesArray.length > -1) {
for (var i = 0; i < snakesArray.length; i++) {
$("." + snakesArray[i]).css("top", player.offset().top + i * 23 + updatePos);
$("." + snakesArray[i]).css("left", player.offset().left + updatePos);
}
}
$(".game-player").css("top", verticalNum);
WObject.isTouching($(".game-player"), $(".game-food"));
} else if (key === keys.DOWN && verticalNum < 475.8000000000002) {
verticalNum += speed;
if (verticalNum > 475.8000000000002) {
verticalNum = 475.8000000000002;
}
if (snakesArray.length > -1) {
for (var i = 0; i < snakesArray.length; i++) {
$("." + snakesArray[i]).css("top", player.offset().top - i * 23 - updatePos);
$("." + snakesArray[i]).css("left", player.offset().left - updatePos);
}
}
$(".game-player").css("top", verticalNum);
WObject.isTouching($(".game-player"), $(".game-food"));
} else if (key === keys.LEFT && horizontalNum > 0 && !String(horizontalNum).includes("e")) {
horizontalNum -= speed;
if (horizontalNum < 0) {
horizontalNum = 0;
}
if (snakesArray.length > -1) {
for (var i = 0; i < snakesArray.length; i++) {
$("." + snakesArray[i]).css("left", player.offset().left + i * 23 + updatePos);
$("." + snakesArray[i]).css("top", player.offset().top + updatePos);
}
}
$(".game-player").css("left", horizontalNum);
WObject.isTouching($(".game-player"), $(".game-food"));
} else if (key === keys.RIGHT && horizontalNum < 475.8000000000002) {
horizontalNum += speed;
if (horizontalNum > 475.8000000000002) {
horizontalNum = 475.8000000000002;
}
if (snakesArray.length > -1) {
for (var i = 0; i < snakesArray.length; i++) {
$("." + snakesArray[i]).css("left", player.offset().left - i * 23 - updatePos);
$("." + snakesArray[i]).css("top", player.offset().top - updatePos);
}
}
$(".game-player").css("left", horizontalNum);
WObject.isTouching($(".game-player"), $(".game-food"));
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>GAME</title>
<style>
.game-background {
background-color: lightgray;
width: 500px;
height: 500px;
position: absolute;
top: 55px;
left: 200px;
border: black;
border-style: solid;
border-width: 10px;
}
.game-player {
background-color: blue;
width: 20px;
height: 20px;
position: relative;
top: 0px;
left: 0px;
border: black;
border-style: inherit;
border-width: 2px;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p class="game-scoreboard">Berries: 0</p>
<div class="game-background">
<div class="game-player"></div>
</div>
<script>
// The Script shown in the JavaScript
</script>
</body>
</html>
It's because of:
speed += 2;
Remove it and it will work fine.
As you're using speed in calculating the verticalNum and horizontalNum everytime, so it increments it with 2 each time, giving a wrong position.
The output after removing it:
I am currently working on making a grid of squares for a tile map. I have it set up so clicking on a tile changes its state to explored from unexplored. I am attempting to have it so that dragging with the mouse down will change the state of all underlying tiles, however I can't seem to get it to work.
I have tried using mousedown and mouseup events to set a down boolean value which I then check inside of a mouseover. I have tried going about this in several ways (i.e. the commented out code). The current code will work for clicking but I really want to be able to do a drag to change multiples feature.
var tableString;
var width = 35;
var height = 15;
var cells = [];
var localX;
var localY;
function cell(x, y, c) {
positionX = x;
positionY = y;
category = c;
}
function createMap() {
for (var i = 0; i < height; i++) {
var row = [];
for (var j = 0; j < width; j++) {
let c = new cell();
c.category = "unexplored";
c.positionX = j;
c.positionY = i;
row.push(c);
}
cells.push(row);
}
}
function drawMap() {
tableString = "<table draggable='false'>";
for (var i = 0; i < height; i++) {
tableString += "<tr draggable='false'>";
for (var j = 0; j < width; j++) {
tableString += '<td draggable="false" class="' + cells[i][j].category + '" data-row="' + j + '" data-column="' + i + '"></td>';
}
tableString += "</tr>";
}
tableString += "</table>";
$("#mainContainer").html(tableString);
console.log("drew it");
}
function updateCellCategory(x, y, c) {
cells[x][y].category = c;
drawMap();
}
$(document).ready(function() {
createMap();
drawMap();
// var down = false;
// $(document,"td").mousedown(function () {
// down = true;
// })
// $(document,"td").mouseup(function () {
// down = false;
// });
// $(document,"td").on('mouseover','td',function () {
// if (down) {
// console.log("hovering and holding");
// localX = $(this).attr("data-row");
// localY = $(this).attr("data-column");
// updateCellCategory(localY, localX, "explored");
// }
// });
});
// $(document).on('mousedown',"td, documen",(function () {
// down = true;
// console.log(down);
// }));
// $(document).on('mouseup',"*",(function () {
// down = false;
// console.log(down);
// }));
// $(document).on('mouseover','td',function () {
// if (down) {
// console.log("hovering and holding");
// localX = $(this).attr("data-row");
// localY = $(this).attr("data-column");
// updateCellCategory(localY, localX, "explored");
// }
// });
$("*").delegate('td', 'click', function() {
localX = $(this).attr("data-row");
localY = $(this).attr("data-column");
updateCellCategory(localY, localX, "explored");
});
html,
body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
}
#mainContainer {
max-width: 100%;
max-height: 90%;
width: 100%;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
}
td {
width: 25px;
height: 25px;
border: .05px solid black;
}
.explored {
background-color: lightblue;
}
.unexplored {
background-color: lightcoral;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div id="mainContainer">
</div>
</body>
</html>
The main issue I found when working on this is that some of the commented code works sometimes, but the second a drag event happens on a td the code breaks and the mouseup is not recognized causing the mouse cursur to continue affecting tiles even though the mouse was not held down.
OK. Using the click event is not what you want, since that involves pressing the mouse and releasing it.
Instead use, the mousemove, mousedown and mouseup events. Also, keep track of whether the mouse is down or not using a variable.
var tableString;
var width = 35;
var height = 15;
var cells = [];
var localX;
var localY;
var mouseDown = false;
function cell(x, y, c) {
positionX = x;
positionY = y;
category = c;
}
function createMap() {
for (var i = 0; i < height; i++) {
var row = [];
for (var j = 0; j < width; j++) {
let c = new cell();
c.category = "unexplored";
c.positionX = j;
c.positionY = i;
row.push(c);
}
cells.push(row);
}
}
function drawMap() {
tableString = "<table draggable='false'>";
for (var i = 0; i < height; i++) {
tableString += "<tr draggable='false'>";
for (var j = 0; j < width; j++) {
tableString += '<td draggable="false" class="' + cells[i][j].category + '" data-row="' + j + '" data-column="' + i + '"></td>';
}
tableString += "</tr>";
}
tableString += "</table>";
$("#mainContainer").html(tableString);
//console.log("drew it");
}
function updateCellCategory(x, y, c) {
cells[x][y].category = c;
drawMap();
}
$(document).ready(function() {
createMap();
drawMap();
});
$("*").on("mousedown", 'td', function()
{
mouseDown = true;
});
$(document).on("mouseup", function()
{
mouseDown = false;
});
$("*").on("mousemove", 'td', function()
{
if(!mouseDown)
return;
localX = $(this).attr("data-row");
localY = $(this).attr("data-column");
updateCellCategory(localY, localX, "explored");
});
html,
body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
}
#mainContainer {
max-width: 100%;
max-height: 90%;
width: 100%;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
}
td {
width: 25px;
height: 25px;
border: .05px solid black;
}
.explored {
background-color: lightblue;
}
.unexplored {
background-color: lightcoral;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div id="mainContainer">
</div>
</body>
</html>
You can check whether or not the mouse is down using the event parameter of the event handler. Look at the last few lines of the snippet.
var tableString;
var width = 35;
var height = 15;
var cells = [];
var localX;
var localY;
function cell(x, y, c) {
positionX = x;
positionY = y;
category = c;
}
function createMap() {
for (var i = 0; i < height; i++) {
var row = [];
for (var j = 0; j < width; j++) {
let c = new cell();
c.category = "unexplored";
c.positionX = j;
c.positionY = i;
row.push(c);
}
cells.push(row);
}
}
function drawMap() {
tableString = "<table draggable='false'>";
for (var i = 0; i < height; i++) {
tableString += "<tr draggable='false'>";
for (var j = 0; j < width; j++) {
tableString += '<td draggable="false" class="' + cells[i][j].category + '" data-row="' + j + '" data-column="' + i + '"></td>';
}
tableString += "</tr>";
}
tableString += "</table>";
$("#mainContainer").html(tableString);
console.log("drew it");
}
function updateCellCategory(x, y, c) {
cells[x][y].category = c;
drawMap();
}
$(document).ready(function() {
createMap();
drawMap();
// var down = false;
// $(document,"td").mousedown(function () {
// down = true;
// })
// $(document,"td").mouseup(function () {
// down = false;
// });
// $(document,"td").on('mouseover','td',function () {
// if (down) {
// console.log("hovering and holding");
// localX = $(this).attr("data-row");
// localY = $(this).attr("data-column");
// updateCellCategory(localY, localX, "explored");
// }
// });
});
// $(document).on('mousedown',"td, documen",(function () {
// down = true;
// console.log(down);
// }));
// $(document).on('mouseup',"*",(function () {
// down = false;
// console.log(down);
// }));
// $(document).on('mouseover','td',function () {
// if (down) {
// console.log("hovering and holding");
// localX = $(this).attr("data-row");
// localY = $(this).attr("data-column");
// updateCellCategory(localY, localX, "explored");
// }
// });
$("*").delegate('td', 'mousedown', function() {
localX = $(this).attr("data-row");
localY = $(this).attr("data-column");
updateCellCategory(localY, localX, "explored");
});
$("*").delegate('td', 'mouseenter', function(event) {
if (event.buttons) {
localX = $(this).attr("data-row");
localY = $(this).attr("data-column");
updateCellCategory(localY, localX, "explored");
}
event.stopPropagation();
});
html,
body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
}
#mainContainer {
max-width: 100%;
max-height: 90%;
width: 100%;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
}
td {
width: 25px;
height: 25px;
border: .05px solid black;
}
.explored {
background-color: lightblue;
}
.unexplored {
background-color: lightcoral;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div id="mainContainer">
</div>
</body>
</html>
I've created a snake game, and when the snake hit the wall or itself, it still wont stop moving. I figured out if I used the clearTimeout(), it would help. but it didn't.
Is there a way to stop the loop? or it is another issue?
jQuery(document).ready(function($) {
init();
});
var move;
function init() {
board.initBoard();
drawSnake();
food.createFood();
}
function play() {
$('.newGame').css('visibility', 'hidden');
$('.playgame').css('visibility', 'hidden');
moveSnake();
getSnakeDir();
}
function gameover() {
clearTimeout(move);
$('.newGame').css('visibility', 'visible');
}
function playGame() {
$('#gameboard').empty();
$('.newGame').hide();
init();
play();
}
var board = {
DIM: 20,
initBoard: function() {
for (var i = 0; i < board.DIM; i++) {
var row = $('<div class="row-' + i + '"></div>');
for (var j = 0; j < board.DIM; j++) {
var col = ('<div class="col-' + j + '-' + i + '"></div>');
$(row).append(col);
}
$("#gameboard").append(row);
}
}
}
var snake = {
position: ['10-10', '10-11', '10-12'],
direction: 'r',
speed: 200,
};
function drawSnake() {
$('.col-10-10').addClass('snake');
$('.col-11-10').addClass('snake');
}
function getSnakeDir() {
$(document).keydown(function(event) {
//event.preventDefault();
if (event.which == 38) {
snake.direction = 'u';
} else if (event.which == 39) {
snake.direction = 'r';
} else if (event.which == 40) {
snake.direction = 'd';
} else if (event.which == 37) {
snake.direction = 'l';
}
});
}
function moveSnake() {
var tail = snake.position.pop();
$('.col-' + tail).removeClass('snake');
var coords = snake.position[0].split('-');
var x = parseInt(coords[0]);
var y = parseInt(coords[1]);
if (snake.direction == 'r') {
x = x + 1;
} else if (snake.direction == 'd') {
y = y + 1;
} else if (snake.direction == 'l') {
x = x - 1;
} else if (snake.direction == 'u') {
y = y - 1;
}
var currentcoords = x + '-' + y;
snake.position.unshift(currentcoords);
$('.col-' + currentcoords).addClass('snake');
//when snake eats food
if (currentcoords == food.coords) {
console.log('true');
$('.col-' + food.coords).removeClass('food');
snake.position.push(tail);
food.createFood();
}
//game over
if (x < 0 || y < 0 || x > board.DIM || y > board.DIM) {
gameover();
}
//if snake touch itself
if (hitItself(snake.position) == true) {
gameover();
}
move=setTimeout(moveSnake, 200);
}
var food = {
coords: "",
createFood: function() {
var x = Math.floor(Math.random() * (board.DIM-1)) + 1;
var y = Math.floor(Math.random() * (board.DIM-1)) + 1;
var fruitCoords = x + '-' + y;
$('.col-' + fruitCoords).addClass('food');
food.coords = fruitCoords;
},
}
function hitItself(array) {
var valuesSoFar = Object.create(null);
for (var i = 0; i < array.length; ++i) {
var value = array[i];
if (value in valuesSoFar) {
return true;
}
valuesSoFar[value] = true;
}
return false;
}
.buttonnewgame {
position: relative;
}
.newGame {
position: absolute;
top: 45%;
left: 25%;
padding: 15px;
font-size: 1em;
font-family: arial;
visibility: hidden;
}
.gameContainer{
width: 100%;
}
#gameboard {
background-color:#eee;
padding:3px;
}
.playgame {
position: absolute;
top: 45%;
left: 20%;
padding: 15px;
font-size: 1em;
font-family: arial;
}
/* styling the board */
div[class^='row'] {
height: 15px;
text-align: center;
}
div[class*='col']{
display: inline-block;
border: 1px solid grey;
width: 15px;
height: 15px;
}
/*display the snake*/
.snake {
background-color: blue;
z-index: 99;
}
.food {
background: red;
z-index: 99;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="game">
<div class="buttonnewgame">
<input type="button" name="new game" value="new game" class="newGame" onclick="playGame()" />
<button class="playgame" onclick="play()">Play Game</button>
<div class="gameContainer">
<div id="gameboard">
<!-- snake game in here -->
</div>
</div>
</div>
</div>
There were a few problems, but here is the 'working version' (there are more bugs).
1) I renamed drawSnake to createSnake. You weren't fully reinitializing the snake when you called init(). The snakes position was not being reset in the previous drawSnake method, so it would seem like the game was not playable.
After that there were 2 more bugs.
2) You have to return after you call gameOver or the game never really ends does it? Once you clear the timeout in gameover, you immediately set another Timeout for on the last line of moveSnake() because you didn't return once the game was over. That lead to weird results that made it seem like the game was unresponsive.
3) You were using a combination of visibility none or visible and $.hide(). $.hide() uses display: none, so when you tried to show it again with the visibility style change, its still display: none so the new game button would stop appearing.
My advice to any game coder is to learn how to separate the code that handles how the game works (logic of the game, how the clock ticks, initialization of game state, etc) , and how it is displayed (the html and css). Modeling the game logic after a cleanly written system is easy to read and debug. The code becomes harder to understand and modify when the display code is mixed in with game logic. In theory, our game should work perfectly without any kind of rendering. Then we could write a renderer that produces an HTML canvas, html DOM, text in the command line, or OpenGL.
Heres an old project I never finished that should illustrate a separation between model and view.
http://tando.us/ganix/ganix.htm
jQuery(document).ready(function($) {
init();
});
var move;
function init() {
board.initBoard();
createSnake();
food.createFood();
}
function play() {
$('.newGame').hide();
$('.playgame').hide();
moveSnake();
getSnakeDir();
}
function gameover() {
clearTimeout(move);
$('.newGame').show();
}
function playGame() {
$('#gameboard').empty();
$('.newGame').hide();
init();
play();
}
var board = {
DIM: 20,
initBoard: function() {
for (var i = 0; i < board.DIM; i++) {
var row = $('<div class="row-' + i + '"></div>');
for (var j = 0; j < board.DIM; j++) {
var col = ('<div class="col-' + j + '-' + i + '"></div>');
$(row).append(col);
}
$("#gameboard").append(row);
}
}
}
var snake = {
position: ['10-10', '10-11', '10-12'],
direction: 'r',
speed: 200,
};
function createSnake() {
$('.col-10-10').addClass('snake');
$('.col-11-10').addClass('snake');
snake.position = ['10-10', '10-11', '10-12'];
}
function getSnakeDir() {
$(document).keydown(function(event) {
//event.preventDefault();
if (event.which == 38) {
snake.direction = 'u';
} else if (event.which == 39) {
snake.direction = 'r';
} else if (event.which == 40) {
snake.direction = 'd';
} else if (event.which == 37) {
snake.direction = 'l';
}
});
}
function moveSnake() {
var tail = snake.position.pop();
$('.col-' + tail).removeClass('snake');
var coords = snake.position[0].split('-');
var x = parseInt(coords[0]);
var y = parseInt(coords[1]);
if (snake.direction == 'r') {
x = x + 1;
} else if (snake.direction == 'd') {
y = y + 1;
} else if (snake.direction == 'l') {
x = x - 1;
} else if (snake.direction == 'u') {
y = y - 1;
}
var currentcoords = x + '-' + y;
snake.position.unshift(currentcoords);
$('.col-' + currentcoords).addClass('snake');
//when snake eats food
if (currentcoords == food.coords) {
console.log('true');
$('.col-' + food.coords).removeClass('food');
snake.position.push(tail);
food.createFood();
}
//game over
if (x < 0 || y < 0 || x > board.DIM || y > board.DIM) {
gameover();
return;
}
//if snake touch itself
if (hitItself(snake.position) == true) {
gameover();
return;
}
move=setTimeout(moveSnake, 200);
}
var food = {
coords: "",
createFood: function() {
var x = Math.floor(Math.random() * (board.DIM-1)) + 1;
var y = Math.floor(Math.random() * (board.DIM-1)) + 1;
var fruitCoords = x + '-' + y;
$('.col-' + fruitCoords).addClass('food');
food.coords = fruitCoords;
},
}
function hitItself(array) {
var valuesSoFar = Object.create(null);
for (var i = 0; i < array.length; ++i) {
var value = array[i];
if (value in valuesSoFar) {
return true;
}
valuesSoFar[value] = true;
}
return false;
}
.buttonnewgame {
position: relative;
}
.newGame {
position: absolute;
top: 45%;
left: 25%;
padding: 15px;
font-size: 1em;
font-family: arial;
}
.gameContainer{
width: 100%;
}
#gameboard {
background-color:#eee;
padding:3px;
}
.playgame {
position: absolute;
top: 45%;
left: 20%;
padding: 15px;
font-size: 1em;
font-family: arial;
}
/* styling the board */
div[class^='row'] {
height: 15px;
text-align: center;
}
div[class*='col']{
display: inline-block;
border: 1px solid grey;
width: 15px;
height: 15px;
}
/*display the snake*/
.snake {
background-color: blue;
z-index: 99;
}
.food {
background: red;
z-index: 99;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="game">
<div class="buttonnewgame">
<input type="button" name="new game" value="new game" class="newGame" style="display:none;" onclick="playGame()" />
<button class="playgame" onclick="play()">Play Game</button>
<div class="gameContainer">
<div id="gameboard">
<!-- snake game in here -->
</div>
</div>
</div>
You could try not to initiate a new setTimeout call at the and of the moveSnake function, but instead using.
function play() {
$('.newGame').css('visibility', 'hidden');
$('.playgame').css('visibility', 'hidden');
move = setInterval(moveSnake, 200);
getSnakeDir();
}
and remove the
move = setTimeout(moveSnake, 200)
from the moveSnake function and do
function gameover() {
clearInterval(move);
$('.newGame').css('visibility', 'visible');
}
I'm trying to learn Java Script Animations and I found really good examples on this site: http://javascript.info/tutorial/animation#maths-the-function-of-progress-delta
But the problem is, as a beginner, I don't understand how the functions and objects work with each other.
Question 01
I copied the example "Let’s create a movement animation on it’s base:" But my version does not work.
<!DOCTYPE HTML>
<html>
<head>
<style>
.example_path{
position: relative;
width: 600px;
height: 100px;
border-style: solid;
border-width: 5px;
}
.example_block{
position: absolute;
width: 100px;
height: 100px;
background-color: yellow;
}
</style>
</head>
<body>
<div onclick="move(this.children[0])" class="example_path">
<div class="example_block"></div>
</div>
<script>
function move(element, delta, duration) {
var to = 500
animate({
delay: 10,
duration: duration || 1000, // 1 sec by default
delta: delta,
step: function(delta) {
element.style.left = to*delta + "px"
}
})
}
</script>
</body>
</html>
output console: ReferenceError: animate is not defined
Does anyone know what the problem is?
Question 02
My second wish is, to integrate the easeInOut function
function makeEaseInOut(delta) {
return function(progress) {
if (progress < .5)
return delta(2*progress) / 2
else
return (2 - delta(2*(1-progress))) / 2
}
}
bounceEaseInOut = makeEaseInOut(bounce)
How can I link both code snippets? The code is also from this page: http://javascript.info/tutorial/animation#maths-the-function-of-progress-delta
Add animate and makeEaseInOut into your script tag then you can use them. You may want to include the functions in a separate JavaScript file eventually.
<script>
function animate(opts) {
var start = new Date
var id = setInterval(function() {
var timePassed = new Date - start
var progress = timePassed / opts.duration
if (progress > 1) progress = 1
var delta = opts.delta(progress)
opts.step(delta)
if (progress == 1) {
clearInterval(id)
}
}, opts.delay || 10)
}
function makeEaseInOut(delta) {
return function(progress) {
if (progress < .5)
return delta(2*progress) / 2
else
return (2 - delta(2*(1-progress))) / 2
}
}
bounceEaseInOut = makeEaseInOut(bounce)
</script>
that's what I tried.
I still have problems.
output console: delta is not a function. bounce is not a function.
I know I have to learn more about creating functions. But right now I'm not that good to solve the problem.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
.example_path{
position: relative;
width: 600px;
height: 100px;
border-style: solid;
border-width: 5px;
}
.example_block{
position: absolute;
width: 100px;
height: 100px;
background-color: yellow;
}
</style>
<script>
function move(element, delta, duration) {
var to = 500;
animate({
delay: 10,
duration: duration || 1000, // 1 sec by default
delta: delta,
step: function(delta) {
element.style.left = to*delta + "px"
}
});
}
function animate(opts) {
var start = new Date;
var id = setInterval(function() {
var timePassed = new Date - start;
var progress = timePassed / opts.duration;
if (progress > 1) progress = 1
var delta = opts.delta(progress);
opts.step(delta);
if (progress == 1) {
clearInterval(id);
}
}, opts.delay || 10);
}
function makeEaseInOut(delta) {
return function(progress) {
if (progress < .5)
return delta(2*progress)/2;
else
return (2 - delta(2*(1-progress)))/2;
};
}
varbounceEaseInOut = makeEaseInOut(bounce);
</script>
</head>
<body>
<div onclick="move(this.children[0], makeEaseInOut(bounce), 3000)" class="example_path">
<div class="example_block"></div>
</div>
</body>
</html>
I've made a very simple animation using javascript, hope it helps, try to "Run code snippet" for better understanding.
/*JavaScript*/
function myMove() {
var elem = document.getElementById("animate");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
function Back() {
var elem1 = document.getElementById("animate");
var id1 = setInterval(frame2, 5);
var pos1 = 350;
function frame2() {
if (pos1 == 0) {
clearInterval(id1);
} else {
pos1--;
elem1.style.top = pos1 + 'px';
elem1.style.left = pos1 + 'px';
}
}
}
/*CSS*/
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
/*HTML*/
<button onclick="myMove()">Click Me</button>
<button onclick="Back()"> roll back</button>
<div id ="container">
<div id ="animate"></div>
</div>
my code lists items from an rss feed onto an html page. although, the java script is a little finicky. it won't read some xml feeds, usually the feeds containing list items over 25. I just need another set of eyes to take a look at the code and tell me if i'm missing something obvious.
.js file-----------------------------------------------
//XML CODE
var http_request = false;
var dataFileName = new Array();
dataFileName[1] = "http://newsrss.bbc.co.uk/rss/newsonline_world_edition/americas/rss.xml";
//dataFileName[2] = "http://newsrss.bbc.co.uk/rss/newsonline_world_edition/uk_news/magazine/rss.xml";
//dataFileName[3] = "http://newsrss.bbc.co.uk/rss/newsonline_world_edition/business/rss.xml";
function getData(dataFileIndex) {
if (window.ActiveXObject) { //IE
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} else if (window.XMLHttpRequest) { //other
http_request = new XMLHttpRequest();
} else {
alert("your browser does not support AJAX");
}
http_request.open("GET",dataFileName[dataFileIndex],true);
http_request.setRequestHeader("Cache-Control", "no-cache");
http_request.setRequestHeader("Pragma", "no-cache");
http_request.onreadystatechange = function() {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
if (http_request.responseText != null) {
processRSS(http_request.responseXML);
} else {
alert("Failed to receive RSS file from the server - file not found.");
return false;
}
}
}
}
http_request.send(null);
}
function processRSS(rssxml) {
RSS = new RSS2Channel(rssxml);
outputData(RSS);
}
function RSS2Channel(rssxml) {
this.items = new Array();
var itemElements = rssxml.getElementsByTagName("item");
for (var i=0; i<itemElements.length; i++) {
Item = new RSS2Item(itemElements[i]);
this.items.push(Item);
}
}
function RSS2Item(itemxml) {
this.title;
this.link;
this.description;
this.pubDate;
this.guid;
var properties = new Array("title", "link", "description", "pubDate", "guid");
var tmpElement = null;
for (var i=0; i<properties.length; i++) {
tmpElement = itemxml.getElementsByTagName(properties[i])[0];
if (tmpElement != null) {
eval("this."+properties[i]+"=tmpElement.childNodes[0].nodeValue");
}
}
}
function outputData(RSS) {
dataString = "";
for (var i=0; i<RSS.items.length; i++) {
dataString += "<div class='itemBlock'>";
newDate = new Date(RSS.items[i].pubDate);
dateString = (newDate.getMonth()+1) + "/" + newDate.getDate() + "/" + newDate.getFullYear();
dataString += "<div class='itemDate'>" + dateString + "</div>";
dataString += "<div class='itemTitle'><a href='" + RSS.items[i].link + "' target='afps_news'>" + RSS.items[i].title + "</a></div>";
//dataString += "<div class='itemDescription'>" + RSS.items[i].description + "</div>";
dataString += "</div>";
}
document.getElementById('outputBlock').innerHTML = dataString;
}
//SCROLL BAR CODE
var ie=document.all;
var nn6=document.getElementById&&!document.all;
var isdrag=false;
var x,y;
var dobj;
var scrollPercent;
var boxTop;
var maxHeight;
var toppoint;
function movemouse(e) {
if (isdrag) {
//dobj.style.left = nn6 ? tx + e.clientX - x : tx + event.clientX - x;
toppoint = (nn6) ? ty + e.clientY - y : ty + event.clientY - y;
boxTop = parseInt(document.getElementById('scrollBarBox').style.top) - scrollBarBoxOffset;
if (toppoint < boxTop) toppoint = boxTop;
boxHeight = parseInt(document.getElementById('scrollBarBox').style.height);
maxHeight = boxTop + boxHeight - parseInt(document.getElementById('scrollBar').style.height);
if (toppoint > maxHeight) toppoint = maxHeight;
dobj.style.top = toppoint + "px";
scrollPercent = toppoint / maxHeight;
document.getElementById('textWindow').style.top = parseInt(0 - (document.getElementById('textWindow').offsetHeight - parseInt(document.getElementById('scrollBarBox').style.height)) * scrollPercent );
return false;
}
}
function selectmouse(e) {
var fobj = nn6 ? e.target : event.srcElement;
var topelement = nn6 ? "HTML" : "BODY";
while (fobj.tagName != topelement && fobj.className != "dragme") {
fobj = nn6 ? fobj.parentNode : fobj.parentElement;
}
if (fobj.className == "dragme") {
isdrag = true;
dobj = fobj;
//tx = parseInt(dobj.style.left + 0);
ty = parseInt(dobj.style.top + 0);
//x = nn6 ? e.clientX : event.clientX;
y = nn6 ? e.clientY : event.clientY;
document.onmousemove = movemouse;
return false;
}
}
document.onmousedown = selectmouse;
document.onmouseup = new Function("isdrag=false;");
html file-------------------------------------------------------------------
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD><TITLE>TEST</TITLE>
<META http-equiv=Content-Type content="text/html; charset=windows-1252">
<SCRIPT src="script1.js"></SCRIPT>
<STYLE>BODY {
MARGIN: 0px; FONT: 8pt arial
}
#widgetBody {
BACKGROUND-Color:gray; WIDTH: 240px; POSITION: relative; HEIGHT: 299px
}
#textWindowBox {
LEFT: 63px; OVERFLOW: hidden; WIDTH: 152px; POSITION: absolute; TOP: 70px; HEIGHT: 221px
}
#textWindow {
PADDING-TOP: 7px; POSITION: relative
}
#scrollBarBox {
LEFT: 221px; WIDTH: 12px; POSITION: absolute; TOP: 74px; HEIGHT: 216px
}
#scrollBar {
BACKGROUND: url(images/widget_scroll-handle1.gif) no-repeat; WIDTH: 12px; POSITION: relative; HEIGHT: 40px
}
#defenseLinkLink {
LEFT: 4px; WIDTH: 20px; CURSOR: pointer; POSITION: absolute; TOP: 155px; HEIGHT: 140px; BACKGROUND-COLOR: transparent
}
#defenseLinkLink A {
DISPLAY: block; WIDTH: 20px; HEIGHT: 140px
}
.dragme {
POSITION: relative
}
.itemBlock {
PADDING-RIGHT: 0px; PADDING-LEFT: 0px; PADDING-BOTTOM: 4px; MARGIN: 0px 0px 3px; PADDING-TOP: 0px; BORDER-BOTTOM: #adafb3 1px dotted
}
.itemDate {
FONT-SIZE: 0.9em; COLOR: #666; LINE-HEIGHT: 1.1em
}
.itemTitle {
FONT-WEIGHT: bold; LINE-HEIGHT: 1.1em
}
.itemTitle A {
COLOR: #254a7d; TEXT-DECORATION: none
}
.itemDescription {
}
</STYLE>
<SCRIPT>
var scrollBarBoxOffset = 74;
function init() {
document.getElementById('scrollBarBox').style.top = "74px";
document.getElementById('scrollBarBox').style.height = "216px";
document.getElementById('scrollBar').style.height = "40px";
}
</SCRIPT>
<META content="MSHTML 6.00.6001.18294" name=GENERATOR></HEAD>
<BODY onload=init()>
<DIV id=widgetBody>
<DIV id=textWindowBox>
<DIV id=textWindow>
<DIV id=outputBlock></DIV></DIV></DIV>
<DIV id=scrollBarBox>
<DIV class=dragme id=scrollBar></DIV></DIV>
<DIV style="CLEAR: both"></DIV></DIV>
<SCRIPT language=javaScript>getData(2)</SCRIPT>
</BODY></HTML>
Ok, got it working.
2 issues.
army.mil does not resolve! Please use "www.army.mil" instead.
IN RSS2Item replace this line:
if (tmpElement != null) {
with this:
if (tmpElement != null && tmpElement.childNodes[0]) {
Oh man, why are you using XMLHttpRequest directly? Use a library for that, and make your life easier :)
You could be running into cross-site scripting security problems. If the RSS feeds exist on a different domain than the page running the JavaScript, the browser will not let your JavaScript make the requests.
dataFileName[1] = "http://newsrss.bbc.co.uk/rss/newsonline_world_edition/americas/rss.xml";
Unless you are (a) a script running from the BBC, or (b) a browser extension, you cannot make an XMLHttpRequest to that server.
dataString += "<div class='itemTitle'><a href='" + RSS.items[i].link
HTML/script injection. If you insist on rolling innerHTML instead of using simple DOM methods you must do HTML escaping to turn <&" into <&".
eval("this."+properties[i]+"=tmpElement.childNodes[0].nodeValue");
Don't use eval, except in the very few unusual cases you need it. This isn't one of them; you can access a JavaScript property by name using:
this[properties[i]]= tmpElement.firstChild.data;
Also, and this is probably where the unreliability is coming in, you can't be sure there will be a single child Text node. If there is no content in that element, firstChild/childNodes[0] will not exist and you will get an exception. If there is complex content in the element (which normally there shouldn't be, but for RSS 0.9 there can be as unencoded HTML), firstChild.nodeValue won't contain the text content of the element. Instead you would have to walk over the Text node descendents gathering their nodeValue/data.