Skip to content

Instantly share code, notes, and snippets.

@shrinktofit
Created September 30, 2020 07:47
Show Gist options
  • Save shrinktofit/10a8330db47545167e3d2784474b57a3 to your computer and use it in GitHub Desktop.
Save shrinktofit/10a8330db47545167e3d2784474b57a3 to your computer and use it in GitHub Desktop.
Simple snake implementation
interface IVec2 {
x: number;
y: number;
}
interface IBounds {
min: IVec2;
max: IVec2;
}
/**
* 方向。
*/
enum Direction {
up,
down,
left,
right,
}
interface ISnakeBodySegment {
/**
* 身子的方向。起点是头部或者上一段身子的尾部。
*/
direction: Direction;
/**
* 身子的长度。
*/
length: number;
}
/**
* 贪吃蛇的抽象定义。
*/
interface ISnake {
/**
* 蛇的头部点。
*/
head: IVec2;
/**
* 蛇身子的每一段。
*/
body: ISnakeBodySegment[];
}
/**
* 让贪吃蛇前进一步。
* @param snake
*/
function step(snake: ISnake) {
const firstBody = snake.body[0];
switch (firstBody.direction) {
case Direction.left: --snake.head.x; break;
case Direction.right: ++snake.head.y; break;
case Direction.up: ++snake.head.y; break;
case Direction.down: --snake.head.y; break;
}
}
/**
* 让贪吃蛇转个弯。
* @param snake
* @param direction
*/
function turn(snake: ISnake, direction: Direction) {
const firstBody = snake.body[0];
let isValid = false;
switch (direction) {
case Direction.left:
case Direction.right:
if (firstBody.direction !== Direction.left &&
firstBody.direction !== Direction.right) {
isValid = true;
}
break;
case Direction.up:
case Direction.down:
if (firstBody.direction !== Direction.up &&
firstBody.direction !== Direction.down) {
isValid = true;
}
break;
}
if (isValid) {
// 添加一节身体到最前面,长度是 1
snake.body.unshift({ direction, length: 1 });
// 减去最后一节身体的长度
const lastBody = snake.body[snake.body.length - 1];
--lastBody.length;
// 如果最后一节身体长度是 0,就销毁它
if (lastBody.length === 0) {
snake.body.pop();
}
}
}
/**
* 判断贪吃蛇是否自杀了(咬到自己了)。
* @param snake
*/
function selfKilled(snake: ISnake) {
return ifSomeBodyPoint(snake, (bodyPoint) => bodyPoint.x === snake.head.x && bodyPoint.y === snake.head.y);
}
/**
* 判断贪吃蛇是否触碰到边界了。
* @param snake
* @param bounds
*/
function touchBounds(snake: ISnake, bounds: IBounds) {
return ifSomeBodyPoint(snake, (bodyPoint) => isVec2OutOfBounds(bodyPoint, bounds));
function isVec2OutOfBounds(point: IVec2, bounds: IBounds) {
return isNumberOutOfBounds(point.x, bounds.min.x, bounds.max.x) ||
isNumberOutOfBounds(point.y, bounds.min.y, bounds.max.y);
}
function isNumberOutOfBounds(v: number, min: number, max: number) {
return v <= min || v >= max;
}
}
function ifSomeBodyPoint(snake: ISnake, visitor: (bodyPoint: IVec2) => boolean) {
let bodyPoint = Object.assign({}, snake.head);
for (const segment of snake.body) {
for (let i = 0; i < segment.length; ++i) {
switch (segment.direction) {
case Direction.left:
++bodyPoint.x;
break;
case Direction.right:
--bodyPoint.x;
break;
case Direction.up:
--bodyPoint.y;
break;
case Direction.down:
++bodyPoint.y;
break;
}
if (visitor(bodyPoint)) {
return true;
}
}
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment