手把手教你用Node.js和HTML创建一个局域网应用(以贪吃蛇游戏为例)

1. 引言

大家好!今天我们要一起动手做一个有趣的小项目——一个可以在局域网内共享的贪吃蛇游戏。这个游戏不仅能够锻炼你的编程技能,还能让你的朋友或家人在同一个网络环境下一起玩耍。我们将使用一些基础但强大的工具:Node.jsHTML/CSS/JavaScript。如果你是编程新手,也不用担心,我会一步一步带你完成这个项目,并尽量用简单易懂的语言来解释每一个步骤。

2. 项目目标

  • 创建一个简单的贪吃蛇游戏。
  • 使用Node.js搭建一个本地服务器。
  • 将游戏部署到服务器上,使其可以通过局域网访问。

3. 准备工作

3.1 安装Node.js

首先,我们需要安装Node.js。Node.js是一个基于Chrome V8引擎的JavaScript运行时,让我们能够在服务器端运行JavaScript代码。

  1. 下载Node.js:

    • 访问Node.js官网
    • 点击“Download”按钮,选择适合你操作系统的版本(推荐LTS版本)。
    • 下载完成后,双击安装包并按照提示完成安装。
  2. 验证安装:

    • 打开命令行工具(Windows用户可以使用“命令提示符”或“PowerShell”,macOS/Linux用户可以使用“终端”)。

    • 输入以下命令并按回车:

      node -v
      
    • 如果安装成功,会显示Node.js的版本号,例如 v20.9.0

    • 同样地,输入以下命令验证npm是否安装成功:

      npm -v
      
    • 这也会显示npm的版本号,例如 10.1.0

3.2 创建项目目录

接下来,我们将在D盘根目录下创建一个新的文件夹来存放我们的项目文件。

  1. 打开命令行工具:

    • Windows用户打开“命令提示符”或“PowerShell”。
    • macOS/Linux用户打开“终端”。
  2. 导航到D盘根目录:

    cd D:\
    
  3. 创建项目文件夹:

    mkdir snake_game_server
    
  4. 进入项目文件夹:

    cd snake_game_server
    

4. 初始化Node.js项目

现在,我们来初始化一个新的Node.js项目。这将创建一个 package.json 文件,用于管理项目的依赖和配置信息。

  1. 初始化项目:

    • 在命令行中输入以下命令并按回车:

      npm init -y
      
    • -y 参数表示使用默认设置,无需手动输入任何信息。

  2. 查看生成的 package.json 文件:

    • 在项目目录下会生成一个 package.json 文件,内容大致如下:

      {
        "name": "snake_game_server",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1"
        },
        "keywords": [],
        "author": "",
        "license": "ISC"
      }
      

5. 编写贪吃蛇游戏代码

接下来,我们将编写贪吃蛇游戏的核心代码。这部分包括HTML、CSS和JavaScript。

5.1 创建HTML文件

  1. 创建 index.html 文件:
    • 在项目目录下创建一个新的文件 index.html
    • 使用你喜欢的文本编辑器(如Notepad++、VS Code等)打开该文件,并粘贴以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇游戏</title>
<style>
  body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background-color: #f4f4f9;
  }
  canvas {
    border: 1px solid #333;
    background-color: #fff;
  }
</style>
</head>
<body>
<canvas id="gameCanvas" width="400" height="400"></canvas>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const gridSize = 20;
const tileCount = canvas.width / gridSize;

let snake = [{ x: 10, y: 10 }];
let dx = 0;
let dy = 0;
let food = { x: 15, y: 15 };
let score = 0;

function getRandomFoodPosition() {
  return {
    x: Math.floor(Math.random() * tileCount),
    y: Math.floor(Math.random() * tileCount)
  };
}

function drawRect(x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
}

