js实现可自己编写新机器人的机器人大战游戏代码
代码语言:html
所属分类:游戏
代码描述:js实现可自己编写新机器人的机器人大战游戏代码,内置了5款机器人,你可以直接写新的机器人加入对战,玩法:两个机器人进入竞技场。获胜的机器人将获得荣耀和财富。 移动 每个机器人都有一个“大脑”类,可以被赋予逻辑,以便在游戏每次更新时做出决定。(默认更新速率为 100 毫秒。) 机器人每次更新可以做出的可能决定有: “左” “右” “上” “下” “射击” 能力 此外,你创建的大脑将有 5 个可能的点数分配给机器人。它们将在以下能力之间分配: 护甲 扫描距离 子弹威力 你可以给每个能力任意数量的点
代码标签: js 自己 编写 新 机器人 机器人 大战 游戏 代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link type="text/css" rel="stylesheet" href="//repo.bfw.wiki/bfwrepo/css/prism.css"> <style> body { background: #222; } #arena { border: 1px solid rgba(255, 255, 255, 0.2); height: auto; width: calc(100% - 2px); max-width: 500px; margin-top: 3em; } button { cursor: pointer; font-family: "Roboto", sans-serif; } .container { left: 50%; position: absolute; transform: translate(-50%); width: 100vw; max-width: 500px; } #log { height: 80px; width: 100%; /* max-width: 500px; */ background: #ccc; border-radius: 10px; font-family: monospace; overflow-y: auto; border: 2px dashed #666; width: calc(100% - 6px); } #log > span { display: block; padding: 5px; border: 1px solid #111; border-radius: 10px; box-sizing: border-box; color: #333; } .shield, .bandolier { transition: stroke-dasharray 200ms; } .bot, .shield, .scan, .bandolier { transition: 100ms; } .startBtn:hover, .endBtn:hover { opacity: 0.7; } .startBtn, .endBtn { border-radius: 5px; display: inline-block; background: none; padding: 5px; width: 100px; font-weight: bold; font-size: 1.8em; color: #f5f5f5; font-family: monospace; } .startBtn { float: right; border: 2px solid forestgreen; } .endBtn { float: left; border: 2px solid firebrick; } .matchupContainer { position: relative; display: inline-block; width: 100%; margin-bottom: 5px; } .versus { color: #f5f5f5; font-family: monospace; position: absolute; left: 50%; transform: translate(-50%); padding-top: 6px; } .s1 { float: left; } .s2 { float: right; } .selectedFighter { cursor: pointer; position: relative; border: 1px solid #111; border-radius: 10px; width: 200px; max-width: 45%; padding: 4px; font-weight: bold; background: goldenrod; color: #fff; /* font-family: "Roboto", sans-serif; */ font-family: monospace; font-size: 1.3em; text-shadow: 0.5px 0px 2px #000; padding-left: 8px; padding-right: 8px; } .selectedFighter > option { cursor: pointer; text-shadow: 0.5px 0px 2px #000; } option { text-align: center; text-shadow: 0.5px 0px 2px #000; cursor: pointer; } .templateLoader { background-color: rgba(155, 155, 155, 0.8); padding: 5px; font-size: 80%; width: 172px; position: relative; left: 50%; border-radius: 0 0 5px 5px; transform: translate(-50%); } /* menu stuff */ .menuArea { position: fixed; top: 0%; right: 0%; z-index: 999; height: 100%; width: 200px; border-left: 3px solid rgb(50, 50, 50); border-right: 3px solid rgb(50, 50, 50); box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; display: flex; flex-wrap: wrap; flex-direction: column; background: rgba(60, 60, 60, 0.8); opacity: 1; transition: 200ms; overflow-y: auto; overflow-x: hidden; box-shadow: -3px 0px 5px rgba(25, 25, 25, 0.5); color: #fff; font-family: "Roboto", sans-serif; } .btnContainer { margin-top: 70px; margin-bottom: 20px; text-align: center; } .btnContainer > button { flex: 1; width: 100%; height: 48px; cursor: pointer; background: rgb(100, 100, 100); color: #fff; font-size: 1.05em; font-family: monospace; } .btnContainer > button:hover { opacity: 0.8; } .leftPlayerBtn { height: 3em; width: 3em; position: fixed; left: 10px; top: 10px; background: rgba(0, 0, 0, 0.8); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24' stroke='%23fff' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round' class='css-i6dzq1'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: center; border: 2px solid rgb(150, 150, 150); border-radius: 50%; z-index: 9999; box-shadow: 2px 0px 3px #000; cursor: pointer; transition: 100ms; } .rightPlayerBtn { height: 3em; width: 3em; position: fixed; right: 10px; top: 10px; background: #000; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' width='24px' height='24px'%3E%3Cpath d='M 35 38 L 28 43 L 24 38 L 16 42 L 8 39 L 6 31 L 13 31 L 18 24 L 5 24 L 11 16 L 14 20 L 18 11 L 32 5 L 20 18 L 31 12 L 38 5 L 50 5 L 39 12 L 35 22 L 30 18 L 23 23 L 17 35 L 26 32 L 26 28 L 30 24 L 31 34 L 38 31 L 35 27 L 42 23 L 43 33 L 48 31 L 44 17 L 47 13 L 51 25 L 58 20 L 50 10 L 59 9 L 59 15 L 63 12 L 63 18 L 69 17 L 74 26 L 67 21 L 58 25 L 53 34 L 42 38 L 42 42 L 27 48 L 32 51 L 39 48 L 48 46 L 51 41 L 56 42 L 56 37 L 62 35 L 61 29 L 68 29 L 73 32 L 72 39 L 74 45 L 72 55 L 67 52 L 68 46 L 67 36 L 63 40 L 57 47 L 48 53 L 39 53 L 35 57 L 44 59 L 56 54 L 62 48 L 63 56 L 67 57 L 61 60 L 54 59 L 51 64 L 56 64 L 53 69 L 58 71 L 55 76' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: center; border: 2px solid rgb(150, 150, 150); border-radius: 50%; z-index: 9999; box-shadow: 2px 0px 3px #000; cursor: pointer; transition: 100ms; } .rightPlayerBtn:hover, .infoBtn:hover { opacity: 0.8; } .rightPlayerBtn:active, .infoBtn:active { box-shadow: 1px 0px 1px #000; } .closed { height: 0%; overflow: hidden; } .closed > * { display: none; } .customBrain { max-width: 180px; min-width: 180px; width: 180px; min-height: 200px; } /* modal stuff */ dialog { z-index: 1000; background: #ccc; box-shadow: 0px 3px 8px #000; border-radius: 8px; border: none; padding-top: 35px; position: relative; font-family: "Roboto", sans-serif; min-width: 350px; max-width: 500px; width: 80%; min-height: 500px; max-height: 700px; height: 80%; overflow: hidden; } .gameInfo { max-height: 100%; width: 100%; overflow-y: auto; } .gameInfo > h3 { text-align: center; } .closeDialog { position: absolute; font-size: 2em; /* font-weight: 900; */ top: 0px; right: 0px; border: none; background: none; cursor: pointer; height: 30px; width: 30px; color: #fff; font-family: monospace; } .closeDialog:hover { color: rgba(0, 0, 0, 0.5); } .header { text-align: center; margin-top: -30px; } #open { margin-top: 12px; width: 100px; border: 2px solid rgba(0,255,255,0.5); background: none; color: #0ff; padding: 10px; font-family: "Roboto", sans-serif; border-radius: 20px; cursor: pointer; position: absolute; } /* width */ ::-webkit-scrollbar { width: 8px; } /* Track */ ::-webkit-scrollbar-track { box-shadow: inset 0 0 3px grey; border-radius: 10px; } /* Handle */ .menuArea::-webkit-scrollbar-thumb { background: #666; border-radius: 10px; } /* Handle on hover */ .menuArea::-webkit-scrollbar-thumb:hover { background: #888; } /* Handle */ .gameInfo::-webkit-scrollbar-thumb { background: #666; border-radius: 10px; } /* Handle on hover */ .gameInfo::-webkit-scrollbar-thumb:hover { background: #888; } /* Handle */ ::-webkit-scrollbar-thumb { background: #666; border-radius: 10px; } /* Handle on hover */ ::-webkit-scrollbar-thumb:hover { background: #888; } .playerLink { word-wrap: break-word; } .newPlayer { border: 2px dotted #fff; border-radius: 5px; padding: 5px; } </style> </head> <body translate="no"> <button title="Settings" class="rightPlayerBtn"></button> <div class="menuArea closed"> <button id="open" title="How the game is played">How to Play</button> <dialog> <h3 class="header">How to Play</h3> <button type="button" class="closeDialog" title="Close">x</button> <div class="gameInfo">Just figure it out... Duh 🙄 <br /> <br /> No, but for real, 2 bots enter. 1 bot leaves victorious, shrouded in glory and riches. <br /> <br /> <hr /> <h3>Moves</h3> Each bot will have a Brain class that can be given logic to make a decision every time the game updates. <br /> <small>(The default update rate is 100ms.)</small> <br /> <br /> The possible decisions a bot can make each update are <ul> <li><b>"left"</b></li> <li><b>"right"</b></li> <li><b>"up"</b></li> <li><b>"down"</b></li> <li><b>"shoot"</b></li> </ul> <br /> <hr /> <h3>Powers</h3> Additionally, your created brain will have <i>5</i> possible points to assign to the bot. They will be split between <ul> <li><svg viewBox="0 0 24 24" width="16" height="16" stroke="green" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1"> <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path> </svg> Armor</li> <li><svg viewBox="0 0 24 24" width="16" height="16" stroke="steelblue" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1"> <circle cx="12" cy="12" r="2"></circle> <path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path> </svg> Scan Distance</li> <li><svg viewBox="0 0 24 24" width="16" height="16" stroke="firebrick" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1"> <circle cx="12" cy="12" r="10"></circle> <circle cx="12" cy="12" r="6"></circle> <circle cx="12" cy="12" r="2"></circle> </svg> Bullet Power</li> </ul> You can give any number of points (up to 5) to each power, but you only have 5 points to give. <br /> <small>**note**: If you go over 5 total points... <br /> (ex. {armor: 3, scanDistance:2, bulletPower:2}) <br /> the fighting arena will set all 3 of your bot's powers to <i>0</i>.</small> <br /> <br /> <hr /> <h3>Location</h3> In addition to having powers to balance, your bot will have access to the 2 following bot functions: <ul> <li> <b>this.scan()</b> <br /> <small>will either return: <ol> <li>an object with the enemy's x and y (if an enemy is within the scanRadius)</li> <li>an empty object (if no enemy within the radius)</li> </ol> </small> </li> <li> <b>this.whereAmI()</b> <br /> <small>will return your x and y in an object (ex: {x: 25, y: 61})</small> </li> </ul> <br /> <hr /> <h3>Logic</h3> The logic for your brain will occur within your <b>decide()</b> function. <br /> <small>This is the function that is triggered every 100ms by the bot</small> <br /> Inside of <i>decide()</i> is where you change the values of: <ul> <li><b>this.decision</b> <small>("shoot", "up", "down", "left", "right")</small></li> <li><b>this.shotAngle</b> <small>(between 0 and 360. Default is 0)</small></li> </ul> <br /> <hr /> <h3>Example Brain</h3> <pre class="language-javascript">class Brain { constructor() { this.name = "Nameless Bot"; this.color = "purple"; this.armor = 0; this.scanDistance = 0; this.bulletPower = 0; // decide() should change these 2 this.decision = "shoot"; this.shotAngle = 0; /* you can add additional variables here if you want */ } /* these calls are available any time and do NOT take the place of a decision this.scan(); will either return: 1. an object with the enemy's x and y (if an enemy is within the scan radius) 2. an empty object (if no enemy within the radius) this.whereAmI(); will return your x and y in an object (ex: {x: 25, y: 61}) */ // the decide call gets made // every time the game updates // (e.g. every 100ms) decide() { // decision-making logic here // (e.g., move left, shoot, etc.) return this.decision; } }</pre> <br /> </div> </dialog> <br /> <div class="btnContainer"> <button id="import">Import Player</button> <dialog> <h3 class="header">Import Player</h3> <button type="button" class="closeDialog" title="Close">x</button> <div class="gameInfo"> <input type="text" id="urlInput" placeholder="Enter URL of JS file"> <button class="import">Add Player</button> <br /> <h6>Example import:</h6> <span class="playerLink"><small>https://repo.bfw.wiki/bfwrepo/js/game1/Bojangles.js</small></span> <br /> <br /> <hr /> Your imported player should be the only thing that exists in the .js file you will import and should look like the following: <br /> <pre class="language-javascript">export default class MyBrain { constructor() { this.name = "Big Brain Bot"; this.color = "purple"; this.armor = 0; this.scanDistance = 0; this.bulletPower = 0; // decide() should change these 2 this.decision = "shoot"; this.shotAngle = 0; /* you can add additional variables here if you want */ } /* these calls are available any time and do NOT take the place of a decision this.scan(); will either return: 1. an object with the enemy's x and y (if an enemy is within the scan radius) 2. an empty object (if no enemy within the radius) this.whereAmI(); will return your x and y in an object (ex: {x: 25, y: 61}) */ // the decide call gets made // every time the game updates // (e.g. every 100ms) decide() { // decision-making logic here // (e.g., move left, shoot, etc.) return this.decision; } }</pre> <br /> <br /> <div class="importedPlayers"> <h5>Imported Players</h5> <hr /> </div> </div> </dialog> <br /> <br /> Game Speed (ms): <div class="gameSpeed"> <input type="range" min="10" max="300" value="100" step="10" class="slider" id="speedRange"> <br /> <small><span class="gameSpeedMs">100</span> ms</small> </div> <div> <hr /> <br /> Bullet Color: <input type="color" id="bulletCol" value="#FF69B4"> </div> <br /> <hr /> <br /> <div> Shield Color: <input type="color" id="shieldCol" value="#00FFFF"> </div> <br /> <hr /> <br /> Log: <br /> <div id="log"><span>Log messages populate here</span></div> <br /> </div> </div> <div class="container"> <svg id="arena" viewBox="0 0 100 100"> <radialGradient id="grad"> <stop offset="0%" stop-color="#000" /> <stop offset="100%" stop-color="#ff8c00" /> </radialGradient> <pattern id="mech" width="310" height="180" patternUnits="userSpaceOnUse" patternTransform="scale(.05 .05) skewY(10) skewX(-10)"> <rect height="180" width="310" opacity="1" fill="url(#grad)" /> <g id="t"> <g id="tl"> <path class="r" fill="rgb(40,40,40)" d="M 155 90 L 105 0 L 155 -90" /> <path fill="rgb(30,30,30)" d="M 155 90 L 205 0 L 155 -90" /> <path stroke="rgb(20,20,20)" d="M 155 180 V -90" /> </g> <use href="#tl" transform="translate(0 180)" /> </g> <use href="#tl" transform="translate(155 90)" /> <use href="#tl" transform="translate(-155 90)" /> <use href="#t" transform="rotate(-60 155 90)" filter="brightness(90%)" /> <use href="#t" transform="rotate(-120 155 90)" filter="brightness(90%)" /> </pattern> <rect width="100%" height="100%" fill="#222" opacity="0.2" /> <rect width="100%" height="100%" fill="url(#mech)" /> <text font-family="'Roboto', sans-serif" x="50" y="50" text-anchor="middle" dominant-baseline="middle" font-size="15" font-weight="900" fill="goldenrod" opacity="0.9">Robo Wars <animate attributeName="x" values="57%; 43%; 57%" dur="5s" begin="0s" repeatCount="indefinite" /> <animate attributeName="y" values="93%; 7%; 93%" dur="15s" begin="-6s" repeatCount="indefinite" /> </text> <!-- Bot and projectile elements will be dynamically created here --> </svg> <div class="matchupContainer"> <select name="bots1" class="selectedFighter s1" id="bot-select-1" > <!--Bots populate here--> </select> <span class="versus">vs.</span> <select name="bots2" class="selectedFighter s2" id="bot-select-2"> <!--Bots also populate here--> </select> </div> <br /> <br /> <button class="endBtn" title="End Game">END</button> <button class="startBtn" title="Start Game">START</button> </div> <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/prism.js"></script> <script type="module"> const arenaSVG = document.getElementById("arena"); let frameInterval = 100; // Update frame every 100ms let bulletColor = "#FF69B4"; let shieldColor = "#0FF"; const gameBG = `<filter id="blur"> <feGaussianBlur stdDeviation="1" /> </filter> <pattern id="rubber" width="20" height="15" patternUnits="userSpaceOnUse" patternTransform="scale(.1 .1)"> <g filter="url(#blur)" fill="#000"> <circle cx="9.8" cy="7.3" r="4" /> <circle cx="19.8" cy="14.8" r="4" /> <circle cx="-0.2" cy="14.8" r="4" /> <circle cx="-0.2" cy="-0.2" r="4" /> <circle cx="19.8" cy="-0.2" r="4" /> </g> <g filter="url(#blur)" fill="#666"> <circle cx="10.2" cy="7.7" r="4" /> <circle cx="20.2" cy="15.2" r="4" /> <circle cx="0.2" cy="15.2" r="4" /> <circle cx="0.2" cy=".2" r="4" /> <circle cx="20.2" cy=".2" r="4" /> </g> <g fill="#111"> <circle cx="20" cy="15" r="4" /> <circle cx="0" cy="15" r="4" /> <circle cx="0" cy="0" r="4" /> <circle cx="10" cy="7.5" r="4" /> <circle cx="20" cy="0" r="4" /> </g> </pattern> <rect width="100%" height="100%" fill="#222" /> <rect width="100%" height="100%" fill="url(#rubber)" />`; let isGameRunning = false; function changeFrameInterval(val) { frameInterval = val; document.querySelector(".gameSpeedMs").innerText = val; } class Arena { constructor(width, height) { this.width = width; this.height = height; this.bots = []; this.projectiles = []; } // Method to get the bots in the arena getBots() { return this.bots; }} // Initialize the game let arena = new Arena(100, 100); // Projectile linked list implementation class ProjectileNode { constructor(projectile) { this.projectile = projectile; this.next = null; }} class ProjectileLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Add a new projectile to the end of the linked list add(projectile) { const newNode = new ProjectileNode(projectile); if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail.next = newNode; this.tail = newNode; } this.length++; } // Remove a projectile from the linked list remove(projectile) { let currentNode = this.head; let prevNode = null; while (currentNode) { if (currentNode.projectile === projectile) { if (currentNode === this.head) { this.head = currentNode.next; } else if (currentNode === this.tail) { this.tail = prevNode; prevNode.next = null; } else { prevNode.next = currentNode.next; } this.length--; break; } prevNode = currentNode; currentNode = currentNode.next; } } // Method to remove all projectiles from the linked list removeAll() { this.head = null; this.tail = null; this.length = 0; } // Get the size of the linked list size() { return this.length; } // Iterate through the linked list and call a callback function on each node forEach(callback) { let currentNode = this.head; while (currentNode) { callback(currentNode.projectile); currentNode = currentNode.next; } }} // Bot class not editable by the Brain Makers class Bot { constructor(x, y, brain) { // eachg bot needs a unique ID const getUUID = () => { let d = new Date().getTime(); let uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function (c) { let r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == "x" ? r : r & 0x3 | 0x8).toString(16); }); return uuid; }; this.id = getUUID(); this.x = x; this.y = y; this.r = 2; this.brain = brain; // Add a property to track the decision counter this.decisionCounter = 0; // Initial bullet count this.bulletCount = 10; // check em before we assign this.checkPowerValues(); // Initial armor value (if 0, armor = 100) this.armor = (this.brain.armor || 0) * 25 + 100; // Initial bullet power this.bulletPower = this.brain.bulletPower || 0; // Initial scan radius this.scanRadius = (this.brain.scanDistance || 0) * 20; // create svg elements this.botElement = this.createBotElement(); this.shieldElement = this.createShieldElement(); this.scanElement = this.createScanElement(); this.bandolierElement = this.createBandolierElement(); this.projectilesList = new ProjectileLinkedList(); // Create a closure to encapsulate the scan function const scan = () => { const enemyPositions = arena.bots. filter(bot => bot !== this && this.isInScanRange(bot.x, bot.y)). map(bot => ({ x: bot.x, y: bot.y })); if (enemyPositions.length > 0) { logMe( `${this.brain.name} scanned and found ${JSON.stringify(enemyPositions[0])}`); } return enemyPositions; }; // Expose only the scan function to the Brain this.brain.scan = scan; // Create a getter function for whereAmI const whereAmI = () => { return { x: this.x, y: this.y }; }; // Add the whereAmI function to the existing brain object this.brain.whereAmI = whereAmI; } // this will punish you if you tried to give your bot too many points checkPowerValues() { function hasPower(power) { if (!power) { return 0; } return power; } function inBounds(power) { if (power >= 6 || power <= -1) { return false; } return true; } if ( hasPower(this.brain.armor) + hasPower(this.brain.bulletPower) + hasPower(this.brain.scanDistance) >= 6 || !inBounds()) { this.brain.armor = 0; this.brain.bulletPower = 0; this.brain.scanDistance = 0; } } // create the circle that represents the bot createBotElement() { const botElement = document.createElementNS( "http://www.w3.org/2000/svg", "circle"); botElement.classList.add("bot"); botElement.setAttribute("r", this.r); botElement.setAttribute("cx", this.x); botElement.setAttribute("cy", this.y); botElement.setAttribute("fill", this.brain.color); // Set the color of the bot circle botElement.setAttribute("data-name", this.brain.name); arenaSVG.appendChild(botElement); return botElement; } // create the other circle that represents the shield element createShieldElement() { const shieldElement = document.createElementNS( "http://www.w3.org/2000/svg", "circle"); let circumference = 2 * Math.PI * (this.r + 1.7); shieldElement.classList.add("shield"); shieldElement.setAttribute("r", this.r + 1.7); shieldElement.setAttribute("cx", this.x); shieldElement.setAttribute("cy", this.y); shieldElement.setAttribute("fill", "none"); shieldElement.setAttribute("stroke", shieldColor); shieldElement.setAttribute("opacity", "0.7"); shieldElement.setAttribute("stroke-width", `1.5`); shieldElement.setAttribute("stroke-dashoffset", -circumference / 4); shieldElement.setAttribute("stroke-linecap", `round`); shieldElement.setAttribute( "stroke-dasharray", `0 ${circumference / (this.armor / 25)}`); // botElement.setAttribute("data-name", this.brain.name); arenaSVG.appendChild(shieldElement); return shieldElement; } // create the other circle that represents the bandolier element createBandolierElement() { const bandElement = document.createElementNS( "http://www.w3.org/2000/svg", "circle"); let circumference = 2 * Math.PI * (this.r + 0.5); bandElement.classList.add("bandolier"); bandElement.setAttribute("r", this.r + 0.5); bandElement.setAttribute("cx", this.x); bandElement.setAttribute("cy", this.y); bandElement.setAttribute("fill", "none"); bandElement.setAttribute("stroke", bulletColor); bandElement.setAttribute("opacity", "0.8"); bandElement.setAttribute("stroke-width", `1.1`); bandElement.setAttribute("stroke-dashoffset", circumference / 4); bandElement.setAttribute("stroke-linecap", `round`); bandElement.setAttribute( "stroke-dasharray", `0 ${circumference / this.bulletCount}`); // botElement.setAttribute("data-name", this.brain.name); arenaSVG.appendChild(bandElement); return bandElement; } // create the other circle that represents the shield element createScanElement() { const scanElement = document.createElementNS( "http://www.w3.org/2000/svg", "circle"); let circumference = 2 * Math.PI * (this.brain.scanDistance * 20); scanElement.classList.add("scan"); scanElement.setAttribute("r", this.brain.scanDistance * 20); scanElement.setAttribute("cx", this.x); scanElement.setAttribute("cy", this.y); scanElement.setAttribute("fill", "none"); // Set the color of the bot circle scanElement.setAttribute("stroke", "rgba(0,255,255,0.3)"); // Set the color of the bot circle scanElement.setAttribute("stroke-width", `0.1`); // botElement.setAttribute("data-name", this.brain.name); arenaSVG.appendChild(scanElement); return scanElement; } // Function to check if a given point (x, y) falls within the scan radius of the bot isInScanRange(x, y) { const distance = Math.sqrt((x - this.x) ** 2 + (y - this.y) ** 2); return distance <= this.scanRadius; } move() { // Increment the decision counter this.decisionCounter++; // Implement decide() method in the Brain class const action = this.brain.decide(); // Check if the bot can fire based on the decision counter and remaining bullets if (this.decisionCounter % 5 == 0 && this.bulletCount <= 10) { this.bulletCount++; } if (action == "shoot" && this.bulletCount <= 0) { return; } // Update the bot's position based on the action (e.g., action = 'left', move left) switch (action) { case "left": this.x -= 1; break; case "right": this.x += 1; break; case "up": this.y -= 1; break; case "down": this.y += 1; break; case "shoot": this.shoot(this.brain.shotAngle); this.bulletCount--; break; // Add more cases for other actions as needed } // Ensure the bot stays within the viewBox boundaries this.x = Math.max(0, Math.min(this.x, 100 - this.r)); this.y = Math.max(0, Math.min(this.y, 100 - this.r)); // Update the position of the bot circle in the SVG this.botElement.setAttribute("cx", this.x); this.botElement.setAttribute("cy", this.y); this.shieldElement.setAttribute("cx", this.x); this.shieldElement.setAttribute("cy", this.y); this.bandolierElement.setAttribute("cx", this.x); this.bandolierElement.setAttribute("cy", this.y); this.scanElement.setAttribute("cx", this.x); this.scanElement.setAttribute("cy", this.y); } // Function to handle taking damage gotHit(damage) { this.armor -= damage; logMe(`${this.brain.name} is at ${this.armor} health!`); if (this.armor <= 0) { // Bot destroyed, handle accordingly (e.g., remove from SVG) this.destroy(); } else { // Change the bot's color when it gets hit this.botElement.setAttribute("fill", "yellow"); this.botElement.setAttribute("stroke", "rgba(255,0,0,0.8)"); this.botElement.setAttribute("stroke-width", 0.5); this.shieldElement.setAttribute( "stroke-dasharray", `0 ${2 * Math.PI * (this.r + 1.7) / (this.armor / 25)}`); // Reset the bot's color after a delay setTimeout(() => { this.botElement.setAttribute(&qu.........完整代码请登录后点击上方下载按钮下载查看
网友评论0