如何解决蛇没有重叠食物
我正在玩js中的基本蛇游戏。 (下面的代码在Web中的inspect元素中有效。)
但是生成的食物没有在画布上的正确位置生成。当蛇到达食物时,它仅覆盖食物区域的一半。蛇确实吃了食物,但没有完全覆盖食物。
我在生成正确位置时遇到问题。有人可以帮我吗?
代码如下:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Snake Game</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<p> <span id="current-score"></span></p>
</header>
<div id="restart">
<p class="res">Restart</p>
</div>
<div id="container"></div>
</body>
<script src="main.js"></script>
</html>
Css
html {
height: 100%;
}
* {
box-sizing: border-box;
outline: 0;
margin: 0;
}
body {
height: 100%;
background: white;
padding: 6px;
font-family: sans-serif;
display: flex;
flex-direction: column;
}
canvas {
border: 2px solid black;
border: 1px solid black;
}
#container {
flex: 1;
display: flex;
justify-content: center;
}
header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
color: white;
}
p {
font-size: 3vmin;
margin: 2%;
}
#restart{
position: absolute;
top: 50%;
left: 50%;
z-index: 3;
-ms-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
visibility: hidden;
}
.res{
font-size: 60px;
color: black;
align-items: center;
}
#parscore{
position: absolute;
top: 5%;
left: 50%;
-ms-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translate(-50%,-50%);
}
#score{
font-size: 40px;
color: black;
}
Js
const getRange = length => [...Array(length).keys()]
const getWithoutLastElement = array => array.slice(0,array.length - 1)
const areEqual = (one,another) => Math.abs(one - another) < 0.00000000001
const getRandomFrom = array => array[Math.floor(Math.random() * array.length)]
const getLastElement = array => array[array.length - 1]
let restart = document.getElementById("restart");
restart.addEventListener('click',restartGame);
// #region geometry
class Vector {
constructor(x,y) {
this.x = x
this.y = y
}
subtract({ x,y }) {
return new Vector(this.x - x,this.y - y)
}
add({ x,y }) {
return new Vector(this.x + x,this.y + y)
}
scaleBy(number) {
return new Vector(this.x * number,this.y * number)
}
length() {
return Math.hypot(this.x,this.y)
}
normalize() {
return this.scaleBy(1 / this.length())
}
isOpposite(vector) {
const { x,y } = this.add(vector)
return areEqual(x,0) && areEqual(y,0)
}
equalTo({ x,y }) {
return areEqual(this.x,x) && areEqual(this.y,y)
}
}
class Segment {
constructor(start,end) {
this.start = start
this.end = end
}
getVector() {
return this.end.subtract(this.start)
}
length() {
return this.getVector().length()
}
isPointInside(point) {
const first = new Segment(this.start,point)
const second = new Segment(point,this.end)
return areEqual(this.length(),first.length() + second.length())
}
getProjectedPoint({ x,y }) {
const { start,end } = this
const { x: px,y: py } = end.subtract(start)
const u = ((x - start.x) * px + (y - start.y) * py) / (px * px + py * py)
return new Vector(start.x + u * px,start.y + u * py)
}
}
const getSegmentsFromVectors = vectors => getWithoutLastElement(vectors)
.map((one,index) => new Segment(one,vectors[index + 1]))
// #endregion
// #region constants
const UPDATE_EVERY = 1000 / 60
const DIRECTION = {
TOP: new Vector(0,-1),RIGHT: new Vector(1,0),DOWN: new Vector(0,1),LEFT: new Vector(-1,0)
}
const DEFAULT_GAME_CONFIG = {
width: 17,height: 15,speed: 0.006,initialSnakeLength: 3,initialDirection: DIRECTION.RIGHT
}
const MOVEMENT_KEYS = {
TOP: [87,38],RIGHT: [68,39],DOWN: [83,40],LEFT: [65,37]
}
const STOP_KEY = 32
// #endregion
// #region game core
const getFood = (width,height,snake) => {
const allPositions = getRange(width).map(x =>
getRange(height).map(y => new Vector(x + 0.5,y + 0.5))
).flat()
const segments = getSegmentsFromVectors(snake)
const freePositions = allPositions
.filter(point => segments.every(segment => !segment.isPointInside(point)))
return getRandomFrom(freePositions)
}
const getGameInitialState = (config = {}) => {
const {
width,speed,initialSnakeLength,initialDirection
} = { ...config,...DEFAULT_GAME_CONFIG }
const head = new Vector(
Math.round(width / 2) - 0.5,Math.round(height / 2) - 0.5
)
const tailtip = head.subtract(initialDirection.scaleBy(initialSnakeLength))
// console.log("head",head.x);
const snake = [tailtip,head]
var food = getFood(width,snake)
if(food.x > 15.5){
food.x = 15.5;
}
return {
width,initialDirection,snake,direction: initialDirection,food,score: 0
}
}
const getNewTail = (oldSnake,distance) => {
const { tail } = getWithoutLastElement(oldSnake).reduce((acc,point,index) => {
if (acc.tail.length !== 0) {
return {
...acc,tail: [...acc.tail,point]
}
}
const next = oldSnake[index + 1]
const segment = new Segment(point,next)
const length = segment.length()
if (length >= distance) {
const vector = segment.getVector().normalize().scaleBy(acc.distance)
return {
distance: 0,point.add(vector)]
}
} else {
return {
...acc,distance: acc.distance - length
}
}
},{ distance,tail: [] })
return tail
}
const getNewDirection = (oldDirection,movement) => {
const newDirection = DIRECTION[movement]
const shouldChange = newDirection && !oldDirection.isOpposite(newDirection)
return shouldChange ? newDirection : oldDirection
}
const getStateAfterMoveProcessing = (state,movement,distance) => {
const newTail = getNewTail(state.snake,distance)
const oldHead = getLastElement(state.snake)
const newHead = oldHead.add(state.direction.scaleBy(distance))
const newDirection = getNewDirection(state.direction,movement)
if (!state.direction.equalTo(newDirection)) {
const { x: oldX,y: oldY } = oldHead
const [
oldXRounded,oldYRounded,newXRounded,newYRounded
] = [oldX,oldY,newHead.x,newHead.y].map(Math.round)
const getStateWithBrokenSnake = (old,oldRounded,newRounded,getBreakpoint) => {
const breakpointComponent = oldRounded + (newRounded > oldRounded ? 0.5 : -0.5)
const breakpoint = getBreakpoint(breakpointComponent)
const vector = newDirection.scaleBy(distance - Math.abs(old - breakpointComponent))
const head = breakpoint.add(vector)
return {
...state,direction: newDirection,snake: [...newTail,breakpoint,head]
}
}
if (oldXRounded !== newXRounded) {
return getStateWithBrokenSnake(
oldX,oldXRounded,x => new Vector(x,oldY)
)
}
if (oldYRounded !== newYRounded) {
return getStateWithBrokenSnake(
oldY,newYRounded,y => new Vector(oldX,y)
)
}
}
return {
...state,newHead]
}
}
const getStateAfterFoodProcessing = (state) => {
const headSegment = new Segment(
getLastElement(getWithoutLastElement(state.snake)),getLastElement(state.snake)
)
if (!headSegment.isPointInside(state.food)) return state
const [tailEnd,beforeTailEnd,...restOfSnake] = state.snake
const tailSegment = new Segment(beforeTailEnd,tailEnd)
const newTailEnd = tailEnd.add(tailSegment.getVector().normalize())
const snake = [newTailEnd,...restOfSnake]
var food = getFood(state.width,state.height,snake)
if(food.x > 15.5){
food.x = 15.5;
}
return {
...state,score: state.score + 1,food
}
}
const isGameOver = ({ snake,width,height }) => {
var { x,y } = getLastElement(snake)
if (x < 0 || x > width || y < 0 || y > getContainer().getBoundingClientRect().y-3.5) {
return true
}
if (snake.length < 5) return false
const [head,...tail] = snake.slice().reverse()
return getSegmentsFromVectors(tail).slice(2).find(segment => {
const projected = segment.getProjectedPoint(head)
if (!segment.isPointInside(projected)) {
return false
}
const distance = new Segment(head,projected).length()
return distance < 0.5
})
}
const getNewGameState = (state,timespan) => {
const distance = state.speed * timespan
const stateAfterMove = getStateAfterMoveProcessing(state,distance)
const stateAfterFood = getStateAfterFoodProcessing(stateAfterMove)
if (isGameOver(stateAfterFood)) {
// return getGameInitialState(state)
getContext(viewWidth,viewHeight).clearRect(0,viewWidth,viewWidth);
clearInterval(intervalGame);
restart.style.visibility = 'visible';
}else{
return stateAfterFood
}
}
const getContainer = () => document.getElementById('container')
const getContainerSize = () => {
const { width,height } = getContainer().getBoundingClientRect()
return { width,height }
}
const clearContainer = () => {
const container = getContainer()
const [child] = container.children
if (child) {
container.removeChild(child)
}
}
const getProjectors = (containerSize,game) => {
const widthRatio = containerSize.width / game.width
const heightRatio = containerSize.height / game.height
const unitOnScreen = Math.min(widthRatio,heightRatio)
// console.log(unitOnScreen);
return {
projectDistance: distance => distance * unitOnScreen,projectPosition: position => position.scaleBy(unitOnScreen)
}
}
var canvas;
const getContext = (width,height) => {
[existing] = document.getElementsByTagName('canvas')
canvas = existing || document.createElement('canvas')
if (!existing) {
getContainer().appendChild(canvas)
}
const context = canvas.getContext('2d')
context.clearRect(0,canvas.width,canvas.height)
canvas.setAttribute('width',width)
canvas.setAttribute('height',height)
return context
}
const renderCells = (context,cellSide,height) => {
context.globalAlpha = 0.2
context.globalAlpha = 1
}
const renderFood = (context,{ x,y }) => {
context.beginPath()
context.fillStyle = 'hsl('+ 100 +',100%,60%,1)';
context.fillRect(x,y,cellSide/2,cellSide/2);
}
const renderSnake = (context,snake) => {
context.lineWidth = cellSide/2
context.strokeStyle = '#3498db'
context.beginPath()
// console.log("snake ",snake);
snake.forEach(({ x,y }) => context.lineTo(x,y))
context.stroke()
}
const renderScores = (score,bestScore) => {
document.getElementById('current-score').innerText = score
foodflag = true;
}
var tempWidth,tempHeight;
const render = ({
game: {
width,score
},bestScore,projectDistance,projectPosition
}) => {
tempWidth = window.innerWidth - ( window.innerWidth * 0.0001 );
tempHeight = window.innerHeight - ( window.innerHeight * 0.1 );
if(tempWidth % 30 != 0){
tempWidth = tempWidth - (tempWidth % 30);
}
if(tempHeight % 30 != 0){
tempHeight = tempHeight - (tempHeight % 30);
}
viewWidth = tempWidth;
viewHeight = tempHeight;
const context = getContext(viewWidth,viewHeight)
const cellSide = viewWidth / width
renderCells(context,height)
renderFood(context,projectPosition(food))
// console.log(snake,snake.map(projectPosition));
renderSnake(context,snake.map(projectPosition))
renderScores(score,bestScore)
}
const getInitialState = () => {
const game = getGameInitialState()
const containerSize = getContainerSize()
// console.log("game ",game);
return {
game,bestScore: parseInt(localStorage.bestScore) || 0,...containerSize,...getProjectors(containerSize,game)
}
}
const getNewStatePropsOnTick = (oldState) => {
if (oldState.stopTime) return oldState
const lastUpdate = Date.now()
if (oldState.lastUpdate) {
const game = getNewGameState(
oldState.game,oldState.movement,lastUpdate - oldState.lastUpdate
)
const newProps = {
game,lastUpdate
}
if (game.score > oldState.bestScore) {
localStorage.setItem('bestScore',game.score)
return {
...newProps,bestScore: game.score
}
}
return newProps
}
return {
lastUpdate
}
}
var updateState;
const startGame = () => {
let state = getInitialState()
console.log(state);
updateState = props => {
state = { ...state,...props }
}
window.addEventListener('resize',() => {
clearContainer()
const containerSize = getContainerSize()
updateState({ ...containerSize,state.game) })
tick()
})
window.addEventListener('keydown',({ which }) => {
const entries = Object.entries(MOVEMENT_KEYS)
const [movement] = entries.find(([,keys]) => keys.includes(which)) || [undefined]
console.log("Movement",which);
updateState({ movement })
})
window.addEventListener('keyup',({ which }) => {
updateState({ movement: undefined })
if (which === STOP_KEY) {
const now = Date.now()
if (state.stopTime) {
updateState({ stopTime: undefined,lastUpdate: state.time + now - state.lastUpdate })
} else {
updateState({ stopTime: now })
}
}
})
const tick = () => {
const newProps = getNewStatePropsOnTick(state)
updateState(newProps)
render(state)
}
intervalGame = setInterval(tick,UPDATE_EVERY)
}
var intervalGame,viewHeight,foodflag= true;
startGame()
function restartGame(){
location.reload();
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。