function clearScreen() {
  ctx.fillStyle = '#f4f4f9';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function moveSnake() {
  const head = { x: snake[0].x + dx, y: snake[0].y + dy };
  snake.unshift(head);

  if (head.x === food.x && head.y === food.y) {
    score++;
    food = getRandomFoodPosition();
  } else {
    snake.pop();
  }

  if (
    head.x < 0 || head.x >= tileCount ||
    head.y < 0 || head.y >= tileCount ||
    snake.slice(1).some(segment => segment.x === head.x && segment.y === head.y)
  ) {
    resetGame();
  }
}

function drawSnake() {
  snake.forEach(segment => drawRect(segment.x, segment.y, 'green'));
}

function drawFood() {
  drawRect(food.x, food.y, 'red');
}

function drawScore() {
  ctx.fillStyle = 'black';
  ctx.font = '20px Arial';
  ctx.fillText(`分数: ${score}`, 10, canvas.height - 10);
}

function gameLoop() {
  clearScreen();
  moveSnake();
  drawSnake();
  drawFood();
  drawScore();
  setTimeout(gameLoop, 100);
}

document.addEventListener('keydown', event => {
  switch (event.key) {
    case 'ArrowUp':
      if (dy === 0) { dx = 0; dy = -1; }
      break;
    case 'ArrowDown':
      if (dy === 0) { dx = 0; dy = 1; }
      break;
    case 'ArrowLeft':
      if (dx === 0) { dx = -1; dy = 0; }
      break;
    case 'ArrowRight':
      if (dx === 0) { dx = 1; dy = 0; }
      break;
  }
});

function resetGame() {
  snake = [{ x: 10, y: 10 }];
  dx = 0;
  dy = 0;
  food = getRandomFoodPosition();
  score = 0;
}

gameLoop();
</script>
</body>
</html>

5.2 解释代码

HTML部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇游戏</title>
<style>
  body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background-color: #f4f4f9;
  }
  canvas {
    border: 1px solid #333;
    background-color: #fff;
  }
</style>
</head>
<body>
<canvas id="gameCanvas" width="400" height="400"></canvas>
  • <!DOCTYPE html>: 声明文档类型为HTML5。
  • <html lang="en">: 设置文档语言为英语。
  • <meta charset="UTF-8">: 设置字符编码为UTF-8。
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">: 设置视口,使页面适应不同设备。
  • <title>贪吃蛇游戏</title>: 设置页面标题。
  • <style>: 内联CSS样式,设置页面布局和样式。
  • <body>: 页面主体内容。
  • <canvas id="gameCanvas" width="400" height="400"></canvas>: 创建一个画布元素,用于绘制游戏画面。
JavaScript部分
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const gridSize = 20;
const tileCount = canvas.width / gridSize;

let snake = [{ x: 10, y: 10 }];
let dx = 0;
let dy = 0;
let food = { x: 15, y: 15 };
let score = 0;

function getRandomFoodPosition() {
  return {
    x: Math.floor(Math.random() * tileCount),
    y: Math.floor(Math.random() * tileCount)
  };
}

function drawRect(x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
}

function clearScreen() {
  ctx.fillStyle = '#f4f4f9';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function moveSnake() {
  const head = { x: snake[0].x + dx, y: snake[0].y + dy };
  snake.unshift(head);

  if (head.x === food.x && head.y === food.y) {
    score++;
    food = getRandomFoodPosition();
  } else {
    snake.pop();
  }

  if (
    head.x < 0 || head.x >= tileCount ||
    head.y < 0 || head.y >= tileCount ||
    snake.slice(1).some(segment => segment.x === head.x && segment.y === head.y)
  ) {
    resetGame();
  }
}

function drawSnake() {
  snake.forEach(segment => drawRect(segment.x, segment.y, 'green'));
}

function drawFood() {
  drawRect(food.x, food.y, 'red');
}

function drawScore() {
  ctx.fillStyle = 'black';
  ctx.font = '20px Arial';
  ctx.fillText(`分数: ${score}`, 10, canvas.height - 10);
}

function gameLoop() {
  clearScreen();
  moveSnake();
  drawSnake();
  drawFood();
  drawScore();
  setTimeout(gameLoop, 100);
}

document.addEventListener('keydown', event => {
  switch (event.key) {
    case 'ArrowUp':
      if (dy === 0) { dx = 0; dy = -1; }
      break;
    case 'ArrowDown':
      if (dy === 0) { dx = 0; dy = 1; }
      break;
    case 'ArrowLeft':
      if (dx === 0) { dx = -1; dy = 0; }
      break;
    case 'ArrowRight':
      if (dx === 0) { dx = 1; dy = 0; }
      break;
  }
});

function resetGame() {
  snake = [{ x: 10, y: 10 }];
  dx = 0;
  dy = 0;
  food = getRandomFoodPosition();
  score = 0;
}

gameLoop();
  • 变量声明:

    • canvas: 获取画布元素。
    • ctx: 获取绘图上下文。
    • gridSize: 每个方格的大小。
    • tileCount: 画布上的方格数量。
    • snake: 蛇的身体,初始位置为 (10, 10)
    • dx, dy: 蛇移动的方向。
    • food: 食物的位置,初始位置为 (15, 15)
    • score: 游戏得分,初始为0。
  • 函数:

    • getRandomFoodPosition(): 随机生成食物的位置。
    • drawRect(x, y, color): 在指定位置绘制矩形。
    • clearScreen(): 清除整个画布。
    • moveSnake(): 移动蛇的身体,处理吃到食物和撞墙的情况。
    • drawSnake(): 绘制蛇的身体。
    • drawFood(): 绘制食物。
    • drawScore(): 显示当前得分。
    • gameLoop(): 游戏主循环,每隔100毫秒执行一次。
    • resetGame(): 重置游戏状态。
  • 事件监听:

    • 监听键盘按键事件,根据方向键改变蛇的移动方向。
  • 启动游戏:

    • 调用 gameLoop() 开始游戏循环。

6. 创建HTTP服务器

为了能够让其他设备通过局域网访问我们的贪吃蛇游戏,我们需要创建一个简单的HTTP服务器。

6.1 创建 server.js 文件

  1. 创建 server.js 文件:
    • 在项目目录下创建一个新的文件 server.js
    • 使用你喜欢的文本编辑器打开该文件,并粘贴以下代码:
const http = require('http');
const fs = require('fs');
const path = require('path');

const hostname = '0.0.0.0'; // 绑定所有网络接口
const port = 3000;

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    fs.readFile(path.join(__dirname, 'index.html'), (err, data) => {
      if (err) {
        res.statusCode = 500;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Internal Server Error');
        return;
      }
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end(data);
    });
  } else {
    res.statusCode = 404;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Not Found');
  }
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

