蛇没有重叠食物

如何解决蛇没有重叠食物

我正在玩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 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res