Comparing version 0.1.3 to 1.0.0
{ | ||
"name": "snekjs", | ||
"version": "0.1.3", | ||
"description": "no step on snek", | ||
"version": "1.0.0", | ||
"description": "A terminal-based Snake implementation written in JavaScript (Node.js) 🐍", | ||
"author": { | ||
@@ -12,3 +12,3 @@ "name": "Tania Rascia", | ||
"scripts": { | ||
"play": "node src/play.js" | ||
"play": "node play.js" | ||
}, | ||
@@ -15,0 +15,0 @@ "dependencies": { |
@@ -1,5 +0,7 @@ | ||
# 🐍 no step on snek [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![snekjs on NPM](https://img.shields.io/npm/v/snekjs.svg?color=green&label=snekjs)](https://www.npmjs.com/package/snekjs) | ||
# 🐍 Snek.js [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![snekjs on NPM](https://img.shields.io/npm/v/snekjs.svg?color=green&label=snekjs)](https://www.npmjs.com/package/snekjs) | ||
Snake written in JavaScript for the terminal (Node.js). | ||
A terminal-based Snake implementation written in JavaScript (Node.js). | ||
![snek.gif](snek.gif) | ||
## Instructions | ||
@@ -10,3 +12,3 @@ | ||
```bash | ||
git clone git@github.com:taniarascia/snek | ||
git clone https://github.com/taniarascia/snek | ||
cd snek | ||
@@ -18,3 +20,3 @@ yarn && yarn play | ||
Install the snek package: | ||
Add the `snekjs` module. | ||
@@ -25,42 +27,14 @@ ```bash | ||
Create `play.js` | ||
Create the game. | ||
```js | ||
// play.js | ||
// index.js | ||
const blessed = require('blessed') | ||
const screen = blessed.screen({ smartCSR: true }) | ||
const { Interface, Game } = require('snekjs') | ||
const ui = new Interface(screen, blessed) | ||
const { UserInterface, Game } = require('snekjs') | ||
const ui = new UserInterface(blessed, blessed.screen()) | ||
const game = new Game(ui) | ||
function tick() { | ||
if (game.gameOver()) { | ||
// Show game over screen on collision and end game | ||
ui.gameOverScreen() | ||
ui.render() | ||
return | ||
} | ||
ui.clearScreen() | ||
ui.clearDirection() | ||
game.renderDot() | ||
game.moveSnake() | ||
game.renderSnake() | ||
ui.render() | ||
} | ||
// Iterate every 50ms | ||
function main() { | ||
setInterval(tick, 50) | ||
} | ||
// Generate the coordinates of the first dot before the game begins | ||
game.generateDot() | ||
// Begin game | ||
main() | ||
game.start() | ||
``` | ||
@@ -71,5 +45,11 @@ | ||
```bash | ||
node play.js | ||
node index.js | ||
``` | ||
## Acknowledgements | ||
- [Vanya Sergeev](https://sergeev.io) for pointing out my snake collision bug, for advising me to make a single, reusable draw method, for showing me how to properly bind methods between classes, and for overall guidance and inspiration | ||
- [Devin McIntyre](https://www.dev-eloper.com/) for general advice | ||
- Panayiotis Nicolaou's [JavaScript Snake for Web](https://medium.freecodecamp.org/think-like-a-programmer-how-to-build-snake-using-only-javascript-html-and-css-7b1479c3339e) for initial logic | ||
## Author | ||
@@ -76,0 +56,0 @@ |
190
src/Game.js
@@ -0,9 +1,32 @@ | ||
const { performance, PerformanceObserver } = require('perf_hooks') | ||
/** | ||
* @class Game | ||
* | ||
* Track the state of the snake, dot, and score | ||
* The Game class tracks the state of three things: | ||
* | ||
* 1. The snake, including its direction, velocity, and location | ||
* 2. The dot | ||
* 3. The score | ||
* | ||
* The i/o of the game is handled by a separate UserInterface class, which is | ||
* responsible for * detecting all event handlers (key press), creating the | ||
* screen, and drawing elements to the screen. | ||
*/ | ||
class Game { | ||
constructor(ui) { | ||
// The snake is an array of x/y coordinates | ||
// User interface class for all i/o operations | ||
this.ui = ui | ||
this.reset() | ||
// Bind handlers to UI so we can detect input change from the Game class | ||
this.ui.bindHandlers( | ||
this.changeDirection.bind(this), | ||
this.quit.bind(this), | ||
this.start.bind(this) | ||
) | ||
} | ||
reset() { | ||
// Set up initial state | ||
this.snake = [ | ||
@@ -17,43 +40,59 @@ { x: 5, y: 0 }, | ||
] | ||
this.dot = {} | ||
this.score = 0 | ||
this.ui = ui // i/o | ||
this.vx = 0 // horizontal velocity | ||
this.vy = 0 // vertical velocity | ||
this.dot = {} // the first random dot will be generated before the game begins | ||
} | ||
moveSnake() { | ||
const goingUp = this.vy === -1 | ||
const goingDown = this.vy === 1 | ||
const goingLeft = this.vx === -1 | ||
const goingRight = this.vx === 1 | ||
if (this.ui.changingDirection) { | ||
return | ||
this.currentDirection = 'right' | ||
this.directions = { | ||
up: { x: 0, y: -1 }, | ||
down: { x: 0, y: 1 }, | ||
right: { x: 1, y: 0 }, | ||
left: { x: -1, y: 0 }, | ||
} | ||
this.changingDirection = false | ||
this.timer = null | ||
this.ui.changingDirection = true | ||
// Generate the first dot before the game begins | ||
this.generateDot() | ||
this.ui.resetScore() | ||
this.ui.render() | ||
} | ||
if (this.ui.currentDirection === 'up' && !goingDown) { | ||
this.vy = -1 | ||
this.vx = 0 | ||
/** | ||
* Support WASD and arrow key controls. Update the direction of the snake, and | ||
* do not allow reversal. | ||
*/ | ||
changeDirection(_, key) { | ||
if ((key.name === 'up' || key.name === 'w') && this.currentDirection !== 'down') { | ||
this.currentDirection = 'up' | ||
} | ||
if (this.ui.currentDirection === 'down' && !goingUp) { | ||
this.vy = 1 | ||
this.vx = 0 | ||
if ((key.name === 'down' || key.name === 's') && this.currentDirection !== 'up') { | ||
this.currentDirection = 'down' | ||
} | ||
if ((key.name === 'left' || key.name === 'a') && this.currentDirection !== 'right') { | ||
this.currentDirection = 'left' | ||
} | ||
if ((key.name === 'right' || key.name === 'd') && this.currentDirection !== 'left') { | ||
this.currentDirection = 'right' | ||
} | ||
} | ||
if (this.ui.currentDirection === 'right' && !goingLeft) { | ||
this.vx = 1 | ||
this.vy = 0 | ||
/** | ||
* Set the velocity of the snake based on the current direction. Create a new | ||
* head by adding a new segment to the beginning of the snake array, | ||
* increasing by one velocity. Remove one item from the end of the array to | ||
* make the snake move, unless the snake collides with a dot - then increase | ||
* the score and increase the length of the snake by one. | ||
* | ||
*/ | ||
moveSnake() { | ||
if (this.changingDirection) { | ||
return | ||
} | ||
this.changingDirection = true | ||
if (this.ui.currentDirection === 'left' && !goingRight) { | ||
this.vx = -1 | ||
this.vy = 0 | ||
// Move the head forward by one pixel based on velocity | ||
const head = { | ||
x: this.snake[0].x + this.directions[this.currentDirection].x, | ||
y: this.snake[0].y + this.directions[this.currentDirection].y, | ||
} | ||
// Move the head forward by one pixel based on velocity | ||
const head = { x: this.snake[0].x + this.vx, y: this.snake[0].y + this.vy } | ||
this.snake.unshift(head) | ||
@@ -64,3 +103,3 @@ | ||
this.score++ | ||
this.ui.setScore(this.score) | ||
this.ui.updateScore(this.score) | ||
this.generateDot() | ||
@@ -73,9 +112,2 @@ } else { | ||
renderSnake() { | ||
// Render each snake segment as a pixel | ||
this.snake.forEach((segment, i) => { | ||
this.ui.drawSnake(segment, i) | ||
}) | ||
} | ||
generateRandomPixelCoords(min, max) { | ||
@@ -89,3 +121,3 @@ // Get a random coordinate from 0 to max container height/width | ||
this.dot.x = this.generateRandomPixelCoords(0, this.ui.gameContainer.width - 1) | ||
this.dot.y = this.generateRandomPixelCoords(0, this.ui.gameContainer.height - 1) | ||
this.dot.y = this.generateRandomPixelCoords(1, this.ui.gameContainer.height - 1) | ||
@@ -100,29 +132,75 @@ // If the pixel is on a snake, regenerate the dot | ||
renderDot() { | ||
drawSnake() { | ||
// Render each snake segment as a pixel | ||
this.snake.forEach(segment => { | ||
this.ui.draw(segment, 'green') | ||
}) | ||
} | ||
drawDot() { | ||
// Render the dot as a pixel | ||
this.ui.drawDot(this.dot) | ||
this.ui.draw(this.dot, 'red') | ||
} | ||
gameOver() { | ||
let collide = false | ||
// If the snake collides with itself, end the game | ||
this.snake.forEach((segment, i) => { | ||
collide = segment.x === this.snake[0].x && segment.y === this.snake[0].y | ||
}) | ||
const collide = this.snake | ||
// Filter out the head | ||
.filter((_, i) => i > 0) | ||
// If head collides with any segment, collision | ||
.some(segment => segment.x === this.snake[0].x && segment.y === this.snake[0].y) | ||
return ( | ||
collide || | ||
// right wall | ||
this.snake[0].x === this.ui.gameContainer.width - 1 || | ||
// left wall | ||
this.snake[0].x === -1 || | ||
// top wall | ||
this.snake[0].y === this.ui.gameContainer.height - 1 || | ||
// bottom wall | ||
this.snake[0].y === -1 | ||
// Right wall | ||
this.snake[0].x >= this.ui.gameContainer.width - 1 || | ||
// Left wall | ||
this.snake[0].x <= -1 || | ||
// Top wall | ||
this.snake[0].y >= this.ui.gameContainer.height - 1 || | ||
// Bottom wall | ||
this.snake[0].y <= -1 | ||
) | ||
} | ||
showGameOverScreen() { | ||
this.ui.gameOverScreen() | ||
this.ui.render() | ||
} | ||
// Set to initial direction and clear the screen | ||
clear() { | ||
this.changingDirection = false | ||
this.ui.clearScreen() | ||
} | ||
tick() { | ||
if (this.gameOver()) { | ||
this.showGameOverScreen() | ||
clearInterval(this.timer) | ||
this.timer = null | ||
return | ||
} | ||
this.clear() | ||
this.drawDot() | ||
this.moveSnake() | ||
this.drawSnake() | ||
this.ui.render() | ||
} | ||
start() { | ||
if (!this.timer) { | ||
this.reset() | ||
this.timer = setInterval(this.tick.bind(this), 50) | ||
} | ||
} | ||
quit() { | ||
process.exit(0) | ||
} | ||
} | ||
module.exports = { Game } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
55449
9
297
1
58
2
1