-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
148 lines (122 loc) · 4.26 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import chalk from "chalk";
import keypress from "keypress";
const
unicode = {
vertical: "┃",
horizontal: "━",
corner: {
top: ["┏", "┓"],
bottom: ["┗", "┛"]
}
},
screenHeight = 8,
screenWidth = 25,
angleMap = new Map(["up", "right", "down", "left"].map((d, i) => [d, i]));
let
// All coordinates are zero indexed
// The snake body is built of pieces that are offset to each other by some amount
// They start offsetting from the head
body = Array(2).fill({ x: 1 }),
head = { x: 4, y: 3 },
fruit = null,
// A number from 0 - 3 which represents the direction the snake is heading towards
// Follows CSS padding order i.e. 0 is top, 1 is right
angle = 3,
// Amount to grow after eating a fruit
// May add special fruits which stimulate bigger growth, hence code is designed to work with arbitrary amount
// Currently the fruit only allows growth by 1
grow = 0;
keypress(process.stdin);
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on("keypress", (char, key) => {
if (key.name === "q" || (key.ctrl && key.name === "c")) {
console.log(chalk.red("Exiting..."));
return process.exit();
}
let newAngle = angleMap.get(key.name);
// Check if the angle isn't in the same axis
if (newAngle !== undefined && (newAngle + angle) % 2 === 1) angle = newAngle;
move();
});
const move = (_ => {
let interval;
function move() {
const angleToYOffset = [-1, 0, 1, 0];
let newHead = structuredClone(head);
newHead.y += angleToYOffset[angle];
newHead.x += angleToYOffset[(angle + 1) % 4];
if (fruit?.x === newHead.x && fruit?.y === newHead.y) {
fruit = null;
grow++;
}
body.unshift({
x: head.x - newHead.x,
y: head.y - newHead.y
});
if (grow === 0) {
body.pop();
} else {
grow--;
}
head = newHead;
draw();
}
return _ => {
clearInterval(interval);
move();
interval = setInterval(move, 1000);
};
})();
function clearScreen() {
process.stdout.cursorTo(0, 1);
process.stdout.clearScreenDown();
}
function draw() {
// The 1s account for the border
const screen = Array(1 + screenHeight + 1).fill(0).map(_ => Array(1 + screenWidth + 1).fill(" "));
// Draw the border
for (let x = 1; x <= screenWidth; x++) {
screen[screenHeight + 1][x] = screen[0][x] = unicode.horizontal;
}
for (let y = 0; y <= screenHeight + 1; y++) {
[screen[y][0], screen[y][screenWidth + 1]] = y === 0 ? unicode.corner.top : y === screenHeight + 1 ? unicode.corner.bottom : Array(2).fill(unicode.vertical);
}
// Draw the snake
let lastPos = head;
function setPart(x, y, string) {
if (screen[y + 1][x + 1] !== " ") {
// Rip the snake either touched itself or the edge
console.log(chalk.red("You lost!"));
console.log("Your final length was", chalk.yellow(body.length + 1));
process.exit();
}
screen[y + 1][x + 1] = string;
}
setPart(head.x, head.y, chalk.green("▲▶▼◀".split("")[angle]));
for (let part of body) {
part.y ??= 0;
part.x ??= 0;
let newPos = { y: lastPos.y + part.y, x: lastPos.x + part.x }
setPart(newPos.x, newPos.y, chalk.green("+"));
lastPos = newPos;
}
// Place the fruit
function generateFruitPosition() {
const
r = Math.floor(Math.random() * screenHeight * screenWidth),
x = r % screenWidth,
y = Math.floor(r / screenWidth);
return screen[y + 1][x + 1] === " " ? { x, y } : generateFruitPosition();
}
fruit ??= generateFruitPosition();
screen[fruit.y + 1][fruit.x + 1] = chalk.yellow("∙");
clearScreen(); // Delete the previous output
const shamelessSelfPromo = "Snake by code913";
process.stdout.write([
" ".repeat(Math.ceil((screenWidth - shamelessSelfPromo.length) / 2) + 1) + chalk.blue(shamelessSelfPromo),
screen.map(row => " " + row.join("")).join("\n"),
"Use arrow keys to move and press q to exit"
].join("\n") + "\n");
}
move();