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');
}
Related
I am currently constructing a roulette system programme ( this is more to keep me from betting than to bet! ) and am just doing the basic framework but already hit a problem with the main window '#results' not scrolling when it is filled with the results. The scroll needs to follow the latest line of content so the latest returned from the input in the modal box is always showing at the bottom. I have spent hours on lots of possible solutions to no avail.
Here is the code: ( i apologise for the length of the full script )
<!DOCTYPE html>
<html>
<body>
<div id="results">
</div>
<div id="modal">
<div id="modal-content">
<p>Select a number between 0 and 36:</p>
<div id="numberButtons"></div>
<button id="close-button" onclick="closeModal()">Close</button>
</div>
</div>
</div>
<style>
/* The modal background */
#modal {
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
}
/* The modal content */
#modal-content {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: #fefefe;
padding: 20px;
width: 18%;
border: 3px solid black;
}
/* The close button */
#close-button {
display: block;
margin: 0 auto;
}
#numberButtons button {
color: white;
width: 50px;
height: 50px;
font-size:30px;
font-weight:600;
}
#modal.inactive{
opacity: 0.2;
}
</style>
<script>
var bankRoll = parseFloat(prompt("Please enter your starting bankroll:"));
if(isNaN(bankRoll)){
bankRoll = parseFloat(prompt("Invalid input. Please enter a valid number for your starting bankroll:"));
}
bankRoll = bankRoll.toFixed(2);
var spinNumber=0;
var backline="";
var isDragging = false;
var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;
const modalContent = document.querySelector("#modal-content");
modalContent.addEventListener("mousedown", dragStart);
modalContent.addEventListener("mouseup", dragEnd);
modalContent.addEventListener("mousemove", drag);
function dragStart(e) {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.target === modalContent) {
isDragging = true;
}
}
let inactivityTimeout;
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
isDragging = false;
clearTimeout(inactivityTimeout);
inactivityTimeout = setTimeout(() => {
modal.classList.add("inactive")
}, 15000)
}
function drag(e) {
if (e.buttons === 1) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, modalContent);
clearTimeout(inactivityTimeout);
modal.classList.remove("inactive")
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
function getNumberType(number) {
if (number === 0) {
return "green";
} else if (number % 2 === 0) {
return "even";
} else {
return "odd";
}
}
function roulette(number) {
// Determine the color of the number
var color;
if (number === 0) {
color = "Green";
} else if (number === 1 || number === 3 || number === 5 || number === 7 || number === 9 || number === 12 || number === 14 || number === 16 || number === 18 || number === 19 || number === 21 || number === 23 || number === 25 || number === 27 || number === 30 || number === 32 || number === 34 || number === 36) {
color = "Red";
} else {
color = "Black";
}
// Map the color names to CSS color values
if (color === "Green") {
return "rgb(0, 128, 0)";
} else if (color === "Red") {
return "rgb(255, 0, 0)";
} else {
return "rgb(0, 0, 0)";
}
}
function backgroundline() {
if (spinNumber % 2 === 0) {
backline="#D3D3D3"
} else {
backline="#E5E4E2";
}
}
function spin(number) {
// Determine the color of the number
var color = roulette(number);
spinNumber= spinNumber+1;
bankRoll=bankRoll-10;
backgroundline();
// Display the result
var resultsDiv = document.getElementById("results");
var resultHTML = `${number}`;
resultHTML = `<div style="padding:10px 0; background: ${backline};vertical-align:middle;margin-bottom:- 20px;">
<div style="padding:5px; display: inline-block; background: yellow; color:black;vertical-align:middle; width:30px; text-align:right;">${spinNumber}</div>
<div style="margin: 0 10px; display:inline-block; width: 70px; text-align:center;vertical-align:middle">£ ${bankRoll}</div>
<div style="color: white; padding: 5px; display: inline-block; width:30px; padding-top:15px;vertical-align:middle; font-size: 25px; font-weight:600; height:30px; text-align:center; background-color: ${color}; ">${resultHTML}</div>
</div>
<br style="height:0px;"/>`;
resultsDiv.innerHTML += resultHTML;
}
// Set up the buttons
for (let i = 0; i <= 36; i++) {
let button = document.createElement("button");
button.innerHTML = i;
button.style.backgroundColor = roulette(i);
button.addEventListener("click", function() {
spin(i);
});
document.getElementById("numberButtons").appendChild(button);
}
function closeModal() {
// Get the modal element
var modal = document.getElementById("modal");
// Remove the modal element from the DOM
modal.remove();
}
</script>
</body>
</html>
I have tried lots of solutions on Stack Overflow and across the Internet further field that I thought might work but it just doesn't seem to want to do it.
after adding element in Results div you have to call this function.
add this function
window.scrollTo(0, document.body.scrollHeight)
after this line
resultsDiv.innerHTML += resultHTML;
I would like to make the face of this snake an image. Currently, it using a fill style with a color but I would like it to be an image. How can I do it with this code?
In addition, I want to find out how to add arrows so that it could work on a mobile phone. Thank you for anyone who can help or provide insight.
(function() {
/////////////////////////////////////////////////////////////
// Canvas & Context
var canvas;
var ctx;
// Snake
var snake;
var snake_dir;
var snake_next_dir;
var snake_speed;
// Food
var food = {
x: 0,
y: 0
};
// Score
var score;
// Wall
var wall;
// HTML Elements
var screen_snake;
var screen_menu;
var screen_setting;
var screen_gameover;
var button_newgame_menu;
var button_newgame_setting;
var button_newgame_gameover;
var button_setting_menu;
var button_setting_gameover;
var ele_score;
var speed_setting;
var wall_setting;
/////////////////////////////////////////////////////////////
var activeDot = function(x, y) {
ctx.fillStyle = "#FFFFFF";
ctx.fillRect(x * 10, y * 10, 10, 10);
}
/////////////////////////////////////////////////////////////
var changeDir = function(key) {
if (key == 38 && snake_dir != 2) {
snake_next_dir = 0;
} else {
if (key == 39 && snake_dir != 3) {
snake_next_dir = 1;
} else {
if (key == 40 && snake_dir != 0) {
snake_next_dir = 2;
} else {
if (key == 37 && snake_dir != 1) {
snake_next_dir = 3;
}
}
}
}
}
/////////////////////////////////////////////////////////////
var addFood = function() {
food.x = Math.floor(Math.random() * ((canvas.width / 10) - 1));
food.y = Math.floor(Math.random() * ((canvas.height / 10) - 1));
for (var i = 0; i < snake.length; i++) {
if (checkBlock(food.x, food.y, snake[i].x, snake[i].y)) {
addFood();
}
}
}
/////////////////////////////////////////////////////////////
var checkBlock = function(x, y, _x, _y) {
return (x == _x && y == _y) ? true : false;
}
/////////////////////////////////////////////////////////////
var altScore = function(score_val) {
ele_score.innerHTML = String(score_val);
}
/////////////////////////////////////////////////////////////
var mainLoop = function() {
var _x = snake[0].x;
var _y = snake[0].y;
snake_dir = snake_next_dir;
// 0 - Up, 1 - Right, 2 - Down, 3 - Left
switch (snake_dir) {
case 0:
_y--;
break;
case 1:
_x++;
break;
case 2:
_y++;
break;
case 3:
_x--;
break;
}
snake.pop();
snake.unshift({
x: _x,
y: _y
});
// --------------------
// Wall
if (wall == 1) {
// On
if (snake[0].x < 0 || snake[0].x == canvas.width / 10 || snake[0].y < 0 || snake[0].y == canvas.height / 10) {
showScreen(3);
return;
}
} else {
// Off
for (var i = 0, x = snake.length; i < x; i++) {
if (snake[i].x < 0) {
snake[i].x = snake[i].x + (canvas.width / 10);
}
if (snake[i].x == canvas.width / 10) {
snake[i].x = snake[i].x - (canvas.width / 10);
}
if (snake[i].y < 0) {
snake[i].y = snake[i].y + (canvas.height / 10);
}
if (snake[i].y == canvas.height / 10) {
snake[i].y = snake[i].y - (canvas.height / 10);
}
}
}
// --------------------
// Autophagy death
for (var i = 1; i < snake.length; i++) {
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
showScreen(3);
return;
}
}
// --------------------
// Eat Food
if (checkBlock(snake[0].x, snake[0].y, food.x, food.y)) {
snake[snake.length] = {
x: snake[0].x,
y: snake[0].y
};
score += 1;
altScore(score);
addFood();
activeDot(food.x, food.y);
}
// --------------------
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// --------------------
for (var i = 0; i < snake.length; i++) {
activeDot(snake[i].x, snake[i].y);
}
// --------------------
activeDot(food.x, food.y);
// Debug
//document.getElementById("debug").innerHTML = snake_dir + " " + snake_next_dir + " " + snake[0].x + " " + snake[0].y;
setTimeout(mainLoop, snake_speed);
}
/////////////////////////////////////////////////////////////
var newGame = function() {
showScreen(0);
screen_snake.focus();
snake = [];
for (var i = 4; i >= 0; i--) {
snake.push({
x: i,
y: 15
});
}
snake_next_dir = 1;
score = 0;
altScore(score);
addFood();
canvas.onkeydown = function(evt) {
evt = evt || window.event;
changeDir(evt.keyCode);
}
mainLoop();
}
/////////////////////////////////////////////////////////////
// Change the snake speed...
// 150 = slow
// 100 = normal
// 50 = fast
var setSnakeSpeed = function(speed_value) {
snake_speed = speed_value;
}
/////////////////////////////////////////////////////////////
var setWall = function(wall_value) {
wall = wall_value;
if (wall == 0) {
screen_snake.style.borderColor = "#606060";
}
if (wall == 1) {
screen_snake.style.borderColor = "#FFFFFF";
}
}
/////////////////////////////////////////////////////////////
// 0 for the game
// 1 for the main menu
// 2 for the settings screen
// 3 for the game over screen
var showScreen = function(screen_opt) {
switch (screen_opt) {
case 0:
screen_snake.style.display = "block";
screen_menu.style.display = "none";
screen_setting.style.display = "none";
screen_gameover.style.display = "none";
break;
case 1:
screen_snake.style.display = "none";
screen_menu.style.display = "block";
screen_setting.style.display = "none";
screen_gameover.style.display = "none";
break;
case 2:
screen_snake.style.display = "none";
screen_menu.style.display = "none";
screen_setting.style.display = "block";
screen_gameover.style.display = "none";
break;
case 3:
screen_snake.style.display = "none";
screen_menu.style.display = "none";
screen_setting.style.display = "none";
screen_gameover.style.display = "block";
break;
}
}
/////////////////////////////////////////////////////////////
window.onload = function() {
canvas = document.getElementById("snake");
ctx = canvas.getContext("2d");
// Screens
screen_snake = document.getElementById("snake");
screen_menu = document.getElementById("menu");
screen_gameover = document.getElementById("gameover");
screen_setting = document.getElementById("setting");
// Buttons
button_newgame_menu = document.getElementById("newgame_menu");
button_newgame_setting = document.getElementById("newgame_setting");
button_newgame_gameover = document.getElementById("newgame_gameover");
button_setting_menu = document.getElementById("setting_menu");
button_setting_gameover = document.getElementById("setting_gameover");
// etc
ele_score = document.getElementById("score_value");
speed_setting = document.getElementsByName("speed");
wall_setting = document.getElementsByName("wall");
// --------------------
button_newgame_menu.onclick = function() {
newGame();
};
button_newgame_gameover.onclick = function() {
newGame();
};
button_newgame_setting.onclick = function() {
newGame();
};
button_setting_menu.onclick = function() {
showScreen(2);
};
button_setting_gameover.onclick = function() {
showScreen(2)
};
setSnakeSpeed(150);
setWall(1);
showScreen("menu");
// --------------------
// Settings
// speed
for (var i = 0; i < speed_setting.length; i++) {
speed_setting[i].addEventListener("click", function() {
for (var i = 0; i < speed_setting.length; i++) {
if (speed_setting[i].checked) {
setSnakeSpeed(speed_setting[i].value);
}
}
});
}
// wall
for (var i = 0; i < wall_setting.length; i++) {
wall_setting[i].addEventListener("click", function() {
for (var i = 0; i < wall_setting.length; i++) {
if (wall_setting[i].checked) {
setWall(wall_setting[i].value);
}
}
});
}
document.onkeydown = function(evt) {
if (screen_gameover.style.display == "block") {
evt = evt || window.event;
if (evt.keyCode == 32) {
newGame();
}
}
}
}
})();
::selection {
color: #FFFFFF;
background: transparent;
}
::-moz-selection {
color: #FFFFFF;
background: transparent;
}
* {
margin: 0;
padding: 0;
font-family: "VT323";
}
body {
background-color: #000000;
}
.wrap {
margin-left: auto;
margin-right: auto;
}
header {
width: 340px;
font-size: 0;
}
canvas {
display: none;
border-style: solid;
border-width: 10px;
border-color: #FFFFFF;
}
canvas:focus {
outline: none;
}
/* Top Styles */
h1 {
display: inline-block;
width: 100px;
font-size: 32px;
color: #FFFFFF;
}
.score {
display: inline-block;
width: 240px;
font-size: 20px;
color: #FFFFFF;
text-align: right;
}
.score_value {
font-size: inherit;
}
/* All screens style */
#gameover a,
#setting a,
#menu a {
display: block;
}
#gameover a,
#setting a:hover,
#menu a:hover {
cursor: pointer;
}
#gameover a:hover::before,
#setting a:hover::before,
#menu a:hover::before {
content: ">";
margin-right: 10px;
}
/* Menu Screen Style */
#menu {
display: block;
width: 340px;
padding-top: 95px;
padding-bottom: 95px;
font-size: 40px;
margin-left: auto;
margin-right: auto;
text-align: center;
color: #FFF;
}
#menu h2 {
-webkit-animation: logo-ani 1000ms linear infinite;
animation: logo-ani 1000ms linear infinite;
margin-bottom: 30px;
}
#menu a {
font-size: 30px;
}
#-webkit-keyframes logo-ani {
50% {
-webkit-transform: scale(1.3, 1.3);
}
100% {
-webkit-transform: scale(1.0, 1.0);
}
}
#keyframes logo-ani {
50% {
transform: scale(1.3, 1.3);
}
100% {
transform: scale(1.0, 1.0);
}
}
/* Game Over Screen Style */
#gameover {
display: none;
width: 340px;
padding-top: 95px;
padding-bottom: 95px;
margin-left: auto;
margin-right: auto;
text-align: center;
font-size: 30px;
color: #FFF;
}
#gameover p {
margin-top: 25px;
font-size: 20px;
}
/* Settings Screen Style */
#setting {
display: none;
width: 340px;
margin-left: auto;
margin-right: auto;
padding-top: 85px;
padding-bottom: 85px;
font-size: 30px;
color: #FFF;
text-align: center;
}
#setting h2 {
margin-bottom: 15px;
}
#setting p {
margin-top: 10px;
}
#setting input {
display: none;
}
#setting label {
cursor: pointer;
}
#setting input:checked+label {
background-color: #FFF;
color: #000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
<header class="wrap">
<h1>Snake</h1>
<p class="score">Score: <span id="score_value">0</span></p>
</header>
<canvas class="wrap" id="snake" width="320" height="320" tabindex="1"></canvas>
<!-- Game Over Screen -->
<div id="gameover">
<h2>Game Over</h2>
<p>press <span style="background-color: #FFFFFF; color: #000000">space</span> to begin a</p>
<a id="newgame_gameover">new game</a>
<a id="setting_gameover">settings</a>
</div>
<!-- Setting screen -->
<div id="setting">
<h2>Settings</h2>
<a id="newgame_setting">new game</a>
<p>Speed:
<input id="speed1" type="radio" name="speed" value="120" checked/>
<label for="speed1">Slow</label>
<input id="speed2" type="radio" name="speed" value="75" />
<label for="speed2">Normal</label>
<input id="speed3" type="radio" name="speed" value="35" />
<label for="speed3">Fast</label>
</p>
<p>Wall:
<input id="wallon" type="radio" name="wall" value="1" checked/>
<label for="wallon">On</label>
<input id="walloff" type="radio" name="wall" value="0" />
<label for="walloff">Off</label>
</p>
</div>
<!-- Main Menu Screen -->
<div id="menu">
<h2>Snake</h2>
<a id="newgame_menu">new game</a>
<a id="setting_menu">settings</a>
</div>
Convert your image to a base64 string, then for the first dot/head use the image instead:
var img = new Image(); // Create new img element
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAA21BMVEXoTTvqTTzpUD7nTjzmTTvrUkDpTjzmSznUPSrVPCrnTDrSOSfTOizlTDrvoJnoqJ/pVkTmU0P5urH1p53mSTjjPiv7nZF0f4NZaGvpn5b9s6qDl5iira/+nI/jPirkPyz7lop+iYtleHzsnpT7raOTpqqqtbf4lovnSjf0lorylonpSDbnTDj5oZf2mIzlSDXkOyjIY1vrd2rrWEbZZ1zYQDLsTTroTz/lRjPalY7+///////j19fVSz7tTjvmTjntRjPialzZhH3sVkXsSzniRTTYQTDmSTbmTT3qb45jAAAAAWJLR0Q7OQ70bAAAAAd0SU1FB+QEDw4AEYzMSFoAAABvSURBVAjXY2BgZGJiZmFlZWNgYGDn4GTn4ubhBTJZ+PgFBIWERdgYRMXEJSSlpGVk5RjkFRSVlFVU1dTlGRg0NLW0dXT19JkZGNgMDI2MTUzNgNrMLSytrG1s7eyBbGYHRydnFwYwYHN1c/dgYAAAtKsKjnGo4BwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDQtMTVUMTQ6MDA6MTctMDQ6MDAVi7z/AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA0LTE1VDE0OjAwOjE3LTA0OjAwZNYEQwAAAABJRU5ErkJggg==";
for (var i = 0; i < snake.length; i++) {
if (!i) {
ctx.drawImage(img, snake[i].x * 10, snake[i].y * 10);
} else {
activeDot(snake[i].x, snake[i].y);
}
}
I'm trying to recreate my own version of FlappyBird. You can see my game here: http://ps11.pstcc.edu/~c2375a22/lab6/lab6.html
The only problem that I've encountered is that none of my "blocks" are showing up. If you have never played FlappyBird, the blocks are objects that you must avoid in order to keep progressing throughout the game. If you touch a block, you lose.
My player controls are working and the collision system seems to be fine as well. Any ideas?
This is my code:
$(function () {
alert("Welcome to Space Dodge!"
+ "\n\nNavigate your vessel through the unknown for as long as you can!"
+ "\n\nUse 'Spacebar' to control the ship."
+ "\n\nIf the ship hits a block, the top, or the bottom, you lose!")
//Game variables
var game_area = $('#game_area');
var ship = $('#ship');
var block = $('.block');
var block1 = $('#block1');
var block2 = $('#block2');
var score = $('#score');
var restart_button = $('#restart_button');
//Set up variables
var game_width = parseInt(game_area.width());
var game_height = parseInt(game_area.height());
var block_initial_position = parseInt(block.css('right'));
var block_initial_height = parseInt(block.css('height'));
var ship_left = parseInt(ship.css('left'));
var ship_height = parseInt(ship.height());
//Starting speed - this will increase by +1 every time the player goes through a checkpoint
var speed = 10;
var go_up = false;
var update_score = false;
var game_over = false;
var the_game = setInterval(function () {
if (collision(ship, block1) || collision(ship, block2) || parseInt(ship.css('top')) <= 0 || parseInt(ship.css('top')) > game_height - ship_height) {
end_game();
alert("Game Over! \n\nClick 'Restart' to play again. ")
} else {
var block_current_position = parseInt(block.css('right'));
//Update the score when the poles have passed the bird successfully
if (block_current_position > game_width - ship_left) {
if (score_updated === false) {
score.text(parseInt(score.text()) + 1);
score_updated = true;
}
}
//Check whether the poles went out of the container
if (block_current_position > game_width) {
var new_height = parseInt(Math.random() * 100);
//Change the pole's height
block1.css('height', block_initial_height + new_height);
block2.css('height', block_initial_height - new_height);
//Increase speed,
speed = speed + 1;
score_updated = false;
pole_current_position = pole_initial_position;
}
//Move blocks
block.css('right', block_current_position + speed);
if (go_up === false) {
go_down();
}
}
}, 40);
//Determine collison domains
function collision($div1, $div2) {
//First set of axis
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;
//Second set of axis
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 the ship meets this condition, collision is false
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
return true;
}
function go_down() {
ship.css('top', parseInt(ship.css('top')) + 5);
}
function up() {
ship.css('top', parseInt(ship.css('top')) - 10);
}
function end_game() {
clearInterval(the_game);
game_over = true;
}
$(document).on('keydown', function (e) {
var key = e.keyCode;
if (key === 32 && go_up === false && game_over === false) {
go_up = setInterval(up, 50);
}
});
$(document).on('keyup', function (e) {
var key = e.keyCode;
if (key === 32) {
clearInterval(go_up);
go_up = false;
}
});
//Reload the page and reset the game
restart_button.click(function () {
location.reload();
});
});
body {
height: 100%;
width: 100%;
margin: 0;
background-color: black;
}
#game_area {
position: relative;
height: 400px;
width: 90%;
border: 2px solid chartreuse;
background-color: black;
margin-top: 4%;
margin-bottom: 4%;
margin-left: 4%;
overflow: hidden;
}
#ship {
position: absolute;
background: url('ship.png');
height: 42px;
width: 65px;
background-size: contain;
background-repeat: no-repeat;
top: 20%;
left: 15%;
}
.block {
position: absolute;
height: 130px;
width: 50px;
background-color: chartreuse;
right: -50px;
}
#block1 {
top: 0;
}
#block2 {
bottom: 0;
}
#score_area {
text-align: center;
font-size: 40px;
color: chartreuse;
}
<body>
<!-- Game area on the page -->
<div class="container" id="game_area">
<div id="ship"></div>
<div id="block1"></div>
<div id="block2"></div>
</div>
<!-- Score section -->
<div class="container" id="score_area">
<P>Score: <span id="score">0</span></P>
</div>
<!-- Restart button -->
<div class="container" id="restart_area">
<center><button type="button" class="btn btn-primary"
id="restart_button">Restart</button></center>
</div>
<!-- Game script -->
<script src="lab6_game.js"></script>
</body>
</html>
Add the class block to... blocks :p
<div id="block1" class="block"></div>
<div id="block2" class="block"></div>
Instead of
<div id="block1" ></div>
<div id="block2" ></div>
I'm having trouble implementing this TicTacToe AI that I found here. I'm relatively new to javascript so I'm sure I'm doing something wrong with variable scoping.
The code wont run in the snippet but heres my codepen
choices = {
0: '#ul',
1: '#um',
2: '#ur',
3: '#ml',
4: '#mm',
5: '#mr',
6: '#ll',
7: '#lm',
8: '#lr'
}
function getGrid() {
var divs = []
for (var i = 0; i < 9; i++) {
divs.push($(choices[i]).html())
}
return divs
}
function getGame() {
var divs = []
for (var i = 0; i < 9; i++) {
divs.push([$(choices[i]).html(), i])
}
return divs
}
function convertGameToGrid(game) {
var divs = []
for (var i = 0; i < game.length; i++) {
divs.push(game[i][0])
}
return divs
}
function checkGrid(divs) {
var options = [
[divs[0], divs[1], divs[2]],
[divs[3], divs[4], divs[5]],
[divs[6], divs[7], divs[8]],
[divs[0], divs[3], divs[6]],
[divs[1], divs[4], divs[7]],
[divs[2], divs[5], divs[8]],
[divs[0], divs[4], divs[8]],
[divs[2], divs[4], divs[6]]
]
for (var i = 0; i < options.length; i++) {
if (options[i][0] == 'X' && options[i][1] == 'X' && options[i][2] == 'X') {
return 'X'
} else if (options[i][0] == 'O' && options[i][1] == 'O' && options[i][2] == 'O') {
return 'O'
}
}
for (var i = 0; i < 9; i++) {
if (divs[i] == '') {
return false //still moves
}
}
return 'Tie' //no winner and no moves
}
var player = 'O'
var ai = 'X'
$(document).ready(function() {
function playerTurn(i) {
return function() {
var g = getGrid()
var cG = checkGrid(g)
if (!cG) {
if ($(choices[i]).html() == '') {
$(choices[i]).html(player)
var g = getGrid()
var cG = checkGrid(g)
if (cG == player) {
console.log('You win')
} else if (cG == 'Tie') {
console.log('Tie')
} else {
aiTurn()
}
}
}
}
}
for (var i = 0; i < 9; i++) {
$(choices[i]).on('click', playerTurn(i));
}
function score(g, depth) {
var cG = checkGrid(g)
console.log(cG, g)
if (cG == ai) {
return 10 - depth
} else if (cG == player) {
return depth - 10
} else {
return 0
}
}
function minimax(game, depth) {
var g = convertGameToGrid(game)
if (checkGrid(g)) {
return score(g, depth)
}
depth += 1
var scores = []
var moves = []
var availMoves = getAvailMoves(game)
console.log('moves', availMoves)
for (var i = 0; i < availMoves.length; i++) {
var possibleGame = game
if (depth % 2 == 0) {
possibleGame[availMoves[i]][0] = ai
} else {
possibleGame[availMoves[i]][0] = player
}
var m = minimax(possibleGame, depth)
scores.push(m)
console.log('mm: ', depth, i, scores)
moves.push(availMoves[i])
}
//even depths are ai, odd are player
if (depth % 2 == 0) {
var max_score_index = 0
var max_score = -100000000
for (var i = 0; i < scores.length; i++) {
if (scores[i] > max_score) {
max_score_index = i
max_score = scores[i]
}
}
if (depth == 0) { //we need the best move
return moves[max_score_index]
} else { //otherwise this function needs scores
return scores[max_score_index]
}
} else {
var min_score_index = 0
var min_score = 100000000
for (var i = 0; i < scores.length; i++) {
if (scores[i] < min_score) {
min_score_index = i
min_score = scores[i]
}
}
return scores[max_score_index]
}
}
function getAvailMoves(game) {
var moves = []
for (var i = 0; i < game.length; i++) {
if (game[i][0] == '') {
moves.push(game[i][1])
}
}
return moves
}
function aiTurn() {
//Dumb ai
// c = Math.floor(Math.random()*9)
// while ($(choices[c]).html()) {
// c = Math.floor(Math.random()*9)
// }
//new strategy taken from http://neverstopbuilding.com/minimax
console.log('ai')
var c;
game = getGame()
c = minimax(game, -1)
$(choices[c]).html('X')
var g = getGrid()
var cG = checkGrid(g)
if (cG == ai) {
console.log('You lose')
} else if (cG == 'Tie') {
console.log('Tie')
}
}
})
#ttt-box {
position: relative;
height: 304px;
width: 304px;
margin: 30px auto;
background-color: #bbb;
border: solid #000 4px;
border-radius: 20%;
}
#l1,
#l2,
#l3,
#l4 {
position: absolute;
background-color: #000;
}
#l1 {
left: 99px;
width: 3px;
height: 296px;
}
#l2 {
left: 199px;
width: 3px;
height: 296px;
}
#l3 {
top: 99px;
width: 296px;
height: 3px;
}
#l4 {
top: 199px;
width: 296px;
height: 3px;
}
#ul,
#um,
#ur,
#ml,
#mm,
#mr,
#ll,
#lm,
#lr {
cursor: pointer;
position: absolute;
width: 99px;
height: 99px;
font-size: 70px;
text-align: center;
}
#ul {
top: 0;
left: 0;
}
#um {
top: 0;
left: 101px;
}
#ur {
top: 0;
left: 201px;
}
#ml {
top: 101px;
left: 0;
}
#mm {
top: 101px;
left: 101px;
}
#mr {
top: 101px;
left: 201px;
}
#ll {
top: 201px;
left: 0;
}
#lm {
top: 201px;
left: 101px;
}
#lr {
top: 201px;
left: 201px;
}
<body>
<div class="container">
<div id="content">
<div id="ttt-box">
<div id="l1"></div>
<div id="l2"></div>
<div id="l3"></div>
<div id="l4"></div>
<div id="boxes">
<div id="ul"></div>
<div id="um"></div>
<div id="ur"></div>
<div id="ml"></div>
<div id="mm"></div>
<div id="mr"></div>
<div id="ll"></div>
<div id="lm"></div>
<div id="lr"></div>
</div>
</div>
</div>
</div>
</body>
The code in particular that I think is breaking is the section below. After the first player's move, I think the console.log should print out 8! times because of all the different paths that should be taken, instead it only prints 8 times as if it went down a single path.
var availMoves = getAvailMoves(game)
console.log('moves',availMoves)
for (var i=0;i<availMoves.length;i++) {
var possibleGame = game
if (depth%2==0) {
possibleGame[availMoves[i]][0] = ai
} else {
possibleGame[availMoves[i]][0] = player
}
var m = minimax(possibleGame,depth)
scores.push(m)
console.log('mm: ', depth,i, scores)
moves.push(availMoves[i])
}
Edit: What I'm noticing is that sometimes the minimax recursion is returning undefined. I've tried finding why that is (see my codepen) but I've been unsuccessful.
Edit2: It appears to be returning undefined because it is completely skipping these recursions. I still can't find a way to fix this though.
First a suggestion, since you're just getting started: learn how to use the debugger. It will be invaluable in cases like these and most modern browsers have them built in.
Regarding your issue, I haven't traced through all your code but I did notice one thing that could be causing the problem in your minmax function. Towards the bottom of that function you have this code:
//even depths are ai, odd are player
if (depth % 2 == 0) {
var max_score_index = 0
// snip...
} else {
var min_score_index = 0
// snip...
return scores[max_score_index]
}
Note that you are declaring and assigning max_score_index in the if block, but also using it in the else block (without assigning it). That will cause it to return undefined from the else block.
All I'm trying to do is to simply move a div around the screen using my arrow keys. I am completely newbie to JavaScript and what I wrote seems like it should be working but it's not. Every keyUp is moving the div based on it's original position and not the current position of the div. Is there something I'm missing?
CSS:
#pawn {
position: absolute;
left: 0;
top: 0;
width: 50px;
height: 50px;
}
JavaScript
function showKeyCode(e) {
var pawn = document.getElementById("pawn");
if(e.keyCode == "37") {
console.log("left");
pawn.style.left = -50+"px";
}
if(e.keyCode == "38") {
console.log("up");
pawn.style.top = -50+"px";
}
if(e.keyCode == "39") {
console.log("right");
pawn.style.left = +50+"px";
}
if(e.keyCode == "40") {
console.log("down");
pawn.style.top = +50+"px";
}
}
HTML:
<body onKeyUp="showKeyCode(event);">
<div id="pawn"></div>
</body>
Can anyone shine some light on this? I've been stuck for hours.
The pawn.style.left properties are text, so you're not actually incrementing the value at all. Essentially the assignments say "set it to positive/negative 50+"px"; every time.
You'll need to convert the values to integers and then add the new value, and then set it as the px value.
A very basic demo in jQuery:
$(function(){
$('body').on('keyup', function(e){
var pawn = $('#pawn');
var newLeft = parseInt(pawn.css('left')) + 50;
pawn.css('left',newLeft+'px');
});
});
Codepen
You can infer how to add support for the different directions/arrow keys.
You need to increment the position value but also take in count that the value you save is a string so you need to convert that back to a number so you can increment it again.
var pawn = document.getElementById("pawn");
var move = 50;
document.body.addEventListener('keyup', showKeyCode);
function showKeyCode(e) {
var num = 0;
if(e.keyCode === 37) {
num = parseInt(pawn.style.left, 10) || 0;
num -= move;
pawn.style.left = num + "px";
}
if(e.keyCode === 38) {
num = parseInt(pawn.style.top, 10) || 0;
num -= move;
pawn.style.top = num + "px";
}
if(e.keyCode === 39) {
num = parseInt(pawn.style.left, 10) || 0;
num += move;
pawn.style.left = num + "px";
}
if(e.keyCode === 40) {
num = parseInt(pawn.style.top, 10) || 0;
num += move;
pawn.style.top = num + "px";
}
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
font-family: "Trebuchet MS", sans-serif;
background-color: #ECEFF1;
}
#pawn {
background-color: #B0BEC5;
color: white;
width: 100px;
display: block;
text-align: center;
padding: 20px;
position: absolute;
left: 0;
top: 0;
}
<div id="pawn">pawn</div>