├── VERSION ├── res ├── Asimov.otf ├── img │ ├── back.png │ ├── icon.gif │ ├── delete.jpg │ ├── refresh.png │ ├── select.jpg │ ├── bgsprites.jpg │ └── piecesprite.png ├── sounds │ ├── drag.ogg │ ├── move.ogg │ ├── click.ogg │ ├── start.ogg │ └── background.ogg ├── stockfish │ ├── build │ │ └── README.txt │ └── README.txt ├── preferences.txt ├── savedGames │ └── README.txt ├── texts │ ├── single2.txt │ ├── single1.txt │ ├── stockfish │ │ ├── nonconfigd.txt │ │ ├── configd.txt │ │ ├── stockfish.txt │ │ ├── other.txt │ │ ├── linux.txt │ │ ├── win.txt │ │ ├── mac.txt │ │ └── linux2.txt │ ├── timer.txt │ ├── howto.txt │ ├── online.txt │ └── about.txt ├── README.txt └── CREDITS.txt ├── screenshots ├── main.jpg ├── pref.jpg ├── chess1.jpg ├── chess2.jpg ├── chess3.jpg ├── loadgame.jpg ├── online1.jpg ├── online2.jpg ├── single.jpg ├── stockfish.jpg └── screenshots.md ├── .gitignore ├── ext ├── README.txt ├── pyFish.py └── pyBox.py ├── tools ├── README.txt ├── utils.py ├── sound.py └── loader.py ├── chess ├── __init__.py ├── README.txt ├── onlinelib │ ├── README.txt │ ├── sockutils.py │ ├── __init__.py │ └── utils.py ├── lib │ ├── README.txt │ ├── heuristics.py │ ├── ai.py │ ├── utils.py │ ├── gui.py │ ├── __init__.py │ └── core.py ├── docs.txt ├── online.py ├── multiplayer.py ├── mysingleplayer.py └── singleplayer.py ├── menus ├── __init__.py ├── README.txt ├── about.py ├── online.py ├── howto.py ├── timer.py ├── singleplayer.py ├── pref.py ├── loadgame.py └── stockfish.py ├── LICENSE ├── README.md ├── onlinehowto.txt ├── CHANGELOG.md ├── pychess.py └── server.py /VERSION: -------------------------------------------------------------------------------- 1 | v3.2.0 2 | -------------------------------------------------------------------------------- /res/Asimov.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/Asimov.otf -------------------------------------------------------------------------------- /res/img/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/img/back.png -------------------------------------------------------------------------------- /res/img/icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/img/icon.gif -------------------------------------------------------------------------------- /res/img/delete.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/img/delete.jpg -------------------------------------------------------------------------------- /res/img/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/img/refresh.png -------------------------------------------------------------------------------- /res/img/select.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/img/select.jpg -------------------------------------------------------------------------------- /res/sounds/drag.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/sounds/drag.ogg -------------------------------------------------------------------------------- /res/sounds/move.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/sounds/move.ogg -------------------------------------------------------------------------------- /res/img/bgsprites.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/img/bgsprites.jpg -------------------------------------------------------------------------------- /res/sounds/click.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/sounds/click.ogg -------------------------------------------------------------------------------- /res/sounds/start.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/sounds/start.ogg -------------------------------------------------------------------------------- /screenshots/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/main.jpg -------------------------------------------------------------------------------- /screenshots/pref.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/pref.jpg -------------------------------------------------------------------------------- /res/img/piecesprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/img/piecesprite.png -------------------------------------------------------------------------------- /screenshots/chess1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/chess1.jpg -------------------------------------------------------------------------------- /screenshots/chess2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/chess2.jpg -------------------------------------------------------------------------------- /screenshots/chess3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/chess3.jpg -------------------------------------------------------------------------------- /screenshots/loadgame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/loadgame.jpg -------------------------------------------------------------------------------- /screenshots/online1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/online1.jpg -------------------------------------------------------------------------------- /screenshots/online2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/online2.jpg -------------------------------------------------------------------------------- /screenshots/single.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/single.jpg -------------------------------------------------------------------------------- /res/sounds/background.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/res/sounds/background.ogg -------------------------------------------------------------------------------- /screenshots/stockfish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankith26/My-PyChess/HEAD/screenshots/stockfish.jpg -------------------------------------------------------------------------------- /res/stockfish/build/README.txt: -------------------------------------------------------------------------------- 1 | PUT A STOCKFISH EXECUTABLE IN THIS DIRECTORY AFTER RENAMING IT TO "stockfish" 2 | ("stockfish.exe" for Windows) 3 | -------------------------------------------------------------------------------- /res/preferences.txt: -------------------------------------------------------------------------------- 1 | sounds = False 2 | flip = False 3 | slideshow = True 4 | show_moves = True 5 | allow_undo = True 6 | show_clock = False 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | notes.txt 2 | 3 | **/__pycache__/ 4 | 5 | res/stockfish/build/stockfish.exe 6 | res/stockfish/path.txt 7 | res/savedGames/game*.txt 8 | -------------------------------------------------------------------------------- /res/savedGames/README.txt: -------------------------------------------------------------------------------- 1 | This is a folder where all games are stored. 2 | If you use savegame feature, the file gets saved here with a gameid. 3 | Load it using the loadgame feature in main menu. -------------------------------------------------------------------------------- /res/texts/single2.txt: -------------------------------------------------------------------------------- 1 | Play chess against the StockFish Chess Engine, the best 2 | chess engine in the world. This playes a very hard level of 3 | chess, even level 1 and 2 give a good fight to chess 4 | intermediates. Not recommended for beginners. -------------------------------------------------------------------------------- /res/texts/single1.txt: -------------------------------------------------------------------------------- 1 | Play chess against a chess player algorithm implemented in 2 | python. This is called MiniMax algorithm and is used with 3 | alpha-beta optimisation technique. Currently, it is setup 4 | to play like an average (weak) player of chess. -------------------------------------------------------------------------------- /res/texts/stockfish/nonconfigd.txt: -------------------------------------------------------------------------------- 1 | You have not configured stockfish. Click the button to 2 | do so. 3 | 4 | You will have to install the stockfish package 5 | seperately. Clicking the button will take you to the 6 | appropriate installation and setup guides. -------------------------------------------------------------------------------- /res/stockfish/README.txt: -------------------------------------------------------------------------------- 1 | This folder is a place to store stockfish chess engine related stuff. 2 | Some stockfish config files are stored here. 3 | 4 | If you are searching for a place to put the stockfish executable, navigate inside 5 | the build directory in this directory. 6 | -------------------------------------------------------------------------------- /ext/README.txt: -------------------------------------------------------------------------------- 1 | This folder is a place to store some external scripts. These scripts are essential for PyChess. They are general purpose modules - you can use them too. 2 | 3 | pyBox.py - Defines a class for textbox in pygame 4 | pyFish.py - Define a class to interface with the stockfish chess engine -------------------------------------------------------------------------------- /res/texts/stockfish/configd.txt: -------------------------------------------------------------------------------- 1 | It appears that you have configured stockfish correctly. 2 | You can play against stockfish now. If you are not able to 3 | play against stockfish, there might be an error. 4 | Try to reconfigure. 5 | If you want to reconfigure stockfish, click the button 6 | above. -------------------------------------------------------------------------------- /tools/README.txt: -------------------------------------------------------------------------------- 1 | A few important modules are placed here, they are handy thoughout the codebase. 2 | 3 | These are: 4 | 5 | loader.py - Load all fonts for each script, put them into their respective classes 6 | sound.py - Load all sounds, define functions for easy sound playback 7 | utils.py - Contains general utility functions, mostly GUI related. -------------------------------------------------------------------------------- /chess/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we import the modules for easy access. 4 | ''' 5 | 6 | from chess.multiplayer import main as multiplayer 7 | from chess.singleplayer import main as singleplayer 8 | from chess.mysingleplayer import main as mysingleplayer 9 | from chess.online import main as online -------------------------------------------------------------------------------- /chess/README.txt: -------------------------------------------------------------------------------- 1 | In this folder we keep all scripts to handle chess gameplay. 2 | Each file has a main() function, which when called, handles the chess gameplay 3 | 4 | multiplayer.py - Handles multiplayer chess 5 | singlepayer.py - Handles singleplayer chess with stockfish chess engine 6 | mysingleplayer.py - Handles singleplayer chess with a chess engine written in python(minimax-algorithm) 7 | online.py - Handles online chess 8 | 9 | For more docmentation of the variables used and such, check docs.txt -------------------------------------------------------------------------------- /chess/onlinelib/README.txt: -------------------------------------------------------------------------------- 1 | In this folder we declare the 'onlinelib' module. This implements all of clients online gamemode related stuff. 2 | This module is used by online.py 3 | 4 | __init__.py - Defines two main functions, chess() to manage chess board and lobby() to manage lobby 5 | utils.py - Define all important utilities for __init__.py. Mostly Gui related. 6 | sockutils.py - As the name suggests, define all utility funcions for socket related stuff. 7 | 8 | For more docmentation of the variables used and such, check docs.txt -------------------------------------------------------------------------------- /res/texts/stockfish/stockfish.txt: -------------------------------------------------------------------------------- 1 | Stockfish is a chess engine, means that it can automatically 2 | play chess against you. It is free and open source, developed 3 | by T. Romstad, M. Costalba, J. Kiiski, G. Linscott and many 4 | others. 5 | 6 | Regarded as one of the best chess engine in the world, it is 7 | very popular among people. 8 | Check out its homepage at stockfishchess.org, source code 9 | is also available at github. 10 | 11 | My-PyChess is meant to act like a GUI and provide a medium 12 | for you to play against it. -------------------------------------------------------------------------------- /res/texts/timer.txt: -------------------------------------------------------------------------------- 1 | 2 | Configure the game timer for the multiplayer chess game! 3 | 4 | 5 | Choose the number of minutes each side will get to 6 | complete the game. 7 | 8 | CHOOSE: 30 15 10 05 03 01 9 | 10 | 11 | Choose number of seconds to be incremented per move. 12 | 13 | CHOOSE: 00 01 02 03 04 14 | 15 | 16 | The number of seconds you selected will be added to your 17 | timer for every move you make. 18 | 19 | 20 | Are you ready to start playing? Start -------------------------------------------------------------------------------- /menus/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we import a few modules for easy access for main file 4 | ''' 5 | 6 | from menus.about import main as aboutmenu 7 | from menus.howto import main as howtomenu 8 | from menus.loadgame import main as loadgamemenu 9 | from menus.online import main as onlinemenu 10 | from menus.pref import main as prefmenu 11 | from menus.singleplayer import main as splayermenu 12 | from menus.stockfish import main as sfmenu 13 | from menus.timer import main as timermenu -------------------------------------------------------------------------------- /res/texts/stockfish/other.txt: -------------------------------------------------------------------------------- 1 | 2 | Python could not determine your OS. That is weird. That's 3 | why My-PyChess cannot provide you any installation guide. 4 | 5 | 6 | In such a situation, you have to build Stockfish from source. 7 | There are a few online tutorials on this topic, follow along 8 | any one of them, you will get an executable. 9 | 10 | 11 | Run the executable, if it gives a line like: 12 | 'Stockfish 11 by T.Romstad, ----' 13 | then executable IS WORKING. 14 | 15 | 16 | Rename the WORKING executable to "stockfish" and place it 17 | as the following file: -------------------------------------------------------------------------------- /res/texts/howto.txt: -------------------------------------------------------------------------------- 1 | So you don't know how to play chess. That's not a problem, 2 | there are tons of helpful resources available online to 3 | learn. 4 | 5 | Some which I found helpful are (I'm not affiliated with any): 6 | 7 | 1) Instructables 8 | 9 | 2) chess.com 10 | 11 | 3) Another Instuctables 12 | 13 | 4) thechesswebsite.com 14 | 15 | 5) Lichess 16 | 17 | Click on the links given, the page will open in your favourite 18 | web browser. 19 | 20 | If you are a chess beginner, I highly recommend that you go 21 | through these links. You may learn something new. -------------------------------------------------------------------------------- /res/texts/online.txt: -------------------------------------------------------------------------------- 1 | Previously, this menu page gave users the option 2 | to connect to an external server I used to host 3 | for testing purposes. 4 | 5 | This server is not being hosted anymore and the 6 | project is not being maintained actively as of 7 | June 2022. 8 | 9 | 10 | 11 | THE SECTION BELOW IS FOR SELF-HOSTED SERVERS 12 | 13 | Enter the server's address below and connect to it - 14 | 15 | 16 | 17 | 18 | Advanced Settings: 19 | Enter the IP protocol of server (IPv4 is default) 20 | 21 | IPv4 IPv6 22 | -------------------------------------------------------------------------------- /res/texts/stockfish/linux.txt: -------------------------------------------------------------------------------- 1 | If you are running Debian or Debian-based OS (like Ubuntu, 2 | Mint, Raspbian, etc), open the shell and type: 3 | >>> sudo apt-get install stockfish 4 | 5 | If you are running Fedora, do: 6 | >>> sudo dnf install stockfish 7 | 8 | If you are running openSUSE or similar OS, do: 9 | >>> sudo zypper install stockfish 10 | 11 | If you are running centOS or similar OS, do: 12 | >>> sudo yum install stockfish 13 | 14 | If your OS has any package manager, use it to install 15 | stockfish and your job is done. 16 | 17 | 18 | Your OS is not on this list and has no package managers? -------------------------------------------------------------------------------- /res/README.txt: -------------------------------------------------------------------------------- 1 | In this folder, we keep all the Resources ("res") required for the project. 2 | Some important config files are also stored here. 3 | For resource credits, checkout credits.txt 4 | 5 | ALL FILES AND FOLDERS, ANYTHING UNDER THIS DIRECTORY IS VERY IMPORTANT FOR MY-PYCHESS TO WORK. 6 | DO NOT DELETE ANYTHING. 7 | 8 | img - Folder to store images 9 | savedGames - Folder to store saved games 10 | sounds - Folder to store sound files 11 | stockfish - Folder to store stockfish executable and config file 12 | texts - Folder to store a few texts 13 | 14 | Asimov.otf - Font file 15 | preferences.txt - config file to store user preferences and settings -------------------------------------------------------------------------------- /res/texts/stockfish/win.txt: -------------------------------------------------------------------------------- 1 | This is the installation guide for Stockfish chess engine. 2 | It is a very simple procedure with four simple steps. 3 | 4 | Step 1: Head over to downloads page of stockfish, 5 | stockfishchess.org. 6 | 7 | Step 2: Download the zipfile for Windows and extract 8 | it in any folder on your computer. 9 | 10 | Step 3: In this Folder, you will see a folder named 11 | Windows. Inside which, there will be a set of 12 | stockfish executables. Choose the one that 13 | says 32-bit executable. 14 | 15 | Step 4: Copy this file and paste in the following 16 | path after renaming the exe to stockfish.exe. -------------------------------------------------------------------------------- /res/texts/stockfish/mac.txt: -------------------------------------------------------------------------------- 1 | This is the Mac installation guide for stockfish. If you have 2 | homebrew installed on your system, enter in shell: 3 | >>> brew install stockfish 4 | 5 | If you do not have homebrew, you have to install it first by 6 | typing this in shell: 7 | >>> /bin/bash -c "$(curl -fsSL https://raw.githubuser 8 | content.com/Homebrew/install/master/install.sh)" 9 | 10 | After this step, you can use the 'brew install stockfish' 11 | command on your shell. 12 | 13 | NOTE 1: For now, Mac users have only one option, using 14 | homebrew. Using an executable directly is not supported 15 | yet. 16 | 17 | NOTE 2: I DO NOT HAVE A MAC, SO THIS METHOD OF 18 | INSTALLATION IS NOT TESTED BY ME. BUT IT SHOULD 19 | STILL WORK NONETHELESS. -------------------------------------------------------------------------------- /res/texts/stockfish/linux2.txt: -------------------------------------------------------------------------------- 1 | Your OS was not on the list. No problem, just head over 2 | to downloads page of stockfishchess.org and download the 3 | zip file for linux. After unzipping, navigate inside the 4 | Linux folder and try to run any executable. 5 | 6 | If it gives a line of text like: 7 | 'Stockfish 11 by T. Romstad, ...... etc', then the 8 | EXECUTABLE IS WORKING. If that does not work (in most 9 | cases it won't), you have to build the executable from 10 | source. 11 | 12 | To achieve this, there are lot of online tutorials. 13 | Follow any one to get a WORKING EXECUTABLE, test it 14 | by the method given above. 15 | 16 | FINALLY, AFTER you got a working executable, place it 17 | as the following file (after renaming the exectable to 18 | 'stockfish') -------------------------------------------------------------------------------- /menus/README.txt: -------------------------------------------------------------------------------- 1 | In this folder, we keep all python scripts that are responible for displaying menus. 2 | Each script has a main() function that needs to be called to display the menu. 3 | 4 | __init__.py - Import all menu modules for easy importing from other files 5 | about.py - Handles about Menu. 6 | howto.py - Handles Chess howto Menu. 7 | loadgame.py - Handles loadgame menu, also defines utility functions for scanning, loading and deleting games. 8 | online.py - Handles online Menu. 9 | pref.py - Handles preferences menu, also defines utility functions for loading and saving user preferences. 10 | singleplayer.py - Handles singleplayer menu 11 | stockfish.py - Handles stockfish menu, also defines functions to handle stockfish install guides. 12 | timer.py - Handles timer menu. -------------------------------------------------------------------------------- /chess/lib/README.txt: -------------------------------------------------------------------------------- 1 | In this folder we declare the 'chess.lib' module. This implements all chess features, minimax algorithm for singleplayer and GUI for chess board. 2 | 3 | 4 | __init__.py - This will be imported when one does from 'chess.lib import *'. Bundles all functions from the lib and also define a few important wrapper functions. 5 | ai.py - This is NOT Artificial Intelligence :). Just implementation of chess player algorithm. 6 | core.py - This is a VERY IMPORTANT module. Defines functions to handle all chess related stuff. 7 | gui.py - As the name suggests, define all gui funcions for chess board. 8 | heuristics.py - Defines a few constants to be used in ai.py 9 | utils.py - Define a few non-gui utility chess functions that are not core-chess related. 10 | 11 | For more docmentation of the variables used and such, check docs.txt -------------------------------------------------------------------------------- /res/texts/about.txt: -------------------------------------------------------------------------------- 1 | My-Pychess was started by me as a small hobby project, I 2 | had hoped I would finish it within a month. But, I could 3 | never stop on improving it. I kept on adding more and more 4 | features which I still do today. I follow 4 principles: 5 | 6 | "Fix a lot of functionality in a small piece of code." 7 | I like to have a lot of features and keep the code very small. 8 | 9 | "Do not compromise on readability of code." 10 | This is important, not only for others to read your code, but 11 | even for you. 12 | 13 | "Try to do as much testing as you can to avoid bugs." 14 | Bugs are literally the worst. I am always trying to minimise 15 | them. 16 | 17 | 18 | Lastly and most importantly, 19 | "Enjoy what you do." 20 | That's what keeps me going. -------------------------------------------------------------------------------- /screenshots/screenshots.md: -------------------------------------------------------------------------------- 1 | # A few Screenshots of My-PyChess 2 | 3 | I have put together a few screenshots of My-PyChess. 4 | Some of those are from older versions of the game. 5 | 6 | ## This is the Main Menu 7 | ![Main Menu](main.jpg) 8 | 9 | ## Started a chess match 10 | ![Started Chess](chess1.jpg) 11 | 12 | ## Oh Check! Gotta save the king 13 | ![Check](chess2.jpg) 14 | 15 | ## Who would do this 16 | ![Checkmate](chess3.jpg) 17 | 18 | ## I want to customise My-PyChess 19 | ![Preferences](pref.jpg) 20 | 21 | ## I want to load the games I saved 22 | ![loadgame](loadgame.jpg) 23 | 24 | ## Let's see how good I play against a computer 25 | ![single player](single.jpg) 26 | 27 | ## Gotta configure stockfish to play with it 28 | ![stockfish](stockfish.jpg) 29 | 30 | ## Play Chess Online! 31 | ![online](online1.jpg) 32 | 33 | ## This is the Online Lobby 34 | ![online](online2.jpg) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ankith(ankith26) 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. -------------------------------------------------------------------------------- /menus/about.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the about menu which is called when user clicks 4 | about button on main menu. 5 | ''' 6 | 7 | import pygame 8 | from tools.loader import ABOUT, BACK 9 | from tools.utils import rounded_rect 10 | 11 | # This shows the screen 12 | def showScreen(win): 13 | win.fill((0, 0, 0)) 14 | rounded_rect(win, (255, 255, 255), (70, 10, 360, 60), 16, 4) 15 | rounded_rect(win, (255, 255, 255), (10, 80, 480, 410), 10, 4) 16 | 17 | win.blit(ABOUT.HEAD, (74, 12)) 18 | for cnt, i in enumerate(ABOUT.TEXT): 19 | win.blit(i, (20, 90 + cnt*18)) 20 | 21 | win.blit(BACK, (460, 0)) 22 | pygame.display.update() 23 | 24 | # This is the main function, called from main menu 25 | def main(win): 26 | showScreen(win) 27 | clock = pygame.time.Clock() 28 | while True: 29 | clock.tick(24) 30 | for event in pygame.event.get(): 31 | if event.type == pygame.QUIT: 32 | return 0 33 | 34 | elif event.type == pygame.MOUSEBUTTONDOWN: 35 | x, y = event.pos 36 | if 460 < x < 500 and 0 < y < 50: 37 | return 1 38 | -------------------------------------------------------------------------------- /chess/docs.txt: -------------------------------------------------------------------------------- 1 | Documentation for commonly used chess variables: 2 | 1. side --> This variable is a boolean that stores which side plays next. 3 | If white is supposed to play next, it stores False. True otherwise. 4 | 5 | 2. board -> A 'board' variable stores the current state of the board. 6 | This is a 2-element tuple, First element is a list of all the 7 | pieces of white, and the second contains all pieces of black. 8 | 9 | 3. piece -> A 'piece' variable is a 3-element list. A piece can be denoted 10 | by it's x and y cordinate on the chess board and it's type 11 | ("k" (king), "q" (queen), etc ...) 12 | 13 | 4. flags -> This stores the required flags for castling and enpassent. 14 | This is a 2-element list, first element is flag for castling. 15 | second is for enpassent. 16 | 17 | See initBoardVars() function at chess.lib.utils for a representation of these 18 | variables in their initial state (when the chess game starts) 19 | 20 | Some other variables are 21 | 22 | 1. win -> The pygame window object. 23 | 2. sel/prevsel/pos -> This is a pair of coordinates. example: [1, 2], [3, 5] etc.. 24 | 3. prefs/load -> A dictionary containing user preferences. 25 | 26 | The coordinate system used across this game is similar to the system used by 27 | pygame. 28 | The top left square is denoted by [1, 1] and as you go right, x value increases 29 | (x is the first element in the list) and as you go down, y value increases 30 | (y is the second element in the list). By this convention, the bottom right 31 | square becomes [8, 8]. -------------------------------------------------------------------------------- /tools/utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we define a few general utilities for My-PyChess. 4 | ''' 5 | 6 | import time 7 | 8 | import pygame 9 | import pygame.gfxdraw 10 | 11 | # This function needs to be called when user wants to draw a rounded rect 12 | def rounded_rect(surf, color, rect, radius=10, border=2, incolor=(0, 0, 0)): 13 | if min(rect[2], rect[3]) > 2 * (radius + border): 14 | _filled_rounded_rect(surf, color, rect, radius) 15 | rect = (rect[0] + border, rect[1] + border, 16 | rect[2] - 2*border, rect[3] - 2*border) 17 | _filled_rounded_rect(surf, incolor, rect, radius) 18 | 19 | # This is the function that draws a solid rounded rect 20 | def _filled_rounded_rect(surf, color, rect, r): 21 | for x, y in [(rect[0] + r, rect[1] + r), 22 | (rect[0] + rect[2] - r - 1, rect[1] + r), 23 | (rect[0] + r, rect[1] + rect[3] - r - 1), 24 | (rect[0] + rect[2] - r - 1, rect[1] + rect[3] - r - 1)]: 25 | pygame.gfxdraw.aacircle(surf, x, y, r, color) 26 | pygame.gfxdraw.filled_circle(surf, x, y, r, color) 27 | 28 | pygame.draw.rect(surf, color, (rect[0] + r, rect[1], rect[2] - 2*r, rect[3])) 29 | pygame.draw.rect(surf, color, (rect[0], rect[1] + r, rect[2], rect[3] - 2*r)) 30 | 31 | # A function to be used as a decorator to check execution time of a function 32 | # Used only while testing. 33 | def timeit(func): 34 | def inner(*args, **kwargs): 35 | start = time.perf_counter() 36 | ret = func(*args, **kwargs) 37 | end = time.perf_counter() 38 | print("Time:", round((end-start)*1000, 4), "ms") 39 | return ret 40 | return inner -------------------------------------------------------------------------------- /chess/online.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the chess gameplay for online section of this 4 | application. 5 | 6 | We use the "online lib" module 7 | ''' 8 | import socket 9 | import threading 10 | 11 | from chess.onlinelib import * 12 | 13 | VERSION = "v3.2.0" 14 | PORT = 26104 15 | 16 | # This is a main function that calls all other functions, socket initialisation 17 | # and the screen that appears just after online menu but just before online lobby. 18 | def main(win, addr, load, ipv6=False): 19 | showLoading(win) 20 | 21 | if ipv6: 22 | sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 23 | servaddr = (addr, PORT, 0, 0) 24 | else: 25 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 26 | servaddr = (addr, PORT) 27 | 28 | try: 29 | sock.connect(servaddr) 30 | 31 | except: 32 | showLoading(win, 1) 33 | return 1 34 | 35 | thread = threading.Thread(target=bgThread, args=(sock,)) 36 | thread.start() 37 | write(sock, "PyChess") 38 | write(sock, VERSION) 39 | 40 | ret = 1 41 | msg = read() 42 | if msg == "errVer": 43 | showLoading(win, 2) 44 | 45 | elif msg == "errBusy": 46 | showLoading(win, 3) 47 | 48 | elif msg == "errLock": 49 | showLoading(win, 4) 50 | 51 | elif msg.startswith("key"): 52 | ret = lobby(win, sock, int(msg[3:]), load) 53 | 54 | else: 55 | print(msg) 56 | showLoading(win, 5) 57 | 58 | write(sock, "quit") 59 | sock.close() 60 | thread.join() 61 | flush() 62 | 63 | if ret == 2: 64 | showLoading(win, -1) 65 | return 1 66 | return ret 67 | -------------------------------------------------------------------------------- /tools/sound.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of of My-PyChess application. 3 | 4 | In this file, we handle all sound related stuff. 5 | """ 6 | 7 | import os.path 8 | import time 9 | 10 | try: 11 | import pygame.mixer 12 | pygame.mixer.init() 13 | 14 | SUCCESS = pygame.mixer.get_init() is not None 15 | 16 | except (ImportError, RuntimeError): 17 | SUCCESS = False 18 | 19 | if SUCCESS: 20 | click = pygame.mixer.Sound(os.path.join("res", "sounds", "click.ogg")) 21 | move = pygame.mixer.Sound(os.path.join("res", "sounds", "move.ogg")) 22 | start = pygame.mixer.Sound(os.path.join("res", "sounds", "start.ogg")) 23 | drag = pygame.mixer.Sound(os.path.join("res", "sounds", "drag.ogg")) 24 | 25 | background = pygame.mixer.Sound(os.path.join("res", "sounds", "background.ogg")) 26 | 27 | class Music: 28 | def __init__(self): 29 | self.playing = False 30 | 31 | def play(self, load): 32 | if SUCCESS and load["sounds"]: 33 | background.play(-1) 34 | self.playing = True 35 | 36 | def stop(self): 37 | if SUCCESS: 38 | background.stop() 39 | self.playing = False 40 | 41 | def is_playing(self): 42 | return self.playing 43 | 44 | def play_click(load): 45 | if SUCCESS and load["sounds"]: 46 | click.play() 47 | time.sleep(0.1) 48 | 49 | def play_start(load): 50 | if SUCCESS and load["sounds"]: 51 | start.play() 52 | 53 | def play_move(load): 54 | if SUCCESS and load["sounds"]: 55 | move.play() 56 | time.sleep(0.1) 57 | 58 | def play_drag(load): 59 | if SUCCESS and load["sounds"]: 60 | drag.play() 61 | 62 | if SUCCESS: 63 | pygame.mixer.quit() 64 | -------------------------------------------------------------------------------- /menus/online.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the online menu which is called when user clicks 4 | online button on main menu. 5 | ''' 6 | 7 | import pygame 8 | from ext.pyBox import TextBox 9 | from tools.loader import ONLINEMENU, BACK, FONT 10 | from tools.utils import rounded_rect 11 | 12 | # This shows the screen 13 | def showScreen(win, sel): 14 | win.fill((0, 0, 0)) 15 | 16 | rounded_rect(win, (255, 255, 255), (120, 10, 260, 70), 20, 4) 17 | rounded_rect(win, (255, 255, 255), (20, 90, 460, 400), 14, 4) 18 | win.blit(ONLINEMENU.HEAD, (175, 15)) 19 | win.blit(BACK, (460, 0)) 20 | 21 | for cnt, i in enumerate(ONLINEMENU.TEXT): 22 | win.blit(i, (40, 100 + cnt*18)) 23 | 24 | rounded_rect(win, (255, 255, 255), (300, 350, 110, 30), 10, 3) 25 | win.blit(ONLINEMENU.CONNECT, (300, 350)) 26 | 27 | pygame.draw.rect(win, (255, 255, 255), (130 + sel*160, 460, 40, 20), 3) 28 | 29 | 30 | # This is the main function, called from main menu 31 | def main(win): 32 | clock = pygame.time.Clock() 33 | sel = 0 34 | 35 | box = TextBox(FONT, (0, 0, 0), (65, 350, 200, 35)) 36 | while True: 37 | clock.tick(24) 38 | showScreen(win, sel) 39 | 40 | pygame.draw.rect(win, (255, 255, 255), (63, 348, 204, 39)) 41 | box.draw(win) 42 | 43 | for event in pygame.event.get(): 44 | box.push(event) 45 | 46 | if event.type == pygame.QUIT: 47 | return 0 48 | 49 | elif event.type == pygame.MOUSEBUTTONDOWN: 50 | x, y = event.pos 51 | if 460 < x < 500 and 0 < y < 50: 52 | return 1 53 | 54 | if 460 < y < 480: 55 | if 130 < x < 170: 56 | sel = 0 57 | 58 | if 290 < x < 320: 59 | sel = 1 60 | 61 | if 300 < x < 410 and 350 < y < 380: 62 | return box.text, bool(sel) 63 | pygame.display.update() 64 | -------------------------------------------------------------------------------- /chess/onlinelib/sockutils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we define a few utility funtions and wrappers for socket related 4 | stuff. 5 | ''' 6 | import queue 7 | import socket 8 | 9 | q = queue.Queue() 10 | isdead = True 11 | 12 | # Define a background thread that continuously runs and gets messages from 13 | # server, formats them and puts them into a queue (IO buffer). 14 | def bgThread(sock): 15 | global isdead 16 | isdead = False 17 | while True: 18 | try: 19 | msg = sock.recv(8).decode("utf-8").strip() 20 | 21 | except: 22 | break 23 | 24 | if not msg or msg == "close": 25 | break 26 | 27 | if msg != "........": 28 | q.put(msg) 29 | isdead = True 30 | 31 | # Returns wether background thread is dead and IO buffer is empty. 32 | def isDead(): 33 | return q.empty() and isdead 34 | 35 | # A function to read messages sent from the server, reads from queue. 36 | def read(): 37 | if isDead(): 38 | return "close" 39 | return q.get() 40 | 41 | # Check wether a message is readable or not 42 | def readable(): 43 | if isDead(): 44 | return True 45 | return not q.empty() 46 | 47 | # Flush IO Buffer. Returns False if quit command is encountered. True otherwise. 48 | def flush(): 49 | while readable(): 50 | if read() == "close": 51 | return False 52 | return True 53 | 54 | # A function to message the server, this is used instead of socket.send() 55 | # beacause it buffers the message, handles packet loss and does not raise 56 | # exception if message could not be sent 57 | def write(sock, msg): 58 | if msg: 59 | buffedmsg = msg + (" " * (8 - len(msg))) 60 | try: 61 | sock.sendall(buffedmsg.encode("utf-8")) 62 | except: 63 | pass 64 | 65 | # A function to query the server for number of people online, returns a list 66 | # of players connected to server if all went well, None otherwise. 67 | def getPlayers(sock): 68 | if not flush(): 69 | return None 70 | 71 | write(sock, "pStat") 72 | 73 | msg = read() 74 | if msg.startswith("enum"): 75 | data = [] 76 | for i in range(int(msg[-1])): 77 | newmsg = read() 78 | if newmsg == "close": 79 | return None 80 | else: 81 | data.append(newmsg) 82 | return tuple(data) -------------------------------------------------------------------------------- /menus/howto.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the about menu which is called when user clicks 4 | about button on main menu. 5 | ''' 6 | import webbrowser 7 | 8 | import pygame 9 | from tools.loader import HOWTO, BACK 10 | from tools.utils import rounded_rect 11 | 12 | LINK = ( 13 | "https://www.instructables.com/id/Playing-Chess/", 14 | "https://www.chess.com/learn-how-to-play-chess", 15 | "https://www.instructables.com/id/Learning-to-Play-Chess/", 16 | "https://www.thechesswebsite.com/learn-to-play-chess/", 17 | "https://lichess.org/learn#/", 18 | ) 19 | 20 | # This shows the screen 21 | def showScreen(win): 22 | win.fill((0, 0, 0)) 23 | rounded_rect(win, (255, 255, 255), (70, 10, 360, 60), 16, 4) 24 | rounded_rect(win, (255, 255, 255), (10, 80, 480, 410), 10, 4) 25 | 26 | # (40, 200, 100, 20) https://www.instructables.com/id/Playing-Chess/ 27 | # (40, 236, 90, 20) https://www.chess.com/learn-how-to-play-chess 28 | # (40, 272, 160, 20) https://www.instructables.com/id/Learning-to-Play-Chess/ 29 | # (40, 308, 170, 20) https://www.thechesswebsite.com/learn-to-play-chess/ 30 | # (40, 344, 60, 20) https://lichess.org/learn#/ 31 | 32 | pygame.draw.line(win, (255, 255, 255), (40, 218), (140, 218), 2) 33 | pygame.draw.line(win, (255, 255, 255), (40, 254), (130, 254), 2) 34 | pygame.draw.line(win, (255, 255, 255), (40, 290), (200, 290), 2) 35 | pygame.draw.line(win, (255, 255, 255), (40, 326), (210, 326), 2) 36 | pygame.draw.line(win, (255, 255, 255), (40, 362), (100, 362), 2) 37 | 38 | win.blit(HOWTO.HEAD, (100, 12)) 39 | for cnt, i in enumerate(HOWTO.TEXT): 40 | win.blit(i, (20, 90 + cnt*18)) 41 | 42 | win.blit(BACK, (460, 0)) 43 | pygame.display.update() 44 | 45 | # This is the main function, called from main menu 46 | def main(win): 47 | showScreen(win) 48 | clock = pygame.time.Clock() 49 | while True: 50 | clock.tick(24) 51 | for event in pygame.event.get(): 52 | if event.type == pygame.QUIT: 53 | return 0 54 | 55 | elif event.type == pygame.MOUSEBUTTONDOWN: 56 | x, y = event.pos 57 | if 460 < x < 500 and 0 < y < 50: 58 | return 1 59 | 60 | if 40 < x < 140 and 200 < y < 220: 61 | webbrowser.open(LINK[0]) 62 | 63 | elif 40 < x < 130 and 236 < y < 256: 64 | webbrowser.open(LINK[1]) 65 | 66 | elif 40 < x < 200 and 272 < y < 292: 67 | webbrowser.open(LINK[2]) 68 | 69 | elif 40 < x < 210 and 308 < y < 328: 70 | webbrowser.open(LINK[3]) 71 | 72 | elif 40 < x < 100 and 344 < y < 364: 73 | webbrowser.open(LINK[4]) 74 | -------------------------------------------------------------------------------- /chess/multiplayer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the chess gameplay for multiplayer section of this 4 | application. 5 | ''' 6 | import time 7 | from chess.lib import * 8 | 9 | # run main code for chess 10 | def main(win, mode, timer, load, movestr=""): 11 | start(win, load) 12 | 13 | moves = movestr.split() 14 | 15 | side, board, flags = convertMoves(moves) 16 | clock = pygame.time.Clock() 17 | sel = prevsel = [0, 0] 18 | 19 | if timer is not None: 20 | timer = list(timer) 21 | while True: 22 | looptime = getTime() 23 | clock.tick(25) 24 | 25 | timedelta = 0 26 | for event in pygame.event.get(): 27 | if event.type == pygame.QUIT: 28 | starttime = getTime() 29 | if prompt(win): 30 | return 0 31 | timedelta += getTime() - starttime 32 | 33 | elif event.type == pygame.MOUSEBUTTONDOWN: 34 | x, y = event.pos 35 | if 460 < x < 500 and 0 < y < 50: 36 | starttime = getTime() 37 | if prompt(win): 38 | return 1 39 | timedelta += getTime() - starttime 40 | 41 | if 50 < x < 450 and 50 < y < 450: 42 | x, y = x // 50, y // 50 43 | if load["flip"] and side: 44 | x, y = 9 - x, 9 - y 45 | 46 | if isOccupied(side, board, [x, y]): 47 | sound.play_click(load) 48 | 49 | prevsel = sel 50 | sel = [x, y] 51 | 52 | if isValidMove(side, board, flags, prevsel, sel): 53 | starttime = getTime() 54 | promote = getPromote(win, side, board, prevsel, sel) 55 | animate(win, side, board, prevsel, sel, load) 56 | 57 | timedelta += getTime() - starttime 58 | timer = updateTimer(side, mode, timer) 59 | 60 | side, board, flags = makeMove( 61 | side, board, prevsel, sel, flags, promote) 62 | moves.append(encode(prevsel, sel, promote)) 63 | 64 | else: 65 | sel = [0, 0] 66 | if 350 < x < 500 and 460 < y < 490: 67 | starttime = getTime() 68 | if prompt(win, saveGame(moves, mode=mode, timer=timer)): 69 | return 1 70 | timedelta += getTime() - starttime 71 | 72 | elif 0 < x < 80 and 0 < y < 50 and load["allow_undo"]: 73 | moves = undo(moves) 74 | side, board, flags = convertMoves(moves) 75 | 76 | showScreen(win, side, board, flags, sel, load) 77 | timer = showClock(win, side, mode, timer, looptime, timedelta) 78 | -------------------------------------------------------------------------------- /chess/mysingleplayer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the chess gameplay for singleplayer section of this 4 | application. This interfaces with a chess engine implemented in pure python. 5 | For the Python Chess Engine, see file at chess.lib.ai 6 | 7 | For a better understanding of the variables used here, checkout docs.txt 8 | ''' 9 | 10 | from chess.lib import * 11 | 12 | # Run main code for chess singleplayer 13 | def main(win, player, load, movestr=""): 14 | start(win, load) 15 | 16 | moves = movestr.split() 17 | side, board, flags = convertMoves(moves) 18 | 19 | clock = pygame.time.Clock() 20 | sel = prevsel = [0, 0] 21 | while True: 22 | clock.tick(25) 23 | end = isEnd(side, board, flags) 24 | for event in pygame.event.get(): 25 | if event.type == pygame.QUIT and prompt(win): 26 | return 0 27 | 28 | elif event.type == pygame.MOUSEBUTTONDOWN: 29 | x, y = event.pos 30 | if 460 < x < 500 and 0 < y < 50 and prompt(win): 31 | return 1 32 | 33 | if 50 < x < 450 and 50 < y < 450: 34 | x, y = x // 50, y // 50 35 | if load["flip"] and player: 36 | x, y = 9 - x, 9 - y 37 | 38 | if isOccupied(side, board, [x, y]): 39 | sound.play_click(load) 40 | 41 | prevsel = sel 42 | sel = [x, y] 43 | 44 | if (side == player 45 | and isValidMove(side, board, flags, prevsel, sel)): 46 | promote = getPromote(win, side, board, prevsel, sel) 47 | animate(win, side, board, prevsel, sel, load, player) 48 | 49 | side, board, flags = makeMove( 50 | side, board, prevsel, sel, flags, promote) 51 | moves.append(encode(prevsel, sel, promote)) 52 | 53 | elif side == player or end: 54 | sel = [0, 0] 55 | if 350 < x < 500 and 460 < y < 490: 56 | if prompt(win, saveGame(moves, "mysingle", player)): 57 | return 1 58 | elif 0 < x < 80 and 0 < y < 50 and load["allow_undo"]: 59 | moves = undo(moves, 2) if side == player else undo(moves) 60 | side, board, flags = convertMoves(moves) 61 | 62 | showScreen(win, side, board, flags, sel, load, player) 63 | 64 | end = isEnd(side, board, flags) 65 | if side != player and not end: 66 | fro, to = miniMax(side, board, flags) 67 | animate(win, side, board, fro, to, load, player) 68 | 69 | promote = getPromote(win, side, board, fro, to, True) 70 | side, board, flags = makeMove(side, board, fro, to, flags) 71 | 72 | moves.append(encode(fro, to, promote)) 73 | sel = [0, 0] 74 | -------------------------------------------------------------------------------- /chess/lib/heuristics.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | In this file, we define heuristic constants required for the python chess 4 | engine. 5 | """ 6 | 7 | pawnEvalWhite = ( 8 | (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), 9 | (8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0), 10 | (2.0, 2.0, 3.0, 5.0, 5.0, 3.0, 2.0, 2.0), 11 | (0.5, 0.5, 1.0, 2.5, 2.5, 1.0, 0.5, 0.5), 12 | (0.0, 0.0, 0.5, 2.0, 2.0, 0.5, 0.0, 0.0), 13 | (0.5, -0.5, -1.0, 0.0, 0.0, -1.0, -0.5, 0.5), 14 | (0.5, 1.0, 0.5, -2.0, -2.0, 0.5, 1.0, 0.5), 15 | (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), 16 | ) 17 | 18 | pawnEvalBlack = tuple(reversed(pawnEvalWhite)) 19 | 20 | knightEval = ( 21 | (-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0), 22 | (-4.0, -2.0, 0.0, 0.0, 0.0, 0.0, -2.0, -4.0), 23 | (-3.0, 0.0, 1.0, 1.5, 1.5, 1.0, 0.0, -3.0), 24 | (-3.0, 0.5, 1.5, 2.0, 2.0, 1.5, 0.5, -3.0), 25 | (-3.0, 0.0, 1.5, 2.0, 2.0, 1.5, 0.0, -3.0), 26 | (-3.0, 0.5, 1.0, 1.5, 1.5, 1.0, 0.5, -3.0), 27 | (-4.0, -2.0, 0.0, 0.5, 0.5, 0.0, -2.0, -4.0), 28 | (-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0), 29 | ) 30 | 31 | bishopEvalWhite = ( 32 | (-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0), 33 | (-1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0), 34 | (-1.0, 0.0, 0.5, 1.0, 1.0, 0.5, 0.0, -1.0), 35 | (-1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, -1.0), 36 | (-1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0), 37 | (-1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0), 38 | (-1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, -1.0), 39 | (-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0), 40 | ) 41 | 42 | bishopEvalBlack = tuple(reversed(bishopEvalWhite)) 43 | 44 | rookEvalWhite = ( 45 | (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), 46 | (0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5), 47 | (-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5), 48 | (-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5), 49 | (-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5), 50 | (-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5), 51 | (-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5), 52 | (0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0), 53 | ) 54 | 55 | rookEvalBlack = tuple(reversed(rookEvalWhite)) 56 | 57 | queenEval = ( 58 | (-2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0), 59 | (-1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0), 60 | (-1.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0), 61 | (-0.5, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5), 62 | (0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5), 63 | (-1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0), 64 | (-1.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, -1.0), 65 | (-2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0), 66 | ) 67 | 68 | kingEvalWhite = ( 69 | (-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0), 70 | (-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0), 71 | (-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0), 72 | (-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0), 73 | (-2.0, -3.0, -3.0, -4.0, -4.0, -3.0, -3.0, -2.0), 74 | (-1.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -1.0), 75 | (2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 2.0, 2.0), 76 | (2.0, 3.0, 3.0, 0.0, 0.0, 1.0, 3.0, 2.0), 77 | ) 78 | 79 | kingEvalBlack = tuple(reversed(kingEvalWhite)) -------------------------------------------------------------------------------- /chess/lib/ai.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | In this file, we implement a basic chess player algorithm in python. 4 | 5 | This implementation is a weak player of chess. 6 | This algorithm is not fast or efficient as any C impemenation of the same, 7 | just an attempt to display the workings of the minimax algorithm with 8 | alpha-beta pruning in Python. 9 | """ 10 | 11 | from chess.lib.core import legalMoves, makeMove 12 | from chess.lib.heuristics import * 13 | 14 | INF = 1000000 15 | DEPTH = 2 16 | 17 | # This is a rudementary and simple evaluative function for a given state of 18 | # board. It gives each piece a value based on its position on the board, 19 | # returns a numeric representation of the board 20 | def evaluate(board): 21 | score = 0 22 | for x, y, piece in board[0]: 23 | if piece == "p": 24 | score += 1 + pawnEvalWhite[y - 1][x - 1] 25 | elif piece == "b": 26 | score += 9 + bishopEvalWhite[y - 1][x - 1] 27 | elif piece == "n": 28 | score += 9 + knightEval[y - 1][x - 1] 29 | elif piece == "r": 30 | score += 14 + rookEvalWhite[y - 1][x - 1] 31 | elif piece == "q": 32 | score += 25 + queenEval[y - 1][x - 1] 33 | elif piece == "k": 34 | score += 200 + kingEvalWhite[y - 1][x - 1] 35 | 36 | for x, y, piece in board[1]: 37 | if piece == "p": 38 | score -= 1 + pawnEvalBlack[y - 1][x - 1] 39 | elif piece == "b": 40 | score -= 9 + bishopEvalBlack[y - 1][x - 1] 41 | elif piece == "n": 42 | score -= 9 + knightEval[y - 1][x - 1] 43 | elif piece == "r": 44 | score -= 14 + rookEvalBlack[y - 1][x - 1] 45 | elif piece == "q": 46 | score -= 25 + queenEval[y - 1][x - 1] 47 | elif piece == "k": 48 | score -= 200 + kingEvalBlack[y - 1][x - 1] 49 | 50 | return score 51 | 52 | # This is the Mini-Max algorithm, implemented with alpha-beta pruning. 53 | def miniMax(side, board, flags, depth=DEPTH, alpha=-INF, beta=INF): 54 | if depth == 0: 55 | return evaluate(board) 56 | 57 | if not side: 58 | bestVal = -INF 59 | for fro, to in legalMoves(side, board, flags): 60 | movedata = makeMove(side, board, fro, to, flags) 61 | nodeVal = miniMax(*movedata, depth - 1, alpha, beta) 62 | if nodeVal > bestVal: 63 | bestVal = nodeVal 64 | if depth == DEPTH: 65 | bestMove = (fro, to) 66 | alpha = max(alpha, bestVal) 67 | if alpha >= beta: 68 | break 69 | 70 | else: 71 | bestVal = INF 72 | for fro, to in legalMoves(side, board, flags): 73 | movedata = makeMove(side, board, fro, to, flags) 74 | nodeVal = miniMax(*movedata, depth - 1, alpha, beta) 75 | if nodeVal < bestVal: 76 | bestVal = nodeVal 77 | if depth == DEPTH: 78 | bestMove = (fro, to) 79 | beta = min(beta, bestVal) 80 | if alpha >= beta: 81 | break 82 | 83 | if depth == DEPTH: 84 | return bestMove 85 | else: 86 | return bestVal 87 | -------------------------------------------------------------------------------- /chess/singleplayer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the chess gameplay for singleplayer section of this 4 | application. This interfaces with the popular stockfish engine with the 5 | help of pyFish module. 6 | 7 | Interface code at ext.pyFish 8 | 9 | For a better understanding of the variables used here, checkout docs.txt 10 | ''' 11 | 12 | from chess.lib import * 13 | from ext.pyFish import StockFish 14 | 15 | # Run main code for chess singleplayer (stockfish) 16 | def main(win, player, level, load, movestr=""): 17 | fish = StockFish(getSFpath(), level) 18 | 19 | if not fish.isActive(): 20 | rmSFpath() 21 | return 1 22 | 23 | start(win, load) 24 | 25 | moves = movestr.split() 26 | fish.startGame(movestr) 27 | side, board, flags = convertMoves(moves) 28 | 29 | if player == 1 and not moves: 30 | fish.startEngine() 31 | 32 | clock = pygame.time.Clock() 33 | sel = prevsel = [0, 0] 34 | while True: 35 | clock.tick(25) 36 | end = isEnd(side, board, flags) 37 | for event in pygame.event.get(): 38 | if event.type == pygame.QUIT and prompt(win): 39 | fish.close() 40 | return 0 41 | 42 | elif event.type == pygame.MOUSEBUTTONDOWN: 43 | x, y = event.pos 44 | if 460 < x < 500 and 0 < y < 50 and prompt(win): 45 | fish.close() 46 | return 1 47 | 48 | if 50 < x < 450 and 50 < y < 450: 49 | x, y = x // 50, y // 50 50 | if load["flip"] and player: 51 | x, y = 9 - x, 9 - y 52 | 53 | if isOccupied(side, board, [x, y]): 54 | sound.play_click(load) 55 | 56 | prevsel = sel 57 | sel = [x, y] 58 | 59 | if (side == player 60 | and isValidMove(side, board, flags, prevsel, sel)): 61 | promote = getPromote(win, side, board, prevsel, sel) 62 | animate(win, side, board, prevsel, sel, load, player) 63 | 64 | side, board, flags = makeMove( 65 | side, board, prevsel, sel, flags, promote) 66 | fish.makeMove(encode(prevsel, sel, promote)) 67 | 68 | elif side == player or end: 69 | sel = [0, 0] 70 | if 350 < x < 500 and 460 < y < 490: 71 | msg = saveGame(fish.moves, "single", player, level) 72 | if prompt(win, msg): 73 | fish.close() 74 | return 1 75 | elif 0 < x < 80 and 0 < y < 50 and load["allow_undo"]: 76 | if side == player: 77 | fish.undo(2) 78 | else: 79 | fish.undo() 80 | side, board, flags = convertMoves(fish.moves) 81 | 82 | end = isEnd(side, board, flags) 83 | 84 | showScreen(win, side, board, flags, sel, load, player) 85 | if side != player and not end and fish.hasMoved(): 86 | fro, to, promote = decode(fish.getMove()) 87 | animate(win, side, board, fro, to, load, player) 88 | 89 | side, board, flags = makeMove(side, board, fro, to, flags, promote) 90 | sel = [0, 0] 91 | -------------------------------------------------------------------------------- /res/CREDITS.txt: -------------------------------------------------------------------------------- 1 | Most of the resources used in this projects are not made by me. 2 | All authors/publishers of the resources have been credited for: 3 | They maintain their respective original licenses. 4 | 5 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 | All images from pixabay.com maintain pixabay license. 7 | It is a simple and permissive. A part of it is given below. 8 | _________________________________________ 9 | Under the Pixabay License you are granted an irrevocable, worldwide, 10 | non-exclusive and royalty free right to use, download, copy, modify 11 | or adapt the Content for commercial or non-commercial purposes. 12 | Attribution of the photographer, videographer, musician or Pixabay is 13 | not required but is always appreciated. 14 | _________________________________________ 15 | For more, 16 | See: https://pixabay.com/service/license/ 17 | 18 | CREDITS FOR IMAGES: 19 | ================== 20 | 21 | Background.jpg: Image by FelixMittermeier from pixabay.com (with slight modification from me) 22 | Background2.jpg: Image by Pexels from pixabay.com 23 | Background3.jpg: Image by Jan Vašek from pixabay.com 24 | Background4.jpg: Image by pixel2013 from pixabay.com 25 | 26 | ALL THE BACKGROUND IMAGES HAVE BEEN COMBINED IN A SINGLE SPRITE IMAGE. 27 | 28 | refresh.png: Image by OpenIcons from pixabay.com 29 | delete.jpg: Image by Clker-Free-Vector-Images from pixabay.com 30 | back.png: Image by Clker-Free-Vector-Images from pixabay.com 31 | icon.gif: Image by OpenClipart-Vectors from pixabay.com 32 | 33 | select.jpg: This is made by me, but I have used the images of some chess pieces. 34 | 35 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 36 | Piecesprites: Image from Wikipedia.com 37 | This file is licensed under the: 38 | Creative Commons Attribution-Share Alike 3.0 Unported license. 39 | Source: http://commons.wikimedia.org/wiki/Template:SVG_chess_pieces 40 | Author: jurgenwesterhof (adapted from work of Cburnett) 41 | 42 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 43 | Asimov.otf: 44 | This is a Font file licenced under Apache License 2.0. 45 | 46 | This is the Asimov Font by 'KineticPlasma Fonts' from fontspace.com. 47 | 48 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 49 | I got sounds from freesound.org, converted them to .ogg format 50 | to use in the app. Some of the sounds have been modified by me. 51 | 52 | move.ogg: 53 | https://freesound.org/people/mh2o/sounds/351518/ 54 | Author: mh2o 55 | This work is licensed under the Creative Commons 0 License. (Public domain) 56 | 57 | drag.ogg: 58 | https://freesound.org/people/ChaosEntertainment/sounds/396705/ 59 | Author: ChaosEntertainment 60 | This work is licensed under the Creative Commons 0 License. (Public domain) 61 | 62 | start.ogg: 63 | https://freesound.org/people/XfiXy8/sounds/467282/ 64 | Author: XfiXy8 65 | This work is licensed under the Creative Commons 0 License. (Public domain) 66 | 67 | click.ogg: 68 | This work is licensed under the Creative Commons Attribution License. 69 | Name: "Splatez07’s YouTube sound pack >> Click.m4a" 70 | Author: Splatez07 71 | Link: https://freesound.org/people/Splatez07/sounds/413690/ 72 | 73 | I have slightly modified the original click sound 74 | (By cutting out the last part of the click sound). 75 | 76 | 77 | background.ogg: 78 | This work is licensed under the Creative Commons Attribution Noncommercial License. 79 | Name: "My love (piano loop)" 80 | Author: Shady Dave 81 | Link: https://freesound.org/people/ShadyDave/sounds/325611/ 82 | 83 | I have not modified this track in my app. 84 | 85 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -------------------------------------------------------------------------------- /menus/timer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the timer menu to configure the timer for multiplayer 4 | chess. 5 | ''' 6 | import pygame 7 | 8 | from tools.loader import TIMER, BACK, putLargeNum 9 | from tools.utils import rounded_rect 10 | 11 | # This shows the initial prompt message 12 | def start(win, load): 13 | rounded_rect(win, (255, 255, 255), (120, 180, 260, 100), 10, 4) 14 | 15 | win.blit(TIMER.PROMPT, (150, 190)) 16 | 17 | win.blit(TIMER.YES, (145, 240)) 18 | win.blit(TIMER.NO, (305, 240)) 19 | pygame.draw.rect(win, (255, 255, 255), (140, 240, 60, 28), 2) 20 | pygame.draw.rect(win, (255, 255, 255), (300, 240, 45, 28), 2) 21 | 22 | pygame.display.flip() 23 | while True: 24 | for event in pygame.event.get(): 25 | if event.type == pygame.MOUSEBUTTONDOWN: 26 | if 240 < event.pos[1] < 270: 27 | if 140 < event.pos[0] < 200: 28 | return None 29 | elif 300 < event.pos[0] < 350: 30 | if load["show_clock"]: 31 | return -1, (0, 0) 32 | else: 33 | return None, None 34 | 35 | # This shows the screen 36 | def showScreen(win, sel, sel2): 37 | win.fill((0, 0, 0)) 38 | 39 | rounded_rect(win, (255, 255, 255), (70, 5, 340, 60), 15, 4) 40 | win.blit(TIMER.HEAD, (100, 7)) 41 | win.blit(BACK, (460, 0)) 42 | 43 | rounded_rect(win, (255, 255, 255), (10, 70, 480, 420), 12, 4) 44 | 45 | for cnt, i in enumerate(TIMER.TEXT): 46 | y = 75 + cnt * 18 47 | win.blit(i, (20, y)) 48 | 49 | for i in range(6): 50 | pygame.draw.rect(win, (255, 255, 255), (110 + 40*i, 200, 28, 23), 3) 51 | 52 | for i in range(5): 53 | pygame.draw.rect(win, (255, 255, 255), (110 + 40*i, 290, 28, 23), 3) 54 | 55 | pygame.draw.rect(win, (50, 100, 150), (110 + 40*sel, 200, 28, 23), 3) 56 | pygame.draw.rect(win, (50, 100, 150), (110 + 40*sel2, 290, 28, 23), 3) 57 | 58 | pygame.draw.rect(win, (255, 255, 255), (300, 416, 50, 23), 3) 59 | pygame.display.update() 60 | 61 | # This is the main function, called from main menu 62 | def main(win, load): 63 | ret = start(win, load) 64 | if ret is not None: 65 | return ret 66 | 67 | sel = sel2 = 0 68 | clock = pygame.time.Clock() 69 | while True: 70 | clock.tick(24) 71 | showScreen(win, sel, sel2) 72 | 73 | for event in pygame.event.get(): 74 | if event.type == pygame.QUIT: 75 | return 0 76 | 77 | elif event.type == pygame.MOUSEBUTTONDOWN: 78 | x, y = event.pos 79 | if 460 < x < 500 and 0 < y < 50: 80 | return 1 81 | 82 | if 300 < x < 350 and 416 < y < 439: 83 | if sel == 0: 84 | temp = (30 * 60 * 1000,)*2 85 | if sel == 1: 86 | temp = (15 * 60 * 1000,)*2 87 | if sel == 2: 88 | temp = (10 * 60 * 1000,)*2 89 | if sel == 3: 90 | temp = (5 * 60 * 1000,)*2 91 | if sel == 4: 92 | temp = (3 * 60 * 1000,)*2 93 | if sel == 5: 94 | temp = (1 * 60 * 1000,)*2 95 | return sel2, temp 96 | 97 | for i in range(6): 98 | if 110 + 40*i < x < 138 + 40*i and 200 < y < 223: 99 | sel = i 100 | break 101 | 102 | for i in range(5): 103 | if 110 + 40*i < x < 138 + 40*i and 290 < y < 313: 104 | sel2 = i 105 | break 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important Note 2 | 3 | This project is not being maintained actively at the moment. I had started this 4 | project back when I was still new to Python and pygame. While I enjoyed and 5 | learnt a lot when I worked on this project, looking back at the codebase now 6 | after over 2 years, I can't help but notice how bad the codebase is. 7 | Please do not use this project as a base for any of your projects or for learning 8 | python and/or pygame. There are many resources out there that are much better 9 | than this project. 10 | 11 | If I ever return to this project, it would mean doing full re-write of the 12 | entire codebase, with the current codebase being archived in a 'legacy' branch. 13 | 14 | ## My-PyChess 15 | 16 | This is a **fully featured chess app** written purely in Python using the 17 | `pygame` library. 18 | 19 | ![main image](screenshots/main.jpg) 20 | 21 | Click [here](screenshots/screenshots.md) to see more few screenshots of My-PyChess in action! 22 | 23 | Any bug-reports, suggestions or questions, you can leave it in the github issues section. 24 | 25 | The My-PyChess project is available under MIT License. The MIT Licence applies to all the resources I have created in this project. This includes all the python files and text files. But some resources(images, sounds and font file) are not created by me, I have downloaded these from the internet. I have given credits to the authors of these resources in [this file](res/CREDITS.txt). All these resources maintain the original licenses that the authors have leased them under (These licenses permit my use of the respective resources in this project). 26 | 27 | ### Getting started 28 | 29 | - Make sure you have Python and `pygame` installed and working. 30 | - Clone this repository (or download zip file and extract it). 31 | - Then, run the `pychess.py` file. Trying to run any other file will not run the game. 32 | 33 | There is also a [lite implementation](https://github.com/ankith26/My-PyChess-lite/) of My-PyChess, that focuses just on chess programming - free from all the code for menus, singleplayer, online etc. 34 | 35 | ### Features 36 | 37 | - Clean GUI with a lot of menus for ease of use. 38 | - Allows users to make only valid move, does not allow users to make moves that puts the users king at check. 39 | - Detects check, checkmate, stalemate and informs user. 40 | - Supports things like castling, pawn promotion, enpassent etc. 41 | - Supports saving and loading games. 42 | - Has single player mode with two different types, levels and ability to play against the stockfish chess engine. 43 | - Has online gamemode, play chess with anyone in the world. 44 | - Has a chess game timer. 45 | - Has a preference menu where you can customize the app to meet your preferences. 46 | - Has a chess howto, about menu and stockfish install/configure menu to make things easy for users. 47 | 48 | ## What's new in this Version 3.2 49 | - Added a Back-Button, to go back to the previous menu. In older versions, the quit button was used for this purpose, but from now on a dedicated button to go back is there in the top-right corner. The quit button will now be used only to exit the app. 50 | - Added a game timer to multiplayer mode, with a new menu to setup the timer. 51 | - Fixed bugs and made many additions and improvements to the client-server in chess online gamemode. 52 | - Made optimisations to core chess module and gui module. 53 | - Added a chess hotwto. 54 | - Minor improvements to game sounds and textbox. 55 | - Upgraded preference menu and made the loadgame interface more robust. 56 | - Several other minor changes and improvements made. 57 | 58 | ## Highlights of v3.x, the latest major release. 59 | - The code was revamped and restructured, fixed bugs and made **major performance improvements**. 60 | - **Online play** was added. This features a online lobby and support upto 10 people to play chess. 61 | - Singleplayer saw big upgrades: Firstly, a decent **python chess engine** that playes chess was added. 62 | - You can play against **stockfish chess engine** (see https://stockfishchess.org). My-PyChess will act as an interface to give you this singleplayer mode. 63 | - En-Passant in chess, undo move, screenflip and sounds were added. 64 | - Lots of changes made to GUI. 65 | 66 | Click [here](CHANGELOG.md) to see full changelog. 67 | 68 | ### Online Gameplay 69 | 70 | You can self-host the My-PyChess online server. Read more about it [here](onlinehowto.txt). 71 | -------------------------------------------------------------------------------- /chess/lib/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | In this file, we define a few other non-gui My-PyChess helper functions. 4 | """ 5 | 6 | from datetime import datetime 7 | import os 8 | import time 9 | 10 | LETTER = ["", "a", "b", "c", "d", "e", "f", "g", "h"] 11 | 12 | # encode() essentially converts a form of data used by the game to denote moves 13 | # into the standard full algebraic notation (the kind that is used to 14 | # communicate with a chess engine). 15 | # To know more about the data format used by this game, checkout docs.txt 16 | def encode(fro, to, promote=None): 17 | data = LETTER[fro[0]] + str(9 - fro[1]) + LETTER[to[0]] + str(9 - to[1]) 18 | if promote is not None: 19 | return data + promote 20 | return data 21 | 22 | # decode does the opposite of encode() 23 | def decode(data): 24 | ret = [ 25 | [LETTER.index(data[0]), 9 - int(data[1])], 26 | [LETTER.index(data[2]), 9 - int(data[3])], 27 | ] 28 | if len(data) == 5: 29 | ret.append(data[4]) 30 | else: 31 | ret.append(None) 32 | return ret 33 | 34 | # Return the initial state (in which a chess game starts) of the chess variables 35 | # side, board and flags 36 | def initBoardVars(): 37 | side = False 38 | board = [ 39 | [ 40 | [1, 7, "p"], [2, 7, "p"], [3, 7, "p"], [4, 7, "p"], 41 | [5, 7, "p"], [6, 7, "p"], [7, 7, "p"], [8, 7, "p"], 42 | [1, 8, "r"], [2, 8, "n"], [3, 8, "b"], [4, 8, "q"], 43 | [5, 8, "k"], [6, 8, "b"], [7, 8, "n"], [8, 8, "r"], 44 | ], [ 45 | [1, 2, "p"], [2, 2, "p"], [3, 2, "p"], [4, 2, "p"], 46 | [5, 2, "p"], [6, 2, "p"], [7, 2, "p"], [8, 2, "p"], 47 | [1, 1, "r"], [2, 1, "n"], [3, 1, "b"], [4, 1, "q"], 48 | [5, 1, "k"], [6, 1, "b"], [7, 1, "n"], [8, 1, "r"], 49 | ] 50 | ] 51 | flags = [[True for _ in range(4)], None] 52 | return side, board, flags 53 | 54 | # A simple function to undo, num corresponds to the number of moves to undo. 55 | def undo(moves, num=1): 56 | if len(moves) in range(num): 57 | return moves 58 | else: 59 | return moves[:-num] 60 | 61 | # Get path to stockfish executable from path.txt config file 62 | def getSFpath(): 63 | conffile = os.path.join("res", "stockfish", "path.txt") 64 | if os.path.exists(conffile): 65 | with open(conffile, "r") as f: 66 | return f.read().strip() 67 | 68 | # Remove stockfish config path file. 69 | def rmSFpath(): 70 | os.remove(os.path.join("res", "stockfish", "path.txt")) 71 | 72 | # Get time returned by time perf_counter in rounded millisconds 73 | def getTime(): 74 | return round(time.perf_counter() * 1000) 75 | 76 | # Update the game-timer after each move 77 | def updateTimer(side, mode, timer): 78 | if timer is None: 79 | return None 80 | 81 | ret = list(timer) 82 | if mode != -1: 83 | ret[side] += (mode * 1000) 84 | return ret 85 | 86 | # This function saves a game into a text file. 87 | # It does this by saving all moves in long algebraic notation 88 | # Also saves other important data in the file depending on the gamemode. 89 | def saveGame(moves, gametype="multi", player=0, level=0, 90 | mode=None, timer=None, cnt=0): 91 | if cnt >= 20: 92 | return -1 93 | 94 | name = os.path.join("res", "savedGames", "game" + str(cnt) + ".txt") 95 | if os.path.isfile(name): 96 | return saveGame(moves, gametype, player, level, mode, timer, cnt + 1) 97 | 98 | else: 99 | if gametype == "single": 100 | gametype += " " + str(player) + " " + str(level) 101 | if gametype == "mysingle": 102 | gametype += " " + str(player) 103 | 104 | dt = datetime.now() 105 | date = "/".join(map(str, [dt.day, dt.month, dt.year])) 106 | time = ":".join(map(str, [dt.hour, dt.minute, dt.second])) 107 | datentime = " ".join([date, time]) 108 | 109 | movestr = " ".join(moves) 110 | 111 | temp = [] 112 | if mode is not None: 113 | temp.append(str(mode)) 114 | 115 | if timer is not None: 116 | temp.extend(map(str, timer)) 117 | 118 | temp = " ".join(temp) 119 | text = "\n".join([gametype, datentime, movestr, temp]) 120 | 121 | with open(name, "w") as file: 122 | file.write(text) 123 | return cnt 124 | -------------------------------------------------------------------------------- /ext/pyFish.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | 4 | Here I have written a python interface class for stockfish chess engine. 5 | This is used in the singleplayer mode of My-PyChess. 6 | """ 7 | 8 | import queue 9 | import subprocess 10 | import threading 11 | 12 | _LEVELDATA = ( 13 | (0, 10, 1), 14 | (1, 50, 1), 15 | (3, 80, 2), 16 | (6, 160, 4), 17 | (8, 250, 6), 18 | (10, 400, 8), 19 | (12, 500, 10), 20 | (14, 700, 12), 21 | (16, 900, 16), 22 | ) 23 | 24 | # StockFish class to interface with stockfish chess engine. 25 | class StockFish: 26 | def __init__(self, path="stockfish", level=1): 27 | self.moves = [] 28 | self.skill, self.movetime, self.depth = _LEVELDATA[level - 1] 29 | 30 | self.thread = threading.Thread() 31 | self.q = queue.Queue(1) 32 | 33 | try: 34 | self.stockfish = subprocess.Popen( 35 | path, 36 | universal_newlines=True, 37 | stdin=subprocess.PIPE, 38 | stdout=subprocess.PIPE, 39 | ) 40 | 41 | if self.stockfish.stdout.readline().split()[0].lower() == "stockfish": 42 | self.active = True 43 | self._put("uci") 44 | self.setOption("Skill Level", self.skill) 45 | else: 46 | self.active = False 47 | self.stockfish.terminate() 48 | 49 | except: 50 | self.active = False 51 | 52 | def _raiseErrorIfInactive(self): 53 | if not self.active: 54 | raise RuntimeError("Intergration with stockfish engine has failed") 55 | 56 | def _put(self, command): 57 | self._raiseErrorIfInactive() 58 | self.stockfish.stdin.write(str(command) + "\n") 59 | self.stockfish.stdin.flush() 60 | 61 | def _isReady(self): 62 | self._raiseErrorIfInactive() 63 | self._put("isready") 64 | while True: 65 | if self.stockfish.stdout.readline().strip() == "readyok": 66 | return 67 | 68 | def _engine(self): 69 | self._isReady() 70 | self._put("position startpos moves " + " ".join(self.moves)) 71 | self._put("go depth {} movetime {}".format(self.depth, self.movetime)) 72 | while True: 73 | msg = self.stockfish.stdout.readline().strip().split(" ") 74 | if msg[0] == "bestmove": 75 | if msg[1] == "(none)": 76 | self.q.put(None) 77 | else: 78 | self.q.put(msg[1]) 79 | break 80 | 81 | def isActive(self): 82 | return self.active 83 | 84 | def startGame(self, moves=""): 85 | self._isReady() 86 | self._put("ucinewgame") 87 | self.moves = moves.split() 88 | 89 | def setOption(self, name, value): 90 | self._isReady() 91 | self._put("setoption name {} value {}".format(name, value)) 92 | 93 | def startEngine(self): 94 | self._raiseErrorIfInactive() 95 | if not self.thread.is_alive() and self.q.empty(): 96 | self.thread = threading.Thread(target=self._engine) 97 | self.thread.start() 98 | else: 99 | raise RuntimeError("Could not start engine") 100 | 101 | def makeMove(self, move): 102 | self.moves.append(move) 103 | self.startEngine() 104 | 105 | def getMove(self, block=True): 106 | self._raiseErrorIfInactive() 107 | if not self.hasMoved() and not block: 108 | self._put("stop") 109 | 110 | enginemove = self.q.get() 111 | self.moves.append(enginemove) 112 | return enginemove 113 | 114 | def hasMoved(self): 115 | self._raiseErrorIfInactive() 116 | return not self.q.empty() and not self.thread.is_alive() 117 | 118 | def undo(self, num=1): 119 | self._raiseErrorIfInactive() 120 | if not self.thread.is_alive(): 121 | if len(self.moves) not in range(num): 122 | self.moves = self.moves[:-num] 123 | 124 | def close(self): 125 | self._isReady() 126 | self._put("quit") 127 | self.stockfish.terminate() 128 | self.active = False 129 | 130 | # A simple function to test wether a stockfish executable works or not. 131 | # Path to stockfish must be specified as an argument 132 | def teststockfish(path): 133 | fish = StockFish(path) 134 | stat = fish.isActive() 135 | if stat: 136 | fish.close() 137 | return stat -------------------------------------------------------------------------------- /menus/singleplayer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the single player menu which is called when user clicks 4 | singleplayer button on main menu. 5 | ''' 6 | 7 | import os.path 8 | import random 9 | 10 | import pygame 11 | 12 | from ext.pyFish import teststockfish 13 | from menus import stockfish 14 | from tools.loader import SINGLE, BACK, putLargeNum 15 | from tools.utils import rounded_rect 16 | 17 | # This is a prompt, called when user has not configured stockfish 18 | def prompt(win): 19 | rounded_rect(win, (255, 255, 255), (100, 200, 300, 100), 10, 4) 20 | 21 | for cnt, i in enumerate(SINGLE.CONFIG): 22 | win.blit(i, (110, 206 + cnt*18)) 23 | 24 | win.blit(SINGLE.OK, (160, 270)) 25 | pygame.draw.rect(win, (255, 255, 255), (160, 270, 25, 20), 3) 26 | win.blit(SINGLE.NOTNOW, (250, 270)) 27 | pygame.draw.rect(win, (255, 255, 255), (250, 270, 70, 20), 3) 28 | 29 | pygame.display.update() 30 | while True: 31 | for event in pygame.event.get(): 32 | if event.type == pygame.MOUSEBUTTONDOWN: 33 | if 270 < event.pos[1] < 290: 34 | if 160 < event.pos[0] < 185: 35 | return True 36 | if 250 < event.pos[0] < 320: 37 | return False 38 | 39 | # This shows the screen 40 | def showScreen(win, sel, sel2, lvl): 41 | win.fill((0, 0, 0)) 42 | 43 | rounded_rect(win, (255, 255, 255), (70, 5, 340, 60), 15, 4) 44 | win.blit(SINGLE.HEAD, (100, 7)) 45 | win.blit(BACK, (460, 0)) 46 | 47 | rounded_rect(win, (255, 255, 255), (10, 70, 480, 180), 12, 4) 48 | for cnt, i in enumerate(SINGLE.PARA1): 49 | y = 75 + cnt * 17 50 | win.blit(i, (20, y)) 51 | win.blit(SINGLE.CHOOSE, (90, 160)) 52 | win.blit(SINGLE.SELECT, (200, 150)) 53 | pygame.draw.rect(win, (50, 100, 150), (200 + sel*50, 150, 50, 50), 3) 54 | 55 | rounded_rect(win, (255, 255, 255), (170, 210, 140, 30), 7, 3) 56 | win.blit(SINGLE.START, (170, 210)) 57 | 58 | win.blit(SINGLE.OR, (220, 250)) 59 | 60 | rounded_rect(win, (255, 255, 255), (10, 295, 480, 200), 12, 4) 61 | for cnt, i in enumerate(SINGLE.PARA2): 62 | y = 300 + cnt * 17 63 | win.blit(i, (20, y)) 64 | win.blit(SINGLE.LEVEL, (30, 380)) 65 | for i in range(9): 66 | pygame.draw.rect(win, (255, 255, 255), (110 + i*35, 380, 25, 30), 3) 67 | putLargeNum(win, i+1, (113 + i*35, 380)) 68 | pygame.draw.rect(win, (50, 100, 150), (75 + lvl*35, 380, 25, 30), 3) 69 | 70 | win.blit(SINGLE.CHOOSE, (40, 440)) 71 | win.blit(SINGLE.SELECT, (150, 430)) 72 | pygame.draw.rect(win, (50, 100, 150), (150 + sel2*50, 430, 50, 50), 3) 73 | 74 | rounded_rect(win, (255, 255, 255), (320, 440, 140, 30), 7, 3) 75 | win.blit(SINGLE.START, (320, 440)) 76 | 77 | # This is the main function, called from main menu 78 | def main(win): 79 | sel = sel2 = 0 80 | lvl = 1 81 | clock = pygame.time.Clock() 82 | while True: 83 | clock.tick(24) 84 | showScreen(win, sel, sel2, lvl) 85 | for event in pygame.event.get(): 86 | if event.type == pygame.QUIT: 87 | return 0 88 | elif event.type == pygame.MOUSEBUTTONDOWN: 89 | x, y = event.pos 90 | if 460 < x < 500 and 0 < y < 50: 91 | return 1 92 | 93 | if 160 < y < 210 and 200 < x < 350: 94 | sel = (x // 50) - 4 95 | 96 | if 430 < y < 480 and 150 < x < 300: 97 | sel2 = (x // 50) - 3 98 | 99 | if 380 < y < 410: 100 | for i in range(9): 101 | if 110 + i*35 < x < 135 + i*35: 102 | lvl = i + 1 103 | 104 | if 170 < x < 310 and 220 < y < 250: 105 | if sel == 2: 106 | return True, random.randint(0, 1) 107 | else: 108 | return True, sel 109 | 110 | if 320 < x < 460 and 440 < y < 470: 111 | pth = os.path.join("res", "stockfish", "path.txt") 112 | if os.path.exists(pth): 113 | with open(pth, "r") as f: 114 | if teststockfish(f.read().strip()): 115 | if sel2 == 2: 116 | return False, random.randint(0, 1), lvl 117 | else: 118 | return False, sel2, lvl 119 | 120 | if prompt(win): 121 | return stockfish.main(win) 122 | pygame.display.update() 123 | -------------------------------------------------------------------------------- /onlinehowto.txt: -------------------------------------------------------------------------------- 1 | =============================================================== 2 | Online Gameplay 3 | --------------- 4 | =============================================================== 5 | Online Gameplay consists of two elements, client and server. 6 | 7 | CLIENT: It is the machine that runs My-PyChess code. 8 | SERVER: It is a machine that accepts connections from different clients(upto 10) 9 | 10 | Back when I was actively working on this project, the networking element of this 11 | app was made with security in mind. I had not used the python "pickle" module, 12 | which is normally used in such applications. This has security risks associated. 13 | 14 | The server keeps absolutely NO DATA of any user that connects to it. 15 | No names, accounts, emails, IP address, Location - nothing. PRIVACY IS RESPECTED. 16 | 17 | The client and server speak over Raw TCP protocol with no sort of encryption. 18 | This means that any bad guys may read the messages that the client and server 19 | send to one another. But the best part is the fact that the client and server DO NOT 20 | EXCHANGE ANY SENSITIVE DATA. This means that the information is practically useless 21 | for the bad guys. So they will not invest their time and efforts to do such things. 22 | If they did so, they would be in major dissapointment. 23 | 24 | The public global test server is not being hosted anymore. 25 | 26 | ========================================================================== 27 | SELF-HOSTING MY_PYCHESS SERVER 28 | ========================================================================== 29 | 30 | HOW TO RUN SERVER: 31 | ================== 32 | Run server.py, as simple as that! 33 | Python v3.6 or above has to be used. 34 | 35 | HOW TO CONNECT TO THIS SERVER: 36 | ============================== 37 | The server and client must be connected to the same router. When the server runs, 38 | it will message it's IP address in the shell. Enter this in the client's menu. 39 | IT IS TO BE NOTED THAT THE IP ADDRESS IT GIVES IS THE PRIVATE IP ADDRESS, NOT PUBLIC 40 | 41 | If for some reason, you want the server's PUBLIC IP ADDRESS, 42 | I have covered that part below. This will not be required for most usecases. 43 | 44 | HOW TO CONFIGURE THE SERVER (FOR ADVANCED USAGE): 45 | ================================================= 46 | 47 | On top of server.py, there are two constants - LOG and IPV6. 48 | They are set to False by default. 49 | IT IS RECOMMENDED YOU DO NOT CHANGE THESE. 50 | But if you really need to, then you can change those. 51 | 52 | If you set 'LOG = True', the server will log it's output to a file. The file 53 | will be named by the current date and time of logging. This feature is not 54 | recommended because it will consume more CPU power and RAM than when it is not 55 | logging. 56 | 57 | If you set 'IPV6 = True', the server will start in IPv6 mode. This means that 58 | any client that is using the IPv6 protocol can connect to the server. Both client 59 | and server must support IPv6 for this to work. By default, the server opens in 60 | IPv4 mode, which is the protocol that is supported by most of the systems of today. 61 | 62 | IPv6 is a more modern protocol, that will take time to be widely adopted because 63 | most of the internet runs on IPv4. 64 | 65 | MANAGING THE SERVER WHILE IT IS RUNNING 66 | ======================================= 67 | 68 | When the server is running, it is constantly messaging you whatever is going 69 | on. You can enter commands to the server. 70 | 71 | Commands are listed below: 72 | 1) report 73 | Gives the current status of the server with useful information like the 74 | number of threads the server is running, number of people online etc etc 75 | 76 | 2) mypublicip 77 | Enter this command to know the server's public IP Address. 78 | 79 | 3) lock 80 | This will put the server in a "locked" state. After this, anyone will not 81 | be able to connect to the server. This will not affect anyone who is 82 | aldready connected to the server. 83 | 84 | 4) unlock 85 | This will "unlock" the server, the server that is locked will be "unlocked", 86 | then anybody can connect to the server. This is the default state of the 87 | server. 88 | 89 | 3) kickall 90 | Kick EVERYONE connected to the server. 91 | 92 | 4) kick [ ...] 93 | Kick a particular player (whose id is ). 94 | Can specify more than one id. 95 | example: 'kick 3294', 'kick 9492 2839 5138', 'kick 9327 4392', etc 96 | 97 | 5) quit 98 | Enter this to stop the server and quit. Recommended way of closing server. 99 | 100 | IF YOU WANT TO CLOSE THE SERVER WHILE IT IS RUNNING, ENTER "quit" IN SHELL. 101 | TRYING TO FORCE CLOSE THE SERVER MAY GIVE UNEXPECTED RESULTS TO CLIENTS 102 | CONNECTED. 103 | 104 | WHAT IS AN ID, WHAT IS A KEY? 105 | ============================= 106 | When the clients connect to server, the server issues the client a unique 107 | 4-digit number as their 'ID' or 'key'. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | This is the basic changelog for My-PyChess. All dates are in dd/mm/yyyy format. 3 | 4 | ## What's new in Version 3.2 (2/9/2020) 5 | - Added a Back-Button, to go back to the previous menu. In older versions, the quit button was used for this purpose, but from now on a dedicated button to go back is there in the top-right corner. The quit button will now be used only to exit the app. 6 | - Added a game timer to multiplayer mode, with a new menu to setup timer. 7 | - Fixed bugs and made many additions and improvements to the client-server in chess online gamemode. 8 | - Made optimisations to core chess module and gui module. 9 | - Added a chess hotwto. 10 | - Minor improvements to game sounds and textbox. 11 | - Upgraded preference menu and made the loadgame interface more robust. 12 | - Several other minor changes and improvements made. 13 | 14 | ## What was new in Version 3.1 (14/6/2020) 15 | - Added basic sounds (beta). 16 | - Added draw and resign option to online gameplay. 17 | - Added a simple "About menu". 18 | - Added compatibility for python v3.5 (server.py still needs python v3.6 to work) 19 | - Made many changes to GUI throughout the app, including more prompt messages. 20 | - Upgraded preferences. 21 | - Fixed a small issue in move animations, added a new startgame animation. 22 | - Fixed a bug where undo in singleplayer after game ended would give weird results. 23 | - Fixed many bugs in online mode (server), made server robust to TCP packet loss among other things. 24 | 25 | ## What was new in Version 3.0 (23/5/2020) 26 | - THIS WAS THE BIGGEST RELEASE OF MY-PYCHESS EVER RELEASED. 27 | - The code came with MIT License instead of the GPL-3 which came with v2.2. 28 | - The code was revamped, fixing minor bugs and MAJOR PERFORMANCE IMPROVEMENTS. 29 | - On my PC, a chess match with v2.2 ran at 22 fps, while this upgrade allowed it to run at over 200 fps if fps was not constrained. 30 | - Online play was added. This featured a ONLINE LOBBY and support upto 10 people to play chess(read more in ref/online.txt) 31 | - Singleplayer saw BIG UPGRADES: A Good Menu, ability to play with a decent PYTHON CHESS ENGINE. 32 | - Now, you can play against STOCKFISH CHESS ENGINE. My-PyChess will act as a GUI interface to give you this singleplayer mode. 33 | - Included with this release, a good menu for stockfish and stockfish install/configure menu to help with installing and configuring stockfish with My-PyChess. 34 | - Code was made more easy to understand with code comments throughout the code. 35 | - EnPassant, Screen flip and Undo moves option were added. 36 | - UI for loadGame menu was revamped, UI for preference menu was simplified. 37 | - Save/load games with a new way of storing gamedata. 38 | 39 | ## What was new in Version 2.3 (4/2/2020) 40 | - This version never made it to github, I dropped it while working on it and started to focus on v3.0 41 | - This had included a few minor GUI changes. 42 | 43 | ## What was new in Version 2.2 (22/1/2020) 44 | - Minor performance upgrades. 45 | - Further upgraded preferences. 46 | - Upgraded loadgame menu (with delete games option). 47 | - Minor changes made to the interface. 48 | - While saving a game, it informed under what name the game was being saved. 49 | 50 | ## What was new in Version 2.1 (11/1/2020) 51 | - The upgrade optimised the game a bit, fixing a bug. 52 | - When you clicked a piece, it would show all the available spaces it could go. 53 | - The upgrade added a new preference menu where you could customise some game features. 54 | - The upgrade added background animations on the home menu. 55 | 56 | ## What was new in Version 2.0 (7/1/2020) 57 | - Revamped the Home Menu so that it looks better. 58 | - Revamped and code, optimised it. 59 | - Fixed Bugs. 60 | - Made some changes that ensure that it can work on all platforms. 61 | - Added Save/load game features. 62 | - Added highly basic SinglePlayer (random move generator) 63 | 64 | # Changelog for older versions 65 | 66 | Below, I will mention about the initial development of this app. At this state, the game was unstable - It lacked many features, had performance issues and lot, lot of bugs. Most of the versions given below have not made it to github. 67 | 68 | ## What was new in Version 1.5 (26/11/2019) 69 | - added choice for pawn promotion 70 | - added piece animations 71 | - added some popup messages 72 | - upgraded home menu 73 | - fixed bugs 74 | 75 | ## What was new in Version 1.4 (9/11/2019) 76 | - Added a home menu 77 | - Added placeholders for future upgrades 78 | - Basic compliance with pep8 79 | 80 | ## What was new in Version 1.3 (2/11/2019) 81 | - coding optimisations 82 | - Checkmate and stalemate report feature added 83 | - fixed bugs 84 | 85 | ## What was new in Version 1.2 (24/10/2019) 86 | - Improved move validation, stopping king from moving into attacked areas. 87 | - fixed bugs 88 | 89 | ## What was new in Version 1.1 (21/10/2019) 90 | - added castling, pawn-promotion 91 | - This version fixed a few bugs, but introduced lot more bugs 92 | 93 | ## What was new in Version 1.0 (19/10/2019) 94 | - Added Move validation for highly basic chess moves 95 | 96 | ## What was new in Version 0.3 (17/10/2019) 97 | - Added a few more basic utility functions. 98 | - Fixed a few bugs. 99 | - Minor changes made to code. 100 | 101 | ## What was new in Version 0.2 (16/10/2019) 102 | - Added GUI for pieces, implemented basic piece logic. 103 | 104 | ## What was new in Version 0.1 (15/10/2019) 105 | - Added GUI for the chess board 106 | -------------------------------------------------------------------------------- /chess/lib/gui.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | In this file, we define some basic gui-related functions 4 | 5 | For a better understanding of the variables used here, checkout docs.txt 6 | """ 7 | import pygame 8 | from tools.loader import CHESS, BACK, putNum, putLargeNum 9 | from tools import sound 10 | 11 | # Apply 'convert_alpha()' on all pieces to optimise images for speed. 12 | def convertPieces(win): 13 | for i in range(2): 14 | for key, val in CHESS.PIECES[i].items(): 15 | CHESS.PIECES[i][key] = val.convert_alpha(win) 16 | 17 | # This function displays the choice menu when called, taking user input. 18 | # Returns the piece chosen by the user 19 | def getChoice(win, side): 20 | win.blit(CHESS.CHOOSE, (130, 10)) 21 | win.blit(CHESS.PIECES[side]["q"], (250, 0)) 22 | win.blit(CHESS.PIECES[side]["b"], (300, 0)) 23 | win.blit(CHESS.PIECES[side]["r"], (350, 0)) 24 | win.blit(CHESS.PIECES[side]["n"], (400, 0)) 25 | pygame.display.update((0, 0, 500, 50)) 26 | while True: 27 | for event in pygame.event.get(): 28 | if event.type == pygame.MOUSEBUTTONDOWN: 29 | if 0 < event.pos[1] < 50: 30 | if 250 < event.pos[0] < 300: 31 | return "q" 32 | elif 300 < event.pos[0] < 350: 33 | return "b" 34 | elif 350 < event.pos[0] < 400: 35 | return "r" 36 | elif 400 < event.pos[0] < 450: 37 | return "n" 38 | 39 | def showTimeOver(win, side): 40 | pygame.draw.rect(win, (0, 0, 0), (100, 190, 300, 120)) 41 | pygame.draw.rect(win, (255, 255, 255), (100, 190, 300, 120), 4) 42 | 43 | win.blit(CHESS.TIMEUP[0], (220, 200)) 44 | win.blit(CHESS.TIMEUP[1], (105, 220)) 45 | win.blit(CHESS.TIMEUP[2], (115, 240)) 46 | 47 | win.blit(CHESS.OK, (230, 270)) 48 | pygame.draw.rect(win, (255, 255, 255), (225, 270, 50, 30), 2) 49 | 50 | pygame.display.update() 51 | while True: 52 | for event in pygame.event.get(): 53 | if event.type == pygame.MOUSEBUTTONDOWN: 54 | if 225 < event.pos[0] < 275 and 270 < event.pos[1] < 300: 55 | return 56 | 57 | def putClock(win, timer): 58 | if timer is None: 59 | return 60 | 61 | m1, s1 = divmod(timer[0] // 1000, 60) 62 | m2, s2 = divmod(timer[1] // 1000, 60) 63 | 64 | putLargeNum(win, format(m1, "02"), (100, 460), False) 65 | win.blit(CHESS.COL, (130, 460)) 66 | putLargeNum(win, format(s1, "02"), (140, 460), False) 67 | putLargeNum(win, format(m2, "02"), (210, 460), False) 68 | win.blit(CHESS.COL, (240, 460)) 69 | putLargeNum(win, format(s2, "02"), (250, 460), False) 70 | 71 | win.blit(CHESS.PIECES[0]["k"], (50, 450)) 72 | win.blit(CHESS.PIECES[1]["k"], (278, 450)) 73 | 74 | pygame.display.update() 75 | 76 | # This function draws the board 77 | def drawBoard(win): 78 | win.fill((100, 200, 200)) 79 | pygame.draw.rect(win, (180, 100, 30), (50, 50, 400, 400)) 80 | for y in range(1, 9): 81 | for x in range(1, 9): 82 | if (x + y) % 2 == 0: 83 | pygame.draw.rect(win, (220, 240, 240), (50 * x, 50 * y, 50, 50)) 84 | 85 | # This funtion draws all pieces onto the board 86 | def drawPieces(win, board, flip): 87 | for side in range(2): 88 | for x, y, ptype in board[side]: 89 | if flip: 90 | x, y = 9 - x, 9 - y 91 | win.blit(CHESS.PIECES[side][ptype], (x * 50, y * 50)) 92 | 93 | # This function displays the prompt screen when a user tries to quit 94 | # User must choose Yes or No, this function returns True or False respectively 95 | def prompt(win, msg=None): 96 | pygame.draw.rect(win, (0, 0, 0), (110, 160, 280, 130)) 97 | pygame.draw.rect(win, (255, 255, 255), (110, 160, 280, 130), 4) 98 | 99 | pygame.draw.rect(win, (255, 255, 255), (120, 160, 260, 60), 2) 100 | 101 | win.blit(CHESS.YES, (145, 240)) 102 | win.blit(CHESS.NO, (305, 240)) 103 | pygame.draw.rect(win, (255, 255, 255), (140, 240, 60, 28), 2) 104 | pygame.draw.rect(win, (255, 255, 255), (300, 240, 50, 28), 2) 105 | 106 | if msg is None: 107 | win.blit(CHESS.MESSAGE[0], (130, 160)) 108 | win.blit(CHESS.MESSAGE[1], (190, 190)) 109 | 110 | elif msg == -1: 111 | win.blit(CHESS.MESSAGE[0], (130, 160)) 112 | win.blit(CHESS.MESSAGE[1], (190, 190)) 113 | 114 | win.blit(CHESS.SAVE_ERR, (115, 270)) 115 | 116 | else: 117 | win.blit(CHESS.MESSAGE2[0], (123, 160)) 118 | win.blit(CHESS.MESSAGE2[1], (145, 190)) 119 | 120 | win.blit(CHESS.MSG, (135, 270)) 121 | putNum(win, msg, (345, 270)) 122 | 123 | pygame.display.flip() 124 | while True: 125 | for event in pygame.event.get(): 126 | if event.type == pygame.MOUSEBUTTONDOWN: 127 | if 240 < event.pos[1] < 270: 128 | if 140 < event.pos[0] < 200: 129 | return True 130 | elif 300 < event.pos[0] < 350: 131 | return False 132 | 133 | # This function shows a small animation when the game starts, while also 134 | # optimising images for display - call only once per game 135 | def start(win, load): 136 | convertPieces(win) 137 | sound.play_start(load) 138 | clk = pygame.time.Clock() 139 | for i in range(101): 140 | clk.tick_busy_loop(140) 141 | drawBoard(win) 142 | 143 | for j in range(8): 144 | win.blit(CHESS.PIECES[0]["p"], (0.5 * i * (j + 1), 225 + 1.25 * i)) 145 | win.blit(CHESS.PIECES[1]["p"], (0.5 * i * (j + 1), 225 - 1.25 * i)) 146 | 147 | for j, pc in enumerate(["r", "n", "b", "q", "k", "b", "n", "r"]): 148 | win.blit(CHESS.PIECES[0][pc], (0.5 * i * (j + 1), 225 + 1.75 * i)) 149 | win.blit(CHESS.PIECES[1][pc], (0.5 * i * (j + 1), 225 - 1.75 * i)) 150 | 151 | pygame.display.update() -------------------------------------------------------------------------------- /chess/lib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | 4 | Used like: 5 | >>> from chess.lib import * 6 | 7 | In this file, we import all the useful functions for chess from the 8 | respective modules, and call it the "My-PyChess Standard Chess Library". 9 | Some functions that need utility of other functions from various other modules 10 | are defined here. 11 | 12 | For a better understanding of the variables used here, checkout docs.txt 13 | """ 14 | 15 | from chess.lib.core import ( 16 | getType, 17 | isOccupied, 18 | isChecked, 19 | isEnd, 20 | isValidMove, 21 | availableMoves, 22 | makeMove, 23 | ) 24 | from chess.lib.gui import ( 25 | pygame, 26 | CHESS, 27 | BACK, 28 | sound, 29 | getChoice, 30 | showTimeOver, 31 | putClock, 32 | drawBoard, 33 | drawPieces, 34 | prompt, 35 | start, 36 | ) 37 | from chess.lib.utils import ( 38 | encode, 39 | decode, 40 | initBoardVars, 41 | undo, 42 | getSFpath, 43 | rmSFpath, 44 | getTime, 45 | updateTimer, 46 | saveGame, 47 | ) 48 | from chess.lib.ai import miniMax 49 | 50 | # This function converts a string of moves(move sequence) of standard notation 51 | # into the notation used by the game. 52 | def convertMoves(moves): 53 | side, board, flags = initBoardVars() 54 | 55 | for fro, to, promote in map(decode, moves): 56 | side, board, flags = makeMove(side, board, fro, to, flags, promote) 57 | 58 | return side, board, flags 59 | 60 | # This is a wrapper for the getChoice GUI function. 61 | # getPromote() first checks wether a pawn has reaches promotion state 62 | # Then, if the game is multiplayer, getPromote() returns getChoice() 63 | # Returns queen otherwise 64 | def getPromote(win, side, board, fro, to, single=False): 65 | if getType(side, board, fro) == "p": 66 | if (side == 0 and to[1] == 1) or (side == 1 and to[1] == 8): 67 | if single: 68 | return "q" 69 | else: 70 | return getChoice(win, side) 71 | 72 | def showClock(win, side, mode, timer, start, timedelta): 73 | if timer is None: 74 | pygame.display.update() 75 | return None 76 | 77 | ret = list(timer) 78 | elaptime = getTime() - (start + timedelta) 79 | if mode == -1: 80 | ret[side] += elaptime 81 | if ret[side] >= 3600000: 82 | ret[side] = 3599000 83 | 84 | else: 85 | ret[side] -= elaptime 86 | if ret[side] < 0: 87 | showTimeOver(win, side) 88 | return None 89 | 90 | putClock(win, ret) 91 | return ret 92 | 93 | # This is a gui function that draws green squares marking the legal moves of 94 | # a seleced piece. 95 | def showAvailMoves(win, side, board, pos, flags, flip): 96 | piece = pos + [getType(side, board, pos)] 97 | for i in availableMoves(side, board, piece, flags): 98 | x = 470 - i[0] * 50 if flip else i[0] * 50 + 20 99 | y = 470 - i[1] * 50 if flip else i[1] * 50 + 20 100 | pygame.draw.rect(win, (0, 255, 0), (x, y, 10, 10)) 101 | 102 | # This function makes a gentle animation of a piece that is getting moved. 103 | # This function needs to be called BEFORE the actual move takes place 104 | def animate(win, side, board, fro, to, load, player=None): 105 | sound.play_drag(load) 106 | if player is None: 107 | FLIP = side and load["flip"] 108 | else: 109 | FLIP = player and load["flip"] 110 | 111 | piece = CHESS.PIECES[side][getType(side, board, fro)] 112 | x1, y1 = fro[0] * 50, fro[1] * 50 113 | x2, y2 = to[0] * 50, to[1] * 50 114 | if FLIP: 115 | x1, y1 = 450 - x1, 450 - y1 116 | x2, y2 = 450 - x2, 450 - y2 117 | 118 | stepx = (x2 - x1) / 50 119 | stepy = (y2 - y1) / 50 120 | 121 | col = (180, 100, 30) if (fro[0] + fro[1]) % 2 else (220, 240, 240) 122 | 123 | clk = pygame.time.Clock() 124 | for i in range(51): 125 | clk.tick_busy_loop(100) 126 | drawBoard(win) 127 | drawPieces(win, board, FLIP) 128 | 129 | pygame.draw.rect(win, col, (x1, y1, 50, 50)) 130 | win.blit(piece, (x1 + (i * stepx), y1 + (i * stepy))) 131 | pygame.display.update() 132 | sound.play_move(load) 133 | 134 | # This is a compilation of all gui functions. This handles the display of the 135 | # screen when chess gameplay takes place. This tool needs to be called 136 | # everytime in the game loop. 137 | def showScreen(win, side, board, flags, pos, load, player=None, online=False): 138 | multi = False 139 | if player is None: 140 | multi = True 141 | player = side 142 | 143 | flip = load["flip"] and player 144 | 145 | drawBoard(win) 146 | win.blit(BACK, (460, 0)) 147 | 148 | if not multi: 149 | win.blit(CHESS.TURN[int(side == player)], (10, 460)) 150 | 151 | if not online: 152 | if load["allow_undo"]: 153 | win.blit(CHESS.UNDO, (10, 12)) 154 | win.blit(CHESS.SAVE, (350, 462)) 155 | 156 | if isEnd(side, board, flags): 157 | if isChecked(side, board): 158 | win.blit(CHESS.CHECKMATE, (100, 12)) 159 | win.blit(CHESS.LOST, (320, 12)) 160 | win.blit(CHESS.PIECES[side]["k"], (270, 0)) 161 | else: 162 | win.blit(CHESS.STALEMATE, (160, 12)) 163 | else: 164 | if online: 165 | win.blit(CHESS.DRAW, (10, 12)) 166 | win.blit(CHESS.RESIGN, (400, 462)) 167 | 168 | if isChecked(side, board): 169 | win.blit(CHESS.CHECK, (200, 12)) 170 | 171 | if isOccupied(side, board, pos) and side == player: 172 | x = (9 - pos[0]) * 50 if flip else pos[0] * 50 173 | y = (9 - pos[1]) * 50 if flip else pos[1] * 50 174 | pygame.draw.rect(win, (255, 255, 0), (x, y, 50, 50)) 175 | 176 | drawPieces(win, board, flip) 177 | if load["show_moves"] and side == player: 178 | showAvailMoves(win, side, board, pos, flags, flip) 179 | 180 | if not multi: 181 | pygame.display.update() 182 | -------------------------------------------------------------------------------- /menus/pref.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the preferences menu which is called when user clicks 4 | preferences button on main menu. 5 | 6 | We also define functions to save and load user preferences. 7 | ''' 8 | 9 | import os.path 10 | import pygame 11 | from tools.loader import PREF, BACK 12 | from tools.utils import rounded_rect 13 | 14 | KEYS = ["sounds", "flip", "slideshow", "show_moves", "allow_undo", "show_clock"] 15 | 16 | DEFAULTPREFS = { 17 | "sounds": False, 18 | "flip": False, 19 | "slideshow": True, 20 | "show_moves": True, 21 | "allow_undo": True, 22 | "show_clock": False 23 | } 24 | 25 | # This function saves user preferences in a text file. 26 | def save(load): 27 | with open(os.path.join("res", "preferences.txt"), "w") as f: 28 | for key, val in load.items(): 29 | f.write(key + " = " + str(val) + '\n') 30 | 31 | # This function loads user preferences from a text file 32 | def load(): 33 | path = os.path.join("res", "preferences.txt") 34 | if not os.path.exists(path): 35 | open(path, "w").close() 36 | 37 | with open(path, "r") as f: 38 | mydict = {} 39 | for line in f.read().splitlines(): 40 | lsplit = line.split("=") 41 | if len(lsplit) == 2: 42 | val = lsplit[1].strip().lower() 43 | if val == "true": 44 | mydict[lsplit[0].strip()] = True 45 | elif val == "false": 46 | mydict[lsplit[0].strip()] = False 47 | 48 | for key in mydict: 49 | if key not in KEYS: 50 | mydict.pop(key) 51 | 52 | for key in KEYS: 53 | if key not in mydict: 54 | mydict[key] = DEFAULTPREFS[key] 55 | return mydict 56 | 57 | # This function displays the prompt screen when a user tries to quit 58 | # User must choose Yes or No, this function returns True or False respectively 59 | def prompt(win): 60 | rounded_rect(win, (255, 255, 255), (110, 160, 280, 130), 4, 4) 61 | 62 | win.blit(PREF.PROMPT[0], (130, 165)) 63 | win.blit(PREF.PROMPT[1], (130, 190)) 64 | 65 | win.blit(PREF.YES, (145, 240)) 66 | win.blit(PREF.NO, (305, 240)) 67 | pygame.draw.rect(win, (255, 255, 255), (140, 240, 60, 28), 2) 68 | pygame.draw.rect(win, (255, 255, 255), (300, 240, 45, 28), 2) 69 | 70 | pygame.display.flip() 71 | while True: 72 | for event in pygame.event.get(): 73 | if event.type == pygame.MOUSEBUTTONDOWN: 74 | if 240 < event.pos[1] < 270: 75 | if 140 < event.pos[0] < 200: 76 | return True 77 | elif 300 < event.pos[0] < 350: 78 | return False 79 | 80 | # This function shows the screen 81 | def showScreen(win, prefs): 82 | win.fill((0, 0, 0)) 83 | 84 | rounded_rect(win, (255, 255, 255), (70, 10, 350, 70), 20, 4) 85 | rounded_rect(win, (255, 255, 255), (10, 85, 480, 360), 12, 4) 86 | 87 | win.blit(BACK, (460, 0)) 88 | win.blit(PREF.HEAD, (110, 15)) 89 | 90 | rounded_rect(win, (255, 255, 255), (10, 450, 310, 40), 10, 3) 91 | win.blit(PREF.TIP, (20, 450)) 92 | win.blit(PREF.TIP2, (55, 467)) 93 | 94 | win.blit(PREF.SOUNDS, (90, 90)) 95 | win.blit(PREF.FLIP, (25, 150)) 96 | win.blit(PREF.SLIDESHOW, (40, 210)) 97 | win.blit(PREF.MOVE, (100, 270)) 98 | win.blit(PREF.UNDO, (25, 330)) 99 | win.blit(PREF.CLOCK, (25, 390)) 100 | 101 | for i in range(6): 102 | win.blit(PREF.COLON, (225, 90 + (i * 60))) 103 | if prefs[KEYS[i]]: 104 | rounded_rect( 105 | win, (255, 255, 255), (249, 92 + (60 * i), 80, 40), 8, 2) 106 | else: 107 | rounded_rect( 108 | win, (255, 255, 255), (359, 92 + (60 * i), 90, 40), 8, 2) 109 | win.blit(PREF.TRUE, (250, 90 + (i * 60))) 110 | win.blit(PREF.FALSE, (360, 90 + (i * 60))) 111 | 112 | rounded_rect(win, (255, 255, 255), (350, 452, 85, 40), 10, 2) 113 | win.blit(PREF.BSAVE, (350, 450)) 114 | 115 | x, y = pygame.mouse.get_pos() 116 | if 100 < x < 220 and 90 < y < 130: 117 | pygame.draw.rect(win, (0, 0, 0), (30, 90, 195, 40)) 118 | win.blit(PREF.SOUNDS_H[0], (45, 90)) 119 | win.blit(PREF.SOUNDS_H[1], (80, 110)) 120 | if 25 < x < 220 and 150 < y < 190: 121 | pygame.draw.rect(win, (0, 0, 0), (15, 150, 210, 50)) 122 | win.blit(PREF.FLIP_H[0], (50, 150)) 123 | win.blit(PREF.FLIP_H[1], (70, 170)) 124 | if 40 < x < 220 and 210 < y < 250: 125 | pygame.draw.rect(win, (0, 0, 0), (15, 210, 210, 40)) 126 | win.blit(PREF.SLIDESHOW_H[0], (40, 210)) 127 | win.blit(PREF.SLIDESHOW_H[1], (30, 230)) 128 | if 100 < x < 220 and 270 < y < 310: 129 | pygame.draw.rect(win, (0, 0, 0), (15, 270, 210, 40)) 130 | win.blit(PREF.MOVE_H[0], (35, 270)) 131 | win.blit(PREF.MOVE_H[1], (25, 290)) 132 | if 25 < x < 220 and 330 < y < 370: 133 | pygame.draw.rect(win, (0, 0, 0), (15, 330, 210, 40)) 134 | win.blit(PREF.UNDO_H[0], (60, 330)) 135 | win.blit(PREF.UNDO_H[1], (85, 350)) 136 | if 25 < x < 220 and 390 < y < 430: 137 | pygame.draw.rect(win, (0, 0, 0), (15, 390, 210, 40)) 138 | win.blit(PREF.CLOCK_H[0], (50, 390)) 139 | win.blit(PREF.CLOCK_H[1], (40, 410)) 140 | 141 | # This is the main function, called by the main menu 142 | def main(win): 143 | prefs = load() 144 | clock = pygame.time.Clock() 145 | while True: 146 | clock.tick(24) 147 | showScreen(win, prefs) 148 | for event in pygame.event.get(): 149 | if event.type == pygame.QUIT and prompt(win): 150 | return 0 151 | 152 | elif event.type == pygame.MOUSEBUTTONDOWN: 153 | x, y = event.pos 154 | if 460 < x < 500 and 0 < y < 50 and prompt(win): 155 | return 1 156 | 157 | if 350 < x < 425 and 450 < y < 490: 158 | save(prefs) 159 | return 1 160 | 161 | for i in range(6): 162 | if 90 + i*60 < y < 130 + i*60: 163 | if 250 < x < 330: 164 | prefs[KEYS[i]] = True 165 | if 360 < x < 430: 166 | prefs[KEYS[i]] = False 167 | pygame.display.update() 168 | -------------------------------------------------------------------------------- /chess/onlinelib/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we define the main functions for online chess, and aggregate 4 | other functions for importing from online.py 5 | ''' 6 | from chess.lib import * 7 | from chess.onlinelib.utils import ( 8 | bgThread, 9 | read, 10 | readable, 11 | flush, 12 | write, 13 | getPlayers, 14 | showUpdateList, 15 | showLoading, 16 | popup, 17 | request, 18 | draw, 19 | showLobby, 20 | ) 21 | 22 | # This handles the online lobby. To display the window, it calls showLobby() 23 | # and does event handling, socket handling and other important stuff. 24 | def lobby(win, sock, key, load): 25 | clock = pygame.time.Clock() 26 | playerList = getPlayers(sock) 27 | while True: 28 | clock.tick(10) 29 | 30 | if playerList is None: 31 | return 2 32 | showLobby(win, key, playerList) 33 | 34 | for event in pygame.event.get(): 35 | if event.type == pygame.QUIT: 36 | write(sock, "quit") 37 | return 0 38 | 39 | elif event.type == pygame.MOUSEBUTTONDOWN: 40 | x, y = event.pos 41 | if 460 < x < 500 and 0 < y < 50: 42 | write(sock, "quit") 43 | return 1 44 | 45 | if 270 < x < 300 and 85 < y < 115: 46 | playerList = getPlayers(sock) 47 | 48 | if 300 < x < 475: 49 | for i in range(len(playerList)): 50 | if 122 + 30 * i < y < 148 + 30 * i: 51 | write(sock, "rg" + playerList[i][:4]) 52 | 53 | msg = read() 54 | if msg == "close": 55 | return 2 56 | 57 | elif msg == "msgOk": 58 | ret = request(win, sock) 59 | if ret in [0, 1, 2]: 60 | return ret 61 | 62 | elif ret == 4: 63 | newret = chess(win, sock, 0, load) 64 | if newret in [0, 1, 2]: 65 | return newret 66 | 67 | elif msg.startswith("err"): 68 | showUpdateList(win) 69 | 70 | playerList = getPlayers(sock) 71 | break 72 | 73 | if readable(): 74 | msg = read() 75 | if msg == "close": 76 | return 2 77 | 78 | elif msg.startswith("gr"): 79 | ret = request(win, sock, msg[2:]) 80 | if ret == 4: 81 | write(sock, "gmOk" + msg[2:]) 82 | newret = chess(win, sock, 1, load) 83 | if newret in [0, 1, 2]: 84 | return newret 85 | 86 | else: 87 | write(sock, "gmNo" + msg[2:]) 88 | if ret == 2: 89 | return ret 90 | playerList = getPlayers(sock) 91 | 92 | # This is called when user enters chess match, handles online chess. 93 | def chess(win, sock, player, load): 94 | start(win, load) 95 | 96 | side, board, flags = initBoardVars() 97 | 98 | clock = pygame.time.Clock() 99 | sel = prevsel = [0, 0] 100 | while True: 101 | clock.tick(25) 102 | for event in pygame.event.get(): 103 | if event.type == pygame.QUIT: 104 | write(sock, "quit") 105 | return 0 106 | 107 | elif event.type == pygame.MOUSEBUTTONDOWN: 108 | x, y = event.pos 109 | if 460 < x < 500 and 0 < y < 50: 110 | write(sock, "end") 111 | return 3 112 | 113 | if 50 < x < 450 and 50 < y < 450: 114 | x, y = x // 50, y // 50 115 | if load["flip"] and player: 116 | x, y = 9 - x, 9 - y 117 | 118 | if isOccupied(side, board, [x, y]) and side == player: 119 | sound.play_click(load) 120 | 121 | prevsel = sel 122 | sel = [x, y] 123 | 124 | if (side == player 125 | and isValidMove(side, board, flags, prevsel, sel)): 126 | promote = getPromote(win, player, board, prevsel, sel) 127 | write(sock, "mov" + encode(prevsel, sel, promote)) 128 | 129 | animate(win, player, board, prevsel, sel, load, player) 130 | side, board, flags = makeMove( 131 | side, board, prevsel, sel, flags, promote) 132 | 133 | elif not isEnd(side, board, flags): 134 | if 0 < x < 70 and 0 < y < 50: 135 | write(sock, "draw?") 136 | ret = draw(win, sock) 137 | if ret in [0, 2, 3]: 138 | return ret 139 | 140 | if 400 < x < 500 and 450 < y < 500: 141 | write(sock, "resign") 142 | return 3 143 | 144 | showScreen(win, side, board, flags, sel, load, player, True) 145 | if readable(): 146 | msg = read() 147 | if msg == "close": 148 | return 2 149 | 150 | elif msg == "quit" or msg == "resign": 151 | return popup(win, sock, msg) 152 | 153 | elif msg == "end": 154 | msg = "end" if isEnd(side, board, flags) else "abandon" 155 | return popup(win, sock, msg) 156 | 157 | elif msg == "draw?": 158 | ret = draw(win, sock, False) 159 | if ret in [2, 3]: 160 | return ret 161 | 162 | elif msg.startswith("mov") and side != player: 163 | fro, to, promote = decode(msg[3:]) 164 | if isValidMove(side, board, flags, fro, to): 165 | animate(win, side, board, fro, to, load, player) 166 | 167 | side, board, flags = makeMove( 168 | side, board, fro, to, flags, promote) 169 | sel = [0, 0] 170 | else: 171 | return 2 172 | -------------------------------------------------------------------------------- /menus/loadgame.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the loadgame menu which is called when user clicks 4 | loadgame button on main menu. 5 | 6 | We also define functions to save, load and scan for games. 7 | ''' 8 | 9 | import os 10 | import pygame 11 | from tools.loader import LOADGAME, BACK, putLargeNum, putDT 12 | from tools.utils import rounded_rect 13 | 14 | # This function scans for saved games 15 | def scan(): 16 | for i in range(20): 17 | pth = os.path.join("res", "savedGames", "game" + str(i) + ".txt") 18 | if os.path.exists(pth): 19 | with open(pth, "r") as f: 20 | data = f.read().splitlines()[:2] 21 | yield (i, data[0].split(" ")[0], data[1]) 22 | 23 | # This function deletes a game. 24 | def delGame(gameId): 25 | name = os.path.join("res", "savedGames", "game" + str(gameId) + ".txt") 26 | if os.path.exists(name): 27 | os.remove(name) 28 | 29 | # This function loads the game, returns the neccessary data 30 | def loadGame(gameId): 31 | name = os.path.join("res", "savedGames", "game" + str(gameId) + ".txt") 32 | if os.path.exists(name): 33 | with open(name, "r") as file: 34 | lines = file.read().splitlines() 35 | 36 | if len(lines) < 4: 37 | lines.extend([""] * (4 - len(lines))) 38 | 39 | if lines[0].strip() == "multi": 40 | temp = list(map(int, lines[3].strip().split())) 41 | if len(temp) == 0: 42 | return "multi", None, None, lines[2] 43 | 44 | elif len(temp) == 1: 45 | return "multi", temp[0], None, lines[2] 46 | 47 | else: 48 | return "multi", temp[0], temp[1:], lines[2] 49 | 50 | else: 51 | temp = lines[0].strip().split() 52 | return [temp[0]] + list(map(int, temp[1:])) + [lines[2]] 53 | else: 54 | return None 55 | 56 | # This prompts the user comfirmation while user deletes a game 57 | def prompt(win): 58 | rounded_rect(win, (255, 255, 255), (110, 160, 280, 130), 10, 4) 59 | 60 | win.blit(LOADGAME.MESSAGE[0], (116, 160)) 61 | win.blit(LOADGAME.MESSAGE[1], (118, 190)) 62 | 63 | win.blit(LOADGAME.YES, (145, 240)) 64 | win.blit(LOADGAME.NO, (305, 240)) 65 | pygame.draw.rect(win, (255, 255, 255), (140, 240, 60, 28), 2) 66 | pygame.draw.rect(win, (255, 255, 255), (300, 240, 46, 28), 2) 67 | 68 | pygame.display.flip() 69 | while True: 70 | for event in pygame.event.get(): 71 | if event.type == pygame.MOUSEBUTTONDOWN: 72 | if 240 < event.pos[1] < 270: 73 | if 140 < event.pos[0] < 200: 74 | return True 75 | elif 300 < event.pos[0] < 350: 76 | return False 77 | 78 | # This function shows the screen 79 | def showScreen(win, pg, scanned): 80 | win.fill((0, 0, 0)) 81 | rounded_rect(win, (255, 255, 255), (70, 15, 340, 60), 15, 4) 82 | win.blit(BACK, (460, 0)) 83 | win.blit(LOADGAME.HEAD, (100, 18)) 84 | win.blit(LOADGAME.LIST, (125, 80)) 85 | pygame.draw.line(win, (255, 255, 255), (125, 122), (360, 122), 3) 86 | 87 | if not scanned: 88 | win.blit(LOADGAME.EMPTY, (40, 130)) 89 | 90 | for cnt, i in enumerate(scanned): 91 | if cnt // 5 == pg: 92 | num = 60 * (cnt % 5) + 120 93 | 94 | rounded_rect(win, (255, 255, 255), (10, num, 480, 50), 10, 3) 95 | 96 | win.blit(LOADGAME.GAME, (15, num + 8)) 97 | putLargeNum(win, i[0], (90, num + 8)) 98 | pygame.draw.line(win, (255, 255, 255), (118, num + 5), 99 | (118, num + 45), 2) 100 | 101 | win.blit(LOADGAME.TYPHEAD, (122, num + 2)) 102 | win.blit(LOADGAME.TYP[i[1]], (122, num + 23)) 103 | pygame.draw.line(win, (255, 255, 255), (226, num + 5), 104 | (226, num + 45), 2) 105 | 106 | win.blit(LOADGAME.DATE, (230, num + 2)) 107 | win.blit(LOADGAME.TIME, (230, num + 23)) 108 | putDT(win, i[2], (278, num + 2)) 109 | 110 | rounded_rect(win, (255, 255, 255), (362, num + 5, 40, 40), 6, 2) 111 | win.blit(LOADGAME.DEL, (366, num + 9)) 112 | rounded_rect(win, (255, 255, 255), (405, num + 5, 80, 40), 6, 2) 113 | win.blit(LOADGAME.LOAD, (410, num + 10)) 114 | 115 | rounded_rect(win, (255, 255, 255), (160, 430, 20, 46), 6, 2) 116 | win.blit(LOADGAME.LEFT, (160, 430)) 117 | rounded_rect(win, (255, 255, 255), (320, 430, 20, 46), 6, 2) 118 | win.blit(LOADGAME.RIGHT, (320, 430)) 119 | 120 | rounded_rect(win, (255, 255, 255), (187, 430, 125, 46), 10, 2) 121 | win.blit(LOADGAME.PAGE[pg], (190, 430)) 122 | pygame.display.update() 123 | 124 | # This is the main function, called by the main menu 125 | def main(win): 126 | scanned = tuple(scan()) 127 | pages = (len(scanned) - 1) // 5 128 | pages = max(pages, 0) 129 | pg = 0 130 | clock = pygame.time.Clock() 131 | while True: 132 | clock.tick(24) 133 | showScreen(win, pg, scanned) 134 | for event in pygame.event.get(): 135 | if event.type == pygame.QUIT: 136 | return 0 137 | 138 | if event.type == pygame.MOUSEBUTTONDOWN: 139 | x, y = event.pos 140 | 141 | if 460 < x < 500 and 0 < y < 50: 142 | return 1 143 | 144 | if 430 < y < 476: 145 | if 160 < x < 180: 146 | pg = pages if pg == 0 else pg - 1 147 | elif 320 < x < 340: 148 | pg = 0 if pg == pages else pg + 1 149 | 150 | if 362 < x < 402: 151 | for i in range(5): 152 | if 120 + 60*i < y < 160 + 60*i: 153 | if scanned == tuple(scan()): 154 | if 5*pg + i < len(scanned): 155 | if prompt(win): 156 | delGame(scanned[5*pg + i][0]) 157 | scanned = tuple(scan()) 158 | pages = (len(scanned) - 1) // 5 159 | pages = max(pages, 0) 160 | if pg > pages: 161 | pg = pages 162 | break 163 | 164 | elif 405 < x < 485: 165 | for i in range(5): 166 | if 120 + 60*i < y < 160 + 60*i: 167 | newScan = tuple(scan()) 168 | if 5*pg + i < len(newScan): 169 | return loadGame(newScan[5*pg + i][0]) 170 | -------------------------------------------------------------------------------- /menus/stockfish.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we manage the stockfish menu. 4 | 5 | This is called from either main menu or singleplayer menu 6 | ''' 7 | import os 8 | import platform 9 | 10 | import pygame 11 | from ext.pyFish import teststockfish 12 | from tools.loader import STOCKFISH, BACK, putLargeNum 13 | from tools.utils import rounded_rect 14 | 15 | # This shows a popup on screen wether stockfish is configured or not. 16 | def install(win, pth): 17 | pygame.draw.rect(win, (0, 0, 0), (100, 200, 300, 100)) 18 | pygame.draw.rect(win, (255, 255, 255), (100, 200, 300, 100), 4) 19 | win.blit(STOCKFISH.LOADING, (100, 200)) 20 | pygame.display.update() 21 | 22 | pygame.draw.rect(win, (0, 0, 0), (100, 200, 300, 100)) 23 | pygame.draw.rect(win, (255, 255, 255), (100, 200, 300, 100), 4) 24 | 25 | pygame.draw.rect(win, (255, 255, 255), (220, 270, 65, 20), 2) 26 | win.blit(STOCKFISH.BACK, (220, 270)) 27 | 28 | active = teststockfish(pth) 29 | if active: 30 | with open(os.path.join("res", "stockfish", "path.txt"), "w") as f: 31 | f.write(pth) 32 | for cnt, i in enumerate(STOCKFISH.SUCCESS): 33 | win.blit(i, (120, 206 + cnt*17)) 34 | else: 35 | for cnt, i in enumerate(STOCKFISH.NOSUCCESS): 36 | win.blit(i, (130, 206 + cnt*17)) 37 | 38 | pygame.display.update() 39 | clock = pygame.time.Clock() 40 | while True: 41 | clock.tick(24) 42 | for event in pygame.event.get(): 43 | if event.type == pygame.MOUSEBUTTONDOWN: 44 | if 220 < event.pos[0] < 285 and 270 < event.pos[1] < 290: 45 | if active: 46 | return 1 47 | else: 48 | return 2 49 | 50 | # This shows a prompt if user decides to quit before configuring stockfish 51 | def prompt(win): 52 | pygame.draw.rect(win, (0, 0, 0), (110, 160, 280, 130)) 53 | pygame.draw.rect(win, (255, 255, 255), (110, 160, 280, 130), 4) 54 | 55 | win.blit(STOCKFISH.PROMPT[0], (130, 170)) 56 | win.blit(STOCKFISH.PROMPT[1], (130, 205)) 57 | 58 | win.blit(STOCKFISH.YES, (145, 240)) 59 | win.blit(STOCKFISH.NO, (305, 240)) 60 | pygame.draw.rect(win, (255, 255, 255), (140, 240, 60, 28), 2) 61 | pygame.draw.rect(win, (255, 255, 255), (300, 240, 45, 28), 2) 62 | 63 | pygame.display.flip() 64 | while True: 65 | for event in pygame.event.get(): 66 | if event.type == pygame.MOUSEBUTTONDOWN: 67 | if 240 < event.pos[1] < 270: 68 | if 140 < event.pos[0] < 200: 69 | return True 70 | elif 300 < event.pos[0] < 345: 71 | return False 72 | 73 | # This shows the installation guide screen 74 | def guideScreen(win, OS, pg): 75 | win.fill((0, 0, 0)) 76 | pygame.draw.rect(win, (255, 255, 255), (10, 10, 480, 480), 4) 77 | win.blit(BACK, (460, 0)) 78 | 79 | win.blit(STOCKFISH.TEST, (50, 425)) 80 | 81 | pygame.draw.rect(win, (255, 255, 255), (180, 450, 90, 30), 2) 82 | win.blit(STOCKFISH.INSTALL, (187, 450)) 83 | 84 | if OS == "Linux": 85 | win.blit(STOCKFISH.LIN_HEAD, (20, 15)) 86 | pygame.draw.line(win, (255, 255, 255), (20, 48), (395, 48), 3) 87 | 88 | if pg: 89 | putLargeNum(win, 1, (380, 15)) 90 | for cnt, i in enumerate(STOCKFISH.LIN_TEXT): 91 | win.blit(i, (20, 50 + cnt*18)) 92 | 93 | pygame.draw.rect(win, (255, 255, 255), (210, 380, 80, 20), 2) 94 | win.blit(STOCKFISH.CLICK, (210, 380)) 95 | 96 | else: 97 | putLargeNum(win, 2, (380, 15)) 98 | for cnt, i in enumerate(STOCKFISH.LIN_TEXT2): 99 | win.blit(i, (20, 50 + cnt*18)) 100 | 101 | elif OS == "Windows": 102 | win.blit(STOCKFISH.WIN_HEAD, (20, 15)) 103 | pygame.draw.line(win, (255, 255, 255), (20, 48), (400, 48), 3) 104 | 105 | for cnt, i in enumerate(STOCKFISH.WIN_TEXT): 106 | win.blit(i, (20, 60 + cnt*18)) 107 | 108 | elif OS == "Darwin": 109 | win.blit(STOCKFISH.MAC_HEAD, (20, 15)) 110 | pygame.draw.line(win, (255, 255, 255), (20, 48), (340, 48), 3) 111 | 112 | for cnt, i in enumerate(STOCKFISH.MAC_TEXT): 113 | win.blit(i, (20, 60 + cnt*18)) 114 | 115 | else: 116 | win.blit(STOCKFISH.OTH_HEAD, (20, 15)) 117 | pygame.draw.line(win, (255, 255, 255), (20, 48), (395, 48), 3) 118 | 119 | for cnt, i in enumerate(STOCKFISH.OTH_TEXT): 120 | win.blit(i, (20, 50 + cnt*18)) 121 | 122 | # This a function for managing the stockfish install guide 123 | def guideMain(win): 124 | OS = platform.system() 125 | pg = True 126 | clock = pygame.time.Clock() 127 | while True: 128 | clock.tick(24) 129 | guideScreen(win, OS, pg) 130 | for event in pygame.event.get(): 131 | if event.type == pygame.QUIT and prompt(win): 132 | return 0 133 | 134 | elif event.type == pygame.MOUSEBUTTONDOWN: 135 | x, y = event.pos 136 | if 460 < x < 500 and 0 < y < 50: 137 | if pg and prompt(win): 138 | return 1 139 | else: 140 | pg = True 141 | 142 | if OS == "Linux" and 210 < x < 290 and 380 < y < 400 and pg: 143 | pg = False 144 | 145 | if 180 < x < 270 and 450 < y < 480: 146 | if (OS == "Linux" and pg) or OS == "Darwin": 147 | pth = "stockfish" 148 | else: 149 | pth = "res/stockfish/build/stockfish" 150 | 151 | return install(win, pth) 152 | pygame.display.update() 153 | 154 | # This shows the screen 155 | def showScreen(win, configured): 156 | win.fill((0, 0, 0)) 157 | 158 | rounded_rect(win, (255, 255, 255), (40, 10, 395, 60), 16, 4) 159 | rounded_rect(win, (255, 255, 255), (10, 80, 480, 410), 10, 4) 160 | win.blit(BACK, (460, 0)) 161 | win.blit(STOCKFISH.HEAD, (50, 12)) 162 | for cnt, i in enumerate(STOCKFISH.TEXT): 163 | win.blit(i, (15, 90 + cnt*18)) 164 | 165 | rounded_rect(win, (255, 255, 255), (120, 320, 250, 30), 6, 3) 166 | win.blit(STOCKFISH.CONFIG, (120, 320)) 167 | 168 | if configured: 169 | for cnt, i in enumerate(STOCKFISH.CONFIGURED): 170 | win.blit(i, (15, 360 + cnt*18)) 171 | else: 172 | for cnt, i in enumerate(STOCKFISH.NONCONFIGURED): 173 | win.blit(i, (15, 360 + cnt*18)) 174 | 175 | # This is the main function, called by main menu 176 | def main(win): 177 | pth = os.path.join("res", "stockfish", "path.txt") 178 | configured = os.path.exists(pth) 179 | if configured: 180 | with open(pth, "r") as f: 181 | configured = teststockfish(f.read().strip()) 182 | 183 | clock = pygame.time.Clock() 184 | while True: 185 | clock.tick(24) 186 | showScreen(win, configured) 187 | for event in pygame.event.get(): 188 | if event.type == pygame.QUIT: 189 | return 0 190 | 191 | elif event.type == pygame.MOUSEBUTTONDOWN: 192 | x, y = event.pos 193 | 194 | if 460 < x < 500 and 0 < y < 50: 195 | return 1 196 | 197 | if 120 < x < 370 and 320 < y < 350: 198 | if configured: 199 | os.remove(pth) 200 | configured = False 201 | 202 | ret = guideMain(win) 203 | if ret in [0, 1]: 204 | return ret 205 | 206 | pygame.display.update() 207 | -------------------------------------------------------------------------------- /chess/onlinelib/utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a part of My-PyChess application. 3 | In this file, we define the gui functions for online chess. 4 | ''' 5 | 6 | import pygame 7 | 8 | from chess.onlinelib.sockutils import * 9 | from tools.loader import ONLINE, BACK, putLargeNum, putNum 10 | 11 | # Shows a small popup when user requests game with wrong player. 12 | def showUpdateList(win): 13 | pygame.draw.rect(win, (0, 0, 0), (110, 220, 280, 60)) 14 | pygame.draw.rect(win, (255, 255, 255), (110, 220, 280, 60), 4) 15 | 16 | win.blit(ONLINE.ERRCONN, (120, 240)) 17 | 18 | pygame.display.update() 19 | for _ in range(50): 20 | pygame.time.delay(50) 21 | for _ in pygame.event.get(): 22 | pass 23 | 24 | # This shows screen just before the lobby, displays error messages too. 25 | def showLoading(win, errcode=0): 26 | pygame.draw.rect(win, (0, 0, 0), (100, 220, 300, 80)) 27 | pygame.draw.rect(win, (255, 255, 255), (100, 220, 300, 80), 4) 28 | 29 | win.blit(ONLINE.ERR[errcode], (115, 240)) 30 | if errcode == 0: 31 | pygame.display.update() 32 | return 33 | 34 | pygame.draw.rect(win, (255, 255, 255), (220, 270, 65, 20), 2) 35 | win.blit(ONLINE.GOBACK, (220, 270)) 36 | pygame.display.update() 37 | while True: 38 | for event in pygame.event.get(): 39 | if event.type == pygame.MOUSEBUTTONDOWN: 40 | if 220 < event.pos[0] < 285 and 270 < event.pos[1] < 290: 41 | return 42 | 43 | # Show a popup message when user left, resigned or draw accepted. 44 | def popup(win, sock, typ): 45 | pygame.draw.rect(win, (0, 0, 0), (130, 220, 240, 80)) 46 | pygame.draw.rect(win, (255, 255, 255), (130, 220, 240, 80), 4) 47 | 48 | win.blit(ONLINE.POPUP[typ], (145, 240)) 49 | 50 | pygame.draw.rect(win, (255, 255, 255), (220, 270, 65, 20), 2) 51 | win.blit(ONLINE.GOBACK, (220, 270)) 52 | pygame.display.update() 53 | 54 | ret = 3 55 | while True: 56 | if readable() and read() == "close": 57 | write(sock, "quit") 58 | ret = 2 59 | 60 | for event in pygame.event.get(): 61 | if event.type == pygame.MOUSEBUTTONDOWN: 62 | if 220 < event.pos[0] < 285 and 270 < event.pos[1] < 290: 63 | write(sock, "end") 64 | return ret 65 | 66 | # It shows a popup message on the screen. It can show two things, 67 | # 1) Waiting for opponent input for game request (when key is None) 68 | # 2) Waiting for player input for game request (when key is not None) 69 | def request(win, sock, key=None): 70 | if key is None: 71 | pygame.draw.rect(win, (0, 0, 0), (100, 210, 300, 100)) 72 | pygame.draw.rect(win, (255, 255, 255), (100, 210, 300, 100), 4) 73 | 74 | win.blit(ONLINE.REQUEST1[0], (120, 220)) 75 | win.blit(ONLINE.REQUEST1[1], (105, 245)) 76 | win.blit(ONLINE.REQUEST1[2], (135, 270)) 77 | 78 | else: 79 | pygame.draw.rect(win, (0, 0, 0), (100, 160, 300, 130)) 80 | pygame.draw.rect(win, (255, 255, 255), (100, 160, 300, 130), 4) 81 | 82 | win.blit(ONLINE.REQUEST2[0], (110, 175)) 83 | win.blit(ONLINE.REQUEST2[1], (200, 175)) 84 | win.blit(ONLINE.REQUEST2[2], (105, 200)) 85 | putNum(win, key, (160, 175)) 86 | 87 | win.blit(ONLINE.OK, (145, 240)) 88 | win.blit(ONLINE.NO, (305, 240)) 89 | pygame.draw.rect(win, (255, 255, 255), (140, 240, 50, 28), 2) 90 | pygame.draw.rect(win, (255, 255, 255), (300, 240, 50, 28), 2) 91 | 92 | pygame.display.flip() 93 | while True: 94 | for event in pygame.event.get(): 95 | if key is None and event.type == pygame.QUIT: 96 | write(sock, "quit") 97 | return 0 98 | 99 | elif event.type == pygame.MOUSEBUTTONDOWN: 100 | if key is None: 101 | if 460 < event.pos[0] < 500 and 0 < event.pos[1] < 50: 102 | write(sock, "quit") 103 | return 1 104 | 105 | elif 240 < event.pos[1] < 270: 106 | if 140 < event.pos[0] < 190: 107 | return 4 108 | elif 300 < event.pos[0] < 350: 109 | return 3 110 | 111 | if readable(): 112 | msg = read() 113 | if msg == "close": 114 | return 2 115 | 116 | if msg == "quit": 117 | return 3 118 | 119 | if key is None: 120 | if msg == "nostart": 121 | write(sock, "pass") 122 | return 3 123 | 124 | if msg == "start": 125 | write(sock, "ready") 126 | return 4 127 | 128 | # It shows a popup message on the screen. It can show two things, 129 | # 1) Waiting for other players input for draw game (when requester is True) 130 | # 2) Waiting for players input for draw game (when requester is False) 131 | def draw(win, sock, requester=True): 132 | if requester: 133 | pygame.draw.rect(win, (0, 0, 0), (100, 220, 300, 60)) 134 | pygame.draw.rect(win, (255, 255, 255), (100, 220, 300, 60), 4) 135 | 136 | win.blit(ONLINE.DRAW1[0], (110, 225)) 137 | win.blit(ONLINE.DRAW1[1], (180, 250)) 138 | 139 | else: 140 | pygame.draw.rect(win, (0, 0, 0), (100, 160, 300, 130)) 141 | pygame.draw.rect(win, (255, 255, 255), (100, 160, 300, 130), 4) 142 | 143 | win.blit(ONLINE.DRAW2[0], (120, 170)) 144 | win.blit(ONLINE.DRAW2[1], (170, 195)) 145 | 146 | win.blit(ONLINE.OK, (145, 240)) 147 | win.blit(ONLINE.NO, (305, 240)) 148 | pygame.draw.rect(win, (255, 255, 255), (140, 240, 50, 28), 2) 149 | pygame.draw.rect(win, (255, 255, 255), (300, 240, 50, 28), 2) 150 | 151 | pygame.display.flip() 152 | while True: 153 | for event in pygame.event.get(): 154 | if requester: 155 | if event.type == pygame.QUIT: 156 | write(sock, "quit") 157 | return 0 158 | 159 | elif event.type == pygame.MOUSEBUTTONDOWN: 160 | if 240 < event.pos[1] < 270: 161 | if 140 < event.pos[0] < 190: 162 | write(sock, "draw") 163 | return 3 164 | 165 | elif 300 < event.pos[0] < 350: 166 | write(sock, "nodraw") 167 | return 4 168 | 169 | if readable(): 170 | msg = read() 171 | if msg == "close": 172 | return 2 173 | 174 | if msg == "quit": 175 | return popup(win, sock, msg) 176 | 177 | if requester: 178 | if msg == "draw": 179 | return popup(win, sock, msg) 180 | 181 | if msg == "nodraw": 182 | return 4 183 | 184 | # Responsible for showing the online Lobby 185 | def showLobby(win, key, playerlist): 186 | win.fill((0, 0, 0)) 187 | 188 | win.blit(ONLINE.LOBBY, (100, 14)) 189 | pygame.draw.rect(win, (255, 255, 255), (65, 10, 355, 68), 4) 190 | win.blit(BACK, (460, 0)) 191 | win.blit(ONLINE.LIST, (20, 75)) 192 | win.blit(ONLINE.REFRESH, (270, 85)) 193 | pygame.draw.line(win, (255, 255, 255), (20, 114), (190, 114), 3) 194 | pygame.draw.line(win, (255, 255, 255), (210, 114), (265, 114), 3) 195 | 196 | if not playerlist: 197 | win.blit(ONLINE.EMPTY, (25, 130)) 198 | 199 | for cnt, player in enumerate(playerlist): 200 | pkey, stat = int(player[:4]), player[4] 201 | yCord = 120 + cnt * 30 202 | 203 | putLargeNum(win, cnt + 1, (20, yCord)) 204 | win.blit(ONLINE.DOT, (36, yCord)) 205 | win.blit(ONLINE.PLAYER, (52, yCord)) 206 | putLargeNum(win, pkey, (132, yCord)) 207 | if stat == "a": 208 | win.blit(ONLINE.ACTIVE, (200, yCord)) 209 | elif stat == "b": 210 | win.blit(ONLINE.BUSY, (200, yCord)) 211 | pygame.draw.rect(win, (255, 255, 255), (300, yCord + 2, 175, 26), 2) 212 | win.blit(ONLINE.REQ, (300, yCord)) 213 | 214 | win.blit(ONLINE.YOUARE, (100, 430)) 215 | pygame.draw.rect(win, (255, 255, 255), (250, 435, 158, 40), 3) 216 | win.blit(ONLINE.PLAYER, (260, 440)) 217 | putLargeNum(win, key, (340, 440)) 218 | 219 | pygame.display.update() -------------------------------------------------------------------------------- /pychess.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is the main file of My-PyChess application. 3 | Run this file to launch the program. 4 | 5 | In this file, we handle the main menu which gets displayed at runtime. 6 | """ 7 | import sys 8 | import pygame 9 | 10 | import chess 11 | import menus 12 | from tools.loader import MAIN 13 | from tools import sound 14 | 15 | # This is a non-important bit of code. Flush stdout - useful incase external 16 | # programs are calling this application. 17 | sys.stdout.flush() 18 | 19 | # Some initialisation 20 | pygame.init() 21 | clock = pygame.time.Clock() 22 | 23 | # Initialise display, set the caption and icon. Use SCALED if on pygame 2. 24 | if pygame.version.vernum[0] >= 2: 25 | win = pygame.display.set_mode((500, 500), pygame.SCALED) 26 | else: 27 | win = pygame.display.set_mode((500, 500)) 28 | pygame.display.set_caption("My-PyChess") 29 | pygame.display.set_icon(MAIN.ICON) 30 | 31 | # Coordinates of buttons in rectangle notation. 32 | sngl = (260, 140, 220, 40) 33 | mult = (280, 200, 200, 40) 34 | onln = (360, 260, 120, 40) 35 | load = (280, 320, 200, 40) 36 | pref = (0, 450, 210, 40) 37 | abt = (390, 450, 110, 40) 38 | hwto = (410, 410, 90, 30) 39 | stok = (0, 410, 240, 30) 40 | 41 | # This is the function that displays the main screen. 42 | # "prefs" value is passed, prefs is a list of all the user settings 43 | def showMain(prefs): 44 | # cnt and image are two global variables, cnt is an integer that is 45 | # incremented in every frame, when cnt reaches 210, it is setback to zero. 46 | # img variable denotes the image that is displayed on the screen 47 | # it can have a value from 0 to 3 (both inclusive) 48 | global cnt, img 49 | 50 | # First, blit background image (based on the img variable) 51 | win.blit(MAIN.BG[img], (0, 0)) 52 | 53 | # Then we check wether user has enabled background animate feature 54 | if prefs["slideshow"]: 55 | # Background animations is a feature that shows a slideshow of images 56 | # in the background, slow fading of screen is also seen. 57 | # To achieve this, a frame counter variable "cnt" is incremented 58 | # every frame. The intended framerate of the game is 30 fps, so 59 | # after seven seconds, the frame counter reaches 210 after which, 60 | # it needs to be reset to zero. 61 | cnt += 1 62 | if cnt >= 150: 63 | # If the counter has reached a value of 150 (means 5 seconds have 64 | # elapsed), then start to slowly fade the image. 65 | # This is achieved by blitting a surface onto the screen 66 | # whose transparancy keeps reducing as each frame goes 67 | s = pygame.Surface((500, 500)) 68 | s.set_alpha((cnt - 150) * 4) 69 | s.fill((0, 0, 0)) 70 | win.blit(s, (0, 0)) 71 | 72 | if cnt == 210: 73 | cnt = 0 74 | img = 0 if img == 3 else img + 1 75 | else: 76 | # User has disabled screen animations, reset the variables 77 | cnt = -150 78 | img = 0 79 | 80 | # Now blit all the texts onto the screen one by one 81 | win.blit(MAIN.HEADING, (80, 20)) 82 | pygame.draw.line(win, (255, 255, 255), (80, 100), (130, 100), 4) 83 | pygame.draw.line(win, (255, 255, 255), (165, 100), (340, 100), 4) 84 | win.blit(MAIN.VERSION, (345, 95)) 85 | 86 | win.blit(MAIN.SINGLE, sngl[:2]) 87 | win.blit(MAIN.MULTI, mult[:2]) 88 | win.blit(MAIN.ONLINE, onln[:2]) 89 | win.blit(MAIN.LOAD, load[:2]) 90 | win.blit(MAIN.PREF, pref[:2]) 91 | win.blit(MAIN.HOWTO, hwto[:2]) 92 | win.blit(MAIN.ABOUT, abt[:2]) 93 | win.blit(MAIN.STOCK, stok[:2]) 94 | 95 | # Initialize a few more variables 96 | cnt = 0 97 | img = 0 98 | run = True 99 | 100 | # Load the settings of the player 101 | prefs = menus.pref.load() 102 | 103 | music = sound.Music() 104 | music.play(prefs) 105 | while run: 106 | # Start the game loop at 30fps, show the screen every time at first 107 | clock.tick(30) 108 | showMain(prefs) 109 | 110 | # We need to get the position of the mouse so that we can blit an image 111 | # on the text over which the mouse hovers 112 | x, y = pygame.mouse.get_pos() 113 | 114 | if sngl[0] < x < sum(sngl[::2]) and sngl[1] < y < sum(sngl[1::2]): 115 | win.blit(MAIN.SINGLE_H, sngl[:2]) 116 | 117 | if mult[0] < x < sum(mult[::2]) and mult[1] < y < sum(mult[1::2]): 118 | win.blit(MAIN.MULTI_H, mult[:2]) 119 | 120 | if onln[0] < x < sum(onln[::2]) and onln[1] < y < sum(onln[1::2]): 121 | win.blit(MAIN.ONLINE_H, onln[:2]) 122 | 123 | if load[0] < x < sum(load[::2]) and load[1] < y < sum(load[1::2]): 124 | win.blit(MAIN.LOAD_H, load[:2]) 125 | 126 | if pref[0] < x < sum(pref[::2]) and pref[1] < y < sum(pref[1::2]): 127 | win.blit(MAIN.PREF_H, pref[:2]) 128 | 129 | if hwto[0] < x < sum(hwto[::2]) and hwto[1] < y < sum(hwto[1::2]): 130 | win.blit(MAIN.HOWTO_H, hwto[:2]) 131 | 132 | if abt[0] < x < sum(abt[::2]) and abt[1] < y < sum(abt[1::2]): 133 | win.blit(MAIN.ABOUT_H, abt[:2]) 134 | 135 | if stok[0] < x < sum(stok[::2]) and stok[1] < y < sum(stok[1::2]): 136 | win.blit(MAIN.STOCK_H, stok[:2]) 137 | 138 | # Begin pygame event loop to catch all events 139 | for event in pygame.event.get(): 140 | if event.type == pygame.QUIT: 141 | run = False 142 | 143 | elif event.type == pygame.MOUSEBUTTONDOWN: 144 | # User has clicked somewhere, determine which button and 145 | # call a function to handle the game into a different window. 146 | x, y = event.pos 147 | 148 | if sngl[0] < x < sum(sngl[::2]) and sngl[1] < y < sum(sngl[1::2]): 149 | sound.play_click(prefs) 150 | ret = menus.splayermenu(win) 151 | if ret == 0: 152 | run = False 153 | elif ret != 1: 154 | if ret[0]: 155 | run = chess.mysingleplayer(win, ret[1], prefs) 156 | else: 157 | run = chess.singleplayer(win, ret[1], ret[2], prefs) 158 | 159 | elif mult[0] < x < sum(mult[::2]) and mult[1] < y < sum(mult[1::2]): 160 | sound.play_click(prefs) 161 | ret = menus.timermenu(win, prefs) 162 | if ret == 0: 163 | run = False 164 | elif ret != 1: 165 | run = chess.multiplayer(win, ret[0], ret[1], prefs) 166 | 167 | elif onln[0] < x < sum(onln[::2]) and onln[1] < y < sum(onln[1::2]): 168 | sound.play_click(prefs) 169 | ret = menus.onlinemenu(win) 170 | if ret == 0: 171 | run = False 172 | elif ret != 1: 173 | run = chess.online(win, ret[0], prefs, ret[1]) 174 | 175 | elif load[0] < x < sum(load[::2]) and load[1] < y < sum(load[1::2]): 176 | sound.play_click(prefs) 177 | ret = menus.loadgamemenu(win) 178 | if ret == 0: 179 | run = False 180 | 181 | elif ret != 1: 182 | if ret[0] == "multi": 183 | run = chess.multiplayer(win, *ret[1:3], prefs, ret[3]) 184 | elif ret[0] == "single": 185 | run = chess.singleplayer(win, *ret[1:3], prefs, ret[3]) 186 | elif ret[0] == "mysingle": 187 | run = chess.mysingleplayer(win, ret[1], prefs, ret[2]) 188 | 189 | elif pref[0] < x < sum(pref[::2]) and pref[1] < y < sum(pref[1::2]): 190 | sound.play_click(prefs) 191 | run = menus.prefmenu(win) 192 | 193 | prefs = menus.pref.load() 194 | if music.is_playing(): 195 | if not prefs["sounds"]: 196 | music.stop() 197 | else: 198 | music.play(prefs) 199 | 200 | elif hwto[0] < x < sum(hwto[::2]) and hwto[1] < y < sum(hwto[1::2]): 201 | sound.play_click(prefs) 202 | run = menus.howtomenu(win) 203 | 204 | elif abt[0] < x < sum(abt[::2]) and abt[1] < y < sum(abt[1::2]): 205 | sound.play_click(prefs) 206 | run = menus.aboutmenu(win) 207 | 208 | elif stok[0] < x < sum(stok[::2]) and stok[1] < y < sum(stok[1::2]): 209 | sound.play_click(prefs) 210 | run = menus.sfmenu(win) 211 | 212 | # Update the screen every frame 213 | pygame.display.flip() 214 | 215 | # Stop music, quit pygame after the loop is done 216 | music.stop() 217 | pygame.quit() 218 | -------------------------------------------------------------------------------- /chess/lib/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | In this file, we define the core chess-related functions. 4 | For a better understanding of the variables used here, checkout docs.txt 5 | """ 6 | # A simple function to make a copy of the board 7 | def copy(board): 8 | return [[list(j) for j in board[i]] for i in range(2)] 9 | 10 | # Return the type of piece given it's position. Return None if Empty. 11 | def getType(side, board, pos): 12 | for piece in board[side]: 13 | if piece[:2] == pos: 14 | return piece[2] 15 | 16 | # Determine wether the position given is occupied by a piece of the given side. 17 | def isOccupied(side, board, pos): 18 | return getType(side, board, pos) is not None 19 | 20 | # Determine wether the position(s) given is(are) empty or not 21 | def isEmpty(board, *poslist): 22 | for pos in poslist: 23 | for side in range(2): 24 | if isOccupied(side, board, pos): 25 | return False 26 | return True 27 | 28 | # Determine wether the king of a given side is in check or not. 29 | def isChecked(side, board): 30 | for piece in board[side]: 31 | if piece[2] == "k": 32 | for i in board[not side]: 33 | if piece[:2] in rawMoves(not side, board, i): 34 | return True 35 | return False 36 | 37 | # Determine all the possible LEGAL moves available for the side. 38 | def legalMoves(side, board, flags): 39 | for piece in board[side]: 40 | for pos in availableMoves(side, board, piece, flags): 41 | yield [piece[:2], pos] 42 | 43 | # This function returns wether a game has ended or not 44 | def isEnd(side, board, flags): 45 | for _ in legalMoves(side, board, flags): 46 | return False 47 | return True 48 | 49 | # This function moves the piece from one coordinate to other while handling the 50 | # capture of enemy, pawn promotion and en-passent. 51 | # One thing to note that this function directly modifies global value of the 52 | # board variable from within the function, so pass a copy of the board 53 | # variable if you do not want global modification of the variable. 54 | def move(side, board, fro, to, promote="p"): 55 | UP = 8 if side else 1 56 | DOWN = 1 if side else 8 57 | ALLOWENP = fro[1] == 4 + side and to[0] != fro[0] and isEmpty(board, to) 58 | for piece in board[not side]: 59 | if piece[:2] == to: 60 | board[not side].remove(piece) 61 | break 62 | 63 | for piece in board[side]: 64 | if piece[:2] == fro: 65 | piece[:2] = to 66 | if piece[2] == "k": 67 | if fro[0] - to[0] == 2: 68 | move(side, board, [1, DOWN], [4, DOWN]) 69 | elif to[0] - fro[0] == 2: 70 | move(side, board, [8, DOWN], [6, DOWN]) 71 | 72 | if piece[2] == "p": 73 | if to[1] == UP: 74 | board[side].remove(piece) 75 | board[side].append([to[0], UP, promote]) 76 | if ALLOWENP: 77 | board[not side].remove([to[0], fro[1], "p"]) 78 | break 79 | return board 80 | 81 | # This function returns wether a move puts ones own king at check 82 | def moveTest(side, board, fro, to): 83 | return not isChecked(side, move(side, copy(board), fro, to)) 84 | 85 | # This function returns wether a move is valid or not 86 | def isValidMove(side, board, flags, fro, to): 87 | if 0 < to[0] < 9 and 0 < to[1] < 9 and not isOccupied(side, board, to): 88 | piece = fro + [getType(side, board, fro)] 89 | if to in rawMoves(side, board, piece, flags): 90 | return moveTest(side, board, fro, to) 91 | 92 | # This is an important wrapper function. It makes the move, updates the 93 | # flags and flips the side, returning the updated data. 94 | def makeMove(side, board, fro, to, flags, promote="q"): 95 | newboard = move(side, copy(board), fro, to, promote) 96 | newflags = updateFlags(side, newboard, fro, to, flags) 97 | return not side, newboard, newflags 98 | 99 | # Does a routine check to update all the flags required for castling and 100 | # enpassent. This function needs to be called AFTER every move played. 101 | def updateFlags(side, board, fro, to, flags): 102 | castle = list(flags[0]) 103 | if [5, 8, "k"] not in board[0] or [1, 8, "r"] not in board[0]: 104 | castle[0] = False 105 | if [5, 8, "k"] not in board[0] or [8, 8, "r"] not in board[0]: 106 | castle[1] = False 107 | if [5, 1, "k"] not in board[1] or [1, 1, "r"] not in board[1]: 108 | castle[2] = False 109 | if [5, 1, "k"] not in board[1] or [8, 1, "r"] not in board[1]: 110 | castle[3] = False 111 | 112 | enP = None 113 | if getType(side, board, to) == "p": 114 | if fro[1] - to[1] == 2: 115 | enP = [to[0], 6] 116 | elif to[1] - fro[1] == 2: 117 | enP = [to[0], 3] 118 | 119 | return castle, enP 120 | 121 | # Given a side, board and piece, it yields all possible legal moves 122 | # of that piece. This function is an extension/wrapper on rawMoves() 123 | def availableMoves(side, board, piece, flags): 124 | for i in rawMoves(side, board, piece, flags): 125 | if 0 < i[0] < 9 and 0 < i[1] < 9 and not isOccupied(side, board, i): 126 | if moveTest(side, board, piece[:2], i): 127 | yield i 128 | 129 | # Given a side, board and piece, it yields all possible moves by the piece. 130 | # If flags are given, it can also yeild the special moves of chess. 131 | # It also returns moves that are illegal, therefore the function is for 132 | # internal use only 133 | def rawMoves(side, board, piece, flags=[None, None]): 134 | x, y, ptype = piece 135 | if ptype == "p": 136 | if not side: 137 | if y == 7 and isEmpty(board, [x, 6], [x, 5]): 138 | yield [x, 5] 139 | if isEmpty(board, [x, y - 1]): 140 | yield [x, y - 1] 141 | 142 | for i in ([x + 1, y - 1], [x - 1, y - 1]): 143 | if isOccupied(1, board, i) or flags[1] == i: 144 | yield i 145 | else: 146 | if y == 2 and isEmpty(board, [x, 3], [x, 4]): 147 | yield [x, 4] 148 | if isEmpty(board, [x, y + 1]): 149 | yield [x, y + 1] 150 | 151 | for i in ([x + 1, y + 1], [x - 1, y + 1]): 152 | if isOccupied(0, board, i) or flags[1] == i: 153 | yield i 154 | 155 | elif ptype == "n": 156 | yield from ( 157 | [x + 1, y + 2], [x + 1, y - 2], [x - 1, y + 2], [x - 1, y - 2], 158 | [x + 2, y + 1], [x + 2, y - 1], [x - 2, y + 1], [x - 2, y - 1] 159 | ) 160 | 161 | elif ptype == "b": 162 | for i in range(1, 8): 163 | yield [x + i, y + i] 164 | if not isEmpty(board, [x + i, y + i]): 165 | break 166 | for i in range(1, 8): 167 | yield [x + i, y - i] 168 | if not isEmpty(board, [x + i, y - i]): 169 | break 170 | for i in range(1, 8): 171 | yield [x - i, y + i] 172 | if not isEmpty(board, [x - i, y + i]): 173 | break 174 | for i in range(1, 8): 175 | yield [x - i, y - i] 176 | if not isEmpty(board, [x - i, y - i]): 177 | break 178 | 179 | elif ptype == "r": 180 | for i in range(1, 8): 181 | yield [x + i, y] 182 | if not isEmpty(board, [x + i, y]): 183 | break 184 | for i in range(1, 8): 185 | yield [x - i, y] 186 | if not isEmpty(board, [x - i, y]): 187 | break 188 | for i in range(1, 8): 189 | yield [x, y + i] 190 | if not isEmpty(board, [x, y + i]): 191 | break 192 | for i in range(1, 8): 193 | yield [x, y - i] 194 | if not isEmpty(board, [x, y - i]): 195 | break 196 | 197 | elif ptype == "q": 198 | yield from rawMoves(side, board, [x, y, "b"]) 199 | yield from rawMoves(side, board, [x, y, "r"]) 200 | 201 | elif ptype == "k": 202 | if flags[0] is not None and not isChecked(side, board): 203 | if flags[0][0] and isEmpty(board, [2, 8], [3, 8], [4, 8]): 204 | if moveTest(0, board, [5, 8], [4, 8]): 205 | yield [3, 8] 206 | if flags[0][1] and isEmpty(board, [6, 8], [7, 8]): 207 | if moveTest(0, board, [5, 8], [6, 8]): 208 | yield [7, 8] 209 | if flags[0][2] and isEmpty(board, [2, 1], [3, 1], [4, 1]): 210 | if moveTest(1, board, [5, 1], [4, 1]): 211 | yield [3, 1] 212 | if flags[0][3] and isEmpty(board, [6, 1], [7, 1]): 213 | if moveTest(1, board, [5, 1], [6, 1]): 214 | yield [7, 1] 215 | 216 | yield from ( 217 | [x - 1, y - 1], [x, y - 1], [x + 1, y - 1], [x - 1, y], 218 | [x - 1, y + 1], [x, y + 1], [x + 1, y + 1], [x + 1, y] 219 | ) -------------------------------------------------------------------------------- /ext/pyBox.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | This Module is WORK-IN-PROGRESS. 4 | 5 | This is a module meant to provide a high-level TextBox in Pygame 6 | It is being designed by me to be fully KEYBOARD and MOUSE INTERACTIVE. 7 | """ 8 | 9 | import os 10 | import pygame 11 | 12 | # Implement the TextBox class 13 | class TextBox: 14 | def __init__(self, font, color, rect, text = ""): 15 | if not os.path.isfile(font): 16 | font = pygame.font.match_font(font) 17 | 18 | self.font = pygame.font.Font(font, rect[3] - 8) 19 | self.COLOR = color 20 | self.RECT = rect 21 | self.text = text 22 | self.cursor = 0 23 | self.startpos = 0 24 | self.active = False 25 | self.mouseheld = False 26 | self.shiftheld = False 27 | self.selected = None 28 | self.clock = pygame.time.Clock() 29 | self.time = 0 30 | self.visible = True 31 | self.SWITCHTIME = 600 32 | self.surf = pygame.Surface(rect[2:]) 33 | 34 | def renderText(self, indices=None): 35 | if indices is None: 36 | indices = [0, len(self.text)] 37 | 38 | return self.font.render( 39 | self.text[indices[0]:indices[1]], True, self.COLOR) 40 | 41 | def insert(self, index, text): 42 | self.text = self.text[:index] + text + self.text[index:] 43 | 44 | def remove(self, indices): 45 | if isinstance(indices, int): 46 | indices = [indices, indices + 1] 47 | self.text = self.text[:indices[0]] + self.text[indices[1]:] 48 | 49 | def getLen(self, indices=None): 50 | return self.renderText(indices).get_width() 51 | 52 | def push(self, event): 53 | if event.type == pygame.MOUSEBUTTONDOWN: 54 | self.mouseheld = True 55 | x, y = event.pos 56 | if (self.RECT[0] < x < (self.RECT[0] + self.RECT[2]) and 57 | self.RECT[1] < y < (self.RECT[1] + self.RECT[3])): 58 | self.active = True 59 | else: 60 | self.active = False 61 | self.selected = None 62 | 63 | elif event.type == pygame.MOUSEBUTTONUP: 64 | self.mouseheld = False 65 | 66 | elif event.type == pygame.KEYUP: 67 | if event.key == pygame.K_RSHIFT or event.key == pygame.K_LSHIFT: 68 | self.shiftheld = False 69 | 70 | elif event.type == pygame.KEYDOWN and self.active: 71 | if event.key in [pygame.K_TAB, pygame.K_ESCAPE, pygame.K_KP_ENTER]: 72 | pass 73 | 74 | elif event.key == pygame.K_RSHIFT or event.key == pygame.K_LSHIFT: 75 | self.shiftheld = True 76 | 77 | elif event.key == pygame.K_BACKSPACE: 78 | if self.selected is None: 79 | if self.cursor > 0: 80 | self.cursor -= 1 81 | self.remove(self.cursor) 82 | else: 83 | self.cursor = self.selected[0] 84 | self.remove(self.selected) 85 | self.selected = None 86 | 87 | elif event.key == pygame.K_DELETE: 88 | if self.selected is None: 89 | if self.cursor < len(self.text): 90 | self.remove(self.cursor) 91 | else: 92 | self.cursor = self.selected[0] 93 | self.remove(self.selected) 94 | self.selected = None 95 | 96 | elif event.key == pygame.K_RIGHT: 97 | if self.cursor < len(self.text): 98 | if self.shiftheld: 99 | if self.selected is None: 100 | self.selected = [self.cursor, self.cursor + 1] 101 | elif self.cursor == self.selected[1]: 102 | self.selected[1] += 1 103 | elif self.cursor == self.selected[0]: 104 | self.selected[0] += 1 105 | 106 | if self.selected[0] == self.selected[1]: 107 | self.selected = None 108 | 109 | else: 110 | self.selected = None 111 | self.cursor += 1 112 | 113 | elif event.key == pygame.K_LEFT: 114 | if self.cursor > 0: 115 | self.cursor -= 1 116 | if self.shiftheld: 117 | if self.selected is None: 118 | self.selected = [self.cursor, self.cursor + 1] 119 | elif self.cursor == self.selected[0] - 1: 120 | self.selected[0] -= 1 121 | elif self.cursor == self.selected[1] - 1: 122 | self.selected[1] -= 1 123 | 124 | if self.selected[0] == self.selected[1]: 125 | self.selected = None 126 | 127 | else: 128 | self.selected = None 129 | 130 | elif event.key == pygame.K_END: 131 | if self.cursor < len(self.text): 132 | if self.shiftheld: 133 | if self.selected is None: 134 | self.selected = [self.cursor, len(self.text)] 135 | else: 136 | self.selected[1] = len(self.text) 137 | else: 138 | self.selected = None 139 | self.cursor = len(self.text) 140 | 141 | elif event.key == pygame.K_HOME: 142 | if self.cursor > 0: 143 | if self.shiftheld: 144 | if self.selected is None: 145 | self.selected = [0, self.cursor] 146 | else: 147 | self.selected[0] = 0 148 | else: 149 | self.selected = None 150 | self.cursor = 0 151 | 152 | elif event.key == pygame.K_RETURN: 153 | self.active = False 154 | 155 | elif len(event.unicode) == 1: 156 | if self.selected is None: 157 | self.insert(self.cursor, event.unicode) 158 | self.cursor += 1 159 | else: 160 | self.remove(self.selected) 161 | self.cursor = self.selected[0] 162 | self.selected = None 163 | self.insert(self.cursor, event.unicode) 164 | self.cursor += 1 165 | 166 | def draw(self, win): 167 | self.time += self.clock.get_time() 168 | if self.time >= self.SWITCHTIME: 169 | self.time %= self.SWITCHTIME 170 | self.visible = not self.visible 171 | 172 | self.surf.fill((0, 0, 0)) 173 | pygame.draw.rect(self.surf, (255, 255, 255), 174 | (3, 3, self.RECT[2] - 6, self.RECT[3] - 6)) 175 | 176 | cursorpos = self.getLen([0, self.cursor]) 177 | 178 | rendered = pygame.Surface((self.getLen() + 2, self.RECT[3] - 8)) 179 | rendered.fill((255, 255, 255)) 180 | 181 | if self.selected is not None: 182 | selrect = (self.getLen([0, self.selected[0]]), 2, 183 | self.getLen(self.selected), self.RECT[3] - 12) 184 | pygame.draw.rect(rendered, (128, 220, 255), selrect) 185 | 186 | rendered.blit(self.renderText(), (0, 0)) 187 | 188 | if self.active: 189 | pygame.draw.rect(self.surf, (0, 0, 255), 190 | (2, 2, self.RECT[2] - 5, self.RECT[3] - 5), 2) 191 | 192 | if self.visible: 193 | pygame.draw.line(rendered, (0, 0, 0), (cursorpos, 2), 194 | (cursorpos, self.RECT[3] - 12), 2) 195 | 196 | if rendered.get_width() > self.RECT[2] - 8: 197 | if cursorpos < self.startpos + 2: 198 | self.startpos = cursorpos - 2 199 | elif cursorpos > self.startpos + self.RECT[2] - 6: 200 | self.startpos = cursorpos - self.RECT[2] + 6 201 | else: 202 | self.surf.blit(rendered, (4 - self.startpos, 4)) 203 | else: 204 | self.surf.blit(rendered, (4, 4)) 205 | 206 | win.blit(self.surf, self.RECT[:2]) 207 | self.clock.tick() 208 | 209 | # This is basic sample code for use with pyBox 210 | if __name__ == "__main__": 211 | pygame.init() 212 | box = TextBox("calibri", (0, 0, 0), (30, 0, 150, 35)) 213 | running = True 214 | win = pygame.display.set_mode((300, 200)) 215 | win.fill((255, 255, 255)) 216 | while running: 217 | for event in pygame.event.get(): 218 | box.push(event) 219 | if event.type == pygame.QUIT: 220 | running = False 221 | box.draw(win) 222 | pygame.display.flip() 223 | # print(box.text) 224 | pygame.quit() 225 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | To run the online server, run this script. 4 | 5 | For more information, see onlinehowto.txt 6 | 7 | IMPORTANT NOTE: 8 | Server.py needs atleast Python v3.6 to work. 9 | """ 10 | 11 | import queue 12 | import random 13 | import socket 14 | import threading 15 | import time 16 | from urllib.request import urlopen 17 | 18 | # These are constants that can be modified by users. Default settings 19 | # are given. Do not change if you do not know what you are doing. 20 | LOG = False 21 | IPV6 = False 22 | 23 | #===================================================== 24 | # DO NOT MODIFY ANYTHING BELOW THIS!! 25 | #===================================================== 26 | 27 | # Define other constants 28 | VERSION = "v3.2.0" 29 | PORT = 26104 30 | START_TIME = time.perf_counter() 31 | LOGFILENAME = time.asctime().replace(" ", "_").replace(":", "-") 32 | 33 | # Initialise a few global variables 34 | busyPpl = set() 35 | end = False 36 | lock = False 37 | logQ = queue.Queue() 38 | players = [] 39 | total = totalsuccess = 0 40 | 41 | # Function to convert string to int. Doesnt raise errors, returns None instead. 42 | def makeInt(num): 43 | try: 44 | return int(num) 45 | except ValueError: 46 | return None 47 | 48 | # A function to display elapsed time in desired format. 49 | def getTime(): 50 | sec = round(time.perf_counter() - START_TIME) 51 | minutes, sec = divmod(sec, 60) 52 | hours, minutes = divmod(minutes, 60) 53 | days, hours = divmod(hours, 24) 54 | return f"{days} days, {hours} hours, {minutes} minutes, {sec} seconds" 55 | 56 | # A function to get IP address. It can give public IP or private. 57 | def getIp(public): 58 | if public: 59 | try: 60 | ip = urlopen("https://api64.ipify.org").read().decode() 61 | except: 62 | ip = "127.0.0.1" 63 | 64 | else: 65 | with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: 66 | try: 67 | s.connect(('10.255.255.255', 1)) 68 | ip = s.getsockname()[0] 69 | except: 70 | ip = '127.0.0.1' 71 | return ip 72 | 73 | # A function to Log/Print text. Used instead of print() 74 | def log(data, key=None, adminput=False): 75 | global logQ 76 | if adminput: 77 | text = "" 78 | elif key is None: 79 | text = "SERVER: " 80 | else: 81 | text = f"Player{key}: " 82 | 83 | if data is not None: 84 | text += data 85 | if not adminput: 86 | print(text) 87 | 88 | if LOG: 89 | logQ.put(time.asctime() + ": " + text + "\n") 90 | else: 91 | logQ.put(None) 92 | 93 | # Used instead of sock.recv(), because it returns the decoded message, handles 94 | # TCP packet loss, timeout and other useful stuff 95 | def read(sock, timeout=None): 96 | try: 97 | sock.settimeout(timeout) 98 | msg = sock.recv(8).decode("utf-8").strip() 99 | 100 | except: 101 | msg = "quit" 102 | 103 | if msg: 104 | return msg 105 | return "quit" 106 | 107 | # A function to message the server, this is used instead of socket.send() 108 | # beacause it buffers the message, handles packet loss and does not raise 109 | # exception if message could not be sent 110 | def write(sock, msg): 111 | if msg: 112 | buffedmsg = msg + (" " * (8 - len(msg))) 113 | try: 114 | sock.sendall(buffedmsg.encode("utf-8")) 115 | except: 116 | pass 117 | 118 | # Generates a random four digit number that is unique 119 | def genKey(): 120 | key = random.randint(1000, 9999) 121 | for player in players: 122 | if player[1] == key: 123 | return genKey() 124 | return key 125 | 126 | # Given a players key, returns the sock object for the player 127 | # Returns None if player does not exist 128 | def getByKey(key): 129 | for player in players: 130 | if player[1] == makeInt(key): 131 | return player[0] 132 | 133 | # Makes the player(s) busy, ie puts the player's key in a list of busy people 134 | def mkBusy(*keys): 135 | global busyPpl 136 | for key in keys: 137 | busyPpl.add(makeInt(key)) 138 | 139 | # Makes the player(s) active, ie removes the player's key from the list of busy 140 | def rmBusy(*keys): 141 | global busyPpl 142 | for key in keys: 143 | busyPpl.discard(makeInt(key)) 144 | 145 | # This simple function handles the chess match. Returns after game ended. 146 | # Returns True if player got disconnected during the match, false otherwise. 147 | def game(sock1, sock2): 148 | while True: 149 | msg = read(sock1) 150 | write(sock2, msg) 151 | if msg == "quit": 152 | return True 153 | 154 | elif msg in ["draw", "resign", "end"]: 155 | return False 156 | 157 | # It handles every player's communiction with server. 158 | def player(sock, key): 159 | while True: 160 | msg = read(sock) 161 | if msg == "quit": 162 | return 163 | 164 | elif msg == "pStat": 165 | log("Made request for players Stats.", key) 166 | latestplayers = list(players) 167 | latestbusy = list(busyPpl) 168 | 169 | if 0 < len(latestplayers) < 11: 170 | write(sock, "enum" + str(len(latestplayers) - 1)) 171 | for _, i in latestplayers: 172 | if i != key: 173 | if i in latestbusy: 174 | write(sock, str(i) + "b") 175 | else: 176 | write(sock, str(i) + "a") 177 | 178 | elif msg.startswith("rg"): 179 | log(f"Made request to play with Player{msg[2:]}", key) 180 | oSock = getByKey(msg[2:]) 181 | if oSock is not None: 182 | if makeInt(msg[2:]) not in busyPpl: 183 | mkBusy(key, msg[2:]) 184 | write(oSock, "gr" + str(key)) 185 | 186 | write(sock, "msgOk") 187 | newMsg = read(sock) 188 | if newMsg == "ready": 189 | log(f"Player{key} is in a game as white") 190 | if game(sock, oSock): 191 | return 192 | else: 193 | log(f"Player{key} finished the game") 194 | 195 | elif newMsg == "quit": 196 | write(oSock, "quit") 197 | return 198 | 199 | rmBusy(key) 200 | 201 | else: 202 | log(f"Player{key} requested busy player") 203 | write(sock, "errPBusy") 204 | else: 205 | log(f"Player{key} Sent invalid key") 206 | write(sock, "errKey") 207 | 208 | elif msg.startswith("gmOk"): 209 | log(f"Accepted Player{msg[4:]} request", key) 210 | oSock = getByKey(msg[4:]) 211 | write(oSock, "start") 212 | log(f"Player{key} is in a game as black") 213 | if game(sock, oSock): 214 | return 215 | else: 216 | log(f"Player{key} finished the game") 217 | rmBusy(key) 218 | 219 | elif msg.startswith("gmNo"): 220 | log(f"Rejected Player{msg[4:]} request", key) 221 | write(getByKey(msg[4:]), "nostart") 222 | rmBusy(key) 223 | 224 | # A thread to log all the texts. Flush from logQ. 225 | def logThread(): 226 | global logQ 227 | while True: 228 | time.sleep(1) 229 | with open("SERVER_LOG_" + LOGFILENAME + ".txt", "a") as f: 230 | while not logQ.empty(): 231 | data = logQ.get() 232 | if data is None: 233 | return 234 | else: 235 | f.write(data) 236 | 237 | # This is a Thread that runs in background to remove disconnected people 238 | def kickDisconnectedThread(): 239 | global players 240 | while True: 241 | time.sleep(10) 242 | for sock, key in players: 243 | try: 244 | ret = sock.send(b"........") 245 | except: 246 | ret = 0 247 | 248 | if ret > 0: 249 | cntr = 0 250 | diff = 8 251 | while True: 252 | cntr += 1 253 | if cntr == 8: 254 | ret = 0 255 | break 256 | 257 | if ret == diff: 258 | break 259 | diff -= ret 260 | 261 | try: 262 | ret = sock.send(b"." * diff) 263 | except: 264 | ret = 0 265 | break 266 | 267 | if ret == 0: 268 | log(f"Player{key} got disconnected, removing from player list") 269 | try: 270 | players.remove((sock, key)) 271 | except: 272 | pass 273 | 274 | # This is a Thread that runs in background to collect user input commands 275 | def adminThread(): 276 | global end, lock 277 | while True: 278 | msg = input().strip() 279 | log(msg, adminput=True) 280 | 281 | if msg == "report": 282 | log(f"{len(players)} players are online right now,") 283 | log(f"{len(players) - len(busyPpl)} are active.") 284 | log(f"{total} connections attempted, {totalsuccess} were successful") 285 | log(f"Server is running {threading.active_count()} threads.") 286 | log(f"Time elapsed since last reboot: {getTime()}") 287 | if players: 288 | log("LIST OF PLAYERS:") 289 | for cnt, (_, player) in enumerate(players): 290 | if player not in busyPpl: 291 | log(f" {cnt+1}. Player{player}, Status: Active") 292 | else: 293 | log(f" {cnt+1}. Player{player}, Status: Busy") 294 | 295 | elif msg == "mypublicip": 296 | log("Determining public IP, please wait....") 297 | PUBIP = getIp(public=True) 298 | if PUBIP == "127.0.0.1": 299 | log("An error occurred while determining IP") 300 | 301 | else: 302 | log(f"This machine has a public IP address {PUBIP}") 303 | 304 | elif msg == "lock": 305 | if lock: 306 | log("Aldready in locked state") 307 | else: 308 | lock = True 309 | log("Locked server, no one can join now.") 310 | 311 | elif msg == "unlock": 312 | if lock: 313 | lock = False 314 | log("Unlocked server, all can join now.") 315 | else: 316 | log("Aldready in unlocked state.") 317 | 318 | elif msg.startswith("kick "): 319 | for k in msg[5:].split(): 320 | sock = getByKey(k) 321 | if sock is not None: 322 | write(sock, "close") 323 | log(f"Kicking player{k}") 324 | else: 325 | log(f"Player{k} does not exist") 326 | 327 | elif msg == "kickall": 328 | log("Attempting to kick everyone.") 329 | latestplayers = list(players) 330 | for sock, _ in latestplayers: 331 | write(sock, "close") 332 | 333 | elif msg == "quit": 334 | lock = True 335 | log("Attempting to kick everyone.") 336 | latestplayers = list(players) 337 | for sock, _ in latestplayers: 338 | write(sock, "close") 339 | 340 | log("Exiting application - Bye") 341 | log(None) 342 | 343 | end = True 344 | if IPV6: 345 | with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 346 | s.connect(("::1", PORT, 0, 0)) 347 | else: 348 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 349 | s.connect(("127.0.0.1", PORT)) 350 | return 351 | 352 | else: 353 | log(f"Invalid command entered ('{msg}').") 354 | log("See 'onlinehowto.txt' for help on how to use the commands.") 355 | 356 | # Does the initial checks and lets players in. 357 | def initPlayerThread(sock): 358 | global players, total, totalsuccess 359 | log("New client is attempting to connect.") 360 | total += 1 361 | 362 | if read(sock, 3) != "PyChess": 363 | log("Client sent invalid header, closing connection.") 364 | write(sock, "errVer") 365 | 366 | elif read(sock, 3) != VERSION: 367 | log("Client sent invalid version info, closing connection.") 368 | write(sock, "errVer") 369 | 370 | elif len(players) >= 10: 371 | log("Server is busy, closing new connections.") 372 | write(sock, "errBusy") 373 | 374 | elif lock: 375 | log("SERVER: Server is locked, closing connection.") 376 | write(sock, "errLock") 377 | 378 | else: 379 | totalsuccess += 1 380 | key = genKey() 381 | log(f"Connection Successful, assigned key - {key}") 382 | players.append((sock, key)) 383 | 384 | write(sock, "key" + str(key)) 385 | player(sock, key) 386 | write(sock, "close") 387 | log(f"Player{key} has Quit") 388 | 389 | try: 390 | players.remove((sock, key)) 391 | except: 392 | pass 393 | rmBusy(key) 394 | sock.close() 395 | 396 | # Initialize the main socket 397 | log(f"Welcome to My-Pychess Server, {VERSION}\n") 398 | log("INITIALIZING...") 399 | 400 | if IPV6: 401 | log("IPv6 is enabled. This is NOT the default configuration.") 402 | 403 | mainSock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 404 | mainSock.bind(("::", PORT, 0, 0)) 405 | else: 406 | log("Starting server with IPv4 (default) configuration.") 407 | mainSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 408 | mainSock.bind(("0.0.0.0", PORT)) 409 | 410 | IP = getIp(public=False) 411 | if IP == "127.0.0.1": 412 | log("This machine does not appear to be connected to a network.") 413 | log("With this limitation, you can only serve the clients ") 414 | log("who are on THIS machine. Use IP address 127.0.0.1\n") 415 | 416 | else: 417 | log(f"This machine has a local IP address - {IP}") 418 | log("USE THIS IP IF THE CLIENT IS ON THE SAME NETWORK.") 419 | log("For more info, read file 'onlinehowto.txt'\n") 420 | 421 | mainSock.listen(16) 422 | log("Successfully Started.") 423 | log(f"Accepting connections on port {PORT}\n") 424 | 425 | threading.Thread(target=adminThread).start() 426 | threading.Thread(target=kickDisconnectedThread, daemon=True).start() 427 | if LOG: 428 | log("Logging is enabled. Starting to log all output") 429 | threading.Thread(target=logThread).start() 430 | 431 | while True: 432 | s, _ = mainSock.accept() 433 | if end: 434 | break 435 | 436 | threading.Thread(target=initPlayerThread, args=(s,), daemon=True).start() 437 | mainSock.close() 438 | -------------------------------------------------------------------------------- /tools/loader.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of My-PyChess application. 3 | This file loads all the images and texts that are used. 4 | 5 | Most of the scripts in this application import specific classes from this 6 | module. Each class is a collection of resources for a particular script. 7 | All font-related stuff is done in this file, the functions to put a number 8 | on the screen and display date and time are also defined here 9 | """ 10 | 11 | import os.path 12 | import pygame 13 | 14 | # Initialize pygame.font module and load the font file. 15 | pygame.font.init() 16 | FONT = os.path.join("res", "Asimov.otf") 17 | 18 | # Load different sizes of the font. 19 | head = pygame.font.Font(FONT, 80) 20 | large = pygame.font.Font(FONT, 50) 21 | medium = pygame.font.Font(FONT, 38) 22 | small = pygame.font.Font(FONT, 27) 23 | vsmall = pygame.font.Font(FONT, 17) 24 | 25 | # Define RGB color constants for use. 26 | WHITE = (255, 255, 255) 27 | GREY = (180, 180, 180) 28 | BLACK = (0, 0, 0) 29 | GREEN = (0, 255, 0) 30 | RED = (200, 20, 20) 31 | 32 | # Define a few constants that contain loaded texts of numbers and chararters. 33 | NUM = [vsmall.render(str(i), True, WHITE) for i in range(10)] 34 | LNUM = [small.render(str(i), True, WHITE) for i in range(10)] 35 | BLNUM = [small.render(str(i), True, BLACK) for i in range(10)] 36 | SLASH = vsmall.render("/", True, WHITE) 37 | COLON = vsmall.render(":", True, WHITE) 38 | 39 | # This function displays a number in a position, very small sized text used. 40 | def putNum(win, num, pos): 41 | for cnt, i in enumerate(list(str(num))): 42 | win.blit(NUM[int(i)], (pos[0] + (cnt * 9), pos[1])) 43 | 44 | # This function displays a number in a position, Small sized text used. 45 | def putLargeNum(win, num, pos, white=True): 46 | for cnt, i in enumerate(list(str(num))): 47 | if white: 48 | win.blit(LNUM[int(i)], (pos[0] + (cnt * 14), pos[1])) 49 | else: 50 | win.blit(BLNUM[int(i)], (pos[0] + (cnt * 14), pos[1])) 51 | 52 | # This function displays the date and time in a position on the screen. 53 | def putDT(win, DT, pos): 54 | var = DT.split() 55 | date = var[0].split("/") 56 | time = var[1].split(":") 57 | 58 | for cnt, num in enumerate(map(lambda x: format(int(x), "02"), date)): 59 | putNum(win, num, (pos[0] + 24 * cnt - 5, pos[1])) 60 | 61 | win.blit(SLASH, (pos[0] + 13, pos[1])) 62 | win.blit(SLASH, (pos[0] + 35, pos[1])) 63 | 64 | for cnt, num in enumerate(map(lambda x: format(int(x), "02"), time)): 65 | putNum(win, num, (pos[0] + 24 * cnt, pos[1] + 21)) 66 | 67 | win.blit(COLON, (pos[0] + 20, pos[1] + 21)) 68 | win.blit(COLON, (pos[0] + 44, pos[1] + 21)) 69 | 70 | # This splits a string at regular intervals of "index" characters 71 | def splitstr(string, index=57): 72 | data = [] 73 | while len(string) >= index: 74 | data.append(string[:index]) 75 | string = string[index:] 76 | data.append(string) 77 | return data 78 | 79 | # Defined important globals for loading background image sprites. 80 | BGSPRITE = pygame.image.load(os.path.join("res", "img", "bgsprites.jpg")) 81 | PSPRITE = pygame.image.load(os.path.join("res", "img", "piecesprite.png")) 82 | 83 | # Load global image for back 84 | BACK = pygame.image.load(os.path.join("res", "img", "back.png")) 85 | 86 | class CHESS: 87 | PIECES = ({}, {}) 88 | for i, ptype in enumerate(["k", "q", "b", "n", "r", "p"]): 89 | for side in range(2): 90 | PIECES[side][ptype] = PSPRITE.subsurface((i * 50, side * 50, 50, 50)) 91 | 92 | CHECK = small.render("CHECK!", True, BLACK) 93 | STALEMATE = small.render("STALEMATE!", True, BLACK) 94 | CHECKMATE = small.render("CHECKMATE!", True, BLACK) 95 | LOST = small.render("LOST", True, BLACK) 96 | CHOOSE = small.render("CHOOSE:", True, BLACK) 97 | SAVE = small.render("Save Game", True, BLACK) 98 | UNDO = small.render("Undo", True, BLACK) 99 | 100 | MESSAGE = ( 101 | small.render("Do you want to quit", True, WHITE), 102 | small.render("this game?", True, WHITE), 103 | ) 104 | 105 | MESSAGE2 = ( 106 | small.render("Game saved. Now do", True, WHITE), 107 | small.render("you want to quit?", True, WHITE), 108 | ) 109 | 110 | YES = small.render("YES", True, WHITE) 111 | NO = small.render("NO", True, WHITE) 112 | MSG = vsmall.render("Game will be saved with ID", True, WHITE) 113 | SAVE_ERR = vsmall.render("ERROR: SaveGame Limit Exeeded", True, WHITE) 114 | 115 | TURN = ( 116 | small.render("Others turn", True, BLACK), 117 | small.render("Your turn", True, BLACK), 118 | ) 119 | 120 | DRAW = small.render("Draw", True, BLACK) 121 | RESIGN = small.render("Resign", True, BLACK) 122 | 123 | TIMEUP = ( 124 | vsmall.render("Time Up!", True, WHITE), 125 | vsmall.render("Technically the game is over, but you", True, WHITE), 126 | vsmall.render("can still continue if you wish to - :)", True, WHITE), 127 | ) 128 | 129 | OK = small.render("Ok", True, WHITE) 130 | COL = small.render(":", True, BLACK) 131 | 132 | class LOADGAME: 133 | HEAD = large.render("Load Games", True, WHITE) 134 | LIST = medium.render("List of Games", True, WHITE) 135 | EMPTY = small.render("There are no saved games yet.....", True, WHITE) 136 | GAME = small.render("Game", True, WHITE) 137 | TYPHEAD = vsmall.render("Game Type:", True, WHITE) 138 | TYP = { 139 | "single": vsmall.render("SinglePlayer", True, WHITE), 140 | "mysingle": vsmall.render("SinglePlayer", True, WHITE), 141 | "multi": vsmall.render("MultiPlayer", True, WHITE), 142 | } 143 | DATE = vsmall.render("Date-", True, WHITE) 144 | TIME = vsmall.render("Time-", True, WHITE) 145 | 146 | DEL = pygame.image.load(os.path.join("res", "img", "delete.jpg")) 147 | LOAD = small.render("LOAD", True, WHITE) 148 | 149 | MESSAGE = ( 150 | small.render("Are you sure that you", True, WHITE), 151 | small.render("want to delete game?", True, WHITE), 152 | ) 153 | YES = small.render("YES", True, WHITE) 154 | NO = small.render("NO", True, WHITE) 155 | 156 | LEFT = medium.render("<", True, WHITE) 157 | RIGHT = medium.render(">", True, WHITE) 158 | PAGE = [medium.render("Page " + str(i), True, WHITE) for i in range(1, 5)] 159 | 160 | class MAIN: 161 | HEADING = head.render("PyChess", True, WHITE) 162 | VERSION = vsmall.render("Version 3.2", True, WHITE) 163 | ICON = pygame.image.load(os.path.join("res", "img", "icon.gif")) 164 | BG = [BGSPRITE.subsurface((i * 500, 0, 500, 500)) for i in range(4)] 165 | 166 | SINGLE = medium.render("SinglePlayer", True, WHITE) 167 | MULTI = medium.render("MultiPlayer", True, WHITE) 168 | ONLINE = medium.render("Online", True, WHITE) 169 | LOAD = medium.render("Load Game", True, WHITE) 170 | HOWTO = small.render("Howto", True, WHITE) 171 | ABOUT = medium.render("About", True, WHITE) 172 | PREF = medium.render("Preferences", True, WHITE) 173 | STOCK = small.render("Configure Stockfish", True, WHITE) 174 | 175 | SINGLE_H = medium.render("SinglePlayer", True, GREY) 176 | MULTI_H = medium.render("MultiPlayer", True, GREY) 177 | ONLINE_H = medium.render("Online", True, GREY) 178 | LOAD_H = medium.render("Load Game", True, GREY) 179 | HOWTO_H = small.render("Howto", True, GREY) 180 | ABOUT_H = medium.render("About", True, GREY) 181 | PREF_H = medium.render("Preferences", True, GREY) 182 | STOCK_H = small.render("Configure Stockfish", True, GREY) 183 | 184 | class PREF: 185 | HEAD = large.render("Preferences", True, WHITE) 186 | 187 | SOUNDS = medium.render("Sounds", True, WHITE) 188 | FLIP = medium.render("Flip screen", True, WHITE) 189 | CLOCK = medium.render("Show Clock", True, WHITE) 190 | SLIDESHOW = medium.render("Slideshow", True, WHITE) 191 | MOVE = medium.render("Moves", True, WHITE) 192 | UNDO = medium.render("Allow undo", True, WHITE) 193 | 194 | COLON = medium.render(":", True, WHITE) 195 | 196 | TRUE = medium.render("True", True, WHITE) 197 | FALSE = medium.render("False", True, WHITE) 198 | 199 | SOUNDS_H = ( 200 | vsmall.render("Play different sounds", True, WHITE), 201 | vsmall.render("and music", True, WHITE), 202 | ) 203 | FLIP_H = ( 204 | vsmall.render("This flips the screen", True, WHITE), 205 | vsmall.render("after each move", True, WHITE), 206 | ) 207 | CLOCK_H = ( 208 | vsmall.render("Show a clock in chess", True, WHITE), 209 | vsmall.render("when timer is disabled", True, WHITE), 210 | ) 211 | SLIDESHOW_H = ( 212 | vsmall.render("This shows a slide of", True, WHITE), 213 | vsmall.render("backgrounds on screen", True, WHITE), 214 | ) 215 | MOVE_H = ( 216 | vsmall.render("This shows all the legal", True, WHITE), 217 | vsmall.render("moves of a selected piece", True, WHITE), 218 | ) 219 | UNDO_H = ( 220 | vsmall.render("This allowes undo if", True, WHITE), 221 | vsmall.render("set to be true", True, WHITE), 222 | ) 223 | 224 | BSAVE = medium.render("Save", True, WHITE) 225 | TIP = vsmall.render("TIP: Hover the mouse over the feature", True, WHITE) 226 | TIP2 = vsmall.render("name to know more about it.", True, WHITE) 227 | 228 | PROMPT = ( 229 | vsmall.render("Are you sure you want to leave?", True, WHITE), 230 | vsmall.render("Any changes will not be saved.", True, WHITE), 231 | ) 232 | 233 | YES = small.render("YES", True, WHITE) 234 | NO = small.render("NO", True, WHITE) 235 | 236 | class ONLINE: 237 | ERR = ( 238 | vsmall.render("Attempting to connect to server..", True, WHITE), 239 | vsmall.render("[ERR 1] Couldn't find the server..", True, WHITE), 240 | vsmall.render("[ERR 2] Versions are incompatible..", True, WHITE), 241 | vsmall.render("[ERR 3] Server is full (max = 10)..", True, WHITE), 242 | vsmall.render("[ERR 4] The server is locked...", True, WHITE), 243 | vsmall.render("[ERR 5] Unknown error occured...", True, WHITE), 244 | vsmall.render("You got disconnected from server..", True, WHITE), 245 | ) 246 | GOBACK = vsmall.render("Go Back", True, WHITE) 247 | 248 | EMPTY = small.render("No one's online, you are alone.", True, WHITE) 249 | 250 | LOBBY = large.render("Online Lobby", True, WHITE) 251 | LIST = medium.render("List of Players", True, WHITE) 252 | PLAYER = small.render("Player", True, WHITE) 253 | DOT = small.render(".", True, WHITE) 254 | 255 | ACTIVE = small.render("ACTIVE", True, GREEN) 256 | BUSY = small.render("BUSY", True, RED) 257 | REQ = small.render("Send Request", True, WHITE) 258 | YOUARE = medium.render("You Are", True, WHITE) 259 | 260 | ERRCONN = vsmall.render("Unable to connect to that player..", True, WHITE) 261 | 262 | REFRESH = pygame.image.load(os.path.join("res", "img", "refresh.png")) 263 | 264 | REQUEST1 = ( 265 | vsmall.render("Please wait for the other player to", True, WHITE), 266 | vsmall.render("accept your request. Game will begin", True, WHITE), 267 | vsmall.render("shortly. You will play as white", True, WHITE), 268 | ) 269 | REQUEST2 = ( 270 | vsmall.render("Player", True, WHITE), 271 | vsmall.render("wants to play with you.", True, WHITE), 272 | vsmall.render("Accept to play. You will play as black", True, WHITE), 273 | ) 274 | 275 | DRAW1 = ( 276 | vsmall.render("Sent a request to your opponent for", True, WHITE), 277 | vsmall.render("draw, wait for reply.", True, WHITE), 278 | ) 279 | 280 | DRAW2 = ( 281 | vsmall.render("Your opponent is requesting for a", True, WHITE), 282 | vsmall.render("draw, please reply.", True, WHITE), 283 | ) 284 | 285 | POPUP = { 286 | "quit": vsmall.render("Opponent got disconnected", True, WHITE), 287 | "resign": vsmall.render("The opponent has resigned", True, WHITE), 288 | "draw": vsmall.render("A draw has been agreed", True, WHITE), 289 | "end": vsmall.render("Game ended, opponent left", True, WHITE), 290 | "abandon": vsmall.render("Opponent abandoned match", True, WHITE), 291 | } 292 | 293 | NO = small.render("NO", True, WHITE) 294 | OK = small.render("OK", True, WHITE) 295 | 296 | class ONLINEMENU: 297 | HEAD = large.render("Online", True, WHITE) 298 | with open(os.path.join("res", "texts", "online.txt")) as f: 299 | TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 300 | 301 | CONNECT = small.render("Connect", True, WHITE) 302 | 303 | class SINGLE: 304 | HEAD = large.render("Singleplayer", True, WHITE) 305 | SELECT = pygame.image.load(os.path.join("res", "img", "select.jpg")) 306 | CHOOSE = small.render("Choose:", True, WHITE) 307 | START = small.render("Start Game", True, WHITE) 308 | OR = medium.render("OR", True, WHITE) 309 | 310 | with open(os.path.join("res", "texts", "single1.txt")) as f: 311 | PARA1 = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 312 | 313 | with open(os.path.join("res", "texts", "single2.txt")) as f: 314 | PARA2 = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 315 | 316 | LEVEL = small.render("Level:", True, WHITE) 317 | 318 | BACK = vsmall.render("Go Back", True, WHITE) 319 | _CONFIG = ( 320 | "It looks like you have not configured", 321 | "stockfish. To play, you have to do", 322 | "that.", 323 | ) 324 | CONFIG = [vsmall.render(i, True, WHITE) for i in _CONFIG] 325 | OK = vsmall.render("Ok", True, WHITE) 326 | NOTNOW = vsmall.render("Not Now", True, WHITE) 327 | 328 | class STOCKFISH: 329 | HEAD = large.render("Stockfish Engine", True, WHITE) 330 | CONFIG = small.render("Configure Stockfish", True, WHITE) 331 | with open(os.path.join("res", "texts", "stockfish", "stockfish.txt"), "r") as f: 332 | TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 333 | 334 | with open(os.path.join("res", "texts", "stockfish", "configd.txt"), "r") as f: 335 | CONFIGURED = [vsmall.render(i, True, GREEN) for i in f.read().splitlines()] 336 | 337 | with open(os.path.join("res", "texts", "stockfish", "nonconfigd.txt"), "r") as f: 338 | NONCONFIGURED = [vsmall.render(i, True, RED) for i in f.read().splitlines()] 339 | 340 | CLICK = vsmall.render("Click Here", True, WHITE) 341 | BACK = vsmall.render("Go Back", True, WHITE) 342 | INSTALL = small.render("Install", True, WHITE) 343 | TEST = vsmall.render( 344 | "After all steps are complete, press button below.", True, WHITE 345 | ) 346 | 347 | WIN_HEAD = small.render("Installation Guide for Windows", True, WHITE) 348 | LIN_HEAD = small.render("Installation Guide for Linux -", True, WHITE) 349 | MAC_HEAD = small.render("Installation Guide for Mac", True, WHITE) 350 | OTH_HEAD = small.render("Installation Guide for Other OS", True, WHITE) 351 | 352 | with open(os.path.join("res", "texts", "stockfish", "win.txt"), "r") as f: 353 | WIN_TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 354 | 355 | with open(os.path.join("res", "texts", "stockfish", "linux.txt"), "r") as f: 356 | LIN_TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 357 | 358 | with open(os.path.join("res", "texts", "stockfish", "linux2.txt"), "r") as f: 359 | LIN_TEXT2 = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 360 | 361 | with open(os.path.join("res", "texts", "stockfish", "mac.txt"), "r") as f: 362 | MAC_TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 363 | 364 | with open(os.path.join("res", "texts", "stockfish", "other.txt"), "r") as f: 365 | OTH_TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 366 | 367 | for line in splitstr(os.path.abspath("res/stockfish/build/stockfish.exe")): 368 | WIN_TEXT.append(vsmall.render(line, True, WHITE)) 369 | 370 | for line in splitstr(os.path.abspath("res/stockfish/build/stockfish")): 371 | LIN_TEXT2.append(vsmall.render(line, True, WHITE)) 372 | OTH_TEXT.append(vsmall.render(line, True, WHITE)) 373 | 374 | LOADING = head.render("Loading", True, WHITE) 375 | _SUCCESS = ("Setup successful, now you can go", "back and play chess.") 376 | _NOSUCCESS = ( 377 | "Setup unsuccessful, try to re-", 378 | "configure. Follow instuctions", 379 | "carefully and try again.", 380 | ) 381 | 382 | SUCCESS = [vsmall.render(i, True, GREEN) for i in _SUCCESS] 383 | NOSUCCESS = [vsmall.render(i, True, RED) for i in _NOSUCCESS] 384 | 385 | PROMPT = ( 386 | small.render("Do you want to quit?", True, WHITE), 387 | vsmall.render("Stockfish is not configured yet.", True, WHITE) 388 | ) 389 | YES = small.render("Yes", True, WHITE) 390 | NO = small.render("No", True, WHITE) 391 | 392 | class ABOUT: 393 | HEAD = large.render("About PyChess", True, WHITE) 394 | 395 | with open(os.path.join("res", "texts", "about.txt"), "r") as f: 396 | TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 397 | 398 | class HOWTO: 399 | HEAD = large.render("Chess Howto", True, WHITE) 400 | 401 | with open(os.path.join("res", "texts", "howto.txt"), "r") as f: 402 | TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 403 | 404 | class TIMER: 405 | HEAD = large.render("Timer Menu", True, WHITE) 406 | 407 | YES = small.render("Yes", True, WHITE) 408 | NO = small.render("No", True, WHITE) 409 | 410 | PROMPT = vsmall.render("Do you want to set timer?", True, WHITE) 411 | 412 | with open(os.path.join("res", "texts", "timer.txt"), "r") as f: 413 | TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] 414 | 415 | 416 | pygame.font.quit() 417 | --------------------------------------------------------------------------------