├── .gitignore ├── LICENSE ├── README.md ├── bin ├── 10k words.txt └── questions.json ├── discordbot ├── categories │ ├── __init__.py │ ├── category.py │ ├── developer.py │ ├── minigames.py │ └── miscellaneous.py ├── commands │ ├── command.py │ ├── developer │ │ ├── clear.py │ │ ├── delete.py │ │ ├── games.py │ │ ├── restart.py │ │ ├── say.py │ │ ├── servers.py │ │ ├── temperature.py │ │ └── xec.py │ ├── minigames │ │ ├── akinator_cmd.py │ │ ├── blackjack_cmd.py │ │ ├── chess_cmd.py │ │ ├── connect4_cmd.py │ │ ├── flood_cmd.py │ │ ├── hangman_cmd.py │ │ ├── mastermind_cmd.py │ │ ├── quiz_cmd.py │ │ └── scramble_cmd.py │ └── miscellaneous │ │ ├── bug_report.py │ │ ├── help.py │ │ ├── info.py │ │ ├── rules.py │ │ ├── set_prefix.py │ │ └── stats.py ├── databasemanager.py ├── discordminigames │ ├── discordminigame.py │ ├── multiplayergames │ │ ├── chess_dc.py │ │ ├── connect4_dc.py │ │ └── multiplayergame.py │ └── singleplayergames │ │ ├── akinator_dc.py │ │ ├── blackjack_dc.py │ │ ├── flood_dc.py │ │ ├── hangman_dc.py │ │ ├── mastermind_dc.py │ │ ├── quiz_dc.py │ │ ├── scramble_dc.py │ │ └── singleplayergame.py ├── gamemanager.py ├── messagemanager.py ├── minigamesbot.py ├── user │ ├── multiplayersession.py │ ├── player.py │ ├── session.py │ └── singleplayersession.py └── utils │ ├── emojis.py │ ├── pager.py │ ├── topgg.py │ └── variables.py ├── generic ├── database.py ├── formatting.py ├── scheduler.py └── stopwatch.py ├── images └── flood.png ├── main.py ├── minigames ├── blackjack.py ├── connect4.py ├── flood.py ├── hangman.py ├── lexicon.py ├── mastermind.py ├── minigame.py └── scramble.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | loop.sh 4 | bin/*.png 5 | bin/*.svg 6 | bin/minigames.db 7 | bin/database_backup.zip 8 | bin/server_prefixes.json 9 | bin/prefixes_backup.zip 10 | discordbot/utils/private.py 11 | discordbot/user/discord_games/checkers_dc.py 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | pip-wheel-metadata/ 35 | share/python-wheels/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | *.py,cover 62 | .hypothesis/ 63 | .pytest_cache/ 64 | 65 | # Translations 66 | *.mo 67 | *.pot 68 | 69 | # Django stuff: 70 | *.log 71 | local_settings.py 72 | db.sqlite3 73 | db.sqlite3-journal 74 | 75 | # Flask stuff: 76 | instance/ 77 | .webassets-cache 78 | 79 | # Scrapy stuff: 80 | .scrapy 81 | 82 | # Sphinx documentation 83 | docs/_build/ 84 | 85 | # PyBuilder 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # IPython 92 | profile_default/ 93 | ipython_config.py 94 | 95 | # pyenv 96 | .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 106 | __pypackages__/ 107 | 108 | # Celery stuff 109 | celerybeat-schedule 110 | celerybeat.pid 111 | 112 | # SageMath parsed files 113 | *.sage.py 114 | 115 | # Environments 116 | .env 117 | .venv 118 | env/ 119 | venv/ 120 | ENV/ 121 | env.bak/ 122 | venv.bak/ 123 | 124 | # Spyder project settings 125 | .spyderproject 126 | .spyproject 127 | 128 | # Rope project settings 129 | .ropeproject 130 | 131 | # mkdocs documentation 132 | /site 133 | 134 | # mypy 135 | .mypy_cache/ 136 | .dmypy.json 137 | dmypy.json 138 | 139 | # Pyre type checker 140 | .pyre/ 141 | /minigames/Minigames/ 142 | /minigames/Minigames/ 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Wouter Huybrechts 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 | # MiniGamesBot 2 | 3 | A Python Discord bot with different kinds of minigames and keeps statistics per minigame and player. 4 | 5 | ## Discord Bot Commands 6 | 7 | ### Miscellaneous 8 | 9 | **?help** *command* 10 | — Gives the help message. 11 | **?info** 12 | — Shows some information about this bot. 13 | **?rules** minigame 14 | — Shows the rules for the given minigame. 15 | **?stats** *@player* 16 | — Shows stats for all minigames for yourself or for the tagged player. 17 | **?set_prefix** new prefix 18 | — Set a new prefix for this bot. 19 | **?bug** bug description 20 | — Report a bug with a description as argument. 21 | 22 | ### Minigames 23 | 24 | **?hangman** 25 | — Play hangman against the bot. 26 | **?scramble** 27 | — Unscramble the letters of a random word. 28 | **?connect4** @player2 29 | — Play connect4 against another player. 30 | **?quiz** 31 | — Answer a random question to a category of your choice. 32 | **?blackjack** 33 | — Play a game of blackjack against the bot as dealer. 34 | **?chess** @player2 35 | — Play chess against another player. 36 | **?flood** 37 | — Get the grid to turn in to one color by iteratively flooding it. 38 | **?mastermind** 39 | — Play mastermind against the bot. 40 | **?akinator** 41 | — Start the akinator to guess with yes/no questions what character you are thinking of. 42 | 43 | ![flood minigame](https://raw.githubusercontent.com/whuybrec/MiniGamesBot/master/images/flood.png) 44 | 45 | Arguments in italic are optional. 46 | There are 5 categories in the quiz minigame: General knowledge, Sports, Video Games, Music, Films. 47 | 48 | This bot keeps statistics for players and minigames. It uploads the database file to a discord channel every once in a 49 | while as a backup. (same for prefixes.json file) 50 | 51 | ## Requirements 52 | 53 | Check requirements.txt for a list of all the libraries that you need. 54 | To install them all: 55 | 56 | > pip install -r requirements.txt 57 | 58 | Additionally, you need to install 'svgexport' from https://www.npmjs.com/package/svgexport. This is necessary to convert 59 | the svg (given by chess library) to a PNG, so it can be uploaded to Discord. 60 | 61 | IMPORTANT in case you run this on a Raspberry Pi: 62 | 63 | - Install node 64 | - Install chromium-browser 65 | - Install puppeteer 66 | - Install svgexport 67 | - In svgexport go to render.js, then edit lines 21 to 23 like this: 68 | 69 | ``` 70 | var browser = await puppeteer.launch({ 71 | product: 'chrome', 72 | executablePath: '/usr/bin/chromium-browser' 73 | headless: true, 74 | args: ['--no-sandbox', '--font-render-hinting=none'] 75 | }); 76 | ``` 77 | 78 | assuming chromium-browser is located in in '/usr/bin' 79 | 80 | ## How to use? 81 | 82 | You are allowed to use this code. A donation is appreciated but not necessary in that case. (link below) 83 | 84 | 1. Create a Discord bot on the discord developers webpage. 85 | 2. Clone this repository. 86 | 3. Create file 'private.py' in folder 'discordbot.utils' and put these dictionaries in there and replace the values 87 | accordingly. (Topgg can remain emtpy) 88 | 89 | ``` 90 | TOPGG = { 91 | "TOKEN": "" 92 | } 93 | 94 | DISCORD = { 95 | "TOKEN": "YOUR DISCORD BOT TOKEN HERE", 96 | "DEVS": [00000000000], # your discord ID in here 97 | "BOT_ID": 00000000000, # bot discord ID here 98 | # a channel ID of your choice for these 99 | "STATISTICS_CHANNEL": 00000000000, 100 | "STACK_CHANNEL": 00000000000, 101 | "BACKUP_CHANNEL": 00000000000, 102 | "BUG_REPORT_CHANNEL": 00000000000, 103 | "ERROR_CHANNEL": 00000000000 104 | } 105 | ``` 106 | 107 | 4. Run 'main.py' 108 | 109 | If you have any trouble with this or you have suggestions/remarks, don't hesitate to contact me. 110 | 111 | ## About me 112 | 113 | My name is Wouter Huybrechts. I study computer science at the KU Leuven in Belgium. My hobbies are: coding, gaming, 114 | cycling, badminton, watching series & movies. If you wish to make a donation, you can buy me a coffee 115 | here: https://www.buymeacoffee.com/whuybrec 116 | 117 | -------------------------------------------------------------------------------- /bin/10k words.txt: -------------------------------------------------------------------------------- 1 | people 2 | history 3 | way 4 | art 5 | world 6 | information 7 | map 8 | two 9 | family 10 | government 11 | health 12 | system 13 | computer 14 | meat 15 | year 16 | thanks 17 | music 18 | person 19 | reading 20 | method 21 | data 22 | food 23 | understanding 24 | theory 25 | law 26 | bird 27 | literature 28 | problem 29 | software 30 | control 31 | knowledge 32 | power 33 | ability 34 | economics 35 | love 36 | internet 37 | television 38 | science 39 | library 40 | nature 41 | fact 42 | product 43 | idea 44 | temperature 45 | investment 46 | area 47 | society 48 | activity 49 | story 50 | industry 51 | media 52 | thing 53 | oven 54 | community 55 | definition 56 | safety 57 | quality 58 | development 59 | language 60 | management 61 | player 62 | variety 63 | video 64 | week 65 | security 66 | country 67 | exam 68 | movie 69 | organization 70 | equipment 71 | physics 72 | analysis 73 | policy 74 | series 75 | thought 76 | basis 77 | boyfriend 78 | direction 79 | strategy 80 | technology 81 | army 82 | camera 83 | freedom 84 | paper 85 | environment 86 | child 87 | instance 88 | month 89 | truth 90 | marketing 91 | university 92 | writing 93 | article 94 | department 95 | difference 96 | goal 97 | news 98 | audience 99 | fishing 100 | growth 101 | income 102 | marriage 103 | user 104 | combination 105 | failure 106 | meaning 107 | medicine 108 | philosophy 109 | teacher 110 | communication 111 | night 112 | chemistry 113 | disease 114 | disk 115 | energy 116 | nation 117 | road 118 | role 119 | soup 120 | advertising 121 | location 122 | success 123 | addition 124 | apartment 125 | education 126 | math 127 | moment 128 | painting 129 | politics 130 | attention 131 | decision 132 | event 133 | property 134 | shopping 135 | student 136 | wood 137 | competition 138 | distribution 139 | entertainment 140 | office 141 | population 142 | president 143 | unit 144 | category 145 | cigarette 146 | context 147 | introduction 148 | opportunity 149 | performance 150 | driver 151 | flight 152 | length 153 | magazine 154 | newspaper 155 | relationship 156 | teaching 157 | cell 158 | dealer 159 | finding 160 | lake 161 | member 162 | message 163 | phone 164 | scene 165 | appearance 166 | association 167 | concept 168 | customer 169 | death 170 | discussion 171 | housing 172 | inflation 173 | insurance 174 | mood 175 | woman 176 | advice 177 | blood 178 | effort 179 | expression 180 | importance 181 | opinion 182 | payment 183 | reality 184 | responsibility 185 | situation 186 | skill 187 | statement 188 | wealth 189 | application 190 | city 191 | county 192 | depth 193 | estate 194 | foundation 195 | grandmother 196 | heart 197 | perspective 198 | photo 199 | recipe 200 | studio 201 | topic 202 | collection 203 | depression 204 | imagination 205 | passion 206 | percentage 207 | resource 208 | setting 209 | ad 210 | agency 211 | college 212 | connection 213 | criticism 214 | debt 215 | description 216 | memory 217 | patience 218 | secretary 219 | solution 220 | administration 221 | aspect 222 | attitude 223 | director 224 | personality 225 | psychology 226 | recommendation 227 | response 228 | selection 229 | storage 230 | version 231 | alcohol 232 | argument 233 | complaint 234 | contract 235 | emphasis 236 | highway 237 | loss 238 | membership 239 | possession 240 | preparation 241 | steak 242 | union 243 | agreement 244 | cancer 245 | currency 246 | employment 247 | engineering 248 | entry 249 | interaction 250 | mixture 251 | preference 252 | region 253 | republic 254 | tradition 255 | virus 256 | actor 257 | classroom 258 | delivery 259 | device 260 | difficulty 261 | drama 262 | election 263 | engine 264 | football 265 | guidance 266 | hotel 267 | owner 268 | priority 269 | protection 270 | suggestion 271 | tension 272 | variation 273 | anxiety 274 | atmosphere 275 | awareness 276 | bath 277 | bread 278 | candidate 279 | climate 280 | comparison 281 | confusion 282 | construction 283 | elevator 284 | emotion 285 | employee 286 | employer 287 | guest 288 | height 289 | leadership 290 | mall 291 | manager 292 | operation 293 | recording 294 | sample 295 | transportation 296 | charity 297 | cousin 298 | disaster 299 | editor 300 | efficiency 301 | excitement 302 | extent 303 | feedback 304 | guitar 305 | homework 306 | leader 307 | mom 308 | outcome 309 | permission 310 | presentation 311 | promotion 312 | reflection 313 | refrigerator 314 | resolution 315 | revenue 316 | session 317 | singer 318 | tennis 319 | basket 320 | bonus 321 | cabinet 322 | childhood 323 | church 324 | clothes 325 | coffee 326 | dinner 327 | drawing 328 | hair 329 | hearing 330 | initiative 331 | judgment 332 | lab 333 | measurement 334 | mode 335 | mud 336 | orange 337 | poetry 338 | police 339 | possibility 340 | procedure 341 | queen 342 | ratio 343 | relation 344 | restaurant 345 | satisfaction 346 | sector 347 | signature 348 | significance 349 | song 350 | tooth 351 | town 352 | vehicle 353 | volume 354 | wife 355 | accident 356 | airport 357 | appointment 358 | arrival 359 | assumption 360 | baseball 361 | chapter 362 | committee 363 | conversation 364 | database 365 | enthusiasm 366 | error 367 | explanation 368 | farmer 369 | gate 370 | girl 371 | hall 372 | historian 373 | hospital 374 | injury 375 | instruction 376 | maintenance 377 | manufacturer 378 | meal 379 | perception 380 | pie 381 | poem 382 | presence 383 | proposal 384 | reception 385 | replacement 386 | revolution 387 | river 388 | son 389 | speech 390 | tea 391 | village 392 | warning 393 | winner 394 | worker 395 | writer 396 | assistance 397 | breath 398 | buyer 399 | chest 400 | chocolate 401 | conclusion 402 | contribution 403 | cookie 404 | courage 405 | dad 406 | desk 407 | drawer 408 | establishment 409 | examination 410 | garbage 411 | grocery 412 | honey 413 | impression 414 | improvement 415 | independence 416 | insect 417 | inspection 418 | inspector 419 | king 420 | ladder 421 | menu 422 | penalty 423 | piano 424 | potato 425 | profession 426 | professor 427 | quantity 428 | reaction 429 | requirement 430 | salad 431 | sister 432 | supermarket 433 | tongue 434 | weakness 435 | wedding 436 | affair 437 | ambition 438 | analyst 439 | apple 440 | assignment 441 | assistant 442 | bathroom 443 | bedroom 444 | beer 445 | birthday 446 | celebration 447 | championship 448 | cheek 449 | client 450 | consequence 451 | departure 452 | diamond 453 | dirt 454 | ear 455 | fortune 456 | friendship 457 | funeral 458 | gene 459 | girlfriend 460 | hat 461 | indication 462 | intention 463 | lady 464 | midnight 465 | negotiation 466 | obligation 467 | passenger 468 | pizza 469 | platform 470 | poet 471 | pollution 472 | recognition 473 | reputation 474 | shirt 475 | sir 476 | speaker 477 | stranger 478 | surgery 479 | sympathy 480 | tale 481 | throat 482 | trainer 483 | uncle 484 | youth 485 | time 486 | work 487 | film 488 | water 489 | money 490 | example 491 | while 492 | business 493 | study 494 | game 495 | life 496 | form 497 | air 498 | day 499 | place 500 | number 501 | part 502 | field 503 | fish 504 | back 505 | process 506 | heat 507 | hand 508 | experience 509 | job 510 | book 511 | end 512 | point 513 | type 514 | home 515 | economy 516 | value 517 | body 518 | market 519 | guide 520 | interest 521 | state 522 | radio 523 | course 524 | company 525 | price 526 | size 527 | card 528 | list 529 | mind 530 | trade 531 | line 532 | care 533 | group 534 | risk 535 | word 536 | fat 537 | force 538 | key 539 | light 540 | training 541 | name 542 | school 543 | top 544 | amount 545 | level 546 | order 547 | practice 548 | research 549 | sense 550 | service 551 | piece 552 | web 553 | boss 554 | sport 555 | fun 556 | house 557 | page 558 | term 559 | test 560 | answer 561 | sound 562 | focus 563 | matter 564 | kind 565 | soil 566 | board 567 | oil 568 | picture 569 | access 570 | garden 571 | range 572 | rate 573 | reason 574 | future 575 | site 576 | demand 577 | exercise 578 | image 579 | case 580 | cause 581 | coast 582 | action 583 | age 584 | bad 585 | boat 586 | record 587 | result 588 | section 589 | building 590 | mouse 591 | cash 592 | class 593 | nothing 594 | period 595 | plan 596 | store 597 | tax 598 | side 599 | subject 600 | space 601 | rule 602 | stock 603 | weather 604 | chance 605 | figure 606 | man 607 | model 608 | source 609 | beginning 610 | earth 611 | program 612 | chicken 613 | design 614 | feature 615 | head 616 | material 617 | purpose 618 | question 619 | rock 620 | salt 621 | act 622 | birth 623 | car 624 | dog 625 | object 626 | scale 627 | sun 628 | note 629 | profit 630 | rent 631 | speed 632 | style 633 | war 634 | bank 635 | craft 636 | half 637 | inside 638 | outside 639 | standard 640 | bus 641 | exchange 642 | eye 643 | fire 644 | position 645 | pressure 646 | stress 647 | advantage 648 | benefit 649 | box 650 | frame 651 | issue 652 | step 653 | cycle 654 | face 655 | item 656 | metal 657 | paint 658 | review 659 | room 660 | screen 661 | structure 662 | view 663 | account 664 | ball 665 | discipline 666 | medium 667 | share 668 | balance 669 | bit 670 | black 671 | bottom 672 | choice 673 | gift 674 | impact 675 | machine 676 | shape 677 | tool 678 | wind 679 | address 680 | average 681 | career 682 | culture 683 | morning 684 | pot 685 | sign 686 | table 687 | task 688 | condition 689 | contact 690 | credit 691 | egg 692 | hope 693 | ice 694 | network 695 | north 696 | square 697 | attempt 698 | date 699 | effect 700 | link 701 | post 702 | star 703 | voice 704 | capital 705 | challenge 706 | friend 707 | self 708 | shot 709 | brush 710 | couple 711 | debate 712 | exit 713 | front 714 | function 715 | lack 716 | living 717 | plant 718 | plastic 719 | spot 720 | summer 721 | taste 722 | theme 723 | track 724 | wing 725 | brain 726 | button 727 | click 728 | desire 729 | foot 730 | gas 731 | influence 732 | notice 733 | rain 734 | wall 735 | base 736 | damage 737 | distance 738 | feeling 739 | pair 740 | savings 741 | staff 742 | sugar 743 | target 744 | text 745 | animal 746 | author 747 | budget 748 | discount 749 | file 750 | ground 751 | lesson 752 | minute 753 | officer 754 | phase 755 | reference 756 | register 757 | sky 758 | stage 759 | stick 760 | title 761 | trouble 762 | bowl 763 | bridge 764 | campaign 765 | character 766 | club 767 | edge 768 | evidence 769 | fan 770 | letter 771 | lock 772 | maximum 773 | novel 774 | option 775 | pack 776 | park 777 | plenty 778 | quarter 779 | skin 780 | sort 781 | weight 782 | baby 783 | background 784 | carry 785 | dish 786 | factor 787 | fruit 788 | glass 789 | joint 790 | master 791 | muscle 792 | red 793 | strength 794 | traffic 795 | trip 796 | vegetable 797 | appeal 798 | chart 799 | gear 800 | ideal 801 | kitchen 802 | land 803 | log 804 | mother 805 | net 806 | party 807 | principle 808 | relative 809 | sale 810 | season 811 | signal 812 | spirit 813 | street 814 | tree 815 | wave 816 | belt 817 | bench 818 | commission 819 | copy 820 | drop 821 | minimum 822 | path 823 | progress 824 | project 825 | sea 826 | south 827 | status 828 | stuff 829 | ticket 830 | tour 831 | angle 832 | blue 833 | breakfast 834 | confidence 835 | daughter 836 | degree 837 | doctor 838 | dot 839 | dream 840 | duty 841 | essay 842 | father 843 | fee 844 | finance 845 | hour 846 | juice 847 | limit 848 | luck 849 | milk 850 | mouth 851 | peace 852 | pipe 853 | seat 854 | stable 855 | storm 856 | substance 857 | team 858 | trick 859 | afternoon 860 | bat 861 | beach 862 | blank 863 | catch 864 | chain 865 | consideration 866 | cream 867 | crew 868 | detail 869 | gold 870 | interview 871 | kid 872 | mark 873 | match 874 | mission 875 | pain 876 | pleasure 877 | score 878 | screw 879 | sex 880 | shop 881 | shower 882 | suit 883 | tone 884 | window 885 | agent 886 | band 887 | block 888 | bone 889 | calendar 890 | cap 891 | coat 892 | contest 893 | corner 894 | court 895 | cup 896 | district 897 | door 898 | east 899 | finger 900 | garage 901 | guarantee 902 | hole 903 | hook 904 | implement 905 | layer 906 | lecture 907 | lie 908 | manner 909 | meeting 910 | nose 911 | parking 912 | partner 913 | profile 914 | respect 915 | rice 916 | routine 917 | schedule 918 | swimming 919 | telephone 920 | tip 921 | winter 922 | airline 923 | bag 924 | battle 925 | bed 926 | bill 927 | bother 928 | cake 929 | code 930 | curve 931 | designer 932 | dimension 933 | dress 934 | ease 935 | emergency 936 | evening 937 | extension 938 | farm 939 | fight 940 | gap 941 | grade 942 | holiday 943 | horror 944 | horse 945 | host 946 | husband 947 | loan 948 | mistake 949 | mountain 950 | nail 951 | noise 952 | occasion 953 | package 954 | patient 955 | pause 956 | phrase 957 | proof 958 | race 959 | relief 960 | sand 961 | sentence 962 | shoulder 963 | smoke 964 | stomach 965 | string 966 | tourist 967 | towel 968 | vacation 969 | west 970 | wheel 971 | wine 972 | arm 973 | aside 974 | associate 975 | bet 976 | blow 977 | border 978 | branch 979 | breast 980 | brother 981 | buddy 982 | bunch 983 | chip 984 | coach 985 | cross 986 | document 987 | draft 988 | dust 989 | expert 990 | floor 991 | god 992 | golf 993 | habit 994 | iron 995 | judge 996 | knife 997 | landscape 998 | league 999 | mail 1000 | mess 1001 | native 1002 | opening 1003 | parent 1004 | pattern 1005 | pin 1006 | pool 1007 | pound 1008 | request 1009 | salary 1010 | shame 1011 | shelter 1012 | shoe 1013 | silver 1014 | tackle 1015 | tank 1016 | trust 1017 | assist 1018 | bake 1019 | bar 1020 | bell 1021 | bike 1022 | blame 1023 | boy 1024 | brick 1025 | chair 1026 | closet 1027 | clue 1028 | collar 1029 | comment 1030 | conference 1031 | devil 1032 | diet 1033 | fear 1034 | fuel 1035 | glove 1036 | jacket 1037 | lunch 1038 | monitor 1039 | mortgage 1040 | nurse 1041 | pace 1042 | panic 1043 | peak 1044 | plane 1045 | reward 1046 | row 1047 | sandwich 1048 | shock 1049 | spite 1050 | spray 1051 | surprise 1052 | till 1053 | transition 1054 | weekend 1055 | welcome 1056 | yard 1057 | alarm 1058 | bend 1059 | bicycle 1060 | bite 1061 | blind 1062 | bottle 1063 | cable 1064 | candle 1065 | clerk 1066 | cloud 1067 | concert 1068 | counter 1069 | flower 1070 | grandfather 1071 | harm 1072 | knee 1073 | lawyer 1074 | leather 1075 | load 1076 | mirror 1077 | neck 1078 | pension 1079 | plate 1080 | purple 1081 | ruin 1082 | ship 1083 | skirt 1084 | slice 1085 | snow 1086 | specialist 1087 | stroke 1088 | switch 1089 | trash 1090 | tune 1091 | zone 1092 | anger 1093 | award 1094 | bid 1095 | bitter 1096 | boot 1097 | bug 1098 | camp 1099 | candy 1100 | carpet 1101 | cat 1102 | champion 1103 | channel 1104 | clock 1105 | comfort 1106 | cow 1107 | crack 1108 | engineer 1109 | entrance 1110 | fault 1111 | grass 1112 | guy 1113 | hell 1114 | highlight 1115 | incident 1116 | island 1117 | joke 1118 | jury 1119 | leg 1120 | lip 1121 | mate 1122 | motor 1123 | nerve 1124 | passage 1125 | pen 1126 | pride 1127 | priest 1128 | prize 1129 | promise 1130 | resident 1131 | resort 1132 | ring 1133 | roof 1134 | rope 1135 | sail 1136 | scheme 1137 | script 1138 | sock 1139 | station 1140 | toe 1141 | tower 1142 | truck 1143 | witness 1144 | a 1145 | you 1146 | it 1147 | can 1148 | will 1149 | if 1150 | one 1151 | many 1152 | most 1153 | other 1154 | use 1155 | make 1156 | good 1157 | look 1158 | help 1159 | go 1160 | great 1161 | being 1162 | few 1163 | might 1164 | still 1165 | public 1166 | read 1167 | keep 1168 | start 1169 | give 1170 | human 1171 | local 1172 | general 1173 | she 1174 | specific 1175 | long 1176 | play 1177 | feel 1178 | high 1179 | tonight 1180 | put 1181 | common 1182 | set 1183 | change 1184 | simple 1185 | past 1186 | big 1187 | possible 1188 | particular 1189 | today 1190 | major 1191 | personal 1192 | current 1193 | national 1194 | cut 1195 | natural 1196 | physical 1197 | show 1198 | try 1199 | check 1200 | second 1201 | call 1202 | move 1203 | pay 1204 | let 1205 | increase 1206 | single 1207 | individual 1208 | turn 1209 | ask 1210 | buy 1211 | guard 1212 | hold 1213 | main 1214 | offer 1215 | potential 1216 | professional 1217 | international 1218 | travel 1219 | cook 1220 | alternative 1221 | following 1222 | special 1223 | working 1224 | whole 1225 | dance 1226 | excuse 1227 | cold 1228 | commercial 1229 | low 1230 | purchase 1231 | deal 1232 | primary 1233 | worth 1234 | fall 1235 | necessary 1236 | positive 1237 | produce 1238 | search 1239 | present 1240 | spend 1241 | talk 1242 | creative 1243 | tell 1244 | cost 1245 | drive 1246 | green 1247 | support 1248 | glad 1249 | remove 1250 | return 1251 | run 1252 | complex 1253 | due 1254 | effective 1255 | middle 1256 | regular 1257 | reserve 1258 | independent 1259 | leave 1260 | original 1261 | reach 1262 | rest 1263 | serve 1264 | watch 1265 | beautiful 1266 | charge 1267 | active 1268 | break 1269 | negative 1270 | safe 1271 | stay 1272 | visit 1273 | visual 1274 | affect 1275 | cover 1276 | report 1277 | rise 1278 | walk 1279 | white 1280 | beyond 1281 | junior 1282 | pick 1283 | unique 1284 | anything 1285 | classic 1286 | final 1287 | lift 1288 | mix 1289 | private 1290 | stop 1291 | teach 1292 | western 1293 | concern 1294 | familiar 1295 | fly 1296 | official 1297 | broad 1298 | comfortable 1299 | gain 1300 | maybe 1301 | rich 1302 | save 1303 | stand 1304 | young 1305 | fail 1306 | heavy 1307 | hello 1308 | lead 1309 | listen 1310 | valuable 1311 | worry 1312 | handle 1313 | leading 1314 | meet 1315 | release 1316 | sell 1317 | finish 1318 | normal 1319 | press 1320 | ride 1321 | secret 1322 | spread 1323 | spring 1324 | tough 1325 | wait 1326 | brown 1327 | deep 1328 | display 1329 | flow 1330 | hit 1331 | objective 1332 | shoot 1333 | touch 1334 | cancel 1335 | chemical 1336 | cry 1337 | dump 1338 | extreme 1339 | push 1340 | conflict 1341 | eat 1342 | fill 1343 | formal 1344 | jump 1345 | kick 1346 | opposite 1347 | pass 1348 | pitch 1349 | remote 1350 | total 1351 | treat 1352 | vast 1353 | abuse 1354 | beat 1355 | burn 1356 | deposit 1357 | print 1358 | raise 1359 | sleep 1360 | somewhere 1361 | advance 1362 | anywhere 1363 | consist 1364 | dark 1365 | double 1366 | draw 1367 | equal 1368 | fix 1369 | hire 1370 | internal 1371 | join 1372 | kill 1373 | sensitive 1374 | tap 1375 | win 1376 | attack 1377 | claim 1378 | constant 1379 | drag 1380 | drink 1381 | guess 1382 | minor 1383 | pull 1384 | raw 1385 | soft 1386 | solid 1387 | wear 1388 | weird 1389 | wonder 1390 | annual 1391 | count 1392 | dead 1393 | doubt 1394 | feed 1395 | forever 1396 | impress 1397 | nobody 1398 | repeat 1399 | round 1400 | sing 1401 | slide 1402 | strip 1403 | whereas 1404 | wish 1405 | combine 1406 | command 1407 | dig 1408 | divide 1409 | equivalent 1410 | hang 1411 | hunt 1412 | initial 1413 | march 1414 | mention 1415 | smell 1416 | spiritual 1417 | survey 1418 | tie 1419 | adult 1420 | brief 1421 | crazy 1422 | escape 1423 | gather 1424 | hate 1425 | prior 1426 | repair 1427 | rough 1428 | sad 1429 | scratch 1430 | sick 1431 | strike 1432 | employ 1433 | external 1434 | hurt 1435 | illegal 1436 | laugh 1437 | lay 1438 | mobile 1439 | nasty 1440 | ordinary 1441 | respond 1442 | royal 1443 | senior 1444 | split 1445 | strain 1446 | struggle 1447 | swim 1448 | train 1449 | upper 1450 | wash 1451 | yellow 1452 | convert 1453 | crash 1454 | dependent 1455 | fold 1456 | funny 1457 | grab 1458 | hide 1459 | miss 1460 | permit 1461 | quote 1462 | recover 1463 | resolve 1464 | roll 1465 | sink 1466 | slip 1467 | spare 1468 | suspect 1469 | sweet 1470 | swing 1471 | twist 1472 | upstairs 1473 | usual 1474 | abroad 1475 | brave 1476 | calm 1477 | concentrate 1478 | estimate 1479 | grand 1480 | male 1481 | mine 1482 | prompt 1483 | quiet 1484 | refuse 1485 | regret 1486 | reveal 1487 | rush 1488 | shake 1489 | shift 1490 | shine 1491 | steal 1492 | suck 1493 | surround 1494 | anybody 1495 | bear 1496 | brilliant 1497 | dare 1498 | dear 1499 | delay 1500 | drunk 1501 | female 1502 | hurry 1503 | inevitable 1504 | invite 1505 | kiss 1506 | neat 1507 | pop 1508 | punch 1509 | quit 1510 | reply 1511 | representative 1512 | resist 1513 | rip 1514 | rub 1515 | silly 1516 | smile 1517 | spell 1518 | stretch 1519 | stupid 1520 | tear 1521 | temporary 1522 | tomorrow 1523 | wake 1524 | wrap 1525 | yesterday -------------------------------------------------------------------------------- /discordbot/categories/__init__.py: -------------------------------------------------------------------------------- 1 | from .developer import Developer 2 | from .minigames import Minigames 3 | from .miscellaneous import Miscellaneous 4 | -------------------------------------------------------------------------------- /discordbot/categories/category.py: -------------------------------------------------------------------------------- 1 | class Category: 2 | name = "nameCategory" 3 | 4 | @classmethod 5 | def has_permission(cls, user_id): 6 | return True 7 | -------------------------------------------------------------------------------- /discordbot/categories/developer.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.category import Category 2 | from discordbot.utils.private import DISCORD 3 | 4 | 5 | class Developer(Category): 6 | name = "developer" 7 | 8 | @classmethod 9 | def has_permission(cls, user_id): 10 | if user_id in DISCORD["DEVS"]: 11 | return True 12 | return False 13 | -------------------------------------------------------------------------------- /discordbot/categories/minigames.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.category import Category 2 | 3 | 4 | class Minigames(Category): 5 | name = "minigames" 6 | -------------------------------------------------------------------------------- /discordbot/categories/miscellaneous.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.category import Category 2 | 3 | 4 | class Miscellaneous(Category): 5 | name = "miscellaneous" 6 | -------------------------------------------------------------------------------- /discordbot/commands/command.py: -------------------------------------------------------------------------------- 1 | class Command: 2 | bot = None 3 | name = "CommandName" 4 | help = "CommandHelp" 5 | brief = "CommandBrief" 6 | args = "CommandArgs" 7 | category = "CommandCategory" 8 | 9 | @classmethod 10 | def add_command(cls, bot): 11 | cls.bot = bot 12 | cls.bot.remove_command(cls.name) 13 | cls.bot.command(name=cls.name, brief=cls.brief, usage=cls.args)(cls.handler) 14 | 15 | @classmethod 16 | async def handler(cls, context): 17 | missing_permissions = cls.bot.get_missing_permissions(context) 18 | if len(missing_permissions) > 0: 19 | await cls.bot.send_missing_permissions(context, missing_permissions) 20 | return 21 | 22 | await cls.invoke(context) 23 | 24 | @classmethod 25 | async def invoke(cls, context): 26 | pass 27 | 28 | @classmethod 29 | def has_permission(cls, user_id): 30 | return True 31 | -------------------------------------------------------------------------------- /discordbot/commands/developer/clear.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.developer import Developer 2 | from discordbot.commands.command import Command 3 | from discordbot.utils.private import DISCORD 4 | 5 | 6 | class ClearCommand(Command): 7 | bot = None 8 | name = "clear" 9 | help = "SKIBIDI BOP MM DADA" 10 | brief = "Deletes the last x messages in this channel." 11 | args = "amount" 12 | category = Developer 13 | 14 | @classmethod 15 | async def invoke(cls, context): 16 | args = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 17 | try: 18 | if cls.has_permission(context.message.author.id): 19 | await context.channel.purge(limit=int(args)) 20 | except Exception as e: 21 | await context.channel.send(e) 22 | await context.channel.send("Yeah you fucked up mate.") 23 | 24 | @classmethod 25 | def has_permission(cls, user_id): 26 | if user_id in DISCORD["DEVS"]: 27 | return True 28 | return False 29 | -------------------------------------------------------------------------------- /discordbot/commands/developer/delete.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.developer import Developer 2 | from discordbot.commands.command import Command 3 | from discordbot.utils.private import DISCORD 4 | 5 | 6 | class DeleteCommand(Command): 7 | bot = None 8 | name = "delete" 9 | help = "It should be pretty damn clear you boomer...." 10 | brief = "Deletes the message with given msg id." 11 | args = "message_id" 12 | category = Developer 13 | 14 | @classmethod 15 | async def invoke(cls, context): 16 | args = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 17 | 18 | if cls.has_permission(context.message.author.id): 19 | msg = await context.message.channel.fetch_message(int(args)) 20 | await msg.delete() 21 | await context.message.delete() 22 | 23 | @classmethod 24 | def has_permission(cls, user_id): 25 | if user_id in DISCORD["DEVS"]: 26 | return True 27 | return False 28 | -------------------------------------------------------------------------------- /discordbot/commands/developer/games.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.developer import Developer 2 | from discordbot.commands.command import Command 3 | from discordbot.databasemanager import DatabaseManager 4 | from discordbot.utils.pager import Pager 5 | from discordbot.utils.private import DISCORD 6 | 7 | 8 | class GamesCommand(Command): 9 | bot = None 10 | name = "games" 11 | help = "Shows minigames statistics." 12 | brief = "ALL OF THE DATA." 13 | args = "" 14 | category = Developer 15 | 16 | @classmethod 17 | async def invoke(cls, context): 18 | if cls.has_permission(context.message.author.id): 19 | pages = [] 20 | 21 | table = DatabaseManager.get_formatted_stats_for_today_of_minigames() 22 | pages.append(f"Stats of today:\n```\n{table}\n```") 23 | 24 | table = DatabaseManager.get_formatted_weekly_stats_of_minigames() 25 | pages.append(f"Weekly stats:\n```\n{table}\n```") 26 | 27 | table = DatabaseManager.get_formatted_monthly_stats_of_minigames() 28 | pages.append(f"Monthly stats:\n```\n{table}\n```") 29 | 30 | table = DatabaseManager.get_formatted_yearly_stats_of_minigames() 31 | pages.append(f"Yearly stats:\n```\n{table}\n```") 32 | 33 | pager = Pager(cls.bot, context, pages) 34 | await pager.show() 35 | await pager.wait_for_user() 36 | 37 | @classmethod 38 | def has_permission(cls, user_id): 39 | if user_id in DISCORD["DEVS"]: 40 | return True 41 | return False 42 | -------------------------------------------------------------------------------- /discordbot/commands/developer/restart.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | from discordbot.categories.developer import Developer 5 | from discordbot.commands.command import Command 6 | from discordbot.utils.emojis import NUMBERS 7 | from discordbot.utils.private import DISCORD 8 | 9 | 10 | class RestartCommand(Command): 11 | name: str = "restart" 12 | help: str = "Restarts this bot. The Restarter:tm:, *smort* or f o r c e restarts." 13 | brief: str = "The Restarter:tm:, *smort* or f o r c e restarts." 14 | args: str = "" 15 | category: str = Developer 16 | 17 | @classmethod 18 | async def invoke(cls, context): 19 | if not cls.has_permission(context.message.author.id): 20 | return 21 | 22 | if cls.bot.game_manager.has_open_sessions(): 23 | await cls.smart_restart(context) 24 | 25 | try: 26 | await cls.bot.on_restart() 27 | await context.send(f"Restarting...") 28 | except Exception as e: 29 | print(e) 30 | await cls.bot.close() 31 | 32 | @classmethod 33 | async def smart_restart(cls, context): 34 | msg = await context.send(f"There are open sessions, are you sure?\n" 35 | f"{NUMBERS[1]}: **force restart**\n" 36 | f"{NUMBERS[2]}: **smart restart**\n") 37 | await msg.add_reaction(NUMBERS[1]) 38 | await msg.add_reaction(NUMBERS[2]) 39 | 40 | def check(r, u): 41 | return r.message.id == msg.id and u.id in DISCORD["DEVS"] 42 | 43 | try: 44 | reaction, user = await cls.bot.wait_for("reaction_add", check=check, timeout=60.0) 45 | except TimeoutError: 46 | return 47 | if reaction.emoji == NUMBERS[1]: 48 | await context.send(f"Force restarting...") 49 | return 50 | 51 | elif reaction.emoji == NUMBERS[2]: 52 | await context.send(f"Smart restarting...") 53 | cls.bot.game_manager.on_pending_update() 54 | start_time = time.time() 55 | while cls.bot.game_manager.has_open_sessions() and time.time() - start_time < 60 * 10: 56 | await asyncio.sleep(10) 57 | return 58 | 59 | @classmethod 60 | def has_permission(cls, user_id): 61 | if user_id in DISCORD["DEVS"]: 62 | return True 63 | return False 64 | -------------------------------------------------------------------------------- /discordbot/commands/developer/say.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.developer import Developer 2 | from discordbot.commands.command import Command 3 | from discordbot.utils.private import DISCORD 4 | 5 | 6 | class SayCommand(Command): 7 | bot = None 8 | name = "say" 9 | help = "Yeah you know what it does smh..." 10 | brief = "The bot sends a message that was given as argument." 11 | args = "some line" 12 | category = Developer 13 | 14 | @classmethod 15 | async def invoke(cls, context): 16 | args = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 17 | if cls.has_permission(context.message.author.id): 18 | await context.channel.send(args) 19 | await context.message.delete() 20 | 21 | @classmethod 22 | def has_permission(cls, user_id): 23 | if user_id in DISCORD["DEVS"]: 24 | return True 25 | return False 26 | -------------------------------------------------------------------------------- /discordbot/commands/developer/servers.py: -------------------------------------------------------------------------------- 1 | import calendar 2 | from datetime import date 3 | 4 | from discordbot.categories.developer import Developer 5 | from discordbot.commands.command import Command 6 | from discordbot.databasemanager import DatabaseManager 7 | from discordbot.utils.pager import Pager 8 | from discordbot.utils.private import DISCORD 9 | from generic.formatting import create_table 10 | 11 | 12 | class ServersCommand(Command): 13 | bot = None 14 | name = "servers" 15 | help = "fetch server join/leave data." 16 | brief = "fetch server join/leave data." 17 | args = "" 18 | category = Developer 19 | 20 | @classmethod 21 | async def invoke(cls, context): 22 | if cls.has_permission(context.message.author.id): 23 | pages = [] 24 | today = date.today() 25 | 26 | lists = [["Day", "Joined", "Left", "Diff"]] 27 | joined_servers = DatabaseManager.get_daily_stats_for_servers_of_month("JOIN", today) 28 | left_servers = DatabaseManager.get_daily_stats_for_servers_of_month("LEAVE", today) 29 | num_days = calendar.monthrange(today.year, today.month)[1] 30 | days = [date(today.year, today.month, day) for day in range(1, num_days + 1)] 31 | for day in days: 32 | joined_on_day = joined_servers[day.strftime("%Y-%m-%d")] 33 | left_on_day = left_servers[day.strftime("%Y-%m-%d")] 34 | if len(joined_on_day) != 0 or len(left_on_day) != 0: 35 | lists.append([day.strftime("%d-%m"), len(joined_on_day), len(left_on_day), 36 | len(joined_on_day) - len(left_on_day)]) 37 | pages.append( 38 | "Daily:\n```\n" + create_table(*lists) + "\n```\nCurrent total: **" + str(len(cls.bot.guilds)) + "**") 39 | 40 | lists = [["Month", "Joined", "Left", "Diff"]] 41 | joined_servers = DatabaseManager.get_monthly_stats_for_servers_of_year("JOIN", today) 42 | left_servers = DatabaseManager.get_monthly_stats_for_servers_of_year("LEAVE", today) 43 | months = [date(today.year, month, 1) for month in range(1, 13)] 44 | for month in months: 45 | joined_on_month = joined_servers[month.strftime("%Y-%m")] 46 | left_on_month = left_servers[month.strftime("%Y-%m")] 47 | if len(joined_on_month) != 0 or len(left_on_month) != 0: 48 | lists.append([month.strftime("%B"), len(joined_on_month), len(left_on_month), 49 | len(joined_on_month) - len(left_on_month)]) 50 | pages.append( 51 | "Monthly:\n```\n" + create_table(*lists) + "\n```\nCurrent total: **" + str(len(cls.bot.guilds)) + "**") 52 | 53 | lists = [["Year", "Joined", "Left", "Diff"]] 54 | joined_servers = DatabaseManager.get_yearly_stats_for_servers("JOIN", today) 55 | left_servers = DatabaseManager.get_yearly_stats_for_servers("LEAVE", today) 56 | years = [date(year, 1, 1) for year in range(today.year - 4, today.year + 1)] 57 | for year in years: 58 | joined_on_year = joined_servers[year.strftime("%Y")] 59 | left_on_year = left_servers[year.strftime("%Y")] 60 | if len(joined_on_year) != 0 or len(left_on_year) != 0: 61 | lists.append( 62 | [year.year, len(joined_on_year), len(left_on_year), len(joined_on_year) - len(left_on_year)]) 63 | pages.append( 64 | "Yearly:\n```\n" + create_table(*lists) + "\n```\nCurrent total: **" + str(len(cls.bot.guilds)) + "**") 65 | 66 | pager = Pager(cls.bot, context, pages) 67 | await pager.show() 68 | await pager.wait_for_user() 69 | 70 | @classmethod 71 | def has_permission(cls, user_id): 72 | if user_id in DISCORD["DEVS"]: 73 | return True 74 | return False 75 | -------------------------------------------------------------------------------- /discordbot/commands/developer/temperature.py: -------------------------------------------------------------------------------- 1 | from gpiozero import CPUTemperature 2 | 3 | from discordbot.categories.developer import Developer 4 | from discordbot.commands.command import Command 5 | from discordbot.utils.private import DISCORD 6 | 7 | 8 | class TemperatureCommand(Command): 9 | bot = None 10 | name = "temp" 11 | help = "Shows the temperature o- UGH why do I even bother..." 12 | brief = "Shows the temperature of the raspberry pi" 13 | args = "" 14 | category = Developer 15 | 16 | @classmethod 17 | async def invoke(cls, context): 18 | if cls.has_permission(context.message.author.id): 19 | await context.channel.send(str(CPUTemperature().temperature) + "°C") 20 | 21 | @classmethod 22 | def has_permission(cls, user_id): 23 | if user_id in DISCORD["DEVS"]: 24 | return True 25 | return False 26 | -------------------------------------------------------------------------------- /discordbot/commands/developer/xec.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from discordbot.categories.developer import Developer 4 | from discordbot.commands.command import Command 5 | from discordbot.utils.private import DISCORD 6 | 7 | ctx = None 8 | bot = None 9 | 10 | 11 | class ExecuteCommand(Command): 12 | name: str = "exec" 13 | help: str = "Execute python code." 14 | brief: str = "Exec snek" 15 | args: str = "snek code" 16 | category: str = Developer 17 | 18 | @classmethod 19 | async def invoke(cls, context): 20 | if not cls.has_permission(context.message.author.id): 21 | return 22 | 23 | global bot, ctx 24 | bot = cls.bot 25 | ctx = context 26 | 27 | lines = context.message.content[len(cls.bot.prefix + cls.name) + 1:].split("\n") 28 | func = ["async def get():", 29 | " try:", 30 | " global returnv", 31 | " returnv = None", 32 | " returnv = await func()", 33 | " if returnv is not None:", 34 | " await ctx.send(f'```python\\n{repr(returnv)}\\n```')", 35 | " except Exception as e:", 36 | " await ctx.send(f'```python\\n{e}\\n```')", 37 | "asyncio.Task(get())", 38 | "async def func():", 39 | " " + "\n ".join(lines[:-1])] 40 | 41 | keywords = ["return ", 42 | "import ", 43 | "from ", 44 | "for ", 45 | "with ", 46 | "def ", 47 | "else "] 48 | 49 | if lines[-1].startswith(" ") or lines[-1].startswith("\t") or any( 50 | lines[-1].startswith(s) for s in keywords): 51 | func.append(" " + lines[-1]) 52 | else: 53 | func += [" try:", 54 | " return " + lines[-1], 55 | " except:", 56 | " " + lines[-1]] 57 | 58 | try: 59 | exec("\n".join(func), globals()) 60 | except Exception as e: 61 | await ctx.send("```python\n" + str(e) + "\n```") 62 | 63 | @classmethod 64 | def dummy(cls): 65 | """this is a dummy method so that the asyncio import doesn't get auto-removed""" 66 | asyncio.tasks = None 67 | 68 | @classmethod 69 | def has_permission(cls, user_id): 70 | if user_id in DISCORD["DEVS"]: 71 | return True 72 | return False 73 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/akinator_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.singleplayergames.akinator_dc import AkinatorDiscord 4 | from discordbot.user.singleplayersession import SinglePlayerSession 5 | 6 | 7 | class AkinatorCommand(Command): 8 | bot = None 9 | name = "akinator" 10 | help = "Start the akinator to guess with yes/no questions what character you are thinking of. Character can be fictional or real." 11 | brief = "Start the akinator to guess with yes/no questions what character you are thinking of." 12 | args = "" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | message = await context.send("Starting **akinator** minigame") 18 | 19 | session = SinglePlayerSession(message, "akinator", AkinatorDiscord, context.author) 20 | await cls.bot.game_manager.start_session(session) 21 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/blackjack_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.singleplayergames.blackjack_dc import BlackjackDiscord 4 | from discordbot.user.singleplayersession import SinglePlayerSession 5 | 6 | 7 | class BlackjackCommand(Command): 8 | bot = None 9 | name = "blackjack" 10 | help = "Play a game of blackjack against the bot as dealer, check out the rules with the rules command." 11 | brief = "Play a game of blackjack against the bot as dealer." 12 | args = "" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | message = await context.send("Starting **blackjack** minigame") 18 | 19 | session = SinglePlayerSession(message, "blackjack", BlackjackDiscord, context.author) 20 | await cls.bot.game_manager.start_session(session) 21 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/chess_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.multiplayergames.chess_dc import ChessDiscord 4 | from discordbot.user.multiplayersession import MultiPlayerSession 5 | 6 | 7 | class ChessCommand(Command): 8 | bot = None 9 | name = "chess" 10 | help = "Play chess against another player, check out the rules with the rules command." 11 | brief = "Play chess against another player." 12 | args = "@player2" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | args = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 18 | if len(args) == 0: 19 | await context.reply("You need to tag a second player to play with.") 20 | return 21 | 22 | import re 23 | try: 24 | player2 = await cls.bot.fetch_user(int(re.findall(r'\d+', args)[0])) 25 | except IndexError: 26 | await context.reply("You need to tag a second player to play with.") 27 | return 28 | 29 | if player2.bot: 30 | await context.reply("You can not start chess with a bot.") 31 | return 32 | 33 | message = await context.send("Starting **chess** minigame") 34 | session = MultiPlayerSession(message, "chess", ChessDiscord, context.author, player2) 35 | await cls.bot.game_manager.start_session(session) 36 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/connect4_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.multiplayergames.connect4_dc import Connect4Discord 4 | from discordbot.user.multiplayersession import MultiPlayerSession 5 | 6 | 7 | class Connect4Command(Command): 8 | bot = None 9 | name = "connect4" 10 | help = "Play connect4 against another player, check out the rules with the rules command." 11 | brief = "Play connect4 against another player." 12 | args = "@player2" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | args = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 18 | if len(args) == 0: 19 | await context.reply("You need to tag a second player to play with.") 20 | return 21 | 22 | import re 23 | try: 24 | player2 = await cls.bot.fetch_user(int(re.findall(r'\d+', args)[0])) 25 | except IndexError: 26 | await context.reply("You need to tag a second player to play with.") 27 | return 28 | 29 | if player2.bot: 30 | await context.reply("You can not start connect4 with a bot.") 31 | return 32 | 33 | message = await context.send("Starting **connect4** minigame") 34 | session = MultiPlayerSession(message, "connect4", Connect4Discord, context.author, player2) 35 | await cls.bot.game_manager.start_session(session) 36 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/flood_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.singleplayergames.flood_dc import FloodDiscord 4 | from discordbot.user.singleplayersession import SinglePlayerSession 5 | 6 | 7 | class FloodCommand(Command): 8 | bot = None 9 | name = "flood" 10 | help = "Get the grid to turn into one color by iteratively flooding it, check out the rules with the rules command." 11 | brief = "Get the grid to turn into one color by iteratively flooding it." 12 | args = "" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | message = await context.send("Starting **flood** minigame") 18 | 19 | session = SinglePlayerSession(message, "flood", FloodDiscord, context.author) 20 | await cls.bot.game_manager.start_session(session) 21 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/hangman_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.singleplayergames.hangman_dc import HangmanDiscord 4 | from discordbot.user.singleplayersession import SinglePlayerSession 5 | 6 | 7 | class HangmanCommand(Command): 8 | bot = None 9 | name = "hangman" 10 | help = "Play hangman against the bot, check out the rules with the rules command." 11 | brief = "Play hangman against the bot." 12 | args = "" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | message = await context.send("Starting **hangman** minigame") 18 | 19 | session = SinglePlayerSession(message, "hangman", HangmanDiscord, context.author) 20 | await cls.bot.game_manager.start_session(session) 21 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/mastermind_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.singleplayergames.mastermind_dc import MastermindDiscord 4 | from discordbot.user.singleplayersession import SinglePlayerSession 5 | 6 | 7 | class MastermindCommand(Command): 8 | bot = None 9 | name = "mastermind" 10 | help = "Play mastermind against the bot, check out the rules with the rules command." 11 | brief = "Play mastermind against the bot." 12 | args = "" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | message = await context.send("Starting **mastermind** minigame") 18 | 19 | session = SinglePlayerSession(message, "mastermind", MastermindDiscord, context.author) 20 | await cls.bot.game_manager.start_session(session) 21 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/quiz_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.singleplayergames.quiz_dc import QuizDiscord 4 | from discordbot.user.singleplayersession import SinglePlayerSession 5 | 6 | 7 | class QuizCommand(Command): 8 | bot = None 9 | name = "quiz" 10 | help = "Answer a random question of a category of your choice, check out the different categories with the rules command." 11 | brief = "Answer a random question of a category of your choice." 12 | args = "" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | message = await context.send("Starting **quiz**") 18 | 19 | session = SinglePlayerSession(message, "quiz", QuizDiscord, context.author) 20 | await cls.bot.game_manager.start_session(session) 21 | -------------------------------------------------------------------------------- /discordbot/commands/minigames/scramble_cmd.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.minigames import Minigames 2 | from discordbot.commands.command import Command 3 | from discordbot.discordminigames.singleplayergames.scramble_dc import ScrambleDiscord 4 | from discordbot.user.singleplayersession import SinglePlayerSession 5 | 6 | 7 | class ScrambleCommand(Command): 8 | bot = None 9 | name = "scramble" 10 | help = "Unscramble the letters of a random word, check out the rules with the rules command." 11 | brief = "Unscramble the letters of a random word." 12 | args = "" 13 | category = Minigames 14 | 15 | @classmethod 16 | async def invoke(cls, context): 17 | message = await context.send("Starting **scramble** minigame") 18 | 19 | session = SinglePlayerSession(message, "scramble", ScrambleDiscord, context.author) 20 | await cls.bot.game_manager.start_session(session) 21 | -------------------------------------------------------------------------------- /discordbot/commands/miscellaneous/bug_report.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.miscellaneous import Miscellaneous 2 | from discordbot.commands.command import Command 3 | from discordbot.utils.private import DISCORD 4 | 5 | 6 | class BugCommand(Command): 7 | bot = None 8 | name = "bug" 9 | help = "If you found a bug, then you can report it to the developer. Please give detailed information (an image is possible) as argument so the bug can be resolved quickly." 10 | brief = "Report a bug with a description as argument." 11 | args = "description" 12 | category = Miscellaneous 13 | 14 | @classmethod 15 | async def invoke(cls, context): 16 | bug = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 17 | if len(bug) == 0: 18 | await context.reply("You need to provide a bug description.") 19 | return 20 | 21 | channel = cls.bot.get_channel(DISCORD["BUG_REPORT_CHANNEL"]) 22 | try: 23 | picture = context.message.attachments[0].url 24 | await channel.send(picture) 25 | except Exception as e: 26 | print(e) 27 | await channel.send(context.author.name + " REPORTS: " + bug + "\n" 28 | + "id: " + str(context.author.id) + "\n" 29 | + "guild: " + str(context.guild.id) + "\n" 30 | + "channel: " + str(context.channel.id) + "\n") 31 | await context.reply("Bug successfully reported!") 32 | -------------------------------------------------------------------------------- /discordbot/commands/miscellaneous/help.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import discord 4 | 5 | from discordbot.categories.miscellaneous import Miscellaneous 6 | from discordbot.commands.command import Command 7 | from discordbot.utils.emojis import ARROW_UP, ARROW_DOWN, STOP 8 | 9 | 10 | class HelpCommand(Command): 11 | bot = None 12 | name = "help" 13 | help = "Gives the help message with a list of all commands." 14 | brief = "Gives the help message." 15 | args = "*command*" 16 | category = Miscellaneous 17 | 18 | @classmethod 19 | async def invoke(cls, context): 20 | # check if user asked help for specific command 21 | args = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 22 | if len(args) > 0: 23 | await cls.extended_help(context, args.split(" ")[0]) 24 | return 25 | 26 | content = cls.get_content(context, 0) 27 | msg: discord.Message = await context.send(content) 28 | await msg.add_reaction(ARROW_UP) 29 | await msg.add_reaction(ARROW_DOWN) 30 | await msg.add_reaction(STOP) 31 | 32 | await cls.wait_for_reaction(context, msg) 33 | 34 | @classmethod 35 | async def extended_help(cls, context, command_name): 36 | if str(context.channel.guild.id) in cls.bot.prefixes.keys(): 37 | prefix = cls.bot.prefixes[str(context.channel.guild.id)] 38 | else: 39 | prefix = cls.bot.prefix 40 | 41 | content = "" 42 | for command in cls.bot.my_commands: 43 | if command.name == command_name and command.category.has_permission(context.author.id): 44 | content += "```diff\n" \ 45 | f"+ {command.category.name}\n" \ 46 | "```" 47 | content += f"**{prefix}{command.name}** {command.args}\n" \ 48 | f" — {command.brief}\n" \ 49 | f"\n{command.help}" 50 | await context.send(content) 51 | return 52 | 53 | @classmethod 54 | async def wait_for_reaction(cls, context, help_msg, page=0): 55 | while True: 56 | def check(r, u): 57 | return u.id == context.message.author.id \ 58 | and (r.emoji == ARROW_DOWN or r.emoji == ARROW_UP or r.emoji == STOP) \ 59 | and r.message.id == help_msg.id 60 | 61 | try: 62 | reaction, user = await cls.bot.wait_for('reaction_add', timeout=60.0, check=check) 63 | if reaction.emoji == STOP: 64 | await help_msg.delete() 65 | break 66 | 67 | if reaction.emoji == ARROW_DOWN and page < (len(cls.bot.categories) - 1): 68 | new_page = page + 1 69 | while not cls.bot.categories[new_page].has_permission(context.author.id): 70 | new_page += 1 71 | if new_page > (len(cls.bot.categories) - 1): 72 | new_page = page 73 | break 74 | page = new_page 75 | elif reaction.emoji == ARROW_UP and page > 0: 76 | new_page = page - 1 77 | while not cls.bot.categories[new_page].has_permission(context.author.id): 78 | new_page -= 1 79 | if new_page < 0: 80 | new_page = page 81 | break 82 | page = new_page 83 | content = cls.get_content(context, page) 84 | await help_msg.edit(content=content) 85 | await help_msg.remove_reaction(reaction.emoji, user) 86 | except asyncio.TimeoutError: 87 | await help_msg.clear_reactions() 88 | break 89 | 90 | @classmethod 91 | def get_content(cls, context, page): 92 | if str(context.channel.guild.id) in cls.bot.prefixes.keys(): 93 | prefix = cls.bot.prefixes[str(context.channel.guild.id)] 94 | else: 95 | prefix = cls.bot.prefix 96 | 97 | content = "**__MiniGamesBot__**\n" 98 | for i in range(len(cls.bot.categories)): 99 | if not cls.bot.categories[i].has_permission(context.author.id): 100 | continue 101 | if page == i: 102 | content += "```diff\n" \ 103 | f"+ {cls.bot.categories[i].name}\n" \ 104 | "```" 105 | for command in cls.bot.my_commands: 106 | if command.category.name == cls.bot.categories[page].name: 107 | content += f"**{prefix}{command.name}** {command.args}\n" \ 108 | f" — {command.brief}\n" 109 | else: 110 | content += "```diff\n" \ 111 | f"- {cls.bot.categories[i].name}\n" \ 112 | "```" 113 | content += "\nArguments in *italic* are optional." \ 114 | f"\nType **{prefix}help command** for more info on a command." 115 | return content 116 | -------------------------------------------------------------------------------- /discordbot/commands/miscellaneous/info.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | from discordbot.categories.miscellaneous import Miscellaneous 5 | from discordbot.commands.command import Command 6 | from discordbot.utils.emojis import STOP 7 | 8 | 9 | class InfoCommand(Command): 10 | bot = None 11 | name = "info" 12 | help = "Shows link and information for minigames, statistics, the bot in general." 13 | brief = "Shows links and information about this bot." 14 | args = "" 15 | category = Miscellaneous 16 | 17 | @classmethod 18 | async def invoke(cls, context): 19 | content = "**__MiniGamesBot__**\n" \ 20 | "Use the **help command** to see the commands for this bot. " \ 21 | "Administrators can set a **different prefix** for the bot. " \ 22 | "Use the **bug command** to notify me for potential bugs. " \ 23 | "You can also **join my server** to tell me about it in there. " 24 | content += "\n\n**__Minigames__**\n" \ 25 | "All the minigames are played by **clicking reactions** the bot added. " \ 26 | f"The game automatically results in **loss** if user clicks {STOP} or has not reacted after **5 minutes**. " \ 27 | "Use the **rules command** to check out the rules for a minigame. " \ 28 | "There are currently 2 multiplayer games: connect4 & chess. " 29 | content += "\n\n**__Statistics__**\n" \ 30 | "This bot tracks the following per **player statistics** for all minigames: " \ 31 | "**wins**, **losses**, **draws**, **total games**, **unfinished games**, **total time played**. " \ 32 | "Unfinished games is the portion of losses where the 5 minute timer got reached. " \ 33 | "Use the **stats command** to ask for yours or another player's stats. " \ 34 | "Use the arrows to navigate through the **different periods**. " 35 | content += "\n\n**__Links__**\n" \ 36 | "Bot invite link: \n" \ 37 | "Leave a reaction or give a thumbs up if you like this bot here: \n" \ 38 | "If you wish to make a donation: https://www.buymeacoffee.com/whuybrec\n" \ 39 | "Github link: \n" \ 40 | "Invite to my server: https://discord.gg/hGeGsWp\n" 41 | content += f"\n*Uptime: {datetime.timedelta(seconds=round(time.time() - cls.bot.uptime))}*" 42 | await context.send(content) 43 | -------------------------------------------------------------------------------- /discordbot/commands/miscellaneous/rules.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.miscellaneous import Miscellaneous 2 | from discordbot.commands.command import Command 3 | from discordbot.utils.variables import * 4 | 5 | RULES = { 6 | "blackjack": BLACKJACK_RULES, 7 | "chess": CHESS_RULES, 8 | "hangman": HANGMAN_RULES, 9 | "quiz": QUIZ_RULES, 10 | "scramble": SCRAMBLE_RULES, 11 | "connect4": CONNECT4_RULES, 12 | "flood": FLOOD_RULES, 13 | "mastermind": MASTERMIND_RULES, 14 | "akinator": AKINATOR_RULES 15 | } 16 | 17 | 18 | class RulesCommand(Command): 19 | bot = None 20 | name = "rules" 21 | help = "Shows the rules for the minigame given as argument to the command." 22 | brief = "Shows the rules for the given minigame." 23 | args = "minigame" 24 | category = Miscellaneous 25 | 26 | @classmethod 27 | async def invoke(cls, context): 28 | args = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 29 | if len(args) == 0: 30 | await context.reply("Please specify a minigame for which you want to see the rules as argument.") 31 | return 32 | 33 | minigame = args.split(" ")[0].lower() 34 | try: 35 | rules = RULES[minigame] 36 | await context.reply(rules) 37 | except KeyError: 38 | await context.reply("Please specify a correct minigame for which you want to see the rules as argument.") 39 | -------------------------------------------------------------------------------- /discordbot/commands/miscellaneous/set_prefix.py: -------------------------------------------------------------------------------- 1 | from discordbot.categories.miscellaneous import Miscellaneous 2 | from discordbot.commands.command import Command 3 | from discordbot.utils.private import DISCORD 4 | 5 | 6 | class SetPrefixCommand(Command): 7 | bot = None 8 | name = "set_prefix" 9 | help = "Set a new prefix for this bot, prefix must be shorter than 10 characters." 10 | brief = "Set a new prefix for this bot." 11 | args = "new prefix" 12 | category = Miscellaneous 13 | 14 | @classmethod 15 | async def invoke(cls, context): 16 | if not context.channel.permissions_for(context.author).administrator and context.author.id not in DISCORD[ 17 | "DEVS"]: 18 | await context.reply("Only admins can change the prefix of the bot.") 19 | return 20 | 21 | prefix = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 22 | if len(prefix) == 0: 23 | await context.reply("You need to provide a new prefix.") 24 | return 25 | 26 | if len(prefix) > 10: 27 | await context.reply("Prefix can not be longer than 10 characters.") 28 | return 29 | 30 | if prefix == "?": 31 | cls.bot.prefix.pop(str(context.guild.id)) 32 | else: 33 | cls.bot.prefixes[str(context.guild.id)] = prefix 34 | await cls.bot.save_prefixes() 35 | await context.reply(f"The prefix of MiniGamesBot is now: **{prefix}**") 36 | -------------------------------------------------------------------------------- /discordbot/commands/miscellaneous/stats.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from discordbot.categories.miscellaneous import Miscellaneous 4 | from discordbot.commands.command import Command 5 | from discordbot.databasemanager import DatabaseManager 6 | from discordbot.utils.pager import Pager 7 | 8 | 9 | class StatsCommand(Command): 10 | bot = None 11 | name = "stats" 12 | help = "Shows yours (if no player was tagged as argument) or another player's statistics for all minigames." 13 | brief = "Shows stats for all minigames." 14 | args = "*@player*" 15 | category = Miscellaneous 16 | 17 | @classmethod 18 | async def invoke(cls, context): 19 | args = context.message.content[len(cls.bot.prefix) + len(cls.name) + 1:].lstrip() 20 | try: 21 | if len(args.lstrip()) > 0: 22 | player = await cls.bot.fetch_user(int(re.findall(r'\d+', args)[0])) 23 | else: 24 | player = context.author 25 | except IndexError: 26 | await context.reply("You need to tag the player you want to view the stats of.") 27 | return 28 | 29 | pages = [] 30 | table = DatabaseManager.get_formatted_stats_for_today_of_player(player.id) 31 | pages.append(f"Stats of today for **{player.name}**:\n```\n{table}\n```") 32 | 33 | table = DatabaseManager.get_formatted_weekly_stats_of_player(player.id) 34 | pages.append(f"Weekly stats for **{player.name}**:\n```\n{table}\n```") 35 | 36 | table = DatabaseManager.get_formatted_monthly_stats_of_player(player.id) 37 | pages.append(f"Monthly stats for **{player.name}**:\n```\n{table}\n```") 38 | 39 | table = DatabaseManager.get_formatted_yearly_stats_of_player(player.id) 40 | pages.append(f"Yearly stats for **{player.name}**:\n```\n{table}\n```") 41 | 42 | pager = Pager(cls.bot, context, pages) 43 | await pager.show() 44 | await pager.wait_for_user() 45 | -------------------------------------------------------------------------------- /discordbot/databasemanager.py: -------------------------------------------------------------------------------- 1 | import calendar 2 | from datetime import date, datetime, timedelta 3 | from time import time 4 | from zipfile import ZipFile 5 | 6 | import discord 7 | 8 | from discordbot.utils.private import DISCORD 9 | from generic.database import Database 10 | from generic.formatting import create_table 11 | 12 | DATABASE_FILE = "bin/minigames.db" 13 | 14 | 15 | class DatabaseManager: 16 | database: Database 17 | bot = None 18 | 19 | @classmethod 20 | def on_startup(cls, bot): 21 | cls.bot = bot 22 | cls.database = Database(DATABASE_FILE) 23 | cls.database.create_table( 24 | "players", 25 | [ 26 | "player_id integer", 27 | "minigame text", 28 | "time_stamp integer", 29 | "wins integer", 30 | "losses integer", 31 | "draws integer", 32 | "total_games integer", 33 | "time_played integer", 34 | "unfinished integer" 35 | ], 36 | [ 37 | "player_id", 38 | "minigame", 39 | "time_stamp" 40 | ] 41 | ) 42 | cls.database.create_table( 43 | "minigames", 44 | [ 45 | "server_id integer", 46 | "minigame text", 47 | "time_stamp integer", 48 | "wins integer", 49 | "losses integer", 50 | "draws integer", 51 | "total_games", 52 | "time_played integer", 53 | "unfinished integer" 54 | ], 55 | [ 56 | "server_id", 57 | "minigame", 58 | "time_stamp" 59 | ] 60 | ) 61 | 62 | cls.database.create_table( 63 | "servers", 64 | [ 65 | "server_id integer", 66 | "time_stamp integer", 67 | "event text" 68 | ], 69 | [ 70 | "server_id", 71 | "time_stamp", 72 | ] 73 | ) 74 | 75 | @classmethod 76 | def add_to_players_table(cls, data): 77 | cls.database.write("players", data) 78 | 79 | @classmethod 80 | def add_to_minigames_table(cls, data): 81 | cls.database.write("minigames", data) 82 | 83 | @classmethod 84 | def add_to_servers_table(cls, server_id, event): 85 | cls.database.write("servers", {"server_id": server_id, "time_stamp": time(), "event": event}) 86 | 87 | # PLAYERS 88 | 89 | @classmethod 90 | def get_all_time_stats_for_player(cls, player_id): 91 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 92 | "FROM players " \ 93 | "WHERE player_id={0} " \ 94 | "GROUP BY minigame;".format(player_id) 95 | return cls.query(q) 96 | 97 | @classmethod 98 | def get_stats_for_player_of_day(cls, player_id, date_): 99 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 100 | "FROM players " \ 101 | "WHERE player_id={0} AND strftime('%Y-%m-%d', time_stamp, 'unixepoch', 'localtime')='{1}' " \ 102 | "GROUP BY minigame;".format(player_id, date_) 103 | return cls.query(q) 104 | 105 | @classmethod 106 | def get_stats_for_player_of_week(cls, player_id, date_): 107 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 108 | "FROM players " \ 109 | "WHERE player_id={0} AND strftime('%W', time_stamp, 'unixepoch', 'localtime')='{1}'" \ 110 | "GROUP BY minigame".format(player_id, date_.strftime('%W')) 111 | return cls.query(q) 112 | 113 | @classmethod 114 | def get_stats_for_player_of_month(cls, player_id, date_): 115 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 116 | "FROM players " \ 117 | "WHERE player_id={0} AND strftime('%Y-%m', time_stamp, 'unixepoch', 'localtime')='{1}' " \ 118 | "GROUP BY minigame;".format(player_id, date_) 119 | return cls.query(q) 120 | 121 | @classmethod 122 | def get_stats_for_player_of_year(cls, player_id, year): 123 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 124 | "FROM players " \ 125 | "WHERE player_id={0} AND strftime('%Y', time_stamp, 'unixepoch', 'localtime')='{1}' " \ 126 | "GROUP BY minigame;".format(player_id, year) 127 | return cls.query(q) 128 | 129 | @classmethod 130 | def get_weekly_stats_for_player_of_month(cls, player_id, date_): 131 | weeks = [date(date_.year, date_.month, day) for day in range(1, 31, 7)] 132 | stats = dict() 133 | for week in weeks: 134 | stats[week.strftime("%W")] = cls.get_stats_for_player_of_week(player_id, week) 135 | return stats 136 | 137 | @classmethod 138 | def get_monthly_stats_for_player_of_year(cls, player_id, date_): 139 | months = [date(date_.year, month, 1) for month in range(1, 13)] 140 | stats = dict() 141 | for month in months: 142 | stats[month.strftime("%Y-%m")] = cls.get_stats_for_player_of_month(player_id, month.strftime("%Y-%m")) 143 | return stats 144 | 145 | @classmethod 146 | def get_yearly_stats_for_player(cls, player_id, date_): 147 | years = [date(year, 1, 1) for year in range(date_.year - 4, date_.year + 1)] 148 | stats = dict() 149 | for year in years: 150 | stats[year.strftime("%Y")] = cls.get_stats_for_player_of_year(player_id, year.strftime("%Y")) 151 | return stats 152 | 153 | # MINIGAMES 154 | 155 | @classmethod 156 | def get_all_time_stats_for_minigames(cls): 157 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 158 | "FROM minigames " \ 159 | "GROUP BY minigame;" 160 | return cls.query(q) 161 | 162 | @classmethod 163 | def get_stats_for_minigames_of_day(cls, date_): 164 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 165 | "FROM minigames " \ 166 | "WHERE strftime('%Y-%m-%d', time_stamp, 'unixepoch', 'localtime')='{0}'" \ 167 | "GROUP BY minigame".format(date_.strftime('%Y-%m-%d')) 168 | return cls.query(q) 169 | 170 | @classmethod 171 | def get_stats_for_minigames_of_week(cls, date_): 172 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 173 | "FROM minigames " \ 174 | "WHERE strftime('%W', time_stamp, 'unixepoch', 'localtime')='{0}'" \ 175 | "GROUP BY minigame".format(date_.strftime('%W')) 176 | return cls.query(q) 177 | 178 | @classmethod 179 | def get_stats_for_minigames_of_month(cls, date_): 180 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 181 | "FROM minigames " \ 182 | "WHERE strftime('%Y-%m', time_stamp, 'unixepoch', 'localtime')='{0}'" \ 183 | "GROUP BY minigame;".format(date_.strftime('%Y-%m')) 184 | return cls.query(q) 185 | 186 | @classmethod 187 | def get_stats_for_minigames_of_year(cls, date_): 188 | q = "SELECT minigame, SUM(wins), SUM(losses), SUM(draws), SUM(total_games), SUM(unfinished), SUM(time_played) " \ 189 | "FROM minigames " \ 190 | "WHERE strftime('%Y', time_stamp, 'unixepoch', 'localtime')='{0}'" \ 191 | "GROUP BY minigame;".format(date_.strftime('%Y')) 192 | return cls.query(q) 193 | 194 | @classmethod 195 | def get_average_played_minigames_of_month(cls, date_): 196 | q = "SELECT minigame, AVG(total_games) as avg " \ 197 | "FROM (" \ 198 | " SELECT minigame, SUM(total_games) as 'total_games', time_stamp as 'time_stamp' " \ 199 | " FROM minigames " \ 200 | " GROUP BY strftime('%Y-%m-%d', time_stamp, 'unixepoch', 'localtime'), minigame" \ 201 | ") " \ 202 | "WHERE strftime('%Y-%m', time_stamp, 'unixepoch', 'localtime')='{0}' AND strftime('%Y-%m-%d', time_stamp, 'unixepoch', 'localtime')!='{1}'" \ 203 | "GROUP BY minigame;".format(date_.strftime('%Y-%m'), date.today().strftime("%Y-%m-%d")) 204 | return cls.query(q) 205 | 206 | @classmethod 207 | def get_weekly_stats_for_minigames_of_month(cls, date_): 208 | weeks = [date(date_.year, date_.month, day) for day in range(1, 31, 7)] 209 | stats = dict() 210 | for week in weeks: 211 | stats[week.strftime("%W")] = cls.get_stats_for_minigames_of_week(week) 212 | return stats 213 | 214 | @classmethod 215 | def get_monthly_stats_for_minigames_of_year(cls, date_): 216 | months = [date(date_.year, month, 1) for month in range(1, 13)] 217 | stats = dict() 218 | for month in months: 219 | stats[month.strftime("%Y-%m")] = cls.get_stats_for_minigames_of_month(month) 220 | return stats 221 | 222 | @classmethod 223 | def get_yearly_stats_for_minigames(cls, date_): 224 | years = [date(year, 1, 1) for year in range(date_.year - 4, date_.year + 1)] 225 | stats = dict() 226 | for year in years: 227 | stats[year.strftime("%Y")] = cls.get_stats_for_minigames_of_year(year) 228 | return stats 229 | 230 | # SERVERS 231 | 232 | @classmethod 233 | def get_servers_of_day(cls, event, date_): 234 | q = "SELECT server_id " \ 235 | "FROM servers " \ 236 | "WHERE event='{0}' AND strftime('%Y-%m-%d', time_stamp, 'unixepoch', 'localtime')='{1}' " \ 237 | "GROUP BY server_id;".format(event, date_) 238 | return cls.query(q) 239 | 240 | @classmethod 241 | def get_servers_of_month(cls, event, date_): 242 | q = "SELECT server_id " \ 243 | "FROM servers " \ 244 | "WHERE event='{0}' AND strftime('%Y-%m', time_stamp, 'unixepoch', 'localtime')='{1}' " \ 245 | "GROUP BY server_id;".format(event, date_) 246 | return cls.query(q) 247 | 248 | @classmethod 249 | def get_servers_of_year(cls, event, year): 250 | q = "SELECT server_id " \ 251 | "FROM servers " \ 252 | "WHERE event='{0}' AND strftime('%Y', time_stamp, 'unixepoch', 'localtime')='{1}' " \ 253 | "GROUP BY server_id;".format(event, year) 254 | return cls.query(q) 255 | 256 | @classmethod 257 | def get_daily_stats_for_servers_of_month(cls, event, date_): 258 | num_days = calendar.monthrange(date_.year, date_.month)[1] 259 | days = [date(date_.year, date_.month, day) for day in range(1, num_days + 1)] 260 | stats = dict() 261 | for day in days: 262 | stats[day.strftime("%Y-%m-%d")] = cls.get_servers_of_day(event, day.strftime("%Y-%m-%d")) 263 | return stats 264 | 265 | @classmethod 266 | def get_monthly_stats_for_servers_of_year(cls, event, date_): 267 | months = [date(date_.year, month, 1) for month in range(1, 13)] 268 | stats = dict() 269 | for month in months: 270 | stats[month.strftime("%Y-%m")] = cls.get_servers_of_month(event, month.strftime("%Y-%m")) 271 | return stats 272 | 273 | @classmethod 274 | def get_yearly_stats_for_servers(cls, event, date_): 275 | years = [date(year, 1, 1) for year in range(date_.year - 4, date_.year + 1)] 276 | stats = dict() 277 | for year in years: 278 | stats[year.strftime("%Y")] = cls.get_servers_of_year(event, year.strftime("%Y")) 279 | return stats 280 | 281 | @classmethod 282 | def query(cls, query): 283 | return cls.database.query(query) 284 | 285 | # FORMATTED STATS 286 | 287 | @classmethod 288 | def get_formatted_stats_for_today_of_player(cls, player_id): 289 | lists = [["Game", "W", "L", "D", "Total", "Unfinished", "Time"]] 290 | mg_stats = cls.get_stats_for_player_of_day(player_id, date.today()) 291 | for stat in mg_stats: 292 | row = cls.manipulate(stat) 293 | lists.append(row) 294 | return create_table(*lists) 295 | 296 | @classmethod 297 | def get_formatted_weekly_stats_of_player(cls, player_id): 298 | lists = [["Week", "Game", "W", "L", "D", "Total", "Unfinished", "Time"]] 299 | mg_weekly_stats = cls.get_weekly_stats_for_player_of_month(player_id, date.today()) 300 | weeks = [date(date.today().year, date.today().month, day) for day in range(1, 31, 7)] 301 | for week in weeks: 302 | if mg_weekly_stats[week.strftime("%W")]: 303 | mg_stats = mg_weekly_stats[week.strftime("%W")] 304 | lists.append([week.strftime("%W"), "", "", "", "", "", ""]) 305 | for stat in mg_stats: 306 | row = cls.manipulate(stat) 307 | row.insert(0, "") 308 | lists.append(row) 309 | return create_table(*lists) 310 | 311 | @classmethod 312 | def get_formatted_monthly_stats_of_player(cls, player_id): 313 | lists = [["Month", "Game", "W", "L", "D", "Total", "Unfinished", "Time"]] 314 | mg_monthly_stats = cls.get_monthly_stats_for_player_of_year(player_id, date.today()) 315 | months = [date(date.today().year, month, 1) for month in range(1, 13)] 316 | for month in months: 317 | if mg_monthly_stats[month.strftime("%Y-%m")]: 318 | lists.append([month.strftime("%B"), "", "", "", "", "", ""]) 319 | mg_stats = mg_monthly_stats[month.strftime("%Y-%m")] 320 | for stat in mg_stats: 321 | row = cls.manipulate(stat) 322 | row.insert(0, "") 323 | lists.append(row) 324 | return create_table(*lists) 325 | 326 | @classmethod 327 | def get_formatted_yearly_stats_of_player(cls, player_id): 328 | lists = [["Year", "Game", "W", "L", "D", "Total", "Unfinished", "Time"]] 329 | mg_yearly_stats = cls.get_yearly_stats_for_player(player_id, date.today()) 330 | years = [date(year, 1, 1) for year in range(date.today().year - 4, date.today().year + 1)] 331 | for year in years: 332 | if mg_yearly_stats[year.strftime("%Y")]: 333 | lists.append([year.year, "", "", "", "", "", ""]) 334 | mg_stats = mg_yearly_stats[year.strftime("%Y")] 335 | for stat in mg_stats: 336 | row = cls.manipulate(stat) 337 | row.insert(0, "") 338 | lists.append(row) 339 | return create_table(*lists) 340 | 341 | @classmethod 342 | def get_formatted_stats_for_today_of_minigames(cls): 343 | lists = [["Game", "W", "L", "D", "Total", "AVG/M", "Unfinished", "Time"]] 344 | avg_stats = cls.get_average_played_minigames_of_month(date.today()) 345 | mg_stats = cls.get_stats_for_minigames_of_day(date.today()) 346 | stats = cls.merge_with_average(mg_stats, avg_stats) 347 | for row in stats: 348 | row = cls.manipulate(row) 349 | lists.append(row) 350 | return create_table(*lists) 351 | 352 | @classmethod 353 | def get_formatted_weekly_stats_of_minigames(cls): 354 | lists = [["Week", "Game", "W", "L", "D", "Total", "Unfinished", "Time"]] 355 | mg_weekly_stats = cls.get_weekly_stats_for_minigames_of_month(date.today()) 356 | weeks = [date(date.today().year, date.today().month, day) for day in range(1, 31, 7)] 357 | for week in weeks: 358 | if mg_weekly_stats[week.strftime("%W")]: 359 | mg_stats = mg_weekly_stats[week.strftime("%W")] 360 | lists.append([week.strftime("%W"), "", "", "", "", "", ""]) 361 | for stat in mg_stats: 362 | row = cls.manipulate(stat) 363 | row.insert(0, "") 364 | lists.append(row) 365 | return create_table(*lists) 366 | 367 | @classmethod 368 | def get_formatted_monthly_stats_of_minigames(cls): 369 | lists = [["Month", "Game", "W", "L", "D", "Total", "Unfinished", "Time"]] 370 | mg_monthly_stats = cls.get_monthly_stats_for_minigames_of_year(date.today()) 371 | months = [date(date.today().year, month, 1) for month in range(1, 13)] 372 | for month in months: 373 | if mg_monthly_stats[month.strftime("%Y-%m")]: 374 | lists.append([month.strftime("%B"), "", "", "", "", "", ""]) 375 | mg_stats = mg_monthly_stats[month.strftime("%Y-%m")] 376 | for stat in mg_stats: 377 | row = cls.manipulate(stat) 378 | row.insert(0, "") 379 | lists.append(row) 380 | return create_table(*lists) 381 | 382 | @classmethod 383 | def get_formatted_yearly_stats_of_minigames(cls): 384 | lists = [["Year", "Game", "W", "L", "D", "Total", "Unfinished", "Time"]] 385 | mg_yearly_stats = cls.get_yearly_stats_for_minigames(date.today()) 386 | years = [date(year, 1, 1) for year in range(date.today().year - 4, date.today().year + 1)] 387 | for year in years: 388 | if mg_yearly_stats[year.strftime("%Y")]: 389 | lists.append([year.year, "", "", "", "", "", ""]) 390 | mg_stats = mg_yearly_stats[year.strftime("%Y")] 391 | for stat in mg_stats: 392 | row = cls.manipulate(stat) 393 | row.insert(0, "") 394 | lists.append(row) 395 | return create_table(*lists) 396 | 397 | @classmethod 398 | def manipulate(cls, stat): 399 | row = list(tuple(stat)) 400 | minigame = row[0] 401 | if minigame == "akinator": 402 | row[1] = row[2] = row[3] = "-" 403 | if minigame == "flood" \ 404 | or minigame == "hangman" \ 405 | or minigame == "mastermind" \ 406 | or minigame == "quiz" \ 407 | or minigame == "scramble": 408 | row[3] = "-" 409 | row[-1] = timedelta(seconds=int(row[-1])) 410 | return row 411 | 412 | @classmethod 413 | def merge_with_average(cls, stats, avg_stats): 414 | result = [] 415 | for stat in stats: 416 | row = list(tuple(stat)) 417 | row.insert(5, "-") 418 | for avg_row in avg_stats: 419 | if avg_row['minigame'] == row[0]: 420 | row[5] = str(round(avg_row["avg"])) 421 | result.append(row) 422 | return result 423 | 424 | @classmethod 425 | async def update(cls): 426 | channel = await cls.bot.fetch_channel(DISCORD["STATISTICS_CHANNEL"]) 427 | message = await channel.history().flatten() 428 | try: 429 | message = message[0] 430 | except discord.Forbidden and IndexError: 431 | message = await channel.send("haha brr") 432 | 433 | table = cls.get_formatted_stats_for_today_of_minigames() 434 | today = f"```diff\n+ Today\n\n{table}\n```" 435 | today += f"\nServers: **{len(cls.bot.guilds)}**" 436 | today += f"\nLast edited: **{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}**" 437 | await message.edit(content=today) 438 | 439 | with ZipFile("bin/database_backup.zip", "w") as zip_f: 440 | zip_f.write(DATABASE_FILE) 441 | channel = await cls.bot.fetch_channel(DISCORD["BACKUP_CHANNEL"]) 442 | await channel.send(file=discord.File("bin/database_backup.zip")) 443 | -------------------------------------------------------------------------------- /discordbot/discordminigames/discordminigame.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class DiscordMinigame(ABC): 5 | @abstractmethod 6 | async def start_game(self): pass 7 | 8 | @abstractmethod 9 | async def end_game(self): pass 10 | 11 | @abstractmethod 12 | async def on_player_timed_out(self): pass 13 | 14 | @abstractmethod 15 | def on_start_move(self): pass 16 | 17 | @abstractmethod 18 | def on_end_move(self): pass 19 | 20 | @abstractmethod 21 | def get_board(self): pass 22 | 23 | @abstractmethod 24 | def update_last_seen(self): pass 25 | 26 | @abstractmethod 27 | def clear_reactions(self): pass 28 | -------------------------------------------------------------------------------- /discordbot/discordminigames/multiplayergames/chess_dc.py: -------------------------------------------------------------------------------- 1 | import random 2 | from string import ascii_lowercase 3 | 4 | import chess 5 | import discord 6 | from chess import Board, svg 7 | 8 | from discordbot.discordminigames.multiplayergames.multiplayergame import MultiPlayerGame, WON, DRAW 9 | from discordbot.messagemanager import MessageManager 10 | from discordbot.utils.emojis import ALPHABET, STOP, NUMBERS, ARROW_LEFT, CHECKMARK 11 | from discordbot.utils.private import DISCORD 12 | 13 | 14 | # TURN: 0 WHITE 15 | # TURN: 1 BLACK 16 | 17 | 18 | class ChessDiscord(MultiPlayerGame): 19 | def __init__(self, session): 20 | super().__init__(session) 21 | self.chessboard = Board() 22 | self.turn = random.randint(0, 1) 23 | self.move = "" 24 | self.choosing_letter = True 25 | self.choosing_number = False 26 | self.id = random.getrandbits(64) 27 | self.file = f'bin/{self.id}' 28 | 29 | async def start_game(self): 30 | await self.update_messages() 31 | 32 | for i in range(8): 33 | emoji = ALPHABET[ascii_lowercase[i]] 34 | await MessageManager.add_reaction_and_event(self.message, emoji, self.players[self.turn].id, self.on_letter_reaction, emoji, self.players[self.turn].id) 35 | for i in range(1, 9): 36 | emoji = NUMBERS[i] 37 | await MessageManager.add_reaction_and_event(self.extra_message, emoji, self.players[self.turn].id, self.on_number_reaction, emoji, self.players[self.turn].id) 38 | 39 | await MessageManager.add_reaction_and_event(self.extra_message, ARROW_LEFT, self.players[self.turn].id, self.on_back_reaction, self.players[self.turn].id) 40 | await MessageManager.add_reaction_and_event(self.extra_message, CHECKMARK, self.players[self.turn].id, self.check_end_move, self.players[self.turn].id) 41 | 42 | await MessageManager.add_reaction_and_event(self.extra_message, STOP, self.players[0].id, self.on_quit_game, self.players[0]) 43 | await MessageManager.add_reaction_event(self.extra_message, STOP, self.players[1].id, self.on_quit_game, self.players[1]) 44 | 45 | async def on_letter_reaction(self, letter_emoji, user_id): 46 | if user_id != self.players[self.turn].id: 47 | return 48 | 49 | self.on_start_move() 50 | 51 | if self.choosing_letter: 52 | for char, emoji in ALPHABET.items(): 53 | if letter_emoji == emoji: 54 | self.move += char 55 | self.choosing_number = True 56 | self.choosing_letter = False 57 | await MessageManager.edit_message(self.message, self.get_board()) 58 | await MessageManager.remove_reaction(self.message, letter_emoji, self.players[self.turn].member) 59 | 60 | async def on_number_reaction(self, number_emoji, user_id): 61 | if user_id != self.players[self.turn].id: 62 | return 63 | 64 | self.on_start_move() 65 | 66 | if self.choosing_number: 67 | for n, emoji in NUMBERS.items(): 68 | if number_emoji == emoji: 69 | self.move += str(n) 70 | self.choosing_number = False 71 | self.choosing_letter = True 72 | await MessageManager.edit_message(self.message, self.get_board()) 73 | await MessageManager.remove_reaction(self.extra_message, number_emoji, self.players[self.turn].member) 74 | 75 | async def on_back_reaction(self, user_id): 76 | if user_id != self.players[self.turn].id: 77 | return 78 | 79 | self.on_start_move() 80 | 81 | if len(self.move) > 0: 82 | self.move = self.move[:-1] 83 | self.choosing_number = not self.choosing_number 84 | self.choosing_letter = not self.choosing_letter 85 | await MessageManager.edit_message(self.message, self.get_board()) 86 | await MessageManager.remove_reaction(self.extra_message, ARROW_LEFT, self.players[self.turn].member) 87 | 88 | async def check_end_move(self, user_id): 89 | if user_id != self.players[self.turn].id: 90 | return 91 | 92 | await MessageManager.remove_reaction(self.extra_message, CHECKMARK, self.players[self.turn].member) 93 | 94 | if len(self.move) != 4: 95 | return 96 | try: 97 | if chess.Move.from_uci(self.move) in self.chessboard.legal_moves: 98 | self.chessboard.push_uci(self.move) 99 | self.move = "" 100 | else: 101 | self.move = "" 102 | await MessageManager.edit_message(self.message, self.get_board() + "\nIncorrect move, try again!") 103 | return 104 | except ValueError: 105 | self.move = "" 106 | await MessageManager.edit_message(self.message, self.get_board() + "\nIncorrect move, try again!") 107 | return 108 | 109 | if self.chessboard.is_checkmate(): 110 | await self.game_won() 111 | return 112 | 113 | elif self.chessboard.is_stalemate() or self.chessboard.is_insufficient_material(): 114 | await self.game_draw() 115 | return 116 | 117 | self.turn = (self.turn + 1) % 2 118 | await self.update_messages() 119 | 120 | for i in range(8): 121 | emoji = ALPHABET[ascii_lowercase[i]] 122 | await MessageManager.remove_reaction_event(self.message, emoji, self.players[self.turn - 1].id) 123 | await MessageManager.add_reaction_event(self.message, emoji, self.players[self.turn].id, self.on_letter_reaction, emoji, self.players[self.turn].id) 124 | for i in range(1, 9): 125 | emoji = NUMBERS[i] 126 | await MessageManager.remove_reaction_event(self.extra_message, emoji, self.players[self.turn - 1].id) 127 | await MessageManager.add_reaction_event(self.extra_message, emoji, self.players[self.turn].id, self.on_number_reaction, emoji, self.players[self.turn].id) 128 | 129 | await MessageManager.remove_reaction_event(self.extra_message, ARROW_LEFT, self.players[self.turn - 1].id) 130 | await MessageManager.add_reaction_event(self.extra_message, ARROW_LEFT, self.players[self.turn].id, self.on_back_reaction, self.players[self.turn].id) 131 | 132 | await MessageManager.remove_reaction_event(self.extra_message, CHECKMARK, self.players[self.turn - 1].id) 133 | await MessageManager.add_reaction_and_event(self.extra_message, CHECKMARK, self.players[self.turn].id, self.check_end_move, self.players[self.turn].id) 134 | 135 | async def update_messages(self): 136 | self.save_board_image() 137 | 138 | dump_channel = await MessageManager.bot.fetch_channel(DISCORD['STACK_CHANNEL']) 139 | msg_dump = await dump_channel.send(file=discord.File(self.file + ".png")) 140 | await self.session.send_extra_message() 141 | await MessageManager.edit_message(self.message, self.get_board()) 142 | await MessageManager.edit_message(self.extra_message, msg_dump.attachments[0].url) 143 | 144 | def get_board(self): 145 | if self.game_state == WON: 146 | content = f"<@{str(self.players[self.turn].id)}> won the game!" 147 | elif self.game_state == DRAW: 148 | content = f"Game ended in draw!" 149 | else: 150 | if self.turn == 0: 151 | color = "White" 152 | else: 153 | color = "Black" 154 | content = f"{color}'s turn: <@{str(self.players[self.turn].id)}>\n" 155 | if self.choosing_number: 156 | content += "\nSelect **number**" 157 | if self.choosing_letter: 158 | content += "\nSelect **letter**" 159 | if self.chessboard.is_check(): 160 | content += "\n**CHECK**" 161 | content += "\nMove: " 162 | if self.move != "": 163 | content += f"**{self.move}**" 164 | return content 165 | 166 | def save_board_image(self): 167 | try: 168 | move = self.chessboard.peek() 169 | drawing = svg.board(self.chessboard, size=250, lastmove=move) 170 | except IndexError: 171 | drawing = svg.board(self.chessboard, size=250) 172 | f = open(f'{self.file}.svg', 'w') 173 | f.write(drawing) 174 | import os 175 | os.system(f'svgexport {self.file}.svg {self.file}.png 1.5x') 176 | -------------------------------------------------------------------------------- /discordbot/discordminigames/multiplayergames/connect4_dc.py: -------------------------------------------------------------------------------- 1 | from discordbot.discordminigames.multiplayergames.multiplayergame import MultiPlayerGame, WON, DRAW 2 | from discordbot.messagemanager import MessageManager 3 | from discordbot.utils.emojis import NUMBERS, STOP 4 | from minigames.connect4 import Connect4 5 | 6 | 7 | class Connect4Discord(MultiPlayerGame): 8 | def __init__(self, session): 9 | super().__init__(session) 10 | self.connect4_game = Connect4() 11 | self.turn = self.connect4_game.turn 12 | 13 | async def start_game(self): 14 | await MessageManager.edit_message(self.message, self.get_board()) 15 | 16 | for i in range(1, 8): 17 | await MessageManager.add_reaction_and_event(self.message, NUMBERS[i], self.players[self.turn].id, 18 | self.on_number_reaction, 19 | NUMBERS[i], self.players[self.turn].id) 20 | 21 | await MessageManager.add_reaction_and_event(self.message, STOP, self.players[0].id, self.on_quit_game, self.players[0]) 22 | await MessageManager.add_reaction_event(self.message, STOP, self.players[1].id, self.on_quit_game, self.players[1]) 23 | 24 | async def on_number_reaction(self, number_emoji, user_id): 25 | if user_id != self.players[self.turn].id: 26 | return 27 | 28 | self.on_start_move() 29 | 30 | for number, emoji in NUMBERS.items(): 31 | if emoji == number_emoji: 32 | self.connect4_game.move(number - 1) 33 | break 34 | 35 | self.turn = self.connect4_game.turn 36 | await MessageManager.remove_reaction(self.message, number_emoji, self.players[self.turn].member) 37 | 38 | if self.connect4_game.has_player_won(): 39 | await self.game_won() 40 | return 41 | 42 | if self.connect4_game.is_board_full(): 43 | await self.game_draw() 44 | return 45 | 46 | await MessageManager.edit_message(self.message, self.get_board()) 47 | 48 | for i in range(1, 8): 49 | await MessageManager.remove_reaction_event(self.message, NUMBERS[i], self.players[self.turn-1].id) 50 | await MessageManager.add_reaction_event(self.message, NUMBERS[i], self.players[self.turn].id, 51 | self.on_number_reaction, 52 | NUMBERS[i], self.players[self.turn].id) 53 | 54 | def get_board(self): 55 | board = self.connect4_game.get_board() 56 | content = "Board:\n" 57 | for i in range(1, 8): 58 | content += NUMBERS[i] 59 | 60 | content += "\n" 61 | for i in range(len(board)): 62 | for j in range(len(board[i])): 63 | if board[i][j] == 0: 64 | content += ":red_circle:" 65 | elif board[i][j] == 1: 66 | content += ":yellow_circle:" 67 | else: 68 | content += ":black_circle:" 69 | content += "\n" 70 | 71 | if self.game_state == WON: 72 | content += f"\n<@{str(self.players[self.turn].id)}> has won!" 73 | elif self.game_state == DRAW: 74 | content += "\nGame ended in draw!" 75 | else: 76 | content += f"\nTurn: <@{str(self.players[self.turn].id)}>" 77 | if self.turn == 0: 78 | content += " :red_circle:" 79 | else: 80 | content += " :yellow_circle:" 81 | return content 82 | -------------------------------------------------------------------------------- /discordbot/discordminigames/multiplayergames/multiplayergame.py: -------------------------------------------------------------------------------- 1 | from discordbot.discordminigames.discordminigame import DiscordMinigame 2 | from discordbot.messagemanager import MessageManager 3 | 4 | PLAYING = 0 # player playing the game 5 | WON = 1 # player has won the game 6 | DRAW = 3 # player has drawn in the game 7 | QUIT = 4 # player force quit the game or was idle for too long 8 | 9 | 10 | class MultiPlayerGame(DiscordMinigame): 11 | def __init__(self, session): 12 | self.session = session 13 | self.message = self.session.message 14 | self.extra_message = self.session.extra_message 15 | self.players = self.session.players 16 | self.game_state = PLAYING 17 | self.turn = None 18 | 19 | async def start_game(self): pass 20 | 21 | async def end_game(self): 22 | self.players[0].played() 23 | self.players[1].played() 24 | 25 | if self.game_state == WON: 26 | self.players[self.turn].win() 27 | self.players[self.turn-1].lose() 28 | elif self.game_state == DRAW: 29 | self.players[self.turn].draw() 30 | self.players[self.turn - 1].draw() 31 | elif self.game_state == QUIT: 32 | self.players[0].did_not_finish() 33 | self.players[1].did_not_finish() 34 | self.players[self.turn].lose() 35 | self.players[self.turn - 1].win() 36 | 37 | await self.clear_reactions() 38 | await self.session.pause() 39 | 40 | async def on_player_timed_out(self): 41 | self.game_state = QUIT 42 | await self.end_game() 43 | 44 | async def on_quit_game(self, player): 45 | self.turn = self.players.index(player) 46 | self.game_state = QUIT 47 | await self.end_game() 48 | 49 | def on_end_move(self): pass 50 | 51 | def on_start_move(self): 52 | self.update_last_seen() 53 | 54 | def get_board(self): pass 55 | 56 | def update_last_seen(self): 57 | self.session.update_last_seen() 58 | 59 | async def clear_reactions(self): 60 | await MessageManager.clear_reactions(self.message) 61 | if self.extra_message is not None: 62 | await MessageManager.clear_reactions(self.extra_message) 63 | 64 | async def game_won(self): 65 | self.game_state = WON 66 | await self.end_game() 67 | 68 | async def game_draw(self): 69 | self.game_state = DRAW 70 | await self.end_game() 71 | -------------------------------------------------------------------------------- /discordbot/discordminigames/singleplayergames/akinator_dc.py: -------------------------------------------------------------------------------- 1 | import json.decoder 2 | 3 | import akinator.exceptions 4 | from akinator.async_aki import Akinator 5 | 6 | from discordbot.discordminigames.singleplayergames.singleplayergame import SinglePlayerGame, UNFINISHED 7 | from discordbot.messagemanager import MessageManager 8 | from discordbot.utils.emojis import ALPHABET, STOP, QUESTION 9 | 10 | 11 | class AkinatorDiscord(SinglePlayerGame): 12 | def __init__(self, session): 13 | super().__init__(session) 14 | self.akinator = Akinator() 15 | self.guessed = False 16 | 17 | async def start_game(self): 18 | await self.akinator.start_game() 19 | await MessageManager.edit_message(self.message, self.get_board()) 20 | 21 | await MessageManager.add_reaction_and_event(self.message, ALPHABET["y"], self.player.id, self.on_yes_reaction) 22 | await MessageManager.add_reaction_and_event(self.message, ALPHABET["n"], self.player.id, self.on_no_reaction) 23 | await MessageManager.add_reaction_and_event(self.message, QUESTION, self.player.id, self.on_dontknow_reaction) 24 | await MessageManager.add_reaction_and_event(self.message, STOP, self.player.id, self.on_quit_game) 25 | 26 | async def on_yes_reaction(self): 27 | self.on_start_move() 28 | await MessageManager.remove_reaction(self.message, ALPHABET["y"], self.player.member) 29 | await self.answer(0) 30 | 31 | async def on_no_reaction(self): 32 | self.on_start_move() 33 | await MessageManager.remove_reaction(self.message, ALPHABET["n"], self.player.member) 34 | await self.answer(1) 35 | 36 | async def on_dontknow_reaction(self): 37 | self.on_start_move() 38 | await MessageManager.remove_reaction(self.message, QUESTION, self.player.member) 39 | await self.answer(2) 40 | 41 | async def answer(self, answer): 42 | try: 43 | await self.akinator.answer(answer) 44 | await MessageManager.edit_message(self.session.message, self.get_board()) 45 | if self.akinator.progression >= 80 or self.akinator.step == 79: 46 | await self.akinator.win() 47 | self.guessed = True 48 | self.game_state = -1 49 | await self.end_game() 50 | except (akinator.exceptions.AkiTimedOut, json.decoder.JSONDecodeError): 51 | self.game_state = -1 52 | await self.end_game() 53 | 54 | def get_board(self): 55 | content = f"Question {int(self.akinator.step) + 1}: *{self.akinator.question}*\n" 56 | if self.guessed: 57 | content = f"Akinator guesses: {self.akinator.first_guess['name']}\n{self.akinator.first_guess['absolute_picture_path']}" 58 | return content 59 | 60 | async def on_quit_game(self): 61 | self.game_state = UNFINISHED 62 | await self.end_game() 63 | -------------------------------------------------------------------------------- /discordbot/discordminigames/singleplayergames/blackjack_dc.py: -------------------------------------------------------------------------------- 1 | from discordbot.messagemanager import MessageManager 2 | from discordbot.discordminigames.singleplayergames.singleplayergame import SinglePlayerGame, WON, LOST, QUIT, DRAW 3 | from discordbot.utils.emojis import STOP, ALPHABET, SPLIT 4 | from minigames.blackjack import Blackjack 5 | 6 | 7 | class BlackjackDiscord(SinglePlayerGame): 8 | def __init__(self, session): 9 | super().__init__(session) 10 | self.blackjack = Blackjack() 11 | 12 | async def start_game(self): 13 | await MessageManager.edit_message(self.message, self.get_board()) 14 | 15 | await MessageManager.add_reaction_and_event(self.message, ALPHABET["h"], self.player.id, self.on_hit_reaction) 16 | await MessageManager.add_reaction_and_event(self.message, ALPHABET["s"], self.player.id, self.on_stand_reaction) 17 | 18 | if self.blackjack.can_split(): 19 | await MessageManager.add_reaction_and_event(self.message, SPLIT, self.player.id, self.on_split_reaction) 20 | await MessageManager.add_reaction_and_event(self.message, STOP, self.player.id, self.on_quit_game) 21 | 22 | async def on_hit_reaction(self): 23 | self.on_start_move() 24 | 25 | if len(self.blackjack.player_hands) == 2: 26 | if max(self.blackjack.player_hands[0].get_value()) > 21: 27 | self.blackjack.hit(hand=1) 28 | else: 29 | self.blackjack.hit() 30 | else: 31 | self.blackjack.hit() 32 | 33 | await MessageManager.edit_message(self.message, self.get_board()) 34 | await MessageManager.remove_reaction(self.message, ALPHABET["h"], self.player.member) 35 | await MessageManager.clear_reaction(self.message, SPLIT) 36 | 37 | if self.blackjack.is_player_busted(): 38 | await self.stand() 39 | 40 | async def on_stand_reaction(self): 41 | self.on_start_move() 42 | await self.stand() 43 | 44 | async def on_split_reaction(self): 45 | self.on_start_move() 46 | 47 | self.blackjack.split_hand() 48 | await MessageManager.clear_reaction(self.message, SPLIT) 49 | await MessageManager.edit_message(self.message, self.get_board()) 50 | 51 | async def stand(self): 52 | self.blackjack.stand() 53 | if self.blackjack.has_ended_in_draw(): 54 | await self.game_draw() 55 | elif self.blackjack.has_player_won(): 56 | await self.game_won() 57 | else: 58 | await self.game_lost() 59 | 60 | def get_board(self): 61 | content = "```diff\n" 62 | if self.blackjack.player_turn: 63 | content += "- Dealer's cards:\n" \ 64 | f" {self.blackjack.dealer_hand.cards[0].__str__()}\n" \ 65 | f" \n" 66 | else: 67 | a, b = self.blackjack.dealer_hand.get_value() 68 | if a == b: 69 | content += f"- Dealer's cards: value = {a}\n" 70 | else: 71 | content += f"- Dealer's cards: value = {a} or {b}\n" 72 | for card in self.blackjack.dealer_hand.cards: 73 | content += f" {card.__str__()}\n" 74 | for hand in self.blackjack.player_hands: 75 | a, b = hand.get_value() 76 | if a == b: 77 | content += f"\n+ Player's cards: value = {a}\n" 78 | else: 79 | content += f"\n+ Player's cards: value = {a} or {b}\n" 80 | for card in hand.cards: 81 | content += f" {card.__str__()}\n" 82 | content += "\n" 83 | 84 | if self.game_state == WON: 85 | content += "You have won!\n" 86 | elif self.game_state == DRAW: 87 | content += "Game ended in draw!\n" 88 | elif self.game_state == LOST or self.game_state == QUIT: 89 | content += "You have lost!\n" 90 | content += "```" 91 | return content 92 | -------------------------------------------------------------------------------- /discordbot/discordminigames/singleplayergames/flood_dc.py: -------------------------------------------------------------------------------- 1 | from discordbot.messagemanager import MessageManager 2 | from discordbot.discordminigames.singleplayergames.singleplayergame import SinglePlayerGame, WON, LOST, QUIT 3 | from discordbot.utils.emojis import STOP, COLORS as COLORS_EMOJI 4 | from minigames.flood import Flood, COLORS 5 | 6 | 7 | class FloodDiscord(SinglePlayerGame): 8 | def __init__(self, session): 9 | super().__init__(session) 10 | self.flood_game = Flood() 11 | self.current_color = COLORS_EMOJI[self.flood_game.grid.matrix[0][0].color] 12 | 13 | async def start_game(self): 14 | await MessageManager.edit_message(self.message, self.get_board()) 15 | for c in COLORS: 16 | await MessageManager.add_reaction_and_event(self.message, COLORS_EMOJI[c], self.player.id, 17 | self.on_color_reaction, COLORS_EMOJI[c]) 18 | await MessageManager.add_reaction_and_event(self.message, STOP, self.player.id, self.on_quit_game) 19 | 20 | async def on_color_reaction(self, color_emoji): 21 | self.on_start_move() 22 | 23 | if color_emoji == self.current_color: 24 | return 25 | 26 | await MessageManager.remove_reaction(self.message, self.current_color, self.player.member) 27 | for color, emoji in COLORS_EMOJI.items(): 28 | if emoji == color_emoji: 29 | self.flood_game.pick_color(color) 30 | self.current_color = emoji 31 | break 32 | 33 | await MessageManager.edit_message(self.message, self.get_board()) 34 | 35 | if self.flood_game.has_won(): 36 | await self.game_won() 37 | return 38 | elif self.flood_game.has_lost(): 39 | await self.game_lost() 40 | return 41 | 42 | def get_board(self): 43 | content = "" 44 | content += f"Moves left: **{self.flood_game.min_allowed_moves - self.flood_game.player_moves}**\n" 45 | for i in range(len(self.flood_game.grid.matrix)): 46 | for j in range(len(self.flood_game.grid.matrix[i])): 47 | content += COLORS_EMOJI[self.flood_game.grid.matrix[i][j].color] 48 | content += "\n" 49 | 50 | if self.game_state == WON: 51 | content += "You have won the game!" 52 | elif self.game_state == LOST or self.game_state == QUIT: 53 | content += f"You have lost the game!" 54 | return content 55 | -------------------------------------------------------------------------------- /discordbot/discordminigames/singleplayergames/hangman_dc.py: -------------------------------------------------------------------------------- 1 | from string import ascii_lowercase 2 | 3 | from discordbot.messagemanager import MessageManager 4 | from discordbot.discordminigames.singleplayergames.singleplayergame import SinglePlayerGame, WON, LOST, QUIT 5 | from discordbot.utils.emojis import ALPHABET, STOP 6 | from minigames.hangman import Hangman, HANGMEN 7 | 8 | 9 | class HangmanDiscord(SinglePlayerGame): 10 | def __init__(self, session): 11 | super().__init__(session) 12 | self.hangman_game = Hangman() 13 | 14 | async def start_game(self): 15 | await self.session.send_extra_message() 16 | await MessageManager.edit_message(self.message, self.get_board()) 17 | 18 | for i in range(len(ascii_lowercase)): 19 | emoji = ALPHABET[ascii_lowercase[i]] 20 | if i < 13: 21 | await MessageManager.add_reaction_and_event(self.message, emoji, self.player.id, self.on_letter_reaction, 22 | emoji) 23 | if i >= 13: 24 | await MessageManager.add_reaction_and_event(self.extra_message, emoji, self.player.id, 25 | self.on_letter_reaction, emoji) 26 | await MessageManager.add_reaction_and_event(self.extra_message, STOP, self.player.id, self.on_quit_game) 27 | 28 | async def on_letter_reaction(self, letter_emoji): 29 | self.on_start_move() 30 | 31 | for letter, emoji in ALPHABET.items(): 32 | if emoji == letter_emoji: 33 | self.hangman_game.guess(letter) 34 | break 35 | 36 | if self.hangman_game.has_won(): 37 | await self.game_won() 38 | return 39 | elif self.hangman_game.has_lost(): 40 | await self.game_lost() 41 | return 42 | 43 | await MessageManager.edit_message(self.message, self.get_board()) 44 | 45 | def get_board(self): 46 | word = self.hangman_game.current_word 47 | hangman = HANGMEN[self.hangman_game.lives] 48 | word_ = "" 49 | for c in word: 50 | if c == "_": 51 | word_ += "__ " 52 | else: 53 | word_ += f"{c} " 54 | 55 | content = f"```\n{hangman}\n\nWord: {word_}\n```" 56 | if self.game_state == WON: 57 | content += "```\nYou have won the game!\n```" 58 | elif self.game_state == LOST or self.game_state == QUIT: 59 | content += f"```\nYou have lost the game!\nThe word was: '{''.join(self.hangman_game.word)}'\n```" 60 | return content 61 | -------------------------------------------------------------------------------- /discordbot/discordminigames/singleplayergames/mastermind_dc.py: -------------------------------------------------------------------------------- 1 | from discordbot.discordminigames.singleplayergames.singleplayergame import SinglePlayerGame, WON, LOST, QUIT 2 | from discordbot.messagemanager import MessageManager 3 | from discordbot.utils.emojis import STOP, COLORS as COLORS_EMOJI, ARROW_LEFT, CHECKMARK, REPEAT 4 | from minigames.mastermind import Mastermind, COLORS 5 | 6 | 7 | class MastermindDiscord(SinglePlayerGame): 8 | def __init__(self, session): 9 | super().__init__(session) 10 | self.mastermind = Mastermind() 11 | self.code = [] 12 | 13 | async def start_game(self): 14 | await MessageManager.edit_message(self.message, self.get_board()) 15 | 16 | for c in COLORS: 17 | await MessageManager.add_reaction_and_event(self.message, COLORS_EMOJI[c], self.player.id, 18 | self.on_color_reaction, COLORS_EMOJI[c]) 19 | await MessageManager.add_reaction_and_event(self.message, ARROW_LEFT, self.player.id, self.on_back_reaction) 20 | await MessageManager.add_reaction_and_event(self.message, CHECKMARK, self.player.id, self.on_checkmark_reaction) 21 | await MessageManager.add_reaction_and_event(self.message, STOP, self.player.id, self.on_quit_game) 22 | 23 | async def on_back_reaction(self): 24 | self.on_start_move() 25 | 26 | await MessageManager.remove_reaction(self.message, ARROW_LEFT, self.player.member) 27 | if len(self.code) > 0: 28 | color = self.code[-1] 29 | await MessageManager.remove_reaction(self.message, COLORS_EMOJI[color], self.player.member) 30 | self.code.remove(color) 31 | await MessageManager.edit_message(self.message, self.get_board()) 32 | 33 | async def on_color_reaction(self, color_emoji): 34 | self.on_start_move() 35 | 36 | for color, emoji in COLORS_EMOJI.items(): 37 | if emoji == color_emoji and color not in self.code and len(self.code) < 4: 38 | self.code.append(color) 39 | elif emoji == color_emoji: 40 | await MessageManager.remove_reaction(self.message, emoji, self.player.member) 41 | await MessageManager.edit_message(self.message, self.get_board()) 42 | 43 | async def on_checkmark_reaction(self): 44 | self.on_start_move() 45 | 46 | await MessageManager.remove_reaction(self.message, CHECKMARK, self.player.member) 47 | if len(self.code) == 4: 48 | self.mastermind.guess(self.code) 49 | for color in self.code: 50 | await MessageManager.remove_reaction(self.message, COLORS_EMOJI[color], self.player.member) 51 | self.code = [] 52 | 53 | if self.mastermind.has_won(): 54 | await self.game_won() 55 | return 56 | elif self.mastermind.has_lost(): 57 | await self.game_lost() 58 | return 59 | 60 | await MessageManager.edit_message(self.message, self.get_board()) 61 | 62 | def get_board(self): 63 | content = f"Lives: {self.mastermind.lives}\nYour guess:" 64 | for code in self.code: 65 | content += COLORS_EMOJI[code] 66 | content += "\n\n" 67 | 68 | if len(self.mastermind.history) > 0: 69 | for history_ in self.mastermind.history: 70 | for color in history_[0]: 71 | content += COLORS_EMOJI[color] 72 | content += " " 73 | content += CHECKMARK * history_[1] 74 | content += REPEAT * history_[2] 75 | content += "\n" 76 | 77 | if self.game_state == WON: 78 | content += "You have won the game!" 79 | if self.game_state == LOST or self.game_state == QUIT: 80 | content += f"You have lost the game!\nThe code was: " 81 | for color in self.mastermind.code: 82 | content += COLORS_EMOJI[color] 83 | return content 84 | -------------------------------------------------------------------------------- /discordbot/discordminigames/singleplayergames/quiz_dc.py: -------------------------------------------------------------------------------- 1 | import html 2 | import random 3 | from string import ascii_lowercase, ascii_uppercase 4 | 5 | from discordbot.messagemanager import MessageManager 6 | from discordbot.discordminigames.singleplayergames.singleplayergame import SinglePlayerGame, WON, LOST, QUIT 7 | from discordbot.utils.emojis import ALPHABET, STOP, NUMBERS 8 | from minigames.lexicon import Lexicon 9 | 10 | 11 | class QuizDiscord(SinglePlayerGame): 12 | def __init__(self, session): 13 | super().__init__(session) 14 | self.category = None 15 | self.question = None 16 | self.correct_answer = None 17 | self.answers = None 18 | self.user_answer = -1 19 | self.selecting_category = True 20 | self.categories = ["General Knowledge", "Sports", "Films", "Music", "Video Games"] 21 | 22 | async def start_game(self): 23 | await MessageManager.edit_message(self.message, self.get_board()) 24 | 25 | for i in range(1, len(self.categories) + 1): 26 | await MessageManager.add_reaction_and_event(self.message, NUMBERS[i], self.player.id, self.choose_category, 27 | NUMBERS[i]) 28 | await MessageManager.add_reaction_and_event(self.message, STOP, self.player.id, self.on_quit_game) 29 | 30 | async def choose_category(self, emoji): 31 | self.on_start_move() 32 | 33 | for n, e in NUMBERS.items(): 34 | if e == emoji: 35 | self.category = self.categories[n - 1] 36 | self.set_question() 37 | await MessageManager.edit_message(self.message, self.get_board()) 38 | 39 | await self.clear_reactions() 40 | for i in range(len(self.answers)): 41 | emoji = ALPHABET[ascii_lowercase[i]] 42 | await MessageManager.add_reaction_and_event(self.message, emoji, self.player.id, self.choose_answer, emoji) 43 | await MessageManager.add_reaction_and_event(self.message, STOP, self.player.id, self.on_quit_game) 44 | 45 | async def choose_answer(self, emoji): 46 | self.on_start_move() 47 | 48 | for c, e in ALPHABET.items(): 49 | if e == emoji: 50 | self.user_answer = ascii_lowercase.index(c) 51 | if self.user_answer == self.correct_answer: 52 | await self.game_won() 53 | else: 54 | await self.game_lost() 55 | break 56 | 57 | def set_question(self): 58 | self.selecting_category = False 59 | questions = Lexicon.QUESTIONS[self.category] 60 | random.shuffle(questions) 61 | quiz = questions[random.randint(0, len(Lexicon.QUESTIONS) - 1)] 62 | self.question = quiz['question'] 63 | self.answers = list(set(quiz['incorrect_answers'])) 64 | self.correct_answer = random.randint(0, len(self.answers)) 65 | self.answers.insert(self.correct_answer, quiz['correct_answer']) 66 | 67 | async def on_quit_game(self): 68 | if self.selecting_category: 69 | self.game_state = -1 70 | else: 71 | self.game_state = QUIT 72 | await self.end_game() 73 | 74 | def get_board(self): 75 | content = "" 76 | if self.selecting_category: 77 | content += "**Categories**\n" 78 | for i in range(len(self.categories)): 79 | content += f"{NUMBERS[i + 1]} *{self.categories[i]}*\n" 80 | return content 81 | 82 | content += f"**{self.category}**\n\n" \ 83 | f"*{html.unescape(self.question)}*\n\n" 84 | for i in range(len(self.answers)): 85 | if i == self.user_answer: 86 | content += f"__{ascii_uppercase[i]}) {html.unescape(self.answers[i])}__\n" 87 | else: 88 | content += f"{ascii_uppercase[i]}) {html.unescape(self.answers[i])}\n" 89 | 90 | if self.game_state == WON: 91 | content += "\nYou answered correct!" 92 | elif self.game_state == LOST or self.game_state == QUIT: 93 | content += f"\nWrong! The correct answer was: {html.unescape(self.answers[self.correct_answer])}" 94 | return content 95 | -------------------------------------------------------------------------------- /discordbot/discordminigames/singleplayergames/scramble_dc.py: -------------------------------------------------------------------------------- 1 | from discordbot.discordminigames.singleplayergames.singleplayergame import SinglePlayerGame, WON, LOST, QUIT 2 | from discordbot.messagemanager import MessageManager 3 | from discordbot.utils.emojis import ALPHABET, STOP, ARROW_LEFT 4 | from minigames.scramble import Scramble 5 | 6 | 7 | class ScrambleDiscord(SinglePlayerGame): 8 | def __init__(self, session): 9 | super().__init__(session) 10 | self.scramble_game = Scramble() 11 | 12 | async def start_game(self): 13 | await MessageManager.edit_message(self.message, self.get_board()) 14 | 15 | await MessageManager.add_reaction_and_event(self.message, STOP, self.player.id, self.on_quit_game) 16 | await MessageManager.add_reaction_and_event(self.message, ARROW_LEFT, self.player.id, self.on_back_reaction) 17 | for c in self.scramble_game.scrambled_word: 18 | await MessageManager.add_reaction_and_event(self.message, ALPHABET[c], self.player.id, self.on_letter_reaction, 19 | ALPHABET[c]) 20 | 21 | async def on_letter_reaction(self, letter_emoji): 22 | self.on_start_move() 23 | 24 | for char, emoji in ALPHABET.items(): 25 | if emoji == letter_emoji: 26 | self.scramble_game.guess(char) 27 | if char not in self.scramble_game.scrambled_word: 28 | await MessageManager.clear_reaction(self.message, letter_emoji) 29 | else: 30 | await MessageManager.remove_reaction(self.message, letter_emoji, self.player.member) 31 | break 32 | await MessageManager.edit_message(self.message, self.get_board()) 33 | 34 | if self.scramble_game.has_won(): 35 | await self.game_won() 36 | return 37 | 38 | async def on_back_reaction(self): 39 | self.on_start_move() 40 | 41 | char = self.scramble_game.remove_last() 42 | if char != "_": 43 | await MessageManager.add_reaction_and_event(self.message, ALPHABET[char], self.player.id, 44 | self.on_letter_reaction, ALPHABET[char]) 45 | await MessageManager.remove_reaction(self.message, ARROW_LEFT, self.player.member) 46 | await MessageManager.edit_message(self.message, self.get_board()) 47 | 48 | def get_board(self): 49 | word_ = "" 50 | for c in self.scramble_game.current_word: 51 | if c == "_": 52 | word_ += "__ " 53 | else: 54 | word_ += f"{c} " 55 | 56 | content = f"```\nLetters: {' '.join(self.scramble_game.scrambled_word)}\n" \ 57 | f"{''.join(word_)}\n```" 58 | 59 | if self.game_state == WON: 60 | content += "```\nYou have won the game!\n```" 61 | elif self.game_state == LOST or self.game_state == QUIT: 62 | content += f"```\nYou have lost the game!\nThe word was: '{''.join(self.scramble_game.word)}'\n```" 63 | elif len(self.scramble_game.scrambled_word) == 0 and not self.scramble_game.has_won(): 64 | content += "```\nWrong word, try again!\n```" 65 | return content 66 | -------------------------------------------------------------------------------- /discordbot/discordminigames/singleplayergames/singleplayergame.py: -------------------------------------------------------------------------------- 1 | from discordbot.discordminigames.discordminigame import DiscordMinigame 2 | from discordbot.messagemanager import MessageManager 3 | 4 | PLAYING = 0 # player playing the game 5 | WON = 1 # player has won the game 6 | LOST = 2 # player has lost the game 7 | DRAW = 3 # player has drawn in the game 8 | QUIT = 4 # player force quit the game or was idle for too long 9 | UNFINISHED = 5 10 | 11 | 12 | class SinglePlayerGame(DiscordMinigame): 13 | def __init__(self, session): 14 | self.session = session 15 | self.message = self.session.message 16 | self.extra_message = self.session.extra_message 17 | self.player = self.session.player 18 | self.game_state = PLAYING 19 | 20 | async def start_game(self): pass 21 | 22 | async def end_game(self): 23 | self.player.played() 24 | if self.game_state == WON: 25 | self.player.win() 26 | elif self.game_state == LOST: 27 | self.player.lose() 28 | elif self.game_state == DRAW: 29 | self.player.draw() 30 | elif self.game_state == QUIT: 31 | self.player.did_not_finish() 32 | self.player.lose() 33 | elif self.game_state == UNFINISHED: 34 | self.player.did_not_finish() 35 | 36 | await self.clear_reactions() 37 | await self.session.pause() 38 | 39 | async def on_player_timed_out(self): 40 | await self.on_quit_game() 41 | 42 | async def on_quit_game(self): 43 | self.game_state = QUIT 44 | await self.end_game() 45 | 46 | def on_end_move(self): pass 47 | 48 | def on_start_move(self): 49 | self.update_last_seen() 50 | 51 | def get_board(self): pass 52 | 53 | def update_last_seen(self): 54 | self.session.update_last_seen() 55 | 56 | async def clear_reactions(self): 57 | await MessageManager.clear_reactions(self.message) 58 | if self.extra_message is not None: 59 | await MessageManager.clear_reactions(self.extra_message) 60 | 61 | async def game_won(self): 62 | self.game_state = WON 63 | await self.end_game() 64 | 65 | async def game_lost(self): 66 | self.game_state = LOST 67 | await self.end_game() 68 | 69 | async def game_draw(self): 70 | self.game_state = DRAW 71 | await self.end_game() 72 | -------------------------------------------------------------------------------- /discordbot/gamemanager.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | class GameManager: 5 | def __init__(self): 6 | self.sessions = list() 7 | 8 | async def start_session(self, session): 9 | self.sessions.append(session) 10 | await session.start_game() 11 | 12 | def close_session(self, session): 13 | self.sessions.remove(session) 14 | 15 | def on_pending_update(self): 16 | for session in self.sessions: 17 | session.on_pending_update() 18 | 19 | async def on_restart(self): 20 | for session in self.sessions: 21 | await session.on_restart() 22 | 23 | def has_open_sessions(self): 24 | return len(self.sessions) > 0 25 | 26 | async def close_inactive_sessions(self): 27 | while True: 28 | for session in self.sessions: 29 | if await session.is_inactive(): 30 | await session.close() 31 | self.close_session(session) 32 | await asyncio.sleep(60 * 5) 33 | -------------------------------------------------------------------------------- /discordbot/messagemanager.py: -------------------------------------------------------------------------------- 1 | class MessageManager: 2 | reaction_events = dict() 3 | bot = None 4 | 5 | @classmethod 6 | def on_startup(cls, bot): 7 | cls.bot = bot 8 | 9 | @classmethod 10 | async def send_message(cls, medium, content): 11 | await medium.send(content) 12 | 13 | @classmethod 14 | async def edit_message(cls, message, content): 15 | try: 16 | if message in cls.bot.cached_messages: 17 | await message.edit(content=content) 18 | else: 19 | channel = await cls.bot.fetch_channel(message.channel.id) 20 | message = await channel.fetch_message(message.id) 21 | await message.edit(content=content) 22 | except Exception as e: 23 | await cls.bot.on_error(f"EDIT MESSAGE\n message_id: {message.id}\n channel_id: {message.channel.id}", e) 24 | 25 | @classmethod 26 | async def delete_message(cls, message): 27 | try: 28 | if message in cls.bot.cached_messages: 29 | await message.delete() 30 | else: 31 | channel = await cls.bot.fetch_channel(message.channel.id) 32 | message = await channel.fetch_message(message.id) 33 | await message.delete() 34 | except Exception as e: 35 | await cls.bot.on_error(f"DELETE MESSAGE\n message_id: {message.id}\n channel_id: {message.channel.id}", e) 36 | 37 | @classmethod 38 | async def add_reaction(cls, message, emoji): 39 | try: 40 | if message in cls.bot.cached_messages: 41 | await message.add_reaction(emoji) 42 | else: 43 | channel = await cls.bot.fetch_channel(message.channel.id) 44 | message = await channel.fetch_message(message.id) 45 | await message.add_reaction(emoji) 46 | except Exception as e: 47 | await cls.bot.on_error(f"ADD REACTION\n message_id: {message.id}\n channel_id: {message.channel.id}", e) 48 | 49 | @classmethod 50 | async def add_reaction_and_event(cls, message, emoji, user_id, handler, *args): 51 | cls.reaction_events[(message.id, emoji, user_id)] = (handler, args) 52 | await cls.add_reaction(message, emoji) 53 | 54 | @classmethod 55 | async def add_reaction_event(cls, message, emoji, user_id, handler, *args): 56 | cls.reaction_events[(message.id, emoji, user_id)] = (handler, args) 57 | 58 | @classmethod 59 | async def remove_reaction_event(cls, message_id, emoji, user_id): 60 | if (message_id, emoji, user_id) in cls.reaction_events.keys(): 61 | cls.reaction_events.pop((message_id, emoji, user_id)) 62 | 63 | @classmethod 64 | async def remove_reaction(cls, message, emoji, user): 65 | try: 66 | if message in cls.bot.cached_messages: 67 | await message.remove_reaction(emoji, user) 68 | else: 69 | channel = await cls.bot.fetch_channel(message.channel.id) 70 | message = await channel.fetch_message(message.id) 71 | await message.remove_reaction(emoji, user) 72 | except Exception as e: 73 | await cls.bot.on_error(f"REMOVE REACTION\n message_id: {message.id}\n channel_id: {message.channel.id}", e) 74 | 75 | @classmethod 76 | async def clear_reaction(cls, message, emoji): 77 | to_remove = [] 78 | for (message_id, emoji_, user_id) in cls.reaction_events.keys(): 79 | if message_id == message.id and emoji_ == emoji: 80 | to_remove.append((message_id, emoji_, user_id)) 81 | for container in to_remove: 82 | cls.reaction_events.pop(container) 83 | 84 | try: 85 | if message in cls.bot.cached_messages: 86 | await message.clear_reaction(emoji) 87 | else: 88 | channel = await cls.bot.fetch_channel(message.channel.id) 89 | message = await channel.fetch_message(message.id) 90 | await message.clear_reaction(emoji) 91 | except Exception as e: 92 | await cls.bot.on_error(f"CLEAR REACTION\n message_id: {message.id}\n channel_id: {message.channel.id}", e) 93 | 94 | @classmethod 95 | async def clear_reactions(cls, message): 96 | to_remove = [] 97 | for (message_id, emoji, user_id) in cls.reaction_events.keys(): 98 | if message_id == message.id: 99 | to_remove.append((message_id, emoji, user_id)) 100 | for container in to_remove: 101 | cls.reaction_events.pop(container) 102 | 103 | try: 104 | if message in cls.bot.cached_messages: 105 | await message.clear_reactions() 106 | else: 107 | channel = await cls.bot.fetch_channel(message.channel.id) 108 | message = await channel.fetch_message(message.id) 109 | await message.clear_reactions() 110 | except Exception as e: 111 | await cls.bot.on_error(f"CLEAR REACTIONS\n message_id: {message.id}\n channel_id: {message.channel.id}", e) 112 | 113 | @classmethod 114 | async def on_raw_reaction(cls, payload): 115 | container = (payload.message_id, payload.emoji.name, payload.user_id) 116 | if container in cls.reaction_events.keys(): 117 | (handler, args) = cls.reaction_events[container] 118 | await handler(*args) 119 | -------------------------------------------------------------------------------- /discordbot/minigamesbot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import importlib 3 | import inspect 4 | import json 5 | import os 6 | import random 7 | import time 8 | import traceback 9 | from zipfile import ZipFile 10 | 11 | import discord 12 | from discord import DMChannel, Permissions 13 | from discord.ext.commands import Bot, CommandNotFound, Cog 14 | from discord.utils import find 15 | 16 | from discordbot.categories import * 17 | from discordbot.commands.command import Command 18 | from discordbot.databasemanager import DatabaseManager 19 | from discordbot.gamemanager import GameManager 20 | from discordbot.messagemanager import MessageManager 21 | from discordbot.utils.private import DISCORD 22 | from discordbot.utils.topgg import TopGG 23 | from discordbot.utils.variables import MINIGAMES 24 | from generic.scheduler import Scheduler 25 | from minigames.lexicon import Lexicon 26 | 27 | PREFIXES_FILE = "bin/server_prefixes.json" 28 | MAX_MESSAGE_LENGTH = 1900 29 | 30 | 31 | class MiniGamesBot(Bot): 32 | def __init__(self, prefix): 33 | self.prefix = prefix 34 | intents = discord.Intents.default() 35 | intents.members = True 36 | intents.reactions = True 37 | super().__init__(command_prefix=self.prefix, intents=intents, max_messages=5000) 38 | 39 | self.called_on_ready = False 40 | self.ctx = None 41 | self.uptime = time.time() 42 | self.prefixes = {} 43 | self.my_commands = [] 44 | self.game_manager = GameManager() 45 | 46 | # load commands 47 | self.categories = [ 48 | Miscellaneous, 49 | Developer, 50 | Minigames 51 | ] 52 | self.load_commands() 53 | 54 | # load managers 55 | DatabaseManager.on_startup(self) 56 | Lexicon.on_startup() 57 | MessageManager.on_startup(self) 58 | 59 | # load prefixes 60 | self.load_prefixes() 61 | 62 | # setup scheduler 63 | self.scheduler = Scheduler() 64 | self.scheduler.add(60, self.game_manager.close_inactive_sessions) 65 | self.scheduler.add(45, self.routine_updates) 66 | 67 | # REMOVE THIS TRY EXCEPT 68 | try: 69 | self.add_cog(TopGG(self)) 70 | except Exception as e: 71 | print(e) 72 | 73 | async def on_message(self, message): 74 | if message.author.bot or isinstance(message.channel, DMChannel): 75 | return 76 | 77 | if str(message.channel.guild.id) in self.prefixes.keys(): 78 | if message.content.startswith(self.prefixes[str(message.channel.guild.id)]): 79 | message.content = self.prefix + message.content[len(self.prefixes[str(message.channel.guild.id)]):] 80 | else: 81 | return 82 | 83 | context = await self.get_context(message) 84 | self.ctx = context 85 | await self.invoke(context) 86 | 87 | async def on_raw_reaction_add(self, payload): 88 | await MessageManager.on_raw_reaction(payload) 89 | 90 | async def on_ready(self): 91 | if not self.called_on_ready: 92 | self.called_on_ready = False 93 | channel = await self.fetch_channel(DISCORD["STACK_CHANNEL"]) 94 | await channel.send("**READY**") 95 | 96 | async def on_guild_remove(self, guild): 97 | if guild.name is None: 98 | return 99 | DatabaseManager.add_to_servers_table(guild.id, "\"LEAVE\"") 100 | channel = await self.fetch_channel(DISCORD["STACK_CHANNEL"]) 101 | await channel.send("LEFT GUILD '{0}' ({1}).".format(guild.name, guild.id)) 102 | 103 | async def on_guild_join(self, guild): 104 | DatabaseManager.add_to_servers_table(guild.id, "\"JOIN\"") 105 | general = find(lambda x: 'general' in x.name, guild.text_channels) 106 | if general and general.permissions_for(guild.me).send_messages: 107 | await general.send('Hello {0}! The command prefix for this bot is **?**.\n' 108 | 'Type **?help** for a list of possible commands.'.format(guild.name)) 109 | channel = await self.fetch_channel(DISCORD["STACK_CHANNEL"]) 110 | await channel.send("JOINED GUILD '{0}' ({1}).".format(guild.name, guild.id)) 111 | 112 | def load_prefixes(self): 113 | try: 114 | file = open(PREFIXES_FILE) 115 | json_strings = file.read() 116 | self.prefixes = json.loads(json_strings) 117 | except FileNotFoundError: 118 | file = open(PREFIXES_FILE, 'w') 119 | prefixes_json = json.dumps(self.prefixes) 120 | file.write(prefixes_json) 121 | file.close() 122 | 123 | def load_commands(self): 124 | modules = self.get_modules(os.path.join(os.getcwd(), "discordbot", "commands"), "discordbot.commands") 125 | for module in modules: 126 | imp = importlib.import_module(module) 127 | for key, cmd in imp.__dict__.items(): 128 | if inspect.isclass(cmd) and issubclass(cmd, Command) and cmd != Command: 129 | self.my_commands.append(cmd) 130 | cmd.add_command(self) 131 | 132 | def get_modules(self, path, module): 133 | modules = [] 134 | for file in os.listdir(path): 135 | if file == "__pycache__": 136 | continue 137 | if os.path.isdir(os.path.join(path, file)): 138 | for submodule in self.get_modules(os.path.join(path, file), f"{module}.{file}"): 139 | modules.append(submodule) 140 | elif os.path.isfile(os.path.join(path, file)): 141 | modules.append(f"{module}.{file[:-3]}") 142 | return modules 143 | 144 | async def send(self, content, channel_id=None): 145 | if content.startswith("```"): 146 | await self.send_formatted(content, channel_id) 147 | return 148 | max_length = 1900 149 | message_length = len(content) 150 | j = 0 151 | 152 | if channel_id is not None: 153 | channel = await self.fetch_channel(channel_id) 154 | else: 155 | channel = self.ctx.channel 156 | 157 | while max_length < message_length: 158 | await channel.send(content[j * max_length:(j + 1) * max_length]) 159 | message_length -= max_length 160 | j += 1 161 | 162 | await channel.send(content[j * max_length:]) 163 | 164 | async def send_formatted(self, content, channel_id=None): 165 | message_length = len(content) 166 | j = 0 167 | content = content[3:-3] 168 | 169 | if channel_id is not None: 170 | channel = await self.fetch_channel(channel_id) 171 | else: 172 | channel = self.ctx.channel 173 | 174 | while MAX_MESSAGE_LENGTH < message_length: 175 | await channel.send("```\n" + content[j * MAX_MESSAGE_LENGTH:(j + 1) * MAX_MESSAGE_LENGTH] + "\n```") 176 | message_length -= MAX_MESSAGE_LENGTH 177 | j += 1 178 | 179 | await channel.send("```\n" + content[j * MAX_MESSAGE_LENGTH:] + "\n```") 180 | 181 | async def send_error(self, content): 182 | channel = self.get_channel(DISCORD["ERROR_CHANNEL"]) 183 | contents = content.split("\n") 184 | content = "" 185 | for part in contents: 186 | temp = content + "\n" + part 187 | if len(temp) > MAX_MESSAGE_LENGTH: 188 | await channel.send("```\n" + content + "\n```") 189 | content = part 190 | else: 191 | content = temp 192 | await channel.send("```\n" + content + "\n```") 193 | 194 | async def routine_updates(self): 195 | while True: 196 | await DatabaseManager.update() 197 | await self.save_prefixes() 198 | await self.change_status() 199 | self.remove_old_binaries() 200 | await asyncio.sleep(60 * 30) 201 | 202 | async def on_restart(self): 203 | await self.game_manager.on_restart() 204 | await DatabaseManager.update() 205 | await self.save_prefixes() 206 | 207 | async def change_status(self): 208 | n = random.randint(0, len(MINIGAMES) - 1) 209 | game = discord.Game(MINIGAMES[n]) 210 | await self.change_presence(status=discord.Status.online, activity=game) 211 | 212 | async def save_prefixes(self): 213 | f = open(PREFIXES_FILE, 'w') 214 | prefixes_json = json.dumps(self.prefixes) 215 | f.write(prefixes_json) 216 | f.close() 217 | 218 | with ZipFile("bin/prefixes_backup.zip", "w") as zip_f: 219 | zip_f.write(PREFIXES_FILE) 220 | channel = await self.fetch_channel(DISCORD["BACKUP_CHANNEL"]) 221 | await channel.send(file=discord.File("bin/prefixes_backup.zip")) 222 | 223 | def remove_old_binaries(self): 224 | direc = os.path.join(os.getcwd(), 'bin') 225 | now = time.time() 226 | dt = now - 60 * 60 * 24 227 | for filename in os.listdir(direc): 228 | f_path = os.path.join(direc, filename) 229 | f_created = os.path.getctime(f_path) 230 | if (filename.endswith(".svg") or filename.endswith(".png")) and f_created < dt: 231 | os.remove(f_path) 232 | 233 | async def on_error(self, event_method, *args, **kwargs): 234 | error = "Time: {0}\n\n" \ 235 | "Ignoring exception in command {1}:\n\n" \ 236 | "args: {2}\n\n" \ 237 | "kwargs: {3}\n\n" \ 238 | "e: {4}\n\n" \ 239 | .format(time.strftime("%b %d %Y %H:%M:%S"), event_method, args, kwargs, traceback.format_exc()) 240 | await self.send_error(error) 241 | 242 | async def on_command_error(self, context, exception): 243 | if isinstance(exception, CommandNotFound): 244 | return 245 | if self.extra_events.get('on_command_error', None): 246 | return 247 | if hasattr(context.command, 'on_error'): 248 | return 249 | 250 | cog = context.cog 251 | if cog and Cog._get_overridden_method(cog.cog_command_error) is not None: 252 | return 253 | 254 | original_error = getattr(exception, 'original', exception) 255 | if isinstance(original_error, discord.Forbidden): 256 | await self.send_missing_permissions(context, self.get_missing_permissions(context)) 257 | 258 | error = "Time: {0}\n\n" \ 259 | "Ignoring exception in command {1}:\n\n" \ 260 | "Exception: \n\n{2}" \ 261 | .format(time.strftime("%b %d %Y %H:%M:%S"), 262 | context.command, 263 | ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__))) 264 | 265 | await self.send_error(error) 266 | 267 | def get_missing_permissions(self, context): 268 | permissions: Permissions = context.channel.permissions_for(context.channel.guild.me) 269 | missing_permissions = list() 270 | if not permissions.manage_messages: 271 | missing_permissions.append("Manage messages") 272 | if not permissions.read_message_history: 273 | missing_permissions.append("Add reactions") 274 | if not permissions.use_external_emojis: 275 | missing_permissions.append("Use external emojis") 276 | if not permissions.attach_files: 277 | missing_permissions.append("Attach files") 278 | return missing_permissions 279 | 280 | async def send_missing_permissions(self, context, missing_permissions): 281 | if len(missing_permissions) == 0: 282 | return 283 | content = "I am missing the following permissions in this channel. Please enable these so the bot can work properly:\n" 284 | for missing_permission in missing_permissions: 285 | content += f"- {missing_permission}\n" 286 | await context.send(content) 287 | -------------------------------------------------------------------------------- /discordbot/user/multiplayersession.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from time import time 3 | 4 | from discordbot.databasemanager import DatabaseManager 5 | from discordbot.messagemanager import MessageManager 6 | from discordbot.user.player import Player 7 | from discordbot.user.session import Session, UPDATE_PENDING_CONTENT 8 | from discordbot.utils.emojis import STOP, REPEAT, CHECKMARK 9 | from generic.formatting import create_table 10 | 11 | 12 | class MultiPlayerSession(Session): 13 | def __init__(self, message, game_name, handler, *players): 14 | super().__init__(message, game_name, handler) 15 | self.players = [Player(player) for player in players] 16 | self.accepted_players = set() 17 | 18 | async def start_game(self): 19 | if self.update_pending: # do not start game, bot update is pending 20 | await MessageManager.edit_message(self.message, UPDATE_PENDING_CONTENT) 21 | await self.close() 22 | return 23 | 24 | self.accepted_players = set() 25 | await MessageManager.clear_reactions(self.message) 26 | await MessageManager.edit_message(self.message, f"Waiting for both players to accept the game...") 27 | 28 | await MessageManager.add_reaction_and_event(self.message, CHECKMARK, self.players[0].id, self.player_accepted_game, self.players[0].id) 29 | await MessageManager.add_reaction_and_event(self.message, STOP, self.players[0].id, self.player_declined_game) 30 | await MessageManager.add_reaction_event(self.message, CHECKMARK, self.players[1].id, self.player_accepted_game, self.players[1].id) 31 | await MessageManager.add_reaction_event(self.message, STOP, self.players[1].id, self.player_declined_game) 32 | 33 | async def player_accepted_game(self, player_id): 34 | self.accepted_players.add(player_id) 35 | if len(self.accepted_players) == 1: 36 | return 37 | 38 | await MessageManager.clear_reactions(self.message) 39 | await MessageManager.edit_message(self.message, "Setting up game...") 40 | 41 | self.game = self.game_handler(self) 42 | self.stopwatch.start() 43 | 44 | try: 45 | await self.game.start_game() 46 | except Exception as e: 47 | await self.close() 48 | raise Exception(e) 49 | 50 | async def player_declined_game(self): 51 | await MessageManager.clear_reactions(self.message) 52 | await MessageManager.edit_message(self.message, "Game invite was declined.") 53 | await self.close() 54 | 55 | async def pause(self): 56 | self.stopwatch.pause() 57 | await MessageManager.edit_message(self.message, self.game.get_board() + "\n" + self.get_summary()) 58 | 59 | for player in self.players: 60 | await MessageManager.add_reaction_and_event(self.message, STOP, player.id, self.close) 61 | await MessageManager.add_reaction_and_event(self.message, REPEAT, player.id, self.start_game) 62 | 63 | async def close(self): 64 | if self.closed: 65 | return 66 | 67 | self.closed = True 68 | await MessageManager.clear_reactions(self.message) 69 | 70 | # save data to DB 71 | for player in self.players: 72 | stats = player.get_stats() 73 | stats["player_id"] = player.id 74 | stats["minigame"] = f"\"{self.game_name}\"" 75 | stats["time_stamp"] = time() 76 | stats["time_played"] = self.stopwatch.get_total_time() 77 | DatabaseManager.add_to_players_table(stats) 78 | 79 | stats = { 80 | "server_id": self.message.channel.guild.id, 81 | "minigame": f"\"{self.game_name}\"", 82 | "time_stamp": time(), 83 | "wins": self.players[0].wins + self.players[1].wins, 84 | "losses": self.players[0].losses + self.players[1].losses, 85 | "draws": self.players[0].draws, 86 | "unfinished": self.players[0].unfinished, 87 | "total_games": self.players[0].total_games, 88 | "time_played": self.stopwatch.get_total_time() 89 | } 90 | DatabaseManager.add_to_minigames_table(stats) 91 | 92 | async def send_extra_message(self): 93 | if self.extra_message is None: 94 | self.extra_message = await self.message.channel.send("** **") 95 | self.game.extra_message = self.extra_message 96 | 97 | def get_summary(self): 98 | lst = [["Player", "Wins", "Losses", "Draws", "Unfinished", "Total played"]] 99 | for player in self.players: 100 | lst.append([player.name, player.wins, player.losses, player.draws, player.unfinished, player.total_games]) 101 | table = create_table(*lst) 102 | summary = \ 103 | f"```\n" \ 104 | f"{table}\n" \ 105 | f"Session Time: {datetime.timedelta(seconds=round(self.stopwatch.get_total_time()))}\n" \ 106 | f"```" 107 | return summary 108 | -------------------------------------------------------------------------------- /discordbot/user/player.py: -------------------------------------------------------------------------------- 1 | class Player: 2 | def __init__(self, member): 3 | self.member = member 4 | self.name = self.member.name 5 | self.id = self.member.id 6 | 7 | self.wins = 0 8 | self.losses = 0 9 | self.draws = 0 10 | self.unfinished = 0 11 | self.total_games = 0 12 | 13 | def get_total_played_games(self): 14 | return self.total_games 15 | 16 | def win(self): 17 | self.wins += 1 18 | 19 | def lose(self): 20 | self.losses += 1 21 | 22 | def draw(self): 23 | self.draws += 1 24 | 25 | def did_not_finish(self): 26 | self.unfinished += 1 27 | 28 | def played(self): 29 | self.total_games += 1 30 | 31 | def get_stats(self): 32 | return { 33 | "wins": self.wins, 34 | "losses": self.losses, 35 | "draws": self.draws, 36 | "unfinished": self.unfinished, 37 | "total_games": self.total_games 38 | } 39 | -------------------------------------------------------------------------------- /discordbot/user/session.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | from discordbot.messagemanager import MessageManager 4 | from generic.stopwatch import Stopwatch 5 | 6 | UPDATE_PENDING_CONTENT = "Sorry! I can't start any new games right now. Boss says I have to restart soon:tm:. Try again later!" 7 | 8 | 9 | class Session: 10 | def __init__(self, message, game_name, handler): 11 | self.message = message 12 | self.game_name = game_name 13 | self.game_handler = handler 14 | 15 | self.stopwatch = Stopwatch() 16 | 17 | self.closed = False 18 | self.game = None 19 | self.extra_message = None 20 | self.update_pending = False 21 | self.last_seen = time() 22 | 23 | async def start_game(self): 24 | if self.update_pending: # do not start game, bot update is pending 25 | await MessageManager.edit_message(self.message, UPDATE_PENDING_CONTENT) 26 | await self.close() 27 | return 28 | 29 | self.game = self.game_handler(self) 30 | self.stopwatch.start() 31 | 32 | await MessageManager.clear_reactions(self.message) 33 | try: 34 | await self.game.start_game() 35 | except Exception as e: 36 | await self.close() 37 | raise Exception(e) 38 | 39 | async def pause(self): pass 40 | 41 | async def close(self): pass 42 | 43 | async def is_inactive(self): 44 | if time() - self.last_seen > 60*5: 45 | await self.game.on_player_timed_out() 46 | return True 47 | 48 | return self.closed 49 | 50 | def update_last_seen(self): 51 | self.last_seen = time() 52 | 53 | async def on_restart(self): 54 | await MessageManager.edit_message(self.message, self.game.get_content() + "\n" + UPDATE_PENDING_CONTENT) 55 | await self.close() 56 | 57 | async def send_extra_message(self): 58 | if self.extra_message is None: 59 | self.extra_message = await self.message.channel.send("** **") 60 | self.game.extra_message = self.extra_message 61 | 62 | def get_summary(self): pass 63 | -------------------------------------------------------------------------------- /discordbot/user/singleplayersession.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from time import time 3 | 4 | from discordbot.databasemanager import DatabaseManager 5 | from discordbot.messagemanager import MessageManager 6 | from discordbot.user.player import Player 7 | from discordbot.user.session import Session 8 | from discordbot.utils.emojis import STOP, REPEAT 9 | from generic.formatting import create_table 10 | 11 | 12 | class SinglePlayerSession(Session): 13 | def __init__(self, message, game_name, handler, player): 14 | super().__init__(message, game_name, handler) 15 | self.player = Player(player) 16 | 17 | async def pause(self): 18 | self.stopwatch.pause() 19 | await MessageManager.edit_message(self.message, self.game.get_board() + "\n" + self.get_summary()) 20 | 21 | await MessageManager.add_reaction_and_event(self.message, STOP, self.player.id, self.close) 22 | await MessageManager.add_reaction_and_event(self.message, REPEAT, self.player.id, self.start_game) 23 | 24 | async def close(self): 25 | if self.closed: 26 | return 27 | self.closed = True 28 | await MessageManager.clear_reactions(self.message) 29 | 30 | # save data to DB 31 | stats = self.player.get_stats() 32 | stats["player_id"] = self.player.id 33 | stats["minigame"] = f"\"{self.game_name}\"" 34 | stats["time_stamp"] = time() 35 | stats["time_played"] = self.stopwatch.get_total_time() 36 | DatabaseManager.add_to_players_table(stats) 37 | 38 | stats.pop("player_id") 39 | stats["server_id"] = self.message.channel.guild.id 40 | DatabaseManager.add_to_minigames_table(stats) 41 | 42 | def get_summary(self): 43 | lst = [["Player", "Wins", "Losses", "Draws", "Unfinished", "Total played"], 44 | [self.player.name, self.player.wins, self.player.losses, self.player.draws, self.player.unfinished, 45 | self.player.total_games]] 46 | table = create_table(*lst) 47 | summary = \ 48 | f"```\n" \ 49 | f"{table}\n" \ 50 | f"Session Time: {datetime.timedelta(seconds=round(self.stopwatch.get_total_time()))}\n" \ 51 | f"```" 52 | return summary 53 | -------------------------------------------------------------------------------- /discordbot/utils/emojis.py: -------------------------------------------------------------------------------- 1 | ARROW_UP = "⬆️" 2 | ARROW_DOWN = "⬇️" 3 | ARROW_LEFT = "⬅️" 4 | ARROW_RIGHT = "➡️" 5 | SPLIT = "↔️" 6 | 7 | CHECKMARK = "✅" 8 | CHECKMARK_2 = "☑️" 9 | STOP = "❌" 10 | REPEAT = "🔁" 11 | QUESTION = "❓" 12 | 13 | ALPHABET = {'a': '🇦', 'b': '🇧', 'c': '🇨', 'd': '🇩', 'e': '🇪', 'f': '🇫', 'g': '🇬', 'h': '🇭', 'i': '🇮', 14 | 'j': '🇯', 15 | 'k': '🇰', 'l': '🇱', 'm': '🇲', 'n': '🇳', 'o': '🇴', 'p': '🇵', 'q': '🇶', 'r': '🇷', 's': '🇸', 16 | 't': '🇹', 17 | 'u': '🇺', 'v': '🇻', 'w': '🇼', 'x': '🇽', 'y': '🇾', 'z': '🇿'} # letter: emoji 18 | 19 | COLORS = {"blue": "🟦", "green": "🟩", "red": "🟥", "yellow": "🟨", "white": "⬜", "purple": "🟪", "orange": "🟧"} 20 | 21 | NUMBERS = {0: "0️⃣", 1: "1️⃣", 2: "2️⃣", 3: "3️⃣", 4: "4️⃣", 5: "5️⃣", 6: "6️⃣", 7: "7️⃣", 8: "8️⃣", 9: "9️⃣"} 22 | -------------------------------------------------------------------------------- /discordbot/utils/pager.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import re 3 | 4 | from discordbot.utils.emojis import ARROW_LEFT, ARROW_RIGHT, STOP 5 | 6 | 7 | class Pager: 8 | def __init__(self, bot, context, pages, timeout=60.0): 9 | self.bot = bot 10 | self.context = context 11 | self.pages = pages 12 | self.timeout = timeout 13 | self.current_page = 0 14 | self.page_msg = None 15 | 16 | async def show(self): 17 | max_length = 1990 18 | if len(self.pages[self.current_page]) > max_length: 19 | formatting = False 20 | if self.pages[self.current_page].startswith("```"): 21 | formatting = True 22 | split_contents = re.split(r"\n", self.pages[self.current_page]) 23 | contents = [""] 24 | for content in split_contents: 25 | temp = contents[0] + content + "\n" 26 | if len(temp) > max_length: 27 | break 28 | else: 29 | contents[0] = temp 30 | if formatting: 31 | contents[0] += "```" 32 | content = contents[0] 33 | else: 34 | content = self.pages[self.current_page] 35 | 36 | if self.page_msg is None: 37 | self.page_msg = await self.context.send(content) 38 | else: 39 | await self.page_msg.edit(content=content) 40 | await self.page_msg.add_reaction(ARROW_LEFT) 41 | await self.page_msg.add_reaction(ARROW_RIGHT) 42 | await self.page_msg.add_reaction(STOP) 43 | 44 | async def update(self, pages): 45 | self.pages = pages 46 | await self.page_msg.edit(content=self.pages[self.current_page]) 47 | 48 | async def wait_for_user(self): 49 | 50 | def check(r, u): 51 | return u == self.context.message.author \ 52 | and (r.emoji == ARROW_LEFT or r.emoji == ARROW_RIGHT or r.emoji == STOP) \ 53 | and r.message.id == self.page_msg.id 54 | 55 | while True: 56 | try: 57 | reaction, user = await self.bot.wait_for('reaction_add', timeout=self.timeout, check=check) 58 | 59 | if reaction.emoji == ARROW_LEFT: 60 | self.current_page -= 1 61 | if self.current_page < 0: 62 | self.current_page = 0 63 | 64 | elif reaction.emoji == ARROW_RIGHT: 65 | self.current_page += 1 66 | if self.current_page >= len(self.pages): 67 | self.current_page = len(self.pages) - 1 68 | else: 69 | await self.page_msg.delete() 70 | return 71 | 72 | await self.page_msg.remove_reaction(reaction.emoji, user) 73 | await self.show() 74 | except asyncio.TimeoutError: 75 | await self.page_msg.clear_reactions() 76 | break 77 | -------------------------------------------------------------------------------- /discordbot/utils/topgg.py: -------------------------------------------------------------------------------- 1 | import dbl 2 | from discord.ext import commands 3 | 4 | from discordbot.utils.private import TOPGG 5 | 6 | 7 | class TopGG(commands.Cog): 8 | """Handles interactions with the top.gg API""" 9 | 10 | def __init__(self, bot): 11 | self.bot = bot 12 | self.token = TOPGG["TOKEN"] 13 | self.dblpy = dbl.DBLClient(self.bot, self.token, 14 | autopost=True) # Autopost will post your guild count every 30 minutes 15 | 16 | async def on_guild_post(self): 17 | print("Server count posted successfully") 18 | -------------------------------------------------------------------------------- /discordbot/utils/variables.py: -------------------------------------------------------------------------------- 1 | from discordbot.utils.emojis import SPLIT, STOP, ALPHABET, ARROW_LEFT, CHECKMARK, REPEAT, QUESTION 2 | 3 | TIMEOUT = 60 * 5 4 | 5 | MINIGAMES = ["Blackjack", "Chess", "Connect4", "Flood", "Mastermind", "Hangman", "Quiz", "Scramble"] 6 | 7 | QUIZ_CATEGORIES = ["General Knowledge", "Sports", "Films", "Music", "Video Games"] 8 | 9 | BLACKJACK_RULES = f"**Blackjack**\n" \ 10 | f"{ALPHABET['h']} to ask for extra card ('hit').\n" \ 11 | f"{ALPHABET['s']} to signal that you have enough cards ('stand').\n" \ 12 | f"{SPLIT} to split your hand when both your cards are of the same rank at the start of the game.\n" \ 13 | f"{STOP} to end the game (automatically results in loss)." 14 | 15 | CHESS_RULES = f"**Chess**\n" \ 16 | f"Click letters and numbers to create your move.\n" \ 17 | f"{STOP} to end the game (automatically results in loss for player who pressed it)." 18 | 19 | CONNECT4_RULES = f"**Connect4**\n" \ 20 | f"Click a number to indicate the column for your coin.\n" \ 21 | f"{STOP} to end the game (automatically results in loss for player who pressed it)." 22 | 23 | HANGMAN_RULES = f"**Hangman**\n" \ 24 | f"Click letters to make your guess.\n" \ 25 | f"{STOP} to end the game (automatically results in loss)." 26 | 27 | QUIZ_RULES = f"**Quiz**\n" \ 28 | f"There are 4 categories available: General Knowledge, Sports, Films, Music and Video Games.\n" \ 29 | f"First select your category, then select the right answer for your question.\n" \ 30 | f"{STOP} to end the game (automatically results in loss)." 31 | 32 | SCRAMBLE_RULES = f"**Scramble**\n" \ 33 | f"Unscramble the given word by clicking on the letters in the correct order.\n" \ 34 | f"{ARROW_LEFT} to undo your last move.\n" \ 35 | f"{STOP} to end the game (automatically results in loss)." 36 | 37 | FLOOD_RULES = f"**Flood**\n" \ 38 | f"Try to get the whole grid to be the same color within the given number of moves, by repeatedly flood-filling the top left corner in different colors.\n" \ 39 | f"Click one of the colors in the reactions to flood-fill the top left corner with that color.\n" \ 40 | f"{STOP} to end the game (automatically results in loss)." 41 | 42 | MASTERMIND_RULES = f"**Mastermind**\n" \ 43 | f"Try to guess the hidden combination of colors. You will be given limited information about each guess you make, enabling you to refine the next guess.\n" \ 44 | f"{CHECKMARK} to indicate the amount of colors that are in the correct place.\n" \ 45 | f"{REPEAT} to indicate the amount of colors that are correct but in the wrong place.\n" \ 46 | f"Click one of the colors in the reactions to make your guess.\n" \ 47 | f"{ARROW_LEFT} to remove your last added color.\n" \ 48 | f"{CHECKMARK} to confirm your guess.\n" \ 49 | f"{STOP} to end the game (automatically results in loss)." 50 | 51 | AKINATOR_RULES = f"**Akinator**\n" \ 52 | f"Think of character and by asking yes/no questions the Akinator will guess who it is. Character can be fictional or real.\n" \ 53 | f"{ALPHABET['y']} to answer the question with 'yes'.\n" \ 54 | f"{ALPHABET['n']} to answer the question with 'no'.\n" \ 55 | f"{QUESTION} if you don't know the answer.\n" \ 56 | f"{STOP} to end the game." 57 | -------------------------------------------------------------------------------- /generic/database.py: -------------------------------------------------------------------------------- 1 | """ 2 | database_old.py 3 | A wrapper around the sqlite3 python library. 4 | """ 5 | 6 | import sqlite3 7 | 8 | 9 | class Database: 10 | def __init__(self, name=None): 11 | """ 12 | The constructor of the Database class. 13 | :param name: (optional) Name of the database 14 | """ 15 | 16 | self.conn = None 17 | self.cursor = None 18 | 19 | if name: 20 | self.open(name) 21 | 22 | def open(self, name): 23 | """ 24 | Opens a new database connection. 25 | :param name: Name of the database to open 26 | :return: 27 | """ 28 | 29 | try: 30 | self.conn = sqlite3.connect(name) 31 | self.conn.row_factory = sqlite3.Row 32 | self.cursor = self.conn.cursor() 33 | 34 | except sqlite3.Error as e: 35 | print(f"Error connecting to database!: {e}") 36 | 37 | def close(self): 38 | """ 39 | Function to close a datbase connection. 40 | :return: 41 | """ 42 | 43 | if self.conn: 44 | self.conn.commit() 45 | self.cursor.close() 46 | self.conn.close() 47 | 48 | def __enter__(self): 49 | return self 50 | 51 | def __exit__(self, exc_type, exc_value, traceback): 52 | self.close() 53 | 54 | def create_table(self, table: str, columns: list, keys: list): 55 | """ 56 | Function to create table in the database. 57 | :param table: The name of the new database's table. 58 | :param columns: The string of columns, comma-separated. 59 | :param keys: The list with primary keys 60 | :return: 61 | """ 62 | 63 | columns = ", ".join(columns) 64 | keys = ", ".join(keys) 65 | query = "CREATE TABLE IF NOT EXISTS {0} ({1}, PRIMARY KEY ({2}));".format(table, columns, keys) 66 | self.cursor.execute(query) 67 | self.conn.commit() 68 | 69 | def add_column(self, table: str, attribute: str, t: str): 70 | try: 71 | query = "alter table {0} add column {1} {2}".format(table, attribute, t) 72 | self.cursor.execute(query) 73 | except sqlite3.OperationalError: 74 | pass 75 | self.conn.commit() 76 | 77 | def get(self, table: str, columns: list, where: dict, limit=None): 78 | """ 79 | Function to fetch/query data from the database. 80 | :param table: The name of the database's table to query from. 81 | :param columns: A list of strings representing the columns which to query. 82 | :param where: Dictionary to filter rows, keys are columns, values are database values. 83 | :param limit: (Optional) limit of items to fetch. 84 | :return: 85 | """ 86 | 87 | where_clause = "WHERE " 88 | for key, value in where.items(): 89 | where_clause += f"{key}={value} AND " 90 | where_clause = where_clause[:-5] 91 | 92 | columns = ", ".join(columns) 93 | if len(where) == 0: 94 | query = "SELECT {0} FROM ({1});".format(columns, table) 95 | else: 96 | query = "SELECT {0} FROM ({1}) {2};".format(columns, table, where_clause) 97 | self.cursor.execute(query) 98 | 99 | # fetch data 100 | rows = self.cursor.fetchall() 101 | return rows[len(rows) - limit if limit else 0:] 102 | 103 | def has(self, table: str, columns: list, where: dict): 104 | """ 105 | Function to check if a row exists in the database. 106 | :param table: The database's table from which to query. 107 | :param columns: A list of strings representing the columns which to query. 108 | :param where: Dictionary to filter rows, keys are columns, values are database values. 109 | :return: 110 | """ 111 | rows = self.get(table, columns, where) 112 | if rows: 113 | return True 114 | return False 115 | 116 | def get_last(self, table: str, columns: list): 117 | """ 118 | Utilty function to get the last row of data from a database. 119 | :param table: The database's table from which to query. 120 | :param columns: The columns which to query. 121 | :return: 122 | """ 123 | 124 | return self.get(table, columns, dict(), limit=1)[0] 125 | 126 | @staticmethod 127 | def to_csv(data, fname="output.csv"): 128 | """ 129 | Utility function that converts a dataset into CSV format. 130 | :param data: The data, retrieved from the get() function. 131 | :param fname: The file name to store the data in. 132 | :return: 133 | """ 134 | 135 | with open(fname, 'a') as file: 136 | file.write(",".join([str(j) for i in data for j in i])) 137 | 138 | def write(self, table, data, where=None): 139 | """ 140 | Function to write data to the database. 141 | :param table: The name of the database's table to write to. 142 | :param data: The new data to insert, a dictionary with keys (as columns) and values 143 | :param where: Dictionary to filter rows, keys are columns, values are database values. 144 | :return: 145 | """ 146 | 147 | if where is None: 148 | columns = "" 149 | values = "" 150 | for column, value in data.items(): 151 | columns += f"{column}, " 152 | values += f"{value}, " 153 | columns = columns[:-2] 154 | values = values[:-2] 155 | 156 | query = "INSERT INTO {0} ({1}) VALUES ({2});".format(table, columns, values) 157 | else: 158 | columns_clause = "" 159 | for key, value in data.items(): 160 | columns_clause += f"{key} = {value}, " 161 | columns_clause = columns_clause[:-2] 162 | 163 | where_clause = "" 164 | for key, value in where.items(): 165 | where_clause += f"{key}={value} AND " 166 | where_clause = where_clause[:-5] 167 | query = "UPDATE {0} SET {1} WHERE {2};".format(table, columns_clause, where_clause) 168 | self.cursor.execute(query) 169 | self.conn.commit() 170 | 171 | def query(self, sql): 172 | """ 173 | Function to query any other SQL statement. 174 | :param sql: A valid SQL statement in string format. 175 | :return: 176 | """ 177 | 178 | self.cursor.execute(sql) 179 | self.conn.commit() 180 | 181 | # fetch data 182 | return self.cursor.fetchall() 183 | 184 | @staticmethod 185 | def summary(rows): 186 | """ 187 | Utility function that summarizes a dataset. 188 | :param rows: The retrieved data. 189 | :return: 190 | """ 191 | 192 | # split the rows into columns 193 | cols = [[r[c] for r in rows] for c in range(len(rows[0]))] 194 | 195 | # the time in terms of fractions of hours of how long ago 196 | # the sample was assumes the sampling period is 10 minutes 197 | t = lambda col: "{:.1f}".format((len(rows) - col) / 6.0) 198 | 199 | # return a tuple, consisting of tuples of the maximum, 200 | # the minimum and the average for each column and their 201 | # respective time (how long ago, in fractions of hours) 202 | # average has no time, of course 203 | ret = [] 204 | 205 | for c in cols: 206 | hi = max(c) 207 | hi_t = t(c.index(hi)) 208 | 209 | lo = min(c) 210 | lo_t = t(c.index(lo)) 211 | 212 | avg = sum(c) / len(rows) 213 | 214 | ret.append(((hi, hi_t), (lo, lo_t), avg)) 215 | 216 | return ret 217 | -------------------------------------------------------------------------------- /generic/formatting.py: -------------------------------------------------------------------------------- 1 | def create_table(*lists): 2 | PADDING = 2 3 | column_sizes = [0 for e in lists[0]] 4 | for i in range(len(lists)): 5 | for j in range(len(lists[i])): 6 | if column_sizes[j] < len(str(lists[i][j])): 7 | column_sizes[j] = len(str(lists[i][j])) 8 | 9 | table = "" 10 | for lst in lists: 11 | for i in range(len(lst)): 12 | table += str(lst[i]).ljust(column_sizes[i] + PADDING) 13 | table += "\n" 14 | return table 15 | -------------------------------------------------------------------------------- /generic/scheduler.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from time import time 3 | 4 | 5 | class Scheduler: 6 | max_wait = 10 * 60 # in seconds 7 | store_metadata = False 8 | 9 | def __init__(self): 10 | self.events = dict() # {scheduler_key: (time, function, args, kwargs, metadata), ...} 11 | self.waiters = set() # {t1, t2, t3, ...} 12 | 13 | # interface methods 14 | 15 | def add(self, delay, function, *args, **kwargs): 16 | """Schedule an event in a specific amount of time.""" 17 | return self.at(time() + delay, function, *args, **kwargs) 18 | 19 | def at(self, t, function, *args, **kwargs): 20 | """Schedule an event at a specific timestamp ``t``.""" 21 | now = time() 22 | t = max(now, t) 23 | 24 | if Scheduler.store_metadata and "metadata" in kwargs: 25 | metadata = kwargs["metadata"] 26 | del kwargs["metadata"] 27 | else: 28 | metadata = None 29 | 30 | key = object() 31 | self.events[key] = (t, function, args, kwargs, metadata) 32 | asyncio.Task(self._create_waiter(t, now)) 33 | 34 | return key 35 | 36 | def cancel(self, key): 37 | """Cancel a scheduled event.""" 38 | if key in self.events: 39 | del self.events[key] 40 | 41 | def is_done(self, key): 42 | """Return whether a scheduled event has finished.""" 43 | return key not in self.events 44 | 45 | def size(self): 46 | """Return the amount of events scheduled.""" 47 | return len(self.events) 48 | 49 | # internal methods 50 | 51 | async def _create_waiter(self, t, now=None): 52 | """Make sure there is a waiter that triggers before timestamp ``t``.""" 53 | if now is None: 54 | now = time() 55 | 56 | # there is already another waiter before ``t``, which will create a waiter on its own once triggered 57 | if any(waiter <= t for waiter in self.waiters): 58 | return 59 | 60 | # never wait more than max_wait to prevent long sleeps 61 | t = min(t, now + Scheduler.max_wait) 62 | 63 | # start waiter 64 | self.waiters.add(t) 65 | asyncio.Task(self._wait(t, now)) 66 | 67 | async def _wait(self, t, now): 68 | """Wait until timestamp ``now``, _pop(), and create new waiter if any events left.""" 69 | 70 | # sleep 71 | await asyncio.sleep(t - now) 72 | self.waiters.remove(t) 73 | 74 | # execute events 75 | await self._pop(t) 76 | 77 | # if there are events left, make sure there is another waiter for the first event 78 | if len(self.events) > 0: 79 | t = min(tup[0] for tup in self.events.values()) 80 | await self._create_waiter(t) 81 | 82 | async def _pop(self, now): 83 | """Execute every event scheduled to run before timestamp ``now``.""" 84 | 85 | # collect every event that should be triggered before ``now`` 86 | events = [] 87 | for key in list(self.events.keys()): 88 | t = self.events[key][0] 89 | if t <= now: 90 | events.append(self.events[key]) 91 | del self.events[key] 92 | 93 | # execute each of those events 94 | for t, function, args, kwargs, metadata in events: 95 | try: 96 | if asyncio.iscoroutinefunction(function): 97 | await function(*args, **kwargs) 98 | elif asyncio.iscoroutine(function): 99 | await function 100 | else: 101 | function(*args, **kwargs) 102 | except Exception as e: 103 | import traceback 104 | print("[EXCEPTION IN SCHEDULER]") 105 | print(e) 106 | print(traceback.format_exc()) 107 | -------------------------------------------------------------------------------- /generic/stopwatch.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class Stopwatch: 5 | def __init__(self): 6 | self.start_t = 0 7 | self.total_t = 0 8 | 9 | def start(self): 10 | self.start_t = time.time() 11 | 12 | def pause(self): 13 | self.total_t += time.time() - self.start_t 14 | 15 | def get_total_time(self): 16 | return self.total_t 17 | 18 | def reset(self): 19 | self.start_t = 0 20 | self.total_t = 0 21 | -------------------------------------------------------------------------------- /images/flood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whuybrec/MiniGamesBot/0dc7bc4dd8aa3fb81d76e44597081ce1ba8c2329/images/flood.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from discordbot.minigamesbot import MiniGamesBot 2 | from discordbot.utils.private import DISCORD 3 | 4 | bot = MiniGamesBot("?") 5 | bot.run(DISCORD["TOKEN"]) 6 | -------------------------------------------------------------------------------- /minigames/blackjack.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # https://gist.github.com/getmehire/013c6157eb0ddb26eb7a3965b5f58ae4 4 | 5 | suits = ('Hearts', 'Diamonds', 'Spades', 'Clubs') 6 | ranks = ('Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Jack', 'Queen', 'King', 'Ace') 7 | values = { 8 | 'Two': 2, 9 | 'Three': 3, 10 | 'Four': 4, 11 | 'Five': 5, 12 | 'Six': 6, 13 | 'Seven': 7, 14 | 'Eight': 8, 15 | 'Nine': 9, 16 | 'Ten': 10, 17 | 'Jack': 10, 18 | 'Queen': 10, 19 | 'King': 10, 20 | 'Ace': 11 21 | } 22 | 23 | 24 | class Card: 25 | def __init__(self, suit, rank): 26 | self.suit = suit 27 | self.rank = rank 28 | 29 | def __str__(self): 30 | return self.rank + " of " + self.suit 31 | 32 | 33 | class Deck: 34 | def __init__(self): 35 | self.deck = [] 36 | for suit in suits: 37 | for rank in ranks: 38 | self.deck.append(Card(suit, rank)) # appending the Card object to self.deck list 39 | 40 | def __str__(self): 41 | composition = '' # set the deck comp as an empty string 42 | for card in self.deck: 43 | composition += '\n' + card.__str__() # Card class string representation 44 | return "The deck has: " + composition 45 | 46 | def shuffle(self): 47 | random.shuffle(self.deck) 48 | 49 | # grab the deck attribute of Deck class then pop off card item from list and set it to single card 50 | def deal(self): 51 | card = self.deck.pop() 52 | return card 53 | 54 | 55 | class Hand: 56 | def __init__(self): 57 | self.cards = [] # start with 0 cards in hand 58 | 59 | def add_card(self, card): 60 | self.cards.append(card) 61 | 62 | def get_value(self): 63 | a = b = 0 64 | for card in self.cards: 65 | if card.rank == 'Ace': 66 | a += 1 67 | b += 11 68 | else: 69 | a += values[card.rank] 70 | b += values[card.rank] 71 | if b > 21: 72 | b = a 73 | return a, b 74 | 75 | 76 | class Blackjack: 77 | def __init__(self): 78 | self.player_turn = True 79 | self.deck = Deck() 80 | self.deck.shuffle() 81 | 82 | self.player_hands = [Hand()] 83 | self.player_hands[0].add_card(self.deck.deal()) 84 | self.player_hands[0].add_card(self.deck.deal()) 85 | 86 | self.dealer_hand = Hand() 87 | self.dealer_hand.add_card(self.deck.deal()) 88 | self.dealer_hand.add_card(self.deck.deal()) 89 | 90 | def can_split(self): 91 | return self.player_hands[0].cards[0].rank == self.player_hands[0].cards[1].rank 92 | 93 | def split_hand(self): 94 | self.player_hands.append(Hand()) 95 | card = self.player_hands[0].cards[0] 96 | self.player_hands[1].cards.append(card) 97 | self.player_hands[0].cards.remove(card) 98 | 99 | def hit(self, hand=0): 100 | self.player_hands[hand].add_card(self.deck.deal()) 101 | 102 | def stand(self): 103 | self.dealer_turn() 104 | 105 | def is_player_busted(self): 106 | busted = True 107 | for hand in self.player_hands: 108 | if hand.get_value()[0] <= 21 or hand.get_value()[1] <= 21: 109 | busted = False 110 | return busted 111 | 112 | def is_dealer_busted(self): 113 | return self.dealer_hand.get_value()[0] > 21 and self.dealer_hand.get_value()[1] > 21 114 | 115 | def dealer_turn(self): 116 | self.player_turn = False 117 | while self.dealer_hand.get_value()[0] < 17 or self.dealer_hand.get_value()[1] < 17: 118 | self.dealer_hand.add_card(self.deck.deal()) 119 | 120 | def get_game_result(self): 121 | if self.is_player_busted() and self.is_dealer_busted(): 122 | return "DRAW" 123 | elif self.is_player_busted(): 124 | return "LOSE" 125 | elif self.is_dealer_busted(): 126 | return "WIN" 127 | 128 | max_hand_value = 0 129 | for hand in self.player_hands: 130 | if max_hand_value < max(hand.get_value()) <= 21: 131 | max_hand_value = max(hand.get_value()) 132 | if max(self.dealer_hand.get_value()) < max_hand_value: 133 | return "WIN" 134 | if max(self.dealer_hand.get_value()) == max_hand_value: 135 | return "DRAW" 136 | return "LOSE" 137 | 138 | def has_ended_in_draw(self): 139 | result = self.get_game_result() 140 | return result == "DRAW" 141 | 142 | def has_player_won(self): 143 | result = self.get_game_result() 144 | return result == "WIN" 145 | -------------------------------------------------------------------------------- /minigames/connect4.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import random 3 | 4 | import numpy 5 | 6 | 7 | # p1 = 0 8 | # p2 = 1 9 | 10 | 11 | class Connect4: 12 | def __init__(self): 13 | self.turn = random.randint(0, 1) 14 | self.board = [[-1 for x in range(7)] for y in range(6)] 15 | 16 | def is_legal_move(self, c): 17 | if c < 0 or c > 6: 18 | return False 19 | if self.board[0][c] != -1: # row can't be full 20 | return False 21 | if self.has_player_won(): 22 | return False 23 | return True 24 | 25 | def move(self, column): 26 | if self.is_legal_move(column): 27 | for i in range(5, -1, -1): 28 | if self.board[i][column] == -1: 29 | self.board[i][column] = self.turn 30 | if not self.has_player_won() or self.is_board_full(): 31 | self.change_turn() 32 | break 33 | 34 | def has_player_won(self): 35 | return self.has_four_vertical() or self.has_four_horizontal() or self.has_four_diagonal() 36 | 37 | def has_four_horizontal(self): 38 | for r in range(len(self.board)): 39 | counter = 0 40 | for c in range(len(self.board[r])): 41 | if self.board[r][c] == self.turn: 42 | counter += 1 43 | else: 44 | counter = 0 45 | if counter == 4: 46 | return True 47 | return False 48 | 49 | def has_four_vertical(self): 50 | copy_board = copy.deepcopy(self.board) 51 | flipped_board = numpy.array(copy_board).transpose() 52 | for r in range(len(flipped_board)): 53 | counter = 0 54 | for c in range(len(flipped_board[r])): 55 | if flipped_board[r][c] == self.turn: 56 | counter += 1 57 | else: 58 | counter = 0 59 | if counter == 4: 60 | return True 61 | return False 62 | 63 | def has_four_diagonal(self): 64 | for r in range(len(self.board)): 65 | for c in range(len(self.board[r])): 66 | try: 67 | counter = 0 68 | for i in range(4): 69 | if self.board[r + i][c + i] == self.turn: 70 | counter += 1 71 | else: 72 | counter = 0 73 | if counter == 4: 74 | return True 75 | except IndexError: 76 | pass 77 | for r in range(len(self.board)): 78 | for c in range(len(self.board[r])): 79 | try: 80 | counter = 0 81 | for i in range(4): 82 | if self.board[r + i][c - i] == self.turn: 83 | counter += 1 84 | else: 85 | counter = 0 86 | if counter == 4: 87 | return True 88 | except IndexError: 89 | pass 90 | return False 91 | 92 | def is_board_full(self): 93 | for c in self.board[0]: 94 | if c == -1: 95 | return False 96 | return True 97 | 98 | def change_turn(self): 99 | if self.turn == 0: 100 | self.turn = 1 101 | else: 102 | self.turn = 0 103 | 104 | def get_board(self): 105 | return copy.deepcopy(self.board) 106 | -------------------------------------------------------------------------------- /minigames/flood.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import math 3 | import random 4 | from queue import PriorityQueue 5 | 6 | from generic.formatting import create_table 7 | from minigames.minigame import Minigame 8 | 9 | COLORS = ["blue", "red", "purple", "yellow", "green", "orange"] 10 | 11 | WIDTH = HEIGHT = 10 12 | MOVES_FACTOR = 8 13 | 14 | 15 | class Grid: 16 | def __init__(self, grid=None, moves=0): 17 | if grid is None: 18 | self.set_random_grid() 19 | else: 20 | self.matrix = copy.deepcopy(grid) 21 | self.colors = set() 22 | self.moves = moves 23 | self.cost = 0 24 | self.heuristic() 25 | self.set_colors() 26 | 27 | def pick_color(self, picked_color): 28 | top_node = self.matrix[0][0] 29 | old_color = top_node.color 30 | top_node.color = picked_color 31 | queue = [top_node] 32 | visited = set() 33 | 34 | while len(queue) > 0: 35 | node = queue.pop() 36 | visited.add(node) 37 | neighbours = self.expand_node(node) 38 | for neighbour in neighbours: 39 | if neighbour.color == old_color: 40 | neighbour.color = picked_color 41 | if neighbour not in visited: 42 | queue.append(neighbour) 43 | self.heuristic() 44 | self.set_colors() 45 | 46 | def expand_node(self, node): 47 | x = node.x 48 | y = node.y 49 | up = self.matrix[(x - 1) if (x - 1) >= 0 else 0][y] 50 | down = self.matrix[(x + 1) if (x + 1) < HEIGHT else (HEIGHT - 1)][y] 51 | left = self.matrix[x][(y - 1) if (y - 1) >= 0 else 0] 52 | right = self.matrix[x][(y + 1) if (y + 1) < WIDTH else (WIDTH - 1)] 53 | return [up, down, left, right] 54 | 55 | def set_random_grid(self): 56 | self.matrix = [] 57 | for i in range(HEIGHT): 58 | self.matrix.append([Node(i, j, COLORS[random.randint(0, len(COLORS) - 1)]) for j in range(WIDTH)]) 59 | 60 | def is_solved(self): 61 | top_color = self.matrix[0][0].color 62 | for i in range(HEIGHT): 63 | for j in range(WIDTH): 64 | if self.matrix[i][j].color != top_color: 65 | return False 66 | return True 67 | 68 | def heuristic(self): 69 | top_node = self.matrix[0][0] 70 | queue = [top_node] 71 | visited = set() 72 | size = 0 73 | 74 | while len(queue) > 0: 75 | node = queue.pop() 76 | visited.add(node) 77 | neighbours = self.expand_node(node) 78 | for neighbour in neighbours: 79 | if neighbour.color == top_node.color and neighbour not in visited: 80 | size += 1 81 | queue.append(neighbour) 82 | self.cost = WIDTH * HEIGHT - size 83 | 84 | def set_colors(self): 85 | colors = set() 86 | for i in range(len(self.matrix)): 87 | for j in range(len(self.matrix[i])): 88 | colors.add(self.matrix[i][j].color) 89 | self.colors = colors 90 | 91 | def __lt__(self, other): 92 | return self.moves + self.cost < other.moves + other.cost 93 | 94 | def __gt__(self, other): 95 | return other.moves + other.cost < self.moves + self.cost 96 | 97 | def __eq__(self, other): 98 | return create_table(*self.matrix) == create_table(*other.matrix) and self.moves == other.moves 99 | 100 | def __hash__(self): 101 | return hash(create_table(*self.matrix) + str(self.moves)) 102 | 103 | 104 | class Node: 105 | def __init__(self, x, y, color): 106 | self.x = x 107 | self.y = y 108 | self.color = color 109 | 110 | def __hash__(self): 111 | return hash((self.x, self.y, self.color)) 112 | 113 | 114 | class Flood(Minigame): 115 | def __init__(self): 116 | self.grid = Grid() 117 | self.min_moves = self.solve() 118 | self.min_allowed_moves = self.min_moves + math.floor(self.min_moves / MOVES_FACTOR) 119 | self.player_moves = 0 120 | 121 | def pick_color(self, color): 122 | self.player_moves += 1 123 | self.grid.pick_color(color) 124 | 125 | def has_won(self): 126 | return self.player_moves <= self.min_allowed_moves and self.grid.is_solved() 127 | 128 | def has_lost(self): 129 | return self.player_moves >= self.min_allowed_moves 130 | 131 | def has_drawn(self): 132 | pass # can't draw in scramble 133 | 134 | def solve(self): 135 | queue = PriorityQueue() 136 | queue.put((0, self.grid)) 137 | visited = set() 138 | 139 | while True: 140 | parent = queue.get()[1] 141 | if parent.is_solved(): 142 | break 143 | 144 | visited.add(parent) 145 | for color in parent.colors: 146 | if color != parent.matrix[0][0].color: 147 | grid = Grid(parent.matrix, parent.moves + 1) 148 | grid.pick_color(color) 149 | if grid not in visited: 150 | queue.put((grid.moves + grid.cost, grid)) 151 | return parent.moves 152 | -------------------------------------------------------------------------------- /minigames/hangman.py: -------------------------------------------------------------------------------- 1 | from minigames.lexicon import Lexicon 2 | from minigames.minigame import Minigame 3 | 4 | 5 | class Hangman(Minigame): 6 | def __init__(self): 7 | self.lives = 10 8 | self.word = list(Lexicon.get_random_word()) 9 | self.current_word = ["_" for i in self.word] 10 | self.guessed = [] 11 | 12 | def guess(self, char): 13 | if char in self.guessed: 14 | return 15 | 16 | if char not in self.word: 17 | self.lives -= 1 18 | self.guessed.append(char) 19 | return 20 | 21 | for i in range(len(self.word)): 22 | if self.word[i] == char: 23 | self.current_word[i] = char 24 | self.guessed.append(char) 25 | 26 | def has_won(self): 27 | return "_" not in self.current_word 28 | 29 | def has_lost(self): 30 | return self.lives <= 0 31 | 32 | def has_drawn(self): 33 | pass # can't draw in hangman 34 | 35 | 36 | HANGMAN0 = "_______" 37 | 38 | HANGMAN1 = " |\n" \ 39 | " |\n" \ 40 | " |\n" \ 41 | " |\n" \ 42 | " _|_ _ _" 43 | 44 | HANGMAN2 = " _____\n" \ 45 | " |\n" \ 46 | " |\n" \ 47 | " |\n" \ 48 | " |\n" \ 49 | "_|_ _ _" 50 | 51 | HANGMAN3 = " _____\n" \ 52 | " |/\n" \ 53 | " |\n" \ 54 | " |\n" \ 55 | " |\n" \ 56 | "_|_ _ _" 57 | 58 | HANGMAN4 = " _____\n" \ 59 | " |/ |\n" \ 60 | " |\n" \ 61 | " |\n" \ 62 | " |\n" \ 63 | "_|_ _ _" 64 | 65 | HANGMAN5 = " _____\n" \ 66 | " |/ |\n" \ 67 | " | 0\n" \ 68 | " |\n" \ 69 | " |\n" \ 70 | "_|_ _ _" 71 | 72 | HANGMAN6 = " _____\n" \ 73 | " |/ |\n" \ 74 | " | o\n" \ 75 | " | |\n" \ 76 | " |\n" \ 77 | "_|_ _ _" 78 | 79 | HANGMAN7 = " _____\n" \ 80 | " |/ |\n" \ 81 | " | o\n" \ 82 | " | /|\n" \ 83 | " |\n" \ 84 | "_|_ _ _" 85 | 86 | HANGMAN8 = " _____\n" \ 87 | " |/ |\n" \ 88 | " | o\n" \ 89 | " | /|\\ \n" \ 90 | " |\n" \ 91 | "_|_ _ _" 92 | 93 | HANGMAN9 = " _____\n" \ 94 | " |/ |\n" \ 95 | " | o\n" \ 96 | " | /|\\ \n" \ 97 | " | /\n" \ 98 | "_|_ _ _" 99 | 100 | HANGMAN10 = " _____\n" \ 101 | " |/ |\n" \ 102 | " | o\n" \ 103 | " | /|\\ \n" \ 104 | " | / \\ \n" \ 105 | "_|_ _ _" 106 | 107 | HANGMEN = [HANGMAN10, HANGMAN9, HANGMAN8, HANGMAN7, HANGMAN6, HANGMAN5, HANGMAN4, HANGMAN3, HANGMAN2, HANGMAN1, 108 | HANGMAN0] 109 | -------------------------------------------------------------------------------- /minigames/lexicon.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | 5 | class Lexicon: 6 | QUESTIONS = dict() 7 | WORDS = [] 8 | 9 | @classmethod 10 | def on_startup(cls): 11 | f = open('bin/questions.json') 12 | cls.QUESTIONS = json.loads(f.read()) 13 | f.close() 14 | 15 | with open("bin/10k words.txt") as f: 16 | cls.WORDS = f.readlines() 17 | f.close() 18 | 19 | @classmethod 20 | def get_random_word(cls): 21 | word = cls.WORDS[random.randint(0, len(cls.WORDS) - 1)].rstrip() 22 | while len(word) < 5: 23 | word = cls.WORDS[random.randint(0, len(cls.WORDS) - 1)].rstrip() 24 | return word 25 | -------------------------------------------------------------------------------- /minigames/mastermind.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from minigames.minigame import Minigame 4 | 5 | COLORS = ["blue", "red", "purple", "yellow", "green", "orange"] 6 | 7 | 8 | class Mastermind(Minigame): 9 | def __init__(self): 10 | self.history = [] 11 | self.code = list() 12 | while len(self.code) < 4: 13 | color = COLORS[random.randint(0, 5)] 14 | if color not in self.code: 15 | self.code.append(color) 16 | self.correct_place = 0 17 | self.wrong_place = 0 18 | self.lives = 10 19 | 20 | def guess(self, code): 21 | self.correct_place = 0 22 | self.wrong_place = 0 23 | for i in range(len(code)): 24 | if code[i] == self.code[i]: 25 | self.correct_place += 1 26 | elif code[i] in self.code: 27 | self.wrong_place += 1 28 | 29 | if not self.has_won(): 30 | self.lives -= 1 31 | 32 | self.history.append([code, self.correct_place, self.wrong_place]) 33 | 34 | def has_won(self): 35 | return self.correct_place == 4 36 | 37 | def has_drawn(self): 38 | pass 39 | 40 | def has_lost(self): 41 | return self.lives == 0 42 | -------------------------------------------------------------------------------- /minigames/minigame.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Minigame(ABC): 5 | @abstractmethod 6 | def has_won(self): 7 | raise NotImplementedError 8 | 9 | @abstractmethod 10 | def has_drawn(self): 11 | raise NotImplementedError 12 | 13 | @abstractmethod 14 | def has_lost(self): 15 | raise NotImplementedError 16 | -------------------------------------------------------------------------------- /minigames/scramble.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from random import shuffle 3 | 4 | from minigames.lexicon import Lexicon 5 | from minigames.minigame import Minigame 6 | 7 | 8 | class Scramble(Minigame): 9 | def __init__(self): 10 | word = list(Lexicon.get_random_word()) 11 | self.word = word 12 | self.pointer = 0 13 | self.scrambled_word = copy.deepcopy(self.word) 14 | self.current_word = ["_" for i in self.word] 15 | shuffle(self.scrambled_word) 16 | 17 | def guess(self, char): 18 | if char not in self.scrambled_word: 19 | return 20 | self.scrambled_word.remove(char) 21 | self.current_word[self.pointer] = char 22 | self.pointer += 1 23 | 24 | def remove_last(self): 25 | if self.pointer - 1 < 0: 26 | return "_" 27 | 28 | self.pointer -= 1 29 | c = self.current_word[self.pointer] 30 | if c != "_": 31 | self.scrambled_word.append(self.current_word[self.pointer]) 32 | self.current_word[self.pointer] = "_" 33 | return c 34 | 35 | def has_won(self): 36 | return "".join(self.word) == "".join(self.current_word) 37 | 38 | def has_lost(self): 39 | pass # no lose condition in scramble 40 | 41 | def has_drawn(self): 42 | pass # can't draw in scramble 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | discord~=1.0.1 2 | numpy~=1.19.5 3 | gpiozero~=1.5.1 4 | chess~=1.5.0 5 | dblpy 6 | akinator.py --------------------------------------------------------------------------------