您现在的位置是:首页 > swoole swoole实现一个简单的网页版pvp(多人对战)贪吃蛇 马力 2021-04-20 14:28:17 【swoole】 4227人已围观 简介swoole实现一个简单的网页版pvp(多人对战)贪吃蛇 #swoole实现pvp贪吃蛇 ##(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> 很赞哦! (0) 上一篇:laravel的上线配置操作 下一篇:tail 查看日志详解 相关文章 随机图文 thinkphp6多应用模式下的路由不生效问题 thinkphp6多应用模式下访问路由显示路由不匹配的解决方法 php实现的滑动时间窗口的限流算法 php实现的滑动时间窗口的限流算法 php中关于冬夏时令切换引发的问题 php中关于冬夏时令切换引发的问题 laravel的上线配置操作 laravel的上线配置操作 文章评论 评论总数:1来说两句吧... 用户名: 验证码: 时间:2022-09-22 17:41:43 评论者:xcz196 评论内容: require_once '../../vendor/autoload.php'; 这里加载了那些? 点击排行 最近更新 swoole实现一个简单的网页版pvp(多人对战)贪吃蛇
时间:2022-09-22 17:41:43 评论者:xcz196
评论内容: require_once '../../vendor/autoload.php'; 这里加载了那些?