swoole实现pvp贪吃蛇

本文介绍了一个基于WebSocket实现的多人在线贪吃蛇游戏。服务器采用PHP+Swoole搭建,客户端利用HTML5 Canvas进行实时渲染。游戏支持多玩家同时在线竞技,通过键盘控制蛇的方向,目标是吃掉地图上的食物并避免碰撞。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

各位客官,欢迎光临小弟网站maliweb.top

(1)服务器端:

服务器端使用php7+版本,swoole-v4.4.2版本

  1. 服务端配置文件:

     <?php
     return [
         'host' => '0.0.0.0',
         'port' => '9552',
       'set' => [
         'worker_num' => 1,
          'daemonize' => 0,
      ]
     ];
    
  2. 服务端代码

     <?php
     require_once '../../vendor/autoload.php';
     /**
      * 房间服务
      * Class RoomServer
      */
     class RoomServer
     {
     	public $server;
    
     	public $config;
    
     	public $table_fds;
    
     	public $table_food;
    
     	const PING = 10000;//心跳
     	const LOGIN = 10001;//进入游戏
     	const FOOD = 10004;//食物
     	const SNAKE_SEND_SYN_FD = 10005;//蛇信息同步
     	const SNAKE_SEND_DIE = 10006;//蛇死了
     	public function __construct()
     	{
     		$this->config = require_once '../config/server.php';//上边的配置信息
     		$this->server = new Swoole\WebSocket\Server($this->config['host'],$this->config['port']);
     		if(!empty($this->config['set'])){
     			$this->server->set($this->config['set']);
     		}
     		$this->server->on('open',[$this,'onOpen']);
     		$this->server->on('message',[$this,'onMessage']);
     		$this->server->on('close',[$this,'onClose']);
     		$this->initData();
     		$this->server->start();
     	}
    
     	/**
     	 * 初始化数据
     	 */
     	public function initData()
     	{
     		$this->table_fds = new Swoole\Table(1024);
     		$this->table_fds->column('fd',Swoole\Table::TYPE_INT);
     		$this->table_fds->create();
    
     		$this->table_food = new Swoole\Table(1024);
     		$this->table_food->column('x',Swoole\Table::TYPE_INT);
     		$this->table_food->column('y',Swoole\Table::TYPE_INT);
     		$this->table_food->create();
     	}
    
     	public function onOpen(\Swoole\Server $server,$request)
     	{
     		$this->table_fds->set($request->fd,['fd' => $request->fd]);
     		$this->log(LOG_NOTICE,"{$request->fd}进入游戏");
     		$food = [];
     		foreach ($this->table_food as $v){
     			$food[] = $v;
     		}
     		$this->server->push($request->fd,json_encode(['type' => self::LOGIN,'data' => ['fd' =>$request->fd,'foods' => $food]]));
     	}
    
     	public function onMessage(\Swoole\Server $server,$frame)
     	{
     		$fd = $frame->fd;
     		$data = json_decode($frame->data,true);
     		$type = $data['type'];
     		switch ($type){
     			case self::FOOD:
     				$this->food($data['data']);
     				break;
     			case self::SNAKE_SEND_SYN_FD:
     				$this->broadCast(json_encode(['type'=>self::SNAKE_SEND_SYN_FD,'from_fd' => $fd,'data' => $data['data']]),$fd);
     				break;
     			case self::SNAKE_SEND_DIE:
     				$this->broadCast(json_encode(['type'=>self::SNAKE_SEND_DIE,'from_fd' => $fd]),$fd);
     				break;
     			case self::PING:
     			default:
     				break;
     		}
     	}
    
     	public function onClose(\Swoole\Server $server, int $fd, int $reactorId)
     	{
    
     	}
    
     	/**
     	 * 广播消息
     	 * @param $data
     	 */
     	public function broadCast($data,$fd = null)
     	{
     		go(function () use ($data,$fd){
     			foreach ($this->table_fds as $key => $v){
     				if(!$this->server->exist($v['fd'])){
     					$this->table_fds->del($v['fd']);
     					continue;
     				}
     				if(!is_null($fd) && $v['fd'] == $fd) continue;
     				$this->server->push($v['fd'],$data);
     			}
     		});
     	}
    
     	public function foodUpdate()
     	{
     		$food = [];
     		foreach ($this->table_food as $key => $v){
     			$food[] = $v;
     		}
     		$this->broadCast(json_encode(['type' => self::FOOD,'data' => $food]));
     	}
    
     	public function food($data)
     	{
     		switch ($data['type']){
     			case 'add':
     				if(count($this->table_food) <= count($this->table_fds)){
     					$this->table_food->set($data['x'].'-'.$data['y'],['x' =>$data['x'],'y' => $data['y']]);
     				}
     				break;
     			case 'remove':
     				$this->table_food->del($data['x'].'-'.$data['y']);
     				break;
     			default:
     				break;
     		}
     		$this->foodUpdate();
     	}
    
     	/**
     	 * 进入游戏
     	 * @param $fd
     	 */
     	public function login($fd)
     	{
     		$this->broadCast(json_encode([
     			'type' => self::SNAKE_SEND_SYN_FD,
     			'to_fd' => $fd,
     		]),$fd);
     	}
    
     	/**
     	 * 日志输出
     	 * @param $level
     	 * @param $format
     	 * @return void
     	 */
     	public function log($level, $format) {
     		$args = func_get_args();
     		$message = [];
    
     		$time = microtime(true);
     		$message[] = date('[Y-m-d H:i:s', $time);
     		$message[] = '][';
     		$message[] = ']';
    
     		if (isset($args[2])) {
     			$message[] = vsprintf($format, array_slice($args, 2));
     		} else {
     			$message[] = $format;
     		}
    
     		if ($level <= 3) {
     			$prefix = "\e[31m";
     		} else if ($level <= 4) {
     			$prefix = "\x1b[33m";
     		} else if ($level <= 5) {
     			$prefix = "\x1b[35m";
     		} else if ($level <= 6) {
     			$prefix = "\x1b[32m";
     		} else {
     			$prefix = "\x1b[37m";
     		}
     		$message[] = "\x1b[0m";
     		echo $prefix . implode('', $message), PHP_EOL;
     	}
     }
    
     new RoomServer();
    