6.2 解释代码

  • 引入模块:

    • http: 创建HTTP服务器。
    • fs: 文件系统模块,用于读取文件。
    • path: 处理和转换文件路径。
  • 服务器配置:

    • hostname: 设置为 '0.0.0.0',表示绑定所有网络接口。
    • port: 设置为 3000,表示监听3000端口。
  • 创建服务器:

    • http.createServer((req, res) => {...}): 创建HTTP服务器实例。
    • if (req.url === '/') {...}: 当请求URL为根路径时,读取 index.html 文件并返回给客户端。
    • res.statusCode = 200: 设置响应状态码为200(OK)。
    • res.setHeader('Content-Type', 'text/html'): 设置响应头,指示内容类型为HTML。
    • res.end(data): 发送文件内容作为响应体。
    • else {...}: 如果请求URL不是根路径,则返回404错误。
  • 启动服务器:

    • server.listen(port, hostname, () => {...}): 启动服务器,并在控制台输出服务器地址。

7. 启动服务器

现在,我们已经完成了所有的准备工作,可以启动服务器了。

  1. 启动服务器:

    • 在命令行中输入以下命令并按回车:

      node server.js
      
    • 你应该会看到类似以下的输出:

      Server running at http://0.0.0.0:3000/
      
  2. 访问服务器:

    • 打开浏览器,输入 http://localhost:3000/http://127.0.0.1:3000/
    • 你应该能够看到并玩你的贪吃蛇游戏。

8. 查找本机IP地址

为了让其他设备通过局域网访问你的服务器,你需要知道你的本机IP地址。

查找方法

Windows
  1. 命令提示符:

    • 打开“命令提示符”或“PowerShell”。

    • 输入以下命令并按回车:

      ipconfig
      
    • 找到“以太网适配器”或“无线局域网适配器”的“IPv4 地址”。例如:

      IPv4 地址 . . . . . . . . . . . : 192.168.1.100
      
macOS
  1. 终端:

    • 打开“终端”。

    • 输入以下命令并按回车:

      ifconfig
      
    • 找到 en0(Wi-Fi)或 en1(有线)的“inet”字段后面的地址。例如:

      inet 192.168.1.100 netmask 0xffffff00 broadcast 192.168.1.255
      
Linux
  1. 终端:

    • 打开“终端”。

    • 输入以下命令并按回车:

      ip addr show
      
    • 找到 wlan0(Wi-Fi)或 eth0(有线)的“inet”字段后面的地址。例如:

      inet 192.168.1.100/24 brd 192.168.1.255 scope global dynamic wlan0
      

9. 通过局域网访问服务器

假设你的本机IP地址是 192.168.1.100,你可以通过以下方式让其他设备访问你的服务器:

  1. 在同一网络下的设备:

    • 打开浏览器,输入 http://192.168.1.100:3000/ 并按回车。
    • 你应该能够看到并玩你的贪吃蛇游戏。
  2. 防火墙设置:

    • 确保你的防火墙允许外部设备访问3000端口。如果不允许,需要在防火墙设置中添加例外规则。

