diff --git a/run.py b/run.py index ce7f4df..ef87bc1 100644 --- a/run.py +++ b/run.py @@ -111,7 +111,7 @@ def on_update(self, delta_time: float): dead_sprites = self.world.step() for sprite_index in dead_sprites: - self.grid_sprite_list[sprite_index].visible = False + self.grid_sprite_list[sprite_index].color = arcade.color.BLACK self.available_sprites.append(sprite_index) def on_draw(self): diff --git a/src/being.py b/src/being.py index 966a6c6..21732ad 100644 --- a/src/being.py +++ b/src/being.py @@ -6,8 +6,8 @@ ENERGY_LOSS_GENERAL = 0.001 ENERGY_LOSS_ACTIONS = 0.005 -FOOD_TO_ENERGY = 0.1 -WATER_TO_ENERGY = 0.1 +FOOD_TO_ENERGY = 0.001 +WATER_TO_ENERGY = 0.001 # Rotation vectors for movement theta = np.deg2rad(45) @@ -23,18 +23,18 @@ class Being: """ def __init__(self, sprite_index): # Subjective states (things the "brain" feels) - self.happiness = 1 - self.hunger = 0 - self.thirst = 0 + self.happiness = 1. + self.hunger = 0. + self.thirst = 0. # Objective states (hidden from the "brain") - self.food = 1 - self.water = 1 - self.energy = 1 + self.food = 1. + self.water = 1. + self.energy = 1. - self.angle = 0 + self.angle = 0. self.direction = [1, 0] # vector from (0,0) (the being) to the direction its facing - self.speed = 0 + self.speed = 0. self.action_space = ['NOOP', 'TURN_LEFT', 'TURN_RIGHT', 'MOVE', 'STOP', 'EAT', 'DRINK'] self.sprite_index = sprite_index @@ -50,18 +50,19 @@ def choose_action(self): return action def step(self): - self.energy -= ENERGY_LOSS_GENERAL + self.energy = max(0, self.energy - ENERGY_LOSS_GENERAL) - if self.food > 0: - self.food -= FOOD_TO_ENERGY - self.energy += FOOD_TO_ENERGY + if self.energy < 1: + if self.food > 0: + self.food = max(0, self.food - FOOD_TO_ENERGY) + self.energy = min(1, self.energy + FOOD_TO_ENERGY) - if self.water > 0: - self.water -= WATER_TO_ENERGY - self.energy += WATER_TO_ENERGY + if self.water > 0: + self.water = max(0, self.water - WATER_TO_ENERGY) + self.energy = min(1, self.energy + WATER_TO_ENERGY) if self.speed > 0: - self.energy -= ENERGY_LOSS_ACTIONS + self.energy = max(0, self.energy - ENERGY_LOSS_ACTIONS) def is_alive(self): return self.energy > 0 \ No newline at end of file diff --git a/src/playground.ipynb b/src/playground.ipynb index e30c7ee..8ee04b3 100644 --- a/src/playground.ipynb +++ b/src/playground.ipynb @@ -9,197 +9,76 @@ "outputs": [], "source": [ "import numpy as np\n", - "import matplotlib.pyplot as plt\n", "import random\n", - "from math import cos, sin\n", - "import matplotlib.animation as animation\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# from math import cos, sin\n", + "# import matplotlib.animation as animation\n", "\n", "random.seed(1)\n", "\n", - "plt.rcParams[\"figure.figsize\"] = (10, 10)\n", + "plt.rcParams[\"figure.figsize\"] = (10, 5)\n", "\n", - "%matplotlib notebook" + "%load_ext autoreload\n", + "%autoreload 2" ] }, { "cell_type": "code", "execution_count": 2, + "outputs": [], + "source": [ + "WORLD_SIZE = 250\n", + "INITIAL_POPULATION = 1000" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 3, "outputs": [ { "data": { - "text/plain": "", - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute('style', 'box-sizing: content-box;');\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n canvas.setAttribute(\n 'style',\n 'width: ' + width + 'px; height: ' + height + 'px;'\n );\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n\n rubberband_canvas.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n rubberband_canvas.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n rubberband_canvas.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband_canvas.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n rubberband_canvas.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n rubberband_canvas.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.rubberband_canvas.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\n// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\nmpl.findpos = function (e) {\n //this section is from http://www.quirksmode.org/js/events_properties.html\n var targ;\n if (!e) {\n e = window.event;\n }\n if (e.target) {\n targ = e.target;\n } else if (e.srcElement) {\n targ = e.srcElement;\n }\n if (targ.nodeType === 3) {\n // defeat Safari bug\n targ = targ.parentNode;\n }\n\n // pageX,Y are the mouse positions relative to the document\n var boundingRect = targ.getBoundingClientRect();\n var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n\n return { x: x, y: y };\n};\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n var canvas_pos = mpl.findpos(event);\n\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n var x = canvas_pos.x * this.ratio;\n var y = canvas_pos.y * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n guiEvent: simpleKeys(event),\n });\n\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We want\n * to control all of the cursor setting manually through the\n * 'cursor' event from matplotlib */\n event.preventDefault();\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + "text/plain": "
", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX0AAAEICAYAAACzliQjAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAAsTAAALEwEAmpwYAAA+SElEQVR4nO3dd3hUVfrA8e87k94bhEACofdQBZXeFFBERBEUBBvrru66lnVx1RVdUdz157p2EREQBFlRQBQVgSisFAGRjnQIvfeEhJzfH/eCYwwt7U55P89zn5m59T2TyTt3zj33HDHGoJRSKjC4nA5AKaVU2dGkr5RSAUSTvlJKBRBN+kopFUA06SulVADRpK+UUgFEk75Sl0BEhorIuMvcZoKI3FhKIfkMEWkvIllF2C5DRL4vjZgCmSZ9PyIirUXkexE5IiIHReR/InKFvWyQiMy7jH2li4gRkaDSi9h/iUgG0AiYar9OEZFpIrLTfl/TC6wfKiKjROSoiOwWkYcLLHeLyHP29sdE5EcRibOXNRCRr0Rkv4j4zY03xpjlwGER6eF0LP5Ek76fEJEYYDrwGpAAVAKeAXKcjKu4fPhL53fAePPL3Y/5wJdA7/OsPxSoCVQBOgCPiUhXj+XPAFcDVwExwAAg216WC0wC7i7B+L3FeKz3UpUUY4xOfjABzYHD51lWFytBnAGOn10PuA74ETgKbAeGemyzDTD2+sexkk0N4FvgCLAf+Og8x0u3tx0M7AR2AY94LHcBQ4CNwAGshJVQYNu77Ri+O88xrgeWAYeB74EMj2VbgMeB1cAh4H0gzGP5vcAG4CAwDajosaw+MNNetgf4mz1/qB3nWOAYsApofoG/xyagdSHzg+zypReYvwO4xuP1P4CJ9vN4+29Q/SKfgRrWv/RFPyt1PMq4DujjsWw08La9/Jj9967isfxq4Af7M/ADcLXHsgT7vd5pv+9T7PntgSzgEWCv/Xm402O77vbf6pj9PjzqsawScAoIdfp/zF8mxwPQqYT+kNbZ3wFgDNANiC+wfBAwr8C89kBDOwln2EnuRnvZ2eQb5LH+BOAJe/2wwpJagW0nAJH2MfYBne3lfwYWAKlAKPAOMKHAtmPtbcML2X9TO3m0BNzAQKxEH2ov3wKsBNLsRPQ/4Dl7WUesL6ym9rFfw/5iAaLthPSIXb5ooKW9bCjWF2d3+5gvAAvOU/5IuwzlCln2m6SPldQNkOwx72Zghf28LdaX21+B3cDPwP2F7PuiSd+ObTtwpx1LU/v9qG8vH42VfNva789/zn5u7PfyENavjCCgn/060V7+OfCRXZ5goJ3H5ywPeNae3x04if0Ztd/zNh7vRdMCMR/F40tdp2LmCqcD0KkE/5jWGf1orLOqPKyz2GR72SAKJP1Ctn8F+Lf9PJ3fJv2xwAgg9SL7ObttHY95/wTes5+vATp5LEvBqqII8ti22gX2/xbwjwLz1nkkmS3AfR7LugMb7efvAf/0WBZlHzvdTmI/nueYQ4FvPF7XA06dZ91KdhnCCllWWNJPK7g+0AXYYj+/zV7+HhCO9QW9D+hSYN+XkvRvBeYWmPcO8LT9fDT2LwyP9+eMHeMAYFGBbefbn60UrCqs+EKO2R7rbN3zs7QXuNJ+vg2rCifmPDHvANo6/f/lL5PW6fsRY8waY8wgY0wq0ACoiJXICyUiLUVkjojsE5EjwH1A0gUO8RggwCIRWSUid10kpO0ez7fa8YBVb/2piBwWkcNYXwJngOTzbFtQFeCRs9vb+0jz2P+Fjl3Rfg2AMeY41i+kSvY+Nl7guLs9np8Ews5zzeGw/Rh9gX15Om4/xnjMi8E64wYrYQI8a4w5ZawLnBOxvswuVxWgZYH37naggsc65947+/05iPW+/eq9s23ll/fuoDHm0HmOe8AYk+fx+iTWFwpY1zm6A1tF5FsRuarAttH88p6qYtKk76eMMWuxztoanJ1VyGofYv0aSDPGxGLV5cr51jfG7DbG3GuMqYh1ZvamiNS4QBhpHs8rY9X1gpVUuhlj4jymMGPMDs/DXWC/24FhBbaPMMZMuIRj78RKfACISCSQiHU2uR2ofoHjXhJjzAmsL49al7j+IawqjkYesxthXTcAWH521eLGhlXGbwu8d1HGmN97rHPuvRORKKxqnZ0UeO9slfnlvUs426LochhjfjDG9ATKA1Owrp2cPX5FIATrl5wqAZr0/YSI1BGRR0Qk1X6dhlVdscBeZQ+QKiIhHptFY52dZYtIC6xqhLP2Yf1cr+ZxjFvO7h+rLtdgnaGfz1MiEiEi9bHqkD+y578NDBORKvZ+y4lIz8so7rvAffYvFRGRSBG5TkQ8z6zvF5FUEUkA/uZx7A+BO0WksYiEAs8DC40xW7BaP1UQkT/bTSijRaTlZcTl6QugnecMEQnDqicHCLVfnzUWeFJE4kWkDtbF5tEAxpiNwFzgCTuuuljVNNPt/Yq9r5Czx7HLVpjpQC0RGSAiwfZ0hb3Ps7rbzX9DsC4oLzTGbLfLVEtEbhORIBG5Fauaa7oxZhcwA+tEIN7eb9uLvUkiEiIit4tIrDEmF6v+3vMz1R6YbYzx6VZoXsXp+iWdSmbC+ok9Ceus64T9+A52PSlWQvgc66f6fnvezVg/z49hJYPXgXEe+3wWK/kfBq7EqpffgVUdsREYfJ5Y0vl1653dwGMey13Aw1hnb8fsfT1fYNugi5S3K1brkcNYZ8n/BaLtZVv4pfXOYayL2xEe295nH/OgXe5Uj2UNgFlYX2q7gSH2/KEF3psLxmnvZxUgHvNMwcljWSgwCivp7QEeLuTv+6X93m8CfldILJ7Tlgu8d7Xtz8I+rKqt2UBje9lofmm9cxz4DqjqsW1rYAlW650leFzMx/pFMMaO/xDwiT2/PZBVIIYtQGesz+WX9vpH7b+p5z4/B25w+v/Lnyax31ilSox949FmINj8uh63rI6/BbjHGPNNWR+7QBwfApOMMVOcjONyiMhorAT9pBfE0hAYYYwpWMevisFXb3xRyusZY267+FrqfIwxK7DuD1ElSOv0lVIqgGj1jlJKBRA901dKqQDi9XX6SUlJJj09vUjbnjhxgsjIyJINyMsEQhkhMMoZCGWEwCinN5RxyZIl+40x5QrO9/qkn56ezuLFi4u0bWZmJu3bty/ZgLxMIJQRAqOcgVBGCIxyekMZRaTg3dOAVu8opVRA0aSvlFIBRJO+UkoFEK+v01dK+bbc3FyysrLIzrYG+oqNjWXNmjUOR1W6yrKMYWFhpKamEhwcfEnra9JXSpWqrKwsoqOjSU9PR0Q4duwY0dGX2uu0byqrMhpjOHDgAFlZWVStWvWStrlo9Y49WPNeEVnpMS9BRGaKyHr7Md5j2eMiskFE1onItR7zm4nICnvZqyIiBY+llPI/2dnZJCYmov/yJU9ESExMPPcr6lJcSp3+aKweDT0NAWYZY2pi9Ug4xA6gHtAXa5zRrljdrLrtbd7C6nWxpj0V3KdSyk9pwi89l/veXrR6xxjznd1roqeeWN2lgtWVaibW+J09sYZaywE2i8gGoIXd62GMMWa+HeRY4Eas/rdLxYIJL7B2v7D0dEppHcIrbNl6mqWn/X98icspZ2xECOmJEVQvF0WF2DDCgt0X30ipAFHUOv1kYw2agDFml4iUt+dX4pdBO8Aaq7US1hikWYXML5SIDMb6VUBycjKZmZmXFZzkn6Hiug+5j23889tNvHXmcsbn8DUGNm5wOogycGnlLNiTVJALOqQFkZHkpkKki5gQITTIO886jx8/ftmfdV8QGxvLsWPHzr0+c+bMr16Xhbi4OOrXr09eXh61a9fm7bffJiIiosT23717d5577jmaNm0KFF7GN954gzvvvPPccXv37s17771HXFxcsY+fnZ19yZ+dkr6QW9h/k7nA/EIZY0ZgDcBN8+bNTZHubGu7hD0jbuaxvR/xWO/W0PSOy9+HD/CGO//KwuWUc/P+E2w/eJKtB0/yf1+vY+bWXGZutbr1jwxx07hyHG6Xi5ZVE7i7dVWv+SXgr3/LNWvW/OqiphMXcsPDw1m+3Bp18vbbb2f8+PE8/PDDJbZ/t9tNZGTkuXIVVsa3336be+6559z8r7/+usSOHxYWRpMmTS5p3aIm/T0ikmKf5adgjWwP1hm859ikqVgjJ2XZzwvOLz1BIayt8yDJ0W6Y/hDEVYFq7S6+nfJ5VZMiqZpk9XtyY+OKZB06xbaDJzl44jSZ6/ay71gOh09m893P+5iwaBuVE6wzr7BgN3e2SqdF1QRCg7zji0CVvDZt2rB8+XIOHjzIXXfdxaZNm4iIiGDEiBFkZGQwdOhQNm7cyI4dO9i+fTuPPfYY9957L5mZmbz00ktMnz4dgAceeIDmzZszaNCgX+3/97//PQsXLiQnJ4ebb76ZZ555hldffZWdO3fSoUMHkpKSmDNnzrkuZpKSknj55ZcZNWoUAPfccw9//vOf2bJlC926daN169Z8//33VKpUialTpxIeHl6s8hc16U8DBgLD7cepHvM/FJGXgYpYF2wXGWPOiMgxEbkSWAjcAbxWrMgvgXG54ZbR8N61MGkA3P0NlLuksaqVn4gOC6ZuSjB1U2IA6Nei8rllc9fv451vN5GTZw3JumjzQWav3Uu9lBimPtCKYLfeu1jSnvlsFSu2H8LtLrkv1XoVY3i6R/1LWjcvL48ZM2bQtWtXnn76aZo0acKUKVOYPXs2d9xxB8uWLQNg+fLlLFiwgBMnTtCkSROuu+66S45n2LBhBAcHExERQadOnVi+fDl/+tOfePnll5kzZw5JSUm/Wn/JkiW8//77LFy4EGMMLVu2pF27dsTHx7N+/XomTJjAu+++S58+fZg8eTL9+/e/5FgKcylNNicA84HaIpIlIndjJfsuIrIe6GK/xhizCmuc1tVY417eb4w5O8jx74GRwAas8UlL7SLur4TFwm0fgTsEPrwFTuwvk8Mq79emZjnG3dOS/953Nf+972q+H9KRu1pVZfWuo/R4bR7zNx5wOkRVQk6dOkXjxo1p3rw5lStX5u6772bevHkMGDAAgI4dO3LgwAGOHDkCQM+ePQkPDycpKYkOHTqwaNGiSz7WpEmTaNOmDU2aNGHVqlWsXr36guvPmzePXr16ERkZSVRUFDfddBNz584FoGrVqjRu3BiAZs2asWXLlssvfAGX0nqn33kWdTrP+sOAYYXMX4w1WHTZi68C/SbC6Otg/C1wx1QIi3EkFOW9yseE8dT1dYkJD+K12Ru4beQCBretxsNdaml1Twl5ukd9x+r0z57Fn1XYAFJnmz8WbAYpIgQFBZGfn39uXmFt4zdv3sxLL73E7NmzqVy5MoMGDbpoG/oLDWQVGhp67rnb7ebUqVMX3NelCJzfr6nNraqe3cthQl84fdLpiJQXEhH+3LkWi5/oTN8rKvPOt5sYOGoRJ3LKfHx3Vcratm3L+PHjAesielJSEjEx1sng1KlTyc7O5sCBA2RmZnLFFVdQpUoVVq9eTU5ODkeOHGHWrFm/2efRo0eJjIwkNjaWPXv2MGPGLxUa0dHRhbZaatu2LVOmTOHkyZOcOHGCTz/9lDZt2pRSqQOtG4ba3aDXOzD5HvioP/SbAEGhF99OBZz4yBBeuKkh6YkRvDBjLbeOmM/0P5beP6Iqe0OHDuXOO+8kIyODiIgIxowZc25ZixYtuO6669i2bRtPPfUUFStWBKBPnz5kZGRQs2bNQlvLNGrUiCZNmtCiRQtq1KhBq1atzi0bPHgw3bp1IyUlhTlz5pyb37RpUwYNGkSLFi0A60JukyZNSqQqp1DGGK+emjVrZopqzpw5hS9YMsaYp2OMmXCbMXm5Rd6/NzhvGf2Mk+W87d35pspfp5vXZ683p/POlNpx/PVvuXr16l+9Pnr0qEORXJqnn37a/Otf/yrWPsq6jAXfY2OMARabQnJq4FTveGp6B3QdDmunw9T7waOeTqmCXuydQfnoUP711To6/l8mc9fvczokpYossKp3PF35e8g5DnOeg6AQuP4/4ArM70B1YanxESx4vBOTl2bx7GerGfDeIn7XrhqPd6vrdGiqFAwdOtTpEEpV4CZ9gLaPQl42zH0JjIEer2riV4VyuYRbmqdxXUYKz0xbzTvfbsIY+Ft3TfzKtwR20heBjk+CuOC7f1qJ/4bXNPGr84oICeIfNzbg0MnTjPhuE0dP5TKsV0PcLu/sz0epgjS7iUDHJ6DdEFg2Dqb9Uev41QWFBLl4qU8jejauyMQftjNz9R6nQ1LqkmnSP6vD49Dur1bin/mUddav1HnEhAXz0i2NiIsI5u1vN3LwxGmnQ1LqkmjS99T+cWjxO5j/Osz7t9PRKC8X7HYxtEd9lm0/TKvhs1m544jTIanzcLvdNG7c+Nw0fPhwp0NyTGDX6RckYjXlPHUQZj0DEQnQbJDTUSkvdmOTSpSLDuXO93/g1nfmM+PBtlROLLl+2lXJKKwbhuLKy8sjKMj3Uqie6RfkckHPN6FGF6tL5lVTnI5IeblWNZIYf29LTpw+wxtzAmFAG/+Rnp7O008/TdOmTWnYsCFr164F4MSJE9x1111cccUVNGnShKlTrY6ER48ezS233EKPHj245pprOHny5Lm7dG+99VZatmzJ4sWLGTt2LA899NC547z77rsl2n9/cfje11RZCAqBPmPhg15Wlw2hUVCjs9NRKS92RXoC/VpUtvrnT4zwqsFZvMqMIYTv+BHcJZh6KjSEbheurjnby+ZZjz/+OLfeeisASUlJLF26lDfffJOXXnqJkSNHMmzYMDp27MioUaM4fPgwLVq0oHNnKwfMnz+f5cuXk5CQwEsvvUR8fDzLly9n5cqV547Ru3dvWrVqxT//+U+Cg4N5//33eeedd0quzMWgZ/rnExJhdclcrg58NAC2LXQ6IuXlHr2mFklR1p27g95fxJl8bQzgLc5W75ydziZ8gJtuugn4ddfFX3/9NcOHD6dx48a0b9+e7Oxstm3bBkCXLl1ISEgArG6R+/btC0CDBg3IyMgAIDIyko4dOzJ9+nTWrl1Lbm4uDRs2LKviXpCe6V9IeBwM+ARGdbW6ZL7zc+usQqlCJEaFMvOhtoyct4k35mzk25/30rFOstNheZduwznlQNfKF3K2+2K3201entWbqjGGyZMnU7t27V+tu3DhQiIjI8+9Nhdo5XfPPffw/PPPU6dOHe68885SiLxo9Ez/YqLKwx1TrCqeD3rBgY1OR6S8WHxkCH/uXIvy0aG88s36c6NyKd9y7bXX8tprr51L6j/++GOh67Vu3ZpJkyYBsHr1alasWHFuWcuWLdm+fTsffvgh/fqdb1iSsqdJ/1LEVYYBU8Dkw5gb4NBWpyNSXizY7eKxrnVYnnWE+8cXnixU2Tpbp392GjJkyAXXf+qpp8jNzSUjI4MGDRrw1FNPFbreH/7wB/bt20dGRgYvvvgiGRkZxMbGnlvep08fWrVqRXx8fImWpzi0eudSlatlJf4xPazpzi8gNvWim6nA1LtpJf63YT+f/riDj37Yxq1XVL74RqrUnDlT+C8uzz7rmzdvTmZmJmBdAyjswuugQYN+NRB6WFgY48aNIywsjI0bN9KpUyeqVKlCTk4OYNX5e7bi8QZ6pn85UjJgwKdw6pB1xn9st9MRKS8lIrzYO4MmleN46eufyc7Vah5/dPLkSVq3bk2jRo3o1asXb731FiEhIRw+fJhatWoRHh5Op06FjizrGD3Tv1yVmkL/yVb9/pgb4M4ZEJnodFTKC4UEuXj0mtrcPnIhU37cQd8Werbvb6Kjo1m8ePFv5sfFxfHzzz87ENHF6Zl+UaS1sJpzHt4K426CbL39XhXu6uqJNKgUw4i5m8gP4CacF2rloornct9bTfpFld4a+nwAe1bChzrQuiqciDC4bXU27TvBtz8H5ohbYWFhHDhwQBN/KTDGcODAAcLCwi55G63eKY5a18BNI+Dju2HSAOg7wbqbVykPXetXICYsiOnLd9GhTnmnwylzqampZGVlsW+f9aWXnZ19WUnKF5VlGcPCwkhNvfRGJZr0i6tBb2vYxc/+BJ/cA71Hlewt5srnhQS56FwvmW/W7CH3TD7B7sD6gR0cHEzVqlXPvc7MzKRJkyYORlT6vLmMgfXpKy3NBsI1w2D1VB1oXRWqa/0KHDmVy5jvtzgdigpwmvRLytUPWEMvLp8I0x/UxK9+pW2tcgCMW6A39ilnadIvSW3/Yk1Lx8KMx3T0LXVOWLCbJ6+ry5YDJ1meddjpcFQA06Rf0jo8AVf/EX54F75+UhO/OufsRdx7xiwO6Oabylma9EuaCHT5B7QYbA27OPs5pyNSXqJ6uSie6F6XvcdyWL3rqNPhqAClSb80iEDXF6HpQJj7Esx5wemIlJfo1bQSIvD5il1Oh6IClCb90uJywfWvQOPb4dvhkPmi0xEpL5AUFUqnOuV5K3MjB47nOB2OCkDFSvoi8pCIrBKRlSIyQUTCRCRBRGaKyHr7Md5j/cdFZIOIrBORa4sfvpdzueCG16DRbZD5PHz7T6cjUl7gbI+bT01d6XAkKhAVOemLSCXgT0BzY0wDwA30BYYAs4wxNYFZ9mtEpJ69vD7QFXhTRPx/EFGXG3q+Do36wZxhsHCE0xEph3Wpl8wfO9bgixW7+XnPMafDUQGmuNU7QUC4iAQBEcBOoCcwxl4+BrjRft4TmGiMyTHGbAY2AC2KeXzf4HJDzzegdnerKeea6U5HpBx2V6uqRIS4eStTR2JTZUuK0wmSiDwIDANOAV8bY24XkcPGmDiPdQ4ZY+JF5HVggTFmnD3/PWCGMebjQvY7GBgMkJyc3GzixIlFiu/48eNERUUVadvS4DqTQ+NlTxJ5Ygs/NfoHR2PrFHuf3lbG0uKP5Ry9KofM7Xn8/aowqsW6/bKMhQmEcnpDGTt06LDEGNP8NwuMMUWagHhgNlAOCAamAP2BwwXWO2Q/vgH095j/HtD7Ysdp1qyZKao5c+YUedtSc3yfMa80MmZ4FWP2ri327ryyjKXAH8u5ZtcRU/epGabHa3ONMf5ZxsIEQjm9oYzAYlNITi1O9U5nYLMxZp8xJhf4BLga2CMiKQD24157/SwgzWP7VKzqoMASmQQDPgFXMIy9UcfbDWB1KsTwYKeaLM86wq4jp5wORwWI4iT9bcCVIhIhIgJ0AtYA04CB9joDgan282lAXxEJFZGqQE1gUTGO77sSqlmJP/cEjO2pwy4GsE51kwlyidbtqzJT5KRvjFkIfAwsBVbY+xoBDAe6iMh6oIv9GmPMKmASsBr4ErjfGBO4A4dWaAi3T4bje60z/hMHnI5IOaBG+Siuz0hh7PytbD0auP8OquwUq/WOMeZpY0wdY0wDY8wAY7XMOWCM6WSMqWk/HvRYf5gxproxprYxZkbxw/dxaVdAvwlwcJM97KLemh+I+rWoTESImzeX5ZB7RntnVaVL78h1WrV20GeMPezirTrsYgBqWS2RV/s2Yc9JwxtzNjgdjvJzmvS9Qe1u0Osd2DYfPuoPeXp7fqDpVLc8DZLcjJy7mZw8reZRpUeTvrdoeDP0+A9snAWT74EzeU5HpMqQiNC5chDHc/JYuOngxTdQqog06XuTZgPh2hdgzTSY9kcdfSvA1Et0Ex7sZubqPU6HovyYJn1vc9UfoP3f4KcP4cu/6iAsASTELbSpmcTM1XvI0wu6qpRo0vdG7R6Dqx6ARSNg9j+cjkaVoZubpbL7aLb2t69KjSZ9byQC1zxnD8LyfzD3ZacjUmWkc91kkmNCmbVm78VXVqoIgpwOQJ2HCFz/bzh9AmY9A6HR0OJep6NSpczlEhqlxung6arU6Jm+N3O5odfbUKsbfPEoLJvgdESqDDRKi2PLgZMcOZnrdCjKD2nS93buYLhlNFRtB1P/ACs/cToiVcpqJUcDsHH/cYcjUf5Ik74vCA6zumtIa2m14ddBWPxa1aRIAJZuPeRwJMofadL3FSGRcNskqNgE/jsIfv7K6YhUKaleLpL6FWP4eEnW2bEnlCoxmvR9SVgM9J8MyfWt7ho2zHI6IlUKRIR+LSqzdvcxvVFLlThN+r4mPA4GfApJtWHibcQdWu50RKoU9L0ijSqJETzz2WoOnjjtdDjKj2jS90URCXDHFIivSsMVz8HW752OSJWwILeLF3tnsOPwKT5ZmuV0OMqPaNL3VZFJcMdUckITYfwtsP0HpyNSJezKaomUjw5lxY4jToei/IgmfV8Wncyyxs9BZDkY1xt2/uh0RKqEtatVjq9W7dbBVVSJ0aTv406HJsLAzyA81hp2cecyp0NSJejqGolk5+azcZ+22VclQ5O+P4hLsxJ/aDSMvQGyljgdkSohV6Qn4BL4cOE2p0NRfkKTvr+IT4c7v4DweBjbE7YtcDoiVQJS4yPo0zyNsfO3MkN73lQlQJO+P4mrDIO+gOhk+OAm2DzX6YhUCXi2ZwPqVIjmjUwdP1cVnyZ9fxNbCQZ9blX5fNgHtsxzOiJVTCFBLno0qsjKHUfZezTb6XCUj9Ok74+iK1h1/LFpVnPOLf9zOiJVTNfWTwZgzPwtzgaifJ4mfX8VVd5O/KlW4tc6fp9Wo3w0PRtX5L15m9l24KTT4Sgfpknfn0UnW4k/ugKMu1lb9fi4v3atg0uEv05erh2xqSLTpO/vzlb1RCbCB720Hb8PqxgXzn3tqjN/0wFW7jjqdDjKR2nSDwSxlazEHxZrNefcoWf8vqr/lVWIDQ/mlW9+djoU5aM06QeKuMowyE78Y26ATd86HZEqgoTIEPo0T2Xu+v1k555xOhzlgzTpB5L4dLjrK+sLYPzNOgKXj+pQuzynz+QzY6XerKUunyb9QBOTYrXjT2kEkwbomLs+6KrqicSGB7Nw00GnQ1E+qFhJX0TiRORjEVkrImtE5CoRSRCRmSKy3n6M91j/cRHZICLrROTa4oeviiQiAQZMgbQr4ZN74eevnY5IXQYR4Yr0BOas28up01rFoy5Pcc/0/wN8aYypAzQC1gBDgFnGmJrALPs1IlIP6AvUB7oCb4qIu5jHV0UVGgW3TbSGXpw0QG/g8jF3tU5nz9EcXpmlF3TV5Sly0heRGKAt8B6AMea0MeYw0BMYY682BrjRft4TmGiMyTHGbAY2AC2KenxVAsJiof8nVh3/hL7ajt+HXF09iZ6NKzJu/laOnMp1OhzlQ6SoN3mISGNgBLAa6yx/CfAgsMMYE+ex3iFjTLyIvA4sMMaMs+e/B8wwxnxcyL4HA4MBkpOTm02cOLFIMR4/fpyoqKgibesrSqKModn7abzsCYJzj/FTo6Eci6lVQtGVHP1b/tbWo2d4+vts+tQKpnu1kFKMrGTp37JsdOjQYYkxpvlvFhhjijQBzYE8oKX9+j/AP4DDBdY7ZD++AfT3mP8e0Ptix2nWrJkpqjlz5hR5W19RYmU8tM2YVzKMeT7NmKzFJbPPEqR/y8Ld9u5802LYTJObd6bkAyol+rcsG8BiU0hOLU6dfhaQZYxZaL/+GGgK7BGRFAD7ca/H+mke26cCO4txfFWS4tJg4HQIj4OxvfQGLh9xx1VW3f7c9fudDkX5iCInfWPMbmC7iNS2Z3XCquqZBgy05w0EptrPpwF9RSRURKoCNYFFRT2+KgVxaVZzTk38PqND7fLERQTzyY87nA5F+Yjitt75IzBeRJYDjYHngeFAFxFZD3SxX2OMWQVMwvpi+BK43xij7c28zW8S/1KnI1IXEBLk4vqMFL5etZtj2XpBV11csZK+MWaZMaa5MSbDGHOjMeaQMeaAMaaTMaam/XjQY/1hxpjqxpjaxpgZxQ9flYq4NBg03Rps/YMbYeePTkekLqBXk1Ry8vJ5d+5mp0NRPkDvyFWFi6tsnfGHxsLYG2HXcqcjUufRtHIcHeuUZ8R3G/VmLXVRmvTV+Z3tpC002uqdc/dKpyNShRARBlxVhezcfBZt0a4Z1IVp0lcXFp8OA6dBcDiMvQH2rHY6IlWIq6olEhcRzIcLtzodivJymvTVxSVUs/rjd4fAmB6wd63TEakCwoLd3NaiMjNX72HH4VNOh6O8mCZ9dWkSq1uJ3+W2Ev8+7fPF2/S9ojL5Bj5frre/qPPTpK8uXVJNK/FjrMS/f4PTESkPlRMjaFgpls9X7HY6FOXFNOmry1OutpX48/NgzPVwYKPTESkPPRql8NP2w6zZpWPoqsJp0leXr3xd6+JuXo51xn9Q24d7iz7N03C7hE+WZjkdivJSmvRV0STXtxJ/7kkr8R/e5nRECoiLCKFTnfJMXLSd/Pyi9aCr/JsmfVV0FRpaI3DlHLUGWz+qY7Z6gy71kjmWk8firYecDkV5IU36qngqNobbJ8OJfVY7/uP7nI4o4F3boALxEcF8sEDb7Kvf0qSvii/tCrhtEhzebvXVc1LvCnVSTFgwneomk7lur1bxqN/QpK9KRnor6Pch7P8Zxt0E2UecjiigdaxTnmPZeTw5VbvOUL+mSV+VnOodoc9Y2L0CxveBnONORxSwutavQN2UGD5duoO9x7KdDkd5EU36qmTV7ga934OsRTCxH+RqlwBOcLmE4Tc15FTuGRZs0uo29QtN+qrk1b8RbnwbNs+FjwZY7flVmatXMYbQIBcLNx1wOhTlRTTpq9LR6Fbo8QpsmAkf3wVndFSnshbsdlG/YgzjF27j4InTToejvIQmfVV6mg2Cri/C2unw6e8gXwf4KGv3tqkGwDdr9jgcifIWmvRV6bryPug8FFZOhml/hPx8pyMKKF0bVCAhMoRFm7VeX1mCnA5ABYDWD0FuNnw73BqMpftLIOJ0VAFBRLgiPZ7vN+znTL7B7dL3PdDpmb4qG+2HwNV/gh9GwtdPgtGbhsrK9RkV2Xkkm+837nc6FOUF9ExflQ0R6PIs5GXD/NchOAI6PuF0VAGhS71kwoJdfLN6D21qlnM6HOUwPdNXZUfEurDb9A747p8w9/+cjigghAW7aVOzHDNX78HoL6yAp0lflS2XC65/BRr2gVnPwvw3nY4oIHSpm8zOI9l6QVdp0lcOcLnhxreg7g3w1eOweJTTEfm9LvWSiQ4L4rXZOsRloNOkr5zhDrK6a6h5LUx/CJZ96HREfi0+MoSBV6Xz/cb9HDiud0gHMk36yjlBIVYHbdXaw9T7rbb8qtR0b5hCvoEpy3Y6HYpykCZ95azgMOj7IaRdCZ8MhrWfOx2R36qbEk3zKvGMnLtJ+9kPYJr0lfNCIuG2jyClMfx3EGz4xumI/JKI0P/KKuw6ks2P23UoxUClSV95h7AY6P8xlKsNE2+3euhUJa5T3fKEBLmYvlzHMw5UmvSV9wiPtwZaj0+HD2+FbQudjsjvRIcF07ZmOb5cuVvb7AeoYid9EXGLyI8iMt1+nSAiM0Vkvf0Y77Hu4yKyQUTWici1xT228kORSXDHVIiuAONvhh1LnY7I73RrUIFdR7KZqhd0A1JJnOk/CKzxeD0EmGWMqQnMsl8jIvWAvkB9oCvwpoi4S+D4yt9EV4CB0yA8zhpoPWuJ0xH5lRsaV6RcdCjTl2vSD0TFSvoikgpcB4z0mN0TGGM/HwPc6DF/ojEmxxizGdgAtCjO8ZUfi02FQZ9bVT5jexJ7eJXTEfmNYLeLHhkVyVy3j6xDJ50OR5Wx4na49grwGBDtMS/ZGLMLwBizS0TK2/MrAQs81suy5/2GiAwGBgMkJyeTmZlZpOCOHz9e5G19hb+XMaTO32n009/JWD6Un/JzOZTQ2OmQSk1Z/i3rB+VjjOHZiXO5rW5omRzzLH//zIJ3l7HISV9Ergf2GmOWiEj7S9mkkHmFXkkyxowARgA0b97ctG9/Kbv/rczMTIq6ra8IhDLSqi3H3+pMo9UvWG36a3RyOqJSUdZ/y093LGT7idO0b9+mzI4JgfGZ9eYyFqd6pxVwg4hsASYCHUVkHLBHRFIA7Me99vpZQJrH9qmAViqqi4sqx0+N/gGJNWHibbBhltMR+YXGaXGs2XWUVTuPOB2KKkNFTvrGmMeNManGmHSsC7SzjTH9gWnAQHu1gcBU+/k0oK+IhIpIVaAmsKjIkauAkhsSY7Xq0cRfYvq1rAzA/I0HHI5ElaXSaKc/HOgiIuuBLvZrjDGrgEnAauBL4H5jjI6UrS5dZOIviX9CP038xVQpLpzq5SKZsmyHdssQQEok6RtjMo0x19vPDxhjOhljatqPBz3WG2aMqW6MqW2MmVESx1YBJjLRas6ZVEsTfwm4s1VVVu44ykqt4gkYekeu8j0RCZr4S8g19ZMBreIJJJr0lW/SxF8iykeHUaN8FN9r0g8YmvSV7yqY+Ndr75xF0ap6Ios2H+Tk6TynQ1FlQJO+8m1nE3+52jCxH6z70umIfE63himcyj3DzNV7nA5FlQFN+sr3nU38yfXho/6w5jOnI/IpLdITqBQXzuSlO5wORZUBTfrKP4THW805KzaGSQN16MXL4HIJNzapyLz1+9h7NNvpcFQp06Sv/EdYLAz4FNJawuR7YNkEpyPyGTc1TSXfwKj/bXE6FFXKNOkr/xIabY3Ald4GptwHi951OiKfUL1cFO1rl+PLlbt0cBU/p0lf+Z+QSLhtEtTqBl88CnNfdjoin9ClXjJbDpxkeZbeqOXPNOkr/xQcBrd+AA1uhlnPwDfPgJ7BXlC7WuUAWLXzqMORqNKkSV/5L3cw3DQCmg2CeS/Dl0M08V9AxdhwokODWLrtkNOhqFKkSV/5N5cbrn8FrvwDLHwbPnsQ8vOdjsoruVxCj8YV+fTHHWw/qCNq+StN+sr/icC1z0ObR2DpGOsC7xm9+7Qw93eoQb4xfLwky+lQVCnRpK8Cgwh0+jt0eBKWfwRTfg/52rN3QZXiwmldI4mPl2Rpd8t+SpO+Cizt/gIdn4IVk2DKHzTxF6JP8zR2HD7Fd+v3OR2KKgXFHRhdKd/T9lHrgu6c50Bc0PN1q+5fAVbTzaSoEJ77fA3tapVDpLDhrZWv0jN9FZja/QXa/w1++hCm/Ukv7noIC3bzyDW12bD3OD9uP+x0OKqEadJXgav9X6HdEFg2Dj7TxO/p+owUQoNcTPlRO2HzN5r0VWBrPwTaPgY/fgDTtTnnWdFhwbSpmcS0n3Zy8MRpp8NRJUiTvgpsItDhb9DmUVg6Fj5/SBO/rVeTVA6fzOXesYu1Px4/oklfKRHo+CS0fhiWjIYvHtE7d4HrMlJ4tmd9lmw9xH+13b7f0KSvFPzSjr/Vn2HxKKujNk38DLiyCg0rxfJ25kbOaLt9v6BJX6mzRKDzULj6T/DDSJjx14BP/CLC79pVY9P+Ezqcop/QdvpKeRKBLs+CyYf5r1vt+Lu+YM0PUF3rV6ByQgRvf7uRa+sna7t9H6dn+koVJALXPAdX3g8L34Kv/hbQZ/xBbhf3tq3Gsu2HWbT5oNPhqGLSpK9UYUTg2mHQ8vew4E34+smATvy3NEslIsTNp9pu3+dp9Y5S5yNiVe2creoB6PIPcAXeuVJYsJsbGlVk4g/b6VinPNfUr+B0SKqIAu/Tq9TlEIFuL0LL+6zE/+lgyMtxOipHPNylFvERwTwxZaX2wOnDNOkrdTEi0HU4dHoaVvwXxvWGU4edjqrMlY8J45meDdh3LIcHJixl37HA/PLzdZr0lboUItDmYeg1ArYtgPe7wZHAu2HpuoYp9GuRxhcrdtPl39/qCFs+qMhJX0TSRGSOiKwRkVUi8qA9P0FEZorIevsx3mObx0Vkg4isE5FrS6IASpWpRrdC/4/h8HYY2QX2rHI6ojLldgkv3JTB5N9fxanTZ7j57e9ZtfOI02Gpy1CcM/084BFjTF3gSuB+EakHDAFmGWNqArPs19jL+gL1ga7AmyKinZgr31OtPdw1AzAwqits/d7piMpcsyoJfPS7q9h//DR3jf5B++bxIUVO+saYXcaYpfbzY8AaoBLQExhjrzYGuNF+3hOYaIzJMcZsBjYALYp6fKUcVaEh3D0TopLhg5vg56+djqjMNU6Lo3/Lyuw5mkPWoVNOh6MukZTEN7SIpAPfAQ2AbcaYOI9lh4wx8SLyOrDAGDPOnv8eMMMY83Eh+xsMDAZITk5uNnHixCLFdfz4caKiooq0ra8IhDKC95Yz+PRhMpY/Q+SJrayp+xD7yrcp8r68tYwXknUsnyf/d4r+dUPoXCX4krbxxXJeLm8oY4cOHZYYY5r/ZoExplgTEAUsAW6yXx8usPyQ/fgG0N9j/ntA74vtv1mzZqao5syZU+RtfUUglNEYLy/nqcPGjOpmzNOxxiwaWeTdeHUZz+PMmXxz/atzTc0nvjBZh05e0ja+WM7L5Q1lBBabQnJqsVrviEgwMBkYb4z5xJ69R0RS7OUpwF57fhaQ5rF5KrCzOMdXyiuExUL/yVDzGvj8Yfj2XwFz967LJbx5e1Py8w3vz9vsdDjqEhSn9Y5gna2vMca87LFoGjDQfj4QmOoxv6+IhIpIVaAmsKiox1fKqwSHQ9/xkHGrNeD6l48HzGAsaQkRtK1Vjo9+2M6Ow1q37+2Kc6bfChgAdBSRZfbUHRgOdBGR9UAX+zXGmFXAJGA18CVwvzHmTLGiV8qbuIPhxret/noWvgVT7oMzuU5HVSb+cm1tDDBk8nKnQ1EXUeS+d4wx84Dz9bHa6TzbDAOGFfWYSnk9l90Vc2QizH7OunP3ltEQEuF0ZKWqbkoMf+pUg+e/WMsXK3bRvWGK0yGp89A7cpUqaSLQ9i9w/b9h/dfwQS84dcjpqEpd/yur0KBSDEMmL+d4Tp7T4ajz0KSvVGlpfhfc8j7sWGLdxHV4u9MRlaqIkCD+0bMBR7PzmLhom9PhqPPQpK9Uaarfy2rZc3QnjOwMu/y7zrtJ5XhaVE1g1LzN5J4JjAvZvkaTvlKlrVo7uOsrcLmtjtrWfu50RKXqd22rsfNINtOXa4tsb6RJX6mykFwP7vkGkmrCxNvgqyf8tmVPh9rlqZYUycRF/l2d5as06StVVmIqWmf8LQZbA7KM6QHHdjsdVYlzuYTrM1JYuPkg89bvdzocVYAmfaXKUlAodP8X9H4Pdv0E77Sz+uf3M7c0T0MEnvt8tY6y5WU06SvlhIY3W9U9IREw+jpY+I5fdd2QlhDBK7c2Zu3uY3ymdfteRZO+Uk5Jrg/3zoEanWHGY9RZ+yrkZjsdVYnpkVGROhWieXDiMsYv3Kp97nsJTfpKOSk8DvpOgHZDqLBnNozubjXv9AMul/DSLY1oWCmWJz5dyVNTVzodkkKTvlLOc7mgw+OsrP847FvnV/X8DSrF8t/7rqJfi8qMW7CNkXM3OR1SwNOkr5SX2F/uSquePzQKRl8P89/wi546w4LdPNuzPp3rlue5z9cwY7N/NlX1FZr0lfIm5evCvbOtvvm/+ht80BMObXE6qmILdrt4u38zrq2fzKR1p5m1Zo/TIQUsTfpKeZvweKtv/h6vQtYSeKMlZA6HXN/uqz7I7eLlPo1JDBf+9dU6zmhTTkdo0lfKG4lAs4HwwA9QuztkvmAl/7Vf+HTTzsjQIG6oHsza3cd4bfZ6cvJ0SI2ypklfKW8WW8nqqfOOadboXBP7wYd94MBGpyMrsisqBFEpLpxXvllPvb9/xauz1mtzzjKkSV8pX1CtHdw3D64ZBlvnw5tXwqxnIfuI05FdtvAgIfMv7fnXzRlUjAvj5Zk/M+zzNazeedTp0AKCJn2lfIU7GK5+AP642Oqyee7/wX8awf/+A6dPOh3dZQl2u7ileRrf/aUD/VpUZuS8zXR/dS4vfrmWvcf85wY1b6RJXylfE10BbhoBg7+FSs1h5t/h1caw6F2f67lTRHjhpoZ88ac2dK6bzFuZG2k9fA6f/pjldGh+S5O+Ur6qYmPo/zHc+SUkVIcvHoU3r/LJi731Ksbwdv+mjLu7JdXKRfLof5fz38XaNXNp0KSvlK+rchXc+QX0m2i9ntgPRrSDlZPhjO+MVRvkdtG6ZhKT7ruKJmlx/OXj5dw/filLth50OjS/oklfKX8gArW7wR/mW+37T5+Aj++C15vBDyN9qo1/TFgwb97elJ6NK/L5il30fms+g95fxNz1+5wOzS9o0lfKn7iDrfb99y+CPh9ARCJ8/gi80hC++xecOuR0hJekfEwY/+nbhHl/7cB97aqzcNNB7hi1iNH/28yP23yjDN5Kk75S/sjlhno3wD2zYNDnkNIYZj8HL9eH6Q9Zd/r6QL1/anwEQ7rV4YcnO1OjXBRDP1tNrze/Z/zCrU6H5rOCnA5AKVWKRCC9tTXtXml14rZsAiweBeXqQpPbof5N1k1gXiwqNIjP/tiaDXuP8/wXa3hyykrS4iNoW6uc06H5HD3TVypQVGgAvd6CR9dBj/9AaDR8/ST8ux6M7GJ9IRzx3qaSYcFuGlSKZcQdzalRLoo7Ri3i7tE/6Di8l0mTvlKBJiwWmg2Ce2bCH5dCx6cg75TVq+e/63v9F0BUaBDvDbyCvlekMWvtXu4YtZDHP1nBgeM5TofmE7R6R6lAllgd2j5qTQc2wuopsGqK9QXw1d+gQgZUbQvpbaymoWGxTkcMQOXECIb3zuBv19Xl/vFLmbBoG1+u3EXD1Di61EsmISKEauUiqZoUSViw2+lwvYomfaWUJbE6tHnEmg5shNVTYeNs607f+a+DuKwLwumtrS+BSs0gMtHRkGPCgvng7pas232Mf8/8mcVbD/Hdz7807SwfHcqwXg1pXSOJ8BBN/qBJXylVmMTq0OZha8rNhqwfYMtc2DwXFrwF379qrRdX2Ur+FZtajymNrJG/yljtCtG8PaAZOXln2HMkh6zDJ9mw9zjPfb6Ge8cuJj4imG4NU3AJZKTG0bZmOSrEhpV5nN5Ak75S6sKCw6BqG2vqgNW5244lsHMp7FhqNf9c9am1rrggqTYk14PEGtYUmwYxFSE6BYJCSjXU0CA3lRMjqJwYwdXVk7iuYQpfrtrNyLmb+XrVbo7n5DFuwTYAqpeLpGJcOKnx4cRHhNCmZjkiQtykJ0YSGxFcqnE6qcyTvoh0Bf4DuIGRxpjhZR2DUqoYQiJ++RI46/i+X74Edi61vhRWfQrGc4xfgajyNJVo2F3H6jguIgkiEqybyM492lNweLFDTYwK5faWVbi9ZRUA8s7ks2jzQb5bv5+1u4+yad8J1uw6xv7jObyZ+csYBXVTYhAgNjyYtIRw6leMpWGqdT0jOSaM8tGh59Z1ieB2SbFjLStlmvRFxA28AXQBsoAfRGSaMWZ1WcahlCphUeWg1rXWdFZejjW+75EsOLoTju6AozvI27zCumawZR5kHz7/PoPCf/1lEBYDwZHWl0FIBAR7TCER1vxg+9EdAq4gELd1o5orCFxBBLmCuDrBxdVXRYAr5tz8nUdPs37fSfadyOe7jQc5lWvIN8LmAydZu/sokxafvyVTiNtFelIELhGiQoOonBjBnt05fLb3J4LdQvVyUQS7f/lSiI8MoVJcOAmRIVSMO/8XW2iQC5GS/zIp6zP9FsAGY8wmABGZCPQENOkr5W+CQqFcbWvysDwzk/bt21svzuRZif/kAXs66PH8gNVtxMkDcGK/9cWRewpyT9iPJTeGQEV7Ari5wDKDQLgLI1YL93xcGAQj1mO+EfKPCQYhzwj5eyAf4KCLfGM9Nwjm3KOVyI0R9nrMK7g8OMhN6APfk5xQsi2mpCyHKRORm4Guxph77NcDgJbGmAcKrDcYGAyQnJzcbOLEiUU63vHjx4mKKvuLSmUpEMoIgVHOQCgjlGA5TT6u/FzcZ7Jx5efgPpON+0wOrvwcxJyxp/wCj57zC5tnPXI2BdvPxXi+Bsi35+V7LPvldW5uLsHBQRhj7AHg7TxrDKfyDPn5hlN5+eeGifwl5QMYjDGcyjXkXPkorqCiXV/o0KHDEmNM84Lzy/pMv7DfKr/51jHGjABGADRv3tycOyu4TJmeZxR+KhDKCIFRzkAoIwRGOb25jGV9R24WkObxOhXYWcYxKKVUwCrrpP8DUFNEqopICNAXmFbGMSilVMAq0+odY0yeiDwAfIXVZHOUMWZVWcaglFKBrMzb6RtjvgC+KOvjKqWU0l42lVIqoGjSV0qpAKJJXymlAogmfaWUCiBlekduUYjIPqCooyAnAf4+lloglBECo5yBUEYIjHJ6QxmrGGN+M4iw1yf94hCRxYXdhuxPAqGMEBjlDIQyQmCU05vLqNU7SikVQDTpK6VUAPH3pD/C6QDKQCCUEQKjnIFQRgiMcnptGf26Tl8ppdSv+fuZvlJKKQ+a9JVSKoD4ZdIXka4isk5ENojIEKfjKQ4RSROROSKyRkRWiciD9vwEEZkpIuvtx3iPbR63y75ORK49/969i4i4ReRHEZluv/arMopInIh8LCJr7b/nVf5WRgARecj+rK4UkQkiEubr5RSRUSKyV0RWesy77DKJSDMRWWEve1VKYxDcizHG+NWE1WXzRqAaEAL8BNRzOq5ilCcFaGo/jwZ+BuoB/wSG2POHAC/az+vZZQ4FqtrvhdvpclxiWR8GPgSm26/9qozAGOAe+3kIEOeHZawEbAbC7deTgEG+Xk6gLdAUWOkx77LLBCwCrsIaRXAG0K2sy+KPZ/rnBl83xpwGzg6+7pOMMbuMMUvt58eANVj/WD2xkgj24432857ARGNMjjFmM7AB6z3xaiKSClwHjPSY7TdlFJEYrMTxHoAx5rQx5jB+VEYPQUC4iAQBEVij4/l0OY0x3wEHC8y+rDKJSAoQY4yZb6xvgLEe25QZf0z6lYDtHq+z7Hk+T0TSgSbAQiDZGLMLrC8GoLy9mq+W/xXgMSDfY54/lbEasA94367CGikikfhXGTHG7ABeArYBu4Ajxpiv8bNy2i63TJXs5wXnlyl/TPqXNPi6rxGRKGAy8GdjzNELrVrIPK8uv4hcD+w1xiy51E0KmefVZcQ6+20KvGWMaQKcwKoSOB9fLCN2vXZPrGqNikCkiPS/0CaFzPP6cl7E+crkFWX1x6Tvd4Ovi0gwVsIfb4z5xJ69x/65iP24157vi+VvBdwgIluwquM6isg4/KuMWUCWMWah/fpjrC8BfyojQGdgszFmnzEmF/gEuBr/Kydcfpmy7OcF55cpf0z6fjX4un11/z1gjTHmZY9F04CB9vOBwFSP+X1FJFREqgI1sS4eeS1jzOPGmFRjTDrW32u2MaY//lXG3cB2Ealtz+oErMaPymjbBlwpIhH2Z7cT1nUofysnXGaZ7CqgYyJypf3e3OGxTdlx+qp4aUxAd6xWLhuBJ5yOp5hlaY31E3A5sMyeugOJwCxgvf2Y4LHNE3bZ1+FA64Bilrc9v7Te8asyAo2BxfbfcgoQ729ltON+BlgLrAQ+wGrF4tPlBCZgXaPIxTpjv7soZQKa2+/LRuB17F4RynLSbhiUUiqA+GP1jlJKqfPQpK+UUgFEk75SSgUQTfpKKRVANOkrpVQA0aSvlFIBRJO+UkoFkP8HswYIFrzIoR4AAAAASUVORK5CYII=\n" }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": "", - "text/html": "
" + "metadata": { + "needs_background": "light" }, - "metadata": {}, "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/opt/homebrew/Caskroom/miniforge/base/envs/evolving/lib/python3.8/site-packages/matplotlib/animation.py:887: UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. `anim`, that exists until you have outputted the Animation using `plt.show()` or `anim.save()`.\n", - " warnings.warn(\n" - ] } ], "source": [ - "# Hyper-parameters\n", - "ENERGY_LOSS_GENERAL = 0.1\n", - "ENERGY_LOSS_ACTIONS = 0.2\n", - "\n", - "FOOD_TO_ENERGY = 0.1\n", - "WATER_TO_ENERGY = 0.1\n", - "\n", - "# Rotation vectors for movement\n", - "theta = np.deg2rad(45)\n", - "rot_right = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]])\n", - "rot_left = np.array([[cos(-theta), -sin(-theta)], [sin(-theta), cos(-theta)]])\n", - "\n", - "class Being:\n", - " \"\"\"\n", - " Living being representation. If energy reaches 0 it will be dead\n", - "\n", - " Energy goes down when performing actions, low energy leads to lower happiness.\n", - " \"\"\"\n", - " # Subjective states (things the \"brain\" feels)\n", - " happiness = 1\n", - " hunger = 0\n", - " thirst = 0\n", - "\n", - " # Objective states (hidden from the \"brain\")\n", - " food = 1\n", - " water = 1\n", - " energy = 1\n", - "\n", - " direction = [1, 0] # vector from (0,0) (the being) to the direction its facing\n", - " action_space = ['NOOP', 'TURN_LEFT', 'TURN_RIGHT', 'MOVE', 'EAT', 'DRINK']\n", - "\n", - " def choose_action(self):\n", - " action = random.choice(self.action_space)\n", - "\n", - " if action != 'NOOP':\n", - " self.energy -= ENERGY_LOSS_ACTIONS\n", - "\n", - " if action == 'TURN_LEFT' or action == 'TURN_RIGHT':\n", - " rot = rot_left if action == 'TURN_LEFT' else rot_right\n", - " self.direction = np.round(np.dot(rot, self.direction), 0).astype(int)\n", - "\n", - " return action\n", - "\n", - " def step(self):\n", - " self.energy -= ENERGY_LOSS_GENERAL\n", - "\n", - " if self.food > 0:\n", - " self.food -= FOOD_TO_ENERGY\n", - " self.energy += FOOD_TO_ENERGY\n", - "\n", - " if self.water > 0:\n", - " self.water -= WATER_TO_ENERGY\n", - " self.energy += WATER_TO_ENERGY\n", - "\n", - " def color(self):\n", - " return 155 + self.energy * 100\n", - "\n", - "class Cell:\n", - " def __init__(self, x, y):\n", - " self.x = x\n", - " self.y = y\n", - " self.type = 'NONE'\n", - " self.content = None\n", - "\n", - " def update(self, type, content=None):\n", - " self.type = type\n", - " self.content = content\n", - "\n", - " def color(self):\n", - " if self.type == 'NONE':\n", - " return 0\n", - "\n", - " if self.type == 'BEING':\n", - " return self.content.color()\n", - "\n", - "class World:\n", - " def __init__(self, w=128, h=128):\n", - " self.w = w\n", - " self.h = h\n", - " self.state = np.empty((w, h), dtype=object)\n", - " for i in range(w):\n", - " for j in range(h):\n", - " self.state[i, j] = Cell(i, j)\n", - "\n", - " self.im = plt.imshow(np.zeros((self.w, self.h)), cmap='gray', vmin=0, vmax=255, animated=True)\n", - "\n", - " def step(self):\n", - " state = np.copy(self.state)\n", - "\n", - " for i, row in enumerate(self.state):\n", - " for j, cell in enumerate(row):\n", - " if cell.type != 'BEING':\n", - " continue\n", - "\n", - " cell.content.step()\n", - " action = cell.content.choose_action()\n", - "\n", - " if action == 'MOVE':\n", - " # lets see if the desired cell is empty\n", - " direction = cell.content.direction\n", - " next_loc = [\n", - " max(0, min(self.w - 1, cell.x + direction[0])),\n", - " max(0, min(self.h - 1, cell.y + direction[1]))\n", - " ]\n", - "\n", - " if state[next_loc[0], next_loc[1]].type == 'NONE':\n", - " # cell is empty, lets move!\n", - " state[next_loc[0], next_loc[1]].update('BEING', cell.content)\n", - " state[i, j].update('NONE')\n", - "\n", - " self.state = state\n", - "\n", - " def spawn(self, number):\n", - " for _ in range(number):\n", - " x = random.randint(0, self.w - 1)\n", - " y = random.randint(0, self.h - 1)\n", - "\n", - " while self.state[x, y].type != 'NONE':\n", - " x = random.randint(0, self.w - 1)\n", - " y = random.randint(0, self.h - 1)\n", - " # TODO: this could be infinite\n", + "from src.world import World\n", "\n", - " being = Being()\n", - " self.state[x, y].update('BEING', being)\n", + "world = World(WORLD_SIZE, WORLD_SIZE)\n", + "for _ in range(INITIAL_POPULATION):\n", + " location, _ = world.spawn(0)\n", "\n", - " def render(self):\n", - " state = np.zeros((self.w, self.h))\n", - " for i, row in enumerate(self.state):\n", - " for j, cell in enumerate(row):\n", - " state[i, j] = cell.color()\n", + "population_size = []\n", + "avg_energy = []\n", "\n", - " self.im.set_array(state)\n", - " return [self.im]\n", - "\n", - "fig = plt.figure(figsize=(10, 10))\n", - "plt.xticks([])\n", - "plt.yticks([])\n", - "\n", - "world = World()\n", - "world.spawn(10)\n", - "world.render()\n", - "\n", - "def animate():\n", + "while len(world.locations) > 0:\n", " world.step()\n", - " return world.render()\n", "\n", - "fps = 30\n", + " population_size.append(world.alive)\n", + "\n", + " total_energy = 0\n", + " for being in world.locations.values():\n", + " total_energy += being.energy\n", + "\n", + " avg_energy.append(total_energy)\n", "\n", - "animation.FuncAnimation(fig, animate, frames=100, interval=1000/fps)\n", + "plt.title(f'Stats per epoch ({len(population_size)} epochs)')\n", + "plt.plot(population_size, label='Population')\n", + "plt.plot(avg_energy, label='Energy')\n", + "plt.grid()\n", + "plt.legend()\n", "plt.show()" ], "metadata": { @@ -211,117 +90,133 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 64, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[ 0 -1]\n", - "[-1 -1]\n", - "[-1 0]\n", - "[-1 1]\n", - "[0 1]\n", - "[1 1]\n", - "[1 0]\n", - "[ 1 -1]\n", - "[ 0 -1]\n", - "[-1 -1]\n" + "vision_pixel_size 9\n", + "angles 247.5 292.5\n", + "(1, 0.5) 27\n", + "(-0.8, -0.5) 212\n", + "(-0.1, -0.6) 261\n", + " chunk: 1 0.6082762530298219\n", + "(-0.67, 0.2) 163\n", + "\n", + "Vision array (shows a 1 when there is a being within viewing distance in that direction)\n", + "[0, 1, 0, 0, 0]\n" ] + }, + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" } ], "source": [ - "from math import cos, sin\n", + "import math\n", "\n", - "theta = np.deg2rad(45)\n", - "rot_right = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]])\n", - "rot_left = np.array([[cos(-theta), -sin(-theta)], [sin(-theta), cos(-theta)]])\n", + "# Vision model\n", "\n", - "vec = np.array([0, -1])\n", + "vision_angle = 45\n", + "vision_pixels = 5\n", + "vision_distance = 1\n", "\n", - "print(vec)\n", - "for i in range(9):\n", - " vec = np.round(np.dot(rot_left, vec), 0).astype(int)\n", - " print(vec)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 5, - "outputs": [ - { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mKeyboardInterrupt\u001B[0m Traceback (most recent call last)", - "Input \u001B[0;32mIn [5]\u001B[0m, in \u001B[0;36m\u001B[0;34m()\u001B[0m\n\u001B[1;32m 31\u001B[0m window \u001B[38;5;241m=\u001B[39m MyGame()\n\u001B[1;32m 32\u001B[0m window\u001B[38;5;241m.\u001B[39msetup()\n\u001B[0;32m---> 33\u001B[0m \u001B[43marcade\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrun\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/evolving/lib/python3.8/site-packages/arcade/window_commands.py:289\u001B[0m, in \u001B[0;36mrun\u001B[0;34m()\u001B[0m\n\u001B[1;32m 286\u001B[0m \u001B[38;5;28;01mimport\u001B[39;00m \u001B[38;5;21;01msys\u001B[39;00m\n\u001B[1;32m 287\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m sys\u001B[38;5;241m.\u001B[39mplatform \u001B[38;5;241m!=\u001B[39m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mwin32\u001B[39m\u001B[38;5;124m'\u001B[39m:\n\u001B[1;32m 288\u001B[0m \u001B[38;5;66;03m# For non windows platforms, just do pyglet run\u001B[39;00m\n\u001B[0;32m--> 289\u001B[0m \u001B[43mpyglet\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mapp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrun\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 290\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m 291\u001B[0m \u001B[38;5;66;03m# Ok, some Windows platforms have a timer resolution > 15 ms. That can\u001B[39;00m\n\u001B[1;32m 292\u001B[0m \u001B[38;5;66;03m# drop our FPS to 32 FPS or so. This reduces resolution so we can keep\u001B[39;00m\n\u001B[1;32m 293\u001B[0m \u001B[38;5;66;03m# FPS up.\u001B[39;00m\n\u001B[1;32m 294\u001B[0m \u001B[38;5;28;01mimport\u001B[39;00m \u001B[38;5;21;01mcontextlib\u001B[39;00m\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/evolving/lib/python3.8/site-packages/pyglet/app/__init__.py:107\u001B[0m, in \u001B[0;36mrun\u001B[0;34m(interval)\u001B[0m\n\u001B[1;32m 99\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mrun\u001B[39m(interval\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m1\u001B[39m\u001B[38;5;241m/\u001B[39m\u001B[38;5;241m60\u001B[39m):\n\u001B[1;32m 100\u001B[0m \u001B[38;5;124;03m\"\"\"Begin processing events, scheduled functions and window updates.\u001B[39;00m\n\u001B[1;32m 101\u001B[0m \n\u001B[1;32m 102\u001B[0m \u001B[38;5;124;03m This is a convenience function, equivalent to::\u001B[39;00m\n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 105\u001B[0m \n\u001B[1;32m 106\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m--> 107\u001B[0m \u001B[43mevent_loop\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrun\u001B[49m\u001B[43m(\u001B[49m\u001B[43minterval\u001B[49m\u001B[43m)\u001B[49m\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/evolving/lib/python3.8/site-packages/pyglet/app/base.py:185\u001B[0m, in \u001B[0;36mEventLoop.run\u001B[0;34m(self, interval)\u001B[0m\n\u001B[1;32m 183\u001B[0m \u001B[38;5;28;01mwhile\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhas_exit:\n\u001B[1;32m 184\u001B[0m timeout \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39midle()\n\u001B[0;32m--> 185\u001B[0m \u001B[43mplatform_event_loop\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mstep\u001B[49m\u001B[43m(\u001B[49m\u001B[43mtimeout\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 187\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mis_running \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mFalse\u001B[39;00m\n\u001B[1;32m 188\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mdispatch_event(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mon_exit\u001B[39m\u001B[38;5;124m'\u001B[39m)\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/evolving/lib/python3.8/site-packages/pyglet/app/cocoa.py:142\u001B[0m, in \u001B[0;36mCocoaEventLoop.step\u001B[0;34m(self, timeout)\u001B[0m\n\u001B[1;32m 138\u001B[0m \u001B[38;5;66;03m# Retrieve the next event (if any). We wait for an event to show up\u001B[39;00m\n\u001B[1;32m 139\u001B[0m \u001B[38;5;66;03m# and then process it, or if timeout_date expires we simply return.\u001B[39;00m\n\u001B[1;32m 140\u001B[0m \u001B[38;5;66;03m# We only process one event per call of step().\u001B[39;00m\n\u001B[1;32m 141\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_is_running\u001B[38;5;241m.\u001B[39mset()\n\u001B[0;32m--> 142\u001B[0m event \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mNSApp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mnextEventMatchingMask_untilDate_inMode_dequeue_\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 143\u001B[0m \u001B[43m \u001B[49m\u001B[43mcocoapy\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mNSAnyEventMask\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mtimeout_date\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mcocoapy\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mNSDefaultRunLoopMode\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43;01mTrue\u001B[39;49;00m\u001B[43m)\u001B[49m\n\u001B[1;32m 145\u001B[0m \u001B[38;5;66;03m# Dispatch the event (if any).\u001B[39;00m\n\u001B[1;32m 146\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m event \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/evolving/lib/python3.8/site-packages/pyglet/libs/darwin/cocoapy/runtime.py:805\u001B[0m, in \u001B[0;36mObjCBoundMethod.__call__\u001B[0;34m(self, *args)\u001B[0m\n\u001B[1;32m 803\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m__call__\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;241m*\u001B[39margs):\n\u001B[1;32m 804\u001B[0m \u001B[38;5;124;03m\"\"\"Call the method with the given arguments.\"\"\"\u001B[39;00m\n\u001B[0;32m--> 805\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmethod\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mobjc_id\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m)\u001B[49m\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/evolving/lib/python3.8/site-packages/pyglet/libs/darwin/cocoapy/runtime.py:775\u001B[0m, in \u001B[0;36mObjCMethod.__call__\u001B[0;34m(self, objc_id, *args)\u001B[0m\n\u001B[1;32m 773\u001B[0m f \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mget_callable()\n\u001B[1;32m 774\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 775\u001B[0m result \u001B[38;5;241m=\u001B[39m \u001B[43mf\u001B[49m\u001B[43m(\u001B[49m\u001B[43mobjc_id\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mselector\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 776\u001B[0m \u001B[38;5;66;03m# Convert result to python type if it is a instance or class pointer.\u001B[39;00m\n\u001B[1;32m 777\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mrestype \u001B[38;5;241m==\u001B[39m ObjCInstance:\n", - "\u001B[0;31mKeyboardInterrupt\u001B[0m: " - ] - } - ], - "source": [ - "import arcade\n", + "vision_pixel_size = vision_angle // vision_pixels\n", "\n", - "SCREEN_WIDTH = 1000\n", - "SCREEN_HEIGHT = 650\n", - "SCREEN_TITLE = \"Platformer\"\n", + "print('vision_pixel_size', vision_pixel_size)\n", "\n", + "# Test data\n", + "main = (0, 0)\n", + "direction = [0, -1]\n", "\n", - "class MyGame(arcade.Window):\n", - " \"\"\"\n", - " Main application class.\n", - " \"\"\"\n", + "beings_x = [1, -0.8, -0.1, -0.67]\n", + "beings_y = [0.5, -0.5, -0.6, 0.2]\n", "\n", - " def __init__(self):\n", + "# Vision calculations\n", "\n", - " # Call the parent class and set up the window\n", - " super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)\n", + "direction_angle = math.degrees(math.atan2(direction[1], direction[0]))\n", + "vision_line = (\n", + " main[0] + direction[0] * vision_distance,\n", + " main[1] + direction[1] * vision_distance,\n", + ")\n", "\n", - " arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)\n", + "# calculate angles between the main being and each of the others\n", + "# the ones that are within +- 45 degrees + within the distance are \"visible\"\n", + "# either this or we ray trace (calculate equations for the lines and see if any being falls within it)\n", + "# and then use the distance for the color\n", "\n", - " def setup(self):\n", - " \"\"\"Set up the game here. Call this function to restart the game.\"\"\"\n", - " pass\n", + "# the issue with the ray tracing is that as we get further, they'll be far from the lines\n", + "# I think it'll be best to do the angle thing, and then map angles to pixels of vision\n", "\n", - " def on_draw(self):\n", - " \"\"\"Render the screen.\"\"\"\n", + "min_angle = direction_angle - vision_angle / 2\n", + "max_angle = direction_angle + vision_angle / 2\n", "\n", - " self.clear()\n", - " # Code to draw the screen goes here\n", + "min_angle %= 360\n", + "max_angle %= 360\n", "\n", + "# min_angle = min_angle - 360 if min_angle > 180 else min_angle\n", + "# max_angle = max_angle - 360 if max_angle > 180 else max_angle\n", "\n", - "window = MyGame()\n", - "window.setup()\n", - "arcade.run()\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "window.close()\n", - "arcade.exit()" + "print('angles', min_angle, max_angle)\n", + "\n", + "vision = [0] * vision_pixels\n", + "\n", + "for coords in zip(beings_x, beings_y):\n", + " # angle between two points:\n", + " # project each coord to the x/y axis\n", + " # PI = 180\n", + " # xx = x\n", + "\n", + " y = coords[1] - main[1]\n", + " x = coords[0] - main[0]\n", + "\n", + " angle = math.atan2(y, x)\n", + " angle_deg = math.degrees(angle)\n", + " angle_deg %= 360\n", + "\n", + " print(coords, round(angle_deg))\n", + "\n", + " if min_angle <= angle_deg <= max_angle:\n", + " dist = math.hypot(x, y)\n", + " if dist <= vision_distance:\n", + " # visible\n", + " vision_chunk = int((angle_deg - min_angle) // vision_pixel_size)\n", + " vision[vision_chunk] += 1\n", + "\n", + " print(' chunk:', vision_chunk, dist)\n", + " else:\n", + " print(' Too far')\n", + "\n", + "print()\n", + "print('Vision array (shows a 1 when there is a being within viewing distance in that direction)')\n", + "print(vision)\n", + "\n", + "plt.rcParams[\"figure.figsize\"] = (10, 10)\n", + "\n", + "plt.title('Vision system test')\n", + "plt.scatter(beings_x, beings_y)\n", + "plt.plot([main[0]], [main[1]], marker='o', color='red')\n", + "\n", + "# Draw some axis to make it easier to \"debug\"\n", + "plt.plot([main[0], -1], [main[1], -1], color='yellow')\n", + "plt.plot([main[0], 1], [main[1], 1], color='yellow')\n", + "plt.plot([main[0], 1], [main[1], -1], color='yellow')\n", + "plt.plot([main[0], -1], [main[1], 1], color='yellow')\n", + "\n", + "for coord in zip(beings_x, beings_y):\n", + " plt.plot([main[0], coord[0]], [main[1], coord[1]], color='red')\n", + "\n", + "plt.plot([main[0], main[0] + direction[0] * vision_distance], [main[1], main[1] + direction[1] * vision_distance], color='green')\n", + "\n", + "plt.grid()\n", + "plt.show()" ], "metadata": { "collapsed": false,