(2)客户端:

静态文件:

  1. ** change.js **:

     window.onload=function(){
     	//定义body的margin由默认值8px->0px
     	document.body.style.margin="0";
     	document.body.style.background="#30333F";
     	//创建canvas画布
     	document.body.appendChild(document.createElement('canvas'));
     	var canvas = document.querySelector('canvas'),
     		ctx = canvas.getContext('2d') //ctx返回一个在canvas上画图的api/dom
     	canvas.width = window.innerWidth;
     	canvas.height = window.innerHeight;
     	canvas.style.position='fixed';
     	ctx.lineWidth = .3;
     	ctx.strokeStyle = (new Color(150)).style;
     	//定义鼠标覆盖范围
     	var mousePosition = {
     		x: 30 * canvas.width / 100,
     		y: 30 * canvas.height / 100
     	};
     	var dots = {
     		nb: 1000,//Dot的总数
     		distance: 50,
     		d_radius: 100,
     		array: []
     	};
     	//创建颜色类,Color类返回字符串型rgba(*,*,*,.8)
     	function mixComponents(comp1, weight1, comp2, weight2) {
     		return (comp1 * weight1 + comp2 * weight2) / (weight1 + weight2);
     	}
     	function averageColorStyles(dot1, dot2) {
     		var color1 = dot1.color,
     			color2 = dot2.color;
    
     		var r = mixComponents(color1.r, dot1.radius, color2.r, dot2.radius),
     			g = mixComponents(color1.g, dot1.radius, color2.g, dot2.radius),
     			b = mixComponents(color1.b, dot1.radius, color2.b, dot2.radius);
     		return createColorStyle(Math.floor(r), Math.floor(g), Math.floor(b));
     	}
     	function colorValue(min) {
     		return Math.floor(Math.random() * 255 + min);
     	}
     	function createColorStyle(r,g,b) {
     		return 'rgba(' + r + ',' + g + ',' + b + ', 0.8)';
     	}
     	function Color(min) {
     		min = min || 0;
     		this.r = colorValue(min);
     		this.g = colorValue(min);
     		this.b = colorValue(min);
     		this.style = createColorStyle(this.r, this.g, this.b);
     	}
     	//创建Dot类以及一系列方法
     	function Dot(){
     		this.x = Math.random() * canvas.width;
     		this.y = Math.random() * canvas.height;
    
     		this.vx = -.5 + Math.random();
     		this.vy = -.5 + Math.random();
    
     		this.radius = Math.random() * 2;
    
     		this.color = new Color();
     	}
    
     	Dot.prototype = {
     		draw: function(){
     			ctx.beginPath();
     			ctx.fillStyle = this.color.style;
     			ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
     			ctx.fill();
     		}
     	};
     	function moveDots() {//Dot对象的移动
     		for(i = 0; i < dots.nb; i++){
    
     			var dot = dots.array[i];
    
     			if(dot.y < 0 || dot.y > canvas.height){
     				dot.vx = dot.vx;
     				dot.vy = - dot.vy;
     			}
     			else if(dot.x < 0 || dot.x > canvas.width){
     				dot.vx = - dot.vx;
     				dot.vy = dot.vy;
     			}
     			dot.x += dot.vx;
     			dot.y += dot.vy;
     		}
     	}
     	function connectDots(){//DOt对象的连接
     		for(i = 0; i < dots.nb; i++){
     			for(j = i; j < dots.nb; j++){
     				i_dot = dots.array[i];
     				j_dot = dots.array[j];
    
     				if((i_dot.x - j_dot.x) < dots.distance && (i_dot.y - j_dot.y) < dots.distance && (i_dot.x - j_dot.x) > - dots.distance && (i_dot.y - j_dot.y) > - dots.distance){
     					if((i_dot.x - mousePosition.x) < dots.d_radius && (i_dot.y - mousePosition.y) < dots.d_radius && (i_dot.x - mousePosition.x) > - dots.d_radius && (i_dot.y - mousePosition.y) > - dots.d_radius){
     						ctx.beginPath();
     						ctx.strokeStyle = averageColorStyles(i_dot, j_dot);
     						ctx.moveTo(i_dot.x, i_dot.y);
     						ctx.lineTo(j_dot.x, j_dot.y);
     						ctx.stroke();//绘制定义的路线
     						ctx.closePath();//创建从当前点回到起始点的路径
     					}
     				}
     			}
     		}
     	}
     	function createDots(){//创建nb个Dot对象
     		for(i = 0; i < dots.nb; i++){
     			dots.array.push(new Dot());
     		}
     	}
     	function drawDots() {//引用Dot原型链,使用draw方法,在canvas上画出Dot对象
     		for(i = 0; i < dots.nb; i++){
     			var dot = dots.array[i];
     			dot.draw();
     		}
     	}
     	function animateDots() {
     		ctx.clearRect(0, 0, canvas.width, canvas.height);//清除画布,否则线条会连在一起
     		moveDots();
     		connectDots();
     		drawDots();
     		requestAnimationFrame(animateDots);
     	}
     	createDots();//使用创建Dot类函数
     	requestAnimationFrame(animateDots);//使用canvas独有的60Hz刷新屏幕画布的方法
    
     	document.querySelector('canvas').addEventListener('mousemove',function(e){
     		mousePosition.x = e.pageX;
     		mousePosition.y = e.pageY;
     	})
    
     	document.querySelector('canvas').addEventListener('mouseleave',function(e){//鼠标离开时,连接自动返回到画布中心
     		mousePosition.x = canvas.width / 2;
     		mousePosition.y = canvas.height / 2;
     	})
    
     }
    
  2. websocket.js

    // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
    // License: New BSD License
    // Reference: http://dev.w3.org/html5/websockets/
    // Reference: http://tools.ietf.org/html/rfc6455
    
    (function() {
    
      if (window.WEB_SOCKET_FORCE_FLASH) {
    	// Keeps going.
      } else if (window.WebSocket) {
    	return;
      } else if (window.MozWebSocket) {
    	// Firefox.
    	window.WebSocket = MozWebSocket;
    	return;
      }
    
      var logger;
      if (window.WEB_SOCKET_LOGGER) {
    	logger = WEB_SOCKET_LOGGER;
      } else if (window.console && window.console.log && window.console.error) {
    	// In some environment, console is defined but console.log or console.error is missing.
    	logger = window.console;
      } else {
    	logger = {log: function(){ }, error: function(){ }};
      }
    
      // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
      if (swfobject.getFlashPlayerVersion().major < 10) {
    	logger.error("Flash Player >= 10.0.0 is required.");
    	return;
      }
      if (location.protocol == "file:") {
    	logger.error(
    	  "WARNING: web-socket-js doesn't work in file:///... URL " +
    	  "unless you set Flash Security Settings properly. " +
    	  "Open the page via Web server i.e. http://...");
      }
    
      /**
       * Our own implementation of WebSocket class using Flash.
       * @param {string} url
       * @param {array or string} protocols
       * @param {string} proxyHost
       * @param {int} proxyPort
       * @param {string} headers
       */
      window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
    	var self = this;
    	self.__id = WebSocket.__nextId++;
    	WebSocket.__instances[self.__id] = self;
    	self.readyState = WebSocket.CONNECTING;
    	self.bufferedAmount = 0;
    	self.__events = {};
    	if (!protocols) {
    	  protocols = [];
    	} else if (typeof protocols == "string") {
    	  protocols = [protocols];
    	}
    	// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
    	// Otherwise, when onopen fires immediately, onopen is called before it is set.
    	self.__createTask = setTimeout(function() {
    	  WebSocket.__addTask(function() {
    		self.__createTask = null;
    		WebSocket.__flash.create(
    			self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
    	  });
    	}, 0);
      };
    
      /**
       * Send data to the web socket.
       * @param {string} data  The data to send to the socket.
       * @return {boolean}  True for success, false for failure.
       */
      WebSocket.prototype.send = function(data) {
    	if (this.readyState == WebSocket.CONNECTING) {
    	  throw "INVALID_STATE_ERR: Web Socket connection has not been established";
    	}
    	// We use encodeURIComponent() here, because FABridge doesn't work if
    	// the argument includes some characters. We don't use escape() here
    	// because of this:
    	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
    	// But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
    	// preserve all Unicode characters either e.g. "\uffff" in Firefox.
    	// Note by wtritch: Hopefully this will not be necessary using ExternalInterface.  Will require
    	// additional testing.
    	var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
    	if (result < 0) { // success
    	  return true;
    	} else {
    	  this.bufferedAmount += result;
    	  return false;
    	}
      };
    
      /**
       * Close this web socket gracefully.
       */
      WebSocket.prototype.close = function() {
    	if (this.__createTask) {
    	  clearTimeout(this.__createTask);
    	  this.__createTask = null;
    	  this.readyState = WebSocket.CLOSED;
    	  return;
    	}
    	if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
    	  return;
    	}
    	this.readyState = WebSocket.CLOSING;
    	WebSocket.__flash.close(this.__id);
      };
    
      /**
       * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
       *
       * @param {string} type
       * @param {function} listener
       * @param {boolean} useCapture
       * @return void
       */
      WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
    	if (!(type in this.__events)) {
    	  this.__events[type] = [];
    	}
    	this.__events[type].push(listener);
      };
    
      /**
       * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
       *
       * @param {string} type
       * @param {function} listener
       * @param {boolean} useCapture
       * @return void
       */
      WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
    	if (!(type in this.__events)) return;
    	var events = this.__events[type];
    	for (var i = events.length - 1; i >= 0; --i) {
    	  if (events[i] === listener) {
    		events.splice(i, 1);
    		break;
    	  }
    	}
      };
    
      /**
       * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
       *
       * @param {Event} event
       * @return void
       */
      WebSocket.prototype.dispatchEvent = function(event) {
    	var events = this.__events[event.type] || [];
    	for (var i = 0; i < events.length; ++i) {
    	  events[i](event);
    	}
    	var handler = this["on" + event.type];
    	if (handler) handler.apply(this, [event]);
      };
    
      /**
       * Handles an event from Flash.
       * @param {Object} flashEvent
       */
      WebSocket.prototype.__handleEvent = function(flashEvent) {
    
    	if ("readyState" in flashEvent) {
    	  this.readyState = flashEvent.readyState;
    	}
    	if ("protocol" in flashEvent) {
    	  this.protocol = flashEvent.protocol;
    	}
    
    	var jsEvent;
    	if (flashEvent.type == "open" || flashEvent.type == "error") {
    	  jsEvent = this.__createSimpleEvent(flashEvent.type);
    	} else if (flashEvent.type == "close") {
    	  jsEvent = this.__createSimpleEvent("close");
    	  jsEvent.wasClean = flashEvent.wasClean ? true : false;
    	  jsEvent.code = flashEvent.code;
    	  jsEvent.reason = flashEvent.reason;
    	} else if (flashEvent.type == "message") {
    	  var data = decodeURIComponent(flashEvent.message);
    	  jsEvent = this.__createMessageEvent("message", data);
    	} else {
    	  throw "unknown event type: " + flashEvent.type;
    	}
    
    	this.dispatchEvent(jsEvent);
    
      };
    
      WebSocket.prototype.__createSimpleEvent = function(type) {
    	if (document.createEvent && window.Event) {
    	  var event = document.createEvent("Event");
    	  event.initEvent(type, false, false);
    	  return event;
    	} else {
    	  return {type: type, bubbles: false, cancelable: false};
    	}
      };
    
      WebSocket.prototype.__createMessageEvent = function(type, data) {
    	if (window.MessageEvent && typeof(MessageEvent) == "function" && !window.opera) {
    	  return new MessageEvent("message", {
    		"view": window,
    		"bubbles": false,
    		"cancelable": false,
    		"data": data
    	  });
    	} else if (document.createEvent && window.MessageEvent && !window.opera) {
    	  var event = document.createEvent("MessageEvent");
    		event.initMessageEvent("message", false, false, data, null, null, window, null);
    	  return event;
    	} else {
    	  // Old IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
    	  return {type: type, data: data, bubbles: false, cancelable: false};
    	}
      };
    
      /**
       * Define the WebSocket readyState enumeration.
       */
      WebSocket.CONNECTING = 0;
      WebSocket.OPEN = 1;
      WebSocket.CLOSING = 2;
      WebSocket.CLOSED = 3;
    
      // Field to check implementation of WebSocket.
      WebSocket.__isFlashImplementation = true;
      WebSocket.__initialized = false;
      WebSocket.__flash = null;
      WebSocket.__instances = {};
      WebSocket.__tasks = [];
      WebSocket.__nextId = 0;
    
      /**
       * Load a new flash security policy file.
       * @param {string} url
       */
      WebSocket.loadFlashPolicyFile = function(url){
    	WebSocket.__addTask(function() {
    	  WebSocket.__flash.loadManualPolicyFile(url);
    	});
      };
    
      /**
       * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
       */
      WebSocket.__initialize = function() {
    
    	if (WebSocket.__initialized) return;
    	WebSocket.__initialized = true;
    
    	if (WebSocket.__swfLocation) {
    	  // For backword compatibility.
    	  window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
    	}
    	if (!window.WEB_SOCKET_SWF_LOCATION) {
    	  logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
    	  return;
    	}
    	if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
    		!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
    		WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
    	  var swfHost = RegExp.$1;
    	  if (location.host != swfHost) {
    		logger.error(
    			"[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
    			"('" + location.host + "' != '" + swfHost + "'). " +
    			"See also 'How to host HTML file and SWF file in different domains' section " +
    			"in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
    			"by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
    	  }
    	}
    	var container = document.createElement("div");
    	container.id = "webSocketContainer";
    	// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
    	// Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
    	// But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
    	// Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
    	// the best we can do as far as we know now.
    	container.style.position = "absolute";
    	if (WebSocket.__isFlashLite()) {
    	  container.style.left = "0px";
    	  container.style.top = "0px";
    	} else {
    	  container.style.left = "-100px";
    	  container.style.top = "-100px";
    	}
    	var holder = document.createElement("div");
    	holder.id = "webSocketFlash";
    	container.appendChild(holder);
    	document.body.appendChild(container);
    	// See this article for hasPriority:
    	// http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
    	swfobject.embedSWF(
    	  WEB_SOCKET_SWF_LOCATION,
    	  "webSocketFlash",
    	  "1" /* width */,
    	  "1" /* height */,
    	  "10.0.0" /* SWF version */,
    	  null,
    	  null,
    	  {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
    	  null,
    	  function(e) {
    		if (!e.success) {
    		  logger.error("[WebSocket] swfobject.embedSWF failed");
    		}
    	  }
    	);
    
      };
    
      /**
       * Called by Flash to notify JS that it's fully loaded and ready
       * for communication.
       */
      WebSocket.__onFlashInitialized = function() {
    	// We need to set a timeout here to avoid round-trip calls
    	// to flash during the initialization process.
    	setTimeout(function() {
    	  WebSocket.__flash = document.getElementById("webSocketFlash");
    	  WebSocket.__flash.setCallerUrl(location.href);
    	  WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
    	  for (var i = 0; i < WebSocket.__tasks.length; ++i) {
    		WebSocket.__tasks[i]();
    	  }
    	  WebSocket.__tasks = [];
    	}, 0);
      };
    
      /**
       * Called by Flash to notify WebSockets events are fired.
       */
      WebSocket.__onFlashEvent = function() {
    	setTimeout(function() {
    	  try {
    		// Gets events using receiveEvents() instead of getting it from event object
    		// of Flash event. This is to make sure to keep message order.
    		// It seems sometimes Flash events don't arrive in the same order as they are sent.
    		var events = WebSocket.__flash.receiveEvents();
    		for (var i = 0; i < events.length; ++i) {
    		  WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
    		}
    	  } catch (e) {
    		logger.error(e);
    	  }
    	}, 0);
    	return true;
      };
    
      // Called by Flash.
      WebSocket.__log = function(message) {
    	logger.log(decodeURIComponent(message));
      };
    
      // Called by Flash.
      WebSocket.__error = function(message) {
    	logger.error(decodeURIComponent(message));
      };
    
      WebSocket.__addTask = function(task) {
    	if (WebSocket.__flash) {
    	  task();
    	} else {
    	  WebSocket.__tasks.push(task);
    	}
      };
    
      /**
       * Test if the browser is running flash lite.
       * @return {boolean} True if flash lite is running, false otherwise.
       */
      WebSocket.__isFlashLite = function() {
    	if (!window.navigator || !window.navigator.mimeTypes) {
    	  return false;
    	}
    	var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
    	if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
    	  return false;
    	}
    	return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
      };
    
      if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
    	// NOTE:
    	//   This fires immediately if web_socket.js is dynamically loaded after
    	//   the document is loaded.
    	swfobject.addDomLoadEvent(function() {
    	  WebSocket.__initialize();
    	});
      }
    
    })();
    