示例

假设你的本机IP地址是 192.168.1.100,以下是具体操作步骤:

  1. 启动服务器:

    cd D:\snake_game_server
    node server.js
    
  2. 查找本机IP地址:

    • 使用上述方法找到你的本机IP地址,例如 192.168.1.100
  3. 在另一台设备上访问:

    • 打开另一台设备上的浏览器。
    • 输入 http://192.168.1.100:3000/ 并按回车。
    • 你应该能够看到并玩你的贪吃蛇游戏。

10. 总结

通过本文,我们从零开始创建了一个简单的贪吃蛇游戏,并将其部署到一个本地服务器上,使其可以通过局域网访问。我们学习了以下内容:

  • 如何安装和验证Node.js。
  • 如何创建和初始化一个Node.js项目。
  • 如何编写HTML、CSS和JavaScript代码来实现贪吃蛇游戏。
  • 如何使用Node.js创建一个简单的HTTP服务器。
  • 如何查找本机IP地址并通过局域网访问服务器。

希望这篇文章对你有所帮助!如果你有任何问题或建议,请随时留言。祝你编程愉快!


附录

完整项目结构

D:\snake_game_server
├── index.html
├── package.json
└── server.js

完整代码

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇游戏</title>
<style>
  body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background-color: #f4f4f9;
  }
  canvas {
    border: 1px solid #333;
    background-color: #fff;
  }
</style>
</head>
<body>
<canvas id="gameCanvas" width="400" height="400"></canvas>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const gridSize = 20;
const tileCount = canvas.width / gridSize;

let snake = [{ x: 10, y: 10 }];
let dx = 0;
let dy = 0;
let food = { x: 15, y: 15 };
let score = 0;

function getRandomFoodPosition() {
  return {
    x: Math.floor(Math.random() * tileCount),
    y: Math.floor(Math.random() * tileCount)
  };
}

function drawRect(x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
}

function clearScreen() {
  ctx.fillStyle = '#f4f4f9';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function moveSnake() {
  const head = { x: snake[0].x + dx, y: snake[0].y + dy };
  snake.unshift(head);

  if (head.x === food.x && head.y === food.y) {
    score++;
    food = getRandomFoodPosition();
  } else {
    snake.pop();
  }

  if (
    head.x < 0 || head.x >= tileCount ||
    head.y < 0 || head.y >= tileCount ||
    snake.slice(1).some(segment => segment.x === head.x && segment.y === head.y)
  ) {
    resetGame();
  }
}

function drawSnake() {
  snake.forEach(segment => drawRect(segment.x, segment.y, 'green'));
}

function drawFood() {
  drawRect(food.x, food.y, 'red');
}

function drawScore() {
  ctx.fillStyle = 'black';
  ctx.font = '20px Arial';
  ctx.fillText(`分数: ${score}`, 10, canvas.height - 10);
}

function gameLoop() {
  clearScreen();
  moveSnake();
  drawSnake();
  drawFood();
  drawScore();
  setTimeout(gameLoop, 100);
}

document.addEventListener('keydown', event => {
  switch (event.key) {
    case 'ArrowUp':
      if (dy === 0) { dx = 0; dy = -1; }
      break;
    case 'ArrowDown':
      if (dy === 0) { dx = 0; dy = 1; }
      break;
    case 'ArrowLeft':
      if (dx === 0) { dx = -1; dy = 0; }
      break;
    case 'ArrowRight':
      if (dx === 0) { dx = 1; dy = 0; }
      break;
  }
});

function resetGame() {
  snake = [{ x: 10, y: 10 }];
  dx = 0;
  dy = 0;
  food = getRandomFoodPosition();
  score = 0;
}

gameLoop();
</script>
</body>
</html>
server.js
const http = require('http');
const fs = require('fs');
const path = require('path');

const hostname = '0.0.0.0'; // 绑定所有网络接口
const port = 3000;

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    fs.readFile(path.join(__dirname, 'index.html'), (err, data) => {
      if (err) {
        res.statusCode = 500;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Internal Server Error');
        return;
      }
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end(data);
    });
  } else {
    res.statusCode = 404;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Not Found');
  }
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
package.json
{
  "name": "snake_game_server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

希望这篇详细的教程能帮助你顺利完成项目!如果有任何疑问,欢迎随时提问。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值