├── .gitignore ├── LICENSE ├── README.md ├── conway ├── __init__.py ├── game.py ├── images.py ├── server.py └── twitter.py ├── misc ├── emoji.txt └── example.gif └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | 62 | # PyBuilder 63 | target/ 64 | 65 | # IPython Notebook 66 | .ipynb_checkpoints 67 | 68 | # pyenv 69 | .python-version 70 | 71 | .env 72 | .DS_Store 73 | 74 | img/* 75 | dump.rdb 76 | *.gif 77 | !misc/example.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Avy Faingezicht 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Conway's Game of life 2 | 3 | **Tweet pattern, get gif. [@tweetgameoflife](https://twitter.com/tweetgameoflife)** 4 | 5 | ![example](misc/example.gif) 6 | -------------------------------------------------------------------------------- /conway/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avyfain/conway/94f40ec2815bfe4c5ffc37efff92558856ce693b/conway/__init__.py -------------------------------------------------------------------------------- /conway/game.py: -------------------------------------------------------------------------------- 1 | """ 2 | conway.game 3 | ~~~~~~~~~~~~~~~ 4 | This module contains an implementation of Conway's Game of Life 5 | 6 | Inspired by Jack Diederich's talk `Stop Writing Classes` 7 | http://pyvideo.org/video/880/stop-writing-classes 8 | 9 | Partially derived from Jason Keene's gist: 10 | https://gist.github.com/jasonkeene/2140276 11 | """ 12 | from itertools import chain, product 13 | 14 | # This comes from product((-1, 0, 1), (-1, 0, 1)), removing (0,0). 15 | # We compute it once instead of on each run simply for an optimization. 16 | # This is a constant, and in fact, we could precompute the neighbors for 17 | # all the board ahead of time, or cache it somewhere if it mattered. 18 | NEIGHBOR_SET = ((0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, 0), (1, -1), (1, 1)) 19 | 20 | def neighbors(point): 21 | """ 22 | Returns the set of adjacent points to the given point. 23 | """ 24 | x, y = point 25 | return set((x + dx, y + dy) for dx, dy in NEIGHBOR_SET) 26 | 27 | class Board(object): 28 | """The :class:`Board ` object, which contains 29 | the cells of a given Conway pattern. 30 | """ 31 | 32 | def __init__(self, pattern, size=100): 33 | """Takes string representations of GoL patterns from 34 | http://www.argentum.freeserve.co.uk/lex.htm and returns""" 35 | self.size = size -1 36 | self.points = set() 37 | self.pattern = pattern 38 | self.frame_num = 0 39 | self.frames = [] 40 | 41 | i, j = 0, 0 42 | for char in pattern: 43 | if char == '\n': 44 | i = 0 45 | j += 1 46 | elif char == '1': 47 | i += 1 48 | elif char == '0': 49 | self.points.add((i, j)) 50 | i += 1 51 | 52 | def __hash__(self): 53 | return hash(frozenset(self.points)) 54 | 55 | def __eq__(self, other): 56 | return other and hash(self) == hash(other) 57 | 58 | def advance(self): 59 | """ 60 | Moves the board one time step according to the basic rules of Conway's Game of Life: 61 | 62 | From https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life: 63 | Any live cell with fewer than two live neighbours dies, as if caused by under-population. 64 | Any live cell with two or three live neighbours lives on to the next generation. 65 | Any live cell with more than three live neighbours dies, as if by over-population. 66 | Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. 67 | """ 68 | newstate = set() 69 | recalc = self.points | set(chain(*(neighbors(point) for point in self.points))) 70 | for point in recalc: 71 | count = len(self.points & neighbors(point)) 72 | if count == 3 or (count == 2 and point in self.points): 73 | newstate.add(point) 74 | self.points = newstate 75 | return self 76 | 77 | def center(self): 78 | """ 79 | Computes the centroid of the Board pattern, and shifts the points accordingly. 80 | """ 81 | center_x, center_y = (sum(dim)/len(self.points) - self.size/2 for dim in zip(*self.points)) 82 | self.points = {(x-center_x, y-center_y) for x, y in self.points} 83 | return self 84 | 85 | def constrain(self): 86 | """ 87 | Removes any points outside of the cell's bounding box. 88 | """ 89 | self.points = set(cell for cell in self.points if self.contains(cell)) 90 | return self 91 | 92 | def contains(self, cell): 93 | """ 94 | Returns true if a cell is part of the bounding box. 95 | """ 96 | return all(0 <= v <= self.size for v in cell) 97 | 98 | def scale(self, square_size): 99 | """ 100 | Turns the board into a series of 0s and 1s scaled up by `square_size` 101 | """ 102 | line = [] 103 | for i, j in product(range(self.size + 1), range(self.size + 1)): 104 | line.extend([0]*square_size if (i, j) in self.points else [1]*square_size) 105 | if j == self.size: 106 | for _ in range(square_size): 107 | yield line 108 | line = [] 109 | -------------------------------------------------------------------------------- /conway/images.py: -------------------------------------------------------------------------------- 1 | """ 2 | conway.images 3 | ~~~~~~~~~~~~~~~ 4 | This module contains the image creation logic. 5 | """ 6 | 7 | import os 8 | from uuid import uuid4 9 | 10 | import png 11 | 12 | import moviepy.editor as mpy 13 | 14 | DEFAULT_SQUARE_SIZE = 4 15 | 16 | def create_gif(): 17 | file_names = (fn for fn in os.listdir('.') if fn.endswith('.png')) 18 | s_file_names = sorted(file_names, key=lambda x: int(x.split('.')[0])) 19 | clip = mpy.ImageSequenceClip(s_file_names, fps=12) 20 | name = '{}.gif'.format(uuid4()) 21 | clip.write_gif(name, fps=12) 22 | delete_pngs() 23 | return name 24 | 25 | def delete_pngs(): 26 | for img in os.listdir('.'): 27 | if img.endswith('.png'): 28 | os.remove(img) 29 | 30 | def to_png(board, square_size=DEFAULT_SQUARE_SIZE): 31 | """ 32 | Serializes the board as a png file. 33 | """ 34 | png_size = (board.size + 1)*square_size 35 | writer = png.Writer(png_size, png_size, greyscale=True, bitdepth=1) 36 | 37 | lines = board.scale(square_size) 38 | board.frame_num += 1 39 | frame_name = '{}.png'.format(board.frame_num) 40 | with open(frame_name, 'wb') as frame: 41 | writer.write(frame, lines) 42 | 43 | def draw_frames(board, max_steps=150): 44 | board.center() 45 | seen = set() 46 | 47 | delete_pngs() 48 | 49 | for _ in range(max_steps): 50 | if _ % 5 == 0: 51 | print("Processing frame {}".format(_)) 52 | 53 | cur_board_repr = hash(board) 54 | if cur_board_repr in seen: 55 | for _ in range(10): 56 | to_png(board) 57 | break 58 | to_png(board) 59 | seen.add(cur_board_repr) 60 | board = board.advance().constrain() 61 | -------------------------------------------------------------------------------- /conway/server.py: -------------------------------------------------------------------------------- 1 | """ 2 | conway.server 3 | ~~~~~~~~~~~~~~~ 4 | This module contains the bot's logic. 5 | """ 6 | import os 7 | import random 8 | 9 | from collections import deque 10 | 11 | from game import Board 12 | from images import draw_frames, create_gif 13 | from twitter import twitter_client 14 | 15 | import redis 16 | r = redis.from_url(os.environ.get("REDIS_URL", "redis://localhost:6379")) 17 | 18 | HASHTAGS = os.environ.get("HASHTAGS") 19 | PROB = float(os.environ.get("PROB", "0.9")) 20 | 21 | my_handle = "@" + twitter_client.me().screen_name 22 | 23 | def grab_tweets(): 24 | """ 25 | Searches for the list of tweets since the last job execution. 26 | Yields relevant ones. 27 | """ 28 | max_id = r.get('max_id') 29 | tweets = twitter_client.search(my_handle, since_id=max_id) 30 | if tweets: 31 | r.set('max_id', str(tweets[0].id)) 32 | else: 33 | print("No one is tweeting, try again later :(") 34 | 35 | for tweet in reversed(tweets): 36 | if '://' not in tweet.text and not tweet.text.startswith('RT'): 37 | yield tweet 38 | 39 | 40 | def reply_with_conway(original_tweet): 41 | """ 42 | Given a tweet, builds a gif and replies to the user. 43 | """ 44 | pattern = create_pattern_from_text(original_tweet.text) 45 | gif_name = gif_from_pattern(pattern) 46 | user_name = original_tweet.user.screen_name 47 | message = "Hi @{}, look at the complexity of your tweet!\n{}".format(user_name, HASHTAGS) 48 | twitter_client.update_with_media(filename=gif_name, 49 | status=message, 50 | in_reply_to_status_id=original_tweet.id_str) 51 | 52 | def generate_random_tweet(): 53 | """ 54 | Come up with a random pattern, tweet it. 55 | """ 56 | with open('misc/emoji.txt') as f: 57 | lines = f.readlines() 58 | 59 | emoji = random.choice(lines)[:-1] 60 | options = (emoji, ' ', ' ') 61 | 62 | deck = deque() 63 | 64 | for _ in range(20): 65 | char = random.choice(options) 66 | deck.appendleft(char) 67 | deck.append(char) 68 | 69 | rand_emoji = ''.join(deck).strip() 70 | text = rand_emoji + '\n' + HASHTAGS 71 | 72 | pattern = create_pattern_from_text(rand_emoji) 73 | gif_name = gif_from_pattern(pattern) 74 | twitter_client.update_with_media(filename=gif_name, 75 | status=text) 76 | 77 | def get_word_bin(string): 78 | """ 79 | Given a string, returns its binary representation in 0s and 1s 80 | """ 81 | return ''.join(format(ord(x), 'b') for x in string) 82 | 83 | def create_pattern_from_text(text): 84 | """ 85 | Given a tweet, returns a pattern of 0s, 1s and \n characters 86 | to be consumed to create a game of life board. 87 | """ 88 | words = text.split(' ') 89 | return '\n'.join(get_word_bin(word) for word in words if word != my_handle) 90 | 91 | def gif_from_pattern(pattern): 92 | """ 93 | Given a conway pattern string, builds a board, draws its frames and writes the .gif file 94 | Returns name of new file. 95 | """ 96 | draw_frames(Board(pattern)) 97 | name = create_gif() 98 | return name 99 | 100 | def main(): 101 | """ 102 | Main application logic 103 | """ 104 | tweets = grab_tweets() 105 | for tweet in tweets: 106 | reply_with_conway(tweet) 107 | 108 | if random.random() > PROB: 109 | print("Random tweet! Let's see what we get...") 110 | generate_random_tweet() 111 | else: 112 | print("No random tweet this time!") 113 | 114 | if __name__ == '__main__': 115 | main() 116 | -------------------------------------------------------------------------------- /conway/twitter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tweepy 3 | 4 | CONSUMER_KEY = os.environ.get('TWITTER_CONSUMER_KEY') 5 | CONSUMER_SECRET = os.environ.get('TWITTER_CONSUMER_SECRET') 6 | ACCESS_KEY = os.environ.get('TWITTER_ACCESS_TOKEN') 7 | ACCESS_SECRET = os.environ.get('TWITTER_ACCESS_TOKEN_SECRET') 8 | auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) 9 | auth.set_access_token(ACCESS_KEY, ACCESS_SECRET) 10 | twitter_client = tweepy.API(auth) 11 | -------------------------------------------------------------------------------- /misc/emoji.txt: -------------------------------------------------------------------------------- 1 | 😁 2 | 😂 3 | 😃 4 | 😄 5 | 😅 6 | 😆 7 | 😉 8 | 😊 9 | 😋 10 | 😌 11 | 😍 12 | 😏 13 | 😒 14 | 😓 15 | 😔 16 | 😖 17 | 😘 18 | 😚 19 | 😜 20 | 😝 21 | 😞 22 | 😠 23 | 😡 24 | 😢 25 | 😣 26 | 😤 27 | 😥 28 | 😨 29 | 😩 30 | 😪 31 | 😫 32 | 😭 33 | 😰 34 | 😱 35 | 😲 36 | 😳 37 | 😵 38 | 😷 39 | 😸 40 | 😹 41 | 😺 42 | 😻 43 | 😼 44 | 😽 45 | 😾 46 | 😿 47 | 🙀 48 | 🙅 49 | 🙆 50 | 🙇 51 | 🙈 52 | 🙉 53 | 🙊 54 | 🙋 55 | 🙌 56 | 🙍 57 | 🙎 58 | 🙏 59 | ✅ 60 | ✊ 61 | ✋ 62 | ✌ 63 | ✏ 64 | ✒ 65 | ✔ 66 | ✖ 67 | ✨ 68 | ✳ 69 | ✴ 70 | ❄ 71 | ❇ 72 | ❌ 73 | ❎ 74 | ❓ 75 | ❔ 76 | ❕ 77 | ❗ 78 | ❤ 79 | ➕ 80 | ➖ 81 | ➗ 82 | ➡ 83 | ➰ 84 | 🚀 85 | 🚃 86 | 🚄 87 | 🚅 88 | 🚇 89 | 🚉 90 | 🚌 91 | 🚏 92 | 🚑 93 | 🚒 94 | 🚓 95 | 🚕 96 | 🚗 97 | 🚙 98 | 🚚 99 | 🚢 100 | 🚤 101 | 🚥 102 | 🚧 103 | 🚨 104 | 🚩 105 | 🚪 106 | 🚫 107 | 🚬 108 | 🚭 109 | 🚲 110 | 🚶 111 | 🚹 112 | 🚺 113 | 🚻 114 | 🚼 115 | 🚽 116 | 🚾 117 | 🛀 118 | ⌚ 119 | ⌛ 120 | ⏩ 121 | ⏪ 122 | ⏫ 123 | ⏬ 124 | ⏰ 125 | ⏳ 126 | ⚪ 127 | ⚫ 128 | ⚽ 129 | ⚾ 130 | ⛄ 131 | ⛅ 132 | ⛎ 133 | ⛔ 134 | ⛪ 135 | ⛲ 136 | ⛳ 137 | ⛵ 138 | ⛺ 139 | ⛽ 140 | ⤴ 141 | ⤵ 142 | ⬅ 143 | ⬆ 144 | ⬇ 145 | ⬛ 146 | ⬜ 147 | ⭐ 148 | ⭕ 149 | 〰 150 | 〽 151 | ㊗ 152 | ㊙ 153 | 🀄 154 | 🃏 155 | 🌀 156 | 🌁 157 | 🌂 158 | 🌃 159 | 🌄 160 | 🌅 161 | 🌆 162 | 🌇 163 | 🌈 164 | 🌉 165 | 🌊 166 | 🌋 167 | 🌌 168 | 🌏 169 | 🌑 170 | 🌓 171 | 🌔 172 | 🌕 173 | 🌙 174 | 🌛 175 | 🌟 176 | 🌠 177 | 🌰 178 | 🌱 179 | 🌴 180 | 🌵 181 | 🌷 182 | 🌸 183 | 🌹 184 | 🌺 185 | 🌻 186 | 🌼 187 | 🌽 188 | 🌾 189 | 🌿 190 | 🍀 191 | 🍁 192 | 🍂 193 | 🍃 194 | 🍄 195 | 🍅 196 | 🍆 197 | 🍇 198 | 🍈 199 | 🍉 200 | 🍊 201 | 🍌 202 | 🍍 203 | 🍎 204 | 🍏 205 | 🍑 206 | 🍒 207 | 🍓 208 | 🍔 209 | 🍕 210 | 🍖 211 | 🍗 212 | 🍘 213 | 🍙 214 | 🍚 215 | 🍛 216 | 🍜 217 | 🍝 218 | 🍞 219 | 🍟 220 | 🍠 221 | 🍡 222 | 🍢 223 | 🍣 224 | 🍤 225 | 🍥 226 | 🍦 227 | 🍧 228 | 🍨 229 | 🍩 230 | 🍪 231 | 🍫 232 | 🍬 233 | 🍭 234 | 🍮 235 | 🍯 236 | 🍰 237 | 🍱 238 | 🍲 239 | 🍳 240 | 🍴 241 | 🍵 242 | 🍶 243 | 🍷 244 | 🍸 245 | 🍹 246 | 🍺 247 | 🍻 248 | 🎀 249 | 🎁 250 | 🎂 251 | 🎃 252 | 🎄 253 | 🎅 254 | 🎆 255 | 🎇 256 | 🎈 257 | 🎉 258 | 🎊 259 | 🎋 260 | 🎌 261 | 🎍 262 | 🎎 263 | 🎏 264 | 🎐 265 | 🎑 266 | 🎒 267 | 🎓 268 | 🎠 269 | 🎡 270 | 🎢 271 | 🎣 272 | 🎤 273 | 🎥 274 | 🎦 275 | 🎧 276 | 🎨 277 | 🎩 278 | 🎪 279 | 🎫 280 | 🎬 281 | 🎭 282 | 🎮 283 | 🎯 284 | 🎰 285 | 🎱 286 | 🎲 287 | 🎳 288 | 🎴 289 | 🎵 290 | 🎶 291 | 🎷 292 | 🎸 293 | 🎹 294 | 🎺 295 | 🎻 296 | 🎼 297 | 🎽 298 | 🎾 299 | 🎿 300 | 🏀 301 | 🏁 302 | 🏂 303 | 🏃 304 | 🏄 305 | 🏆 306 | 🏈 307 | 🏊 308 | 🏠 309 | 🏡 310 | 🏢 311 | 🏣 312 | 🏥 313 | 🏦 314 | 🏧 315 | 🏨 316 | 🏩 317 | 🏪 318 | 🏫 319 | 🏬 320 | 🏭 321 | 🏮 322 | 🏯 323 | 🏰 324 | 🐌 325 | 🐍 326 | 🐎 327 | 🐑 328 | 🐒 329 | 🐔 330 | 🐗 331 | 🐘 332 | 🐙 333 | 🐚 334 | 🐛 335 | 🐜 336 | 🐝 337 | 🐞 338 | 🐟 339 | 🐠 340 | 🐡 341 | 🐢 342 | 🐣 343 | 🐤 344 | 🐥 345 | 🐦 346 | 🐧 347 | 🐨 348 | 🐩 349 | 🐫 350 | 🐬 351 | 🐭 352 | 🐮 353 | 🐯 354 | 🐰 355 | 🐱 356 | 🐲 357 | 🐳 358 | 🐴 359 | 🐵 360 | 🐶 361 | 🐷 362 | 🐸 363 | 🐹 364 | 🐺 365 | 🐻 366 | 🐼 367 | 🐽 368 | 🐾 369 | 👀 370 | 👂 371 | 👃 372 | 👄 373 | 👅 374 | 👆 375 | 👇 376 | 👈 377 | 👉 378 | 👊 379 | 👋 380 | 👌 381 | 👍 382 | 👎 383 | 👏 384 | 👐 385 | 👑 386 | 👒 387 | 👓 388 | 👔 389 | 👕 390 | 👖 391 | 👗 392 | 👘 393 | 👙 394 | 👚 395 | 👛 396 | 👜 397 | 👝 398 | 👞 399 | 👟 400 | 👠 401 | 👡 402 | 👢 403 | 👣 404 | 👤 405 | 👦 406 | 👧 407 | 👨 408 | 👩 409 | 👪 410 | 👫 411 | 👮 412 | 👯 413 | 👰 414 | 👱 415 | 👲 416 | 👳 417 | 👴 418 | 👵 419 | 👶 420 | 👷 421 | 👸 422 | 👹 423 | 👺 424 | 👻 425 | 👼 426 | 👽 427 | 👾 428 | 👿 429 | 💀 430 | 💁 431 | 💂 432 | 💃 433 | 💄 434 | 💅 435 | 💆 436 | 💇 437 | 💈 438 | 💉 439 | 💊 440 | 💋 441 | 💌 442 | 💍 443 | 💎 444 | 💏 445 | 💐 446 | 💑 447 | 💒 448 | 💓 449 | 💔 450 | 💕 451 | 💖 452 | 💗 453 | 💘 454 | 💙 455 | 💚 456 | 💛 457 | 💜 458 | 💝 459 | 💞 460 | 💟 461 | 💠 462 | 💡 463 | 💢 464 | 💣 465 | 💤 466 | 💥 467 | 💦 468 | 💧 469 | 💨 470 | 💩 471 | 💪 472 | 💫 473 | 💬 474 | 💮 475 | 💯 476 | 💰 477 | 💱 478 | 💲 479 | 💳 480 | 💴 481 | 💵 482 | 💸 483 | 💹 484 | 💺 485 | 💻 486 | 💼 487 | 💽 488 | 💾 489 | 💿 490 | 📀 491 | 📁 492 | 📂 493 | 📃 494 | 📄 495 | 📅 496 | 📆 497 | 📇 498 | 📈 499 | 📉 500 | 📊 501 | 📋 502 | 📌 503 | 📍 504 | 📎 505 | 📏 506 | 📐 507 | 📑 508 | 📒 509 | 📓 510 | 📔 511 | 📕 512 | 📖 513 | 📗 514 | 📘 515 | 📙 516 | 📚 517 | 📛 518 | 📜 519 | 📝 520 | 📞 521 | 📟 522 | 📠 523 | 📡 524 | 📢 525 | 📣 526 | 📤 527 | 📥 528 | 📦 529 | 📧 530 | 📨 531 | 📩 532 | 📪 533 | 📫 534 | 📮 535 | 📰 536 | 📱 537 | 📲 538 | 📳 539 | 📴 540 | 📶 541 | 📷 542 | 📹 543 | 📺 544 | 📻 545 | 📼 546 | 🔃 547 | 🔊 548 | 🔋 549 | 🔌 550 | 🔍 551 | 🔎 552 | 🔏 553 | 🔐 554 | 🔑 555 | 🔒 556 | 🔓 557 | 🔔 558 | 🔖 559 | 🔗 560 | 🔘 561 | 🔙 562 | 🔚 563 | 🔛 564 | 🔜 565 | 🔝 566 | 🔞 567 | 🔟 568 | 🔠 569 | 🔡 570 | 🔢 571 | 🔣 572 | 🔤 573 | 🔥 574 | 🔦 575 | 🔧 576 | 🔨 577 | 🔩 578 | 🔪 579 | 🔫 580 | 🔮 581 | 🔯 582 | 🔰 583 | 🔱 584 | 🔲 585 | 🔳 586 | 🔴 587 | 🔵 588 | 🔶 589 | 🔷 590 | 🔸 591 | 🔹 592 | 🔺 593 | 🔻 594 | 🔼 595 | 🔽 596 | 🕐 597 | 🕑 598 | 🕒 599 | 🕓 600 | 🕔 601 | 🕕 602 | 🕖 603 | 🕗 604 | 🕘 605 | 🕙 606 | 🕚 607 | 🕛 608 | 🗻 609 | 🗼 610 | 🗽 611 | 🗾 612 | 🗿 613 | 😀 614 | 😇 615 | 😈 616 | 😎 617 | 😐 618 | 😑 619 | 😕 620 | 😗 621 | 😙 622 | 😛 623 | 😟 624 | 😦 625 | 😧 626 | 😬 627 | 😮 628 | 😯 629 | 😴 630 | 😶 631 | 🚁 632 | 🚂 633 | 🚆 634 | 🚈 635 | 🚊 636 | 🚍 637 | 🚎 638 | 🚐 639 | 🚔 640 | 🚖 641 | 🚘 642 | 🚛 643 | 🚜 644 | 🚝 645 | 🚞 646 | 🚟 647 | 🚠 648 | 🚡 649 | 🚣 650 | 🚦 651 | 🚮 652 | 🚯 653 | 🚰 654 | 🚱 655 | 🚳 656 | 🚴 657 | 🚵 658 | 🚷 659 | 🚸 660 | 🚿 661 | 🛁 662 | 🛂 663 | 🛃 664 | 🛄 665 | 🛅 666 | 🌍 667 | 🌎 668 | 🌐 669 | 🌒 670 | 🌖 671 | 🌗 672 | 🌘 673 | 🌚 674 | 🌜 675 | 🌝 676 | 🌞 677 | 🌲 678 | 🌳 679 | 🍋 680 | 🍐 681 | 🍼 682 | 🏇 683 | 🏉 684 | 🏤 685 | 🐀 686 | 🐁 687 | 🐂 688 | 🐃 689 | 🐄 690 | 🐅 691 | 🐆 692 | 🐇 693 | 🐈 694 | 🐉 695 | 🐊 696 | 🐋 697 | 🐏 698 | 🐐 699 | 🐓 700 | 🐕 701 | 🐖 702 | 🐪 703 | 👥 704 | 👬 705 | 👭 706 | 💭 707 | 💶 708 | 💷 709 | 📬 710 | 📭 711 | 📯 712 | 📵 713 | 🔀 714 | 🔁 715 | 🔂 716 | 🔄 717 | 🔅 718 | 🔆 719 | 🔇 720 | 🔉 721 | 🔕 722 | 🔬 723 | 🔭 724 | 🕜 725 | 🕝 726 | 🕞 727 | 🕟 728 | 🕠 729 | 🕡 730 | 🕢 731 | 🕣 732 | 🕤 733 | 🕥 734 | 🕦 735 | 🕧 -------------------------------------------------------------------------------- /misc/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avyfain/conway/94f40ec2815bfe4c5ffc37efff92558856ce693b/misc/example.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | decorator==4.0.10 2 | imageio==1.5 3 | moviepy==0.2.2.11 4 | numpy==1.10.4 5 | oauthlib==1.1.2 6 | pypng==0.0.18 7 | redis==2.10.5 8 | requests==2.10.0 9 | requests-oauthlib==0.6.1 10 | six==1.10.0 11 | tqdm==4.7.4 12 | tweepy==3.5.0 13 | wsgiref==0.1.2 14 | --------------------------------------------------------------------------------