前端代码:

  1. index.html

     <!DOCTYPE html>
     <html lang="en">
     <head>
     	<meta charset="UTF-8">
     	<title>贪吃蛇</title>
     	<script src="./js/change.js" type="text/javascript"></script>
     	<script src="./js/web_socket.js" type="text/javascript"></script>
     </head>
     <style>
     	body {
     		margin: 0;
     		padding: 0;
     	}
     	.main {
     		position: absolute;
     		z-index: 10000;
     		margin: auto;
     		left: 400px;
     		top: 100px;
     		right: 0;
     		bottom: 0;
     	}
     	.btn {
     		width: 100px;
     		height: 40px;
     	}
     	.map {
     		position: relative;
     		width: 1000px;
     		height: 600px;
     		background: #ccc;
     		opacity: 0.8;
     	}
     </style>
     <body>
     <div class="main">
     	<div class="map" id="map"></div>
    
     </div>
    
     <script type="text/javascript">
     	var map = document.getElementById('map');
     	var fd;
     	var snakes = foods = {};
     	var timer;
     	socket = new WebSocket("ws://192.168.74.131:9552");
     	socket.onopen = function(event){
     		setInterval('heart()',3000);
     	};
     	socket.onclose = function(evt){
     		console.log("连接关闭");
     	};
     	socket.onerror = function(event){
     		console.log("error:"+event.data);
     	};
     	socket.onmessage = function (evt) {
     		var data = JSON.parse(evt.data);
     		type = data['type'];
     		switch (type) {
     			case 10001://登陆成功生成蛇和食物
     				data = data['data'];
     				fd = data['fd'];
     				snake = new Snake();
     				snakes[fd] = snake;
     				snakes[fd].display();
     				sendMessage({"type":10004, "data":{x:Math.floor(Math.random()*99),'y':Math.floor(Math.random()*59),'type':'add'}});
     				// 给body加按键事件,上下左右
     				document.body.onkeydown = function (e) {
     					// 有事件对象就用事件对象,没有就自己创建一个,兼容低版本浏览器
     					var ev = e || window.event;
     					switch (ev.keyCode) {
     						case 38:
     							if (snakes[fd].direction != 'down') {  // 不允许返回,向上的时候不能向下
     								snakes[fd].direction = "up";
     							}
     							break;
     						case 40:
     							if (snakes[fd].direction != "up") {
     								snakes[fd].direction = "down";
     							}
     							break;
     						case 37:
     							if (snakes[fd].direction != "right") {
     								snakes[fd].direction = "left";
     							}
     							break;
     						case 39:
     							if (snakes[fd].direction != "left") {
     								snakes[fd].direction = "right";
     							}
     							break;
     					}
     				};
     				clearInterval(timer);
     				timer = setInterval("snakes[fd].run()", 200);
     				break;
     			case 10004://食物信息更新
     				updateFoods(data['data']);
     				break;
     			case 10005://蛇信息更新
     				updateSnake(data);
     				break;
     			case 10006://蛇死亡
     				removeSnake(data['from_fd']);
     				break;
     			default:
     				break;
    
     		}
     	};
    
     	function removeSnake(fd) {
     		var s = snakes[fd];
     		if(s != null){
     			for (var i=0; i<s.body.length; i++) {
     				if (s.body[i].flag != null) {   // 如果刚吃完就死掉,会加一个值为null的
     					map.removeChild(s.body[i].flag);
     				}
     			}
     		}
     		snakes[fd] = null;
     	}
    
     	//发送消息
     	function sendMessage(data){
     		data = JSON.stringify(data);
     		socket.send(data);
     	}
    
     	/**
     	 * 更新蛇的信息
     	 */
     	function updateFoods(data)
     	{
     		for(var i in foods){
     			if(foods[i].flag != null){
     				foods[i].flag.remove()
     			}
     		}
     		foods = {};
     		for(var i in data){
     			newfood = new Food(data[i]['x'],data[i]['y'])
     			foods[i] = newfood
     			foods[i].display()
     		}
     	}
    
     	/**
     	 * 更新蛇的信息
     	 */
     	function updateSnake(data)
     	{
     		var nowFd = data['from_fd'];
     		data = data['data'];
     		if(snakes[nowFd] != null){
     			for(var i in snakes[nowFd].body){
     				if(snakes[nowFd].body[i].flag != null){
     					map.removeChild(snakes[nowFd].body[i].flag);
     				}
     			}
     		}
     		var sn = new Snake(data['direction'],data['body'],data['color']);
     		snakes[nowFd] = sn;
     		snakes[nowFd].display()
     	}
    
     	//心跳检测
     	function heart()
     	{
     		var data = {"type":10000, "data":{}};
     		sendMessage(data)
     	}
    
     	// 构造食物
     	function Food(x,y)
     	{
     		this.width = 10;
     		this.height = 10;
    
     		this.display = function() {
     			var f = document.createElement('div');
     			f.className += 'food';
     			this.flag = f;
     			f.style.width = this.width + 'px';
     			f.style.height = this.height + 'px';
     			f.style.background = 'red';
     			f.style.borderRadius = '50%';
     			f.style.position = 'absolute';
     			// this.x = Math.floor(Math.random()*80);
     			// this.y = Math.floor(Math.random()*40);
     			this.x = x;
     			this.y = y;
     			f.style.left = this.x * this.width + 'px';
     			f.style.top = this.y * this.height + 'px';
     			map.appendChild(f);
     		}
     	}
     	// 使用构造方法创建蛇,
     	function Snake(direction = 'right',body = [{x:0, y:0}],color = "rgb(" + Math.floor(Math.random()*256) + "," + Math.floor(Math.random()*256) + "," + Math.floor(Math.random()*256) + ")")
     	{
     		// 设置蛇的宽、高、默认走的方向
     		this.width = 10;
     		this.height = 10;
     		this.direction = direction;
     		this.color = color;
    
     		// 记住蛇的状态,当吃完食物的时候,就要加一个,初始为3个小点为一个蛇,
     		this.body = body;
    
     		// 显示蛇
     		this.display = function() {
     			// 创建蛇
     			for (var i=0; i<this.body.length; i++) {
     				if (this.body[i].x != null) {   // 当吃到食物时,x==null,不能新建,不然会在0,0处新建一个
     					var s = document.createElement('div');
     					// 将节点保存到状态中,以便于后面删除
     					this.body[i].flag = s;
     					// 设置宽高
     					s.style.width = this.width + 'px';
     					s.style.height = this.height + 'px';
     					s.style.borderRadius =  "50%";
     					s.style.background = this.color;
     					// 设置位置
     					s.style.position = 'absolute';
     					s.style.left = this.body[i].x * this.width + 'px';
     					s.style.top = this.body[i].y * this.height + 'px';
     					// 添加进去
     					map.appendChild(s);
     				}
     			}
     		};
    
     		// 让蛇跑起来,后一个元素到前一个元素的位置
     		// 蛇头根据方向处理,所以i不能等于0
     		this.run = function() {
     			// 后一个元素到前一个元素的位置
     			for (var i=this.body.length-1; i>0; i--) {
     				this.body[i].x = this.body[i-1].x;
     				this.body[i].y = this.body[i-1].y;
     			}
    
     			// 根据方向处理蛇头
     			switch(this.direction)
     			{
     				case "left":
     					this.body[0].x -= 1;
     					break;
     				case "right":
     					this.body[0].x += 1;
     					break;
     				case "up":
     					this.body[0].y -= 1;
     					break;
     				case "down":
     					this.body[0].y += 1;
     					break;
     			}
    
     			// 判断是否出界,一蛇头判断,出界的话,
     			if (this.body[0].x < 0 || this.body[0].x > 99 || this.body[0].y < 0 || this.body[0].y > 59) {
     				clearInterval(timer);   // 清除定时器,
     				sendMessage({'type':10006})
     				alert('您自杀了')
     				snakes[fd] = null;
     				// 删除旧的
     				for (var i=0; i<this.body.length; i++) {
     					if (this.body[i].flag != null) {   // 如果刚吃完就死掉,会加一个值为null的
     						map.removeChild(this.body[i].flag);
     					}
     				}
     				return false;   // 结束
     			}
     			for(var i in foods){
     				// 判断蛇头吃到食物,xy坐标重合,
     				if (this.body[0].x == foods[i].x && this.body[0].y == foods[i].y) {
     					// 蛇加一节,因为根据最后节点定,下面display时,会自动赋值的
     					this.body.push({x:null, y:null, flag: null});
    
     					// 清除食物,重新生成食物
     					map.removeChild(foods[i].flag);
     					sendMessage({"type":10004, "data":{x:foods[i].x,'y':foods[i].y,'type':'remove'}});
     					sendMessage({"type":10004, "data":{x:Math.floor(Math.random()*99),'y':Math.floor(Math.random()*59),'type':'add'}});
     					break;
     				}
     			}
    
     			// 吃到自己死亡,从第五个开始与头判断,因为前四个永远撞不到
     			for (var i=4; i<this.body.length; i++) {
     				if (this.body[0].x == this.body[i].x && this.body[0].y == this.body[i].y) {
     					clearInterval(timer);   // 清除定时器,
     					alert("傻子!你怎么能吃自己呢?");
     					snakes[fd] = null;
     					// 删除旧的
     					for (var j=0; j<this.body.length; j++) {
     						if (this.body[j].flag != null) {   // 如果刚吃完就死掉,会加一个值为null的
     							map.removeChild(this.body[j].flag);
     						}
     					}
     					sendMessage({'type':10006})
     					return false;   // 结束
     				}
     			}
     			/**
     			 * 判断是否撞到敌人
     			 */
     			for (var j in snakes){
     				if(j == fd || snakes[j] == null ) continue;
     				for (var i=0; i<snakes[j].body.length; i++) {
     					if (this.body[0].x == snakes[j].body[i].x && this.body[0].y == snakes[j].body[i].y) {
     						// 删除旧的
     						for (var i=0; i<this.body.length; i++) {
     							if (this.body[i].flag != null) {   // 如果刚吃完就死掉,会加一个值为null的
     								map.removeChild(this.body[i].flag);
     							}
     						}
     						sendMessage({'type':10006})
     						clearInterval(timer);   // 清除定时器,
     						alert('您可太惨了,您被撞死了');
     						return ;
     					}
     				}
     			}
    
     			// 先删掉初始的蛇,在显示新蛇
     			for (var i=0; i<this.body.length; i++) {
     				if (this.body[i].flag != null) {   // 当吃到食物时,flag是等于null,且不能删除
     					map.removeChild(this.body[i].flag);
     				}
     			}
     			// 重新显示蛇
     			this.display();
     			//同步蛇信息
     			sendMessage({'type':10005,'data':{'direction':snakes[fd].direction,'body':snakes[fd].body,'color':snakes[fd].color}})
     		}
     	}
     </script>
    
     </body>
     </html>
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值